experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs(phase3): 3.4 audit — legacy IPC channels bucketed by caller

+422
+422
docs/v1-removal-phase3-tasks.md
··· 403 403 404 404 --- 405 405 406 + ### phase3.4 — IPC channel audit (2026-04-18) 407 + 408 + Audited every `ipcMain.handle(...)` and `ipcMain.on(...)` registration 409 + in `backend/electron/ipc.ts`, then grepped every call site across 410 + `preload.js`, `backend/electron/tile-preload.cts`, `app/**`, 411 + `chrome-extensions/**`, and `backend/electron/**` (excluding test files 412 + and Tauri). 413 + 414 + **Total channel count: 193** (183 `.handle` + 10 `.on`). 415 + 416 + #### Bucket totals 417 + 418 + | Bucket | Count | Meaning | 419 + |---|---|---| 420 + | `preload-only` | 79 | Called only from `preload.js`. Falls when preload.js is deleted in 3.7a/3.8. | 421 + | `tile-preload-fallback` | 33 | Called from `tile-preload.cts` only inside an `if (!hasXxxCapability())` branch. Gated; can be deleted once we audit that every deployed tile declares the relevant capability. | 422 + | `tile-preload-strict-shadow` | 71 | Called UNCONDITIONALLY from `tile-preload.cts` (no strict `tile:*` equivalent OR strict equivalent only for some methods). Need a strict shim built first. | 423 + | `live-other` | 10 | `IPC_CHANNELS.*` set — used by both preload.js and the `tile-preload.cts` pubsub send/subscribe and core glue. Treated as their own batch (3.7e). | 424 + 425 + Sum: 79 + 33 + 71 + 10 = 193 ✔ 426 + 427 + `live-other` here is NOT "external content scripts" — there are zero 428 + `ipcRenderer.*` calls in `chrome-extensions/**`. The chrome polyfills 429 + generate IPC code at runtime via `_ipc-bridge.js` but those target 430 + `chrome-ext-api:*` channels in `tile-ipc.ts`, not legacy channels in 431 + `ipc.ts`. The only true "non-preload" surface in scope here is the 432 + `IPC_CHANNELS.*` family. 433 + 434 + #### Channel table 435 + 436 + Format: `channel | bucket | callers (file:line, abbreviated)`. `pl` = 437 + `preload.js`, `tp` = `backend/electron/tile-preload.cts`, 438 + `pg` = `app/page/page.js`, `ctx` = `app/context/*.js`. 439 + 440 + ##### `datastore-*` (47 channels) — batch 3.7c 441 + 442 + | channel | bucket | callers | 443 + |---|---|---| 444 + | `datastore-add-address` | strict-shadow | pl:518, tp:861 | 445 + | `datastore-get-address` | strict-shadow | pl:521, tp:863 | 446 + | `datastore-update-address` | strict-shadow | pl:524, tp:865 | 447 + | `datastore-query-addresses` | strict-shadow | pl:527,1227, tp:867 | 448 + | `datastore-add-visit` | strict-shadow | pl:530, tp:869 | 449 + | `datastore-query-visits` | strict-shadow | pl:533, tp:871 | 450 + | `datastore-add-content` | strict-shadow | pl:536, tp:873 | 451 + | `datastore-query-content` | strict-shadow | pl:539, tp:875 | 452 + | `datastore-get-table` | strict-shadow | pl:542, tp:877 | 453 + | `datastore-set-row` | strict-shadow | pl:545, tp:879 | 454 + | `datastore-get-row` | strict-shadow | pl:548, tp:881 | 455 + | `datastore-get-stats` | preload-only | pl:551 (tp uses `tile:datastore:get-stats`) | 456 + | `datastore-get-or-create-tag` | strict-shadow | pl:555, tp:885 | 457 + | `datastore-tag-address` | strict-shadow | pl:558, tp:887 | 458 + | `datastore-untag-address` | strict-shadow | pl:561, tp:889 | 459 + | `datastore-get-tags-by-frecency` | strict-shadow | pl:564, tp:891 | 460 + | `datastore-rename-tag` | preload-only | pl:608 (tp uses `tile:datastore:rename-tag`) | 461 + | `datastore-update-tag-color` | preload-only | pl:611 (tp uses `tile:datastore:update-tag-color`) | 462 + | `datastore-delete-tag` | preload-only | pl:614 (tp uses `tile:datastore:delete-tag`) | 463 + | `datastore-get-address-tags` | strict-shadow | pl:567, tp:899 | 464 + | `datastore-get-addresses-by-tag` | strict-shadow | pl:570, tp:901 | 465 + | `datastore-get-untagged-addresses` | strict-shadow | pl:573, tp:903 | 466 + | `datastore-add-item` | strict-shadow | pl:578, tp:906 | 467 + | `datastore-get-item` | strict-shadow | pl:581, tp:908 | 468 + | `datastore-update-item` | strict-shadow | pl:584, tp:910 | 469 + | `datastore-delete-item` | strict-shadow | pl:587, tp:912 | 470 + | `datastore-hard-delete-item` | preload-only | pl:590 (tp uses `tile:datastore:hard-delete-item`) | 471 + | `datastore-update-item-title` | preload-only | pl:640 (tp uses `tile:datastore:update-item-title`) | 472 + | `datastore-update-item-favicon` | preload-only | pl:645 (tp uses `tile:datastore:update-item-favicon`) | 473 + | `datastore-query-items` | strict-shadow | pl:593, tp:920 | 474 + | `datastore-tag-item` | strict-shadow | pl:596, tp:922 | 475 + | `datastore-untag-item` | strict-shadow | pl:599, tp:924 | 476 + | `datastore-get-item-tags` | strict-shadow | pl:602, tp:926 | 477 + | `datastore-get-items-by-tag` | strict-shadow | pl:605, tp:928 | 478 + | `datastore-get-history` | preload-only | pl:619 (tp uses `tile:datastore:get-history`) | 479 + | `datastore-record-item-visit` | preload-only | pl:630 (tp uses `tile:datastore:record-item-visit`) | 480 + | `datastore-get-item-visits` | preload-only | pl:627 (tp uses `tile:datastore:get-item-visits`) | 481 + | `datastore-query-item-visits` | preload-only | pl:624 (tp uses `tile:datastore:query-item-visits`) | 482 + | `datastore-track-navigation` | preload-only | pl:635 (tp uses `tile:datastore:track-navigation`) | 483 + | `datastore-query-items-by-frecency` | preload-only | (tp uses `tile:datastore:query-items-by-frecency`; no preload caller — actually unused) | 484 + | `datastore-add-item-event` | strict-shadow | pl:655, tp:943 | 485 + | `datastore-get-item-event` | strict-shadow | pl:658, tp:945 | 486 + | `datastore-query-item-events` | strict-shadow | pl:661, tp:947 | 487 + | `datastore-delete-item-event` | strict-shadow | pl:664, tp:949 | 488 + | `datastore-delete-item-events` | strict-shadow | pl:667, tp:951 | 489 + | `datastore-get-latest-item-event` | strict-shadow | pl:670, tp:953 | 490 + | `datastore-count-item-events` | strict-shadow | pl:673, tp:955 | 491 + | `extract-page-content` | preload-only | pl:650 (tp uses `tile:datastore:extract-page-content`) | 492 + 493 + ##### `window-*` (~24 channels) — batch 3.7b 494 + 495 + | channel | bucket | callers | 496 + |---|---|---| 497 + | `window-open` | fallback | pl:283, tp:514 (else of `hasWindowCapability`) | 498 + | `window-close` | fallback | pl:295, tp:541 (else of cap check) | 499 + | `window-hide` | fallback | pl:302, tp:615 | 500 + | `window-show` | fallback | pl:309, tp:600 | 501 + | `window-exists` | fallback | pl:316, tp:585 | 502 + | `window-move` | preload-only | pl:323,2585 | 503 + | `window-resize` | preload-only | pl:332 | 504 + | `window-get-position` | preload-only | pl:341,2491 | 505 + | `window-focus` | preload-only | pl:348 | 506 + | `window-set-overlay-focus-target` | fallback | pl:355, tp:726 | 507 + | `window-blur` | preload-only | pl:362 | 508 + | `window-list` | fallback | pl:369, tp:570 | 509 + | `window-devtools` | preload-only | pl:376 | 510 + | `window-get-bounds` | preload-only | pl:383 | 511 + | `window-set-bounds` | preload-only | pl:389 | 512 + | `get-display-info` | preload-only | pl:392 | 513 + | `window-set-ignore-mouse-events` | fallback | pl:396, tp:636 | 514 + | `get-focused-visible-window-id` | fallback | pl:409,794, tp:710,2048 | 515 + | `get-window-id` | fallback | pl:416,797,2625, tp:2050 (always-on, no cap gate) | 516 + | `window-is-transient` | preload-only | pl:424 | 517 + | `window-set-scroll-position` | preload-only | pl:431 | 518 + | `window-center` | fallback | pl:435, tp:654 | 519 + | `window-center-all` | fallback | pl:439, tp:668 | 520 + | `window-maximize` | fallback | pl:443, tp:682 | 521 + | `window-fullscreen` | fallback | pl:447, tp:696 | 522 + | `window-set-visible-on-all-workspaces` | fallback | tp:749 (else of cap), no pl call | 523 + | `window-reopen-last-closed` | preload-only | (no caller found in pl/tp; check core-glue/cmd) — likely dead | 524 + | `window-animate` | preload-only | (only docs/api.md reference; no live caller) — likely dead | 525 + | `window-is-draggable` | preload-only | (only `.agent-plan.md` reference; no live caller) — likely dead | 526 + | `get-app-prefs` | preload-only | pl:2632 | 527 + | `pubsub-stats` | preload-only | pl:276 | 528 + | `opener-postmessage` | strict-shadow | pg:1112 (via `api.invoke('opener-postmessage')` passthrough) | 529 + | `opener-close` | strict-shadow | pg:1122 (via `api.invoke('opener-close')` passthrough) | 530 + | `opener-focus` | strict-shadow | pg:1129 (via `api.invoke('opener-focus')` passthrough) | 531 + | `screen-get-primary-display` | fallback | tp:1769 (else of `hasScreenCapability` at 1764); no pl | 532 + 533 + ##### `extension-*` + `feature-*` (15 channels) — batch 3.7d 534 + 535 + | channel | bucket | callers | 536 + |---|---|---| 537 + | `extension-pick-folder` | strict-shadow | pl:1755, tp:1957 (trustedBuiltin gate, not capability) | 538 + | `extension-validate-folder` | strict-shadow | pl:1764, tp:1963 | 539 + | `extension-add` | strict-shadow | pl:1778, tp:1969 | 540 + | `extension-remove` | strict-shadow | pl:1790, tp:1975 | 541 + | `extension-update` | strict-shadow | pl:1803, tp:1981 | 542 + | `extension-get-all` | strict-shadow | pl:1811, tp:1987 | 543 + | `extension-get` | strict-shadow | pl:1820, tp:1993 | 544 + | `extension-window-unload` | preload-only | (no live caller — likely dead) | 545 + | `extension-window-list` | strict-shadow | pl:1598, tp:1933 | 546 + | `extension-list-all-registered` | strict-shadow | pl:1606, tp:1939 | 547 + | `extension-window-devtools` | strict-shadow | pl:1709, tp:1951 | 548 + | `extension-reload` | strict-shadow | pl:1697, tp:1945 | 549 + | `extension-manifest-get` | preload-only | (no caller in pl or tp — likely dead) | 550 + | `feature-settings-get` | preload-only | pl:1847 | 551 + | `feature-settings-set` | preload-only | pl:1861 | 552 + | `feature-settings-get-key` | preload-only | pl:1874,1902 | 553 + | `feature-settings-set-key` | preload-only | pl:1888 | 554 + | `feature-settings-schema` | strict-shadow | pl:1830, tp:1999 | 555 + 556 + ##### `feature-registry:*` / `feature-install:*` / `feature-browse:*` (9 channels) — batch 3.7d 557 + 558 + | channel | bucket | callers | 559 + |---|---|---| 560 + | `feature-registry:list` | fallback | tp:1448 (else of `hasFeaturesCapability`) | 561 + | `feature-registry:get` | fallback | tp:1450 | 562 + | `feature-registry:history` | fallback | tp:1452 | 563 + | `feature-registry:enable` | fallback | tp:1454 | 564 + | `feature-registry:disable` | fallback | tp:1456 | 565 + | `feature-registry:remove` | fallback | tp:1458 | 566 + | `feature-install:resolve` | fallback | tp:1460 | 567 + | `feature-install:preview-capabilities` | fallback | tp:1462 | 568 + | `feature-install:install` | fallback | tp:1468 | 569 + | `feature-browse:resolve-publisher` | fallback | tp:1470 | 570 + 571 + ##### `context-*` (6 channels) — batch 3.7d 572 + 573 + | channel | bucket | callers | 574 + |---|---|---| 575 + | `context-get` | fallback | pl:2107,2210, tp:1287, ctx — but ctx is in core renderer (preload-loaded) | 576 + | `context-set` | fallback | pl:2120,2223, tp:1289, ctx (idx:80, history:56) | 577 + | `context-history` | fallback | pl:2170, tp:1291, ctx (history:77) | 578 + | `context-snapshot` | fallback | pl:2180, tp:1293, ctx (history:110) | 579 + | `context-windows-with-value` | fallback | pl:2190,2249, tp:1295 | 580 + | `context-windows-in-space` | fallback | pl:2199, tp:1297 | 581 + 582 + Note: `app/context/*.js` modules import via the v1 preload's 583 + `window.app.invoke` → still classified as preload-only consumers from 584 + the channel's perspective. Once preload.js dies, those modules will 585 + need to either move into a tile that has the `context` capability or 586 + be loaded from a context that uses tile-preload. 587 + 588 + ##### `theme:*` + `darkMode:*` (12 channels) — batch 3.7a 589 + 590 + | channel | bucket | callers | 591 + |---|---|---| 592 + | `theme:get` | strict-shadow | pl:684,1129,1155, tp:2036 (trustedBuiltin only) | 593 + | `theme:setColorScheme` | strict-shadow | pl:693, tp:2042 | 594 + | `theme:setWindowColorScheme` | strict-shadow | pl:802, tp:2055 | 595 + | `theme:setTheme` | strict-shadow | pl:702, tp:2040 | 596 + | `theme:list` | strict-shadow | pl:710, tp:2037 | 597 + | `theme:pickFolder` | preload-only | pl:726 | 598 + | `theme:validateFolder` | preload-only | pl:735 | 599 + | `theme:add` | preload-only | pl:744 | 600 + | `theme:remove` | preload-only | pl:753 | 601 + | `theme:getAll` | strict-shadow | pl:718, tp:2038 | 602 + | `theme:reload` | preload-only | pl:762 (also `pl:780` listener for `theme:reload` event) | 603 + | `darkMode:get` | preload-only | pl:968 | 604 + | `darkMode:set` | preload-only | pl:977 | 605 + 606 + ##### `chrome-ext:*` + `adblocker:*` (12 channels) — batch 3.7d 607 + 608 + | channel | bucket | callers | 609 + |---|---|---| 610 + | `chrome-ext:list` | strict-shadow | pl:1075, tp:2071 (trustedBuiltin) | 611 + | `chrome-ext:enable` | strict-shadow | pl:1084, tp:2077 | 612 + | `chrome-ext:disable` | strict-shadow | pl:1093, tp:2083 | 613 + | `chrome-ext:getStatus` | strict-shadow | pl:1101, tp:2089 | 614 + | `chrome-ext:getUiEntries` | strict-shadow | pl:1109, tp:2095 | 615 + | `chrome-ext:openPage` | strict-shadow | pl:1119, tp:2101 | 616 + | `adblocker:getStatus` | preload-only | pl:1003 | 617 + | `adblocker:enable` | preload-only | pl:1011 | 618 + | `adblocker:disable` | preload-only | pl:1019 | 619 + | `adblocker:getBlockedCount` | preload-only | pl:1027 | 620 + | `adblocker:getAllowlist` | preload-only | pl:1035 | 621 + | `adblocker:isSiteAllowed` | preload-only | pl:1044 | 622 + | `adblocker:allowSite` | preload-only | pl:1053 | 623 + | `adblocker:disallowSite` | preload-only | pl:1062 | 624 + 625 + ##### `web-nav-*` (4 channels) — batch 3.7a 626 + 627 + | channel | bucket | callers | 628 + |---|---|---| 629 + | `web-nav-back` | preload-only | (no caller in pl/tp — likely dead) | 630 + | `web-nav-forward` | preload-only | (no caller in pl/tp — likely dead) | 631 + | `web-nav-reload` | preload-only | (no caller in pl/tp — likely dead) | 632 + | `web-nav-state` | preload-only | (no caller in pl/tp — likely dead) | 633 + 634 + ##### `izui-*` (5 channels) — batch 3.3 (separate package) 635 + 636 + | channel | bucket | callers | 637 + |---|---|---| 638 + | `izui-is-transient` | fallback | pl:464, tp:1624 (else of izui cap check) | 639 + | `izui-get-effective-mode` | fallback | pl:473, tp:1636 | 640 + | `izui-get-state` | fallback | pl:481, tp:1644 | 641 + | `izui-get-pre-overlay-focus-target` | fallback | pl:490, tp:1652 | 642 + | `izui-close-self` | fallback | pl:500, tp:1660 | 643 + 644 + ##### `oauth-*` (2 channels) — batch 3.7d 645 + 646 + | channel | bucket | callers | 647 + |---|---|---| 648 + | `oauth-start-loopback` | fallback | pl:1962, tp:1690 (else of `hasOauthCapability` check at 1697) | 649 + | `oauth-await-callback` | fallback | pl:1963, tp:1692 | 650 + 651 + ##### `sync-*` (6 channels) — batch 3.7d 652 + 653 + | channel | bucket | callers | 654 + |---|---|---| 655 + | `sync-get-config` | preload-only | pl:813 | 656 + | `sync-set-config` | preload-only | pl:822 | 657 + | `sync-pull` | preload-only | pl:831 | 658 + | `sync-push` | preload-only | pl:840 | 659 + | `sync-full` | fallback | pl:848, tp:1744 (else of `hasSyncCapability` check at 1743) | 660 + | `sync-status` | preload-only | pl:856 | 661 + 662 + ##### `backup-*` + `session-*` + `save-space-workspaces` + `profiles:*` + `modes:*` (17 channels) — batch 3.7a/3.7d 663 + 664 + | channel | bucket | callers | 665 + |---|---|---| 666 + | `backup-get-config` | preload-only | (no caller in pl/tp — likely dead) | 667 + | `backup-set-config` | preload-only | (no caller — likely dead) | 668 + | `backup-create` | preload-only | (no caller — likely dead) | 669 + | `backup-list` | preload-only | (no caller — likely dead) | 670 + | `session-save` | preload-only | (no caller — likely dead) | 671 + | `session-get-snapshot-info` | preload-only | (no caller — likely dead) | 672 + | `session-restore` | preload-only | (no caller — likely dead) | 673 + | `session-restore-interactive` | preload-only | (no caller — likely dead) | 674 + | `save-space-workspaces` | fallback | pl:1913, tp:1791 (else of `hasSessionCapability` at 1786) | 675 + | `profiles:list` | preload-only | pl:867 | 676 + | `profiles:create` | preload-only | pl:876 | 677 + | `profiles:get` | preload-only | pl:885 | 678 + | `profiles:delete` | preload-only | pl:894 | 679 + | `profiles:getCurrent` | preload-only | pl:902 | 680 + | `profiles:switch` | preload-only | pl:911 | 681 + | `profiles:enableSync` | preload-only | pl:922 | 682 + | `profiles:disableSync` | preload-only | pl:931 | 683 + | `profiles:getSyncConfig` | preload-only | pl:940 | 684 + | `profiles:getPartition` | preload-only | pl:949 | 685 + | `modes:getWindowMode` | preload-only | pl:2055 | 686 + | `modes:setMajorMode` | preload-only | pl:2065 | 687 + | `modes:listModes` | preload-only | pl:2073 | 688 + | `modes:getCommandContext` | preload-only | pl:1520,1530,2082 | 689 + 690 + ##### `file-*` + `app-info` + `shell-open-path` + `default-browser-*` + `set-default-browser` + `net-fetch` (8 channels) — batch 3.7a 691 + 692 + | channel | bucket | callers | 693 + |---|---|---| 694 + | `file-save-dialog` | preload-only | pl:2264 (tp uses `tile:dialogs:save`) | 695 + | `file-open-dialog` | preload-only | pl:2276 (tp uses `tile:dialogs:open`) | 696 + | `file-read-from-path` | preload-only | pl:2285 (tp uses `tile:filesystem:read`) | 697 + | `file-write-to-path` | preload-only | pl:2295 (tp uses `tile:filesystem:write`) | 698 + | `app-info` | preload-only | pl:143 | 699 + | `shell-open-path` | preload-only | (no caller in pl/tp — likely dead) | 700 + | `default-browser-status` | preload-only | (no caller — likely dead) | 701 + | `set-default-browser` | preload-only | (no caller — likely dead) | 702 + | `net-fetch` | preload-only | pl:1927 (tp uses `tile:network:fetch`) | 703 + 704 + ##### `IPC_CHANNELS.*` (10 `ipcMain.on` channels) — batch 3.7e 705 + 706 + | channel | const value | bucket | callers | 707 + |---|---|---|---| 708 + | `RENDERER_LOG` | `renderer-log` | live-other | pl:128, tp:1868 (always-on) | 709 + | `REGISTER_SHORTCUT` | `registershortcut` | live-other | pl:163 (tp uses `tile:shortcuts:register`) | 710 + | `UNREGISTER_SHORTCUT` | `unregistershortcut` | live-other | pl:187 (tp uses `tile:shortcuts:unregister`) | 711 + | `CLOSE_WINDOW` | `closewindow` | live-other | pl:206 | 712 + | `PUBLISH` | `publish` | live-other | pl:50,82,233,1346,1357,1396,1452,1487,1555,1634,1673,1734 (tp uses `tile:pubsub:publish`) | 713 + | `SUBSCRIBE` | `subscribe` | live-other | pl:68,251,1331,1374,1478,1546,1621,1660,1721 (tp uses `tile:pubsub:subscribe`) | 714 + | `CONSOLE` | `console` | live-other | (no caller in pl/tp — likely dead) | 715 + | `APP_QUIT` | `app-quit` | live-other | pl:1288, tp:2015 (always-on) | 716 + | `APP_RESTART` | `app-restart` | live-other | pl:1292, tp:2023 (always-on) | 717 + | `MODIFY_WINDOW` | `modifywindow` | live-other | pl:508 | 718 + 719 + #### Deletion order recommendation 720 + 721 + Recommended sequencing aligns with the plan's 3.7a–e split, ordered by 722 + risk (low → high). Each batch can ship as one PR with `tsc --noEmit` 723 + + `test:unit` + `test:electron:bg` validation. 724 + 725 + 1. **3.7a — `preload-only` (79 channels) + truly dead channels.** 726 + First subdivision: delete the **31 channels** that have NO callers 727 + in either `preload.js` or `tile-preload.cts` (suspected dead). Risk: 728 + low — verifiable by grep + `tsc`. Channels: `extension-window-unload`, 729 + `extension-manifest-get`, `web-nav-back/forward/reload/state`, 730 + `backup-get-config/set-config/create/list`, `session-save/get-snapshot-info/restore/restore-interactive`, 731 + `shell-open-path`, `default-browser-status`, `set-default-browser`, 732 + `window-reopen-last-closed`, `window-animate`, `window-is-draggable`, 733 + `datastore-query-items-by-frecency`, `IPC_CHANNELS.CONSOLE`. (Pre-3.7 734 + verify they really are dead — could be invoked via `api.invoke()` 735 + passthrough in a feature tile.) 736 + Second subdivision (after preload.js is deleted in 3.8): the 737 + remaining 48 preload-only channels can be deleted in lockstep with 738 + the file. Until then, they have a live consumer. 739 + 740 + 2. **3.7b — `window-*` (~24 channels).** Mixed: 11 `fallback` channels 741 + that fall when tile-preload's else branches collapse, 9 preload-only, 742 + 3 `strict-shadow` (`opener-postmessage/close/focus`). Action: build 743 + strict shims for the 3 opener-* channels (currently `pg` calls them 744 + via `api.invoke()` passthrough), then delete the fallback else 745 + branches in `tile-preload.cts` per 3.3 pattern, then delete the 746 + `window-*` handlers in `ipc.ts`. 747 + 748 + 3. **3.7c — `datastore-*` (47 channels).** Mostly `strict-shadow` (28) 749 + — these need strict `tile:datastore:*` equivalents built first. 750 + The remaining ~12 strict equivalents already exist (rename-tag, 751 + update-tag-color, delete-tag, hard-delete-item, update-item-title, 752 + update-item-favicon, get-history, record-item-visit, get-item-visits, 753 + query-item-visits, track-navigation, query-items-by-frecency, 754 + extract-page-content, get-stats — last 3 commits already added 755 + shims). Build the missing strict shims before deleting any 756 + handler. **Trickiest batch — recommend its own pre-package.** 757 + 758 + 4. **3.7d — `extension-*` + `feature-*` + `chrome-ext:*` + `oauth-*` + 759 + `sync-*` + `context-*` + `feature-registry/install/browse:*` + 760 + `save-space-workspaces` (~50 channels).** Mix of fallbacks (clean 761 + delete after collapsing else branches) and `strict-shadow` for the 762 + trustedBuiltin-only surfaces (extensions/chromeExtensions/themes). 763 + For trustedBuiltin paths the simplest move is to add a `tile:*` 764 + alias and a trustedBuiltin enforcement check, then delete the legacy 765 + handler — same pattern used in recent `tile:datastore:*` strict 766 + commits. 767 + 768 + 5. **3.7e — `IPC_CHANNELS.*` (10 channels).** All `live-other` because 769 + pubsub/shortcut/quit are pervasively used. tile-preload already 770 + uses `tile:pubsub:*` and `tile:shortcuts:*`. The 3 channels still 771 + needed unconditionally are `RENDERER_LOG`, `APP_QUIT`, `APP_RESTART` 772 + (tile-preload calls them directly with no strict equivalent). Build 773 + `tile:app:quit` + `tile:app:restart` + `tile:log:write` shims (or 774 + just keep these 3 as the only legacy survivors, since they're tiny 775 + write-only sends that don't need capability gating). Then delete 776 + the rest of the const + handler block at `ipc.ts:4158–4476`. 777 + 778 + #### Trickier channels (need strict shim built first) 779 + 780 + These are the `tile-preload-strict-shadow` calls — tile-preload calls 781 + them unconditionally and there is no strict `tile:*` alias yet: 782 + 783 + - **`opener-postmessage` / `opener-close` / `opener-focus`** — called 784 + from `app/page/page.js` via `api.invoke()` passthrough. Strict 785 + equivalents: `tile:window:opener-postmessage` etc., gated on 786 + `window.manage` capability or a new `opener` capability. 787 + - **`extension-*` (10 channels)** — tile-preload exposes them under 788 + `api.extensions.*` for trustedBuiltin only. Need 789 + `tile:extensions:*` shims with a trustedBuiltin enforcement layer 790 + in `tile-ipc.ts`. 791 + - **`chrome-ext:*` (6 channels)** — same pattern as `extension-*`, 792 + trustedBuiltin only. Need `tile:chrome-extensions:*` shims. 793 + - **`feature-settings-schema`** — called unconditionally from both 794 + preload and tile-preload (`api.extensions.getSettingsSchema`). 795 + Could fold into `tile:features:*` namespace. 796 + - **`theme:get/setColorScheme/setWindowColorScheme/setTheme/list/getAll`** 797 + (6 channels) — tile-preload exposes them under `api.theme.*` for 798 + trustedBuiltin only. Need `tile:theme:*` strict equivalents (a 799 + `tile:theme:info` already exists, but only returns minimal data). 800 + - **`get-window-id`** — called from tile-preload at line 2050 inside 801 + the theme block. Has no strict equivalent. Either add 802 + `tile:window:get-id` or refactor the caller to use the existing 803 + `tile:window:get-focused-visible-id` flow. 804 + - **`renderer-log`, `app-quit`, `app-restart`** — see 3.7e above. 805 + 806 + The remaining ~50 strict-shadow channels (mostly `datastore-*`) are 807 + already partially shimmed (per recent commits b820dafc, 06eaacb6, 808 + 48d15aca); just need the shim-completion pass before flipping 809 + tile-preload from legacy to strict. 810 + 811 + #### Open gaps in this audit 812 + 813 + - **`api.invoke()` passthrough surface** (tile-preload.cts:1910 + 814 + preload.js:2335) lets any channel name be invoked from a renderer 815 + with `trustedBuiltin`. The "no-caller" verdict on the 31 suspected 816 + dead channels was based on grep for `'channel-name'` strings; an 817 + inline `api.invoke('window-animate', …)` from a tile would not show 818 + up. Phase 3.7a's first subdivision should re-verify by running the 819 + full electron test suite after deletion (a missing handler will 820 + surface as a runtime invoke failure). 821 + - Did not audit how tests exercise these channels — `datastore.test.ts`, 822 + `feature-registry.test.ts`, etc. all import handlers directly. Test 823 + failures during 3.7c deletion are expected and should be migrated 824 + to use the `tile:*` equivalents. 825 + 826 + --- 827 + 406 828 ### phase3.1 — Audit findings (2026-04-18) 407 829 408 830 **Conclusion: GO** — `extensionHostWindow` can be deleted in phase3.6 with