Recording HUD
Recording HUD: two variants, one shared live meter & controls
With logo: the canonical default (task 2623), Nockerl is listening
No logo: explicit opt-out for tightly embedded chrome
State: Recording · 0:08. The HUD is live & token-colored.
On Voice (macOS) this floating command pill is the top-of-window surface. Voice is a
menu-bar utility with no title bar, so the pill (not a TopBar) is
its brand-anchored header: the mark, a divider, and the morphing status content in a capsule
with the 1.5pt cyan outline. Android and web carry a real top bar; Voice uses this pill.
// macOS / iOS: SwiftUI, from the NockerlDesign package (the packaged pill// aligned VALUE-EXACT to the web canon: logo → gray divider → the ONLY warm dot →// theme-ink mono timer → cyan meter, cyan pill). Driven by `phase`; the app's// NSPanel still owns window presentation, this is the pill it hosts.import NockerlDesign
NockerlRecordingHUD(phase: .recording, elapsedLabel: "0:07", levels: levels) // pulsing dot + mono timer + live cyan meterNockerlRecordingHUD(phase: .paused, elapsedLabel: "0:41", levels: frozen) // dimmed dot + muted timer/meter + "Paused"NockerlRecordingHUD(phase: .transcribing) // indeterminate spinner + "Transcribing…"NockerlRecordingHUD(phase: .error, errorMessage: "Mic unavailable") // warn triangle + text; border → statusWarning
// showBrand:false drops the logo+divider (embedded chrome); animate:false freezes// the pulse for snapshots; animateBars opts into the ~80ms per-bar ease.// Android: Jetpack Compose, from the NockerlDesign package (the packaged// pill aligned VALUE-EXACT to the web canon; same anatomy + color split). Driven// by `phase`.import com.dizyx.nockerl.design.components.NockerlRecordingHudimport com.dizyx.nockerl.design.components.NockerlRecordingHudPhase
NockerlRecordingHud( phase = NockerlRecordingHudPhase.RECORDING, elapsedLabel = voice.elapsedLabel, amplitudes = voice.amplitudes, // 5 levels, ln(raw+1)/ln(32768))// …or PAUSED (frozen muted meter + "Paused") / TRANSCRIBING (spinner) / ERROR (warn row).// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform; this// is the intended @dizyx/nockerl-react API, a single controlled overlay component.// The level meter geometry/math matches the native apps exactly.)
export function DictationOverlay({ rec }: { rec: RecorderState }) { return ( <RecordingHud phase={rec.phase} // 'recording' | 'paused' | 'transcribing' | 'result' | 'error' levels={rec.levels} // number[] (0…1), one per meter bar elapsed={rec.elapsedSeconds} // drives the m:ss mono timer pasted={rec.pasted} // result phase: true → "Pasted", false → "Copied to clipboard" error={rec.error} // shown in the error row when phase === 'error' /> );}Parameters
Section titled “Parameters”RecordingHUD is an imperative @MainActor controller: you call phase methods, not
pass props. The 5-bar Equalizer + PulsingDot are file-private views.
| Style | Type | Default | Description |
|---|---|---|---|
showRecording() | func | Enters the recording phase: resets the 5-sample level ring + the elapsed clock, starts the 250ms ticker, and presents the panel. | |
updateLevel(_:) | func (Float) | Pushes one audio level (0…1) into the ring buffer. Each bar maps via ln(raw+1)/ln(32768) (raw = level·32767), clamped to [0.08, 1]. | |
showTranscribing() | func | Swaps the pill content to an indeterminate ProgressView + "Transcribing…"; stops the ticker. | |
showResult(pasted:) | func (Bool) | Success row: checkmark.circle.fill "Pasted" or doc.on.clipboard "Copied to clipboard". Auto-hides after 1.8s. | |
showError(_:) | func (String) | Error row: a warning triangle (warm) + the message (≤4 lines, ≤380pt). The pill border turns warning. Auto-hides after 8s. | |
Equalizer | View (private) | 5 capsule bars, 3pt wide, 3pt gap, 20pt tall; bar height eases over 0.08s. The brand cyan accent fill. |
Two internal @Composable strips selected by recorder state. The HUD is uncontrolled
beyond its inputs; depth comes from the nockerlShadow Level3 on the chrome pill.
| Parameter | Type | Default | Description |
|---|---|---|---|
elapsedSeconds * | Int | Elapsed recording time. Rendered m:ss in the error (warm) color, bodySmall medium. | |
amplitudes | List<Float> | emptyList() | The live levels (0…1) for LiveEqualizer; held as a 5-slot ring in VoiceRecorderState, log-normalized like Swift. |
accent * | Color | Engine accent for the pill border (1.5dp). Matches the input bar so the strip clearly belongs to it. | |
onCancel * | () -> Unit | Discards the take. Rendered inline as a GHOST NockerlButton ("Cancel"). | |
onStop * | () -> Unit | Stops + sends to transcription. (Wired through; the strip currently surfaces only Cancel inline.) | |
LiveEqualizer | @Composable | A Canvas of rounded bars (3dp wide, 3dp gap, 20dp tall), each height tweened 80ms; min visible 0.08. Drawn in the error color. |
| Prop | Type | Default | Description |
|---|---|---|---|
phase * | 'recording' | 'paused' | 'transcribing' | 'result' | 'error' | Which surface the HUD shows. recording animates the meter; paused freezes it; transcribing shows the indeterminate spinner; result shows the success check + "Pasted"/"Copied to clipboard"; error shows the warning row. | |
levels * | number[] | Audio levels (0…1), one per meter bar (5 to match native). Mapped through the same ln(raw+1)/ln(32768) curve; only bar height animates. | |
elapsed * | number | Elapsed seconds, rendered m:ss in the font.family.mono token, tabular, in the warm record color. | |
pasted | boolean | For phase === "result": true → check + "Pasted"; false → clipboard + "Copied to clipboard"; omit → generic "Done". Mirrors Voice result(pasted: Bool). The mark carries the earned brand cyan (not green), a genuine success beat. | |
dismissing | boolean | Plays the result dismiss. After the ~1.8s success beat the pill shoots straight down and fades (constant height; width already shrunk to the check + word). Motion, eyeball-gated; VRT captures the resting still only. | |
error | string | Message shown in the error row (color + icon + text, never color alone) when phase === "error". |
Stop / pause / cancel are real, keyboard-operable icon buttons (each with an
aria-label); the meter play/pause is a separate control. The surface carries
role="status" with an off-screen live label so the phase + elapsed are announced
without re-announcing on every meter frame.