From 902aafcfb1ce5c7a22afb23131b533eee8d1aa42 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 26 Feb 2026 14:42:38 +0100 Subject: [PATCH] dashboard: add toc-sidebar utility; move live indicator into TOC column MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracts the TOC-injection pattern into a reusable component: src/components/toc-sidebar.js injectTocTop(id, element) — prepends an element to #observablehq-toc, removing any previous instance with the same id first so reactive cells can re-inject on each poll without accumulating duplicates. decisions.md now uses injectTocTop to place a single widget (live indicator + Decision Health KPI box) into the right-column sidebar, removing the standalone live-indicator cell and the ad-hoc id/remove pattern that was previously inlined. Co-Authored-By: Claude Sonnet 4.6 --- dashboard/src/components/toc-sidebar.js | 29 +++++++++++++++++++++++++ dashboard/src/decisions.md | 21 +++++++----------- 2 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 dashboard/src/components/toc-sidebar.js diff --git a/dashboard/src/components/toc-sidebar.js b/dashboard/src/components/toc-sidebar.js new file mode 100644 index 0000000..7986db9 --- /dev/null +++ b/dashboard/src/components/toc-sidebar.js @@ -0,0 +1,29 @@ +/** + * toc-sidebar — inject a persistent widget into Observable Framework's + * right-column table-of-contents sidebar. + * + * Observable Framework renders a non-scrolling TOC aside (#observablehq-toc) + * in the right column. This helper lets you prepend a custom element to it, + * replacing any previously injected element with the same id on each call so + * reactive cells can refresh the widget without accumulating duplicates. + * + * Usage: + * import {injectTocTop} from "./components/toc-sidebar.js"; + * + * const el = html`
`; + * injectTocTop("my-widget-id", el); // call again on each reactive update + * + * @param {string} id Stable id used to find and remove the previous + * instance. Must be unique per widget on the page. + * @param {HTMLElement} element Element to inject. Its id will be set to `id`. + * @returns {boolean} true if injected into the TOC sidebar; + * false if #observablehq-toc was not found. + */ +export function injectTocTop(id, element) { + document.getElementById(id)?.remove(); + element.id = id; + const toc = document.querySelector("#observablehq-toc"); + if (!toc) return false; + toc.prepend(element); + return true; +} diff --git a/dashboard/src/decisions.md b/dashboard/src/decisions.md index bca6a20..7b98581 100644 --- a/dashboard/src/decisions.md +++ b/dashboard/src/decisions.md @@ -103,7 +103,8 @@ function fmtDuration(ms) { # Decisions ```js -import {withDocHelp} from "./components/doc-overlay.js"; +import {withDocHelp} from "./components/doc-overlay.js"; +import {injectTocTop} from "./components/toc-sidebar.js"; // ── KPI computation (uses full data, not filtered) ────────────────────────── const _resolved5 = data @@ -150,22 +151,16 @@ const _kpiBox = html`
withDocHelp(_kpiBox, "/docs/decisions-kpi"); -// ── Inject into the TOC sidebar (right column, non-scrolling) ─────────────── -_kpiBox.id = "decisions-kpi-box"; -const _toc = document.querySelector("#observablehq-toc"); -if (_toc) { - document.getElementById("decisions-kpi-box")?.remove(); - _toc.prepend(_kpiBox); -} else display(html`
${_kpiBox}
`); -``` - -```js -display(html`
+// ── Build sidebar widget: live indicator + KPI box ────────────────────────── +const _liveEl = html`
${_ok ? `Live · updated ${_ts?.toLocaleTimeString()}` : html`Offline — run: make api`} -
`); +
`; + +const _tocInjected = injectTocTop("decisions-sidebar", html`
${_liveEl}${_kpiBox}
`); +if (!_tocInjected) display(html`
${_liveEl}${_kpiBox}
`); ``` ## Resolution History