recording save dialog: Cancel discards the footage instead of saving the default
The creator reported that clicking Cancel in the post-recording save prompt still
saved the file under the default name: "cancel should cancel the save option,
discarding the recording" — Enter is what accepts the default.
The modal was always correct (Enter -> DialogResult=true, Cancel/Escape/X -> false).
The bug was in the caller: FinalizeRecordingAsync only overwrote `stem` when the
dialog returned true and then ran File.Move UNCONDITIONALLY, so a falsy result fell
straight through into the save. The nullable stem made "I don't want this" look
like "I accept the default".
Extracted the decision into internal MainViewModel.CompleteRecordingSave so it is
testable against real files without a frame pump:
creatorSaved == false (Cancel/Escape/X) -> delete the temp file, leave the
videos folder empty; a failed delete returns DiscardFailed and the toast
names the file + folder (a "discarded" recording still on disk is worse
than no report).
creatorSaved == true (Save, or Enter on the pre-filled default) -> File.Move.
A blank box still means "keep the auto name", but only on an explicit Save.
Test: ytLive.Tests/RecordingSaveDialogTests.cs — 5 facts over real temp files. The
headline asserts Cancel leaves the directory EMPTY, not merely "the stem is
unchanged"; a modal's own test cannot cover the decision it feeds.
Full suite 362/362 (the RealMouseDrag flake passed this run).
This commit is contained in:
+44
-7
@@ -1,10 +1,36 @@
|
||||
# HANDOFF — current state
|
||||
|
||||
**Branch:** `main` (pre-1.0, no feature branches). **Last pushed:** `7f14ffb`.
|
||||
**This session's work is COMMITTED LOCALLY, NOT PUSHED** — two commits ahead of `origin/main`.
|
||||
Push only when the creator says so.
|
||||
**This session's work is COMMITTED LOCALLY, NOT PUSHED** — **four** commits ahead of `origin/main`
|
||||
(`fae8ec5`, `5aedca7`, `f5a9d46`, `de81fa3`) plus this unit. Push only when the creator says so.
|
||||
|
||||
## Two work units landed this session
|
||||
## The 2026-09-26 proof-of-concept round (creator reporting)
|
||||
|
||||
Context: **no YouTube private test recordings are being saved**, so the creator is judging
|
||||
compositing from **local recordings**. They assumed the live path needed a separate "redirect" —
|
||||
**it does not, and that is verified, not assumed:** one `_framePump` is constructed in the
|
||||
`MainViewModel` ctor with a single `brandFlash:` callback, and both `Streaming.Operations.cs:68`
|
||||
(go-live) and `:199` (record) call that same `StartAsync`. Record+simulcast is one ffmpeg with two
|
||||
outputs. Five items, tracked in `TASKS.md` → "Creator-reported batch".
|
||||
|
||||
### Done this unit: recording save dialog — Cancel now discards
|
||||
|
||||
`RenameRecordingDialog` was always correct (Enter → `DialogResult=true`, Cancel/X/Escape → `false`).
|
||||
**The caller was not**: `FinalizeRecordingAsync` only overwrote `stem` when the dialog returned true
|
||||
and then ran `File.Move` **unconditionally**, so a cancelled dialog silently saved the recording
|
||||
under the default name. Extracted the decision into internal
|
||||
`MainViewModel.CompleteRecordingSave(startPath, dir, autoStem, chosenStem, creatorSaved)`:
|
||||
|
||||
- `creatorSaved == false` (Cancel/Escape/X) → **delete the temp file**; the videos folder is left
|
||||
empty. A failed delete returns `DiscardFailed` and the toast names the file + folder.
|
||||
- `creatorSaved == true` (Save, or Enter on the pre-filled default) → `File.Move` to the final name.
|
||||
Blank box still means "keep the auto name" — but only on an explicit Save.
|
||||
|
||||
Test: `ytLive.Tests/RecordingSaveDialogTests.cs` (5 facts, real temp files, no WPF needed) — the
|
||||
headline asserts Cancel leaves the directory **empty**, not merely "the stem is unchanged".
|
||||
Lesson recorded in `MyMistakes.md` (falsy `ShowDialog()` falling through into a side effect).
|
||||
|
||||
## Two earlier work units (committed)
|
||||
|
||||
### 1. Branding flash is now composited into the OUTPUT (TASK 36 shipped)
|
||||
|
||||
@@ -71,8 +97,19 @@ The entire implementation is inside `#if DEBUG`. Release compiles to `DataRoot =
|
||||
|
||||
## Next
|
||||
|
||||
1. Push the two commits when the creator asks.
|
||||
2. Test-console chat UX (queued): don't clear the chat window, 20px right padding on the pull-out
|
||||
1. **Ungate the brand flash from `IsLive`** (TASK batch #2) — it must run in every scene and appear
|
||||
in recordings; today it only starts in `UpdateLiveVisuals()`'s live branch, so a recording made
|
||||
without ever going live carries no credit.
|
||||
2. **Confine the ticker to the alert box** (#3) — it is still a global 1920×48 top overlay
|
||||
(`BlitOverlay(…, 0, 0)`); needs a rect + scale/clip decision in both preview and output.
|
||||
3. **Multi-instance** (#4) — route `FfmpegLocator._toolsDir` through `InstanceProfile`, and confirm
|
||||
the launch method: `dotnet run` while the first app holds `bin/…/ytLive.exe` fails with MSB3027
|
||||
*before any instance starts* (a build-output lock, not a log conflict). `$env:YTLIVE_INSTANCE=2`
|
||||
+ `dotnet run --no-build` in a second shell is the known-good path.
|
||||
4. **Post-session efficacy report** (#5) — roll up `CurrentHealth` (dropped frames, duration, health
|
||||
message) when a stream or recording ends.
|
||||
5. Push the commits when the creator asks.
|
||||
6. Test-console chat UX (queued): don't clear the chat window, 20px right padding on the pull-out
|
||||
input, Enter inserts a newline.
|
||||
3. Decide the alert-gating copy question above.
|
||||
4. Ship-checklist items live in `TASKS.md` → "1.0 gates".
|
||||
7. Decide the alert-gating copy question above.
|
||||
8. Ship-checklist items live in `TASKS.md` → "1.0 gates".
|
||||
|
||||
Reference in New Issue
Block a user