Build Cobble project structure from the handoff

- Remove the preset document suite (letters, emails, invoices, quotes,
  PDF generation, line items) with its routes, API handlers and tables
- Rebrand to Cobble: site data, Lato, handoff design tokens (light and
  dark) in main.css, brand assets in public/brand, ember stone logo and
  regenerated favicons
- Add bots, bot_messages, bot_tasks and bot_task_steps tables, regenerate
  DB types (BUN_SQLITE_COBBLE_*) and add a sample crew seed (seed:bots)
- Add the chat shell at /admin/bots: rail with pinned coordinator and
  user popover, bottom-anchored conversation with composer, working-on
  panel, and a send-message API scoped to the session user
- Add BotStone and Wordmark components, crew status and relative time
  utils with tests, and update AGENTS.md

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-18 17:10:36 +01:00
co-authored by Claude Opus 5.5
parent 81e4a8aecc
commit 3286b80029
340 changed files with 3161 additions and 12060 deletions
+18 -22
View File
@@ -1,5 +1,7 @@
# Agent Instructions
This is **Cobble** (`cobble.moduletrace.org`): an open-source crew of small AI worker bots. Product and design source of truth is the handoff in `assets/handoff/` (gitignored locally): `PRODUCT.md`, `IMPLEMENTATION.md`, `tokens/`. Unit noun is **bot**, never "agent" or "expert".
## Stack
- **Runtime:** Bun - not Node. Use `bun` commands, never `npm`/`npx`.
@@ -8,7 +10,7 @@
- **UI:** Tailwind CSS v4 + the `@moduletrace/twui` component library (installed package) + shared components in `src/components/general/`. There is **no** shadcn/ui, no `tailwind.config`, and no `components.json` - twui is the only component system. CSS chain: `src/pages/__root.tsx` imports `src/styles/main.css`, which imports `@moduletrace/twui/base.css` (`@import "tailwindcss"`). Theme tokens (colors/radius/fonts) are overridden via the `@theme inline` block in `src/styles/main.css`.
- **Icons:** `lucide-react`.
- **Validation:** `zod` v4 (`[email protected]`).
- **Branding:** Site name/slug live only in `src/data/site-data.ts`. Logo source of truth is `public/logos/logo.svg` (rendered inline by `src/components/general/logo-icon.tsx`); browser favicon/assets are generated from it.
- **Branding:** Site name/slug live only in `src/data/site-data.ts`. Logo source of truth is `public/logos/logo.svg` (the ember stone; rendered inline via `BotStone` in `src/components/general/logo-icon.tsx`); browser favicon/assets are generated from it. Handoff brand assets (stones, coordinator, wordmarks, app icon) live in `public/brand/`.
## Entrypoint
@@ -32,6 +34,7 @@
| DB admin UI | `bun run db:admin` |
| DB backup | `bun run db:backup` |
| Seed super admin user | `bun run seed` (runs `src/scripts/seed-super-admin.tsx`) |
| Seed sample bot crew | `bun run seed:bots [email]` (runs `src/scripts/seed-bots.ts`, idempotent) |
| Query DB (CLI) | `bun llm-code-assistant/query-db.ts "<sql>"` |
| Install | `bun install` |
@@ -74,19 +77,14 @@ Inspect live data with `bun llm-code-assistant/query-db.ts "<sql>"` - a read-onl
`api/admin/crud/[[...paths]].ts` dispatches `GET/POST/PUT/DELETE` by table name: `userAuth` → `read_only` GET-only gate → per-table switch → generic fallback (super admin only). Tables without custom handlers fall through to generic CRUD with zero code. Customize a table by adding `src/pages/api/admin/crud/(tables)/<table>/` with `index.ts` + per-method files, then add a `case` in the router.
### Documents (Letters / Emails / Invoices / Quotes)
### Bots (the chat shell)
The preset ships a complete document suite on top of the generic CRUD:
- **Tables:** `letters`, `emails`, `email_attachments`, `invoices`, `invoice_items`, `quotes`, `quote_items`, `invoice_quotes` (statuses/enums in `src/dict/document-dict.ts`)
- **Admin sections:** `/admin/letters`, `/admin/emails`, `/admin/invoices`, `/admin/quotes` - full list/create/view/edit CRUD, line items, quote-to-invoice linking, PDF download, send-email modals
- **PDF:** `src/functions/backend/pdf/` - `pdf-helpers.ts`, `letter-pdf.ts`, `invoice-pdf.ts` (drives both invoices and quotes via `kind`), `render-blocks.ts` + `html-blocks.ts` (TinyMCE HTML → PDF blocks), `letter-date.ts`
- **Email:** `src/functions/backend/email/` - `email-client.ts` (nodemailer SMTP), `send-email.tsx` (renders ReactNode into the `src/email/root.tsx` shell), `document-emails.tsx` (invoice/quote bodies)
- **API routes:** `api/admin/{letters,invoices,quotes}/generate-pdf.ts` (streams PDF binary), `api/admin/{emails,invoices,quotes}/send-email.tsx`, `api/admin/emails/sync-attachments.ts`
- **DB grabs:** `db/documents/`, `db/invoices/grab-invoice.ts`, `db/quotes/grab-quote.ts`
- **Client helpers:** `src/functions/frontend/document-actions.ts` (PDF download + send-email actions)
- **Numbering:** invoice/quote numbers auto-generate as `<SLUG>-YYYYMM-###` / `<SLUG>Q-YYYYMM-###` from `SiteData["SiteSlug"]` in the `invoices`/`quotes` CRUD handlers
- **Shared UI:** `src/components/general/` `pdf-preview.tsx`, `sortable-line-items-editor.tsx` (+ `sortable-line-item-row.tsx`), `copy-from-existing-select.tsx`, `main-hero-section.tsx`
- **Tables:** `bots` (per-user workspace; `kind` worker|coordinator, `hue`, `role`, `face`, `status_line`, `unread`), `bot_messages`, `bot_tasks`, `bot_task_steps`. Enums in `src/dict/bot-dict.ts`.
- **Routes:** `/admin/bots` redirects to the first bot (coordinator pinned first); `/admin/bots/[bot_id]` renders conversation + working-on panel. Both render in the full-bleed `src/layouts/bots/` layout (rail of conversations + user popover), selected in `__root.tsx` before the admin layout.
- **Server:** `src/functions/backend/db/bots/` (`grab-bots`, `grab-bot-messages`, `grab-active-bot-task`, `grab-user-bot`, `mark-bot-read`, `insert-user-message`). `api/admin/bots/send-message.ts` posts a user message to a bot owned by the session user (zod: `src/schemas/bot-schema.ts`).
- **Avatars:** `BotStone` (`src/components/general/bot-stone.tsx`) renders the stone inline from `src/data/stone-geometry.ts`, hue as a fill token, face (`idle|working|done|asleep|needs-you`) drawn as eyes. The coordinator is hue-less (ink body, paper eyes; inverts in dark). Never put a stone in a circle/badge or add outline/shadow. Wordmark is vector (`Wordmark`, `src/data/wordmark-paths.ts`), never live text.
- **Copy helpers:** `src/utils/crew-status.ts` (`3 working · 1 needs you`, "Three bots are working. One needs you."), `src/utils/relative-time.ts` (relative timestamps only).
- **Not built yet:** add-bot flow, attachments, routines, help, live updates over WebSocket, the model layer.
## Admin Route Hierarchy
@@ -94,10 +92,8 @@ Actual preset routes under `/admin`:
- `/admin` - dashboard
- `/admin/people` - admin users list, plus `create/`, `[user_id]/` (profile), `[user_id]/edit/`
- `/admin/letters` - letter templates, plus `create/`, `[id]/`, `[id]/edit/`
- `/admin/emails` - email records/drafts, plus `create/`, `[id]/`, `[id]/edit/`, `template/` (live email-shell preview)
- `/admin/invoices` - invoices, plus `create/`, `[id]/`, `[id]/edit/`
- `/admin/quotes` - quotes, plus `create/`, `[id]/`, `[id]/edit/`
- `/admin/bots` - the Cobble chat shell, plus `[bot_id]/`
- `/admin/logs` - audit log (super admin; also the "Activity" item in the bots popover)
- `/admin/backups` - super admin only: create/list/download database snapshots in `db/.backups`, plus `restore/` (restore a listed backup, or upload a SQLite database file which is saved to `.backups` then restored). Shells out to the `bun-sqlite` CLI (`backup`, and `import <file>` since `restore` is interactive) via `src/functions/backend/backups/`; API routes in `api/admin/backups/`
- `/admin/settings` - current user profile, plus `password/` (only when `AUTH_LOGIN_METHOD=password`; redirects to `/admin/settings` otherwise). The aside renders it as a collapsible sub-nav via `children` in `(dict)/admin-aside-groups-dict.tsx`
@@ -124,8 +120,8 @@ Server functions use `index.server.ts` alongside `index.tsx` (e.g. `single-docum
- **`src/hooks/`** - Shared React hooks: `use-admin-crud-get`, `use-fetch-get`, `use-form-init`, `use-local-user`, `use-users-get`
- **`src/functions/`** - Shared functions split by target:
- `src/functions/backend/` - Server-only: auth (`backend/auth/`), DB queries (`backend/db/` incl. `documents/`, `invoices/`, `quotes/`), email client + templates (`email/`), PDF generation (`pdf/`), media upload, `logger.ts` (audit logging)
- `src/functions/frontend/` - Client-safe: CRUD handlers, fetch handlers, media upload/delete handlers, `document-actions.ts` (PDF download/send actions)
- `src/functions/backend/` - Server-only: auth (`backend/auth/`), DB queries (`backend/db/` incl. `bots/`), email client + templates (`email/`), media upload, `logger.ts` (audit logging)
- `src/functions/frontend/` - Client-safe: CRUD handlers, fetch handlers, media upload/delete handlers
- `src/functions/server/` - Server utilities (media server handler etc.)
- **`src/utils/`** - Utility modules: cookies, crypt, ejson, money, safe-compare, text-to-html, and more. Most have corresponding `.test.ts` files.
- **`src/utils/rbac/`** - RBAC engine: `user-type-assignment` (matrix interpreter), `is-pass-through-user`, `can-*` permission helpers, `filter-assignable-user-types` - with `bun:test` suites driven by fixture matrices
@@ -140,7 +136,7 @@ Server functions use `index.server.ts` alongside `index.tsx` (e.g. `single-docum
| Directory | Description |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `@moduletrace/twui` | **The** design system - external package. Contains layout primitives, elements, editors (TinyMCE), form components, hooks, SVGs, and utilities. Import from `@moduletrace/twui` or a deep path such as `@moduletrace/twui/layout/Button` |
| `src/components/general/` | Shared page components built on twui: `logo`, `logo-icon`, `inner-page-hero-section`, `main-hero-section`, `admin-hero`, `login-shell`, `tagline-section`, `pdf-preview`, `sortable-line-items-editor`, `copy-from-existing-select`, media/image helpers |
| `src/components/general/` | Shared page components built on twui: `logo`, `logo-icon`, `wordmark`, `bot-stone` (+ `bot-stone-face`, `stone-eye-*`), `inner-page-hero-section`, `main-hero-section`, `admin-hero`, `login-shell`, `tagline-section`, media/image helpers |
## UI & Styling Rules (MANDATORY)
@@ -151,8 +147,8 @@ Server functions use `index.server.ts` alongside `index.tsx` (e.g. `single-docum
- Shared: `Logo`, `LogoIcon`, `InnerPageHeroSection`, `AdminHero`, `LoginShell` (auth pages/forms), `TaglineSection`
- **UNCHANGEABLE RULE - twui components always win over raw JSX elements.** Use twui components (`H1`-`H5`, `P`, `Span`, `List`, `Img`, `Row`, `Stack`, `Container`, `Section`, `Card`, `Button`, `Tag`, `Link`, etc.) before basic JSX elements (`<h1>`-`<h6>`, `<p>`, `<span>`, `<ul>`, `<li>`, `<img>`, `<a>`, `<button>`, `<div>`-as-card, etc.). Never introduce or reintroduce a raw JSX element where a twui equivalent exists. Override twui defaults via `className` rather than reaching for the raw element.
- **Never create new CSS classes, CSS files, or inline `style={{...}}` hacks.** Style exclusively by composing existing component props/classNames, existing twui classNames (`twui-*`), semantic theme-token utilities (`bg-dark`, `bg-light`, `text-dark`, `text-primary`, `bg-primary`, `text-accent`, `bg-accent`, `text-secondary`, `border-slate-200`, etc.), and utility classes already used elsewhere in this codebase. Build up from what exists; extend component code only when the existing API cannot express the need.
- **Theming:** colors, radii, and fonts change only in two places: the `@theme inline` block in `src/styles/main.css` (overrides `@moduletrace/twui/base.css` defaults). Never hardcode hex colors, radii, or font stacks inside components.
- **Branding:** `public/logos/logo.svg` is the single source of truth for the logo mark; `logo-icon.tsx` renders it inline and `logo.tsx` renders the wordmark from `SiteData["SiteName"]`. Whenever the logo changes, run `bun run gen:favicon` to regenerate `public/favicon.ico`, `public/favicon.png`, `public/apple-touch-icon.png`, and `public/logos/logo.png` (the last is embedded in generated PDFs). Font + favicon links live in `__root.tsx` `Head()`.
- **Theming:** colors, radii, and fonts change only in `src/styles/main.css`: Cobble tokens as CSS vars on `:root` (light) and `.dark`, exposed in the `@theme inline` block as `paper`, `surface`, `sunken`, `ink`, `ink-secondary`, `ink-disabled`, `hairline`, `hairline-strong`, `on-ink`, `hue-<name>`, plus `shadow-popover` and `--m-fast/base/slow` motion vars (zeroed under reduced motion). Font is Lato (stand-in). Never hardcode hex colors, radii, or font stacks inside components. Hues are bot identity only, never state; use `src/dict/bot-hue-classes-dict.ts` for hue classes.
- **Branding:** `public/logos/logo.svg` is the single source of truth for the logo mark; `logo-icon.tsx` renders it inline (via `BotStone`) and `logo.tsx` renders the stone beside the vector `Wordmark`. Whenever the logo changes, run `bun run gen:favicon` to regenerate `public/favicon.ico`, `public/favicon.png`, `public/apple-touch-icon.png`, and `public/logos/logo.png` (the last is used in emails). Font + favicon links live in `__root.tsx` `Head()`.
- **Site name:** brand literals live only in `src/data/site-data.ts`. Page meta titles, emails, and UI copy must interpolate `SiteData["SiteName"]` - never hardcode a brand name in page files.
## Conventions