Files
netfelix-audio-fix/AGENTS.md
Felix Förtsch 5ac44b7551 restructure to react spa + hono api, fix missing server/ and lib/
rewrite from monolithic hono jsx to react 19 spa with tanstack router
+ hono json api backend. add scan, review, execute, nodes, and setup
pages. multi-stage dockerfile (node for vite build, bun for runtime).

previously, server/ and src/shared/lib/ were silently excluded by
global gitignore patterns (/server/ from emacs, lib/ from python).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 22:57:40 +01:00

10 KiB
Raw Blame History

AGENTS.md — PWA Stack Reference

This file is the authoritative guide for AI agents (Claude Code, Cursor, Copilot, etc.) working on any app in this repository. Always follow this stack. Never introduce dependencies outside of it without explicit approval.


Stack Overview

Layer Tool Version
Runtime Bun latest
Build Vite latest
UI Framework React 19+
Component Library shadcn/ui + Tailwind CSS v4 latest
Routing TanStack Router latest
State Management Zustand latest
Local Database PGlite (PostgreSQL in WASM) latest
Forms TanStack Form + Zod latest
Animations CSS View Transitions API native
PWA vite-plugin-pwa + Workbox latest
Testing (unit) Vitest + Testing Library latest
Testing (e2e) Playwright latest
Code Quality Biome + lint-staged + simple-git-hooks latest

Project Structure

src/
├── features/              # One folder per domain feature
│   ├── auth/
│   │   ├── components/    # UI components for this feature
│   │   ├── hooks/         # Custom hooks
│   │   ├── store.ts       # Zustand store (if needed)
│   │   ├── schema.ts      # Zod schemas for this feature
│   │   └── index.ts       # Public API of the feature
│   └── [feature-name]/
│       └── ...
├── shared/
│   ├── components/        # Generic reusable UI (Button, Modal, etc.)
│   ├── hooks/             # Generic hooks (useMediaQuery, etc.)
│   ├── db/
│   │   ├── client.ts      # PGlite instance (singleton)
│   │   ├── migrations/    # SQL migration files (001_init.sql, etc.)
│   │   └── schema.ts      # Zod schemas mirroring DB tables
│   └── lib/               # Pure utility functions
├── routes/                # TanStack Router route files
│   ├── __root.tsx         # Root layout
│   ├── index.tsx          # / route
│   └── [feature]/
│       └── index.tsx
├── app.tsx                # App entry, router provider
└── main.tsx               # Bun/Vite entry point
public/
├── manifest.webmanifest   # PWA manifest
└── icons/                 # PWA icons (all sizes)

Rules by Layer

React & TypeScript

  • Use React 19 features: use(), Suspense, transitions.
  • All files are .tsx or .ts. No .js or .jsx.
  • Prefer named exports over default exports (except route files — TanStack Router requires default exports).
  • No any. Use unknown and narrow with Zod when type is uncertain.
  • Use satisfies operator for type-safe object literals.

Styling (Tailwind + shadcn/ui)

  • Never write custom CSS files. Use Tailwind utility classes exclusively.
  • Use cn() (from src/shared/lib/utils.ts) for conditional class merging.
  • shadcn/ui components live in src/shared/components/ui/never modify them directly. Wrap them instead.
  • Dark mode via Tailwind's dark: variant. The class strategy is used (not media).
  • Responsive design: mobile-first. sm:, md:, lg: for larger screens.
  • For "native app" feel on mobile: use touch-action: manipulation on interactive elements, remove tap highlight with [-webkit-tap-highlight-color:transparent].

Routing (TanStack Router)

  • Use file-based routing via @tanstack/router-plugin/vite.
  • Route files live in src/routes/. Each file exports a Route created with createFileRoute.
  • Always use Link and useNavigate from TanStack Router. Never <a href> for internal navigation.
  • Search params are typed via Zod — define validateSearch on every route that uses search params.
  • Loaders (loader option on route) are the correct place for data fetching that should block navigation.
// Example route with typed search params
export const Route = createFileRoute("/runs/")({
	validateSearch: z.object({
		page: z.number().default(1),
		filter: z.enum(["all", "recent"]).default("all"),
	}),
	component: RunsPage,
});

Database (PGlite)

  • The PGlite instance is a singleton exported from src/shared/db/client.ts.
  • Never import PGlite directly in components. Always go through a hook or a feature's data layer.
  • Use numbered SQL migration files: src/shared/db/migrations/001_init.sql, 002_add_segments.sql, etc. Run them in order on app start.
  • Zod schemas in src/shared/db/schema.ts mirror every DB table. Use them to validate data coming out of PGlite.
  • For reactive UI updates from DB changes, use a thin wrapper with Zustand's subscribeWithSelector or re-query on relevant user actions.
// src/shared/db/client.ts
import { PGlite } from "@electric-sql/pglite";

let _db: PGlite | null = null;

export async function getDb(): Promise<PGlite> {
	if (!_db) {
		_db = new PGlite("idb://myapp");
		await runMigrations(_db);
	}
	return _db;
}

State Management (Zustand)

  • Zustand is for ephemeral UI state only: modals, active tabs, current user session, UI preferences.
  • Persistent data lives in PGlite, not Zustand. Do not use zustand/middleware persist for app data.
  • One store file per feature: src/features/auth/store.ts.
  • Use subscribeWithSelector middleware when components need to subscribe to slices.
  • Keep stores flat. Avoid deeply nested state.
// Example store
import { create } from "zustand";

interface UIStore {
	activeTab: string;
	setActiveTab: (tab: string) => void;
}

export const useUIStore = create<UIStore>((set) => ({
	activeTab: "home",
	setActiveTab: (tab) => set({ activeTab: tab }),
}));

Forms (TanStack Form + Zod)

  • All forms use useForm from @tanstack/react-form.
  • Validation is always done with a Zod schema via the validators option.
  • Reuse Zod schemas from src/shared/db/schema.ts or src/features/[x]/schema.ts — do not duplicate validation logic.
  • Error messages are shown inline below the field, never in a toast.
const form = useForm({
	defaultValues: { name: "" },
	validators: { onChange: myZodSchema },
	onSubmit: async ({ value }) => {
		/* ... */
	},
});

Animations (CSS View Transitions API)

  • Use the native View Transitions API for page transitions. No animation libraries.
  • Wrap navigation calls with document.startViewTransition().
  • TanStack Router's RouterDevtools and upcoming native support handle this — check the TanStack Router docs for the current recommended integration.
  • Use view-transition-name CSS property on elements that should animate between routes.
  • Provide a @media (prefers-reduced-motion: reduce) fallback that disables transitions.
/* Disable transitions for users who prefer it */
@media (prefers-reduced-motion: reduce) {
	::view-transition-old(*),
	::view-transition-new(*) {
		animation: none;
	}
}

PWA (vite-plugin-pwa)

  • Config lives in vite.config.ts under the VitePWA() plugin.
  • Strategy: generateSW (not injectManifest) unless custom SW logic is needed.
  • Always precache the PGlite WASM files — without this the app won't work offline.
  • manifest.webmanifest must include: name, short_name, start_url: "/", display: "standalone", theme_color, icons at 192×192 and 512×512.
  • Register the SW with registerType: 'autoUpdate' and show a "New version available" toast.

Testing

Unit/Component Tests (Vitest)

  • Test files co-located with source: src/features/auth/auth.test.ts.
  • Use Testing Library for component tests. Query by role, label, or text — never by class or id.
  • Mock PGlite with an in-memory instance (no idb:// prefix) in tests.
  • Run with: bun test

E2E Tests (Playwright)

  • Test files in e2e/.
  • Focus on critical user flows: onboarding, data entry, offline behavior, install prompt.
  • Use page.evaluate() to inspect IndexedDB/PGlite state when needed.
  • Run with: bun e2e

Code Quality (Biome)

  • Biome handles both formatting and linting — no Prettier, no ESLint.
  • Config in biome.json at project root.
  • lint-staged + simple-git-hooks run Biome on staged files before every commit.
  • CI also runs biome check --apply — commits that fail Biome are rejected.
  • Never disable Biome rules with inline comments without a code comment explaining why.

Commands

bun install          # Install dependencies
bun dev              # Start dev server
bun build            # Production build
bun preview          # Preview production build locally
bun test             # Run Vitest unit tests
bun e2e              # Run Playwright E2E tests
bun lint             # Run Biome linter
bun format           # Run Biome formatter

Do Not

  • Do not use npm or yarn — always use bun
  • Do not add ESLint, Prettier, or Husky — Biome + simple-git-hooks covers this
  • Do not use react-query or swr — data comes from PGlite, not a remote API
  • Do not store persistent app data in Zustand — use PGlite
  • Do not use Framer Motion / React Spring — use CSS View Transitions
  • Do not use <a href> for internal links — use TanStack Router's <Link>
  • Do not write raw CSS files — use Tailwind utilities
  • Do not modify files in src/shared/components/ui/ — wrap shadcn components instead
  • Do not introduce a new dependency without checking if the existing stack already covers the need

Adding a New Feature Checklist

  1. Create src/features/[name]/ folder
  2. Add Zod schema in src/features/[name]/schema.ts
  3. Add DB migration in src/shared/db/migrations/ if needed
  4. Add route file in src/routes/[name]/index.tsx
  5. Add Zustand store in src/features/[name]/store.ts if UI state is needed
  6. Write unit tests co-located with the feature
  7. Write at least one Playwright E2E test for the happy path
  8. Run bun lint and bun test before committing