Smart household management frontend for Grocy servers. Features: - Dashboard with PulseRing status indicator - Shopping list with checkable items - Expiring soon / low stock alerts - Chores summary - Quick add with create product flow - PWA installable Tech: React 19, TypeScript, Vite, TanStack Router, Zustand, Tailwind CSS Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1001 lines
27 KiB
Markdown
1001 lines
27 KiB
Markdown
# Grocy2 Design Specification
|
|
|
|
> A modern, delightful household management interface for Grocy
|
|
|
|
## Table of Contents
|
|
|
|
1. [Vision & Goals](#vision--goals)
|
|
2. [User Research & Personas](#user-research--personas)
|
|
3. [Design Principles](#design-principles)
|
|
4. [Information Architecture](#information-architecture)
|
|
5. [Dashboard Design](#dashboard-design)
|
|
6. [Component Library](#component-library)
|
|
7. [Responsive Strategy](#responsive-strategy)
|
|
8. [Interaction Patterns](#interaction-patterns)
|
|
9. [Visual Design System](#visual-design-system)
|
|
10. [Accessibility](#accessibility)
|
|
11. [Performance Budget](#performance-budget)
|
|
12. [Siri & Shortcuts Integration](#siri--shortcuts-integration)
|
|
13. [Technical Architecture](#technical-architecture)
|
|
14. [Implementation Phases](#implementation-phases)
|
|
|
|
---
|
|
|
|
## Vision & Goals
|
|
|
|
### Vision Statement
|
|
|
|
Grocy2 transforms household inventory management from a chore into a seamless part of daily life. It anticipates needs, rewards good habits, and stays out of the way when everything is running smoothly.
|
|
|
|
### Primary Goals
|
|
|
|
1. **Reduce food waste** - Surface expiring items before they go bad
|
|
2. **Eliminate "what do we need?" confusion** - Smart shopping lists that sync
|
|
3. **Enable meal decisions** - Know what you can cook with what you have
|
|
4. **Work for couples/families** - Multi-user, real-time sync
|
|
5. **Voice-first option** - Siri integration for hands-free kitchen use
|
|
|
|
### Success Metrics
|
|
|
|
- Time to complete common action: < 3 seconds
|
|
- First contentful paint: < 1 second
|
|
- User returns to app daily
|
|
- Food waste reduction (self-reported)
|
|
|
|
---
|
|
|
|
## User Research & Personas
|
|
|
|
### Primary Persona: The Household Manager
|
|
|
|
**Name:** Sarah & Tony (shared responsibility couple)
|
|
|
|
**Context:**
|
|
- Both work, limited time for household admin
|
|
- Shop 2-3x per week at different stores
|
|
- Cook 4-5 dinners at home per week
|
|
- Have experienced food waste frustration
|
|
- Use iPhones, occasionally iPad in kitchen
|
|
|
|
**Pain Points:**
|
|
- "Did we already buy milk?"
|
|
- "What's going bad that we need to use?"
|
|
- "I'm at the store but forgot what we needed"
|
|
- "I want to cook but don't know what we can make"
|
|
|
|
**Jobs to Be Done:**
|
|
1. When I'm leaving the store, I want to know if I forgot anything
|
|
2. When I'm cooking, I want to mark ingredients as used without friction
|
|
3. When I'm planning the week, I want to see what we have and what we need
|
|
4. When something is expiring, I want to be reminded before it's too late
|
|
|
|
### Usage Contexts
|
|
|
|
| Context | Device | Attention | Primary Need |
|
|
|---------|--------|-----------|--------------|
|
|
| Kitchen (cooking) | Phone/iPad | Partial | Quick consume, recipe check |
|
|
| Store (shopping) | Phone | Focused | Shopping list, barcode scan |
|
|
| Couch (planning) | iPad/Phone | Full | Meal planning, inventory review |
|
|
| On-the-go | Phone | Minimal | Quick add to list, voice command |
|
|
|
|
---
|
|
|
|
## Design Principles
|
|
|
|
### 1. Context-Aware
|
|
|
|
The interface adapts to time of day and user behavior. Morning shows different priorities than evening. Shopping mode looks different than home mode.
|
|
|
|
### 2. Progressive Disclosure
|
|
|
|
Simple surface, power underneath. The most common actions are obvious. Advanced features are discoverable but not overwhelming.
|
|
|
|
### 3. Forgiving
|
|
|
|
Every action is undoable. Nothing is permanent. The system assumes good intent and makes recovery easy.
|
|
|
|
### 4. Glanceable
|
|
|
|
Status is visible in 1 second. Action takes 2 seconds. Exploration takes 3+. The dashboard is not a wall of text.
|
|
|
|
### 5. Rewarding
|
|
|
|
Completing tasks feels good. Subtle animations, haptic feedback, and visual confirmation make the mundane satisfying.
|
|
|
|
### 6. Calm Technology
|
|
|
|
When everything is fine, the app is quiet. It only demands attention when something needs attention. No anxiety-inducing red badges for non-urgent items.
|
|
|
|
---
|
|
|
|
## Information Architecture
|
|
|
|
### Navigation Structure
|
|
|
|
```
|
|
├── Dashboard (/)
|
|
│ ├── Pulse Status
|
|
│ ├── Smart Cards (contextual)
|
|
│ └── Quick Actions
|
|
│
|
|
├── Inventory (/stock)
|
|
│ ├── All Items (searchable, filterable)
|
|
│ ├── By Location (Fridge, Pantry, Freezer)
|
|
│ ├── By Category
|
|
│ └── Expiring Soon
|
|
│
|
|
├── Shopping (/shopping)
|
|
│ ├── Current List
|
|
│ ├── Shopping Mode (focused checkout)
|
|
│ └── List History
|
|
│
|
|
├── Recipes (/recipes)
|
|
│ ├── All Recipes
|
|
│ ├── Can Make Now
|
|
│ ├── Meal Planning
|
|
│ └── Recipe Detail
|
|
│
|
|
└── Settings (/settings)
|
|
├── Connection
|
|
├── Preferences
|
|
├── Locations
|
|
├── Categories
|
|
└── About
|
|
```
|
|
|
|
### Data Model (from Grocy API)
|
|
|
|
```
|
|
Stock
|
|
├── Products (master list)
|
|
├── Stock Entries (actual inventory)
|
|
├── Locations (Fridge, Pantry, etc.)
|
|
├── Quantity Units (pcs, liters, kg)
|
|
└── Product Groups (Dairy, Produce, etc.)
|
|
|
|
Shopping
|
|
├── Shopping Lists (can have multiple)
|
|
└── Shopping List Items
|
|
|
|
Recipes
|
|
├── Recipes
|
|
├── Recipe Ingredients
|
|
└── Meal Plan
|
|
```
|
|
|
|
---
|
|
|
|
## Dashboard Design
|
|
|
|
### Layout Structure
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ Status Bar (native) │
|
|
├─────────────────────────────────────┤
|
|
│ │
|
|
│ ◯ Pulse Ring ◯ │
|
|
│ (ambient status glow) │
|
|
│ │
|
|
├─────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────────────────────┐ │
|
|
│ │ Smart Card 1 │ │
|
|
│ │ (highest priority) │ │
|
|
│ └─────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────┐ │
|
|
│ │ Smart Card 2 │ │
|
|
│ └─────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────┐ │
|
|
│ │ Smart Card 3 │ │
|
|
│ └─────────────────────────┘ │
|
|
│ │
|
|
├─────────────────────────────────────┤
|
|
│ [+ Add] [📷 Scan] [🎤 Voice] │
|
|
│ Quick Action Bar │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
### Pulse Ring Component
|
|
|
|
**Purpose:** Ambient awareness of household status at a glance.
|
|
|
|
**States:**
|
|
- `healthy` - Soft green glow, gentle pulse. All systems nominal.
|
|
- `attention` - Amber glow, slightly faster pulse. Items expiring in 1-3 days.
|
|
- `urgent` - Red glow, prominent pulse. Expired items or critical stock.
|
|
|
|
**Interaction:**
|
|
- Tap to expand into list of items needing attention
|
|
- Ring shows count badge when collapsed
|
|
|
|
**Technical:**
|
|
- CSS animation with `@keyframes`
|
|
- Color transitions via CSS custom properties
|
|
- Reduced motion support for accessibility
|
|
|
|
### Smart Cards
|
|
|
|
Cards appear based on relevance and urgency. Maximum 4 cards visible; scroll for more.
|
|
|
|
#### Card Priority Algorithm
|
|
|
|
```
|
|
Priority Score = Base Weight + Urgency Modifier + Time Modifier
|
|
|
|
Base Weights:
|
|
- Expired items: 100
|
|
- Expiring today: 90
|
|
- Shopping list (non-empty): 70
|
|
- Low stock items: 60
|
|
- Expiring this week: 50
|
|
- Recipe suggestions: 40
|
|
|
|
Time Modifiers:
|
|
- Morning (6-10am): +20 to expiring, +10 to shopping
|
|
- Midday (10am-2pm): +30 to shopping
|
|
- Evening (4-8pm): +30 to recipes, +20 to expiring
|
|
- Weekend: +20 to meal planning
|
|
|
|
Context Modifiers:
|
|
- Shopping list > 5 items: +20 to shopping
|
|
- Nothing expiring: -50 to expiring card (hide)
|
|
```
|
|
|
|
#### Card Types
|
|
|
|
**1. Expiring Soon Card**
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ 🕐 Use Soon 3 ▾ │
|
|
├─────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────────────────────────────┐ │
|
|
│ │ Milk Tomorrow [→] │ │
|
|
│ └─────────────────────────────────┘ │
|
|
│ ┌─────────────────────────────────┐ │
|
|
│ │ Spinach 2 days [→] │ │
|
|
│ └─────────────────────────────────┘ │
|
|
│ ┌─────────────────────────────────┐ │
|
|
│ │ Greek Yogurt 3 days [→] │ │
|
|
│ └─────────────────────────────────┘ │
|
|
│ │
|
|
│ See all 7 items → │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
**Swipe Actions:**
|
|
- Left swipe: "Used" - Consume 1 unit, animate out
|
|
- Right swipe: "Replace" - Add to shopping list
|
|
|
|
**2. Shopping List Card**
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ 🛒 Shopping List 12 ▾ │
|
|
├─────────────────────────────────────┤
|
|
│ │
|
|
│ ○ Eggs (dozen) │
|
|
│ ○ Bread │
|
|
│ ○ Olive oil │
|
|
│ ○ Chicken breast │
|
|
│ +8 more... │
|
|
│ │
|
|
│ [ 🛍 Start Shopping Trip ] │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
**Interactions:**
|
|
- Tap item to check off
|
|
- "Start Shopping Trip" enters focused shopping mode
|
|
- Badge shows count
|
|
|
|
**3. Low Stock Card**
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ 📉 Running Low 4 ▾ │
|
|
├─────────────────────────────────────┤
|
|
│ │
|
|
│ Coffee ██░░░░░░ 2 bags │
|
|
│ Rice █░░░░░░░ 1 bag │
|
|
│ Pasta ██░░░░░░ 2 boxes │
|
|
│ Olive Oil █░░░░░░░ ¼ bottle │
|
|
│ │
|
|
│ [ Add All to List ] │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
**4. Recipe Suggestions Card**
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ 🍳 Tonight's Options 5 ▾ │
|
|
├─────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────┐ │
|
|
│ │ 🍝 │ Pasta Carbonara │
|
|
│ │ │ ✓ Have everything │
|
|
│ └─────────┘ │
|
|
│ ┌─────────┐ │
|
|
│ │ 🥗 │ Caesar Salad │
|
|
│ │ │ Missing: croutons │
|
|
│ └─────────┘ │
|
|
│ │
|
|
│ Browse Recipes → │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
### Quick Action Bar
|
|
|
|
Fixed to bottom of screen in thumb-reach zone.
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ │
|
|
│ ┌─────┐ ┌─────┐ ┌─────┐ │
|
|
│ │ + │ │ 📷 │ │ 🎤 │ │
|
|
│ │ Add │ │Scan │ │Voice│ │
|
|
│ └─────┘ └─────┘ └─────┘ │
|
|
│ │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
**Add Button:**
|
|
- Opens bottom sheet with:
|
|
- Recent items (last 10 added)
|
|
- Search field
|
|
- Quick category buttons
|
|
- "New Product" option
|
|
|
|
**Scan Button:**
|
|
- Opens camera for barcode
|
|
- Recognizes product → shows add/consume dialog
|
|
- Unknown barcode → offers to create product
|
|
|
|
**Voice Button:**
|
|
- Activates speech recognition
|
|
- Shows live transcription
|
|
- Parses intent:
|
|
- "Add milk to shopping list" → adds item
|
|
- "Used 2 eggs" → consumes from stock
|
|
- "What's expiring?" → shows expiring list
|
|
|
|
---
|
|
|
|
## Component Library
|
|
|
|
### Design Tokens
|
|
|
|
```css
|
|
/* Colors */
|
|
--color-primary: #6B8E6B; /* Sage green */
|
|
--color-primary-light: #8FAF8F;
|
|
--color-primary-dark: #4A6B4A;
|
|
|
|
--color-accent: #E8927C; /* Soft coral */
|
|
--color-accent-light: #F4B8A8;
|
|
--color-accent-dark: #C75B39;
|
|
|
|
--color-urgent: #C75B39; /* Terracotta */
|
|
--color-warning: #E4A853; /* Amber */
|
|
--color-success: #6B8E6B; /* Green */
|
|
|
|
--color-background: #FAFAF8; /* Warm white */
|
|
--color-surface: #FFFFFF;
|
|
--color-surface-elevated: #FFFFFF;
|
|
|
|
--color-text-primary: #2D3436; /* Warm charcoal */
|
|
--color-text-secondary: #636E72;
|
|
--color-text-tertiary: #9CA3A8;
|
|
|
|
--color-border: #E8E8E6;
|
|
--color-border-light: #F0F0EE;
|
|
|
|
/* Spacing */
|
|
--space-xs: 4px;
|
|
--space-sm: 8px;
|
|
--space-md: 16px;
|
|
--space-lg: 24px;
|
|
--space-xl: 32px;
|
|
--space-2xl: 48px;
|
|
|
|
/* Typography */
|
|
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
--font-size-xs: 12px;
|
|
--font-size-sm: 14px;
|
|
--font-size-md: 16px;
|
|
--font-size-lg: 18px;
|
|
--font-size-xl: 24px;
|
|
--font-size-2xl: 32px;
|
|
|
|
--font-weight-normal: 400;
|
|
--font-weight-medium: 500;
|
|
--font-weight-semibold: 600;
|
|
--font-weight-bold: 700;
|
|
|
|
--line-height-tight: 1.2;
|
|
--line-height-normal: 1.5;
|
|
--line-height-relaxed: 1.75;
|
|
|
|
/* Border Radius */
|
|
--radius-sm: 4px;
|
|
--radius-md: 8px;
|
|
--radius-lg: 12px;
|
|
--radius-xl: 16px;
|
|
--radius-full: 9999px;
|
|
|
|
/* Shadows */
|
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
|
|
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
|
|
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
|
|
|
|
/* Transitions */
|
|
--transition-fast: 150ms ease;
|
|
--transition-normal: 250ms ease;
|
|
--transition-slow: 350ms ease;
|
|
|
|
/* Z-Index Scale */
|
|
--z-dropdown: 100;
|
|
--z-sticky: 200;
|
|
--z-overlay: 300;
|
|
--z-modal: 400;
|
|
--z-toast: 500;
|
|
```
|
|
|
|
### Core Components
|
|
|
|
#### Button
|
|
|
|
Variants: `primary`, `secondary`, `ghost`, `danger`
|
|
Sizes: `sm`, `md`, `lg`
|
|
|
|
```tsx
|
|
<Button variant="primary" size="md" icon={<Plus />}>
|
|
Add Item
|
|
</Button>
|
|
```
|
|
|
|
#### Card
|
|
|
|
```tsx
|
|
<Card
|
|
title="Use Soon"
|
|
icon={<Clock />}
|
|
badge={3}
|
|
onExpand={() => {}}
|
|
>
|
|
{children}
|
|
</Card>
|
|
```
|
|
|
|
#### SwipeableRow
|
|
|
|
```tsx
|
|
<SwipeableRow
|
|
leftAction={{ icon: <Check />, color: 'success', onSwipe: handleUse }}
|
|
rightAction={{ icon: <Plus />, color: 'primary', onSwipe: handleAdd }}
|
|
>
|
|
<ProductRow product={product} />
|
|
</SwipeableRow>
|
|
```
|
|
|
|
#### BottomSheet
|
|
|
|
```tsx
|
|
<BottomSheet isOpen={isOpen} onClose={onClose} snapPoints={[0.5, 0.9]}>
|
|
<AddItemForm />
|
|
</BottomSheet>
|
|
```
|
|
|
|
#### PulseRing
|
|
|
|
```tsx
|
|
<PulseRing status="attention" count={3} onClick={handleExpand} />
|
|
```
|
|
|
|
---
|
|
|
|
## Responsive Strategy
|
|
|
|
### Breakpoints
|
|
|
|
```css
|
|
/* Mobile first */
|
|
--bp-sm: 640px; /* Large phones, small tablets */
|
|
--bp-md: 768px; /* Tablets portrait */
|
|
--bp-lg: 1024px; /* Tablets landscape, small laptops */
|
|
--bp-xl: 1280px; /* Desktops */
|
|
--bp-2xl: 1536px; /* Large desktops */
|
|
```
|
|
|
|
### Layout Adaptations
|
|
|
|
#### Mobile (< 640px)
|
|
|
|
- Single column layout
|
|
- Bottom navigation bar
|
|
- Full-screen modals
|
|
- Swipe gestures primary
|
|
- Cards stack vertically
|
|
- Quick action bar fixed to bottom
|
|
|
|
#### Tablet (640px - 1024px)
|
|
|
|
- Two-column card grid
|
|
- Bottom navigation or side tabs
|
|
- Slide-over sheets instead of full modals
|
|
- Split view for list + detail
|
|
- Floating action button option
|
|
|
|
#### Desktop (> 1024px)
|
|
|
|
- Persistent sidebar navigation
|
|
- Three-column layout possible
|
|
- Modal dialogs (not full screen)
|
|
- Hover states and tooltips
|
|
- Keyboard shortcuts
|
|
- Command palette (⌘K)
|
|
- Drag and drop
|
|
|
|
### Touch Targets
|
|
|
|
- Minimum touch target: 44x44px (Apple HIG)
|
|
- Comfortable spacing between targets: 8px minimum
|
|
- Thumb zone consideration for mobile
|
|
|
|
---
|
|
|
|
## Interaction Patterns
|
|
|
|
### Gestures
|
|
|
|
| Gesture | Context | Action |
|
|
|---------|---------|--------|
|
|
| Pull down | Any list | Refresh data |
|
|
| Swipe left | List item | Primary action (use/delete) |
|
|
| Swipe right | List item | Secondary action (add to list) |
|
|
| Long press | List item | Context menu |
|
|
| Pinch | Recipe photo | Zoom |
|
|
| Shake | Anywhere | Undo last action |
|
|
|
|
### Keyboard Shortcuts (Desktop)
|
|
|
|
| Shortcut | Action |
|
|
|----------|--------|
|
|
| `/` or `⌘K` | Open command palette |
|
|
| `n` | New item |
|
|
| `s` | Focus search |
|
|
| `1-5` | Navigate to section |
|
|
| `Escape` | Close modal/cancel |
|
|
| `⌘Z` | Undo |
|
|
| `⌘⇧Z` | Redo |
|
|
| `Enter` | Confirm/submit |
|
|
| `↑/↓` | Navigate list |
|
|
| `Space` | Toggle selected item |
|
|
|
|
### Feedback Patterns
|
|
|
|
**Optimistic Updates:**
|
|
- UI updates immediately
|
|
- Background sync with server
|
|
- Rollback on error with toast notification
|
|
|
|
**Loading States:**
|
|
- Skeleton screens for initial load
|
|
- Inline spinners for actions
|
|
- Pull-to-refresh indicator
|
|
|
|
**Success Feedback:**
|
|
- Checkmark animation
|
|
- Subtle color flash
|
|
- Haptic feedback (mobile)
|
|
- Item animates out of list
|
|
|
|
**Error Handling:**
|
|
- Inline error messages
|
|
- Toast for system errors
|
|
- Retry option always available
|
|
- Never lose user input
|
|
|
|
---
|
|
|
|
## Visual Design System
|
|
|
|
### Color Usage
|
|
|
|
**Primary (Sage Green):**
|
|
- Navigation active states
|
|
- Primary buttons
|
|
- Success states
|
|
- "Healthy" status
|
|
|
|
**Accent (Coral):**
|
|
- Call-to-action buttons
|
|
- Highlights and badges
|
|
- Important notifications
|
|
|
|
**Urgent (Terracotta):**
|
|
- Expired items
|
|
- Critical alerts
|
|
- Delete confirmations
|
|
|
|
**Neutral Palette:**
|
|
- Background, cards, borders
|
|
- Text hierarchy
|
|
- Disabled states
|
|
|
|
### Typography Scale
|
|
|
|
```
|
|
Display: 32px / 700 / 1.2 - Page titles (rare)
|
|
Heading 1: 24px / 600 / 1.2 - Section headers
|
|
Heading 2: 18px / 600 / 1.3 - Card titles
|
|
Body: 16px / 400 / 1.5 - Default text
|
|
Body Small: 14px / 400 / 1.5 - Secondary text
|
|
Caption: 12px / 400 / 1.4 - Metadata, timestamps
|
|
```
|
|
|
|
### Iconography
|
|
|
|
- Use Lucide icons (already in project)
|
|
- Consistent 24px size in navigation
|
|
- 20px in buttons and list items
|
|
- 16px for inline icons
|
|
- 2px stroke weight
|
|
|
|
### Motion Principles
|
|
|
|
1. **Purposeful** - Motion guides attention and confirms actions
|
|
2. **Quick** - 150-300ms for most transitions
|
|
3. **Natural** - Spring physics, not linear
|
|
4. **Reducible** - Respect `prefers-reduced-motion`
|
|
|
|
**Standard Easings:**
|
|
```css
|
|
--ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* Enter */
|
|
--ease-in: cubic-bezier(0.7, 0, 0.84, 0); /* Exit */
|
|
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* Move */
|
|
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* Bounce */
|
|
```
|
|
|
|
---
|
|
|
|
## Accessibility
|
|
|
|
### Requirements
|
|
|
|
- WCAG 2.1 AA compliance minimum
|
|
- Screen reader support (VoiceOver, TalkBack)
|
|
- Keyboard navigation complete
|
|
- Color contrast 4.5:1 minimum for text
|
|
- Focus indicators visible
|
|
- Reduced motion support
|
|
- Touch targets 44x44px minimum
|
|
|
|
### Implementation
|
|
|
|
- Semantic HTML elements
|
|
- ARIA labels where needed
|
|
- Skip navigation link
|
|
- Focus management for modals
|
|
- Announce dynamic content changes
|
|
- Alt text for images
|
|
- Form labels and error messages
|
|
|
|
---
|
|
|
|
## Performance Budget
|
|
|
|
### Targets
|
|
|
|
| Metric | Target | Max |
|
|
|--------|--------|-----|
|
|
| First Contentful Paint | < 1s | 1.5s |
|
|
| Largest Contentful Paint | < 2s | 2.5s |
|
|
| Time to Interactive | < 3s | 4s |
|
|
| Cumulative Layout Shift | < 0.1 | 0.15 |
|
|
| First Input Delay | < 50ms | 100ms |
|
|
|
|
### Bundle Size Budget
|
|
|
|
| Asset | Target | Max |
|
|
|-------|--------|-----|
|
|
| Initial JS | < 100KB | 150KB |
|
|
| Initial CSS | < 20KB | 30KB |
|
|
| Total initial | < 150KB | 200KB |
|
|
| Lazy chunks | < 50KB each | 75KB |
|
|
|
|
### Strategies
|
|
|
|
- Code splitting by route
|
|
- Lazy load below-fold content
|
|
- Image optimization (WebP, lazy loading)
|
|
- Preconnect to API server
|
|
- Service worker for caching
|
|
- Virtual lists for long inventories
|
|
|
|
---
|
|
|
|
## Siri & Shortcuts Integration
|
|
|
|
### Approach
|
|
|
|
Since this is a web app, native Siri integration requires a companion approach:
|
|
|
|
1. **PWA with URL Schemes** - Handle `grocy://` URLs
|
|
2. **Shortcuts Integration** - Create iOS Shortcuts that call our web app
|
|
3. **Future: Native Wrapper** - Capacitor/React Native for App Intents
|
|
|
|
### URL Scheme Design
|
|
|
|
```
|
|
grocy://add?item=milk&list=shopping
|
|
grocy://consume?item=eggs&amount=2
|
|
grocy://open?page=shopping
|
|
grocy://open?page=expiring
|
|
```
|
|
|
|
### Shortcuts Templates
|
|
|
|
**"Add to Shopping List"**
|
|
```
|
|
1. Ask for input: "What do you want to add?"
|
|
2. Open URL: grocy://add?item=[input]&list=shopping
|
|
```
|
|
|
|
**"What's Expiring?"**
|
|
```
|
|
1. Open URL: grocy://open?page=expiring
|
|
```
|
|
|
|
**"Start Shopping Trip"**
|
|
```
|
|
1. Open URL: grocy://open?page=shopping&mode=trip
|
|
```
|
|
|
|
### Voice Input (Web Speech API)
|
|
|
|
For in-app voice commands:
|
|
|
|
```tsx
|
|
const recognition = new webkitSpeechRecognition();
|
|
recognition.continuous = false;
|
|
recognition.interimResults = true;
|
|
|
|
// Parse intents from transcription
|
|
const intents = {
|
|
add: /add (.+) to (shopping list|stock)/i,
|
|
consume: /used? (\d+)? ?(.+)/i,
|
|
query: /do we have (.+)/i,
|
|
expiring: /what'?s expiring/i,
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Technical Architecture
|
|
|
|
### State Management
|
|
|
|
**Zustand Store Structure:**
|
|
|
|
```typescript
|
|
interface AppState {
|
|
// Connection
|
|
isConnected: boolean;
|
|
serverUrl: string | null;
|
|
|
|
// User
|
|
user: User | null;
|
|
systemInfo: SystemInfo | null;
|
|
|
|
// Data
|
|
stock: StockProduct[];
|
|
products: Product[];
|
|
locations: Location[];
|
|
quantityUnits: QuantityUnit[];
|
|
productGroups: ProductGroup[];
|
|
shoppingList: ShoppingListItem[];
|
|
recipes: Recipe[];
|
|
|
|
// UI State
|
|
dashboardCards: CardConfig[];
|
|
isShoppingMode: boolean;
|
|
|
|
// Actions
|
|
connect: (url: string, key: string) => Promise<void>;
|
|
disconnect: () => void;
|
|
refreshAll: () => Promise<void>;
|
|
|
|
// Stock actions
|
|
consumeProduct: (id: number, amount: number) => Promise<void>;
|
|
addToStock: (id: number, amount: number, options?: StockOptions) => Promise<void>;
|
|
|
|
// Shopping actions
|
|
addToShoppingList: (productId: number, amount: number) => Promise<void>;
|
|
checkOffItem: (itemId: number) => Promise<void>;
|
|
clearCompletedItems: () => Promise<void>;
|
|
}
|
|
```
|
|
|
|
### Data Fetching Strategy
|
|
|
|
- **React Query** for server state
|
|
- Automatic background refetching
|
|
- Optimistic updates for mutations
|
|
- Stale-while-revalidate pattern
|
|
- Offline support with persistence
|
|
|
|
### Folder Structure
|
|
|
|
```
|
|
src/
|
|
├── api/
|
|
│ ├── client.ts # API client
|
|
│ └── hooks.ts # React Query hooks
|
|
├── components/
|
|
│ ├── ui/ # Base components
|
|
│ │ ├── Button.tsx
|
|
│ │ ├── Card.tsx
|
|
│ │ ├── Input.tsx
|
|
│ │ └── ...
|
|
│ ├── dashboard/ # Dashboard-specific
|
|
│ │ ├── PulseRing.tsx
|
|
│ │ ├── SmartCard.tsx
|
|
│ │ ├── QuickActionBar.tsx
|
|
│ │ └── ...
|
|
│ ├── stock/ # Stock page components
|
|
│ ├── shopping/ # Shopping components
|
|
│ └── layout/ # Layout components
|
|
├── hooks/
|
|
│ ├── useVoiceInput.ts
|
|
│ ├── useSwipeGesture.ts
|
|
│ └── ...
|
|
├── lib/
|
|
│ ├── cardPriority.ts # Card sorting algorithm
|
|
│ ├── voiceParser.ts # Voice command parsing
|
|
│ └── utils.ts
|
|
├── pages/
|
|
│ ├── Dashboard.tsx
|
|
│ ├── Stock.tsx
|
|
│ ├── Shopping.tsx
|
|
│ ├── Recipes.tsx
|
|
│ └── Settings.tsx
|
|
├── store/
|
|
│ └── index.ts # Zustand store
|
|
├── styles/
|
|
│ ├── tokens.css # Design tokens
|
|
│ └── global.css
|
|
├── types/
|
|
│ └── grocy.ts # TypeScript types
|
|
├── App.tsx
|
|
└── main.tsx
|
|
```
|
|
|
|
---
|
|
|
|
## Implementation Phases
|
|
|
|
### Phase 1: Foundation (Week 1)
|
|
|
|
**Goal:** Design system and responsive shell
|
|
|
|
- [ ] Set up design tokens (CSS custom properties)
|
|
- [ ] Create base UI components (Button, Card, Input, etc.)
|
|
- [ ] Implement responsive layout shell
|
|
- [ ] Create bottom navigation for mobile
|
|
- [ ] Create sidebar navigation for desktop
|
|
- [ ] Set up React Query for data fetching
|
|
- [ ] Dark mode support (CSS variables)
|
|
|
|
**Deliverable:** Navigable app shell that looks good on all devices
|
|
|
|
### Phase 2: Smart Dashboard (Week 2)
|
|
|
|
**Goal:** Context-aware dashboard with smart cards
|
|
|
|
- [ ] Implement PulseRing component
|
|
- [ ] Create SmartCard component system
|
|
- [ ] Build card priority algorithm
|
|
- [ ] Implement ExpiringCard with swipe actions
|
|
- [ ] Implement ShoppingListCard
|
|
- [ ] Implement LowStockCard
|
|
- [ ] Implement RecipeSuggestionsCard
|
|
- [ ] Add time-based card prioritization
|
|
- [ ] Create QuickActionBar
|
|
|
|
**Deliverable:** Functional smart dashboard
|
|
|
|
### Phase 3: Quick Actions (Week 3)
|
|
|
|
**Goal:** Friction-free adding and consuming
|
|
|
|
- [ ] Build AddItem bottom sheet
|
|
- [ ] Implement recent items list
|
|
- [ ] Add product search with typeahead
|
|
- [ ] Implement barcode scanning (camera API)
|
|
- [ ] Add voice input (Web Speech API)
|
|
- [ ] Build voice command parser
|
|
- [ ] Implement optimistic updates
|
|
- [ ] Add undo/redo support
|
|
|
|
**Deliverable:** Can add/consume items in < 3 seconds
|
|
|
|
### Phase 4: Full Pages (Week 4)
|
|
|
|
**Goal:** Complete Stock, Shopping, Recipes pages
|
|
|
|
- [ ] Stock page with filtering and search
|
|
- [ ] Stock item detail view
|
|
- [ ] Shopping page with list management
|
|
- [ ] Shopping mode (focused checkout experience)
|
|
- [ ] Recipes page with filtering
|
|
- [ ] Recipe detail with ingredient availability
|
|
- [ ] Settings page
|
|
|
|
**Deliverable:** All pages functional
|
|
|
|
### Phase 5: Polish & Native (Week 5)
|
|
|
|
**Goal:** PWA, Siri integration, final polish
|
|
|
|
- [ ] PWA manifest and icons
|
|
- [ ] Service worker for offline
|
|
- [ ] URL scheme handlers
|
|
- [ ] Shortcuts integration documentation
|
|
- [ ] Home screen widgets (if possible via PWA)
|
|
- [ ] Animation polish
|
|
- [ ] Performance optimization
|
|
- [ ] Accessibility audit
|
|
- [ ] User testing and fixes
|
|
|
|
**Deliverable:** Production-ready app
|
|
|
|
---
|
|
|
|
## Appendix
|
|
|
|
### API Endpoints Reference
|
|
|
|
```
|
|
GET /api/system/info
|
|
GET /api/users/me
|
|
|
|
GET /api/stock
|
|
POST /api/stock/products/{id}/add
|
|
POST /api/stock/products/{id}/consume
|
|
POST /api/stock/products/{id}/open
|
|
|
|
GET /api/objects/{entity}
|
|
POST /api/objects/{entity}
|
|
PUT /api/objects/{entity}/{id}
|
|
DELETE /api/objects/{entity}/{id}
|
|
|
|
GET /api/recipes
|
|
GET /api/recipes/{id}/fulfillment
|
|
```
|
|
|
|
### Grocy Entity Types
|
|
|
|
- products
|
|
- locations
|
|
- quantity_units
|
|
- product_groups
|
|
- shopping_list
|
|
- shopping_lists
|
|
- recipes
|
|
- recipes_pos
|
|
- chores
|
|
- tasks
|
|
|
|
---
|
|
|
|
*Document Version: 1.0*
|
|
*Last Updated: January 2025*
|
|
*Authors: Claude & Tony*
|