Files
waggle-os/docs/redesign-warm-hive/pr7-recon/03-screen-auth-design.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

284 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PR7 Recon · Screen 13 — Auth (Clerk, themed) · DESIGN spec
> RECON ONLY. No product code touched. Every claim cites `file:line`.
> Scope: faithful component-level breakdown of the Auth screen design so a build
> agent can implement it against the warm token system, PLUS the REAL-vs-BUILD
> reality of auth in this monorepo (which is the load-bearing surprise here).
Sources read in full:
- `docs/design_handoff_waggle_app/design-files/screens/auth.html` (181 lines)
- `docs/design_handoff_waggle_app/SCREENS.md` §13 (lines 269-278)
- `docs/design_handoff_waggle_app/design-files/styles/waggle.css` (155 lines, token grounding)
- `docs/redesign-warm-hive/BUILD-PLAN.md` §6 (PR7 row) + §7.5 (the BYO/metered gate)
- Codebase auth reality: `packages/server/src/plugins/auth.ts`, `…/local/security-middleware.ts`,
`…/services/user-service.ts`, `apps/web/src/components/os/AppShell.tsx`,
`apps/web/src/lib/adapter.ts`, `apps/www/app/sign-in/[[...sign-in]]/page.tsx`,
`apps/www/app/api/stripe/checkout/route.ts`
---
## 0. The headline (read this before building)
**The desktop app (`apps/web`) — PR7's build target — has NO authenticated user
identity and NO Clerk React SDK today.** Auth screen 13 is therefore overwhelmingly
**EXTERNAL-DEP + decision-gated**, not a re-skin of something already wired.
Two distinct server modes coexist; the design's "Clerk" assumption only matches ONE
of them, and it's NOT the one the desktop talks to:
| Mode | Auth mechanism | Has a real user account? | Where the design's screen would live |
|---|---|---|---|
| **Local sidecar** (what the Tauri desktop / `apps/web` talks to) | per-process **machine bearer token** via `GET /api/auth/session-token`, exchanged so loopback callers can't drive the API. NOT a login. (`packages/server/src/local/security-middleware.ts:235-377`, `:238`) | **No.** "Identity" is the local IdentityLayer name the user types in onboarding (`adapter.getIdentity()``/api/identity`, `IdentityResponse.name`), a memory record, not an account. (`apps/web/src/lib/adapter.ts:1090-1095`; `tauri-bindings.ts:133-145`) | n/a today — there is no `/auth` route in `apps/web` (grep for `'/auth'`/`appId.*auth`**No matches**) |
| **Cloud / Team server** (`packages/server/src/index.ts` + `plugins/auth.ts`) | **real Clerk**`verifyToken()`, `clerkClient.users.getUser()`, Drizzle `users` table, auto-provision on first auth (`packages/server/src/plugins/auth.ts:3,19-52`; `services/user-service.ts:30-58`) | Yes (Clerk user → internal UUID) | n/a in `apps/web` either |
| **`apps/www`** (Next.js landing) | **real Clerk UI**, hosted `<SignIn/>`/`<SignUp/>` catch-all pages, themed via `<ClerkProvider>` (`apps/www/app/sign-in/[[...sign-in]]/page.tsx:1,17-23`); `@clerk/nextjs@^7.3.0` + `@clerk/themes@^2.4.57` (`apps/www/package.json:15-16`) | Yes | **This is the only place Clerk's themeable React UI already exists.** |
So the build decision PR7 must surface: **does screen 13 ship as a real auth flow in
the desktop at all, or is desktop auth always optional/local and "sign in for sync"
links out to the `apps/www` Clerk flow?** The design copy itself ("account is optional —
Waggle runs fully local without one") leans toward the latter. See §6 Decisions.
---
## 1. Exact layout (split: brand-panel left + form right)
From `auth.html`:
- **Top control bar** (`.controls`, `auth.html:78-87`) — concept-harness chrome: a label
`Auth · Clerk · state` (`:79`), a 4-way segmented state switcher
`Sign in / Sign up / Verify / SSO` (`:80-85`), and a theme toggle button (`:86`).
**This bar is concept scaffolding for previewing states — NOT product UI.** In the
real build the "state" is route/Clerk-flow-driven, not a manual segmented control.
- **Split grid** (`.split`, `:89`; CSS `:20`) — `grid-template-columns: 1.05fr 1fr`
(brand panel slightly wider than the form).
- **Left brand panel** (`.brandside`, `:90-101`; CSS `:22-33`):
- 56px padding, `linear-gradient(160deg, var(--bg-2), var(--bg))`, right border
`--line-soft`, full-bleed honeycomb texture `.comb` masked by a radial gradient
at 30%/30% (CSS `:23`).
- Three vertical zones via `justify-content:space-between`: **brand lockup** (hex "W"
mark + "Waggle" wordmark, `:92`), **pitch** (h2 + p, `:93-96`), **trust lines**
(`:97-100`).
- **Hidden below 820px** — `@media (max-width:820px){ .brandside{display:none} }`
(CSS `:73`). Mobile = form only.
- **Right form panel** (`.formside`, `:103-152`; CSS `:36-37`): centered, `max-width:380px`
card, scrollable. Holds the four state views (`.view`, only one `.on` at a time, CSS `:72`).
---
## 2. Every state — verbatim copy + behavior
### 2a. Sign in (`data-view="signin"`, `auth.html:106-117`) — default
- Heading **"Welcome back"**; sub **"New to Waggle? Create an account"** (link → signup) (`:107`).
- **SSO block** (`.sso`, `:108-111`): two buttons — **"Continue with Google"** (mono "G"
badge) and **"Continue with Apple"** (mono "⌥" badge).
- Divider **"or"** (`.divider`, `:112`).
- **Email** field, demo value `mara@egzakta.com` (`:113`).
- **Password** field with a `lrow` header: label + **"Forgot?"** link (→ verify view in
the demo; in product → Clerk reset) (`:114`).
- Primary **"Sign in"** button (`data-go="home"` → routes to Home) (`:115`).
- Fineprint: **"By continuing you agree to the Terms & Privacy Policy."** (`:116`).
### 2b. Sign up (`data-view="signup"`, `:120-132`) — carries the local-first note
- Heading **"Create your hive"**; sub **"Already have an account? Sign in"** (`:121`).
- **`.localnote` honey banner** (`:122`; CSS `:67-70`) — THE load-bearing trust copy:
> **"You don't need this to start."** Waggle works locally right away — create an
> account only when you want sync or a team.
- **SSO block**: single **"Sign up with Google"** (`:124`).
- Divider **"or"** (`:126`).
- Fields: **Name** (demo `Mara Kovač`), **Email** (placeholder `you@company.com`),
**Password** (placeholder **"At least 10 characters"**) (`:127-129`).
- Primary **"Create account"** (→ verify) (`:130`).
- Fineprint: **"We'll send a code to verify your email."** (`:131`).
> Password rule "At least 10 characters" (`:129`) is design copy. Real minimum is
> Clerk-policy-driven — do NOT hardcode "10" in validation; mirror whatever the Clerk
> instance enforces, or omit the count.
### 2c. Verify — 6-box OTP (`data-view="verify"`, `:135-140`)
- Heading **"Check your email"**; sub **"We sent a 6-digit code to mara@egzakta.com"**
(the email is bolded in `--text-2`) (`:136`).
- `.otp` row of **6 single-char inputs** (`:137`; CSS `:60-64`): 48×56px, mono 22px,
`maxlength=1`, `inputmode="numeric"`; a `.filled` class flips border + text to honey
on a non-empty box.
- **Auto-advance / backspace nav** (the explicit design requirement), in the demo script
(`:163-169`): `input` event focuses the next box when filled; `keydown` Backspace on an
empty box focuses the previous box. The demo pre-fills boxes 0-2 with `[2,4,9]`.
- Primary **"Verify & continue"** (→ Home) (`:138`).
- **"Didn't get it? Resend code · Use a different email"** (→ back to sign in) (`:139`).
### 2d. SSO / enterprise (`data-view="sso"`, `:143-150`)
- Heading **"Single sign-on"**; sub **"Use your organization's identity provider."** (`:144`).
- Field **"Work email or organization"** (placeholder `you@company.com`) (`:145`).
- Primary **"Continue with SSO"** (`:146`).
- Divider **"enterprise"** (`:147`).
- **Muted `.localnote`** (neutral, not honey — `background:var(--bg-2)`) (`:148`), verbatim:
> SAML, SCIM provisioning, and audit logs are available on **Teams** and **KVARK**.
> **Talk to sales →**
- Back link **"← Back to sign in"** (`:149`).
### 2e. Brand-panel pitch + trust copy (verbatim, `:93-100`)
- h2: **"Your work follows you, *everywhere.*"** ("everywhere." in honey via `em`, CSS `:29`).
- p: **"Sign in to sync your hive across devices, collaborate with a team, and pick up any
project exactly where you left off — on any machine."**
- Trust line 1 (shield icon): **"An account is optional — Waggle runs fully local without one"**
- Trust line 2 (arrow icon): **"Your memory stays yours; sign-in only adds sync"**
---
## 3. Warm tokens + primitives used (for faithful build)
All from `waggle.css` (dark `:9-61`, light `:63-101`). The auth HTML uses these named
tokens directly:
| Primitive | Tokens (from `auth.html` `<style>` + `waggle.css`) |
|---|---|
| Brand-panel bg | `linear-gradient(160deg, --bg-2, --bg)`; border `--line-soft` (`auth.html:22`) |
| Honeycomb texture | `.comb` data-URI honey @ 5% stroke, radial mask (CSS `:119-122`) |
| Hex mark | `.hex` clip-path (`waggle.css:117`) + `linear-gradient(150deg,--honey-bright,--honey-deep)` + `--honey-glow` (`auth.html:25`) |
| SSO buttons | `--surface` bg, `--line-strong` border; hover → `--honey-line` + `--surface-2` (`auth.html:44-45`) |
| Divider | flex rule, `--line-soft` lines, mono `--text-dim` label (`auth.html:47-48`) |
| Inputs | `--surface` bg, `--line` border, `--r:11px`; focus → `--honey-line` + `--honey-glow` (`auth.html:52-53`) |
| OTP boxes | mono, `--r:12px`; `.filled``--honey-line` + honey text (`auth.html:62-64`) |
| Primary submit | `--honey` bg, text `#1a1407`; hover → `--honey-bright` (`auth.html:56-57`) |
| Honey trust banner | `.localnote``--honey-wash` bg + `--honey-line` border (`auth.html:68`) |
| Neutral enterprise banner | `.localnote` overridden to `--bg-2` + `--line-soft` (`auth.html:148`) |
| Links / accents | `--honey`; fineprint `--text-dim` (`auth.html:41,55,58`) |
| Focus ring (global) | `:focus-visible{outline:2px solid --honey}` (`waggle.css:125`) |
| Fonts | `--sans` Hanken Grotesk, `--mono` JetBrains Mono (`waggle.css:51-53`) |
In the real build these map to the **PR1-landed warm tokens** in `apps/web`
(`BUILD-PLAN.md §3.1`, already shipped per MEMORY.md — `index.css` carries the verbatim
`waggle.css` names + the shadcn HSL recolor). So **no new tokens are needed** — the build
re-skins Clerk/custom components against the already-present token set. Honey "#1a1407"
button-foreground is the same `--primary-foreground` PR1 set (`BUILD-PLAN.md:67`).
`#1a1407` (honey-button text) and `data-theme` theming are app-global; the screen
inherits dark default + the warm-paper light variant for free.
---
## 4. Clerk's themeable components vs custom
Per SCREENS.md §13: **"Build with Clerk components themed to the tokens."** Mapping the
design's pieces to what Clerk's `appearance` API covers:
| Design piece | Clerk coverage | Notes |
|---|---|---|
| Sign in (Google/Apple SSO + email/pw) | **`<SignIn/>`** | Social buttons, email/pw, "Forgot?" reset are first-class. Theme via `appearance.variables` (`colorPrimary``--honey`, `colorBackground``--surface`, etc.) + `elements` overrides. `@clerk/themes` already a www dep (`apps/www/package.json:16`). |
| Sign up + local-first note | **`<SignUp/>`** + **custom** | The form is Clerk; the **honey `.localnote` "you don't need this to start"** banner is custom chrome placed above/around `<SignUp/>`. |
| Verify 6-box OTP (auto-advance/backspace) | **Clerk built-in** | Clerk's email-code step renders its own OTP input with auto-advance. Re-skinning to the exact 48×56 honey boxes needs `elements.otpCodeField*` overrides (or Clerk Elements / a fully custom flow if pixel-parity is required). |
| SSO / enterprise (SAML/SCIM → Teams/KVARK) | **partial Clerk + custom** | Clerk Enterprise SSO exists but is a paid Clerk feature + per-org config. The design's panel is mostly a **custom "Talk to sales" CTA** (KVARK funnel), not a live SAML form. Safe build: custom panel, link to sales. |
| Left brand panel + pitch + trust lines | **fully custom** | Pure layout chrome around the Clerk `<SignIn/>`/`<SignUp/>` card. |
**To match the warm design, two integration styles are possible:**
1. **Themed Clerk prebuilt** (`<SignIn appearance={…}/>`) wrapped in the custom split
layout — fastest, matches SCREENS.md's instruction, but OTP/element pixel-parity is
limited to what `appearance.elements` exposes.
2. **Clerk Elements / headless** (`useSignIn`, `useSignUp`) feeding the design's exact
custom inputs/OTP/buttons — full visual control, more code, the only way to get the
exact 48×56 honey OTP boxes + custom SSO buttons.
The brand panel, dividers, local-first banner, and enterprise→sales CTA are **custom in
either case**.
---
## 5. Honesty contract — where Auth could fabricate (MUST be gated off)
Auth is the single highest-risk screen for fabrication because the desktop has no real
account. Each of these must be **real or absent — never invented**:
1. **A logged-in identity that isn't real.** The demo hardcodes `mara@egzakta.com` /
`Mara Kovač` (`auth.html:113,127,136`). A build MUST NOT pre-fill or display a fake
signed-in user. The desktop's only "identity" is the local IdentityLayer name
(`adapter.getIdentity()`, `apps/web/src/lib/adapter.ts:1090`), which is **not** an
authenticated account and must never be rendered as "signed in".
2. **Fake SSO success.** Google/Apple/SSO buttons that "succeed" without a real Clerk
(or any) provider configured are fabrication. If Clerk isn't wired in the desktop,
these buttons must be honestly disabled / "coming soon" / route to `apps/www`, not
fake a session. (No Clerk publishable key path exists in `apps/web` today.)
3. **Fake OTP verification.** The demo's "Verify & continue" advances on any input
(`auth.html:138,164`). Real verify must check a real code via Clerk; otherwise the
verify state must not claim to have verified anything.
4. **"Continue → Home" as a real auth boundary.** In the demo all submits just navigate
to Home (`auth.html:171-172`). The desktop already has a **real structural auth gate**
(the session-token `ensureReady()` contract, `adapter.authgate.test.ts`) — but that
gates the *local sidecar*, not a user login. The Auth screen must not imply a login
happened when only the local app opened.
5. **SAML/SCIM as live.** The enterprise panel names SAML/SCIM/audit logs
(`auth.html:148`). These are Teams/KVARK/Clerk-Enterprise features — the panel is a
**sales CTA**, and must stay one unless those are genuinely provisioned. Do not render
a SAML form that does nothing.
**Gate-off rule:** if Clerk is not configured for the desktop, the entire authenticated
path (SSO, email/pw, OTP, SSO/org) should degrade to the honest local-first framing the
design itself already provides ("an account is optional — Waggle runs fully local") and
a single "Sign in for sync →" link to the real flow, rather than a non-functional
look-alike.
---
## 6. Decisions a build agent must get answered first
1. **Does desktop auth ship at all, or link out?**
- Options: (a) full Clerk in `apps/web` (add `@clerk/clerk-react` + publishable key +
a `/auth` route — none exist today); (b) desktop stays local-only, "Sign in for sync"
deep-links to the existing `apps/www` Clerk flow (`apps/www/app/sign-in/...`); (c)
embed/redirect to `apps/www` in a webview.
- Recommendation: **(b)** for first ship — matches "account is optional", reuses the
real, already-themed `apps/www` Clerk surface, and avoids standing up a second Clerk
React integration + token bridge into the local sidecar (which currently authenticates
with a *machine* token, not a *user* token).
- Blast radius: large if (a) — new dep, new route, new token-exchange between Clerk
user-JWT and the local bearer; small if (b)/(c).
2. **BYO-key vs Waggle-metered (DESIGN_POV §4 / BUILD-PLAN §7.5 #5).** Explicitly flagged
as **blocking Billing/PR7** (`DESIGN_POV.md:62-70`, `:88-90`; `BUILD-PLAN.md:165-166`).
It reshapes whether "sign in" is even required to use models (BYO = local key, no
account needed; metered = account + payment up front). **Settle before building 13/14.**
- Blast radius: shapes Auth (is sign-in required for inference?), Onboarding model gate,
Billing, Usage. Founder decision, not a build choice.
3. **OTP fidelity: themed Clerk prebuilt vs Clerk Elements/headless.** Pixel-exact 48×56
honey OTP boxes need headless; "good enough" needs only `appearance` overrides.
- Recommendation: themed prebuilt first (ships SCREENS.md's instruction), upgrade to
Elements only if review demands the exact boxes. Blast radius: small/local.
4. **SSO/enterprise panel = sales CTA only (no live SAML).** Recommendation: keep it a
custom "Talk to sales → KVARK/Teams" panel; do not implement live SAML in PR7.
Blast radius: small.
---
## 7. REAL vs DERIVABLE vs MUST-BUILD vs EXTERNAL-DEP (screen 13)
| Feature | Status | Evidence / note |
|---|---|---|
| Warm tokens + primitives the screen needs | **REAL** | PR1 landed verbatim `waggle.css` tokens in `apps/web` (`BUILD-PLAN.md §3.1`, MEMORY.md PR1). No new tokens. |
| Split brand panel, pitch, trust lines, dividers, local-first banner, enterprise CTA | **MUST-BUILD** (custom chrome, low risk) | Pure layout/copy; no backend. All copy verbatim in §2/§4 above. |
| Clerk `<SignIn/>`/`<SignUp/>`/OTP UI in `apps/web` | **EXTERNAL-DEP** | No `@clerk/clerk-react` in `apps/web`; no publishable key; no `/auth` route (grep: no matches). Clerk React UI exists ONLY in `apps/www` (`apps/www/app/sign-in/[[...sign-in]]/page.tsx`). |
| Real user account / login / session | **EXTERNAL-DEP** | Real Clerk auth lives in cloud/team server (`packages/server/src/plugins/auth.ts:3,31-48`) + `users` table (`services/user-service.ts`). Desktop sidecar auth is a **machine bearer token**, not a user (`local/security-middleware.ts:235-377`). |
| Local "identity" (name) for the user row | **REAL but NOT an account** | `adapter.getIdentity()``/api/identity` → IdentityLayer name (`adapter.ts:1090`; `tauri-bindings.ts:133-145`); used in `AppShell.tsx:98-108`, degrades to "Account". Must NOT be shown as "signed in". |
| SSO with Google/Apple | **EXTERNAL-DEP** | Clerk social providers; need Clerk + OAuth app config. Not wired in desktop. |
| SAML / SCIM (enterprise) | **EXTERNAL-DEP** (Teams/KVARK/Clerk-Enterprise) | Design panel is a sales CTA, not a live form (`auth.html:148`). |
| "Continue → Home" navigation | **DERIVABLE** | Routes to `/home`; desktop already has the structural sidecar auth gate (`adapter.authgate.test.ts`) but that is not a user login. |
| Theme toggle / segmented state switcher (top bar) | **N/A — concept scaffolding** | `.controls` is harness chrome for previewing states (`auth.html:78-87`), not product UI. |
---
## 8. One-paragraph build brief (for the implementer)
Build screen 13 as a **custom warm split layout** (left brand panel: hex "W" + honeycomb
`.comb` + verbatim pitch/trust copy from §2e; right: a centered `max-width:380px` card)
in `apps/web`, against the **already-present PR1 warm tokens** (no new tokens). The form
itself is **EXTERNAL-DEP on Clerk**, which is wired only in `apps/www` today — so the
**first, honest ship is local-first**: the desktop stays usable without an account
(reuse the design's own "you don't need this to start" `.localnote`), and a single
**"Sign in for sync →"** links to the real, already-themed `apps/www` Clerk flow rather
than a non-functional Clerk look-alike in the desktop. If founder confirms full in-app
Clerk (decision §6.1a), add `@clerk/clerk-react` + a publishable key + a `/auth` route +
a user-JWT→local-sidecar token bridge, and theme `<SignIn/>`/`<SignUp/>`/OTP via
`appearance` (Elements only if pixel-exact OTP boxes are required). **Never** render a
fabricated signed-in identity, fake SSO/OTP success, or a dead SAML form (§5). The
**BYO-vs-metered decision (§6.2) blocks this screen and Billing** and must be settled
first.