Commit graph

803 commits

Author SHA1 Message Date
lexis
4a2b2fca4f docs(web-ui): logs flyout fills side-panel height (follow-up to #543) 2026-05-29 00:22:19 +02:00
damocles
994166b20c turn: snapshot file_count alongside mtime to survive unreadable meta.modified() 2026-05-28 20:08:27 +02:00
damocles
6e833b22d6 turn: wait_for_login resumes only on credentials mtime bump (closes #542) 2026-05-28 20:08:27 +02:00
damocles
b5be139fb7 forge_notify: diff mentions against raw excerpt to avoid heading-mention duplication 2026-05-28 20:08:05 +02:00
damocles
4b30f20dcf forge_notify: embed issue/pr body excerpt + surface truncated mention lines (closes #539) 2026-05-28 20:08:05 +02:00
iris
8bbb3e4905 tabs: logs flyout fills the side-panel height (closes #541)
The journald viewer opens in the side panel, but the <pre> only takes
the height of its content — so a short log fetch leaves the bottom 80%
of the panel empty, and a long one pushes the controls past the
viewport. Wrap the body in a column-flex container that fills the
side-panel-body, keep controls fixed at the top, and let the <pre>
flex-grow into the rest of the panel with its own overflow:

  .journal-body { display: flex; flex-direction: column; height: 100%; }
  .journal-controls { flex: 0 0 auto; }
  .journal-output { flex: 1 1 0; min-height: 0; overflow: auto; }

`min-height: 0` is the canonical "let me actually flex-shrink for
overflow" escape hatch on flex children. Also moved the auto-scroll
target from side-panel-body onto the <pre> itself — the panel-body no
longer overflows now that journal-body fills it; the <pre> is the new
scroll surface.

Pure CSS + one JS line; no DOM shape change.
2026-05-28 18:54:02 +02:00
damocles
2d9109d1bd docs(web-ui): describe new single-table schedules layout (#124#535) 2026-05-28 15:25:32 +02:00
iris
42a2503834 tabs: render schedules as a single table with tilted agent columns (closes #535)
Replaces the per-schedule card layout with one table:

  | # | src | next | every | owner | body | …agents… | actions |
  |---|-----|------|-------|-------|------|----------|---------|
  | 5 | op  | 5m   | 10m   | mara  | "…"  | ✓ ✓ . ✓ |  ↯ ✎ ✕  |

Each schedule is one <tr> in the tbody. Agent columns are dynamic —
operator + manager + live containers + any "extra" name that appears
as a target on some schedule but isn't a current container (same
membership rule buildTargetChips uses, so the table + new/edit forms
agree on what's addressable). Per-agent cells:

  - active target  → <button>✓</button> that cancels just that target
    on click (replaces the per-row ✕ from the old targets sub-table)
  - cancelled target → muted ✕ glyph (no button; re-add flows through
    the edit form's targets multi-select)
  - not a target → empty cell

Agent column headers tilt -45° via CSS so each column reads as 28px
of horizontal real estate instead of the full word width. Standard
rotated-header pattern: 95px-tall <th> with position:relative, inner
<div> positioned absolute at bottom-left, transform rotates about
left-bottom.

Body cell truncates with ellipsis + full-text title. Description used
to be a separate visible block on the card layout; the table folds
it into the body cell's title to keep row height tight. If mara wants
description visible in-table it's a small follow-up — easier to
iterate on a rejection.

Edit form expands inline into a colspan'd row underneath the schedule
row it edits (instead of inside the card). Wrapper drops the form's
background so it reads as a row extension.

No backend changes; everything renders from existing schedulesState
+ containersState.
2026-05-28 14:22:39 +02:00
iris
78a503d8b9 nix/frontend: refresh stale dist-layout comment
The output-layout block was written before:
- the dashboard's app.js → tabs.js rename (#495)
- the flow.html / flow.js page (#406 / #485)
- the SharedWorker stream-worker.js (#448)
- the build.mjs split-into-static/ subdir convention

Brings the comment in line with what `find dist -type f` actually
prints for both packages. Pure documentation refresh; install phase
and build hashes untouched.
2026-05-28 14:17:52 +02:00
iris
bc1c910044 tabs: extract scheduleField() helper — collapse 8 label/caption sites
Every field on the new- and edit-schedule forms (plus buildIntervalComposer
and buildTargetChips) builds the same wrapper shape:

    el('label', { class: 'schedule-field' },
      el('span', { class: 'schedule-field-label' }, '…'))

then appends an input. Eight identical-modulo-text sites. Extracting one
small helper:

    function scheduleField(labelText, ...children) {
      return el('label', { class: 'schedule-field' },
        el('span', { class: 'schedule-field-label' }, labelText),
        ...children);
    }

drops the per-site cost to a single `form_.append(scheduleField('caption',
input));` line. -10 LOC net; no behaviour change.
2026-05-28 14:13:35 +02:00
damocles
1d7feeb029 hive-forge: pr-create --push opts into branch push + hint suppression (#222) 2026-05-28 01:08:43 +02:00
damocles
def06fb388 docs(web-ui): SharedWorker heartbeat + watchdog (#124 follow-up to #515) 2026-05-28 00:57:15 +02:00
damocles
800d9925a5 prompt: matched-close check + drop dead-code guard (argus #527 nits) 2026-05-28 00:33:43 +02:00
damocles
1c8e72c81f docs(CLAUDE.md): file map reflects unified prompts/system.md + src/prompt.rs 2026-05-28 00:33:43 +02:00
damocles
01af5003d1 harness: unify agent + manager prompts into single template (closes #519) 2026-05-28 00:33:43 +02:00
iris
88476976b7 stream-worker: honest comment on heartbeat catch (argus nit on #529)
argus pointed out the inline catch comment claimed dead ports get cleaned up on
next subscribe, but nothing actually prunes the allPorts Set on subscribe — the
honest answer is the one already at the bottom of onconnect: dead entries are
left in the Set, the bound cost is acceptable, and postMessage's throw is the
ambient signal we use. Point at that comment instead of repeating a wrong
description.
2026-05-27 23:40:38 +02:00
iris
b9df538940 frontend/dashboard: heartbeat + watchdog so a dead SharedWorker self-heals (#515)
Firefox kills "idle" SharedWorkers under memory pressure with no native
signal to the client. The page's port silently becomes a no-op and
events stop flowing — observable symptom: mara's "dashboard never
refreshes; F5 fixes it" (because F5 creates a fresh page that creates
a fresh worker).

The worker now pings every connected port every 30s. The client tracks
last-activity-from-worker on every message arrival (incl. pings, since
those carry no URL — bumped before the URL filter in the route handler).
A visibility-gated watchdog polls every 15s; if the page is visible AND
has active subs AND hasn't heard from the worker in >90s, it presumes
the worker dead, logs a console warning, and re-subscribes on a fresh
port. Three pings missed before we act, so a normal tab-throttle blip
doesn't false-positive.

The fresh-port re-subscribe re-uses the bfcache-restore code path
(same shape: drop stale listeners, getSharedPort → new SharedWorker,
re-attach each route + repost subscribe). Recovery is per-tab — when
one tab's watchdog fires and brings up a new worker, other tabs that
share the named worker pick it up on their own watchdog cycle.

Falls back gracefully on environments without SharedWorker (the
existing direct-EventSource path is untouched) and is invisible on the
healthy path — pings are 30s apart, no UI surface.
2026-05-27 23:37:21 +02:00
damocles
c786b9ec37 docs(persistence): broker.sqlite is six tables now (#124 follow-up) 2026-05-27 23:07:15 +02:00
lexis
29d8c264ca docs: update README flake example to nixos-26.05 2026-05-27 22:12:32 +02:00
damocles
308b2b4773 docs: point upstream URL at public mirror at forge.darkest.space (closes #523) 2026-05-27 22:09:42 +02:00
damocles
7d4811504a forge_notify: strict ATX detection + preserve trailing newline (argus nits on #518) 2026-05-27 19:39:39 +02:00
iris
e8c86bef3b tabs: dedup target-chips + interval-parse between new/edit schedule forms
mara: \"look through the code for dedups, structural improvements
and so on\". Two near-identical blocks across the new-schedule and
edit-schedule forms folded into shared helpers.

## `buildTargetChips({ idPrefix, fieldName, checked, extraNames })`

Was inlined twice in 18-line blocks that built the same
`<label class="schedule-field">` + `<div class="schedule-targets">`
+ candidate-list logic (containers + operator + manager). Now one
function, two callers; `extraNames` lets the edit form keep
showing already-active targets that have vanished from the live
container list so the operator can still uncheck them
intentionally.

## `intervalSecondsFromFormData(fd, namePrefix)`

Both submit handlers had the same ~13-line d/h/m/s → total-seconds
parser (with `NaN` propagation on bad input). Pulled into one
helper next to `buildIntervalComposer`; call sites become 3 lines:

  const intervalTotal = intervalSecondsFromFormData(fd, 'interval_');
  if (Number.isNaN(intervalTotal)) { alert(...); return; }
  const interval_seconds = intervalTotal > 0 ? intervalTotal : null;

Net -13 lines, but the bigger win is shape — when (not if) a new
schedule field surfaces, there's one chip-render path + one
interval-parser to thread it through instead of two.

Zero behaviour change. Built clean.
2026-05-27 19:22:59 +02:00
damocles
12f2c8311e forge_notify: escape ATX headings in embedded body so they don't blow into wrapper h1/h2 (closes #455) 2026-05-27 19:22:39 +02:00
damocles
0460bbe9ec docs: ContainerCrash mentions recent-transient grace (#124 followup to #425) 2026-05-27 19:19:08 +02:00
damocles
dca62fda40 harness: remove deprecated manager-side request_spawn surface (closes #442) 2026-05-27 18:58:04 +02:00
damocles
03d39788d9 prompts: cancel_loose_end approval kind + lint verb in hive-forge list (#124) 2026-05-27 17:06:40 +02:00
damocles
dfc944d5fa flake: expose agent-base + manager toplevels as packages, opt-in pre-build via hive-c0re module (closes #97) 2026-05-27 14:37:50 +02:00
damocles
c62c73e546 harness: anchor 'update allow-list' reminder on both #[tool_router] impls (argus nit on #511) 2026-05-27 14:26:04 +02:00
damocles
e02b279785 harness: add 6 missing manager tools to allow-list (unblocks #509) 2026-05-27 14:26:04 +02:00
damocles
aba1d3153e crash_watch: suppress crash event on recently-cleared transient (closes #425) 2026-05-27 14:24:13 +02:00
lexis
8dca741f09 docs: add lint verb and approval cancel_loose_end (follow-up to #507 #508) 2026-05-27 12:53:46 +02:00
damocles
402ee95d0c questions: unit-test the manager-only guard on approval cancel (argus nit on #508) 2026-05-27 12:48:30 +02:00
damocles
c1f27e3b7b approvals: manager can withdraw pending approvals (closes #250) 2026-05-27 12:48:30 +02:00
damocles
f866f1822e hive-forge: require --reviewer explicitly for lint no-reviewer (mara nit on #507) 2026-05-27 12:01:54 +02:00
damocles
8f4cb3ed29 hive-forge: paginate no-reviewer comment fetch (argus nit on #507) 2026-05-27 12:01:54 +02:00
damocles
703018106a hive-forge: add lint verb for triage queries (closes #505) 2026-05-27 12:01:54 +02:00
lexis
c11424dd9a docs: add step field to QueueEntry in CLAUDE.md file map (follow-up to #501) 2026-05-27 10:17:24 +02:00
iris
1dfcc8953c rqe-step: drop tabular-nums (no digits in step names)
argus 🟡 on #504 — `font-variant-numeric: tabular-nums` has no
effect on `.rqe-step` since step strings are text-only
("nixos-container update" / "meta prepare_deploy" / etc.),
no digit columns to align. Harmless but cleaner without it.
2026-05-27 02:00:42 +02:00
iris
50986b5a05 dashboard: render step sub-line on rebuild queue entries (closes #437)
Frontend half of #437 — paired with damocles's #501 backend which
adds `step: Option<String>` to `QueueEntry` and annotates 9
worker phases across ApplyCommit / Spawn / MetaUpdate / Rebuild
pipelines.

When `entry.step` is present, render a cyan `↳ <step>` sub-line
below the main row in `renderQueueEntry`. Wraps to its own flex
line via `flex-basis: 100%`, indented 1.8em to align under the
state glyph + kind. Terminal transitions clear `step` on the
backend, so the sub-line just disappears on Done / Failed rows —
no client-side staleness handling needed.

CSS: `.rqe-step` keys off cyan to visually group with the
running-spinner color cue (vs the muted `.rqe-when` /
`.rqe-reason` chips). `tabular-nums` keeps the indented arrow
column stable as step strings change length mid-build.

docs/web-ui.md updated to describe the new sub-line behaviour
in the R3BU1LD QU3U3 section.
2026-05-27 02:00:42 +02:00
damocles
a286ae777c rebuild_queue: per-entry step label + worker phase annotations (#437) 2026-05-27 01:58:34 +02:00
iris
ac4c8456e1 flow.js: subscribe to /dashboard/stream with ?kinds= filter (closes #408)
Frontend half of #408 — wires flow.js's terminal subscribe URL to
the kinds-filter damocles shipped in #499. Only the four kinds
flow.js actually handles get sent on the wire:

- sent / delivered → broker terminal renderer
- container_state_changed / container_removed → local autocomplete
  cache (flowContainers Map used by the @-mention composer)

Everything else (approval_*, question_*, transient_*, tombstones_*,
meta_*, rebuild_queue_*) is now dropped server-side before the
JSON-serialise, instead of being deserialised + dispatched to
flow.js's no-op `_default: () => {}` per frame.

Tabs.js keeps the unfiltered `/dashboard/stream` subscribe since
it routes every mutation kind into its derived stores.

## SharedWorker note

`openStream` keys subscriptions by full URL (#448, #453). The
filtered URL is therefore its own upstream connection to
hive-c0re — when both /index.html and /flow.html are open at the
same time the worker holds two upstreams (one filtered, one full)
instead of today's single shared upstream. The trade is small
per-frame wire-byte + serialise savings vs +1 backend connection
in the both-open case. The flow-page-only case is a pure win.
Acceptable per #408's intent ("split flow messages from main
endpoint").

Round-trip note: if the backend's `kind_tag` strings ever drift
from what's listed here, the affected kind silently never matches
and flow loses that event class. damocles's #499 added a serde
round-trip test against `kind_tag` to guard the backend side;
the frontend mirrors the spelling exactly from the PR body.
2026-05-26 23:02:52 +02:00
damocles
d143abb63d dashboard: filter /dashboard/stream by ?kinds= allow-list (#408) 2026-05-26 23:01:46 +02:00
damocles
f540584553 docs: document rebuild_queue dispatch for approval kinds (#124, #441 followup) 2026-05-26 23:00:51 +02:00
lexis
2173d211a5 docs: add hive-forge diff verb to gotchas cheat sheet (follow-up to #481) 2026-05-26 20:32:27 +02:00
lexis
e368586682 docs: add SCH3DUL3S to FL0W cross-page tab strip (follow-up to #490) 2026-05-26 20:09:22 +02:00
damocles
9ddfd9bc63 docs: document move-agent write API + set-parent surfaces (#124, #486 followup) 2026-05-26 19:56:53 +02:00
iris
a2c5348ca4 dashboard: rename src/app.js → src/tabs.js (closes #406)
#406 was the JS-split refactor: pull shared helpers into
common.js (step 1), pull the flow-only IIFEs into flow.js
(step 2), then rename the legacy combined entry from app.js
to tabs.js (step 3 — this commit) to reflect that the bundle
now owns the dashboard tabs surface only.

What moved:
- `frontend/packages/dashboard/src/app.js` → `tabs.js`
- `build.mjs` entry: `src('app.js')` → `src('tabs.js')`;
  output is now `static/tabs.js`
- `index.html` `<script>` src: `/static/app.js` →
  `/static/tabs.js`; the FL0W-section comment retouched
- `flow.html` reference from `/static/app.js`-as-tabs to
  `/static/tabs.js`; notif + offscreen-inbox comments rewired
  to point at the correct owners (common.js for NOTIF,
  flow.js for renderInbox)
- `common.js`, `flow.js`, `tabs.js` headers: scrub stale
  "app.js" references; document that #406 steps 2 + 3 are
  done and both pages import directly from common.js
- `dashboard.css` comments: every "by app.js" → "by tabs.js"
- `docs/web-ui.md`, `docs/conventions.md`: legacy
  `assets/app.js` → `assets/tabs.js` (the path prefix is
  itself stale from a much-earlier rename, but consistent
  with the rest of those docs)
- `CLAUDE.md` file map: refresh the dashboard package's
  src/ and dist/ layouts to reflect the post-split shape
  (tabs.js, flow.js, common.js, stream-worker.js)

What did NOT move:
- `frontend/packages/agent/src/app.js` (per-agent UI entry)
  stays named `app.js` — it's a separate package, has only
  one entry point, no split happened there
- The two "no-op when target absent" guards in renderContainers
  and renderQuestions are kept as belt-and-suspenders for any
  future page that adds tabs.js without the corresponding
  sections; comments updated to note this rather than the
  pre-split flow.html reason

Functional behaviour identical; this is a pure rename + comment
sweep.
2026-05-26 19:39:27 +02:00
damocles
3dbef583a7 topology: require --parent/--root explicitly + fix stale form doc (#492 nits) 2026-05-26 19:30:46 +02:00
damocles
5456377622 topology: operator-driven move-agent via set_parent (#486) 2026-05-26 19:30:46 +02:00
iris
501a86b725 flow: body to its own full-width line under the meta chips (closes #485)
mara: "the 'timestamp from -> to' part is long enough to warrant
its own line. then the actual messages can be rendered (nearly)
full width."

Confirmed in the layout — previously `.live .msgrow .msg-body`
sat inline with `flex: 1 1 0`, eating whatever the chips left.
With a 14:23:42 timestamp + agent names + arrows that was ~30ch
of prefix; long bodies wrapped awkwardly.

One-line CSS fix: `flex: 1 1 100%` on `.msg-body` forces it to
wrap to its own flex line in the existing `flex-wrap: wrap`
container. Metadata chips stay on the row above; body takes the
full width down to the row's content edge.

`min-width: 0` retained so `word-break: break-word` keeps
working. Reply rows keep their `padding-left: 1.2em` border-left
indent — the body lands within that frame, matching the visual
"this is a reply" nesting.

Out of scope but worth knowing:
- consecutive-message grouping (one header per agent run) was
  the second design option I floated; happy to land it as a
  follow-up if reading still feels chatty.
- timestamp-on-hover was the third; skipping unless asked.
2026-05-26 19:30:28 +02:00