docs(07): create phase plan for weight unit selection
This commit is contained in:
@@ -44,11 +44,11 @@
|
||||
1. User can select a weight unit (g, oz, lb, kg) from a visible control and the selection persists after closing and reopening the app
|
||||
2. Every weight value in the app (item cards, candidate cards, category headers, totals bar, setup details) displays in the selected unit with appropriate precision
|
||||
3. Weight input fields accept values and store them correctly regardless of display unit (no rounding drift across edit cycles)
|
||||
**Plans**: TBD
|
||||
**Plans:** 2 plans
|
||||
|
||||
Plans:
|
||||
- [ ] 07-01: TBD
|
||||
- [ ] 07-02: TBD
|
||||
- [ ] 07-01-PLAN.md -- TDD formatWeight unit conversion core + useWeightUnit hook
|
||||
- [ ] 07-02-PLAN.md -- Wire unit toggle into TotalsBar and update all 8 call sites
|
||||
|
||||
### Phase 8: Search, Filter, and Candidate Status
|
||||
**Goal**: Users can find items quickly and track candidate purchase progress
|
||||
@@ -93,6 +93,6 @@ Plans:
|
||||
| 4. Database & Planning Fixes | v1.1 | 2/2 | Complete | 2026-03-15 |
|
||||
| 5. Image Handling | v1.1 | 2/2 | Complete | 2026-03-15 |
|
||||
| 6. Category Icons | v1.1 | 3/3 | Complete | 2026-03-15 |
|
||||
| 7. Weight Unit Selection | v1.2 | 0/? | Not started | - |
|
||||
| 7. Weight Unit Selection | v1.2 | 0/2 | Not started | - |
|
||||
| 8. Search, Filter, and Candidate Status | v1.2 | 0/? | Not started | - |
|
||||
| 9. Weight Classification and Visualization | v1.2 | 0/? | Not started | - |
|
||||
|
||||
238
.planning/phases/07-weight-unit-selection/07-01-PLAN.md
Normal file
238
.planning/phases/07-weight-unit-selection/07-01-PLAN.md
Normal file
@@ -0,0 +1,238 @@
|
||||
---
|
||||
phase: 07-weight-unit-selection
|
||||
plan: 01
|
||||
type: tdd
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/client/lib/formatters.ts
|
||||
- src/client/hooks/useWeightUnit.ts
|
||||
- tests/lib/formatters.test.ts
|
||||
autonomous: true
|
||||
requirements:
|
||||
- UNIT-02
|
||||
- UNIT-03
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "formatWeight converts grams to g, oz, lb, kg with correct precision"
|
||||
- "formatWeight defaults to grams when no unit is specified (backward compatible)"
|
||||
- "formatWeight handles null/undefined input for all units"
|
||||
- "useWeightUnit hook returns a valid WeightUnit from settings, defaulting to 'g'"
|
||||
artifacts:
|
||||
- path: "src/client/lib/formatters.ts"
|
||||
provides: "WeightUnit type export and parameterized formatWeight function"
|
||||
exports: ["WeightUnit", "formatWeight", "formatPrice"]
|
||||
contains: "WeightUnit"
|
||||
- path: "src/client/hooks/useWeightUnit.ts"
|
||||
provides: "Convenience hook wrapping useSetting for weight unit"
|
||||
exports: ["useWeightUnit"]
|
||||
- path: "tests/lib/formatters.test.ts"
|
||||
provides: "Unit tests for formatWeight with all 4 units and edge cases"
|
||||
min_lines: 30
|
||||
key_links:
|
||||
- from: "src/client/hooks/useWeightUnit.ts"
|
||||
to: "src/client/hooks/useSettings.ts"
|
||||
via: "useSetting('weightUnit')"
|
||||
pattern: "useSetting.*weightUnit"
|
||||
- from: "src/client/hooks/useWeightUnit.ts"
|
||||
to: "src/client/lib/formatters.ts"
|
||||
via: "imports WeightUnit type"
|
||||
pattern: "import.*WeightUnit.*formatters"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Create the weight unit conversion core: a parameterized `formatWeight` function with a `WeightUnit` type and a `useWeightUnit` convenience hook, all backed by tests.
|
||||
|
||||
Purpose: Establish the conversion contracts (type, function, hook) that Plan 02 will wire into every component. TDD approach ensures the conversion math is correct before any UI work.
|
||||
Output: Working `formatWeight(grams, unit)` with tests green, `useWeightUnit()` hook ready for consumption.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/jlmak/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/07-weight-unit-selection/07-RESEARCH.md
|
||||
|
||||
@src/client/lib/formatters.ts
|
||||
@src/client/hooks/useSettings.ts
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Key types and contracts the executor needs. Extracted from codebase. -->
|
||||
|
||||
From src/client/lib/formatters.ts (current):
|
||||
```typescript
|
||||
export function formatWeight(grams: number | null | undefined): string {
|
||||
if (grams == null) return "--";
|
||||
return `${Math.round(grams)}g`;
|
||||
}
|
||||
|
||||
export function formatPrice(cents: number | null | undefined): string {
|
||||
if (cents == null) return "--";
|
||||
return `$${(cents / 100).toFixed(2)}`;
|
||||
}
|
||||
```
|
||||
|
||||
From src/client/hooks/useSettings.ts:
|
||||
```typescript
|
||||
export function useSetting(key: string) {
|
||||
return useQuery({
|
||||
queryKey: ["settings", key],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const result = await apiGet<Setting>(`/api/settings/${key}`);
|
||||
return result.value;
|
||||
} catch (err: any) {
|
||||
if (err?.status === 404) return null;
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateSetting() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ key, value }: { key: string; value: string }) =>
|
||||
apiPut<Setting>(`/api/settings/${key}`, { value }),
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["settings", variables.key] });
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
</interfaces>
|
||||
|
||||
<feature>
|
||||
<name>formatWeight unit conversion</name>
|
||||
<files>src/client/lib/formatters.ts, tests/lib/formatters.test.ts</files>
|
||||
<behavior>
|
||||
Conversion constants: 1 oz = 28.3495g, 1 lb = 453.592g, 1 kg = 1000g
|
||||
|
||||
- formatWeight(100, "g") -> "100g"
|
||||
- formatWeight(100, "oz") -> "3.5 oz"
|
||||
- formatWeight(1000, "lb") -> "2.20 lb"
|
||||
- formatWeight(1500, "kg") -> "1.50 kg"
|
||||
- formatWeight(null, "oz") -> "--"
|
||||
- formatWeight(undefined, "kg") -> "--"
|
||||
- formatWeight(100) -> "100g" (default unit, backward compatible)
|
||||
- formatWeight(0, "oz") -> "0.0 oz"
|
||||
- formatWeight(5, "lb") -> "0.01 lb" (small weight precision)
|
||||
- formatWeight(50000, "kg") -> "50.00 kg" (large weight)
|
||||
</behavior>
|
||||
<implementation>
|
||||
1. Add `WeightUnit` type export: `"g" | "oz" | "lb" | "kg"`
|
||||
2. Add conversion constants as module-level consts (not exported)
|
||||
3. Modify `formatWeight` signature to `(grams: number | null | undefined, unit: WeightUnit = "g"): string`
|
||||
4. Keep the null guard as-is at the top
|
||||
5. Add switch statement for unit-specific formatting:
|
||||
- g: `Math.round(grams)` + "g" (0 decimals, current behavior)
|
||||
- oz: `.toFixed(1)` + " oz" (1 decimal)
|
||||
- lb: `.toFixed(2)` + " lb" (2 decimals)
|
||||
- kg: `.toFixed(2)` + " kg" (2 decimals)
|
||||
6. Do NOT modify `formatPrice` — leave it untouched
|
||||
</implementation>
|
||||
</feature>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: TDD formatWeight with unit parameter</name>
|
||||
<files>src/client/lib/formatters.ts, tests/lib/formatters.test.ts</files>
|
||||
<behavior>
|
||||
- formatWeight(100, "g") returns "100g"
|
||||
- formatWeight(100, "oz") returns "3.5 oz"
|
||||
- formatWeight(1000, "lb") returns "2.20 lb"
|
||||
- formatWeight(1500, "kg") returns "1.50 kg"
|
||||
- formatWeight(null) returns "--" for all units
|
||||
- formatWeight(undefined, "kg") returns "--"
|
||||
- formatWeight(100) returns "100g" (backward compatible, no second arg)
|
||||
- formatWeight(0, "oz") returns "0.0 oz"
|
||||
</behavior>
|
||||
<action>
|
||||
RED: Create `tests/lib/formatters.test.ts`. Import `formatWeight` from `@/client/lib/formatters`. Write tests for:
|
||||
- All 4 units with a known gram value (e.g., 1000g = "1000g", "35.3 oz", "2.20 lb", "1.00 kg")
|
||||
- Null and undefined input returning "--" for each unit
|
||||
- Default parameter (no second arg) producing current "g" behavior
|
||||
- Zero grams producing "0g", "0.0 oz", "0.00 lb", "0.00 kg"
|
||||
- Precision edge cases (small values like 5g in lb = "0.01 lb")
|
||||
|
||||
Run tests — they should fail because formatWeight does not accept a unit parameter yet.
|
||||
|
||||
GREEN: Modify `src/client/lib/formatters.ts`:
|
||||
- Export `type WeightUnit = "g" | "oz" | "lb" | "kg"`
|
||||
- Add constants: `GRAMS_PER_OZ = 28.3495`, `GRAMS_PER_LB = 453.592`, `GRAMS_PER_KG = 1000`
|
||||
- Change signature to `formatWeight(grams: number | null | undefined, unit: WeightUnit = "g")`
|
||||
- Add switch statement after the null guard for unit-specific conversion and formatting
|
||||
- Leave `formatPrice` completely untouched
|
||||
|
||||
Run tests — all should pass.
|
||||
|
||||
REFACTOR: None expected — the code is already minimal.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>bun test tests/lib/formatters.test.ts</automated>
|
||||
</verify>
|
||||
<done>formatWeight handles all 4 units with correct precision, null handling, and backward-compatible default. WeightUnit type is exported. All tests pass.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Create useWeightUnit convenience hook</name>
|
||||
<files>src/client/hooks/useWeightUnit.ts</files>
|
||||
<action>
|
||||
Create `src/client/hooks/useWeightUnit.ts`:
|
||||
|
||||
```typescript
|
||||
import { useSetting } from "./useSettings";
|
||||
import type { WeightUnit } from "../lib/formatters";
|
||||
|
||||
const VALID_UNITS: WeightUnit[] = ["g", "oz", "lb", "kg"];
|
||||
|
||||
export function useWeightUnit(): WeightUnit {
|
||||
const { data } = useSetting("weightUnit");
|
||||
if (data && VALID_UNITS.includes(data as WeightUnit)) {
|
||||
return data as WeightUnit;
|
||||
}
|
||||
return "g";
|
||||
}
|
||||
```
|
||||
|
||||
This hook:
|
||||
- Wraps `useSetting("weightUnit")` for a typed return value
|
||||
- Validates the stored value is a known unit (protects against bad data)
|
||||
- Defaults to "g" when no setting exists (backward compatible — UNIT-03 persistence works via existing settings API)
|
||||
- Returns `WeightUnit` type so components can pass directly to `formatWeight`
|
||||
</action>
|
||||
<verify>
|
||||
<automated>bun run lint</automated>
|
||||
</verify>
|
||||
<done>useWeightUnit hook exists, imports from useSettings and formatters, returns typed WeightUnit with "g" default. Lint passes.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `bun test tests/lib/formatters.test.ts` passes with all unit conversion tests green
|
||||
- `bun run lint` passes with no errors
|
||||
- `src/client/lib/formatters.ts` exports `WeightUnit` type and updated `formatWeight` function
|
||||
- `src/client/hooks/useWeightUnit.ts` exists and exports `useWeightUnit`
|
||||
- Existing tests still pass: `bun test` (full suite)
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- formatWeight("g") produces identical output to the old function (backward compatible)
|
||||
- formatWeight with oz/lb/kg produces correct conversions with appropriate decimal precision
|
||||
- WeightUnit type is exported for use by Plan 02 components
|
||||
- useWeightUnit hook is ready for components to consume
|
||||
- All existing tests remain green (no regressions)
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/07-weight-unit-selection/07-01-SUMMARY.md`
|
||||
</output>
|
||||
247
.planning/phases/07-weight-unit-selection/07-02-PLAN.md
Normal file
247
.planning/phases/07-weight-unit-selection/07-02-PLAN.md
Normal file
@@ -0,0 +1,247 @@
|
||||
---
|
||||
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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/jlmak/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<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
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Contracts created by Plan 01 that this plan consumes -->
|
||||
|
||||
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<Setting, Error, { key: string; value: string }>;
|
||||
```
|
||||
|
||||
Usage pattern for every component:
|
||||
```typescript
|
||||
import { useWeightUnit } from "../hooks/useWeightUnit";
|
||||
// ...
|
||||
const unit = useWeightUnit();
|
||||
// ...
|
||||
{formatWeight(weightGrams, unit)}
|
||||
```
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Add unit toggle to TotalsBar and update all call sites</name>
|
||||
<files>
|
||||
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
|
||||
</files>
|
||||
<action>
|
||||
**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`.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>bun test && bun run lint</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- 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
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<name>Task 2: Verify weight unit selection end-to-end</name>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>
|
||||
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
|
||||
</verify>
|
||||
<done>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.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `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
|
||||
</verification>
|
||||
|
||||
<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>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/07-weight-unit-selection/07-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user