docs: PRODUCT.md + DESIGN.md (ground-up GUI redesign brief)
Captures the impeccable context for the GUI redesign greenlit on 2026-05-12:
PRODUCT.md - register=product, primary persona=solo broadcast operator at
1:50am with a live show twenty minutes out. Strategic principles foreground
progressive disclosure over progressive density. Anti-references explicitly
name the "vibe-coded GUI" failure mode (cards-in-a-grid, hero-metric template,
Zoom pastel, generic SaaS dashboard) so the redesign can be measured against
what it must not become.
DESIGN.md - tokens for the WinUI 3 replatform target. Dark + light palettes
as ThemeDictionary entries, context-aware accent split (accent.cyan.surface
for fill, accent.cyan.text for the darker AA-passing variant on light bg).
Theming via {ThemeResource}; toggle in title bar + settings drawer; System/
Dark/Light tri-state persisted in UIPreferences.Theme.
This commit is contained in:
parent
f12cbe7517
commit
94b0a71edc
2 changed files with 499 additions and 0 deletions
325
DESIGN.md
Normal file
325
DESIGN.md
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
# DESIGN.md — TeamsISO design system
|
||||
|
||||
Target framework: **WinUI 3 (Windows App SDK)**. Tokens are framework-agnostic;
|
||||
the WinUI XAML implementation lives in `src/TeamsISO.App/Themes/` (post-port).
|
||||
|
||||
## Color
|
||||
|
||||
### Strategy
|
||||
|
||||
**Restrained — committed accent + neutral surface.** The surface is the work;
|
||||
the cyan accent is reserved for live state, focus, and the few moments that
|
||||
actually need attention. Coral is reserved for destructive and error.
|
||||
Everything else is neutral.
|
||||
|
||||
This means: no rainbow status pills, no per-feature accent colors, no
|
||||
Slack-style chroma everywhere. If something is cyan, the operator's eye
|
||||
should know why.
|
||||
|
||||
### Scene sentence
|
||||
|
||||
**Dark (default):** A solo broadcast operator at 1:50am, ambient room lights
|
||||
at 5%, leaning into a 24-inch monitor, twenty minutes before a live
|
||||
international interview.
|
||||
|
||||
**Light:** A morning recording session in a glass-walled conference room with
|
||||
the sun coming through the blinds, monitor brightness at 80%. Or a daytime
|
||||
producer monitoring a remote interview from a hotel desk during a working
|
||||
session before lunch.
|
||||
|
||||
The default is dark — that's the dominant operator scene. Light mode exists
|
||||
because not every show happens at 1:50am.
|
||||
|
||||
### Dark palette
|
||||
|
||||
Every neutral is tinted toward cyan (h ≈ 200, chroma 0.005–0.008) so the
|
||||
dark surface reads as deliberate dark, not as chromatically dead.
|
||||
|
||||
| Token | Role | Hex | OKLCH (approx) |
|
||||
|---|---|---|---|
|
||||
| `bg.canvas` | Window canvas | `#0A0A0A` | `oklch(0.12 0.005 200)` |
|
||||
| `bg.rail` | Left rail | `#080808` | `oklch(0.10 0.005 200)` |
|
||||
| `bg.surface` | Card / row | `#141416` | `oklch(0.18 0.006 200)` |
|
||||
| `bg.elevated` | Popovers, menus | `#1C1C1F` | `oklch(0.22 0.007 200)` |
|
||||
| `bg.hover` | Hover fill | `#26272B` | `oklch(0.28 0.008 200)` |
|
||||
| `bg.active` | Pressed fill | `#33343A` | `oklch(0.34 0.010 200)` |
|
||||
| `border.subtle` | Hairlines | `#26272B` | `oklch(0.28 0.008 200)` |
|
||||
| `border.strong` | Hover / focus | `#3A3B40` | `oklch(0.36 0.010 200)` |
|
||||
| `fg.primary` | Body text | `#F4F4F6` | `oklch(0.96 0.004 200)` |
|
||||
| `fg.secondary` | Subdued text | `#A3A4AA` | `oklch(0.70 0.006 200)` |
|
||||
| `fg.tertiary` | Captions | `#6B6C72` | `oklch(0.50 0.006 200)` |
|
||||
| `fg.disabled` | Disabled | `#404145` | `oklch(0.32 0.006 200)` |
|
||||
|
||||
### Light palette
|
||||
|
||||
Mirrored token names; cyan-tinted off-white so the surface still reads as
|
||||
Wild Dragon, not as generic white.
|
||||
|
||||
| Token | Role | Hex | OKLCH (approx) |
|
||||
|---|---|---|---|
|
||||
| `bg.canvas` | Window canvas | `#FAFAFB` | `oklch(0.98 0.003 200)` |
|
||||
| `bg.rail` | Left rail | `#F0F1F3` | `oklch(0.95 0.004 200)` |
|
||||
| `bg.surface` | Card / row | `#FFFFFF` | `oklch(1.00 0.000 200)` |
|
||||
| `bg.elevated` | Popovers, menus | `#FFFFFF` | `oklch(1.00 0.000 200)` (+ shadow) |
|
||||
| `bg.hover` | Hover fill | `#ECEEF1` | `oklch(0.93 0.005 200)` |
|
||||
| `bg.active` | Pressed fill | `#E0E3E7` | `oklch(0.89 0.006 200)` |
|
||||
| `border.subtle` | Hairlines | `#E5E7EB` | `oklch(0.91 0.004 200)` |
|
||||
| `border.strong` | Hover / focus | `#D1D5DA` | `oklch(0.85 0.006 200)` |
|
||||
| `fg.primary` | Body text | `#0A0A0A` | `oklch(0.12 0.005 200)` |
|
||||
| `fg.secondary` | Subdued text | `#4A4B50` | `oklch(0.36 0.006 200)` |
|
||||
| `fg.tertiary` | Captions | `#71747A` | `oklch(0.53 0.006 200)` |
|
||||
| `fg.disabled` | Disabled | `#B3B6BC` | `oklch(0.76 0.005 200)` |
|
||||
|
||||
### Accents — context-aware
|
||||
|
||||
Some accents work in both modes; others need a darker variant for AA contrast
|
||||
when used as text on the light canvas. The token table splits them:
|
||||
|
||||
| Token | Dark | Light | Reserved for |
|
||||
|---|---|---|---|
|
||||
| `accent.cyan.surface` | `#97EDF0` | `#97EDF0` | Primary button fill, badge fill (text on top is near-black in both modes — works) |
|
||||
| `accent.cyan.text` | `#97EDF0` | `#0E7C82` | Cyan-as-text (links, "live" labels, active state) |
|
||||
| `accent.cyan.hover` | `#B5F2F4` | `#0890A0` | Cyan hover |
|
||||
| `accent.cyan.muted` | `#1B3537` | `#E6F8F9` | Cyan tint background, active speaker row fill |
|
||||
| `accent.coral` | `#FB819C` | `#D43E5C` | Destructive, error, alert (as both border + text) |
|
||||
| `accent.coral.bg` | `#3A1922` | `#FDECF0` | Coral tint background |
|
||||
| `status.live` | `#4ADE80` | `#15803D` | Recording active, REC dot, "live" pill |
|
||||
| `status.live.bg` | `#13261A` | `#DCFCE7` | Live pill background |
|
||||
| `status.warn` | `#FBBF24` | `#B45309` | Low disk, NDI degraded |
|
||||
|
||||
**Discipline.** Cyan is the only color that competes with body text for
|
||||
attention. It earns its place — wasted cyan is the design failing.
|
||||
`accent.cyan.surface` (#97EDF0) reads identically in both modes because
|
||||
its text is always near-black. `accent.cyan.text` exists specifically so
|
||||
captions and inline labels stay readable on a light canvas.
|
||||
|
||||
## Theming
|
||||
|
||||
### The toggle
|
||||
|
||||
A single icon button (sun ↔ moon) lives in the title bar, positioned to the
|
||||
left of the window controls. One click swaps the theme. State persists via
|
||||
`UIPreferences.Theme` (`Dark | Light | System`). Default is `System` which
|
||||
follows the Windows app-mode preference.
|
||||
|
||||
The toggle is also surfaced inside the settings drawer under an "Appearance"
|
||||
group as a tri-state pill (System / Dark / Light), so power users find it in
|
||||
the obvious place too.
|
||||
|
||||
### Implementation (WinUI 3)
|
||||
|
||||
WinUI 3's `ThemeDictionary` pattern handles automatic swapping based on
|
||||
`FrameworkElement.RequestedTheme`. Tokens live as `Color` resources keyed by
|
||||
theme, then `SolidColorBrush` references them via `{ThemeResource}`:
|
||||
|
||||
```xml
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<Color x:Key="BgCanvasColor">#0A0A0A</Color>
|
||||
<SolidColorBrush x:Key="BgCanvas" Color="{ThemeResource BgCanvasColor}"/>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<Color x:Key="BgCanvasColor">#FAFAFB</Color>
|
||||
<SolidColorBrush x:Key="BgCanvas" Color="{ThemeResource BgCanvasColor}"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
```
|
||||
|
||||
At runtime, the root `Page.RequestedTheme = ElementTheme.Dark | Light`
|
||||
switch propagates down the visual tree instantly — no app restart, no
|
||||
flicker. The custom title bar (drawn via `AppWindow.TitleBar.ExtendsContent`)
|
||||
gets a manual color update on the same code path since its system buttons
|
||||
aren't part of the XAML tree.
|
||||
|
||||
### System mode
|
||||
|
||||
When `UIPreferences.Theme == System`, the app reads
|
||||
`Application.Current.RequestedTheme` at startup and re-reads when the OS
|
||||
theme changes (via `UISettings.ColorValuesChanged`). This is the default —
|
||||
operators who don't care get whatever their Windows session is set to.
|
||||
|
||||
## Typography
|
||||
|
||||
### Scale (1.25 step ratio enforced)
|
||||
|
||||
| Token | Family | Size | Weight | Line-height | Letter-spacing |
|
||||
|---|---|---|---|---|---|
|
||||
| `text.display` | Inter | 22 | 600 | 1.2 | -0.01em |
|
||||
| `text.title` | Inter | 18 | 600 | 1.25 | -0.005em |
|
||||
| `text.heading` | Inter | 14 | 600 | 1.3 | 0 |
|
||||
| `text.body` | Inter | 13 | 400 | 1.45 | 0 |
|
||||
| `text.subtle` | Inter | 13 | 400 | 1.45 | 0 |
|
||||
| `text.caption` | Inter | 11 | 500 | 1.3 | 0.04em (smallcaps) |
|
||||
| `text.mono` | JetBrains Mono | 12 | 400 | 1.4 | 0 |
|
||||
|
||||
Body text caps at 65–75ch where it wraps. Inline status text doesn't wrap —
|
||||
it truncates with ellipsis.
|
||||
|
||||
### Fonts in WinUI 3
|
||||
|
||||
WPF's `pack://application:,,,/Assets/Fonts/#Inter` resource URI doesn't carry
|
||||
over. WinUI 3 uses `ms-appx:///Assets/Fonts/Inter.ttf#Inter`. Migration is
|
||||
mechanical but required.
|
||||
|
||||
## Spacing (8px grid)
|
||||
|
||||
| Token | Value | Use |
|
||||
|---|---|---|
|
||||
| `space.xs` | 4 | Icon-to-text, tiny gaps |
|
||||
| `space.s` | 8 | Row internal padding, pill padding |
|
||||
| `space.m` | 12 | Card internal padding |
|
||||
| `space.l` | 16 | Card padding, between cards |
|
||||
| `space.xl` | 24 | Section gap |
|
||||
| `space.xxl` | 32 | Page edge padding |
|
||||
| `space.xxxl` | 48 | Hero section / large blocks |
|
||||
|
||||
**Rhythm rule.** No two adjacent regions share the same padding value. The
|
||||
participant table breathes at `space.xl`; in-row controls compress to
|
||||
`space.s`. Same padding everywhere is monotony.
|
||||
|
||||
## Radii
|
||||
|
||||
| Token | Value | Use |
|
||||
|---|---|---|
|
||||
| `radius.s` | 6 | Pills, inline tags, menu items |
|
||||
| `radius.m` | 8 | Buttons, text inputs, dropdowns |
|
||||
| `radius.l` | 12 | Cards, drawers, modals |
|
||||
| `radius.pill` | 999 | Status pills, ISO toggle |
|
||||
|
||||
## Elevation
|
||||
|
||||
Elevation through **tone**, not through shadow. The dark surface makes
|
||||
realistic drop-shadows look bolted-on. A `bg.elevated` tone difference does
|
||||
the same job with less visual noise.
|
||||
|
||||
| Layer | Background | Border |
|
||||
|---|---|---|
|
||||
| Canvas | `bg.canvas` | none |
|
||||
| Card | `bg.surface` | `border.subtle` |
|
||||
| Drawer / Popover | `bg.elevated` | `border.strong` |
|
||||
| Modal | `bg.elevated` | `border.strong` + 50% canvas scrim |
|
||||
|
||||
## Icons
|
||||
|
||||
**Single icon system, one stroke width, one optical size.** The previous GUI
|
||||
inlined ~12 bespoke `<Path Data="...">` icons with stroke widths varying
|
||||
between 1.2 and 1.6. The redesign uses **WinUI 3's bundled Segoe Fluent Icons
|
||||
font** as the baseline, with a custom subset added only where a broadcast
|
||||
concept isn't covered (e.g. NDI signal lock, ISO routing state).
|
||||
|
||||
Sizes: 16 (inline), 20 (button), 24 (rail / hero).
|
||||
Stroke: inherited from font; no hand-stroked paths.
|
||||
|
||||
## Motion
|
||||
|
||||
- Ease-out exponential (`cubic-bezier(0.16, 1, 0.3, 1)`) for entry.
|
||||
- Ease-in-out for state changes that aren't entries.
|
||||
- Durations: 120ms for affordance feedback, 200ms for panel transitions,
|
||||
280ms hero (rarely used).
|
||||
- No bounce. No elastic. No spring overshoots.
|
||||
- **Never animate** layout properties. Animate `Translation` and `Opacity`
|
||||
(WinUI 3's composition layer handles these GPU-cheaply).
|
||||
|
||||
## Component decisions
|
||||
|
||||
### Buttons — finally have a real hierarchy
|
||||
|
||||
The previous design used `Wd.Button.Ghost` for everything. The redesign has
|
||||
**three commitments**:
|
||||
|
||||
| Variant | Use | Look |
|
||||
|---|---|---|
|
||||
| `Primary` | Single per surface, the brand action ("Apply", "Start session") | Cyan fill, near-black text |
|
||||
| `Secondary` | Common operator actions ("Refresh", "Presets") | Transparent fill, `border.strong`, hover cyan border |
|
||||
| `Tertiary` | Inline, low-frequency ("Dismiss", "Show advanced") | Text-only, no border, cyan on hover |
|
||||
| `Destructive` | Stop, leave, delete | Coral border, coral text, no fill |
|
||||
|
||||
**One Primary per surface.** If a screen has two primaries, the design is
|
||||
unranked.
|
||||
|
||||
### ISO toggle — keep, refine
|
||||
|
||||
The status-coded pill (LIVE cyan / ERROR coral / NO SIGNAL amber) is good.
|
||||
Two evolutions:
|
||||
|
||||
1. The hover treatment thickens to a 2px cyan border — preserve.
|
||||
2. Add a half-height ascender showing instantaneous audio level above the
|
||||
pill. The operator sees who's talking without needing the active-speaker
|
||||
row highlight to fire on next tick.
|
||||
|
||||
### Tables (Participants)
|
||||
|
||||
This is the product. The table gets:
|
||||
|
||||
- Row height 56 (current) → 64 to give the audio meter + signal indicator
|
||||
room to breathe.
|
||||
- The "active speaker" cyan left-border treatment stays. It's good.
|
||||
- One participant action per row at rest (the ISO toggle). Other actions
|
||||
(open preview, custom name, presets) live in a right-click context menu
|
||||
(already exists) and in a row hover-revealed kebab — *not* visible at rest.
|
||||
- Column count: avatar+name · NDI signal+codec · audio meter · output name ·
|
||||
ISO toggle. Five columns. The current six-plus + custom-name editing
|
||||
inline pushes density too far.
|
||||
|
||||
### Status — one place, not three
|
||||
|
||||
Recording / disk / session / control-surface state currently lives in:
|
||||
1. Rail bottom dot (engine status)
|
||||
2. Header right pill (status text)
|
||||
3. Footer columns (six monospace fields)
|
||||
|
||||
The redesign consolidates to **two places only**:
|
||||
|
||||
- **Header right** — session timer, REC indicator + count, disk-free.
|
||||
These are at-a-glance.
|
||||
- **Status overlay (popover from rail bottom dot)** — control surface URLs,
|
||||
log path, version, control-surface tokens. These are on-demand.
|
||||
|
||||
The footer goes away entirely. It was theatre, not information.
|
||||
|
||||
### Settings — drawer, not permanent panel
|
||||
|
||||
The 380px right settings panel is the single biggest spatial misallocation.
|
||||
Settings are rarely changed mid-show. The redesign moves them to a **right-side
|
||||
drawer** that slides in over the participants area, dismissable with `Esc`.
|
||||
The participants table reclaims full width when the drawer is closed.
|
||||
|
||||
Trigger: rail "settings" icon. Same affordance as today, different surface.
|
||||
|
||||
### Onboarding
|
||||
|
||||
First-launch only. Three panes max, each one panes deep — no carousel.
|
||||
Operator-tone copy ("Pick your NDI groups" not "Welcome to TeamsISO!").
|
||||
Skippable from the first frame.
|
||||
|
||||
### Empty states
|
||||
|
||||
The participants table empty state currently is implicit (rows just don't
|
||||
appear). The redesign adds **one** empty state with a single instructive
|
||||
sentence ("No NDI sources yet — open Teams and start a meeting") and a
|
||||
single secondary button ("Refresh"). No illustration. No mascot.
|
||||
|
||||
## Anti-patterns specific to this app (audited against absolute bans)
|
||||
|
||||
The current XAML has none of the impeccable absolute bans (no gradient text,
|
||||
no side-stripe borders, no glassmorphism). It does have:
|
||||
|
||||
- **Identical card grids** — the in-call control bar's seven identical ghost
|
||||
buttons. Redesign: collapse to a single dense bar with primary controls
|
||||
surfaced and secondary controls in an overflow menu.
|
||||
- **Status duplication** — fix as above.
|
||||
- **Bespoke SVG icons** — fix as above.
|
||||
|
||||
## Migration boundary
|
||||
|
||||
The view-model surface in `src/TeamsISO.App/ViewModels/` is the contract.
|
||||
The redesign rewrites everything in `Views/` (WinUI 3) but leaves view-model
|
||||
properties and commands untouched. Any place where the redesign needs a new
|
||||
piece of view-model state, the contract widens via additive properties —
|
||||
existing bindings keep working until the new view stops needing the old shape.
|
||||
|
||||
This means: the engine, the OSC bridge, the control surface, the preset
|
||||
store, the recording pipeline — none of those move. The redesign is
|
||||
a frontend-only operation.
|
||||
174
PRODUCT.md
Normal file
174
PRODUCT.md
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
# PRODUCT.md — TeamsISO
|
||||
|
||||
## Register
|
||||
|
||||
**Product.** This is a tool, not a destination. The design serves the operator
|
||||
running a live broadcast. The UI is judged by how invisible it gets once the
|
||||
show is rolling.
|
||||
|
||||
## Product purpose
|
||||
|
||||
TeamsISO is a per-participant NDI ISO controller for Microsoft Teams. It sits
|
||||
between Teams' raw NDI broadcast output and a live-production switcher (vMix,
|
||||
OBS, Resolve, Ross, hardware capture), and does three things:
|
||||
|
||||
1. **Routes** each guest as a clean, individually-addressable, normalized NDI
|
||||
source (consistent framerate, resolution, aspect, audio routing — regardless
|
||||
of what each participant's webcam is doing).
|
||||
2. **Records** every active ISO to disk simultaneously — raw BGRA + manifest
|
||||
+ ffmpeg convert script — so post-production gets a per-guest archive
|
||||
ready to cut from.
|
||||
3. **Orchestrates Teams itself** — launch/hide Teams windows, drive in-call
|
||||
controls (mute, camera, share, leave, raise hand, quick-join) via
|
||||
UIAutomation, so the operator never has to alt-tab away from the routing
|
||||
table while the show is live.
|
||||
|
||||
External control surface (REST + WebSocket + OSC on localhost) lets a
|
||||
Companion / Stream Deck / TouchOSC controller drive routing remotely.
|
||||
|
||||
## Users — the primary persona
|
||||
|
||||
**Solo operator.** One person, one Windows laptop or desk machine, often
|
||||
running the show alone from a hotel room, conference green room, or home
|
||||
studio. Picture them at 1:50am, twenty minutes before a live international
|
||||
broadcast, ambient room lights down, the Teams call already started, four
|
||||
guests joining staggered over the next ten minutes. They need to:
|
||||
|
||||
- See which participants are present, online, and producing NDI signal.
|
||||
- Toggle each one's ISO on as they join.
|
||||
- Confirm at a glance that recording is live, the disk has room, and the
|
||||
control surface is reachable.
|
||||
- Drop a marker if the host says something quotable.
|
||||
- Mute themselves without alt-tabbing.
|
||||
|
||||
If the UI demands more than a glance for any of those, the show suffers.
|
||||
|
||||
### Secondary personas (informed, not designed-for)
|
||||
|
||||
- **TD at a broadcast desk** — multi-monitor, may use the OSC bridge to a
|
||||
hardware control surface. Can tolerate a denser layout because their eyes
|
||||
aren't the only thing on the surface.
|
||||
- **Producer monitoring** — glances occasionally, mostly hands-off. Will see
|
||||
this app over someone's shoulder; first read matters.
|
||||
- **IT/AV admin** — installs it once, tunes config, walks away. Needs settings
|
||||
to be findable, not present-at-all-times.
|
||||
|
||||
The design optimizes for the solo operator. Everyone else is downstream.
|
||||
|
||||
## Brand
|
||||
|
||||
**Wild Dragon LLC.** Reference: wilddragon.net.
|
||||
|
||||
Palette anchors:
|
||||
- Canvas: near-black (`#0A0A0A`)
|
||||
- Primary accent: cyan (`#97EDF0`)
|
||||
- Secondary blue: (`#9AE0FD`)
|
||||
- Coral (error / destructive): (`#FB819C`)
|
||||
- Earth (warning): (`#423825`)
|
||||
|
||||
Typography:
|
||||
- Sans: **Inter** (variable, bundled as a resource — not assumed installed).
|
||||
- Mono: **JetBrains Mono** (also bundled).
|
||||
|
||||
The brand carries the surface but doesn't shout. Wild Dragon's authority is
|
||||
in the restraint, not the saturation.
|
||||
|
||||
## Voice and tone
|
||||
|
||||
**Operator-first, terse, broadcaster-native.** The UI talks like a confident
|
||||
peer, not a Slack bot.
|
||||
|
||||
- "Stop all" not "Are you sure you want to stop all ISOs?"
|
||||
- "Disk low — 8.3 GB" not "Heads up! Your disk space is running low."
|
||||
- "Joined call · 4 guests" not "You have successfully joined a Teams meeting!"
|
||||
- Numbers carry their unit, no sentence wraps them.
|
||||
- Never apologetic. Never bubbly. Never "Let's get started!"
|
||||
|
||||
When something goes wrong, name it: "NDI receiver dropped — restarting" beats
|
||||
"Something went wrong, please try again."
|
||||
|
||||
## Strategic principles
|
||||
|
||||
These are the design's load-bearing commitments. Any choice that contradicts
|
||||
one of these is wrong, even if it would otherwise be pretty.
|
||||
|
||||
### 1. One operator, one screen, one show.
|
||||
|
||||
The design is for someone running a live broadcast alone. Their attention
|
||||
budget for chrome is roughly zero. Anything that's not the participants table
|
||||
should fade until it's needed.
|
||||
|
||||
### 2. The participants table IS the product.
|
||||
|
||||
Everything else is support staff. Routing toggles, ISO state, and per-guest
|
||||
signal health get the real estate, the contrast, and the typographic hierarchy.
|
||||
|
||||
### 3. Progressive disclosure, not progressive density.
|
||||
|
||||
The current GUI's failure mode is "every feature gets its own visible button."
|
||||
The redesign's failure mode would be the opposite — burying important things
|
||||
in menus. The discipline: surface the half-dozen actions an operator needs
|
||||
mid-show; hide setup, presets, control-surface config, and exotic options
|
||||
behind purposeful entry points.
|
||||
|
||||
### 4. At-a-glance status is sacred.
|
||||
|
||||
Recording state, disk health, control-surface reachability, session timer,
|
||||
NDI signal-per-participant — these are the operator's situational awareness.
|
||||
They must be readable in peripheral vision, in one place, without scanning.
|
||||
|
||||
### 5. Confident neutrality over decorative warmth.
|
||||
|
||||
This is a broadcast tool. It looks like one. No empty-state mascots, no
|
||||
illustrated onboarding cards, no celebratory toasts. Restraint is the brand.
|
||||
|
||||
## Anti-references — what this is NOT
|
||||
|
||||
The "vibe-coded GUI" failure mode is the enemy. The redesign should never
|
||||
read as AI-generated. Concretely, this means none of:
|
||||
|
||||
- **Generic SaaS dashboard.** No "hero metric + supporting stats + gradient
|
||||
accent" cards. No "icon + heading + body text" card grids.
|
||||
- **Cards-in-a-grid template.** Same-sized cards repeated endlessly is the
|
||||
defining LLM-design tell. If a layout would benefit from cards-in-a-grid,
|
||||
it benefits more from a table.
|
||||
- **Card-with-icon-and-text rows.** The in-call control bar's current
|
||||
"icon + label" buttons (Mute / Camera / Share / Marker / Notes / Leave)
|
||||
read AI-generated. The redesign uses iconography differently.
|
||||
- **Zoom pastel.** Soft purples, friendly mint greens, rounded everything,
|
||||
Inter-at-low-weight.
|
||||
- **Skeuomorphic broadcast hardware.** No woodgrain, no chrome bezels, no
|
||||
fake LCD readouts, no metallic gradients. Wild Dragon's confidence is in
|
||||
flat surfaces with real typography.
|
||||
- **Tour-everything onboarding.** No "Let's get started!" wizards with cute
|
||||
copy. The OnboardingWindow exists for first-launch config, not pageantry.
|
||||
- **Modal-as-first-thought.** Settings, presets, help all currently live in
|
||||
modals; some should be drawers or inline-progressive. Modal is a last resort.
|
||||
|
||||
## Technical constraints (informing design)
|
||||
|
||||
- Windows-only (Teams' NDI is Windows-only anyway).
|
||||
- **WinUI 3 / Windows App SDK** is the new frontend target.
|
||||
- Engine layer (.NET 8) is preserved verbatim — view-model surface is the
|
||||
swap boundary.
|
||||
- Bundled fonts via WinUI 3's `FontFamily` packaging (the WPF
|
||||
`pack://...#Inter` resource URI doesn't translate; needs migration).
|
||||
- MSIX-signed installer is on the v1.0 path; the new shell needs to package
|
||||
cleanly through that pipeline.
|
||||
- The external control surface (REST/WebSocket on `:9755`, OSC on `:9000`)
|
||||
must not regress — its HTML control panel at `/ui` is a separate design
|
||||
surface but shares brand tokens.
|
||||
|
||||
## What "done" looks like
|
||||
|
||||
The redesign is finished when:
|
||||
|
||||
1. A first-time operator can launch TeamsISO, join a Teams meeting, and
|
||||
route their first ISO without reading documentation.
|
||||
2. A returning operator at 1:50am can find the four things they need
|
||||
(participant signal · ISO toggle · recording state · disk free) in under
|
||||
half a second of glance.
|
||||
3. Nothing on the surface reads as AI-generated. Show this to a working
|
||||
broadcast engineer and they say "someone who knows the job built this."
|
||||
4. The design system is documented in DESIGN.md tightly enough that a future
|
||||
contributor can add a new view that looks like it belongs.
|
||||
Loading…
Reference in a new issue