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): flip theme:* to strict + delete legacy (Phase 3.6b)

Remove 6 legacy ipcMain.handle('theme:get|setColorScheme|setWindowColorScheme|
setTheme|list|getAll') blocks from ipc.ts. The tile:theme:* strict counterparts
(added in Phase 3.5b) are now the sole path.

In tile-preload.cts, the trustedBuiltin override block is collapsed to only the
setWindowColorScheme wrapper (which must resolve the focused-visible-window-id
before forwarding to tile:theme:setWindowColorScheme). The five other methods
(get, list, getAll, setTheme, setColorScheme) now route through the base
api.theme.* implementations which already invoke tile:theme:* directly.

TypeScript clean; grep confirms no ipcRenderer.invoke('theme:') survivors.

+12 -211
-189
backend/electron/ipc.ts
··· 1515 1515 * Register theme-related IPC handlers 1516 1516 */ 1517 1517 export function registerThemeHandlers(): void { 1518 - // Get current theme settings 1519 - ipcMain.handle('theme:get', () => { 1520 - const themeId = getThemeSetting(THEME_ID_KEY) || getActiveThemeId(); 1521 - const colorScheme = getThemeSetting(THEME_COLOR_SCHEME_KEY) || 'system'; 1522 - const isDark = nativeTheme.shouldUseDarkColors; 1523 - 1524 - return { 1525 - themeId, 1526 - colorScheme, 1527 - isDark, 1528 - effectiveScheme: colorScheme === 'system' ? (isDark ? 'dark' : 'light') : colorScheme 1529 - }; 1530 - }); 1531 - 1532 - // Set color scheme preference (system/light/dark) 1533 - ipcMain.handle('theme:setColorScheme', (_ev, colorScheme: string) => { 1534 - if (!['system', 'light', 'dark'].includes(colorScheme)) { 1535 - return { success: false, error: 'Invalid color scheme' }; 1536 - } 1537 - 1538 - setThemeSetting(THEME_COLOR_SCHEME_KEY, colorScheme); 1539 - 1540 - // Update nativeTheme to match (affects new windows) 1541 - nativeTheme.themeSource = colorScheme as 'system' | 'light' | 'dark'; 1542 - 1543 - // Broadcast to all windows 1544 - broadcastThemeChange(colorScheme); 1545 - 1546 - return { 1547 - success: true, 1548 - colorScheme, 1549 - effectiveScheme: colorScheme === 'system' 1550 - ? (nativeTheme.shouldUseDarkColors ? 'dark' : 'light') 1551 - : colorScheme 1552 - }; 1553 - }); 1554 - 1555 - // Set color scheme for a specific window only (doesn't affect global setting) 1556 - ipcMain.handle('theme:setWindowColorScheme', (_ev, { windowId, colorScheme }: { windowId: number; colorScheme: string }) => { 1557 - if (!['system', 'light', 'dark', 'global'].includes(colorScheme)) { 1558 - return { success: false, error: 'Invalid color scheme' }; 1559 - } 1560 - 1561 - const win = BrowserWindow.fromId(windowId); 1562 - if (!win || win.isDestroyed()) { 1563 - return { success: false, error: 'Window not found' }; 1564 - } 1565 - 1566 - // Send to specific window only 1567 - win.webContents.send('theme:windowChanged', { colorScheme }); 1568 - 1569 - return { success: true, windowId, colorScheme }; 1570 - }); 1571 - 1572 - // Set active theme 1573 - ipcMain.handle('theme:setTheme', (_ev, themeId: string) => { 1574 - if (!setActiveThemeId(themeId)) { 1575 - return { success: false, error: 'Theme not found' }; 1576 - } 1577 - setThemeSetting(THEME_ID_KEY, themeId); 1578 - 1579 - // Broadcast to all windows to reload their CSS 1580 - const windows = BrowserWindow.getAllWindows(); 1581 - for (const win of windows) { 1582 - win.webContents.send('theme:themeChanged', { themeId }); 1583 - } 1584 - 1585 - return { success: true, themeId }; 1586 - }); 1587 - 1588 - // List available themes 1589 - ipcMain.handle('theme:list', () => { 1590 - const themeIds = getRegisteredThemeIds(); 1591 - const themes = themeIds.map(id => { 1592 - const themePath = getThemePath(id); 1593 - if (!themePath) return null; 1594 - 1595 - try { 1596 - const manifestPath = path.join(themePath, 'manifest.json'); 1597 - const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')); 1598 - return { 1599 - id: manifest.id || id, 1600 - name: manifest.name || id, 1601 - version: manifest.version || '1.0.0', 1602 - description: manifest.description || '', 1603 - colorSchemes: manifest.colorSchemes || ['light', 'dark'], 1604 - }; 1605 - } catch { 1606 - return { id, name: id, version: '1.0.0', description: '', colorSchemes: ['light', 'dark'] }; 1607 - } 1608 - }).filter(Boolean); 1609 - 1610 - // Sort: 'peek' first (default theme), then alphabetically 1611 - themes.sort((a: any, b: any) => { 1612 - if (a.id === 'peek') return -1; 1613 - if (b.id === 'peek') return 1; 1614 - return a.name.localeCompare(b.name); 1615 - }); 1616 - 1617 - return { themes }; 1618 - }); 1619 - 1620 1518 // Listen for system theme changes 1621 1519 nativeTheme.on('updated', () => { 1622 1520 const colorScheme = getThemeSetting(THEME_COLOR_SCHEME_KEY) || 'system'; ··· 1780 1678 db.prepare('DELETE FROM themes WHERE id = ?').run(themeId); 1781 1679 1782 1680 return { success: true, data: { id: themeId } }; 1783 - } catch (error) { 1784 - const message = error instanceof Error ? error.message : String(error); 1785 - return { success: false, error: message }; 1786 - } 1787 - }); 1788 - 1789 - // Get all themes (builtin + external) 1790 - ipcMain.handle('theme:getAll', async () => { 1791 - try { 1792 - const db = getDb(); 1793 - 1794 - // Get builtin themes from registry 1795 - const builtinIds = getRegisteredThemeIds(); 1796 - const themes: Array<{ 1797 - id: string; 1798 - name: string; 1799 - description: string; 1800 - version: string; 1801 - author: string; 1802 - path: string; 1803 - builtin: boolean; 1804 - colorSchemes: string[]; 1805 - }> = []; 1806 - 1807 - // Add builtin themes 1808 - for (const id of builtinIds) { 1809 - const themePath = getThemePath(id); 1810 - if (!themePath) continue; 1811 - 1812 - try { 1813 - const manifestPath = path.join(themePath, 'manifest.json'); 1814 - const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')); 1815 - themes.push({ 1816 - id: manifest.id || id, 1817 - name: manifest.name || id, 1818 - description: manifest.description || '', 1819 - version: manifest.version || '1.0.0', 1820 - author: manifest.author || '', 1821 - path: themePath, 1822 - builtin: true, 1823 - colorSchemes: manifest.colorSchemes || ['light', 'dark'], 1824 - }); 1825 - } catch { 1826 - themes.push({ 1827 - id, 1828 - name: id, 1829 - description: '', 1830 - version: '1.0.0', 1831 - author: '', 1832 - path: themePath, 1833 - builtin: true, 1834 - colorSchemes: ['light', 'dark'], 1835 - }); 1836 - } 1837 - } 1838 - 1839 - // Add external themes from database 1840 - const externalThemes = db.prepare('SELECT * FROM themes WHERE builtin = 0').all() as Array<{ 1841 - id: string; 1842 - name: string; 1843 - description: string; 1844 - version: string; 1845 - author: string; 1846 - path: string; 1847 - metadata?: string; 1848 - }>; 1849 - 1850 - for (const ext of externalThemes) { 1851 - let colorSchemes = ['light', 'dark']; 1852 - try { 1853 - const metadata = JSON.parse(ext.metadata || '{}'); 1854 - colorSchemes = metadata.colorSchemes || colorSchemes; 1855 - } catch { /* ignore */ } 1856 - 1857 - themes.push({ 1858 - id: ext.id, 1859 - name: ext.name, 1860 - description: ext.description, 1861 - version: ext.version, 1862 - author: ext.author, 1863 - path: ext.path, 1864 - builtin: false, 1865 - colorSchemes, 1866 - }); 1867 - } 1868 - 1869 - return { success: true, data: themes }; 1870 1681 } catch (error) { 1871 1682 const message = error instanceof Error ? error.message : String(error); 1872 1683 return { success: false, error: message };
+12 -22
backend/electron/tile-preload.cts
··· 1077 1077 }); 1078 1078 }, 1079 1079 1080 - // ── tile:theme:* strict shims (Phase 3.5b) ────────────────────────── 1081 - // These route through the new tile:theme:* strict channels which enforce 1080 + // ── tile:theme:* strict shims ──────────────────────────────────────── 1081 + // These route through the tile:theme:* strict channels which enforce 1082 1082 // trustedBuiltin gating on the main-process side. Non-trusted callers 1083 1083 // will receive { success: false, error: 'trustedBuiltin required' }. 1084 - // 1085 - // NOTE: the trustedBuiltin block below OVERRIDES get/list/getAll/setTheme/ 1086 - // setColorScheme/setWindowColorScheme with legacy theme:* routes for now. 1087 - // Wave 3.6b will flip that block to call tile:theme:* and remove the 1088 - // override, at which point these base implementations become the sole path. 1084 + // setWindowColorScheme has a thin trustedBuiltin wrapper below that 1085 + // resolves the target windowId before forwarding to the strict channel. 1089 1086 1090 1087 /** 1091 1088 * Get current theme settings (id, colorScheme, isDark, effectiveScheme). ··· 2148 2145 2149 2146 // ── Theme (trustedBuiltin extensions) ──────────────────────────────── 2150 2147 // 2151 - // The strict tile:theme surface only exposes `getInfo` and `onChange`. 2152 - // Core renderers (app/index.js in particular) need the full v1 theme 2153 - // surface for the cmd palette theme commands (`theme light`, `theme`, 2154 - // `theme light here`, etc.). The channels are plain `ipcMain.handle` 2155 - // entries in `ipc.ts`, so we can expose them here under trustedBuiltin 2156 - // as thin wrappers. Non-trustedBuiltin tiles continue to use the 2157 - // strict tile:theme:* surface declared above. 2148 + // Core renderers (app/index.js in particular) call setWindowColorScheme 2149 + // with only a colorScheme argument (no windowId), relying on window-ID 2150 + // resolution via `get-focused-visible-window-id`. The strict 2151 + // tile:theme:setWindowColorScheme channel requires an explicit windowId, 2152 + // so we keep a thin trustedBuiltin wrapper here that resolves the ID 2153 + // before forwarding to the strict channel. All other theme methods route 2154 + // through the base api.theme.* implementations above (tile:theme:*). 2158 2155 if (trustedBuiltin) { 2159 - (api.theme as Record<string, unknown>).get = () => ipcRenderer.invoke('theme:get'); 2160 - (api.theme as Record<string, unknown>).list = () => ipcRenderer.invoke('theme:list'); 2161 - (api.theme as Record<string, unknown>).getAll = () => ipcRenderer.invoke('theme:getAll'); 2162 - (api.theme as Record<string, unknown>).setTheme = (themeId: string) => 2163 - ipcRenderer.invoke('theme:setTheme', themeId); 2164 - (api.theme as Record<string, unknown>).setColorScheme = (colorScheme: string) => 2165 - ipcRenderer.invoke('theme:setColorScheme', colorScheme); 2166 2156 (api.theme as Record<string, unknown>).setWindowColorScheme = async (colorScheme: string) => { 2167 2157 // Core/test renderers target the "last focused visible window" via 2168 2158 // `get-focused-visible-window-id`, not their own hidden ··· 2175 2165 if (!windowId) { 2176 2166 return { success: false, error: 'No visible window to target' }; 2177 2167 } 2178 - return ipcRenderer.invoke('theme:setWindowColorScheme', { windowId, colorScheme }); 2168 + return ipcRenderer.invoke('tile:theme:setWindowColorScheme', { token: tileToken, windowId, colorScheme }); 2179 2169 }; 2180 2170 } 2181 2171