This repository has no description
0

Configure Feed

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

fix: frontend handles island summaries without full vertex/edge data

- Island interface: vertices, edges, vertexMeta now optional
- Card renderer shows title preview for summaries, graph for full data
- summarizeIsland checks title before vertices
- Fixed landing page to query D1 directly instead of internal fetch

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>

author
nandi
co-author
Letta Code
date (May 14, 2026, 2:59 AM UTC) commit 8c325252 parent 28ea0d15
+76 -30
+38 -24
src/frontend/app.ts
··· 27 27 28 28 interface Island { 29 29 id: string; 30 - vertices: string[]; 31 - edges: IslandEdge[]; 32 - vertexMeta: Record<string, VertexMeta>; 30 + lexmin?: string; 31 + vertices?: string[]; 32 + edges?: IslandEdge[]; 33 + vertexMeta?: Record<string, VertexMeta>; 33 34 summary: string | null; 35 + title?: string | null; 34 36 strata: StrataData | null; 37 + recordUri?: string | null; 35 38 } 36 39 37 40 interface StrataData { ··· 137 140 const cy = this.height / 2; 138 141 const r = Math.min(this.width, this.height) * 0.35; 139 142 140 - for (const uri of island.vertices) { 141 - const meta = island.vertexMeta[uri]; 143 + for (const uri of island.vertices!) { 144 + const meta = island.vertexMeta![uri]; 142 145 const angle = Math.random() * Math.PI * 2; 143 146 const dist = Math.random() * r; 144 147 const node: GraphNode = { ··· 155 158 } 156 159 157 160 // Build edges 158 - for (const e of island.edges) { 161 + for (const e of island.edges!) { 159 162 const sNode = this.nodeMap.get(e.source); 160 163 const tNode = this.nodeMap.get(e.target); 161 164 if (!sNode || !tNode) continue; ··· 461 464 </div> 462 465 `; 463 466 464 - // Initialize graphs 467 + // Initialize graphs (only for islands with full data) 465 468 document.querySelectorAll("canvas.island-graph").forEach(canvas => { 466 469 const islandId = (canvas as HTMLElement).dataset.islandId; 467 470 const island = islands.find(i => i.id === islandId); 468 - if (island) { 469 - new ForceGraph(canvas as HTMLCanvasElement, island); 471 + if (island && island.vertices && island.edges) { 472 + new ForceGraph(canvas as HTMLCanvasElement, island as any); 470 473 } 471 474 }); 472 475 ··· 482 485 } 483 486 484 487 function summarizeIsland(island: Island): string { 488 + if (island.title) return island.title; 485 489 if (island.summary) return island.summary; 486 490 487 - // Fallback: build from metadata 491 + // Fallback: build from metadata (only if we have full data) 488 492 const vertices = island.vertices; 489 - const meta = island.vertexMeta; 493 + if (!vertices || vertices.length === 0) return "Island"; 494 + 495 + const meta = island.vertexMeta || {}; 490 496 491 497 const domains = new Map<string, number>(); 492 498 for (const v of vertices) { ··· 515 521 const hasStrata = !!island.strata; 516 522 const strataBtnLabel = hasStrata ? "View Strata" : "Strata"; 517 523 const strataBtnClass = hasStrata ? "strata-btn island-strata-btn has-strata" : "strata-btn island-strata-btn"; 524 + const hasFullData = !!(island.vertices && island.edges); 525 + const sizeLabel = hasFullData 526 + ? `${island.vertices!.length} nodes &middot; ${island.edges!.length} edges` 527 + : ""; 528 + 529 + const graphOrTitle = hasFullData 530 + ? `<canvas class="island-graph" data-island-id="${escHtml(island.id)}" width="400" height="260"></canvas>` 531 + : `<div class="island-title-preview">${escHtml(summary)}</div>`; 518 532 519 533 return ` 520 534 <div class="island-card" data-island-id="${escHtml(island.id)}"> 521 - <canvas class="island-graph" data-island-id="${escHtml(island.id)}" width="400" height="260"></canvas> 535 + ${graphOrTitle} 522 536 <div class="island-card-footer"> 523 - <span class="island-size">${island.vertices.length} nodes &middot; ${island.edges.length} edges</span> 524 - <p class="island-summary">${escHtml(summary)}</p> 537 + ${sizeLabel ? `<span class="island-size">${sizeLabel}</span>` : ""} 538 + ${hasFullData ? `<p class="island-summary">${escHtml(summary)}</p>` : ""} 525 539 <button class="${strataBtnClass}" data-island-id="${escHtml(island.id)}">${strataBtnLabel}</button> 526 540 </div> 527 541 </div> ··· 541 555 return; 542 556 } 543 557 544 - const edgeList = island.edges.map(e => { 558 + const edgeList = island.edges!.map(e => { 545 559 const icon = connectionTypeIcon(e.connectionType); 546 560 const type = connectionTypeLabel(e.connectionType); 547 - const sourceMeta = island.vertexMeta[e.source]; 548 - const targetMeta = island.vertexMeta[e.target]; 561 + const sourceMeta = island.vertexMeta![e.source]; 562 + const targetMeta = island.vertexMeta![e.target]; 549 563 const sourceTitle = sourceMeta?.title || truncateUri(e.source); 550 564 const targetTitle = targetMeta?.title || truncateUri(e.target); 551 565 return ` ··· 563 577 <div class="island-detail"> 564 578 <a href="/" class="back-link">&larr; Explore</a> 565 579 <h2>Island</h2> 566 - <p class="island-meta">${island.vertices.length} vertices &middot; ${island.edges.length} edges</p> 580 + <p class="island-meta">${island.vertices!.length} vertices &middot; ${island.edges!.length} edges</p> 567 581 568 582 <canvas class="island-graph-large" id="island-detail-graph" width="900" height="500"></canvas> 569 583 ··· 654 668 655 669 // Relationships — link entity names to their Semble pages 656 670 const titleToUri = new Map<string, string>(); 657 - for (const uri of island.vertices) { 658 - const m = island.vertexMeta[uri]; 671 + for (const uri of island.vertices!) { 672 + const m = island.vertexMeta![uri]; 659 673 const title = m?.title; 660 674 if (title && title.length > 3 && !title.startsWith("http") && !title.startsWith("at://")) { 661 675 titleToUri.set(title, uri); ··· 721 735 <div class="strata-page"> 722 736 <a href="/" class="back-link">&larr; Explore</a> 723 737 <p class="island-summary-large">${escHtml(summary)}</p> 724 - <p class="island-meta">${island.vertices.length} vertices &middot; ${island.edges.length} edges</p> 738 + <p class="island-meta">${island.vertices!.length} vertices &middot; ${island.edges!.length} edges</p> 725 739 726 740 <canvas class="island-graph-large" id="strata-graph" width="900" height="500"></canvas> 727 741 ··· 736 750 737 751 <div class="strata-section"> 738 752 <h3>Edges</h3> 739 - <div class="strata-edges">${island.edges.map(e => { 740 - const sourceMeta = island.vertexMeta[e.source]; 741 - const targetMeta = island.vertexMeta[e.target]; 753 + <div class="strata-edges">${island.edges!.map(e => { 754 + const sourceMeta = island.vertexMeta![e.source]; 755 + const targetMeta = island.vertexMeta![e.target]; 742 756 const sourceTitle = sourceMeta?.title || truncateUri(e.source); 743 757 const targetTitle = targetMeta?.title || truncateUri(e.target); 744 758 const type = connectionTypeLabel(e.connectionType);
+38 -6
src/worker.ts
··· 381 381 app.get("/", async (c) => { 382 382 let islandsJson = "[]"; 383 383 try { 384 - const resp = await fetch(new URL("/xrpc/org.latha.strata.getIslands", c.req.url)); 385 - const data = await resp.json() as { islands: any[] }; 386 - islandsJson = JSON.stringify(data.islands); 384 + await ensureContrailReady(db); 385 + const rows = await db 386 + .prepare("SELECT uri, record FROM records_strata ORDER BY time_us DESC LIMIT 50") 387 + .all<{ uri: string; record: string }>(); 388 + 389 + const islands: any[] = []; 390 + for (const row of rows.results || []) { 391 + try { 392 + const rec = JSON.parse(row.record); 393 + const lexmin = rec.source?.uri; 394 + if (!lexmin) continue; 395 + const lexminHash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(lexmin)); 396 + const id = Array.from(new Uint8Array(lexminHash)).map(b => b.toString(16).padStart(2, "0")).join("").slice(0, 16); 397 + islands.push({ 398 + id, 399 + lexmin, 400 + title: rec.analysis?.title || null, 401 + recordUri: row.uri, 402 + }); 403 + } catch {} 404 + } 405 + islandsJson = JSON.stringify(islands); 387 406 } catch (e) { 388 407 console.error("Failed to load islands:", e); 389 408 } ··· 799 818 app.get(path, async (c) => { 800 819 let islandsJson = "[]"; 801 820 try { 802 - const resp = await fetch(new URL("/xrpc/org.latha.strata.getIslands", c.req.url)); 803 - const data = await resp.json() as { islands: any[] }; 804 - islandsJson = JSON.stringify(data.islands); 821 + await ensureContrailReady(db); 822 + const rows = await db 823 + .prepare("SELECT uri, record FROM records_strata ORDER BY time_us DESC LIMIT 50") 824 + .all<{ uri: string; record: string }>(); 825 + const islands: any[] = []; 826 + for (const row of rows.results || []) { 827 + try { 828 + const rec = JSON.parse(row.record); 829 + const lexmin = rec.source?.uri; 830 + if (!lexmin) continue; 831 + const lexminHash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(lexmin)); 832 + const id = Array.from(new Uint8Array(lexminHash)).map(b => b.toString(16).padStart(2, "0")).join("").slice(0, 16); 833 + islands.push({ id, lexmin, title: rec.analysis?.title || null, recordUri: row.uri }); 834 + } catch {} 835 + } 836 + islandsJson = JSON.stringify(islands); 805 837 } catch {} 806 838 807 839 const page = LANDING_PAGE.replace(