1. Splash & onboarding
/ src/app/index.tsxAtmospheric, cinematic, calm — the brand premise in one breath. Canonical wordmark in the upper third with a soft luminous sigil behind it; Oxy as a bottom-anchored silhouette watermark, not a character; spaced-uppercase tagline; a single Copper CTA pinned to the thumb zone. Mode-invariant composition: dark variant ships by default, light is a paper-warm twin of the same structure. No animation. The wordmark, the halo, and Oxy are the brand statement — copy is held to seven words.
consequential conversation
consequential conversation
Implementor notes
- Center-weighted composition, generous negative space. Wordmark in the upper third with a luminous sigil halo behind it, Oxy bottom-anchored as a soft silhouette watermark, CTA cluster pinned to the thumb zone. Eye lands on the wordmark first, the tagline second, the CTA last.
- Oxy is a watermark, not a character. 240px, ~42% opacity in dark / ~70% in light, with a soft sage-tinted glow halo behind. Sits below the wordmark cluster and bleeds into the gradient — Oxy is part of the brand, not a mascot taking center stage. (Per the 2026-05-18 splash brief: "Oxy is part of the brand, not a character taking center stage.")
- The sigil is a luminous halo (radial sage gradient at low opacity) above the wordmark. Carries the "atmospheric / instrument-driven" feeling without illustration. Light variant warms toward a copper-sage blend.
- Starfield specks are six 1×1px radial gradients placed in the upper half of the canvas. At 10–20% opacity they are barely perceptible — they exist to make the gradient feel inhabited.
- Tagline in spaced uppercase (
CAPTURE EVERY · CONSEQUENTIAL CONVERSATION) — 10px, 0.22em letter-spacing, broken onto two lines. One of the very few all-caps treatments we permit; it earns it as a brand-typographic moment, not a UI label. - Wordmark is mode-aware. Dark variant uses
wordmark-dark.svg; light useswordmark-light.svg. The wordmark itself flips with the canvas — the brand canon was designed for that. - Light variant is paper-warm, not sterile white.
radial-gradient(ellipse at 50% 28%, #f6f1e4 0%, #ebe3cf 75%). Oxy reads more clearly against the lighter background. - One Copper element only: "Get started" pill (
--copper-600, 4.6:1 contrast with white). "I have an account" is text-only secondary. - No animation on first launch. Idle screens spend zero battery. The Scribe device follows the same rule. If we ever animate this surface, it's the sigil that breathes — never the wordmark or Oxy.
2. Sign in
/login src/app/login.tsx
Replaces the current inline TextInput blocks with the proposed Input primitive (label above field, focus ring, error helper). Generous vertical rhythm; the Copper "Sign in" button is the only Copper element. Email auth is foreground; OAuth is below the fold because it's secondary in our team's reality.
Welcome back
Welcome back
Implementor notes
- Label above field, never inside. DESIGN §10: placeholder-as-label fails when the user starts typing. The label stays visible.
- Focus ring is Copper at 18% alpha + Copper border. One Copper element, applied via state (focus), not chrome.
- Error is color + shape + label. The
⊘glyph carries meaning even with red-green color blindness. Per §11 Rule 3. - Caret blink pauses in Calm mode. Toggle ● Calm to verify.
3. Home — Today
/(tabs) src/app/(tabs)/index.tsx
Reframed from "activity feed" to Today — a single screen that tells you what's happening, what's still cooking, and what's at the edge. The persistent Instrument Bar at the top is the most-violated Apple principle in the current app: it brings the recording session out from behind a tab and into the user's peripheral vision. Recent recordings are ListRows with quiet StatusBadges; suppressed-state cards (Ready) drop the badge entirely per loading-processing-and-status.md.
Implementor notes
- Today > Activity. The current "recent activity feed" is generic. "Today" creates a frame the user can fail or succeed at — and the most important thing inside that frame is "is anything recording right now?" Hence the Instrument Bar.
- Instrument Bar replaces the modal sheet when a recording is in flight. Tapping it lifts the recording detail sheet without unmounting the home stack — preserves position when dismissed.
- Ready recordings carry no badge. Per
loading-processing-and-status.md, terminal success is suppressed. The presence of the row is the success. - One Copper per screen: the central Record tab. Public-tag pill is teal, not copper — public is its own signal (DESIGN §3).
- Slug-line badges (10.5px, muted) demote the team/tag chip — borrows directly from KB v2's plain-language hierarchy. Recording title is the headline; everything else is a slug.
3a. widget — InstrumentBar
— src/components/InstrumentBar.tsx (new)The persistent edge-status component. Sits just below the safe-area inset on every authenticated screen, showing the live recording session state. Calm + present, never modal. Five visual states; tapping any non-idle state lifts the recording detail sheet.
Implementor notes
- This is the Sacred-data invariant made visible. If the user can always see "9 chunks queued — offline", they trust the app. The current build hides this until they open the recording detail.
- Idle is dashed border, no fill. Quiet by default. Present, but not nagging.
- Recording uses the
--rec-reddot (#EF4444) — universal, not theme-dependent. Same hex on the Scribe device. - Tap target is the full bar. Min height 40pt, max 48pt. Sits in the title area so it doesn't crowd content.
3b. widget — StatusBadge
— src/components/StatusBadge.tsx (new)Color + shape + label — never color alone (DESIGN §11 Rule 3). Suppresses terminal success states ("Ready" carries no fill, just a quiet outline). Failed uses a triangle so it's legible even with red-green color blindness.
Implementor notes
- Five shapes: circle (uploading, ready, live), diamond (processing, public), triangle (failed). Shape stays consistent across both themes.
- Ready is intentionally quiet — no fill, neutral border, muted text. In list contexts, prefer no badge at all per
loading-processing-and-status.md. Use this variant only when the list mixes terminal + in-flight states. - Public uses teal
#14B8A6— universal across themes, reserved for "publicly accessible." Never mix with Copper.
4. Record — Setup
/team/[teamId]/record src/app/team/[teamId]/record/index.tsxFriction kill. Default to the user's last team and let them go. Title is optional, mic permission is checked silently, and the Copper "Start recording" pill is the only thing competing for attention. Public-recording toggle is collapsed by default (Linear's "metadata collapses" principle) and reveals on tap.
Implementor notes
- Default team is sticky. 80%+ of recordings go to the same team. Don't ask twice.
- Mic check is inline, not a permission modal. The dot turns red if permission is missing; tap reveals the system permission flow. No popup-as-greeting.
- Public toggle uses teal swatch glyph, not Copper. Mirrors the StatusBadge color.
- Team switcher is a Sheet, not a center modal. Pull-to-dismiss handle. The dimmed background preserves spatial continuity.
4b. Record — into a Knowledge Bubble Soon
/kb/[bubbleId]/record src/app/kb/[bubbleId]/record/index.tsxSame Setup surface, different destination. Knowledge Bubbles are the SageOx scope-and-share unit — personal, team, repo, or custom. Recording into a bubble files the conversation under that scope and inherits its access list. The destination picker reveals the four bubble types with their canonical glyphs (KB v2 vocabulary) and a recent-bubbles row above the picker.
Record into…
Implementor notes
- Knowledge Bubbles are coming soon. The Setup surface stays the same shape; only the destination picker changes — instead of "which team?" it asks "which bubble (or team)?" Bubble glyphs follow KB v2's
kb-bubbleavatar convention: 9pt radius, single-fill, monogram, type-coded color (personal = copper, team = sage, repo = charcoal, custom = violet). - Access inherits the bubble. No separate visibility toggle — recording into
@ryan/researchmeans only @ryan sees it. Recording into#engineeringmeans the engineering team sees it. Predictability over toggles. - The picker sheet is unified across destination types. Bubbles + Teams in one sheet. KB-coded section labels in sentence-case-uppercase (the only all-caps we still permit, since these are namespace markers).
- "Soon" tag is Copper-text-only, no fill. The tag is the only Copper element on this section's heading row; the Start pill on the in-phone surface is the one Copper element per viewport.
5. Record — Active
/team/[teamId]/record/active src/app/team/[teamId]/record/active.tsx
The hero surface. A chronograph, not a form. A 56-pt monospace timer with smooth digit transitions, a 24-bar live audio sparkline, and a Sacred-data telemetry strip below: chunks · queued · last receipt · network. The single Copper element is the Stop pill — Pause and Mute are quiet ghost circles. Status arc at the top borrows from the Scribe speaker (DESIGN §6) — "RECORDING" in spaced uppercase, with the universal red dot pulsing at 1200ms cadence.
Implementor notes
- Timer is the centerpiece. 54px JetBrains Mono with tabular numerics — digits don't jiggle as they tick. The
.3tenths are not there for precision; they make the timer feel alive without animation. - The status arc replaces the page title. Per the Scribe device — "every screen answers one of three questions, and this one is always 'Am I recording?'"
- Sacred-data telemetry strip is the trust-builder. When you can see
queued: 0in tabular monospace, you trust the upload pipeline. This is what's missing in the current build. - Stop is Copper, Pause is Sage outline. Stop is destructive enough to deserve the only Copper element. Pause is reversible; doesn't earn Copper.
- Paused state freezes the sparkline at 35% opacity. Audio sampling stops; visual continuity preserved. No "all bars at 0" — that reads as broken.
- Calm mode pauses the bar wave after 3 loops (locked at 75% scaleY). Recording continues; only the visual loop quiets.
- ★ Bold move — shared element transition from InstrumentBar. Tapping the persistent
InstrumentBaron Home should expand into this surface: the IB icon morphs into the central pulse, the IB primary text becomes the title row, the IB meta becomes the chronograph timer. One shared element transition (350ms slow tier, gentle spring damping 18 / stiffness 180), not a stack push or modal sheet. Implemented in RN viareact-native-reanimatedSharedTransitionorexpo-routershared-element transitions. This is the screenshot moment — touching the bar is touching the instrument.
5a. widget — RecordingPulse
— src/components/RecordingPulse.tsx (new)Central record-button control across four states. Pulse ring expands 0.85→1.4 over 1200ms (mirrors the Scribe firmware exactly). Idle is a hollow outline; recording is filled red + square stop glyph; paused is amber with two bars; uploading is info-blue with up-arrow.
Implementor notes
- Pulse spec: ring scales 0.85→1.4, opacity 0.5→0, duration 1200ms, easing
cubic-bezier(0.4,0,0.2,1), loop infinite. Same numbers as Scribe firmware so the device + phone feel like one product. - Reduced-motion fallback: drop the ring; keep the filled red circle. Recording is still unambiguous.
- Calm mode pauses the ring animation at the smallest visible scale.
6. Recording detail
/team/[teamId]/recordings/[recId] src/app/team/[teamId]/recordings/[recId].tsxLifecycle made visible as a quiet stepper at the top — Captured · Uploaded · Processed · Ready — Sage for done, Copper for active, neutral for pending. Then a scrubber, then content tabs (Transcript / Notes / Decisions). The transcript uses the KB-v2 demoted-byline pattern: speaker name + timestamp slug, content at full weight.
Implementor notes
- Stepper as status, not progress. Each stage is a state, not a percentage. When Ready, all four bars are Sage — the screen tells you "this is done." During processing, the active step is Copper (the only Copper element).
- Decisions get a Copper check. Decisions are the most actionable artifact — they earn the accent. Notes and transcript share the same hierarchy as the rest of the app.
- Speaker bubbles use 6px radius, not 50%. KB v2 monogram-bubble carry-over — flatter, more architectural than circles.
10. Devices
/(tabs)/devices · /devices/[devId] src/app/(tabs)/devices/index.tsx · src/app/devices/[devId].tsxPaired Scribe devices — list with online dot + last-seen timestamp; detail with quiet key-value rows, firmware status, and the "doesn't exist yet" empty state (dashed border, sage CTA, no Copper) when you have none. Pair button is Copper — the one primary action.
Implementor notes
- The device "portrait" on detail is intentionally photographic — a small 3D moment that says "this is your physical object." Carries the brand into the digital surface.
- Online dot uses success-500, not Copper. Status is its own semantic; Copper is action.
- Unpair is danger-ghost, not Copper. Destructive actions never wear Copper — Copper is reserved for the page's affirmative primary action.
- Empty state reuses the dashed-border pattern and pairs it with a sage CTA — the "doesn't exist yet" canon.
11. Notifications
/notifications (new) src/app/notifications.tsx (proposed)
Replaces the current native Alert.alert() calls scattered through the codebase. A toast layer (transient) and an inbox tray (persistent). Three semantic variants: success (sage tick), error (red triangle), info (blue dot) — each with a 3px left-border so the user can identify variant at a glance without parsing the icon.
Implementor notes
- Toast is inline, never blocking. Replaces native
Alert.alert()— those interrupt; toasts inform. The user keeps using the app. - 3px left-border carries the variant. Even with the icon hidden (Calm mode, accessibility), the variant is identifiable.
- Error toast doesn't show a Copper "Retry" button — the toast itself is tappable, and the inbox detail offers retry. Don't force a binary choice in a transient surface.
12. Account & Settings
/account src/app/account.tsx
Theme picker as the proposed Segment primitive (replaces the current inline segmented control). The remaining list is quiet: ListRows grouped by section, sign-out is a danger-ghost at the foot. No Copper — Account is not a primary-action surface.
Implementor notes
- No Copper on this screen. Account is administrative; nothing here is a primary intent.
- Sign-out is danger-ghost at foot, per Apple HIG. Destructive actions live where the thumb naturally lands when you've finished a settings sweep.
- Calm mode is a first-class setting, not buried. The toggle includes a one-sentence explanation of what it does.
- Build number visible. Bug reports want it; designers want to know which build a screenshot came from.
13a. widget — ListRow
— src/components/ListRow.tsx (new)
The single row primitive used on Home, Teams, Devices, Sessions, Notifications. Avatar/icon · primary · meta · trailing · chevron. Live status is a small red dot on the avatar (badge superimposed). Hover/press treatment is surface-2 background fill — no transform, no shadow lift.
Implementor notes
- Avatar is 36×36 with 9pt radius (KB v2 monogram-bubble convention). Settings-style rows swap avatar for a 32×32 8pt icon tile, calmer.
- Divider is left-inset at 60px (one avatar-width + gap). Reads as grouped rather than tabular — feels less like a database query.
- Trailing slot accepts either a timestamp or a StatusBadge. Never both. Pick the more informative one for the row's context.
- Press feedback is fill only. No transform, no scale, no shadow — Linear discipline.
13c. widget — Input
— src/components/Input.tsx (new)
Replaces all inline TextInput blocks in the codebase. Label above field (never placeholder-as-label, DESIGN §10). Four states: rest, focused, error, disabled. Focus ring is Copper × 18% alpha; error ring is danger-500.
Implementor notes
- Label is always visible. Even when typing. Reduces re-orientation cost when the user looks away and back.
- Helper text is always reserved space — 12px below. Prevents layout shift when error appears.
- Focus ring is 3px outside the border — adds spatial padding so the field "claims" the area visually. Linear-style.
13d. widget — EmptyState (refresh)
— src/components/EmptyState.tsx (update)
Aligns the existing primitive to the canonical "doesn't exist yet" pattern: dashed border, surface-2 icon tile, sage-700 icon color, sage CTA (not Copper). Never alarmist — calm, instructional, optional next step.
Implementor notes
- Dashed border for "could exist." Solid border for "no result for your query right now." Two different empty states.
- CTA is sage, not copper — DESIGN §11 Rule 6 reserves Copper for the page's primary action, not for empty-state recovery.
13e. widget — Toast / Notification
— src/components/Toast.tsx (new)Three variants × two contexts (inline list, transient overlay). Auto-dismiss at 5s for success/info; error toasts persist until tapped or swiped. Stacked toasts collapse with a "+N more" affordance.
Implementor notes
- Error toasts are sticky. Recording-related errors are sticky and route the user to the offending recording on tap.
- Stacked toasts show the newest on top with a "+2 more" pill above; tapping expands the stack.
- Toasts respect the instrument bar. They slide in below it, never over it. The instrument bar's status is more important than any toast.
14. ★ Motion + Calm policy
— cross-cutting DESIGN.md §6 · §11 Rule 2
Four motion tiers, applied uniformly across every surface in this mockup. Three motion-suppression layers: OS prefers-reduced-motion → SageOx Calm mode → per-loop budget. Recording pulse has its own spec (1200ms loop, fade-out ring) borrowed exactly from the Scribe firmware.
.pulse-ring { animation: scale 0.85 → 1.4, opacity 0.5 → 0; duration 1200ms; easing cubic-bezier(0.4,0,0.2,1); loop infinite }
Same numbers as ~/Code/sageox/speaker/lib/ui/. The device and the phone breathe in sync.
Implementor notes
- Test Calm mode now: toggle the
● Calm modebutton in the top chrome. Every pulse ring in the page should pause, badges should dim. Layout should not shift. - Easing is always ease-in-out on this surface. No spring overshoot in navigation transitions (DESIGN §10). Springs are allowed only for direct manipulation (drag, pull-to-dismiss).
- The recording pulse is the only continuous animation on idle screens. Battery budget — animations on inactive surfaces are forbidden.
15. ★ Light + dark contrast audit
— cross-cutting DESIGN.md §11 Rule 5
Every text-on-surface pairing the mockup uses. Both modes verified at the WCAG AA 4.5:1 minimum for body copy (3:1 acceptable for ≥18pt large text, but the mockup uses none below the body threshold on a muted token, so we lock at 4.5:1 across the board). Token values from KB v2 v2.1's borderline correction are inherited (text-muted bumped to #9aa3ab in dark).
| Pairing | Mode | FG | BG | Ratio | AA body |
|---|---|---|---|---|---|
| text on bg | Dark | #e7e9ea | #0a0d0f | 17.5 | PASS |
| text on surface-1 | Dark | #e7e9ea | #14181b | 15.7 | PASS |
| text-muted on surface-1 | Dark | #9aa3ab | #14181b | 7.2 | PASS |
| text-muted on surface-2 | Dark | #9aa3ab | #1e2326 | 6.3 | PASS |
| text-subtle on surface-1 | Dark | #7d858c | #14181b | 4.7 | PASS |
| copper-600 button text (new CTA base) | Dark | white | #a8613b | 4.6 | PASS · body |
| copper-500 button text — display only | Dark | white | #c47a4a | 3.41 | FAIL · body · use copper-600 |
| sage-500 on white | Dark (CTA hint) | #7a8f78 | #14181b | 4.6 | PASS |
| info-500 status | Dark | #5b7e99 | #14181b | 4.6 | PASS |
| danger-500 status | Dark | #c63838 | #14181b | 5.3 | PASS |
| text on bg | Light | #111518 | #fafbfa | 17.4 | PASS |
| text-muted on surface-1 | Light | #4c5358 | #ffffff | 8.4 | PASS |
| text-subtle on surface-1 | Light | #6f767c | #ffffff | 5.0 | PASS |
| text-muted on surface-2 | Light | #4c5358 | #eef2eb | 7.8 | PASS |
| copper-600 button text (new CTA base) | Light | white | #a8613b | 4.6 | PASS · body |
| danger-500 status | Light | #c63838 | #fafbfa | 5.5 | PASS |
Implementor notes
- Ratios computed with sRGB relative luminance (WCAG 2.2). Numbers are calculated, not eyeballed.
- Copper CTA surface is
--copper-600(#a8613b), not--copper-500. Recomputed contrast vs white is 4.6:1 — PASS at body weight, no large-text loophole needed. Copper-500 stays as the brand display color (icons, dividers, accents on dark surfaces) but never as a text-bearing surface. This split — copper-500 display / copper-600 CTA — is the single highest-leverage token change in the redesign. - text-muted bumped from
#8f99a3to#9aa3abin dark — KB v2 v2.1 correction inherited. The earlier value failed AA on surface-2 below 4.5:1. - This audit is the gate. Adding a new text/surface pairing? Run it through this table first; if it fails, fix the token, not the screen.
philadelphia-v3/proposals/2026-05-18-sageox-app-redesign/index.html ·
Tokens mirrored from tokens/colors.yaml ·
Conventions per proposals/AUTHORING.md ·
Visual language carries forward from 2026-05-15-kb-pages-mockup/v2 + the Scribe device's docs/design-system.md.