Skip to content

feat(herdr): show the custom sidebar data in the phone layout - #29

Merged
undeemed merged 7 commits into
mainfrom
fm/cf-herdr-mobile-custom-m2
Oct 1, 2026
Merged

undeemed merged 7 commits into
mainfrom
fm/cf-herdr-mobile-custom-m2

Conversation

@undeemed

@undeemed undeemed commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Intent

"this shit is missing my custom herdr sidebar" (on the phone, over mosh)

"also i want the mobile ui navigation back, i can't see the chat at all"

"keep both"

"i want custom + the navigation"

"just make sure it keeps this look"

Context for reading those asks: Code-Factory renders a custom Herdr sidebar from ansible/templates/herdr.toml.j2 ([ui.sidebar.spaces] and [ui.sidebar.agents] rows). Its tokens are fed by maintenance/herdr-spaces.py (workspace tokens: $machine/$host box stats, $decisions, $crew, $queue, $res, $alert) and by the omp extension config/herdr-sidebar.ts (pane tokens: $pr, $issue, $add, $del, $files, plus the terminal title). On a phone in portrait (about 50 columns, at or below Herdr's [ui] mobile_width_threshold, default 64), Herdr 0.9.3 switches to its single-column mobile layout. That layout has the navigation and full-width chat the user wants, but per Herdr's docs it "keeps its compact layout" and ignores the custom sidebar rows. Its entries show only fixed fields. Spaces: Local · <workspace label>, then a second line such as shell · tab 1. Agents: Local · <workspace label>, then <tab> · <state> · <display agent>, for example 1 · working · omp. Raising the threshold brought the custom sidebar back but lost the mobile navigation and the chat, so the user wants both: the mobile layout, with the custom sidebar data visible inside it.

"This look" is the user's current desktop custom sidebar: the whole-machine box stats line first (CPU, RAM and disk); then each home as its own entry with its colored short name, its queue count (◷) and its per-home CPU, RAM and disk (⚙ ▤ ⛁) line; then the agents, grouped by home, each shown by its home's short name with its session/task title below. The desktop sidebar must stay exactly as it is.

What Changed

  • maintenance/herdr-spaces.py now also writes the sidebar data into the fields Herdr's single-column mobile layout shows. That layout ignores the custom sidebar rows. Each agent pane gets its session topic as the display agent. Its state labels (idle, working, done, unknown; blocked keeps Herdr's own word) carry the compact data, with glyphs unspaced and parts single-spaced. A home's own agent shows the home's counts, CPU/RAM/disk shares, a [WARN]watcher flag and the pull request line. Any other agent shows its pull request line. Both are written through herdr pane report-metadata. The reporter also clears the labels when the data is empty.
  • The machine workspace's first tab is pinned by tab id and renamed to the machine's CPU/RAM/disk shares, so the phone's machine Space line shows them. It is renamed at most every 30 seconds (TAB_TTL). It is renamed only while its label is still Herdr's own number or the reporter's last text, so an operator-chosen name is left alone. Desktop sidebar rows, tokens and herdr.toml.j2 are not touched.
  • docs/herdr.md gains an "On a phone" section. It covers what each Space and Agent entry shows, the tab-rename rules, and the limits of the mobile layout. The herdr-spaces.service.j2 comment is updated to match. tests/test_herdr_sidebar.py adds cases for the mobile reports (same data, settling, following a changed pane) and for the pinned, throttled machine tab. It also adds a parametrized shares test.

Risk Assessment

⚠️ Medium: The change is display-only, fails soft, and leaves the desktop sidebar rows and templates untouched. It deliberately omits part of the requested look (per-home shares, short names, colors). It also performs a persistent, assumption-laden rename of the machine workspace's active tab, which needs the author's decision.

Testing

The 74 targeted pytest cases passed, as unit-level evidence only. I drove the reporter live against isolated real Herdr servers. The 50-column mobile switch menu shows each home's ◷ ⚙ ▤ ⛁ counts, pull request line and title in the extension's real token forms. The machine tab is pinned, throttled to 30 s and never overwrites an operator-named label. The agent-exit guard clears the reporter's metadata, while an unguarded control write survives. The reporter makes no writes when nothing changes, and the CPU label rises and falls with the res token. The A/B comparison of the base and target reporters showed identical desktop sidebar columns apart from live host digits. The left-out issue is documented, and the border-label effect could not be observed in the fixture. All disposable servers and files were torn down and the worktree is clean.

  • Live validation: ✅ go - 15 of 16 scenarios driven live against the product
Scenario Result Live Evidence
On a 50-column phone, the user opens the switch menu and each home agent entry shows its ◷ count, ⚙ ▤ ⛁ shares and pull request line, with the session title after it. Pane tokens come from the real `p… ✅ pass live 15-mobile-machine-switch.png, 15-mobile-home-capture.txt: ◷3 ⚙0% ▤0% ⛁0% ⎇1537 +12847 −3902 ✎214 · Plann… and ◷1 ⚙0% ▤0% ⛁0% +84 −12 ✎3 · Fix ratings screen
The issue (○ 1529) seeded on a home pane shows on the desktop agent row but is left out of the mobile entry, and docs/herdr.md lists that as a forced difference ✅ pass live 12-desktop-AB.txt (desktop row ⎇ 1537 · ○ 1529 · +12847 · −3902 · ✎ 214) vs 15-mobile-machine-switch.png (no ○); 14-per-home-cpu-change.txt last line; docs/herdr.md:193 `The issue (○) is left out…
The user keeps the mobile layout: full-width chat panes with a switch button and the navigation menu (agents, spaces, tabs, menu) ✅ pass live 15-mobile-home-main.png and 15-mobile-home-switch.png
The whole-machine CPU, RAM and disk shares show on the phone via the machine workspace's pinned first tab, in both the Space entry and the tabs list ✅ pass live 15-mobile-machine-switch.png (shell · tab ⚙31% ▤28% ⛁71% and 1 · ⚙31% ▤28% ⛁71%); 13-fresh-install-tab-and-idempotence.txt
Fresh install: the machine tab shows without ⚙ on the first run, stays unchanged on an immediate re-run (30 s throttle), and gains ⚙ after 30 s or more ✅ pass live 13-fresh-install-tab-and-idempotence.txt: ▤28% ⛁71%, then unchanged, then ⚙13% ▤28% ⛁71% after 31 s. A fresh install shows a CPU-less machine tab for up to 30 s (informational).
Adversarial: an operator-named machine tab (htop) is left alone after the throttle expires, and a second focused tab is never renamed ✅ pass live 05-machine-tab-adversarial.txt cases A and B: 0 renames, htop kept, w3:t2 stays 2
Adversarial: the tab is taken back only once the operator resets it to a number, renames are throttled to one per 30 s, and the pin moves to the new first tab when the pinned tab is closed ✅ pass live 05-machine-tab-adversarial.txt cases C, D, E; 13-fresh-install-tab-and-idempotence.txt
Home workspaces' tabs are never renamed, so the orchestrator still finds agents by tab label ✅ pass live 05-machine-tab-adversarial.txt case F: w1:t1 and w2:t1 stay 1
Idempotence: with nothing else perturbing workspace CPU, six back-to-back reporter runs make no pane report-metadata call and no tab rename ✅ pass live 13-fresh-install-tab-and-idempotence.txt (6 runs, 0 writes, 0 renames)
The phone label follows the per-home CPU both ways: with a busy process in the home the label rises to ⚙12% and matches the res token, and on the first run after the process exits it returns to ⚙0%… ✅ pass live 14-per-home-cpu-change.txt: res ⚙ 12% ↔ label ⚙12%, then res ⚙ 0% ↔ label ⚙0% on the first post-exit run and the two runs after it
Adversarial: when omp exits, the reporter's topic and state labels disappear, while an unguarded control write survives ✅ pass live 06-agent-exit-guard.txt: reporter metadata cleared after release-agent; control display_agent=&#34;stale topic&#34; survived. This run used a hand-seeded +84 token, which does not affect the guard.
A worker agent pane (no who token) keeps the orchestrator's display agent, and its real-form pull request parts go to the state labels ✅ pass live 07-worker-pane.txt: └ fix-token-race unchanged; state labels ⎇1540 +5 −1 ✎2
The session topic follows a session rename, a title over 80 characters is capped at 80, and an unchanged title causes no rewrite ✅ pass live 08-topic-follow-and-cap.txt
The desktop sidebar is unchanged by the new reporter: A/B at 120 columns with the repo's rendered herdr.toml.j2, base reporter cdd9549 then target 5fc8fb5, firstmate focused ✅ pass live 12-desktop-AB.txt: sidebar columns 0-45 differ only in the live host line's ⚙ and ▤ digits; the digit-masked diff is IDENTICAL; columns 46 and beyond identical
The reporter exits 0 and does not crash when no Herdr server is reachable ✅ pass live 11-no-server.txt
Documented effect: a home pane in a split shows the session topic on its desktop pane border instead of omp (decision C) ⏸️ untested no Tried a split workspace with the repo's rendered herdr.toml.j2 (show_agent_labels_on_pane_borders = true) at 120 columns, with panes whose agent was reported through pane report-agent. Herdr dre…

Mobile switch menu at 50 columns, machine focused: agents carry ◷ ⚙ ▤ ⛁ and the real-form pull request line plus title; the machine Space and its tab carry the shares (the ⛁ glyph renders as a box because of the renderer's font)
Mobile switch menu at 50 columns, firstmate focused
Mobile main view at 50 columns, firstmate focused: full-width chat panes and switch button

Evidence: Mobile captures as text (home and machine focus)
===== main (50x30) =====
 · machine           tab ⚙31% ▤28% ⛁71% │
 1 working · 1 idle                     │ switch
ubuntu@vps-7560d371:/tmp/herdr-live$



























===== switch (50x30) =====
 switch                                 │  close
                                        │    ×
──────────────────────────────────────────────────
  agents
   ● Local · firstmate
   ◷3 ⚙0% ▤0% ⛁0% ⎇1537 +12847 −3902 ✎214 · Plann…
   ○ Local · 2ndmate-webapp-mate-s4
   ◷1 ⚙0% ▤0% ⛁0% +84 −12 ✎3 · Fix ratings screen
  spaces
   + new workspace
   ● Local · firstmate
   master · tab 1
   ○ Local · 2ndmate-webapp-mate-s4
   master · tab 1
   · Local · machine
   shell · tab ⚙31% ▤28% ⛁71%
  tabs
   + new tab
   1 · ⚙31% ▤28% ⛁71%
  menu
   settings
   keybinds
   reload config
   detach
Evidence: A/B desktop sidebar: base reporter vs target reporter at 120 columns, with raw and digit-masked column diffs
== A (base reporter cdd9549) and B (target 5fc8fb5), 120x30, rendered repo herdr.toml, firstmate focused ==
-- A
 spaces                                      │   1      +
                                             │┌───────────────────────────────────┐┌───────────────────────────────────┐
 ● firstmate · ◷ 3                           ││ubuntu@vps-7560d371:/tmp/herdr-liv ││ubuntu@vps-7560d371:/tmp/herdr-liv │
   ⚙ 0%  ▤ 0%  ⛁ 0%                          ││e/fm$ printf '\033]0;Planning the  ││ubuntu@vps-7560d371:/tmp/herdr-liv │
 ○ webapp · ◷ 1                              ││release checklist\007'             ││e/fm$                              │
   ⚙ 0%  ▤ 0%  ⛁ 0%                          ││ubuntu@vps-7560d371:/tmp/herdr-liv ││                                   │
 · ⌂ ⚙ 19% ▤ 6.3/22.9G 28% ⛁ 137/193G 71%    ││ubuntu@vps-7560d371:/tmp/herdr-liv ││                                   │
                                             ││e/fm$                              ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
 new                                     menu││                                   ││                                   │
─────────────────────────────────────────────││                                   ││                                   │
 agents                               grouped││                                   ││                                   │
                                             ││                                   ││                                   │
 ● firstmate                                 ││                                   ││                                   │
   Planning the release checklist            ││                                   ││                                   │
   ⠀⠀⎇ 1537 · ○ 1529 · +12847 · −3902 · ✎ 214││                                   ││                                   │
 ○ webapp                                    ││                                   ││                                   │
   Fix ratings screen                        ││                                   ││                                   │
   ⠀⠀+84 · −12 · ✎ 3                         ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                            «│└───────────────────────────────────┘└───────────────────────────────────┘
-- B
 spaces                                      │   1      +
                                             │┌───────────────────────────────────┐┌───────────────────────────────────┐
 ● firstmate · ◷ 3                           ││ubuntu@vps-7560d371:/tmp/herdr-liv ││ubuntu@vps-7560d371:/tmp/herdr-liv │
   ⚙ 0%  ▤ 0%  ⛁ 0%                          ││e/fm$ printf '\033]0;Planning the  ││ubuntu@vps-7560d371:/tmp/herdr-liv │
 ○ webapp · ◷ 1                              ││release checklist\007'             ││e/fm$                              │
   ⚙ 0%  ▤ 0%  ⛁ 0%                          ││ubuntu@vps-7560d371:/tmp/herdr-liv ││                                   │
 · ⌂ ⚙ 15% ▤ 6.4/22.9G 28% ⛁ 137/193G 71%    ││ubuntu@vps-7560d371:/tmp/herdr-liv ││                                   │
                                             ││e/fm$                              ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
 new                                     menu││                                   ││                                   │
─────────────────────────────────────────────││                                   ││                                   │
 agents                               grouped││                                   ││                                   │
                                             ││                                   ││                                   │
 ● firstmate                                 ││                                   ││                                   │
   Planning the release checklist            ││                                   ││                                   │
   ⠀⠀⎇ 1537 · ○ 1529 · +12847 · −3902 · ✎ 214││                                   ││                                   │
 ○ webapp                                    ││                                   ││                                   │
   Fix ratings screen                        ││                                   ││                                   │
   ⠀⠀+84 · −12 · ✎ 3                         ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                             ││                                   ││                                   │
                                            «│└───────────────────────────────────┘└───────────────────────────────────┘

== sidebar columns 0-45, raw diff (CPU digits may jitter) ==
--- A
+++ B
@@ -7 +7 @@
- · ⌂ ⚙ 19% ▤ 6.3/22.9G 28% ⛁ 137/193G 71%    │
+ · ⌂ ⚙ 15% ▤ 6.4/22.9G 28% ⛁ 137/193G 71%    │
== sidebar columns, digits masked ==
IDENTICAL

== non-sidebar columns 46+ (pane borders / tab bar), diff ==
identical
Evidence: Fresh-install tab (CPU-less for up to 30 s) and six-run idempotence
\### T. fresh install: empty cache, machine tab back to Herdr's '1'
run1 (no CPU sample yet) tab: ['▤28% ⛁71%']
run2 immediately (TTL 30s) tab: ['▤28% ⛁71%']   <- unchanged, still CPU-less
workspace token meanwhile: ⌂ ⚙ 28% ▤ 6.3/22.9G 28% ⛁ 137/193G 71%
run3 after 31s tab: ['⚙13% ▤28% ⛁71%']   <- now has CPU

\### P. idempotence with nothing else running (pytest finished; only fixture processes in workspace ids)
run 1: pane report-metadata=0 tab rename=0
run 2: pane report-metadata=0 tab rename=0
run 3: pane report-metadata=0 tab rename=0
run 4: pane report-metadata=0 tab rename=0
run 5: pane report-metadata=0 tab rename=0
run 6: pane report-metadata=0 tab rename=0

   w1:p1 display_agent= "Planning the release checklist" working= "◷3 ⚙0% ▤0% ⛁0% ⎇1537 +12847 −3902 ✎214"
   w2:p1 display_agent= "Fix ratings screen" working= "◷1 ⚙0% ▤0% ⛁0% +84 −12 ✎3"
Evidence: Per-home CPU rise and fall: `res` token vs phone label at each step, and the issue left out of the mobile label
\### Q2. per-home ⚙ rising and falling: does the phone label follow the res token?
  idle baseline                      res token='⚙ 0%  ▤ 0%  ⛁ 0%'  label.working='◷3 ⚙0% ▤0% ⛁0% ⎇1537 +12847 −3902 ✎214'
  busy process running               res token='⚙ 12%  ▤ 0%  ⛁ 0%'  label.working='◷3 ⚙12% ▤0% ⛁0% ⎇1537 +12847 −3902 ✎214'
  1st run after exit (previously uncounted) res token='⚙ 0%  ▤ 0%  ⛁ 0%'  label.working='◷3 ⚙0% ▤0% ⛁0% ⎇1537 +12847 −3902 ✎214'
  2nd run after exit                 res token='⚙ 0%  ▤ 0%  ⛁ 0%'  label.working='◷3 ⚙0% ▤0% ⛁0% ⎇1537 +12847 −3902 ✎214'
  3rd run after exit                 res token='⚙ 0%  ▤ 0%  ⛁ 0%'  label.working='◷3 ⚙0% ▤0% ⛁0% ⎇1537 +12847 −3902 ✎214'
  mobile label has no issue token (docs/herdr.md:193 lists it as forced difference): True
Evidence: Machine-tab adversarial scenarios A-F
\### A. operator names the pinned tab 'htop'; TTL expired; reporter must leave it alone
   tab renames this run: 0
   tabs: [('w3:t1', 'htop', '')]
\### B. operator opens + focuses a 2nd tab in machine; TTL expired; reporter must not rename the active tab
   tab renames this run: 0
   tabs: [('w3:t1', 'htop', ''), ('w3:t2', '2', 'active')]
\### C. operator resets pinned tab to Herdr's number '1'; TTL expired; reporter takes it back (pinned id w3:t1, not active tab)
   tab renames this run: 1
   tabs: [('w3:t1', '⚙31% ▤27% ⛁71%', ''), ('w3:t2', '2', 'active')]
\### D. same run again immediately (TTL fresh): no rename even though shares changed
   tab renames this run: 0
   tabs: [('w3:t1', '⚙31% ▤27% ⛁71%', ''), ('w3:t2', '2', 'active')]
\### E. operator closes the pinned tab; remaining tab is default-named; reporter moves pin to new first tab
   tab renames this run: 1
   tabs: [('w3:t2', '⚙34% ▤27% ⛁71%', 'active')]
\### F. home workspaces' tabs never renamed
[('w1:t1', '1'), ('w2:t1', '1')]
Evidence: Agent-exit guard with unguarded control
\### G. guard: reporter writes metadata tagged --agent omp; omp exits (agent released) -> topic and counts must vanish
before exit:
   w2:p1 agent= omp display_agent= "Fix ratings screen" state_labels(working)= "◷1 ⚙0% ▤0% ⛁0% +84"
after agent released:
   w2:p1 agent= None display_agent= null state_labels(working)= null
\### G2. control: metadata written WITHOUT --agent (what an unguarded reporter would do) survives the exit
\### G2 control: same pane, metadata written WITHOUT --agent (unguarded) then agent released
before exit:
   w2:p1 agent= omp display_agent= "stale topic" state_labels(working)= "◷1 ⚙0% ▤0% ⛁0% +84"
after exit (unguarded record is still there):
   w2:p1 agent= None display_agent= "stale topic" state_labels(working)= null
Evidence: Worker pane with real prParts tokens
\### H. worker agent pane (no 'who'): the orchestrator's display agent survives; PR parts (real prParts output, indent 0) go to state labels
tokens: {'pr': '⎇ 1540', 'add': '+5', 'del': '−1', 'files': '✎ 2'}
before reporter:
   w1:p2 agent= claude display_agent= "└ fix-token-race" state_labels(working)= null
after reporter:
   w1:p2 agent= claude display_agent= "└ fix-token-race" state_labels(working)= "⎇1540 +5 −1 ✎2"
home pane w1:p1 (has who token) gets the topic:
   w1:p1 agent= omp display_agent= "Planning the release checklist" state_labels(working)= "◷3 ⚙0% ▤0% ⛁0% ⎇1537 +12847 −3902 ✎214"
Evidence: Topic follows a rename; 80-character cap; idempotent re-run
\### I. session renamed -> display agent follows on next run
   w2:p1 display_agent= "Ship the ratings fix" 20
   w2:p1 display_agent= "Renamed: rate limiter rewrite" 29
\### J. 120-char title -> capped at 80 (Herdr metadata limit); reporter still idempotent on re-run
   w2:p1 display_agent= "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 80
pane writes run1: 1
pane writes run2 (title unchanged): 0
Evidence: Reporter with no herdr server: exit 0
\### K. no herdr server reachable: reporter must exit 0 and never raise
herdr-spaces: Command '['herdr', 'workspace', 'list']' returned non-zero exit status 1.
exit=0
Evidence: Targeted pytest run (unit evidence, not a live scenario)
........................................................................ [ 97%]
..                                                                       [100%]
74 passed in 26.51s

Author's before/after captures

Captured from a throwaway Herdr 0.9.3 session (never the live session), through a real client attached in a 50-column and a 120-column terminal. "Before" ran the reporter from main; "after" ran this branch's reporter. Fixture: workspaces machine, a primary home (label firstmate) with its own agent plus a worker tab whose display agent the orchestrator set to └ fix-token-race, and a second-level home 2ndmate-webapp-mate-s4, with the pane tokens the omp extension reports.

Phone, 50 columns, switch menu: before

 switch                                 │  close
                                        │    ×
──────────────────────────────────────────────────
  agents
   ● Local · firstmate
   1 · working · omp
   ● Local · firstmate
   fix-token-race · working · └ fix-token-race
   ● Local · 2ndmate-webapp-mate-s4
   working · omp
  spaces
   + new workspace
   · Local · machine
   shell · tab 1
   ● Local · firstmate
   shell · tab 1 · 1/2
   ● Local · 2ndmate-webapp-mate-s4
   shell · tab 1
  tabs

Phone, 50 columns, switch menu: after

 switch                                 │  close
                                        │    ×
──────────────────────────────────────────────────
  agents
   ● Local · firstmate
   1 · ⚑3 ▶1 ◷4 ⚙0% ▤0% ⛁0% · Planning the releas…
   ● Local · firstmate
   fix-token-race · ⎇1537 +12847 −3902 ✎214 · └ f…
   ● Local · 2ndmate-webapp-mate-s4
   ⚑2 ◷3 ⚙0% ▤0% ⛁0% ⚠watcher ⎇1561 +84 −12 ✎3 · …
  spaces
   + new workspace
   · Local · machine
   shell · tab ⚙5% ▤27% ⛁71%
   ● Local · firstmate
   shell · tab 1 · 1/2
   ● Local · 2ndmate-webapp-mate-s4
   shell · tab 1
  tabs

Desktop, 120 columns, sidebar (first 46 columns)

Before and after differ only in the live CPU number on the machine line (⌂ ⚙ 10% before, ⌂ ⚙ 8% after, sampled seconds apart); every other line is identical. After:

 spaces                                      │
                                             │
 · ⌂ ⚙ 8% ▤ 6.2/22.9G 27% ⛁ 137/193G 71%     │
 ● firstmate · ⚑ 3 · ▶ 1 · ◷ 4               │
   ⚙ 0%  ▤ 0%  ⛁ 0%                          │
 ● webapp · ⚑ 2 · ◷ 3                        │
   ⚙ 0%  ▤ 0%  ⛁ 0%                          │
   ⚠ watcher silent                          │
                                             │
                                             │
                                             │
                                             │
                                             │
                                             │
 new                                     menu│
─────────────────────────────────────────────│
 agents                               grouped│
                                             │
 ● firstmate                                 │
   Planning the release checklist            │
 ● └ Fixing the token refresh race           │
   ⠀⠀⎇ 1537 · ○ 1529 · +12847 · −3902 · ✎ 214│
 ● webapp                                    │
   Fix ratings cache key collision           │
   ⎇ 1561 · ○ 1558 · +84 · −12 · ✎ 3         │
                                             │
                                             │
                                             │
                                             │
                                            «│

Also checked in the same session: a machine tab renamed by hand (htop) was left alone by the next run, and once an agent is released from a pane its display agent and state labels no longer show.

Compared with the desktop look: differences the mobile layout forces

  • The switch menu lists agents before spaces, so the machine stats are not the first line; they sit on the machine space entry.
  • Entries show the full workspace label with a Local · prefix (2ndmate-webapp-mate-s4, not webapp), and no colors: these fields are plain text.
  • A home's counts and ⚙ ▤ ⛁ shares share one line with its pull request line and topic; Herdr cuts the end with …, so the topic or worker name is often cut at 50 columns.
  • The state label replaces Herdr's state word (working); the state dot still shows the state.
  • Home space entries keep Herdr's shell · tab N line: home tab names are matched by label elsewhere, so they are never renamed. The issue (○) is left out for width. Without a machine workspace, the machine shares do not show on a phone.
  • On the desktop, a home pane in a split shows its session topic on its border instead of omp, and the machine tab shows the shares in the tab bar while machine is focused. The sidebar itself is unchanged.

Lean review verdict (one round)

  • Keep: "drop the machine_tab cache". The reporter runs every second; without it the tab would be renamed every second. The review gate later tightened this to one pinned tab, a 30-second throttle, and never overwriting a label the reporter did not write.
  • Keep: "extract the per-pane body into a helper". It moves code into a new function without removing any.
  • Keep: "split tight() into two named steps". The review itself marks it no line savings, flag only.

Pipeline

Updates from git push no-mistakes

... (19 earlier update rounds omitted to keep the PR body within GitHub's 65536-char limit; full history is in the run log.)

⚠️ **Review** - 2 issues (1 warning, 1 info)

Source evidence, Herdr v0.9.3 (the installed binary; src/terminal/metadata.rs is identical to main):

  • TerminalState::set_agent_metadata takes the else branch when no clear flag is set.
  • That branch runs self.agent_metadata.insert(report.source, AgentMetadata{ display_agent: report.display_agent, state_labels: report.state_labels, .. }).
  • It replaces the whole code-factory:spaces record, so any field absent from the report is dropped.
  • The --clear-* branch is the only one that patches.
  • --agent and --applies-to-source are unset here, so no other source supplies these fields on a home pane. The omp extensions never write them.

Failing sequence for a home pane (one with a who token), starting from the correct state {display_agent: &#34;Topic A&#34;, state_labels: T1}:

  1. The counts or PR line change from T1 to T2 (queue 4→3, or +84 becomes +90), or the topic changes. These are routine.
  2. Tick N: the display agent matches, so line 401 adds nothing. The labels mismatch, so line 411 adds only --state-label. The report replaces the record with {labels: T2} and the display agent is gone.
  3. Tick N+1: display_agent is now None, so line 402 sends --display-agent alone. The record becomes {display_agent} and the labels are gone.
  4. Tick N+2: the labels mismatch and the display agent matches again. Back to step 2, forever.

The two fields are never both missing in the same tick, so it never converges. The phone's switch menu alternates between losing the counts and losing the topic. Herdr gets one subprocess and one metadata event per home pane per second. The tests miss it because the static stub neither models Herdr's replace semantics nor runs twice. The only home-pane case it covers (w1:p2) has both fields stale, which sends them together.

Fix at the single report site (lines 391-418): compute the desired display agent and labels together. If either differs, send both in the same call: --display-agent when there is a title, and either the labels or --clear-state-labels. Sending both is safe. --clear-state-labels puts Herdr in patch mode, so a display agent sent alongside it is kept.

Sites that must follow the same rule:

  • line 401-402: display agent sent alone.
  • line 409-411: labels sent alone when the display agent already matches.
  • line 415: the single report-metadata call that must carry the full set.

The workspace token reports (line 380-384) and the omp extension's token-only pane reports patch independently, so they are not affected. Add a regression test using a stateful stub that applies the replace-per-source semantic. Fixture: a who pane with a matching display agent and stale labels. Run main() repeatedly and assert the second run sends nothing.

  • ⚠️ maintenance/herdr-spaces.py:397 - The mobile layout does not carry all of the stated "look". The intent says each home should be its own entry with "its colored short name, its queue count (◷) and its per-home CPU, RAM and disk (⚙ ▤ ⛁) line". It also says agents should show "by its home's short name". The change deliberately drops these, and docs/herdr.md:174-190 admits it:
  • The per-home ⚙ ▤ ⛁ shares are left out (comment at line 397; res stays desktop-only).
  • Home Space entries in the switch menu keep Herdr's own shell · tab 1 line.
  • Short names and colors are replaced by the workspace label and plain text.
  • A home's queue count appears only on the agent entry of a home that has a running who pane.
  • The whole-machine line shows only if a machine workspace exists.

The author's reason is the 50-column width. Whether dropping per-home shares, short names and the home's Space-entry data is acceptable is a product decision. Adding them back, for example in a trimmed topic or another field, changes the design. The remedy needs authorization, not the defect.

  • ⚠️ maintenance/herdr-spaces.py:373 - The reporter overwrites a user-visible tab name (herdr tab rename) every time the machine line changes. That rename policy needs a decision. The rename is real: Herdr's mobile Space line is &lt;branch|shell&gt; · tab {active tab label} (src/client/shell/mobile.rs, compact_tab_status). The side effects:
  1. The text includes CPU%, RAM% and disk%, which change on nearly every one-second run. So tab rename, a persistent session change, fires about once a second. docs/herdr.md:191 says it renames "only when its text changes", which is true but understates this.
  2. It renames the machine workspace's active_tab_id. If an operator opens a second tab in machine and focuses it, that tab's name is overwritten within a second. The first tab keeps its last shares text as a stale label. docs/herdr.md:181 says "the orchestrator never names that tab", but operators can.
  3. On the desktop the tab bar shows the shares while machine is focused (admitted at docs/herdr.md:191).

Pinning one tab, restoring labels, dropping the rename, or throttling it are each product choices, so the remedy needs authorization.

  • ⚠️ maintenance/herdr-spaces.py:402 - Setting the home pane's --display-agent to the session topic changes the desktop pane border, and docs/herdr.md says otherwise. The doc says none of these fields shows on the desktop except the renamed tab. That is accurate for the sidebar: config/default.yml uses only state_icon, $who and terminal_title.

It is not accurate for pane borders. TerminalState::border_label(show_agent_labels) in Herdr src/terminal/state.rs falls back to effective_display_agent(). This repo sets show_agent_labels_on_pane_borders = true (ansible/group_vars/all.yml:198, rendered at ansible/templates/herdr.toml.j2:21).

Sequence: a home omp pane is in a split. Its border label was omp and becomes the session topic, changing on every rename. The intent says the desktop "sidebar must stay exactly as it is". Borders are not the sidebar, but this is a desktop-visible change nobody has approved. Either accept it and document it, or avoid the display-agent write. That is a product decision about the desktop look.

  • ⚠️ maintenance/herdr-spaces.py:417 - The pane reports omit --agent, so the topic and counts this script writes stay on a pane after its agent exits. The script never cleans them up itself. Unlike the other findings, this is a source-proven defect with a small remedy.

Sequence: a home omp pane (who token set) gets --display-agent &lt;topic&gt; and state labels from this script. The user quits omp, or the extension clears who on session shutdown. pane.get(&#34;agent&#34;) is now falsy, so the loop hits continue (the if not pane.get(&#34;agent&#34;) check, ~line 392) and the pane is never visited again.

Herdr keeps the record. In v0.9.3, set_agent_metadata stores agent_label = None for a report without --agent. metadata_guards_match (src/terminal/metadata.rs) only hides metadata whose agent_label is set and no longer equals effective_agent_label(). The exit handler (src/terminal/state.rs:573-583) only clears metadata whose agent_label equals the exited agent. metadata_report_blocked_by_process_exit likewise only blocks --agent-tagged reports. So this source's record is never hidden or cleared.

Result: pane list keeps returning the old display_agent and state_labels. border_label (state.rs, ~line 2409) falls back to effective_display_agent() while show_agent_labels_on_pane_borders is true (ansible/group_vars/all.yml:198). So a split shell pane shows the dead session's topic on its desktop border indefinitely.

Same class, sibling sites, all using the same report call at line 417:

  • Line 410 sends --display-agent with no guard.
  • Line 412 sends --state-label with no guard.
  • Line 414 sends --clear-state-labels with no guard.
  • A pane that stays an agent but loses who (so title becomes "") keeps its old display_agent. The title and ... test at ~line 409 skips it, and the replace-style report at line 412 only fires when the labels also differ.

Fix at the shared boundary: add --agent &lt;pane[&#34;agent&#34;]&gt; to every report at line 417. I confirmed from Herdr's source that --agent on report-metadata is only a guard. set_agent_metadata stores it, metadata_guards_match hides the record when the pane's agent differs, and the process-exit handler clears it. It does not claim or relabel the pane.

Idempotence is unaffected: pane list's agent is effective_agent_label(), the same value the guard compares against.

Update the stub arguments in tests/test_herdr_sidebar.py (test_mobile_layout_carries_the_same_data, test_mobile_reports_settle_and_follow_a_changed_pane, and the w3:p4/w3:p8 expectations).

  • ⚠️ maintenance/herdr-spaces.py:402 - The mobile layout does not carry all of "this look". The intent says each home is "its own entry with its colored short name, its queue count (◷) and its per-home CPU, RAM and disk (⚙ ▤ ⛁) line", and that agents show "by its home's short name". The change drops several of these, and docs/herdr.md ("What the mobile layout forces") admits it:
  • Per-home ⚙ ▤ ⛁ shares are left out (comment at line 402). The res token stays desktop-only.
  • Home Space entries in the switch menu keep Herdr's own shell · tab 1 line.
  • Short names and colors are replaced by the plain workspace label.
  • A home's ◷ count shows only on the agent entry of a home that has a running who pane.
  • The whole-machine line shows only when a machine workspace exists.

The author's reason is the 50-column width. I confirmed in Herdr's mobile.rs that the Agent line is [tab] · state label · display agent and the Space line is Herdr-built, so some limits are real. Whether dropping per-home shares and the home Space-entry data is acceptable is a product decision. Adding them back (another field, or a trimmed topic) would change the design, so the remedy needs authorization, not the defect.

  • ⚠️ maintenance/herdr-spaces.py:371 - The reporter renames the machine workspace's active tab, a persistent and user-visible tab name, whenever the shares text changes. The remedy is a rename policy, so it needs authorization.

I checked Herdr's handle_tab_rename (src/app/api/tabs.rs): each rename calls set_custom_name, schedule_session_save() and emits a TabRenamed event. The text holds CPU%, RAM% and disk%, which change nearly every tick. So a session-save request and an event can fire about once a second, although docs/herdr.md says "only when its text changes".

The rename targets ws.get(&#34;active_tab_id&#34;). Concrete sequence: an operator opens a second tab in machine (for example htop) and focuses it. That tab's name is overwritten within a second. The first tab keeps its last shares text as a stale label, because the cache only remembers the last [tab, short] and nothing restores earlier names. This contradicts docs/herdr.md ("the orchestrator never names that tab"), since operators can name tabs there.

The renamed tab is also desktop-visible beyond the tab bar the docs mention: the desktop navigator uses a custom tab label as the row name (aggregate_navigation.rs ~line 366).

Options to choose between: pin the rename to one tab, throttle it, restore labels, or drop it. Dropping it means the whole-machine line is absent on mobile, which the intent requires.

  • ⚠️ maintenance/herdr-spaces.py:410 - Setting --display-agent on home panes changes the desktop pane border, and docs/herdr.md does not say so. The docs say none of these fields shows on the desktop except the renamed tab. That is accurate for the sidebar: config/default.yml is the only definition of sidebar_agent_rows in the repo, and its rows use only state_icon, $who, terminal_title and the $pr/$add/$del/$files tokens. It uses no agent or state_text token, so agent_sidebar.rs's reads of display_agent and state_labels never reach the screen. aggregate_navigation.rs uses display_agent only as a fallback when agent is unset and for search matching.

It is not accurate for pane borders. Herdr's TerminalState::border_label(show_agent_labels) (src/terminal/state.rs) falls back to effective_display_agent() when no title or manual label is set. This repo renders show_agent_labels_on_pane_borders = true (ansible/group_vars/all.yml:198, templates/herdr.toml.j2:21).

Sequence: a home omp pane sits in a split pane. Its border label was omp and is now the session topic, which changes on every rename.

The intent says the desktop "sidebar must stay exactly as it is". Borders are not the sidebar, but this desktop-visible change is unapproved and undocumented. Either accept it and document it, or avoid the display-agent write. That is a product decision about the desktop look. Mobile needs display_agent to show the session title, because Herdr's mobile Agent line has no other field for it.

🔧 Fix applied.
1 warning still open:

  • ⚠️ maintenance/herdr-spaces.py:417 - The pane reports omit --agent, so the topic and counts this script writes stay on a pane after its agent exits. The script never cleans them up itself. Unlike the other findings, this is a source-proven defect with a small remedy.

Sequence: a home omp pane (who token set) gets --display-agent &lt;topic&gt; and state labels from this script. The user quits omp, or the extension clears who on session shutdown. pane.get(&#34;agent&#34;) is now falsy, so the loop hits continue (the if not pane.get(&#34;agent&#34;) check, ~line 392) and the pane is never visited again.

Herdr keeps the record. In v0.9.3, set_agent_metadata stores agent_label = None for a report without --agent. metadata_guards_match (src/terminal/metadata.rs) only hides metadata whose agent_label is set and no longer equals effective_agent_label(). The exit handler (src/terminal/state.rs:573-583) only clears metadata whose agent_label equals the exited agent. metadata_report_blocked_by_process_exit likewise only blocks --agent-tagged reports. So this source's record is never hidden or cleared.

Result: pane list keeps returning the old display_agent and state_labels. border_label (state.rs, ~line 2409) falls back to effective_display_agent() while show_agent_labels_on_pane_borders is true (ansible/group_vars/all.yml:198). So a split shell pane shows the dead session's topic on its desktop border indefinitely.

Same class, sibling sites, all using the same report call at line 417:

  • Line 410 sends --display-agent with no guard.
  • Line 412 sends --state-label with no guard.
  • Line 414 sends --clear-state-labels with no guard.
  • A pane that stays an agent but loses who (so title becomes "") keeps its old display_agent. The title and ... test at ~line 409 skips it, and the replace-style report at line 412 only fires when the labels also differ.

Fix at the shared boundary: add --agent &lt;pane[&#34;agent&#34;]&gt; to every report at line 417. I confirmed from Herdr's source that --agent on report-metadata is only a guard. set_agent_metadata stores it, metadata_guards_match hides the record when the pane's agent differs, and the process-exit handler clears it. It does not claim or relabel the pane.

Idempotence is unaffected: pane list's agent is effective_agent_label(), the same value the guard compares against.

Update the stub arguments in tests/test_herdr_sidebar.py (test_mobile_layout_carries_the_same_data, test_mobile_reports_settle_and_follow_a_changed_pane, and the w3:p4/w3:p8 expectations).

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

  • ⚠️ maintenance/herdr-spaces.py:417 - The pane reports omit --agent, so the topic and counts this script writes stay on a pane after its agent exits. The script never cleans them up itself. Unlike the other findings, this is a source-proven defect with a small remedy.

Sequence: a home omp pane (who token set) gets --display-agent &lt;topic&gt; and state labels from this script. The user quits omp, or the extension clears who on session shutdown. pane.get(&#34;agent&#34;) is now falsy, so the loop hits continue (the if not pane.get(&#34;agent&#34;) check, ~line 392) and the pane is never visited again.

Herdr keeps the record. In v0.9.3, set_agent_metadata stores agent_label = None for a report without --agent. metadata_guards_match (src/terminal/metadata.rs) only hides metadata whose agent_label is set and no longer equals effective_agent_label(). The exit handler (src/terminal/state.rs:573-583) only clears metadata whose agent_label equals the exited agent. metadata_report_blocked_by_process_exit likewise only blocks --agent-tagged reports. So this source's record is never hidden or cleared.

Result: pane list keeps returning the old display_agent and state_labels. border_label (state.rs, ~line 2409) falls back to effective_display_agent() while show_agent_labels_on_pane_borders is true (ansible/group_vars/all.yml:198). So a split shell pane shows the dead session's topic on its desktop border indefinitely.

Same class, sibling sites, all using the same report call at line 417:

  • Line 410 sends --display-agent with no guard.
  • Line 412 sends --state-label with no guard.
  • Line 414 sends --clear-state-labels with no guard.
  • A pane that stays an agent but loses who (so title becomes "") keeps its old display_agent. The title and ... test at ~line 409 skips it, and the replace-style report at line 412 only fires when the labels also differ.

Fix at the shared boundary: add --agent &lt;pane[&#34;agent&#34;]&gt; to every report at line 417. I confirmed from Herdr's source that --agent on report-metadata is only a guard. set_agent_metadata stores it, metadata_guards_match hides the record when the pane's agent differs, and the process-exit handler clears it. It does not claim or relabel the pane.

Idempotence is unaffected: pane list's agent is effective_agent_label(), the same value the guard compares against.

Update the stub arguments in tests/test_herdr_sidebar.py (test_mobile_layout_carries_the_same_data, test_mobile_reports_settle_and_follow_a_changed_pane, and the w3:p4/w3:p8 expectations).

  • ℹ️ maintenance/herdr-spaces.py:385 - Two changes are visible on the desktop outside the sidebar. The intent says the desktop sidebar must stay exactly as it is, and the sidebar itself is unchanged. The user can decide whether these two effects fit "keep this look".

Sidebar unchanged (checked):

  • The default sidebar rows in config/default.yml:27-65 use only state_icon, $who, terminal_title, $pr, $issue, $add, $del, $files, $short, workspace, $decisions, $crew, $queue, $machine, $res, $host and $alert.
  • rows_by_agent, state_text and the agent token appear nowhere in the repo's Herdr config.
  • Herdr's configuration docs name state_text as the token that shows a reported state label. display_agent is the pane's displayed agent name.
  • The mobile reporter writes only those two fields, so no sidebar row reads them.

Desktop effects outside the sidebar, both disclosed in docs/herdr.md:183 and :185:

  1. herdr-spaces.py:384-389 renames the machine workspace's first tab to its CPU, RAM and disk shares. The new label shows in the desktop tab bar and navigator while that workspace is focused. It is needed because the mobile Space entry has no other field for the machine line.
  2. herdr-spaces.py:425 sends the session topic as a home pane's display agent. The factory sets show_agent_labels_on_pane_borders = true (ansible/group_vars/all.yml:198), so a home pane in a split shows the topic on its border instead of omp.

No action needed if these fit the intent. If they do not, the options are dropping the tab rename, or setting factory_herdr_show_agent_labels to false. The second option would also change the desktop border labels for every agent pane, so it is a user decision.

This is separate from the advisory's scenario of a pane that loses who while omp keeps running. I judged that scenario not worth a finding:

  • config/herdr-sidebar.ts clears who only in session_shutdown (lines 286-294). session_switch and session_start re-report it.
  • The --agent guard added in round 5 drops the record when the agent exits. So any stale topic lasts only the short gap between the shutdown clear and Herdr dropping the agent.
  • A reporter-side fix would need per-pane memory of which display agents it wrote, because pane list merges the orchestrator's worker display agents with ours. That is new durable state, so it would be the user's call.
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 15 of 16 scenarios driven live against the product
Scenario Result Live Evidence
On a 50-column phone, the user opens the switch menu and each home agent entry shows its ◷ count, ⚙ ▤ ⛁ shares and pull request line, with the session title after it. Pane tokens come from the real `p… ✅ pass live 15-mobile-machine-switch.png, 15-mobile-home-capture.txt: ◷3 ⚙0% ▤0% ⛁0% ⎇1537 +12847 −3902 ✎214 · Plann… and ◷1 ⚙0% ▤0% ⛁0% +84 −12 ✎3 · Fix ratings screen
The issue (○ 1529) seeded on a home pane shows on the desktop agent row but is left out of the mobile entry, and docs/herdr.md lists that as a forced difference ✅ pass live 12-desktop-AB.txt (desktop row ⎇ 1537 · ○ 1529 · +12847 · −3902 · ✎ 214) vs 15-mobile-machine-switch.png (no ○); 14-per-home-cpu-change.txt last line; docs/herdr.md:193 `The issue (○) is left out…
The user keeps the mobile layout: full-width chat panes with a switch button and the navigation menu (agents, spaces, tabs, menu) ✅ pass live 15-mobile-home-main.png and 15-mobile-home-switch.png
The whole-machine CPU, RAM and disk shares show on the phone via the machine workspace's pinned first tab, in both the Space entry and the tabs list ✅ pass live 15-mobile-machine-switch.png (shell · tab ⚙31% ▤28% ⛁71% and 1 · ⚙31% ▤28% ⛁71%); 13-fresh-install-tab-and-idempotence.txt
Fresh install: the machine tab shows without ⚙ on the first run, stays unchanged on an immediate re-run (30 s throttle), and gains ⚙ after 30 s or more ✅ pass live 13-fresh-install-tab-and-idempotence.txt: ▤28% ⛁71%, then unchanged, then ⚙13% ▤28% ⛁71% after 31 s. A fresh install shows a CPU-less machine tab for up to 30 s (informational).
Adversarial: an operator-named machine tab (htop) is left alone after the throttle expires, and a second focused tab is never renamed ✅ pass live 05-machine-tab-adversarial.txt cases A and B: 0 renames, htop kept, w3:t2 stays 2
Adversarial: the tab is taken back only once the operator resets it to a number, renames are throttled to one per 30 s, and the pin moves to the new first tab when the pinned tab is closed ✅ pass live 05-machine-tab-adversarial.txt cases C, D, E; 13-fresh-install-tab-and-idempotence.txt
Home workspaces' tabs are never renamed, so the orchestrator still finds agents by tab label ✅ pass live 05-machine-tab-adversarial.txt case F: w1:t1 and w2:t1 stay 1
Idempotence: with nothing else perturbing workspace CPU, six back-to-back reporter runs make no pane report-metadata call and no tab rename ✅ pass live 13-fresh-install-tab-and-idempotence.txt (6 runs, 0 writes, 0 renames)
The phone label follows the per-home CPU both ways: with a busy process in the home the label rises to ⚙12% and matches the res token, and on the first run after the process exits it returns to ⚙0%… ✅ pass live 14-per-home-cpu-change.txt: res ⚙ 12% ↔ label ⚙12%, then res ⚙ 0% ↔ label ⚙0% on the first post-exit run and the two runs after it
Adversarial: when omp exits, the reporter's topic and state labels disappear, while an unguarded control write survives ✅ pass live 06-agent-exit-guard.txt: reporter metadata cleared after release-agent; control display_agent=&#34;stale topic&#34; survived. This run used a hand-seeded +84 token, which does not affect the guard.
A worker agent pane (no who token) keeps the orchestrator's display agent, and its real-form pull request parts go to the state labels ✅ pass live 07-worker-pane.txt: └ fix-token-race unchanged; state labels ⎇1540 +5 −1 ✎2
The session topic follows a session rename, a title over 80 characters is capped at 80, and an unchanged title causes no rewrite ✅ pass live 08-topic-follow-and-cap.txt
The desktop sidebar is unchanged by the new reporter: A/B at 120 columns with the repo's rendered herdr.toml.j2, base reporter cdd9549 then target 5fc8fb5, firstmate focused ✅ pass live 12-desktop-AB.txt: sidebar columns 0-45 differ only in the live host line's ⚙ and ▤ digits; the digit-masked diff is IDENTICAL; columns 46 and beyond identical
The reporter exits 0 and does not crash when no Herdr server is reachable ✅ pass live 11-no-server.txt
Documented effect: a home pane in a split shows the session topic on its desktop pane border instead of omp (decision C) ⏸️ untested no Tried a split workspace with the repo's rendered herdr.toml.j2 (show_agent_labels_on_pane_borders = true) at 120 columns, with panes whose agent was reported through pane report-agent. Herdr dre…
  • python3 -m pytest tests/test_herdr_sidebar.py -q -p no:cacheprovider (74 passed). Unit-level evidence only, not counted as a live scenario.
  • Isolated real herdr server (own HOME, XDG_CONFIG_HOME, XDG_RUNTIME_DIR, XDG_CACHE_HOME). herdr workspace list showed only the fixture's workspaces, and a /proc check found only the fixture's own pane process carrying HERDR_WORKSPACE_ID=w1. The unmodified maintenance/herdr-spaces.py ran against it.
  • Pane tokens seeded from the real exported prParts in config/herdr-sidebar.ts (run with bun, sent with the extension's report-metadata argument shape), including an issue token to check it is left out of the mobile line
  • Herdr TUI in 50x30 and 50x40 ptys (non-zero TIOCSWINSZ, drained master, patched pyte capture, PNG render): mobile main view and prefix+w switch menu, with a home workspace focused and with machine focused
  • A/B desktop capture at 120x30 with the repo's herdr.toml.j2 rendered from the repo config (herdr config check ok): base-commit reporter first, then target reporter on the same server, with a split pane. Compared sidebar columns 0-45 raw and digit-masked, and columns 46+ separately.
  • Fresh-install run: empty cache, machine tab reset to 1. Run 1 named the tab without ⚙. Run 2 immediately after left it unchanged. Run 3 after 31 s added ⚙.
  • Six back-to-back reporter runs with only fixture processes present: 0 pane writes and 0 tab renames each
  • Re-run of the CPU rise and fall on a fresh isolated server, printing the res token next to state_labels.working at every step. Idle was ⚙0%, with a busy process tagged HERDR_WORKSPACE_ID=w1 the label read ⚙12%, and the first run after the process exited gave ⚙0% again. The label matched the token at every step.
  • Machine-tab adversarial cases: operator renames the pinned tab, opens and focuses a second tab, resets the label to a number, closes the pinned tab. Home workspaces' tabs stay 1.
  • Agent-exit guard: pane release-agent after the reporter's --agent-tagged write, plus a control write without --agent
  • Worker pane in a split (no who token) with real prParts output: the orchestrator's display agent kept, pull request parts written to state labels
  • Session topic follows a rename, is capped at 80 characters, and an unchanged title causes no rewrite
  • Reporter with no reachable herdr server: exit 0, no traceback
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Herdr's single-column mobile layout ignores the custom sidebar rows. The
herdr-spaces reporter now also writes a compact form of the same data into
the fields that layout shows: agent state labels carry each home's counts
and alert plus every agent's pull request line, a home agent's display
agent carries its session topic, and the machine workspace's tab carries
the machine's shares. Firstmate's own display agent, workspace labels and
tab names are never written. The desktop sidebar is unchanged.
@undeemed
undeemed merged commit 8900929 into main Oct 1, 2026
2 checks passed
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.

1 participant