···11+# V1 Removal — Phase 3 Work Packages
22+33+Break Phase 3 (destructive cleanup of remaining v1 plumbing) into small
44+agent-sized tasks. Each package is sized for a 15–20 min agent spawn.
55+66+See `docs/v1-removal-plan.md` for background, the rolled-back Phase 3
77+attempt's lessons, and the Phase 2/2.5 work that's already landed.
88+99+---
1010+1111+## Current state (what's still v1)
1212+1313+After Phase 2.5 cleanup, these v1 surfaces are still present:
1414+1515+1. **`extensionHostWindow`** BrowserWindow + `createExtensionHostWindow()`
1616+ — still created at startup. Loads `peek://app/extension-host.html`.
1717+ Now empty (no iframes; cmd/hud/page moved out). Still needed because
1818+ smoke tests assert on the URL's existence (`smoke.spec.ts:4177, 4189,
1919+ 4359, 5292, 5299, 5379`) and the v1 portion of `extensionBroadcaster`
2020+ still iterates its frames.
2121+2. **`extensionWindows` map** — never populated after Phase 2.5 #3
2222+ (`createExtensionWindow` was deleted). Still referenced by
2323+ `destroyExtensionWindow`, `getExtensionWindow`, `getRunningExtensions`,
2424+ `getAllRegisteredExtensions`, broadcaster loop. Dead-path: all callers
2525+ hit an always-empty map.
2626+3. **`loadedConsolidatedExtensions` Set + `CONSOLIDATED_EXTENSION_IDS`
2727+ list** — list has 20 ids, but post-v2 migration none of them load
2828+ through the consolidated host. `loadedConsolidatedExtensions.add(...)`
2929+ still called for `cmd`/`hud`/`page` solely to keep them visible in
3030+ `getRunningExtensions()`.
3131+4. **Legacy IPC channels in `ipc.ts`** — ~103 `ipcMain.handle/on` calls
3232+ for `window-*`, `datastore-*`, `context-*`, `chrome-ext:*`, `oauth-*`,
3333+ `izui-*`, `extension-*`, `feature-registry:*`, `feature-install:*`,
3434+ `feature-settings-schema`, `screen-*`, `save-space-workspaces`,
3535+ `sync-full`, `renderer-log`, `app-quit`, `app-restart`, and the
3636+ `IPC_CHANNELS.*` set (`SUBSCRIBE`, `PUBLISH`, `CLOSE_WINDOW`,
3737+ `REGISTER_SHORTCUT`, `UNREGISTER_SHORTCUT`, `MODIFY_WINDOW`,
3838+ `CONSOLE`). Callers: `preload.js` and `tile-preload.cts` fallback
3939+ branches.
4040+5. **`preload.js`** (2693 LOC) — still referenced by `entry.ts:152`
4141+ (`setPreloadPath`), webview v1 fallback in `entry.ts:207`, and
4242+ `ipc.ts:2268,2670` window-open fallback for non-tile URLs.
4343+6. **Fallback branches in `tile-preload.cts`** — 4 explicit
4444+ `if (!hasXxxCapability()) return ipcRenderer.invoke('legacy-*')`
4545+ branches for `izui-*` + ~70 unconditional `ipcRenderer.invoke(...)`
4646+ calls against legacy non-`tile:*` channels (datastore-*, window-*,
4747+ context-*, extension-*, chrome-ext:*, oauth-*, screen-*, feature-*,
4848+ sync-full, etc.). Each is a candidate to re-route to a strict
4949+ `tile:*` handler or (if the capability gate is redundant) to delete
5050+ entirely. ~10 sites need validation.
5151+7. **`IPC_CHANNELS` const** in `backend/config.ts:16-27` — imported by
5252+ `backend/electron/ipc.ts`, `backend/electron/index.ts`,
5353+ `backend/electron/config.ts`. Delete after no callers remain.
5454+8. **`getRunningExtensions()` / `getAllRegisteredExtensions()` /
5555+ `isConsolidatedExtension()`** — still called from `ipc.ts:1300,1310,
5656+ 1323,1333`. Features pane + extension-management IPCs depend on them.
5757+ Need to keep the product surface but rewrite to use
5858+ `getLoadedTileIds()` + `getTileManifest()` instead of the v1 maps.
5959+6060+Other minor surfaces:
6161+- `INTERNAL_URLS` array in `ipc.ts:2306` references
6262+ `peek://app/extension-host.html` for opener-parent detection — follows
6363+ from #1.
6464+- `tests/fixtures/desktop-app.ts:227` — test helper filters out the
6565+ extension-host window. Follows from #1.
6666+6767+---
6868+6969+## Dependency graph
7070+7171+```
7272+phase3.1 (audit ext-host deletion feasibility)
7373+ |
7474+ +-- phase3.2 (delete extensionWindows map + friends) [indep]
7575+ |
7676+ +-- phase3.3 (collapse tile-preload izui fallbacks) [indep]
7777+ |
7878+ +-- phase3.4 (audit legacy IPC channels vs callers) [indep, audit-only]
7979+ |
8080+ v
8181+phase3.5 (rewrite getRunningExtensions / getAllRegisteredExtensions for v2)
8282+ |
8383+ v
8484+phase3.6 (delete extensionHostWindow + host HTML + broadcaster v1 branch)
8585+ |
8686+ v
8787+phase3.7 (delete IPC_CHANNELS.* + preload.js consumers) <- depends on 3.4
8888+ |
8989+ v
9090+phase3.8 (delete preload.js file + cleanup)
9191+```
9292+9393+### Parallelism
9494+9595+- **Wave 1 (parallel):** 3.1, 3.2, 3.3, 3.4 — all independent, audit or
9696+ narrowly-scoped delete.
9797+- **Wave 2 (sequential):** 3.5 after 3.1 (needs audit outcome).
9898+- **Wave 3:** 3.6 after 3.5.
9999+- **Wave 4:** 3.7 after 3.4 + 3.6.
100100+- **Wave 5:** 3.8 after 3.7.
101101+102102+---
103103+104104+## Work packages
105105+106106+### phase3.1 — Audit: can `extensionHostWindow` be removed?
107107+108108+**Scope:** Read-only audit. Document whether the 6 smoke-test assertions
109109+on `peek://app/extension-host.html` represent a real product assertion
110110+(there must be exactly one extension host window) or are historical. For
111111+each of the 6 sites in `smoke.spec.ts`, note what the test is actually
112112+validating and whether the assertion can be rewritten against v2 tile
113113+windows or deleted. Produce a short report (append to this doc or in a
114114+commit body) recommending one of: (a) rewrite tests + delete window,
115115+(b) delete tests + delete window, (c) keep window as empty shell.
116116+117117+**Files:**
118118+- `/Users/dietrich/misc/mpeek/tests/desktop/smoke.spec.ts` (lines 1383,
119119+ 4177, 4189, 4359, 5292, 5299, 5379)
120120+- `/Users/dietrich/misc/mpeek/tests/fixtures/desktop-app.ts:227`
121121+- `/Users/dietrich/misc/mpeek/tests/README.md:140` (docs reference)
122122+123123+**Dependencies:** none
124124+125125+**Budget:** 20 min
126126+127127+**Validation:** none (audit-only). Commit the recommendation doc.
128128+129129+**Risk:** low — no source changes. Output is a decision input for 3.6.
130130+131131+---
132132+133133+### phase3.2 — Delete `extensionWindows` map + `destroyExtensionWindow` + `getExtensionWindow`
134134+135135+**Scope:** After Phase 2.5 #3 deleted `createExtensionWindow()`, nothing
136136+populates `extensionWindows`. Delete the map, its inserts/deletes, the
137137+`destroyExtensionWindow` + `getExtensionWindow` exports, and the
138138+broadcaster loop iterating it. Update callers in `ipc.ts:1199, 1290,
139139+1333` — they become no-ops returning `false`/`null`, or delete the IPC
140140+handlers entirely if no tile uses them.
141141+142142+**Files:**
143143+- `/Users/dietrich/misc/mpeek/backend/electron/main.ts` (lines 73-77,
144144+ 247-258, 1335-1343, 1393-1397, 1418-1442, 1447-1450)
145145+- `/Users/dietrich/misc/mpeek/backend/electron/index.ts:149-151`
146146+- `/Users/dietrich/misc/mpeek/backend/electron/ipc.ts:97-98, 1199,
147147+ 1290, 1333`
148148+149149+**Dependencies:** none (map is already orphan data)
150150+151151+**Budget:** 20 min
152152+153153+**Validation:**
154154+- `yarn tsc --noEmit`
155155+- `yarn test:unit`
156156+- `yarn test:electron:bg -g "smoke"` (smoke suite to catch regressions)
157157+158158+**Risk:** low — dead code elimination; map is already never populated.
159159+160160+---
161161+162162+### phase3.3 — Collapse `hasIzuiCapability()` fallback branches in tile-preload
163163+164164+**Scope:** Four sites in `tile-preload.cts` have
165165+`if (!hasIzuiCapability()) return ipcRenderer.invoke('izui-*')`.
166166+These fall back to legacy non-`tile:*` channels. Every current v2 tile
167167+with izui needs should declare the `izui` capability. Audit each of the
168168+4 methods: verify the strict `tile:*` handler exists, verify no
169169+currently-deployed tile relies on the fallback. Replace with an
170170+unconditional error throw + remove the fallback branch + remove the
171171+corresponding `ipcMain.handle('izui-*')` legacy handler in `ipc.ts` if
172172+no other caller.
173173+174174+**Files:**
175175+- `/Users/dietrich/misc/mpeek/backend/electron/tile-preload.cts` (lines
176176+ 1624, 1636, 1644, 1652, 1660)
177177+- `/Users/dietrich/misc/mpeek/backend/electron/ipc.ts` (`izui-*` handler
178178+ sites — grep first)
179179+180180+**Dependencies:** none
181181+182182+**Budget:** 20 min
183183+184184+**Validation:**
185185+- `yarn tsc --noEmit`
186186+- `yarn test:unit`
187187+- `yarn test:electron:bg -g "izui"` (+ windows-switcher tests, which
188188+ exercise izui heavily)
189189+190190+**Risk:** medium — if any tile was silently relying on the fallback
191191+(i.e. not declaring `izui` capability), it'll start throwing. The audit
192192+step is the mitigation.
193193+194194+---
195195+196196+### phase3.4 — Audit: which legacy IPC channels are still live?
197197+198198+**Scope:** Read-only. For each of the ~103 `ipcMain.handle/on`
199199+registrations in `ipc.ts`, grep who calls it. Three buckets per channel:
200200+(a) only called from `preload.js` → falls when preload.js falls; (b)
201201+also called from `tile-preload.cts` unconditionally → needs strict
202202+migration; (c) called from chrome-extension content scripts or other
203203+non-preload surface → keep. Produce a table: channel → bucket → action.
204204+Output as a new section in this doc.
205205+206206+**Files to grep (callers):**
207207+- `/Users/dietrich/misc/mpeek/preload.js`
208208+- `/Users/dietrich/misc/mpeek/backend/electron/tile-preload.cts`
209209+- `/Users/dietrich/misc/mpeek/chrome-extensions/**` (content scripts)
210210+- `/Users/dietrich/misc/mpeek/backend/tauri/preload.js` (reference,
211211+ don't touch)
212212+- `/Users/dietrich/misc/mpeek/app/**` (legacy `app.*` call sites)
213213+214214+**Dependencies:** none
215215+216216+**Budget:** 30 min (genuine audit with unknown scope — 103 channels)
217217+218218+**Validation:** none (audit). Commit audit table.
219219+220220+**Risk:** low — read-only. Output drives 3.7 sizing.
221221+222222+---
223223+224224+### phase3.5 — Rewrite `getRunningExtensions()` / `getAllRegisteredExtensions()` for v2
225225+226226+**Scope:** These two functions still read `loadedConsolidatedExtensions`
227227++ `extensionWindows` + `declarativeExtensions` + `CONSOLIDATED_EXTENSION_IDS`.
228228+Rewrite to: running = `getLoadedTileIds()` + `declarativeExtensions` +
229229+core-renderer ids (cmd/hud/page; hardcoded list since they're explicit).
230230+Registered = `getRegisteredExtensionIds()` (unchanged) with running-set
231231+derived the same way. Delete `isConsolidatedExtension()` + the
232232+`loadedConsolidatedExtensions` adds at lines 1214/1225/1237. Verify
233233+Features pane still displays everything correctly.
234234+235235+**Files:**
236236+- `/Users/dietrich/misc/mpeek/backend/electron/main.ts` (lines 89, 97,
237237+ 1214, 1225, 1237, 1313-1373, 1380-1413, 1462-1464)
238238+- `/Users/dietrich/misc/mpeek/backend/electron/ipc.ts:100, 1300, 1310,
239239+ 1323` (`isConsolidatedExtension` callsite + handlers)
240240+- `/Users/dietrich/misc/mpeek/backend/electron/index.ts` (if exported)
241241+242242+**Dependencies:** phase3.1 (if 3.1 recommends deleting the host window,
243243+the cleanup is tighter)
244244+245245+**Budget:** 30 min
246246+247247+**Validation:**
248248+- `yarn tsc --noEmit`
249249+- `yarn test:electron:bg -g "features"` (Features pane / settings tests)
250250+- `yarn test:electron:bg -g "extension-list"` if any exist
251251+- Manual smoke: launch app, check Settings > Features shows everything.
252252+253253+**Risk:** medium — Features pane is a real user-facing surface. Test
254254+coverage on it may be thin; run a manual smoke.
255255+256256+---
257257+258258+### phase3.6 — Delete `extensionHostWindow` + host HTML + broadcaster v1 branch
259259+260260+**Scope:** Conditional on phase3.1's recommendation being (a) or (b).
261261+Delete `extensionHostWindow` variable, `createExtensionHostWindow()`,
262262+`getExtensionHostWindow()`, the `await createExtensionHostWindow()` call
263263+at `main.ts:1208`, the broadcaster loop iterating host frames
264264+(`main.ts:226-244`), the `INTERNAL_URLS` reference in `ipc.ts:2306`, and
265265+the `app/extension-host.html` file. Update smoke tests per 3.1 outcome.
266266+267267+**Files:**
268268+- `/Users/dietrich/misc/mpeek/backend/electron/main.ts` (lines 80, 226-
269269+ 244, 767-771, 1103-1176, 1208, 1321, 1386, 1455-1457)
270270+- `/Users/dietrich/misc/mpeek/backend/electron/ipc.ts:2306`
271271+- `/Users/dietrich/misc/mpeek/app/extension-host.html` — delete
272272+- `/Users/dietrich/misc/mpeek/tests/desktop/smoke.spec.ts` (lines per
273273+ 3.1)
274274+- `/Users/dietrich/misc/mpeek/tests/fixtures/desktop-app.ts:227`
275275+- `/Users/dietrich/misc/mpeek/tests/README.md:140`
276276+277277+**Dependencies:** phase3.1 (audit), phase3.5 (rewrites the map consumers
278278+that reference the host window)
279279+280280+**Budget:** 30 min
281281+282282+**Validation:**
283283+- `yarn tsc --noEmit`
284284+- `yarn test:electron:bg -g "smoke"` (full smoke suite)
285285+- `yarn test:electron:bg` (full) if smoke passes
286286+287287+**Risk:** high — this is the "big one." Deletes a window that's existed
288288+since the start of v1. Any test or product code still implicitly
289289+depending on it will break. Phase 3.1's audit is the safety net.
290290+291291+---
292292+293293+### phase3.7 — Delete unused legacy IPC channels + `IPC_CHANNELS` const
294294+295295+**Scope:** For each channel from phase3.4 bucket (a) (only preload.js
296296+callers), delete the `ipcMain.handle/on(...)` registration in `ipc.ts`.
297297+For bucket (b) channels, convert the `tile-preload.cts` calls to strict
298298+`tile:*` paths (audit which need capability gates, which are privileged).
299299+Delete the `IPC_CHANNELS` const from `backend/config.ts`. Update
300300+`backend/electron/ipc.ts:121`, `backend/electron/index.ts:13`,
301301+`backend/electron/config.ts:14` imports. Delete the
302302+`IPC_CHANNELS.SUBSCRIBE/PUBLISH/CLOSE_WINDOW/REGISTER_SHORTCUT/
303303+UNREGISTER_SHORTCUT/MODIFY_WINDOW/CONSOLE/RENDERER_LOG/APP_QUIT/
304304+APP_RESTART` handler blocks at `ipc.ts:4158-4476`.
305305+306306+**Files:**
307307+- `/Users/dietrich/misc/mpeek/backend/electron/ipc.ts` (many lines —
308308+ see phase3.4 output)
309309+- `/Users/dietrich/misc/mpeek/backend/electron/tile-preload.cts` (the
310310+ ~70 `ipcRenderer.invoke('legacy-*')` sites in bucket (b))
311311+- `/Users/dietrich/misc/mpeek/backend/config.ts:16-27`
312312+- `/Users/dietrich/misc/mpeek/backend/electron/config.ts:14`
313313+- `/Users/dietrich/misc/mpeek/backend/electron/index.ts:13`
314314+315315+**Dependencies:** phase3.4 (must know bucket per channel), phase3.6
316316+(host window gone = no frame.send path using IPC_CHANNELS names), phase
317317+3.2 (broadcaster loop simplified)
318318+319319+**Budget:** Split into sub-packages — the whole sweep is too big for
320320+one agent. Suggested split:
321321+- **phase3.7a** (20 min): bucket-(a) channels (preload-only callers).
322322+ Just delete the handler and the preload.js wrapper.
323323+- **phase3.7b** (20 min): window-* channels (~14 channels). Route
324324+ tile-preload calls to existing `tile:window:*` handlers; delete
325325+ legacy window-* handlers.
326326+- **phase3.7c** (20 min): datastore-* channels (~25 channels). Route
327327+ tile-preload calls to existing `tile:datastore:*` handlers; add
328328+ strict variants where missing.
329329+- **phase3.7d** (20 min): extension-* + feature-* channels (~12
330330+ channels) + context-* + chrome-ext:* audit — these are the ones
331331+ most likely to need product decisions; may bounce back to user.
332332+- **phase3.7e** (15 min): `IPC_CHANNELS.*` handlers (9 channels) +
333333+ delete the const.
334334+335335+**Validation per sub-package:**
336336+- `yarn tsc --noEmit`
337337+- `yarn test:unit`
338338+- `yarn test:electron:bg` (each sub-package)
339339+340340+**Risk:** medium–high per sub-package. Bucket (b) tile-preload sites
341341+may hit product-critical paths. Split per domain (window/datastore/
342342+extension) so each agent has a tight scope and the blast radius stays
343343+small.
344344+345345+---
346346+347347+### phase3.8 — Delete `preload.js` file + final cleanup
348348+349349+**Scope:** After 3.7, nothing should import or use `preload.js`. Delete
350350+the file. Remove `preloadPath`/`setPreloadPath`/`getPreloadPath` from
351351+`backend/electron/config.ts`, `entry.ts`, `index.ts`, and the window-open
352352+fallback at `ipc.ts:2268,2670`. Update `will-attach-webview` in
353353+`entry.ts:189-211` to drop the v1 fallback branch — all peek:// webview
354354+children get `tile-preload.cjs` unconditionally (or throw if not a v2
355355+tile).
356356+357357+**Files:**
358358+- `/Users/dietrich/misc/mpeek/preload.js` — delete (2693 LOC)
359359+- `/Users/dietrich/misc/mpeek/backend/electron/entry.ts` (lines 63, 152,
360360+ 157, 189-211)
361361+- `/Users/dietrich/misc/mpeek/backend/electron/config.ts` (preload path
362362+ getters/setters)
363363+- `/Users/dietrich/misc/mpeek/backend/electron/index.ts` (exports)
364364+- `/Users/dietrich/misc/mpeek/backend/electron/ipc.ts` (lines 119,
365365+ 2268, 2670)
366366+367367+**Dependencies:** phase3.7 (all buckets) + phase3.6
368368+369369+**Budget:** 20 min
370370+371371+**Validation:**
372372+- `yarn tsc --noEmit`
373373+- `yarn test:unit`
374374+- `yarn test:electron:bg` (full suite)
375375+- `git grep preload.js` → should only match comments, docs, or
376376+ Tauri-local copy (which is out of scope per Phase 2.5 #5)
377377+- `git grep "IPC_CHANNELS\b"` → zero matches
378378+379379+**Risk:** medium — the final delete. If anything sneaks through 3.7, it
380380+breaks here. Mitigation: the tsc + test runs in 3.7 should catch it
381381+first.
382382+383383+---
384384+385385+## Out of scope (deferred to Phase 4)
386386+387387+- Tauri-side preload migration (`backend/tauri/preload.js`,
388388+ `backend/tauri-mobile/.../include_str!`). Per Phase 2.5 #5 — copy to a
389389+ Tauri-local path if Electron deletion would break `include_str!`.
390390+- Manifest v3 bump / `type?` field deletion / `windowHints` cleanup —
391391+ Phase 4.
392392+- `docs/tile-preload-trimming-plan.md`, `docs/tiles-single-file.md`,
393393+ `docs/command-audit.md` doc updates — Phase 4.
394394+395395+---
396396+397397+## Sizing principle (per feedback_agent_budgets_short.md)
398398+399399+All packages target 15–30 min. Packages that hit the ceiling (3.4, 3.6,
400400+3.7) are the ones with genuine unknowns; if they overrun, the agent
401401+commits what it has + reports blocker so the next spawn adjusts scope.
402402+Prefer shipping 5 validated packages over 10 sketchy ones.