M2-02: Playback — hear a memo #42

Merged
wk merged 1 commit from m2-02-playback into main 2026-08-09 15:01:13 -04:00
Member

Closes #29.

A transport at the bottom of the memo detail screen — play/pause, a scrubber, elapsed and
remaining. It hangs off a safe area inset rather than the transcript list, because three of that
screen's four branches are not lists and playback must not depend on a transcript existing: a
memo whose words were deleted from the Files app is exactly one somebody wants to hear.

The substance is the audio session. Capture and playback hand it over and never share it —
PlaybackSession mirrors RecordingSession, each states its category on every activation, and
neither inherits anything from the other. Playback refuses while a capture runs and answers with a
sentence rather than a dead button. A capture starting while a memo plays stops the playback on the
first line of start(), synchronously, before the session is touched.

One player for the app, like the recorder and the library, because two windows over one session is
two memos playing at once.

The decision record is 2026-08-09-handing-the-audio-session-between-capture-and-playback.md in
stash-docs, on the branch PR #5 carries.

What the reviews changed

Four Codex passes, with every disposition in .agent/m2-02-playback/SUMMARY.md. The four that
changed the design rather than a detail:

  • Pausing playback by watching a recorder flag from the view is not an ordered handover. SwiftUI
    delivers that change whenever it schedules the update, which can be after the session has already
    been reconfigured for capture — and the pause would then deactivate the recording's session.
  • A player per detail view breaks under multiple scenes.
  • With two windows, the play button acted on whatever the shared player held, so it could have
    played the other window's recording.
  • releaseForCapture() gave up ownership and then declined to deactivate, so a start that failed
    at the microphone prompt left the playback session active with nobody able to release it.

What was validated

In the simulator, before this branch was pushed: a memo plays, pauses and resumes; a memo played to
its end rewinds and plays again from the start; tapping play during a recording says why; a
recording starts after a memo has been played; a memo plays after a recording has been stopped; a
memo whose audio will not decode says so, with no scrubber and no total, and goes on saying so when
play is tapped.

318 unit tests pass. AudioDuration's CMTime validation is the memo list's existing rule, which
had never had a test until now.

What was NOT validated

  • Scrubbing. The simulator takes no drag input from a script, so the seek and the suppression
    around it are unexercised. This is the largest gap in the claim.
  • The lock screen Record control taking the session from a playing memo.
  • Headphones pulled out mid-playback.
  • Two windows.

The build is on the device; those five are the on-device pass.

Closes #29. A transport at the bottom of the memo detail screen — play/pause, a scrubber, elapsed and remaining. It hangs off a safe area inset rather than the transcript list, because three of that screen's four branches are not lists and **playback must not depend on a transcript existing**: a memo whose words were deleted from the Files app is exactly one somebody wants to hear. The substance is the audio session. Capture and playback hand it over and never share it — `PlaybackSession` mirrors `RecordingSession`, each states its category on every activation, and neither inherits anything from the other. Playback refuses while a capture runs and answers with a sentence rather than a dead button. A capture starting while a memo plays stops the playback on the first line of `start()`, synchronously, before the session is touched. One player for the app, like the recorder and the library, because two windows over one session is two memos playing at once. The decision record is `2026-08-09-handing-the-audio-session-between-capture-and-playback.md` in `stash-docs`, on the branch PR #5 carries. ## What the reviews changed Four Codex passes, with every disposition in `.agent/m2-02-playback/SUMMARY.md`. The four that changed the design rather than a detail: - Pausing playback by watching a recorder flag from the view is not an ordered handover. SwiftUI delivers that change whenever it schedules the update, which can be after the session has already been reconfigured for capture — and the pause would then deactivate the *recording's* session. - A player per detail view breaks under multiple scenes. - With two windows, the play button acted on whatever the shared player held, so it could have played the other window's recording. - `releaseForCapture()` gave up ownership and then declined to deactivate, so a start that failed at the microphone prompt left the playback session active with nobody able to release it. ## What was validated In the simulator, before this branch was pushed: a memo plays, pauses and resumes; a memo played to its end rewinds and plays again from the start; tapping play during a recording says why; a recording starts after a memo has been played; a memo plays after a recording has been stopped; a memo whose audio will not decode says so, with no scrubber and no total, and goes on saying so when play is tapped. 318 unit tests pass. `AudioDuration`'s `CMTime` validation is the memo list's existing rule, which had never had a test until now. ## What was NOT validated - **Scrubbing.** The simulator takes no drag input from a script, so the seek and the suppression around it are unexercised. This is the largest gap in the claim. - The lock screen Record control taking the session from a playing memo. - Headphones pulled out mid-playback. - Two windows. The build is on the device; those five are the on-device pass.
A transport at the bottom of the memo detail screen — play/pause, a
scrubber, elapsed and remaining. It hangs off a safe area inset rather
than the transcript list, because playback must not depend on a
transcript existing.

Capture and playback hand the audio session over and never share it.
Playback refuses while a recording runs and says why; a recording
starting while a memo plays stops the playback on the first line of
start(), before the session is touched. Playback may only ever
deactivate a session it still owns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
wk merged commit 8a67d1373d into main 2026-08-09 15:01:13 -04:00
wk deleted branch m2-02-playback 2026-08-09 15:01:13 -04:00
Sign in to join this conversation.
No reviewers
No milestone
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
Stash/stash-ios!42
No description provided.