diff --git a/CHANGELOG.md b/CHANGELOG.md index d8911e5..ddedf7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,21 @@ has a matching GitHub release whose body comes from its section below. `berth config check` refuses a slice name no unit can take. An entry with no slice starts its scope exactly as before, on every platform. +### Changed + +- The harbor's About tab is rewritten for v0.4. It leads with the node's version and links to the docs, + changelog, issues and source, adds a section on machines with several cards, and drops the list of planned + features that had shipped or changed. Its sections are cards in two columns on a wide screen, with the same + section rail as the other tabs. +- The harbor header no longer clips the ADVANCED switch or cuts its tagline off: below 1600 px the tagline + and the "& REQUESTS" half of the engines tab are hidden. On a phone an alert's message wraps instead of + ending in an ellipsis, with its time on its own line. + +### Fixed + +- A harbor link to a section, such as `#sec-library` from the engines tab's rail, opened the card tab on a + reload. It now opens the tab that holds the section and scrolls to it. + ## [v0.4.0] - 2026-10-29 One berthd now governs every card on a machine. `berth run --gpu gpu1` puts an engine on one card, diff --git a/docs/harbor.md b/docs/harbor.md index 5ce41b7..a348cd8 100644 --- a/docs/harbor.md +++ b/docs/harbor.md @@ -170,8 +170,10 @@ question an operator actually asks. recent requests and context residency** answer the questions behind the request log, and the storage panel (advanced) says what the harbor's own store keeps. -**The ABOUT tab** is this page's own documentation: the modes, the shortcut keys, -and what every state word and figure means. +**The ABOUT tab** is this page's own documentation: the node's version, links to these docs and the +changelog, several cards, the modes, the shortcut keys, and what every state word and figure means. A +section rail jumps between its sections, as on the other tabs. A section link, from any tab's rail, +opens the tab that holds the section when you share or reload it. ## Telemetry (built in, no other tool needed) - **GPU:** memory, utilisation, temperature, power against its limit, fan, SM and memory diff --git a/internal/harbor/web/harbor.css b/internal/harbor/web/harbor.css index 96ea9f1..f3477c2 100644 --- a/internal/harbor/web/harbor.css +++ b/internal/harbor/web/harbor.css @@ -310,7 +310,10 @@ body.mode-basic .adv-only { display: none !important; } .mode-btn + .mode-btn { border-left: 1px solid var(--hair-2); } .mode-btn[aria-pressed="true"] { color: var(--cyan); background: var(--cyan-wash); } .mode-btn:hover { color: var(--ink); } -.tabs { display: flex; gap: 4px; margin-left: auto; } +.tabs { display: flex; gap: 4px; margin-left: auto; flex: none; } +/* the mode switch and the tab words never shrink: the descriptor gives way first */ +.app-header .mode { flex: none; } +.tab-label { white-space: nowrap; } .tab { appearance: none; background: transparent; @@ -339,7 +342,7 @@ body.mode-basic .adv-only { display: none !important; } padding: 0 6px; line-height: 1.6; } .tab[aria-current="page"] .count { color: var(--amber); border-color: var(--amber-dim); } -.header-status { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; } +.header-status { display: flex; align-items: center; justify-content: flex-end; gap: var(--s2) var(--s3); flex-wrap: wrap; } .node-version { font-family: var(--mono); font-size: var(--fs-small); color: var(--dim); } .update-notice { display: inline-flex; align-items: center; gap: 6px; font-family: var(--mono); font-size: var(--fs-small); } .update-notice .update-link { color: var(--cyan); text-decoration: none; } @@ -1606,16 +1609,48 @@ dialog.sheet::backdrop { background: var(--scrim); backdrop-filter: blur(3px); } .sheet-hint { margin-left: auto; color: var(--dim-2); font-size: var(--fs-micro); letter-spacing: 0; } /* ---------- about ---------- */ -.about { - border: 1px solid var(--hair); +/* a lead card, then the reference sections as cards that flow down two + columns on a wide screen; the rail above jumps between them */ +.about-hero { + border: 1px solid var(--hair-2); border-radius: var(--r2); - background: linear-gradient(180deg, var(--panel-hi) 0, var(--panel) 132px); - box-shadow: var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.035); + background: linear-gradient(180deg, var(--panel-hi) 0, var(--panel) 160px); + box-shadow: var(--e2), inset 0 1px 0 rgba(255, 255, 255, 0.035); padding: var(--s6); display: flex; flex-direction: column; gap: var(--s4); - max-width: 920px; + margin-bottom: var(--s5); +} +.about-hero p { margin: 0; color: var(--ink); font-size: var(--fs-body); line-height: 1.6; max-width: 88ch; } +.about-hero .about-word { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2) var(--s3); color: var(--dim); font-size: var(--fs-label); } +.about-facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 4px var(--s4); margin: 0; font-family: var(--mono); font-size: var(--fs-small); } +.about-facts dt { color: var(--dim-2); letter-spacing: 1px; text-transform: uppercase; } +.about-facts dd { margin: 0; color: var(--ink); overflow-wrap: anywhere; } +.about-links { display: flex; flex-wrap: wrap; gap: var(--s2); margin: 0; padding: 0; list-style: none; } +.about-links a, .about-sec a { + color: var(--cyan); text-decoration: underline; text-decoration-color: var(--cyan-dim); + text-underline-offset: 3px; +} +.about-links a { + display: inline-flex; align-items: center; min-height: 36px; padding: 6px var(--s3); + border: 1px solid var(--hair-2); border-radius: var(--r1); background: var(--panel-2); + font-family: var(--mono); font-size: var(--fs-small); letter-spacing: 1px; text-decoration: none; +} +.about-links a:hover, .about-sec a:hover { color: var(--ink-strong); border-color: var(--cyan-dim); } +.about-links a:focus-visible, .about-sec a:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; } +.about { columns: 2 520px; column-gap: var(--s5); } +.about-sec { + break-inside: avoid; + border: 1px solid var(--hair); + border-radius: var(--r2); + background: var(--panel); + box-shadow: var(--e1); + padding: var(--s5); + margin: 0 0 var(--s5); + display: flex; + flex-direction: column; + gap: var(--s3); } .about .section-title { font-family: var(--mono); @@ -1623,14 +1658,17 @@ dialog.sheet::backdrop { background: var(--scrim); backdrop-filter: blur(3px); } letter-spacing: 2px; color: var(--amber); font-weight: 600; - margin: var(--s3) 0 0 0; -} -.about p { margin: 0; color: var(--ink); font-size: var(--fs-body); max-width: 80ch; } -.about-dl { display: grid; grid-template-columns: 170px minmax(0, 1fr); gap: 6px var(--s4); margin: 0; } -.about-dl dt { font-family: var(--mono); font-size: var(--fs-small); letter-spacing: 1px; color: var(--amber); padding-top: 2px; } -.about-dl dd { margin: 0; font-size: var(--fs-body); color: var(--dim); } -.about-ul { margin: 0; padding-left: 18px; color: var(--dim); font-size: var(--fs-body); } -.about-ul li { margin: 4px 0; } + margin: 0; +} +.about p { margin: 0; color: var(--ink); font-size: var(--fs-body); line-height: 1.6; } +.about-dl { display: grid; grid-template-columns: minmax(96px, max-content) minmax(0, 1fr); gap: 8px var(--s4); margin: 0; } +.about-dl dt { font-family: var(--mono); font-size: var(--fs-small); letter-spacing: 1px; color: var(--amber); padding-top: 2px; white-space: nowrap; } +.about-dl dd { margin: 0; font-size: var(--fs-body); color: var(--dim); line-height: 1.5; } +.about-keys kbd { + display: inline-block; min-width: 22px; padding: 1px 6px; text-align: center; + font-family: var(--mono); font-size: var(--fs-small); color: var(--ink); + border: 1px solid var(--hair-2); border-bottom-width: 2px; border-radius: var(--r1); background: var(--panel-3); +} .about-pre { margin: 0; font-family: var(--mono); @@ -1658,6 +1696,11 @@ dialog.sheet::backdrop { background: var(--scrim); backdrop-filter: blur(3px); } } /* ---------- responsive: one query per breakpoint ---------- */ +@media (max-width: 1599px) { + /* below this the descriptor only fits cut off; the wordmark names the app + and the about tab leads with the same line */ + .tab-long, .descriptor { display: none; } +} @media (max-width: 1279px) { .harbor-grid { grid-template-columns: minmax(0, 7fr) minmax(300px, 5fr); } } @@ -1805,6 +1848,11 @@ dialog.sheet::backdrop { background: var(--scrim); backdrop-filter: blur(3px); } should not have to aim at a 24 px glyph */ .toast-close, .conn .retry, .alert-card .alert-dismiss { min-width: var(--touch); min-height: var(--touch); } .alert-card .alert-dismiss { margin: -8px -6px -8px 0; } + /* an alert's message is the warning itself: on a phone it wraps instead of + ending at "2 waitin..." */ + .alert-card { flex-wrap: wrap; row-gap: 2px; } + .alert-card .alert-msg { white-space: normal; overflow-wrap: anywhere; flex: 1 1 0; } + .alert-card .alert-when { order: 9; flex-basis: 100%; } .conn .retry { margin: -6px -8px -6px 0; } /* every chart's shape, not its words, takes the phone's minimum height: an axis label has had room to be 13 px since the frame stopped scaling text */ @@ -1846,6 +1894,7 @@ dialog.sheet::backdrop { background: var(--scrim); backdrop-filter: blur(3px); } .ticket-stub { display: none; } .ticket-paper { --fold-x: 100%; } .about-dl { grid-template-columns: 1fr; } + .about-hero, .about-sec { padding: var(--s4); } } /* ---------- motion preferences and other media ---------- */ diff --git a/internal/harbor/web/harbor.js b/internal/harbor/web/harbor.js index 30d7b69..1d9873b 100644 --- a/internal/harbor/web/harbor.js +++ b/internal/harbor/web/harbor.js @@ -483,10 +483,19 @@ function wireTabs() { show(key); }); } + // A hash is a tab, a ticket, or a section the rail wrote: a section opens + // the tab that holds it, so a shared or reloaded link lands where it says. const h = location.hash.slice(1); - show(h.startsWith("ticket=") ? "harbor" : h); + if (tabs[h] || h.startsWith("ticket=")) return show(h); + const target = h ? document.getElementById(h) : null; + const key = target ? Object.keys(panels).find((k) => panels[k].contains(target)) : undefined; + show(key); + if (key) pendingSection = target; } let pendingTicket = location.hash.startsWith("#ticket=") ? decodeURIComponent(location.hash.slice(8)) : null; +// A linked section waits for the first status, whose alerts and panels move +// it down the page. +let pendingSection = null; // ---------------- page chrome ---------------- // The sub-navigation, the collapsible panels, the keyboard shortcuts, the @@ -502,6 +511,12 @@ const ENGINES_SECTIONS = [ ["sec-throughput", "Throughput"], ["sec-ledger", "Ledger"], ["sec-lb", "Leaderboard"], ["sec-recent", "Recent"], ["sec-residency", "Residency"], ["sec-storage", "Storage"], ]; +const ABOUT_SECTIONS = [ + ["about-top", "Berth"], ["about-cards", "Cards"], ["about-modes", "Modes"], ["about-nav", "Getting around"], + ["about-keys", "Keyboard"], ["about-states", "Status words"], ["about-kpis", "KPIs"], ["about-counters", "Counters"], + ["about-kv", "Context and KV"], ["about-telemetry", "GPU and machine"], ["about-library", "Library"], + ["about-holders", "Games and stowaways"], ["about-next", "Terminal"], +]; function wireChrome() { try { const stored = localStorage.getItem(COLLAPSE_KEY); @@ -509,6 +524,7 @@ function wireChrome() { } catch (e) { state.collapsed = []; } buildSubnav($("subnav-harbor"), HARBOR_SECTIONS); buildSubnav($("subnav-engines"), ENGINES_SECTIONS); + buildSubnav($("subnav-about"), ABOUT_SECTIONS); wireCollapse(); wireShortcuts(); wireRailMenu(); @@ -1185,8 +1201,14 @@ function demoInsights(range) { } function onStatus(s) { nodeVersion.textContent = "node " + (s.version || "") + (s.harbor_version ? " / harbor " + s.harbor_version : ""); + setText($("about-version"), nodeVersion.textContent); renderAll(s); paintOverall(); + if (pendingSection) { + const target = pendingSection; + pendingSection = null; + requestAnimationFrame(() => target.scrollIntoView({ block: "start" })); + } if (pendingTicket) { const key = pendingTicket; pendingTicket = null; diff --git a/internal/harbor/web/index.html b/internal/harbor/web/index.html index ceae2f3..ac19e45 100644 --- a/internal/harbor/web/index.html +++ b/internal/harbor/web/index.html @@ -281,100 +281,132 @@

STORAGE