mirror of
https://github.com/tiennm99/ai-coding-workflow-labs.git
synced 2026-09-17 06:24:20 +00:00
docs: complete project research
This commit is contained in:
@@ -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<itemId, personIds[]> │ │
|
||||
│ │ - tipPreferences: Map<personId, percentage> │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 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*
|
||||
@@ -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*
|
||||
@@ -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 `<script>alert(1)</script>` 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*
|
||||
@@ -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*
|
||||
@@ -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*
|
||||
Reference in New Issue
Block a user