Skip to content

feat(bearings): compact open-work board with status colors and expandable status - #47

Merged
doitdigital0495 merged 5 commits into
mainfrom
fm/bearings-board-redesign
Oct 2, 2026
Merged

doitdigital0495 merged 5 commits into
mainfrom
fm/bearings-board-redesign

Conversation

@doitdigital0495

Copy link
Copy Markdown
Owner

Intent

"I find this view not insightful enough. Too much blank project that have nothing running. Also too much text between things. Keep all open work cleanly beneath each other with color for status and I want to expand the section for current status."
The view is the Bearings board page generated by bin/fm-bearings-board.sh (rendered at .lavish/bearings-board.html, snapshot data from bin/fm-bearings-snapshot.sh).

What Changed

  • The Bearings board template now shows underway and queued tasks as one stacked "Open work" list. Each task is a native <details> row with a color-coded left border and badge for its state (danger, warn, online, info or neutral), and its status text expands under a "Current status" cue. The two-column layout is gone, Completed is now a collapsed Completed · N disclosure, and spacing and padding are tighter.
  • Empty sections no longer take space. Needs you, Open work, the queue, Completed and Projects are hidden when they have nothing to show, and a single "No open work." line appears only when no work section is visible. A project panel renders only when the project has people or open questions, so the "No live worker" filler line is gone. The SKILL.md description is updated to match.
  • The dark palette is rebased onto inlined Firstmate design tokens. The render harness and fm-bearings-board-render.test.sh add cases for:
    • hidden idle projects and empty sections
    • status colors and disclosures in the mixed-project list
    • Needs you ordering first
    • the empty-state message
    • the unchanged Captain's Call answer contract

🤖 Generated with Claude Code

Risk Assessment

⚠️ Medium: The change only touches the renderer and its tests, and the answer contract and dark/offline rules are kept. But one stated intent item may still be unmet: idle project panels still render on the status page. The change also makes several layout and disclosure decisions the intent did not ask for, and those need the captain's confirmation.

Testing

Both bearings unit suites passed. Three real boards (fleet, status page, empty) were built through fm-bearings-board.sh with real lavish-axi in an isolated lab home. They were rendered in headless Chrome, screenshotted collapsed and after clicking the 'Current status' disclosures, with computed tones, border colors and section order dumped to JSON. Every intent scenario passed. The Completed details was seen collapsed but not clicked open. The lab session, its listener and its temp dirs were torn down.

  • Live validation: ✅ go - 10 of 10 scenarios driven live against the product
Scenario Result Live Evidence
Open work across projects stacks in one list, each row colored by status (working green, blocked red, paused amber, reviewing blue, unknown neutral) ✅ pass live fleet-collapsed.png; fleet-facts.json shows tones online/danger/warn/info/neutral with distinct borderLeftColor
Clicking 'Current status +' on a row expands its current status in place, and other rows stay collapsed ✅ pass live fleet-expanded.png shows 'Reviewing changes' and 'Waiting for capacity' under the clicked rows, with the cue flipped to '-'
Idle projects with nothing running get no panel on the status page ✅ pass live status-collapsed.png and status-facts.json: projects == [Reports, Portal]; Idle Alpha/Idle Beta absent
Adversarial: a project with no worker but an open question still shows its question ✅ pass live status-collapsed.png Portal card shows 'Hosting is not chosen.' / 'Use dedicated hosting.'
Needs you (Captain Call) renders above Open work and Waiting to start, and its answer form is intact ✅ pass live fleet-facts.json sectionOrder [call, current, charted, landed]; render test 'Captain Call still queues fm-bearings-answer.v1'
Underway rows show the repo only, never the worker kind or the routing id (null repo means no subtitle) ✅ pass live fleet-facts.json: 'Unlabelled job' (id mate/child-1, repo null) has sub ''; no 'ship'/'scout' text on rows
A queued row with no reason reads as neutral 'queued', not yellow 'waiting' ✅ pass live fleet-collapsed.png: 'Add CSV export' has a QUEUED neutral badge, and 'Remove old tabs' (with a reason) has WAITING
Adversarial: an empty fleet takes no space beyond one 'No open work.' line ✅ pass live empty-collapsed.png; empty-facts.json idleMsgVisible true, no visible sections
Completed stays collapsed but one click away ✅ pass live fleet-collapsed.png shows a '▶ Completed · 1' disclosure. It was not clicked in this run.
Board stays explicitly dark and offline (color-scheme dark, dark body background) ✅ pass live fleet-facts.json colorScheme 'dark', bodyBg rgb(12, 18, 28)

Fleet board, collapsed (stacked open work, status colors, Needs you first)

Evidence: Computed render facts (tones, border colors, section order, expanded detail)

Source: Computed render facts (tones, border colors, section order, expanded detail)

{
  "before": {
    "label": "collapsed",
    "sectionOrder": [
      "bb-call-section",
      "bb-current-section",
      "bb-charted-section",
      "bb-landed-section"
    ],
    "rows": [
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "online",
        "title": "Speed up portal load",
        "sub": "alpha",
        "badge": "working",
        "borderColor": "rgb(136, 212, 173)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "danger",
        "title": "Fix export crash",
        "sub": "beta",
        "badge": "blocked",
        "borderColor": "rgb(241, 138, 120)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "warn",
        "title": "Migrate invoices",
        "sub": "alpha",
        "badge": "paused",
        "borderColor": "rgb(236, 192, 104)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "info",
        "title": "Audit dashboards",
        "sub": "beta",
        "badge": "reviewing",
        "borderColor": "rgb(139, 199, 240)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "neutral",
        "title": "Unlabelled job",
        "sub": "",
        "badge": "future-state",
        "borderColor": "rgb(147, 163, 185)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "warn",
        "title": "Remove old tabs",
        "sub": "gamma",
        "badge": "waiting",
        "borderColor": "rgb(236, 192, 104)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "neutral",
        "title": "Add CSV export",
        "sub": "gamma",
        "badge": "queued",
        "borderColor": "rgb(147, 163, 185)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "danger",
        "title": "Repair inventory",
        "sub": "delta",
        "badge": "needs repair",
        "borderColor": "rgb(241, 138, 120)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DIV",
        "open": null,
        "tone": "",
        "title": "Update domains",
        "sub": "alpha · ship",
        "badge": "",
        "borderColor": "rgb(212, 220, 232)",
        "detailVisible": null,
        "detail": ""
      }
    ],
    "projects": [],
    "idleMsgVisible": false,
    "completed": null,
    "bodyBg": "rgb(12, 18, 28)",
    "colorScheme": "dark",
    "errors": ""
  },
  "after": {
    "label": "expanded-first-underway-and-charted",
    "sectionOrder": [
      "bb-call-section",
      "bb-current-section",
      "bb-charted-section",
      "bb-landed-section"
    ],
    "rows": [
      {
        "tag": "DETAILS",
        "open": true,
        "tone": "online",
        "title": "Speed up portal load",
        "sub": "alpha",
        "badge": "working",
        "borderColor": "rgb(136, 212, 173)",
        "detailVisible": true,
        "detail": "Reviewing changes"
      },
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "danger",
        "title": "Fix export crash",
        "sub": "beta",
        "badge": "blocked",
        "borderColor": "rgb(241, 138, 120)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "warn",
        "title": "Migrate invoices",
        "sub": "alpha",
        "badge": "paused",
        "borderColor": "rgb(236, 192, 104)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "info",
        "title": "Audit dashboards",
        "sub": "beta",
        "badge": "reviewing",
        "borderColor": "rgb(139, 199, 240)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "neutral",
        "title": "Unlabelled job",
        "sub": "",
        "badge": "future-state",
        "borderColor": "rgb(147, 163, 185)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DETAILS",
        "open": true,
        "tone": "warn",
        "title": "Remove old tabs",
        "sub": "gamma",
        "badge": "waiting",
        "borderColor": "rgb(236, 192, 104)",
        "detailVisible": true,
        "detail": "Waiting for capacity"
      },
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "neutral",
        "title": "Add CSV export",
        "sub": "gamma",
        "badge": "queued",
        "borderColor": "rgb(147, 163, 185)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DETAILS",
        "open": false,
        "tone": "danger",
        "title": "Repair inventory",
        "sub": "delta",
        "badge": "needs repair",
        "borderColor": "rgb(241, 138, 120)",
        "detailVisible": true,
        "detail": ""
      },
      {
        "tag": "DIV",
        "open": null,
        "tone": "",
        "title": "Update domains",
        "sub": "alpha · ship",
        "badge": "",
        "borderColor": "rgb(212, 220, 232)",
        "detailVisible": null,
        "detail": ""
      }
    ],
    "projects": [],
    "idleMsgVisible": false,
    "completed": null,
    "bodyBg": "rgb(12, 18, 28)",
    "colorScheme": "dark",
    "errors": ""
  }
}
Evidence: Real build output (session live, bound, armed)

Source: Real build output (session live, bound, armed)

board: /tmp/fm-lab-bearings-redesign.SuRc/.lavish/bearings-board.html
session:
  file: /tmp/fm-lab-bearings-redesign.SuRc/.lavish/bearings-board.html
  url: "https://desktop-agentic.tail1dbb2d.ts.net:8387/session/51030bb3717977cc"
  status: opened
session: live
served: /tmp/fm-lab-bearings-redesign.SuRc/.lavish/bearings-board.html
bound: lavish-51030bb3717977cc
armed: lavish-51030bb3717977cc
listening: live
Evidence: Built fleet board HTML

Source: Built fleet board HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="dark" />
<title>Task status</title>
<style>
/* ============================================================
   Bearings board - Firstmate Design System, inlined for portability.
   Firstmate's sign, card and status vocabulary with self-contained dark
   tokens and system fonts. No network assets are needed.
   ============================================================ */
:root {
  color-scheme: dark;
  --rust-600: #f18a78; --rust-500: #d35f3f; --rust-400: #ec9279; --rust-050: #302026;
  --navy-700: #101722;
  --gold-600: #ecc068; --gold-500: #e0a52e; --gold-300: #f0d38c; --gold-100: #302a1c;
  --ocean-600: #8bc7f0; --ocean-500: #3c7ea6; --ocean-050: #172a3c;
  --sea-700: #88d4ad; --sea-500: #2f6b4f; --sea-200: #315344; --sea-050: #182c24;
  --paper-000: #182230; --paper-100: #0c121c; --paper-300: #283444;
  --cream-line: #334154;
  --ink-900: #070c13; --ink-500: #a9b6c8; --ink-300: #93a3b9;
  --white: #f1f5fa;
  --bg-page: var(--paper-100);
  --surface-card: #131c29;
  --surface-card-warm: var(--paper-000);
  --text-strong: var(--white); --text-body: #d4dce8;
  --text-muted: var(--ink-500); --text-faint: var(--ink-300);
  --border-default: var(--cream-line); --border-soft: var(--paper-300);
  --status-online: var(--sea-500); --status-online-soft: var(--sea-050);
  --status-warn: var(--gold-600); --status-warn-soft: var(--gold-100);
  --status-danger: var(--rust-600); --status-danger-soft: var(--rust-050);
  --status-info: var(--ocean-500); --status-info-soft: var(--ocean-050);
  --font-display: "Chango", "Cooper Black", Rockwell, Georgia, serif;
  --font-sans: "Jost", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --fs-h3: 1.4rem; --fs-h4: 1.15rem; --fs-base: 1rem; --fs-sm: 0.9375rem;
  --fs-xs: 0.8125rem; --fs-2xs: 0.6875rem;
  --ls-caps: 0.16em;
  --radius-xs: 6px; --radius-sm: 9px; --radius-md: 12px; --radius-banner: 7px;
  --radius-lg: 18px; --radius-pill: 999px;
  --shadow-sm: 0 1px 2px rgba(36, 28, 20, 0.06), 0 4px 10px rgba(36, 28, 20, 0.06);
  --shadow-hard: 4px 4px 0 var(--ink-900);
  --shadow-hard-sm: 3px 3px 0 var(--ink-900);
  --focus-ring: 0 0 0 3px var(--gold-300);
  --container-app: 1180px;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: #0c121c; }
[hidden] { display: none !important; }
body {
  font-family: var(--font-sans);
  color: var(--text-body);
  background: var(--bg-page);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none; }

/* ---- fm-badge (enamel sign-pin), verbatim contract ---- */
.fm-badge {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-2xs); font-weight: 800; line-height: 1;
  padding: 5px 9px 4px; text-transform: uppercase; letter-spacing: 0.07em;
  border-radius: var(--radius-xs); border: 1.5px solid var(--ink-900);
  box-shadow: 2px 2px 0 var(--ink-900); white-space: nowrap;
}
.fm-badge--online  { background: var(--sea-050); color: #88d4ad; }
.fm-badge--warn    { background: var(--gold-500); color: var(--navy-700); }
.fm-badge--danger  { background: var(--rust-050); color: var(--rust-600); }
.fm-badge--info    { background: var(--ocean-050); color: var(--ocean-600); }
.fm-badge--neutral { background: var(--paper-000); color: var(--text-muted); }
.fm-badge--solid   { background: var(--rust-050); color: var(--rust-600); }

/* ---- fm-btn ---- */
.fm-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--font-sans); font-weight: 800; line-height: 1; white-space: nowrap;
  border: 2px solid transparent; border-radius: var(--radius-banner); cursor: pointer;
  text-decoration: none; transition: background 120ms, border-color 120ms, transform 120ms, box-shadow 120ms;
}
.fm-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.fm-btn:active { transform: translateY(1px); }
.fm-btn--sm { font-size: var(--fs-xs); padding: 8px 16px; }
.fm-btn--primary { background: var(--rust-500); color: var(--white); border-color: var(--ink-900); box-shadow: var(--shadow-hard-sm); }
.fm-btn--primary:hover { background: var(--rust-700); }
.fm-btn--gold { background: var(--gold-500); color: var(--navy-700); border-color: var(--ink-900); box-shadow: var(--shadow-hard-sm); }
.fm-btn--gold:hover { background: var(--gold-600); color: var(--navy-700); }
.fm-btn[disabled] { opacity: 0.5; cursor: not-allowed; }

/* ---- fm-card ---- */
.fm-card {
  background: var(--surface-card); border: 1px solid var(--border-default);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); overflow: hidden;
}
.fm-card--poster { border: 2px solid var(--ink-900); box-shadow: var(--shadow-hard); border-radius: var(--radius-md); }
.fm-card--warm { background: var(--surface-card-warm); }

/* ---- fm-sign (eyebrow section label) ---- */
.fm-sign {
  display: inline-flex; align-items: center; gap: 8px; font-weight: 800;
  text-transform: uppercase; letter-spacing: var(--ls-caps); font-size: var(--fs-xs);
  line-height: 1; white-space: nowrap;
}
.fm-sign--eyebrow { color: #f2a187; }
.fm-sign--muted { color: var(--text-muted); }
.fm-sign svg { width: 14px; height: 14px; }
.fm-ico { width: 1.15em; height: 1.15em; flex: none; vertical-align: -0.18em; }

/* ============================================================
   Board chrome (bb-*) - built on the same tokens
   ============================================================ */
.bb-nav {
  position: sticky; top: 0; z-index: 20;
  background: color-mix(in srgb, var(--paper-100) 86%, transparent);
  backdrop-filter: saturate(150%) blur(10px);
  border-bottom: 1px solid var(--border-default);
}
.bb-nav__inner {
  max-width: var(--container-app); margin: 0 auto; padding: 0 28px; height: 64px;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.bb-brand { display: inline-flex; align-items: center; gap: 12px; }
.bb-brand__disc {
  display: grid; place-items: center; width: 34px; height: 34px; flex: none;
  border-radius: 999px; background: var(--rust-500); color: var(--white);
  border: 2px solid var(--ink-900); box-shadow: var(--shadow-hard-sm);
}
.bb-brand__disc svg { width: 60%; height: 60%; }
.bb-brand__wm { font-family: var(--font-display); font-size: 23px; color: var(--text-strong); line-height: 1; }
.bb-meta-mono { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-faint); white-space: nowrap; }

.bb-main {
  max-width: var(--container-app); margin: 0 auto;
  padding: 18px 28px 40px; display: flex; flex-direction: column; gap: 16px;
}

/* stat strip */
.bb-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
.bb-stat {
  display: flex; flex-direction: column; gap: 4px; padding: 10px 12px; min-width: 0;
  background: var(--surface-card-warm); border: 1px solid var(--border-soft); border-radius: var(--radius-md);
}
.bb-stat--call { background: var(--rust-050); border: 2px solid var(--rust-500); }
.bb-stat__num { font-family: var(--font-mono); font-size: var(--fs-h3); font-weight: 600; line-height: 1; color: var(--text-strong); }
.bb-stat--call .bb-stat__num { color: var(--rust-600); }
.bb-stat__label { font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); }

.bb-section { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.bb-section__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.bb-cols { display: flex; flex-direction: column; gap: 16px; }

/* ---- Captain's Call: one card at a time, dealt off a visible stack ---- */
.bb-callwrap { display: flex; flex-direction: column; gap: 12px; }
/* extra top margin clears the pile edges that poke below the active card */
.bb-stacknav { display: flex; align-items: center; gap: 10px; margin-top: 6px; }
.bb-stacknav__count { flex: 1 1 auto; font-family: var(--font

... [30929 bytes truncated] ...

   window.lavish.queuePrompt(
          "Captain's Call answer - " + item.title + ": " + displayAnswer,
          { tag: "choice", text: item.title + " -> " + displayAnswer, element: form,
            data: ctxData }
        );
      }
      card.classList.add("is-queued");
      /* deal the next card once this one is answered */
      if (!statusPage) setTimeout(function () { showCard(active < cards.length - 1 ? active + 1 : active); }, 450);
    });

    pad.appendChild(form);
    card.appendChild(pad);
    deck.appendChild(card);
  });

  /* stack navigation: one card at a time, dealt off the pile */
  var cards = Array.prototype.slice.call(deck.children);
  var active = 0;
  var stackCount = document.getElementById("bb-stack-count");
  var stackPrev = document.getElementById("bb-stack-prev");
  var stackNext = document.getElementById("bb-stack-next");
  var stackNav = stackCount.parentNode;
  function showCard(i) {
    active = Math.max(0, Math.min(cards.length - 1, i));
    cards.forEach(function (c, j) { c.hidden = j !== active; });
    var answered = deck.querySelectorAll(".is-queued").length;
    stackCount.textContent = "card " + (active + 1) + " of " + cards.length +
      (answered ? " · " + answered + " answered" : "");
    stackPrev.disabled = active === 0;
    stackNext.disabled = active === cards.length - 1;
    /* the pile thins as the deal approaches the bottom */
    deck.classList.toggle("bb-call--penult", active === cards.length - 2);
    deck.classList.toggle("bb-call--last", active === cards.length - 1);
  }
  if (cards.length && statusPage) {
    stackNav.hidden = true;
  } else if (cards.length) {
    stackPrev.addEventListener("click", function () { showCard(active - 1); });
    stackNext.addEventListener("click", function () { showCard(active + 1); });
    showCard(0);
  } else {
    deck.classList.add("bb-call--empty");
    deck.appendChild(el("div", "bb-empty", (data.coverage || []).length ?
      "No answer is requested in the checked tasks. Some task information is incomplete; see the notes below." :
      "Nothing needs your action right now."));
    stackNav.hidden = true;
  }

  /* Only populated sections render. */
  document.getElementById("bb-current-section").hidden = !data.underway.length;
  document.getElementById("bb-call-section").hidden = !callTotal;
  document.getElementById("bb-charted-section").hidden =
    !data.charted.length && !chartedMoreQueued && !chartedMoreWarnings;
  document.getElementById("bb-landed-section").hidden = !data.landed.length;
  document.getElementById("bb-landed-label").textContent = "Completed · " + data.landed.length;

  function stateTone(state) {
    if (/^(blocked|failed|needs-decision|needs_decision)$/.test(state)) return "danger";
    if (/^(paused|waiting|held|captain_hold|stalled)$/.test(state)) return "warn";
    if (/^(working|running|fixing)$/.test(state)) return "online";
    if (/^(review|reviewing|validating|testing)$/.test(state)) return "info";
    return "neutral";
  }
  function workRow(tone, title, repo, status, detail) {
    var row = el("details", "bb-row bb-work bb-work--" + tone);
    var summary = el("summary", "bb-work__summary");
    summary.appendChild(badge(tone, status));
    var main = el("div", "bb-row__main");
    main.appendChild(el("div", "bb-row__title", title));
    main.appendChild(el("div", "bb-row__sub", repo));
    summary.appendChild(main);
    summary.appendChild(el("span", "bb-work__cue", "Current status"));
    row.appendChild(summary);
    row.appendChild(el("div", "bb-work__detail", detail));
    return row;
  }

  /* Underway: one row per task, never grouped into empty project cards.
     Keep the worker kind and routing identity out of the status line. */
  var uw = document.getElementById("bb-underway");
  data.underway.forEach(function (t) {
    uw.appendChild(workRow(stateTone(t.state), t.name, t.repo || "", t.state, t.doing));
  });

  /* Landed */
  var ld = document.getElementById("bb-landed");
  data.landed.forEach(function (t) {
    var row = el("div", "bb-row");
    var chk = el("span", "bb-row__check");
    chk.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>';
    row.appendChild(chk);
    var main = el("div", "bb-row__main");
    main.appendChild(el("div", "bb-row__title", t.what));
    main.appendChild(el("div", "bb-row__sub", (t.repo || t.id) + " · " + t.owner));
    row.appendChild(main);
    if (t.pr_url) {
      var a = el("a", "bb-row__pr", "#" + t.pr_url.split("/").pop());
      a.href = t.pr_url; a.title = t.pr_url; a.target = "_blank"; a.rel = "noopener";
      row.appendChild(a);
    }
    ld.appendChild(row);
  });

  /* Charted Next + dispatch picker */
  var ch = document.getElementById("bb-charted");
  var bar = document.getElementById("bb-dispatch");
  var barCount = document.getElementById("bb-dispatch-count");
  var barBtn = document.getElementById("bb-dispatch-btn");
  var anyPickable = false;

  function pickedIds() {
    return Array.prototype.map.call(ch.querySelectorAll(".bb-pick:checked"), function (c) { return c.value; });
  }
  function refreshBar(limitMessage) {
    var n = pickedIds().length;
    barCount.textContent = limitMessage || (n ? n + " selected to start" : "select work to start");
    barBtn.disabled = !n;
  }

  chartedOrder(data.charted).forEach(function (t) {
    var tone = isWarning(t) ? "danger" : t.reason ? "warn" : "neutral";
    var row = workRow(tone, t.title, t.repo || (statusPage ? "" : t.id),
      isWarning(t) ? "needs repair" : t.reason ? "waiting" : "queued", t.reason || "Ready for dispatch.");
    var summary = row.children[0];
    if (t.dispatchable && !isWarning(t)) {
      anyPickable = true;
      var pick = document.createElement("input");
      pick.type = "checkbox"; pick.className = "bb-pick"; pick.value = t.id;
      pick.setAttribute("aria-label", "Select " + t.title + " to start");
      pick.addEventListener("change", function () {
        if (pick.checked && utf8ByteLength(pickedIds().join(",")) > 512) {
          pick.checked = false;
          refreshBar("Selection limit reached (512 bytes maximum). ");
          return;
        }
        refreshBar();
      });
      summary.appendChild(pick);
    }
    ch.appendChild(row);
  });
  var chartedShown = chartedQueued(data.charted).length;
  var chartedTotal = chartedShown + chartedMoreQueued;
  document.getElementById("bb-charted-sub").textContent =
    chartedMoreQueued ? "showing " + chartedShown + " of " + chartedTotal : "";
  if (chartedMoreQueued) {
    ch.appendChild(el("span", "bb-morechip", "+" + chartedMoreQueued + " more waiting - ask for the complete status page"));
  }
  if (chartedMoreWarnings) {
    ch.appendChild(el("span", "bb-morechip", "+" + chartedMoreWarnings + " more repair warning" + (chartedMoreWarnings === 1 ? "" : "s") + " - ask for the complete status page"));
  }

  if (statusPage && !anyPickable) document.getElementById("bb-charted-section").hidden = true;
  document.getElementById("bb-idle").hidden = ["bb-current-section", "bb-call-section", "bb-charted-section"]
    .some(function (id) { return !document.getElementById(id).hidden; });
  if (anyPickable) {
    bar.hidden = false;
    refreshBar();
    barBtn.addEventListener("click", function () {
      var ids = pickedIds();
      var dispatchAnswer = ids.join(",");
      if (!ids.length) return;
      if (utf8ByteLength(dispatchAnswer) > 512) {
        refreshBar("Selection is too long to queue (512 bytes maximum). ");
        return;
      }
      if (window.lavish && window.lavish.queuePrompt) {
        window.lavish.queuePrompt(
          "Dispatch order - start this queued work now: " + ids.join(", "),
          { tag: "choice", text: "Dispatch: " + ids.join(", "), element: bar,
            queueKey: "dispatch.charted",
            data: { question: "dispatch.charted", answer: dispatchAnswer } }
        );
      }
      bar.classList.add("is-queued");
    });
  }
  } catch (e) {
    renderBoardError("The saved page information could not be displayed");
  }
})();
</script>
</body>
</html>

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

🔧 **Rebase** - 3 issues found → auto-fixed ✅
  • ⚠️ .agents/skills/bearings/assets/board-template.html - merge conflict rebasing onto origin/main
  • ⚠️ tests/assets/board-render-harness.mjs - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-bearings-board-render.test.sh - merge conflict rebasing onto origin/main

🔧 Fix applied.
✅ Re-checked - no issues remain.

⚠️ **Review** - 2 issues (1 warning, 1 info)
  • 🚨 .agents/skills/bearings/assets/board-template.html:595 - Intent: "Too much blank project that have nothing running." The all-projects status page is rendered to the same .lavish/bearings-board.html. On that page data.projects.forEach(renderProject) (lines 595-597) still renders a full card for every registered project. An idle project still gets a full card with name, delivery, status badge, detail and "No live worker is recorded for this project." (line 583). That is the blank project panel the captain complained about, and this diff leaves it unchanged. The existing test test_projects_include_idle_work_shared_leads_and_unassigned_people still asserts that idle projects render. The new test test_empty_sections_and_idle_projects_do_not_take_up_space only renders a board without projects[], so it never exercises this path. The new comment at line 771 ("idle projects have no panel at all") is false for the status page. Decision needed: should idle projects collapse into one line, or be dropped from the status page entirely?
  • ⚠️ .agents/skills/bearings/assets/board-template.html:807 - This reverses an earlier deliberate decision. The old code said "Keep the worker kind and routing identity out of the status line", and its test asserted .sub | test(&#34;ship&#34;) | not. The underway row now shows t.kind as detail meta (workRow(..., t.doing, t.kind)). It also uses t.id as the subtitle whenever t.repo is null, and the test now expects .sub == &#34;mate/child-1&#34;. The status-page project test separately insists on "without routing ids". The intent does not ask for kind or routing ids to be shown. Confirm this re-exposure is wanted, or keep t.repo only and drop the kind meta.
  • ⚠️ .agents/skills/bearings/assets/board-template.html:390 - Simplification/intent pass: Captain's Call ("Needs you") used to be the first section. It is now rendered after Open work and after the Charted Next queue. Decisions that block work now sit below backlog rows. The intent asks only for open work to stack with status colors. It does not ask for decisions to drop below the queue. Confirm the reorder, or put Needs you back above Charted Next.
  • ⚠️ .agents/skills/bearings/assets/board-template.html:271 - Simplification/intent pass: Completed changed from an always-visible section to a collapsed &lt;details&gt; ("Completed · N"). The intent asks for an expandable current status, not a collapsed completions list. The bearings SKILL also describes Recently Landed as "rendered in full on every run". Confirm the collapse, or keep Completed open.
  • ℹ️ .agents/skills/bearings/assets/board-template.html:848 - Every non-warning Charted row now gets a yellow "waiting" badge, including a dispatchable row with no reason. Its expanded detail then reads "Ready for dispatch.", so the badge and the detail contradict each other. Before this change such a row had no badge. Consider a neutral "queued" tone and label when t.reason is absent.
  • ℹ️ .agents/skills/bearings/assets/board-template.html:778 - bb-idle visibility is computed from data.charted.length before line 879 hides bb-charted-section on the status page when !anyPickable. Example: a status page whose only open items are non-dispatchable charted rows, with no underway and no calls. The charted section is hidden, bb-idle also stays hidden, and the open-work area shows nothing and no "No open work." line. Compute bb-idle after the final charted visibility is known.

🔧 Fix applied.
8 issues (1 error, 4 warnings, 3 infos) still open:

  • 🚨 .agents/skills/bearings/assets/board-template.html:595 - Intent: "Too much blank project that have nothing running." The all-projects status page is rendered to the same .lavish/bearings-board.html. On that page data.projects.forEach(renderProject) (lines 595-597) still renders a full card for every registered project. An idle project still gets a full card with name, delivery, status badge, detail and "No live worker is recorded for this project." (line 583). That is the blank project panel the captain complained about, and this diff leaves it unchanged. The existing test test_projects_include_idle_work_shared_leads_and_unassigned_people still asserts that idle projects render. The new test test_empty_sections_and_idle_projects_do_not_take_up_space only renders a board without projects[], so it never exercises this path. The new comment at line 771 ("idle projects have no panel at all") is false for the status page. Decision needed: should idle projects collapse into one line, or be dropped from the status page entirely?
  • ⚠️ .agents/skills/bearings/assets/board-template.html:807 - This reverses an earlier deliberate decision. The old code said "Keep the worker kind and routing identity out of the status line", and its test asserted .sub | test(&#34;ship&#34;) | not. The underway row now shows t.kind as detail meta (workRow(..., t.doing, t.kind)). It also uses t.id as the subtitle whenever t.repo is null, and the test now expects .sub == &#34;mate/child-1&#34;. The status-page project test separately insists on "without routing ids". The intent does not ask for kind or routing ids to be shown. Confirm this re-exposure is wanted, or keep t.repo only and drop the kind meta.
  • ⚠️ .agents/skills/bearings/assets/board-template.html:390 - Simplification/intent pass: Captain's Call ("Needs you") used to be the first section. It is now rendered after Open work and after the Charted Next queue. Decisions that block work now sit below backlog rows. The intent asks only for open work to stack with status colors. It does not ask for decisions to drop below the queue. Confirm the reorder, or put Needs you back above Charted Next.
  • ⚠️ .agents/skills/bearings/assets/board-template.html:271 - Simplification/intent pass: Completed changed from an always-visible section to a collapsed &lt;details&gt; ("Completed · N"). The intent asks for an expandable current status, not a collapsed completions list. The bearings SKILL also describes Recently Landed as "rendered in full on every run". Confirm the collapse, or keep Completed open.
  • ℹ️ .agents/skills/bearings/assets/board-template.html:848 - Every non-warning Charted row now gets a yellow "waiting" badge, including a dispatchable row with no reason. Its expanded detail then reads "Ready for dispatch.", so the badge and the detail contradict each other. Before this change such a row had no badge. Consider a neutral "queued" tone and label when t.reason is absent.
  • ℹ️ .agents/skills/bearings/assets/board-template.html:778 - bb-idle visibility is computed from data.charted.length before line 879 hides bb-charted-section on the status page when !anyPickable. Example: a status page whose only open items are non-dispatchable charted rows, with no underway and no calls. The charted section is hidden, bb-idle also stays hidden, and the open-work area shows nothing and no "No open work." line. Compute bb-idle after the final charted visibility is known.
  • ⚠️ .agents/skills/bearings/assets/board-template.html:593 - This round 1 fix introduced a regression. if (people.length) renderProject(p, people) drops every project that has no crew. That includes a project whose own questions[] is non-empty, which the board contract allows (bin/fm-bearings-board.sh:83, project_item validates questions). renderProject is the only place project.questions is rendered (line 580), so those open questions now disappear from the page without any message. Concrete case: projects:[{name:"Portal",...,questions:[<a hold>]}] with crew:[] gives no panel and the question is not shown anywhere. bearings SKILL.md:126 says to explain such questions "rather than silently dropping them". The firstmate decision said to drop idle projects but keep projects with live work, and it did not cover a project that has no worker but does have an open question. Suggested remedy: render the card when people.length || p.questions.length. That changes the user's recorded decision, so confirm it first.
  • ℹ️ .agents/skills/bearings/assets/board-template.html:610 - The stat strip still counts every registered project (data.projects.length), while the panel now shows only projects with people. Example: in the updated test, 3 projects are given and the stat reads "3 projects", but only Reports and Portal render, plus the synthetic "Project not confirmed" card. The mismatch is minor. It may be intended if the stat means portfolio size.

🔧 Fix applied.
3 issues (1 warning, 2 infos) still open:

  • ⚠️ .agents/skills/bearings/assets/board-template.html:271 - Simplification/intent pass: Completed changed from an always-visible section to a collapsed &lt;details&gt; ("Completed · N"). The intent asks for an expandable current status, not a collapsed completions list. The bearings SKILL also describes Recently Landed as "rendered in full on every run". Confirm the collapse, or keep Completed open.
  • ℹ️ .agents/skills/bearings/assets/board-template.html:778 - bb-idle visibility is computed from data.charted.length before line 879 hides bb-charted-section on the status page when !anyPickable. Example: a status page whose only open items are non-dispatchable charted rows, with no underway and no calls. The charted section is hidden, bb-idle also stays hidden, and the open-work area shows nothing and no "No open work." line. Compute bb-idle after the final charted visibility is known.
  • ℹ️ .agents/skills/bearings/assets/board-template.html:610 - The stat strip still counts every registered project (data.projects.length), while the panel now shows only projects with people. Example: in the updated test, 3 projects are given and the stat reads "3 projects", but only Reports and Portal render, plus the synthetic "Project not confirmed" card. The mismatch is minor. It may be intended if the stat means portfolio size.

🔧 Fix applied.
2 issues (1 warning, 1 info) still open:

  • ⚠️ .agents/skills/bearings/assets/board-template.html:271 - Simplification/intent pass: Completed changed from an always-visible section to a collapsed &lt;details&gt; ("Completed · N"). The intent asks for an expandable current status, not a collapsed completions list. The bearings SKILL also describes Recently Landed as "rendered in full on every run". Confirm the collapse, or keep Completed open.
  • ℹ️ .agents/skills/bearings/assets/board-template.html:610 - The stat strip still counts every registered project (data.projects.length), while the panel now shows only projects with people. Example: in the updated test, 3 projects are given and the stat reads "3 projects", but only Reports and Portal render, plus the synthetic "Project not confirmed" card. The mismatch is minor. It may be intended if the stat means portfolio size.
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 10 of 10 scenarios driven live against the product
Scenario Result Live Evidence
Open work across projects stacks in one list, each row colored by status (working green, blocked red, paused amber, reviewing blue, unknown neutral) ✅ pass live fleet-collapsed.png; fleet-facts.json shows tones online/danger/warn/info/neutral with distinct borderLeftColor
Clicking 'Current status +' on a row expands its current status in place, and other rows stay collapsed ✅ pass live fleet-expanded.png shows 'Reviewing changes' and 'Waiting for capacity' under the clicked rows, with the cue flipped to '-'
Idle projects with nothing running get no panel on the status page ✅ pass live status-collapsed.png and status-facts.json: projects == [Reports, Portal]; Idle Alpha/Idle Beta absent
Adversarial: a project with no worker but an open question still shows its question ✅ pass live status-collapsed.png Portal card shows 'Hosting is not chosen.' / 'Use dedicated hosting.'
Needs you (Captain Call) renders above Open work and Waiting to start, and its answer form is intact ✅ pass live fleet-facts.json sectionOrder [call, current, charted, landed]; render test 'Captain Call still queues fm-bearings-answer.v1'
Underway rows show the repo only, never the worker kind or the routing id (null repo means no subtitle) ✅ pass live fleet-facts.json: 'Unlabelled job' (id mate/child-1, repo null) has sub ''; no 'ship'/'scout' text on rows
A queued row with no reason reads as neutral 'queued', not yellow 'waiting' ✅ pass live fleet-collapsed.png: 'Add CSV export' has a QUEUED neutral badge, and 'Remove old tabs' (with a reason) has WAITING
Adversarial: an empty fleet takes no space beyond one 'No open work.' line ✅ pass live empty-collapsed.png; empty-facts.json idleMsgVisible true, no visible sections
Completed stays collapsed but one click away ✅ pass live fleet-collapsed.png shows a '▶ Completed · 1' disclosure. It was not clicked in this run.
Board stays explicitly dark and offline (color-scheme dark, dark body background) ✅ pass live fleet-facts.json colorScheme 'dark', bodyBg rgb(12, 18, 28)
  • bash tests/fm-bearings-board-render.test.sh (all ok, drives the real built template under a DOM shim)
  • bash tests/fm-bearings-board.test.sh (exit 0)
  • FM_HOME=&lt;lab&gt; bin/fm-bearings-board.sh build payload-fleet.json with real lavish-axi -> session live, bound, armed
  • FM_HOME=&lt;lab&gt; bin/fm-bearings-board.sh build payload-status.json (status page with 2 idle projects, 1 active, 1 question-only)
  • FM_HOME=&lt;lab&gt; bin/fm-bearings-board.sh build payload-empty.json (empty fleet)
  • node cdp-shoot.mjs headless Chrome CDP: full-page screenshot, click first underway + first charted summary, screenshot expanded, dump computed tones/border colors/section order
  • Teardown: lavish-axi end &lt;lab board&gt; -> status ended, lab procevent watchdog stopped, lab dirs removed
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@doitdigital0495
doitdigital0495 merged commit 837f39d into main Oct 2, 2026
21 checks passed
doitdigital0495 added a commit that referenced this pull request Oct 2, 2026
Removes the all-projects Lavish status page and the compact open-work
board from PRs #46 (e10503d) and #47 (837f39d), restoring every
bearings path to its state at ac35f70: the bearings skill and board
template, fm-bearings-board.sh, fm-bearings-snapshot.sh, the fleet
snapshot projects projection, their tests, the bearings test-count pin,
and the board-only cross-references in docs and fm-project-mode.sh
(--list-json). The Pi stock-call and export-visibility fixes that rode
along in #46 are unrelated to the board and stay.

Co-authored-by: Daan Aerts <daan@daeverhuur.nl>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants