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>
This commit is contained in:
2026-03-02 22:57:40 +01:00
parent ea536ce533
commit 5ac44b7551
78 changed files with 5468 additions and 3043 deletions

253
AGENTS.md Normal file
View File

@@ -0,0 +1,253 @@
# 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