diff --git a/ui/src/main.js b/ui/src/main.js index 32ec3b3..4675a93 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -41,6 +41,10 @@ const validation = ref(null); const freshness = ref(null); const docs = ref([]); + const selectedDocPath = ref(""); + const selectedDoc = ref(null); + const docLoading = ref(false); + const docError = ref(""); const inventoryTypes = ref([]); const selectedInventoryType = ref(""); const inventoryLoading = ref(false); @@ -71,6 +75,27 @@ { key: "freshness", term: "Freshness", value: freshness.value?.status || "unknown" } ]); + const selectedDocFrontmatterJson = computed(() => { + if (!selectedDoc.value?.frontmatter) { + return ""; + } + return JSON.stringify(selectedDoc.value.frontmatter, null, 2); + }); + + const loadDoc = async (path) => { + selectedDocPath.value = path; + selectedDoc.value = null; + docLoading.value = true; + docError.value = ""; + try { + selectedDoc.value = await apiGet(`/docs/read?path=${encodeURIComponent(path)}`); + } catch (caught) { + docError.value = caught instanceof Error ? caught.message : `Failed to load document ${path}`; + } finally { + docLoading.value = false; + } + }; + const selectedInventoryRecord = computed(() => { if (!selectedInventoryId.value) { return null; @@ -137,6 +162,9 @@ inventoryTypes.value = inventoryData; changes.value = changesData; gitStatus.value = gitData; + if (!selectedDocPath.value && docsData.length) { + await loadDoc(docsData[0].path); + } if (!selectedInventoryType.value && inventoryData.length) { await loadInventoryType(inventoryData[0]); } @@ -152,6 +180,8 @@ return { activeTab, changes, + docError, + docLoading, docs, error, freshness, @@ -163,6 +193,7 @@ inventoryRecords, inventoryTypes, load, + loadDoc, loadInventoryType, loading, overview, @@ -170,6 +201,9 @@ selectedInventoryRecord, selectedInventoryRecordJson, selectedInventoryType, + selectedDoc, + selectedDocFrontmatterJson, + selectedDocPath, selectInventoryRecord, tabs, validation, @@ -211,11 +245,39 @@
diff --git a/ui/src/styles.css b/ui/src/styles.css index be83a8c..053f615 100644 --- a/ui/src/styles.css +++ b/ui/src/styles.css @@ -51,7 +51,8 @@ background: var(--gn-surface-muted, #eef2f6); } -.gnb-inventory { +.gnb-inventory, +.gnb-docs { display: grid; grid-template-columns: minmax(220px, 320px) minmax(0, 1fr); gap: 24px; @@ -82,6 +83,35 @@ line-height: 1.5; } +.gnb-json-small { + min-height: 120px; + max-height: 240px; +} + +.gnb-doc-detail { + display: grid; + gap: 20px; +} + +.gnb-doc-detail h3 { + margin: 0 0 8px; + font-size: 14px; +} + +.gnb-markdown { + min-height: 360px; + max-height: 680px; + overflow: auto; + margin: 0; + padding: 16px; + border: 1px solid var(--gn-border, #dfe3e8); + background: var(--gn-surface, #ffffff); + color: var(--gn-text, #101828); + font-size: 14px; + line-height: 1.6; + white-space: pre-wrap; +} + .gnb-error { margin-top: 16px; color: var(--gn-danger, #b42318); @@ -94,6 +124,7 @@ .gnb-status, .gnb-row, + .gnb-docs, .gnb-inventory, .gnb-detail-grid { grid-template-columns: 1fr;