TeamIDE-methodology/templates/claude.md

22 KiB

Project Guidelines

Protecting This File

IMPORTANT: This file (claude.md) is gitignored and can be lost during branch operations!

Git doesn't protect untracked/ignored files during git checkout or branch switches. Before major git operations:

cp claude.md claude.md.backup

Coding Best Practices (CRITICAL)

Read coding-best-practices.md before writing any code. It contains personal coding conventions that apply to every project — naming rules, file/function size limits, folder nesting patterns, persistence rules, and language-specific structure guides. Those rules override defaults and must be followed.

File Header Check Rule

Every time you open a file to work on it, check the file header first. Look for outstanding @todo items, unresolved @issues, or outdated @status notes. If any are completed or resolved, clean them up before doing other work. If a TODO is relevant to the current task, address it. This ensures nothing falls through the cracks.

Project Structure (CRITICAL)

This project follows a standardized structure for future modularity. The long-term goal is to enable drag-and-drop of components between projects. This structure must be followed.

ProjectRoot/              # git root
├── claude.md             # (gitignored) AI assistant guidelines
├── coding-best-practices.md # Coding conventions (read before Phase 1)
├── build_spec.md         # (gitignored) Full project specification
├── build_plan.md         # (gitignored) Phased task checklist
├── .gitignore
├── docs/                 # Public-facing documentation (parsed to HTML for website)
│   ├── README.md         # Docs index with table of contents
│   ├── getting-started.md
│   ├── architecture.md
│   ├── development.md
│   ├── deployment.md
│   └── feature-*.md      # Feature docs (added per phase)
├── InternalDocs/         # Internal development documentation (not public)
│   └── ...               # Design decisions, technical debt, internal API docs
└── app/                  # ALL project code lives here
    ├── src/
    │   ├── assets/       # Static assets
    │   ├── boot/         # Quasar boot files (nhost init, etc.)
    │   ├── components/   # Vue components
    │   ├── composables/  # Vue composables
    │   ├── layouts/      # Page layouts
    │   ├── pages/        # Route pages
    │   ├── router/       # Vue Router config
    │   ├── stores/       # Pinia stores
    │   ├── services/     # API services
    │   └── types/        # TypeScript types
    ├── nhost/            # Nhost configuration
    │   ├── functions/    # Serverless functions
    │   ├── migrations/   # Database migrations
    │   └── metadata/     # Hasura metadata
    ├── public/           # Public static files
    ├── docker-compose.yml
    ├── .env              # Dev environment (gitignored)
    └── .env.prod         # Prod environment (gitignored)

Terminology

  • "build spec" or "spec" refers to build_spec.md - the full project description
  • "build plan" or "plan" refers to build_plan.md - the phased task checklist

Onboarding Questions (Ask at Project Start)

At the start of every new project conversation, gather these details before writing any code:

  1. Brand colors & fonts - Primary, secondary, accent colors. Typography preferences. Any existing brand guidelines or color palette?
  2. App personality & tone - Formal or casual? Dark or light default theme? Dense or spacious layout? Any design inspiration or reference apps?
  3. Target platform - Desktop-first, mobile-first, or responsive? Specific OS targets (macOS, Windows, Linux)? Minimum screen sizes?
  4. Auth requirements - Login method (email/password, OAuth, magic link)? User roles and permissions? Public or private app?

Don't assume defaults for any of these. Ask, get answers, then document them in the Project-Specific Configuration section at the bottom of this file.

Tech Stack

  • Frontend: Quasar Framework (Vue 3) - SPA mode
  • Desktop (optional): Tauri (Rust) - Cross-platform builds (macOS, Windows, Linux)
  • Backend: Nhost (self-hosted via Docker)
    • PostgreSQL - Database
    • Hasura - GraphQL API
    • Auth - Authentication service
    • Storage - File storage
    • Functions - Serverless functions (Node.js)
  • Containerization: Docker / Docker Compose

Tauri Desktop Apps

When building desktop apps with Tauri:

  • Add src-tauri/ folder inside app/
  • Use the build-and-run approach (no hot reload) for consistent dev/prod builds
  • Separate bundle IDs for dev and prod (can run both simultaneously)

Testing Changes (CRITICAL)

After every code editing session, you MUST rebuild the app so the user can test your changes.

cd app
./build.sh -dev -open

Why This Matters

  • This uses a build-and-run approach, not hot reload
  • Your code changes are NOT visible until you rebuild
  • Always rebuild before asking the user to test
  • If the user reports issues, check the logs first: ./build.sh logs

Build Script

Always use build.sh for building the app. This ensures consistent builds and logging for collaborative debugging.

cd app

./build.sh -dev -open    # Build dev and open app
./build.sh -prod         # Build prod to build/prod/
./build.sh -install      # Build prod and open Finder for drag-to-Applications
./build.sh logs          # View latest console output (last 100 lines)
./build.sh clean         # Clean build artifacts (preserves logs)
./build.sh clean-logs    # Remove all logs

Commands

Command Description
-dev Development build. Bundle ID: com.[app].dev
-prod Production build. Builds to build/prod/.
-install Build prod and open Finder for drag-to-Applications.
-open Open the app after building (use with -dev or -prod)
logs Show last 100 lines of console output
clean Remove build artifacts (preserves logs)
clean-logs Remove all log files

Why Build-and-Run (No Hot Reload)

This project uses a build-and-run approach instead of hot reload:

Aspect Hot Reload Build-and-Run
Speed Instant updates Full rebuild required
Dev/Prod parity Different environments Identical to production
Debugging Dev-only issues possible What you test = what ships
Logging Console only Full logs to files

The tradeoff is slower iteration, but you always test exactly what will ship.

What the Build Does

  1. Build with Tauri - npx tauri build does all of the following:
    • Build frontend (npm run build → dist/spa/)
    • Build Rust binary with embedded frontend
    • Create .app bundle in src-tauri/target/release/bundle/macos/
  2. Copy to build folder - Copies bundle to build/dev/ or build/prod/
  3. Update Info.plist - Sets correct bundle ID for dev/prod
  4. Install (prod only) - Copies to ~/Applications

Important: Using npx tauri build ensures the frontend is properly embedded in the binary. Manual cargo build --release does NOT embed the frontend.

Build Output

Builds are organized in separate folders:

app/build/
├── dev/
│   └── AppName.app      # Dev build (bundle ID: .dev)
├── prod/
│   └── AppName.app      # Prod build
├── logs/
│   ├── build_dev_*.log  # Build output logs
│   └── build_latest.log # Symlink to latest
└── console/
    ├── console_*.log    # App runtime console output
    └── console_latest.log # Symlink to latest

Dev and prod have different bundle IDs, so both can run simultaneously.

Logging

All build output and app console output is logged:

  • Build logs: build/logs/build_latest.log - compilation output, errors
  • Console logs: build/console/console_latest.log - runtime console.log(), errors

When debugging, run ./build.sh logs to see the latest console output.

Console Logging Setup (Tauri)

For frontend console.log() to appear in the logs, you need:

  1. Boot file - Copy templates/scripts/console-logger.ts to app/src/boot/
  2. Register boot - Add 'console-logger' to boot array in quasar.config.js
  3. Rust command - Add log_to_stdout command to src-tauri/src/commands.rs
  4. Cargo dep - Add chrono = "0.4" to Cargo.toml

The console-logger.ts template includes the Rust code you need to add.

How it works:

console.log() → console-logger.ts → invoke('log_to_stdout') → Rust println!() → stdout → build.sh logs

When to Clean

Run ./build.sh clean when:

  • Switching between major dependency versions
  • Build errors reference stale/cached files
  • Rust compilation issues after Tauri config changes
  • Before creating a fresh production build

Node.js Environment

Node.js version: Always match Debian stable (currently Node.js 18.x for Debian 12 Bookworm)

Check current Debian stable Node version: https://packages.debian.org/stable/nodejs

Package manager: Use npm. Commit lock files for reproducible builds.

Rules:

  • Always commit package-lock.json - required for deterministic installs
  • Never ignore lock files (package-lock.json, yarn.lock, pnpm-lock.yaml)
  • Use npm install to install dependencies
  • Use npm run <script> to run package scripts

Communication Rules

Always explain before acting. Before running any command, SSH, database query, or making changes:

  • Give a short, concise explanation of WHAT you're about to do and WHY
  • Wait for approval on anything that modifies production data or server state
  • Don't chain multiple server commands without explaining each step

Database Rules (CRITICAL)

NEVER make direct database changes via Hasura MCP or Hasura Console.

All database changes MUST be done through migrations so they:

  1. Are tracked in version control
  2. Persist across restarts (nhost down / nhost up)
  3. Can be reproduced in production

Hasura MCP Usage

  • READ-ONLY: Use Hasura MCP only to inspect schema, check what tables exist, verify tracking status
  • NO MUTATIONS: Never use it to create tables, add columns, track tables, or modify permissions
  • MIGRATIONS ONLY: All schema changes go in app/nhost/migrations/default/
  • METADATA ONLY: All Hasura config (tracking, permissions, relationships) go in app/nhost/metadata/

How to Make Database Changes

  1. Write SQL migration in app/nhost/migrations/default/<N>_<name>/up.sql (N = next version number)
  2. Write rollback in app/nhost/migrations/default/<N>_<name>/down.sql
  3. Add Hasura metadata YAML in app/nhost/metadata/databases/default/tables/
  4. Restart dev environment to apply migrations
  5. Commit and push

Migration folder naming:

app/nhost/migrations/default/
├── 1_init_schema/
├── 2_add_users/
├── 3_add_settings/
└── ...next version number...

Hasura Role Permissions (CRITICAL)

NEVER define permissions for the admin role in Hasura metadata.

Hasura reserves admin as a special role with full unrestricted access. Defining explicit permissions for admin causes metadata errors:

"cannot define permission for admin role"

Correct approach:

  • Define permissions only for user, public, and custom roles
  • Users with X-Hasura-Role: admin automatically bypass all permission checks

Example - WRONG:

select_permissions:
  - role: admin        # DON'T DO THIS
    permission:
      columns: [id, name]
      filter: {}

Example - CORRECT:

select_permissions:
  - role: user         # Only define for non-admin roles
    permission:
      columns: [id, name]
      filter: { created_by: { _eq: X-Hasura-User-Id } }

Development Approach

Integrate-as-you-go - Connect real backend services as you build. Don't build elaborate mock systems. The Docker infrastructure provides:

  • Database & Hasura - Real tables and GraphQL
  • Nhost Auth - Real authentication
  • Real data from the start

Persistence Rules (CRITICAL)

All persistence requires an explicit user action. No exceptions.

Never auto-save. Never sync state in the background. Never write to the database on a timer or watcher. Every single write to a database, file, or storage must be traceable to a deliberate user action.

The Rule

Data is only persisted when the user deliberately triggers it — clicking "Save", submitting a form, confirming a dialog, toggling a setting, etc.

WRONG — Never Do This

  • Auto-saving form fields as the user types
  • Syncing component state to the database on mount/unmount
  • Watchers that write to storage when reactive data changes
  • Debounced save on input change
  • Persisting UI state automatically (sidebar position, tab selection, scroll position)
  • Writing to the database during navigation or route changes
  • Background sync intervals or polling that writes data

RIGHT — Always Do This

  • User clicks "Save" → write to database
  • User submits a form → persist the data
  • User explicitly toggles a setting → save that setting
  • User clicks "Delete" → confirm, then remove from database
  • User drags an item to reorder → save on drop (explicit action)

Why This Matters

Auto-save creates unpredictable behavior, race conditions, and persists data the user didn't intend to save. Every write should be traceable to a conscious user decision. If you can't point to the button or action the user took to trigger the save, it shouldn't be saving.

Implementation Pattern

User Action → Service Call → Database Write → Confirm Success to User
  1. User triggers action (click, submit, confirm)
  2. Call the appropriate service/API
  3. Write to database
  4. Show success feedback (Quasar Notify or UI update)
  5. If it fails, show an error — don't silently retry

Code Quality

TypeScript

  • Use TypeScript strict mode
  • Define interfaces/types for all data structures in src/types/
  • No any types unless absolutely unavoidable — use unknown and narrow instead
  • Type all function parameters and return values

Vue / Quasar Conventions

  • Use Composition API with <script setup lang="ts"> for all components
  • One component per file, named in PascalCase (e.g., UserProfile.vue)
  • Composables go in src/composables/, prefixed with use (e.g., useAuth.ts)
  • Keep components focused — if it's doing too much, split it
  • Props down, events up — don't mutate props, emit events to parent

Naming

  • Files: PascalCase for components (UserCard.vue), camelCase for everything else (userService.ts)
  • Variables/functions: camelCase
  • Constants: UPPER_SNAKE_CASE for true constants, camelCase for derived/computed values
  • Types/Interfaces: PascalCase, no I prefix (use User not IUser)
  • CSS classes: kebab-case

General

  • No magic numbers or strings — use named constants
  • Keep functions short and single-purpose
  • Colocate related code — don't scatter logic across files unnecessarily
  • Delete dead code, don't comment it out

Error Handling

User-Facing Errors

  • Always show human-readable messages — never expose raw error objects, stack traces, or technical details to the user
  • Use Quasar's Notify plugin for transient feedback (success, warning, error)
  • Use inline error messages for form validation

Technical Errors

  • Log technical details to console for debugging (console.error with context)
  • Include the operation that failed and relevant IDs in log messages
  • Never swallow errors silently — if you catch it, either handle it or log it

Pattern

try {
  const result = await userService.updateProfile(data)
  Notify.create({ type: 'positive', message: 'Profile updated' })
} catch (error) {
  console.error('Failed to update profile:', error)
  Notify.create({ type: 'negative', message: 'Could not update profile. Try again.' })
}

Where to Catch

  • Catch at service boundaries (API calls, database operations), not deep in utility functions
  • Let errors propagate up to where they can be meaningfully handled
  • Don't wrap every line in try/catch — catch at the level where you can show the user feedback

Documentation

docs/ vs InternalDocs/

This project has two documentation directories with different audiences:

Directory Audience Purpose Parsed for website?
docs/ Public User-facing app documentation — getting started, features, architecture Yes
InternalDocs/ Developers Internal dev context — design decisions, technical debt, internal API contracts, Claude context No

docs/ contains ONLY what the public needs. It gets parsed into HTML and served on the app website. No internal implementation details, no dev-only context.

InternalDocs/ contains everything internal — design rationale, known technical debt, internal API documentation, integration notes, debugging guides, anything that helps the development team but doesn't belong in public docs.

Both directories are committed to git.

Phase Documentation

Every phase ends with documentation. After completing a feature or phase:

  1. Create feature docs - Copy docs/TEMPLATE-feature.md to docs/feature-[name].md
  2. Update the index - Add links to docs/README.md table of contents
  3. Update core docs - Keep architecture.md, development.md current
  4. Update internal docs - Add design decisions, technical debt notes, or internal API docs to InternalDocs/ as needed

docs/ Structure

File Purpose
docs/README.md Index with table of contents
docs/getting-started.md Installation & setup
docs/architecture.md Tech stack & design decisions
docs/development.md Development workflow
docs/deployment.md Build & deployment
docs/feature-*.md Feature-specific documentation

Naming Convention

  • feature-[name].md - Feature docs (e.g., feature-authentication.md)
  • component-[name].md - Component docs
  • integration-[name].md - Integration docs
  • guide-[topic].md - How-to guides

Commit Rules

  • Never mention Claude, AI, or "Generated with Claude Code" in commits
  • All commits are authored by the developer - no co-author tags
  • Write commit messages in first person as the developer
  • Keep commit messages concise and descriptive of the changes

Branch Strategy

  • main - Production branch
  • test - Staging/testing branch

Workflow:

  1. Develop and test locally
  2. Push to test branch for staging
  3. Verify on staging
  4. Merge test → main for production

Environment Variables

# Nhost
NHOST_ADMIN_SECRET=

# PostgreSQL
POSTGRES_PASSWORD=

# JWT
JWT_SECRET=

# App
APP_URL=

Project-Specific Configuration

Project Name

[PROJECT_NAME]

Description

[Brief description from build_spec.md]

Database Schema

[Tables and relationships]

URLs

Service Local Production
App http://localhost:[dynamic]
GraphQL http://localhost:8080

Note: For Tauri desktop apps, use dynamic port allocation. The dev server will find the first available port.

MCP Servers

[Configure in .mcp.json if needed]

Servers

[Production server details if applicable]

Dockerized Frontend for Production

When deploying a fully dockerized project, add a frontend service to docker-compose:

1. Create app/Dockerfile.frontend

# Build stage
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Production stage
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

2. Create app/nginx.conf

server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

3. Add frontend service to docker-compose.yml

All Traefik hostnames use hardcoded localhost values. TeamIDE's proxy rewrites the Host header to localhost, so this works both locally and in production.

frontend:
  build:
    context: .
    dockerfile: Dockerfile.frontend
  depends_on:
    graphql:
      condition: service_healthy
    auth:
      condition: service_healthy
  labels:
    traefik.enable: "true"
    traefik.http.routers.frontend.rule: Host(`localhost`)
    traefik.http.routers.frontend.entrypoints: web
    traefik.http.services.frontend.loadbalancer.server.port: "80"
  extra_hosts:
    - host.docker.internal:host-gateway
    - localhost:host-gateway
    - auth.localhost:host-gateway
    - hasura.localhost:host-gateway
    - storage.localhost:host-gateway
  restart: always

4. Traefik hostname pattern

All services use hardcoded localhost hostnames:

  • Frontend: Host(\localhost`)`
  • Auth: Host(\auth.localhost`)`
  • Hasura: Host(\hasura.localhost`)`
  • Storage: Host(\storage.localhost`)`
  • Functions: Host(\functions.localhost`)`

TeamIDE Hosting

This project is deployed via TeamIDE.

  • Workspace ID: your-workspace-id-here
  • Use teamide_deploy to deploy changes
  • Use teamide_set_env_vars to configure environment variables
  • Use teamide_get_logs to check build status

To get your workspace ID, run:

teamide workspace list