# Tasks: enterprise-browser-broadcast-channel **Change ID**: enterprise-browser-broadcast-channel **Package**: `@suaveplan/broadcast-channel` **Target path**: `browser/platform/broadcast-channel` **Prerequisite packages on Verdaccio**: `@suaveplan/events`, `@suaveplan/error`, `@suaveplan/crypto`, `@suaveplan/logger`, `@suaveplan/schema-registry` --- ## 0. Pre-flight (non-negotiable) - [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each) - [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest. ## 1. Scaffold - [ ] Create directory tree per `proposal.md`. - [ ] `package.json` v0.0.0, `"sideEffects": false`. - `dependencies`: `@suaveplan/events workspace:^`, `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/logger workspace:^`, `@suaveplan/crypto workspace:^`, `@suaveplan/schema-registry workspace:^`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build` - `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `happy-dom catalog:testing`, `@playwright/test catalog:testing`, `@suaveplan/testing workspace:^` - Subpath exports: `"."`, `"./transport"`, `"./leader"`, `"./types"` - [ ] `tsconfig.json`, `vitest.config.ts` (pool: forks, happy-dom environment, `fileParallelism: false`). --- ## 2. Error codes - [ ] `CHANNEL_CLOSED`, `MESSAGE_MALFORMED`, `PAYLOAD_TOO_LARGE`. - [ ] `registerBroadcastChannelErrorCodes()` — call eagerly. --- ## 3. Types (`src/types/broadcast-channel.types.ts`) - [ ] `ChannelConfig`, `ChannelMessage`, `TabInfo`, `BroadcastChannelEventMap`, `ChannelHandler`, `ChannelLike` (typed `MessageEvent`), `ChannelTransport`, `AskOptions`. - [ ] Write `broadcast-channel.types.md`. --- ## 4. Internal ID generator (`src/internal/id.ts`) - [ ] `generateId(): string` — wraps `@suaveplan/crypto` random bytes → hex string. - [ ] Replaces duplicate `generateTabId`/`generateMsgId` that existed in the original. --- ## 5. `ChannelMessage` schema (`src/schema/channel-message-schema/`) - [ ] `channel-message-schema.ts` — Zod `ChannelMessageSchema`; `registerChannelMessageSchema()`. - [ ] Write `channel-message-schema.md`. --- ## 6. `BroadcastTransport` (`src/transport/broadcast/`) - [ ] `broadcast.ts` — wraps native `BroadcastChannel`; implements `ChannelTransport`. - [ ] Write `broadcast.test.ts` (send, receive, close). Write `broadcast.md`. --- ## 7. `LocalStorageTransport` (`src/transport/local-storage/`) - [ ] `local-storage.ts` — `localStorage` + `storage` event fallback; same `ChannelTransport` interface. - [ ] Auto-polyfill detection: `typeof BroadcastChannel === 'undefined'`. - [ ] Write `local-storage.test.ts` (send, receive, close). Write `local-storage.md`. --- ## 8. Leader strategies (`src/leader/leader/`) - [ ] `leader.ts` — `LeaderStrategy` interface; `LowestIdStrategy` (lowest tab ID wins); `ActiveTabStrategy` (prefers visible tab via `visibilitychange`, tie-break by `joinedAt`). - [ ] Write `leader.test.ts` (LowestId election, ActiveTab election, disconnect scenario). Write `leader.md`. --- ## 9. `ask` primitive (`src/ask/ask/`) - [ ] `ask.ts` — `ask(channel, type, payload, options): Promise` using correlation IDs and timeout. - [ ] Reject with `CHANNEL_CLOSED` on close; reject with `WORKER_REQUEST_TIMEOUT` on timeout. - [ ] Write `ask.test.ts` (success, timeout, channel-close rejection, collision). Write `ask.md`. --- ## 10. `TypedBroadcastChannel` (`src/channel/channel/`) - [ ] `channel.ts` — composing transport + leader + ask + schema validation. - [ ] `handleIncoming` validates via `ChannelMessageSchema`; drops + emits `error` on malformed. - [ ] `send()` guards against `maxMessageBytes`; throws `PAYLOAD_TOO_LARGE`. - [ ] Wire OTel metrics. - [ ] Wire `@suaveplan/logger` for dropped frames and stale-tab cleanup. - [ ] Write `channel.test.ts` (send, receive, malformed drop, payload-too-large, leader election, stale cleanup). Write `channel.md`. --- ## 11. Integration test (`tests/integration.test.ts`) - [ ] `BroadcastTransport` → `TypedBroadcastChannel` round-trip. - [ ] `LocalStorageTransport` same API round-trip. - [ ] `ask` with timeout: verify rejection. - [ ] Leader election: two channels, close leader, verify re-election. --- ## 12. Playwright E2E (`e2e/broadcast-channel.spec.ts`) - [ ] Chromium: two browser tabs on same origin; one sends, other receives. - [ ] WebKit: `LocalStorageTransport` fallback verified (inject config to skip `BroadcastChannel`). - [ ] Leader election: three tabs, close leader, verify re-election in remaining two. --- ## 13. Docs + verify - [ ] Write `.md` per module (≥200 words, API reference, examples). - [ ] Write `README.md` with subpath table, transport guide, leader-election guide, `ask` example. - [ ] `bunx turbo lint typecheck test --filter=@suaveplan/broadcast-channel` — green. - [ ] `bunx turbo test:coverage` — 100%. - [ ] No `@ts-expect-error` in `src/` or tests. No `any` in `ChannelLike`. - [ ] Playwright E2E green on Chromium and WebKit. - [ ] `bun scripts/publish.ts --package @suaveplan/broadcast-channel`. - [ ] Archive to `openspec/archive/2026-04-24-enterprise-browser-broadcast-channel/`.