Files
stash-pile/docs/DESIGN_SPEC.md
tonym cbca17a682 Initial commit - Stashpile v0.1
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>
2026-02-02 21:14:12 -06:00

27 KiB

Grocy2 Design Specification

A modern, delightful household management interface for Grocy

Table of Contents

  1. Vision & Goals
  2. User Research & Personas
  3. Design Principles
  4. Information Architecture
  5. Dashboard Design
  6. Component Library
  7. Responsive Strategy
  8. Interaction Patterns
  9. Visual Design System
  10. Accessibility
  11. Performance Budget
  12. Siri & Shortcuts Integration
  13. Technical Architecture
  14. 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

/* 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

<Button variant="primary" size="md" icon={<Plus />}>
  Add Item
</Button>

Card

<Card
  title="Use Soon"
  icon={<Clock />}
  badge={3}
  onExpand={() => {}}
>
  {children}
</Card>

SwipeableRow

<SwipeableRow
  leftAction={{ icon: <Check />, color: 'success', onSwipe: handleUse }}
  rightAction={{ icon: <Plus />, color: 'primary', onSwipe: handleAdd }}
>
  <ProductRow product={product} />
</SwipeableRow>

BottomSheet

<BottomSheet isOpen={isOpen} onClose={onClose} snapPoints={[0.5, 0.9]}>
  <AddItemForm />
</BottomSheet>

PulseRing

<PulseRing status="attention" count={3} onClick={handleExpand} />

Responsive Strategy

Breakpoints

/* 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:

--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:

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:

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