···11-# Phase 2.5 #3 Agent Report — createExtensionWindow migration audit
11+# Phase 2.5 #3 Agent Report — createExtensionWindow migration audit + dead-code cleanup
2233-## Audit (Phase A)
33+## Audit findings (Phase A)
4455-Target: `createExtensionWindow()` in `backend/electron/main.ts` (line 1106).
66-Still uses v1 `preload.js` (`config.preloadPath`). Function creates a
77-BrowserWindow per extension and loads `peek://ext/{id}/background.html`.
55+Target: `createExtensionWindow()` in `backend/electron/main.ts`. Still uses v1
66+`preload.js`. All 8 call sites classified:
8799-### Call sites (6 inside main.ts, 2 inside ipc.ts)
88+| Site | Caller | Reachability | Bucket |
99+|------|--------|--------------|--------|
1010+| main.ts `loadEnabledExtensions()` | none | DEAD (exported but unreferenced; replaced by `loadExtensions()` at entry.ts:741) | dead code |
1111+| main.ts `loadExtensions()` — `externalBuiltinIds` loop | startup | EMPTY in practice — all 31 features in `features/` are `manifestVersion: 2` and get loaded by `loadV2Feature()` into `v2FeatureIds`, so the filter `!CONSOLIDATED ∩ !v2FeatureIds` yields nothing | dead in practice |
1212+| main.ts `loadExtensions()` — `enabledExternalExts` loop | startup | EMPTY — `extensions` DB table is only populated by the legacy settings-pane `api.extensions.add()` flow, superseded by features-manager (which writes to the v2 feature registry) | dead in practice |
1313+| main.ts `loadDevExtension()` | CLI `--load-extension` | LIVE (dev-only) | v1 path, live |
1414+| main.ts `reloadExtension()` | features-manager UI via `api.extensions.reload()` | LIVE | v1 path, live |
1515+| ipc.ts `extension-window-load` handler | none | DEAD | dead code |
1616+| ipc.ts `extension-window-reload` handler | none | DEAD | dead code |
10171111-| Site | Caller | Runtime reachability | Bucket |
1212-|------|--------|----------------------|--------|
1313-| main.ts:1213 | `loadEnabledExtensions()` | **DEAD** — exported but never invoked. Replaced by `loadExtensions()` (entry.ts:741). | dead code |
1414-| main.ts:1458 | `loadExtensions()` — `externalBuiltinIds` loop | **EMPTY in practice.** Filter = `registered ∩ !CONSOLIDATED_EXTENSION_IDS ∩ enabled ∩ !v2FeatureIds`. All 35 features in `features/` are v2 (`manifestVersion: 2`) and get loaded by `loadV2Feature()` into `v2FeatureIds`, so this set is empty. | dead in practice |
1515-| main.ts:1467 | `loadExtensions()` — `enabledExternalExts` from `extensions` DB table | Empty under normal test / dev. The `extensions` table is populated by the legacy settings UI `api.extensions.add()` — superseded by features-manager (which installs into the v2 feature registry, not this table). | dead in practice |
1616-| main.ts:1672 | `loadDevExtension()` | Live when user passes `--load-extension` CLI flag. Dev-only. | v1 path, dev-only |
1717-| main.ts:1747 | `reloadExtension()` | **LIVE.** Called by `ipcMain.handle('extension-reload')` (ipc.ts:1374), which is invoked by `api.extensions.reload(id)` on trustedBuiltin tiles (features-manager, test fixtures). | v1 path, live |
1818-| ipc.ts:1287 | `ipcMain.handle('extension-window-load')` | **DEAD** — no renderer invokes this channel (grep). | dead code |
1919-| ipc.ts:1315 | `ipcMain.handle('extension-window-reload')` | **DEAD** — no renderer invokes this channel (grep). | dead code |
1818+Bucket counts: **0** v2-migratable (already migrated via `loadV2Tile`), **0**
1919+chrome extensions (those use `session.loadExtension` in
2020+`chrome-extensions.ts`, not `createExtensionWindow`), **2** live v1 callers
2121+that need a `relaunchTile(tileId)` helper that doesn't exist yet, **4** dead
2222+call sites safe to remove.
20232121-### Bucket summary
2424+## Implementation choice + rationale
22252323-- **v2-migratable builtin externals:** 0. All features are already loaded through
2424- `loadV2Tile()` / `launchTile()` today — `createExtensionWindow` is never
2525- invoked for them at runtime.
2626-- **Chrome extensions:** 0 — Chrome extensions use a separate path
2727- (`backend/electron/chrome-extensions.ts` + `session.loadExtension()`), not
2828- `createExtensionWindow`. Out of scope as planned.
2929-- **v1-only live callers:** 2 — `reloadExtension` (features-manager) and
3030- `loadDevExtension` (CLI). Both need a v2 "launch/relaunch a tile by id"
3131- helper that doesn't exist yet (tile-launcher has `launchTile(opts)` but no
3232- "find the manifest, revoke the old token, close the old window, relaunch"
3333- convenience).
3434-- **Dead code:** 4 call sites (`loadEnabledExtensions`, two IPC handlers,
3535- + the two startup branches that are empty in practice). Safe to drop.
2626+The common assumption in the v1-removal plan — "`createExtensionWindow` exists
2727+for external builtins like `example`" — is outdated. `example` is already a v2
2828+tile (eagerly launched via its `type: "background"` entry), and
2929+`createExtensionWindow` is dead on the startup path.
36303737-### Implementation choice
3131+The two remaining LIVE callers (`reloadExtension`, `loadDevExtension`) each
3232+need a v2 "launch a tile by id, revoking any existing window/token" helper —
3333+tile-launcher currently exposes `launchTile(opts)` that takes a parsed
3434+manifest, not an id + reload-aware-by-default. Building that is a dedicated
3535+Phase 2.5 #3b (est. 1–2h).
38363939-**Exit at audit boundary.** Migrating `reloadExtension` and `loadDevExtension`
4040-requires:
3737+For this pass: **commit the audit**, then **remove the dead code** so Phase 3
3838+destructive cleanup has a smaller surface when the remaining two live callers
3939+are migrated.
41404242-1. A `relaunchTile(tileId)` helper in `tile-launcher.ts` that re-reads the
4343- manifest from disk, revokes the old token, closes the old window, and
4444- calls `launchTile` again.
4545-2. A "find the tile's resident/launch entry from manifest" utility (currently
4646- only `loadV2Tile` does this, and it's tangled with eager/lazy branching).
4747-3. Handling the dev-extension case where the path is registered
4848- transiently via `registerDevExtension` and not in the feature registry.
4141+## What landed
49425050-That's 1–2 commits of new infrastructure, not a "remove v1 call site" swap.
5151-Given the 60-minute budget and the audit finding that `createExtensionWindow`
5252-is effectively already replaced for the common path (startup loading), the
5353-correct move is to (a) remove the dead-code call sites cleanly in a follow-up
5454-and (b) defer the remaining two live v1 call sites to a dedicated Phase 2.5 #3b.
4343+Three commits on top of `28d07a05`:
55445656-### Open items for Phase 3 / follow-up
4545+1. `cb7b2778` — `chore: triage stub for Phase 2.5 #3`
4646+2. (merged into audit commit) `chore: audit createExtensionWindow callers`
4747+3. `25c154ea` — `refactor(electron): remove dead createExtensionWindow callers`
57485858-- **Phase 2.5 #3b** — implement `relaunchTile(tileId)` in tile-launcher.ts;
5959- rewrite `reloadExtension()` and `loadDevExtension()` to use it; delete
6060- `createExtensionWindow`, `loadEnabledExtensions`, the dead IPC handlers,
6161- and the dead `externalBuiltinIds` / `enabledExternalExts` branches.
6262-- Phase 3 can proceed for everything not behind `createExtensionWindow` —
6363- features-manager reload and CLI dev extensions are the only surfaces
6464- that still need v1 `preload.js`.
4949+Files changed in the refactor commit:
65506666-### Files read during audit
5151+- `backend/electron/main.ts`:
5252+ - Deleted `loadEnabledExtensions()` function (51 lines, dead).
5353+ - Demoted `createExtensionWindow()` from `export` to module-private and
5454+ added a doc-comment explaining its remaining live callers + pointing to
5555+ Phase 2.5 #3b for the full removal.
5656+- `backend/electron/ipc.ts`:
5757+ - Deleted `ipcMain.handle('extension-window-load')` (14 lines).
5858+ - Deleted `ipcMain.handle('extension-window-reload')` (16 lines).
5959+ - Removed the now-unused `createExtensionWindow` import.
6060+- `backend/electron/index.ts`: dropped `createExtensionWindow` and
6161+ `loadEnabledExtensions` from the public surface.
6262+- `app/index.js`: updated stale "calls loadEnabledExtensions()" comment to
6363+ point at the real startup path.
67646868-- `backend/electron/main.ts` — createExtensionWindow, loadExtensions, loadEnabledExtensions, reloadExtension, loadDevExtension, discoverBuiltinExtensions
6969-- `backend/electron/ipc.ts` — extension-window-* handlers, extension-reload
7070-- `backend/electron/tile-compat.ts` — loadV2Tile, ensureTileIpcHandlers
7171-- `backend/electron/tile-loader.ts` — loadV2Feature, loadFeaturesFromRegistry
7272-- `backend/electron/feature-startup.ts` — initializeFeatures
7373-- `backend/electron/feature-installer.ts` — syncBuiltinFeatures
7474-- `backend/electron/tile-launcher.ts` — launchTile
7575-- `backend/electron/extensions.ts` — getExternalExtensions (reads `extensions` DB table)
7676-- All `features/*/manifest.json` — confirmed 31 of 31 are `manifestVersion: 2`
6565+Net LOC delta: −110 / +20. Public exports shrink by 2.
77667867## Validation
79688080-N/A at audit-commit. No code changes landed.
6969+- `npx tsc -p backend/tsconfig.json --noEmit` — clean.
7070+- `yarn test:unit` — 565/565 pass.
7171+- `yarn test:grep "v2 trustedBuiltin tile"` — 1/1.
7272+- `yarn test:grep "direct pubsub kagi execution works"` — 1/1.
7373+- `yarn test:grep "open and close settings"` — 1/1.
7474+- `yarn test:grep "Group Mode Context"` — 5/5.
7575+- `yarn test:grep "HUD"` — 11/11.
7676+- `yarn test:grep "example"` — 1 test, skipped (not a regression; pre-existing).
7777+7878+## Open items for Phase 3 / follow-up
7979+8080+- **Phase 2.5 #3b** — add `relaunchTile(tileId)` in `tile-launcher.ts`
8181+ (re-read manifest, revoke old token via `revokeTokensForTile`, close window
8282+ via `tileWindows` map, call `launchTile` with the resident/launch entry).
8383+ Rewrite `reloadExtension()` and `loadDevExtension()` on top of it. Then
8484+ delete `createExtensionWindow`, the remaining dead branches in
8585+ `loadExtensions()` (`externalBuiltinIds` + `enabledExternalExts` loops),
8686+ and the `extensionWindows` map.
8787+- **Chrome extensions** stay on `session.loadExtension()` — documented in
8888+ `docs/v1-removal-plan.md` and not part of v1 removal.
8989+- `extension-window-unload` IPC handler is probably also dead but intentionally
9090+ left in this pass — scoped audit confirmed `-load` / `-reload` only.