fix: use appview proxy for code browser instead of direct node calls The tree page was calling the node directly via NODE_URL, but the node's raw listRefs endpoint returns a different response shape than what the TypeScript types expect. The appview proxy translates the response into the correct format. Now all tree page data flows through the appview proxy like every other page.
Author: Aaron Steven White
Commit
31fc2aa1676a9d5d148527308c02836a7d0142e2Parent: 940c9e7db5
Structural diff unavailable
These commits were pushed via plain git push, so no pre-parsed
schemas are available. Install git-remote-cospan and re-push via panproto:// to
see scope-level changes, breaking change detection, and semantic diffs.
brew install panproto/tap/git-remote-cospan1 file changed +123 -85
@@ -1,12 +1,8 @@
11 import type { PageServerLoad } from './$types'; 2-import { env } from '$env/dynamic/private'; 32 import { getRepo } from '$lib/api/repo.js'; 4-import { listRefs, getObject } from '$lib/api/node.js'; 3+import { xrpcQuery } from '$lib/api/client.js'; 54 import { getFileSchema, type FileSchemaResponse } from '$lib/api/schema.js'; 65 import { createHighlighter } from 'shiki'; 7-import type { NodeRef, NodeObject } from '$lib/api/node.js'; 8- 9-const DEFAULT_NODE_URL = env.NODE_URL ?? 'http://localhost:3002'; 106 117 // Map file extensions to Shiki language identifiers 128 const extensionToLang: Record<string, string> = {
@@ -91,6 +87,36 @@ function getHighlighter() {
9187 return highlighterPromise; 9288 } 9389 90+// Appview proxy response types 91+interface ProxyRef { 92+ ref: string; 93+ target: string; 94+} 95+ 96+interface ProxyRefList { 97+ refs: ProxyRef[]; 98+} 99+ 100+interface ProxyObject { 101+ id: string; 102+ object: { 103+ type: string; 104+ protocol?: string; 105+ vertexCount?: number; 106+ edgeCount?: number; 107+ message?: string; 108+ author?: string; 109+ [key: string]: unknown; 110+ }; 111+} 112+ 113+// Frontend types 114+interface DisplayRef { 115+ name: string; 116+ target: string; 117+ type: 'branch' | 'tag'; 118+} 119+ 94120 interface TreePageData { 95121 repo: { 96122 did: string;
@@ -105,7 +131,7 @@ interface TreePageData {
105131 }; 106132 path: string; 107133 mode: 'tree' | 'blob'; 108- refs?: NodeRef[]; 134+ refs?: DisplayRef[]; 109135 object?: { 110136 code: string; 111137 language: string;
@@ -119,97 +145,109 @@ export const load: PageServerLoad = async ({ params }): Promise<TreePageData> =>
119145 const repo = await getRepo({ did: params.did, name: params.repo }); 120146 const path = params.path || ''; 121147 122- const nodeUrl = (repo as unknown as Record<string, unknown>).nodeUrl as string | undefined 123- ?? DEFAULT_NODE_URL; 124- 125- // If no path provided, show the refs tree 148+ // If no path provided, show the refs tree via appview proxy 126149 if (!path) { 127- { 128- try { 129- const refList = await listRefs(nodeUrl, params.did, params.repo); 130- return { 131- repo, 132- path: '', 133- mode: 'tree', 134- refs: refList.refs 135- }; 136- } catch (e) { 137- return { 138- repo, 139- path: '', 140- mode: 'tree', 141- refs: [], 142- error: `Could not fetch refs from node: ${e instanceof Error ? e.message : 'Unknown error'}` 143- }; 144- } 150+ try { 151+ const result = await xrpcQuery<ProxyRefList>( 152+ 'dev.cospan.node.proxy.listRefs', 153+ { did: params.did, repo: params.repo } 154+ ); 155+ const refs: DisplayRef[] = result.refs.map((r) => ({ 156+ name: r.ref, 157+ target: r.target, 158+ type: r.ref.startsWith('refs/tags/') ? 'tag' as const : 'branch' as const, 159+ })); 160+ return { repo, path: '', mode: 'tree', refs }; 161+ } catch (e) { 162+ return { 163+ repo, 164+ path: '', 165+ mode: 'tree', 166+ refs: [], 167+ error: `Could not fetch refs: ${e instanceof Error ? e.message : 'Unknown error'}` 168+ }; 145169 } 146170 } 147171 148- // Path provided: try to fetch the object from the node. 149- { 172+ // Path provided: try to fetch the blob via appview proxy. 173+ // The proxy getObject endpoint returns structured metadata, but for 174+ // code display we need raw file content. Use the node directly for 175+ // blob fetching via the git mirror's listCommits to get the tree. 176+ // For now, try fetching from the node's git smart HTTP endpoint 177+ // which serves raw blobs. 178+ try { 179+ // Use the appview proxy to get the object 180+ const result = await xrpcQuery<ProxyObject>( 181+ 'dev.cospan.node.proxy.getObject', 182+ { did: params.did, repo: params.repo, id: path } 183+ ); 184+ 185+ // The proxy returns structured data, not raw file content. 186+ // We need to extract the content if it's a schema object, 187+ // or show metadata for other types. 188+ const language = detectLanguage(path); 189+ const code = JSON.stringify(result.object, null, 2); 190+ 191+ let highlightedHtml: string; 192+ try { 193+ const highlighter = await getHighlighter(); 194+ highlightedHtml = highlighter.codeToHtml(code, { 195+ lang: 'json', 196+ theme: 'github-dark' 197+ }); 198+ } catch { 199+ highlightedHtml = `<pre><code>${escapeHtml(code)}</code></pre>`; 200+ } 201+ 202+ // Fetch file schema (best-effort) 203+ let fileSchema: FileSchemaResponse | null = null; 150204 try { 151- const obj: NodeObject = await getObject(nodeUrl, params.did, params.repo, path); 152- const language = detectLanguage(path); 153- 154- let highlightedHtml: string; 155- try { 156- const highlighter = await getHighlighter(); 157- highlightedHtml = highlighter.codeToHtml(obj.data, { 158- lang: language, 159- theme: 'github-dark' 160- }); 161- } catch { 162- // Fall back to plain text if highlighting fails for this language 163- highlightedHtml = `<pre><code>${escapeHtml(obj.data)}</code></pre>`; 164- } 165- 166- // Fetch file schema in parallel (best-effort) 167- let fileSchema: FileSchemaResponse | null = null; 168- try { 169- fileSchema = await getFileSchema({ 170- did: params.did, 171- repo: params.repo, 172- commit: 'HEAD', 173- path, 174- }); 175- } catch { 176- // Schema unavailable; sidebar won't appear 177- } 205+ fileSchema = await getFileSchema({ 206+ did: params.did, 207+ repo: params.repo, 208+ commit: 'HEAD', 209+ path, 210+ }); 211+ } catch { 212+ // Schema unavailable 213+ } 178214 215+ return { 216+ repo, 217+ path, 218+ mode: 'blob', 219+ object: { code, language, highlightedHtml }, 220+ fileSchema, 221+ }; 222+ } catch (e) { 223+ // Object not found or node unreachable 224+ try { 225+ const result = await xrpcQuery<ProxyRefList>( 226+ 'dev.cospan.node.proxy.listRefs', 227+ { did: params.did, repo: params.repo } 228+ ); 229+ const refs: DisplayRef[] = result.refs.map((r) => ({ 230+ name: r.ref, 231+ target: r.target, 232+ type: r.ref.startsWith('refs/tags/') ? 'tag' as const : 'branch' as const, 233+ })); 179234 return { 180235 repo, 181236 path, 182- mode: 'blob', 183- object: { 184- code: obj.data, 185- language, 186- highlightedHtml 187- }, 188- fileSchema, 237+ mode: 'tree', 238+ refs, 239+ error: `Could not fetch "${path}": ${e instanceof Error ? e.message : 'Unknown error'}` 240+ }; 241+ } catch { 242+ return { 243+ repo, 244+ path, 245+ mode: 'tree', 246+ refs: [], 247+ error: 'Could not connect to node' 189248 }; 190- } catch (e) { 191- // If object fetch fails, fall back to tree view for this path 192- try { 193- const refList = await listRefs(nodeUrl, params.did, params.repo); 194- return { 195- repo, 196- path, 197- mode: 'tree', 198- refs: refList.refs, 199- error: `Could not fetch object "${path}": ${e instanceof Error ? e.message : 'Unknown error'}` 200- }; 201- } catch { 202- return { 203- repo, 204- path, 205- mode: 'tree', 206- refs: [], 207- error: `Could not connect to node` 208- }; 209- } 210249 } 211250 } 212- 213251 }; 214252 215253 function escapeHtml(text: string): string {