Files
pantry/docs/development/workflow.md
Pantry Lead Agent b1ef7e43be
Some checks failed
Deploy to Coolify / Code Quality (pull_request) Has been cancelled
Deploy to Coolify / Run Tests (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Development (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Production (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Test (pull_request) Has been cancelled
Pull Request Checks / Validate PR (pull_request) Has been cancelled
docs: restructure documentation into organized folders
Organized docs into logical subdirectories:

**New Structure:**
- docs/
  - README.md (index with quick links)
  - PROJECT_PLAN.md (root level - main roadmap)
  - development/
    - getting-started.md (5-min quickstart)
    - local-setup.md (detailed Docker Compose guide)
    - workflow.md (daily development)
    - git-workflow.md (branching strategy)
  - architecture/
    - overview.md (tech stack, design)
    - database.md (schema, RLS, migrations)
    - api.md (endpoints, functions)
  - deployment/
    - production.md (Docker, Coolify)
    - ci-cd.md (automated pipelines)

**Cleaned Up:**
- Moved DEV_SETUP.md → docs/development/local-setup.md
- Removed outdated SETUP.md (referenced old Coolify setup)
- Replaced with getting-started.md (current Docker Compose flow)
- Updated README.md links to new structure

All paths tested, no broken links.
2026-02-09 13:45:57 +00:00

679 lines
13 KiB
Markdown

# Pantry - Development Guide
**Version:** 1.0
**Last Updated:** 2026-02-08
---
## 🚀 Quick Start
### Prerequisites
- **Node.js** 20+ (or Bun 1.0+)
- **Docker** & Docker Compose
- **Git**
- **Code editor** (VS Code recommended)
### Clone & Setup
```bash
# Clone repository
git clone https://gitea.jeanlucmakiola.de/pantry-app/pantry.git
cd pantry
# Install dependencies (using Bun)
bun install
# Copy environment template
cp .env.example .env
# Start Supabase (PostgreSQL + Auth + Realtime)
docker-compose -f docker/docker-compose.dev.yml up -d
# Run database migrations
cd supabase
supabase db reset # Creates schema + seeds data
# Start Nuxt dev server
cd ../app
bun run dev
# Access app at http://localhost:3000
```
---
## 📁 Project Structure
```
pantry/
├── app/ # Nuxt 4 frontend
│ ├── components/
│ │ ├── inventory/ # Inventory UI components
│ │ ├── scan/ # Barcode scanner components
│ │ ├── tags/ # Tag management
│ │ └── common/ # Shared UI components
│ ├── composables/ # Shared logic (Vue Composition API)
│ │ ├── useBarcode.ts
│ │ ├── useInventory.ts
│ │ └── useSupabase.ts
│ ├── pages/ # Route pages
│ │ ├── index.vue # Inventory list
│ │ ├── scan.vue # Barcode scanner
│ │ └── settings.vue # Settings
│ ├── utils/ # Pure functions
│ │ ├── conversions.ts # Unit conversion math
│ │ └── validation.ts
│ ├── nuxt.config.ts # Nuxt configuration
│ └── package.json
├── supabase/ # Database & backend
│ ├── migrations/ # SQL migrations
│ │ ├── 001_schema.sql
│ │ ├── 002_seed_units.sql
│ │ └── 003_rls.sql
│ ├── functions/ # Edge functions
│ │ └── product-lookup/
│ ├── seed/ # Seed data (JSON)
│ │ ├── units.json
│ │ └── tags.json
│ └── config.toml # Supabase config
├── docker/ # Docker configs
│ ├── docker-compose.dev.yml # Development
│ └── docker-compose.prod.yml # Production
├── docs/ # Documentation
├── scripts/ # Utility scripts
│ ├── seed-db.ts
│ └── export-schema.ts
├── .env.example
├── .gitignore
└── README.md
```
---
## 🛠️ Development Workflow
### 1. Create a Feature Branch
```bash
git checkout -b feature/barcode-scanner
```
### 2. Make Changes
**Add a new component:**
```bash
# Create component file
touch app/components/scan/BarcodeScanner.vue
# Use in a page
# app/pages/scan.vue
<template>
<BarcodeScanner @scan="handleScan" />
</template>
```
**Add a database migration:**
```bash
cd supabase
supabase migration new add_location_field
# Edit supabase/migrations/XXX_add_location_field.sql
ALTER TABLE inventory_items ADD COLUMN location TEXT;
# Apply locally
supabase db reset
```
### 3. Test Locally
```bash
# Run type check
bun run typecheck
# Run linter
bun run lint
# Run tests (when implemented)
bun run test
# E2E tests
bun run test:e2e
```
### 4. Commit & Push
```bash
git add .
git commit -m "feat: Add barcode scanner component"
git push origin feature/barcode-scanner
```
### 5. Create Pull Request
- Go to Gitea: https://gitea.jeanlucmakiola.de/pantry-app/pantry
- Create PR from your branch to `main`
- Request review
- Merge when approved
---
## 📝 Code Conventions
### Naming
**Files:**
- Components: `PascalCase.vue` (e.g., `BarcodeScanner.vue`)
- Composables: `camelCase.ts` (e.g., `useBarcode.ts`)
- Utils: `camelCase.ts` (e.g., `conversions.ts`)
- Pages: `kebab-case.vue` (e.g., `item-detail.vue`)
**Variables:**
- `camelCase` for variables, functions
- `PascalCase` for types, interfaces
- `SCREAMING_SNAKE_CASE` for constants
```typescript
// Good
const itemCount = 5
const fetchProducts = () => {}
interface Product { ... }
const MAX_ITEMS = 100
// Bad
const ItemCount = 5
const fetch_products = () => {}
interface product { ... }
const maxItems = 100 // for constants
```
### Vue Component Structure
```vue
<script setup lang="ts">
// 1. Imports
import { ref, computed } from 'vue'
import type { InventoryItem } from '~/types'
// 2. Props & Emits
interface Props {
item: InventoryItem
editable?: boolean
}
const props = withDefaults(defineProps<Props>(), {
editable: false
})
const emit = defineEmits<{
save: [item: InventoryItem]
cancel: []
}>()
// 3. Composables
const { convert } = useUnits()
const supabase = useSupabaseClient()
// 4. State
const quantity = ref(props.item.quantity)
const isEditing = ref(false)
// 5. Computed
const displayQuantity = computed(() =>
`${quantity.value} ${props.item.unit.abbreviation}`
)
// 6. Methods
const handleSave = async () => {
const { error } = await supabase
.from('inventory_items')
.update({ quantity: quantity.value })
.eq('id', props.item.id)
if (!error) emit('save', { ...props.item, quantity: quantity.value })
}
// 7. Lifecycle (if needed)
onMounted(() => {
console.log('Component mounted')
})
</script>
<template>
<div class="item-card">
<h3>{{ item.name }}</h3>
<p>{{ displayQuantity }}</p>
<button v-if="editable" @click="handleSave">
Save
</button>
</div>
</template>
<style scoped>
.item-card {
/* Prefer Tailwind classes in template */
/* Use scoped styles only for complex/unique styles */
}
</style>
```
### TypeScript
**Prefer interfaces over types:**
```typescript
// Good
interface InventoryItem {
id: string
name: string
quantity: number
}
// Only use type for unions, intersections
type Status = 'active' | 'expired'
```
**Use strict typing:**
```typescript
// Good
const fetchItem = async (id: string): Promise<InventoryItem | null> => {
const { data } = await supabase
.from('inventory_items')
.select('*')
.eq('id', id)
.single()
return data
}
// Bad (implicit any)
const fetchItem = async (id) => {
const { data } = await supabase...
return data
}
```
### Composables
**Naming:** Always start with `use`
**Structure:**
```typescript
// composables/useInventory.ts
export function useInventory() {
const supabase = useSupabaseClient<Database>()
const items = ref<InventoryItem[]>([])
const loading = ref(false)
const fetchItems = async () => {
loading.value = true
const { data, error } = await supabase
.from('inventory_items')
.select('*')
if (data) items.value = data
loading.value = false
}
const addItem = async (item: NewInventoryItem) => {
const { data, error } = await supabase
.from('inventory_items')
.insert(item)
.select()
.single()
if (data) items.value.push(data)
return { data, error }
}
// Return reactive state + methods
return {
items: readonly(items),
loading: readonly(loading),
fetchItems,
addItem
}
}
```
### Database Migrations
**Naming:**
```
001_initial_schema.sql
002_seed_defaults.sql
003_add_location_field.sql
```
**Structure:**
```sql
-- Migration: Add location field to inventory items
-- Created: 2026-02-08
BEGIN;
ALTER TABLE inventory_items
ADD COLUMN location TEXT;
-- Update existing items (optional)
UPDATE inventory_items
SET location = 'Pantry'
WHERE location IS NULL;
COMMIT;
```
**Rollback (optional):**
```sql
-- To rollback:
-- ALTER TABLE inventory_items DROP COLUMN location;
```
---
## 🧪 Testing
### Unit Tests (Vitest)
```typescript
// app/utils/conversions.test.ts
import { describe, it, expect } from 'vitest'
import { convertUnit } from './conversions'
describe('convertUnit', () => {
it('converts grams to kilograms', () => {
const result = convertUnit(500, {
conversion_factor: 0.001,
base_unit_id: 'kg'
}, {
conversion_factor: 1,
base_unit_id: null
})
expect(result).toBe(0.5)
})
it('throws error for incompatible units', () => {
expect(() => {
convertUnit(1,
{ unit_type: 'weight', conversion_factor: 1 },
{ unit_type: 'volume', conversion_factor: 1 }
)
}).toThrow('Cannot convert')
})
})
```
**Run tests:**
```bash
bun test
bun test --watch # Watch mode
```
### E2E Tests (Playwright)
```typescript
// tests/e2e/inventory.spec.ts
import { test, expect } from '@playwright/test'
test('can add item to inventory', async ({ page }) => {
await page.goto('/')
// Login
await page.fill('[name=email]', 'test@example.com')
await page.fill('[name=password]', 'password')
await page.click('button[type=submit]')
// Add item
await page.click('[data-testid=add-item]')
await page.fill('[name=name]', 'Test Item')
await page.fill('[name=quantity]', '2')
await page.click('button:has-text("Add")')
// Verify
await expect(page.locator('text=Test Item')).toBeVisible()
})
```
**Run E2E:**
```bash
bun run test:e2e
bun run test:e2e --ui # Interactive mode
```
---
## 🔧 Supabase Local Development
### Start Supabase
```bash
cd supabase
supabase start
```
**Outputs:**
```
API URL: http://localhost:54321
Studio URL: http://localhost:54323
Anon key: eyJhb...
Service key: eyJhb...
```
**Access Supabase Studio:**
- Open http://localhost:54323
- View tables, run queries, manage auth
### Apply Migrations
```bash
# Reset DB (drops all data, reapplies migrations)
supabase db reset
# Create new migration
supabase migration new add_feature
# Apply migrations (non-destructive)
supabase migration up
```
### Generate Types
```bash
# Generate TypeScript types from database schema
supabase gen types typescript --local > app/types/supabase.ts
```
**Usage:**
```typescript
import type { Database } from '~/types/supabase'
const supabase = useSupabaseClient<Database>()
```
---
## 🌐 Environment Variables
### `.env` (Development)
```bash
# Supabase (from `supabase start`)
SUPABASE_URL=http://localhost:54321
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# App
PUBLIC_APP_URL=http://localhost:3000
# Open Food Facts (optional, no key needed)
OPENFOODFACTS_API_URL=https://world.openfoodfacts.org
```
### `.env.production`
```bash
# Supabase (production)
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# App
PUBLIC_APP_URL=https://pantry.yourdomain.com
```
---
## 🐛 Debugging
### Nuxt DevTools
**Enable:**
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
devtools: { enabled: true }
})
```
**Access:** Press `Shift + Alt + D` or visit http://localhost:3000/__devtools__
### Vue DevTools
Install browser extension:
- Chrome: [Vue DevTools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- Firefox: [Vue DevTools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
### Supabase Logs
```bash
# View realtime logs
supabase logs --tail
# Filter by service
supabase logs --service postgres
supabase logs --service auth
```
### Database Queries
**Supabase Studio:**
- Open http://localhost:54323
- Go to "SQL Editor"
- Run queries directly
**CLI:**
```bash
# psql into local database
supabase db shell
# Run query
SELECT * FROM inventory_items LIMIT 5;
```
---
## 📦 Build & Preview
### Build for Production
```bash
cd app
bun run build
```
**Output:** `.output/` directory (ready for deployment)
### Preview Production Build
```bash
bun run preview
```
**Access:** http://localhost:3000
---
## 🔄 Git Workflow
### Branch Naming
```
feature/barcode-scanner
fix/tag-duplication-bug
chore/update-dependencies
docs/api-reference
```
### Commit Messages (Conventional Commits)
```bash
# Format: <type>(<scope>): <subject>
feat(scan): add barcode detection
fix(inventory): prevent duplicate items
chore(deps): update Nuxt to 4.1
docs(api): add product lookup endpoint
refactor(tags): simplify tag picker logic
test(units): add conversion edge cases
style(ui): apply Tailwind spacing
```
**Types:**
- `feat`: New feature
- `fix`: Bug fix
- `chore`: Maintenance (deps, config)
- `docs`: Documentation
- `refactor`: Code restructuring
- `test`: Adding/updating tests
- `style`: Code style (formatting, no logic change)
---
## 🚨 Common Issues
### Supabase won't start
```bash
# Check Docker
docker ps
# Restart Supabase
supabase stop
supabase start
```
### Types out of sync
```bash
# Regenerate types after schema change
supabase gen types typescript --local > app/types/supabase.ts
```
### Port already in use
```bash
# Nuxt (3000)
lsof -ti:3000 | xargs kill
# Supabase (54321)
supabase stop
```
---
## 📚 Resources
**Docs:**
- [Nuxt 4](https://nuxt.com)
- [Supabase](https://supabase.com/docs)
- [Vue 3](https://vuejs.org)
- [Tailwind CSS](https://tailwindcss.com)
**Community:**
- [Pantry Discussions](https://gitea.jeanlucmakiola.de/pantry-app/pantry/issues)
- [Nuxt Discord](https://discord.com/invite/ps2h6QT)
- [Supabase Discord](https://discord.supabase.com)
---
**Next:** [Deployment Guide](./DEPLOYMENT.md)