feat(01-09): GREEN — Bug B route user-stopped-sharing → IDLE; other codes → ERROR (preserved)
Patches the RECORDING_ERROR onMessage handler in src/background/index.ts
(lines 725-744 pre-patch) with conditional routing on the incoming
`message.error` payload:
- 'user-stopped-sharing' → setIdleMode() (popup empties; badge OFF;
isRecording flipped to false). Recovery notification suppressed —
the operator stopped deliberately, surfacing one would be UX noise.
The offscreen recorder's onUserStoppedSharing has already cleared
the buffer (src/offscreen/recorder.ts:457 resetBuffer), so IDLE is
the correct landing state.
- all other codes → setErrorMode() + recovery notification, preserving
the existing operator-facing surface for genuine capture failures
(codec-unsupported, wrong-display-surface, capture-failed, etc.).
Closes the operator-lockout regression observed in Plan 01-09 Task 5
empirical UAT: after Chrome's "Stop sharing" banner click, the badge
stayed yellow and the popup pinned to SAVE-only, gating
chrome.action.onClicked behind the popup forever. Operator had no
restart path. With IDLE routing, the popup empties and the toolbar
click fires startVideoCapture as designed.
Tests: 83/83 GREEN (was 81; +2 from Tests E+F). tsc clean. Build exit 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user