Workshopr Companion (iOS)
The Companion is a facilitator's in-the-room tool: a native React Native / Expo app that runs live while the web platform stays back at the planning stage. It is not a phase app in the Learn → Plan → Facilitate → Reflect sense — it's a cross-phase in-room companion that a facilitator opens once the workshop is running. Web plans; Companion runs the room.
App Identity
#E54D4D
Not a web page. This is a native iOS app (React Native / Expo) — a separate codebase at workshopr-companion/, with no route under this platform and no --companion-primary CSS token. It ships the same unified Learn Red family as the rest of the platform (D1, Red Unification) — there is no separate "mobile brand."
Design Philosophy
The Companion is a stage tool, not a dashboard. It's read at arm's length on a table or glanced at mid-sentence while facilitating — every decision optimizes for that, not for density.
PressableScale — a scale-down (0.97) plus haptic feedback on press — instead of opacity-only or color-only feedback.Mobile Palette
Defined in workshopr-companion/src/theme/colors.ts — the code source of truth. Never hardcode hex in a Companion component; always go through useAppTheme().colors. Light-first: the app is used in daylight rooms, not a dark-mode dashboard. A darkColors variant exists and is wired through ThemeProvider, but v1 pins light unconditionally.
Surface & Text
Brand & Accent
Timer States
The single highest-stakes visual signal in the app. Per the accessibility NFR, state is never conveyed by color alone — each state also changes the label and icon.
/* workshopr-companion/src/theme/colors.ts — the code source of truth. These are NOT platform-tokens.css variables: the Companion is a standalone RN/Expo app, not a page rendered by this stylesheet. */ pageCream: '#FDF8F1' surfaceWhite: '#FFFFFF' stageCharcoal: '#241C13' /* the one dark "stage" surface */ ink: '#2A2116' inkSecondary: '#6B6152' inkOnStage: '#FDF8F1' inkOnStageSecondary: '#C9BFAE' red: '#E54D4D' redDark: '#C73E3E' redTint: '#FBEAE8' gold: '#B8894B' border: '#E7E5E4' borderOnStage: '#3C3226' success: '#3F8A5C' /* Timer states */ timerNormal: ink /* #2A2116 */ timerWarning: gold /* #B8894B */ timerOverrun: redDark /* #C73E3E */
Mobile Type Scale
Same brand fonts as web — Fraunces for display, Inter for body — never swapped. Defined in workshopr-companion/src/theme/typography.ts and only ever consumed through the Text primitive, never React Native's raw Text. No text below ~13px anywhere in the app.
| Variant | Font | Size / Line Height | Use |
|---|---|---|---|
display |
Fraunces Bold | 34 / 40 | Largest display moments |
title |
Fraunces SemiBold | 26 / 32 | Screen titles |
heading |
Fraunces SemiBold | 20 / 26 | Block titles, card headers |
body |
Inter Regular | 16 / 23 | Body copy |
bodyMedium |
Inter Medium | 16 / 23 | Emphasized body copy |
label |
Inter SemiBold | 13 / 17 (+0.3 tracking) | Labels, chips, outline steps |
caption |
Inter Regular | 13 / 18 | Captions, meta text |
timer |
Fraunces Bold, tabular-nums | 64 / 68 | The hero numeral of Run Mode |
timerCompact |
Fraunces Bold, tabular-nums | 28 / 32 | Compact timer contexts (rows, sheets) |
Spacing & Radius (4pt grid)
Generous by default — Run Mode is read at arm's length, not scanned like a dense dashboard. Defined in src/theme/spacing.ts.
spacing: xxs 4 · xs 8 · sm 12 · md 16 · lg 24 · xl 32 · xxl 48 · xxxl 64 radius: sm 8 · md 12 · lg 16 · xl 24 · pill 999
iOS Component Inventory
Live in workshopr-companion/src/components/. Grouped by area — primitives every screen uses, Run Mode (the home surface), Capture (Parking Lot), and Cards.
Primitives
background theme token and an optional padded flag. Respects top/bottom insets and the home indicator.Text. Takes a variant (display/title/heading/body/bodyMedium/label/caption/timer/timerCompact) and a color (any theme color token). Supports Dynamic Type.Pressable/TouchableOpacity. Fast spring: speed 40 no-bounce on press-in, speed 30 slight-bounce on release.expo-symbols' SymbolView). Pass plain icon-name strings; verify against the Apple SF Symbols app or fonts.google.com/icons before adding new ones.Run Mode
startedAt, not a ticking interval, so it survives the device lock screen without drifting.ws_empty_state()'s philosophy on web.Capture (Parking Lot)
Cards
iOS Patterns & Platform Notes
What's different from the web platform, and why. These aren't stylistic choices — they follow from the app being native, live, and used mid-facilitation.
| Pattern | What it means |
|---|---|
| One home, two escapes | Run Mode is the root route. Cards (src/app/cards.tsx) and Parking Lot (src/app/parking-lot.tsx) are presentation: 'modal' routes, each with an explicit close control back to Run Mode. Never a tab bar; never three equal-weight destinations. |
| Safe areas | Every screen root uses the Screen primitive to respect top/bottom insets and the home indicator, portrait only. |
| Tactile press + haptics | No hover exists on a phone. PressableScale (0.97 scale + haptic) replaces hover as the feedback channel everywhere. |
| Dynamic Type & arm's-length legibility | This is a stage tool glanced at from a table, not scanned up close. Type respects Dynamic Type; nothing renders below ~13px; the timer is set at 64px specifically to be readable across a room. |
| Dark-mode-ready, light-first | A full darkColors variant exists and is wired through ThemeProvider, but v1 pins light unconditionally — Run Mode is a daylight tool and the timer's color-coded states need to stay predictable across every venue. Flipping to useColorScheme() is a one-line change when dark mode is prioritized. |
| Timestamp-based timer | The Run Mode timer is computed from a stored startedAt value, not a ticking interval — it survives the device being locked and stays accurate when the app resumes. |
| Offline-first | Captures queue locally and sync when connectivity returns. QueueStrip makes the synced/queued state visible so the facilitator trusts the app mid-session without a network. |
| SF Symbols iconography | Icons render as native SF Symbols on iOS (Material Symbols on Android) via AppIcon, not the web platform's Material-only icon font. |
workshopr-companion/src/theme/ and mirror the values on this page exactly — if they ever disagree, the code wins. See workshopr-companion/DESIGN.md for the full design bible.Do / Don't
Shared System Alignment
The Companion doesn't consume ws_* PHP helpers — it's a separate native codebase — but its component philosophy mirrors the web design system's discipline.
| Web concept | Companion equivalent | Alignment |
|---|---|---|
ws_empty_state() |
EmptyState |
Same philosophy: an invitation with a next step, never a blank screen. |
| Learn Red brand family | colors.ts red / redDark / redTint |
Identical hex values (#E54D4D / #C73E3E), per D1 Red Unification — one brand, two codebases. |
| Fraunces + Inter font mandate | typography.ts textVariants |
Same two font families, same never-swap rule. |
| Planner activity-type colors (exercise blue, icebreaker green, break gray, custom purple) | BlockTypeBadge |
Same functional-coding exception rationale: items must stay distinguishable at a glance. |
| Token-first styling discipline | useAppTheme().colors |
Never hardcode hex in a component — always resolve through the theme, same rule as "never hardcode hex when a token exists" on web. |