experiments in a post-browser web
10
fork

Configure Feed

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

fix(tags): add protocol 404 logging, handle all item types, fix filter counts

- protocol.ts: log FILE NOT FOUND with path and request URL instead of silent failures
- tags/home.js: add entity/feed/series type icons, default to ? for unknown types
- tags/home.js: filter counts now only count tagged items (matching displayed results)

+53 -18
+29 -13
backend/electron/protocol.ts
··· 11 11 12 12 import { protocol, net } from 'electron'; 13 13 import path from 'node:path'; 14 + import fs from 'node:fs'; 14 15 import { pathToFileURL } from 'node:url'; 15 16 import { getDb } from './datastore.js'; 16 17 import { DEBUG } from './config.js'; ··· 42 43 'lit-element': 'node_modules/lit-element/lit-element.js', 43 44 'lit-element/': 'node_modules/lit-element/' 44 45 }; 46 + 47 + /** 48 + * Fetch a local file, logging a warning if it doesn't exist. 49 + * Replaces bare net.fetch(fileURL) calls so we get visibility into 404s. 50 + */ 51 + function fetchFile(absolutePath: string, requestUrl: string): Response { 52 + if (!fs.existsSync(absolutePath)) { 53 + console.error(`[protocol] FILE NOT FOUND: ${absolutePath} (requested: ${requestUrl})`); 54 + return new Response('Not Found', { status: 404 }); 55 + } 56 + return net.fetch(pathToFileURL(absolutePath).toString()) as unknown as Response; 57 + } 45 58 46 59 /** 47 60 * Resolve a bare import specifier to a file path ··· 187 200 * The protocol request handler - extracted so it can be registered on multiple sessions 188 201 */ 189 202 async function handleProtocolRequest(req: Request): Promise<Response> { 203 + try { 190 204 let { host, pathname } = new URL(req.url); 191 205 192 206 // trim leading slash ··· 220 234 if (resolvedPath) { 221 235 DEBUG && console.log(`[protocol] Resolved bare import: ${bareSpecifier} -> ${resolvedPath}`); 222 236 const absolutePath = path.resolve(rootDir, resolvedPath); 223 - const fileURL = pathToFileURL(absolutePath).toString(); 224 - return net.fetch(fileURL); 237 + return fetchFile(absolutePath, req.url); 225 238 } 226 239 227 240 // Handle extension content: peek://ext/{ext-id}/{path} ··· 247 260 248 261 DEBUG && console.log(`[protocol] ext ${extId}/${extPath} -> ${absolutePath}`); 249 262 250 - const fileURL = pathToFileURL(absolutePath).toString(); 251 - return net.fetch(fileURL); 263 + return fetchFile(absolutePath, req.url); 252 264 } 253 265 254 266 // Handle extensions infrastructure: peek://extensions/{path} ··· 263 275 return new Response('Forbidden', { status: 403 }); 264 276 } 265 277 266 - const fileURL = pathToFileURL(absolutePath).toString(); 267 - return net.fetch(fileURL); 278 + return fetchFile(absolutePath, req.url); 268 279 } 269 280 270 281 // Handle theme files: peek://theme/{path} or peek://theme/{themeId}/{path} ··· 300 311 return new Response('Forbidden', { status: 403 }); 301 312 } 302 313 303 - const fileURL = pathToFileURL(absolutePath).toString(); 304 - 305 314 // For CSS and font files, add no-cache headers to ensure theme changes take effect 306 315 if (themePath.endsWith('.css') || themePath.endsWith('.woff2') || themePath.endsWith('.woff')) { 316 + if (!fs.existsSync(absolutePath)) { 317 + console.error(`[protocol] FILE NOT FOUND: ${absolutePath} (requested: ${req.url})`); 318 + return new Response('Not Found', { status: 404 }); 319 + } 320 + const fileURL = pathToFileURL(absolutePath).toString(); 307 321 const response = await net.fetch(fileURL); 308 322 const body = await response.arrayBuffer(); 309 323 return new Response(body, { ··· 317 331 }); 318 332 } 319 333 320 - return net.fetch(fileURL); 334 + return fetchFile(absolutePath, req.url); 321 335 } 322 336 323 337 // Handle per-extension hosts: peek://{ext-id}/{path} ··· 335 349 return new Response('Forbidden', { status: 403 }); 336 350 } 337 351 338 - const fileURL = pathToFileURL(absolutePath).toString(); 339 - return net.fetch(fileURL); 352 + return fetchFile(absolutePath, req.url); 340 353 } 341 354 342 355 let relativePath = pathname; ··· 349 362 } 350 363 351 364 const absolutePath = path.resolve(rootDir, relativePath); 352 - const fileURL = pathToFileURL(absolutePath).toString(); 353 365 354 - return net.fetch(fileURL); 366 + return fetchFile(absolutePath, req.url); 367 + } catch (err) { 368 + console.error(`[protocol] Failed to handle ${req.url}:`, err); 369 + return new Response('Not Found', { status: 404 }); 370 + } 355 371 } 356 372 357 373 /**
+24 -5
extensions/tags/home.js
··· 720 720 * Update filter button counts 721 721 */ 722 722 const updateFilterCounts = () => { 723 - const pageCount = state.items.filter(item => !item.type || item.type === 'url' || item.uri).length; 724 - const textCount = state.items.filter(item => item.type === 'text').length; 725 - const tagsetCount = state.items.filter(item => item.type === 'tagset').length; 726 - const imageCount = state.items.filter(item => item.type === 'image').length; 723 + // Only count items that have tags (matching what getFilteredItems shows) 724 + const tagged = state.items.filter(item => { 725 + const tags = state.itemTags.get(item.id); 726 + return tags && tags.length > 0; 727 + }); 728 + 729 + const pageCount = tagged.filter(item => !item.type || item.type === 'url' || item.uri).length; 730 + const textCount = tagged.filter(item => item.type === 'text').length; 731 + const tagsetCount = tagged.filter(item => item.type === 'tagset').length; 732 + const imageCount = tagged.filter(item => item.type === 'image').length; 727 733 728 734 document.querySelector('[data-count="page"]').textContent = pageCount; 729 735 document.querySelector('[data-count="text"]').textContent = textCount; ··· 1168 1174 const itemType = item.type || 'url'; 1169 1175 const noteUrl = (itemType === 'text') ? extractUrl(item.content) : null; 1170 1176 1171 - let title, subtitle, faviconUrl; 1177 + let title, subtitle; 1178 + let faviconUrl = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">?</text></svg>'; 1172 1179 1173 1180 if (isAddress) { 1174 1181 title = item.title || item.uri; ··· 1196 1203 title = item.content || 'Image'; 1197 1204 subtitle = 'Image'; 1198 1205 faviconUrl = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">🖼️</text></svg>'; 1206 + } else if (itemType === 'entity') { 1207 + title = item.title || item.content || 'Entity'; 1208 + subtitle = 'Entity'; 1209 + faviconUrl = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">💎</text></svg>'; 1210 + } else if (itemType === 'feed') { 1211 + title = item.title || item.content || 'Feed'; 1212 + subtitle = 'Feed'; 1213 + faviconUrl = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">📡</text></svg>'; 1214 + } else if (itemType === 'series') { 1215 + title = item.title || item.content || 'Series'; 1216 + subtitle = 'Series'; 1217 + faviconUrl = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">📚</text></svg>'; 1199 1218 } 1200 1219 } 1201 1220