From 0c62bbf1cd2f284341adf21aeab3b254d908534a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Wed, 20 May 2026 11:01:16 +0200 Subject: [PATCH] dashboard: render markdown file previews in the side panel clicking a .md / .markdown path reference now opens a marked-rendered view in the slide-in panel instead of raw text; other files stay raw in a
. serves the vendored marked bundle at /static/marked.js and
scopes a .md stylesheet to the panel body.
---
 hive-c0re/assets/app.js        | 20 +++++++++++++----
 hive-c0re/assets/dashboard.css | 40 ++++++++++++++++++++++++++++++++++
 hive-c0re/assets/index.html    |  1 +
 hive-c0re/src/dashboard.rs     | 10 +++++++++
 4 files changed, 67 insertions(+), 4 deletions(-)

diff --git a/hive-c0re/assets/app.js b/hive-c0re/assets/app.js
index 881c02e..35cd865 100644
--- a/hive-c0re/assets/app.js
+++ b/hive-c0re/assets/app.js
@@ -85,13 +85,25 @@
     return text;
   }
   // Lazy-load `path` from /api/state-file into the side panel.
+  // Markdown files render through `marked` into a `.md` block; every
+  // other file stays raw text in a 
.
   async function openFilePanel(path) {
-    const pre = el('pre', { class: 'path-preview-body' }, '(fetching…)');
-    Panel.open('↳ ' + path, pre);
+    const isMd = /\.(md|markdown)$/i.test(path);
+    const view = isMd
+      ? el('div', { class: 'md' })
+      : el('pre', { class: 'path-preview-body' });
+    view.textContent = '(fetching…)';
+    Panel.open('↳ ' + path, view);
     try {
-      pre.textContent = await fetchStateFile(path);
+      const text = await fetchStateFile(path);
+      if (isMd && window.marked && typeof window.marked.parse === 'function') {
+        marked.setOptions({ breaks: true, gfm: true });
+        view.innerHTML = marked.parse(text);
+      } else {
+        view.textContent = text;
+      }
     } catch (e) {
-      pre.textContent = 'error: ' + (e.message || e);
+      view.textContent = 'error: ' + (e.message || e);
     }
   }
   function makePathLink(path) {
diff --git a/hive-c0re/assets/dashboard.css b/hive-c0re/assets/dashboard.css
index 282aeba..5990b1b 100644
--- a/hive-c0re/assets/dashboard.css
+++ b/hive-c0re/assets/dashboard.css
@@ -805,3 +805,43 @@ footer a { color: var(--purple); }
   overflow: auto;
   padding: 1em;
 }
+/* Markdown file previews rendered by `marked`. TERMINAL_CSS scopes
+   its own `.md` rules to `.live .row`, so the panel needs its own. */
+.side-panel-body .md { color: var(--fg); line-height: 1.5; }
+.side-panel-body .md > :first-child { margin-top: 0; }
+.side-panel-body .md > :last-child { margin-bottom: 0; }
+.side-panel-body .md p { margin: 0.5em 0; }
+.side-panel-body .md h1,
+.side-panel-body .md h2,
+.side-panel-body .md h3,
+.side-panel-body .md h4 { color: var(--purple); margin: 0.9em 0 0.4em; }
+.side-panel-body .md code {
+  background: var(--bg);
+  border: 1px solid var(--border);
+  border-radius: 3px;
+  padding: 0.05em 0.3em;
+  font-size: 0.9em;
+}
+.side-panel-body .md pre {
+  background: var(--bg);
+  border: 1px solid var(--border);
+  border-radius: 3px;
+  padding: 0.6em 0.8em;
+  overflow-x: auto;
+}
+.side-panel-body .md pre code { background: none; border: none; padding: 0; }
+.side-panel-body .md a { color: var(--cyan); }
+.side-panel-body .md ul,
+.side-panel-body .md ol { margin: 0.4em 0; padding-left: 1.5em; }
+.side-panel-body .md blockquote {
+  margin: 0.5em 0;
+  padding-left: 0.8em;
+  border-left: 2px solid var(--border);
+  color: var(--muted);
+}
+.side-panel-body .md table { border-collapse: collapse; margin: 0.5em 0; }
+.side-panel-body .md th,
+.side-panel-body .md td {
+  border: 1px solid var(--border);
+  padding: 0.2em 0.5em;
+}
diff --git a/hive-c0re/assets/index.html b/hive-c0re/assets/index.html
index 1fb304c..605e347 100644
--- a/hive-c0re/assets/index.html
+++ b/hive-c0re/assets/index.html
@@ -102,6 +102,7 @@
   
 
   
+  
   
 
 
diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs
index cee5bd9..5dae339 100644
--- a/hive-c0re/src/dashboard.rs
+++ b/hive-c0re/src/dashboard.rs
@@ -65,6 +65,7 @@ pub async fn serve(port: u16, coord: Arc) -> Result<()> {
         .route("/dashboard/stream", get(dashboard_stream))
         .route("/dashboard/history", get(dashboard_history))
         .route("/static/hive-fr0nt.js", get(serve_shared_js))
+        .route("/static/marked.js", get(serve_marked_js))
         .with_state(AppState { coord });
     let addr = SocketAddr::from(([0, 0, 0, 0], port));
     let listener = bind_with_retry(addr).await?;
@@ -146,6 +147,15 @@ async fn serve_shared_js() -> impl IntoResponse {
     )
 }
 
+/// Vendored `marked` bundle — the side panel renders markdown file
+/// previews with it.
+async fn serve_marked_js() -> impl IntoResponse {
+    (
+        [("content-type", "application/javascript")],
+        hive_fr0nt::MARKED_JS,
+    )
+}
+
 #[derive(Serialize)]
 struct StateSnapshot {
     /// Broker seq at the moment this snapshot was assembled. Clients