Milestone v1 (v2.0.0): Mokosh — Session Capture #1

Merged
strategy155 merged 297 commits from gsd/phase-04-harden-clean-up-optional into main 2026-05-31 15:34:17 +00:00
Showing only changes of commit b9eeeeb386 - Show all commits

View File

@@ -725,21 +725,55 @@ chrome.runtime.onMessage.addListener((message: Message, sender, sendResponse) =>
case 'RECORDING_ERROR': case 'RECORDING_ERROR':
// Plan 01-09 — the offscreen recorder broadcasts this on capture // Plan 01-09 — the offscreen recorder broadcasts this on capture
// failure (codec missing, picker cancelled, wrong-display-surface, // failure (codec missing, picker cancelled, wrong-display-surface,
// mid-record stream end, etc.). Surface to the operator via the // mid-record stream end, etc.) AND on operator-initiated stop-sharing
// badge + a recovery notification. // (src/offscreen/recorder.ts: onUserStoppedSharing emits
// RECORDING_ERROR{error:'user-stopped-sharing'} after resetBuffer +
// track release).
//
// Bug B (debug 01-09-recovery-flow) conditional routing: the
// operator-initiated stop is NOT an error condition, it is a
// deliberate end-of-session signal. Routing it through setErrorMode
// would (a) leave the popup pinned to src/popup/index.html (SAVE-only)
// so chrome.action.onClicked cannot re-fire — the popup wins the
// toolbar click forever, and (b) paint the badge yellow as if a
// capture failure occurred. Both lock the operator out of restart.
// Route 'user-stopped-sharing' through setIdleMode instead: popup
// empties (re-enabling onClicked-driven restart), badge returns to
// OFF (resetBuffer has already cleared the offscreen buffer so SAVE
// mode would be meaningless). No recovery notification: the operator
// performed the stop deliberately; surfacing a notification would
// be UX noise.
// All other error codes preserve the original setErrorMode + recovery
// notification routing (defensive fallback for genuine capture
// failures — codec-unsupported, wrong-display-surface, capture-failed,
// permission-denied, empty-video-buffer, unknown).
logger.warn('RECORDING_ERROR received:', message); logger.warn('RECORDING_ERROR received:', message);
setErrorMode(); {
try { // Narrow `message` to read the optional `error` payload. The
const recoveryId = NOTIFICATION_RECOVERY_PREFIX + Date.now(); // canonical Message interface (src/shared/types.ts) does not
chrome.notifications.create(recoveryId, { // typify it (Message has type/data/tabId only); the offscreen
type: 'basic', // recorder emits the extra `error` field as part of the
iconUrl: chrome.runtime.getURL(NOTIFICATION_ICON_PATH), // RECORDING_ERROR wire shape — read it via a minimal cast that
title: 'Mokosh stopped', // keeps us off `as any`.
message: 'Recording stopped. Click here to start a new session.', const errorCode = (message as unknown as { error?: unknown }).error;
priority: 1, if (errorCode === 'user-stopped-sharing') {
}); isRecording = false;
} catch (e) { setIdleMode();
logger.warn('Recovery notification create failed:', e); } else {
setErrorMode();
try {
const recoveryId = NOTIFICATION_RECOVERY_PREFIX + Date.now();
chrome.notifications.create(recoveryId, {
type: 'basic',
iconUrl: chrome.runtime.getURL(NOTIFICATION_ICON_PATH),
title: 'Mokosh stopped',
message: 'Recording stopped. Click here to start a new session.',
priority: 1,
});
} catch (e) {
logger.warn('Recovery notification create failed:', e);
}
}
} }
return false; return false;