M1-11: Memo list on real data, and capture wired to it #28

Merged
wk merged 5 commits from m1-11-memo-list into main 2026-08-08 18:22:11 -04:00
Member

Closes #11.

The memo list stops showing sample data and reads the documents directory: every completed
capture, newest first, with the length read off its audio and a permanently not-synced cloud.
Swiping a row and tapping Delete removes the memo, its transcript and any leftover parts folder.
Relaunching shows the same list, because the list was never anything but a reading of the
directory.

Two commits, deliberately

The issue was written as the list alone. At the planning checkpoint Wes was offered the record
button as a separate issue and chose to fold it in — "Include it here." So the branch also
retires issue 03's design stub: the button drives AudioRecorder, the bar meters real audio
through AudioFileSink.onWrite, and DebugCaptureView loses its record controls and becomes
Settings → Diagnostics. The history splits along that line if the review wants two sittings.

The decisions worth arguing with

A memo's timestamp is the file's creation date, not its filename. The name carries local wall
time with no UTC offset, so it cannot name an instant — a memo made at 09:00 in California and
read on a phone still set to Eastern parses three hours out, which the row's relative dates put
straight on screen. The name stays authoritative for identity and for vault inbox processing.
Consequence for #12: a memo copied back onto the phone carries the copy's creation date.

The active capture is excluded on isFinalizing, not isRecording. stop() clears
isRecording before it closes the file and assembles the transcript. The rule lives in
CaptureNaming.activeBase rather than on the recorder, so it can be tested without an audio
engine.

Deletion removes the audio last, and only if nothing else failed. The audio file is what puts
the row on screen, so keeping it keeps the memo visible and the deletion retryable. There is no
Files-app route to a stranded file until #12.

Discovery requires a regular file, not just a matching name. A directory called
<base>_audio.m4a passes every name check there is, and once #12 opens the folder, treating one
as a memo means the first swipe deletes it recursively.

No confirmation dialog on delete. Swipe, then Delete. Wes on the first build: "I don't need
the second modal popup to confirm delete. Just swipe --> Delete."
This overrides the issue text,
which asked for a confirmation. Full swipe is off, so the row cannot go on release.

DebugCaptureView was kept, not deleted. Its ShareLinks are the only way a memo leaves the
phone until #12 turns on UIFileSharingEnabled, and removing it now would strand every recording
made in between.

Codex

Four passes, all recorded with their dispositions in .agent/m1-11-memo-list/SUMMARY.md.

  • Plan — caught the isRecording lifecycle boundary and the directory-masquerading-as-audio
    recursive delete. Both accepted; both would have shipped.
  • Review — seven findings, all accepted. The load-bearing ones: a partial delete still removed
    the audio and stranded the transcript with no row to retry from; the Record button stayed live
    during start(), so two taps built two engines; Diagnostics offered to share the in-flight
    recording.
  • Tests — showed the first 16 tests would survive several new guards being deleted. Now 116 in
    12 suites, and every new guard-test was verified by mutation: the production check was
    deleted or inverted, the named test confirmed red, the code restored. One finding rejected —
    the concurrent-start guard needs a real start(), so it is a device concern, not a unit test.
  • Security — no critical, high or medium. One low accepted (an unreadable file vanished from
    the list with no handle to delete it). One low deferred to #12 with a reason: the .parts
    folder is removed recursively, which only matters once the Files app can put things in it, and
    TranscriptAssembly already removes those folders the same way.

Two production changes came out of the test review rather than the code review, because the
guards were untestable as written: CaptureNaming.activeBase was extracted from AudioRecorder,
and date(ofBase:) now requires the whole name to be a capture base instead of parsing a prefix
and ignoring the tail.

Validated

  • make test — 116 tests in 12 suites, green. make lint — clean.
  • Simulator: the list renders real captures from the documents directory, newest first, with
    durations read off the audio and titles derived from the capture time.
  • On wes-iphone: recording works and the memo lands in the list. Wes confirmed the list and
    capture, then directed the delete change, which was rebuilt and reinstalled.

Not validated

Stating this plainly because a claim of coverage that does not exist is worse than none.

  • Simulator UI automation could not post clicks on this machine, so nothing interactive was
    driven there. The interactive paths rest on unit tests at the logic layer and on the device.
  • Of the eight numbered device tests handed over, Wes reported back on recording and the list.
    The others — double-tap on Record, the meter against a real voice, stopping from the Live
    Activity, force-quit recovery with the new list on screen, delete removing both files, and the
    time-zone change — were not reported and should be assumed unrun.
  • No microphone has reached LevelMonitor; it is tested with synthetic buffers only.
  • en_US_POSIX cannot be detected from a unit test — removing it leaves Locale.current, which
    is Gregorian on the simulator. The 2569-year failure only appears on a non-Gregorian device.

Follow-on

#27 — reading a memo's transcript on the phone. Came directly out of this: the list made it
obvious it is the only thing you can see about a memo.

Closes #11. The memo list stops showing sample data and reads the documents directory: every completed capture, newest first, with the length read off its audio and a permanently not-synced cloud. Swiping a row and tapping Delete removes the memo, its transcript and any leftover parts folder. Relaunching shows the same list, because the list was never anything but a reading of the directory. ## Two commits, deliberately The issue was written as the list alone. At the planning checkpoint Wes was offered the record button as a separate issue and chose to fold it in — *"Include it here."* So the branch also retires issue 03's design stub: the button drives `AudioRecorder`, the bar meters real audio through `AudioFileSink.onWrite`, and `DebugCaptureView` loses its record controls and becomes Settings → Diagnostics. The history splits along that line if the review wants two sittings. ## The decisions worth arguing with **A memo's timestamp is the file's creation date, not its filename.** The name carries local wall time with no UTC offset, so it cannot name an instant — a memo made at 09:00 in California and read on a phone still set to Eastern parses three hours out, which the row's relative dates put straight on screen. The name stays authoritative for identity and for vault inbox processing. Consequence for #12: a memo copied back onto the phone carries the copy's creation date. **The active capture is excluded on `isFinalizing`, not `isRecording`.** `stop()` clears `isRecording` before it closes the file and assembles the transcript. The rule lives in `CaptureNaming.activeBase` rather than on the recorder, so it can be tested without an audio engine. **Deletion removes the audio last, and only if nothing else failed.** The audio file is what puts the row on screen, so keeping it keeps the memo visible and the deletion retryable. There is no Files-app route to a stranded file until #12. **Discovery requires a regular file, not just a matching name.** A directory called `<base>_audio.m4a` passes every name check there is, and once #12 opens the folder, treating one as a memo means the first swipe deletes it recursively. **No confirmation dialog on delete.** Swipe, then Delete. Wes on the first build: *"I don't need the second modal popup to confirm delete. Just swipe --> Delete."* This overrides the issue text, which asked for a confirmation. Full swipe is off, so the row cannot go on release. **`DebugCaptureView` was kept, not deleted.** Its ShareLinks are the only way a memo leaves the phone until #12 turns on `UIFileSharingEnabled`, and removing it now would strand every recording made in between. ## Codex Four passes, all recorded with their dispositions in `.agent/m1-11-memo-list/SUMMARY.md`. - **Plan** — caught the `isRecording` lifecycle boundary and the directory-masquerading-as-audio recursive delete. Both accepted; both would have shipped. - **Review** — seven findings, all accepted. The load-bearing ones: a partial delete still removed the audio and stranded the transcript with no row to retry from; the Record button stayed live during `start()`, so two taps built two engines; Diagnostics offered to share the in-flight recording. - **Tests** — showed the first 16 tests would survive several new guards being deleted. Now 116 in 12 suites, and **every new guard-test was verified by mutation**: the production check was deleted or inverted, the named test confirmed red, the code restored. One finding rejected — the concurrent-start guard needs a real `start()`, so it is a device concern, not a unit test. - **Security** — no critical, high or medium. One low accepted (an unreadable file vanished from the list with no handle to delete it). One low deferred to #12 with a reason: the `.parts` folder is removed recursively, which only matters once the Files app can put things in it, and `TranscriptAssembly` already removes those folders the same way. Two production changes came out of the test review rather than the code review, because the guards were untestable as written: `CaptureNaming.activeBase` was extracted from `AudioRecorder`, and `date(ofBase:)` now requires the whole name to be a capture base instead of parsing a prefix and ignoring the tail. ## Validated - `make test` — 116 tests in 12 suites, green. `make lint` — clean. - Simulator: the list renders real captures from the documents directory, newest first, with durations read off the audio and titles derived from the capture time. - **On `wes-iphone`:** recording works and the memo lands in the list. Wes confirmed the list and capture, then directed the delete change, which was rebuilt and reinstalled. ## Not validated Stating this plainly because a claim of coverage that does not exist is worse than none. - **Simulator UI automation could not post clicks on this machine**, so nothing interactive was driven there. The interactive paths rest on unit tests at the logic layer and on the device. - Of the eight numbered device tests handed over, **Wes reported back on recording and the list**. The others — double-tap on Record, the meter against a real voice, stopping from the Live Activity, force-quit recovery with the new list on screen, delete removing both files, and the time-zone change — were not reported and should be assumed unrun. - No microphone has reached `LevelMonitor`; it is tested with synthetic buffers only. - `en_US_POSIX` cannot be detected from a unit test — removing it leaves `Locale.current`, which is Gregorian on the simulator. The 2569-year failure only appears on a non-Gregorian device. ## Follow-on #27 — reading a memo's transcript on the phone. Came directly out of this: the list made it obvious it is the only thing you can see about a memo.
The list stops showing sample data and reads the documents directory: every
completed capture, newest first, with the length read off its audio and a
not-synced cloud. Swipe a row to delete, with confirmation, which removes the
audio, the transcript and any parts folder left beside them.

The files are the record. There is no index and no cache except the durations,
and those are keyed to the file they were read from.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The list reads the documents directory instead of SampleMemos, the record
button drives AudioRecorder, and the meter shows the audio reaching the file.

Not a claim that it works: nothing has been driven by tapping, on a simulator
or a device. See .agent/m1-11-memo-list/SUMMARY.md for what was and was not
verified.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Revealing the swipe button and tapping it is already two deliberate actions,
and a modal on top of it is not what any other list on the phone does. Full
swipe stays off, so the row cannot go on release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
wk merged commit e5e5f71a6c into main 2026-08-08 18:22:11 -04:00
wk deleted branch m1-11-memo-list 2026-08-08 18:22:11 -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!28
No description provided.