experiments in a post-browser web
10
fork

Configure Feed

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

fix(lex): seed recent lexicons from collections, fix sidebar styling

+190 -10
+9 -9
extensions/lex/home.css
··· 348 348 349 349 /* Recent lexicons in sidebar */ 350 350 .recent-lexicons { 351 - border-top: 1px solid var(--base02); 352 - padding-top: 4px; 351 + padding-top: 0; 353 352 } 354 353 355 354 .recent-label { 356 - font-size: 10px; 357 - font-weight: 600; 355 + display: flex; 356 + align-items: center; 357 + gap: 8px; 358 + padding: 10px 16px; 359 + font-size: 13px; 358 360 color: var(--base04); 359 - text-transform: uppercase; 360 - letter-spacing: 0.05em; 361 - padding: 8px 16px 2px; 361 + user-select: none; 362 362 } 363 363 364 364 .recent-item { 365 365 display: flex; 366 366 align-items: center; 367 367 justify-content: space-between; 368 - padding: 6px 16px; 368 + padding: 6px 16px 6px 42px; 369 369 font-size: 12px; 370 370 color: var(--base04); 371 371 cursor: pointer; ··· 410 410 .recent-empty { 411 411 font-size: 11px; 412 412 color: var(--base03); 413 - padding: 6px 16px 4px; 413 + padding: 6px 16px 4px 42px; 414 414 font-style: italic; 415 415 } 416 416
+11 -1
extensions/lex/home.js
··· 1178 1178 state.collections = counts; 1179 1179 renderCollections(); 1180 1180 1181 + // Seed recent lexicons from existing collections on first visit 1182 + if (state.recentLexicons.length === 0 && counts.length > 0) { 1183 + state.recentLexicons = counts 1184 + .sort((a, b) => (b.count || 0) - (a.count || 0)) 1185 + .slice(0, MAX_RECENT_LEXICONS) 1186 + .map(c => c.nsid); 1187 + saveRecentLexicons(); 1188 + renderRecentLexicons(); 1189 + } 1190 + 1181 1191 // Update "new" commands with repo collections 1182 1192 onCollectionsChanged(counts.map(c => c.nsid)); 1183 1193 } catch (err) { ··· 2476 2486 2477 2487 if (state.recentLexicons.length === 0) { 2478 2488 recentLexiconsEl.innerHTML = ` 2479 - <div class="recent-label">Recent Lexicons</div> 2489 + <div class="recent-label"><span class="nav-icon">&#8986;</span> Recent</div> 2480 2490 <div class="recent-empty">No recent lexicons yet</div> 2481 2491 `; 2482 2492 return;
+45
extensions/search/background.html
··· 1 + <!DOCTYPE html> 2 + <html> 3 + <head> 4 + <meta charset="utf-8"> 5 + <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"> 6 + <title>Search Extension</title> 7 + </head> 8 + <body> 9 + <script type="module"> 10 + import extension from './background.js'; 11 + 12 + const api = window.app; 13 + const extId = extension.id; 14 + 15 + // Initialize extension BEFORE signaling ready 16 + // (lazy loading waits for ext:ready, so handlers must be registered first) 17 + if (extension.init) { 18 + await extension.init(); 19 + } 20 + 21 + // Signal ready to main process 22 + api.publish('ext:ready', { 23 + id: extId, 24 + manifest: { 25 + id: extension.id, 26 + version: '1.0.0' 27 + } 28 + }, api.scopes.SYSTEM); 29 + 30 + // Handle shutdown request from main process 31 + api.subscribe('app:shutdown', () => { 32 + if (extension.uninit) { 33 + extension.uninit(); 34 + } 35 + }, api.scopes.SYSTEM); 36 + 37 + // Handle extension-specific shutdown 38 + api.subscribe(`ext:${extId}:shutdown`, () => { 39 + if (extension.uninit) { 40 + extension.uninit(); 41 + } 42 + }, api.scopes.SYSTEM); 43 + </script> 44 + </body> 45 + </html>
+66
extensions/search/background.js
··· 1 + /** 2 + * Search Extension Background Script 3 + * 4 + * Opens a search view with text and tag-based search. 5 + * Runs in consolidated extension host (iframe). 6 + * Loads lazily on first command invocation. 7 + */ 8 + 9 + const id = 'search'; 10 + const api = window.app; 11 + const debug = api.debug; 12 + 13 + const baseAddress = 'peek://ext/search/home.html'; 14 + 15 + let isOpeningSearch = false; 16 + 17 + const openSearchWindow = async (query) => { 18 + if (isOpeningSearch) return; 19 + isOpeningSearch = true; 20 + try { 21 + const url = query 22 + ? `${baseAddress}?q=${encodeURIComponent(query)}` 23 + : baseAddress; 24 + 25 + await api.window.open(url, { 26 + role: 'workspace', 27 + key: 'search-home', 28 + height: 600, 29 + width: 700, 30 + trackingSource: 'cmd', 31 + trackingSourceId: 'search' 32 + }); 33 + } catch (error) { 34 + console.error('[ext:search] Failed to open search window:', error); 35 + } finally { 36 + isOpeningSearch = false; 37 + } 38 + }; 39 + 40 + const init = async () => { 41 + // Register command handler 42 + api.commands.register({ 43 + name: 'search', 44 + description: 'Search items by text and tags', 45 + execute: async (params) => { 46 + const query = params?.text || ''; 47 + openSearchWindow(query); 48 + } 49 + }); 50 + 51 + // Listen for search requests from cmd palette 52 + api.subscribe('cmd:execute:search', (msg) => { 53 + const query = msg?.data?.query || msg?.data?.text || ''; 54 + openSearchWindow(query); 55 + }, api.scopes.GLOBAL); 56 + }; 57 + 58 + const uninit = () => { 59 + // Nothing to clean up 60 + }; 61 + 62 + export default { 63 + id, 64 + init, 65 + uninit 66 + };
+43
extensions/search/home.html
··· 1 + <!DOCTYPE html> 2 + <html> 3 + <head> 4 + <meta charset="utf-8"> 5 + <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"> 6 + <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1"> 7 + <title>Search</title> 8 + <link rel="stylesheet" type="text/css" href="home.css"> 9 + 10 + <!-- Import map for resolving bare module specifiers --> 11 + <script type="importmap"> 12 + { 13 + "imports": { 14 + "lit": "peek://node_modules/lit/index.js", 15 + "lit/": "peek://node_modules/lit/", 16 + "lit-html": "peek://node_modules/lit-html/lit-html.js", 17 + "lit-html/": "peek://node_modules/lit-html/", 18 + "lit-element": "peek://node_modules/lit-element/lit-element.js", 19 + "lit-element/": "peek://node_modules/lit-element/", 20 + "@lit/reactive-element": "peek://node_modules/@lit/reactive-element/reactive-element.js", 21 + "@lit/reactive-element/": "peek://node_modules/@lit/reactive-element/" 22 + } 23 + } 24 + </script> 25 + 26 + <!-- Import peek-components --> 27 + <script type="module"> 28 + import 'peek://app/components/peek-card.js'; 29 + import 'peek://app/components/peek-grid.js'; 30 + import 'peek://app/components/peek-grid-toolbar.js'; 31 + </script> 32 + </head> 33 + <body> 34 + <div class="search-header"> 35 + <h1 class="search-title"></h1> 36 + </div> 37 + 38 + <peek-grid-toolbar class="grid-toolbar" view-mode="list"></peek-grid-toolbar> 39 + <peek-grid class="cards" min-item-width="180" gap="8"></peek-grid> 40 + 41 + <script type="module" src="home.js"></script> 42 + </body> 43 + </html>
+16
extensions/search/manifest.json
··· 1 + { 2 + "id": "search", 3 + "shortname": "search", 4 + "name": "Search", 5 + "description": "Search across items by text and tags", 6 + "version": "1.0.0", 7 + "background": "background.html", 8 + "builtin": true, 9 + "commands": [ 10 + { 11 + "name": "search", 12 + "description": "Search items by text and tags", 13 + "action": { "type": "execute" } 14 + } 15 + ] 16 + }