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:
253
AGENTS.md
Normal file
253
AGENTS.md
Normal 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
|
||||
Reference in New Issue
Block a user