// project context
Project Context
The full briefing for WorklogLM — what it is, the stack, every route, the database and the open items. Paste it into an LLM to get instant context.
PROJECT_CONTEXT.md
# WorklogLM — Project Context
Single-source briefing for developers (and for pasting into an LLM). Describes what the app
is, how it is built, what exists today, and what is still open.
---
## 1. What the app is
WorklogLM is a work-logging system for a team/company. People log what they built or learned
("shipped a course on Ollama Qwen 3 in Godot"), tagged with date/time, tools used, status, an
optional code snippet and attachments. Those entries roll up into:
- a **timeline** of who did what, when;
- **heatmaps** of activity per person and for the whole company;
- an **analytics dashboard** (contributions, tools, categories, time-of-day patterns, rankings);
- a **master file** — a single high-level text/markdown/document export of everything the team
has done, primarily for marketing and for feeding into an LLM;
- **public profiles** so an individual can share their own log publicly.
Two data layers currently coexist (see §5): an original localStorage demo layer that powers the
company-wide public pages, and a Supabase layer that powers accounts, personal logs, projects
and teams.
## 2. Stack
- **TanStack Start v1** (React 19, SSR) on **Vite 7** — file-based routing in `src/routes`.
- **Tailwind CSS v4** via `src/styles.css` (`@theme` tokens, no `tailwind.config.js`).
- **shadcn/ui** components in `src/components/ui`, `sonner` for toasts.
- **Supabase** (external project, ref `xufhtugscxcprsnfvjhb`) for auth + Postgres + RLS.
- Dark, terminal/engineering-log aesthetic: mono type for metadata, semantic color tokens only.
## 3. Routes
### Public (no login)
| Route | File | Purpose |
| --- | --- | --- |
| `/` | `routes/index.tsx` | Marketing homepage: hero (uploaded illustration), numbered feature grid, "How it works", "Who it's for", pricing (Free individual / Mammoth Club / Teams waitlist), FAQ, Organization + FAQPage JSON-LD. |
| `/demo` | `routes/demo.tsx` | Company dashboard demo: KPIs, company + per-person heatmaps, recent timeline, master-file preview. Read-only demo mode. |
| `/timeline` | `routes/timeline.tsx` | Vertical center-axis timeline; cards alternate left/right, expand for a summary, click for full detail. Filters: person, tool, free-text search. |
| `/master` | `routes/master.tsx` | Master file. Two views — **Text** (`.txt` / `.md` toggle) and **Document** (editorial layout with a "On this page" TOC and per-project sections). Copy button on each, plus download. No code snippets — high level only. |
| `/analytics` | `routes/analytics.tsx` | Visual dashboard: radial pulse dial, KPI band, SVG area "Shipping Pulse", category donut, tool constellation bubbles, day×hour punchcard, 24h radial clock, contributor leaderboard with sparklines, versatility/streak/momentum rankings. Range filter: all / 30d / 90d / 6m / 12m with adaptive bucketing. |
| `/new` | `routes/new.tsx` | Demo-mode log form that mirrors the real authenticated form exactly: title, date, tools, project (with inline "new project"), optional status, summary, details, code, visibility, team, image/document attachments (≤3MB each). Includes the AI paste panel. Submitting shows a toast instead of writing. |
| `/edit/$id` | `routes/edit.$id.tsx` | Edit an existing entry; records `updatedAt` / `updatedBy` and an edit-history log. |
| `/people` | `routes/people.tsx` | Manage the demo roster of employees. |
| `/auth` | `routes/auth.tsx` | Email+password sign-in/sign-up, Google OAuth (via the Lovable broker), password reset, email verification. |
| `/reset-password` | `routes/reset-password.tsx` | Set a new password from the reset link. |
| `/u/$username` | `routes/u.$username.tsx` | Public profile: bio, contribution heatmap, skills derived from tools, public projects and logs. |
| `/sitemap.xml` | `routes/sitemap[.]xml.ts` | Server-generated sitemap for all public routes. |
### Authenticated (`src/routes/_authenticated/`, gated by the managed `route.tsx`, `ssr: false`, redirects to `/auth`)
| Route | File | Purpose |
| --- | --- | --- |
| `/onboarding` | `onboarding.tsx` | 4-step wizard: username, profile, usage mode / main goal, finish. |
| `/dashboard` | `dashboard.tsx` | Personal log: stats, streak, contribution calendar, quick add. |
| `/logs` | `logs.tsx` | Create/manage own work logs: project, team, status, visibility, delete, "Copy AI context". |
| `/projects` | `projects.tsx` | Create/manage projects that group logs. |
| `/teams` | `teams.index.tsx` | Create teams, accept pending invites. |
| `/teams/$teamId` | `teams.$teamId.tsx` | Team workspace: team heatmap, activity feed, member roster with owner/admin/member roles, email invites. |
| `/settings` | `settings.tsx` | Full profile edit, public-profile and available-for-work toggles. |
| `/settings/ai-usage` | `settings_.ai-usage.tsx` | AI credits & usage: allowance, remaining, purchased, next renewal, used this period, recent AI actions (status / cost / model / date), exhaustion notice + Mammoth Club CTA. |
`src/routes/__root.tsx` holds the shell: head metadata (including the Google Search Console
`google-site-verification` tag), the SaaS-style header, the `<Toaster />`, and a single
`supabase.auth.onAuthStateChange` subscriber that invalidates the router and query cache on
`SIGNED_IN` / `SIGNED_OUT` / `USER_UPDATED` only. Header behaviour:
- **Logged out** — a "Live demo" dropdown (all dummy views), `Sign in`, and a high-contrast
`Start free` CTA.
- **Logged in** — operational nav (My log, Entries, Projects, Teams), a prominent `+ Log work`
button, and an avatar menu.
- **Mobile** — a Sheet menu with grouped sections; all views are responsive single-column.
## 4. Library modules
| File | Role |
| --- | --- |
| `lib/store.ts` | localStorage-backed store (`useSyncExternalStore`) for demo employees + submissions. Returns an empty server snapshot so SSR and client agree. |
| `lib/demo-data.ts` | 5 seeded people and 25 historical submissions (Godot, Ollama, Python, frontend) so heatmaps/timeline/analytics look real. |
| `lib/master-file.ts` | `buildText` and `buildMarkdown` generators for the master file. Deliberately excludes code snippets. |
| `lib/work-logs.ts` | Supabase CRUD for `work_logs` and `projects`, public read helpers, `heatItems`, `streakDays`, `logToText` (the AI-context format), `slugify`, `parseTools`, status/visibility option lists. |
| `lib/teams.ts` | Supabase helpers for teams, membership, roles and the invite/accept flow. |
| `lib/profiles.ts` | Profile fetch/update and username normalisation. |
| `lib/format.ts` | Deterministic **UTC** date/time formatting — required to avoid SSR hydration mismatches. |
| `lib/ai-autofill.ts` | `extractDraft` — placeholder parser turning pasted code/notes/docs into title, summary, details, code and tools. Same shape the real model call will return. |
| `lib/ai-stub.ts` | Placeholder AI helpers behind the inline buttons ("Suggest tools", "Polish"). |
| `lib/public-profile.functions.ts` | Server functions for server-rendered public profiles. |
| `lib/track.ts` | Lightweight event tracking helper. |
| `lib/ai-actions.ts` | Single source of truth for AI action types, labels and credit costs (`AI_ACTIONS`), plus `CreditBalance` / `AiUsageEvent` types. |
| `lib/entitlements.ts` | Typed plans (`free` 25, `mammoth_club` 250, `team` configurable) and `AI_RATE_LIMITS` (10/min, 100/day). `resolvePlanId` is the single hook billing plugs into later. |
| `lib/ai-credits.server.ts` | Server-only credit ledger: `ensureCreditBalance`, `resetMonthlyCreditsIfNeeded`, `getCreditBalance`, `canSpendCredits`, `checkRateLimit`, `recordSuccessfulUsage`, `recordFailedUsage`, `refundUsage`, `getRecentUsage`, `usedThisPeriod`. All mutations use the service-role client; costs come from `AI_ACTIONS`, never from the browser. |
| `lib/ai-credits.functions.ts` | `getMyAiCredits` / `getMyAiUsage` server functions (auth-gated readers). |
| `lib/ai.functions.ts` | `autofillLogDraft` — auth + rate limit + credit check + charge-once-per-`requestKey` wrapper around the gateway call. |
| `hooks/use-ai-credits.ts` | `useAiCredits()` query used by the badge and panels. |
| `components/ai-credit-badge.tsx` | Small nav/panel indicator: `AI 18 / 25` (+purchased), links to `/settings/ai-usage`. |
| `components/ai-paste-panel.tsx` | "Let AI write the log" paste box that fills the form via `extractDraft`. |
| `components/vector-art.tsx` | Neon-green/white abstract technical SVG art. Human figures were replaced with abstract geometric marks; hero, "How it works" and "Who it's for" panels now use uploaded illustrations served from the CDN. |
| `components/heatmap.tsx` | Generic GitHub-style calendar; accepts any `{ createdAt: string }[]`, all grid math in UTC. |
| `hooks/use-auth.ts` | `session`, `user`, `loading`, `isAuthenticated` from `onAuthStateChange` + `getSession`. |
## 5. Database (Supabase, public schema)
App-owned tables:
- **`profiles`** — `id` (= `auth.users.id`), `username`, `display_name`, `avatar_url`, job/company/bio/location, social URLs, `is_public`, `available_for_work`, `work_type`, `usage_mode`, `main_goal`, `onboarding_completed`. Auto-created by the `handle_new_user()` trigger. Readable by everyone only when `is_public = true`; otherwise owner-only.
- **`projects`** — `user_id`, `name`, `slug`, `description`, `category`, `color`, `status` (`log_status`), `visibility` (`visibility`), optional `team_id`.
- **`work_logs`** — `user_id`, optional `project_id` / `team_id`, `title`, `summary`, `details`, `code_snippet`, `tools text[]`, `category`, `status`, `visibility`, `occurred_at`.
- **`ai_credit_balances`** — `user_id` (PK → `profiles.id`), `monthly_allowance` (default 25), `monthly_used`, `purchased_credits`, `period_started_at`, `period_renews_at`. Owner-read only; no client write policy — all mutations go through the service role.
- **`ai_usage_events`** — `user_id`, optional `team_id`, `action_type`, `credit_cost`, `provider`, `model`, `input_tokens`, `output_tokens`, `status` (`succeeded | failed | rate_limited | refunded`), `request_key`, `error_code`, `metadata`. Unique `(user_id, request_key)` makes retries free; indexes on `(user_id, created_at desc)`, `(team_id, created_at desc)`, `(action_type, created_at desc)`. Owner-read only.
- **`teams`**, **`team_members`** (role `owner|admin|member`), **`team_invites`** (token, expiry, `accepted_at`).
Enums: `log_status` = `in_progress | shipped | blocked | planned`; `visibility` = `private | team | public`; `team_role` = `owner | admin | member`.
Security-definer helpers used inside RLS to avoid recursion: `is_team_member(team_id, user_id)`,
`has_team_role(team_id, user_id, roles[])`. Plus `handle_new_user()`,
`add_team_owner_membership()`, `update_updated_at_column()`.
**Visibility model** (enforced by RLS, not the UI): owners always see their own rows;
`visibility = 'public'` rows are readable by anyone; `visibility = 'team'` rows are readable by
members of the row's `team_id`. Everything else is private.
Pre-existing, unrelated tables from an earlier project also live in this database:
`courses`, `course_content`, `podcast_content`, `conversations`, `chat_messages`. The first
three still have permissive `USING (true)` write policies — a known outstanding item, untouched
because nothing in WorklogLM reads them.
## 6. Server-side conventions
- App-internal server logic uses `createServerFn` from `@tanstack/react-start`; **no Supabase
Edge Functions** on this stack. External callers (webhooks/cron) would go under
`src/routes/api/public/*` with signature verification in the handler.
- Browser reads use the generated `@/integrations/supabase/client` (RLS as the signed-in user).
`client.server.ts` (service role) is server-only and must never be imported by components.
- `src/start.ts` registers the bearer-token `functionMiddleware` so protected server functions
receive the session token.
## 7. Build history (what has been done, in order)
1. **Initial build** — localStorage store, submission form, company dashboard, heatmap component, master-file generator, dark engineering-log design.
2. **Demo data** — 25 seeded submissions across 5 people, plus the first timeline.
3. **Timeline as a tab** — rebuilt as a vertical center axis with alternating cards, expandable summaries and click-through detail; person/tool/search filters.
4. **Master file as a tab** — dedicated `/master` page with preview, copy and download.
5. **Master file formats** — `.txt` / `.md` toggle; code snippets removed to keep it high-level and marketing-friendly.
6. **Document view** — second master-file view: editorial one-pager with an auto-generated TOC and per-project sections, each view with its own Copy button.
7. **Analytics tab** — comprehensive metrics: KPIs, tools, contributions, categories, monthly volume, weekday/hour patterns, person×category matrix, versatility/streak/momentum rankings.
8. **Analytics visual rebuild** — custom SVG visualisations (radial dial, area chart, donut, tool constellation, punchcard, radial clock, sparkline leaderboard).
9. **Date ranges** — all / 30d / 90d / 6m / 12m filter with adaptive bucketing and range-relative sparklines.
10. **Rename to WorklogLM** — wordmark, head metadata and export headers.
11. **Accounts (Phase 1a)** — Supabase auth, `profiles` + trigger, `/auth`, `/reset-password`, 4-step `/onboarding`, personal `/dashboard`, `/settings`, UTC formatting helpers to kill hydration errors.
12. **Supabase data layer (Phase 1b)** — `projects` and `work_logs` tables with RLS and the status/visibility enums, `lib/work-logs.ts`, `/logs`, `/projects`, public profiles at `/u/$username`, "Copy AI context".
13. **Stability pass** — hydration fixes on `/`, UTC-safe heatmap grid, empty SSR snapshot in the store.
14. **Team workspaces (Phase 1c)** — `teams` / `team_members` / `team_invites` with RLS and helper functions, `lib/teams.ts`, `/teams` and `/teams/$teamId`, `team_id` on logs and projects, Teams nav link, team reassignment on existing logs.
15. **Mobile pass** — responsive shell with a Sheet menu, single-column timeline and analytics.
16. **Attachments + AI buttons** — multi-file upload (images, PDF, MD) on the log form, plus placeholder AI actions via `lib/ai-stub.ts`.
17. **Edit + audit trail** — `/edit/$id`, `updatedAt` / `updatedBy` and an edit-history log.
18. **Inline project creation** — create a project from inside the log form; status became optional.
19. **Security pass 1** — migration hardening legacy-table RLS and `SECURITY DEFINER` functions.
20. **Marketing homepage + demo mode** — `/` became the sales page, the company dashboard moved to `/demo`, and all dummy views became non-destructive demo mode.
21. **High-volume demo data** — ~780 entries across 8 developers, multiple entries per day, grouped behind expandable day dots on the timeline.
22. **Brand + art** — WorklogLM logo (also the favicon), neon-green vector art, then uploaded hero / how-it-works / who-it's-for illustrations referenced by direct CDN URL (route code-splitting drops JSON asset imports).
23. **AI paste-and-autofill** — `lib/ai-autofill.ts` + `components/ai-paste-panel.tsx`; placeholder parser until API keys are added.
24. **Header rebuild** — SaaS-grade nav for both logged-out and logged-in states.
25. **SEO pass** — `public/robots.txt`, dynamic `/sitemap.xml`, Organization + FAQPage JSON-LD, `og:url` and tuned titles/descriptions on public routes, descriptive image alt text, GSC meta verification tag in `__root.tsx`.
26. **Timeline polish** — day marker no longer overlaps the date pill; rainbow dots replaced with stacked contributor chips plus an "N people" label.
27. **Demo log form parity** — `/new` rebuilt to match the authenticated form field-for-field.
28. **Unified data layer (Phase 1).** Removed the split between the localStorage demo store and
Supabase for the shared read views. New `lib/worklog-data.ts` exposes a `WorklogScope`
(`demo` | `personal` | `team` | `public-user` | `public-team`), normalises Supabase
`work_logs` / `projects` / `profiles` rows into one `WorklogEntry` / `Contributor` shape, and
serves them via `useWorklogDataset(scope)` (TanStack Query) plus `useWorklogScope()` for
scope selection (only the UI preference is stored locally, never records). All aggregation
moved into `lib/worklog-analytics.ts` (`RANGE_OPTIONS`, `filterByRange`, `groupByDay`,
`categorize`, `computeStats`, streaks, `employeeName`). `components/scope-picker.tsx` lets a
signed-in user switch between My work, each team, and the read-only demo dataset. `/timeline`,
`/analytics`, `/master` and `/demo` now read exclusively through this layer; `/new`,
`/edit/$id` and `/people` still use the legacy local store for the non-destructive demo flows.
29. **Unified write path (Phase 2).** New `lib/worklog-write.ts` owns every mutation: a single
`WorklogDraft` shape (title, date, tools, project or inline new project, optional status,
summary, details, code snippet, visibility, team) plus `saveWorklogEntry`,
`updateWorklogEntry`, `fetchLogById` and `deleteWorklogEntry`, all resolving inline project
creation through `lib/work-logs.ts`. `/new` now persists to Supabase for signed-in users
(their real projects and teams populate the selects) and keeps the non-destructive
"sign up to keep this log" flow for visitors. `/edit/$id` was rebuilt on the same draft
shape: signed-in users load the real `work_logs` row (created / last-edited stamps, save and
delete); visitors get a read-only demo card. `/logs` rows gained an Edit link and every write
there invalidates the shared `worklog` query cache so timeline, analytics and the master file
stay in sync. Attachments remain preview-only until a storage bucket exists.
30. **Attachments (Phase 3).** Private Supabase Storage bucket `worklog-attachments` plus
`log_attachments` (log_id, user_id, kind, file_name, mime_type, size_bytes, storage_path) with
RLS: owners manage their own, team-visible logs expose files to team members, public logs to
everyone. Storage policies key off the first folder segment (`<userId>/<logId>/<file>`).
`lib/attachments.ts` handles upload, listing, signed URLs (bucket is private) and delete.
`/new` now uploads picked files right after the log row is created (10MB each for signed-in
users, demo stays preview-only); `/edit/$id` lists existing files with thumbnails, open links
and delete, and uploads newly picked ones on save.
31. **People on the unified layer (Phase 4).** `/people` no longer touches the legacy local store.
It now reads through `useWorklogScope` + `useWorklogDataset`, so it shows the signed-in user's
own work, any team they belong to, or the read-only demo workspace via the shared
`ScopePicker`. Each contributor row aggregates logs, projects, active days, current streak,
top tools and dominant category for the selected range, with a search box (name / role / tool)
and an expandable panel containing that person's contribution heatmap and latest entries.
Manual add/remove of fake people was dropped — contributors come from real profiles and team
rosters.
32. **Attachments on read views (Phase 5).** New storage policies let team members open files on
team-visible logs and anyone open files on public logs (owners unchanged), matching the
existing `log_attachments` row policies. `lib/attachments.ts` gained batched readers
(`listAttachmentsForLogs`, `signedUrlMap`, `attachmentsByLog`), and the unified data layer now
fills each entry's `files` with signed URLs, so `/timeline` renders image thumbnails and
document download chips in the detail panel. `lib/public-profile.functions.ts` resolves signed
URLs server-side for public logs and `/u/$username` shows those thumbnails inline.
33. **Real AI autofill (Phase 6).** `lib/ai.functions.ts` exposes an `autofillLogDraft` server
function that reads `LOVABLE_API_KEY` inside its handler and calls the Lovable AI Gateway
Responses API (`openai/gpt-5.6-sol`) through `lib/ai.server.ts` with a strict `json_schema`
format, so the model returns exactly `{ title, summary, details, code, tools }`. `AiPastePanel` now calls it via
`useServerFn` and only falls back to the local heuristic parser in `lib/ai-autofill.ts` when the
key is missing or the request fails (rate limit / credits / network), telling the user which
path ran. Applies to both `/new` (demo) and `/logs` (authenticated).
34. **Own OpenAI key stored.** `OPENAI_API_KEY` now exists as a server-side project secret (never
exposed to the browser). Autofill still runs through the Lovable AI Gateway with
`LOVABLE_API_KEY`; switching `lib/ai.server.ts` to call OpenAI directly with the personal key
is an open option, not yet wired.
35. **AI credits, usage tracking and rate limits (Phase 7).** Migration
`20260805181818_8f779aa4-3b96-4936-9020-1cc01990febb.sql` added `ai_credit_balances` and
`ai_usage_events` (grants + RLS: owner SELECT only, no client writes). `lib/ai-actions.ts` is
the only place credit costs live; `lib/entitlements.ts` defines Free (25/mo), Mammoth Club
(250/mo) and Team plus the rate limits (10/min, 100/day per user). `lib/ai-credits.server.ts`
owns the ledger — monthly reset, monthly-before-purchased spend order, charge-once per
`request_key`, no charge on failure, refunds, recent usage. `autofillLogDraft` now requires auth
and returns the updated balance with every result; logged-out visitors keep the local heuristic
parser labelled as demo assistance and never hit the paid model. The header and AI paste panel
show a small `AI n / 25` badge, `/settings/ai-usage` shows the full picture, and exhaustion
degrades gracefully (manual logging + local parser, renewal date, one Mammoth Club CTA, no
modals). Tests live in `src/lib/__tests__` (vitest): cost registry, spend split, monthly reset,
single deduction, idempotent retries, free failures, purchased-after-monthly, exhaustion,
per-user usage scoping, rate limiting, and demo autofill making no network call.
### Phase 8 — App shell redesign (rail + bottom nav)
`src/components/app-shell.tsx` is the terminal-styled application shell: a fixed left rail on
desktop (logo, `+ Log work` primary action, `Workspace` group for signed-in users, `Read views` /
`Live demo` group, AI credit badge, account menu, collapse toggle persisted in `localStorage`
under `workloglm.rail-collapsed`) and a mobile layout with a slim top bar plus a five-slot fixed
bottom nav with a raised centre `Add` action. `src/routes/__root.tsx` now picks the chrome per
route: marketing/auth-style paths (`/`, `/auth*`, `/onboarding`, `/context`, `/reset-password`,
`/u/*`) keep `SiteHeader`; everything else renders inside `AppShell`.
### Phase 9 — Projects × teams
`work-logs.ts` gained `listVisibleProjects(userId, teamIds)` (own projects OR projects shared with
any team I belong to) and `countLogsByProject(ids)`. `/projects` now loads my teams first, shows a
scope filter row (All / Personal / one chip per team, each with a count), lets a project be created
under Personal or a team via an `Owner` select, badges each card with its owner scope, and allows
re-assigning a project between Personal and any of my teams inline. Team detail pages already list
`listTeamProjects`.
### Phase 10 — Team activity + roles
`lib/teams.ts` gained a role model that the UI reads instead of ad-hoc checks: `teamPermissions(role,
isOwner)` returns `canInvite / canManageRoles / canPromoteToAdmin / canRemoveMembers / canEditTeam /
canDeleteTeam`, and `canActOnMember(actorRole, targetRole, isOwner)` enforces the rank order (owners
outrank admins, admins only act on members, nobody can act on the owner). It also gained
`listTeamLogsWithAuthors(teamId)` (team logs joined to author profiles) and `memberActivity(members,
logs)` which rolls up per-member totals, shipped count, logs in the last 7 days, last activity date
and top three tools.
`/teams/$teamId` now shows the viewer's own role (and flags read-only membership), a "Who did what"
activity board with a bar per member that doubles as a filter for the team feed, author names in the
feed, and role controls that respect the rank rules — only owners can promote to admin, admins can
only manage members, and members see plain role badges with no controls.
## 8. Known gaps / next steps
- **Billing.** Entitlements are typed and wired behind `resolvePlanId`, but no payment provider is
connected — everyone resolves to Free (25 AI credits/month) and `purchased_credits` has no
purchase flow yet.
- **Marketing shell.** The app shell (rail + bottom nav) covers app/read routes; the marketing
home, `/auth`, `/onboarding`, `/context` and public profiles still use the top header bar.
- **Google OAuth** requires the provider to be enabled in the Supabase dashboard (external
project) and the app URL added to the redirect allowlist.
- **Legacy tables** (`courses`, `course_content`, `podcast_content`) have permissive write
policies that should be tightened or the tables dropped.
- **Supabase dashboard settings** (not changeable from code): enable leaked-password protection,
reduce email OTP expiry to ≤3600s, upgrade Postgres to the patched version.
- **Google Search Console** verification and sitemap submission are blocked until the site is
published with the verification meta tag live.
## 9. Working rules for anyone (or any LLM) continuing this project
- Never add `react-router-dom` or another router — routing is TanStack Router, file-based.
- All colors go through semantic tokens in `src/styles.css`; no `text-white` / `bg-[#...]`.
- Any date rendered during SSR must use the UTC helpers in `lib/format.ts`.
- Schema changes go through migrations; every new public-schema table needs `GRANT`s plus RLS
policies in the same migration.
- Auth-required pages live under `src/routes/_authenticated/`; public shareable pages stay
top-level with SSR on and no auth gate.
- **Keep this file current.** Every meaningful change (new route, schema change, design or
feature pass) is appended to §7 and reflected in §3–§6 as part of the same work.