promptward

Plan 010: Deduplicate the worker protocol types and escapeHtml

Executor instructions: Follow this plan step by step. Run every verification command and confirm the expected result before moving to the next step. If anything in the “STOP conditions” section occurs, stop and report — do not improvise. When done, update the status row for this plan in plans/README.md — unless a reviewer dispatched you and told you they maintain the index.

Drift check (run first): git diff --stat a6293e1..HEAD -- src/offscreen.ts src/rampart-worker.ts src/content/review-modal.ts src/sidepanel.ts Plan 004 may have already touched offscreen.ts (timeouts) — that’s fine; what matters is whether the duplicated WorkerRequest unions and escapeHtml bodies still exist in both places. If either duplication is already gone, skip its steps and note that in the status row.

Status

Why this matters

Two verbatim duplications invite silent drift:

  1. The WorkerRequest union is declared identically in src/offscreen.ts:9-22 and src/rampart-worker.ts:6-19. These are the two ends of a postMessage protocol; if one side adds a field the other doesn’t know about, the compiler cannot catch it — exactly the failure TypeScript exists to prevent. (WorkerResponse lives only in offscreen.ts today; moving it too gives the worker’s replies a checked contract.)
  2. escapeHtml is implemented twice, byte-identical, in src/content/review-modal.ts:130-145 and src/sidepanel.ts:270-285. It is the XSS boundary for prompt text rendered into innerHTML; a future “fix” applied to one copy but not the other is a security regression.

Current state

Commands you will need

Purpose Command Expected on success
Typecheck npx tsc -p tsconfig.json --noEmit exit 0
Tests npm test all pass
Build npm run build exit 0

Scope

In scope:

Out of scope:

Git workflow

Steps

Step 1: Extract the worker protocol

Create src/shared/worker-protocol.ts containing WorkerRequest (moved verbatim from src/offscreen.ts:9-22) and WorkerResponse (moved verbatim from src/offscreen.ts:24-36), both exported. It may import PlaceholderSummary from ./messages.

In src/offscreen.ts: delete the local declarations, import both types from ./shared/worker-protocol. In src/rampart-worker.ts: delete the local WorkerRequest, import it from ./shared/worker-protocol. Additionally, type the worker’s replies: the self.postMessage({ id: ..., ok: true, ...response }) success path and the error path — introduce a local function respond(response: WorkerResponse): void { self.postMessage(response); } and route both through it, so the compiler checks reply shapes. Note the success path spreads Record<string, unknown> from handleRequest; if making that fully type-safe requires restructuring handleRequest’s return types, do the minimal version: keep handleRequest as-is and cast at the single respond call site with a comment. Do not restructure the handler.

Verify: npx tsc -p tsconfig.json --noEmit → exit 0. Verify: grep -c "type: \"protect\"" src/offscreen.ts src/rampart-worker.ts0 in both (the union literals now live only in src/shared/worker-protocol.ts).

Step 2: Extract escapeHtml

Create src/shared/html.ts with the function moved verbatim (either copy — they are identical). In src/content/review-modal.ts and src/sidepanel.ts, delete the local copies and import from ../shared/html / ./shared/html respectively.

Verify: npx tsc -p tsconfig.json --noEmit → exit 0. Verify: grep -rn "function escapeHtml" src/ → exactly one match, in src/shared/html.ts.

Step 3: Test the shared escapeHtml

Create tests/html.test.ts (pattern: tests/debug.test.ts):

Verify: npx vitest run tests/html.test.ts → all pass.

Step 4: Full verification

Verify: npm test → exit 0 (the review-modal and sidepanel behavior is covered by tests/review-modal.test.ts and, if plans 001–004 landed, tests/content-flow.test.ts — all must stay green). Verify: npm run build → exit 0 (proves the worker bundle still resolves the shared import).

Test plan

Step 3, plus the existing suites as regression cover. No new tests for the type move — the compiler is the test.

Done criteria

STOP conditions

Maintenance notes