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

254 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
```ts
// 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.
```ts
// 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.
```ts
// 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.
```ts
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.
```css
/* 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
```bash
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