docs: Initial project documentation
- README: Project overview, quick start, tech stack - PROJECT_PLAN: Vision, roadmap, 6-week MVP timeline - ARCHITECTURE: System design, data model, tech decisions Establishes foundation for Pantry - self-hosted kitchen inventory PWA with barcode scanning.
This commit is contained in:
100
README.md
Normal file
100
README.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Pantry 🍳
|
||||||
|
|
||||||
|
Self-hosted kitchen inventory management — family-friendly PWA with barcode scanning.
|
||||||
|
|
||||||
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
## 🎯 Vision
|
||||||
|
|
||||||
|
A simple, modern kitchen inventory app that the whole family can actually use. Born from frustration with existing tools:
|
||||||
|
- **Grocy:** Powerful but overwhelming
|
||||||
|
- **KitchenOwl:** Nice UI but lacks features
|
||||||
|
|
||||||
|
**Pantry** bridges the gap — simple enough for daily use, powerful enough to be useful.
|
||||||
|
|
||||||
|
## ✨ Key Features (Planned)
|
||||||
|
|
||||||
|
- 📱 **PWA** — Install on phone, works offline
|
||||||
|
- 📸 **Barcode scanning** — Scan → Add (3 taps)
|
||||||
|
- 🏷️ **Tag-based organization** — Flexible categories (fridge, dairy, etc.)
|
||||||
|
- 📊 **Unit conversions** — kg ↔ g, L ↔ mL
|
||||||
|
- 👥 **Multi-user** — Shared inventory (no household complexity)
|
||||||
|
- 🔒 **Self-hosted** — Your data stays yours
|
||||||
|
- 🌐 **Open Food Facts** — Auto-fill product data from barcodes
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone
|
||||||
|
git clone https://gitea.jeanlucmakiola.de/pantry-app/pantry.git
|
||||||
|
|
||||||
|
# Start services (Docker Compose)
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Access at http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
|
||||||
|
- [**Project Plan**](docs/PROJECT_PLAN.md) — Vision, roadmap, phases
|
||||||
|
- [**Architecture**](docs/ARCHITECTURE.md) — Tech stack, data model, design decisions
|
||||||
|
- [**Database Schema**](docs/DATABASE.md) — Tables, relationships, RLS policies
|
||||||
|
- [**API Reference**](docs/API.md) — Endpoints, Supabase functions
|
||||||
|
- [**Development Guide**](docs/DEVELOPMENT.md) — Setup, workflow, conventions
|
||||||
|
- [**Deployment**](docs/DEPLOYMENT.md) — Docker, Coolify, production setup
|
||||||
|
|
||||||
|
## 🛠️ Tech Stack
|
||||||
|
|
||||||
|
| Layer | Technology |
|
||||||
|
|-------|------------|
|
||||||
|
| Frontend | Nuxt 4 (Vue 3) |
|
||||||
|
| Styling | Tailwind CSS + Nuxt UI |
|
||||||
|
| Backend | Supabase (Postgres, Auth, Realtime) |
|
||||||
|
| Barcode | html5-qrcode |
|
||||||
|
| Runtime | Bun |
|
||||||
|
| Deployment | Docker Compose |
|
||||||
|
| External API | Open Food Facts |
|
||||||
|
|
||||||
|
## 🗂️ Monorepo Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
pantry/
|
||||||
|
├── app/ # Nuxt 4 PWA
|
||||||
|
├── supabase/ # Database, migrations, functions
|
||||||
|
├── docker/ # Docker Compose, configs
|
||||||
|
├── docs/ # Documentation
|
||||||
|
└── scripts/ # Utilities
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Principles
|
||||||
|
|
||||||
|
1. **Family-friendly** — If your parents can't use it, it's too complex
|
||||||
|
2. **Start simple** — Ship small, iterate fast
|
||||||
|
3. **Extendable** — Clean architecture for future features
|
||||||
|
4. **Self-hosted first** — No SaaS plans, no lock-in
|
||||||
|
|
||||||
|
## 📋 MVP Status
|
||||||
|
|
||||||
|
**Target:** v0.1 (6-week sprint)
|
||||||
|
|
||||||
|
See [PROJECT_PLAN.md](docs/PROJECT_PLAN.md) for detailed roadmap.
|
||||||
|
|
||||||
|
- [ ] Foundation (Nuxt + Supabase + Auth)
|
||||||
|
- [ ] Core inventory (CRUD, tags, units)
|
||||||
|
- [ ] Barcode scanning (PWA camera + Open Food Facts)
|
||||||
|
- [ ] Mobile polish (PWA, offline)
|
||||||
|
- [ ] Docker deployment
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
This is an early-stage project. Contributions welcome once v0.1 ships.
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
MIT © 2026 Jean-Luc Makiola
|
||||||
|
|
||||||
|
## 🙏 Acknowledgments
|
||||||
|
|
||||||
|
- Open Food Facts for product data
|
||||||
|
- Supabase for the backend platform
|
||||||
|
- Nuxt/Vue ecosystem
|
||||||
655
docs/ARCHITECTURE.md
Normal file
655
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,655 @@
|
|||||||
|
# Pantry - Architecture
|
||||||
|
|
||||||
|
**Version:** 1.0
|
||||||
|
**Last Updated:** 2026-02-08
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ System Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ User Devices │
|
||||||
|
│ (Phone, Tablet, Desktop - PWA) │
|
||||||
|
└──────────────────┬──────────────────────────┘
|
||||||
|
│ HTTPS
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ Nuxt 4 Frontend (SSR) │
|
||||||
|
│ - Vue 3 Components │
|
||||||
|
│ - Tailwind CSS + Nuxt UI │
|
||||||
|
│ - PWA (offline-first) │
|
||||||
|
│ - Barcode Scanner │
|
||||||
|
└──────────────────┬──────────────────────────┘
|
||||||
|
│ WebSocket + REST
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ Supabase Platform │
|
||||||
|
│ ┌─────────────────────────────────────┐ │
|
||||||
|
│ │ PostgreSQL (Database) │ │
|
||||||
|
│ │ - Items, Products, Tags, Units │ │
|
||||||
|
│ │ - Row Level Security (RLS) │ │
|
||||||
|
│ └─────────────────────────────────────┘ │
|
||||||
|
│ ┌─────────────────────────────────────┐ │
|
||||||
|
│ │ Auth (GoTrue) │ │
|
||||||
|
│ │ - Email/Password │ │
|
||||||
|
│ │ - OIDC (optional) │ │
|
||||||
|
│ └─────────────────────────────────────┘ │
|
||||||
|
│ ┌─────────────────────────────────────┐ │
|
||||||
|
│ │ Realtime (WebSocket) │ │
|
||||||
|
│ │ - Live updates across devices │ │
|
||||||
|
│ └─────────────────────────────────────┘ │
|
||||||
|
│ ┌─────────────────────────────────────┐ │
|
||||||
|
│ │ Edge Functions (Deno) │ │
|
||||||
|
│ │ - Product lookup │ │
|
||||||
|
│ │ - Barcode cache │ │
|
||||||
|
│ └─────────────────────────────────────┘ │
|
||||||
|
└──────────────────┬──────────────────────────┘
|
||||||
|
│ HTTP
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ Open Food Facts API │
|
||||||
|
│ (External - product data) │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Tech Stack
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
**Nuxt 4** (Vue 3)
|
||||||
|
- **Why:** Meta-framework, SSR, excellent DX
|
||||||
|
- **Alternatives considered:** Next.js (React), SvelteKit
|
||||||
|
- **Decision:** Vue ecosystem maturity + Nuxt UI
|
||||||
|
|
||||||
|
**Tailwind CSS**
|
||||||
|
- **Why:** Utility-first, fast iteration
|
||||||
|
- **Alternatives:** CSS Modules, UnoCSS
|
||||||
|
- **Decision:** Industry standard, Nuxt UI built on it
|
||||||
|
|
||||||
|
**Nuxt UI**
|
||||||
|
- **Why:** Pre-built components, accessible
|
||||||
|
- **Alternatives:** Headless UI, Shadcn
|
||||||
|
- **Decision:** First-party Nuxt support
|
||||||
|
|
||||||
|
**html5-qrcode**
|
||||||
|
- **Why:** PWA camera support, multi-format
|
||||||
|
- **Alternatives:** ZXing, QuaggaJS
|
||||||
|
- **Decision:** Best mobile performance in tests
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
**Supabase**
|
||||||
|
- **Why:** Postgres + Auth + Realtime in one
|
||||||
|
- **Alternatives:** Firebase, Appwrite, custom backend
|
||||||
|
- **Decision:** Self-hosted, SQL flexibility, mature
|
||||||
|
|
||||||
|
**PostgreSQL 15+**
|
||||||
|
- **Why:** Robust, supports JSONB, full-text search
|
||||||
|
- **Alternatives:** MySQL, MongoDB
|
||||||
|
- **Decision:** Comes with Supabase, best for relational data
|
||||||
|
|
||||||
|
**Deno Edge Functions**
|
||||||
|
- **Why:** Serverless, TypeScript-native
|
||||||
|
- **Alternatives:** Node.js functions, custom API
|
||||||
|
- **Decision:** Part of Supabase, fast cold starts
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
**Docker Compose**
|
||||||
|
- **Why:** Simple multi-container orchestration
|
||||||
|
- **Alternatives:** Kubernetes, bare metal
|
||||||
|
- **Decision:** Right complexity level for self-hosted
|
||||||
|
|
||||||
|
**Bun** (runtime)
|
||||||
|
- **Why:** Fast, npm-compatible, built-in TypeScript
|
||||||
|
- **Alternatives:** Node.js, pnpm
|
||||||
|
- **Decision:** Modern, faster than Node
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗄️ Data Model
|
||||||
|
|
||||||
|
### Core Entities
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
INVENTORY_ITEMS ||--o| PRODUCTS : references
|
||||||
|
INVENTORY_ITEMS }o--o{ TAGS : tagged_with
|
||||||
|
INVENTORY_ITEMS ||--|| UNITS : uses
|
||||||
|
PRODUCTS ||--|| UNITS : default_unit
|
||||||
|
UNITS ||--o| UNITS : converts_to
|
||||||
|
ITEM_TAGS }|--|| INVENTORY_ITEMS : item
|
||||||
|
ITEM_TAGS }|--|| TAGS : tag
|
||||||
|
|
||||||
|
INVENTORY_ITEMS {
|
||||||
|
uuid id PK
|
||||||
|
uuid product_id FK
|
||||||
|
string name
|
||||||
|
decimal quantity
|
||||||
|
uuid unit_id FK
|
||||||
|
date expiry_date
|
||||||
|
uuid added_by FK
|
||||||
|
timestamp created_at
|
||||||
|
timestamp updated_at
|
||||||
|
}
|
||||||
|
|
||||||
|
PRODUCTS {
|
||||||
|
uuid id PK
|
||||||
|
string barcode UK
|
||||||
|
string name
|
||||||
|
string brand
|
||||||
|
string image_url
|
||||||
|
uuid default_unit_id FK
|
||||||
|
timestamp cached_at
|
||||||
|
}
|
||||||
|
|
||||||
|
TAGS {
|
||||||
|
uuid id PK
|
||||||
|
string name
|
||||||
|
enum category
|
||||||
|
string icon
|
||||||
|
string color
|
||||||
|
uuid created_by FK
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITS {
|
||||||
|
uuid id PK
|
||||||
|
string name
|
||||||
|
string abbreviation
|
||||||
|
enum unit_type
|
||||||
|
uuid base_unit_id FK
|
||||||
|
decimal conversion_factor
|
||||||
|
boolean is_default
|
||||||
|
uuid created_by FK
|
||||||
|
}
|
||||||
|
|
||||||
|
ITEM_TAGS {
|
||||||
|
uuid item_id FK
|
||||||
|
uuid tag_id FK
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Relationships
|
||||||
|
|
||||||
|
1. **Items ↔ Products:** Many-to-one (multiple items from same product)
|
||||||
|
2. **Items ↔ Tags:** Many-to-many (items can have multiple tags)
|
||||||
|
3. **Units ↔ Units:** Self-referential (g → kg conversion)
|
||||||
|
4. **Items → Users:** Audit trail (who added)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Security Model
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
**Supabase Auth (GoTrue)**
|
||||||
|
|
||||||
|
**Email/Password (Default):**
|
||||||
|
```typescript
|
||||||
|
const { user, error } = await supabase.auth.signUp({
|
||||||
|
email: 'user@example.com',
|
||||||
|
password: 'secure-password'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**OIDC (Optional - Admin Config):**
|
||||||
|
```yaml
|
||||||
|
# Admin sets via environment
|
||||||
|
SUPABASE_AUTH_GOOGLE_ENABLED=true
|
||||||
|
SUPABASE_AUTH_GOOGLE_CLIENT_ID=...
|
||||||
|
SUPABASE_AUTH_GOOGLE_SECRET=...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
**Row Level Security (RLS)**
|
||||||
|
|
||||||
|
**Inventory Items:**
|
||||||
|
```sql
|
||||||
|
-- Everyone can read (shared inventory)
|
||||||
|
CREATE POLICY "items_select" ON inventory_items
|
||||||
|
FOR SELECT USING (true);
|
||||||
|
|
||||||
|
-- Authenticated users can insert
|
||||||
|
CREATE POLICY "items_insert" ON inventory_items
|
||||||
|
FOR INSERT WITH CHECK (
|
||||||
|
auth.uid() IS NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Users can update any item (trust model)
|
||||||
|
CREATE POLICY "items_update" ON inventory_items
|
||||||
|
FOR UPDATE USING (
|
||||||
|
auth.uid() IS NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Users can delete any item
|
||||||
|
CREATE POLICY "items_delete" ON inventory_items
|
||||||
|
FOR DELETE USING (
|
||||||
|
auth.uid() IS NOT NULL
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Products (read-only for users):**
|
||||||
|
```sql
|
||||||
|
-- Everyone reads
|
||||||
|
CREATE POLICY "products_select" ON products
|
||||||
|
FOR SELECT USING (true);
|
||||||
|
|
||||||
|
-- Only service role writes (via Edge Functions)
|
||||||
|
-- (No user-facing policy needed)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tags (user-managed):**
|
||||||
|
```sql
|
||||||
|
-- Default tags: everyone reads
|
||||||
|
-- Custom tags: creator manages
|
||||||
|
CREATE POLICY "tags_select" ON tags
|
||||||
|
FOR SELECT USING (true);
|
||||||
|
|
||||||
|
CREATE POLICY "tags_insert" ON tags
|
||||||
|
FOR INSERT WITH CHECK (
|
||||||
|
auth.uid() IS NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE POLICY "tags_update" ON tags
|
||||||
|
FOR UPDATE USING (
|
||||||
|
created_by = auth.uid() OR created_by IS NULL
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Data Flow
|
||||||
|
|
||||||
|
### 1. Barcode Scan Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
User taps "Scan" button
|
||||||
|
↓
|
||||||
|
Camera permission requested (if first time)
|
||||||
|
↓
|
||||||
|
Camera opens (html5-qrcode)
|
||||||
|
↓
|
||||||
|
Barcode detected (e.g., "8000500310427")
|
||||||
|
↓
|
||||||
|
Frontend calls Edge Function
|
||||||
|
POST /functions/v1/product-lookup
|
||||||
|
{ barcode: "8000500310427" }
|
||||||
|
↓
|
||||||
|
Edge Function checks products table
|
||||||
|
├─ Found: return cached product
|
||||||
|
└─ Not found:
|
||||||
|
↓
|
||||||
|
Fetch from Open Food Facts API
|
||||||
|
https://world.openfoodfacts.org/api/v2/product/{barcode}
|
||||||
|
↓
|
||||||
|
Parse response (name, brand, image, etc.)
|
||||||
|
↓
|
||||||
|
Cache in products table
|
||||||
|
↓
|
||||||
|
Return product data
|
||||||
|
↓
|
||||||
|
Frontend displays product card
|
||||||
|
↓
|
||||||
|
User sets quantity, tags, expiry (optional)
|
||||||
|
↓
|
||||||
|
Frontend inserts into inventory_items table
|
||||||
|
↓
|
||||||
|
Realtime updates other devices (WebSocket)
|
||||||
|
↓
|
||||||
|
Done!
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Manual Add Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
User taps "Add Manually"
|
||||||
|
↓
|
||||||
|
Form: name, quantity, unit, tags, expiry
|
||||||
|
↓
|
||||||
|
Frontend validates (required: name, quantity)
|
||||||
|
↓
|
||||||
|
Insert into inventory_items
|
||||||
|
↓
|
||||||
|
Realtime broadcast to other devices
|
||||||
|
↓
|
||||||
|
Done!
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Unit Conversion Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
User selects unit (e.g., g)
|
||||||
|
↓
|
||||||
|
Item has quantity (e.g., 500g)
|
||||||
|
↓
|
||||||
|
User wants to view in kg
|
||||||
|
↓
|
||||||
|
Frontend calls conversion logic:
|
||||||
|
convert(500, g, kg)
|
||||||
|
↓
|
||||||
|
Find g.base_unit = kg
|
||||||
|
Find g.conversion_factor = 0.001
|
||||||
|
↓
|
||||||
|
result = 500 * 0.001 = 0.5 kg
|
||||||
|
↓
|
||||||
|
Display: "0.5 kg (500 g)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 API Design
|
||||||
|
|
||||||
|
### Supabase Client (Direct Access)
|
||||||
|
|
||||||
|
**Inventory CRUD:**
|
||||||
|
```typescript
|
||||||
|
// List items
|
||||||
|
const { data } = await supabase
|
||||||
|
.from('inventory_items')
|
||||||
|
.select(`
|
||||||
|
*,
|
||||||
|
product:products(*),
|
||||||
|
unit:units(*),
|
||||||
|
tags:item_tags(tag:tags(*))
|
||||||
|
`)
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
|
||||||
|
// Add item
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('inventory_items')
|
||||||
|
.insert({
|
||||||
|
product_id: '...',
|
||||||
|
quantity: 1,
|
||||||
|
unit_id: '...',
|
||||||
|
added_by: user.id
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update quantity (consume)
|
||||||
|
const { data } = await supabase
|
||||||
|
.from('inventory_items')
|
||||||
|
.update({ quantity: item.quantity - 1 })
|
||||||
|
.eq('id', itemId)
|
||||||
|
|
||||||
|
// Delete item
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('inventory_items')
|
||||||
|
.delete()
|
||||||
|
.eq('id', itemId)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Edge Functions
|
||||||
|
|
||||||
|
**Product Lookup:**
|
||||||
|
```typescript
|
||||||
|
// POST /functions/v1/product-lookup
|
||||||
|
// Body: { barcode: string }
|
||||||
|
|
||||||
|
interface Request {
|
||||||
|
barcode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Response {
|
||||||
|
product: Product | null
|
||||||
|
cached: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns cached product or fetches from Open Food Facts
|
||||||
|
```
|
||||||
|
|
||||||
|
**Batch Lookup (future):**
|
||||||
|
```typescript
|
||||||
|
// POST /functions/v1/products/batch
|
||||||
|
// Body: { barcodes: string[] }
|
||||||
|
|
||||||
|
// For offline sync (scan multiple, upload batch)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 PWA Architecture
|
||||||
|
|
||||||
|
### Service Worker Strategy
|
||||||
|
|
||||||
|
**Network-first with cache fallback:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// App shell: cache-first
|
||||||
|
self.addEventListener('fetch', (event) => {
|
||||||
|
if (event.request.url.includes('/app')) {
|
||||||
|
event.respondWith(
|
||||||
|
caches.match(event.request)
|
||||||
|
.then(cached => cached || fetch(event.request))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// API calls: network-first
|
||||||
|
if (event.request.url.includes('/api')) {
|
||||||
|
event.respondWith(
|
||||||
|
fetch(event.request)
|
||||||
|
.catch(() => caches.match(event.request))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Offline Support
|
||||||
|
|
||||||
|
**Offline Queue:**
|
||||||
|
```typescript
|
||||||
|
// Queue scans when offline
|
||||||
|
if (!navigator.onLine) {
|
||||||
|
await db.offlineQueue.add({
|
||||||
|
type: 'scan',
|
||||||
|
barcode: '123456',
|
||||||
|
timestamp: Date.now()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync when online
|
||||||
|
window.addEventListener('online', async () => {
|
||||||
|
const queue = await db.offlineQueue.getAll()
|
||||||
|
for (const item of queue) {
|
||||||
|
await processQueueItem(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Frontend Architecture
|
||||||
|
|
||||||
|
### Component Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
app/
|
||||||
|
├── components/
|
||||||
|
│ ├── inventory/
|
||||||
|
│ │ ├── ItemList.vue # Main inventory view
|
||||||
|
│ │ ├── ItemCard.vue # Single item card
|
||||||
|
│ │ ├── QuickActions.vue # Consume, restock buttons
|
||||||
|
│ │ └── FilterBar.vue # Search & filter
|
||||||
|
│ ├── scan/
|
||||||
|
│ │ ├── BarcodeScanner.vue # Camera + detection
|
||||||
|
│ │ ├── ScanButton.vue # Floating action button
|
||||||
|
│ │ └── ProductCard.vue # After scan confirmation
|
||||||
|
│ ├── tags/
|
||||||
|
│ │ ├── TagPicker.vue # Multi-select tags
|
||||||
|
│ │ ├── TagBadge.vue # Display tag
|
||||||
|
│ │ └── TagManager.vue # Settings: manage tags
|
||||||
|
│ ├── units/
|
||||||
|
│ │ ├── UnitSelector.vue # Dropdown with conversions
|
||||||
|
│ │ └── UnitDisplay.vue # Show quantity + unit
|
||||||
|
│ └── common/
|
||||||
|
│ ├── AppHeader.vue
|
||||||
|
│ ├── AppFooter.vue
|
||||||
|
│ └── LoadingState.vue
|
||||||
|
├── composables/
|
||||||
|
│ ├── useBarcode.ts # Camera, detection logic
|
||||||
|
│ ├── useInventory.ts # CRUD operations
|
||||||
|
│ ├── useUnits.ts # Conversions
|
||||||
|
│ ├── useTags.ts # Tag filtering
|
||||||
|
│ └── useOpenFoodFacts.ts # API wrapper
|
||||||
|
├── pages/
|
||||||
|
│ ├── index.vue # Inventory list
|
||||||
|
│ ├── scan.vue # Full-screen scanner
|
||||||
|
│ ├── item/[id].vue # Item detail/edit
|
||||||
|
│ └── settings.vue # Tags, units, profile
|
||||||
|
└── utils/
|
||||||
|
├── conversions.ts # Unit conversion math
|
||||||
|
├── barcode-formats.ts # Supported formats
|
||||||
|
└── offline-queue.ts # Offline sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
|
||||||
|
**No Vuex/Pinia needed (for MVP):**
|
||||||
|
- Supabase Realtime = reactive state
|
||||||
|
- Composables for shared logic
|
||||||
|
- Props/emits for component state
|
||||||
|
|
||||||
|
**If needed later:**
|
||||||
|
- Pinia for complex UI state (filters, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Development Workflow
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Terminal 1: Supabase
|
||||||
|
docker-compose -f docker/docker-compose.dev.yml up
|
||||||
|
|
||||||
|
# Terminal 2: Nuxt dev server
|
||||||
|
cd app
|
||||||
|
bun run dev
|
||||||
|
|
||||||
|
# Access:
|
||||||
|
# - App: http://localhost:3000
|
||||||
|
# - Supabase Studio: http://localhost:54323
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Migrations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create migration
|
||||||
|
supabase migration new add_tags_table
|
||||||
|
|
||||||
|
# Apply locally
|
||||||
|
supabase db reset
|
||||||
|
|
||||||
|
# Push to production
|
||||||
|
supabase db push
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Unit tests
|
||||||
|
bun test
|
||||||
|
|
||||||
|
# E2E tests (Playwright)
|
||||||
|
bun run test:e2e
|
||||||
|
|
||||||
|
# Type check
|
||||||
|
bun run typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Deployment
|
||||||
|
|
||||||
|
### Docker Compose (Production)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
supabase:
|
||||||
|
image: supabase/postgres:15
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
supabase-auth:
|
||||||
|
image: supabase/gotrue
|
||||||
|
environment:
|
||||||
|
SITE_URL: ${SITE_URL}
|
||||||
|
# OIDC config (optional)
|
||||||
|
|
||||||
|
pantry:
|
||||||
|
build: ./app
|
||||||
|
environment:
|
||||||
|
SUPABASE_URL: http://supabase:8000
|
||||||
|
SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY}
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Coolify Deployment
|
||||||
|
|
||||||
|
**Service Config:**
|
||||||
|
- Type: Docker Compose
|
||||||
|
- Repo: pantry-app/pantry
|
||||||
|
- Compose File: `docker/docker-compose.yml`
|
||||||
|
- Domain: `pantry.yourdomain.com`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Performance Considerations
|
||||||
|
|
||||||
|
### Image Optimization
|
||||||
|
- Open Food Facts images cached locally
|
||||||
|
- Nuxt Image module for responsive images
|
||||||
|
- WebP format with fallbacks
|
||||||
|
|
||||||
|
### Database Indexing
|
||||||
|
```sql
|
||||||
|
-- Fast barcode lookup
|
||||||
|
CREATE INDEX idx_products_barcode ON products(barcode);
|
||||||
|
|
||||||
|
-- Fast tag filtering
|
||||||
|
CREATE INDEX idx_item_tags_tag ON item_tags(tag_id);
|
||||||
|
|
||||||
|
-- Fast user queries
|
||||||
|
CREATE INDEX idx_items_added_by ON inventory_items(added_by);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Realtime Optimization
|
||||||
|
- Only subscribe to user's inventory (RLS filters)
|
||||||
|
- Debounce updates (don't broadcast every keystroke)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔮 Future Considerations
|
||||||
|
|
||||||
|
### Scalability
|
||||||
|
- **Database:** Postgres scales to millions of rows
|
||||||
|
- **Storage:** Images stored in Supabase Storage (S3-compatible)
|
||||||
|
- **Caching:** Redis for product lookup cache (if Open Food Facts is slow)
|
||||||
|
|
||||||
|
### Multi-Tenancy (v2.0+)
|
||||||
|
- Add `household_id` to all tables
|
||||||
|
- RLS policies filter by household
|
||||||
|
- Invite system for family members
|
||||||
|
|
||||||
|
### Native Mobile App (v2.0+)
|
||||||
|
- Capacitor wrapper around Nuxt
|
||||||
|
- Native barcode scanner (faster than PWA)
|
||||||
|
- Push notifications (expiry alerts)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 References
|
||||||
|
|
||||||
|
- [Supabase Docs](https://supabase.com/docs)
|
||||||
|
- [Nuxt 4 Docs](https://nuxt.com)
|
||||||
|
- [Open Food Facts API](https://wiki.openfoodfacts.org/API)
|
||||||
|
- [html5-qrcode](https://github.com/mebjas/html5-qrcode)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Next:** [Database Schema](./DATABASE.md)
|
||||||
323
docs/PROJECT_PLAN.md
Normal file
323
docs/PROJECT_PLAN.md
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
# Pantry - Project Plan
|
||||||
|
|
||||||
|
**Version:** 1.0
|
||||||
|
**Last Updated:** 2026-02-08
|
||||||
|
**Status:** Planning
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Vision & Goals
|
||||||
|
|
||||||
|
### Problem Statement
|
||||||
|
Kitchen inventory management is either:
|
||||||
|
- Too complex (Grocy) — intimidating for casual users
|
||||||
|
- Too simple (KitchenOwl) — missing critical features
|
||||||
|
|
||||||
|
**Gap:** No self-hosted solution that's both powerful AND easy enough for the whole family.
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
**Pantry** — A PWA that makes inventory management invisible:
|
||||||
|
- **Barcode scanning** reduces data entry to 3 taps
|
||||||
|
- **Tag-based** organization (no rigid categories)
|
||||||
|
- **Unit conversions** handled automatically
|
||||||
|
- **Multi-user** without household complexity
|
||||||
|
|
||||||
|
### Success Criteria
|
||||||
|
- [ ] Non-technical family member can add items solo
|
||||||
|
- [ ] Adding via barcode takes <10 seconds
|
||||||
|
- [ ] Zero onboarding docs needed (self-explanatory UI)
|
||||||
|
- [ ] Deployed and used in production by v0.2
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Core Principles
|
||||||
|
|
||||||
|
### 1. Family-Friendly First
|
||||||
|
**Test:** Can your parents use it without help?
|
||||||
|
|
||||||
|
- No technical jargon in UI
|
||||||
|
- Defaults for everything
|
||||||
|
- One primary action per screen
|
||||||
|
- Forgiving (easy undo)
|
||||||
|
|
||||||
|
### 2. Barcode-First Workflow
|
||||||
|
**Most common action should be fastest:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Scan → Confirm → Done (3 taps)
|
||||||
|
```
|
||||||
|
|
||||||
|
Everything else is secondary.
|
||||||
|
|
||||||
|
### 3. Progressive Disclosure
|
||||||
|
**Simple by default, powerful when needed:**
|
||||||
|
|
||||||
|
- Quick add: just quantity
|
||||||
|
- Advanced: tags, expiry, custom units
|
||||||
|
- Settings: hidden until needed
|
||||||
|
|
||||||
|
### 4. Tag Everything
|
||||||
|
**No rigid hierarchies:**
|
||||||
|
|
||||||
|
- Position tags: fridge, freezer, pantry
|
||||||
|
- Type tags: dairy, meat, vegan
|
||||||
|
- Custom tags: meal-prep, groceries-to-buy
|
||||||
|
- Multi-tag items (dairy + fridge)
|
||||||
|
|
||||||
|
### 5. Self-Hosted Only
|
||||||
|
**No SaaS plans, ever:**
|
||||||
|
|
||||||
|
- One-click Docker Compose deploy
|
||||||
|
- All data stays local
|
||||||
|
- No phone-home analytics
|
||||||
|
- MIT licensed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗺️ Roadmap
|
||||||
|
|
||||||
|
### MVP (v0.1) — 6 weeks
|
||||||
|
|
||||||
|
**Target:** Usable for single household
|
||||||
|
|
||||||
|
**Core features:**
|
||||||
|
- ✅ Barcode scanning (PWA camera)
|
||||||
|
- ✅ Auto-fill from Open Food Facts
|
||||||
|
- ✅ Tag-based organization
|
||||||
|
- ✅ Unit conversions (metric defaults)
|
||||||
|
- ✅ Multi-user (email/password auth)
|
||||||
|
- ✅ PWA (installable, offline-ready)
|
||||||
|
|
||||||
|
**Out of scope for v0.1:**
|
||||||
|
- OIDC providers (added later)
|
||||||
|
- Shopping lists
|
||||||
|
- Recipe integration
|
||||||
|
- Expiry notifications
|
||||||
|
- Mobile app (PWA only)
|
||||||
|
|
||||||
|
### v0.2 — Polish (4 weeks)
|
||||||
|
- Quick actions (consume, restock)
|
||||||
|
- Search & advanced filters
|
||||||
|
- Expiry date tracking
|
||||||
|
- Low-stock alerts
|
||||||
|
- OIDC auth (Authentik, Google)
|
||||||
|
|
||||||
|
### v0.3 — Expansion (TBD)
|
||||||
|
- Shopping list generation
|
||||||
|
- Recipe integration
|
||||||
|
- Meal planning
|
||||||
|
- Barcode printer labels
|
||||||
|
- Import/export
|
||||||
|
|
||||||
|
### v1.0 — Production-Ready (TBD)
|
||||||
|
- Full test coverage
|
||||||
|
- Performance optimization
|
||||||
|
- Mobile app (React Native/Capacitor)
|
||||||
|
- Admin dashboard
|
||||||
|
- Multi-language support
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 MVP Timeline (6 Weeks)
|
||||||
|
|
||||||
|
### Week 1: Foundation
|
||||||
|
**Goal:** Core infrastructure + auth
|
||||||
|
|
||||||
|
- [x] Create organization + monorepo
|
||||||
|
- [ ] Nuxt 4 scaffold (Tailwind + Nuxt UI)
|
||||||
|
- [ ] Supabase local setup (Docker)
|
||||||
|
- [ ] Database schema (initial)
|
||||||
|
- [ ] Email/password auth
|
||||||
|
- [ ] Deploy to development environment
|
||||||
|
|
||||||
|
**Deliverable:** Authenticated empty app
|
||||||
|
|
||||||
|
### Week 2: Core Inventory
|
||||||
|
**Goal:** Manual inventory management
|
||||||
|
|
||||||
|
- [ ] Database migrations (items, tags, units)
|
||||||
|
- [ ] Seed default tags & units
|
||||||
|
- [ ] Item CRUD UI
|
||||||
|
- [ ] Tag picker component
|
||||||
|
- [ ] Unit selector with conversions
|
||||||
|
- [ ] Inventory list view
|
||||||
|
|
||||||
|
**Deliverable:** Can add/edit items manually
|
||||||
|
|
||||||
|
### Week 3: Barcode Scanning
|
||||||
|
**Goal:** Scan → Add workflow
|
||||||
|
|
||||||
|
- [ ] PWA camera permissions
|
||||||
|
- [ ] Barcode scanner component (html5-qrcode)
|
||||||
|
- [ ] Open Food Facts API integration
|
||||||
|
- [ ] Product lookup & cache
|
||||||
|
- [ ] Scan UI flow
|
||||||
|
- [ ] Quick-add from scan
|
||||||
|
|
||||||
|
**Deliverable:** Barcode scanning works end-to-end
|
||||||
|
|
||||||
|
### Week 4: Tag System
|
||||||
|
**Goal:** Flexible organization
|
||||||
|
|
||||||
|
- [ ] Tag management UI
|
||||||
|
- [ ] Tag categories (position, type, custom)
|
||||||
|
- [ ] Multi-tag selection
|
||||||
|
- [ ] Filter by tags
|
||||||
|
- [ ] Tag-based search
|
||||||
|
- [ ] Tag statistics
|
||||||
|
|
||||||
|
**Deliverable:** Full tag system functional
|
||||||
|
|
||||||
|
### Week 5: PWA & Offline
|
||||||
|
**Goal:** Mobile experience
|
||||||
|
|
||||||
|
- [ ] PWA manifest
|
||||||
|
- [ ] Service worker (offline cache)
|
||||||
|
- [ ] Install prompt
|
||||||
|
- [ ] Mobile UI polish
|
||||||
|
- [ ] Touch gestures
|
||||||
|
- [ ] Loading states
|
||||||
|
|
||||||
|
**Deliverable:** Installable PWA with offline support
|
||||||
|
|
||||||
|
### Week 6: Deployment
|
||||||
|
**Goal:** Production-ready
|
||||||
|
|
||||||
|
- [ ] Docker Compose production config
|
||||||
|
- [ ] Environment variables
|
||||||
|
- [ ] Coolify deployment guide
|
||||||
|
- [ ] Backup strategy
|
||||||
|
- [ ] Monitoring setup
|
||||||
|
- [ ] User documentation
|
||||||
|
|
||||||
|
**Deliverable:** Running in production
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ Architecture Decisions
|
||||||
|
|
||||||
|
### Monorepo Structure
|
||||||
|
**Decision:** Single repo with `/app` and `/supabase`
|
||||||
|
|
||||||
|
**Rationale:**
|
||||||
|
- Simpler deployment (one repo clone)
|
||||||
|
- Easier version coordination
|
||||||
|
- Less overhead for solo/small team
|
||||||
|
|
||||||
|
### Supabase Direct Access
|
||||||
|
**Decision:** Frontend talks to Supabase directly (no custom backend)
|
||||||
|
|
||||||
|
**Rationale:**
|
||||||
|
- Supabase handles auth, RLS, realtime
|
||||||
|
- Edge functions for complex logic only
|
||||||
|
- Faster development
|
||||||
|
- Less infrastructure
|
||||||
|
|
||||||
|
### Tag-Based Organization
|
||||||
|
**Decision:** Unified tags (no separate locations/categories)
|
||||||
|
|
||||||
|
**Rationale:**
|
||||||
|
- More flexible (dairy + fridge + meal-prep)
|
||||||
|
- Easier to expand (just add tags)
|
||||||
|
- Simpler data model
|
||||||
|
- Better search/filter
|
||||||
|
|
||||||
|
### Unit Conversions
|
||||||
|
**Decision:** Base units + conversion factors
|
||||||
|
|
||||||
|
**Rationale:**
|
||||||
|
- Handles metric conversions (kg ↔ g)
|
||||||
|
- Supports custom units (can, jar)
|
||||||
|
- User-friendly (pick any unit, we convert)
|
||||||
|
- Grocy-style but simpler
|
||||||
|
|
||||||
|
### Multi-User Without Households
|
||||||
|
**Decision:** Shared inventory, no tenant isolation
|
||||||
|
|
||||||
|
**Rationale:**
|
||||||
|
- Simpler for single family use
|
||||||
|
- Trust model (users can edit anything)
|
||||||
|
- Avoid SaaS complexity
|
||||||
|
- Can add later if needed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testing Strategy
|
||||||
|
|
||||||
|
### Manual Testing (v0.1)
|
||||||
|
- Playwright E2E tests (critical paths)
|
||||||
|
- Mobile testing (real devices)
|
||||||
|
- Barcode scanning (various formats)
|
||||||
|
|
||||||
|
### Automated Testing (v0.2+)
|
||||||
|
- Unit tests (conversion logic, etc.)
|
||||||
|
- Component tests (Vue Test Utils)
|
||||||
|
- API tests (Supabase functions)
|
||||||
|
|
||||||
|
### User Testing (v0.3+)
|
||||||
|
- Family members (non-technical)
|
||||||
|
- Real inventory data
|
||||||
|
- Feedback loop
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Success Metrics
|
||||||
|
|
||||||
|
### MVP Launch (v0.1)
|
||||||
|
- [ ] 100+ items scanned successfully
|
||||||
|
- [ ] <10s average scan-to-add time
|
||||||
|
- [ ] Zero crashes in 1 week of use
|
||||||
|
- [ ] Positive feedback from 3+ users
|
||||||
|
|
||||||
|
### v1.0 Goals
|
||||||
|
- 10+ active deployments (GitHub issues as proxy)
|
||||||
|
- 100+ GitHub stars
|
||||||
|
- Positive user testimonials
|
||||||
|
- No major bugs >1 week old
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚧 Known Challenges
|
||||||
|
|
||||||
|
### 1. Barcode Quality
|
||||||
|
**Problem:** Low-light, damaged labels
|
||||||
|
**Mitigation:** Fallback to manual barcode entry
|
||||||
|
|
||||||
|
### 2. Open Food Facts Coverage
|
||||||
|
**Problem:** Not all products in database
|
||||||
|
**Mitigation:** Allow manual product creation
|
||||||
|
|
||||||
|
### 3. Unit Conversion Edge Cases
|
||||||
|
**Problem:** Custom units (e.g., "1 can")
|
||||||
|
**Mitigation:** Store base quantity, let user define conversions
|
||||||
|
|
||||||
|
### 4. PWA Install Friction
|
||||||
|
**Problem:** iOS Safari limitations
|
||||||
|
**Mitigation:** Clear install instructions, test on iOS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Review & Iteration
|
||||||
|
|
||||||
|
**Weekly Review:**
|
||||||
|
- What shipped?
|
||||||
|
- What blocked us?
|
||||||
|
- Adjust next week's scope
|
||||||
|
|
||||||
|
**Post-MVP:**
|
||||||
|
- User feedback sessions
|
||||||
|
- GitHub issues prioritization
|
||||||
|
- Feature roadmap votes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 References
|
||||||
|
|
||||||
|
- [Architecture](./ARCHITECTURE.md)
|
||||||
|
- [Database Schema](./DATABASE.md)
|
||||||
|
- [API Reference](./API.md)
|
||||||
|
- [Development Guide](./DEVELOPMENT.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Next Steps:** Review this plan, then build [ARCHITECTURE.md](./ARCHITECTURE.md)
|
||||||
Reference in New Issue
Block a user