Skip to content

Commit e4da54f

Browse files
feat(browser): add --browser panes, a pane-aware data browser augmented by local panes
A third selectable data browser alongside json and folder, chosen with the existing --browser switch as 'panes'. It reuses the folder/JSON rendering and adds type-driven panes for single resources: read the #dataisland resource, match the primary subject's @type, and render the first pod-local pane that handles it (collapsible Source underneath); containers and unknown types fall back exactly like the folder browser. Crucially the browser ships NO panes — they live on the pod under /public/panes/ and are discovered/imported at render time. Adding a pane is 'drop a module on your pod', never 'fork the shipped browser'. - data-browser-panes.js: the shell (folder/JSON + /public/panes/ discovery) - data-browser-panes.css: empty sibling stub (JSS auto-fetches it) - index.js / lib/start.js: widen --browser to accept 'panes' - examples/panes/{bookmark,tracker}.js: reference panes (bookmark:Bookmark, wf:Tracker) data-browser-folder.js and data-browser.js are unchanged — fully opt-in. Refs #69
1 parent d277bc7 commit e4da54f

6 files changed

Lines changed: 355 additions & 4 deletions

File tree

data-browser-panes.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/* Intentionally empty. Styles live inside data-browser-panes.js so the
2+
browser is a single file. JSS auto-fetches this sibling URL
3+
(javascript-solid-server/src/mashlib/index.js:307); shipping an empty
4+
200 keeps the console warning-free until JSS makes the .css fetch
5+
optional. */

data-browser-panes.js

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
// jspod pane-aware data browser. Sibling of data-browser.js (json) and
2+
// data-browser-folder.js (folder); selected with `--browser panes`. JSS
3+
// embeds the resource as JSON-LD in #dataisland; this script:
4+
// 1. If the resource is an LDP container, render breadcrumb + table
5+
// (NAME / TYPE / SIZE / MODIFIED) — identical to the folder browser.
6+
// 2. If it's a single resource, load the pod's own panes from
7+
// /public/panes/ and render the first whose canHandle(@type) matches,
8+
// with a collapsible Source. Panes are NOT shipped here — they live on
9+
// the pod and are augmented locally (drop a module in /public/panes/).
10+
// 3. No matching pane → pretty-printed JSON-LD (same as data-browser.js).
11+
//
12+
// Single file, no build, no framework. Local pane contract (ES module):
13+
// export default { canHandle(node, h) -> boolean, render(node, h) -> htmlString }
14+
// where h = { escape, prop, propAll, idOf, types, host, fmtDate, localName }.
15+
16+
document.head.insertAdjacentHTML('beforeend', `<style>
17+
body{font:14px/1.55 system-ui,-apple-system,sans-serif;margin:0;color:#222;background:#f3eee5}
18+
.db{max-width:880px;margin:2em auto;padding:0 1em}
19+
.db-nav{display:flex;gap:1.2em;margin:0 0 .75em;padding:.6em 1em;background:#fff;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,.05);font-size:.9em}
20+
.db-card{background:#fff;border-radius:12px;box-shadow:0 2px 12px rgba(0,0,0,.08);padding:1.5em 2em}
21+
.db-bc{display:flex;align-items:center;flex-wrap:wrap;gap:.25em;font-size:.95em;margin-bottom:.5em}
22+
.db-bc a{color:#7a4ed8;font-weight:500}
23+
.db-bc .sep{color:#888}
24+
.db-bc .here{color:#222;font-weight:600}
25+
.db-title{margin:.2em 0 .15em;font-size:1.6em;font-weight:500}
26+
.db-url{color:#888;font-size:.85em;font-family:"SFMono-Regular",Consolas,monospace;margin-bottom:.6em;word-break:break-all}
27+
.db-count{color:#666;font-size:.9em;margin:.6em 0 1em}
28+
.db-table{width:100%;border-collapse:collapse}
29+
.db-table th{text-align:left;padding:9px 8px;font-weight:600;font-size:11px;color:#666;text-transform:uppercase;letter-spacing:.06em;border-bottom:2px solid #7a4ed8}
30+
.db-table td{padding:11px 8px;border-bottom:1px solid #eee;font-size:14px}
31+
.db-table tr:hover td{background:#faf7f0}
32+
.db-name{display:flex;align-items:center;gap:.6em}
33+
.db-name a{color:#7a4ed8;text-decoration:none}
34+
.db-name a:hover{text-decoration:underline}
35+
.db-icon{font-size:18px;width:24px;text-align:center;flex-shrink:0}
36+
.db-meta{color:#666;white-space:nowrap;font-size:13px}
37+
.db-num{text-align:right}
38+
.db-empty{padding:2em;text-align:center;color:#888}
39+
.db-pre{padding:1.5em;background:#fff;border-radius:12px;box-shadow:0 2px 12px rgba(0,0,0,.08);overflow:auto;white-space:pre-wrap;word-break:break-all;margin:0}
40+
.db-pre a{color:#0a66c2;text-decoration:none}
41+
.db-pre a:hover{text-decoration:underline}
42+
.db-pre .pod-os{color:#7a4ed8;text-decoration:none;font-size:1em;font-weight:600;margin:0 .15em 0 .3em;opacity:.65;transition:opacity .15s}
43+
.db-pre .pod-os:hover{opacity:1;text-decoration:none}
44+
.db-pane-wrap{max-width:880px;margin:0 auto}
45+
.db-src{max-width:880px;margin:1em auto;padding:0 1em;color:#999;font-size:.85em}
46+
.db-src summary{cursor:pointer;user-select:none}
47+
</style>`);
48+
49+
(function () {
50+
main();
51+
52+
async function main() {
53+
const here = window.location.pathname;
54+
const up = (here === '/' || !here)
55+
? null
56+
: (s => { const i = s.lastIndexOf('/'); return i === 0 ? '/' : s.slice(0, i) + '/'; })(here.replace(/\/$/, ''));
57+
const navHTML = `<nav class="db-nav">${
58+
up ? `<a href="${up}" title="One level up">↑ Up</a>` : ''
59+
}<a href="/">Home</a><a href="/account.html">Account</a><a href="/docs.html">Docs</a></nav>`;
60+
61+
let doc = null;
62+
try { doc = JSON.parse(document.getElementById('dataisland').textContent); } catch (e) {}
63+
64+
const items = doc ? parseContainer(doc, window.location.href) : null;
65+
const isContainer = Array.isArray(items);
66+
67+
const target = document.getElementById('mashlib');
68+
if (isContainer) {
69+
target.innerHTML = `<div class="db">${navHTML}<div class="db-card">${renderFolder(items, here)}</div></div>`;
70+
return;
71+
}
72+
73+
// Single resource: try a pod-local pane, else fall back to the JSON dump.
74+
const node = primaryNode(doc);
75+
const paneHTML = node ? await renderLocalPane(node) : null;
76+
if (paneHTML) {
77+
target.innerHTML = `<div class="db">${navHTML}<div class="db-pane-wrap">${paneHTML}</div>` +
78+
`<details class="db-src"><summary>Source</summary>${renderJson(doc)}</details></div>`;
79+
} else {
80+
target.innerHTML = `<div class="db">${navHTML}${renderJson(doc)}</div>`;
81+
}
82+
}
83+
84+
// ---- local panes: discovered from /public/panes/, augmentable per-pod ----
85+
86+
async function renderLocalPane(node) {
87+
const panes = await loadLocalPanes();
88+
if (!panes.length) return null;
89+
const h = { escape, prop, propAll, idOf, types: typesOf, host: hostOf, fmtDate: fmtDay, localName: localType };
90+
for (const p of panes) {
91+
try { if (p.canHandle(node, h)) return p.render(node, h); } catch (e) { /* skip */ }
92+
}
93+
return null;
94+
}
95+
96+
async function loadLocalPanes() {
97+
try {
98+
const base = new URL('/public/panes/', window.location.origin).href;
99+
const r = await fetch(base, { headers: { Accept: 'application/ld+json' } });
100+
if (!r.ok) return [];
101+
const items = parseContainer(await r.json(), base) || [];
102+
const urls = items
103+
.filter(it => it.type !== 'container' && it.url.endsWith('.js') && !nameOf(it.url).startsWith('.'))
104+
.map(it => it.url);
105+
const mods = await Promise.all(urls.map(u => import(u).then(m => m.default).catch(() => null)));
106+
return mods.filter(p => p && typeof p.canHandle === 'function' && typeof p.render === 'function');
107+
} catch (e) {
108+
return [];
109+
}
110+
}
111+
112+
// The primary subject of a single-resource doc: #this, else the @graph node
113+
// carrying an @type, else the doc itself.
114+
function primaryNode(d) {
115+
if (!d) return null;
116+
const graph = Array.isArray(d['@graph']) ? d['@graph'] : null;
117+
if (graph) {
118+
return graph.find(n => /(^|#)this$/.test(n['@id'] || '')) ||
119+
graph.find(n => n['@type']) || graph[0] || null;
120+
}
121+
return d;
122+
}
123+
function localType(t) { return String(t).split(/[#/:]/).pop(); }
124+
function typesOf(node) {
125+
const t = node && node['@type'];
126+
return (Array.isArray(t) ? t : (t == null ? [] : [t])).map(localType);
127+
}
128+
// Read a property tolerant of prefixed / aliased / expanded keys (pass short key).
129+
function prop(node, key) {
130+
if (!node) return undefined;
131+
for (const k of Object.keys(node)) {
132+
if (k.startsWith('@')) continue;
133+
if (k === key || k.endsWith(':' + key) || k.endsWith('/' + key) || k.endsWith('#' + key)) return node[k];
134+
}
135+
return undefined;
136+
}
137+
function propAll(node, key) { const v = prop(node, key); return v == null ? [] : (Array.isArray(v) ? v : [v]); }
138+
function idOf(v) { return v == null ? '' : (typeof v === 'string' ? v : (v['@id'] || v.id || '')); }
139+
function hostOf(u) { try { return new URL(u).host; } catch (e) { return u; } }
140+
function fmtDay(iso) {
141+
if (!iso) return '';
142+
const d = new Date(iso);
143+
return isNaN(d) ? '' : d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
144+
}
145+
146+
function renderFolder(items, path) {
147+
const folders = items.filter(i => i.type === 'container').length;
148+
const files = items.length - folders;
149+
items.sort((a, b) => {
150+
if (a.type !== b.type) return a.type === 'container' ? -1 : 1;
151+
return nameOf(a.url).localeCompare(nameOf(b.url));
152+
});
153+
154+
const segments = path.replace(/\/$/, '').split('/').filter(Boolean);
155+
const here = segments[segments.length - 1] || window.location.host;
156+
let acc = '/';
157+
const crumbs = [`<a href="/">${escape(window.location.host)}</a>`].concat(
158+
segments.map((s, i) => {
159+
acc += s + '/';
160+
return i === segments.length - 1
161+
? `<span class="sep">/</span><span class="here">${escape(decodeURIComponent(s))}</span>`
162+
: `<span class="sep">/</span><a href="${escape(acc)}">${escape(decodeURIComponent(s))}</a>`;
163+
})
164+
).join('');
165+
166+
const rows = items.length
167+
? `<table class="db-table">
168+
<thead><tr><th>NAME</th><th>TYPE</th><th class="db-num">SIZE</th><th>MODIFIED</th></tr></thead>
169+
<tbody>${items.map(rowHTML).join('')}</tbody>
170+
</table>`
171+
: `<div class="db-empty">Empty container</div>`;
172+
173+
return `
174+
<div class="db-bc">${crumbs}</div>
175+
<div class="db-title">${escape(decodeURIComponent(here))}</div>
176+
<div class="db-url">${escape(window.location.href)}</div>
177+
<div class="db-count">${folders} folder${folders === 1 ? '' : 's'}, ${files} file${files === 1 ? '' : 's'}</div>
178+
${rows}
179+
`;
180+
}
181+
182+
function rowHTML(it) {
183+
const name = nameOf(it.url);
184+
const tl = typeLabel(it);
185+
const icon = iconFor(it);
186+
return `<tr>
187+
<td class="db-name"><span class="db-icon">${icon}</span><a href="${escape(it.url)}">${escape(name)}${it.type === 'container' ? '/' : ''}</a></td>
188+
<td class="db-meta">${escape(tl)}</td>
189+
<td class="db-meta db-num">${it.size != null ? fmtBytes(it.size) : '—'}</td>
190+
<td class="db-meta">${it.modified ? fmtDate(it.modified) : '—'}</td>
191+
</tr>`;
192+
}
193+
194+
function renderJson(d) {
195+
if (!d) return `<div class="db-pre">No data.</div>`;
196+
const pretty = JSON.stringify(d, null, 2)
197+
.replace(/[<>&]/g, c => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;' }[c]))
198+
.replace(/https?:\/\/[^"\s]+/g, m =>
199+
`<a href="${m}">${m}</a><a class="pod-os" href="https://browser.pod-os.org/?uri=${encodeURIComponent(m)}" target="_blank" rel="noopener" title="View in pod-os">↗</a>`);
200+
return `<pre class="db-pre">${pretty}</pre>`;
201+
}
202+
203+
// ---- container parsing (JSON-LD with ldp:contains) ----
204+
205+
function parseContainer(doc, baseUrl) {
206+
const nodes = Array.isArray(doc?.['@graph']) ? doc['@graph'] : [doc];
207+
const container = nodes.find(n =>
208+
n['@id'] === baseUrl ||
209+
(typeof n['@id'] === 'string' && resolveUrl(n['@id'], baseUrl) === baseUrl)
210+
) || nodes[0];
211+
if (!container) return null;
212+
const raw = container['contains']
213+
?? container['ldp:contains']
214+
?? container['http://www.w3.org/ns/ldp#contains'];
215+
if (raw == null) return null;
216+
const arr = Array.isArray(raw) ? raw : [raw];
217+
return arr.map(c => {
218+
if (typeof c === 'string') return { url: resolveUrl(c, baseUrl), type: c.endsWith('/') ? 'container' : 'resource' };
219+
const url = resolveUrl(c['@id'], baseUrl);
220+
const types = [].concat(c['@type'] || []);
221+
const isC = types.some(t => /(?:#|\/)(?:BasicContainer|Container)$/.test(t)) || url.endsWith('/');
222+
return {
223+
url,
224+
type: isC ? 'container' : 'resource',
225+
size: c['stat:size'] ?? c['http://www.w3.org/ns/posix/stat#size'],
226+
modified: c['dcterms:modified'] ?? c['http://purl.org/dc/terms/modified'] ?? c['dc:modified'],
227+
};
228+
}).filter(x => x.url && x.url !== baseUrl);
229+
}
230+
231+
// ---- formatting helpers ----
232+
233+
function nameOf(url) {
234+
return decodeURIComponent(url.replace(/\/$/, '').split('/').pop() || url);
235+
}
236+
function typeLabel(it) {
237+
if (it.type === 'container') return 'Folder';
238+
const ext = nameOf(it.url).split('.').pop()?.toLowerCase() || '';
239+
const map = {
240+
html: 'HTML', htm: 'HTML',
241+
md: 'Markdown', markdown: 'Markdown',
242+
json: 'JSON', jsonld: 'JSON-LD',
243+
ttl: 'Turtle', n3: 'N3', rdf: 'RDF',
244+
css: 'CSS', js: 'JavaScript', ts: 'TypeScript',
245+
png: 'Image', jpg: 'Image', jpeg: 'Image', gif: 'Image', webp: 'Image', svg: 'SVG', avif: 'Image',
246+
mp3: 'Audio', ogg: 'Audio', wav: 'Audio', flac: 'Audio',
247+
mp4: 'Video', webm: 'Video', mov: 'Video',
248+
pdf: 'PDF',
249+
txt: 'Text', text: 'Text',
250+
acl: 'ACL',
251+
};
252+
return map[ext] || (ext ? ext.toUpperCase() : '—');
253+
}
254+
function iconFor(it) {
255+
if (it.type === 'container') return '📁';
256+
const ext = nameOf(it.url).split('.').pop()?.toLowerCase() || '';
257+
if (/^(html|htm)$/.test(ext)) return '🌐';
258+
if (/^(json|jsonld|ttl|n3|rdf)$/.test(ext)) return '🔗';
259+
if (/^(md|markdown|txt|text)$/.test(ext)) return '📝';
260+
if (/^(png|jpg|jpeg|gif|webp|svg|avif)$/.test(ext)) return '🖼';
261+
if (/^(mp3|ogg|wav|flac)$/.test(ext)) return '🎵';
262+
if (/^(mp4|webm|mov)$/.test(ext)) return '🎬';
263+
if (ext === 'pdf') return '📕';
264+
if (ext === 'acl') return '🔒';
265+
return '📄';
266+
}
267+
function fmtBytes(n) {
268+
if (n < 1024) return n + ' B';
269+
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB';
270+
if (n < 1024 * 1024 * 1024) return (n / 1024 / 1024).toFixed(1) + ' MB';
271+
return (n / 1024 / 1024 / 1024).toFixed(2) + ' GB';
272+
}
273+
function fmtDate(iso) {
274+
try {
275+
return new Date(iso).toLocaleString(undefined, {
276+
month: 'short', day: 'numeric', year: 'numeric',
277+
hour: 'numeric', minute: '2-digit'
278+
});
279+
} catch { return String(iso); }
280+
}
281+
function resolveUrl(href, base) { try { return new URL(href, base).href; } catch { return href; } }
282+
function escape(s) {
283+
return String(s ?? '').replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
284+
}
285+
})();

examples/panes/bookmark.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Pod-local pane for bookmark:Bookmark (dc:title / bookmark:recalls / dcterms:created).
2+
// Loaded by the `--browser panes` data browser from /public/panes/.
3+
// Contract: export default { canHandle(node, h) -> bool, render(node, h) -> htmlString }
4+
// h = { escape, prop, propAll, idOf, types, host, fmtDate, localName }.
5+
6+
export default {
7+
canHandle(node, h) {
8+
return h.types(node).includes('Bookmark');
9+
},
10+
11+
render(node, h) {
12+
const url = h.idOf(h.prop(node, 'recalls'));
13+
const title = h.prop(node, 'title') || url || 'Untitled bookmark';
14+
const site = h.host(url);
15+
const date = h.fmtDate(h.prop(node, 'created'));
16+
const fav = url ? h.escape(new URL('/favicon.ico', url).href) : '';
17+
return `<div style="font-family:Inter,-apple-system,sans-serif;padding:24px 0 8px;">
18+
<div style="font-family:Georgia,serif;font-size:14px;font-style:italic;color:#999;margin-bottom:18px;">Bookmark</div>
19+
<a href="${h.escape(url)}" target="_blank" rel="noopener" style="display:flex;gap:18px;align-items:flex-start;text-decoration:none;color:inherit;border:1px solid rgba(127,127,127,0.18);border-radius:14px;padding:24px;background:#fff;">
20+
<img src="${fav}" alt="" width="44" height="44" onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🔖',style:'font-size:32px;line-height:44px;width:44px;text-align:center'}))" style="width:44px;height:44px;border-radius:9px;flex:0 0 auto;background:rgba(127,127,127,0.08);object-fit:contain;" />
21+
<div style="min-width:0;">
22+
<div style="font-size:21px;font-weight:600;color:#1a1a1a;line-height:1.3;margin-bottom:6px;overflow-wrap:anywhere;">${h.escape(title)}</div>
23+
<div style="font-size:13px;color:#7c3aed;font-family:monospace;overflow-wrap:anywhere;">${h.escape(site)}</div>
24+
</div>
25+
</a>
26+
<div style="display:flex;gap:10px;align-items:center;margin-top:18px;">
27+
<a href="${h.escape(url)}" target="_blank" rel="noopener" style="font-size:13px;font-weight:600;color:#fff;background:#7c3aed;padding:9px 16px;border-radius:8px;text-decoration:none;">Open ↗</a>
28+
${date ? `<span style="margin-left:auto;font-size:12px;color:#aaa;">saved ${h.escape(date)}</span>` : ''}
29+
</div>
30+
</div>`;
31+
}
32+
};

examples/panes/tracker.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Pod-local pane for wf:Tracker / ical:Vtodo (a task list).
2+
// Loaded by the `--browser panes` data browser from /public/panes/.
3+
// Contract: export default { canHandle(node, h) -> bool, render(node, h) -> htmlString }
4+
// h = { escape, prop, propAll, idOf, types, host, fmtDate, localName }.
5+
6+
export default {
7+
canHandle(node, h) {
8+
return h.types(node).some(t => t === 'Tracker' || t === 'Vtodo');
9+
},
10+
11+
render(node, h) {
12+
const issues = h.propAll(node, 'issue');
13+
const done = i => {
14+
const s = (i && (h.prop(i, 'status') || i.status)) || 'NEEDS-ACTION';
15+
return s === 'COMPLETED' || s === 'CANCELLED';
16+
};
17+
const active = issues.filter(i => !done(i));
18+
const title = h.prop(node, 'title') || 'Tasks';
19+
const row = i => `<li style="padding:8px 0;border-bottom:1px solid rgba(127,127,127,0.12);${done(i) ? 'color:#aaa;text-decoration:line-through;' : ''}">${done(i) ? '☑' : '☐'} ${h.escape(h.prop(i, 'summary') || h.idOf(i))}</li>`;
20+
return `<div style="font-family:Inter,-apple-system,sans-serif;padding:24px 0 8px;">
21+
<div style="font-family:Georgia,serif;font-size:14px;font-style:italic;color:#999;margin-bottom:6px;">Tasks</div>
22+
<div style="font-size:26px;font-weight:600;color:#1a1a1a;margin-bottom:4px;">${h.escape(title)}</div>
23+
<div style="font-size:13px;color:#888;margin-bottom:14px;">${active.length === 0 ? 'All clear.' : active.length + ' task' + (active.length === 1 ? '' : 's') + ' remaining.'}</div>
24+
<ul style="list-style:none;padding:0;margin:0;">${issues.map(row).join('')}</ul>
25+
</div>`;
26+
}
27+
};

index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -381,9 +381,9 @@ for (let i = 0; i < args.length; i++) {
381381
options.nostrMaxEvents = parsed;
382382
} else if (arg === '--browser') {
383383
const raw = requireValue(arg, args[++i]);
384-
if (raw !== 'json' && raw !== 'folder') {
384+
if (raw !== 'json' && raw !== 'folder' && raw !== 'panes') {
385385
console.error(chalk.red(`✗ Invalid --browser value: ${raw}`));
386-
console.error(chalk.dim('Must be one of: json, folder'));
386+
console.error(chalk.dim('Must be one of: json, folder, panes'));
387387
process.exit(1);
388388
}
389389
options.browser = raw;
@@ -408,7 +408,7 @@ for (let i = 0; i < args.length; i++) {
408408
console.log(chalk.green(' --no-auth') + chalk.dim(' Disable authentication'));
409409
console.log(chalk.green(' --no-open') + chalk.dim(' Do not open the browser automatically'));
410410
console.log(chalk.green(' --no-git') + chalk.dim(' Disable JSS\'s git HTTP backend (it is on by default)'));
411-
console.log(chalk.green(' --browser ') + chalk.yellow('<folder|json>') + chalk.dim(' Data browser style (default: folder)'));
411+
console.log(chalk.green(' --browser ') + chalk.yellow('<folder|json|panes>') + chalk.dim(' Data browser style (default: folder; panes = type-driven panes from /public/panes/)'));
412412
console.log(chalk.green(' --provision-keys') + chalk.dim(' Generate a Nostr-compatible owner keypair on first start'));
413413
console.log(chalk.green(' --mcp') + chalk.dim(' Expose /mcp (Model Context Protocol) tool surface for agents'));
414414
console.log(chalk.green(' --nostr') + chalk.dim(' Run a Nostr relay (NIP-01) at <pod>/relay'));

lib/start.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,9 @@ export async function start(userOptions = {}) {
230230

231231
// The data browser URL is a version-pinned jsdelivr asset so the
232232
// running pod always loads the matching browser code for this jspod.
233-
const browserFile = options.browser === 'folder' ? 'data-browser-folder.js' : 'data-browser.js';
233+
const browserFile = options.browser === 'folder' ? 'data-browser-folder.js'
234+
: options.browser === 'panes' ? 'data-browser-panes.js'
235+
: 'data-browser.js';
234236
const dataBrowserUrl = `https://cdn.jsdelivr.net/npm/jspod@${pkg.version}/${browserFile}`;
235237

236238
const RUNG_1_PASSWORD = process.env.JSS_SINGLE_USER_PASSWORD || 'me';

0 commit comments

Comments
 (0)