SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
16.8 KB · 160 lines markdown
Rendered Raw Blame History
1# PolyLLM 1.0 — "AI operating system" upgrade (2026-09-11)23Massive product/UX/UI/mobile-first upgrade of www.polyllm.io. Goal: a premium, app-like multi-model workspace4("One interface. Every model.") that feels native on iPhone/Android and excellent on desktop, without breaking any5existing backend behaviour (auth, encrypted keys, chat history, Arena, presets, usage, exports, folders, attachments).67This document is the shared contract between the parallel workstreams. Read it fully before touching code.89## Ground rules (all workstreams)10111. **Do not break existing functionality.** Reuse services in `src/lib/*`; extend, don't rewrite. Provider quirks stay in12   `src/lib/ai/providers/*`; components only see `PolyModel` capabilities (see `CLAUDE.md`).132. **Ownership.** Edit only the files/dirs listed for your workstream (plus new files you create there). If you need a change14   in someone else's file, add a small **adapter in your own area** and leave a `// TODO(integration: <area>)` comment, or15   put the request in `docs/upgrade-notes/<your-area>.md`. Exception: `src/lib/client/types.ts` — append-only.163. **Schema.** Tables for the upgrade live in `src/db/schema-workspace.ts` (already migrated: `drizzle/0001_workspace_upgrade.sql`).17   If you need more columns/tables: append to `schema-workspace.ts` (or create `src/db/schema-<area>.ts`, re-export it in18   `src/db/index.ts`), then run `pnpm db:generate --name <area>` **and** `pnpm db:migrate` locally. Never edit existing19   migration files. Never drop/rename columns.204. **No dev server in this phase.** Multiple `next dev` in the same tree conflict on `.next`. Validate with21   `pnpm typecheck`, `pnpm lint`, `pnpm test` (vitest) and, for server logic, small vitest units in `tests/unit`. Visual QA22   happens in the integration phase against a single dev server.235. **No fake functionality.** Every button works or is explicitly labelled "Coming soon" (use `<ComingSoon />` badge from24   `components/ui/misc` — add it if missing). No placeholder handlers.256. **Mobile first.** Design for 375 / 390 / 393 / 430 px widths first, then tablet, then desktop. Rules:26   - Phones: bottom sheets instead of centered modals (`ResponsiveDialog`, `BottomSheet`, `ActionSheet` in `components/ui/sheet.tsx`),27     long-press for message/row actions (`useLongPress`), swipe carousels (`.snap-row` + `useSnapCarousel`), 44 px touch28     targets (`.tap` helper or explicit min-h), 16 px inputs, `100dvh`/`.h-app`, safe areas (`pb-safe`, `pt-safe`, `--sab`).29   - Never `window.prompt/confirm/alert` — use `PromptDialog` / `ConfirmDialog` (`components/common`).30   - No desktop tables overflowing on phones: use stacked rows/cards below `md`, tables from `md` up.31   - No horizontal overflow, no clipped text, no fixed element blocking content. Respect `prefers-reduced-motion`.32   - Hover-only controls must also be reachable on touch (`.hover-reveal` shows them on coarse pointers).337. **Design language** (`src/app/globals.css` tokens): neutral warm graphite palette, iris accent, fewer borders, spatial34   grouping (`.panel`, hairlines) over boxed cards, restrained 150–300 ms motion, `text-balance` headlines, tabular numbers35   for metrics. Use `Segmented`/`ChipRow` (`components/ui/segmented.tsx`) for filters. Icons: lucide only.368. **Performance.** No new heavy dependencies without a note in your area doc (allowed if < ~40 KB gz and clearly justified —37   e.g. `katex` for LaTeX). Virtualize long lists (`@tanstack/react-virtual` is installed). Memoize streaming renders.389. **Accessibility.** Labels/aria on icon buttons, focus-visible, keyboard nav for lists/palettes, semantic elements.3910. **Commit nothing.** The integrator commits. Leave the tree compiling (`pnpm typecheck && pnpm lint` green) when you finish.4011. **Final report.** End with: files created/changed, new API routes (method, path, body → response), anything left as41    "Coming soon", and QA notes (what to check visually) in `docs/upgrade-notes/<area>.md`.4243## Shared primitives (already implemented — use them)4445| What | Where | Notes |46| --- | --- | --- |47| Tokens, utilities | `src/app/globals.css` | `.h-app`, `.pb-nav`, `.pb-safe`, `.tap`, `.hover-reveal`, `.snap-row`, `.panel`, `.glass-strong`, `.mask-x`, `.marquee`, `coarse:`/`fine:` variants |48| Hooks | `src/lib/client/hooks.ts` | `useIsMobile`, `useIsTablet`, `useIsCoarsePointer`, `useMounted`, `useKeyboardInset`, `useLongPress`, `useSwipe`, `useLocalStorage`, `useDebounced`, `useCopy`, `useSnapCarousel`, `useStandalone` |49| Sheets | `src/components/ui/sheet.tsx` | `BottomSheet` (handle, snap `content|half|full`, swipe-dismiss), `ResponsiveDialog` (sheet on phone, dialog/panel on desktop), `ActionSheet` (iOS-style list) |50| Segmented / chips | `src/components/ui/segmented.tsx` | `Segmented`, `ChipRow` |51| Dialog replacements | `src/components/common/prompt-dialog.tsx`, `confirm-dialog.tsx` | |52| Shell | `src/components/app/shell.tsx`, `bottom-nav.tsx`, `sidebar.tsx`, `store.tsx` | Mobile bottom nav (Chat/Arena/Models/Usage/Account), drawer with edge-swipe, keyboard inset. Store adds `labels`, `setLabel`, `activeProjectId`, `searchOpen`, `AUTO_MODEL_KEY`. |53| Token/cost estimator | `src/lib/client/tokens.ts` | `estimateTextTokens`, `estimateAttachmentTokens`, `estimateContext`, `estimateCost`, `formatEstimate`, `COST_CONFIRM_THRESHOLD_USD` |54| Smart router | `src/lib/client/router.ts` | `analyzePrompt`, `routeModels`, `explainRoute`, `ROUTER_MODES` (pure, transparent) |55| Brand | `src/components/brand/logo.tsx`, `public/icon.svg`, `public/og.png`, `scripts/brand-assets.sh`, `src/app/manifest.ts` | "Prism P" mark; `Wordmark` |56| Schema | `src/db/schema-workspace.ts` | `projects`, `project_files`, `prompts`, `user_model_labels`, `custom_endpoints`, `arena_votes`, `shared_arena_sessions`; `conversations.project_id` |57| Cross-area stubs | `components/projects/sidebar-section.tsx`, `components/library/file-library-picker.tsx` | Baselines; the Projects workstream owns/finishes them. Chat imports `FileLibraryPicker` from that path. |5859## Workstreams & ownership6061### A — Chat & composer (`components/chat/*` except `model-selector.tsx`, `model-config.tsx`, `model-badges.tsx`; `components/markdown/*`; `app/app/chat/*`; `lib/chat/*`; `app/api/chat/*`; `app/api/attachments/*`)62Mobile composer (+ sheet: upload image/document, camera, paste, from library, tools, web search, structured output, system63prompt, temporary chat), auto-grow 1→6 lines, voice dictation (Web Speech API where available, hidden otherwise), active model64pill above composer (opens the model picker via `ModelSelector` — keep its public props), attachments (drag/drop, paste,65camera, chips, multiple), context indicator (`43K / 200K`, warnings at 80/95 %), cost estimate + confirm above66`COST_CONFIRM_THRESHOLD_USD` with one-tap model alternatives, Smart Router AUTO mode (`selectedModelKey === AUTO_MODEL_KEY` →67recommendation card before sending: Use / Choose another / Always auto-route), temporary chat (server: `ephemeral: true` in68`chatRequestSchema` → stream without persisting; badge "Not stored in history"), message toolbar (desktop hover / mobile69long-press ActionSheet: copy, retry, edit, branch, compare, change model from here, save as prompt, export, rate, more),70expandable message metadata (model, provider, tokens, cost, TTFT, tok/s, reasoning level), deprecated-model warning with71replacement suggestion, humanized provider errors with Retry / Switch model / Details, streaming performance (memoized72blocks, stable markdown, code block Copy/Download/Wrap/Expand, LaTeX via `katex` + `rehype-katex` allowed), inline73**Compare with…** (2–4 models; desktop side-by-side, mobile swipeable panels with sticky tabs; reuse `/api/arena` +74`/api/arena/stream` to run, store as Arena session, "Continue with this model"), empty state with quick actions75(Write / Code / Research / Analyze a file / Compare models), project instructions applied when `activeProjectId` is set76(fetch `/api/projects/:id` — Projects workstream provides it; degrade gracefully if 404).7778### B — Models (`components/chat/model-selector.tsx`, `model-config.tsx`, `model-badges.tsx`; `app/app/models/*`; new `app/(marketing)/models/*`, `app/(marketing)/compare/*`; `components/models/*`; `lib/ai/registry/*` (additive); `app/api/models/*` (additive); `components/presets/*`, `app/app/presets/*`; `lib/models/*`)79Model picker redesign (bottom sheet on phones; desktop dialog), fast search with intent parsing ("cheap vision model",80"1M context", "under $1/M", "fastest gemini", "json schema"), sections (Favorites, Recent, Recommended, Fast, Best81reasoning, Cheapest, Largest context, Vision, Coding, Open source, New), compact metadata rows, star favorites, custom labels82(`store.setLabel`), AUTO entry (Smart Router) with mode selector, model badges (NEW/FAST/CHEAP/REASONING/VISION/CODING/LONG83CONTEXT), lifecycle badges (New/Active/Deprecated/Retiring/Unavailable), model profile sheet (all fields; show "—" for84unknown — never invent release dates or cutoffs), `/app/models` catalog (sortable, filters, mobile stacked rows, select →85Compare), public `/models` and `/compare/[slug]` (`gpt-5.5-vs-claude-sonnet-5`) pages with SEO metadata reading the registry86server-side (no auth), capability-aware settings grouped Generation / Reasoning / Output / Tools / Advanced with Reset and87Save preset (presets apply across compatible models).8889### C — Arena (`components/arena/*`; `app/app/arena/*`; `lib/arena/*`; `app/api/arena/*`; new `app/share/arena/*`)90Up to 4 models: desktop adaptive 2×2 / side-by-side, phone one panel at a time (`.snap-row`, sticky model tabs, swipe),91live metrics (status, TTFT, tok/s, in/out tokens, cost), votes (Best / Most accurate / Best writing / Best coding / Best value92/ Fastest + custom criteria) stored in `arena_votes`, Arena Winner summary with comparative metrics, Blind Arena (Model A/B/C93until vote; reveal), history, personal scoreboard `/app/arena/scoreboard` (win rate per model, filters Coding/Research/94Writing/Reasoning/Cost efficiency), exports (Markdown/JSON incl. models, parameters, metrics, ratings), public share of an95Arena comparison (`shared_arena_sessions`, `/share/arena/[id]`, revoke).9697### D — Projects, prompt library, context library (`components/projects/*`, `components/library/*`, `components/prompts/*`; `app/app/projects/*`, `app/app/library/*`, `app/app/prompts/*`; `lib/projects/*`, `lib/library/*`, `lib/prompts/*`; `app/api/projects/*`, `app/api/library/*`, `app/api/prompts/*`)98Projects CRUD (`/api/projects`, `/api/projects/[id]` returning `{ project, conversations, files, prompts }`), project page99(instructions, preferred models, notes, files, conversations), `activeProjectId` selector, conversations filtered by project100(add `projectId` to `ListFilter`/PATCH in `lib/conversations/service.ts` — small additive change allowed), file library101(`/api/library/files` GET/POST/DELETE with `estimatedTokens`, `/api/library/files/attach` POST `{fileIds}` → `{attachments}`102copying into `message_attachments`), prompt library with `{{variables}}` (parse, defaults, fill-in sheet before insert into103composer via `?prompt=` / `router.push('/app/chat?promptId=…')` and a `usePromptInsert` event on `window`), folders/tags/104favorites, keep existing `prompt_presets`/`model_presets` pages working (migrate the `/app/prompts` page to the new library105while still listing legacy presets). Finish the two stubs listed above.106107### E — Usage analytics, providers, custom endpoints (`components/usage/*`; `app/app/usage/*`; `lib/usage/*`; `app/api/usage/*`; `app/app/settings/*`; `components/providers/*`; `lib/providers/*`; `app/api/providers/*`; new `lib/ai/providers/custom/*`, `app/api/endpoints/*`)108Usage dashboard (KPIs: requests, tokens, cost, avg TTFT, tok/s, avg context, error rate; charts: cost/tokens/requests over109time, cost by provider/model, requests by model, latency by model; filters Today/7d/30d/90d/Custom + provider/model/project;110projections "Estimated monthly cost"; savings opportunities computed from real usage_records + pricing; mobile: stacked111KPI tiles, chart cards full-width, tables → rows). Follow `dataviz` skill guidance (palette from tokens, accessible).112Provider management redesign (status Connected/Not connected/Validation failed, last validated, models available, Test113connection with latency, key rotation, remove), extensible provider list. Custom OpenAI-compatible endpoints (Ollama, LM114Studio, vLLM, llama.cpp, MLX…): `custom_endpoints` table exists; implement adapter via `createOpenAICompatAdapter` with115per-endpoint baseURL/headers, discovery (`GET {baseUrl}/models`) or manual models, models keyed `custom/<endpointId>:<modelId>`116surfaced in `/api/models`, chat routing in `lib/chat/service.ts` resolution (coordinate via a `resolveCustomEndpoint()`117helper you export; document the one-line hook needed in the chat service in your notes if you can't wire it without118editing A's files — the integrator will wire it). Add `"custom"` to `PROVIDER_IDS` only if every switch over providers stays119exhaustive-safe (grep first). Settings pages mobile-first (full-screen sections, bottom sheets).120121### F — Marketing, onboarding, auth, PWA polish (`components/marketing/*`; `app/(marketing)/*` except `models`/`compare`; `app/(auth)/*`; `components/app/onboarding.tsx`; `app/app/onboarding/*`; `app/sitemap.ts`; `public/*` (additive))122Homepage redesign: hero "One interface. / Every model." + sub-line (Bring your own keys. Compare models. Control every123parameter. Track every token.), CTAs "Start using PolyLLM" / "Try Arena", animated **real product UI** in the hero (no abstract124art), live model strip (public `GET /api/public/models` summary — create it; marquee with provider marks, context, price,125status), interactive demo switchable Chat/Arena/Models/Usage with simulated streaming and a phone-shaped mobile demo that126mirrors the real mobile UI, features, security page `/security` (Browser → PolyLLM encrypted server layer → Provider; key127lifecycle; AES-256-GCM, Argon2id, HttpOnly cookies, CSP, rate limits, audit logs — technically accurate only, verify in code),128FAQ, footer with **contact@spboucher.ai · Simon-Pierre Boucher · Hosted on MacLustr (www.maclustr.io)**, `/contact` optional.129Onboarding 6 steps (account → providers → first key → validate → favorite models → first prompt; skippable; progress;130swipeable on phone) as a full page `/app/onboarding` + first-run redirect logic in `components/app/onboarding.tsx` (keep the131existing `Onboarding` export working). Auth pages polish (mobile-first). Keep all metadata/OG (already set in `app/layout.tsx`).132133### G — Search, command palette, share/export (`components/app/command-palette.tsx`; new `components/search/*`; `app/api/search/*`; `lib/search/*`; `app/share/[id]/*`; `lib/conversations/service.ts` export/share functions (additive); `app/api/conversations/[id]/actions/route.ts` (additive))134Full-text search API with filter syntax `model:claude project:research after:2026-08-01 before: provider: role:` over titles135+ message content (Postgres `ilike`/`to_tsvector` — add a GIN index via a new migration if you use tsvector), grouped results,136snippets with highlights; search UI: desktop inside ⌘K (search mode), phone full-screen sheet (`store.searchOpen`); universal137command palette (New chat, Search, Switch model, Open Arena/Usage/Providers/Project, Upload file, Toggle theme, New temporary138chat (`/app/chat?temporary=1`), Copy conversation URL, keyboard-first). Share: options Entire conversation / Selected messages139(`messageIds`) with privacy warning sheet, revoke, list of active links in Settings → Data; share page redesign (mobile-first,140brand). Export: Markdown, TXT, JSON, PDF (print-optimized route `/share/print/[id]` + `window.print()` is acceptable — label it141"PDF (print)").142143## Navigation architecture144145- Desktop: sidebar (New chat, Search, Chat/Arena/Models/Prompts/Presets/Library/Usage, Projects, Pinned, Folders, Recent,146  account menu). Collapsible ⌘B.147- Phone: bottom nav Chat / Arena / Models / Usage / Account; drawer (hamburger or edge-swipe) for conversations, projects,148  search; page headers use a compact 48 px bar with a hamburger on the left (`setSidebarOpen(true)`).149- Every app page must reserve space for the bottom nav (the shell adds `padding-bottom`; pages use `h-full min-h-0 flex-col`150  layouts and scroll inside, never the document).151152## Integration phase (integrator)1531541. Wire cross-area hooks (custom endpoint resolution in chat service, project instructions, prompt insert).1552. `pnpm typecheck && pnpm lint && pnpm test`, `pnpm build`.1563. Single dev server → Playwright QA at 375×812, 390×844, 393×852, 430×932 + desktop 1440 for every screen listed in the brief157   (login, signup, onboarding, chat, composer+keyboard, attachments, model switcher, Arena + swipe, drawer, search, projects,158   catalog, usage, providers, account, sheets). Fix overflow/clipping/tap targets.1594. Migration on prod via mld hook (`pnpm db:migrate`), deploy `mld stage . polyllm && mld deploy polyllm --node M3U96a`.160