SPB Git
6.0 KB

# Developer conventions — Note SPBoucher AI

Read this before touching the code. Stack: Next.js 16 (App Router, React 19, TypeScript strict), Tailwind v4 (design tokens in src/app/globals.css: bg, bg-elev, bg-sunk, fg, fg-muted, fg-faint, line, accent, ai, danger, success, warning), Drizzle ORM + PostgreSQL 17 + pgvector (src/db/schema.ts, snake_case casing), pg-boss worker (src/worker), official openai + @anthropic-ai/sdk behind src/lib/ai.

# Hard rules

  • Do not edit src/db/schema.ts and do not add migrations. Every table you need already exists (see schema). Flexible data goes in the existing jsonb columns (pages.properties, database_properties.config, database_rows.values, database_views.config, ai_executions.*, settings, dashboard_layouts.widgets, experiments.params/metrics, references.fields).
  • Do not add npm dependencies (no pnpm add). Installed and available: next, react, drizzle-orm, postgres, pg-boss, zod, openai, @anthropic-ai/sdk, @tiptap/* (core, react, starter-kit, list, table, image, mention, mathematics, details, code-block-lowlight, drag-handle-react, file-handler, bubble-menu, extensions), lowlight, katex, motion, @tanstack/react-query, zustand, react-force-graph-2d, pdf-parse, mammoth, papaparse, turndown, marked, jszip, bibtex-parse-js, nanoid, sanitize-html, react-markdown + remark-gfm/remark-math/rehype-katex, @dnd-kit/, cmdk, lucide-react, date-fns, qrcode, otplib, @simplewebauthn/, @radix-ui/* (dialog, dropdown-menu, popover, tooltip, select, switch, tabs, scroll-area, checkbox, slot, separator, toast, context-menu, avatar), class-variance-authority, clsx, tailwind-merge, tippy.js, pino, dotenv.
  • Never run pnpm build, never start anything on port 8480 (a production server is running there). Typecheck with npx tsc --noEmit. To try your feature in a browser use pnpm exec next dev -p <your port> with the .env in the project root (it already points to the real database through an SSH tunnel on 127.0.0.1:15432; if nc -z 127.0.0.1 15432 fails, run ssh -f -N -L 15432:127.0.0.1:5432 M4M64c). Login: username simon, password = the value of OWNER_INITIAL_PASSWORD in ~/Desktop/Cluster/secrets/note-spboucher-ai/.env (read it with grep; never print it in your report). The worker is also running (/tmp/note-worker.log); you can run a second worker with npx tsx src/worker/index.ts if you change handlers (stop it when done).
  • Only touch the files/directories assigned to you; do not reformat other files. New routes go in src/app/(app)/<route>/page.tsx (server component that calls requireUserPage() + getWorkspace() then renders a client component).
  • Secrets: never log API keys. AI calls go through src/lib/ai only (router → providers); record usage with recordUsage.
  • UI quality bar: minimalist, dense, premium; reuse src/components/ui/* primitives (Button, Input, Dialog, Popover, Menu*, Tabs*, Badge, Switch, Tip, Empty, Spinner, toast). Responsive: every screen must work on a 390px-wide phone (stack columns, horizontal scroll for wide tables, touch targets ≥ 40px) and on desktop. Dark mode via tokens only (no hard-coded colours). Keyboard: Escape closes, Enter confirms.

# Server-side helpers

  • handler(({ req, user, ws, params }) => Response) from src/lib/api.ts wraps a route handler with auth + workspace + error mapping; parseBody(req, zodSchema); HttpError(status, msg); uuid zod helper. Example: src/app/api/pages/[id]/route.ts.
  • db, schema from @/db; audit(userId, action, target?, detail?) from @/lib/audit.
  • Pages service src/lib/pages/service.ts (createPage, updatePage, getPage, listTree, softDeletePage, …). Pages are the universal container: type = page | database | research; database rows are pages with database_id set (row page content = the row's body).
  • Search: hybridSearch(wsId, q, { limit, pageIds, sourceTypes }), indexSource(...) in src/lib/search/index.ts; chunking in chunk.ts; embeddings in embed.ts.
  • AI: route({ task, tools?, schema?, vision?, estTokens?, preferred? }) from src/lib/ai/router.ts returns { model, provider, effort, reason }; then callAI({ model: model.id, provider, system, messages, jsonSchema?, tools?, effort }) (non-streaming) or streamAI(...) (async generator of {type:"text"|"tool_call"|"done"|"error"}) from src/lib/ai/providers.ts; sse(generator) from src/lib/ai/chat.ts turns a generator into an SSE Response. Task classes: chat | quick | write | reason | deep | code | extract | longctx | agent | judge. Prompts: src/lib/ai/prompts.ts (INJECTION_GUARD must be appended to any system prompt that sees workspace content; wrap workspace content in <workspace> tags).
  • Jobs: enqueue(QUEUES.x, data) from src/lib/jobs; handlers in src/worker/handlers/* (imports must use relative paths like ../../db/index, not @/, and must not import next/*).
  • Editor JSON helpers (src/lib/editor/text.ts): docText, docBlocks, toMarkdown, emptyDoc; client: markdownToDoc in src/lib/editor/markdown-client.ts.

# Client-side helpers

  • api(url, { method, json }) and sse(url, body, onEvent, signal) in src/lib/client/api.ts; UI store useUI() in src/lib/client/store.ts (openAI({ pageId, prefill }), theme, panels); cn, timeAgo, fmtDate, money in src/lib/utils.ts; Markdown renderer src/components/markdown.tsx; Editor (src/components/editor/Editor.tsx, props: content, onChange, pageId, editable).
  • Pages are opened at /p/<id>; PageView.tsx renders DatabaseView for type === "database" and ResearchPanel for type === "research".
  • React Query keys in use: ["tree"] (sidebar), ["ai-models"], ["convs", pageId], ["search", mode, q]. Invalidate ["tree"] after creating/renaming/deleting pages.

# Reporting

When done: run npx tsc --noEmit (must be clean for your files), list the files you created/changed, what you verified in the browser or with curl, and anything left incomplete. Do not commit.

Files · 2c37cb5a3e2af18e5ba361996d453cab97da82c2
Menu

Theme