QuickCapture silently discards a voice note recorded before the entry is saved #90

Closed
opened 2026-07-15 19:49:59 +00:00 by claude-bot · 1 comment
Contributor

Severity: High · Confidence: High · Effort: S · Category: ui

Evidence

  • app/src/components/layout/QuickCapture.tsx:54-63 - stop(): if (!entryId) { setState(''idle''); resolve(); return; } drops the recording.
  • app/src/components/layout/QuickCapture.tsx:186 - savedEntryId set only after handleSave succeeds; the mic button is reachable before any save.

Problem
Opening Quick Capture, tapping the mic, recording, and tapping Stop before saving text throws the recording away with no error, toast, or indication - a natural order ("let me just say what I noticed first").

Impact
Real, silent data loss in the flagship mobile capture flow.

Fix
Short-term: disable the mic until the entry is saved (with an explanatory tooltip). Longer-term: auto-create a draft entry on first Record tap so audio always has a target.

Acceptance criteria

  • It is not possible to record audio that silently vanishes; either the control is disabled pre-save with an explanation, or the audio is retained and attached.

Filed from the 2026-07-15 codebase audit. Full report: docs/.internal/report-2026-07-15.md (gitignored).

**Severity:** High · **Confidence:** High · **Effort:** S · Category: ui **Evidence** - `app/src/components/layout/QuickCapture.tsx:54-63` - `stop()`: `if (!entryId) { setState(''idle''); resolve(); return; }` drops the recording. - `app/src/components/layout/QuickCapture.tsx:186` - `savedEntryId` set only after `handleSave` succeeds; the mic button is reachable before any save. **Problem** Opening Quick Capture, tapping the mic, recording, and tapping Stop before saving text throws the recording away with no error, toast, or indication - a natural order ("let me just say what I noticed first"). **Impact** Real, silent data loss in the flagship mobile capture flow. **Fix** Short-term: disable the mic until the entry is saved (with an explanatory tooltip). Longer-term: auto-create a draft entry on first Record tap so audio always has a target. **Acceptance criteria** - [ ] It is not possible to record audio that silently vanishes; either the control is disabled pre-save with an explanation, or the audio is retained and attached. --- _Filed from the 2026-07-15 codebase audit. Full report: `docs/.internal/report-2026-07-15.md` (gitignored)._
Author
Contributor

Fixed in 4f8e0fe (v7.1.0 wave 4).

The bug was broader than filed. It isn't only "record before saving" — savedEntryId is set only by handleSave, which also calls setOpen(false), and the close effect (useEffect on open) resets savedEntryId to null and calls voice.reset(). So entryId was always null while the voice panel was open, and every Quick Capture voice note hit the if (!entryId) { setState('idle'); resolve(); } path and vanished. There was no working path at all.

That also rules out the suggested short-term fix: disabling the mic until the entry is saved would have disabled it permanently. So I went with the longer-term option:

  • ensureEntryId() — tapping Record creates the entry first (using the typed content, or a 'Voice note' placeholder), so the recorder always has a target by the time Stop runs.
  • handleSave updates that entry (PUT /api/entries/:id) instead of creating a duplicate, and a voice-only note can now be saved with no typed text (the Save button no longer requires content once an entry exists; the transcript's "Use as content" still works).
  • The no-entry path now errors ('No entry to attach this recording to — nothing was saved.') instead of silently returning to idle — a safety net that should be unreachable.

Acceptance criteria:

  • It is not possible to record audio that silently vanishes — the audio is retained and attached, and the unreachable fallback surfaces an error rather than dropping it.

Verified on the dev server: app build clean, 63/63 app tests, api 238/238. CI green.

One trade-off worth noting: recording then closing without saving now leaves an entry with the 'Voice note' placeholder and the audio attached, rather than discarding the recording. That seemed clearly right for a tool whose first principle is that her data is irreplaceable — but it's easy to revisit.

Fixed in `4f8e0fe` (v7.1.0 wave 4). **The bug was broader than filed.** It isn't only "record before saving" — `savedEntryId` is set *only* by `handleSave`, which also calls `setOpen(false)`, and the close effect (`useEffect` on `open`) resets `savedEntryId` to `null` and calls `voice.reset()`. So `entryId` was **always** `null` while the voice panel was open, and **every** Quick Capture voice note hit the `if (!entryId) { setState('idle'); resolve(); }` path and vanished. There was no working path at all. That also rules out the suggested short-term fix: disabling the mic until the entry is saved would have disabled it permanently. So I went with the longer-term option: - **`ensureEntryId()`** — tapping Record creates the entry first (using the typed content, or a `'Voice note'` placeholder), so the recorder always has a target by the time Stop runs. - **`handleSave`** updates that entry (`PUT /api/entries/:id`) instead of creating a duplicate, and a voice-only note can now be saved with no typed text (the Save button no longer requires content once an entry exists; the transcript's "Use as content" still works). - **The no-entry path now errors** (`'No entry to attach this recording to — nothing was saved.'`) instead of silently returning to idle — a safety net that should be unreachable. **Acceptance criteria:** - [x] It is not possible to record audio that silently vanishes — the audio is retained and attached, and the unreachable fallback surfaces an error rather than dropping it. Verified on the dev server: app build clean, 63/63 app tests, api 238/238. CI green. One trade-off worth noting: recording then closing without saving now leaves an entry with the `'Voice note'` placeholder and the audio attached, rather than discarding the recording. That seemed clearly right for a tool whose first principle is that her data is irreplaceable — but it's easy to revisit.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/TeaLeaves#90
No description provided.