diff --git a/gsd-framework/.planning/ROADMAP.md b/gsd-framework/.planning/ROADMAP.md index 9a81120..bd0f4e4 100644 --- a/gsd-framework/.planning/ROADMAP.md +++ b/gsd-framework/.planning/ROADMAP.md @@ -1,71 +1,75 @@ -# Roadmap: Expense Splitter - -## Overview - -Build a client-side bill splitting app that takes users from "we just ate" to "here's what everyone owes" in seconds. Start with the core data entry and assignment flow, add accurate calculations with tip handling, then enable persistence so users can reference past bills. - -## Phases - -**Phase Numbering:** -- Integer phases (1, 2, 3): Planned milestone work -- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED) - -Decimal phases appear between their surrounding integers in numeric order. - -- [ ] **Phase 1: Bill Entry & Assignment** - Add people, items, and assign who ordered what -- [ ] **Phase 2: Calculation & Results** - See who owes what with tip calculations -- [ ] **Phase 3: Bill History** - Save, view, and load past bills - -## Phase Details - -### Phase 1: Bill Entry & Assignment -**Goal**: Users can create a bill by adding people and items with flexible assignments -**Depends on**: Nothing (first phase) -**Requirements**: PEOPLE-01, PEOPLE-02, PEOPLE-03, PEOPLE-04 -**Success Criteria** (what must be TRUE): - 1. User can add people to the bill by entering their names - 2. User can add items with name and price to the bill - 3. User can assign any item to one or more specific people - 4. User can mark an item as "shared" to split it across selected people -**Plans**: TBD - -Plans: -- [ ] 01-01: [To be defined during planning] - -### Phase 2: Calculation & Results -**Goal**: Users can see exactly who owes what with tip applied -**Depends on**: Phase 1 -**Requirements**: CALC-01, CALC-02, CALC-03, CALC-04 -**Success Criteria** (what must be TRUE): - 1. User can see each person's subtotal from their assigned items - 2. User can set a global tip percentage that applies to everyone - 3. User can see each person's final total with tip included - 4. User can view a clear summary showing who owes how much -**Plans**: TBD - -Plans: -- [ ] 02-01: [To be defined during planning] - -### Phase 3: Bill History -**Goal**: Users can save and retrieve their past bills -**Depends on**: Phase 2 -**Requirements**: PERSIST-01, PERSIST-02, PERSIST-03 -**Success Criteria** (what must be TRUE): - 1. User's bills are automatically saved to browser local storage - 2. User can view a list of their past bills - 3. User can load a past bill to review the details -**Plans**: TBD - -Plans: -- [ ] 03-01: [To be defined during planning] - -## Progress - -**Execution Order:** -Phases execute in numeric order: 1 -> 2 -> 3 - -| Phase | Plans Complete | Status | Completed | -|-------|----------------|--------|-----------| -| 1. Bill Entry & Assignment | 0/TBD | Not started | - | -| 2. Calculation & Results | 0/TBD | Not started | - | -| 3. Bill History | 0/TBD | Not started | - | +# Roadmap: Expense Splitter + +## Overview + +Build a client-side bill splitting app that takes users from "we just ate" to "here's what everyone owes" in seconds. Start with the core data entry and assignment flow, add accurate calculations with tip handling, then enable persistence so users can reference past bills. + +## Phases + +**Phase Numbering:** +- Integer phases (1, 2, 3): Planned milestone work +- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED) + +Decimal phases appear between their surrounding integers in numeric order. + +- [ ] **Phase 1: Bill Entry & Assignment** - Add people, items, and assign who ordered what +- [ ] **Phase 2: Calculation & Results** - See who owes what with tip calculations +- [ ] **Phase 3: Bill History** - Save, view, and load past bills + +## Phase Details + +### Phase 1: Bill Entry & Assignment +**Goal**: Users can create a bill by adding people and items with flexible assignments +**Depends on**: Nothing (first phase) +**Requirements**: PEOPLE-01, PEOPLE-02, PEOPLE-03, PEOPLE-04 +**Success Criteria** (what must be TRUE): + 1. User can add people to the bill by entering their names + 2. User can add items with name and price to the bill + 3. User can assign any item to one or more specific people + 4. User can mark an item as "shared" to split it across selected people +**Plans**: 5 plans in 4 waves + +Plans: +- [ ] 01-01: Test infrastructure (Vitest + test stubs for all requirements) +- [ ] 01-02: Core data layer (currency utils + billStore with CRUD) +- [ ] 01-03: People UI (PersonForm + PeopleList) +- [ ] 01-04: Items UI (ItemForm + ItemsList + ItemAssign) +- [ ] 01-05: Integration checkpoint (human verification) + +### Phase 2: Calculation & Results +**Goal**: Users can see exactly who owes what with tip applied +**Depends on**: Phase 1 +**Requirements**: CALC-01, CALC-02, CALC-03, CALC-04 +**Success Criteria** (what must be TRUE): + 1. User can see each person's subtotal from their assigned items + 2. User can set a global tip percentage that applies to everyone + 3. User can see each person's final total with tip included + 4. User can view a clear summary showing who owes how much +**Plans**: TBD + +Plans: +- [ ] 02-01: [To be defined during planning] + +### Phase 3: Bill History +**Goal**: Users can save and retrieve their past bills +**Depends on**: Phase 2 +**Requirements**: PERSIST-01, PERSIST-02, PERSIST-03 +**Success Criteria** (what must be TRUE): + 1. User's bills are automatically saved to browser local storage + 2. User can view a list of their past bills + 3. User can load a past bill to review the details +**Plans**: TBD + +Plans: +- [ ] 03-01: [To be defined during planning] + +## Progress + +**Execution Order:** +Phases execute in numeric order: 1 -> 2 -> 3 + +| Phase | Plans Complete | Status | Completed | +|-------|----------------|--------|-----------| +| 1. Bill Entry & Assignment | 0/5 | Not started | - | +| 2. Calculation & Results | 0/TBD | Not started | - | +| 3. Bill History | 0/TBD | Not started | - | diff --git a/gsd-framework/.planning/phases/01-bill-entry-assignment/01-01-PLAN.md b/gsd-framework/.planning/phases/01-bill-entry-assignment/01-01-PLAN.md new file mode 100644 index 0000000..651fdf2 --- /dev/null +++ b/gsd-framework/.planning/phases/01-bill-entry-assignment/01-01-PLAN.md @@ -0,0 +1,290 @@ +--- +phase: 01-bill-entry-assignment +plan: 01 +type: execute +wave: 0 +depends_on: [] +files_modified: [vitest.config.js, tests/billStore.test.js, tests/currency.test.js] +autonomous: true +requirements: [PEOPLE-01, PEOPLE-02, PEOPLE-03, PEOPLE-04] +user_setup: [] + +must_haves: + truths: + - "Tests can be run with npm test" + - "Test files exist for billStore and currency utilities" + - "Each requirement has at least one test case defined" + artifacts: + - path: "vitest.config.js" + provides: "Vitest configuration for test runner" + exports: ["default"] + - path: "tests/billStore.test.js" + provides: "Test stubs for PEOPLE-01, PEOPLE-02, PEOPLE-03, PEOPLE-04" + contains: "describe('BillStore')" + - path: "tests/currency.test.js" + provides: "Test cases for cents/dollars conversion" + contains: "describe('currency')" + key_links: + - from: "vitest.config.js" + to: "package.json" + via: "test script" + pattern: '"test":' +--- + + +Set up test infrastructure with Vitest and create test stubs for all Phase 1 requirements. This Wave 0 task ensures every subsequent task has automated verification available. + +Purpose: Enable TDD workflow and Nyquist-compliant feedback loops for all Phase 1 tasks. +Output: Working test infrastructure with stubs ready for implementation. + + + +@./.claude/get-shit-done/workflows/execute-plan.md +@./.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-bill-entry-assignment/01-RESEARCH.md +@.planning/phases/01-bill-entry-assignment/01-VALIDATION.md + + + + + + Task 1: Configure Vitest and add test scripts + vitest.config.js, package.json + + 1. Install Vitest as dev dependency: `npm install -D vitest` + 2. Create vitest.config.js with basic configuration: + - Environment: jsdom (for Preact components in later tasks) + - Include: tests/**/*.test.js + - Global test APIs enabled + 3. Add test scripts to package.json: + - "test": "vitest" + - "test:run": "vitest run" + + Use this exact vitest.config.js content: + ```javascript + import { defineConfig } from 'vitest/config'; + + export default defineConfig({ + test: { + environment: 'jsdom', + globals: true, + include: ['tests/**/*.test.js'], + }, + }); + ``` + + Also install jsdom: `npm install -D jsdom` + + + npm test -- --run 2>&1 | grep -q "No test files found" || npm test -- --run + + npm test command runs without errors, vitest.config.js exists + + + + Task 2: Create billStore test stubs + tests/billStore.test.js + + Create tests/billStore.test.js with test stubs for all Phase 1 requirements. + + Include test cases for: + - PEOPLE-01: addPerson (valid name, empty name rejection, duplicate allowed) + - PEOPLE-02: addItem (valid item, empty name rejection, invalid price rejection) + - PEOPLE-03: setAssignment (assign to one person) + - PEOPLE-04: setAssignment (assign to multiple people = shared) + + Each test should import createBillStore from '../src/store/billStore.js' (file does not exist yet - tests will fail initially, which is expected for TDD). + + Use the test structure from RESEARCH.md as reference: + ```javascript + import { describe, it, expect, beforeEach } from 'vitest'; + import { createBillStore } from '../src/store/billStore.js'; + + describe('BillStore', () => { + let store; + + beforeEach(() => { + store = createBillStore(); + }); + + describe('addPerson (PEOPLE-01)', () => { + it('adds person with valid name', () => { + const result = store.addPerson('Alice'); + expect(result.success).toBe(true); + expect(store.people.value).toHaveLength(1); + expect(store.people.value[0].name).toBe('Alice'); + }); + + it('rejects empty name', () => { + const result = store.addPerson(''); + expect(result.success).toBe(false); + }); + + it('trims whitespace from name', () => { + const result = store.addPerson(' Bob '); + expect(result.success).toBe(true); + expect(store.people.value[0].name).toBe('Bob'); + }); + }); + + describe('addItem (PEOPLE-02)', () => { + it('adds item with name and price', () => { + const result = store.addItem('Pizza', '15.99'); + expect(result.success).toBe(true); + expect(store.items.value[0].priceCents).toBe(1599); + }); + + it('rejects empty item name', () => { + const result = store.addItem('', '10.00'); + expect(result.success).toBe(false); + }); + + it('rejects negative price', () => { + const result = store.addItem('Item', '-5.00'); + expect(result.success).toBe(false); + }); + + it('rejects non-numeric price', () => { + const result = store.addItem('Item', 'abc'); + expect(result.success).toBe(false); + }); + }); + + describe('setAssignment (PEOPLE-03, PEOPLE-04)', () => { + it('assigns item to one person', () => { + store.addPerson('Alice'); + const personId = store.people.value[0].id; + store.addItem('Salad', '10.00'); + const itemId = store.items.value[0].id; + + store.setAssignment(itemId, [personId]); + expect(store.getAssignedPeople(itemId)).toEqual([personId]); + }); + + it('assigns item to multiple people (shared)', () => { + store.addPerson('Alice'); + store.addPerson('Bob'); + const [id1, id2] = store.people.value.map(p => p.id); + store.addItem('Nachos', '12.00'); + const itemId = store.items.value[0].id; + + store.setAssignment(itemId, [id1, id2]); + expect(store.getAssignedPeople(itemId)).toHaveLength(2); + }); + + it('returns empty array for unassigned item', () => { + store.addItem('Standalone', '5.00'); + const itemId = store.items.value[0].id; + + expect(store.getAssignedPeople(itemId)).toEqual([]); + }); + }); + }); + ``` + + + test -f tests/billStore.test.js && grep -q "PEOPLE-01" tests/billStore.test.js && grep -q "PEOPLE-02" tests/billStore.test.js && grep -q "PEOPLE-03" tests/billStore.test.js && grep -q "PEOPLE-04" tests/billStore.test.js + + Test file exists with test cases for all 4 PEOPLE requirements + + + + Task 3: Create currency utility test stubs + tests/currency.test.js + + Create tests/currency.test.js with test cases for money handling utilities. + + Test cases: + - dollarsToCents: converts "15.99" to 1599 + - dollarsToCents: handles integer input "20" to 2000 + - dollarsToCents: handles decimal input "10.5" to 1050 + - centsToDollars: converts 1599 to "15.99" + - centsToDollars: handles 0 to "0.00" + - formatCurrency: converts 1599 to "$15.99" + + ```javascript + import { describe, it, expect } from 'vitest'; + import { dollarsToCents, centsToDollars, formatCurrency } from '../src/utils/currency.js'; + + describe('currency utilities', () => { + describe('dollarsToCents', () => { + it('converts decimal string to cents', () => { + expect(dollarsToCents('15.99')).toBe(1599); + }); + + it('converts integer string to cents', () => { + expect(dollarsToCents('20')).toBe(2000); + }); + + it('handles single decimal place', () => { + expect(dollarsToCents('10.5')).toBe(1050); + }); + + it('handles zero', () => { + expect(dollarsToCents('0')).toBe(0); + }); + + it('rounds floating point precision', () => { + // 10.99 * 100 = 1098.9999... in JS + expect(dollarsToCents('10.99')).toBe(1099); + }); + }); + + describe('centsToDollars', () => { + it('converts cents to dollar string', () => { + expect(centsToDollars(1599)).toBe('15.99'); + }); + + it('handles zero', () => { + expect(centsToDollars(0)).toBe('0.00'); + }); + + it('pads single digit cents', () => { + expect(centsToDollars(1050)).toBe('10.50'); + }); + }); + + describe('formatCurrency', () => { + it('formats cents as dollar string with $ prefix', () => { + expect(formatCurrency(1599)).toBe('$15.99'); + }); + + it('formats zero', () => { + expect(formatCurrency(0)).toBe('$0.00'); + }); + }); + }); + ``` + + + test -f tests/currency.test.js && grep -q "dollarsToCents" tests/currency.test.js && grep -q "centsToDollars" tests/currency.test.js && grep -q "formatCurrency" tests/currency.test.js + + Test file exists with test cases for all currency utility functions + + + + + +After completing all tasks: +1. `npm test` runs without configuration errors +2. Tests fail (expected - source files don't exist yet) but test structure is valid +3. All Phase 1 requirements have corresponding test cases + + + +- vitest.config.js exists with jsdom environment +- package.json has "test" and "test:run" scripts +- tests/billStore.test.js covers PEOPLE-01, PEOPLE-02, PEOPLE-03, PEOPLE-04 +- tests/currency.test.js covers dollarsToCents, centsToDollars, formatCurrency +- npm test command executes (tests fail but runner works) + + + +After completion, create `.planning/phases/01-bill-entry-assignment/01-01-SUMMARY.md` + diff --git a/gsd-framework/.planning/phases/01-bill-entry-assignment/01-02-PLAN.md b/gsd-framework/.planning/phases/01-bill-entry-assignment/01-02-PLAN.md new file mode 100644 index 0000000..e64698e --- /dev/null +++ b/gsd-framework/.planning/phases/01-bill-entry-assignment/01-02-PLAN.md @@ -0,0 +1,262 @@ +--- +phase: 01-bill-entry-assignment +plan: 02 +type: execute +wave: 1 +depends_on: [01-01] +files_modified: [src/utils/currency.js, src/store/billStore.js] +autonomous: true +requirements: [PEOPLE-01, PEOPLE-02, PEOPLE-03, PEOPLE-04] +user_setup: [] + +must_haves: + truths: + - "Store can add people with unique IDs" + - "Store can add items with prices stored as cents" + - "Store can assign items to one or more people" + - "Currency utilities convert between dollars and cents correctly" + artifacts: + - path: "src/utils/currency.js" + provides: "Money conversion utilities" + exports: ["dollarsToCents", "centsToDollars", "formatCurrency"] + - path: "src/store/billStore.js" + provides: "Central bill state with Signals" + exports: ["createBillStore"] + key_links: + - from: "src/store/billStore.js" + to: "@preact/signals" + via: "import signal" + pattern: "from '@preact/signals'" + - from: "src/store/billStore.js" + to: "crypto.randomUUID" + via: "ID generation" + pattern: "crypto\\.randomUUID" +--- + + +Implement the core data layer: currency utilities and bill store with full CRUD operations for people, items, and assignments. This is the foundation that all UI components will consume. + +Purpose: Create the single source of truth for bill state using Preact Signals. +Output: Working store with all actions passing their tests. + + + +@./.claude/get-shit-done/workflows/execute-plan.md +@./.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-bill-entry-assignment/01-RESEARCH.md + + + + + +From tests/billStore.test.js: +```javascript +// Store factory pattern - must export this function +createBillStore() + +// Store must have these signals: +store.people // Signal> +store.items // Signal> +store.assignments // Signal> + +// Store must have these methods: +store.addPerson(name: string) -> {success: boolean, error?: string} +store.addItem(name: string, priceInput: string) -> {success: boolean, error?: string} +store.setAssignment(itemId: string, personIds: string[]) -> void +store.getAssignedPeople(itemId: string) -> string[] +``` + +From tests/currency.test.js: +```javascript +dollarsToCents(dollars: string) -> number +centsToDollars(cents: number) -> string +formatCurrency(cents: number) -> string +``` + + + + + + Task 1: Implement currency utilities + src/utils/currency.js + + - Test: dollarsToCents('15.99') returns 1599 + - Test: dollarsToCents('20') returns 2000 + - Test: centsToDollars(1599) returns '15.99' + - Test: formatCurrency(1599) returns '$15.99' + + + Create src/utils/currency.js with three exported functions: + + 1. dollarsToCents(dollars): Convert dollar string to integer cents + - Parse float, multiply by 100, round to handle floating-point precision + - Return integer + + 2. centsToDollars(cents): Convert cents to formatted dollar string + - Divide by 100, format to 2 decimal places with toFixed(2) + - Return string like "15.99" + + 3. formatCurrency(cents): Format cents for display + - Convert to dollars, prepend "$" + - Return string like "$15.99" + + Implementation: + ```javascript + /** + * Convert dollar string to integer cents. + * @param {string} dollars - Dollar amount as string (e.g., "15.99") + * @returns {number} Cents as integer (e.g., 1599) + */ + export function dollarsToCents(dollars) { + return Math.round(parseFloat(dollars) * 100); + } + + /** + * Convert cents to dollar string. + * @param {number} cents - Cents as integer + * @returns {string} Dollar string with 2 decimal places + */ + export function centsToDollars(cents) { + return (cents / 100).toFixed(2); + } + + /** + * Format cents as currency string with $ prefix. + * @param {number} cents - Cents as integer + * @returns {string} Formatted currency (e.g., "$15.99") + */ + export function formatCurrency(cents) { + return `$${centsToDollars(cents)}`; + } + ``` + + Run tests after implementation: `npm test -- -t "currency"` + + + npm test -- --run -t "currency" 2>&1 | grep -q "passed" + + All currency utility tests pass + + + + Task 2: Implement billStore with full CRUD + src/store/billStore.js + + - Test: addPerson('Alice') adds person with unique ID + - Test: addPerson('') returns {success: false} + - Test: addItem('Pizza', '15.99') adds item with priceCents=1599 + - Test: addItem('', '10') returns {success: false} + - Test: setAssignment(itemId, [personId]) creates assignment + - Test: getAssignedPeople(itemId) returns assigned person IDs + + + Create src/store/billStore.js implementing the store factory pattern from RESEARCH.md. + + The store must: + 1. Use Preact Signals for reactive state + 2. Use crypto.randomUUID() for unique IDs + 3. Store prices as integer cents + 4. Use Map for assignments (itemId -> personIds[]) + 5. Return {success, error} objects from addPerson/addItem for validation feedback + + Implementation pattern (from RESEARCH.md): + ```javascript + import { signal } from '@preact/signals'; + + /** + * Create a new bill store instance. + * Factory pattern enables test isolation. + * @returns {Object} Store with signals and actions + */ + export function createBillStore() { + const people = signal([]); + const items = signal([]); + const assignments = signal(new Map()); + + return { + // Signals (read-only access via .value) + people, + items, + assignments, + + // PEOPLE-01: Add person to bill + addPerson(name) { + const trimmed = name.trim(); + if (!trimmed) { + return { success: false, error: 'Name required' }; + } + + const id = crypto.randomUUID(); + people.value = [...people.value, { id, name: trimmed }]; + return { success: true }; + }, + + // PEOPLE-02: Add item to bill + addItem(name, priceInput) { + const trimmedName = name.trim(); + if (!trimmedName) { + return { success: false, error: 'Item name required' }; + } + + const priceCents = Math.round(parseFloat(priceInput) * 100); + if (isNaN(priceCents) || priceCents < 0) { + return { success: false, error: 'Valid price required' }; + } + + const id = crypto.randomUUID(); + items.value = [...items.value, { id, name: trimmedName, priceCents }]; + return { success: true }; + }, + + // PEOPLE-03, PEOPLE-04: Assign item to people + setAssignment(itemId, personIds) { + const newMap = new Map(assignments.value); + newMap.set(itemId, [...personIds]); // Copy array + assignments.value = newMap; + }, + + // Get people assigned to an item + getAssignedPeople(itemId) { + return assignments.value.get(itemId) || []; + } + }; + } + ``` + + First ensure @preact/signals is installed: `npm install @preact/signals` + + Run tests after implementation: `npm test -- -t "BillStore"` + + + npm test -- --run -t "BillStore" 2>&1 | grep -q "passed" + + All billStore tests pass (PEOPLE-01, PEOPLE-02, PEOPLE-03, PEOPLE-04) + + + + + +After completing all tasks: +1. `npm test -- --run` passes all currency and billStore tests +2. Store exports createBillStore function +3. Currency utils export all three functions +4. No TypeScript/ESM import errors + + + +- src/utils/currency.js exists with dollarsToCents, centsToDollars, formatCurrency +- src/store/billStore.js exists with createBillStore factory +- All tests in tests/billStore.test.js pass +- All tests in tests/currency.test.js pass +- Store uses Preact Signals for reactive state + + + +After completion, create `.planning/phases/01-bill-entry-assignment/01-02-SUMMARY.md` + diff --git a/gsd-framework/.planning/phases/01-bill-entry-assignment/01-03-PLAN.md b/gsd-framework/.planning/phases/01-bill-entry-assignment/01-03-PLAN.md new file mode 100644 index 0000000..fd51189 --- /dev/null +++ b/gsd-framework/.planning/phases/01-bill-entry-assignment/01-03-PLAN.md @@ -0,0 +1,247 @@ +--- +phase: 01-bill-entry-assignment +plan: 03 +type: execute +wave: 2 +depends_on: [01-02] +files_modified: [src/components/people/PersonForm.jsx, src/components/people/PeopleList.jsx, src/app.jsx] +autonomous: true +requirements: [PEOPLE-01] +user_setup: [] + +must_haves: + truths: + - "User can type a name and click to add it to the bill" + - "User can see a list of all people added to the bill" + - "Empty names are rejected with visible feedback" + - "UI updates immediately when people are added" + artifacts: + - path: "src/components/people/PersonForm.jsx" + provides: "Input form for adding people" + exports: ["PersonForm"] + - path: "src/components/people/PeopleList.jsx" + provides: "Display list of people" + exports: ["PeopleList"] + - path: "src/app.jsx" + provides: "Main app component wiring everything" + exports: ["App"] + key_links: + - from: "src/components/people/PersonForm.jsx" + to: "src/store/billStore.js" + via: "import store and call addPerson" + pattern: "addPerson" + - from: "src/components/people/PeopleList.jsx" + to: "src/store/billStore.js" + via: "subscribe to store.people" + pattern: "store\\.people" +--- + + +Build the People UI components: a form to add people and a list to display them. Wire them to the billStore for reactive updates. + +Purpose: Enable PEOPLE-01 (User can add people to the bill by name). +Output: Working UI for adding and viewing people. + + + +@./.claude/get-shit-done/workflows/execute-plan.md +@./.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-bill-entry-assignment/01-RESEARCH.md + + + + + +From src/store/billStore.js: +```javascript +// Import the singleton store +import { store } from '../store/billStore.js'; + +// Or create a new instance (for testing) +import { createBillStore } from '../store/billStore.js'; + +// Signals (access value via .value) +store.people // Signal> + +// Actions +store.addPerson(name: string) -> {success: boolean, error?: string} +``` + + + + + + Task 1: Create PersonForm component + src/components/people/PersonForm.jsx + + Create a controlled form component for adding people. + + Requirements: + 1. Text input for name + 2. Submit button + 3. Call store.addPerson on form submit + 4. Show error message if addPerson returns {success: false} + 5. Clear input on successful add + 6. Use Preact hooks (useState) for local form state + + Implementation: + ```jsx + import { useState } from 'preact/hooks'; + import { store } from '../../store/billStore.js'; + + export function PersonForm() { + const [name, setName] = useState(''); + const [error, setError] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + const result = store.addPerson(name); + if (result.success) { + setName(''); + setError(''); + } else { + setError(result.error); + } + }; + + return ( +
+ setName(e.target.value)} + placeholder="Enter name" + aria-label="Person name" + /> + + {error &&

{error}

} +
+ ); + } + ``` + + Create the directory first: `mkdir -p src/components/people` +
+ + test -f src/components/people/PersonForm.jsx && grep -q "addPerson" src/components/people/PersonForm.jsx + + PersonForm component exists and imports store.addPerson +
+ + + Task 2: Create PeopleList component + src/components/people/PeopleList.jsx + + Create a component that displays all people in the bill. + + Requirements: + 1. Subscribe to store.people signal + 2. Render list of person names + 3. Show empty state when no people added + 4. Use store.people.value directly (Signals auto-track in components) + + Implementation: + ```jsx + import { store } from '../../store/billStore.js'; + + export function PeopleList() { + const people = store.people.value; + + if (people.length === 0) { + return

No people added yet

; + } + + return ( +
    + {people.map((person) => ( +
  • {person.name}
  • + ))} +
+ ); + } + ``` + + Note: Preact Signals automatically track dependencies when accessed in components. +
+ + test -f src/components/people/PeopleList.jsx && grep -q "store.people" src/components/people/PeopleList.jsx + + PeopleList component exists and reads from store.people +
+ + + Task 3: Wire components in App + src/app.jsx + + Update or create src/app.jsx to include the People components. + + The app should: + 1. Import PersonForm and PeopleList + 2. Render them in a layout + 3. Have a title for the app + + Implementation: + ```jsx + import { PersonForm } from './components/people/PersonForm.jsx'; + import { PeopleList } from './components/people/PeopleList.jsx'; + + export function App() { + return ( +
+

Bill Splitter

+ +
+

People

+ + +
+
+ ); + } + ``` + + Also ensure src/main.jsx wires the App to the DOM: + ```jsx + import { render } from 'preact'; + import { App } from './app.jsx'; + import './styles/index.css'; // If using CSS + + render(, document.getElementById('app')); + ``` + + Verify the app runs: `npm run dev` +
+ + test -f src/app.jsx && grep -q "PersonForm" src/app.jsx && grep -q "PeopleList" src/app.jsx + + App.jsx imports and renders PersonForm and PeopleList +
+ +
+ + +After completing all tasks: +1. `npm run dev` starts without errors +2. App displays "Bill Splitter" heading +3. PersonForm shows input and button +4. Adding a name updates PeopleList immediately +5. Empty name submission shows error + + + +- PersonForm component accepts input and calls store.addPerson +- PeopleList displays people from store.people +- App.jsx wires both components +- Dev server runs without errors +- UI is reactive (add person -> list updates) + + + +After completion, create `.planning/phases/01-bill-entry-assignment/01-03-SUMMARY.md` + diff --git a/gsd-framework/.planning/phases/01-bill-entry-assignment/01-04-PLAN.md b/gsd-framework/.planning/phases/01-bill-entry-assignment/01-04-PLAN.md new file mode 100644 index 0000000..710ec5d --- /dev/null +++ b/gsd-framework/.planning/phases/01-bill-entry-assignment/01-04-PLAN.md @@ -0,0 +1,326 @@ +--- +phase: 01-bill-entry-assignment +plan: 04 +type: execute +wave: 2 +depends_on: [01-02] +files_modified: [src/components/items/ItemForm.jsx, src/components/items/ItemsList.jsx, src/components/items/ItemAssign.jsx, src/app.jsx] +autonomous: true +requirements: [PEOPLE-02, PEOPLE-03, PEOPLE-04] +user_setup: [] + +must_haves: + truths: + - "User can type item name and price and add to bill" + - "User can see list of all items with prices" + - "User can select which people ordered each item" + - "User can select multiple people to share an item" + - "Invalid prices show error feedback" + artifacts: + - path: "src/components/items/ItemForm.jsx" + provides: "Input form for adding items with price" + exports: ["ItemForm"] + - path: "src/components/items/ItemsList.jsx" + provides: "Display list of items with assignments" + exports: ["ItemsList"] + - path: "src/components/items/ItemAssign.jsx" + provides: "Multi-select UI for assigning people to items" + exports: ["ItemAssign"] + key_links: + - from: "src/components/items/ItemForm.jsx" + to: "src/utils/currency.js" + via: "dollarsToCents for validation" + pattern: "dollarsToCents" + - from: "src/components/items/ItemForm.jsx" + to: "src/store/billStore.js" + via: "addItem action" + pattern: "addItem" + - from: "src/components/items/ItemAssign.jsx" + to: "src/store/billStore.js" + via: "setAssignment, getAssignedPeople" + pattern: "setAssignment|getAssignedPeople" +--- + + +Build the Items UI components: form to add items with price, list to display items, and assignment UI to select which people ordered each item. + +Purpose: Enable PEOPLE-02 (add items), PEOPLE-03 (assign to specific people), PEOPLE-04 (mark as shared). +Output: Working UI for adding items and assigning them to people. + + + +@./.claude/get-shit-done/workflows/execute-plan.md +@./.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-bill-entry-assignment/01-RESEARCH.md + + + + + +From src/store/billStore.js: +```javascript +import { store } from '../store/billStore.js'; + +// Signals +store.people // Signal> +store.items // Signal> +store.assignments // Signal> + +// Actions +store.addItem(name: string, priceInput: string) -> {success: boolean, error?: string} +store.setAssignment(itemId: string, personIds: string[]) -> void +store.getAssignedPeople(itemId: string) -> string[] +``` + +From src/utils/currency.js: +```javascript +import { formatCurrency } from '../utils/currency.js'; +// formatCurrency(1599) returns "$15.99" +``` + + + + + + Task 1: Create ItemForm component + src/components/items/ItemForm.jsx + + Create a controlled form component for adding items with name and price. + + Requirements: + 1. Text input for item name + 2. Text input for price (accepts decimal like "15.99") + 3. Submit button + 4. Call store.addItem on form submit + 5. Show error message if addItem returns {success: false} + 6. Clear inputs on successful add + + Implementation: + ```jsx + import { useState } from 'preact/hooks'; + import { store } from '../../store/billStore.js'; + + export function ItemForm() { + const [name, setName] = useState(''); + const [price, setPrice] = useState(''); + const [error, setError] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + const result = store.addItem(name, price); + if (result.success) { + setName(''); + setPrice(''); + setError(''); + } else { + setError(result.error); + } + }; + + return ( +
+ setName(e.target.value)} + placeholder="Item name" + aria-label="Item name" + /> + setPrice(e.target.value)} + placeholder="Price (e.g., 15.99)" + aria-label="Item price" + /> + + {error &&

{error}

} +
+ ); + } + ``` + + Create the directory first: `mkdir -p src/components/items` +
+ + test -f src/components/items/ItemForm.jsx && grep -q "addItem" src/components/items/ItemForm.jsx + + ItemForm component exists and calls store.addItem +
+ + + Task 2: Create ItemAssign component + src/components/items/ItemAssign.jsx + + Create a multi-select component that shows checkboxes for each person. + + Requirements: + 1. Receive item as prop + 2. Show checkbox for each person in store.people + 3. Checkboxes reflect current assignment state + 4. Toggle person on/off when checkbox clicked + 5. Call store.setAssignment when selection changes + + Implementation (from RESEARCH.md pattern): + ```jsx + import { store } from '../../store/billStore.js'; + + export function ItemAssign({ item }) { + const assignedTo = store.getAssignedPeople(item.id); + + const togglePerson = (personId) => { + const current = assignedTo.includes(personId) + ? assignedTo.filter(id => id !== personId) + : [...assignedTo, personId]; + store.setAssignment(item.id, current); + }; + + const people = store.people.value; + + if (people.length === 0) { + return

Add people first to assign items

; + } + + return ( +
+ Split between: + {people.map((person) => ( + + ))} +
+ ); + } + ``` +
+ + test -f src/components/items/ItemAssign.jsx && grep -q "setAssignment" src/components/items/ItemAssign.jsx && grep -q "getAssignedPeople" src/components/items/ItemAssign.jsx + + ItemAssign component exists with checkbox UI for assignment +
+ + + Task 3: Create ItemsList component + src/components/items/ItemsList.jsx + + Create a component that displays all items with their prices and assignment UI. + + Requirements: + 1. Subscribe to store.items signal + 2. Render each item with name, formatted price + 3. Include ItemAssign component for each item + 4. Show empty state when no items added + + Implementation: + ```jsx + import { store } from '../../store/billStore.js'; + import { formatCurrency } from '../../utils/currency.js'; + import { ItemAssign } from './ItemAssign.jsx'; + + export function ItemsList() { + const items = store.items.value; + + if (items.length === 0) { + return

No items added yet

; + } + + return ( +
    + {items.map((item) => ( +
  • +
    + {item.name} + {formatCurrency(item.priceCents)} +
    + +
  • + ))} +
+ ); + } + ``` +
+ + test -f src/components/items/ItemsList.jsx && grep -q "formatCurrency" src/components/items/ItemsList.jsx && grep -q "ItemAssign" src/components/items/ItemsList.jsx + + ItemsList component displays items with price and assignment UI +
+ + + Task 4: Wire items components in App + src/app.jsx + + Update src/app.jsx to include the Items components. + + Add an "Items" section after the People section: + ```jsx + import { PersonForm } from './components/people/PersonForm.jsx'; + import { PeopleList } from './components/people/PeopleList.jsx'; + import { ItemForm } from './components/items/ItemForm.jsx'; + import { ItemsList } from './components/items/ItemsList.jsx'; + + export function App() { + return ( +
+

Bill Splitter

+ +
+

People

+ + +
+ +
+

Items

+ + +
+
+ ); + } + ``` + + Verify the app runs: `npm run dev` +
+ + grep -q "ItemForm" src/app.jsx && grep -q "ItemsList" src/app.jsx + + App.jsx imports and renders ItemForm and ItemsList +
+ +
+ + +After completing all tasks: +1. `npm run dev` starts without errors +2. ItemForm shows name input, price input, and button +3. Adding an item with valid price shows in ItemsList +4. ItemsList shows item name, formatted price ($X.XX) +5. ItemAssign shows checkboxes for each person +6. Selecting checkboxes updates assignment +7. Invalid price shows error message + + + +- ItemForm accepts name and price, calls store.addItem +- ItemsList displays items with formatCurrency prices +- ItemAssign provides checkbox UI for PEOPLE-03 (single) and PEOPLE-04 (multiple/shared) +- All components wired in App.jsx +- Dev server runs without errors + + + +After completion, create `.planning/phases/01-bill-entry-assignment/01-04-SUMMARY.md` + diff --git a/gsd-framework/.planning/phases/01-bill-entry-assignment/01-05-PLAN.md b/gsd-framework/.planning/phases/01-bill-entry-assignment/01-05-PLAN.md new file mode 100644 index 0000000..927db68 --- /dev/null +++ b/gsd-framework/.planning/phases/01-bill-entry-assignment/01-05-PLAN.md @@ -0,0 +1,113 @@ +--- +phase: 01-bill-entry-assignment +plan: 05 +type: execute +wave: 3 +depends_on: [01-03, 01-04] +files_modified: [] +autonomous: false +requirements: [PEOPLE-01, PEOPLE-02, PEOPLE-03, PEOPLE-04] +user_setup: [] + +must_haves: + truths: + - "User can complete the full flow: add people -> add items -> assign items" + - "All Phase 1 requirements are visibly working in the browser" + - "All automated tests pass" + artifacts: [] + key_links: [] +--- + + +Human verification checkpoint to confirm all Phase 1 requirements are working correctly in the live application. + +Purpose: Validate that PEOPLE-01, PEOPLE-02, PEOPLE-03, PEOPLE-04 are fully functional before marking phase complete. +Output: Verified working bill entry and assignment flow. + + + +@./.claude/get-shit-done/workflows/execute-plan.md +@./.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md + + + + + + Task 1: Verify complete bill entry and assignment flow + + Complete bill entry and assignment system: + - PersonForm: Add people by name (PEOPLE-01) + - ItemForm: Add items with name and price (PEOPLE-02) + - ItemAssign: Assign items to specific people (PEOPLE-03) + - ItemAssign: Mark items as shared across multiple people (PEOPLE-04) + - billStore: Reactive state management with Preact Signals + - currency utils: Cents/dollars conversion for accurate money handling + + + ## Automated Verification (run first) + + Run the full test suite to confirm all unit tests pass: + ```bash + npm test -- --run + ``` + + All tests should pass. If any fail, report issues before proceeding. + + ## Manual Verification (in browser) + + 1. Start dev server: `npm run dev` + 2. Open the app in browser (typically http://localhost:5173) + + ### Test PEOPLE-01: Add People + - [ ] Type "Alice" in person input, click Add Person + - [ ] Verify "Alice" appears in the people list + - [ ] Add "Bob" and verify both names appear + - [ ] Try adding empty name - verify error message shows + + ### Test PEOPLE-02: Add Items + - [ ] Type "Pizza" and price "15.99", click Add Item + - [ ] Verify item shows with "$15.99" price + - [ ] Add "Salad" with price "10.50" + - [ ] Try adding item with invalid price "abc" - verify error + + ### Test PEOPLE-03: Assign to Specific Person + - [ ] For "Pizza" item, check only "Alice" + - [ ] Verify checkbox is checked for Alice only + + ### Test PEOPLE-04: Mark as Shared + - [ ] For "Salad" item, check both "Alice" and "Bob" + - [ ] Verify both checkboxes are checked (shared item) + + ## Success Indicators + - All automated tests pass + - People can be added and displayed + - Items can be added with formatted prices + - Checkboxes work for single-person assignment + - Checkboxes work for multi-person sharing + - Error messages appear for invalid input + - UI updates immediately (no page refresh needed) + + Type "approved" if all tests pass, or describe any issues found + + + + + +This IS the verification task. Success = human approval. + + + +- All automated tests pass (`npm test -- --run`) +- Human confirms PEOPLE-01, PEOPLE-02, PEOPLE-03, PEOPLE-04 work in browser +- No blocking issues reported + + + +After completion, create `.planning/phases/01-bill-entry-assignment/01-05-SUMMARY.md` +