SPB Git

spb/polyllm

Public

Foundation for 1.0 upgrade: design tokens, mobile shell (bottom nav, drawer, keyboard inset), sheet primitives, brand (Prism P, favicon, OG, PWA manifest), workspace schema, router & token estimators, upgrade plan

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Sep 11, 2026) parent f5497ba Browse files

41 changed files +6,051 −204

added docs/UPGRADE-PLAN.md +159 −0
@@ -0,0 +1,159 @@
1+# PolyLLM 1.0 — "AI operating system" upgrade (2026-09-11)
2+
3+Massive product/UX/UI/mobile-first upgrade of www.polyllm.io. Goal: a premium, app-like multi-model workspace
4+("One interface. Every model.") that feels native on iPhone/Android and excellent on desktop, without breaking any
5+existing backend behaviour (auth, encrypted keys, chat history, Arena, presets, usage, exports, folders, attachments).
6+
7+This document is the shared contract between the parallel workstreams. Read it fully before touching code.
8+
9+## Ground rules (all workstreams)
10+
11+1. **Do not break existing functionality.** Reuse services in `src/lib/*`; extend, don't rewrite. Provider quirks stay in
12+ `src/lib/ai/providers/*`; components only see `PolyModel` capabilities (see `CLAUDE.md`).
13+2. **Ownership.** Edit only the files/dirs listed for your workstream (plus new files you create there). If you need a change
14+ in someone else's file, add a small **adapter in your own area** and leave a `// TODO(integration: <area>)` comment, or
15+ put the request in `docs/upgrade-notes/<your-area>.md`. Exception: `src/lib/client/types.ts` — append-only.
16+3. **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 in
18+ `src/db/index.ts`), then run `pnpm db:generate --name <area>` **and** `pnpm db:migrate` locally. Never edit existing
19+ migration files. Never drop/rename columns.
20+4. **No dev server in this phase.** Multiple `next dev` in the same tree conflict on `.next`. Validate with
21+ `pnpm typecheck`, `pnpm lint`, `pnpm test` (vitest) and, for server logic, small vitest units in `tests/unit`. Visual QA
22+ happens in the integration phase against a single dev server.
23+5. **No fake functionality.** Every button works or is explicitly labelled "Coming soon" (use `<ComingSoon />` badge from
24+ `components/ui/misc` — add it if missing). No placeholder handlers.
25+6. **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 touch
28+ 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).
33+7. **Design language** (`src/app/globals.css` tokens): neutral warm graphite palette, iris accent, fewer borders, spatial
34+ grouping (`.panel`, hairlines) over boxed cards, restrained 150–300 ms motion, `text-balance` headlines, tabular numbers
35+ for metrics. Use `Segmented`/`ChipRow` (`components/ui/segmented.tsx`) for filters. Icons: lucide only.
36+8. **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.
38+9. **Accessibility.** Labels/aria on icon buttons, focus-visible, keyboard nav for lists/palettes, semantic elements.
39+10. **Commit nothing.** The integrator commits. Leave the tree compiling (`pnpm typecheck && pnpm lint` green) when you finish.
40+11. **Final report.** End with: files created/changed, new API routes (method, path, body → response), anything left as
41+ "Coming soon", and QA notes (what to check visually) in `docs/upgrade-notes/<area>.md`.
42+
43+## Shared primitives (already implemented — use them)
44+
45+| 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. |
58+
59+## Workstreams & ownership
60+
61+### 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/*`)
62+Mobile composer (+ sheet: upload image/document, camera, paste, from library, tools, web search, structured output, system
63+prompt, temporary chat), auto-grow 1→6 lines, voice dictation (Web Speech API where available, hidden otherwise), active model
64+pill above composer (opens the model picker via `ModelSelector` — keep its public props), attachments (drag/drop, paste,
65+camera, chips, multiple), context indicator (`43K / 200K`, warnings at 80/95 %), cost estimate + confirm above
66+`COST_CONFIRM_THRESHOLD_USD` with one-tap model alternatives, Smart Router AUTO mode (`selectedModelKey === AUTO_MODEL_KEY` →
67+recommendation card before sending: Use / Choose another / Always auto-route), temporary chat (server: `ephemeral: true` in
68+`chatRequestSchema` → stream without persisting; badge "Not stored in history"), message toolbar (desktop hover / mobile
69+long-press ActionSheet: copy, retry, edit, branch, compare, change model from here, save as prompt, export, rate, more),
70+expandable message metadata (model, provider, tokens, cost, TTFT, tok/s, reasoning level), deprecated-model warning with
71+replacement suggestion, humanized provider errors with Retry / Switch model / Details, streaming performance (memoized
72+blocks, stable markdown, code block Copy/Download/Wrap/Expand, LaTeX via `katex` + `rehype-katex` allowed), inline
73+**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 actions
75+(Write / Code / Research / Analyze a file / Compare models), project instructions applied when `activeProjectId` is set
76+(fetch `/api/projects/:id` — Projects workstream provides it; degrade gracefully if 404).
77+
78+### 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/*`)
79+Model 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, Best
81+reasoning, Cheapest, Largest context, Vision, Coding, Open source, New), compact metadata rows, star favorites, custom labels
82+(`store.setLabel`), AUTO entry (Smart Router) with mode selector, model badges (NEW/FAST/CHEAP/REASONING/VISION/CODING/LONG
83+CONTEXT), lifecycle badges (New/Active/Deprecated/Retiring/Unavailable), model profile sheet (all fields; show "—" for
84+unknown — never invent release dates or cutoffs), `/app/models` catalog (sortable, filters, mobile stacked rows, select →
85+Compare), public `/models` and `/compare/[slug]` (`gpt-5.5-vs-claude-sonnet-5`) pages with SEO metadata reading the registry
86+server-side (no auth), capability-aware settings grouped Generation / Reasoning / Output / Tools / Advanced with Reset and
87+Save preset (presets apply across compatible models).
88+
89+### C — Arena (`components/arena/*`; `app/app/arena/*`; `lib/arena/*`; `app/api/arena/*`; new `app/share/arena/*`)
90+Up to 4 models: desktop adaptive 2×2 / side-by-side, phone one panel at a time (`.snap-row`, sticky model tabs, swipe),
91+live metrics (status, TTFT, tok/s, in/out tokens, cost), votes (Best / Most accurate / Best writing / Best coding / Best value
92+/ Fastest + custom criteria) stored in `arena_votes`, Arena Winner summary with comparative metrics, Blind Arena (Model A/B/C
93+until vote; reveal), history, personal scoreboard `/app/arena/scoreboard` (win rate per model, filters Coding/Research/
94+Writing/Reasoning/Cost efficiency), exports (Markdown/JSON incl. models, parameters, metrics, ratings), public share of an
95+Arena comparison (`shared_arena_sessions`, `/share/arena/[id]`, revoke).
96+
97+### 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/*`)
98+Projects CRUD (`/api/projects`, `/api/projects/[id]` returning `{ project, conversations, files, prompts }`), project page
99+(instructions, preferred models, notes, files, conversations), `activeProjectId` selector, conversations filtered by project
100+(add `projectId` to `ListFilter`/PATCH in `lib/conversations/service.ts` — small additive change allowed), file library
101+(`/api/library/files` GET/POST/DELETE with `estimatedTokens`, `/api/library/files/attach` POST `{fileIds}` → `{attachments}`
102+copying into `message_attachments`), prompt library with `{{variables}}` (parse, defaults, fill-in sheet before insert into
103+composer via `?prompt=` / `router.push('/app/chat?promptId=…')` and a `usePromptInsert` event on `window`), folders/tags/
104+favorites, keep existing `prompt_presets`/`model_presets` pages working (migrate the `/app/prompts` page to the new library
105+while still listing legacy presets). Finish the two stubs listed above.
106+
107+### 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/*`)
108+Usage dashboard (KPIs: requests, tokens, cost, avg TTFT, tok/s, avg context, error rate; charts: cost/tokens/requests over
109+time, cost by provider/model, requests by model, latency by model; filters Today/7d/30d/90d/Custom + provider/model/project;
110+projections "Estimated monthly cost"; savings opportunities computed from real usage_records + pricing; mobile: stacked
111+KPI tiles, chart cards full-width, tables → rows). Follow `dataviz` skill guidance (palette from tokens, accessible).
112+Provider management redesign (status Connected/Not connected/Validation failed, last validated, models available, Test
113+connection with latency, key rotation, remove), extensible provider list. Custom OpenAI-compatible endpoints (Ollama, LM
114+Studio, vLLM, llama.cpp, MLX…): `custom_endpoints` table exists; implement adapter via `createOpenAICompatAdapter` with
115+per-endpoint baseURL/headers, discovery (`GET {baseUrl}/models`) or manual models, models keyed `custom/<endpointId>:<modelId>`
116+surfaced in `/api/models`, chat routing in `lib/chat/service.ts` resolution (coordinate via a `resolveCustomEndpoint()`
117+helper you export; document the one-line hook needed in the chat service in your notes if you can't wire it without
118+editing A's files — the integrator will wire it). Add `"custom"` to `PROVIDER_IDS` only if every switch over providers stays
119+exhaustive-safe (grep first). Settings pages mobile-first (full-screen sections, bottom sheets).
120+
121+### 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))
122+Homepage redesign: hero "One interface. / Every model." + sub-line (Bring your own keys. Compare models. Control every
123+parameter. Track every token.), CTAs "Start using PolyLLM" / "Try Arena", animated **real product UI** in the hero (no abstract
124+art), live model strip (public `GET /api/public/models` summary — create it; marquee with provider marks, context, price,
125+status), interactive demo switchable Chat/Arena/Models/Usage with simulated streaming and a phone-shaped mobile demo that
126+mirrors the real mobile UI, features, security page `/security` (Browser → PolyLLM encrypted server layer → Provider; key
127+lifecycle; AES-256-GCM, Argon2id, HttpOnly cookies, CSP, rate limits, audit logs — technically accurate only, verify in code),
128+FAQ, footer with **contact@spboucher.ai · Simon-Pierre Boucher · Hosted on MacLustr (www.maclustr.io)**, `/contact` optional.
129+Onboarding 6 steps (account → providers → first key → validate → favorite models → first prompt; skippable; progress;
130+swipeable on phone) as a full page `/app/onboarding` + first-run redirect logic in `components/app/onboarding.tsx` (keep the
131+existing `Onboarding` export working). Auth pages polish (mobile-first). Keep all metadata/OG (already set in `app/layout.tsx`).
132+
133+### 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))
134+Full-text search API with filter syntax `model:claude project:research after:2026-08-01 before: provider: role:` over titles
135++ message content (Postgres `ilike`/`to_tsvector` — add a GIN index via a new migration if you use tsvector), grouped results,
136+snippets with highlights; search UI: desktop inside ⌘K (search mode), phone full-screen sheet (`store.searchOpen`); universal
137+command palette (New chat, Search, Switch model, Open Arena/Usage/Providers/Project, Upload file, Toggle theme, New temporary
138+chat (`/app/chat?temporary=1`), Copy conversation URL, keyboard-first). Share: options Entire conversation / Selected messages
139+(`messageIds`) with privacy warning sheet, revoke, list of active links in Settings → Data; share page redesign (mobile-first,
140+brand). Export: Markdown, TXT, JSON, PDF (print-optimized route `/share/print/[id]` + `window.print()` is acceptable — label it
141+"PDF (print)").
142+
143+## Navigation architecture
144+
145+- 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).
151+
152+## Integration phase (integrator)
153+
154+1. Wire cross-area hooks (custom endpoint resolution in chat service, project instructions, prompt insert).
155+2. `pnpm typecheck && pnpm lint && pnpm test`, `pnpm build`.
156+3. Single dev server → Playwright QA at 375×812, 390×844, 393×852, 430×932 + desktop 1440 for every screen listed in the brief
157+ (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.
159+4. Migration on prod via mld hook (`pnpm db:migrate`), deploy `mld stage . polyllm && mld deploy polyllm --node M3U96a`.
modified drizzle.config.ts +1 −1
@@ -2,7 +2,7 @@ import { defineConfig } from "drizzle-kit";
2 2
3 export default defineConfig({ 3 export default defineConfig({
4 dialect: "postgresql", 4 dialect: "postgresql",
5− schema: "./src/db/schema.ts", 5+ schema: ["./src/db/schema.ts", "./src/db/schema-*.ts"],
6 out: "./drizzle", 6 out: "./drizzle",
7 dbCredentials: { url: process.env.DATABASE_URL ?? "postgres://localhost:5432/polyllm" }, 7 dbCredentials: { url: process.env.DATABASE_URL ?? "postgres://localhost:5432/polyllm" },
8 strict: true, 8 strict: true,
added drizzle/0001_workspace_upgrade.sql +122 −0
@@ -0,0 +1,122 @@
1+CREATE TABLE "arena_votes" (
2+ "id" text PRIMARY KEY NOT NULL,
3+ "user_id" text NOT NULL,
4+ "session_id" text NOT NULL,
5+ "response_id" text NOT NULL,
6+ "model_key" text NOT NULL,
7+ "criterion" text NOT NULL,
8+ "category" text,
9+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
10+);
11+--> statement-breakpoint
12+CREATE TABLE "custom_endpoints" (
13+ "id" text PRIMARY KEY NOT NULL,
14+ "user_id" text NOT NULL,
15+ "name" text NOT NULL,
16+ "base_url" text NOT NULL,
17+ "encrypted_key" text,
18+ "key_hint" text,
19+ "encrypted_headers" text,
20+ "models_path" text DEFAULT '/models' NOT NULL,
21+ "manual_models" jsonb DEFAULT '[]'::jsonb NOT NULL,
22+ "status" text DEFAULT 'unverified' NOT NULL,
23+ "last_validated_at" timestamp with time zone,
24+ "last_validation_error" text,
25+ "last_latency_ms" integer,
26+ "models_available" integer,
27+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
28+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
29+);
30+--> statement-breakpoint
31+CREATE TABLE "project_files" (
32+ "id" text PRIMARY KEY NOT NULL,
33+ "user_id" text NOT NULL,
34+ "project_id" text,
35+ "kind" text NOT NULL,
36+ "name" text NOT NULL,
37+ "mime_type" text NOT NULL,
38+ "size_bytes" integer NOT NULL,
39+ "data_base64" text NOT NULL,
40+ "width" integer,
41+ "height" integer,
42+ "estimated_tokens" integer,
43+ "description" text,
44+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
45+);
46+--> statement-breakpoint
47+CREATE TABLE "projects" (
48+ "id" text PRIMARY KEY NOT NULL,
49+ "user_id" text NOT NULL,
50+ "name" text NOT NULL,
51+ "description" text,
52+ "icon" text,
53+ "color" text,
54+ "instructions" text,
55+ "preferred_model_keys" jsonb DEFAULT '[]'::jsonb NOT NULL,
56+ "default_settings" jsonb DEFAULT '{}'::jsonb NOT NULL,
57+ "notes" text,
58+ "archived" boolean DEFAULT false NOT NULL,
59+ "sort_order" integer DEFAULT 0 NOT NULL,
60+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
61+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
62+);
63+--> statement-breakpoint
64+CREATE TABLE "prompts" (
65+ "id" text PRIMARY KEY NOT NULL,
66+ "user_id" text NOT NULL,
67+ "project_id" text,
68+ "kind" text DEFAULT 'user' NOT NULL,
69+ "name" text NOT NULL,
70+ "description" text,
71+ "content" text NOT NULL,
72+ "variables" jsonb DEFAULT '[]'::jsonb NOT NULL,
73+ "schema" jsonb,
74+ "folder" text,
75+ "tags" jsonb DEFAULT '[]'::jsonb NOT NULL,
76+ "favorite" boolean DEFAULT false NOT NULL,
77+ "default_model_key" text,
78+ "uses" integer DEFAULT 0 NOT NULL,
79+ "last_used_at" timestamp with time zone,
80+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
81+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
82+);
83+--> statement-breakpoint
84+CREATE TABLE "shared_arena_sessions" (
85+ "id" text PRIMARY KEY NOT NULL,
86+ "session_id" text NOT NULL,
87+ "user_id" text NOT NULL,
88+ "snapshot" jsonb NOT NULL,
89+ "is_public" boolean DEFAULT true NOT NULL,
90+ "view_count" integer DEFAULT 0 NOT NULL,
91+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
92+ "revoked_at" timestamp with time zone
93+);
94+--> statement-breakpoint
95+CREATE TABLE "user_model_labels" (
96+ "user_id" text NOT NULL,
97+ "model_key" text NOT NULL,
98+ "label" text NOT NULL,
99+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
100+ CONSTRAINT "user_model_labels_user_id_model_key_pk" PRIMARY KEY("user_id","model_key")
101+);
102+--> statement-breakpoint
103+ALTER TABLE "conversations" ADD COLUMN "project_id" text;--> statement-breakpoint
104+ALTER TABLE "arena_votes" ADD CONSTRAINT "arena_votes_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
105+ALTER TABLE "custom_endpoints" ADD CONSTRAINT "custom_endpoints_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
106+ALTER TABLE "project_files" ADD CONSTRAINT "project_files_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
107+ALTER TABLE "project_files" ADD CONSTRAINT "project_files_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
108+ALTER TABLE "projects" ADD CONSTRAINT "projects_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
109+ALTER TABLE "prompts" ADD CONSTRAINT "prompts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
110+ALTER TABLE "prompts" ADD CONSTRAINT "prompts_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
111+ALTER TABLE "shared_arena_sessions" ADD CONSTRAINT "shared_arena_sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
112+ALTER TABLE "user_model_labels" ADD CONSTRAINT "user_model_labels_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
113+CREATE UNIQUE INDEX "arena_votes_session_criterion_uq" ON "arena_votes" USING btree ("session_id","criterion");--> statement-breakpoint
114+CREATE INDEX "arena_votes_user_model_idx" ON "arena_votes" USING btree ("user_id","model_key");--> statement-breakpoint
115+CREATE INDEX "custom_endpoints_user_idx" ON "custom_endpoints" USING btree ("user_id");--> statement-breakpoint
116+CREATE INDEX "project_files_user_idx" ON "project_files" USING btree ("user_id");--> statement-breakpoint
117+CREATE INDEX "project_files_project_idx" ON "project_files" USING btree ("project_id");--> statement-breakpoint
118+CREATE INDEX "projects_user_idx" ON "projects" USING btree ("user_id");--> statement-breakpoint
119+CREATE INDEX "prompts_user_idx" ON "prompts" USING btree ("user_id");--> statement-breakpoint
120+CREATE INDEX "prompts_user_folder_idx" ON "prompts" USING btree ("user_id","folder");--> statement-breakpoint
121+CREATE INDEX "shared_arena_session_idx" ON "shared_arena_sessions" USING btree ("session_id");--> statement-breakpoint
122+CREATE INDEX "conversations_user_project_idx" ON "conversations" USING btree ("user_id","project_id");
\ No newline at end of file
added drizzle/meta/0001_snapshot.json +3661 −0
Large diff (3,662 lines) is not loaded by default to keep the page fast.Load diff
modified drizzle/meta/_journal.json +7 −0
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
modified package.json +2 −1
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
added public/apple-icon.png +0 −0

Binary file not shown.

added public/brand/icon-maskable.svg +1 −0
Large diff (1 lines) is not loaded by default to keep the page fast.Load diff
added public/brand/og.svg +32 −0
Large diff (32 lines) is not loaded by default to keep the page fast.Load diff
added public/favicon-16.png +0 −0

Binary file not shown.

added public/favicon-32.png +0 −0

Binary file not shown.

added public/favicon.ico +0 −0

Binary file not shown.

added public/icon-192.png +0 −0

Binary file not shown.

added public/icon-512.png +0 −0

Binary file not shown.

added public/icon-maskable-512.png +0 −0

Binary file not shown.

modified public/icon.svg +1 −1
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
added public/og.png +0 −0

Binary file not shown.

added scripts/brand-assets.sh +20 −0
Large diff (20 lines) is not loaded by default to keep the page fast.Load diff
modified src/app/api/models/route.ts +16 −6
Large diff (35 lines) is not loaded by default to keep the page fast.Load diff
modified src/app/globals.css +178 −41
Large diff (339 lines) is not loaded by default to keep the page fast.Load diff
modified src/app/layout.tsx +21 −5
Large diff (45 lines) is not loaded by default to keep the page fast.Load diff
added src/app/manifest.ts +30 −0
Large diff (30 lines) is not loaded by default to keep the page fast.Load diff
added src/components/app/bottom-nav.tsx +52 −0
Large diff (52 lines) is not loaded by default to keep the page fast.Load diff
modified src/components/app/shell.tsx +76 −23
Large diff (149 lines) is not loaded by default to keep the page fast.Load diff
modified src/components/app/sidebar.tsx +155 −108
Large diff (410 lines) is not loaded by default to keep the page fast.Load diff
modified src/components/app/store.tsx +38 −2
Large diff (125 lines) is not loaded by default to keep the page fast.Load diff
modified src/components/brand/logo.tsx +44 −15
Large diff (79 lines) is not loaded by default to keep the page fast.Load diff
added src/components/common/prompt-dialog.tsx +86 −0
Large diff (86 lines) is not loaded by default to keep the page fast.Load diff
added src/components/library/file-library-picker.tsx +110 −0
Large diff (110 lines) is not loaded by default to keep the page fast.Load diff
added src/components/projects/sidebar-section.tsx +46 −0
Large diff (46 lines) is not loaded by default to keep the page fast.Load diff
modified src/components/ui/misc.tsx +5 −0
Large diff (8 lines) is not loaded by default to keep the page fast.Load diff
added src/components/ui/segmented.tsx +90 −0
Large diff (90 lines) is not loaded by default to keep the page fast.Load diff
added src/components/ui/sheet.tsx +265 −0
Large diff (265 lines) is not loaded by default to keep the page fast.Load diff
modified src/db/index.ts +4 −1
Large diff (13 lines) is not loaded by default to keep the page fast.Load diff
added src/db/schema-workspace.ts +201 −0
Large diff (201 lines) is not loaded by default to keep the page fast.Load diff
modified src/db/schema.ts +3 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added src/lib/ai/registry/user-models.ts +22 −0
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
added src/lib/client/hooks.ts +295 −0
Large diff (295 lines) is not loaded by default to keep the page fast.Load diff
added src/lib/client/router.ts +215 −0
Large diff (215 lines) is not loaded by default to keep the page fast.Load diff
added src/lib/client/tokens.ts +90 −0
Large diff (90 lines) is not loaded by default to keep the page fast.Load diff
modified src/lib/client/types.ts +3 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
Menu

Theme