원클릭으로
lifecycle-guard
Protocols for preventing memory leaks and event listener accumulation in VS Code webview extensions.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Protocols for preventing memory leaks and event listener accumulation in VS Code webview extensions.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
# Dev Cycle Protocol: CDP Shell Sovereignty (v2.5.1)
Protocol for high-density, symmetrical shorthand logging in VS Code extensions.
Protocol for local session metadata persistence in the Virgo extension.
Architectural map and development guidelines for the Virgo extension. Mandatory reference for all agent modifications.
Protocol for high-integrity conversational AI injections and sensory parity in the Virgo extension.
Governance protocol and automated scripts for packaging and publishing the Virgo MCP standalone server to the public NPM registry. Enforces safety gates and clean extraction logic.
| name | lifecycle_guard |
| description | Protocols for preventing memory leaks and event listener accumulation in VS Code webview extensions. |
Webview components in VS Code often rely on global event listeners (window.addEventListener) and reactive stores. In a test environment like vitest (jsdom), these listeners persist across test suites unless explicitly removed, causing memory leaks, process hangs, and erratic test failures.
Every singleton or component that registers global listeners MUST implement a dispose() or cleanup() method.
export class MessageClient {
private handlers = new Map<string, Function>();
constructor() {
this.onMessage = this.onMessage.bind(this);
window.addEventListener('message', this.onMessage);
}
public dispose() {
window.removeEventListener('message', this.onMessage);
this.handlers.clear();
}
}
Use a vitest.setup.ts file to mock missing browser APIs (e.g., indexedDB, scrollIntoView) and ensure environmental consistency.
import { vi } from 'vitest';
// Global mocks
if (typeof window !== 'undefined') {
window.scrollIntoView = vi.fn();
}
// Global cleanup hooks
afterEach(() => {
WebviewStore.getInstance().dispose();
MessageClient.getInstance().dispose();
});
setTimeout, setInterval, or addEventListener calls that lack a corresponding remove or clear call.[!WARNING] Scope: Test environment only. This is NOT a production concern.
WebviewAudioEngine.playBlob() sets audio.src, calls audio.load(), then awaits canplay → play() → ended. In jsdom, HTMLAudioElement.load() is a stub — it executes synchronously but dispatches no media events. This means canplay never fires and the inner Promise in playBlob() hangs indefinitely.
Symptom: Vitest reports Error: Test timed out in 5000ms for any test directly calling playBlob().
In beforeEach of any test suite that exercises playBlob(), mock load() to synchronously dispatch canplay:
// For instance-specific audio elements (preferred when you have engine.audioElement):
const audio = engine.audioElement;
vi.spyOn(audio, 'load').mockImplementation(function(this: HTMLAudioElement) {
this.dispatchEvent(new Event('canplay'));
});
// For all HTMLAudioElement instances (use when engine is reconstructed per-test):
vi.spyOn(HTMLMediaElement.prototype, 'load').mockImplementation(function(this: HTMLAudioElement) {
this.dispatchEvent(new Event('canplay'));
});
After applying the mock:
ended listener IS registered by playBlob() (verify via addEventListener spy).ended listener to resolve the Promise.engine.isBusy() returns false after await playPromise.tests/webview/core/RaceCondition.test.ts:47 — "SHOULD allow audio packets that match the current intent"tests/webview/core/WebviewAudioEngine.test.ts:49 — "should acquire lock for playBlob and release it on completion"