# 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 ``/`` catch-all pages, themed via `` (`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` `