experiments in a post-browser web
10
fork

Configure Feed

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

refactor(lex): drop background tile, home stays resident + chain-form window

Third in the consolidation series. Lex had 3 tile entries: background
(lazy), home (window), chain-form (window). The lex-specific internal
pubsub was lex:session-changed, lex:recent-lexicons-changed, and
lex:open-create.

Option B chosen (two tiles remain, not one): chain-form is a
short-lived modal opened by cmd panel infrastructure via the
chain-popup:content / chain-popup:result channels owned by
app/cmd/panel.js. That wire cannot be inlined. Home is the long-lived
owner of state and commands (now resident:true), chain-form stays
ephemeral.

Changes:
- manifest.json: background tile removed, home marked resident:true,
lex:session-changed removed from pubsub topics.
- home.js: added noun-registration block (discoverCollections,
getAllNsids, registerLexNoun), removed lex:session-changed
publishes from saveSession and clearSession, replaced
DOMContentLoaded bootstrap with an async IIFE that calls
api.initialize, registers the noun, then runs UI init.
- background.js and background.html left on disk.

Preserved topics: lex:recent-lexicons-changed (home to chain-form,
still cross-window), lex:open-create (cmd palette to home),
chain-popup:content / chain-popup:result (cmd panel infra).

+139 -20
+133 -5
features/lex/home.js
··· 1 1 /** 2 2 * Lexicon Studio Home — AT Protocol identity and data browser UI 3 3 * 4 + * Single resident tile combining command/noun registration (formerly background.js) 5 + * and the UI (formerly home.js window tile). chain-form remains a separate window 6 + * tile (short-lived modal opened by the cmd panel's chain-popup protocol). 7 + * 4 8 * Orchestrator: loads a template pack and wires up events between templates. 5 9 * Manages state (auth, current panel, selected lexicon) and handles IPC/pubsub. 6 10 * All rendering is delegated to the active template pack. ··· 9 13 import { 10 14 nsidToFriendlyName, 11 15 } from './lexicon-ui.js'; 16 + 17 + import { registerNoun, unregisterNoun } from 'peek://ext/cmd/nouns.js'; 12 18 13 19 import { getDefaultPack, listTemplatePackMetadata } from './templates/index.js'; 14 20 import { escapeHtml, rkeyFromUri } from './templates/raw/utils.js'; ··· 30 36 const api = window.app; 31 37 32 38 // ============================================================================ 39 + // Noun registration (formerly background.js) 40 + // ============================================================================ 41 + 42 + // Collection NSIDs discovered from user's repo — kept in sync after auth 43 + let knownCollectionNsids = []; 44 + 45 + /** 46 + * Discover collection NSIDs from the user's repo. 47 + * Uses the shared getCollections() from atproto.js. 48 + * Non-blocking — called after session load. 49 + */ 50 + async function discoverCollections() { 51 + if (!currentSession) return; 52 + try { 53 + const collections = await getCollections(currentSession, onSessionRefresh); 54 + knownCollectionNsids = collections || []; 55 + console.log(`[lex] Discovered ${knownCollectionNsids.length} collections for noun query`); 56 + } catch (err) { 57 + console.warn('[lex] Failed to discover collections for noun query:', err); 58 + } 59 + } 60 + 61 + /** 62 + * Get all known NSIDs — merge user's collection NSIDs with lexicon directory. 63 + */ 64 + async function getAllNsids() { 65 + const dirLexicons = await fetchLexiconDirectory(); 66 + const dirNsids = dirLexicons ? dirLexicons.map(l => l.nsid) : []; 67 + return [...new Set([...knownCollectionNsids, ...dirNsids])].sort(); 68 + } 69 + 70 + function registerLexNoun() { 71 + registerNoun({ 72 + name: 'lex', 73 + singular: 'lex', 74 + description: 'AT Protocol lexicons', 75 + produces: 'application/x-lexicon-form', 76 + 77 + query: async ({ search }) => { 78 + const allNsids = await getAllNsids(); 79 + let filtered = allNsids; 80 + if (search) { 81 + const q = search.toLowerCase(); 82 + filtered = allNsids.filter(nsid => { 83 + const friendly = nsidToFriendlyName(nsid).toLowerCase(); 84 + return nsid.toLowerCase().includes(q) || friendly.includes(q); 85 + }); 86 + } 87 + return { 88 + success: true, 89 + output: { 90 + data: filtered.slice(0, 30).map(nsid => ({ 91 + name: `${nsidToFriendlyName(nsid)} — ${nsid}`, 92 + nsid, 93 + })), 94 + mimeType: 'application/json', 95 + title: `Lexicons (${filtered.length})`, 96 + }, 97 + }; 98 + }, 99 + 100 + create: async ({ search }) => { 101 + if (!currentSession) { 102 + openLexWindow(); 103 + return { success: false, message: 'Not logged in. Opening Lexicon Studio...' }; 104 + } 105 + const nsid = (search && search.includes('.')) 106 + ? search.trim() 107 + : (search && search.includes(' — ')) 108 + ? search.split(' — ').pop().trim() 109 + : null; 110 + 111 + if (!nsid) { 112 + openLexWindow(); 113 + return { success: true }; 114 + } 115 + 116 + const friendlyName = nsidToFriendlyName(nsid); 117 + return { 118 + success: true, 119 + output: { 120 + data: { nsid }, 121 + mimeType: 'application/x-lexicon-form', 122 + title: friendlyName, 123 + interactive: 'peek://lex/chain-form.html', 124 + }, 125 + }; 126 + }, 127 + 128 + browse: async () => { 129 + openLexWindow(); 130 + }, 131 + }); 132 + 133 + console.log('[lex] Noun registered: lex'); 134 + } 135 + 136 + // ============================================================================ 33 137 // Template pack 34 138 // ============================================================================ 35 139 ··· 103 207 if (currentProfile) { 104 208 await api.settings.set(PROFILE_KEY, currentProfile); 105 209 } 106 - // Notify background script of session change 107 - api.pubsub.publish('lex:session-changed', { session: currentSession }, api.scopes.GLOBAL); 210 + // Session changed — refresh collection NSIDs for noun query (non-blocking) 211 + discoverCollections(); 108 212 } catch (err) { 109 213 console.warn('[lex] Failed to save session:', err); 110 214 } ··· 130 234 async function clearSession() { 131 235 currentSession = null; 132 236 currentProfile = null; 237 + knownCollectionNsids = []; 133 238 if (api) { 134 239 try { 135 240 await api.settings.set(STORAGE_KEY, null); 136 241 await api.settings.set(PROFILE_KEY, null); 137 - // Notify background script of session clear 138 - api.pubsub.publish('lex:session-changed', { session: null }, api.scopes.GLOBAL); 139 242 } catch (err) { 140 243 console.warn('[lex] Failed to clear session:', err); 141 244 } ··· 672 775 lexiconTemplate.setSession(currentSession); 673 776 updateFormTemplateUploader(); 674 777 showAccountView(); 778 + 779 + // Discover collection NSIDs for noun query (non-blocking) 780 + discoverCollections(); 675 781 676 782 // Refresh profile in background 677 783 try { ··· 1744 1850 // Bootstrap 1745 1851 // ============================================================================ 1746 1852 1747 - document.addEventListener('DOMContentLoaded', init); 1853 + (async () => { 1854 + console.log('[ext:lex] home.js loaded'); 1855 + 1856 + // Initialize v2 tile — validates capability token with main process 1857 + console.log('[ext:lex] initializing v2 tile'); 1858 + await api.initialize(); 1859 + 1860 + // Register noun (formerly background.js responsibility) 1861 + registerLexNoun(); 1862 + 1863 + // Register shutdown handler 1864 + api.onShutdown(() => { 1865 + console.log('[ext:lex] received shutdown'); 1866 + unregisterNoun('lex'); 1867 + }); 1868 + 1869 + // Initialize UI once DOM is ready 1870 + if (document.readyState === 'loading') { 1871 + document.addEventListener('DOMContentLoaded', init); 1872 + } else { 1873 + await init(); 1874 + } 1875 + })();
+6 -15
features/lex/manifest.json
··· 8 8 "builtin": true, 9 9 "tiles": [ 10 10 { 11 - "id": "background", 12 - "type": "background", 13 - "url": "background.html", 14 - "lazy": true 15 - }, 16 - { 17 11 "id": "home", 18 - "type": "window", 19 12 "url": "home.html", 20 - "windowHints": { 21 - "role": "workspace", 22 - "key": "lex-home", 23 - "width": 860, 24 - "height": 620, 25 - "title": "Lexicon Studio" 26 - } 13 + "width": 860, 14 + "height": 620, 15 + "title": "Lexicon Studio", 16 + "role": "workspace", 17 + "key": "lex-home", 18 + "resident": true 27 19 }, 28 20 { 29 21 "id": "chain-form", ··· 44 36 "ext:ready", 45 37 "ext:lex:shutdown", 46 38 "app:shutdown", 47 - "lex:session-changed", 48 39 "lex:recent-lexicons-changed", 49 40 "lex:open-create", 50 41 "chain-popup:content",