refactor(fe): trim >30-line comment blocks (second pass, #2077)
This commit is contained in:
parent
ccc69bbc7b
commit
95b866a53d
5 changed files with 55 additions and 118 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue