feat(web): lamp-only header on the content width

Drops #master-verdict entirely - element, .rail-verdict rules and every
line that wrote into it. The lamp is the only header signal; the
unreachable detail moves onto the board's arr module so nothing is lost.
Rail contents move into .rail-inner capped at the deck's 68rem and
centred, leaving the panel full-bleed.

Committed by the blitz orchestrator: the authoring session wedged with
the work complete on disk and stopped accepting input.
This commit is contained in:
Miguel Palhas
2026-08-24 14:36:18 +01:00
parent ed39b1ca49
commit d1d6b4f9a0
4 changed files with 48 additions and 67 deletions
+3 -3
View File
@@ -235,9 +235,9 @@
"name": "Rail", "name": "Rail",
"kind": "nav", "kind": "nav",
"refersTo": "ground", "refersTo": "ground",
"description": "Full-width panel strip framing the board top (identity + verdict + controls) and bottom (readouts). Hairline-ruled, panel ground, inset highlight.", "description": "Full-width panel strip framing the board top (identity + controls) and bottom (readouts). Hairline-ruled, panel ground, inset highlight. Contents sit in a .rail-inner column capped at 68rem, centred — the panel stays full-bleed. The lamp beside the wordmark is the only header status signal.",
"html": "<header class=\"rail\"><div class=\"rail-id\"><span class=\"lamp\" data-state=\"ok\" aria-hidden=\"true\"></span><h1 class=\"wordmark\">arr</h1></div><p class=\"rail-verdict readout\" data-tone=\"ok\">all signals nominal</p></header>", "html": "<header class=\"rail\"><div class=\"rail-inner\"><div class=\"rail-id\"><span class=\"lamp\" data-state=\"ok\" aria-hidden=\"true\"></span><h1 class=\"wordmark\">arr</h1></div></div></header>",
"css": ".rail { display: flex; align-items: center; gap: var(--space-6); padding: var(--space-3) var(--space-6); border-bottom: 1px solid var(--line); background: var(--panel); box-shadow: inset 0 1px var(--highlight); }" "css": ".rail { padding: var(--space-3) var(--space-6); border-bottom: 1px solid var(--line); background: var(--panel); box-shadow: inset 0 1px var(--highlight); } .rail-inner { display: flex; align-items: center; gap: var(--space-6); width: 100%; max-width: 68rem; margin-inline: auto; }"
} }
], ],
"narrative": { "narrative": {
+32 -31
View File
@@ -18,7 +18,7 @@
fault red) with a soft glow, phosphor-cyan for the one interactive control. fault red) with a soft glow, phosphor-cyan for the one interactive control.
STORY: the operator opens the board, reads the master lamp, and knows in a STORY: the operator opens the board, reads the master lamp, and knows in a
second whether arr can do its job — and which upstream is down, by name. second whether arr can do its job — and which upstream is down, by name.
FIRST VIEWPORT: a top rail (wordmark + master lamp + verdict + version), FIRST VIEWPORT: a top rail (wordmark + master lamp + version),
then the chain: TMDB and PROWLARR modules on the left feeding the central then the chain: TMDB and PROWLARR modules on the left feeding the central
arr module, TRANSMISSION driven on the right, joined by bus hairlines with arr module, TRANSMISSION driven on the right, joined by bus hairlines with
direction arrows. On a phone the chain stacks vertically. direction arrows. On a phone the chain stacks vertically.
@@ -65,36 +65,37 @@
interaction hue. interaction hue.
--> -->
<header class="rail"> <header class="rail">
<div class="rail-id"> <div class="rail-inner">
<span class="lamp" id="master-lamp" data-state="probing" aria-hidden="true"></span> <div class="rail-id">
<h1 class="wordmark">arr</h1> <span class="lamp" id="master-lamp" data-state="probing" aria-hidden="true"></span>
</div> <h1 class="wordmark">arr</h1>
<nav class="rail-nav" aria-label="views"> </div>
<button type="button" class="control" id="nav-library" aria-pressed="false"> <nav class="rail-nav" aria-label="views">
library <button type="button" class="control" id="nav-library" aria-pressed="false">
</button> library
<button type="button" class="control" id="nav-queues" aria-pressed="false"> </button>
queues<span class="nav-count readout" id="queues-count" hidden></span> <button type="button" class="control" id="nav-queues" aria-pressed="false">
</button> queues<span class="nav-count readout" id="queues-count" hidden></span>
<button type="button" class="control" id="nav-settings" aria-pressed="false"> </button>
settings <button type="button" class="control" id="nav-settings" aria-pressed="false">
</button> settings
</nav> </button>
<div class="rail-search"> </nav>
<input <div class="rail-search">
id="search" <input
class="search-input readout" id="search"
type="text" class="search-input readout"
spellcheck="false" type="text"
autocomplete="off" spellcheck="false"
placeholder="search · title / tmdb id / tt… / magnet" autocomplete="off"
aria-label="unified search titles, TMDB or IMDb ids, magnets and .torrent links" placeholder="search · title / tmdb id / tt… / magnet"
/> aria-label="unified search — titles, TMDB or IMDb ids, magnets and .torrent links"
<span class="search-hint readout" id="search-hint" aria-hidden="true">/</span> />
</div> <span class="search-hint readout" id="search-hint" aria-hidden="true">/</span>
<p class="rail-verdict readout" id="master-verdict" role="status">probing chain…</p> </div>
<div class="rail-meta"> <div class="rail-meta">
<span class="readout dim" id="version" data-testid="version">v —</span> <span class="readout dim" id="version" data-testid="version">v —</span>
</div>
</div> </div>
</header> </header>
+2 -6
View File
@@ -148,7 +148,6 @@ function must<T extends Element>(selector: string): T {
function main() { function main() {
const board = must<HTMLElement>(".board"); const board = must<HTMLElement>(".board");
const masterLamp = must<HTMLElement>("#master-lamp"); const masterLamp = must<HTMLElement>("#master-lamp");
const verdict = must<HTMLElement>("#master-verdict");
const version = must<HTMLElement>("#version"); const version = must<HTMLElement>("#version");
const master = moduleRefs("master"); const master = moduleRefs("master");
@@ -193,10 +192,9 @@ function main() {
if (probe.kind === "unreachable") { if (probe.kind === "unreachable") {
masterLamp.dataset.state = "unreachable"; masterLamp.dataset.state = "unreachable";
verdict.dataset.tone = "fault";
verdict.textContent = `daemon unreachable — ${probe.detail}`;
version.textContent = "v —"; version.textContent = "v —";
setModule(master, "unreachable", "fault", "down"); // the outage detail lives on the board's arr module — the header has no verdict line
setModule(master, "unreachable", "fault", "down", probe.detail);
for (const refs of Object.values(checks)) { for (const refs of Object.values(checks)) {
setModule(refs, "off", "idle", "no signal"); setModule(refs, "off", "idle", "no signal");
} }
@@ -210,8 +208,6 @@ function main() {
const degraded = report.status === "degraded"; const degraded = report.status === "degraded";
masterLamp.dataset.state = degraded ? "degraded" : "ok"; masterLamp.dataset.state = degraded ? "degraded" : "ok";
verdict.dataset.tone = degraded ? "warn" : "ok";
verdict.textContent = degraded ? "chain degraded" : "all signals nominal";
version.textContent = `v${report.version}`; version.textContent = `v${report.version}`;
setModule(master, degraded ? "degraded" : "ok", degraded ? "warn" : "ok", report.status); setModule(master, degraded ? "degraded" : "ok", degraded ? "warn" : "ok", report.status);
+11 -27
View File
@@ -136,15 +136,22 @@ body {
/* ---- rails ---------------------------------------------------------- */ /* ---- rails ---------------------------------------------------------- */
.rail { .rail {
display: flex;
align-items: center;
gap: var(--space-6);
padding: var(--space-3) var(--space-6); padding: var(--space-3) var(--space-6);
border-bottom: 1px solid var(--line); border-bottom: 1px solid var(--line);
background: var(--panel); background: var(--panel);
box-shadow: inset 0 1px var(--highlight); box-shadow: inset 0 1px var(--highlight);
} }
/* contents share the deck's column; the panel itself stays full-bleed */
.rail-inner {
display: flex;
align-items: center;
gap: var(--space-6);
width: 100%;
max-width: 68rem;
margin-inline: auto;
}
.rail-id { .rail-id {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -160,24 +167,6 @@ body {
line-height: 1; line-height: 1;
} }
.rail-verdict {
margin: 0;
flex: 1;
color: var(--ink-muted);
}
.rail-verdict[data-tone="ok"] {
color: var(--signal-ok);
}
.rail-verdict[data-tone="warn"] {
color: var(--signal-warn);
}
.rail-verdict[data-tone="fault"] {
color: var(--signal-fault);
}
.rail-meta { .rail-meta {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -1770,7 +1759,7 @@ body {
/* ---- small screens --------------------------------------------------- */ /* ---- small screens --------------------------------------------------- */
@media (max-width: 46rem) { @media (max-width: 46rem) {
.rail { .rail-inner {
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--space-3) var(--space-4); gap: var(--space-3) var(--space-4);
} }
@@ -1788,11 +1777,6 @@ body {
white-space: nowrap; white-space: nowrap;
} }
.rail-verdict {
flex-basis: 100%;
order: 3;
}
.rail-search { .rail-search {
order: 4; order: 4;
flex-basis: 100%; flex-basis: 100%;