feat(redesign): [P2] data model + migration + Jest setup #1

Closed
kaykayyali wants to merge 1 commits from wt/redesign-data-model into main
Owner

Phase 2 — Trello-style data model + Jest setup

Establishes the v2 normalized shape and the one-shot v1->v2 migration. Stands up Jest + React Testing Library so every subsequent phase (P3-P8) has a real test harness.

v2 shape (localStorage key ultra-todo-v2-state)

{
  schemaVersion: 2,
  activeBoardId: string | null,
  boards: { [id]: { id, name, listIds: [listId,...] } },
  boardOrder: [boardId,...],
  lists:  { [id]: { id, boardId, name, cardIds: [cardId,...] } },
  cards:  { [id]: { id, boardId, listId, title, description, dueDate, createdAt } },
}
  • Boards, lists, cards are normalized by id — @dnd-kit/sortable will need stable ids for reorder.
  • Each card denormalizes its boardId + listId so cleanup is easy if a list is dropped.
  • createdAt is a number (Date.now()) for stable ordering.

Migration

  • One-shot, synchronous, idempotent, guarded by schemaVersion === 2.
  • Reads ultra-todo-items (v1), builds a single Inbox board with one Todo list, maps each item to a card (text->title, drop completed, preserve dueDate).
  • v1 key is not deleted — leaves rollback path open.

Store

  • useBoardStore() returns { state, actions }. Pure reducer (useState + functional updates), persists on every state change. No async.
  • Actions cover: boards (add/rename/delete/setActive), lists (add/rename/delete/move), cards (add/update/delete/move).
  • Migration runs in the useState initializer so the first paint already has data — no empty-board flash.

Tests (25 pass)

  • src/lib/migrate.test.js (7): existing v2 untouched, fresh empty state, v1->v2 mapping, v1 key intact, idempotent, corrupt v1, persistence.
  • src/store/boardStore.test.js (17): initial state, persistence, every action's happy path.
  • src/store/boardStore.migration.test.js (1): end-to-end — write v1, mount hook, verify v2 shape and v1 key intact.

Jest + RTL setup

  • DevDeps: jest@29, jest-environment-jsdom@29, @testing-library/react@16, @testing-library/jest-dom@6, @testing-library/user-event@14, babel-jest@29, @babel/preset-env@7, @babel/preset-react@7.
  • babel.config.js is only for Jest's transform — Vite's esbuild ignores it for dev/build.
  • jest.config.js: jsdom env, setupFilesAfterEnv for jest-dom, transform ^.+\.(js|jsx)$ with babel-jest, testEnvironmentOptions for crypto.randomUUID compat, transformIgnorePatterns pre-wired for @dnd-kit (P5).
  • Scripts: npm test, npm run test:watch.

No UI yet

  • App.jsx and the v1 components (TodoForm/TodoList/TodoItem) are untouched. Old UI still renders and the production build still produces the same bundle (verified: npm run build succeeds, 197 kB JS / 2.3 kB CSS).

Acceptance

  • npm test runs and 25/25 boardStore tests pass
  • npm run build succeeds (old UI still renders)
  • Migration tested: fake v1 array -> v2 state correct, v1 key untouched
  • Branch wt/redesign-data-model pushed to origin
  • PR opened against main

Out of scope (later phases)

  • P3: Board + List components
  • P4: Card component + CRUD + detail modal
  • P5: @dnd-kit drag-and-drop
  • P6: Multi-board sidebar
  • P7: Integration overlay
  • P8: E2E + mobile screenshot
## Phase 2 — Trello-style data model + Jest setup Establishes the v2 normalized shape and the one-shot v1->v2 migration. Stands up Jest + React Testing Library so every subsequent phase (P3-P8) has a real test harness. ### v2 shape (localStorage key `ultra-todo-v2-state`) ```js { schemaVersion: 2, activeBoardId: string | null, boards: { [id]: { id, name, listIds: [listId,...] } }, boardOrder: [boardId,...], lists: { [id]: { id, boardId, name, cardIds: [cardId,...] } }, cards: { [id]: { id, boardId, listId, title, description, dueDate, createdAt } }, } ``` - Boards, lists, cards are normalized by id — `@dnd-kit/sortable` will need stable ids for reorder. - Each card denormalizes its `boardId` + `listId` so cleanup is easy if a list is dropped. - `createdAt` is a number (Date.now()) for stable ordering. ### Migration - One-shot, synchronous, idempotent, guarded by `schemaVersion === 2`. - Reads `ultra-todo-items` (v1), builds a single Inbox board with one Todo list, maps each item to a card (text->title, drop `completed`, preserve `dueDate`). - v1 key is **not** deleted — leaves rollback path open. ### Store - `useBoardStore()` returns `{ state, actions }`. Pure reducer (useState + functional updates), persists on every state change. No async. - Actions cover: boards (add/rename/delete/setActive), lists (add/rename/delete/move), cards (add/update/delete/move). - Migration runs in the useState initializer so the first paint already has data — no empty-board flash. ### Tests (25 pass) - `src/lib/migrate.test.js` (7): existing v2 untouched, fresh empty state, v1->v2 mapping, v1 key intact, idempotent, corrupt v1, persistence. - `src/store/boardStore.test.js` (17): initial state, persistence, every action's happy path. - `src/store/boardStore.migration.test.js` (1): end-to-end — write v1, mount hook, verify v2 shape and v1 key intact. ### Jest + RTL setup - DevDeps: jest@29, jest-environment-jsdom@29, @testing-library/react@16, @testing-library/jest-dom@6, @testing-library/user-event@14, babel-jest@29, @babel/preset-env@7, @babel/preset-react@7. - `babel.config.js` is **only** for Jest's transform — Vite's esbuild ignores it for dev/build. - `jest.config.js`: jsdom env, `setupFilesAfterEnv` for jest-dom, transform `^.+\.(js|jsx)$` with babel-jest, testEnvironmentOptions for crypto.randomUUID compat, transformIgnorePatterns pre-wired for @dnd-kit (P5). - Scripts: `npm test`, `npm run test:watch`. ### No UI yet - `App.jsx` and the v1 components (TodoForm/TodoList/TodoItem) are **untouched**. Old UI still renders and the production build still produces the same bundle (verified: `npm run build` succeeds, 197 kB JS / 2.3 kB CSS). ### Acceptance - [x] `npm test` runs and 25/25 boardStore tests pass - [x] `npm run build` succeeds (old UI still renders) - [x] Migration tested: fake v1 array -> v2 state correct, v1 key untouched - [x] Branch `wt/redesign-data-model` pushed to origin - [x] PR opened against main ### Out of scope (later phases) - P3: Board + List components - P4: Card component + CRUD + detail modal - P5: `@dnd-kit` drag-and-drop - P6: Multi-board sidebar - P7: Integration overlay - P8: E2E + mobile screenshot
kaykayyali added 1 commit 2026-06-24 05:02:26 +00:00
- src/lib/migrate.js: one-shot v1->v2 migration under 'ultra-todo-v2-state'.
  Maps legacy ultra-todo-items to a single Inbox/Todo board, preserves
  text/title and dueDate, drops completed. Leaves v1 key intact for
  rollback. Guarded by schemaVersion === 2.
- src/store/boardStore.js: useBoardStore() hook returning { state, actions }.
  Pure reducer (useState + functional updates), persists on every change.
  Actions: addBoard/renameBoard/deleteBoard/setActiveBoard,
  addList/renameList/deleteList/moveList,
  addCard/updateCard/deleteCard/moveCard. Migration runs synchronously
  in the useState initializer so no empty-board flash on first paint.
- src/hooks/useLocalStorage.js: JSDoc updated to document v1/v2 key contract.
- Jest + RTL setup: babel-jest + jsdom, @testing-library/jest-dom, scripts
  test / test:watch. 25 tests pass; npm run build still produces old UI.
kaykayyali closed this pull request 2026-06-24 06:27:52 +00:00
kaykayyali deleted branch wt/redesign-data-model 2026-06-24 06:27:52 +00:00

Pull request closed

Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kaykayyali/ultra-todo#1