refactor(fe): trim >30-line comment blocks (second pass, #2077)

This commit is contained in:
iris 2026-06-29 00:55:31 +02:00 committed by mara
commit 95b866a53d
5 changed files with 55 additions and 118 deletions

View file

@ -88,14 +88,31 @@ SSE replay. Pages register a `kind → renderer` map; unknown kinds
fall through to a JSON-dump note row. The factory ships three row
shapes the renderers call:
- `api.row(cls, text)` — single-line row with an inline `linkify`
pass over the text.
- `api.details(cls, summary, body)` — collapsible `<details>` with
- `api.row(cls, text, icon?)` — single-line row with an inline `linkify`
pass over the text; optional `icon` lands in a fixed-width `.row-glyph`
cell so glyphs of varying rendered width all align.
- `api.details(cls, summary, body, icon?)` — collapsible `<details>` with
a `<pre>` body (used by long tool-results and stack traces).
- `api.detailsDiff(cls, summary, body)` — same shape, splits the
- `api.detailsDiff(cls, summary, body, icon?)` — same shape, splits the
body on newlines and tags each line as `diff-add` / `diff-del` /
`diff-ctx` so the renderer's diff bodies get coloured without
emitting raw HTML.
- `api.placeholder(text)` — replaces the log with a single muted row,
cleared automatically when the next real row arrives.
- `api.fromHistory``true` while backfill replay is running; renderers
use it to suppress live-only side effects.
**`create(opts)` options:** `logEl` (log container element), `streamUrl`
(SSE endpoint), `historyUrl?` (optional replay endpoint — skipped if absent
or if the fetch fails), `renderers` (kind→fn map; unknown kinds fall through
to `renderers._default`, itself defaulting to a JSON-dump note row),
`onLiveEvent?(ev)` (live-only side effects), `onAnyEvent?(ev, {fromHistory})`
(runs for both replay and live — use for derived views that need the full
event picture), `onBackfillDone?(count)` (one-shot after replay; `count=0` on
failed/skipped fetch), `onStreamOpen?()` (fires on every EventSource
(re)connect — use to re-sync snapshot-derived state after a reconnect gap),
`pillAnchor?` (parent element for the "↓ N new" pill; defaults to
`logEl.parentElement`).
**Sticky-bottom + snap animation.** `stickToBottom` is the
operator's intent: true means "keep snapping to bottom on every

View file

@ -4,39 +4,26 @@
// account and store its access token, without editing the agent's config
// repo. Companion to the multi-account harness support.
//
// Backend contract (v2 — BE-4 adds live/user_id/as_of from the daemon snapshot):
// Backend contract:
// GET /api/matrix-accounts?agent=<name>
// -> { accounts: [ { name, homeserver: string|null, token_present: bool,
// live: bool, user_id: string|null } ],
// -> { accounts: [{name, homeserver, token_present, live, user_id}],
// as_of_unix: int|null }
// POST /api/matrix-account-login (x-www-form-urlencoded, operator-auth)
// POST /api/matrix-account-login (x-www-form-urlencoded)
// fields: agent, account, homeserver, mode=password|token,
// user_id?, password?, token?
// -> 200 JSON { ok: true, user_id } on success
// -> error body in transition: today a bare plain-text body
// (hive-c0re's error_response), migrating to RFC 9457
// application/problem+json { type, title, detail, … }. The error
// path reads shape-agnostically (text first, then JSON `detail` if
// it parses) so both shapes work regardless of BE/FE merge order.
// The token is NEVER echoed back in any response, and this page never
// re-renders a submitted secret.
// -> 200 { ok: true, user_id }. Error body shape-agnostic: plain text
// today, migrating to RFC 9457 problem+json { detail, … }.
// Token is never echoed; page never re-renders a submitted secret.
//
// Live status dot (coordinated with the BE snapshot + heartbeat): the daemon
// force-rewrites its host-visible snapshot every ~30s (heartbeat, BE), so
// `as_of_unix` advances while the daemon is alive — a stalled `as_of` is now an
// honest "daemon stopped publishing" signal, not just "snapshot is old". We
// render —
// green (live + running + fresh) = online
// dim green (live but as_of stale > ~90s) = heartbeat stopped, likely dead
// amber (live + container DOWN) = stale (container down ⟹ down)
// amber (token_present + !live) = provisioned but offline
// grey (no token) = not provisioned
// The dim-green age case is meaningful only because of the heartbeat: 3 missed
// ~30s beats (>90s) without the container being explicitly down means the
// daemon is up-but-dead or wedged. The container cross-ref still takes
// precedence (a stopped container is definitively stale regardless of age).
// `as_of_unix` is tooltipped throughout so freshness is always legible. When
// `live` is absent (v1 backend) the dot falls back to token-present rendering.
// Live status dot — the daemon heartbeats every ~30s (advances as_of_unix),
// so a stalled as_of = daemon dead, not just stale snapshot:
// green live + running + fresh = online
// dim green live but as_of stale > ~90s = heartbeat stopped
// amber live + container DOWN = definitively stale
// amber token_present + !live = provisioned but offline
// grey no token = not provisioned
// Container state takes precedence; as_of_unix is tooltipped for freshness.
// v1 backend (no `live` field) falls back to token-present rendering.
import { $, el, esc, fmtAgeSecs, renderServerWarnings } from './common.js';

View file

@ -14,21 +14,12 @@
// it reads — to union live containers with agents already named in the
// assignments map.
//
// Editing model (the save-all issue): the operator toggles any number of
// checkboxes across BOTH matrices, then clicks the single "save all (N)"
// button. We diff each checkbox against the baseline captured at render
// time (stored on `data-baseline`) and POST only the perm-types that
// actually changed per agent to `POST /api/permissions`:
//
// { changes: [ { agent, tool_groups?, capabilities? } ] }
//
// Omitted field = leave that perm-type untouched (no commit, no diff);
// an included array fully replaces that perm-type for that agent. The
// backend coalesces caps+groups for one agent into ONE combined queue
// entry → one rebuild per agent (no more double-rebuilds). The batch is
// atomic: validated whole, applied whole, or rejected whole with
// `{error}` — so the saved→rebuilding transition only fires on a clean
// 200.
// Editing model: checkboxes are diffed against a `data-baseline` captured at
// render time; only changed perm-types per agent are POSTed to
// `POST /api/permissions` as `{ changes: [{agent, tool_groups?, capabilities?}] }`.
// Omitted field = leave untouched; included array = full replace. The backend
// coalesces caps+groups per agent into ONE queue entry (one rebuild, no
// double-rebuild). Batch is atomic — saved→rebuilding only fires on a clean 200.
import { $, el } from './common.js';
import { containersState } from './state.js';

View file

@ -9,32 +9,19 @@
// { kind: 'unsubscribe', url: '/api/dashboard/stream' }
//
// worker → tab
// { kind: 'open', url: '...' } relayed from EventSource.onopen,
// plus a synthetic open fired to
// a brand-new subscriber when the
// upstream is already OPEN — so
// the page's onStreamOpen still
// runs and triggers a snapshot
// re-sync after a reconnect gap.
// { kind: 'open', url: '...' } relayed from EventSource.onopen; also
// fired synthetically to new subscribers
// when upstream is already OPEN (so
// onStreamOpen runs + re-syncs state).
// { kind: 'message', url: '...', data: '<raw SSE data string>' }
// { kind: 'error', url: '...' } relayed from EventSource.onerror.
// { kind: 'ping' } heartbeat — fired every
// PING_INTERVAL_MS to every
// connected port. The client's
// watchdog uses these as
// proof-of-life; silence past
// ~3× the interval triggers a
// re-subscribe on a fresh port
// (recovers from Firefox killing
// the SharedWorker out from
// under us, which it does under
// memory pressure with no native
// signal to the client).
// { kind: 'ping' } heartbeat every PING_INTERVAL_MS.
// Silence past ~3× triggers a re-subscribe
// (guards Firefox silently GCing the
// SharedWorker under memory pressure).
//
// Subscriptions are tracked per (port, url): a single port can
// subscribe to multiple URLs (only one is in use today).
// Unsubscribing the last port for a URL closes the EventSource so we
// don't keep idle streams open.
// Per (port, url) subscriptions; unsubscribing the last port for a URL closes
// the EventSource. One URL in use today; design allows multiple.
const streams = new Map();
// All currently-connected ports. Used by the heartbeat tick to fan

View file

@ -3,54 +3,9 @@
// owns scroll behaviour, animation suppression on backfill, and the
// EventSource lifecycle.
//
// Usage:
//
// import { create, linkify } from '@hive/shared/terminal.js';
//
// create({
// logEl: document.getElementById('msgflow'),
// historyUrl: '/messages/history?limit=200', // optional
// streamUrl: '/messages/stream',
// renderers: {
// sent: (ev, api) => api.row('msgrow sent', ...),
// delivered: (ev, api) => api.row('msgrow delivered', ...),
// _default: (ev, api) => api.row('note', JSON.stringify(ev)),
// },
// onLiveEvent: (ev) => { /* live-only side effects (notif, state pokes) */ },
// onAnyEvent: (ev, { fromHistory }) => { /* runs for every event in
// both backfill replay and live — use for derived views that need
// the full picture (e.g. a per-recipient inbox built from broker
// events) */ },
// onBackfillDone: (count) => { /* one-shot after history replay */ },
// onStreamOpen: () => { /* fires on every EventSource (re)connect —
// use to re-sync snapshot-derived state after a reconnect gap */ },
// pillAnchor: document.getElementById('msgflow').parentElement,
// });
//
// Renderers receive (ev, api) where api exposes:
//
// api.row(cls, text, icon?) → appends a flat <div class="row cls">;
// optional `icon` lands in a
// fixed-width `.row-glyph` cell
// api.details(cls, summary, body, icon?) → appends <details class="row cls">
// with a <pre.tool-body>; `icon` (if
// given) shares the `.row-glyph` column
// api.detailsDiff(cls, summary, body, icon?) → ditto but body is
// line-coloured by leading "+ "/"- "
// api.placeholder(text) → replaces log content with a single
// muted "(placeholder)" row, cleared
// on the next real row
// api.fromHistory → true while backfill is replaying
//
// Each kind is dispatched to `renderers[ev.kind]`; unknown kinds fall
// through to `renderers._default` (which itself defaults to a JSON-dump
// note row). The convention is that the SSE/history endpoints emit
// objects with a `kind` field.
//
// Backfill is best-effort: if `historyUrl` is unset or the fetch fails,
// we skip straight to SSE. The optional `onBackfillDone(count)` hook
// fires after replay finishes (or after a failed/skipped fetch with
// count=0); pages use it to set state flags from the replayed history.
// create(opts) — full options list + renderer api (api.row, api.details,
// api.detailsDiff, api.placeholder, api.fromHistory) + behavioral notes:
// docs/web-ui/shape.md §Shared terminal pane.
const NEAR_BOTTOM_PX = 48;
// Scroll distance from the top of the log that triggers an automatic