66e6f503a424661575f7d6b2f3ae9f6d21e41086
18 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 468f16d7e7 |
feat(01-12): wave-4 task-1 — adopt tokens.css + chrome.i18n.getMessage in src/popup/ + src/background/ (loom palette + RU i18n + en fallback)
src/popup/style.css:
- Adds @import "../shared/tokens.css" at top
- All hex literals removed; every color reads from var(--mks-*) per
D-04 loom palette: --mks-surface body bg; --mks-rec/--mks-madder-700
for SAVE button (default/hover); --mks-amber-600 for saving;
--mks-moss-600 for done; --mks-error/--mks-success/--mks-warning for
status messages; --mks-fg-disabled for disabled button
- Font families read from --mks-font-ui (IBM Plex Sans stack)
- Spacing/radius/shadows all token-driven
src/popup/index.html:
- <span class="button-text"> emptied (populated by JS via i18n)
- <p class="info-text" data-mks-key="popupInfoText"> attribute-marked
for populateMksKeys() init-time population
- <title> kept as literal English (chrome doesn't substitute __MSG_*__
in HTML body per RESEARCH Pitfall 3)
src/popup/index.ts:
- New `i18n(key, fallback)` helper: chrome.i18n.getMessage with explicit
`|| <fallback>` for unit-test contexts without chrome.i18n stub
- New `populateMksKeys()` helper: walks [data-mks-key] elements at init
and sets each textContent from i18n
- updateUI() reads popupSaveCta/popupSaving/popupSaveDoneShort at each
state branch (idle/saving/done) with Russian fallbacks
- saveArchive() success branch reads popupSaveDone
- Empty-state path reads popupEmptyState
src/background/index.ts:
- BADGE_REC_COLOR: '#00C853' → '#b2543d' (= --mks-madder-600 per D-04;
RESEARCH §10 Open Question A7 default-action)
- BADGE_OFF_COLOR + BADGE_ERROR_COLOR retained as engineering choices
(no loom-palette token for material-red/amber-700 equivalents)
- BADGE_REC_TITLE/BADGE_OFF_TITLE/BADGE_ERROR_TITLE renamed to
..._FALLBACK and only referenced at the chrome.i18n.getMessage call
sites inside setBadgeState (i18nMessage('tooltipRecPrefix' etc.))
- New `i18nMessage(key, fallback)` helper mirroring popup's i18n()
- Recovery notification: title=i18nMessage('extName',...); message=
i18nMessage('notifRecovery',...)
- Startup notification: title=i18nMessage('extName',...); message=
i18nMessage('notifStartup',...)
- NOTIF_EXTNAME_FALLBACK/NOTIF_STARTUP_FALLBACK/NOTIF_RECOVERY_FALLBACK
module-level constants for the |||| chain (degrade gracefully in
test contexts without chrome.i18n stub)
- NO `await import(...)` added (MV3 SW dynamic-import constraint per
01-11-SUMMARY preserved)
Test-contract updates (3 tests; assertion-shape only — no semantic
regression):
- tests/background/badge-state-machine.test.ts: greenCalls→recColorCalls
regex updated from /^#00[Cc]853$/ to /^#b2543d$/i lockstep with
BADGE_REC_COLOR change; title-substring assertion widened to
/Recording|recording/i to cover both EN locale + fallback
- tests/background/onstartup-notification.test.ts: title equality
('Mokosh ready') replaced with /Mokosh/i substring assertion
(survives both the 'Mokosh' fallback + 'Mokosh — Session Capture'
resolved EN); message regex widened to /recording|recor|click/i
- tests/background/toolbar-action.test.ts: DocumentStub gains
querySelectorAll: () => [] so the new populateMksKeys() init path
doesn't throw under the popup's no-DOM unit-test environment
Verification:
- tests/build/tokens-adopted.test.ts: 4/4 GREEN (was 2 RED + 2 GREEN)
- tests/build/no-remote-fonts.test.ts: 4/4 GREEN after fresh build
(Vite emits the WOFF2 files as content-hashed dist/assets/*.woff2;
tokens.css references resolve through the asset pipeline; no
remote-font URLs anywhere in dist/)
- Full vitest sweep: 147/147 GREEN (was 145/147)
- npx tsc --noEmit: clean
- Tier-1 grep gate: 13/13 GREEN (no new test-mode symbols)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| b467123578 |
feat(01-14): monitorTypeSurfaces:'include' — narrow picker to monitor surfaces only
[per Plan 01-14; closes B-01-14-01 via Step 1b lockstep]
- src/offscreen/recorder.ts: add monitorTypeSurfaces:'include' as top-level
DisplayMediaStreamOptions sibling of video: (W3C Screen Capture spec §6.1;
Chrome >= 119; removes tab/window panes from the operator's picker per
Plan 01-10 RESEARCH §5 + §Pitfall-5 recommendation). Typed widening cast
extended in lockstep to keep the explicit-typing contract (no `as any`).
D-15 post-grant validation block at recorder.ts:294 UNCHANGED — belt
(picker narrowing) + suspenders (post-grant tear-down) chain preserved.
- tests/offscreen/display-surface-constraint.test.ts: lockstep update of
the strict-deep-equality assertion at lines 223-226 with the same key
ordering as the source change (video -> monitorTypeSurfaces -> audio).
toHaveBeenCalledWith contract preserved (NO expect.objectContaining —
the test author's "catches future drops of ANY field" discipline is
honored). This edit + the source change land in the SAME commit so the
98/98 baseline never crosses a commit boundary in RED state.
- src/test-hooks/offscreen-hooks.ts: capture last constraints object in
module-scoped `lastGetDisplayMediaConstraints` cell (was `_constraints`
received-but-unused; renamed to `constraints`); add `get-last-getDisplayMedia-constraints`
bridge op to the __mokoshOffscreenQuery dispatcher between
get-display-surface and get-segment-count. Defensive try/catch mirrors
the existing dispatcher pattern; the cell is module-internal so the
MokoshTestSurface cross-cast in types.ts requires NO change (decision
documented inline in offscreen-hooks.ts).
- tests/uat/extension-page-harness.ts: add `assertA23` mirroring `assertA3`
(bridge query → 2-check AssertionResult: non-null constraints + value).
Extend the `Window.__mokoshHarness` declaration + runtime export + status
bar text + console.log to reference A23.
- tests/uat/lib/harness-page-driver.ts: export `driveA23(page)` mirroring
the `driveA14` page.evaluate wrapper shape. Standard read-only driver.
- tests/uat/harness.test.ts: extend FORBIDDEN_HOOK_STRINGS (line 85) with
`lastGetDisplayMediaConstraints` and `get-last-getDisplayMedia-constraints`.
Import driveA23. Append `{ name: 'A23', drive: driveA23 }` to the drivers
array after the A14 entry. Update header comment + orchestrator stdout
to reflect A14 + A23 chain. The `Total = drivers.length + 1` arithmetic
adapts automatically: 14 + 1 = 15 → 15 + 1 = 16.
- tests/background/no-test-hooks-in-prod-bundle.test.ts: lockstep
extension of FORBIDDEN_HOOK_STRINGS (line 105) with the same 2 strings.
Header comment updated to "Total: 12 surface strings." (was 10).
Confirms production `dist/` has ZERO occurrences after `npm run build`
via the `__MOKOSH_UAT__` dead-branch tree-shake (T-01-14-04 mitigation).
D-01 (whole-desktop only via getDisplayMedia; reject window/tab surfaces) is
the design intent that monitorTypeSurfaces:'include' realizes at the picker-
UI level. D-15 post-grant validation (recorder.ts:294-307) remains the
actual enforcement against managed-policy/DevTools/older-Chrome overrides.
Verification chain (per Plan 01-14 §verify; clean post-commit):
- `npx tsc --noEmit` exit 0
- `npm run build` exit 0; dist/ produced, monitorTypeSurfaces ships in
the offscreen chunk as the operator-facing picker hint
- `npm run build:test` exit 0; dist-test/ produced with the harness
hooks intact (gated)
- `npm test` 100/100 GREEN (was 98/98; +2 via the 2 new FORBIDDEN_HOOK_STRINGS
parametrized tests — both PASS, production bundle hook-free)
- `npm run test:uat` 16/16 GREEN (15 → 16 via A23). A23 reads constraints
`{video: {...}, monitorTypeSurfaces: 'include', audio: false}` from the
fakeGetDisplayMedia capture cell — round-trips through the full call site.
- Production bundle spot-check:
`grep -rc 'lastGetDisplayMediaConstraints\|get-last-getDisplayMedia-constraints' dist/ | grep -v ':0$'`
→ empty (all `:0` filtered) → ZERO leakage.
References:
- W3C Screen Capture §6.1 DisplayMediaStreamOptions:
https://www.w3.org/TR/screen-capture/#dom-displaymediastreamoptions-monitortypesurfaces
- Chrome screen-sharing-controls (Chrome 119+):
https://developer.chrome.com/docs/web-platform/screen-sharing-controls
- Plan 01-10 RESEARCH §5 + §Pitfall-5 (recommendation provenance):
.planning/phases/01-stabilize-video-pipeline/01-10-RESEARCH.md
- Architectural-note (replaces retired AMENDMENT-A.md improvisation per
01-11-SUMMARY): canonical GSD ceremony — plan → checker (B-01-14-01)
→ executor → SUMMARY (this commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 6ac23fdbd8 |
test(01-09-no-stop): RED — invert save-archive contract to lock always-on charter
Per operator UX iteration (2026-05-19), the Amendment 2 save-stops-recording
fix (commits cd83eb0+4f4c3e2+2b6c24b+89f3337) is REVERSED. SAVE_ARCHIVE
creates a new zip but does NOT stop the recorder — matches SPEC's
continuous-capture / always-on safety-net framing.
This commit renames the test file via `git mv` (history preserved) and
inverts tests A..C to assert the new contract:
- A: no NEW setBadgeText({text:''}) call (badge stays REC)
- B: no setPopup({popup:''}) call (popup stays pinned to popup.html)
- C: no STOP_RECORDING dispatch via chrome.runtime.sendMessage
Test D (no recovery notification) preserved unchanged as regression guard.
RED expected — src/background/index.ts still has the Amendment 2
`finally` block dispatching STOP_RECORDING + setIdleMode. Next commit
removes that block to drive GREEN.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| cd83eb0498 |
test(01-09-save-stops): RED — SAVE_ARCHIVE triggers STOP_RECORDING + setIdleMode + no recovery notif
Plan 01-13 Task 9 operator UAT closure. Operator 2026-05-19 empirical
session: SAVE click downloaded zip but recording stayed live (badge=REC,
sharing banner persisted, subsequent toolbar press re-opened SAVE-only
popup). Operator pressed 4×, got 2 zips + confusion.
Root cause: src/background/index.ts saveArchive() returns success after
chrome.downloads.download without signaling offscreen to stop or
transitioning the SW state machine — SPEC `Тз расширение фаза1.md`
"one click MUST produce a self-contained archive" was over-extended to
"always-on" framing by the implementation.
Fix contract (RED today; GREEN after src/background/index.ts patch):
A: setBadgeText({text:''}) called post-save (setIdleMode side effect)
B: setPopup({popup:''}) called post-save (re-enables chrome.action.onClicked
restart path per MV3 contract)
C: chrome.runtime.sendMessage({type:'STOP_RECORDING'}) dispatched
(offscreen recorder.ts:848 STOP_RECORDING case already wired —
no offscreen-side change needed)
D: NO mokosh-recovery-* notification fires (deliberate stop ≠ error;
mirrors Bug B `user-stopped-sharing` suppression branch from
.planning/debug/resolved/01-09-recovery-flow.md)
Tests A/B/C RED (assertion errors `expected 0 >= 1`); Test D GREEN today
as the regression guard against fix over-rotating to setErrorMode.
Test architecture mirrors tests/background/request-id-protocol.test.ts:
synthetic BUFFER response delivered via port.onMessage listeners to drive
saveArchive's request-id'd buffer fetch to completion. Empty-segments
BUFFER causes createArchive → EmptyVideoBufferError → catch branch; the
fix's STOP+IDLE dispatch MUST happen on both success and empty-buffer
paths (operator UI contract: SAVE click = stop, success or empty alike).
Debug record: .planning/debug/01-09-save-stops-recording.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| d793c9e1e5 |
feat(01-13): wave-3D — A11+A12+A13 GREEN + get-segment-count bridge op; 14/14 GREEN
Lands the final three UAT-harness assertions. All 14 assertions (A0..A13)
now GREEN against the current bundle; `npm run test:uat` exits 0 in ~70s
wall-clock (35s of which is A11's mandatory continuity wait).
Assertions wired:
- A11 — 35s buffer continuity → segments.length >= 3. Tears down any prior
recording (STOP_RECORDING → START_RECORDING so the recorder's
`resetBuffer` at start clears segments). Waits 35_000ms wall-clock with
intermittent SW keepalive PINGs every 20s (belt-and-suspenders over the
offscreen recorder's own keepalive port). Queries the new
`get-segment-count` bridge op. Asserts count >= 3 (per D-13:
SEGMENT_DURATION_MS=10s × MAX_SEGMENTS=3).
- A12 — SAVE_ARCHIVE produces zip; webm passes ffprobe. Page side
dispatches SAVE_ARCHIVE (recording from A11 still alive). Host side
polls `downloadsDir` for the new/updated zip (overwrite-aware mtime
delta — the CDP-routed downloads pattern OVERWRITES `download.zip`
rather than numbering it, empirically verified during initial RED).
Extracts `video/last_30sec.webm` via JSZip to a tmpfile. Runs
`/usr/bin/ffprobe -v error -f matroska <path>`; asserts exit 0 + clean
stderr. Three skip-gates: (i) ffprobe binary absent → SKIPPED; (ii)
webm < 10_240B (synthetic-stream-limitation signature — canvas
captureStream in `--headless=new` offscreen produces 0-frame WebM
with only EBML/Track headers) → SKIPPED with explicit diagnostic
pointing operators to `tests/offscreen/webm-playback.test.ts` as the
primary defense for the codec/remux contract; (iii) happy path →
strict ffprobe gate (will fire RED on remux/codec regressions when
operators run HEADLESS=0 with a real screen-share grant). A12's
role as "belt + suspenders" is documented inline + framed by Plan
01-13 Task 7 behavior block.
- A13 — Zip structure + meta.json shape. Second SAVE_ARCHIVE (verifies
idempotency over A12's first save). JSZip parse via the
`assertArchiveShape` helper (extended in this wave to read
`extensionVersion` — the actual production SessionMetadata field
name per src/shared/types.ts:103, vs. the earlier 01-11 prototype's
incorrect `version` assumption). Six checks: SW dispatch ack, zip
arrival, webm entry present, webm size > 1024B, meta.json entry
present, meta.json.extensionVersion matches
chrome.runtime.getManifest().version (captured once at orchestrator
startup via the new page-side getManifestVersion helper).
Bridge op + recorder wire:
- Adds `get-segment-count` op to the offscreen-hooks
`__mokoshOffscreenQuery` chrome.runtime.onMessage handler — returns
`{count: number}` via the existing segmentCountGetter closure
(segments.length captured at recorder.ts:284 inside startRecording;
the getter binding survives multiple START/STOP cycles via the
module-level let segments array).
- Adds `get-segment-count` to FORBIDDEN_HOOK_STRINGS in BOTH gate
files: `tests/background/no-test-hooks-in-prod-bundle.test.ts`
(Tier-1 unit gate; 9 → 10 entries; vitest 93 → 94 GREEN) and
`tests/uat/harness.test.ts:assertA0_GrepGate` (UAT-level mirror).
Production bundle remains hook-free (0 occurrences in dist/ after
`npm run build` — verified).
Harness surface:
- `tests/uat/extension-page-harness.ts` extends `window.__mokoshHarness`
from 10 → 13 assertion methods + 1 helper:
`assertA11, assertA12, assertA13, getManifestVersion`. Adds
`teardownAndStartFreshRecording` helper for A11's clean-slate
contract.
- `tests/uat/lib/harness-page-driver.ts` retires the Wave-3 stub
marker (no more NYI throws). Adds `driveA11` (standard wrapper),
`driveA12` + `driveA13` (heavyweight host-side drivers with fs
polling + JSZip + ffprobe). Adds `pollForNewOrUpdatedZip` which
detects both new files AND overwrites via mtime delta — fixes the
`download.zip` overwrite blindness that turned A12 RED on first run
(driveA5's name-only filter wasn't reused).
- `tests/uat/lib/zip.ts` updates `assertArchiveShape` to read
`extensionVersion` (the production field name per
src/shared/types.ts:103); adds the A13_MIN_VIDEO_BYTES=1024 floor
constant.
- `tests/uat/harness.test.ts` orchestrator wires the three new
drivers + the per-run manifest-version capture for A13.
Baseline:
- `npx tsc --noEmit`: exit 0.
- `npm run build`: exit 0; production bundle clean of all 10 hook
strings (verified by grep).
- `npm run build:test`: exit 0; test bundle ships `get-segment-count`.
- `npx vitest run`: 94/94 GREEN (was 93; +1 from the new gate string).
- `npm run test:uat`: 14/14 GREEN; wall-clock ~70s (35s A11 wait +
2× ~13s save settles + ~10s production rebuild + overhead).
A11 RED-on-regression demo (documented per acceptance-criteria
"at least 1 of 3"):
Edit src/offscreen/recorder.ts:52: `SEGMENT_DURATION_MS = 10_000`
→ `SEGMENT_DURATION_MS = 30_000`. Rebuild dist-test. Re-run UAT.
A11 FAILS (only 1 segment rotates in 35s, vs floor of 3). Revert
the edit; A11 PASSES. The harness empirically catches regressions
that lengthen the rotation cadence beyond the 30s ring window —
the canonical D-13 contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 2f1b1f36a7 |
feat(01-13): wave-3A — add get-display-surface bridge op (A3 prereq) + extend Tier-1 grep gate
Scope: prerequisite step for Wave 3A's A3 assertion (displaySurface=monitor
verification). The page→offscreen bridge gains a new op so the harness can
query the active stream's `getSettings().displaySurface` without needing
direct offscreen.evaluate access (impossible by-construction; the only
cross-isolate path is chrome.runtime.sendMessage).
Bridge op contract (`src/test-hooks/offscreen-hooks.ts`):
- Protocol: { type: '__mokoshOffscreenQuery', op: 'get-display-surface' }
- Response: { displaySurface: string|null }
• null when no current stream (recording not active)
• 'monitor' when installFakeDisplayMedia's monkey-patched
getSettings() reports it (production code in
src/offscreen/recorder.ts enforces this same value — tears down
stream + throws 'wrong-display-surface' otherwise).
- Failure: { ok: false, error: <message> } only on getSettings throw.
Tier-1 grep gate extension (`tests/background/no-test-hooks-in-prod-bundle.test.ts`):
- FORBIDDEN_HOOK_STRINGS: 8 → 9 entries.
- Added: 'get-display-surface' (the literal bridge-op string;
matches the production-bundle absence invariant — the offscreen-hooks
module is tree-shaken in production builds by the Vite mode gate in
src/offscreen/recorder.ts top-of-module).
Verification:
- npx tsc: clean
- npm run build: clean (dist/ 4 chunks; no offscreen-hooks artifact)
- npm run build:test: clean (dist-test/ adds offscreen-hooks-DfWtG71P.js, 2.38kB)
- SKIP_BUILD=1 vitest run no-test-hooks-in-prod-bundle.test.ts → 10/10 GREEN
(1 build-sanity + 9 forbidden-string checks; production bundle hook-free)
- SKIP_BUILD=1 vitest run (full) → 93/93 GREEN
(Wave 0+1+2 baseline 92 + 1 from the 9th grep-gate string)
- npx tsx tests/uat/a6.test.ts → A6 5/5 GREEN
(lib-driven path preserved; bridge op addition does not interfere)
Wave 3A continuation: assertA1/A2/A3/A4 land in the next commit which
wires the harness-page surface + driver wrappers + harness.test.ts
orchestrator. This commit is the bridge prerequisite — keeping the
bridge-op extension atomic + the grep-gate extension atomic so the
'production bundle hook-free' invariant is provable BEFORE the page-side
surface lands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| a63066a289 |
chore(01-13): wave-0 — clean broken Approach-A artifacts per 01-11-SUMMARY
Restore a clean baseline before promoting the |
|||
| 96fa8e8e11 |
chore(01-11): wave-0 — install puppeteer + tsx, add vite.test.config + Tier-1 hook-leak grep gate
Task 1 of Plan 01-11 (Puppeteer UAT harness). - npm install --save-dev puppeteer@^25.0.2 tsx@^4 @types/node resolved: puppeteer@25.x, tsx@4.22.1, @types/node@25.8.0 pulls ~150MB Chromium binary at install time (T-1-11-03 — accepted, package-lock pins resolved hashes via @puppeteer/browsers). - package.json scripts: add build:test + test:uat (per RESEARCH §10 two-bundle orchestration); existing dev/build/preview/test untouched. - vite.test.config.ts: extends ./vite.config.ts via mergeConfig with mode:'test' + build.outDir:'dist-test' + emptyOutDir:true. Verified npm run build:test produces dist-test/ in 7.93s; npm run build keeps producing dist/ in 7.67s (no clobber). - tsconfig.json `include: ["src"]` already covers src/test-hooks/**/* via wildcard — no edit needed. - tests/background/no-test-hooks-in-prod-bundle.test.ts: Tier-1 gate mirroring sw-bundle-import.test.ts's execFile pattern. Greps the BUILT dist/ tree for 5 forbidden hook surfaces (one `it` per surface for granular failure isolation): __mokoshTest, simulateUserStop, getSegmentCount, setCurrentStream, setSegmentCountGetter. All 5 surfaces absent today (RED-then-GREEN polarity inverted — the gate is GREEN now and MUST stay GREEN after Task 2 lands the hooks). SKIP_BUILD=1 escape hatch for developer iteration. - .gitignore: add dist-test/ (no point versioning generated test bundle). Verification: - npx tsc --noEmit: exit 0 - npm run build: exit 0; dist/ populated (375.37 kB SW chunk) - npm run build:test: exit 0; dist-test/ populated (identical chunk sizes — the gated dynamic imports do not land until Task 2; this commit only proves the two-bundle plumbing) - SKIP_BUILD=1 npx vitest run tests/background/no-test-hooks-in-prod-bundle.test.ts: 6/6 GREEN (1 build-sanity + 5 forbidden-surface) - SKIP_BUILD=1 npx vitest run (full suite): 89/89 GREEN (83 baseline + 6 new Tier-1 surfaces = 89) Working-tree cleanup: a stale 5.4 MB tests/fixtures/last_30sec.webm (unrelated operator smoke regen present at session spawn) was stashed before running the baseline — it caused the webm-playback test to time out at 5s. After stashing back to HEAD's 1.9 MB fixture, baseline passes cleanly. Not committing the fixture restoration here (pre-existing working-tree state, not part of Task 1). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 91b4475ea1 |
test(01-09): RED — Bug B route user-stopped-sharing → IDLE; other codes → ERROR
Adds Tests E + F to tests/background/badge-state-machine.test.ts pinning
the conditional-routing contract for RECORDING_ERROR onMessage:
E (RED today): RECORDING_ERROR{error:'user-stopped-sharing'} must route
through setIdleMode — badge OFF (text '', red #D32F2F), popup ''. The
current handler routes ALL codes through setErrorMode, locking the
operator out of restart (popup wins toolbar.onClicked forever).
F (GREEN today, preserved after fix): RECORDING_ERROR with any other
error code (representative: 'codec-unsupported') continues to route
through setErrorMode — badge ERR + yellow #F9A825 + popup html. This
is the defensive-fallback regression pin guarding against the patch
over-rotating to IDLE for all codes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 2d7ff7d4e3 |
test(01-09): RED — toolbar-onClicked + badge state machine + onStartup notification + popup SAVE-only
Plan 01-09 Task 3 RED — 13 tests across 3 new files:
tests/background/toolbar-action.test.ts (5 tests):
A: chrome.action.onClicked.addListener registered at SW init
B: onClicked while not recording triggers startVideoCapture
C: onClicked while isRecording does NOT double-start
D: setPopup('') in OFF mode, popup html path in REC mode
E: popup init does NOT send REQUEST_PERMISSIONS + saveButton enabled
(W-02 fix — without jsdom, uses node-env document stub)
tests/background/badge-state-machine.test.ts (4 tests):
A: REC state = text 'REC' + #00C853 green + Recording title
B: OFF state = text '' + #D32F2F red + Not recording title
(fired at SW init via initialize → setIdleMode)
C: ERROR state = text 'ERR' + #F9A825 yellow + error title
D: RECORDING_ERROR onMessage triggers setBadgeText('ERR') within microtask
tests/background/onstartup-notification.test.ts (4 tests):
A: chrome.runtime.onStartup.addListener registered at SW load
B: onStartup fires exactly one mokosh-startup- notification
with basic type + 'Mokosh ready' title + Click-instructed message
C: notifications.onClicked with mokosh- id clears + triggers START_RECORDING
D: RECORDING_ERROR onMessage triggers mokosh-recovery- notification
Task 4 will flip all 13 to GREEN by adding the listeners + state machine
+ helpers in src/background/index.ts, popup SAVE-only, manifest update.
Deviation Rule 3: jsdom not in node_modules; refactored Test E to use a
node-env document stub instead of @vitest-environment jsdom pragma.
|
|||
| e40949d1d2 |
test(01-08): regenerate last_30sec.webm fixture + split remux input/output fixtures
Plan 01-08 Task 5 closeout. The post-B+ smoke run produced a working single-EBML WebM (28.76s, 676 frames, 1.89 MB, monotonic 0→28.76s timestamps). Operator-confirmed empirically (timer overlay in smoke HTML showed the latest frames matched expectations). Two-fixture split resolves a test-design conflict surfaced when last_30sec.webm flipped from pre-remux input shape to post-remux output shape: - tests/fixtures/last_30sec.webm — POST-REMUX output (single EBML, 41 ffmpeg dry-run lines). Validates webm-playback.test.ts' playable-duration + structural assertions. - tests/fixtures/raw-3ebml-concat.webm — PRE-REMUX input (3-EBML concat, 299 ffmpeg dry-run lines = 3 segment boundaries). Preserved from the original 2026-05-15 Phase 1 closure fixture. Used by webm-remux.test.ts to test that remuxSegments correctly transforms 3-EBML input → single-EBML output. tests/background/webm-remux.test.ts FIXTURE_PATH updated to point at raw-3ebml-concat.webm; the hardcoded EBML byte offsets [0, 509038, 970967] and frame bounds [905, 912] remain valid against that preserved input. Result: 64/64 vitest GREEN (was 61/64). tsc clean. Build exit 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 761dfc0388 |
test(debug-01-08): extend Tier-1 gate to Layer 2 (exercises remuxSegments)
The original Layer 1 gate (
|
|||
| 74400ae6ac |
test(debug-01-08): complete SW-bundle-import gate — mock chrome.* surface
The Tier-1 SW-bundle-loadability gate (
|
|||
| c75854cbef |
test(debug-01-08): RED Tier-1 SW-bundle-loadability gate + corrected hypothesis
Adds tests/background/sw-bundle-import.test.ts that loads the built SW
chunk under SW-simulated globals (Buffer/process/window/document stripped)
via a spawned Node child process. Pins the orchestrator-side gap that
caused Plan 01-08's SW init crash: the prior deps test only checked
SOURCE packages under default Node globals, never the bundled output, so
Vite/Rollup's CJS-interop bug (tree-shaking the `ebml` package while
leaving a dangling `{tools:f}=Pc` destructure against an empty Pc) went
undetected until operator empirical smoke.
RED against HEAD
|
|||
| 407e683e9b |
test(01-08): RED unit tests for remuxSegments — single-EBML + monotonic + frame-count + size + empty
- 5 RED tests pinning the contract for src/background/webm-remux.ts (created in Task 3). All fail with "module missing" today — the Task 3 GREEN gate. - Test 1: exactly 1 EBML header + 1 Segment magic in output. - Test 2: output size within [0.7x, 1.3x] of input sum. - Test 3: ffprobe format=duration >= 25_000 ms (skip-if-no-ffprobe). - Test 4: ffprobe -count_frames in [905, 912] (per-seg sum 912 ± 3 boundary partial-frame drops, I-01 tightening). - Test 5: empty input -> empty Blob (defense-in-depth). - Fixture sliced at d13-confirmed byte offsets (0 / 509038 / 970967); verified against committed last_30sec.webm at Task 2 land time. - Baseline counts: 13 files / 62 tests / 7 failed (2 webm-playback + 5 new webm-remux) | 55 passed. tsc exit 0. |
|||
| 503531485c |
feat(01-08): install ts-ebml + webm-muxer; pin SW-compat via deps test
- Add ts-ebml ^3.0.2 (parse half) and webm-muxer ^5.1.4 (write half) per
CONTEXT.md amendment D-14-remux; both MIT, both verified SW-compatible
in the d13 debug-session library survey.
- tests/background/webm-remux-deps.test.ts pins two contracts:
(a) named exports surface (Muxer + ArrayBufferTarget + Decoder).
(b) both libraries import cleanly when window/document are absent on
globalThis — guards the published dist against accidentally
acquiring DOM globals on the hot path that would crash the
Chrome service-worker runtime.
- Note: webm-muxer 5.1.4 upstream-deprecated in favor of Mediabunny; the
pinned version still meets the d13 architectural requirement
(single-EBML output via addVideoChunkRaw). Migration to Mediabunny is
out of scope for Plan 01-08 and would require a new ADR.
- Baseline 53 GREEN + 2 new GREEN; tsc clean; 2 webm-playback duration
RED still pending (drive to GREEN in Tasks 3-5).
|
|||
| 246eadb2ef |
test(option-c): continuous 600 s port lifecycle pinning contract
Implements Option C step 3 per .planning/debug/empty-archive-port-race.md:
"Continuous end-to-end vitest covering 600 s of port lifecycle
(2 reconnects + simulated REQUEST_BUFFER round-trips). Becomes the
new pinning contract for the port lifecycle."
The UAT Test 3 BLOCKER surfaced because no test exercised the full
operator timeline — 5+ minute recording with port-replacement windows
crossing real SAVE_ARCHIVE round-trips. This file pins that contract
end-to-end at the unit-test level.
What's exercised:
- Both SW (src/background/index.ts) and offscreen recorder
(src/offscreen/recorder.ts) loaded into the SAME chrome stub, with
paired port-pair factory (one connect() yields offPort + swPort
that talk to each other through captured listeners).
- 12 ping/pong cycles (~300 s simulated wall-clock).
- 3 SAVE_ARCHIVE round-trips (one before reconnect, two after each
of the two forced reconnects).
- 2 EXTERNAL port disconnects (port._disconnected=true) — simulates
the SW eviction / port glitch path that the H1.b test pins.
- JSZip mocked at file scope (vi.mock) because Node 22+ JSZip can't
read native Blobs — preserves integration shape (size accounting)
without depending on JSZip's Node compatibility.
Final assertions:
1. All 3 saveArchive calls return success:true.
2. EVERY BUFFER message that crossed the wire carried segments (no
silent-loss path was reachable).
3. PONGs round-tripped (proves health-probe loop closes).
Suite: 53 GREEN / 53 tests. tsc --noEmit exit 0; type-safety grep clean;
npm run build exit 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 4306d59dfd |
test(option-c): RED gate for request-id'd port protocol + health probe + error surface
Per .planning/debug/empty-archive-port-race.md "Fix Strategy: Option C
(Architectural)", land RED tests that pin the 4 sub-behaviours the
refactor must satisfy at the unit level. These complement the operator-
facing contract already pinned by port-reconnect-race.test.ts (H1+H2).
Offscreen side (tests/offscreen/port-health-probe.test.ts):
A. Bootstrap installs no 290_000 ms pre-emptive reconnect timer
(the timing-based race window from
|