From 0ac2ab1a7b0a5f5d96a9b20f8be2e69820d809be Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Wed, 11 Mar 2026 20:11:51 +0000 Subject: [PATCH] docs: complete project research --- .../.planning/research/ARCHITECTURE.md | 390 ++++++++++++++++++ gsd-framework/.planning/research/FEATURES.md | 200 +++++++++ gsd-framework/.planning/research/PITFALLS.md | 371 +++++++++++++++++ gsd-framework/.planning/research/STACK.md | 118 ++++++ gsd-framework/.planning/research/SUMMARY.md | 160 +++++++ 5 files changed, 1239 insertions(+) create mode 100644 gsd-framework/.planning/research/ARCHITECTURE.md create mode 100644 gsd-framework/.planning/research/FEATURES.md create mode 100644 gsd-framework/.planning/research/PITFALLS.md create mode 100644 gsd-framework/.planning/research/STACK.md create mode 100644 gsd-framework/.planning/research/SUMMARY.md diff --git a/gsd-framework/.planning/research/ARCHITECTURE.md b/gsd-framework/.planning/research/ARCHITECTURE.md new file mode 100644 index 0000000..f556c27 --- /dev/null +++ b/gsd-framework/.planning/research/ARCHITECTURE.md @@ -0,0 +1,390 @@ +# Architecture Research: Client-Side Bill Splitting App + +**Domain:** Client-side web application (expense splitting) +**Researched:** 2026-03-11 +**Confidence:** HIGH + +## Standard Architecture + +### System Overview + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ UI Layer │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ PeopleList │ │ ItemsList │ │ Summary │ │ +│ │ Component │ │ Component │ │ Component │ │ +│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ +│ │ │ │ │ +│ ┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐ │ +│ │ PersonForm │ │ ItemForm │ │ TipConfig │ │ +│ │ Component │ │ Component │ │ Component │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ State Manager │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ BillStore (Single Source of Truth) │ │ +│ │ - people: Person[] │ │ +│ │ - items: Item[] │ │ +│ │ - assignments: Map │ │ +│ │ - tipPreferences: Map │ │ +│ └──────────────────────────────────────────────────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ Business Logic │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Calculator │ │ Validator │ │ Normalizer │ │ +│ │ Service │ │ Service │ │ Service │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ Persistence Layer │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ StorageService (localStorage wrapper) │ │ +│ └──────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Component Responsibilities + +| Component | Responsibility | Typical Implementation | +|-----------|----------------|------------------------| +| BillStore | Single source of truth for bill state | Plain JS object with observer pattern or framework state | +| CalculatorService | Split calculations, tip distribution, totals | Pure functions for testability | +| StorageService | localStorage read/write, serialization | Async wrapper around localStorage API | +| ValidatorService | Input validation, business rules | Pure functions returning validation results | +| PeopleList/PersonForm | CRUD for people in the bill | UI components bound to store | +| ItemsList/ItemForm | CRUD for items, price entry | UI components with assignment UI | +| SummaryComponent | Display final split amounts | Computed from store via calculator | +| TipConfigComponent | Per-person tip percentage | UI bound to tipPreferences map | + +## Recommended Project Structure + +``` +src/ +├── index.html # Entry point +├── main.js # App initialization, wire dependencies +├── components/ # UI components (view layer) +│ ├── people/ +│ │ ├── PeopleList.js # List all people +│ │ └── PersonForm.js # Add/edit person +│ ├── items/ +│ │ ├── ItemsList.js # List all items +│ │ ├── ItemForm.js # Add/edit item +│ │ └── ItemAssign.js # Assign item to people +│ ├── summary/ +│ │ ├── Summary.js # Final breakdown display +│ │ └── TipConfig.js # Per-person tip settings +│ └── common/ +│ ├── Button.js # Reusable button +│ ├── Input.js # Reusable input +│ └── Modal.js # Reusable modal +├── store/ # State management +│ ├── BillStore.js # Main store with state +│ └── StoreObserver.js # Pub/sub for UI updates +├── services/ # Business logic +│ ├── Calculator.js # Split math, totals +│ ├── Validator.js # Input validation +│ └── StorageService.js # localStorage wrapper +├── models/ # Data shapes (if using TS or JSDoc) +│ ├── Person.js +│ ├── Item.js +│ └── Assignment.js +└── utils/ # Pure utility functions + ├── currency.js # Format/parse money + └── math.js # Rounding helpers +``` + +### Structure Rationale + +- **components/:** Organized by domain (people, items, summary) with shared UI in common/ +- **store/:** Centralized state with observer pattern for reactive updates without framework overhead +- **services/:** Pure business logic separated from UI, easy to test in isolation +- **models/:** Data shape definitions, factory functions for creating valid entities +- **utils/:** Stateless helpers, no dependencies on app state + +## Architectural Patterns + +### Pattern 1: Unidirectional Data Flow + +**What:** State changes flow in one direction: Action -> Store Update -> UI Re-render + +**When to use:** Always for this domain - prevents state sync bugs + +**Trade-offs:** Slightly more boilerplate, but eliminates "where did this come from?" bugs + +**Example:** +```javascript +// User clicks "Add Person" +// 1. Component dispatches action +function handleAddPerson(name) { + const action = { type: 'ADD_PERSON', payload: { name } }; + store.dispatch(action); +} + +// 2. Store updates state +function reducer(state, action) { + if (action.type === 'ADD_PERSON') { + return { + ...state, + people: [...state.people, { id: generateId(), name: action.payload.name }] + }; + } + return state; +} + +// 3. Store notifies subscribers +store.subscribe((newState) => { + peopleList.render(newState.people); +}); +``` + +### Pattern 2: Pure Calculator Functions + +**What:** All calculation logic is pure functions with no side effects + +**When to use:** For any math/derivation from state - enables easy testing and debugging + +**Trade-offs:** May need to pass more parameters, but worth it for testability + +**Example:** +```javascript +// Pure function - same input always = same output, no side effects +function calculateSplit(items, assignments, tipPreferences, taxRate) { + const perPerson = new Map(); + + // Initialize per-person totals + for (const personId of Object.keys(tipPreferences)) { + perPerson.set(personId, { subtotal: 0, tip: 0, tax: 0, total: 0 }); + } + + // Assign item costs + for (const item of items) { + const assignedTo = assignments.get(item.id) || []; + const splitCount = assignedTo.length || 1; + const perPersonShare = item.price / splitCount; + + for (const personId of assignedTo) { + perPerson.get(personId).subtotal += perPersonShare; + } + } + + // Apply tax and tips + for (const [personId, totals] of perPerson) { + totals.tax = totals.subtotal * taxRate; + totals.tip = (totals.subtotal + totals.tax) * (tipPreferences[personId] / 100); + totals.total = totals.subtotal + totals.tax + totals.tip; + } + + return perPerson; +} +``` + +### Pattern 3: Storage Adapter Pattern + +**What:** Wrap localStorage in a service with consistent interface, handle serialization + +**When to use:** Always - isolates persistence details, enables future migration + +**Trade-offs:** Thin abstraction, but provides test seam and error handling + +**Example:** +```javascript +const StorageService = { + save(key, data) { + try { + localStorage.setItem(key, JSON.stringify(data)); + return { success: true }; + } catch (e) { + if (e.name === 'QuotaExceededError') { + return { success: false, error: 'Storage full' }; + } + return { success: false, error: e.message }; + } + }, + + load(key) { + try { + const raw = localStorage.getItem(key); + return raw ? JSON.parse(raw) : null; + } catch (e) { + console.error('Failed to load from storage:', e); + return null; + } + } +}; +``` + +## Data Flow + +### Request Flow (User Action to UI Update) + +``` +[User adds item] + | + v +[ItemForm Component] --> dispatches action + | + v +[BillStore.reducer()] --> returns new state + | + v +[StorageService.save()] --> persists to localStorage + | + v +[Store notifies subscribers] + | + v +[ItemsList re-renders] + [Summary recalculates] +``` + +### State Management + +``` +┌─────────────────────────────────────────────────────────────┐ +│ BillStore │ +│ state: { │ +│ people: [{id, name}], │ +│ items: [{id, name, price}], │ +│ assignments: {itemId: [personId, ...]}, │ +│ tipPreferences: {personId: percentage}, │ +│ taxRate: number │ +│ } │ +└──────────────────────┬──────────────────────────────────────┘ + | (subscribe) + ┌──────────────┼──────────────┐ + v v v + [PeopleList] [ItemsList] [Summary] + | | | + └──────────────┴──────────────┘ + | + v + [Calculator.calculateSplit()] + | + v + Derived data for display +``` + +### Key Data Flows + +1. **Add Person Flow:** User input -> PersonForm -> dispatch ADD_PERSON -> Store updates people array -> StorageService persists -> Subscribers notified -> PeopleList + ItemAssign dropdowns update + +2. **Assign Item Flow:** User clicks person chip -> ItemAssign -> dispatch ASSIGN_ITEM -> Store updates assignments map -> StorageService persists -> Summary recalculates split + +3. **Calculate Summary Flow:** Store state change -> Summary component receives new state -> calls Calculator.calculateSplit() -> renders per-person breakdown + +4. **Load History Flow:** App init -> StorageService.load('bills') -> parse JSON -> hydrate BillStore -> all components render + +## Build Order (Dependencies) + +``` +Phase 1: Foundation (no dependencies) +├── utils/currency.js # Format money, parse input +├── utils/math.js # Rounding, division +└── models/Person.js # Person factory/validator + +Phase 2: Core Services (depends on Phase 1) +├── services/Validator.js # Uses currency utils +├── services/StorageService.js +└── store/StoreObserver.js # Pub/sub base class + +Phase 3: State Layer (depends on Phase 2) +├── models/Item.js # Item factory +├── models/Assignment.js # Assignment factory +└── store/BillStore.js # Uses Observer, StorageService + +Phase 4: Business Logic (depends on Phase 2-3) +└── services/Calculator.js # Pure functions, uses math utils + +Phase 5: UI Components (depends on Phase 3-4) +├── components/common/* # Reusable UI primitives +├── components/people/* # Depends on BillStore +├── components/items/* # Depends on BillStore +└── components/summary/* # Depends on BillStore, Calculator + +Phase 6: Integration (depends on all) +├── main.js # Wire everything together +└── index.html # Load and initialize +``` + +### Build Order Rationale + +1. **Utils first:** Zero dependencies, needed everywhere +2. **Services second:** Need utils, don't need state +3. **Store third:** Needs services (storage), provides state to UI +4. **Calculator parallel:** Pure functions, only needs utils +5. **UI last:** Consumes everything above +6. **Integration final:** Wires the dependency graph + +## Anti-Patterns + +### Anti-Pattern 1: Storing Derived Data + +**What people do:** Store calculated totals per person in localStorage + +**Why it's wrong:** Source of truth drifts - if items change but totals don't recalc, data is inconsistent + +**Do this instead:** Store only source data (people, items, assignments). Calculate totals on read. + +### Anti-Pattern 2: Direct localStorage in Components + +**What people do:** Components call localStorage directly + +**Why it's wrong:** Hard to test, hard to change storage strategy, no error handling + +**Do this instead:** All storage goes through StorageService, components only interact with store + +### Anti-Pattern 3: Two-Way Binding Without Store + +**What people do:** Form inputs directly update DOM elements that display totals + +**Why it's wrong:** Unpredictable update order, hard to debug, state scattered + +**Do this instead:** All state changes go through store, UI subscribes and re-renders + +### Anti-Pattern 4: Storing Money as Floating Point + +**What people do:** Store prices as `12.99` (float) + +**Why it's wrong:** Floating point errors accumulate (`0.1 + 0.2 !== 0.3`) + +**Do this instead:** Store as cents (integers: `1299`) or use a decimal library. Format for display only. + +## Scaling Considerations + +| Scale | Architecture Adjustments | +|-------|--------------------------| +| Single user, <100 bills | Current architecture is optimal | +| Single user, 1000+ bills | Add bill list/archive view, lazy load, consider IndexedDB | +| Multi-device sync | Requires backend - outside current scope | + +### Scaling Priorities + +1. **First bottleneck:** localStorage size (5-10MB limit) - mitigate with bill archival, only keep recent in active storage +2. **Second bottleneck:** UI performance with many items - mitigate with virtualization or pagination + +## Integration Points + +### External Services + +| Service | Integration Pattern | Notes | +|---------|---------------------|-------| +| None | N/A | Client-side only, no external API calls | + +### Internal Boundaries + +| Boundary | Communication | Notes | +|----------|---------------|-------| +| UI <-> Store | Subscribe/notify pattern | Components subscribe to store changes | +| Store <-> StorageService | Direct function calls | Synchronous for simplicity | +| Store <-> Calculator | Pull on demand | Calculator called when summary renders | + +## Sources + +- MDN Web Docs: MVC Architecture - https://developer.mozilla.org/en-US/docs/Glossary/MVC +- MDN Web Docs: Client-side Frameworks - https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction +- MDN Web Docs: Web Storage API - https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API +- SplitPro Reference Implementation - https://github.com/oss-apps/split-pro (full-stack reference, adapted for client-only) + +--- +*Architecture research for: Client-side bill splitting web application* +*Researched: 2026-03-11* diff --git a/gsd-framework/.planning/research/FEATURES.md b/gsd-framework/.planning/research/FEATURES.md new file mode 100644 index 0000000..4f356cb --- /dev/null +++ b/gsd-framework/.planning/research/FEATURES.md @@ -0,0 +1,200 @@ +# Feature Research + +**Domain:** Bill Splitting Web Application +**Researched:** 2026-03-11 +**Confidence:** MEDIUM + +## Feature Landscape + +### Table Stakes (Users Expect These) + +Features users assume exist. Missing these = product feels incomplete. + +| Feature | Why Expected | Complexity | Notes | +|---------|--------------|------------|-------| +| Add people by name | Basic requirement - users need to identify who's splitting | LOW | Simple input form, array of names | +| Add items with prices | Core data entry - can't split without items | LOW | Form with item name + price fields | +| Assign items to people | The fundamental split operation | MEDIUM | UI challenge - need intuitive assignment flow | +| Calculate individual totals | Why users open a bill splitter | LOW | Sum assigned items per person | +| View final summary | Users need to see "who owes what" | LOW | Display per-person breakdown | +| Basic tip calculation | Tipping is standard in many contexts | LOW | Percentage applied to subtotal | +| Clear/reset bill | Users make mistakes or start fresh | LOW | Button to clear all data | + +### Differentiators (Competitive Advantage) + +Features that set the product apart. Not required, but valuable. + +| Feature | Value Proposition | Complexity | Notes | +|---------|-------------------|------------|-------| +| Custom tip per person | Reflects real-world where people tip differently (15% vs 20%) | MEDIUM | Per-person tip override, more calculation logic | +| Shared item handling | Appetizers, shared plates - common restaurant scenario | MEDIUM | Split item cost across selected people | +| Tax calculation/assignment | Tax can be split evenly or proportionally | MEDIUM | Different split methods for tax | +| Bill history (local storage) | Reference past bills, see patterns | LOW | localStorage persistence, list view | +| Uneven split percentages | Someone pays 60%, others 20% each | LOW | Percentage-based assignment option | +| Real-time running totals | See totals update as you assign items | LOW | Reactive UI updates | +| Receipt photo reference | Visual aid without OCR complexity | LOW | Store image, manual entry still required | +| Multiple currency support | Travel/dining abroad scenarios | MEDIUM | Currency selection and display | +| Equal split option | Quick split for simple bills | LOW | One-click divide evenly | + +### Anti-Features (Commonly Requested, Often Problematic) + +Features that seem good but create problems. + +| Feature | Why Requested | Why Problematic | Alternative | +|---------|---------------|-----------------|-------------| +| Payment processing | "Send money directly" convenience | Adds legal/compliance complexity, requires backend, PCI scope | Link to Venmo/PayPal profiles | +| Receipt OCR scanning | "Just snap and done" speed | OCR is unreliable, corrections take longer than manual entry | Photo reference + manual entry | +| User accounts/authentication | "Access bills anywhere" | Requires backend, auth complexity, security concerns | Local-only with export option | +| Bill sharing via link | "Send to friends" collaboration | Requires backend/database, sync complexity | Screenshot or text summary export | +| Real-time collaboration | "Everyone enters their own" | WebSocket infrastructure, conflict resolution, auth | Single-device entry (one person enters all) | +| Integration with payment apps | "Auto-pay what you owe" | API partnerships, varies by region, maintenance burden | Clear summary with manual payment | +| Expense tracking over time | "See spending patterns" | Scope creep into personal finance app | Keep focused: bill splitting only | +| Recurring bills | "Monthly dinner group" | Complexity for edge case, increases scope | Create new bill each time | + +## Feature Dependencies + +``` +[Add People] + └──required by──> [Assign Items] + └──required by──> [Calculate Totals] + └──required by──> [View Summary] + +[Add Items] + └──required by──> [Assign Items] + +[Tip Calculation] + └──enhances──> [View Summary] + +[Shared Items] + └──enhances──> [Assign Items] (more assignment options) + +[Local Storage] + └──enables──> [Bill History] + +[Custom Tip Per Person] + └──requires──> [Add People] + └──conflicts──> [Simple Global Tip] +``` + +### Dependency Notes + +- **Assign Items requires Add People + Add Items:** Cannot assign items until both people and items exist in the system +- **Calculate Totals requires Assign Items:** No totals until items are assigned to people +- **View Summary requires Calculate Totals:** Summary displays the calculated breakdown +- **Local Storage enables Bill History:** Persistence layer must exist before history feature works +- **Custom Tip Per Person conflicts with Simple Global Tip:** Two different mental models - pick one default, allow override + +## MVP Definition + +### Launch With (v1) + +Minimum viable product - what's needed to validate the concept. + +- [ ] Add people by name - Core: identify who's splitting +- [ ] Add items with prices - Core: what was ordered +- [ ] Assign items to specific people - Core: who ordered what +- [ ] Mark items as "shared" - Common scenario: appetizers, shared plates +- [ ] Calculate per-person totals - Core: math must work +- [ ] Set tip percentage (global default) - Standard expectation +- [ ] View final summary with breakdown - Core: "who owes what" +- [ ] Clear/reset to start fresh - Basic usability + +### Add After Validation (v1.x) + +Features to add once core is working. + +- [ ] Custom tip per person - Trigger: users request it, different tipping preferences +- [ ] Bill history (local storage) - Trigger: users want to reference past bills +- [ ] Equal split option - Trigger: users want quick simple splits +- [ ] Tax handling - Trigger: users confused about tax allocation +- [ ] Export summary as text/image - Trigger: users want to share results + +### Future Consideration (v2+) + +Features to defer until product-market fit is established. + +- [ ] Receipt photo reference (no OCR) - Nice-to-have visual aid +- [ ] Multiple currency support - Edge case: international dining +- [ ] Percentage-based splits - Advanced: partial ownership scenarios +- [ ] Bill templates - Recurring similar bills + +## Feature Prioritization Matrix + +| Feature | User Value | Implementation Cost | Priority | +|---------|------------|---------------------|----------| +| Add people by name | HIGH | LOW | P1 | +| Add items with prices | HIGH | LOW | P1 | +| Assign items to people | HIGH | MEDIUM | P1 | +| Mark items as shared | HIGH | MEDIUM | P1 | +| Calculate totals | HIGH | LOW | P1 | +| View summary | HIGH | LOW | P1 | +| Global tip percentage | HIGH | LOW | P1 | +| Clear/reset | MEDIUM | LOW | P1 | +| Custom tip per person | MEDIUM | MEDIUM | P2 | +| Bill history (local storage) | MEDIUM | LOW | P2 | +| Equal split option | MEDIUM | LOW | P2 | +| Tax handling | MEDIUM | MEDIUM | P2 | +| Export summary | LOW | LOW | P3 | +| Receipt photo reference | LOW | MEDIUM | P3 | +| Multiple currency | LOW | MEDIUM | P3 | + +**Priority key:** +- P1: Must have for launch +- P2: Should have, add when possible +- P3: Nice to have, future consideration + +## Competitor Feature Analysis + +| Feature | Splitwise | Splid | Settle Up | Our Approach | +|---------|-----------|-------|-----------|--------------| +| Add people | Yes (contacts) | Yes (manual) | Yes (manual) | Manual entry, simple | +| Add items | Yes | Yes | Yes | Manual entry with price | +| Item assignment | Yes | Yes | Yes | Person picker per item | +| Shared items | Yes (split) | Yes | Yes | Explicit "shared" toggle | +| Tip handling | Global percentage | Per-person | Global | Global default, P2 per-person | +| Tax handling | Included | Separate | Separate | P2 feature | +| Bill history | Yes (cloud) | Yes (local) | Yes (cloud) | Local storage only | +| User accounts | Required | No | Required | No accounts (local only) | +| Payment integration | Yes (Venmo/PayPal) | No | No | No - out of scope | +| Receipt scanning | Yes (OCR) | No | Yes (OCR) | No - out of scope | +| Collaboration | Real-time sync | No | Real-time sync | No - single device | +| Export | Yes | Yes | Yes | P3: text/image export | + +### Competitor Insights + +**Splitwise (Market Leader):** +- Feature-rich but complex UI +- Requires account creation - friction for quick bill splitting +- OCR scanning is flagship feature but often inaccurate +- Payment integrations are key differentiator for power users +- Overkill for simple restaurant bill scenarios + +**Splid:** +- Simple, focused on quick splits +- No accounts - closer to our approach +- Less feature bloat, faster to use +- Good UX reference for simplicity + +**Settle Up:** +- Mid-ground between Splitwise and Splid +- Currency support for travel +- Some OCR features + +**Our Position:** +- Focus on the "quick restaurant bill" use case +- No accounts = instant start, no friction +- Simple UI beats feature completeness +- Manual entry is faster than correcting bad OCR +- Custom tip per person is our differentiator (most don't do this well) + +## Sources + +- Splitwise.com - Official website feature tour +- Splitwise Google Play Store listing - Feature list and user reviews +- Settle Up (settleup.io) - Website feature overview +- Splid (splid.app) - Website feature overview +- PROJECT.md constraints and requirements + +--- +*Feature research for: Bill Splitting Web Application* +*Researched: 2026-03-11* diff --git a/gsd-framework/.planning/research/PITFALLS.md b/gsd-framework/.planning/research/PITFALLS.md new file mode 100644 index 0000000..9d9daad --- /dev/null +++ b/gsd-framework/.planning/research/PITFALLS.md @@ -0,0 +1,371 @@ +# Pitfalls Research + +**Domain:** Bill Splitting Web Application +**Researched:** 2026-03-11 +**Confidence:** MEDIUM (based on domain expertise and web development best practices) + +--- + +## Critical Pitfalls + +### Pitfall 1: Floating-Point Money Calculations + +**What goes wrong:** +Using JavaScript's native `number` type (IEEE 754 floating-point) for currency calculations leads to precision errors. The classic example is `0.1 + 0.2 = 0.30000000000000004` instead of exactly `0.3`. This causes incorrect totals, awkward display values ("You owe $12.33333333333"), and failed equality checks. + +**Why it happens:** +Floating-point numbers cannot exactly represent most decimal fractions. Only numbers that can be expressed as fractions with denominators that are powers of 2 (like 0.5, 0.25, 0.125) can be represented exactly. Common currency values like $0.10 or $0.20 cannot be represented precisely in binary floating-point. + +**How to avoid:** +Store all amounts as integers representing cents (or the smallest currency unit). Perform all calculations in cents, then divide by 100 only for display. For example, store $12.50 as `1250` cents. Alternatively, use a library like `decimal.js` or `big.js` for arbitrary-precision decimal arithmetic. + +```javascript +// WRONG +const total = 0.1 + 0.2; // 0.30000000000000004 + +// CORRECT - work in cents +const totalCents = 10 + 20; // 30 cents +const displayTotal = (totalCents / 100).toFixed(2); // "0.30" +``` + +**Warning signs:** +- Test cases failing with "expected 3.00, got 3.0000000000000004" +- Users reporting incorrect totals +- Display showing excessive decimal places +- Equality checks like `if (total === 10.00)` failing unexpectedly + +**Phase to address:** +Phase 1 (Core Calculation Engine) - This must be solved before any calculation logic is written, as it affects the entire data model. + +--- + +### Pitfall 2: Uneven Split Remainder Handling + +**What goes wrong:** +When splitting a bill evenly among N people, the division often doesn't result in whole cents. For example, splitting $100 among 3 people is $33.333... per person. Developers often round and distribute, resulting in $33.33 x 3 = $99.99, leaving $0.01 unaccounted for. This "penny error" accumulates and confuses users who expect the math to add up exactly. + +**Why it happens:** +Mathematical division of currency amounts often produces fractional cents. Without explicit handling of the remainder, rounding errors leave the total short or over by a few cents. + +**How to avoid:** +Implement a "remainder distribution" algorithm. Calculate the base amount everyone pays, then distribute the leftover cents (typically 0 to N-1 cents) one by one to participants. Document which approach you use: first person pays extra, last person pays extra, or distribute evenly. + +```javascript +function splitEvenly(totalCents, numPeople) { + const baseAmount = Math.floor(totalCents / numPeople); + const remainder = totalCents % numPeople; + + const splits = new Array(numPeople).fill(baseAmount); + // Distribute remainder to first N people + for (let i = 0; i < remainder; i++) { + splits[i] += 1; + } + return splits; +} +// splitEvenly(10000, 3) => [3334, 3333, 3333] (sums to 10000) +``` + +**Warning signs:** +- Sum of individual amounts doesn't equal the total +- Users asking "where did the extra penny go?" +- Test cases with assertions like `expect(splits.reduce(sum) - total).toBe(0)` failing + +**Phase to address:** +Phase 1 (Core Calculation Engine) - Part of the core splitting logic. + +--- + +### Pitfall 3: Shared Item Assignment Complexity + +**What goes wrong:** +Shared items (appetizers, pitchers, wine) can be shared among any subset of people, creating exponential complexity. A naive implementation may not handle partial sharing correctly (e.g., "Sarah and Mike share the nachos, but Jen doesn't"). Users get frustrated when they can't express real-world sharing scenarios, or worse, the math is wrong. + +**Why it happens:** +The data model for item-to-person assignment is often designed as binary (either one person or everyone), but real-world sharing is many-to-many. Developers underestimate the UX complexity of selecting multiple people per item and the calculation complexity of dividing shared items. + +**How to avoid:** +Design the data model from the start as many-to-many: each item has an array of participant IDs. The cost is divided evenly among participants. For the UI, use checkboxes or a multi-select mechanism per item. Test edge cases: item assigned to 0 people, item assigned to 1 person (should work like individual item), item assigned to everyone. + +```javascript +// Data model +{ + items: [ + { id: 1, name: "Nachos", priceCents: 1200, participants: ["sarah", "mike"] }, + { id: 2, name: "Salad", priceCents: 900, participants: ["jen"] }, + { id: 3, name: "Wine", priceCents: 2400, participants: ["sarah", "mike", "jen"] } + ] +} + +// Calculation +function calculateItemShare(item, personId) { + if (!item.participants.includes(personId)) return 0; + return Math.floor(item.priceCents / item.participants.length); +} +``` + +**Warning signs:** +- Users unable to express "just Sarah and Mike" sharing +- UI forcing all-or-nothing sharing selection +- Bugs when 1 person is selected (should behave like individual item) +- Confusion about what "shared" means in the UI + +**Phase to address:** +Phase 1 (Core Calculation Engine) - Data model must support this from the start. UI can be refined in Phase 2. + +--- + +### Pitfall 4: Tax and Tip Distribution Logic + +**What goes wrong:** +Tax and tip are often added as flat percentages at the end, but this can be unfair when people ordered items at different price points. If one person ordered a $5 salad and another a $30 steak, adding 20% tip and splitting it equally is unfair. Alternatively, if tax is split by percentage but tip is flat, the logic becomes confusing. + +**Why it happens:** +There's no single "correct" way to distribute tax and tip. Developers either pick one approach without considering alternatives, or implement multiple options with confusing UX. The per-person tip requirement in this project adds additional complexity. + +**How to avoid:** +Decide on a clear policy and document it. Common approaches: +1. **Proportional**: Tax/tip distributed proportional to each person's subtotal +2. **Equal split**: Tax/tip divided equally among all participants +3. **Per-item**: Tax/tip applied to each item before splitting + +For per-person custom tips (as specified in PROJECT.md), calculate each person's tip on their subtotal, then sum. + +```javascript +function calculateWithCustomTips(items, people, taxRate) { + const personTotals = {}; + + for (const person of people) { + let subtotal = 0; + for (const item of items) { + if (item.participants.includes(person.id)) { + subtotal += Math.floor(item.priceCents / item.participants.length); + } + } + const tax = Math.floor(subtotal * taxRate); + const tip = Math.floor(subtotal * person.tipRate); + personTotals[person.id] = subtotal + tax + tip; + } + return personTotals; +} +``` + +**Warning signs:** +- Confusion in code comments about "is this right?" +- Test cases with hand-calculated expected values that don't match +- Users questioning why their total doesn't match their mental math +- Tip calculation taking the wrong base (pre-tax vs post-tax) + +**Phase to address:** +Phase 1 (Core Calculation Engine) - Must be decided before any calculation logic is implemented. + +--- + +### Pitfall 5: LocalStorage Data Loss Scenarios + +**What goes wrong:** +LocalStorage is volatile. Users can clear browser data, use private/incognito mode, switch browsers, or use different devices. Bill history can disappear unexpectedly. Additionally, localStorage has a quota (typically 5-10MB) and throws `QuotaExceededError` when full. + +**Why it happens:** +LocalStorage is designed for preferences and small caches, not as a reliable database. It's origin-scoped and browser-specific. Private browsing modes may not persist data at all. + +**How to avoid:** +1. Set expectations in the UI: "Bills are saved on this device only" +2. Implement an export feature (JSON download) for backup +3. Handle `QuotaExceededError` gracefully with a user-friendly message +4. Consider adding a "clear old bills" feature to manage storage +5. Feature-detect localStorage availability before use + +```javascript +function saveBill(bill) { + try { + const bills = JSON.parse(localStorage.getItem('bills') || '[]'); + bills.push(bill); + localStorage.setItem('bills', JSON.stringify(bills)); + return true; + } catch (e) { + if (e.name === 'QuotaExceededError') { + alert('Storage full! Please export and clear old bills.'); + } + return false; + } +} + +function storageAvailable() { + try { + const test = '__storage_test__'; + localStorage.setItem(test, test); + localStorage.removeItem(test); + return true; + } catch (e) { + return false; + } +} +``` + +**Warning signs:** +- Bug reports of "my bills disappeared" +- Errors in console about `QuotaExceededError` +- App crashes when localStorage is disabled +- Data not persisting between sessions + +**Phase to address:** +Phase 2 (Persistence Layer) - When implementing bill history and localStorage integration. + +--- + +### Pitfall 6: Input Validation Edge Cases + +**What goes wrong:** +Users enter unexpected inputs: negative prices, empty names, duplicate names, prices with more than 2 decimal places, extremely large numbers, or special characters. Without validation, the app produces nonsensical results or crashes. + +**Why it happens:** +Developers assume well-behaved input during initial development. Edge cases are discovered only when real users interact with the app. + +**How to avoid:** +Implement input validation at entry points: + +| Input | Validation | +|-------|------------| +| Price | Must be non-negative number, max 2 decimal places, reasonable upper bound | +| Person name | Required, non-empty after trimming, unique within bill | +| Tip percentage | Must be 0-100 (or allow >100 for generous tippers) | +| Item name | Required, non-empty after trimming | + +```javascript +function validatePrice(input) { + const num = parseFloat(input); + if (isNaN(num) || num < 0) return { valid: false, error: 'Price must be a positive number' }; + if (num > 99999.99) return { valid: false, error: 'Price exceeds maximum' }; + if (!/^\d+(\.\d{1,2})?$/.test(input)) { + return { valid: false, error: 'Price must have at most 2 decimal places' }; + } + return { valid: true, value: Math.round(num * 100) }; // Convert to cents +} +``` + +**Warning signs:** +- App accepting negative prices +- Two people with the same name causing calculation bugs +- Decimal input like "12.345" being accepted and causing display issues +- Very large numbers causing floating-point overflow + +**Phase to address:** +Phase 2 (UI/UX Polish) - Input validation goes hand-in-hand with form handling. + +--- + +## Technical Debt Patterns + +Shortcuts that seem reasonable but create long-term problems. + +| Shortcut | Immediate Benefit | Long-term Cost | When Acceptable | +|----------|-------------------|----------------|-----------------| +| Store prices as floats | Faster to implement | Floating-point errors propagate everywhere | Never | +| Skip remainder handling | Simpler code | Totals don't add up, user confusion | Never | +| Use person name as ID | Simpler data model | Breaks with duplicate names, renames | Prototyping only | +| No input validation | Faster to code | Garbage data causes crashes later | Never | +| Inline all logic in UI components | Quick start | Hard to test, can't reuse logic | Prototyping only | +| Skip localStorage availability check | Simpler code | Crashes in private browsing | Never | + +--- + +## Performance Traps + +Patterns that work at small scale but fail as usage grows. + +| Trap | Symptoms | Prevention | When It Breaks | +|------|----------|------------|----------------| +| Storing all bills in single localStorage key | Slow load/save with many bills | Use indexed keys or pagination | ~100+ bills | +| Re-rendering entire UI on every change | Sluggish feel with many items | Use targeted updates | ~50+ items | +| No pagination on bill history | Long list becomes unusable | Add pagination or lazy loading | ~20+ bills | +| Storing full bill objects in memory | Memory pressure with large history | Load on demand, archive old bills | ~1000+ bills | + +Note: For a learning project with local storage, most of these won't be practical concerns. Focus on the first two if the app feels slow. + +--- + +## Security Mistakes + +Domain-specific security issues beyond general web security. + +| Mistake | Risk | Prevention | +|---------|------|------------| +| XSS via item/person names | Malicious scripts execute when rendering user input | Escape all user input before rendering, use textContent not innerHTML | +| Storing sensitive data in localStorage | Visible to anyone with device access | Don't store sensitive info; this app stores only bill data (low sensitivity) | +| No input sanitization | Unexpected behavior or crashes | Validate and sanitize all inputs | + +Note: Since this is a client-side-only app with no authentication, security concerns are minimal. The main risk is XSS through user-provided names. + +--- + +## UX Pitfalls + +Common user experience mistakes in bill splitting apps. + +| Pitfall | User Impact | Better Approach | +|---------|-------------|-----------------| +| Requiring exact price entry (no $ symbol) | Users confused whether to type "$12.50" or "12.50" | Accept both, parse flexibly | +| Forcing a specific flow (add people first, then items) | Users may want to add items as they see them on receipt | Allow flexible order, validate only at calculation time | +| No visual feedback on who's sharing what | Users lose track of assignments | Use color coding or icons to show assignments | +| Showing too many decimal places | "You owe $12.333333" looks unprofessional | Always display as currency: $12.33 | +| No summary before final calculation | Users can't verify inputs before seeing totals | Show itemized summary with ability to edit | +| Confusing "shared" terminology | Does "shared" mean everyone or selected people? | Use explicit "Share with..." with multi-select | +| No way to undo/clear | One mistake requires starting over | Add undo, clear, and edit functionality | + +--- + +## "Looks Done But Isn't" Checklist + +Things that appear complete but are missing critical pieces. + +- [ ] **Calculation Engine:** Often missing remainder handling - verify sum of splits equals original total exactly +- [ ] **Shared Items:** Often missing partial sharing (subset of people) - test with 2 of 3 people sharing an item +- [ ] **Tip Calculation:** Often using wrong base (pre-tax vs post-tax) - verify against manual calculation +- [ ] **LocalStorage:** Often missing error handling - test in private browsing mode +- [ ] **Input Validation:** Often missing edge cases - test negative numbers, empty strings, duplicates +- [ ] **Display Formatting:** Often showing raw numbers - verify all currency displays show $ and 2 decimals +- [ ] **Bill History:** Often missing ability to view/delete past bills - test full CRUD cycle + +--- + +## Recovery Strategies + +When pitfalls occur despite prevention, how to recover. + +| Pitfall | Recovery Cost | Recovery Steps | +|---------|---------------|----------------| +| Floating-point throughout codebase | HIGH | Refactor all money handling to use cents; audit every calculation | +| No remainder handling | MEDIUM | Add remainder distribution logic; existing bills may have penny errors (user can re-calculate) | +| Wrong data model for sharing | HIGH | Migrate data model to support many-to-many; may require re-entering bills | +| No localStorage error handling | LOW | Add try-catch and user messaging; no data migration needed | +| Missing input validation | MEDIUM | Add validation; clean up any corrupted data in storage | + +--- + +## Pitfall-to-Phase Mapping + +How roadmap phases should address these pitfalls. + +| Pitfall | Prevention Phase | Verification | +|---------|------------------|--------------| +| Floating-point calculations | Phase 1 (Core Engine) | Unit test: 0.1 + 0.2 must equal 0.30 exactly | +| Uneven split remainders | Phase 1 (Core Engine) | Unit test: sum of splits must equal original total | +| Shared item complexity | Phase 1 (Core Engine) | Unit test: item shared by 2 of 3 people | +| Tax/tip distribution | Phase 1 (Core Engine) | Unit test: verify against hand-calculated expected value | +| LocalStorage issues | Phase 2 (Persistence) | Integration test: save/load in private mode | +| Input validation | Phase 2 (UI/UX) | Manual test: try negative prices, empty names, duplicates | +| XSS prevention | Phase 2 (UI/UX) | Manual test: enter `` as name | +| UX polish items | Phase 2 (UI/UX) | User testing: observe first-time users | + +--- + +## Sources + +- Stack Overflow: "Is floating-point math broken?" (https://stackoverflow.com/questions/588004/is-floating-point-math-broken) - HIGH confidence +- MDN Web Docs: "Using the Web Storage API" (https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API) - HIGH confidence +- Personal experience with financial application development and bill splitting UX patterns - MEDIUM confidence +- Common patterns observed in existing bill splitting applications (Splitwise, Tricount, etc.) - MEDIUM confidence + +--- +*Pitfalls research for: Bill Splitting Web Application* +*Researched: 2026-03-11* diff --git a/gsd-framework/.planning/research/STACK.md b/gsd-framework/.planning/research/STACK.md new file mode 100644 index 0000000..f433d36 --- /dev/null +++ b/gsd-framework/.planning/research/STACK.md @@ -0,0 +1,118 @@ +# Stack Research + +**Domain:** Client-side bill splitting web application +**Researched:** 2026-03-11 +**Confidence:** HIGH + +## Recommended Stack + +### Core Technologies + +| Technology | Version | Purpose | Why Recommended | +|------------|---------|---------|-----------------| +| Preact | 10.x | UI framework | 3KB bundle size, React-compatible API, perfect for learning React patterns without the overhead. Official Vite integration. Ideal for small client-side apps. | +| Vite | 7.x | Build tool | Industry standard for modern web development. Instant HMR, native ES modules, first-class Preact support via `@preact/preset-vite`. Zero-config setup. | +| Preact Signals | 1.x | State management | Built into Preact, simpler than external libraries. Automatic fine-grained reactivity, no provider boilerplate. Perfect for small-medium apps like bill splitting. | + +### Supporting Libraries + +| Library | Version | Purpose | When to Use | +|---------|---------|---------|-------------| +| Tailwind CSS | 3.4.x | Utility-first styling | Use for rapid UI development. Alternative: vanilla CSS for learning fundamentals. | +| Vitest | 2.x | Unit testing | Vite-native, fast, Jest-compatible API. Use when adding test coverage. | +| TypeScript | 5.x | Type safety | Optional but recommended for learning. Adds compile-time safety. | + +### Development Tools + +| Tool | Purpose | Notes | +|------|---------|-------| +| Node.js | 20.19+ or 22.12+ | Required by Vite 7.x. Use LTS version. | +| pnpm | Package manager | Faster than npm, efficient disk usage. Alternative: npm. | + +## Installation + +```bash +# Create new Preact project with Vite +npm create vite@latest expense-splitter -- --template preact + +# Or with pnpm +pnpm create vite expense-splitter --template preact + +# Install dependencies +cd expense-splitter +npm install + +# Add Tailwind CSS (optional) +npm install -D tailwindcss postcss autoprefixer +npx tailwindcss init -p + +# Add Vitest for testing +npm install -D vitest @vitest/ui +``` + +## Alternatives Considered + +| Recommended | Alternative | When to Use Alternative | +|-------------|-------------|-------------------------| +| Preact | React | If you need React's full ecosystem or are building for a team already using React | +| Preact | Svelte | If you prefer compile-time frameworks and want zero runtime overhead | +| Preact Signals | Zustand | If you need time-travel debugging, middleware, or are coming from React ecosystem | +| Tailwind CSS | Vanilla CSS | If you want to learn CSS fundamentals without abstractions | +| Vite | Parcel | If you want zero-configuration bundling (but Vite has better DX) | + +## What NOT to Use + +| Avoid | Why | Use Instead | +|-------|-----|-------------| +| Create React App (CRA) | Deprecated, no longer maintained, slow builds | Vite with Preact or React template | +| Webpack (for new projects) | Complex configuration, slower than Vite | Vite (simpler, faster, modern) | +| Redux | Overkill for bill splitting app, excessive boilerplate | Preact Signals or Zustand | +| Next.js | Requires server-side, overkill for client-only app | Vite + Preact | +| jQuery | Outdated pattern, doesn't teach modern React concepts | Preact (modern, lightweight) | +| LocalStorage wrapper libraries | Adds unnecessary dependency for simple use case | Native `localStorage` API | + +## Stack Patterns by Variant + +**If prioritizing minimal bundle size:** +- Use Preact + Preact Signals + vanilla CSS +- Because: 3KB framework + 1KB state management + zero CSS runtime + +**If prioritizing learning React ecosystem:** +- Use React + Zustand + Tailwind +- Because: Transferable skills to larger React codebases + +**If prioritizing developer experience:** +- Use Preact + Preact Signals + Tailwind +- Because: Best balance of simplicity, speed, and modern tooling + +## Version Compatibility + +| Package A | Compatible With | Notes | +|-----------|-----------------|-------| +| Vite 7.x | Node.js 20.19+ / 22.12+ | Required minimum Node version | +| Preact 10.x | Vite 7.x | Use `@preact/preset-vite` for optimal integration | +| Preact Signals 1.x | Preact 10.x | First-party, built for Preact | +| Tailwind 3.4.x | Vite 7.x | Use `postcss` plugin integration | +| Vitest 2.x | Vite 7.x | Shares Vite config automatically | + +## Confidence Assessment + +| Recommendation | Confidence | Rationale | +|----------------|------------|-----------| +| Preact | HIGH | Official docs recommend Vite, 3KB size verified, actively maintained | +| Vite 7.x | HIGH | Current version confirmed from official docs (v7.3.1) | +| Preact Signals | HIGH | First-party solution, simpler than alternatives for this use case | +| Tailwind CSS | MEDIUM | Popular choice but vanilla CSS equally valid for learning | +| Vitest | HIGH | Vite-native, industry standard for Vite projects | + +## Sources + +- Vite Official Docs (https://vite.dev/) - Current version 7.3.1, Node.js requirements - HIGH confidence +- Preact Official Docs (https://preactjs.com/) - Vite integration, Signals documentation - HIGH confidence +- Svelte Docs (https://svelte.dev/) - Verified Vite compatibility without SvelteKit - HIGH confidence +- Zustand GitHub (https://github.com/pmndrs/zustand) - Current version 5.0.11 - HIGH confidence +- Tailwind CSS Docs (https://tailwindcss.com/) - Current version 3.4.17 - HIGH confidence + +--- +*Stack research for: Client-side bill splitting web application* +*Researched: 2026-03-11* diff --git a/gsd-framework/.planning/research/SUMMARY.md b/gsd-framework/.planning/research/SUMMARY.md new file mode 100644 index 0000000..cd6e72f --- /dev/null +++ b/gsd-framework/.planning/research/SUMMARY.md @@ -0,0 +1,160 @@ +# Project Research Summary + +**Project:** Expense Splitter (Client-side Bill Splitting Application) +**Domain:** Client-side web application (expense splitting) +**Researched:** 2026-03-11 +**Confidence:** HIGH + +## Executive Summary + +This is a client-side bill splitting web application designed for quick restaurant bill scenarios. Industry leaders like Splitwise and Splid demonstrate that the core value proposition is accurate per-person calculations with minimal friction. The recommended approach is a lightweight Preact + Vite stack with Preact Signals for state management, prioritizing instant-load, no-account-required simplicity over feature completeness. + +The key architectural decision is using unidirectional data flow with pure calculation functions, storing money as cents (integers) to avoid floating-point errors. Critical risks include floating-point precision bugs, uneven split remainder handling, and localStorage volatility. These are mitigated by designing the data model correctly from day one: cents-based calculations, many-to-many item assignments, and clear user expectations about local-only storage. + +## Key Findings + +### Recommended Stack + +Use Preact 10.x with Vite 7.x for a minimal 3KB framework footprint with React-compatible APIs. Preact Signals handles state management without boilerplate. This stack prioritizes learning React patterns while keeping bundle size tiny for a client-only app. + +**Core technologies:** +- **Preact 10.x:** UI framework - 3KB bundle, React-compatible API, official Vite integration +- **Vite 7.x:** Build tool - instant HMR, native ES modules, zero-config setup (requires Node.js 20.19+ or 22.12+) +- **Preact Signals 1.x:** State management - built into Preact, automatic fine-grained reactivity, no provider boilerplate + +**Supporting technologies:** +- **Tailwind CSS 3.4.x:** Styling - rapid UI development (alternative: vanilla CSS for learning) +- **Vitest 2.x:** Testing - Vite-native, Jest-compatible API + +### Expected Features + +**Must have (table stakes - P1):** +- Add people by name - basic requirement for identifying who's splitting +- Add items with prices - core data entry for what was ordered +- Assign items to people (including shared items) - fundamental split operation +- Mark items as "shared" - common scenario for appetizers, shared plates +- Calculate per-person totals - core value proposition +- Set tip percentage (global default) - standard expectation +- View final summary with breakdown - "who owes what" +- Clear/reset to start fresh - basic usability + +**Should have (competitive - P2):** +- Custom tip per person - differentiator, most competitors don't do this well +- Bill history (local storage) - reference past bills +- Equal split option - quick simple splits +- Tax handling - users often confused about tax allocation +- Export summary as text/image - share results without backend + +**Defer (v2+):** +- Receipt photo reference (no OCR) - nice-to-have visual aid +- Multiple currency support - edge case for international dining +- Percentage-based splits - advanced partial ownership scenarios +- User accounts, payment processing, real-time collaboration - all out of scope (anti-features) + +### Architecture Approach + +Use unidirectional data flow with a single BillStore as source of truth. All calculation logic is pure functions for testability. Storage is isolated behind a StorageService wrapper. Money is always stored as cents (integers) to avoid floating-point errors. + +**Major components:** +1. **BillStore** - single source of truth for people, items, assignments, tipPreferences +2. **Calculator** - pure functions for split calculations, tip distribution, totals +3. **StorageService** - localStorage wrapper with error handling (QuotaExceededError) +4. **UI Components** - PeopleList/PersonForm, ItemsList/ItemForm/ItemAssign, Summary/TipConfig + +**Key data model:** +``` +state: { + people: [{id, name}], + items: [{id, name, priceCents, participants: [personId, ...]}], + tipPreferences: {personId: percentage}, + taxRate: number +} +``` + +### Critical Pitfalls + +1. **Floating-Point Money Calculations** - Store all amounts as cents (integers), divide by 100 only for display. Never use JavaScript floats for currency. +2. **Uneven Split Remainder Handling** - Implement remainder distribution algorithm. Sum of splits must equal original total exactly. +3. **Shared Item Assignment Complexity** - Design data model as many-to-many from the start. Each item has array of participant IDs. +4. **Tax and Tip Distribution Logic** - Decide clear policy (proportional vs equal split). Per-person custom tips calculate on individual subtotals. +5. **LocalStorage Data Loss** - Set expectations ("saved on this device only"), handle QuotaExceededError, add export feature for backup. + +## Implications for Roadmap + +Based on research, suggested phase structure: + +### Phase 1: Core Calculation Engine +**Rationale:** Must be solved before any UI work. Data model and calculation logic are the foundation - get this wrong and everything else is built on sand. +**Delivers:** Working calculation engine with unit tests proving correctness +**Addresses:** Add people, add items, assign items, mark shared, calculate totals, global tip, view summary +**Avoids:** Floating-point errors, remainder bugs, shared item complexity, tax/tip confusion +**Stack:** Preact, Vite, Vitest +**Architecture:** BillStore, Calculator (pure functions), models (Person, Item, Assignment) + +### Phase 2: UI Components & Persistence +**Rationale:** With calculation engine proven correct, build UI that consumes it. Add localStorage for bill history. +**Delivers:** Functional user interface with bill history +**Uses:** Preact components, Preact Signals, StorageService +**Implements:** PeopleList, ItemsList, ItemAssign, Summary, TipConfig components +**Addresses:** Clear/reset, bill history (local storage) +**Avoids:** XSS (escape user input), localStorage errors (QuotaExceededError handling), input validation edge cases + +### Phase 3: Polish & P2 Features +**Rationale:** Enhance core experience with competitive differentiators after MVP is solid. +**Delivers:** Custom tip per person, equal split option, tax handling, export summary +**Uses:** Existing Calculator patterns extended for per-person tips +**Addresses:** Custom tip per person (differentiator), equal split, tax handling, export + +### Phase Ordering Rationale + +- **Phase 1 first:** Calculation correctness is non-negotiable. All pitfalls in PITFALLS.md trace back to Phase 1 decisions. Unit tests prove correctness before UI work. +- **Phase 2 second:** UI consumes proven calculation engine. Persistence layer is independent of calculation logic. +- **Phase 3 last:** P2 features are enhancements, not blockers. Get core right first. + +### Research Flags + +Phases likely needing deeper research during planning: +- **Phase 3 (Custom tip per person):** UX for per-person tip configuration needs design exploration - how to present without cluttering UI + +Phases with standard patterns (skip research-phase): +- **Phase 1 (Core Engine):** Well-documented patterns for money handling, pure functions, unit testing +- **Phase 2 (UI Components):** Standard Preact component patterns, localStorage API is well-documented + +## Confidence Assessment + +| Area | Confidence | Notes | +|------|------------|-------| +| Stack | HIGH | Official docs verified for Vite 7.x, Preact 10.x, Signals 1.x. Version compatibility confirmed. | +| Features | MEDIUM | Competitor analysis based on public websites, not internal data. User need prioritization is inferred. | +| Architecture | HIGH | Standard patterns for client-side apps, well-documented in MDN and community resources. | +| Pitfalls | MEDIUM | Based on domain expertise and web development best practices. Some patterns from personal experience. | + +**Overall confidence:** HIGH + +### Gaps to Address + +- **Per-person tip UX:** How to present per-person tip configuration without cluttering the interface. Solution: Design exploration during Phase 3 planning, consider progressive disclosure (global tip shown, "customize" reveals per-person). +- **Shared item UI:** Multi-select mechanism for assigning items to subset of people. Solution: Use checkbox list or person chips per item, test with real users in Phase 2. +- **Bill history navigation:** How to present history list when user has many bills. Solution: Defer to Phase 3, implement pagination if needed. + +## Sources + +### Primary (HIGH confidence) +- Vite Official Docs (https://vite.dev/) - Version 7.3.1, Node.js requirements +- Preact Official Docs (https://preactjs.com/) - Vite integration, Signals documentation +- MDN Web Docs: Web Storage API - localStorage patterns, error handling +- Stack Overflow: "Is floating-point math broken?" - Floating-point precision issues + +### Secondary (MEDIUM confidence) +- Splitwise.com - Feature analysis from public website +- Splid (splid.app) - Feature analysis, UX reference for simplicity +- Settle Up (settleup.io) - Feature analysis +- SplitPro GitHub (https://github.com/oss-apps/split-pro) - Reference implementation patterns +- Personal experience with financial application development + +### Tertiary (LOW confidence) +- Competitor feature gaps inferred from public information - validate during user testing + +--- +*Research completed: 2026-03-11* +*Ready for roadmap: yes*