SageOx mobile · future — webview-delivered cast & meeting · 2026-05-18

Mode

0. ★ Why these surfaces are webview-delivered

— rationale delivered by web, not app push

The 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.

What goes here
  • 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.
What stays native
  • 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)
How it feels to the user
  • 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.
Why this matters
Cast and meeting are where we innovate fastest. We do not want to be gated by app-store review on a UX experiment. Native owns sacred capture; the web owns everything around it.
Implementor notes
  • Token parity is required. The webview must consume the same tokens/colors.yaml the native app does — either via a published @sageox/design package 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.

Idle · default
Nothing recording
all clear
Recording
Sprint planning
12:47 · chunks 38/38
Uploading
Uploading "Sprint planning"
chunks 38/47 · 3 min
Processing
Processing 2 recordings
transcript · notes · decisions
Offline — queued
9 chunks queued — offline
will resume on reconnect
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-red dot (#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.

Pending
Pending
Uploading
Uploading
Processing
Processing
Ready (quiet)
Ready
Failed
Failed
Live
Recording
Public recording
Public
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.

All clear · recording happily
Chunks
47/47
Queued
0
Last receipt
3s
Net
5G
Backpressure · network slow
Chunks
120/127
Queued
7
Last receipt
14s
Net
3G
Offline · queuing locally
Chunks
204/213
Queued
9
Last receipt
2m ago
Net
Offline
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].tsx

Lifecycle 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.

Dark · ready
9:41

Sprint planning

SageOx · Engineering  ·  Mar 17 · 12:47 min · 3 speakers
Captured
Uploaded
Processed
Ready
4:02 / 12:47
RS
Ryan Snodgrass · 0:14
Let's lock the goal for this sprint — recording reliability first, then live cast.
EM
Emily M. · 0:31
Agreed. I'd add the Sacred-data telemetry surface — we need the user to see chunks landing.
DR
Devon R. · 1:02
+1. Can we get a chronograph view on the Active screen — pull the speaker pulse vocabulary?
Light · decisions tab
9:41

Sprint planning

SageOx · Engineering  ·  Mar 17 · 12:47 min
Captured
Uploaded
Processed
Ready
4:02 / 12:47
Recording reliability is the sprint priority over Cast.
decided by Ryan · 0:48 → 1:14
Add Sacred-data telemetry strip to the Active recording screen.
decided by Emily, Ryan · 1:24 → 2:01
Adopt chronograph vocabulary from the Scribe device on mobile.
decided by Devon · 2:14 → 2:41
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 state

Same 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.

Dark · processing
9:41

Sprint planning

SageOx · Engineering  ·  just finished · 12:47 min
Captured
Uploaded
Processing
Ready

Preparing your transcript

We're diarizing speakers and drafting notes. Typically 2–3 minutes for a recording this length.
Diarization · started 1m ago
See what's happening ↓
Audio
3.7 MB
Speakers
~3
Stage
Diarize
ETA
2m
Light · upload failed (recoverable)
9:41

Sprint planning

SageOx · Engineering  ·  9 chunks pending upload
Captured
Uploading
Processing
Ready

Waiting for a network

Your audio is safe. 9 chunks are queued on this device and will upload as soon as you're back online.
Captured
213/213
Uploaded
204
Queued
9
Net
Offline
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.tsx

Teams 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.tsx

The 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.

Dark · in-room
9:41
Office hours · Cohort 7
Live · 48 min · 11 in room
RSYou
EM
DRDevon
ALAna
JK+7
Devon · 41 min
Welcome everyone — we're casting from the TV. Use the reaction strip below to raise a hand.
Emily · 36 min
Could you share the link to the proposal again?
You · 35 min
sageox-design.netlify.app/2026-05-18-sageox-app-redesign/
Ana · 32 min
Got it. Mind if I screen-share my notes after this section?
Devon is typing…
+
Say something…
Light · hand raised
9:41
Office hours · Cohort 7
Live · 48 min
RS You · ✋
DRDevon
ALAna
JK+8
You raised your hand.
Devon will call on you next.
Lower
Devon · 41 min
Welcome everyone — we're casting from the TV.
You · 35 min
sageox-design.netlify.app/2026-05-18-sageox-app-redesign/
Ana · 32 min
Got it. Mind if I screen-share my notes after this section?
+
Say something…
Implementor notes
  • User-message no-bubble pattern from KB v2: right-aligned text + 2px --sage-500 left-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.

Dark · inbox
9:41

Notifications

Today
"Sprint planning" is ready
Transcript, notes & 4 decisions · 9:58a
Emily mentioned you in a decision
"Sprint planning" · 9:42a
Could not finish uploading "Standup"
9 chunks still queued · tap to retry
Earlier
Devon invited you to "Cast variants"
Yesterday · 4:14p
Ox · kitchen firmware updated to v1.4.0
Mon · 7:02a
Light · live toast (transient)
9:41

Today

RS
Nothing recording
all clear
Today
"Sprint planning" is ready
Transcript, notes & 4 decisions ready to view
Open
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.

Three flavors:
  • List sheet — picking between known options (team, account, language).
  • Confirm sheet — single-step destructive confirmation (unpair, delete).
  • Form sheet — short form (rename device, edit title).

Unpair Ox · kitchen?

The device will stop sending recordings to this account. Any audio still on the device will sync first.
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.

"Sprint planning" is ready
Transcript, notes & 4 decisions · 9:58a
Emily mentioned you
In a decision on "Sprint planning"
Could not finish uploading
9 chunks still queued · tap to retry
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.

Instant
50ms
Tap feedback (button press opacity, list row fill). Sub-perceptual; feels immediate.
Fast
150ms
Hover/focus state changes, segment toggle, tab swap. Reactive but unhurried.
Normal
250ms
Sheet entry/exit, toast slide-in, theme transition. Carries spatial continuity.
Slow
350ms
Larger surface reveal (instrument bar expand, sheet pull-to-dismiss). Used sparingly.
Recording pulse spec (the only looping animation): .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.
Layer 1 · OS
prefers-reduced-motion
All loops drop to zero. Pulse becomes a static red dot. Bars freeze at 75% scaleY.
Layer 2 · App
SageOx Calm mode
Pulse + sparkline pause. Badge opacity 55%. Caret stops blinking. Transitions still play.
Layer 3 · Loop budget
Per-component cap
No animation loops longer than 60s without a quiet state. Recording pulse exempt while a session is live.
Implementor notes
  • Test Calm mode now: toggle the ● Calm mode button 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 bgDark#e7e9ea#0a0d0f17.5PASS
text on surface-1Dark#e7e9ea#14181b15.7PASS
text-muted on surface-1Dark#9aa3ab#14181b7.2PASS
text-muted on surface-2Dark#9aa3ab#1e23266.3PASS
text-subtle on surface-1Dark#7d858c#14181b4.7PASS
copper-600 button text (new CTA base)Darkwhite#a8613b4.6PASS · body
copper-500 button text — display onlyDarkwhite#c47a4a3.41FAIL · body · use copper-600
sage-500 on whiteDark (CTA hint)#7a8f78#14181b4.6PASS
info-500 statusDark#5b7e99#14181b4.6PASS
danger-500 statusDark#c63838#14181b5.3PASS
text on bgLight#111518#fafbfa17.4PASS
text-muted on surface-1Light#4c5358#ffffff8.4PASS
text-subtle on surface-1Light#6f767c#ffffff5.0PASS
text-muted on surface-2Light#4c5358#eef2eb7.8PASS
copper-600 button text (new CTA base)Lightwhite#a8613b4.6PASS · body
danger-500 statusLight#c63838#fafbfa5.5PASS
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 #8f99a3 to #9aa3ab in 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.
Mockup output: 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.