0. ★ Why these surfaces are webview-delivered
— rationale delivered by web, not app pushThe surfaces in this mockup are not native-app code. They run inside an in-app webview the React Native shell launches with a one-time signed session token. Why: cast and meeting participation are our innovation surface — we expect to iterate on this UX weekly, and the latency of app-store updates (3–7 days even on best-case TestFlight) is the wrong cadence for that. Deploy through Netlify, ship in seconds, every user sees the change on next launch.
- Recording detail — transcript, decisions, playback, processing states.
- Live meeting participation — chat, reactions, hand-raise, host TV controls.
- Notifications — toast + inbox tray.
- Activity feeds & archives — list views over a team's preserved conversations.
- Sign-in & session establishment
- The recording capture flow itself (audio APIs, background mode, chunk pipeline)
- Device pairing (BLE / camera permissions)
- Account & theme selection
- The persistent InstrumentBar chrome (lives outside the webview, always visible)
- Indistinguishable from native — same fonts, same tokens, same motion, same dark/light.
- Webview chrome is hidden; native StatusBar + InstrumentBar wrap it.
- Deep-link round-trip allows any "Open in app" pivot when native is warranted.
- Failure mode: webview offline → cached HTML + "Reconnecting…" affordance.
Implementor notes
- Token parity is required. The webview must consume the same
tokens/colors.yamlthe native app does — either via a published@sageox/designpackage or a build-time inline. Drift between native and web is the death of this approach. - The InstrumentBar stays native. It lives in the persistent chrome above the webview viewport, so even during a slow web load the user sees the recording state.
- Session handoff is a one-time JWT appended to the webview URL as a fragment. No cookies. The web app exchanges it for a short-lived session.
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.
5b. widget — SacredTelemetry
— src/components/SacredTelemetry.tsx (new)
The trust strip. Four cells, tabular numerics, calm by default. Cells flip semantic color when something is wrong: queued > 0 turns amber; last receipt > 10s turns amber; Net: offline turns red. This is Tufte at its most literal — a sparkline of state, in text.
Implementor notes
- Numbers are tabular, via
font-feature-settings: "tnum". Digits don't shift as values change. - Color only shifts on anomaly. The default state is "all values neutral, success values quietly green." Color expresses change, not state — Tufte.
- Never reads "uploading…" with a spinner here. The numbers are the spinner. The user can see why.
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.
7. Recording detail — Processing
/team/[teamId]/recordings/[recId] — same route, in-flight stateSame screen, mid-lifecycle. Stepper shows two Sage (done), one Copper (active), one neutral (pending). Empty-state copy reassures: "Your transcript is being prepared — typically 2-3 minutes." A tiny see what's happening link reveals the Sacred-data telemetry strip if the user is anxious.
Preparing your transcript
Waiting for a network
Implementor notes
- Offline state never reads "failed." Failure is permanent. "Waiting for a network" is recoverable, and the copy must say so. Sacred-data invariant: the user must feel safe.
- The single Copper CTA on the offline state is "Try now." Retry is the user's primary intent; the "leave it" option is one tap below and ghost-styled.
- Stepper colors flip: on offline, the active step turns amber (matching the surface tint and the queued numeral). Color + position + bar-color all redundant.
8. Teams + Team home
/(tabs)/teams · /team/[teamId] src/app/(tabs)/teams/index.tsx · src/app/team/[teamId]/index.tsxTeams list is dense (Linear). Team home is a quiet four-tab dashboard (Discussions / Sessions / Repos / Coworkers) with a compact metadata header. The new ListRow primitive carries the same shape across both surfaces — same avatar treatment, same demoted slug-line, same trailing meta.
Implementor notes
- Team home leads with action, not metadata. The Copper "Record into this team" is foot-of-screen — thumb-reachable, the page's primary intent.
- Tabs use Recording / Sessions / Repos / People order — frequency-of-use, not alphabetical. Recordings is the live working surface; People is admin.
- Discover empty state uses the dashed-border / sage-icon pattern from DESIGN §11 Rule 6. No Copper — Copper is reserved for the page's one primary action.
9. Meeting room — phone participant
/meeting/[recId]/join src/app/meeting/[recId]/join.tsxThe phone-side of a live cast (host on TV/tablet). Participants as compact chips at the top; chat in the middle with KB v2's no-bubble user messages (right-aligned, 2px sage left-border) — designer-eye change vs. iMessage-style bubbles, but lets the user's own text recede so the room's conversation reads as a whole. Reaction strip and hand-raise are persistent above the composer.
Implementor notes
- User-message no-bubble pattern from KB v2: right-aligned text + 2px
--sage-500left-border, no fill. Your own messages quiet down; the conversation belongs to the room. - Reaction strip is persistent — five reactions, hand-raise on the right (warm color = call-to-attention). Min 44pt tap target on each.
- Hand-raised banner is amber, not Copper. Amber is the warn semantic role (DESIGN tokens). Copper would compete with the (future) "Stop participating" CTA.
- Live dot in the title uses the universal pulse. Calm mode pauses it. Recording does not stop.
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.
13b. widget — Sheet
— src/components/Sheet.tsx (new)
Bottom sheet for any modal need — team switch, destructive confirm, share, etc. Replaces every Alert.alert() call in the codebase. Pull-to-dismiss handle, surface-3 background (one tier above the page so it visibly elevates), large radius 24pt at the top corners.
Implementor notes
- Pull-to-dismiss handle is functional, not decorative. Tap, drag, or backdrop tap dismisses. Backdrop dim is 60% black, fades in 200ms.
- Sheet uses
surface-3, the highest of the four surface tiers (bg → surface-1 → surface-2 → surface-3). KB v2 nesting principle. - Destructive confirm puts Unpair on top — counter-intuitive to iOS HIG, but correct for thumb-reach: the destructive option is what the user came here to do.
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.