experiments in a post-browser web
10
fork

Configure Feed

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

refactor(ipc) + docs(phase3): delete 8 dead channels (Phase 3.7a) + Wave 2 re-plan

+675 -171
-171
backend/electron/ipc.ts
··· 43 43 getItemVisits, 44 44 queryItemVisits, 45 45 trackNavigation, 46 - queryItemsByFrecency, 47 46 // Item event operations (series & feeds) 48 47 addItemEvent, 49 48 getItemEvent, ··· 169 168 170 169 import { 171 170 getBackupConfig, 172 - setBackupConfig, 173 171 createBackup, 174 172 listBackups, 175 173 } from './backup.js'; ··· 935 933 } 936 934 }); 937 935 938 - ipcMain.handle('datastore-query-items-by-frecency', async (ev, data = {}) => { 939 - try { 940 - const result = queryItemsByFrecency(data.filter); 941 - return { success: true, data: result }; 942 - } catch (error) { 943 - const message = error instanceof Error ? error.message : String(error); 944 - return { success: false, error: message }; 945 - } 946 - }); 947 - 948 936 // Item event operations (series & feeds) 949 937 ipcMain.handle('datastore-add-item-event', async (ev, data) => { 950 938 try { ··· 1435 1423 `).run(`${extId}_${data.key}`, extId, data.key, jsonValue, Date.now()); 1436 1424 1437 1425 return { success: true, data: { key: data.key, value: data.value } }; 1438 - } catch (error) { 1439 - const message = error instanceof Error ? error.message : String(error); 1440 - return { success: false, error: message }; 1441 - } 1442 - }); 1443 - 1444 - ipcMain.handle('extension-manifest-get', async (ev, data) => { 1445 - try { 1446 - const extPath = getExtensionPath(data.id); 1447 - if (!extPath) { 1448 - // Check database for external extensions 1449 - const db = getDb(); 1450 - const ext = db.prepare('SELECT * FROM extensions WHERE id = ?').get(data.id) as { path?: string } | undefined; 1451 - if (!ext || !ext.path) { 1452 - return { success: false, error: `Extension ${data.id} not found` }; 1453 - } 1454 - const manifest = loadExtensionManifest(ext.path); 1455 - return { success: true, data: manifest }; 1456 - } 1457 - const manifest = loadExtensionManifest(extPath); 1458 - return { success: true, data: manifest }; 1459 1426 } catch (error) { 1460 1427 const message = error instanceof Error ? error.message : String(error); 1461 1428 return { success: false, error: message }; ··· 3858 3825 } 3859 3826 }); 3860 3827 3861 - // Animate window bounds (position and/or size) 3862 - // Animates from current bounds (or specified 'from') to target bounds over duration 3863 - ipcMain.handle('window-animate', async (ev, msg) => { 3864 - DEBUG && console.log('window-animate', msg); 3865 - 3866 - try { 3867 - let win: BrowserWindow | null = null; 3868 - if (msg?.id) { 3869 - win = BrowserWindow.fromId(msg.id); 3870 - } else { 3871 - win = BrowserWindow.fromWebContents(ev.sender); 3872 - } 3873 - 3874 - if (!win) { 3875 - return { success: false, error: 'Window not found' }; 3876 - } 3877 - 3878 - const currentBounds = win.getBounds(); 3879 - const from = msg.from || currentBounds; 3880 - const to = msg.to; 3881 - const duration = msg.duration || 150; // ms 3882 - 3883 - if (!to) { 3884 - return { success: false, error: 'Target bounds (to) are required' }; 3885 - } 3886 - 3887 - // Calculate animation parameters 3888 - const startX = from.x ?? currentBounds.x; 3889 - const startY = from.y ?? currentBounds.y; 3890 - const startW = from.width ?? currentBounds.width; 3891 - const startH = from.height ?? currentBounds.height; 3892 - 3893 - const endX = to.x ?? startX; 3894 - const endY = to.y ?? startY; 3895 - const endW = to.width ?? startW; 3896 - const endH = to.height ?? startH; 3897 - 3898 - // Animation timing 3899 - const timerInterval = 10; // ms 3900 - const numTicks = Math.max(1, Math.floor(duration / timerInterval)); 3901 - let tick = 0; 3902 - 3903 - return new Promise((resolve) => { 3904 - const timer = setInterval(() => { 3905 - tick++; 3906 - 3907 - if (tick >= numTicks || win!.isDestroyed()) { 3908 - clearInterval(timer); 3909 - // Set final bounds 3910 - if (!win!.isDestroyed()) { 3911 - win!.setBounds({ x: endX, y: endY, width: endW, height: endH }); 3912 - } 3913 - resolve({ success: true }); 3914 - return; 3915 - } 3916 - 3917 - // Calculate progress (0 to 1) 3918 - const progress = tick / numTicks; 3919 - // Use easeOutQuad for smooth deceleration 3920 - const eased = 1 - (1 - progress) * (1 - progress); 3921 - 3922 - const x = Math.round(startX + (endX - startX) * eased); 3923 - const y = Math.round(startY + (endY - startY) * eased); 3924 - const w = Math.round(startW + (endW - startW) * eased); 3925 - const h = Math.round(startH + (endH - startH) * eased); 3926 - 3927 - win!.setBounds({ x, y, width: w, height: h }); 3928 - }, timerInterval); 3929 - }); 3930 - } catch (error) { 3931 - console.error('Failed to animate window:', error); 3932 - const message = error instanceof Error ? error.message : String(error); 3933 - return { success: false, error: message }; 3934 - } 3935 - }); 3936 - 3937 3828 ipcMain.handle('get-display-info', (ev) => { 3938 3829 const win = BrowserWindow.fromWebContents(ev.sender); 3939 3830 if (!win || win.isDestroyed()) return { success: false, error: 'Window not found' }; ··· 4272 4163 } 4273 4164 }); 4274 4165 4275 - // Check if window is draggable (default: true) 4276 - ipcMain.handle('window-is-draggable', (ev) => { 4277 - try { 4278 - const win = BrowserWindow.fromWebContents(ev.sender); 4279 - if (!win) { 4280 - return { success: false, error: 'Window not found' }; 4281 - } 4282 - const winInfo = getWindowInfo(win.id); 4283 - // Default to draggable if not specified 4284 - const draggable = winInfo?.params?.draggable !== false; 4285 - return { success: true, draggable }; 4286 - } catch (error) { 4287 - const message = error instanceof Error ? error.message : String(error); 4288 - return { success: false, error: message }; 4289 - } 4290 - }); 4291 - 4292 4166 // Web navigation handlers - operate on the target http/https window 4293 4167 const resolveWebWindow = (windowId?: number): BrowserWindow | null => { 4294 4168 const id = windowId || lastFocusedVisibleWindowId; ··· 4437 4311 }); 4438 4312 }); 4439 4313 4440 - // Console log from renderer 4441 - ipcMain.on(IPC_CHANNELS.CONSOLE, (_ev, msg) => { 4442 - DEBUG && console.log('r:', msg.source, msg.text); 4443 - }); 4444 - 4445 4314 // App quit request 4446 4315 ipcMain.on(IPC_CHANNELS.APP_QUIT, (_ev, msg) => { 4447 4316 DEBUG && console.log('app-quit requested from:', msg?.source); ··· 4962 4831 try { 4963 4832 const config = getBackupConfig(); 4964 4833 return { success: true, data: config }; 4965 - } catch (error) { 4966 - const message = error instanceof Error ? error.message : String(error); 4967 - return { success: false, error: message }; 4968 - } 4969 - }); 4970 - 4971 - // Set backup configuration 4972 - ipcMain.handle('backup-set-config', async (_ev, data) => { 4973 - try { 4974 - setBackupConfig(data); 4975 - return { success: true }; 4976 4834 } catch (error) { 4977 4835 const message = error instanceof Error ? error.message : String(error); 4978 4836 return { success: false, error: message }; ··· 5667 5525 try { 5668 5526 saveSessionSnapshot('manual'); 5669 5527 return { success: true, message: 'Session saved' }; 5670 - } catch (error) { 5671 - const message = error instanceof Error ? error.message : String(error); 5672 - return { success: false, error: message }; 5673 - } 5674 - }); 5675 - 5676 - // Get snapshot info (window count) without restoring 5677 - ipcMain.handle('session-get-snapshot-info', () => { 5678 - try { 5679 - const info = getSessionSnapshotInfo(); 5680 - if (!info) return { success: true, data: null }; 5681 - return { success: true, data: info }; 5682 - } catch (error) { 5683 - const message = error instanceof Error ? error.message : String(error); 5684 - return { success: false, error: message }; 5685 - } 5686 - }); 5687 - 5688 - // Manual session restore (restores on top of existing windows) 5689 - // Skip crash dialog — user explicitly requested restore 5690 - ipcMain.handle('session-restore', async () => { 5691 - try { 5692 - const prefs = getPrefs(); 5693 - const result = await restoreSessionSnapshot(prefs, { cleanShutdown: true, crashCount: 0 }); 5694 - return { 5695 - success: true, 5696 - message: `Restored ${result.restored} of ${result.total} window(s)`, 5697 - ...result, 5698 - }; 5699 5528 } catch (error) { 5700 5529 const message = error instanceof Error ? error.message : String(error); 5701 5530 return { success: false, error: message };
+675
docs/v1-removal-phase3-tasks.md
··· 892 892 **Net test deletion:** 4 full tests + 2 partial assertions + 1 fixture 893 893 rewrite + 3 README rows. No production code coverage is lost — every 894 894 remaining behaviour is exercised by tests on the v2 tile path. 895 + 896 + --- 897 + 898 + ### Wave 2 — re-planned (2026-04-18 after 3.4 audit) 899 + 900 + The 3.4 audit revealed 193 channels (vs. 103 estimated), with **71 901 + strict-shadow channels** that need `tile:*` equivalents BEFORE the 902 + legacy handler can be deleted, plus a hidden dependency: `app/context/ 903 + *.js` calls `context-*` directly via the v1 preload, blocking 904 + `preload.js` deletion until those modules migrate. 905 + 906 + This re-plan replaces the original 3.5–3.8 packages with smaller, 907 + strictly-sized (15–25 min) work items. Every package is shippable by a 908 + Sonnet agent in one spawn. 909 + 910 + #### Re-planned dependency graph 911 + 912 + ``` 913 + Wave 2a — strict shim builders (parallel, no inter-deps) 914 + 3.5a opener-* shims (3 ch) 915 + 3.5b theme:* shims (6 ch) 916 + 3.5c extension-* shims (10 ch) 917 + 3.5d chrome-ext:* shims (6 ch) 918 + 3.5e feature-settings-schema + 919 + get-window-id shims (2 ch) 920 + 3.5f app-control shims 921 + (renderer-log, app-quit, 922 + app-restart) (3 ch) 923 + 3.5g datastore-* shim completion 924 + (28 ch verify + fill) 925 + | 926 + +-- (each above unblocks one tile-preload route flip) 927 + 928 + Wave 2b — flip tile-preload to strict (after matching 2a shim) 929 + 3.6a flip opener-* (after 3.5a) 930 + 3.6b flip theme:* (after 3.5b) 931 + 3.6c flip extension-* (after 3.5c) 932 + 3.6d flip chrome-ext:* (after 3.5d) 933 + 3.6e flip feature-settings-schema 934 + + get-window-id (after 3.5e) 935 + 3.6f flip renderer-log/quit/ 936 + restart (after 3.5f) 937 + 3.6g flip datastore-* (28 ch) (after 3.5g) 938 + 939 + Wave 2c — collapse fallback else-branches (after corresponding flip) 940 + 3.7a collapse window-* fallbacks (11 ch, no deps) 941 + 3.7b collapse feature-registry/ 942 + install/browse fallbacks (10 ch, after 3.6e) 943 + 3.7c collapse context-* fallbacks 944 + (6 ch, after Wave 2d-context) 945 + 3.7d collapse oauth + sync-full 946 + + screen + save-space-ws + 947 + window-set-visible-on-all- 948 + workspaces (5 ch) 949 + 950 + Wave 2d — context module migration (gates 3.7c + preload.js delete) 951 + 3.8a audit app/context/*.js call 952 + surface 953 + 3.8b port context modules to use 954 + tile-preload api.context.* 955 + (or wrap as a tile) 956 + 957 + Wave 2e — getRunning/getAllRegistered rewrite (was 3.5) 958 + 3.9 rewrite for v2 tiles (after 3.1 → already GO) 959 + 960 + Wave 2f — extensionHostWindow delete (was 3.6) 961 + 3.10 delete host window + iframe 962 + loader + INTERNAL_URLS + 963 + smoke tests + fixture 964 + (after 3.9) 965 + 966 + Wave 2g — preload.js + IPC_CHANNELS deletion (was 3.7e/3.8) 967 + 3.11a delete suspected-dead 968 + channels (~31 ch, no 969 + callers grep'd) 970 + 3.11b delete remaining preload- 971 + only handlers (~48 ch) + 972 + delete preload.js file 973 + (after Wave 2b + 2c + 2d + 2f) 974 + 3.11c delete IPC_CHANNELS const + 975 + remaining .on handlers 976 + (after 3.11b) 977 + ``` 978 + 979 + #### Parallelism summary 980 + 981 + - **Wave 2a (3.5a–g):** all 7 packages independent. Run in parallel 982 + with isolated workspaces. 983 + - **Wave 2b (3.6a–g):** each gated on its 2a sibling. Within 2b the 984 + flips are independent (different domains in tile-preload.cts). 985 + - **Wave 2c (3.7a–d):** mostly independent of each other; 3.7b waits 986 + on 3.6e, 3.7c waits on Wave 2d. 987 + - **Wave 2d (3.8a–b):** sequential (audit → port). 988 + - **Wave 2e (3.9):** independent of everything in Wave 2 — can run 989 + alongside any other wave. Already cleared by 3.1 audit. 990 + - **Wave 2f (3.10):** depends on 3.9. 991 + - **Wave 2g (3.11):** the final lockstep deletion. 3.11a can run 992 + earlier as a low-risk isolated package. 993 + 994 + --- 995 + 996 + #### 3.5a — Build `tile:window:opener-*` strict shims 997 + 998 + **Scope:** Add `tile:window:opener-postmessage`, `tile:window:opener- 999 + close`, `tile:window:opener-focus` handlers in `tile-ipc.ts`. Mirror 1000 + existing `opener-*` semantics from `ipc.ts`. Gate on `window.manage` 1001 + capability (or new `opener` cap if more appropriate — pick the simpler 1002 + one). 1003 + 1004 + **Files:** `backend/electron/tile-ipc.ts`, `backend/electron/tile- 1005 + preload.cts` (add wrapper methods only — DO NOT remove legacy invoke 1006 + yet; that's 3.6a). 1007 + 1008 + **Dependencies:** none 1009 + 1010 + **Budget:** 20 min 1011 + 1012 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1013 + `yarn test:electron:bg -g "page"` (page widget exercises opener-*). 1014 + 1015 + **Risk:** low — additive only. 1016 + 1017 + --- 1018 + 1019 + #### 3.5b — Build `tile:theme:*` strict shims (6 channels) 1020 + 1021 + **Scope:** Add `tile:theme:get`, `tile:theme:setColorScheme`, 1022 + `tile:theme:setWindowColorScheme`, `tile:theme:setTheme`, 1023 + `tile:theme:list`, `tile:theme:getAll` handlers in `tile-ipc.ts`. All 1024 + require trustedBuiltin enforcement (theme handlers should refuse 1025 + non-trusted callers — pattern from existing `tile:datastore:*` 1026 + trustedBuiltin sites in recent commits). 1027 + 1028 + **Files:** `backend/electron/tile-ipc.ts`, `backend/electron/tile- 1029 + preload.cts` (add wrappers, additive). 1030 + 1031 + **Dependencies:** none 1032 + 1033 + **Budget:** 20 min 1034 + 1035 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1036 + `yarn test:electron:bg -g "theme"`. 1037 + 1038 + **Risk:** low — additive; trustedBuiltin gating is well-tested. 1039 + 1040 + --- 1041 + 1042 + #### 3.5c — Build `tile:extensions:*` strict shims (10 channels) 1043 + 1044 + **Scope:** Add `tile:extensions:pickFolder`, `validateFolder`, `add`, 1045 + `remove`, `update`, `getAll`, `get`, `windowList`, 1046 + `listAllRegistered`, `windowDevtools`, `reload` (10 total). 1047 + TrustedBuiltin enforcement. 1048 + 1049 + **Files:** `backend/electron/tile-ipc.ts`, `backend/electron/tile- 1050 + preload.cts` (add wrappers). 1051 + 1052 + **Dependencies:** none 1053 + 1054 + **Budget:** 25 min (10 channels — split if it overruns 25) 1055 + 1056 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1057 + `yarn test:electron:bg -g "extension"` (or features pane). 1058 + 1059 + **Risk:** low — additive. 1060 + 1061 + --- 1062 + 1063 + #### 3.5d — Build `tile:chrome-extensions:*` strict shims (6 channels) 1064 + 1065 + **Scope:** Add `tile:chrome-extensions:list`, `enable`, `disable`, 1066 + `getStatus`, `getUiEntries`, `openPage`. TrustedBuiltin enforcement. 1067 + 1068 + **Files:** `backend/electron/tile-ipc.ts`, `backend/electron/tile- 1069 + preload.cts`. 1070 + 1071 + **Dependencies:** none 1072 + 1073 + **Budget:** 20 min 1074 + 1075 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1076 + `yarn test:electron:bg -g "chrome-ext"`. 1077 + 1078 + **Risk:** low — additive. 1079 + 1080 + --- 1081 + 1082 + #### 3.5e — Build `tile:features:settings-schema` + `tile:window:get-id` shims 1083 + 1084 + **Scope:** Two unrelated small shims grouped because each is too small 1085 + for its own package. Add `tile:features:settings-schema` (mirror of 1086 + `feature-settings-schema`) and `tile:window:get-id` (mirror of 1087 + `get-window-id`). Both used unconditionally in tile-preload. 1088 + 1089 + **Files:** `backend/electron/tile-ipc.ts`, `backend/electron/tile- 1090 + preload.cts`. 1091 + 1092 + **Dependencies:** none 1093 + 1094 + **Budget:** 15 min 1095 + 1096 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1097 + `yarn test:electron:bg`. 1098 + 1099 + **Risk:** low — additive. 1100 + 1101 + --- 1102 + 1103 + #### 3.5f — Build `tile:app:*` shims (renderer-log, quit, restart) 1104 + 1105 + **Scope:** Add `tile:log:write` (= renderer-log), `tile:app:quit`, 1106 + `tile:app:restart` shims. These are write-only sends (no return). May 1107 + opt to keep the legacy `ipcMain.on` handlers permanently as the only 1108 + survivors instead — decide based on capability story (per 3.7e 1109 + recommendation in audit). 1110 + 1111 + **Files:** `backend/electron/tile-ipc.ts`, `backend/electron/tile- 1112 + preload.cts`. 1113 + 1114 + **Dependencies:** none 1115 + 1116 + **Budget:** 15 min 1117 + 1118 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1119 + `yarn test:electron:bg -g "smoke"`. 1120 + 1121 + **Risk:** low — additive. 1122 + 1123 + --- 1124 + 1125 + #### 3.5g — Datastore strict-shim completion sweep (28 channels) 1126 + 1127 + **Scope:** Verify every `datastore-*` strict-shadow channel listed in 1128 + the 3.4 audit table has a working `tile:datastore:*` equivalent. The 1129 + last 3 commits (b820dafc, 06eaacb6, 48d15aca) added many; this package 1130 + is the "fill the gaps" pass. For each missing, add a strict shim with 1131 + permissive datastore-capability gating (existing pattern). 1132 + 1133 + **Files:** `backend/electron/tile-ipc.ts`, `backend/electron/tile- 1134 + preload.cts`. Reference: 3.4 audit table for the 28 strict-shadow 1135 + datastore channels. 1136 + 1137 + **Dependencies:** none 1138 + 1139 + **Budget:** 25 min (split into 3.5g1/g2 by channel count if over) 1140 + 1141 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1142 + `yarn test:electron:bg -g "datastore"`. 1143 + 1144 + **Risk:** low–medium — additive but high channel count = high test 1145 + surface. 1146 + 1147 + --- 1148 + 1149 + #### 3.6a — Flip tile-preload `opener-*` to strict + delete legacy handlers 1150 + 1151 + **Scope:** Replace the 3 `ipcRenderer.invoke('opener-*', …)` calls in 1152 + tile-preload with `tile:window:opener-*`. Then delete the 3 1153 + `ipcMain.handle('opener-*')` blocks in `ipc.ts`. 1154 + 1155 + **Files:** `backend/electron/tile-preload.cts` (lines around 1112, 1156 + 1122, 1129 in `app/page/page.js` consumer paths), `backend/electron/ 1157 + ipc.ts`. 1158 + 1159 + **Dependencies:** 3.5a 1160 + 1161 + **Budget:** 15 min 1162 + 1163 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1164 + `yarn test:electron:bg -g "page"`. 1165 + 1166 + **Risk:** low. 1167 + 1168 + --- 1169 + 1170 + #### 3.6b — Flip tile-preload `theme:*` to strict + delete legacy handlers 1171 + 1172 + **Scope:** Replace 6 unconditional `ipcRenderer.invoke('theme:*', …)` 1173 + sites in tile-preload with strict equivalents. Delete the 6 legacy 1174 + handlers. 1175 + 1176 + **Files:** `backend/electron/tile-preload.cts` (lines 2036–2055), 1177 + `backend/electron/ipc.ts`. 1178 + 1179 + **Dependencies:** 3.5b 1180 + 1181 + **Budget:** 20 min 1182 + 1183 + **Validation:** `yarn tsc --noEmit`, `yarn test:electron:bg -g "theme"`. 1184 + 1185 + **Risk:** low — straight rename of channel string + handler delete. 1186 + 1187 + --- 1188 + 1189 + #### 3.6c — Flip tile-preload `extension-*` to strict + delete legacy handlers 1190 + 1191 + **Scope:** Replace 10 `ipcRenderer.invoke('extension-*', …)` sites 1192 + (1933–1993). Delete legacy handlers in `ipc.ts`. 1193 + 1194 + **Files:** `backend/electron/tile-preload.cts`, `backend/electron/ 1195 + ipc.ts`. 1196 + 1197 + **Dependencies:** 3.5c 1198 + 1199 + **Budget:** 20 min 1200 + 1201 + **Validation:** `yarn tsc --noEmit`, `yarn test:electron:bg 1202 + -g "extension"`. 1203 + 1204 + **Risk:** medium — extension management touches Features pane. 1205 + 1206 + --- 1207 + 1208 + #### 3.6d — Flip tile-preload `chrome-ext:*` to strict + delete legacy handlers 1209 + 1210 + **Scope:** Replace 6 sites (2071–2101). Delete legacy handlers. 1211 + 1212 + **Files:** `backend/electron/tile-preload.cts`, `backend/electron/ 1213 + ipc.ts`. 1214 + 1215 + **Dependencies:** 3.5d 1216 + 1217 + **Budget:** 20 min 1218 + 1219 + **Validation:** `yarn tsc --noEmit`, `yarn test:electron:bg 1220 + -g "chrome-ext"`. 1221 + 1222 + **Risk:** low. 1223 + 1224 + --- 1225 + 1226 + #### 3.6e — Flip `feature-settings-schema` + `get-window-id` to strict + delete 1227 + 1228 + **Scope:** Replace tile-preload calls + delete handlers. 1229 + 1230 + **Files:** `backend/electron/tile-preload.cts`, `backend/electron/ 1231 + ipc.ts`. 1232 + 1233 + **Dependencies:** 3.5e 1234 + 1235 + **Budget:** 15 min 1236 + 1237 + **Validation:** `yarn tsc --noEmit`, `yarn test:electron:bg`. 1238 + 1239 + **Risk:** low. 1240 + 1241 + --- 1242 + 1243 + #### 3.6f — Flip `renderer-log`/`app-quit`/`app-restart` to strict (or keep as survivors) 1244 + 1245 + **Scope:** Either flip to `tile:*` (per 3.5f shims) and delete legacy 1246 + `.on` handlers, OR explicitly choose to keep the 3 as legacy-permanent 1247 + survivors. Document the decision inline in `ipc.ts`. 1248 + 1249 + **Files:** `backend/electron/tile-preload.cts`, `backend/electron/ 1250 + ipc.ts`. 1251 + 1252 + **Dependencies:** 3.5f 1253 + 1254 + **Budget:** 15 min 1255 + 1256 + **Validation:** `yarn tsc --noEmit`, `yarn test:electron:bg -g "smoke"`. 1257 + 1258 + **Risk:** low. 1259 + 1260 + --- 1261 + 1262 + #### 3.6g — Flip tile-preload `datastore-*` to strict + delete legacy handlers 1263 + 1264 + **Scope:** Replace 28 unconditional `ipcRenderer.invoke('datastore-*', 1265 + …)` sites in tile-preload (lines 861–955) with `tile:datastore:*` 1266 + equivalents. Delete the matching 28 legacy handlers in `ipc.ts`. 1267 + 1268 + **Files:** `backend/electron/tile-preload.cts`, `backend/electron/ 1269 + ipc.ts`. 1270 + 1271 + **Dependencies:** 3.5g (shims must exist first) 1272 + 1273 + **Budget:** 25 min — split into 3.6g1 (first 14) + 3.6g2 (last 14) 1274 + if it overruns. 1275 + 1276 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1277 + `yarn test:electron:bg -g "datastore"`, 1278 + `yarn test:electron:bg -g "items"`, 1279 + `yarn test:electron:bg -g "tags"`. 1280 + 1281 + **Risk:** medium-high — datastore is product-critical. Split if 1282 + unsure. 1283 + 1284 + --- 1285 + 1286 + #### 3.7a — Collapse `window-*` fallback else-branches (11 channels) 1287 + 1288 + **Scope:** Convert the 11 `window-*` `if (!hasWindowCapability()) 1289 + return ipcRenderer.invoke('window-*', …)` else branches in tile-preload 1290 + to throw + delete. Mirror the 3.3 izui pattern. Delete the legacy 1291 + `window-*` handlers in `ipc.ts` for the 11 affected channels. 1292 + 1293 + **Files:** `backend/electron/tile-preload.cts` (lines 514, 541, 570, 1294 + 585, 600, 615, 636, 654, 668, 682, 696, 710, 726, 749), `backend/ 1295 + electron/ipc.ts`. 1296 + 1297 + **Dependencies:** none 1298 + 1299 + **Budget:** 25 min 1300 + 1301 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1302 + `yarn test:electron:bg -g "window"`. 1303 + 1304 + **Risk:** medium — many call sites; if any v2 tile is silently relying 1305 + on the fallback (cap not declared), it'll throw. Pre-audit by grep 1306 + through all tile manifests for `window` capability. 1307 + 1308 + --- 1309 + 1310 + #### 3.7b — Collapse `feature-registry/install/browse:*` fallbacks (10 channels) 1311 + 1312 + **Scope:** Convert the 10 `feature-*` else branches in tile-preload 1313 + (lines 1448–1470) to throw + delete fallback. Delete the 10 legacy 1314 + handlers in `ipc.ts`. 1315 + 1316 + **Files:** `backend/electron/tile-preload.cts`, `backend/electron/ 1317 + ipc.ts`. 1318 + 1319 + **Dependencies:** 3.6e (clears related surfaces) 1320 + 1321 + **Budget:** 20 min 1322 + 1323 + **Validation:** `yarn tsc --noEmit`, `yarn test:electron:bg 1324 + -g "feature"`. 1325 + 1326 + **Risk:** medium — features pane. 1327 + 1328 + --- 1329 + 1330 + #### 3.7c — Collapse `context-*` fallbacks (6 channels) 1331 + 1332 + **Scope:** Convert the 6 `context-*` else branches in tile-preload 1333 + (lines 1287–1297) to throw + delete fallback. Delete the 6 legacy 1334 + handlers in `ipc.ts`. 1335 + 1336 + **Files:** `backend/electron/tile-preload.cts`, `backend/electron/ 1337 + ipc.ts`. 1338 + 1339 + **Dependencies:** 3.8b (`app/context/*.js` must already use 1340 + tile-preload — otherwise they break). 1341 + 1342 + **Budget:** 15 min 1343 + 1344 + **Validation:** `yarn tsc --noEmit`, `yarn test:electron:bg 1345 + -g "context"`, `yarn test:electron:bg -g "history"`. 1346 + 1347 + **Risk:** medium — context is used by cmd, history, idx. 1348 + 1349 + --- 1350 + 1351 + #### 3.7d — Collapse misc fallbacks (oauth, sync-full, screen, save-space-workspaces, set-visible-on-all-workspaces) — 5 channels 1352 + 1353 + **Scope:** Convert 5 misc fallback branches and delete the 5 legacy 1354 + handlers. Per-channel sites: 1355 + - `oauth-start-loopback`/`oauth-await-callback`: tp:1690, 1692 1356 + - `sync-full`: tp:1744 1357 + - `screen-get-primary-display`: tp:1769 1358 + - `save-space-workspaces`: tp:1791 1359 + - `window-set-visible-on-all-workspaces`: tp:749 1360 + 1361 + **Files:** `backend/electron/tile-preload.cts`, `backend/electron/ 1362 + ipc.ts`. 1363 + 1364 + **Dependencies:** none 1365 + 1366 + **Budget:** 20 min 1367 + 1368 + **Validation:** `yarn tsc --noEmit`, `yarn test:electron:bg`. 1369 + 1370 + **Risk:** medium — oauth flows are infrequently exercised in tests. 1371 + 1372 + --- 1373 + 1374 + #### 3.8a — Audit `app/context/*.js` call surface 1375 + 1376 + **Scope:** Read-only. Enumerate every direct `window.app.invoke( 1377 + 'context-*')` (or equivalent) call in `app/context/*.js`. List the 1378 + modules' entry points and which renderers load them. Determine the 1379 + migration path: (i) move into a tile with `context` capability, (ii) 1380 + load via tile-preload across the board, or (iii) inline the calls into 1381 + each consumer renderer that already has a tile-preload. Output a short 1382 + recommendation appended to this doc. 1383 + 1384 + **Files (read-only):** `app/context/*.js`, plus grep for 1385 + `context-get`, `context-set`, `context-history`, `context-snapshot`, 1386 + `context-windows-*` across `app/**`. 1387 + 1388 + **Dependencies:** none 1389 + 1390 + **Budget:** 20 min 1391 + 1392 + **Validation:** none (audit). Commit the audit append. 1393 + 1394 + **Risk:** low — read-only. 1395 + 1396 + --- 1397 + 1398 + #### 3.8b — Port `app/context/*.js` to use tile-preload `api.context.*` 1399 + 1400 + **Scope:** Per 3.8a recommendation, refactor every direct `context-*` 1401 + invoke in `app/context/*.js` to use `api.context.*` (tile-preload's 1402 + strict surface). Verify load contexts already use tile-preload (cmd, 1403 + hud, page glue all do). For any consumer still on v1 preload, defer to 1404 + 3.11b. 1405 + 1406 + **Files:** `app/context/*.js` (all), plus consumers per 3.8a output. 1407 + 1408 + **Dependencies:** 3.8a 1409 + 1410 + **Budget:** 25 min — split into 3.8b1/b2 by file count if over. 1411 + 1412 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1413 + `yarn test:electron:bg -g "context"`, 1414 + `yarn test:electron:bg -g "history"`. 1415 + 1416 + **Risk:** medium — context drives history widget + cmd context. 1417 + 1418 + --- 1419 + 1420 + #### 3.9 — Rewrite `getRunningExtensions` / `getAllRegisteredExtensions` for v2 1421 + 1422 + **Scope:** (Was 3.5 in original plan.) Rewrite both functions to use 1423 + `getLoadedTileIds()` + `declarativeExtensions` + a hardcoded core- 1424 + renderer ids list (cmd/hud/page). Delete `isConsolidatedExtension()` 1425 + and the `loadedConsolidatedExtensions.add(...)` calls. Verify Features 1426 + pane. 1427 + 1428 + **Files:** `backend/electron/main.ts` (lines 89, 97, 1214, 1225, 1237, 1429 + 1313–1373, 1380–1413, 1462–1464), `backend/electron/ipc.ts:100, 1300, 1430 + 1310, 1323`, `backend/electron/index.ts` (if exported). 1431 + 1432 + **Dependencies:** 3.1 (cleared — GO). 1433 + 1434 + **Budget:** 25 min 1435 + 1436 + **Validation:** `yarn tsc --noEmit`, `yarn test:electron:bg 1437 + -g "features"`, manual smoke of Settings > Features. 1438 + 1439 + **Risk:** medium — Features pane is user-facing. 1440 + 1441 + --- 1442 + 1443 + #### 3.10 — Delete `extensionHostWindow` + host HTML + broadcaster v1 branch 1444 + 1445 + **Scope:** (Was 3.6 in original plan.) Per 3.1 audit's option (a): 1446 + delete the host window, the iframe loader, INTERNAL_URLS reference, 1447 + the broadcaster v1 branch in main.ts:226–244, the `app/extension- 1448 + host.html` file. Update smoke tests per 3.1's per-site recommendations 1449 + (delete tests 4174-4179, 4181-4217, 5291-5371, 5373-5433; trim 1450 + 1380-1386 and 4359-4366; rewrite fixture helper at desktop-app.ts: 1451 + 222-244; trim README.md:140-141 + 148). 1452 + 1453 + **Files:** `backend/electron/main.ts`, `backend/electron/ipc.ts:2306`, 1454 + `app/extension-host.html` (delete), smoke.spec.ts, fixtures, README. 1455 + 1456 + **Dependencies:** 3.9 1457 + 1458 + **Budget:** 25 min — split into 3.10a (source delete) + 3.10b (test + 1459 + fixture rewrite) if over. 1460 + 1461 + **Validation:** `yarn tsc --noEmit`, `yarn test:electron:bg -g "smoke"`, 1462 + `yarn test:electron:bg` (full). 1463 + 1464 + **Risk:** high — biggest single delete. 3.1 audit is the safety net. 1465 + 1466 + --- 1467 + 1468 + #### 3.11a — Delete suspected-dead channels (~31 channels) 1469 + 1470 + **Scope:** Delete the 31 channels with NO callers in `preload.js` or 1471 + `tile-preload.cts` (per 3.4 audit). List: `extension-window-unload`, 1472 + `extension-manifest-get`, `web-nav-back/forward/reload/state`, 1473 + `backup-get-config/set-config/create/list`, `session-save/get-snapshot 1474 + -info/restore/restore-interactive`, `shell-open-path`, `default- 1475 + browser-status`, `set-default-browser`, `window-reopen-last-closed`, 1476 + `window-animate`, `window-is-draggable`, `datastore-query-items-by- 1477 + frecency`, `IPC_CHANNELS.CONSOLE`. Pre-delete: re-verify via grep for 1478 + `api.invoke('<name>')` passthrough usage. 1479 + 1480 + **Files:** `backend/electron/ipc.ts`, `backend/config.ts` (CONSOLE). 1481 + 1482 + **Dependencies:** none — independent low-risk package, runnable any 1483 + time after 3.4. 1484 + 1485 + **Budget:** 20 min 1486 + 1487 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1488 + `yarn test:electron:bg`. 1489 + 1490 + **Risk:** low–medium. Risk hinges on the `api.invoke()` passthrough 1491 + gap (per 3.4 "Open gaps"). Full electron suite catches any runtime 1492 + miss. 1493 + 1494 + --- 1495 + 1496 + #### 3.11b — Delete remaining preload-only handlers + delete `preload.js` file 1497 + 1498 + **Scope:** With Wave 2b + 2c + 2d complete, the remaining ~48 preload- 1499 + only handlers in `ipc.ts` have no live consumers. Delete them all. 1500 + Then delete `preload.js`. Remove `setPreloadPath`/`getPreloadPath` 1501 + from `backend/electron/config.ts`, `entry.ts`, `index.ts`, and the 1502 + window-open fallback at `ipc.ts:2268,2670`. Update `will-attach- 1503 + webview` in `entry.ts:189-211` to drop the v1 fallback branch. 1504 + 1505 + **Files:** `backend/electron/ipc.ts` (~48 handler blocks), `preload.js` 1506 + (delete 2693 LOC), `backend/electron/entry.ts` (lines 63, 152, 157, 1507 + 189–211), `backend/electron/config.ts`, `backend/electron/index.ts`. 1508 + 1509 + **Dependencies:** Wave 2b (3.6a–g) + Wave 2c (3.7a–d) + Wave 2d 1510 + (3.8b) + Wave 2f (3.10) all complete. 1511 + 1512 + **Budget:** 25 min — if it overruns, split into 3.11b1 (delete handler 1513 + blocks only) + 3.11b2 (delete preload.js + update glue). 1514 + 1515 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1516 + `yarn test:electron:bg` (full), 1517 + `git grep preload.js` → only Tauri-local + docs, 1518 + `git grep "preload-only"` → zero in source. 1519 + 1520 + **Risk:** high — final delete. Mitigations: 2b/2c/2d should have 1521 + caught all live consumers; tsc + full electron suite is the final net. 1522 + 1523 + --- 1524 + 1525 + #### 3.11c — Delete `IPC_CHANNELS` const + remaining `.on` handlers 1526 + 1527 + **Scope:** Delete `IPC_CHANNELS` const from `backend/config.ts:16-27`. 1528 + Update imports in `backend/electron/ipc.ts:121`, `backend/electron/ 1529 + index.ts:13`, `backend/electron/config.ts:14`. Delete the 1530 + `IPC_CHANNELS.SUBSCRIBE/PUBLISH/CLOSE_WINDOW/REGISTER_SHORTCUT/ 1531 + UNREGISTER_SHORTCUT/MODIFY_WINDOW/RENDERER_LOG/APP_QUIT/APP_RESTART/ 1532 + CONSOLE` handler blocks at `ipc.ts:4158-4476`. 1533 + 1534 + **Files:** `backend/config.ts`, `backend/electron/config.ts`, `backend/ 1535 + electron/index.ts`, `backend/electron/ipc.ts`. 1536 + 1537 + **Dependencies:** 3.11b. Also requires 3.6f decision (if `renderer- 1538 + log`/`app-quit`/`app-restart` are kept as legacy survivors, they stay; 1539 + otherwise delete here). 1540 + 1541 + **Budget:** 15 min 1542 + 1543 + **Validation:** `yarn tsc --noEmit`, `yarn test:unit`, 1544 + `yarn test:electron:bg` (full), 1545 + `git grep "IPC_CHANNELS\b"` → zero matches. 1546 + 1547 + **Risk:** medium. Final cleanup. 1548 + 1549 + --- 1550 + 1551 + #### Risk-aware execution order (TL;DR for dispatchers) 1552 + 1553 + 1. Spawn 3.5a–g and 3.11a in parallel (8 packages, all low-risk 1554 + additive or deletion-of-truly-dead). 1555 + 2. Spawn 3.8a (audit) in parallel with the above. 1556 + 3. As each 3.5x lands, spawn matching 3.6x. 1557 + 4. Spawn 3.9 anytime in parallel. 1558 + 5. After 3.5g + 3.6g land: spawn 3.7a (window cleanup). 1559 + 6. After 3.8a lands: spawn 3.8b. 1560 + 7. After 3.6e lands: spawn 3.7b. 1561 + 8. After 3.8b lands: spawn 3.7c. 1562 + 9. Spawn 3.7d anytime (independent). 1563 + 10. After 3.9 lands: spawn 3.10. 1564 + 11. After Wave 2b + 2c + 2d + 2f all complete: spawn 3.11b. 1565 + 12. After 3.11b lands: spawn 3.11c. 1566 + 1567 + Each agent follows the 25-min ceiling; if any package overruns, 1568 + commit-what-you-have + report blocker. Splits noted inline (3.6g, 3.8b, 1569 + 3.10, 3.11b) are pre-authorised.