--- phase: 07-weight-unit-selection plan: 02 type: execute wave: 2 depends_on: - "07-01" files_modified: - src/client/components/TotalsBar.tsx - src/client/components/ItemCard.tsx - src/client/components/CandidateCard.tsx - src/client/components/CategoryHeader.tsx - src/client/components/SetupCard.tsx - src/client/components/ItemPicker.tsx - src/client/routes/index.tsx - src/client/routes/setups/$setupId.tsx autonomous: false requirements: - UNIT-01 - UNIT-02 - UNIT-03 must_haves: truths: - "User can see a unit toggle (g/oz/lb/kg) in the TotalsBar" - "Clicking a unit in the toggle changes all weight displays across the app" - "Weight unit selection persists after page refresh" - "Every weight display in the app uses the selected unit" artifacts: - path: "src/client/components/TotalsBar.tsx" provides: "Unit toggle UI and unit-aware weight display" contains: "useWeightUnit" - path: "src/client/components/ItemCard.tsx" provides: "Unit-aware item weight display" contains: "useWeightUnit" - path: "src/client/components/CandidateCard.tsx" provides: "Unit-aware candidate weight display" contains: "useWeightUnit" - path: "src/client/components/CategoryHeader.tsx" provides: "Unit-aware category total weight display" contains: "useWeightUnit" - path: "src/client/components/SetupCard.tsx" provides: "Unit-aware setup weight display" contains: "useWeightUnit" - path: "src/client/components/ItemPicker.tsx" provides: "Unit-aware item picker weight display" contains: "useWeightUnit" - path: "src/client/routes/index.tsx" provides: "Unit-aware dashboard weight display" contains: "useWeightUnit" - path: "src/client/routes/setups/$setupId.tsx" provides: "Unit-aware setup detail weight display" contains: "useWeightUnit" key_links: - from: "src/client/components/TotalsBar.tsx" to: "/api/settings/weightUnit" via: "useUpdateSetting mutation" pattern: "useUpdateSetting.*weightUnit" - from: "src/client/components/ItemCard.tsx" to: "src/client/hooks/useWeightUnit.ts" via: "useWeightUnit hook import" pattern: "useWeightUnit" - from: "src/client/components/TotalsBar.tsx" to: "src/client/lib/formatters.ts" via: "formatWeight(grams, unit)" pattern: "formatWeight\\(.*,\\s*unit" --- Wire weight unit selection through the entire app: add a segmented unit toggle to TotalsBar and update all 8 formatWeight call sites to use the selected unit. Purpose: Deliver the complete user-facing feature. After this plan, users can select g/oz/lb/kg and see all weights update instantly across collection, planning, setups, and dashboard. Output: Fully functional weight unit selection with persistent preference. @/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md @/home/jlmak/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/07-weight-unit-selection/07-RESEARCH.md @.planning/phases/07-weight-unit-selection/07-01-SUMMARY.md From src/client/lib/formatters.ts (after Plan 01): ```typescript export type WeightUnit = "g" | "oz" | "lb" | "kg"; export function formatWeight(grams: number | null | undefined, unit?: WeightUnit): string; export function formatPrice(cents: number | null | undefined): string; ``` From src/client/hooks/useWeightUnit.ts (after Plan 01): ```typescript export function useWeightUnit(): WeightUnit; ``` From src/client/hooks/useSettings.ts (existing): ```typescript export function useUpdateSetting(): UseMutationResult; ``` Usage pattern for every component: ```typescript import { useWeightUnit } from "../hooks/useWeightUnit"; // ... const unit = useWeightUnit(); // ... {formatWeight(weightGrams, unit)} ``` Task 1: Add unit toggle to TotalsBar and update all call sites src/client/components/TotalsBar.tsx, src/client/components/ItemCard.tsx, src/client/components/CandidateCard.tsx, src/client/components/CategoryHeader.tsx, src/client/components/SetupCard.tsx, src/client/components/ItemPicker.tsx, src/client/routes/index.tsx, src/client/routes/setups/$setupId.tsx **TotalsBar.tsx** -- Add unit toggle and wire formatWeight: 1. Import `useWeightUnit` from `../hooks/useWeightUnit`, `useUpdateSetting` from `../hooks/useSettings`, and `WeightUnit` type from `../lib/formatters` 2. Inside the component function, call `const unit = useWeightUnit()` and `const updateSetting = useUpdateSetting()` 3. Define `const UNITS: WeightUnit[] = ["g", "oz", "lb", "kg"]` 4. Add a segmented pill toggle to the right side of the TotalsBar, between the title and the stats. The toggle should be a `div` with `flex items-center gap-1 bg-gray-100 rounded-full px-1 py-0.5` containing a button per unit: ``` ``` 5. Update the default stats construction (the `data?.global` branch) to pass `unit` to both `formatWeight` calls: - `formatWeight(data.global.totalWeight, unit)` and `formatWeight(null, unit)` 6. Position the toggle: place it in the flex container between the title and stats, using a wrapper div that pushes stats to the right. The toggle should be visible but not dominant -- it's a small utility control. **ItemCard.tsx** -- 3-line change: 1. Add import: `import { useWeightUnit } from "../hooks/useWeightUnit";` 2. Inside component: `const unit = useWeightUnit();` 3. Change `{formatWeight(weightGrams)}` to `{formatWeight(weightGrams, unit)}` **CandidateCard.tsx** -- Same 3-line pattern as ItemCard: 1. Add import: `import { useWeightUnit } from "../hooks/useWeightUnit";` 2. Inside component: `const unit = useWeightUnit();` 3. Change `{formatWeight(weightGrams)}` to `{formatWeight(weightGrams, unit)}` **CategoryHeader.tsx** -- Same 3-line pattern: 1. Add import: `import { useWeightUnit } from "../hooks/useWeightUnit";` 2. Inside component: `const unit = useWeightUnit();` 3. Change `{formatWeight(totalWeight)}` to `{formatWeight(totalWeight, unit)}` **SetupCard.tsx** -- Same 3-line pattern: 1. Add import: `import { useWeightUnit } from "../hooks/useWeightUnit";` 2. Inside component: `const unit = useWeightUnit();` 3. Change `{formatWeight(totalWeight)}` to `{formatWeight(totalWeight, unit)}` **ItemPicker.tsx** -- Same 3-line pattern: 1. Add import: `import { useWeightUnit } from "../hooks/useWeightUnit";` 2. Inside component: `const unit = useWeightUnit();` 3. Change `formatWeight(item.weightGrams)` to `formatWeight(item.weightGrams, unit)` **routes/index.tsx** (Dashboard) -- Same 3-line pattern: 1. Add import: `import { useWeightUnit } from "../hooks/useWeightUnit";` 2. Inside `DashboardPage`: `const unit = useWeightUnit();` 3. Change `formatWeight(global?.totalWeight ?? null)` to `formatWeight(global?.totalWeight ?? null, unit)` **routes/setups/$setupId.tsx** (Setup Detail) -- Same 3-line pattern: 1. Add import: `import { useWeightUnit } from "../../hooks/useWeightUnit";` 2. Inside `SetupDetailPage`: `const unit = useWeightUnit();` 3. Change `{formatWeight(totalWeight)}` to `{formatWeight(totalWeight, unit)}` **Completeness check:** After all changes, grep for `formatWeight(` across `src/client/` -- every call must have a second `unit` argument EXCEPT the function definition itself in `formatters.ts`. bun test && bun run lint - All 8 components pass `unit` to `formatWeight` - TotalsBar renders a g/oz/lb/kg toggle - Clicking a toggle button calls `useUpdateSetting` with key "weightUnit" - No `formatWeight` call site in src/client/ is missing the unit argument (except the definition) - All tests and lint pass Task 2: Verify weight unit selection end-to-end Human verifies the complete weight unit selection feature works correctly across all pages. Start the dev servers: `bun run dev:client` and `bun run dev:server` Open http://localhost:5173 in a browser and walk through the verification steps below. 1. Navigate to the Collection page -- verify the TotalsBar shows a g/oz/lb/kg toggle 2. The default should be "g" -- weights display as before (e.g., "450g") 3. Click "oz" -- all weight badges on ItemCards, CategoryHeaders, and the TotalsBar total should update to ounces (e.g., "15.9 oz") 4. Click "kg" -- weights should update to kilograms (e.g., "0.45 kg") 5. Click "lb" -- weights should update to pounds (e.g., "0.99 lb") 6. Navigate to the Dashboard (/) -- the Collection card weight should show in the selected unit 7. Navigate to a Setup detail page -- the sticky sub-bar weight total and all ItemCards should show the selected unit 8. Refresh the page -- the selected unit should persist (still showing the last chosen unit) 9. Switch back to "g" -- all weights should return to the original gram display User confirms all weight displays update correctly across all pages, unit toggle is visible and functional, and selection persists across refresh. Type "approved" or describe issues. - `bun test` passes (full suite, no regressions) - `bun run lint` passes - grep `formatWeight(` across `src/client/` shows all call sites have unit parameter - Unit toggle is visible in TotalsBar on all pages that show it - Selecting a unit updates all weight displays instantly - Selected unit persists across page refresh - UNIT-01: User can select g/oz/lb/kg from the TotalsBar toggle -- visible and functional - UNIT-02: Every weight display (ItemCard, CandidateCard, CategoryHeader, SetupCard, ItemPicker, Dashboard, Setup Detail, TotalsBar) reflects the selected unit - UNIT-03: Weight unit persists across sessions via the existing settings API (PUT/GET /api/settings/weightUnit) After completion, create `.planning/phases/07-weight-unit-selection/07-02-SUMMARY.md`