Files
tapir/docs/specs/landing-page-and-doc-reconciliation.md
T
mathias 6b817f11b9
CI / Lint / Test / Vet (push) Successful in 11s
CI / Build & Import (push) Successful in 10s
CI / Mirror to GitHub (push) Failing after 3s
docs: add landing-page + doc-reconciliation build spec
Workstream A: public /welcome landing page (bubbletea aesthetic, one Dex login
flow, logged-in shortcuts) — with the oidc.go facts verified against main, incl.
the two corrections that only surface from reading the code (logout must redirect
to /welcome not /auth/login; bare-/ vs deep-link redirect split).

Workstream B: reconcile the guardrail docs against deployed reality (v0.4.0) —
auth.go comments, data-model isolation status + migrations 002-006 schema,
architecture web surface, use-case scenarios for the Stage-1 features, ADR
ordering, and a requirements-vs-shipped deviation check. Structured as two
parallel workstreams so the doc audit isn't done cursorily alongside the build.
2026-06-03 19:18:40 +00:00

8.7 KiB
Raw Permalink Blame History

Spec — Landing page + documentation reconciliation

Date: 2026-06-03 Status: Ready to build Scope: Two parallel workstreams — (A) a public landing page; (B) reconciling the requirements / use-case / architecture / data-model docs against the deployed reality (v0.4.0). These are separate concerns; do not let one worker do both, or the audit gets done cursorily.

All work: read CLAUDE.md + DECISIONS.md first. TBD — commit directly to main, one logical change per commit, conventional commits, task check green before every commit. After editing any .templ, run templ generate (the repo commits both views.templ and the generated views_templ.go).


Workstream A — Public landing page

Goal

A public landing page at /welcome, in the established bubbletea aesthetic, that lets a visitor sign in (one Dex flow) and, if already logged in, jump to their Tapir page or log out. New public transport surface only — no engine/core change (ADR-003).

Verified facts (read from internal/web/oidc/oidc.go @ main — do not re-guess)

  • Auth endpoints are exactly /auth/login, /auth/callback, /auth/logout.
  • isPublicPath(p) = p == "/healthz" || strings.HasPrefix(p, "/auth/") — the single public-route chokepoint inside DexAuth.Middleware.
  • DexAuth.CurrentUser(r) (web.User, bool) reads the session cookie and does NOT redirect — this is the "peek" the landing page uses to branch logged-in vs logged-out.
  • handleCallback redirects to / on success (correct — leave as-is).
  • handleLogout currently redirects to loginPath (/auth/login) — this is wrong for this feature (see A3).
  • There is NO separate "sign up" against Dex/OIDC: one authorization flow. Registration is Tapir's own /register step (ADR-012), reached after first login for an unknown subject.

Tasks

A1 — make /welcome public. In oidc.go, extend isPublicPath:

func isPublicPath(p string) bool {
    return p == "/healthz" || p == "/welcome" || strings.HasPrefix(p, "/auth/")
}

A2 — unauthenticated bare-//welcome; deep links unchanged. In DexAuth.Middleware, the unauthenticated branch currently always calls redirectToLogin. Change it so that when r.URL.Path == "/" an unauthenticated visitor is redirected to /welcome; for any other guarded path keep redirectToLogin (so a shared /v/{id} deep link still bounces through Dex and returns to the destination). Keep the isPublicPath check first (redirect-loop guard).

A3 — logout lands on /welcome, not login. In handleLogout, change the final redirect from loginPath to /welcome. As written it sends the user to /auth/login, which immediately starts a fresh Dex login — visibly failing to log out. This intentionally breaks the existing logout test (oidc_test.go) which asserts redirect to /auth/login; update that test to expect /welcome. That break is expected, not a regression.

A4 — mount the landing handler in internal/web/handlers.go Router(), on root, OUTSIDE Auth.Middleware, alongside /healthz:

root.HandleFunc("GET /welcome", a.handleWelcome)

handleWelcome peeks a.Auth.CurrentUser(r) and renders WelcomePage(user, ok). Not behind Auth.Middleware or registrationGate.

A5 — WelcomePage templ component in views.templ. Reuse the existing shared layout/header partial and the established aesthetic (#7653FC purple rounded ╭─╮╰─╯ box, pink tapir mascot, #0EF9B6 mint accents) — match the existing pages, do not reinvent styling.

  • Logged out (ok == false): tapir mascot + tagline; one primary CTA "Get Started"/auth/login; honest sub-text: "New here? You'll set up your account right after signing in — returning users go straight through." One button only (see verified facts: no separate Dex sign-up; two buttons to the same URL would mislead).
  • Logged in (ok == true): "Go to my Tapir" → /; "Log Out" → /auth/logout. May greet via user.Email.

A6 — tests (extend handlers_test.go patterns). Note StubAuth.CurrentUser always returns true; for the logged-out case use a fake Auth returning (web.User{}, false).

  • GET /welcome, no session → "Get Started" → /auth/login.
  • GET /welcome, with session → "Go to my Tapir" + "Log Out".
  • Unauthenticated GET / → 302 /welcome.
  • Unauthenticated GET /v/{id} → still 302 /auth/login (deep link preserved).
  • Authenticated GET / → still serves the list, unchanged.
  • oidc: handleLogout → 302 /welcome (update the existing test).

A out of scope: no Dex config change, no new auth/session logic, no sign-up backend.


Workstream B — Documentation reconciliation

Why

The guardrail docs were written before Stage 1 and the web surface. Several now describe the opposite of the deployed reality (v0.4.0). Stale guardrail docs are worse than none — a future cold session (human or agent) trusts them. This workstream brings requirements, use cases, architecture, and data-model back in sync with main. Each fix is one commit; cite the ADR or migration that is the source of truth.

Known drift to fix (verified this session — not exhaustive; the worker confirms against code)

B1 — internal/web/auth.go comments. The User.Subject doc and package doc still say "single-user allowlist (ADR-011)" / "Stage-0". Code is multi-user (ADR-012). Update the comments to describe the current multi-user reality; reference ADR-012.

B2 — docs/data-model.md isolation status. It says isolation enforcement is "dormant at Stage 0". It is now LIVE: Postgres RLS, FORCEd on all user-owned tables, with a passing two-user isolation test (ADR-012, migration 003). Rewrite that section to describe enforced RLS as the current state; keep the history honest (was dormant at Stage 0, enforced from Stage 1).

B3 — docs/data-model.md schema completeness. The doc predates migrations 002006. Add the entities/columns that now exist: summary_actions (002), RLS (003), user_identities (004, dex_subject→user_id), video_connections (005), users.auto_summarize + videos.summarize_requested (006). The ER section should match the live schema. Cross-check against internal/adapters/store/migrations/*.up.sql — those are ground truth.

B4 — docs/architecture/architecture.md. Predates the entire web surface. Update the C4 container diagram and text to include: tapir serve (HTMX+Templ web reader/writer), the Dex OIDC session layer (internal/web/oidc), registration gate, web-initiated YouTube connect, account management, and the immediate-processing path (web "Summarize" button → background goroutine → status poll). The engine/ports/sinks core is unchanged (ADR-003) — show the web surface as a new transport over the same core, not a core change.

B5 — docs/use-cases/*.feature. Add scenarios for the behaviours now live and unspecced: register (new subject → registration → user row; returning user straight through), connect YouTube (web OAuth), disconnect, delete-account (cascade + secret purge, Dex untouched — ADR-013), manual-vs-auto summarize mode + the Summarize button, and the landing page (logged-out CTA; logged-in shortcuts). Keep them as executable-style Gherkin consistent with the existing files.

B6 — DECISIONS.md ADR ordering (cosmetic). ADR-010 sits before ADR-009/011 (append order). Reorder to numeric while you're in the file. Pure tidy, no content change.

B7 — requirements check. If a requirements doc exists (e.g. docs/ui-spec.md, referenced by ADR-011), reconcile it with what shipped: note where the build deviated (e.g. the spinner / immediate processing / summarize mode were beyond the original spec) so the spec reflects reality or explicitly records the deviation. Do not silently rewrite history — record deviations as deviations.

B working method

  • Source of truth order: migrations + code > ADRs > prose docs. When a prose doc disagrees with code, the code wins and the doc is corrected (unless the code is the bug — then flag it, don't quietly doc around it).
  • One logical doc per commit. Cite the ADR/migration that justifies each change in the commit body.
  • This is an audit, not a rewrite: preserve the docs' structure and the "rejected alternatives / history" honesty. The goal is current and trustworthy, not pretty.

Coordination

A and B touch mostly different files (A: oidc.go, handlers.go, views.templ, tests; B: docs/* + auth.go comments). The one overlap is auth.go (B1 edits comments) vs A (reads it) — no conflict. Run A and B in parallel; commit independently to main.

If anything in B reveals that code, not docs, is wrong (e.g. an isolation gap, a migration that doesn't match the data-model intent), STOP and surface it — that's a finding, not a doc edit.