experiments in a post-browser web
10
fork

Configure Feed

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

refactor(settings): rename Features pane route + internal ext/extension identifiers to feature vocabulary

The Features settings tab's route id, DOM ids, function names, local
variables, and comments all used v1-era 'extension' vocabulary while
its user-facing label is already 'Features'. The separate 'Extensions'
tab (bundled chrome browser extensions — ad blocker, consent-o-matic)
keeps its distinct 'privacy' route id and real-chrome-extension
identifiers; this sweep only touches the tile-backed-features side.

app/index.js
- SETTINGS_PANES entry value 'extensions' -> 'features'
(also drops the vestigial subtitle mention of 'extensions')

app/settings/settings.js
- Route + DOM:
- section id 'section-extensions' -> 'section-features'
- listContainer id 'extensions-list-container' -> 'features-list-container'
- nav dataset.section, showSection argument, nav variable name
- local DOM variables extNav/extSection/extTitle -> featuresNav/featuresSection/featuresTitle
- Function + global names:
- renderExtensionsSettings -> renderFeaturesSettings
- refreshExtensionsList -> refreshFeaturesList
- window._refreshExtensionsList -> window._refreshFeaturesList
- Features-loop internals:
- allExtensions -> allFeatures
- isCmdExtension -> isCmdFeature
- iteration variable ext -> feat (Chrome browser extension loop keeps ext)
- CSS class 'extension-error' -> 'feature-error' (class was assigned but never targeted)
- renderFeatureSettings (single-feature options):
- extId -> featureId
- Datastore browser: drop the deleted 'extensions' SQLite table from
the Features table-group and tableDescriptions map; relabel the group
from 'Extensions' to 'Features'
- Comments across the Features pane rewritten in feature/tile vocabulary

app/settings/settings.css
- Delete stale .nav-item-extension rule + comment (class was never referenced)

Session-restore impact: users with URL hash #extensions from prior
sessions will land on the default pane instead of the Features pane
once; showSection handles the missing-section case gracefully.

Out of scope (stays as-is): api.chromeExtensions surface, the 'privacy'
browser-extensions tab, identifiers inside the bundled-Chrome-extensions
render path.

+87 -96
+1 -1
app/index.js
··· 227 227 228 228 const SETTINGS_PANES = [ 229 229 { value: 'core', title: 'Core', subtitle: 'General application settings' }, 230 - { value: 'extensions', title: 'Features', subtitle: 'Manage peek features and extensions' }, 230 + { value: 'features', title: 'Features', subtitle: 'Manage Peek features' }, 231 231 { value: 'privacy', title: 'Extensions', subtitle: 'Browser extensions (ad blocker, etc.)' }, 232 232 { value: 'dark-mode', title: 'Dark Mode', subtitle: 'Dark mode settings' }, 233 233 { value: 'sync', title: 'Sync', subtitle: 'Sync configuration and status' },
-8
app/settings/settings.css
··· 99 99 font-weight: 500; 100 100 } 101 101 102 - /* Extension nav items - visual indicator (kept for external extensions if needed) */ 103 - .nav-item-extension::before { 104 - content: '⚙'; 105 - font-size: 11px; 106 - margin-right: 6px; 107 - opacity: 0.5; 108 - } 109 - 110 102 /* Content Area */ 111 103 .content { 112 104 flex: 1;
+86 -87
app/settings/settings.js
··· 1734 1734 }; 1735 1735 1736 1736 // Render features settings (Peek's own features: cmd, groups, peeks, slides, windows) 1737 - const renderExtensionsSettings = async () => { 1737 + const renderFeaturesSettings = async () => { 1738 1738 const container = document.createElement('div'); 1739 1739 1740 1740 // Note: feature install (Add Feature) is now handled by the dedicated ··· 1749 1749 1750 1750 // Features list container (for refresh) 1751 1751 const listContainer = document.createElement('div'); 1752 - listContainer.id = 'extensions-list-container'; 1752 + listContainer.id = 'features-list-container'; 1753 1753 container.appendChild(listContainer); 1754 1754 1755 1755 // Function to refresh features list 1756 - const refreshExtensionsList = async () => { 1756 + const refreshFeaturesList = async () => { 1757 1757 listContainer.innerHTML = ''; 1758 1758 1759 1759 const loading = document.createElement('div'); ··· 1783 1783 ...(builtinResult?.entries || []), 1784 1784 ]; 1785 1785 1786 - const allExtensions = allEntries.map(entry => { 1786 + const allFeatures = allEntries.map(entry => { 1787 1787 const isBuiltin = entry.source?.type === 'builtin'; 1788 1788 const feature = features.find(f => f.name.toLowerCase() === entry.id); 1789 1789 // Builtins respect the local features-list "enabled" flag ··· 1820 1820 }; 1821 1821 }); 1822 1822 1823 - if (allExtensions.length === 0) { 1823 + if (allFeatures.length === 0) { 1824 1824 const empty = document.createElement('div'); 1825 1825 empty.className = 'help-text'; 1826 1826 empty.textContent = 'No features installed. Click "Add Feature" to install one.'; ··· 1828 1828 return; 1829 1829 } 1830 1830 1831 - const extSection = document.createElement('div'); 1832 - extSection.className = 'form-section'; 1831 + const featuresSection = document.createElement('div'); 1832 + featuresSection.className = 'form-section'; 1833 1833 1834 1834 const title = document.createElement('h3'); 1835 1835 title.className = 'form-section-title'; 1836 1836 title.textContent = 'Installed Features'; 1837 - extSection.appendChild(title); 1837 + featuresSection.appendChild(title); 1838 1838 1839 - allExtensions.filter(ext => !(ext.manifest || {}).hidden).forEach(ext => { 1840 - const manifest = ext.manifest || {}; 1841 - const isBuiltin = manifest.builtin || ext.source === 'builtin'; 1839 + allFeatures.filter(feat => !(feat.manifest || {}).hidden).forEach(feat => { 1840 + const manifest = feat.manifest || {}; 1841 + const isBuiltin = manifest.builtin || feat.source === 'builtin'; 1842 1842 1843 1843 const card = document.createElement('div'); 1844 1844 card.className = 'item-card no-collapse'; ··· 1851 1851 const leftSide = document.createElement('div'); 1852 1852 leftSide.style.cssText = 'display: flex; align-items: center; gap: 12px;'; 1853 1853 1854 - // Enable/disable checkbox 1855 - // cmd extension cannot be disabled - it's required infrastructure 1856 - const isCmdExtension = ext.id === 'cmd'; 1854 + // Enable/disable checkbox. 1855 + // The cmd feature is required infrastructure — can't be disabled. 1856 + const isCmdFeature = feat.id === 'cmd'; 1857 1857 const checkbox = document.createElement('input'); 1858 1858 checkbox.type = 'checkbox'; 1859 - checkbox.checked = ext.enabled; 1860 - checkbox.disabled = isCmdExtension; 1861 - checkbox.title = isCmdExtension ? 'Required - cannot be disabled' : ''; 1862 - checkbox.style.cssText = `width: 16px; height: 16px; cursor: ${isCmdExtension ? 'not-allowed' : 'pointer'}; ${isCmdExtension ? 'opacity: 0.5;' : ''}`; 1859 + checkbox.checked = feat.enabled; 1860 + checkbox.disabled = isCmdFeature; 1861 + checkbox.title = isCmdFeature ? 'Required - cannot be disabled' : ''; 1862 + checkbox.style.cssText = `width: 16px; height: 16px; cursor: ${isCmdFeature ? 'not-allowed' : 'pointer'}; ${isCmdFeature ? 'opacity: 0.5;' : ''}`; 1863 1863 checkbox.addEventListener('change', async (e) => { 1864 1864 const newEnabled = e.target.checked; 1865 1865 checkbox.disabled = true; ··· 1868 1868 // Update features storage for persistence 1869 1869 const coreStore = _coreStore || await createDatastoreStore('core', appConfig.defaults); 1870 1870 const featuresList = coreStore.get(appConfig.storageKeys.ITEMS) || []; 1871 - const featureIndex = featuresList.findIndex(f => f.name.toLowerCase() === ext.id); 1871 + const featureIndex = featuresList.findIndex(f => f.name.toLowerCase() === feat.id); 1872 1872 if (featureIndex >= 0) { 1873 1873 featuresList[featureIndex].enabled = newEnabled; 1874 1874 await coreStore.set(appConfig.storageKeys.ITEMS, featuresList); ··· 1877 1877 // Use the feature toggle mechanism to load/unload 1878 1878 // This triggers the core:feature:toggle handler in app/index.js 1879 1879 api.publish('core:feature:toggle', { 1880 - featureId: ext.id, 1880 + featureId: feat.id, 1881 1881 enabled: newEnabled 1882 1882 }); 1883 - } else if (ext.source === 'datastore') { 1883 + } else if (feat.source === 'datastore') { 1884 1884 // Toggle via the unified registry — enable / disable mark the 1885 1885 // entry in registry.json and broadcast feature:enabled / 1886 1886 // feature:disabled. Tile windows are launched / unloaded by 1887 1887 // the main process side-effect inside the strict handler. 1888 1888 const toggleResult = newEnabled 1889 - ? await api.features.enable(ext.id) 1890 - : await api.features.disable(ext.id); 1889 + ? await api.features.enable(feat.id) 1890 + : await api.features.disable(feat.id); 1891 1891 if (toggleResult?.error) { 1892 - console.error(`[settings] Feature ${ext.id} toggle failed:`, toggleResult.error); 1892 + console.error(`[settings] Feature ${feat.id} toggle failed:`, toggleResult.error); 1893 1893 } 1894 1894 } 1895 1895 1896 1896 checkbox.disabled = false; 1897 1897 // Refresh to show current state 1898 - setTimeout(refreshExtensionsList, 300); 1898 + setTimeout(refreshFeaturesList, 300); 1899 1899 }); 1900 1900 leftSide.appendChild(checkbox); 1901 1901 ··· 1904 1904 cardTitle.style.margin = '0'; 1905 1905 1906 1906 const nameSpan = document.createElement('span'); 1907 - nameSpan.textContent = manifest.name || ext.id; 1907 + nameSpan.textContent = manifest.name || feat.id; 1908 1908 cardTitle.appendChild(nameSpan); 1909 1909 1910 1910 if (manifest.version) { ··· 1921 1921 cardTitle.appendChild(authorSpan); 1922 1922 } 1923 1923 1924 - // Show "required" badge for cmd extension (cannot be disabled) 1925 - if (isCmdExtension) { 1924 + // Show "required" badge for the cmd feature (cannot be disabled). 1925 + if (isCmdFeature) { 1926 1926 const requiredBadge = document.createElement('span'); 1927 1927 requiredBadge.style.cssText = 'margin-left: 8px; font-size: 10px; padding: 2px 6px; background: var(--base0E, #c678dd); border-radius: 4px; color: white;'; 1928 1928 requiredBadge.textContent = 'required'; ··· 1937 1937 actions.style.cssText = 'display: flex; gap: 8px; align-items: center;'; 1938 1938 1939 1939 // Reload button (only when running) 1940 - if (ext.isRunning) { 1940 + if (feat.isRunning) { 1941 1941 const reloadBtn = document.createElement('button'); 1942 1942 reloadBtn.textContent = 'Reload'; 1943 1943 reloadBtn.style.cssText = ` ··· 1953 1953 reloadBtn.textContent = '...'; 1954 1954 reloadBtn.disabled = true; 1955 1955 try { 1956 - const result = await api.features.devReload(ext.id); 1956 + const result = await api.features.devReload(feat.id); 1957 1957 reloadBtn.textContent = result && !result.error ? '✓' : '✗'; 1958 1958 } catch (err) { 1959 1959 reloadBtn.textContent = '✗'; ··· 1961 1961 setTimeout(() => { 1962 1962 reloadBtn.textContent = 'Reload'; 1963 1963 reloadBtn.disabled = false; 1964 - refreshExtensionsList(); 1964 + refreshFeaturesList(); 1965 1965 }, 1000); 1966 1966 }); 1967 1967 actions.appendChild(reloadBtn); 1968 1968 } 1969 1969 1970 - // Remove button (only for external extensions) 1970 + // Remove button (only for external features). 1971 1971 if (!isBuiltin) { 1972 1972 const removeBtn = document.createElement('button'); 1973 1973 removeBtn.textContent = 'Remove'; ··· 1981 1981 cursor: pointer; 1982 1982 `; 1983 1983 removeBtn.addEventListener('click', async () => { 1984 - if (!confirm(`Remove feature "${manifest.name || ext.id}"?`)) return; 1984 + if (!confirm(`Remove feature "${manifest.name || feat.id}"?`)) return; 1985 1985 1986 1986 removeBtn.textContent = '...'; 1987 1987 removeBtn.disabled = true; 1988 1988 1989 1989 // Remove from the unified registry — the strict handler 1990 1990 // tears down any running tile windows before deleting files. 1991 - const result = await api.features.remove(ext.id); 1991 + const result = await api.features.remove(feat.id); 1992 1992 if (result && !result.error) { 1993 - refreshExtensionsList(); 1993 + refreshFeaturesList(); 1994 1994 } else { 1995 1995 removeBtn.textContent = 'Error'; 1996 1996 setTimeout(() => { ··· 2016 2016 body.appendChild(desc); 2017 2017 } 2018 2018 2019 - // Show path for external extensions 2020 - if (ext.path && !isBuiltin) { 2019 + // Show path for external features. 2020 + if (feat.path && !isBuiltin) { 2021 2021 const pathInfo = document.createElement('div'); 2022 2022 pathInfo.className = 'help-text'; 2023 2023 pathInfo.style.cssText = 'font-family: monospace; font-size: 11px; margin-bottom: 4px;'; 2024 - pathInfo.textContent = ext.path; 2024 + pathInfo.textContent = feat.path; 2025 2025 body.appendChild(pathInfo); 2026 2026 } 2027 2027 2028 - // Show error if any 2029 - if (ext.lastError) { 2028 + // Show error if any. 2029 + if (feat.lastError) { 2030 2030 const errorInfo = document.createElement('div'); 2031 - errorInfo.className = 'extension-error'; 2031 + errorInfo.className = 'feature-error'; 2032 2032 errorInfo.style.cssText = 'margin-top: 8px; padding: 8px; background: var(--error-bg, #fef2f2); border: 1px solid var(--error-border, #fecaca); border-radius: 4px; font-size: 12px; color: var(--error-text, #dc2626);'; 2033 - errorInfo.textContent = `Error: ${ext.lastError}`; 2033 + errorInfo.textContent = `Error: ${feat.lastError}`; 2034 2034 body.appendChild(errorInfo); 2035 2035 } 2036 2036 2037 - // Inline Options accordion for extensions with settings schemas 2037 + // Inline Options accordion for features with settings schemas. 2038 2038 if (manifest.schemas && (manifest.schemas.prefs || manifest.schemas.item)) { 2039 2039 const optionsAccordion = document.createElement('div'); 2040 2040 optionsAccordion.className = 'ext-options-accordion'; ··· 2068 2068 try { 2069 2069 const feature = { 2070 2070 id: manifest.id, 2071 - shortname: manifest.shortname || ext.id || manifest.id, 2071 + shortname: manifest.shortname || feat.id || manifest.id, 2072 2072 labels: { name: manifest.name, ...(manifest.labels || {}) }, 2073 2073 schemas: manifest.schemas, 2074 2074 storageKeys: manifest.storageKeys || { PREFS: 'prefs', ITEMS: 'items' }, ··· 2077 2077 const settingsContent = await renderFeatureSettings(feature); 2078 2078 optionsContent.appendChild(settingsContent); 2079 2079 } catch (err) { 2080 - console.error(`[settings] Failed to load options for ${ext.id}:`, err); 2080 + console.error(`[settings] Failed to load options for ${feat.id}:`, err); 2081 2081 const errorMsg = document.createElement('div'); 2082 2082 errorMsg.className = 'help-text'; 2083 2083 errorMsg.style.color = 'var(--error-text, #dc2626)'; ··· 2092 2092 } 2093 2093 2094 2094 card.appendChild(body); 2095 - extSection.appendChild(card); 2095 + featuresSection.appendChild(card); 2096 2096 }); 2097 2097 2098 - listContainer.appendChild(extSection); 2098 + listContainer.appendChild(featuresSection); 2099 2099 } catch (err) { 2100 2100 loading.remove(); 2101 2101 const error = document.createElement('div'); 2102 2102 error.className = 'help-text'; 2103 - error.textContent = `Error loading extensions: ${err.message}`; 2103 + error.textContent = `Error loading features: ${err.message}`; 2104 2104 listContainer.appendChild(error); 2105 2105 } 2106 2106 }; 2107 2107 2108 - // Initial load (may be incomplete if extensions still loading) 2109 - await refreshExtensionsList(); 2108 + // Initial load (may be incomplete if features are still loading). 2109 + await refreshFeaturesList(); 2110 2110 2111 2111 // Store refresh function for external access (feature:all-loaded is subscribed once in main init) 2112 - window._refreshExtensionsList = refreshExtensionsList; 2112 + window._refreshFeaturesList = refreshFeaturesList; 2113 2113 2114 2114 return container; 2115 2115 }; ··· 2119 2119 const renderFeatureSettings = async (feature) => { 2120 2120 const { id, labels, schemas, storageKeys, defaults } = feature; 2121 2121 2122 - // Use extension shortname for datastore key (e.g., 'peeks', 'slides', 'websearch') 2123 - // This must match what api.settings uses (derived from peek://{shortname}/... URL) 2124 - const extId = feature.shortname || labels.name.toLowerCase(); 2122 + // Use the feature's shortname for the datastore key (e.g. 'peeks', 'slides', 'websearch'). 2123 + // Must match what api.settings uses (derived from peek://{shortname}/... URL). 2124 + const featureId = feature.shortname || labels.name.toLowerCase(); 2125 2125 2126 - // Load from datastore 2127 - const store = await createDatastoreStore(extId, defaults); 2126 + // Load from datastore. 2127 + const store = await createDatastoreStore(featureId, defaults); 2128 2128 2129 2129 let prefs = store.get(storageKeys.PREFS); 2130 2130 let items = store.get(storageKeys.ITEMS); 2131 2131 2132 2132 const container = document.createElement('div'); 2133 2133 2134 - // Topic for notifying feature of settings changes (e.g., 'peeks:settings-changed') 2135 - // Use extId (shortname) so the topic matches what extensions subscribe to 2136 - const settingsChangedTopic = `${extId}:settings-changed`; 2134 + // Topic for notifying the feature of settings changes (e.g. 'peeks:settings-changed'). 2135 + // Uses the feature's shortname so the topic matches what features subscribe to. 2136 + const settingsChangedTopic = `${featureId}:settings-changed`; 2137 2137 2138 2138 const save = async () => { 2139 2139 // Save to datastore ··· 3102 3102 3103 3103 // Listen for feature toggle events (refresh Features list when toggled) 3104 3104 api.subscribe('core:feature:toggle', () => { 3105 - if (window._refreshExtensionsList) { 3106 - window._refreshExtensionsList(); 3105 + if (window._refreshFeaturesList) { 3106 + window._refreshFeaturesList(); 3107 3107 } 3108 3108 }); 3109 3109 3110 - // Add Features management section (Peek extensions like peeks, slides, groups) 3111 - const extNav = document.createElement('a'); 3112 - extNav.className = 'nav-item'; 3113 - extNav.textContent = 'Features'; 3114 - extNav.dataset.section = 'extensions'; 3115 - extNav.addEventListener('click', () => showSection('extensions')); 3116 - sidebarNav.appendChild(extNav); 3110 + // Add Features management section (tile-backed features: peeks, slides, groups, etc.). 3111 + const featuresNav = document.createElement('a'); 3112 + featuresNav.className = 'nav-item'; 3113 + featuresNav.textContent = 'Features'; 3114 + featuresNav.dataset.section = 'features'; 3115 + featuresNav.addEventListener('click', () => showSection('features')); 3116 + sidebarNav.appendChild(featuresNav); 3117 3117 3118 - // Create features section with async content 3119 - const extSection = document.createElement('div'); 3120 - extSection.className = 'section'; 3121 - extSection.id = 'section-extensions'; 3118 + // Create features section with async content. 3119 + const featuresSection = document.createElement('div'); 3120 + featuresSection.className = 'section'; 3121 + featuresSection.id = 'section-features'; 3122 3122 3123 - const extTitle = document.createElement('h2'); 3124 - extTitle.className = 'section-title'; 3125 - extTitle.textContent = 'Features'; 3126 - extSection.appendChild(extTitle); 3123 + const featuresTitle = document.createElement('h2'); 3124 + featuresTitle.className = 'section-title'; 3125 + featuresTitle.textContent = 'Features'; 3126 + featuresSection.appendChild(featuresTitle); 3127 3127 3128 - // Load extensions content async 3129 - renderExtensionsSettings().then(content => { 3130 - extSection.appendChild(content); 3128 + // Load features content async. 3129 + renderFeaturesSettings().then(content => { 3130 + featuresSection.appendChild(content); 3131 3131 }); 3132 3132 3133 - contentArea.appendChild(extSection); 3133 + contentArea.appendChild(featuresSection); 3134 3134 3135 - // Extension settings schemas are loaded inline in the Features pane (via Options accordion) 3135 + // Feature settings schemas are loaded inline in the Features pane (via Options accordion). 3136 3136 // No separate nav items or panes needed. 3137 3137 3138 - // Listen for all extensions loaded event to refresh the Extensions list 3139 - // NOTE: Only ONE feature:all-loaded subscription per source - pubsub overwrites duplicates 3138 + // Listen for feature:all-loaded to refresh the Features list. 3139 + // NOTE: Only ONE feature:all-loaded subscription per source — pubsub overwrites duplicates. 3140 3140 api.subscribe('feature:all-loaded', () => { 3141 - // Refresh the Extensions list if it's been rendered 3142 - if (window._refreshExtensionsList) { 3143 - window._refreshExtensionsList(); 3141 + if (window._refreshFeaturesList) { 3142 + window._refreshFeaturesList(); 3144 3143 } 3145 3144 }); 3146 3145 3147 - // Add Extensions section (bundled webextensions: ad blocker, consent-o-matic, etc.) 3146 + // Add Extensions section (bundled chrome browser extensions: ad blocker, consent-o-matic, etc.) 3148 3147 const privacyNav = document.createElement('a'); 3149 3148 privacyNav.className = 'nav-item'; 3150 3149 privacyNav.textContent = 'Extensions';