// 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.

A single unified data layer serves every read view (see §5). `lib/worklog-data.ts` normalises
Supabase `work_logs` / `projects` / `profiles` rows into one `WorklogEntry` / `Contributor` shape,
selected by a `WorklogScope` (`demo` | `personal` | `team` | `public-user` | `public-team`). The
legacy localStorage demo store in `lib/store.ts` remains only for visitor demo-mode previews where
no account exists.

## 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. |
| `/dev-login` | `routes/dev-login.tsx` | One-click sign-in for QA test accounts (noindex). |
| `/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. |
| `/context` | `routes/context.tsx` | Public page displaying the full `PROJECT_CONTEXT.md` with copy/download. |

### 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` | 8 seeded people and ~780 historical submissions so heatmaps, timeline and analytics look like a real week of work. |
| `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.

### Phase 11 — Example public profile template (`/u/demo`)

The public profile markup moved out of `routes/u.$username.tsx` into
`components/public-profile-view.tsx` (`PublicProfileView`), which takes a `PublicProfilePayload`
plus optional `banner` / `footer` slots. `routes/u.$username.tsx` is now just loader + SEO head +
empty states, and its "not found" state links to the example profile.

`lib/demo-profile.ts` derives a deterministic sample payload (Alex Chen, 40 logs, 6 projects) from
`DEMO_SUBMISSIONS` — no database access — and `routes/u.demo.tsx` renders it with an "example
profile" banner and "Claim your profile" CTAs. Because it is a static route it takes precedence over
`/u/$username`. It is listed in `sitemap.xml` and linked from the homepage feature grid and profile
preview card so visitors can see a real profile before signing up.

### Phase 12 — Timeline as the primary surface

`/timeline` is now the default destination everywhere: sign-in, sign-up, onboarding completion and
password reset all redirect there instead of `/dashboard`; it is the first item in the desktop rail
workspace group (moved out of the "read" group), the first slot in both mobile bottom navs, the
first entry in the marketing header's app links, and the primary homepage hero CTA
("See the live timeline"). `/dashboard` remains as the personal "My log" view.

### Phase 13 — Test accounts

Three confirmed QA accounts exist in Supabase Auth (password `WorklogTest!2026`):
`owner@test.workloglm.com` (`testowner`), `admin@test.workloglm.com` (`testadmin`),
`member@test.workloglm.com` (`testmember`). All have completed onboarding and public profiles.
They share the "Test Team" (`test-team`) with owner/admin/member roles, a team project
"QA Sandbox", and three seeded team-visible work logs. Also: Timeline is now pinned to the top of
the sidebar for guests as well as signed-in users.

### Phase 14 — Adding people to a team (bulk)

The team page's invite card became an "Add people" card: paste any number of emails (commas,
spaces or new lines — `src/lib/emails.ts` parses and dedupes them), pick Member/Admin, then either
**Send invites** (rows in `team_invites`, claimed on the invitee's Teams page) or **Create
accounts** (accounts made directly, no signup, one-time passwords shown once in the UI).
Account creation runs in `addTeamPeople` (`src/lib/team-people.functions.ts` →
`team-people.server.ts`), which re-checks `has_team_role(owner|admin)` for the caller before using
the service-role client, creates confirmed users with a display name from the email local part, and
upserts `team_members`. Existing accounts are added to the team instead of failing.
Sign-in fix: the `/auth` submit button stays disabled until the page hydrates, so an early click
can no longer trigger a plain form GET that silently clears the form.

### Phase 15 — Optional timeline thumbnails

`TimelineView` gained a **thumbnails on/off** toggle in its header (preference in
`localStorage` under `worklog:timeline-thumbs`, default **off**, so the original text-only timeline
is unchanged). With thumbnails on, collapsed day rows show a small 12×8 tile and zoomed snippet
cards render the thumbnail **inside** the card box as a 96px full-width banner at the top; entries without
an image get a dashed "no thumbnail" placeholder. A log's thumbnail is simply its first image
attachment (`src/lib/timeline-thumbnails.ts`: `entryThumbnail`, `useThumbnailPref`,
`uploadThumbnail`). Owners of an entry get an **add/change thumb** control on the snippet card and in
the detail panel, which uploads an image straight into `worklog-attachments` for that log and
refetches the dataset. `/timeline` passes `currentUserId` and an `onEntryChanged` refetch; read-only
surfaces (public profile, demo) pass neither, so they only display thumbnails.

### Phase 16 — Solo test accounts

Expanded QA roster beyond the Test Team with two standalone individuals:
`solo@test.workloglm.com` (`testsolo`, Sam Rivera — indie dev) and
`indie@test.workloglm.com` (`testindie`, Priya Nair — freelance designer). Both have completed
onboarding, public profiles, personal projects and sample logs (some private, some public).

### Phase 17 — One-click test role sign-in

`/dev-login` (noindex) lists all QA accounts with a single button each: it signs out any current
session, signs in with the shared test password and redirects to `/timeline`. Linked from `/auth`.
Accounts are grouped into "team roles" and "individuals".

### Phase 20 — Second owner QA account

Added `mammoth@test.workloglm.com` (`testmammoth`, Test Mammoth) as owner of a second sandbox team
**Test Mammoth** (`test-mammoth`), so owner-only flows can be tested without touching Test Team data.
`/dev-login` now shows a third group, "team roles — test mammoth".

### Phase 21 — Email infrastructure

Sending domain `notify.workloglm.com` (display From `workloglm.com`) registered with Lovable's
managed email service; DNS delegation pending. App email plumbing lives in
`src/lib/email-templates/` (`registry.ts`, server-only `send-email.ts`) with the preview route at
`src/routes/lovable/email/transactional/preview.ts`. No queue, cron, or email tables — sends go
through `sendTemplateEmail` from server code only. `vite.config.ts` pins the `entities` resolution
to the hoisted v4.5.0 copy for React Email SSR.

### Phase 22 — Invite and credentials emails

Two branded React Email templates registered in `src/lib/email-templates/registry.ts`:
`team-invite` (who invited you, team, role, accept button) and `team-credentials` (temporary
email/password for admin-created accounts). Both use a white email body with neon-green CTA and
monospace headings. `addPeopleToTeam` in `src/lib/team-people.server.ts` sends them after the
invite row / account is written — invite mode sends `team-invite`, create mode sends
`team-credentials` for new accounts and `team-invite` for existing ones — keyed idempotently per
invite/user. Delivery problems never fail the add: each result carries `emailStatus`
(`sent` / `skipped` for suppressed recipients / `failed`), surfaced next to each address in the
add-people results list on `/teams/$teamId`.

### Phase 18 — Team workspace, SaaS-grade layout

`/teams/$teamId` was rebuilt on standard SaaS patterns: breadcrumb, page header with team monogram,
role badge, overlapping member avatar stack, primary **Invite people** dialog and an overflow menu
(all teams / delete or leave). A four-cell metric strip (members, logs, projects, this week) sits
above tabs: **Overview** (activity heatmap + contribution leaderboard), **Members** (roster table
with inline role selects, per-row overflow remove, `#add-people` invite card, pending invites),
**Activity** (team feed, filterable by member from the leaderboard) and **Settings** (team details +
danger zone). Invite form is shared between the dialog and the Members tab card.

### Phase 19 — Compact teams list + rail sub-menus

`/teams` index was compacted: the "New team" form collapses behind a "+ New team" toggle and team
cards lead with an "Add people" button. `src/components/app-shell.tsx` gained expandable rail
branches for **Projects** and **Teams**; the Teams branch lists the signed-in user's actual teams as
sub-items.

### Phase 20 — Aug 14 issue report fixes

Auth: signup now sets `emailRedirectTo` to a new public `/auth-callback` route which reads both
hash and query params, establishes the session and forwards to onboarding or the timeline (kills
the 404 / `localhost:3000` loop).

Log form (`/logs`): the redundant title field is gone — the one-line "What did you work on?" feeds
both `title` and `summary`. Project, status, visibility, team and type are remembered in
`localStorage`, a free-text **Type** field with suggestions (`ENTRY_TYPE_SUGGESTIONS`) writes
`work_logs.entry_type`, entries can be filtered by type, Edit is a solid secondary button and
Delete goes through a confirm dialog. `/edit/$id` edits `entry_type` too.

Projects: optional `start_date` / `end_date` (shown via `fmtTimeframe`, newest timeframe first) and
a contributor count per project (`countContributorsByProject`) so shared team projects read clearly.

Dates: `localDayKey()` in `lib/format.ts` anchors heatmaps, streaks and "today's activity" to the
viewer's local calendar day, and the dashboard refetches on window focus.

## 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.