I did not choose what products may vary. emubrain-auth PR #26
already defines it in src/fleet-ui-contract.ts, and the branding block
is z.object({…}).strict() — a seventh key is a parse error. So this artifact
specifies the shell against those exact six fields and reports where the brief and the contract
disagree, rather than inventing a parallel budget.
| Field | Value | Source |
|---|---|---|
| Artifact | Fleet shared auth UI — design specification | this document |
| Artifact version | 0.1.0-design.1 | tracks the package it specifies |
| Package | @emubrain-studio/auth-ui@0.1.0 | FLEET_AUTH_UI_PACKAGE, FLEET_AUTH_UI_INITIAL_VERSION |
| Contract version | 1 | FLEET_AUTH_UI_CONTRACT_VERSION |
| Aligned to | emubrain-auth PR #26, head c5e5c75 | branch axel/auth-shared-ui-contract, base main, OPEN |
| Engine | @emubrain-studio/auth, Better Auth 1.6.25, Argon2id | proposal, canonical engine inventory |
Read at the PR head, not at a branch name. I pinned
c5e5c75 because a named ref moved under me three times this week. If the PR
advances, this artifact's alignment claim is only true of that head until re-checked.
Everything else in the auth surface is package-owned
| Manifest field | Schema | Design use |
|---|---|---|
branding.logoPath | /^\/(?!\/)[^?#]{1,240}$/ | The mark, 28×28, top-left. Never scaled per product. |
branding.logoAlt | 1–120 chars, trimmed | Accessible name and the visible product name. Truncates — see Edges. |
branding.accentColor | #RRGGBB | Primary button fill, link colour, mark fill. |
branding.accentForegroundColor | #RRGGBB | Text and glyphs sitting on the accent. |
branding.fontToken | /^[A-Za-z][A-Za-z0-9_-]{0,39}$/ | Selects one stack from a package-owned registry. See delta 3. |
branding.theme | light | dark | system | Picks a palette; does not define one. |
Not variable, and this is the point of the package: field order, validation placement, focus order, error mapping, security copy, spacing, radii, breakpoints, the neutral palette, the dark values, and the focus ring. The proposal is explicit — a product "may not fork a screen, override security copy, add providers, or call raw Better Auth endpoints."
| View | States | Required? |
|---|---|---|
| Sign in | default · field validation · credentials rejected · submitting | Always — emailPassword: z.literal(true) |
| Sign up | default · address unavailable | Always |
| Verify email | pending · success · expired | Only when capabilities.emailVerification is true |
| Forgot password | request · sent | Always — passwordReset: z.literal(true) |
| Reset password | choose new · mismatch · link expired | Always |
| Logout | signed out | Always — logout-revocation is a conformance case |
| Shared | loading · rate-limited · auth unavailable · safe generic error | Always |
Sixteen states across ten views, all drawn at desktop and 360 on the
States page. Verification is the only conditional group, and
requiredFleetAuthConformanceCases() is what makes it conditional — it appends
verification-required-before-signin and verification-consume only when
the manifest enables the capability.
--focus, package-owned, because a
product-variable focus indicator has product-variable contrast, and then it is not an
indicator. 2px solid, 2px offset, both themes.aria-describedby; the field is
aria-invalid. Block-level failures use role="alert"; successes and
pending states use role="status".#454C58 on #FFFFFF
= —, secondary #646C7A = —, error
#A8321E on its tint = —.Three places where the brief, the contract, or both need a decision
Delta 1 — the brief permits two variations the contract forbids.
Adam's task lists product variation as "logo, font, accent tokens, sender/support copy,
and optional illustration". Neither exists in branding, and the block is
.strict(), so neither can be added without a contract version bump.
bindings.fromAddress does exist but it is an env-bound email under
bindings, not branding, and the proposal states products may not override security
copy. I designed to the contract and excluded both. If they are genuinely wanted, that is
a contract change (v1 → v2) and Adam's call, not a design decision.
Delta 2 — the schema validates that the accent pair is hex, not that
it is legible. hexColorSchema is /^#[0-9A-Fa-f]{6}$/ and nothing
else. A manifest with — parses, passes CI, and ships a primary button
whose label is invisible. Contrast of that example: —.
Recommend the schema enforce a floor — accentForegroundColor against
accentColor at ≥ 4.5:1 — as a superRefine alongside the origin checks
that already exist. It is the same shape of rule and the same file. The three demonstration
products all clear it: —.
Delta 3 — fontToken is a name with nothing to resolve
it. The pattern /^[A-Za-z][A-Za-z0-9_-]{0,39}$/ accepts any identifier, so a
manifest can declare Comic_Sans and the package has no registry to map it to a
stack. Recommend the package own an explicit token→stack table and reject unknown tokens,
which also keeps font loading inside the package where the licence and the fallback chain can be
controlled. Three tokens are enough to start: a system sans, a serif, and a monospace.
Stated explicitly because the brief asked for them, and because on an auth surface the boundary is the deliverable:
emubrain-auth PR #26 at head
c5e5c75 (branch axel/auth-shared-ui-contract). Presentation only —
no code, secrets, deployment or production action. Review authority per the proposal:
Rhea reviews the contract, Adam resolves architecture exceptions.