diff --git a/plugins/voyage/playground/voyage-playground.html b/plugins/voyage/playground/voyage-playground.html index 853d43a..6be85c3 100644 --- a/plugins/voyage/playground/voyage-playground.html +++ b/plugins/voyage/playground/voyage-playground.html @@ -1422,6 +1422,163 @@ playground first-run shows a complete round-trip-able artifact. announce('Dashboard lastet — ' + tiles.length + ' artifacts vist.'); } + // ---- v4.3 Step 15 — drill-down + back-nav + URL routing ---------- + // Click on a fleet-tile drills into renderArtifactDetail; the + // back-to-dashboard button (or breadcrumb-click) returns to the + // dashboard without state-loss. URL parameter `?project=` is + // additive: at page-load we surface a guide-panel hint because + // webkitdirectory cannot be triggered without a user-gesture. + // popstate handler keeps browser back/forward in sync with view-state. + + function resolveArtifactEntry(a, key) { + if (!a) return null; + if (key === 'brief') return a.brief; + if (key === 'plan') return a.plan; + if (key === 'review') return a.review; + if (key === 'progress') return a.progress; + if (key === 'research') return (a.research && a.research.length) ? a.research[0] : null; + return null; + } + + function renderArtifactDetail(artifactKey) { + var a = __voyageCurrentArtifacts; + if (!a) return; + var slot = $('voyage-detail'); + if (!slot) return; + + var entry = resolveArtifactEntry(a, artifactKey); + var bodyHtml; + if (artifactKey === 'research') { + if (a.research && a.research.length) { + bodyHtml = '
Ingen research-briefs i prosjektet.
'; + } + } else if (!entry) { + bodyHtml = 'Artifact mangler i prosjektmappen.
'; + } else if (artifactKey === 'progress') { + bodyHtml = '' + escapeHtml(entry.content || '') + '';
+ } else {
+ bodyHtml = renderArtifact(entry.content || '');
+ }
+
+ var titleMap = { brief: 'Brief', plan: 'Plan', review: 'Review',
+ research: 'Research', progress: 'Progress' };
+ var artifactName = titleMap[artifactKey] || artifactKey;
+ var projectName = shortenBasePath(a.basePath);
+
+ slot.innerHTML = renderPageShell({
+ eyebrow: 'Artifact detail',
+ title: artifactName,
+ lede: 'Project: ' + projectName,
+ meta: entry ? ('path: ' + (entry.path || '')) : 'mangler'
+ }, '' + bodyHtml);
+ slot.hidden = false;
+
+ // Hide dashboard + paste-flow stages.
+ var dash = $('voyage-dashboard'); if (dash) dash.hidden = true;
+ var emptyState = $('empty-state'); if (emptyState) emptyState.hidden = true;
+ var pasteRow = document.querySelector('.paste-import-row'); if (pasteRow) pasteRow.hidden = true;
+ var layout = document.querySelector('.voyage-layout'); if (layout) layout.hidden = true;
+
+ renderTopbar([
+ { label: 'Voyage', href: '#' },
+ { label: projectName, href: '#' },
+ { label: artifactName }
+ ]);
+ announce('Detail-visning: ' + artifactName);
+ }
+
+ function showDashboardFromState() {
+ if (!__voyageCurrentArtifacts) return;
+ var detail = $('voyage-detail'); if (detail) detail.hidden = true;
+ renderDashboard(__voyageCurrentArtifacts);
+ }
+
+ function pushDashboardURL() {
+ try {
+ if (!window.history || !window.history.pushState) return;
+ var a = __voyageCurrentArtifacts;
+ var params = new URLSearchParams(window.location.search);
+ if (a && a.basePath) params.set('project', a.basePath);
+ params.delete('artifact');
+ var qs = params.toString();
+ var url = window.location.pathname + (qs ? ('?' + qs) : '');
+ window.history.pushState({ view: 'dashboard', basePath: a ? a.basePath : null }, '', url);
+ } catch (_) { /* file:// + privatmodus */ }
+ }
+
+ function pushDetailURL(artifactKey) {
+ try {
+ if (!window.history || !window.history.pushState) return;
+ var a = __voyageCurrentArtifacts;
+ var params = new URLSearchParams(window.location.search);
+ if (a && a.basePath) params.set('project', a.basePath);
+ params.set('artifact', artifactKey);
+ var qs = params.toString();
+ var url = window.location.pathname + (qs ? ('?' + qs) : '');
+ window.history.pushState({ view: 'detail', basePath: a ? a.basePath : null, artifact: artifactKey }, '', url);
+ } catch (_) { /* file:// + privatmodus */ }
+ }
+
+ function wireDashboardNavigation() {
+ document.addEventListener('click', function (e) {
+ var tile = e.target && e.target.closest && e.target.closest('.fleet-tile[data-artifact]');
+ if (tile) {
+ e.preventDefault();
+ var key = tile.getAttribute('data-artifact');
+ if (key) {
+ renderArtifactDetail(key);
+ pushDetailURL(key);
+ }
+ return;
+ }
+ var back = e.target && e.target.closest && e.target.closest('[data-action="back-to-dashboard"]');
+ if (back) {
+ e.preventDefault();
+ showDashboardFromState();
+ pushDashboardURL();
+ }
+ });
+ // Browser back/forward → restore view from history state.
+ window.addEventListener('popstate', function (e) {
+ var s = e.state;
+ if (!s || !__voyageCurrentArtifacts) return;
+ if (s.view === 'detail' && s.artifact) {
+ renderArtifactDetail(s.artifact);
+ } else if (s.view === 'dashboard') {
+ showDashboardFromState();
+ }
+ });
+ }
+
+ function maybeShowProjectURLHint() {
+ // Caveat per Step 15 spec: webkitdirectory cannot be triggered
+ // programmatically without a user-gesture, so a `?project=` URL
+ // surfaces a guide-panel hint instead of attempting auto-load.
+ try {
+ var qs = new URLSearchParams(window.location.search);
+ var projectQ = qs.get('project');
+ if (!projectQ) return;
+ var emptyState = $('empty-state');
+ if (!emptyState) return;
+ var titleEl = emptyState.querySelector('.guide-panel__title');
+ var bodyEl = emptyState.querySelector('.guide-panel__body');
+ if (titleEl) titleEl.textContent = 'Project deep-link oppdaget';
+ if (bodyEl) {
+ bodyEl.innerHTML = 'URL inneholder ?project=' +
+ escapeHtml(projectQ) + '. Browseren krever et bruker-klikk ' +
+ 'før prosjektmappen kan leses; bruk knappen «Last prosjektmappe» ' +
+ 'eller dra mappen til vinduet for å fortsette.