# 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
```
**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
{{ item.name }}
{{ displayQuantity }}
```
### 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 => {
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()
const items = ref([])
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()
```
---
## ๐ 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: ():
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)