--- /home/yeff/public_html/devon/docs/index.html.bootfix.20260329_181133 2026-03-29 18:10:19.473874410 -0300 +++ /home/yeff/public_html/devon/docs/index.html 2026-04-10 22:02:24.032902725 -0300 @@ -1,3 +1,4 @@ + @@ -75,6 +76,25 @@ min-width:0; } + .sidebar, + .rail{ + scrollbar-width:thin; + scrollbar-color:rgba(138,167,255,.22) transparent; + } + .sidebar::-webkit-scrollbar, + .rail::-webkit-scrollbar{ + width:10px; + } + .sidebar::-webkit-scrollbar-track, + .rail::-webkit-scrollbar-track{ + background:transparent; + } + .sidebar::-webkit-scrollbar-thumb, + .rail::-webkit-scrollbar-thumb{ + background:rgba(138,167,255,.22); + border-radius:999px; + } + .brand{ display:flex; flex-direction:column; @@ -404,7 +424,7 @@ .workspace{ display:grid; - grid-template-columns:1.12fr .88fr; + grid-template-columns:1.2fr .8fr; gap:18px; } @@ -656,7 +676,7 @@
Open Panel Manifest JSON - Checkpoint MD + Continuity MD
@@ -707,13 +727,14 @@
+
+
-
Select a document in the Architecture Tree.
@@ -745,6 +766,8 @@ read-only canon no fake status docs as architecture + doc audit only + runtime in operator panel @@ -758,6 +781,11 @@
+

Audit split

+

Documentation Hub audits canonical definition, category coverage, authority ownership, phase and layer binding, dependencies and documentation completion gaps. Operator Panel audits real implementation state, runtime evidence, health, telemetry, pipeline execution and server-side materialization.

+
+ +

Legend

control and structure layers
@@ -769,13 +797,19 @@
+

Documentation structural coverage

+

Waiting for structural coverage analysis.

+
+
+ +

Canonical sources

../panel/data/master_architecture_index.md
../panel/data/panel_manifest.json
../panel/data/panel_content_index.json
../panel/data/project_scope_canonical.json
-
../panel/data/devon_panel_chat_checkpoint.md
+
../panel/data/devon_continuity.md
../panel/data/cas.md
../panel/data/cgs.md
@@ -834,17 +868,30 @@ let HUB = null; async function loadHub() { - const res = await fetch('../panel/data/hub_index.json'); - if (!res.ok) throw new Error('hub load fail'); - HUB = await res.json(); + if (HUB) return HUB; + + const [hubRes, treeRes] = await Promise.all([ + fetch('../panel/data/hub_index.json', { cache: 'no-store' }), + fetch('../panel/data/panel_canonical_tree.json', { cache: 'no-store' }) + ]); + + if (!hubRes.ok) throw new Error('hub load fail'); + if (!treeRes.ok) throw new Error('canonical tree load fail'); + + const [hubIndex, canonicalTree] = await Promise.all([ + hubRes.json(), + treeRes.json() + ]); + + HUB = { + index: hubIndex, + tree: canonicalTree + }; + + return HUB; } -const state = { - phaseId: HUB.phases[0].id, - categoryId: HUB.categories[0].id, - docId: HUB.categories[0].docs[0].id, - mode: "architecture" - }; +let state = { phaseId: null, categoryId: null, docId: null, selectedSubcategory: null, mode: "architecture" }; const treeRoot = document.getElementById("tree-root"); const phaseStrip = document.getElementById("phase-strip"); @@ -862,37 +909,138 @@ const docMap = document.getElementById("doc-map"); const docViewer = document.getElementById("doc-viewer"); const relationMap = document.getElementById("relation-map"); + const docCompletenessSummary = document.getElementById("doc-completeness-summary"); + const docCompletenessList = document.getElementById("doc-completeness-list"); const btnViewArchitecture = document.getElementById("btn-view-architecture"); const btnViewContract = document.getElementById("btn-view-contract"); const btnViewSource = document.getElementById("btn-view-source"); + function hubIndex(){ + return HUB && HUB.index ? HUB.index : { phases: [], categories: [] }; + } - function getCategory(categoryId){ - return HUB.categories.find(c => c.id === categoryId); - } + function hubTree(){ + return HUB && HUB.tree ? HUB.tree : { phases: [] }; + } - function getDoc(categoryId, docId){ - const category = getCategory(categoryId); - return category ? category.docs.find(d => d.id === docId) : null; - } + function getCategory(categoryId){ + return (hubIndex().categories || []).find(c => c.id === categoryId) || null; + } + + function getDoc(categoryId, docId){ + const category = getCategory(categoryId); + return category ? ((category.docs || []).find(d => d.id === docId) || null) : null; + } + + function getPhase(phaseId){ + return (hubIndex().phases || []).find(p => p.id === phaseId) || null; + } + + function getTreePhase(phaseId){ + return (hubTree().phases || []).find(p => p.id === phaseId) || null; + } + + function getTreeCategory(phaseId, categoryId){ + const phase = getTreePhase(phaseId); + return phase ? ((phase.categories || []).find(c => c.id === categoryId) || null) : null; + } + + function getSubcategories(phaseId, categoryId){ + const category = getTreeCategory(phaseId, categoryId); + return category && Array.isArray(category.subcategories) ? category.subcategories : []; + } + + function getSelectedSubcategory(){ + const subcategories = getSubcategories(state.phaseId, state.categoryId); + if (!subcategories.length) return null; + if (state.selectedSubcategory) { + const hit = subcategories.find(s => s.id === state.selectedSubcategory); + if (hit) return hit; + } + return subcategories[0]; + } - function getPhase(phaseId){ - return HUB.phases.find(p => p.id === phaseId); - } function metricData(){ - const totalCategories = HUB.categories.length; - const totalDocs = HUB.categories.reduce((acc, cat) => acc + cat.docs.length, 0); - const totalPhases = HUB.phases.length; + const totalCategories = hubIndex().categories.length; + const totalDocs = hubIndex().categories.reduce((acc, cat) => acc + ((cat.docs || []).length), 0); + const totalPhases = hubIndex().phases.length; const totalSources = totalDocs; + const categoriesWithGaps = hubIndex().categories.filter(cat => !Array.isArray(cat.docs) || !cat.docs.length).length; return [ { value: totalPhases, label: "Phases", note: "macro build stages" }, { value: totalCategories, label: "Categories", note: "navigation domains" }, { value: totalDocs, label: "Documents", note: "canonical source set" }, - { value: totalSources, label: "Mapped Sources", note: "doc-to-source bindings" } + { value: totalSources, label: "Mapped Sources", note: "doc-to-source bindings" }, + { value: totalCategories - categoriesWithGaps, label: "Ready Categories", note: "categories with mapped docs" } ]; } + function documentationCompletenessSnapshot(){ + const categories = Array.isArray(hubIndex().categories) ? hubIndex().categories : []; + const phases = Array.isArray(hubIndex().phases) ? hubIndex().phases : []; + const docs = categories.reduce((acc, cat) => acc + (Array.isArray(cat.docs) ? cat.docs.length : 0), 0); + + const categoriesWithDocs = categories.filter(cat => Array.isArray(cat.docs) && cat.docs.length > 0); + const missingCategoryDocs = categories.filter(cat => !Array.isArray(cat.docs) || cat.docs.length === 0); + + const docsMissingPhase = []; + const docsMissingDepends = []; + const docsMissingUsedBy = []; + + categories.forEach(cat => { + (Array.isArray(cat.docs) ? cat.docs : []).forEach(doc => { + if (!doc.phase) docsMissingPhase.push(doc.label || doc.id || "unnamed-doc"); + if (!Array.isArray(doc.depends_on) || doc.depends_on.length === 0) docsMissingDepends.push(doc.label || doc.id || "unnamed-doc"); + if (!Array.isArray(doc.used_by) || doc.used_by.length === 0) docsMissingUsedBy.push(doc.label || doc.id || "unnamed-doc"); + }); + }); + + return { + totalPhases: phases.length, + totalCategories: categories.length, + totalDocs: docs, + categoriesWithDocs: categoriesWithDocs.length, + missingCategoryDocs, + docsMissingPhase, + docsMissingDepends, + docsMissingUsedBy + }; + } + + function buildDocumentationCompleteness(){ + if (!docCompletenessSummary || !docCompletenessList) return; + + const snap = documentationCompletenessSnapshot(); + const categoriesWithoutDocs = snap.missingCategoryDocs.length; + const docsMissingPhase = snap.docsMissingPhase.length; + const docsMissingDepends = snap.docsMissingDepends.length; + const docsMissingUsedBy = snap.docsMissingUsedBy.length; + + const categoriesReady = snap.totalCategories - categoriesWithoutDocs; + const totalGapCount = categoriesWithoutDocs + docsMissingPhase + docsMissingDepends + docsMissingUsedBy; + + docCompletenessSummary.textContent = + totalGapCount === 0 + ? "Structural coverage is complete for the current Hub surface." + : "Structural coverage is active, but mapped gaps still exist and are listed below."; + + docCompletenessList.innerHTML = ""; + + [ + { title: "Covered categories", text: `${categoriesReady} / ${snap.totalCategories} categories with mapped docs.` }, + { title: "Category gaps", text: categoriesWithoutDocs ? `${categoriesWithoutDocs} categories still have no mapped docs.` : "No category-level doc gaps detected." }, + { title: "Phase binding gaps", text: docsMissingPhase ? `${docsMissingPhase} docs still have no explicit phase binding.` : "No phase-binding gaps detected." }, + { title: "Dependency gaps", text: docsMissingDepends ? `${docsMissingDepends} docs still have no depends_on mapping.` : "No depends_on gaps detected." }, + { title: "Usage gaps", text: docsMissingUsedBy ? `${docsMissingUsedBy} docs still have no used_by mapping.` : "No used_by gaps detected." } + ].forEach(item => { + const el = document.createElement("div"); + el.className = "mini-item"; + el.innerHTML = `${item.title}${item.text}`; + docCompletenessList.appendChild(el); + }); + } + function buildMetrics(){ metricGrid.innerHTML = ""; metricData().forEach(item => { @@ -906,10 +1054,9 @@ metricGrid.appendChild(el); }); } - function buildPhases(){ phaseStrip.innerHTML = ""; - HUB.phases.forEach(phase => { + (hubIndex().phases || []).forEach(phase => { const btn = document.createElement("button"); btn.type = "button"; btn.className = "phase" + (phase.id === state.phaseId ? " active" : ""); @@ -921,68 +1068,86 @@ btn.title = phase.summary; btn.addEventListener("click", () => { state.phaseId = phase.id; - const matchingCategory = HUB.categories.find(cat => cat.docs.some(doc => doc.phase === phase.id)); - if (matchingCategory) { - state.categoryId = matchingCategory.id; - state.docId = matchingCategory.docs[0].id; + + const phaseTree = getTreePhase(phase.id); + const firstCategory = phaseTree && Array.isArray(phaseTree.categories) + ? phaseTree.categories[0] + : null; + + if (firstCategory) { + state.categoryId = firstCategory.id; + state.selectedSubcategory = null; } + + state.docId = null; render(); }); phaseStrip.appendChild(btn); }); } - function buildTree(){ treeRoot.innerHTML = ""; - HUB.categories.forEach(cat => { - const node = document.createElement("div"); - const isOpen = cat.id === state.categoryId; - node.className = "tree-node" + (isOpen ? " open" : ""); - - const head = document.createElement("div"); - head.className = "tree-head"; - head.innerHTML = ` + + (hubIndex().phases || []).forEach(phase => { + const phaseNode = document.createElement("div"); + const phaseOpen = phase.id === state.phaseId; + phaseNode.className = "tree-node" + (phaseOpen ? " open" : ""); + + const phaseHead = document.createElement("div"); + phaseHead.className = "tree-head"; + phaseHead.innerHTML = `
-
${cat.title}
-
${cat.sub}
+
${phase.name}
+
${phase.summary || ""}
- ${cat.badge} + ${phase.badge || phase.step || ""} `; - head.addEventListener("click", () => { - state.categoryId = cat.id; - const firstDoc = cat.docs[0]; - if (firstDoc) { - state.docId = firstDoc.id; - state.phaseId = firstDoc.phase; + phaseHead.addEventListener("click", () => { + state.phaseId = phase.id; + + const phaseTree = getTreePhase(phase.id); + const firstCategory = phaseTree && Array.isArray(phaseTree.categories) + ? phaseTree.categories[0] + : null; + + if (firstCategory) { + state.categoryId = firstCategory.id; + state.selectedSubcategory = null; } + + state.docId = null; render(); }); - const children = document.createElement("div"); - children.className = "tree-children"; + const phaseChildren = document.createElement("div"); + phaseChildren.className = "tree-children"; - cat.docs.forEach(doc => { + const phaseTree = getTreePhase(phase.id); + (phaseTree && Array.isArray(phaseTree.categories) ? phaseTree.categories : []).forEach(catTree => { + const catMeta = getCategory(catTree.id) || {}; const btn = document.createElement("button"); btn.type = "button"; - btn.className = "tree-doc" + (doc.id === state.docId ? " active" : ""); - btn.innerHTML = `${doc.label}${doc.layer} • ${doc.type}`; + btn.className = "tree-doc" + (catTree.id === state.categoryId ? " active" : ""); + btn.innerHTML = `${catMeta.title || catTree.id}${catMeta.sub || "category"}`; btn.addEventListener("click", (ev) => { ev.stopPropagation(); - state.categoryId = cat.id; - state.docId = doc.id; - state.phaseId = doc.phase; + state.phaseId = phase.id; + state.categoryId = catTree.id; + state.selectedSubcategory = null; + state.docId = null; render(); }); - children.appendChild(btn); + phaseChildren.appendChild(btn); }); - node.appendChild(head); - node.appendChild(children); - treeRoot.appendChild(node); + phaseNode.appendChild(phaseHead); + phaseNode.appendChild(phaseChildren); + treeRoot.appendChild(phaseNode); }); } function buildPhaseMap(){ + const phase = getPhase(state.phaseId); if (!phase) return; @@ -1225,16 +1390,37 @@ }; } - function render(){ + +function init(){ + state.phaseId = HUB.phases[0].id; + state.categoryId = HUB.categories[0].id; + state.docId = HUB.categories[0].docs[0].id; + + bindModes(); + render(); +} + +function render(){ buildTree(); buildPhases(); buildMetrics(); + buildDocumentationCompleteness(); buildPhaseMap(); renderDocViewer(); } - bindModes(); - render(); - + + +(async () => { + try { + await loadHub(); + init(); + } catch (e) { + console.error("BOOT_FAIL", e); + document.body.innerHTML = "
Failed to load HUB
"; + } +})(); + +