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

13 KiB

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

# 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

git checkout -b feature/barcode-scanner

2. Make Changes

Add a new component:

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

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

# 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

git add .
git commit -m "feat: Add barcode scanner component"
git push origin feature/barcode-scanner

5. Create Pull Request


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

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

// Good
interface InventoryItem {
  id: string
  name: string
  quantity: number
}

// Only use type for unions, intersections
type Status = 'active' | 'expired'

Use strict typing:

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

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

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

-- To rollback:
-- ALTER TABLE inventory_items DROP COLUMN location;

🧪 Testing

Unit Tests (Vitest)

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

bun test
bun test --watch  # Watch mode

E2E Tests (Playwright)

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

bun run test:e2e
bun run test:e2e --ui  # Interactive mode

🔧 Supabase Local Development

Start Supabase

cd supabase
supabase start

Outputs:

API URL: http://localhost:54321
Studio URL: http://localhost:54323
Anon key: eyJhb...
Service key: eyJhb...

Access Supabase Studio:

Apply Migrations

# 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

# Generate TypeScript types from database schema
supabase gen types typescript --local > app/types/supabase.ts

Usage:

import type { Database } from '~/types/supabase'

const supabase = useSupabaseClient<Database>()

🌐 Environment Variables

.env (Development)

# 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

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

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

Supabase Logs

# View realtime logs
supabase logs --tail

# Filter by service
supabase logs --service postgres
supabase logs --service auth

Database Queries

Supabase Studio:

CLI:

# psql into local database
supabase db shell

# Run query
SELECT * FROM inventory_items LIMIT 5;

📦 Build & Preview

Build for Production

cd app
bun run build

Output: .output/ directory (ready for deployment)

Preview Production Build

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)

# 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

# Check Docker
docker ps

# Restart Supabase
supabase stop
supabase start

Types out of sync

# Regenerate types after schema change
supabase gen types typescript --local > app/types/supabase.ts

Port already in use

# Nuxt (3000)
lsof -ti:3000 | xargs kill

# Supabase (54321)
supabase stop

📚 Resources

Docs:

Community:


Next: Deployment Guide