Skip to content

feat(bearings): add all-projects Lavish status page to bearings - #46

Merged
doitdigital0495 merged 11 commits into
mainfrom
fm/fm-lavish-status-board-skill
Oct 1, 2026
Merged

doitdigital0495 merged 11 commits into
mainfrom
fm/fm-lavish-status-board-skill

Conversation

@doitdigital0495

@doitdigital0495 doitdigital0495 commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Intent

Captain wants an on-demand Lavish all-tasks status-page skill, merged into bearings when that avoids duplicate ownership. Preserve all requests and their truthful statuses, plain wording, fully explained Needs you options, self-contained dark styling with no external requests, and existing structured snapshot/decision sources and supervised Lavish adapter; no new daemon or configuration knobs. Follow-up on PR 46 requires every project in data/projects.md, including idle projects, with registered delivery posture and merge authority in plain words, and every present worker and registered secondmate grouped by known project associations, with 1-2-line status and open questions explaining the issue, actual choice and recommendation. Unknown progress, absent project links and bounded or stale delegated coverage must remain explicit, never guessed; shared leads may appear under multiple projects, unassigned people remain visible. Build from current fleet data and verify at 390px, serve through lavish-axi, report URL and remain available for the supervised feedback loop. The live page already shows all 10 registered projects plus shared work, 21 present workers and 91 requests at https://desktop-agentic.tail1dbb2d.ts.net:8387/session/faa900220fea1c21; explicit html/body dark backgrounds persist under light preference, width/scrollWidth both 390, no external resource requests, listener live. Extend fm-project-mode's existing annotation parser with structured complete enumeration, canonical registered-secondmate project associations, and bearings opt-in task inventory with complete main crew plus sampled delegated crew; preserve all default digest formats and honest canonical coverage limits. New board fields are validated and rendered by the shipped template, with no alternate board design or polling mechanism. Earlier Pi stock rendering and hidden export visibility compatibility repairs and all no-mistakes fixes remain in branch history. They must remain compatible with Pi 0.86 and 0.99, with no Pi pin/downgrade, visibility-policy change, gate skip or override. Four targeted board/render/snapshot/task-delivery suites pass after correcting one fixture association assertion; lint, documentation audience guard and whitespace checks pass. Publish to existing PR 46 via the normal pipeline; do not merge, and only call delivery complete with genuinely green checks. Preserve the earlier preview alongside .lavish/validation/status-all-projects.html; live session remains hosted during validation.

What Changed

  • Bearings now has a plain-language, self-contained dark Lavish status page (board-template.html, fm-bearings-board.sh, fm-bearings-snapshot.sh, SKILL.md). It lists every registered project, including idle ones, with its delivery posture and merge authority. Workers and registered secondmates are grouped under the projects they are linked to, and each request shows its truthful status, with a full explanation of any "Needs you" option. Unknown progress, missing project links and sampled or stale delegated coverage are labelled as such, never guessed.
  • fm-project-mode.sh gains a --list-json mode that lists every entry in the project registry using the existing annotation parser. A registry mode it does not recognise is marked recognised:false, and a missing registry is reported as absent. fm-fleet-snapshot.sh now also reads the project links of each registered secondmate. The default single-project output formats are unchanged.
  • The Pi outcomes tool rows (fm_branch_outcomes, fm_branch_processed) now hand their call line to Pi's own stock tool rendering, so newer Pi versions show the call arguments without duplicating Pi's formatter. Also included: matching tests and docs, plus updated Bearings test count in CI (59 to 61).

🤖 Generated with Claude Code

Risk Assessment

✅ Low: The fix round moves the existing registry parser into a shared function without changing what it parses. --list-json now adds recognised and annotation to each project row. fm-bearings-snapshot passes that registry data through unchanged, and the board's delivery field is free text, so the composer can write "Delivery plan not confirmed" without changing the schema. The default and --raw digest output is unchanged. The test runs the real --list-json command and checks both the unknown-mode row and the intentional legacy row.

Testing

The four targeted board, render, snapshot and task-delivery suites pass. The new --list-json CLI was driven live on a sandbox registry: a typo mode comes back as recognised:false with its raw annotation, while legacy defaults stay recognised:true. A missing registry reports present:false. The real fleet registry lists all 10 projects, none unrecognised. The bearings snapshot was driven on the real fleet (read-only). --fields tasks refuses without --json. With --json it yields the project registry, 24 crew rows and 93 backlog rows, and the default output still has no new fields. A real board build in a throwaway home was rendered in headless Chrome at 390px with light preference. html and body stay dark (rgb 14,17,22), scrollWidth is 390, and there are no external requests. The page shows idle projects, a shared lead under two projects, an unconfirmed-project group for the unassigned person, explained questions (what it is, your choice, recommendation), all five request statuses and the coverage note. A payload with a question missing its recommendation is refused. The live session faa900220fea1c21 is open and its bearings-board listener is running. Screenshots of each section are in the evidence directory. Not driven live: secondmate crew grouping on the real fleet (the live home has no registered secondmates; the snapshot suite covers it) and the Pi 0.86/0.99 compatibility, which belongs to the Pi extension suites and CI. Lab browser session and temp home are torn down; the worktree is clean.

  • Live validation: ✅ go - 9 of 11 scenarios driven live against the product
Scenario Result Live Evidence
Captain lists the project registry as JSON: every row appears, and a typo mode is marked recognised:false with its raw annotation while legacy defaults stay recognised ✅ pass live project-mode-list-json.txt
Adversarial: mechanical callers of the typo'd project still get the safe 'no-mistakes off' fallback with a warning; a missing registry reads present:false, not an empty portfolio; an extra argument is… ✅ pass live project-mode-list-json.txt
Real fleet registry lists all 10 registered projects, none unrecognised ✅ pass live project-mode-list-json.txt
bearings snapshot --json --fields tasks on the real fleet returns project_registry, crew with project links and the full task inventory; TOON with tasks is refused; default JSON and TOON gain no new f… ✅ pass live bearings-snapshot-tasks.txt
Built status board at 390px under light preference: dark html and body backgrounds, no horizontal overflow, no external requests ✅ pass live render-checks-390-light.txt, network.txt, status-board-390-light-top.png
Board groups people by project: idle project shown, shared lead under both projects, unassigned person under 'Project not confirmed', open question shows issue, choice and recommendation, no routing i… ✅ pass live status-board-390-projects.png, status-board-390-unassigned.png, render-checks-390-light.txt
Requests table keeps all five truthful statuses, a 'Review the proposed changes' link and the coverage note; Needs you cards explain every option ✅ pass live status-board-390-requests.png, status-board-390-light-top.png
Adversarial: a board payload whose open question lacks a recommendation is refused by build ✅ pass live board-build-adversarial.txt
Live hosted bearings session stays open with its supervised listener running ✅ pass live live-session-status.txt, live-session-390.png, process list showing fm-procevent-lavish.sh poll for ~/.firstmate/.lavish/bearings-board.html
Registered secondmates and their sampled children appear in crew with registry project links ⏸️ untested no The real fleet home has no registered secondmate (crew roles are all workers), and creating one would mutate fleet FM_HOME state. It could be driven by provisioning a secondmate in a Herdr lab home vi…
Pi stock rendering and export-visibility repairs stay compatible with Pi 0.86 and 0.99 ⏸️ untested no This needs both Pi versions installed side by side. Installing packages is outside the worktree boundary, so remote CI's Pi extension shards own this check.

Built board, top (stats + Needs you) at 390px, light preference

Evidence: Rendered board HTML built by fm-bearings-board.sh

Source: Rendered board HTML built by fm-bearings-board.sh

<!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 status board - local system fonts and dark surfaces.
   Layout tokens retain the existing board's design vocabulary.
   ============================================================ */
/* System fonts only: this artifact makes no external requests. */

:root {
  --rust-700: #8f2f17; --rust-600: #a93a1f; --rust-500: #c0452a;
  --rust-400: #d35f3f; --rust-300: #e08365; --rust-100: #f4d8c9; --rust-050: #fbece3;
  --navy-700: #1a2238; --navy-600: #222c49; --navy-500: #2a3656;
  --navy-300: #6c7796; --navy-100: #d9deea;
  --gold-600: #b5791c; --gold-500: #e0a52e; --gold-300: #f0d38c; --gold-100: #f8ecc9;
  --ocean-600: #2f6688; --ocean-500: #3c7ea6; --ocean-200: #b6d4e2; --ocean-050: #e8f1f5;
  --sea-700: #234e3a; --sea-500: #2f6b4f; --sea-200: #b9d4c5; --sea-050: #e9f2ec;
  --paper-000: #fbf4e2; --paper-100: #f6ecd3; --paper-200: #f0e3c4; --paper-300: #e7d6ae;
  --cream-line: #ddc89c;
  --ink-900: #241c14; --ink-700: #3f3224; --ink-500: #6f5e46; --ink-300: #9c8a6c;
  --white: #fffdf7;
  color-scheme: dark;
  --bg-page: #0e1116;
  --surface-card: #171c24;
  --surface-card-warm: #131820;
  --text-strong: #e6e9ee; --text-body: #e6e9ee;
  --text-muted: #b2bdcc; --text-faint: #a5b2c4;
  --border-default: #3a4456; --border-soft: #2a3240;
  --paper-000: #e6e9ee; --paper-100: #171c24;
  --rust-050: #2b1b17; --rust-500: #c0452a; --rust-600: #f2a187;
  --sea-050: #12351f; --sea-200: #285e3b; --sea-500: #7ee2a0; --sea-700: #7ee2a0;
  --ocean-050: #12283f; --ocean-500: #8cc4ff; --ocean-600: #8cc4ff;
  --gold-100: #3a2f10; --gold-600: #ffd06b;
  --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);
  --texture-paper: none;
  --focus-ring: 0 0 0 3px var(--gold-300);
  --container-app: 1180px;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: #0e1116; color: #e6e9ee; }
[hidden] { display: none !important; }
body {
  font-family: var(--font-sans);
  color: var(--text-body);
  background: var(--bg-page);
  background-image: var(--texture-paper);
  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: var(--sea-500); }
.fm-badge--warn    { background: var(--gold-500); color: var(--navy-700); }
.fm-badge--danger  { background: #3d1a1a; color: #ff9a9a; }
.fm-badge--info    { background: var(--ocean-050); color: var(--ocean-500); }
.fm-badge--neutral { background: var(--paper-000); color: var(--ink-900); }
.fm-badge--solid   { background: var(--rust-500); color: var(--paper-000); }

/* ---- 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(--paper-000);
  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: 26px 28px 72px; display: flex; flex-direction: column; gap: 22px;
}

/* stat strip */
.bb-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.bb-stat {
  display: flex; flex-direction: column; gap: 4px; padding: 14px 16px; 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: 12p

... [30992 bytes truncated] ...

2) {
        answerLimit.textContent = "Answer is too long to queue (512 bytes maximum).";
        answerLimit.classList.add("is-visible");
        return;
      }
      if (window.lavish && window.lavish.queuePrompt) {
        /* The versioned context keeps the selected option separate from its
           note, while close carries the composer-declared completion mode. */
        var ctxData = {
          schema: "fm-bearings-answer.v1",
          question: item.key,
          selection: value || "",
          note: note
        };
        if (item.close) ctxData.close = item.close;
        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;
  }

  /* Underway */
  var uw = document.getElementById("bb-underway");
  if (!data.underway.length) uw.appendChild(el("div", "bb-empty", "Nothing is underway."));
  data.underway.forEach(function (t) {
    var row = el("div", "bb-row");
    row.appendChild(badge(t.state === "working" ? "online" : "info", t.state));
    var main = el("div", "bb-row__main");
    /* the snapshot's durable name-or-id label leads the row so a scan says
       WHICH task this is; the run status keeps its place on the second line */
    main.appendChild(el("div", "bb-row__title", t.name));
    /* Keep the worker kind and routing identity out of the status line. */
    main.appendChild(el("div", "bb-row__sub",
      t.doing + (t.repo ? " · " + t.repo : "")));
    row.appendChild(main);
    uw.appendChild(row);
  });

  /* Landed */
  var ld = document.getElementById("bb-landed");
  if (!data.landed.length) ld.appendChild(el("div", "bb-empty", "No recent completions are in the current baseline."));
  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;
  }

  if (!chartedQueued(data.charted).length && !chartedMoreQueued) {
    ch.appendChild(el("div", "bb-empty", "Nothing is queued."));
  }
  chartedOrder(data.charted).forEach(function (t) {
    var row = el("div", "bb-row");
    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();
      });
      row.appendChild(pick);
    } else {
      row.appendChild(el("span", "bb-pick-spacer"));
    }
    var main = el("div", "bb-row__main");
    main.appendChild(el("div", "bb-row__title", t.title));
    /* second line keeps the title uncrowded in the half-width column */
    var chSub = t.repo || (statusPage ? "" : t.id);
    main.appendChild(el("div", "bb-row__sub", t.reason ? t.reason + (chSub ? " · " + chSub : "") : chSub));
    row.appendChild(main);
    if (isWarning(t)) row.appendChild(badge("danger", "needs repair"));
    else if (t.reason) row.appendChild(badge("warn", "waiting"));
    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-waiting-section").hidden = true;
  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>
Evidence: Render checks at 390px, light preference

Source: Render checks at 390px, light preference

lightPref:true htmlBg:rgb(14,17,22) bodyBg:rgb(14,17,22) innerWidth:390 scrollWidth:390 external:[] hasIdle hasUnconfirmed notConfirmedDelivery leadInPortal:2 routingIdsLeak:false nullLeak:false

result: "\"{\\\"lightPref\\\":true,\\\"htmlBg\\\":\\\"rgb(14, 17, 22)\\\",\\\"bodyBg\\\":\\\"rgb(14, 17, 22)\\\",\\\"innerWidth\\\":390,\\\"scrollWidth\\\":390,\\\"external\\\":[],\\\"hasIdle\\\":true,\\\"hasUnconfirmed\\\":true,\\\"notConfirmedDelivery\\\":true,\\\"leadInPortal\\\":2,\\\"routingIdsLeak\\\":false,\\\"hasRecommendation\\\":true,\\\"nullLeak\\\":false}\""
help[1]:
  Run `chrome-devtools-axi snapshot` to see current page state
Evidence: fm-project-mode --list-json transcript

Source: fm-project-mode --list-json transcript

$ fm-project-mode.sh --list-json  (sandbox registry with typo mode)
- alpha [direct-PR] - Flat
- beta - Legacy default
- gamma [no-mistakez] - Typo
- delta [no-mistakes-prod-only +yolo] - Prod
{
  "present": true,
  "projects": [
    {
      "name": "alpha",
      "mode": "direct-PR",
      "yolo": "off",
      "recognised": true
    },
    {
      "name": "beta",
      "mode": "no-mistakes",
      "yolo": "off",
      "recognised": true
    },
    {
      "name": "gamma",
      "mode": "no-mistakes",
      "yolo": "off",
      "recognised": false,
      "annotation": "no-mistakez"
    },
    {
      "name": "delta",
      "mode": "no-mistakes-prod-only",
      "yolo": "on",
      "recognised": true
    }
  ]
}
exit=0

$ fm-project-mode.sh gamma (mechanical fallback preserved)
warn: unknown mode "no-mistakez" for gamma; defaulting to no-mistakes off
no-mistakes off
$ --raw gamma
warn: unknown mode "no-mistakez" for gamma; defaulting to no-mistakes off
no-mistakes off

$ missing registry
{"present":false,"projects":[]}
$ --list-json extra arg
usage: fm-project-mode.sh --list-json
exit=2

$ real fleet registry (read-only)
{"present":true,"n":10,"unrecognised":[],"names":["cosmic-show-hub","agentic-setup","herdr","rentmax-ops","daeproductions","doitdigital-v2","hermes","fabric_monorepo","daan-profile","claims"]}
Evidence: bearings snapshot --fields tasks transcript (real fleet)

Source: bearings snapshot --fields tasks transcript (real fleet)

$ fm-bearings-snapshot.sh --fields tasks   (TOON, must refuse)
fm-bearings-snapshot: --fields tasks requires --json
exit=2

$ fm-bearings-snapshot.sh --json --fields tasks | summary
{"registry_present":true,"projects":10,"crew":24,"crew_roles":{"worker":24},"crew_no_project":0,"main_tasks":93,"unfiled":1,"homes":[]}
exit=0

$ default json has no task fields
{"has_tasks":false,"has_crew":false,"has_registry":false,"keys":["contributions","decisions_open","gates","generated","home","in_flight","landed","omitted","prs","recorded_prs","reports","schema","secondmate_reconcile","secondmates"]}

$ default TOON (first lines)
exit=0 lines=115
Evidence: Board build output and adversarial refusal

Source: Board build output and adversarial refusal

fm-bearings-board: board data does not satisfy fm-bearings-board.v1: /tmp/fm-lab-board.yAqH/bad.json
bad exit=1
![Live hosted session screenshot](https://github.com/user-attachments/assets/341448ac-d852-41ce-bfa6-f3388db58f0e) - Evidence: [Live hosted session screenshot](https://github.com/doitdigital0495/firstmate/blob/cc425deb86f34c791f5977254f55fa1e2541ed3b/.no-mistakes/evidence/fm/fm-lavish-status-board-skill/live-session-390.png) - Outcome: ⚠️ 1 info across 1 run (11m19s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed ✅
  • ℹ️ bin/fm-project-mode.sh:61 - --list-json resolves each project through the --raw path, which maps an unknown or typo'd annotation to "no-mistakes off" and only warns on stderr (lines 106-108). The JSON row then looks identical to a deliberately registered legacy no-mistakes project (tests/fm-task-delivery.test.sh asserts typoproj -> no-mistakes/off). The board composer will describe a delivery posture the captain never registered, which conflicts with the intent's 'unknown ... must remain explicit, never guessed'. A possible remedy is a recognised:false flag or the raw annotation on such rows. That changes the documented output, so the author should decide.

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

⚠️ **Test** - 1 info
  • ℹ️ .agents/skills/bearings/assets/board-template.html - At 390px the request title column breaks a long word mid-word ('Unconfirme / d request'). It is cosmetic, but the page is meant to look pixel-perfect on a phone. overflow-wrap: anywhere / word-break on the narrow title column causes it; hyphens: auto or a wider minimum column would fix it.
  • Live validation: ✅ go - 9 of 11 scenarios driven live against the product
Scenario Result Live Evidence
Captain lists the project registry as JSON: every row appears, and a typo mode is marked recognised:false with its raw annotation while legacy defaults stay recognised ✅ pass live project-mode-list-json.txt
Adversarial: mechanical callers of the typo'd project still get the safe 'no-mistakes off' fallback with a warning; a missing registry reads present:false, not an empty portfolio; an extra argument is… ✅ pass live project-mode-list-json.txt
Real fleet registry lists all 10 registered projects, none unrecognised ✅ pass live project-mode-list-json.txt
bearings snapshot --json --fields tasks on the real fleet returns project_registry, crew with project links and the full task inventory; TOON with tasks is refused; default JSON and TOON gain no new f… ✅ pass live bearings-snapshot-tasks.txt
Built status board at 390px under light preference: dark html and body backgrounds, no horizontal overflow, no external requests ✅ pass live render-checks-390-light.txt, network.txt, status-board-390-light-top.png
Board groups people by project: idle project shown, shared lead under both projects, unassigned person under 'Project not confirmed', open question shows issue, choice and recommendation, no routing i… ✅ pass live status-board-390-projects.png, status-board-390-unassigned.png, render-checks-390-light.txt
Requests table keeps all five truthful statuses, a 'Review the proposed changes' link and the coverage note; Needs you cards explain every option ✅ pass live status-board-390-requests.png, status-board-390-light-top.png
Adversarial: a board payload whose open question lacks a recommendation is refused by build ✅ pass live board-build-adversarial.txt
Live hosted bearings session stays open with its supervised listener running ✅ pass live live-session-status.txt, live-session-390.png, process list showing fm-procevent-lavish.sh poll for ~/.firstmate/.lavish/bearings-board.html
Registered secondmates and their sampled children appear in crew with registry project links ⏸️ untested no The real fleet home has no registered secondmate (crew roles are all workers), and creating one would mutate fleet FM_HOME state. It could be driven by provisioning a secondmate in a Herdr lab home vi…
Pi stock rendering and export-visibility repairs stay compatible with Pi 0.86 and 0.99 ⏸️ untested no This needs both Pi versions installed side by side. Installing packages is outside the worktree boundary, so remote CI's Pi extension shards own this check.
  • bash tests/fm-bearings-board.test.sh, tests/fm-bearings-board-render.test.sh, tests/fm-bearings-snapshot.test.sh, tests/fm-task-delivery.test.sh (all exit 0)
  • FM_HOME=&lt;sandbox&gt; bin/fm-project-mode.sh --list-json with registry rows direct-PR, legacy default, typo [no-mistakez], [no-mistakes-prod-only +yolo]
  • bin/fm-project-mode.sh gamma and --raw gamma (mechanical fallback for the typo mode)
  • FM_DATA_OVERRIDE=&lt;absent&gt; bin/fm-project-mode.sh --list-json and --list-json x (usage refusal)
  • FM_HOME=~/.firstmate bin/fm-project-mode.sh --list-json (read-only, real registry)
  • FM_HOME=~/.firstmate bin/fm-bearings-snapshot.sh --fields tasks (TOON refusal), --json --fields tasks, default --json and default TOON
  • fm-bearings-board.sh build payload.json in a throwaway private home with a stub lavish-axi (projects, crew, requests, coverage, cards), plus an adversarial payload with a question missing its recommendation
  • chrome-devtools-axi: emulate 390x844 mobile with light colour preference, open the built board, eval computed backgrounds, scrollWidth, resource entries and text checks, network list, screenshots of each section
  • lavish-axi session list plus chrome-devtools-axi open of the live session faa900220fea1c21 (read-only)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…aused by this PR, and it is fixed. The PR added two new passing checks to tests/fm-bearings-snapshot.test.sh ("task inventory includes all 11 backlog rows..." and "task inventory preserves delegated children..."). But .github/workflows/ci.yml still expected exactly 59 Bearings tests, which broke the rule that the pinned count must match the checks the suite actually runs. I changed the pin and its error message from 59 to 61. That was the only place with this pin. A local run of `/bin/bash tests/fm-bearings-snapshot.test.sh` exited 0 with 61 passing checks and 0 failures. ci-1 and ci-2 (Behavior portable serial 3 and 8) were not caused by this PR, so I made no code change for them. The failing tests are tests/fm-calm-pi-extension.test.sh ("rendered export DOM violated the Calm conversation boundary") and tests/fm-pi-branch-extension.test.sh ("Calm-off ToolExecutionComponent rendering differs from Pi stock"). This PR does not touch Pi or Calm code. CI installs @earendil-works/pi-coding-agent without a pinned version. PR 45's run at 14:07 installed 0.87.1, and these same shards passed. This run installed 0.99.1, and they failed. The cause is a newer Pi release upstream, not this change. Pinning the Pi version or adapting Calm to Pi 0.99 is outside this PR's scope and should be decided separately
@doitdigital0495
doitdigital0495 force-pushed the fm/fm-lavish-status-board-skill branch from 1c20dbc to ce0b9c8 Compare October 1, 2026 10:51
@doitdigital0495 doitdigital0495 changed the title feat(bearings): add plain all-tasks Lavish status page to bearings lavish mode feat(bearings): add all-tasks Lavish status page to bearings lavish mode Oct 1, 2026
…y). This PR caused it. The CI parse sweep runs `/bin/bash -n` (stock Bash 3.2.57) and failed on `tests/fm-pi-branch-extension.test.sh` with "unexpected EOF while looking for matching `''". Cause: commit ce0b9c8 added the comment `// Both outcomes tools must inherit the installed Pi's argument formatter,` to the JS heredoc that sits inside `out=$(... <<'JS' ... JS)`. Bash 3.2 reads the whole `$(...)` text for quotes without knowing it is a heredoc. The bare apostrophe in "Pi's" opened a single quote that never closed, so the parser hit end of file. Rule that must hold: no quote character in a heredoc inside `$(...)` may be left unpaired outside a double-quoted string. I checked every other place in that heredoc. The only other apostrophe (line 4700, already on the base branch) is inside a "..." string literal, so Bash 3.2 ignores it. The other new lines have balanced double quotes and balanced parentheses. CI reported no other file. Fix: I reworded the comment to "inherit the argument formatter of the installed Pi". It is a comment-only change; the test logic is unchanged. What I checked: - `bash -n` on the file passes on the local Linux Bash. - After the fix, the heredoc has no bare apostrophes left. - I could not run the real Bash 3.2: it is not installed here and I did not install it. The evidence is from a scan of the heredoc, not from that shell
@doitdigital0495 doitdigital0495 changed the title feat(bearings): add all-tasks Lavish status page to bearings lavish mode feat(bearings): add all-projects Lavish status page to bearings Oct 1, 2026
@doitdigital0495
doitdigital0495 merged commit e10503d into main Oct 1, 2026
22 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