Commit graph

770 commits

Author SHA1 Message Date
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
damocles
c279cbe85a hive-forge: parse git-quoted paths in diff parser (#222 followup) 2026-05-26 19:30:07 +02:00
damocles
67a3c9e2da hive-forge: stat-style +N -M counts in collapsed-lockfile placeholder (#222) 2026-05-26 19:30:07 +02:00
damocles
15521179fc hive-forge: collapse autogenerated lockfile hunks in diff (#222) 2026-05-26 19:30:07 +02:00
iris
cfef348a7f flow.html: add SCH3DUL3S to cross-page tab strip (closes #489)
mara: "why can this even be an issue?" — fair. The SCH3DUL3S
tab was added to index.html's chrome in #459 but flow.html's
parallel tab strip was never updated to match. The flow page
operator hit a dead end going back to the schedules pane.

One-line fix: copy the same `<a class="tab" href="/#schedules"
role="tab" data-tab="schedules">` link from index.html into
flow.html's chrome, positioned between SYST3M and FL0W to match
the dashboard ordering. Count pill stays hidden (flow.js
doesn't sync `schedulesState` — same reason SW4RM/Y3R C4LL/
SYST3M pills also stay hidden on this page).
2026-05-26 18:09:33 +02:00
lexis
1e23017bd9 docs: add edit_schedule manager MCP tool to turn-loop.md (follow-up to #478) 2026-05-26 17:31:18 +02:00
iris
befe5c0523 dashboard: edit-form target multi-select (#474 follow-up)
mara confirmed target editing on q #195 ("also let me edit targets").
Damocles wired `targets_add` / `targets_remove` onto PATCH
/api/schedules/{id} in #478. UI side: the schedule edit form's
read-only targets callout becomes a multi-select checkbox box
(same chip styling as the new-schedule form) pre-checked for
currently-active targets.

Submit logic:

- diff new selection against `originalActiveTargets` to populate
  `targets_add` (checked, not originally active) and
  `targets_remove` (originally active, now unchecked)
- only include each key in the PATCH body when non-empty
- guard against zero-target submission with a clear alert
  pointing the operator at `✕ cancel all` as the intended path
- already-active targets that aren't in the live candidate list
  (e.g. a since-destroyed container) still surface so the
  operator can intentionally drop them

Note (re-add semantics): backend's replace-on-conflict drops
the cancelled-target tombstone + history on re-add — per the
design discussion with damocles, operator intent on re-adding
reads as "fresh start, target is active again." UI copy below
the chip row reflects this.

Carry persistence extended to the `targets` array so the
checkbox state survives a state-poll re-render mid-edit;
listens on `change` in addition to `input` for checkbox events.
2026-05-26 17:08:33 +02:00
iris
062e2d3055 renderReminders: re-indent body inside paintAtomic callback
argus 🟡 nit on #480 — the `if (!rows.length)` block sat at +6
inside the paintAtomic callback, but the `const ul` + for loop
underneath stayed at the pre-wrap +4. Code was correct, just
visually inconsistent. Re-indented the whole callback body
uniformly.

renderScheduleNewForm has the same cosmetic mis-indent but its
body is ~100 lines; leaving that for a separate sweep so the
diff stays focused.
2026-05-26 17:07:19 +02:00
iris
d4a53d83d3 dashboard: atomic-swap paint for schedules + reminders (#464)
argus flagged + mara confirmed: operators see a brief "blink" on
every poll cycle when refreshState fires. Root cause for the
async-fetch sections (refreshReminders, refreshSchedules): the
`await resp.json()` yield is a paint opportunity the browser can
take BEFORE the renderer's `root.innerHTML = ''` + `root.append`
land. The "loading…" placeholder (or the previous render's stale
content) may briefly show through.

Fix: render off-DOM into a `DocumentFragment`, then atomically
swap into the live root with `replaceChildren`. The browser only
sees the new content; no intermediate empty state is reachable.

Added `paintAtomic(liveRoot, build)` helper near the top of the
IIFE — minimal-churn pattern where each renderer's existing
`root.append(...)` body carries over unchanged, just wrapped in
a builder callback that receives the fragment as its `root`
parameter.

Applied to the async-fetch sections argus's note + mara's report
specifically called out:

- `refreshReminders` (both http-error + catch paths)
- `renderReminders`
- `refreshSchedules` (both error paths)
- `renderScheduleNewForm` (carry read still happens against the
  live root BEFORE the swap so mid-typing values are preserved)
- `renderSchedulesList`

Kept scope tight to the async paths. The sync renderers
(renderContainers / renderTombstones / etc.) run inside the
same JS turn as refreshState's other sync work, so the browser
can't paint between their clear+populate steps — no flash to fix
there. If mara still sees blink on those sections after this
lands, extending the pattern is a clean follow-up.

docs/web-ui.md no change needed; this is an implementation
detail of the existing managed-section render machinery.
2026-05-26 17:07:19 +02:00
damocles
a9af7adf5c scheduled prompts: refresh patch_schedule doc for target edits (#478) 2026-05-26 17:06:48 +02:00
damocles
2bf90c9db2 scheduled prompts: refresh stale docs for target edits (#478) 2026-05-26 17:06:48 +02:00
damocles
99bf4d635d scheduled prompts: add/remove targets on edit (#474 fast-follow) 2026-05-26 17:06:48 +02:00
lexis
376b37fed7 docs: add POST /api/rebuild-queue/{id}/cancel endpoint (follow-up to #469) 2026-05-26 17:06:28 +02:00
lexis
4589a61853 docs: add fire_schedule_now tool and /api/schedules/{id}/fire-now endpoint (follow-up to #472) 2026-05-26 17:06:28 +02:00
iris
c4be1e9841 schedules fire-now: textContent + DOM nodes instead of innerHTML
argus 🟡 note on #471 — values rendered into the button flash are
all server-side ints/bool today, but textContent + element children
is the safer pattern if a stringy field ever lands in FireNowReport.
Captures original children on enter so error paths can restore
faithfully (the previous innerHTML round-trip would have already
lost any nested element structure).
2026-05-26 17:06:14 +02:00
iris
f35382e57c schedules fire-now: render FireNowReport inline on the button
damocles's #472 returns `{ ok, failed, missing, one_shot_consumed }`
from the fire-now endpoint. Parse the response and flash the
per-target outcome on the button itself for ~1.5s before
refreshSchedules() repaints — operator sees the result
immediately without a modal alert or an `/api/schedules`
re-fetch round-trip.

Button label transitions:
  ↯ fire now  →  ◐ firing…  →  ↯ fired: 3 ok, 1 missing — consumed
                                (green flash, then row refresh)
2026-05-26 17:06:14 +02:00
iris
7e5b496aa8 dashboard: fire-now button on schedule rows (closes #467)
Operator wants to trigger a scheduled prompt immediately
instead of waiting for the next interval. Adds a `↯ fire now`
button on every active schedule row, next to `✕ cancel all`.

Semantics (per design discussion with damocles):

- recurring schedules → out-of-band pulse, `next_fire_at_unix`
  untouched; the regular cadence keeps firing on the original
  schedule. Operator gets an extra fan-out, not a phase shift.
- one-shots → consumed after the manual fire. Operator's
  intent reads as "the scheduled time was wrong, send NOW";
  leaving the original time would be surprising.

Confirm dialog spells out the recurring-vs-one-shot
difference up front so the operator knows what they're about
to do. Button is disabled when every target is already
cancelled (nothing to fire).

Talks to `POST /api/schedules/{id}/fire-now` (damocles is
wiring the backend in parallel). Mauve styling distinguishes
it from cancel (red) and submit (amber); fits the existing
btn pattern.

docs/web-ui.md updated.
2026-05-26 17:06:14 +02:00
damocles
9fee1a1e56 scheduled prompts: edit existing schedule (closes #474) 2026-05-26 16:07:13 +02:00
damocles
0072946ea3 dashboard: POST /api/rebuild-queue/{id}/cancel for queued entries (closes #447) 2026-05-26 16:00:01 +02:00
iris
dd92ef07fa dashboard: inline edit form on schedule rows (closes #474)
Per damocles's PATCH /api/schedules/{id} backend (#475), each
non-cancelled schedule row gets a `✎ edit` button that toggles
an inline form pre-filled with current values. Editable:

- body (textarea)
- description (blank to clear)
- next-fire (datetime-local)
- interval (shared composer from #466 — all-zero flips to one-shot)

Targets stay immutable per the design call with damocles:
per-target last-result history is keyed on them; cancel + new
schedule is the documented retarget workaround. The form
surfaces the active target list as a read-only note explaining
this.

Submit semantics — the form computes a PATCH diff against the
original schedule and only includes keys for fields that
actually changed. Blank description → `null` (clear), all-zero
interval on a recurring schedule → `null` (flip to one-shot).
No-op submit (no fields changed) just closes the edit form.

Refactored the interval composer (#466) into a shared
`buildIntervalComposer({ label, namePrefix, initialSeconds })`
helper so the new-schedule and edit-schedule forms use the same
chip/d/h/m/s/preview widget. New-schedule form behavior
unchanged; edit-form input names are prefixed `edit_interval_`
to avoid FormData collisions when both happen to mount.

State survives state-poll re-renders via `editingSchedules`
(Set of ids being edited) + `scheduleEditCarry` (per-id
mid-edit values) — same pattern the new-schedule form uses
with `readScheduleFormCarry`.
2026-05-26 15:58:28 +02:00
damocles
d0a1ee037b hive-forge: make --json a global flag (closes #421) 2026-05-26 15:55:59 +02:00
damocles
3cd9240594 scheduled prompts: fire-now operator + manager surfaces (closes #467) 2026-05-26 15:55:35 +02:00
iris
caa0c6ce4c dashboard: friendlier interval input for scheduled prompts (closes #466)
Replaces the single raw-seconds field in the SCH3DUL3D PR0MPTS
creation form with a structured composer:

- Preset chip row (1m / 5m / 15m / 30m / 1h / 6h / 12h / 1d / 7d
  / one-shot) that fills the parts inputs in one click
- Four small d/h/m/s number inputs combined into total seconds
  on submit (all-zero = one-shot, preserving the backend null
  semantic)
- Live "↻ every …" preview using the existing fmtDuration helper
  so the operator sees what they're about to queue

Carry semantics extended to round-trip the split fields across
state-poll re-renders; legacy `interval` carry still honoured if
an older bundle is in the page.

Docs (web-ui.md) updated to describe the new composer.
2026-05-26 15:55:20 +02:00
iris
4fdc061c9d dashboard: move queued reminders into SCH3DUL3S tab (closes #460)
mara wanted one place for everything that fires at a future time.
The QU3U3D R3M1ND3RS section moves out of the SYST3M tab and
lands at the bottom of the SCH3DUL3S tab, below QU3U3D SCH3DUL3S.

Pure layout change — refreshReminders() and /api/reminders
unchanged; the section just lives under a different parent now.
Reminders don't contribute to the SCH3DUL3S pill count (kept as
"active schedules" only — reminders are self-scheduled and high
cardinality; adding them would make the pill noisy).

docs/web-ui.md picks up a SCH3DUL3S tab section (the previous
docs lumped the schedules subsection under SYST3M, but the tab
has been separate since #459); reminders subsection follows
schedules in the same tab.
2026-05-26 13:39:42 +02:00
lexis
0b92d597f8 docs: document scheduled prompts tab in SYST3M (follow-up to #461) 2026-05-26 11:58:51 +02:00
lexis
cbe058f509 docs: fix scheduled_prompts.rs entry — auth logic is in manager_server.rs not scheduled_prompts.rs 2026-05-26 11:46:51 +02:00
lexis
22d970fbe9 docs: add scheduled_prompts to file map + manager schedule tools (follow-up to #454) 2026-05-26 11:46:51 +02:00
lexis
f221cdbb37 docs: document side-panel drag-to-resize and SharedWorker SSE multiplexing (follow-up to #451 #453) 2026-05-26 11:46:17 +02:00
iris
c233f8a924 dashboard: scheduled prompts tab + creation form (closes #459)
frontend for the #444 scheduled-prompts feature. backend is
already merged (PR #454 + sibling commits): GET /api/schedules
(snapshot), POST /api/schedules (operator-direct submit), POST
/api/schedules/{id}/cancel (whole or per-target).

new chrome

- SCH3DUL3S tab in the dashboard tab strip, between SYST3M and
  the FL0W link. count pill shows the number of schedules with
  at least one still-active target.
- pane has two sections: N3W SCH3DUL3 (creation form) +
  QU3U3D SCH3DUL3S (list of cards).

creation form

- targets multi-select rendered as chip-style checkboxes;
  candidates pulled from containersState plus the special
  `operator` and `manager` recipients.
- prompt body textarea (required, non-empty trim).
- first-fire datetime-local input, defaulted to "5 minutes from
  now" so the form has a sensible pre-filled future timestamp.
- optional interval (seconds) input — blank = one-shot.
- optional description (one-liner shown on the schedule card).
- mid-typing carry: re-rendering the form preserves field values
  + checkbox state. the operator never loses what they were
  typing when the schedule list refreshes underneath them.
- POSTs SchedulePromptPayload JSON to /api/schedules; on success
  re-fetches the list to surface the new row.

schedules list

- one card per schedule, active rows first (sorted by
  next_fire_at_unix), cancelled tail dimmed.
- header: id, source chip (`operator` or `approval` — reuses
  the rebuild-queue rqe-source styling for visual consistency),
  next-fire countdown / overdue label, recurring vs one-shot
  badge, owner.
- body: prompt text in a styled <pre>-ish block with linkified
  path references.
- per-target table: target name, last fire age, last result,
  per-row cancel button.
- whole-schedule "✕ cancel all" button.
- per-target cancel posts { targets: ["name"] }; cancel-all
  posts no body (== cancel whole row).

no-SSE refresh

- the backend doesn't emit SchedulesChanged dashboard events
  yet (damocles flagged this as a follow-up PR C). list
  re-fetches on:
  - tab activation (so switching to SCH3DUL3S never lands stale)
  - cold load via refreshState
  - after every submit + cancel POST
  the operator's typical interactions all force a refresh; the
  remaining gap (worker fires while you're staring at the tab)
  is the natural argument for PR C.

files

- frontend/packages/dashboard/src/index.html — new tab + pane
  with the two sections.
- frontend/packages/dashboard/src/app.js — schedulesState
  cache, refreshSchedules, render*ScheduleNewForm, submit +
  cancel helpers, tab routing extended for `schedules`, count
  pill wired into refreshTabCounts.
- frontend/packages/dashboard/src/dashboard.css — schedule
  form + card styling + chip checkboxes + targets table. small
  .btn-inline-small helper for the per-target cancel.

validation

- npm run build --workspace=@hive/dashboard clean. app.js
  158 kb → 161 kb. CSS 41.3 kb → 43.9 kb.
- browser smoke test isn't possible from inside iris's container;
  endpoints are wire-compatible (backend types unchanged) and
  the form serialisation matches SchedulePromptPayload's JSON
  shape exactly (targets / body / first_fire_at_unix /
  interval_seconds / description).
2026-05-26 11:45:57 +02:00
damocles
2593896383 ag3nt: manager MCP tools + prompt for scheduled prompts (#444) 2026-05-26 01:32:51 +02:00
iris
002419ba4d dashboard: drop stale data-spw note in side-panel-drawer comment (#451)
argus nit on #456: the CSS comment claimed "The body adds
data-spw=<px> once persisted" — that's leftover from a prior
design and no such attribute is ever set. The actual flow is
localStorage → JS reads on Panel.bind → sets --side-panel-w via
inline style. Replaced the misleading line with an accurate
description of the persistence path.
2026-05-26 01:27:42 +02:00
iris
f4635cc256 dashboard: full-height inbox + drag-to-resize side panel (#450 #451)
two related flyout improvements — both small, single PR.

#450 — full-height inbox

dropped the .inbox max-height: 24em cap. the side-panel-body
already provides overflow: auto, so the cap was just clamping the
inbox list short of the available panel height on tall viewports.
the inbox now fills as much of the panel as it needs and the
panel itself scrolls.

#451 — drag-to-resize side panel

added a 6px hit-strip on the drawer's left edge. mousedown/move/
up handlers track the drag and update --side-panel-w on the
drawer; the CSS variable defaults to min(760px, 94vw) (preserving
pre-#451 behaviour) and is clamped to [320px, 96vw] so a bad
stored value can never wedge the drawer off-screen.

ergonomics

- handle is invisible at rest, mauve glow on hover + during drag
  so the affordance is discoverable but doesn't compete with the
  2px mauve border-left for the visual boundary.
- body.side-panel-resizing class forces ew-resize cursor + kills
  user-select page-wide so the cursor doesn't flip back to
  default the moment it leaves the 6px band during a fast drag.
- final width persists to localStorage (key
  hyperhive:side-panel-width) so it survives reload. window
  resize re-clamps so a stored width that exceeds the new 96vw
  shrinks accordingly.
- handle is a separator role with aria-orientation: vertical +
  aria-label for screen readers.

files

- frontend/packages/dashboard/src/dashboard.css
  - .inbox: dropped max-height (#450).
  - .side-panel-drawer: width = var(--side-panel-w, min(760px,
    94vw)) + min/max clamp (#451).
  - new .side-panel-resize + body.side-panel-resizing rules.
- frontend/packages/dashboard/src/common.js
  - Panel.bind now also calls applyStoredWidth + bindResize.
  - resize handle is prepended to the drawer at bind time so
    every flyout (inbox, file preview, diff, journal) gets it.

validation

- npm run build --workspace=@hive/dashboard clean. CSS 40.9kb →
  41.3kb. app.js + flow.js grew ~0.5kb each (resize handler).
- browser smoke test isn't possible from inside iris's container;
  the resize math is straightforward (width = innerWidth -
  clientX, clamped) and the CSS variable + localStorage
  persistence are standard patterns.
2026-05-26 01:27:42 +02:00
damocles
f1896a99b8 scheduled_prompts: fix worker doc nits (argus review on #454) 2026-05-26 01:27:25 +02:00
damocles
a31cd8bb15 dashboard: operator-direct schedule submit + cancel endpoints (#444 step 3) 2026-05-26 01:27:25 +02:00