experiments in a post-browser web
10
fork

Configure Feed

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

refactor(tile-preload,ipc): collapse misc fallbacks + delete legacy (Phase 3.7d)

All 6 channels were already backed by strict tile:* handlers in tile-ipc.ts;
no new shims needed. Collapsed fallback branches to strict-only in
tile-preload.cts and deleted legacy ipcMain.handle blocks from ipc.ts:

- tile:window:set-visible-on-all-workspaces (was window-set-visible-on-all-workspaces)
- tile:oauth:start-loopback / tile:oauth:await-callback (were oauth-start-loopback / oauth-await-callback)
- tile:sync:sync-all (was sync-full)
- tile:screen:get-primary-display (was screen-get-primary-display)
- tile:session:save-space-workspaces (was save-space-workspaces)

Also removed now-unused createLoopbackServer import from ipc.ts.
Pre-audit confirmed zero legacy callers in app/ or features/.

+40 -172
-92
backend/electron/ipc.ts
··· 8 8 import { ipcMain, nativeTheme, dialog, BrowserWindow, app, screen, shell, webContents, net, Menu, clipboard } from 'electron'; 9 9 import fs from 'node:fs'; 10 10 import path from 'node:path'; 11 - import { createLoopbackServer } from './oauth-loopback.js'; 12 11 13 12 import { 14 13 // Datastore operations ··· 2939 2938 return { success: true }; 2940 2939 }); 2941 2940 2942 - // Set visible on all workspaces (macOS Spaces) 2943 - ipcMain.handle('window-set-visible-on-all-workspaces', (ev, msg) => { 2944 - const win = msg?.id ? BrowserWindow.fromId(msg.id) : BrowserWindow.fromWebContents(ev.sender); 2945 - if (!win || win.isDestroyed()) return { success: false }; 2946 - win.setVisibleOnAllWorkspaces(msg.visible, msg.options || {}); 2947 - return { success: true }; 2948 - }); 2949 - 2950 - // Get primary display info 2951 - ipcMain.handle('screen-get-primary-display', () => { 2952 - const display = screen.getPrimaryDisplay(); 2953 - return { 2954 - success: true, 2955 - data: { 2956 - bounds: display.bounds, 2957 - workArea: display.workArea, 2958 - scaleFactor: display.scaleFactor, 2959 - size: display.size, 2960 - } 2961 - }; 2962 - }); 2963 2941 2964 2942 // Return full bounds (position + size) for any window 2965 2943 ipcMain.handle('window-get-bounds', (ev, msg) => { ··· 4095 4073 } 4096 4074 }); 4097 4075 4098 - // ============================================================================ 4099 - // OAuth Loopback Server 4100 - // ============================================================================ 4101 - 4102 - const pendingOAuthFlows = new Map<number, { waitForCallback: () => Promise<{ params: Record<string, string> }>; cancel: () => void }>(); 4103 - 4104 - ipcMain.handle('oauth-start-loopback', async (_ev, options?: { callbackPath?: string; timeoutMs?: number }) => { 4105 - try { 4106 - const server = await createLoopbackServer(options); 4107 - pendingOAuthFlows.set(server.port, { waitForCallback: server.waitForCallback, cancel: server.cancel }); 4108 - return { success: true, port: server.port }; 4109 - } catch (error) { 4110 - const message = error instanceof Error ? error.message : String(error); 4111 - return { success: false, error: message }; 4112 - } 4113 - }); 4114 - 4115 - ipcMain.handle('oauth-await-callback', async (_ev, data: { port: number }) => { 4116 - try { 4117 - const flow = pendingOAuthFlows.get(data.port); 4118 - if (!flow) { 4119 - return { success: false, error: 'No pending OAuth flow for this port' }; 4120 - } 4121 - const result = await flow.waitForCallback(); 4122 - pendingOAuthFlows.delete(data.port); 4123 - return { success: true, params: result.params }; 4124 - } catch (error) { 4125 - pendingOAuthFlows.delete(data.port); 4126 - const message = error instanceof Error ? error.message : String(error); 4127 - return { success: false, error: message }; 4128 - } 4129 - }); 4130 4076 } 4131 4077 4132 4078 /** ··· 4229 4175 // Update lastSyncTime after successful push 4230 4176 const activeProfile = getActiveProfile(); 4231 4177 updateLastSyncTime(activeProfile.id, syncTime); 4232 - 4233 - return { success: true, data: result }; 4234 - } catch (error) { 4235 - const message = error instanceof Error ? error.message : String(error); 4236 - return { success: false, error: message }; 4237 - } 4238 - }); 4239 - 4240 - // Full bidirectional sync 4241 - ipcMain.handle('sync-full', async () => { 4242 - try { 4243 - const config = getSyncConfig(); 4244 - if (!config.serverUrl || !config.apiKey) { 4245 - return { success: false, error: 'Sync not configured. Set serverUrl and apiKey first.' }; 4246 - } 4247 - 4248 - const result = await syncAll(config.serverUrl, config.apiKey); 4249 - 4250 - // Notify all windows that sync pulled new data so they can refresh 4251 - if (result.pulled > 0) { 4252 - publish('system', PubSubScopes.GLOBAL, 'sync:pull-completed', { 4253 - pulled: result.pulled, 4254 - pushed: result.pushed, 4255 - conflicts: result.conflicts 4256 - }); 4257 - } 4258 4178 4259 4179 return { success: true, data: result }; 4260 4180 } catch (error) { ··· 4878 4798 cancelled: false, 4879 4799 ...result, 4880 4800 }; 4881 - } catch (error) { 4882 - const message = error instanceof Error ? error.message : String(error); 4883 - return { success: false, error: message }; 4884 - } 4885 - }); 4886 - 4887 - // Save space workspace layouts on demand (called from groups extension) 4888 - ipcMain.handle('save-space-workspaces', async () => { 4889 - try { 4890 - const { saveSpaceWorkspaces } = await import('./session.js'); 4891 - saveSpaceWorkspaces(); 4892 - return { success: true }; 4893 4801 } catch (error) { 4894 4802 const message = error instanceof Error ? error.message : String(error); 4895 4803 return { success: false, error: message };
+40 -80
backend/electron/tile-preload.cts
··· 729 729 }, 730 730 731 731 /** 732 - * Pin a window across macOS Spaces. Strict path requires 733 - * `window.manage`; otherwise falls back to legacy 734 - * `window-set-visible-on-all-workspaces`. 732 + * Pin a window across macOS Spaces. Requires the `window` capability. 735 733 */ 736 734 setVisibleOnAllWorkspaces: ( 737 735 id: number | null | undefined, 738 736 visible: boolean, 739 737 options?: { visibleOnFullScreen?: boolean; skipTransformProcessType?: boolean }, 740 738 ) => { 741 - if (hasWindowCapability()) { 742 - return ipcRenderer.invoke('tile:window:set-visible-on-all-workspaces', { 743 - token: tileToken, 744 - id: typeof id === 'number' ? id : undefined, 745 - visible, 746 - options, 747 - }); 739 + if (!hasWindowCapability()) { 740 + return Promise.reject(new Error('[tile-preload] setVisibleOnAllWorkspaces requires the window capability')); 748 741 } 749 - return ipcRenderer.invoke('window-set-visible-on-all-workspaces', { 742 + return ipcRenderer.invoke('tile:window:set-visible-on-all-workspaces', { 743 + token: tileToken, 750 744 id: typeof id === 'number' ? id : undefined, 751 745 visible, 752 - options: options || {}, 746 + options, 753 747 }); 754 748 }, 755 749 ··· 1725 1719 1726 1720 // ── OAuth ───────────────────────────────────────────────────────── 1727 1721 // 1728 - // Dual-path implementation mirroring api.shortcuts / api.context: 1729 - // 1730 - // - STRICT: when the tile's manifest declared an `oauth` capability 1731 - // (`true` or `{ providers: [...] }`), route through 1732 - // `tile:oauth:*`. Main-process handlers validate the token + 1733 - // capability shape + optional per-provider allowlist. 1734 - // 1735 - // - V1-COMPAT: when no oauth capability was declared, fall back to 1736 - // the legacy `oauth-start-loopback` / `oauth-await-callback` 1737 - // channels. Those remain available through the Phase 3/4 1738 - // migration window and continue to back wonderwall / lex until 1739 - // they opt in. 1740 - // 1741 - // The decision is made per-call because `grantedCapabilities` is 1742 - // populated asynchronously by `initialize()`. 1722 + // Strict-only: tiles must declare an `oauth` capability in their 1723 + // manifest. Routes through `tile:oauth:*`; main-process handlers 1724 + // validate token + capability shape + optional per-provider allowlist. 1743 1725 1744 1726 function hasOAuthCapability(): boolean { 1745 1727 const oc = grantedCapabilities?.oauth; ··· 1748 1730 return false; 1749 1731 } 1750 1732 1751 - const oauthCompat = { 1752 - startLoopback: (options?: { callbackPath?: string; timeoutMs?: number }) => 1753 - ipcRenderer.invoke('oauth-start-loopback', options), 1754 - awaitCallback: (port: number) => 1755 - ipcRenderer.invoke('oauth-await-callback', { port }), 1756 - }; 1757 - 1758 - const oauthStrict = { 1759 - startLoopback: (options?: { callbackPath?: string; timeoutMs?: number; provider?: string }) => 1760 - ipcRenderer.invoke('tile:oauth:start-loopback', { 1761 - token: tileToken, 1762 - callbackPath: options?.callbackPath, 1763 - timeoutMs: options?.timeoutMs, 1764 - provider: options?.provider, 1765 - }), 1766 - awaitCallback: (port: number) => 1767 - ipcRenderer.invoke('tile:oauth:await-callback', { 1768 - token: tileToken, 1769 - port, 1770 - }), 1771 - }; 1772 - 1773 1733 api.oauth = { 1774 1734 startLoopback: (options: unknown) => { 1735 + if (!hasOAuthCapability()) { 1736 + return Promise.reject(new Error('[tile-preload] api.oauth.startLoopback requires the oauth capability')); 1737 + } 1775 1738 const opts = (options || {}) as { callbackPath?: string; timeoutMs?: number; provider?: string }; 1776 - return hasOAuthCapability() 1777 - ? oauthStrict.startLoopback(opts) 1778 - : oauthCompat.startLoopback(opts); 1739 + return ipcRenderer.invoke('tile:oauth:start-loopback', { 1740 + token: tileToken, 1741 + callbackPath: opts.callbackPath, 1742 + timeoutMs: opts.timeoutMs, 1743 + provider: opts.provider, 1744 + }); 1779 1745 }, 1780 1746 awaitCallback: (port: unknown) => { 1781 - return hasOAuthCapability() 1782 - ? oauthStrict.awaitCallback(port as number) 1783 - : oauthCompat.awaitCallback(port as number); 1747 + if (!hasOAuthCapability()) { 1748 + return Promise.reject(new Error('[tile-preload] api.oauth.awaitCallback requires the oauth capability')); 1749 + } 1750 + return ipcRenderer.invoke('tile:oauth:await-callback', { 1751 + token: tileToken, 1752 + port: port as number, 1753 + }); 1784 1754 }, 1785 1755 }; 1786 1756 1787 1757 // ── Sync ────────────────────────────────────────────────────────── 1788 1758 // 1789 - // Dual-path implementation mirroring api.shortcuts: 1790 - // 1791 - // - STRICT: when the tile's manifest declared `sync: true`, route 1792 - // through `tile:sync:sync-all`. Main-process handler validates 1793 - // the token + capability (and `sync` is builtin-only at manifest 1794 - // resolution). 1795 - // 1796 - // - V1-COMPAT: when no sync capability was declared, fall back to 1797 - // the legacy `sync-full` channel. 1759 + // Strict-only: tiles must declare `sync: true` (builtin-only capability). 1760 + // Routes through `tile:sync:sync-all`; main-process handler validates 1761 + // the token + capability. 1798 1762 1799 1763 function hasSyncCapability(): boolean { 1800 1764 return grantedCapabilities?.sync === true; ··· 1802 1766 1803 1767 api.sync = { 1804 1768 syncAll: () => { 1805 - return hasSyncCapability() 1806 - ? ipcRenderer.invoke('tile:sync:sync-all', { token: tileToken }) 1807 - : ipcRenderer.invoke('sync-full'); 1769 + if (!hasSyncCapability()) { 1770 + return Promise.reject(new Error('[tile-preload] api.sync.syncAll requires the sync capability')); 1771 + } 1772 + return ipcRenderer.invoke('tile:sync:sync-all', { token: tileToken }); 1808 1773 }, 1809 1774 }; 1810 1775 ··· 1823 1788 // callers that read `result.success && result.data` work unchanged. 1824 1789 api.screen = { 1825 1790 getPrimaryDisplay: () => { 1826 - if (tokenValid) { 1827 - return ipcRenderer.invoke('tile:screen:get-primary-display', { 1828 - token: tileToken, 1829 - }); 1830 - } 1831 - // Fallback if called before initialize() — use legacy channel. 1832 - return ipcRenderer.invoke('screen-get-primary-display'); 1791 + return ipcRenderer.invoke('tile:screen:get-primary-display', { 1792 + token: tileToken, 1793 + }); 1833 1794 }, 1834 1795 }; 1835 1796 ··· 1845 1806 1846 1807 api.session = { 1847 1808 saveSpaceWorkspaces: () => { 1848 - if (hasSessionCapability() && tokenValid) { 1849 - return ipcRenderer.invoke('tile:session:save-space-workspaces', { 1850 - token: tileToken, 1851 - }); 1809 + if (!hasSessionCapability()) { 1810 + return Promise.reject(new Error('[tile-preload] api.session.saveSpaceWorkspaces requires the session capability')); 1852 1811 } 1853 - // V1-compat fallback: legacy ungated channel. 1854 - return ipcRenderer.invoke('save-space-workspaces'); 1812 + return ipcRenderer.invoke('tile:session:save-space-workspaces', { 1813 + token: tileToken, 1814 + }); 1855 1815 }, 1856 1816 }; 1857 1817