Components

Recorder components

Everything on the Record sheet: status and clock, the cassette and level meter, where the file will land, warnings, the action row, the dialog shell and the cassette transport preferences.

Rendered from the shipped source with synthetic demo data. Not captured from a running app. These are UI states only: native microphone capture has not passed host qualification, so nothing here implies working hardware recording.

Capture status and clock

The two facts the recorder never hides: what state the capture is in and how long it has run. Everything else on the sheet hangs off these.

●
Capture-state pill3 of 7 styled
Tells the user at a glance whether the recorder is idle, starting, running, paused, saving, saved or has failed. Seen at the top of the Recorder dialog every time it opens.
apps/desktop/src/App.tsx (RecordModal) · state derivation; styles.css .capture-state
Ready · not recording, not busy, no error, status not saved. Seen when the dialog opens onto an idle capture (resume path). There is no state-ready CSS rule, so the dot is the default green.
Ready
Starting · busy and not recording. The first moments after Record is pressed while the microphone preference is resolved. No state-starting rule: default green dot.
Starting
Recording · status.state recording. Red text and red dot (.state-recording).
Recording
Paused · status.state paused. Amber text and dot (.state-paused).
Paused
Saving · local saving flag or status.state saving. No state-saving rule: it looks exactly like Ready, Starting and Saved.
Saving
Saved · status.state saved. Transient: the poller hands the saved asset to the library and the dialog closes. No state-saved rule.
Saved
Error · any error text is set. Red text and red dot (.state-error).
Error
⇅
State precedencequirk
Several things can be true at once (saving, a leftover error, a busy command) but the label shows only one. This shows which one wins and what it hides.
apps/desktop/src/App.tsx (RecordModal) · state ternary chain
Saving beats Error · saving=true and an error is set: the label says Saving, the error banner still shows below
Saving
Error beats Paused · status.state paused with a leftover error: the label says Error and Paused is never shown
Error
Error beats Recording · a failed Stop & save sets error while capture continues: the label stays Error while clock and reels keep running
Error
Error beats Saved · the saved asset is not found in the library: the label says Error although the audio saved
Error
Starting beats Saved · busy while not recording with status saved: Starting wins. Saved only shows when nothing is busy
Starting
⏱
Recording clock
Shows elapsed capture time as hh:mm:ss so the user can see the take is progressing. Freezes while paused.
apps/desktop/src/App.tsx (RecordModal) · clock; styles.css .recording-clock
Start · 00:00:00 before any audio, and in the Starting and failed-start states
00:00:00
Mid-take · 42 seconds
00:00:42
Just under an hour · minutes and seconds only roll at 60
00:59:59
Over an hour · 1h 14m 9s
01:14:09
Over 99 hours · hours are padded to two digits, not capped: three digits just widen the string
100:00:00

Cassette and level meter

The two live visuals: the cassette whose reels turn while audio is being captured, and the 36-segment meter that shows input level and warns about clipping.

📼
Clock and cassette tape
The recorder display row: clock on the left, cassette on the right. The reels turn only while a take is actively recording, so the cassette doubles as a heartbeat.
apps/desktop/src/App.tsx (RecordModal) · apps/desktop/src/cassette.tsx (CassetteTape)
Idle · active=false: static tape
00:00:00
Recording · status.state recording and not saving, animation preference on: tape-running turns both reels (3s linear loop). A system reduced-motion setting overrides it (final media rule: animation none !important)
00:00:42
Recording, animation preference off · same state with Transport preference "Animate cassette reels" unchecked: no tape-running class, reels static
00:00:42
Paused · active=false while paused: the class is removed, so the reels return to their rest angle rather than freezing in place
00:01:20
Saving · the active expression excludes saving: reels stop. The clock keeps its last value
00:25:00
▮
Input level meter
Shows how loud the microphone is right now in 36 segments, turning amber then red near the top, so the user can back off before the audio distorts. Warns in words when it clips.
apps/desktop/src/App.tsx (RecordModal) · level-panel, meter; styles.css .meter i.lit.amber / .red
Level 0 · silence: no segments lit
Input level - Studio USB Microphone
Level 0.25 · 9 segments lit, all green
Input level - Studio USB Microphone
Level 0.6 · 22 lit, still green (amber begins at the 27th segment)
Input level - Studio USB Microphone
Level 0.75 · 27 lit: the 27th segment is the first amber one (index > 25)
Input level - Studio USB Microphone
Level 0.9 · 33 lit: amber 27-31, red 32-33 (red is index > 30)
Input level - Studio USB Microphone
Level 0.95 · exactly 0.95: 35 lit, but the clipping label needs level > 0.95
Input level - Studio USB Microphone
Level 0.97 · clipping label appears; 35 lit including 4 red
Input level - Studio USB MicrophoneClipping — lower your input
Level 1 · full scale: all 36 lit, clipping label
Input level - Studio USB MicrophoneClipping — lower your input
🎙
Input name in the level panel
Names the microphone the level belongs to. Shows a loading placeholder while the preference resolves and a failure label when no microphone could be used.
apps/desktop/src/App.tsx (RecordModal) · inputName
Loading · initial value before the start command resolves
Input level - Loading microphone preference...
Device name · after the preferred device is resolved
Input level - Studio USB Microphone
Unavailable · start failed and no capture is active
Input level - Microphone unavailable
Active microphone · resumed capture where the device id is not in the device list: the raw id, or the literal "Active microphone"
Input level - Active microphone

Pending metadata and destination

What the recording will be called and where it will land. These can be edited while the take runs and are applied when the user stops and saves.

T
Pending title fieldsaving gate differs
Lets the user name the take while recording instead of afterwards. Seen on the recorder sheet above the location.
apps/desktop/src/App.tsx (RecordModal) · .pending-title
Default · initial value is a timestamped placeholder; blank is allowed
Named · edited, or hydrated from status.title
Disabled · only while the local saving flag is true (Stop & save in flight). Not disabled when the backend reports saving after a resume
.wav
Pending filename fielddesktop only
Lets the user choose the file name the audio will be saved under. Desktop only, because the browser preview stores audio in IndexedDB rather than as a file.
apps/desktop/src/App.tsx (RecordModal) · api.desktop && …
Editable · hydrated from status.pendingFilename, which includes the .wav extension
Empty while starting · before the first status arrives the value is "" and the field is disabled because busy
Disabled · disabled when finalizing or busy: also greyed during every start, pause and resume command
Browser preview · not rendered at all (api.desktop false): the dialog goes straight from the title field to the location block
(no markup rendered)
📁
Record location
Tells the user where the file will be written and offers the folder controls. The label changes as the capture moves from the default workspace to a chosen folder to a finished file.
apps/desktop/src/App.tsx (RecordModal) · record-location, api.chooseDestination, api.openFolder
Managed workspace · no destination reported yet: falls back to the workspace data directory. Open folder is disabled because status.destination is empty
Managed workspaceC:\Users\Demo\Music\CassetteFilename and folder changes apply when you stop and save.
Disk space unavailable
Loading location · no workspace known yet
Managed workspaceLoading workspace location...Filename and folder changes apply when you stop and save.
Disk space unavailable
Save to · a destination exists: the full target path (folder plus pending filename) is shown
Save toC:\Users\Demo\Music\Cassette\weekly-planning-notes.wavFilename and folder changes apply when you stop and save.
48300.0 MB free - about 139h 45m remaining
Saved to · status.state saved with savedPath
Saved toC:\Users\Demo\Music\Cassette\weekly-planning-notes.wavFilename and folder changes apply when you stop and save.
48300.0 MB free - about 139h 45m remaining
Choosing a folder · native folder dialog open: Change folder reads "Choosing folder..." and is disabled
Save toC:\Users\Demo\Music\Cassette\weekly-planning-notes.wavFilename and folder changes apply when you stop and save.
48300.0 MB free - about 139h 45m remaining
Finalizing · both folder buttons disabled while saving
Save toC:\Users\Demo\Music\Cassette\weekly-planning-notes.wavFilename and folder changes apply when you stop and save.
48300.0 MB free - about 139h 45m remaining
Browser preview · wording changes to IndexedDB; no folder buttons and no disk line
Managed workspaceLoading workspace location...Browser preview stores audio in IndexedDB.
💾
Free space and time remainingsize() has no GB
Shows how much room the destination drive has and roughly how long the recorder can keep going, so a long take is not lost to a full disk.
apps/desktop/src/App.tsx (RecordModal) · size() in apps/desktop/src/timeline.ts
Plenty · size() has no GB tier, so 48.3 GB prints as 48300.0 MB. 503125 s at 96,000 bytes/s is 139h 45m
Save toC:\Users\Demo\Music\Cassette\weekly-planning-notes.wavFilename and folder changes apply when you stop and save.
48300.0 MB free - about 139h 45m remaining
Below 1 GB · 640.0 MB free (the backend also raises its 1 GB warning)
Save toC:\Users\Demo\Music\Cassette\weekly-planning-notes.wavFilename and folder changes apply when you stop and save.
640.0 MB free - about 1h 51m remaining
Below 30 minutes · 144.0 MB at 48 kHz mono 16-bit is exactly 25 minutes
Save toC:\Users\Demo\Music\Cassette\weekly-planning-notes.wavFilename and folder changes apply when you stop and save.
144.0 MB free - about 0h 25m remaining
Free space known, no estimate · remainingSeconds null: only the free-space part is printed
Save toC:\Users\Demo\Music\Cassette\weekly-planning-notes.wavFilename and folder changes apply when you stop and save.
48300.0 MB free
Unavailable · freeBytes null
Save toC:\Users\Demo\Music\Cassette\weekly-planning-notes.wavFilename and folder changes apply when you stop and save.
Disk space unavailable
▸
Recording details disclosure
Tucks the less-used fields (tags, which microphone) behind a disclosure so the recorder stays calm. The microphone is shown read-only because the choice is made in Settings.
apps/desktop/src/App.tsx (RecordModal) · details.record-details
Collapsed · default state; the whole disclosure is hidden in compact view
Recording details
Open, empty · tags placeholder "Ideas, interview…"; the microphone select is always disabled
Recording details
Open, with tags · tags hydrated from status.tags and joined with ", "
Recording details
Open, saving · tags input disabled while saving; the microphone select has the hover tooltip "Choose the microphone in Settings before recording"
Recording details
Open, microphone unavailable · single fallback option when the device list has no match
Recording details

Warnings and errors

What the recorder says when disk space runs low or something fails. Both appear above the action buttons so they are seen before the user stops.

⚠
Disk warning bannerfallback unreachable
Warns the user in plain words that the drive is nearly full so they can stop and save or move the destination. Rendered with role=status.
apps/desktop/src/App.tsx (RecordModal) · .form-error[role=status]; strings from apps/desktop/src-tauri/src/backend.rs
Under 1 GB · backend status.warning
Less than 1 GB is free on the destination drive.
Under 30 minutes · backend status.warning
Less than 30 minutes of recording space remain.
Recording stopped by low disk · backend context.warning, overrides the thresholds
Recording stopped because disk space is low. Check the saved path or recovery error.
Component fallback text · shown only if free space or remaining time is under threshold but status.warning is empty; the desktop backend always sets a warning in those cases, so this text is effectively unreachable
Low disk space. Stop and save soon, or choose a destination with more room.
✖
Error bannerdouble full stop
Explains a failure in the user's terms and says what to do. Rendered with role=alert. It uses the same amber panel as the warning.
apps/desktop/src/App.tsx (RecordModal) · .form-error[role=alert]; startWithPreferredInput (cassette.tsx); stopWithPendingMetadata (recording.ts)
Selected microphone unavailable · thrown by startWithPreferredInput; Cassette never silently switches input
Command still busy · backend gate message surfaced from a failed pause or stop
Stop failed and details failed too · stopWithPendingMetadata joins messages with ". "; a message that already ends in a full stop produces two
Saved but not found · raised by the poller when the saved asset is missing from the library list
⚡
Error shown while capture continueslabel vs state
A transient error pins the Error label even though the take is still running, because only the next command clears it. Shown so a reader knows the label can disagree with the clock.
apps/desktop/src/App.tsx (RecordModal) · error is cleared only by start() or act()
Local error, status recording · the clock and reels keep running; Pause is still enabled because it checks status.state, not the label
Error
00:00:42
Backend error state with a live capture · status.state error: Pause disabled, Stop & save still available, reels stopped
Error
00:05:10

Actions and layout

The buttons that start, pause and finish a take, and the compact view that shrinks the sheet to its essentials.

▶
Recorder action row
The two big controls the spec calls out: pause/resume and stop. When no take is active the row collapses to a single start/retry button.
apps/desktop/src/App.tsx (RecordModal) · .recorder-actions
Recording · Pause and Stop & save
Paused · Pause becomes Resume
Command in flight · busy: both disabled, labels unchanged
Saving · Stop & save becomes a spinning "Saving…" (shared .spin animation); Pause disabled
Backend error state · Pause disabled, Stop & save still enabled so the take can be rescued
Starting · single disabled button "Starting…"
Not recording · start failed, idle or already saved: "Retry recording", even when nothing failed
⇱
Compact vs expanded recorder
Lets the user shrink the recorder to the clock, meter and controls when they want it out of the way. It is the same dialog in the same window, not a floating widget.
apps/desktop/src/App.tsx (RecordModal) · compact state; styles.css .recorder-workspace.compact
Expanded · default: cassette and Recording details visible, button reads "Compact view"
Recording
00:00:42
Input level - Studio USB Microphone
Save toC:\Users\Demo\Music\Cassette\weekly-planning-notes.wavFilename and folder changes apply when you stop and save.
48300.0 MB free - about 139h 45m remaining
Recording details
Compact · cassette illustration and details disclosure are hidden, display row shrinks to 70px, button reads "Expand recorder"
Recording
00:00:42
Input level - Studio USB Microphone
Save toC:\Users\Demo\Music\Cassette\weekly-planning-notes.wavFilename and folder changes apply when you stop and save.
48300.0 MB free - about 139h 45m remaining
Recording details

Recorder dialog shell

The modal the recorder lives in. Whether it can be dismissed depends on what the capture is doing, so a running take is never closed by accident.

▭
Recorder dialog and close gating
Holds the recorder over the library. The close button, and Escape, work only when nothing is recording and nothing is busy. Clicking the dimmed backdrop does nothing in any state.
apps/desktop/src/App.tsx (Modal) · apps/desktop/src/App.tsx (RecordModal, closeable={!recording && !busy})
Closeable · idle after a failed start: close button shown
Error
00:00:00
Input level - Microphone unavailable
Managed workspaceC:\Users\Demo\Music\CassetteFilename and folder changes apply when you stop and save.
Disk space unavailable
Recording details
Not closeable: recording · no close button; Escape is swallowed (onCancel calls preventDefault and onClose is not called)
Recording
00:00:42
Input level - Studio USB Microphone
Save toC:\Users\Demo\Music\Cassette\weekly-planning-notes.wavFilename and folder changes apply when you stop and save.
48300.0 MB free - about 139h 45m remaining
Recording details
Not closeable: starting · busy: no close button
Starting
00:00:00
Input level - Loading microphone preference...
Managed workspaceC:\Users\Demo\Music\CassetteFilename and folder changes apply when you stop and save.
Disk space unavailable
Recording details
Not closeable: saving · recording is still true while the file finalizes
Saving
00:25:00
Input level - Studio USB Microphone
Save toC:\Users\Demo\Music\Cassette\weekly-planning-notes.wavFilename and folder changes apply when you stop and save.
48300.0 MB free - about 139h 45m remaining
Recording details

Transport preferences

The optional cassette feel: whether the reels animate and whether the transport plays soft mechanical sounds. Stored on this device only. Lives in Settings but governs the recorder and the player.

⚙
Cassette transport preferencespreview can be silent
Lets the user turn reel animation and mechanical sounds on or off, set their volume, and choose which of the five cues (start, stop, eject, rewind, fast forward) may sound. Seen in Settings under the microphone panel. Saved to localStorage key cassette.transport.v1.
apps/desktop/src/cassette.tsx (TransportPreferences, TransportProvider)
Defaults · animation on, sound off, volume 20%, all five cues on; Preview button disabled while sound is off

Cassette transport

Reels move only during recording or playback. Your system’s reduced-motion setting takes priority.

Optional local cues may be picked up by your microphone. Off by default.

Sound on · volume 60%; Preview eject sound becomes enabled

Cassette transport

Reels move only during recording or playback. Your system’s reduced-motion setting takes priority.

Optional local cues may be picked up by your microphone. Off by default.

Animation off · reels static during recording and playback

Cassette transport

Reels move only during recording or playback. Your system’s reduced-motion setting takes priority.

Optional local cues may be picked up by your microphone. Off by default.

Some cues off · only Start and Stop will sound (cue() needs sound on, the cue ticked and volume above 0)

Cassette transport

Reels move only during recording or playback. Your system’s reduced-motion setting takes priority.

Optional local cues may be picked up by your microphone. Off by default.

Volume zero, sound on · Preview eject sound is enabled but plays nothing: cue() needs volume above 0

Cassette transport

Reels move only during recording or playback. Your system’s reduced-motion setting takes priority.

Optional local cues may be picked up by your microphone. Off by default.

Eject cue unticked, sound on · Preview eject sound is enabled but silent because the eject cue is off

Cassette transport

Reels move only during recording or playback. Your system’s reduced-motion setting takes priority.

Optional local cues may be picked up by your microphone. Off by default.