Files

11 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
07-weight-unit-selection 02 execute 2
07-01
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
false
UNIT-01
UNIT-02
UNIT-03
truths artifacts key_links
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
path provides contains
src/client/components/TotalsBar.tsx Unit toggle UI and unit-aware weight display useWeightUnit
path provides contains
src/client/components/ItemCard.tsx Unit-aware item weight display useWeightUnit
path provides contains
src/client/components/CandidateCard.tsx Unit-aware candidate weight display useWeightUnit
path provides contains
src/client/components/CategoryHeader.tsx Unit-aware category total weight display useWeightUnit
path provides contains
src/client/components/SetupCard.tsx Unit-aware setup weight display useWeightUnit
path provides contains
src/client/components/ItemPicker.tsx Unit-aware item picker weight display useWeightUnit
path provides contains
src/client/routes/index.tsx Unit-aware dashboard weight display useWeightUnit
path provides contains
src/client/routes/setups/$setupId.tsx Unit-aware setup detail weight display useWeightUnit
from to via pattern
src/client/components/TotalsBar.tsx /api/settings/weightUnit useUpdateSetting mutation useUpdateSetting.*weightUnit
from to via pattern
src/client/components/ItemCard.tsx src/client/hooks/useWeightUnit.ts useWeightUnit hook import useWeightUnit
from to via pattern
src/client/components/TotalsBar.tsx src/client/lib/formatters.ts formatWeight(grams, unit) 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.

<execution_context> @/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md @/home/jlmak/.claude/get-shit-done/templates/summary.md </execution_context>

@.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):

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):

export function useWeightUnit(): WeightUnit;

From src/client/hooks/useSettings.ts (existing):

export function useUpdateSetting(): UseMutationResult<Setting, Error, { key: string; value: string }>;

Usage pattern for every component:

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:
   ```
   <button
     key={u}
     onClick={() => updateSetting.mutate({ key: "weightUnit", value: u })}
     className={`px-2 py-0.5 text-xs rounded-full transition-colors ${
       unit === u
         ? "bg-white text-gray-700 shadow-sm font-medium"
         : "text-gray-400 hover:text-gray-600"
     }`}
   >
     {u}
   </button>
   ```
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

<success_criteria>

  • 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) </success_criteria>
After completion, create `.planning/phases/07-weight-unit-selection/07-02-SUMMARY.md`