diff --git a/src/background/index.ts b/src/background/index.ts index e05b64a..d15eba4 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -613,21 +613,25 @@ async function downloadArchive(archiveBlob: Blob) { // Сохранение архива (полный процесс) // -// Plan 01-13 Task 9 (debug session 01-09-save-stops-recording): SAVE is -// one-shot per SPEC intent (`Тз расширение фаза1.md`: "one click MUST -// produce a self-contained archive"). After the zip lands (success path) -// OR an EmptyVideoBufferError surfaces (empty-buffer path), the SW -// auto-stops the recording — dispatches STOP_RECORDING to offscreen, -// flips isRecording=false, transitions to IDLE. This mirrors the Bug B -// `user-stopped-sharing` branch in the RECORDING_ERROR handler: deliberate -// stop, NOT an error — no recovery notification. +// Plan 01-09 Amendment 3 (2026-05-19, debug session +// 01-09-save-does-not-stop-recording): REVERSES the prior Amendment 2 +// save-stops-recording fix. SAVE_ARCHIVE creates a new zip but does +// NOT stop the recorder — per operator UX iteration preferring the +// original "continuous capture / always-on safety net" charter +// (`Тз расширение фаза1.md`). The only termination paths are: +// 1. Operator clicks Chrome's "Stop sharing" banner → +// offscreen's onUserStoppedSharing emits RECORDING_ERROR{error: +// 'user-stopped-sharing'} → SW's RECORDING_ERROR handler routes +// through setIdleMode (Bug B branch — preserved). +// 2. Browser closes (SW + offscreen torn down). +// 3. Extension uninstalled. // -// The post-save teardown is invoked from a `finally` block so both -// branches share the same teardown semantics: the operator perceives -// SAVE click as ALWAYS stopping the session, success or empty-buffer -// alike. Otherwise an empty-buffer error would leave the recording live -// + an ERR badge + a recovery notification, defeating the operator UX -// contract. +// Operator workflow: hit a bug → click SAVE (zip lands) → keep working +// → the next bug is also captured because the ring buffer keeps +// filling. Contract pinned by +// `tests/background/save-archive-does-not-stop-recording.test.ts` and +// Plan 01-13 harness assertion A14 (both inverted from the prior +// Amendment 2 contract). async function saveArchive() { try { logger.log('Starting archive save process'); @@ -716,53 +720,12 @@ async function saveArchive() { } } return { success: false, error }; - } finally { - // Plan 01-13 Task 9 post-save auto-stop. Runs on BOTH the success - // path (zip downloaded) and the catch path (EmptyVideoBufferError + - // any other createArchive failure). The operator clicked SAVE — the - // session is over from their perspective regardless of internal - // success/failure. Releasing the MediaStream + transitioning to - // IDLE is the consistent operator-facing UX contract. - // - // Three steps, each independently best-effort: - // 1. STOP_RECORDING → offscreen via chrome.runtime.sendMessage. - // The offscreen recorder's onMessage handler at - // src/offscreen/recorder.ts:848 dispatches STOP_RECORDING - // through `stopRecording()`, which nulls mediaStream, stops - // the MediaRecorder, releases all tracks (closes Chrome's - // sharing banner), and clears the rotation timer. - // 2. isRecording = false on the SW side so the next - // chrome.action.onClicked toolbar gesture isn't blocked by - // the guard at line ~877. - // 3. setIdleMode → empties the popup (re-enables onClicked - // restart path per MV3 contract) and paints the OFF badge. - // - // RECORDING_ERROR path note: if the catch branch above dispatched - // RECORDING_ERROR{error:'empty-video-buffer'}, the SW's own - // RECORDING_ERROR handler at line ~756 would run setErrorMode - // (yellow ERR badge + recovery notification). The current ordering - // — error broadcast in catch, then setIdleMode in finally — means - // the FINAL state is IDLE: setIdleMode lands AFTER the RECORDING_ERROR - // self-message because the handler is synchronous and runs - // immediately on dispatch (chrome.runtime.sendMessage to self in - // SW context is dispatched within the same event loop turn). The - // post-save IDLE state machine wins; the operator sees a clean - // OFF badge + empty popup + sharing banner closed. The - // empty-buffer notification still fires (operator sees the - // recovery notif briefly) but the badge resolves to OFF — a - // documented trade-off keeping the empty-buffer surface visible - // while preserving the SAVE=stop contract. - try { - chrome.runtime.sendMessage({ type: 'STOP_RECORDING' }); - } catch (sendErr) { - // Offscreen may be unreachable mid-teardown; non-fatal — SW state - // still transitions to IDLE so the operator regains the restart - // path. - logger.warn('STOP_RECORDING post-save dispatch failed:', sendErr); - } - isRecording = false; - setIdleMode(); } + // Plan 01-09 Amendment 3 (2026-05-19): NO `finally` block here. The + // prior Amendment 2 finally block (STOP_RECORDING dispatch + isRecording=false + // + setIdleMode) is REMOVED. SAVE_ARCHIVE creates a zip and returns; + // the recorder and state machine stay in REC. See the function-level + // docstring above for the full charter rationale. } // checkPermissions / requestPermissions удалены: старая permission