From b0eb824cfae7ce1ba5d12c628dcb622ce771c7eb Mon Sep 17 00:00:00 2001 From: iris Date: Sat, 6 Jun 2026 11:06:39 +0200 Subject: [PATCH] feat(frontend): add the H0M3 menu-hub page (#1464 step 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First step of the dashboard-tab consolidation (#1464): a static menu page linking to every top-level surface, so the tab strip can later shed its `→` page-links and the dashboard can graduate Stats/Settings to their own pages. - dashboard/src/home.{html,css,js}: a responsive grid of link tiles (D4SHB04RD, FL0W, L0GS, M4TR1X). Pure portal — no tabbar/SSE. Colours from the shared theme.css, base typography from common.css. The Matrix tile is hidden until home.js confirms `matrix_gui_enabled` (same gating as the dashboard's M4TR1X tab); home.js also fills the swarm/hive identity line. - build.mjs: emit home.{html,css,js}. Served at `/home.html` for now (additive — reachable via the existing ServeDir, links to surfaces at their current routes). Promoting it to `/` (and relocating the dashboard to `/dashboard` + wiring `← home` back-links) is the next step — a route swap that touches hive-c0re's static router, coordinating with damocles. Deliberately decoupled so this page ships standalone without conflicting with the in-flight tabs.js change (#1449/#1451). Part of #1464. --- frontend/packages/dashboard/build.mjs | 6 +-- frontend/packages/dashboard/src/home.css | 59 +++++++++++++++++++++++ frontend/packages/dashboard/src/home.html | 55 +++++++++++++++++++++ frontend/packages/dashboard/src/home.js | 33 +++++++++++++ 4 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 frontend/packages/dashboard/src/home.css create mode 100644 frontend/packages/dashboard/src/home.html create mode 100644 frontend/packages/dashboard/src/home.js diff --git a/frontend/packages/dashboard/build.mjs b/frontend/packages/dashboard/build.mjs index 46c2724b..d8a46579 100644 --- a/frontend/packages/dashboard/build.mjs +++ b/frontend/packages/dashboard/build.mjs @@ -43,7 +43,7 @@ mkdirSync(staticDir(''), { recursive: true }); // follow-up once asset sizes warrant it). esbuild writes each entry // to `static/.js` based on the entryPoint basename. await build({ - entryPoints: [src('tabs.js'), src('flow.js'), src('logs.js')], + entryPoints: [src('tabs.js'), src('flow.js'), src('logs.js'), src('home.js')], outdir: staticDir(''), bundle: true, format: 'esm', @@ -81,7 +81,7 @@ await build({ // (the standalone Catppuccin palette — kept its own file so a theme // swap replaces only it) + common.css (shared typography, badges, // buttons, inbox, side panel) plus its own page-specific bundle. -for (const entry of ['theme.css', 'common.css', 'dashboard.css', 'flow.css', 'logs.css']) { +for (const entry of ['theme.css', 'common.css', 'dashboard.css', 'flow.css', 'logs.css', 'home.css']) { await build({ entryPoints: [src(entry)], outfile: staticDir(entry), @@ -91,7 +91,7 @@ for (const entry of ['theme.css', 'common.css', 'dashboard.css', 'flow.css', 'lo }); } -for (const html of ['index.html', 'flow.html', 'logs.html']) { +for (const html of ['index.html', 'flow.html', 'logs.html', 'home.html']) { copyFileSync(src(html), dist(html)); } diff --git a/frontend/packages/dashboard/src/home.css b/frontend/packages/dashboard/src/home.css new file mode 100644 index 00000000..a95c6758 --- /dev/null +++ b/frontend/packages/dashboard/src/home.css @@ -0,0 +1,59 @@ +/* H0M3 — the menu-hub page (#1464). Minimal portal styling: a banner + + a responsive grid of link tiles. Colours come from theme.css (the + shared palette); base typography from common.css. When the shared + page-chrome component lands (#1464 phase 2 / #1451) the banner + tile + styling can fold into it. */ + +body.home-shell { + margin: 0; + padding: 0 1.5em 2em; + min-height: 100vh; +} + +.home-header { + text-align: center; + padding: 1.5em 0 0.5em; +} +.home-header .banner { + font-size: 1.05em; +} + +.home-main { + max-width: 60em; + margin: 1.5em auto 0; +} + +.home-menu { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(16em, 1fr)); + gap: 1em; +} + +.home-tile { + display: flex; + flex-direction: column; + gap: 0.35em; + padding: 1.1em 1.2em; + border: 1px solid var(--purple-dim); + border-radius: 6px; + background: rgba(24, 24, 37, 0.55); + text-decoration: none; + color: var(--fg); + transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease, + box-shadow 0.15s ease; +} +.home-tile:hover { + border-color: var(--purple); + background: rgba(203, 166, 247, 0.06); + box-shadow: 0 -2px 14px -6px rgba(203, 166, 247, 0.5); +} + +.home-tile-label { + font-weight: bold; + letter-spacing: 0.08em; + color: var(--purple); +} +.home-tile-desc { + font-size: 0.85em; + color: var(--muted); +} diff --git a/frontend/packages/dashboard/src/home.html b/frontend/packages/dashboard/src/home.html new file mode 100644 index 00000000..6df5f6f2 --- /dev/null +++ b/frontend/packages/dashboard/src/home.html @@ -0,0 +1,55 @@ + + + + + + hyperhive // h0m3 + + + + + + + + +
+ + +
+ +
+ +
+ + + + diff --git a/frontend/packages/dashboard/src/home.js b/frontend/packages/dashboard/src/home.js new file mode 100644 index 00000000..4151a1b8 --- /dev/null +++ b/frontend/packages/dashboard/src/home.js @@ -0,0 +1,33 @@ +// H0M3 page script (#1464). The page is static markup; this only does +// two small things off a single `/api/state` read: +// 1. reveal the Matrix tile when the matrix GUI is enabled (same +// gating as the dashboard's M4TR1X tab — no dead link otherwise); +// 2. fill the swarm/hive identity line when configured. +// No SSE, no shared deps — a portal doesn't need live updates. + +const $ = (id) => document.getElementById(id); + +async function init() { + let state; + try { + const resp = await fetch('/api/state'); + if (!resp.ok) return; + state = await resp.json(); + } catch { + return; // best-effort: the tiles still work without it + } + + if (state.matrix_gui_enabled) { + const tile = $('home-tile-matrix'); + if (tile) tile.hidden = false; + } + + const ident = $('hive-identity'); + if (ident && (state.swarm_name || state.hive_name)) { + const parts = [state.swarm_name, state.hive_name].filter(Boolean); + ident.textContent = parts.join(' / '); + ident.hidden = false; + } +} + +init();