Commit graph

1,041 commits

Author SHA1 Message Date
iris
7bb55bca43 docs/agent-hierarchy.md: scrub self-ref cookies (#719 batch 5) 2026-05-31 15:49:37 +02:00
iris
bd6b48a883 docs/persistence.md: scrub self-ref cookies (#719 batch 4) 2026-05-31 15:49:32 +02:00
iris
680d13b9e9 docs/gotchas.md: scrub self-ref cookies (#719 batch 3) 2026-05-31 15:46:02 +02:00
atlas
8a50f36c0c docs/agent-hierarchy.md: extract harness systemd unit shape (#718 batch 4)
Move the harness systemd unit rationale (role-driven unit name,
manager-only forge defaults, PATH wrapper-dir trick, env vars,
standalone-eval fallbacks, RuntimeDirectory + User= reasoning)
from `nix/templates/harness-base.nix` to a new
`docs/agent-hierarchy.md::Harness systemd unit shape (per-role)`
section.

In-code comments trim to short purpose statements + pointers; the
PATH /bin auto-append behaviour is already documented in
docs/gotchas.md, so the harness file just cross-refs both.

`description = ''…''` blocks (operator-facing options docs)
preserved per iris #718.

`nix flake check` clean; `nix fmt` clean (after formatter pass).
2026-05-31 15:44:41 +02:00
damocles
35a7ff03b7 hive-c0re: bind-mount /run/hive-agent/<name>/ per sub-agent (#784 phase 2 step 2b)
builds on step 2a (#809). lifecycle::set_nspawn_flags now adds a
--bind={socket_dir}:{socket_dir} flag per sub-agent so the harness's
HIVE_WEB_SOCKET bind (PR #800) lives in a dir both the agent
container and the host can see.

design (matches #809's a1a601d explanation):
- bind the SUBDIR, not the socket file. file bind-mounts drop on
  unlink; the harness's bind_unix unlinks any stale socket before
  binding, so a file bind would land the new socket in the agent's
  private namespace, invisible to the gateway. dir bind keeps both
  sides on the same dir inode.
- per-agent dir (one /run/hive-agent/<name>/ per agent, not a shared
  /run/hive-agent/ mount). The agent's container only sees its own
  subdir — never siblings' (mara on #800).
- manager skipped — the manager's UI serves at / via the c0re
  dashboard upstream, not via /agent/<name>/, so it never needs the
  per-agent socket dir.

mkdir source defensively before bind: nspawn refuses to start when
the bind source is missing, and /run/hive-agent/ doesn't exist on
fresh hosts.

remaining work in this phase:
- step 3 (atlas): gateway proxy_pass http://unix:/run/hive-agent/<name>/web.sock:/
- per-agent: flip HIVE_WEB_SOCKET in agent.nix to opt in (separate PRs)
- step 4 (later): drop TCP fallback once everyone's flipped
2026-05-31 15:44:26 +02:00
iris
5ca96b8c85 docs: scrub self-ref cookies across 5 files (#719 batch 2)
All five files scrubbed to 0 issue-ref cookies. Most refs were
'(closes #NNN)' style attribution after closed work landed.

- docs/turn-loop.md: 6 → 0 (#598 unified binary, #419 auth-fail,
  #542 mtime resumption, #519 role markers, #442 spawn path,
  #474/#478 partial edit)
- docs/approvals.md: 5 → 0 (#317 flake validation, #250 withdraw,
  #441 rebuild-queue dispatch, #753 manager port hash, #425
  crash-watch grace)
- docs/terminal-rendering.md: 2 → 0 (#666 ask-operator inline)
- docs/conventions.md: 3 → 0 (#753 port hash, #692 parent
  sentinel, #486 reparenting)
- docs/security.md: 4 → 0 (#240 + #658 ×2 + #673/#678 attribution)

Combined with PR #810 (docs/web-ui.md 14→0), this PR closes the
small / medium files. Remaining: agent-hierarchy.md (13),
gotchas.md (13), persistence.md (13). gateway.md is atlas's lane.
2026-05-31 15:39:09 +02:00
atlas
a8d8159038 docs/matrix.md: extract hive-matrix prose (#718 batch 5)
New top-level matrix doc covering everything that doesn't fit
elsewhere:

- Container shape (nixos-container, shared host netns, name choice,
  state persistence) — sibling to gateway.md::hive-forge container shape.
- Identity vs API listener: serverName vs gatewayHost split with the
  #660 breaking change.
- Default-closed firewall + federation port 8448 caveat.
- Provisioning flow: registration token activation, bind-mount,
  LoadCredential, hive-c0re's per-agent register + access_token
  persistence. Captures #565 first-boot race + #644 / iris 8043
  ownership shape.
- Assertion rationale (serverName, gatewayHost == "").
- fluffychat-web build fixes (#685): Imaging.{js,wasm} emscripten
  derivation + dart compile worker fixup, build-CWD path lesson
  from #685 / #733.
- Sequencing history.

In-code # comments trim to short purpose statements + docs pointers.
description = '' blocks (operator-facing options docs) preserved per
iris #718. ~140 lines removed from hive-matrix.nix.

`nix flake check` clean; `nix fmt` clean.
2026-05-31 15:38:43 +02:00
iris
ac83404f1c docs/web-ui.md: scrub self-ref cookies (#719 batch 1)
14 issue-ref cookies removed from docs/web-ui.md. Most were
attribution refs to closed issues that landed the feature being
described — the prose around them already describes the current
behaviour, the cookie was just a 'this was issue NNN' breadcrumb.

Scrubbed:
- #753 (manager port hash 8100-8999)
- #233 (linkify XSS)
- #448 (SSE multiplexing SharedWorker)
- #515 (worker-death self-heal)
- #451 (side-panel drag-to-resize)
- #635 (matrix.gui.enable defaults)
- #609 + #15 (gateway re-root) → 'lives in docs/gateway.md'
- #66 (ctx badge thresholds)
- #541 (journald panel-body column-flex)
- #447 (rebuild-queue cancel)
- #559 (mark all read)
- #474 (schedules PATCH partial edit)
- #467 (schedules fire-now)
- #589 (hive-qualified label phase A)

docs/web-ui.md: 14 → 0 issue-ref cookies. Self-contained read.
2026-05-31 15:38:28 +02:00
damocles
91f5588134 hive-c0re: per-agent subdir socket layout (#809 design fix)
was: /run/hive-agent/<name>.sock (flat single-file bind-mount).
issue: file bind-mounts don't survive the harness's 'unlink stale
socket then bind(2) a new one' cycle. The unlink drops the bind
inside the container; the rebind happens in private container
namespace; host never sees the new inode → gateway can't connect.

now: /run/hive-agent/<name>/web.sock (per-agent SUBDIR + fixed
filename). Lifecycle bind-mounts the parent dir per agent (step 2b)
so both sides see the same dir inode; the socket appears on the
host the moment the harness binds it.

new helpers:
- AGENT_SOCKET_DIR const (parent, gateway binds this whole tree)
- SOCKET_FILENAME const ("web.sock")
- agent_dir_for(name) (per-agent subdir, lifecycle bind-mounts this)
- socket_path_for(name) (= agent_dir_for(name).join(SOCKET_FILENAME))

per-agent dir isolation also satisfies mara on #800 directly:
agent's container only sees its own subdir + socket, never siblings'.

8 tests now (added agent_dir_for_is_socket_parent invariant).
2026-05-31 15:38:04 +02:00
damocles
c7a8cec2b5 hive-c0re: agent_sockets.json writer (#784 phase 2, step 2a)
sibling to agent_ports.rs (#748 / #15). Writes
/var/lib/hyperhive/agent-sockets.json mapping agent name → unix
socket path under /run/hive-agent/<name>.sock — the source of truth
for the gateway's eventual unix-socket upstreams once it flips off
TCP per #784's transition plan.

shape mirrors agent_ports.rs:
- BTreeMap for deterministic JSON output
- atomic <path>.tmp + rename for partial-write safety
- idempotent (skip rewrite when content unchanged)
- MANAGER_NAME filter (gateway routes manager via dashboard upstream,
  not /agent/<n>/)

socket_path_for(name) is pure-deterministic so a name alone resolves
to the same path the c0re writes + the harness binds via
HIVE_WEB_SOCKET (post-PR #800).

hooked into meta::sync_agents alongside the existing
agent_ports::write call (same best-effort + non-fatal shape).

next: step 2b (lifecycle bind-mount /run/hive-agent/ into agent +
gateway containers, with per-agent isolation per mara's #800 nudge
'agents can only access their own sockets'). then atlas's step 3
(gateway proxy_pass http://unix:...). then step 4 drops TCP.
2026-05-31 15:38:04 +02:00
iris
dc8c71c687 dashboard: fix rebuild spinner shape (#804)
Mara reported the rebuild spinner rendered as a rotating L-corner
('_|' / '⌐') instead of a smooth orbiting arc. Two issues:

1. The previous version coloured TWO adjacent border sides amber
   (border-top + border-right). With the rest of the border
   transparent, this paints a sharp L-shape at the icon corner —
   which when rotated reads as a spinning border-corner, not a
   loading spinner.

2. @keyframes had only `to` defined. Safer to include explicit
   `from { transform: rotate(0deg) }` so any browser that doesn't
   default cleanly still picks up the rotation.

Fix is the classic CSS spinner shape: faint amber ring around the
full icon perimeter (`border: 2px solid rgba(250, 179, 135, 0.2)`)
with one brighter top arc (`border-top-color: var(--amber)`) that
rotates. Reads unambiguously as a loading indicator.

Closes #804.
2026-05-31 15:36:38 +02:00
iris
4cee50a3be agent/app.js: final cookie scrub — 0 cookies (#713 batch 3 final)
All 19 remaining cookies in app.js were attribution refs to
closed issues / PRs / past mara/argus reviews. Substance is
already in docs from earlier batches (PR #780). This batch
collapses the cookie attributions to brief inline notes pointing
at docs where appropriate.

- #360 side-panel duplication note → drop closed-issue framing
- #394 vibec0re header redesign (×5) → drop cookies, prose stays
- #589 hive-qualified label glyphic-title note → drop cookie
- #576/#542/#584 logout chain rationale → drop cookies, prose
  preserved (the WHY + .credentials.json + mcp-needs-auth-
  cache.json detail stays inline)
- #14 relative paths convention → docs/web-ui.md::Per-agent
  relative paths pointer
- #233 marked autolinks new-tab → drop cookie
- #559/#566 mark-all-read flyout (×3) → docs/web-ui.md::Per-agent
  page (Loose-ends flyout) pointer + drop attribution
- #666 ask→operator inline-answer slot (×2) → docs/web-ui.md::
  Per-agent page (Ask → operator inline-answer binding) pointer

app.js: 19 → 0 cookies (100% reduction). Combined with PR #780
+ batch 1 + 2, app.js is fully migrated for #713 scope.

Closes #713 — all agent UI files (app.js, agent.css, screen.html,
index.html, stats.js, stats.html) at 0 issue-ref cookies.
2026-05-31 15:36:23 +02:00
atlas
db2a48cde6 docs/gotchas.md: extract nix/{assets,docs,templates/weston-vnc} prose (#718 batch 3)
- assets.nix: cargo-cache-invalidation rationale → "Split asset
  derivations away from the rust workspace" section.
- templates/weston-vnc.nix: port allocation, weston bind-address
  quirk, PAM service name, Type=simple choice, idle-time=0 →
  "Weston VNC compositor (per-agent hyperhive.gui.enable)" section.
- docs/default.nix: rendering pipeline + subtree-pick + output-tree
  history → "Nix options reference" section.

In-code comments trimmed to short purpose statements + docs pointers.
description = '' blocks (operator-facing options docs) preserved per
iris #718.

`nix flake check` + `nix build .#docs` clean.
2026-05-31 15:24:39 +02:00
iris
f60a90d752 shared terminal + docs: migrate sticky-scroll + backfill prose (#714 batch 1)
Substantial prose migration from @hive/shared/terminal.js (the
shared HiveTerminal factory backing #msgflow + #live across both
dashboard and per-agent UIs) into a new docs subsection.

Added to docs/web-ui.md as a new ### Shared terminal pane
subsection under ## Shape (shared by both) — ~78 lines of new
substantive prose:

- **api shape**: row / details / detailsDiff factory contract
- **Sticky-bottom + snap animation**: stickToBottom semantics +
  140ms ease-out vs 500ms browser default + 24px short-circuit;
  per-frame target re-eval extends destination through
  mid-animation mutations
- **Mid-animation scroll-event guard**: smoothScrollingUntil
  timestamp swallows the rAF-driven scroll events so the eased
  positions don't flip stickToBottom false partway
- **Post-append MutationObserver**: catches renderer mutations
  after api.row returns (badges, multi-line bodies, tool panes)
  + why programmatic scrollTop writes don't feedback-loop
- **Backfill + SSE**: history/stream envelope shape (seq, events),
  kind-aware seq dedupe at the boundary, .no-anim during replay,
  optional streamFactory for SharedWorker integration
- **linkify**: text-node-only autolink, XSS-safe by construction,
  trailing-punctuation strip

Collapsed in terminal.js (cookies en passant):
- #400 (snap animation timing — closed) × 3 → docs pointers
- #393 (post-append MutationObserver — closed) × 1 → docs pointer
- #375 (pre-append nearBottom snapshot — closed) × 1 → docs pointer
- #448 (streamFactory SharedWorker hook — closed) × 1 → docs pointer
- #163 (seq dedupe + onStreamOpen resync — closed) × 2 → drop
  cookies; substance lives in docs
- #233 (linkify) × 1 → docs pointer + terminal.css cookie scrub

terminal.js: 8 → 0 #NNN cookies (100% reduction).
terminal.css: 1 → 0 issue-ref cookies (remaining 1 match is a
hex color literal).
Net effect: ~50 lines of substantive WHY-prose moved out of
shared frontend into docs/web-ui.md, where it documents the
factory's contract for both consumer pages.
2026-05-31 15:18:31 +02:00
iris
b15c534e67 tabs.js: final cookie scrub — 0 issue refs (#712 batch 14)
Final pass on tabs.js. All 6 remaining cookies were either
attribution refs to closed issues / past reviews or pure-history
mile-markers:

- #259 disabled meta-update state → drop attribution
- dashboard.rs#2170 backend handler line ref → drop the line
  number (handler still lives in dashboard.rs, line drifts)
- mara on #695 M0V3 picker any-selection → drop attribution
  (substance already captured in docs/web-ui.md::Selection bar)
- #541 journald scroll surface → drop the migration history
  framing, keep present-state behaviour
- argus on #471 textContent vs innerHTML review → drop the review
  attribution, keep the safer-pattern rationale inline
- #369 tab routing section header → drop the cookie, keep the
  section divider

tabs.js: 6 → 0 issue-ref cookies (100% reduction). Across
batches 1-14 (#712): 73 → 0 issue-ref cookies, with substantive
prose (Topology tree CSS geometry, Container row icon layout,
Pending-state derivation, Selection bar, R3BU1LD QU3U3 cancel +
step annotations, Approval card requested-at, SSE multiplexing
worker-death self-heal, FL0W page MESS4GE FL0W body layout)
migrated to docs/web-ui.md across the batches.

dashboard SPA files all at 0 cookies now (PR #795 covers the
other dashboard files; this PR covers the tabs.js residual).
2026-05-31 15:14:53 +02:00
damocles
0ef79b8032 harness: opt-in UnixListener bind via HIVE_WEB_SOCKET (#784 phase 1)
phase 1 of #784 (unix-domain agent web UI for #14 prerequisite).
backwards-compatible: when HIVE_WEB_SOCKET is set + non-empty, bind a
UnixListener at that path instead of the legacy TCP bind on HIVE_PORT.
empty env var treated as unset so a stray HIVE_WEB_SOCKET= doesn't
trap an un-bindable empty path.

bind_unix helper:
- mkdir -p the socket parent (covers first-boot fresh /run/hive-agent/
  bind-mount target)
- best-effort unlink of stale socket (clean exit removes it, crash
  leaves it; bind(2) refuses to overwrite)
- mode 0o660 so gateway peers in the same unix group can connect (the
  bind-mount source dir ACL is the real gate; perms are defence in
  depth)

axum 0.8's Listener trait covers tokio::net::UnixListener directly
(no extra feature/dep required).

next phases (separate PRs):
- step 2: c0re bind-mounts /run/hive-agent/ + writes agent-sockets.json
  alongside agent-ports.json
- step 3: gateway proxy_pass http://unix:… (atlas)
- step 4: drop TCP bind once gateway no longer needs it
2026-05-31 15:14:46 +02:00
damocles
224385af37 hive-forge: extract format_event helper; tests call it directly (argus 🟡 on #798)
was: `print_event` did the format + println inline; tests had a
parallel `captured` helper that re-implemented the dispatch and only
covered 4 of 14 event types. brittle — a new arm in `print_event`
silently went uncovered.

now: pure `format_event(ev: &Value) -> String` builds the line;
`print_event` is a thin wrapper that adds the trailing newline.
tests assert on `format_event` output directly so every new arm
gets test coverage by construction.

bonus: added 3 more test cases (assignees add/remove, pull_push
commit count + force-push, commit_ref sha truncation) since the
helper extraction made them cheap. 9 tests total, all green.
2026-05-31 15:14:08 +02:00
damocles
8f9866f5e1 hive-forge: timeline verb — issue/PR audit trail (closes #783)
last unstarted piece of the original #694 epic. agents kept falling
back to curl for 'who closed this?' / 'when was this labelled?'
archaeology because view + comments only surface the body + comments,
not the structured timeline events (label adds, assignments, closes,
reopens, pushes, commit refs, review submissions, milestone changes).

separate verb rather than view --timeline because:
- composes naturally with view <n> / comments <n>
- keeps existing verb output shapes stable (no script breakage)
- argus on #770 already noted view's output is busy

human-readable by default ('**actor @ ts**: <summary>'), --json for
raw piping. unknown event types fall through to a '[<type>]'
placeholder so a forge schema bump doesn't panic the verb.

--tail N is a follow-up: timeline endpoint doesn't expose a total-count
field so the count-then-page strategy from #770 doesn't apply
directly.

6 tests cover comment / label add/remove / close / unknown-type
placeholder / missing-user fallback.
2026-05-31 15:14:08 +02:00
damocles
7cc59c4338 hive-forge: update --tail field doc to match count-then-page impl (argus on #770) 2026-05-31 15:12:41 +02:00
damocles
f57cf916a1 hive-forge: count-then-page strategy for comments --tail (mara feedback #770)
read the issue's `comments` count first, compute which page contains the
tail, fetch only `ceil(n/50) + 1` pages. drops the TAIL_MAX_PAGES cap
entirely — it was paging from the WRONG end (first 1000 comments instead
of the last n) on long threads, defeating the whole purpose of --tail.
work is now bounded by n, not by thread length.
2026-05-31 15:12:41 +02:00
damocles
7d31b7def4 hive-forge: warn when --tail saturates the pagination cap (argus 🟡 on #770) 2026-05-31 15:12:41 +02:00
damocles
5f4494c239 hive-forge: comments --tail N flag (#694 part 3) — last N comments in chronological order 2026-05-31 15:12:41 +02:00
iris
f7e38c0b42 common.js + flow.js + index.html + flow.html: cookie scrub + doc pointers (#712 batch 13 cont.)
Final dashboard frontend cleanup. Four files at 0 cookies each.

common.js (11 → 0):
- #406 module-split history → drop, present-state intro
- #448 SharedWorker SSE pipe (×3) → docs/web-ui.md SSE multiplexing
  paragraph pointer
- #515 worker-death self-heal (×3) → docs pointer + brief inline
- #453 bfcache argus-nit (×2) → drop framing, keep inline why
- #451 drag-to-resize → docs/web-ui.md::Side panel pointer
- #233 marked autolinks new-tab → drop cookie

flow.js (8 → 0):
- #406 module-split history → drop
- #389 banner-in-footer → drop framing
- #375 stacking context → docs/web-ui.md::Per-agent page Terminal-wrap
- #408 server-side filter, #499 backend allow-list → drop cookies,
  keep inline why
- #448 SharedWorker per-URL keying → docs pointer
- #163 SSE catchup drift → drop cookie, keep inline why

index.html (13 → 0):
- #389 chrome history → drop
- #459 schedules, #607 matrix, #609 nginx-front, #15 → drop cookies,
  keep functional comments + docs/web-ui.md::Tab strip + gateway.md
  pointers for matrix
- #385 dropped C0NTAINERS heading → drop framing
- #460 reminders-moved-here → drop framing
- #444 SchedulesChanged SSE absence → drop cookie, keep inline why
- #443 selection bar → docs/web-ui.md::Selection bar pointer
- #564 fold-create-into-table → drop framing
- #369#issuecomment-3437 → drop attribution
- #406 step 3 bundle rename → drop history

flow.html (3 → 0):
- #389 slug + #362 pill pattern + #406 step 2 bundle → drop cookies,
  preserve functional descriptions

Total this batch (across all 4 files): **35 cookies scrubbed**.
Net effect: the dashboard SPA's HTML + JS comments all point at
docs/web-ui.md for substantive design context now, with inline
comments only retaining present-state operational descriptions.
2026-05-31 15:09:54 +02:00
iris
6c9b28903f dashboard.css + docs: migrate MESS4GE FL0W body layout + final cookie scrub (#712 batch 13)
Second dashboard.css batch — scrubs all remaining #NNN cookies
(post-batch-12 dashboard.css went from 51 → 36 raw matches, mostly
hex colors). This batch zeroes out the actual issue refs.

Moved to docs/web-ui.md::FL0W page:
- **MESS4GE FL0W (extended)**: msgrow flex-wrap row + body
  flex-basis: 100% + min-width: 0 rationale (~10 lines new prose).
  Without flex-basis: 100% the body sat inline eating whatever the
  chips left, starting ~30ch in on long timestamps + agent names
  and wrapping awkwardly. Pushing it to its own line lets the body
  use the full row width.

Collapsed in dashboard.css (en passant cookie scrubs across many
small comments):
- #485 msgrow body flex (→ FL0W docs pointer)
- #450 inbox max-height (→ inline rationale only, cookie dropped)
- #451 side-panel drag-to-resize × 2 (→ Side panel docs pointer)
- #369 dashboard chrome (→ Tab strip pointer)
- #416 full-width layout (→ inline rationale, cookie dropped)
- #389 slug banner footer (→ inline rationale, cookie dropped)
- #369#issuecomment-3437 + #362 + #383 + #383 + #362 flow.html
  section (→ FL0W page pointer + per-agent header pills pointer)
- #375 tail pill stacking context (→ Per-agent page Terminal-wrap
  pointer to existing docs)
- #66 ctx badge thresholds (→ Container row Line 1 pointer)
- #541 panel-body column flex × 2 (→ inline rationale, cookie dropped)
- #188 / #192 image preview (→ Side panel pointer)
- #275 bulk select × 2 (→ inline, cookies dropped)
- #259 background meta-update (→ inline, cookie dropped)
- #474 inline edit button + form (→ inline, cookies dropped)
- #466 interval composer (→ inline, cookie dropped)
- #535 schedules-as-table × 2 (→ SCH3DUL3S tab pointer + inline)
- #564 inline create row (→ inline, cookie dropped)
- #443 selection bar (→ Selection bar pointer)
- #486 M0V3 → <pick> (→ Selection bar pointer)
- #459 scheduled prompts tab section (→ SCH3DUL3S tab pointer)

dashboard.css: ~20 issue refs scrubbed in this batch + ~10 lines
substantive prose migrated to MESS4GE FL0W. Total dashboard.css
across batches 12 + 13: 51 raw matches → 0 issue-ref cookies
(only hex colors remain).
2026-05-31 15:09:54 +02:00
lexis
eccceeaadf docs(CLAUDE.md): add docs/gateway.md to file index + reading paths (follow-up to #775 #793) 2026-05-31 15:07:06 +02:00
atlas
24775845a3 nix/hive-gateway: static not-found + unreachable pages for /agent/<name>/ (#755)
mara on #755: "e.g. /agent/name should show an error page stating
that the agent could not be found if missing in json or that it is
not reachable if we get a connection error. we dont want a fully
generic fallback, only for routes already special cased in the
nginx config."

Adds two static HTML pages built at deploy time via
`pkgs.runCommand "hyperhive-agent-error-pages"`:

- **not-found.html** — served when `/agent/<unknown>/...` hits the
  bare `/agent/` catch-all. The catch-all `return 404`s, and
  `error_page 404 = /__hive_agent_not_found` rewrites to the static
  page.
- **unreachable.html** — served when `/agent/<known>/...` proxy_pass
  to the harness returns 502 / 503 / 504. `proxy_intercept_errors
  on` + `error_page 502 503 504 = /__hive_agent_unreachable` on each
  per-agent location block rewrites to the static page.

Mechanics:

- `agentErrorPagesDir` (in the `let` block) is a `runCommand` that
  emits two HTML files using a `<<EOF` heredoc — no template engine
  needed.
- Two `internal` nginx locations (`= /__hive_agent_not_found`,
  `= /__hive_agent_unreachable`) `alias` the exact files. `internal`
  keeps the URIs unreachable from direct operator request — only
  nginx's own error-handling can hit them.
- Per-agent location blocks pick up the `error_page` directive
  through the existing `lib.mapAttrs'` over `agentPortsTable`. No
  per-agent generated content; same static page for all.
- `/agent/` catch-all generates from a tiny optionalAttrs alongside
  the per-agent block — both are no-op when the agent table is
  empty (matches the pre-#15 shape).

Pages: minimal inline CSS, catppuccin palette matching the
dashboard (`#1e1e2e` bg, `#cdd6f4` text, `#cba6f7` not-found heading,
`#f9e2af` unreachable heading). No frontend-dist dependency — render
even when hive-c0re is down. Both link back to `/`.

Per mara's "only for routes already special cased" — scope stays
narrow. Forge / matrix / fluffychat keep nginx defaults; extending
the custom-error pattern to other vhosts is a separate follow-up
if/when needed.

Verified:
- nginx location attrset has `["/", "/agent/", "= /__hive_agent_not_found", "= /__hive_agent_unreachable"]`
- container toplevel builds clean (`nixos-system-hive-gateway-26.05pre-git`)
- `docs/gateway.md::Per-agent error pages` section captures the
  design + rationale + intentional narrowness

Closes #755.
2026-05-31 15:04:54 +02:00
atlas
ba5a6181fc docs/gateway.md: add host-side-services.forgejo conflict-avoidance to container-shape section (argus #793 🟡) 2026-05-31 15:04:02 +02:00
damocles
2ada0e22ca harness: collapse Surface::DEFAULT_LABEL → single 'hive' fallback (#788)
closes #788. drops the per-role HIVE_LABEL fallback const ('hive-ag3nt'
on AgentSurface, 'hm1nd' on ManagerSurface) and replaces both with a
single literal 'hive' at the env-var unwrap site. real deploys set
HIVE_LABEL unconditionally via the meta-flake envelope; the fallback
is unreachable in production and there's no semantic reason for it to
differ per role.

nix-side standalone-eval fallback (HIVE_LABEL = 'hm1nd' in the
manager systemd unit) stays — that's wired so 'nixos-rebuild against
nixosConfigurations.manager' produces a sensibly-labelled container
even without the meta-flake wrapper.

Surface trait now: FLAVOR + FORGE_IS_MANAGER + 7 async wire methods.
next steps per #778 roadmap: #691#786#789.
2026-05-31 15:02:28 +02:00
atlas
ea90814809 docs/gateway.md: extract hive-c0re + hive-forge prose blocks (#718 batch 2)
Continues #718 docs-extraction. Three more blocks moved to
`docs/gateway.md` (which already houses the gateway architecture
story from #775):

1. **Firewall posture (gateway on vs off)** — was a 22-line block
   above `networking.firewall = lib.mkIf ...` in hive-c0re.nix.
   Trimmed to 3-line ref. New `docs/gateway.md::Firewall posture
   (host-level)` section covers the gateway-on / gateway-off
   trade-off + why dashboard port stays loopback-only.

2. **`HIVE_FORGE_URL` loopback rationale** — was a 14-line block
   above the env-var assignment. Trimmed to 5-line ref. New
   `docs/gateway.md::HIVE_FORGE_URL: loopback for in-cluster,
   sub-domain for the operator` section covers the in-cluster vs
   external split + why agent containers can't use the sub-domain.

3. **hive-forge container shape** — was a 15-line top-of-`config`
   block in hive-forge.nix explaining the nixos-container + host
   netns choices. Trimmed to 4-line ref. New
   `docs/gateway.md::hive-forge container shape` section captures
   the same content with state-dir + wipe-via-destroy notes.

Net: hive-c0re.nix -29 lines, hive-forge.nix -11 lines, gateway.md
+44 lines. Same pattern as #782 (first pass) per iris's #10114
guidance — substantive WHY moves to docs as named sub-paragraphs,
in-code shrinks to `// see docs/<file>::<section>` refs.

Verified: `nix eval` on agent-base toplevel still resolves
cleanly; firewall posture unchanged (still 0 ports opened in the
gateway-on case + the same 8100..8999 range in the gateway-off
case).

Continues #718. Follow-up batches: remaining harness-base.nix
blocks, nix/docs/default.nix, nix/assets.nix, nix/templates/weston-vnc.nix.
2026-05-31 14:57:50 +02:00
damocles
df71d8deac harness: drop manager special case from plugins notify (#778, mara feedback)
mara on #778: 'remove the manager special case argus nitted about'.

`plugins::install_configured` no longer takes a `notify_recipient`
hardcoding "manager". Now returns a Vec<String> of failure messages;
serve_main<S> iterates them and routes each through S::send_to_parent
— the same <parent> sentinel failure-notify uses everywhere else
(#703). Manager plugin failures now reach operator via root → operator
fallback (improvement on the pre-PR silent-drop).

Also rename FORGE_MENTIONS_ONLY → FORGE_IS_MANAGER to fix the misnomer:
the boolean picks which wire enum (AgentRequest::Wake vs
ManagerRequest::Wake) the forge_notify poller uses, not anything about
mentions-only filtering (that's a separate nix-side option). Real fix
is to lift Surface into the lib crate and make forge_notify::run
generic; deferred to its own issue.

Net: -20 LOC.
2026-05-31 14:53:59 +02:00
damocles
c3b5f970c1 harness: collapse per-role helpers behind Surface trait (#692 v2)
mara on #778: "I would have expected the manager and agents to share
the exact same turn function, making one obsolete. I don't see that
in the code, why not?" — fair. went further.

introduces a Surface trait + AgentSurface / ManagerSurface zero-sized
impls wrapping the disjoint Request/Response enums + boot-time
constants (FLAVOR / DEFAULT_LABEL / PLUGINS_PARENT / FORGE_MENTIONS_ONLY).
the turn loop itself collapses to one generic implementation:

- serve_main<S> replaces agent_serve_main + manager_serve_main
- serve_loop<S> replaces agent_serve_loop + manager_serve_loop
- handle_turn<S> replaces handle_agent_turn + handle_manager_turn
- wake<S> replaces agent_wake + manager_wake

RecvOutcome enum decouples the per-role Response shape from the loop's
match arms so serve_loop never sees either enum.

main's dispatch picks the type parameter from HIVE_ROLE; everything
downstream is identical by construction.

net: -62 LOC vs main even with the new manager notify-on-failure +
continue-sentinel features kept.
2026-05-31 14:53:59 +02:00
damocles
9bcd6976fe harness: converge agent + manager turn paths (#692)
three shared helpers replace the duplicated pre-#598 patterns:

- `log_system_event` lifts the HelperEvent parse + bus emit out of
  handle_manager_turn so agents log QuestionAnswered/ContainerCrash/
  reparent notifications the same way (#692 part 1).
- `format_turn_failure` produces the failure-notification body using
  identity::qualified_label() instead of a label param threaded through
  three layers. drops `label` from handle_agent_turn, agent_serve_loop,
  agent_check_and_inject_continue.
- `consume_continue_sentinel` lifts the file-probe so both surfaces
  reuse it (#692 part 2 — sentinel now works for manager too).

agent_notify_manager_of_failure → agent_notify_parent_of_failure: routes
via the <parent> sentinel landed in #703 instead of the literal string
'manager'. mirrored on manager side; root-manager failures resolve to
operator via topology::resolve_recipient.

handle_*_turn signatures now identical modulo the wire-type prefix
(part 3 acceptance from the issue).
2026-05-31 14:53:59 +02:00
iris
9c72fd369a dashboard.css + docs: migrate topology indent + icon/pending prose (#712 batch 12)
dashboard.css carried many CSS WHY-blocks duplicating prose
already in docs/web-ui.md (after PRs #722/#724/#728/#776).
Substantive new migration: topology indent geometry. Remaining
cookies collapse to docs pointers.

Moved to docs/web-ui.md::Topology tree:
- Indent + lane geometry paragraph (~18 lines new prose):
  per-depth 1.8em ladder rationale + hardcoded six levels +
  CSS Values 5 attr() partial-support caveat (Chromium-only as
  of 2026) + lane center continuation bars + bottom: -0.4em
  prefix extension to merge sibling vertical lines + horizontal
  stub at icon midline.

Collapsed in dashboard.css:
- Topology indent block (#363/#361) → 3-line pointer
- Tree prefix lanes block (#388) → 3-line pointer
- Tree prefix depth-step ladder (~6 lines) → 4-line tighter
  paraphrase (lane-meets-icon rationale stays inline as it's
  immediate context for the values below)
- Live cards icon-left split (#177/#344) → 6-line tighter
  paraphrase + docs pointer
- Icon selection-toggle hover/focus (#443) → docs pointer
- Icon img absolute positioning (#177) → docs pointer
- Icon favicon fallback (#195/#202) → docs pointer
- Pending state queued vs running (#769) → docs pointer
- Icon-only nav strip (#262/#333) → 7-line tighter + docs pointer
- Approval requested-at chip (#272) → 2-line pointer
- In-flight step indicator (#437) → 4-line pointer
- M0V3 affordance (#486) → 3-line pointer
- Cancel-X queued (#575) → 5-line pointer

dashboard.css: 51 → 36 cookies (counting hex-color false positives;
~15 actual issue refs scrubbed). Net ~50 lines of CSS WHY-prose
collapsed to brief docs pointers; ~18 lines of new substantive
prose migrated to docs/web-ui.md::Topology tree.
2026-05-31 14:48:46 +02:00
iris
dfbaa2654b agent/index.html: collapse header comments to docs pointers (#713 batch 3 cont.)
3 #NNN cookies scrubbed from inline HTML comments. The header
two-row layout (#394) and the overflow-extra-click rationale
already live in docs/web-ui.md::Per-agent page (after PRs #780
and #781 batches 1-2). Each comment shrinks to a doc-pointer.

index.html: 3 → 0 #NNN cookies (100% reduction).
2026-05-31 14:47:32 +02:00
iris
815f6561b7 screen.html + docs: migrate canvas-sizing + RFB design prose (#713 batch 3)
Expanded the /screen endpoint description in docs/web-ui.md to
cover the substantive design rationale that lived in screen.html
comments. screen.html shrinks correspondingly.

Moved to docs/web-ui.md::Per-agent endpoints (GET /screen):
- Deliberate thinness — minimal RFB renderer; noVNC vendoring
  path called out for production-grade replacement.
- Fit-mode flex-item min-width:auto clamp: a flex item's automatic
  minimum size resolves to the canvas's intrinsic framebuffer
  resolution and clamps CSS max-* back up, making fit mode a
  silent no-op. The fix (flex: none + min-width: 0 + min-height: 0
  + explicit px sizing via relayoutCanvas()) is now documented.
- localStorage persistence for fit-mode (key screen-fit; default
  on).
- Pointer rescale in sendPointer keeps clicks accurate.
- ExtendedDesktopSize pseudo-encoding (-308 rect) gates the
  match-size button.

Collapsed in screen.html: 8 #NNN cookies scrubbed across all
inline comments. #133 (canvas-sizing fit bug — closed) ×5,
#52 (noVNC vendoring — closed) ×1, #14 (relative URL — closed
my piece) ×1, plus one CSS-block #133. Each comment shrinks to
a brief pointer.

screen.html: 8 → 0 #NNN cookies (100% reduction). Net ~26 lines
of substantive prose moved into docs/web-ui.md.
2026-05-31 14:47:32 +02:00
iris
b07d74b51b agent.css + docs: migrate icon sizing + popover :not([hidden]) prose (#713 batch 2)
agent.css carried 17 #NNN cookies, mostly attribution refs to the
#394 vibec0re overhaul (mara's full-screen redesign). Most were
not substantive WHY-prose — just "this exists because of #394"
breadcrumbs. The two genuine WHY-explanations move to docs.

Moved to docs/web-ui.md::Per-agent page:
- **Agent icon paragraph**: rewrote with explicit-em-sizing
  rationale (intrinsic <img> dimensions push parent flex container
  open via align-items: stretch height feedback) + the 5em ≈ 6em
  min-height - 0.5em padding × 2 derivation + align-self:
  flex-start sticks-to-top.
- **Overflow button paragraph**: added the :not([hidden]) display
  scoping rationale (UA stylesheet sets display:none on [hidden],
  but author display:flex would override — scope to :not([hidden])
  so the popover stays hidden until JS unhides).

Collapsed in agent.css: 17 cookies scrubbed across the file:
- #360 (full-screen vibec0re overhaul — closed) ×2: section
  header preface + side-panel section header
- #394 (vibec0re header redesign — closed) ×7: header height,
  main column, agent icon, meta-nav, overflow trigger, two
  orphaned-style tombstones
- #411 (popover scoping + icon align-start — closed) ×2: icon
  sticks-to-top + overflow popover scoping
- #568 (OAuth code mask + reveal — closed) ×1: show/hide toggle
- #666 (ask→operator inline-answer slot — closed) ×1: slot
  styling (substance partly in PR #780 docs section)
- #559 (mark all read header row — closed) ×1
- #376 (inbox row layout — closed) ×1: long-message wrap
- #375 (tail pill z-index — closed) ×1: collision fix

agent.css: 17 → 0 refs (100% reduction, fully migrated).
2026-05-31 14:47:32 +02:00
damocles
ea5f70629c harness-base: generic bash-env.sh + _bashEnvFragments accumulator (mara on #779)
mara: 'if we replace it with one thing, that should be named more
generic so we dont have to change it for future additions'.

extract the BASH_ENV plumbing into a shared shape:

- new internal option `hyperhive._bashEnvFragments` (types.lines)
  accumulates shell snippets across feature modules.
- file path is now `/etc/hyperhive/bash-env.sh` (was the
  cargo-specific bash-cargo-short.sh).
- the file + BASH_ENV + interactiveShellInit are gated on
  `_bashEnvFragments != """ so a fully feature-disabled agent has
  no overhead.

cargo function moves to a `lib.mkIf cargo.shortMessages` contribution
to `_bashEnvFragments` — same behaviour, no rename when the next
hook (nix-env helper, claude-cmd helpers, whatever) lands.
2026-05-31 14:46:29 +02:00
damocles
5f61528133 harness-base: cargo --message-format short by default (#777)
closes #777. saves tokens by collapsing per-crate progress lines into
warning/error summaries when claude (or the operator) runs cargo
inside an agent container.

implementation: /etc/hyperhive/bash-cargo-short.sh defines a 'cargo'
bash function that injects '--message-format short' on compile
subcommands (build/check/clippy/test/run/doc/bench/install/rustc/fix).
loaded via BASH_ENV in non-interactive shells (claude's Bash tool
runs 'bash -c') and via programs.bash.interactiveShellInit in
interactive shells (operator SSH inside the container).

handles the '+toolchain' selector (cargo +nightly build), skips
injection when the caller already passes --message-format (any
form), leaves third-party cargo-* subcommands alone.

new option: hyperhive.cargo.shortMessages (default true) — agents
that parse cargo json output should set false.
2026-05-31 14:46:29 +02:00
atlas
309879dba0 docs: extract 3 substantive harness-base.nix prose blocks (#718, first pass)
iris's #718 scope: move substantive design context from `#` comment
blocks in `nix/` to corresponding `docs/` files, leave short
references in code. iris handed it back to me on #10114 since
nix/ is my lane + #775 established the pattern.

First pass — three highest-density blocks in harness-base.nix:

1. **First-boot agent-user migration** (~70 lines → `~20 lines code +
   short ref` in the activation script). Substantive prose moves to
   new `docs/persistence.md::First-boot agent-user migration (post-#658)`
   section explaining the 4 steps the script performs + the eventual
   removability of the marker-guarded body.

2. **nix-daemon `sandbox-fallback = true`** (10-line block → 5-line
   ref). New `docs/gotchas.md::Containerized nix-daemon needs
   sandbox-fallback = true` section covers the user-namespaces
   rationale + nixpkgs-default override.

3. **Matrix daemon + token-arrival trigger** (~50 lines across two
   systemd units → ~10 lines code + short refs). New
   `docs/persistence.md::Matrix per-agent daemon + token-arrival
   trigger` covers the socket-path rationale, the runtime-dir
   ownership story, and the first-boot ordering pattern.

Net: harness-base.nix -84 lines, docs +74 lines. Substantive design
context moves to durable docs; in-code refs follow iris's pattern
from her #712 batches (`see docs/<file>::<section>`).

Follow-ups: hive-c0re.nix, hive-forge.nix, hive-matrix.nix (already
trimmed via #775 but a couple of remaining blocks could go), and
the smaller files in #718's scope table. Shipping this first to get
the pattern reviewed before larger batches.

Verified: `nix eval` on agent-base toplevel still resolves.
2026-05-31 14:44:12 +02:00
iris
3f84b6492a agent/app.js + docs: migrate ask→operator inline-answer binding prose (#713 batch 1 cont.)
Moves the #666 ask→operator inline-answer wiring rationale from
app.js into a new docs/web-ui.md::Per-agent page sub-paragraph
**Ask → operator inline-answer binding**. Substantial block —
the slot-registry / reconcileAskBinds / buildAnswerForm trio has
real design rationale (async question id, text-match pairing,
resolved-vs-cancelled-vs-expired neutrality, defensive prune,
no-regression-fallback to side-panel answer) that belongs in
docs, not three JSDoc blocks scattered through app.js.

Moved to docs (~30 lines of substantive prose):
- pendingAskBinds slot-registry rationale
- reconcileAskBinds matching algorithm (text-match, first-unbound
  to first-unclaimed pairing stability)
- defensive prune for disconnected slots
- [resolved] vs ✓ rationale (answered / cancelled / TTL-expired
  ambiguity — neutral label)
- fallback to side-panel answer when slot stays unbound

Collapsed in app.js: 3 JSDoc blocks + inline comments → ~10-line
total pointer. Drops #666 ×3, #559, #668 cookies en passant
(substance now lives in docs).

app.js: 24 → 19 refs in this commit; 27 → 19 across batch 1
(30% reduction so far). Net ~57 lines of substantive prose
migrated from app.js to docs/web-ui.md across both commits in
this PR.
2026-05-31 14:35:09 +02:00
iris
8366c3739b agent/app.js + docs: migrate stacking-context + OAuth + nav-link prose to docs (#713 batch 1)
First batch of #713 (agent per-container UI prose migration).
Moves 3 substantive WHY-explanations from app.js into
docs/web-ui.md::Per-agent page, replaces each in-code mention
with a brief pointer.

Moved to docs:
- **Terminal-wrap pill anchor** (~11 lines): why the `↓ N new`
  pill is anchored in `.agent-main` rather than the default
  `.terminal-wrap` parent — backdrop-filter creates a stacking
  context, anchoring inside it traps the pill's z-index below the
  composer.
- **OAuth code input** (~14 lines across two blocks): masked
  password + reveal toggle + `autocomplete="one-time-code"`
  rationale (shoulder-surfer / screenshot exposure + WHATWG
  semantic value + suppresses browser save-password prompt).
- **NavLink kind resolution + XSS-safe DOM-build** (~8 lines):
  Container → same-origin, Forge → `http://<host>:3000<url>`,
  External → already absolute; agent-declared strings never reach
  innerHTML.

Collapsed in app.js: each block becomes a 3-4 line pointer to the
matching docs section. Drops #262 / #568 / #592 cookies en passant
since their substance now lives in docs. The `#14` cookie on
`historyUrl` / `streamUrl` is also scrubbed — the path-relative
shape is the convention, not an open issue. `#375` (agent.css ID
ref) drops as part of the pill-anchor block collapse since it was
sole-purpose pointing inside that comment.

app.js: 27 → 24 refs. Net ~35 lines of substantive prose moved out
of app.js into docs/web-ui.md::Per-agent page, where it belongs.
2026-05-31 14:35:09 +02:00
atlas
8d25b087b6 nix/hive-matrix: trim option-description prose to docs/gateway.md refs (argus #775 🔴)
argus on #775 v3: "the `gatewayHost` option description's
server_name-vs-gatewayHost essay + federation SRV note are also
candidates for [docs/gateway.md] section."

Cuts the gatewayHost option's description from ~40 lines (with
inline duplication of the discovery flow, when-to-set-which, and
federation port caveat) down to ~8 lines pointing at
`docs/gateway.md`. The brief `server_name vs gatewayHost` clarifier
stays in code because it disambiguates two SIMILAR-LOOKING options
on the same module — operators reading option docs need the
distinction inline, not behind a doc link.

Also trimmed `matrix.gui.enable` + `matrix.gui.package` descriptions
to similar shapes — point at docs/gateway.md for the architecture,
keep the override-shape hints in code.

Push includes the rebase onto current main (#764 + 0af6ea1 + others
landed since #775 was opened; cherry-picked commits get skipped
cleanly).

Net: matrix.nix loses ~70 lines of inline prose. No behavioral
change (verified gatewayHost still resolves to `matrix.<hive>`).
2026-05-31 14:29:40 +02:00
atlas
922057e81b docs/gateway.md: extract big-picture gateway docs, trim in-code comments (mara #775:9988)
mara on PR #775: "this is too much docs in code - move bigger picture
stuff to md files and put refs in code"

New `docs/gateway.md` consolidates the gateway architecture story
that was spreading across long inline comments in `hive-gateway.nix`,
`hive-matrix.nix`, and `hive-forge.nix`:

- vhost map (which URL serves what, which upstream, which option)
- matrix discovery flow (.well-known → sub-domain delegation
  sequence)
- Accept-header SPA fallback pattern (#686 / #729 design history)
- local-dev `localHostsEntry` story
- sub-domain rationale (mara verdict tracking) + when sub-path is
  right (hyperhive-internal apps)
- per-vhost tuning knobs (forge LFS, matrix long-poll, agent SSE)
- sequencing history (which PR added which routing piece)

In-code comments in the two nix modules get trimmed to short refs
into the doc — keeps the *why* in the markdown while the *what*
stays alongside the code:

- hive-gateway.nix: top-of-file comment, `agentPortsTable`,
  `appendHttpConfig`, every location block + vhost
- hive-matrix.nix: `fluffychat-web-fixed`, `fluffychat-web-imaging`,
  the dart compile postInstall

README.md gets a new row in the docs table pointing at gateway.md.

Verified `nix eval` still resolves the same vhost + location layout
after the comment trim — no behavioral change, just less in-code
prose.
2026-05-31 14:29:40 +02:00
atlas
df549ed2a5 nix/hive-{matrix,gateway}: host fluffychat at matrix.<hive>/, 301 from <hive>/matrix/ (#772)
mara on #764:9897: "host the fluffy chat app at / as follow up?"

Moves fluffychat-web from the bare-domain sub-path
(`<hive>/matrix/`) to the matrix sub-domain root
(`matrix.<hive>/`). Follow-up to #764 (matrix vhost itself), per
mara's gateway-architecture verdict (sub-domain for external standard
apps, sub-path for hyperhive-internal). Stacked on
`atlas/747-matrix-behind-gateway` — depends on #764 landing first.

## Mechanics

**hive-matrix.nix:**
- Drop `flutterBuildFlags = [ "--base-href" "/matrix/" ]` from
  `fluffychat-web-fixed`. Upstream default `--base-href "/"` is correct
  at sub-domain root.
- Update option docs to reflect new mount point.

**hive-gateway.nix:**
- `$matrix_spa_target` map target flips from `/matrix/index.html` →
  `/index.html` (sub-domain root now).
- New `<hive>/matrix/*` location: `rewrite ^/matrix/(.*)$
  matrix.<hive>/$1 permanent;` — 301 redirect preserves bookmark +
  deep-link compatibility for `<hive>/matrix/#/rooms/...` URLs during
  the transition.
- `<hive>/matrix/config.json` location removed (moved to `/config.json`
  on the matrix vhost).
- Matrix vhost (#764) gains `/` location: serves fluffychat dist as
  static files with the Accept-header SPA fallback (`/_matrix/`
  proxying to tuwunel keeps working via nginx longer-prefix-wins
  precedence). When `gui.enable = false`, `/` returns 404 cleanly.
- Matrix vhost gains `= /config.json` for the FluffyChat boot-config
  pre-fill (#736).

## Verified

```
vhosts:        ["_", "forge.test.local", "matrix.test.local"]
bare locations: ["/", "/matrix/", "= /.well-known/matrix/client",
                 "= /.well-known/matrix/server"]
matrix vhost locations: ["/", "/_matrix/", "= /config.json"]
/matrix/ extraConfig: "rewrite ^/matrix/(.*)$ http://matrix.test.local/$1 permanent;"
matrix vhost / alias: /nix/store/...fluffychat-web-2.6.0/
```

Full container toplevel builds clean.

## Risk

Medium. Two breaking changes for operators:

1. **Bookmark migration**: `http://<hive>/matrix/#/rooms/...` 301s
   to `http://matrix.<hive>/#/rooms/...`. Browser bookmarks +
   shared links keep working via the redirect; can be cleaned up
   once it's been in the wild long enough.

2. **fluffychat-web dist hash changes**: dropping the
   `--base-href "/matrix/"` flag changes the derivation hash, so
   `gui.package` rebuilds even though the source is the same.
   Operators on substitute caches will fetch the new dist; building
   from source takes the same time as before.

The `.well-known/matrix/{client,server}` delegation (already
advertising `matrix.<hive>` per #764) means matrix clients
auto-discover the new location — no client config change needed.

## Sequencing

**Depends on #764** — needs the matrix vhost to host the new `/`
location. Merge after #764 lands + soaks. If #764 changes shape
during review I'll rebase + force-push.

Closes #772.
2026-05-31 14:29:40 +02:00
iris
ea1c6e4d05 tabs.js + docs: migrate icon + pending-state prose to docs (#712 batch 11)
Mara on #774: previous batches were cookie-stripping rather than
prose migration. This batch actually moves substantive comment
prose from tabs.js into docs/web-ui.md.

Moved to docs/web-ui.md:
- Container row → **Icon layout + load strategy** sub-paragraph
  (new): absolute-positioning rationale (so img load state can't
  reflow row), fire-and-forget load pattern (no pre-check
  reachability), favicon fallback chain, icon-unreachable class,
  the immediate-fallback-when-stopped optimisation.
- Container row → **Pending-state derivation** paragraph (new):
  three-source priority order (transient → in-flight queue → none),
  why ContainerStateChanged isn't enough, the opRunning flag's role
  in driving the pending-running class + spinner.

Collapsed in tabs.js:
- Icon block (~14 lines of WHY comments + pointer to docs) →
  4-line pointer + behavioural one-liner. Drops #177 / #195 / #202
  cookies en passant since their substance now lives in docs.
- Pending-state block (~22 lines split across two paragraphs) →
  4-line pointer. Drops #769 self-cookie (the queued vs running
  split lives in docs::Container row now).
- SharedWorker EventSource (~7 lines) → 5-line pointer. Drops
  #448 cookie (the SSE multiplexing paragraph in docs already
  has the design + Firefox throttling rationale; the in-code
  comment was duplicating).
- M4TR1X tab gating (~4 lines) → 2-line pointer. Drops #607 cookie
  in both tabs.js + docs/web-ui.md::Tab strip (the substance was
  already in docs, just had the cookie attached).

tabs.js: 11 → 6 refs (92% reduction from baseline 73). Net effect:
~47 lines of substantive prose moved out of tabs.js into
docs/web-ui.md, where it belongs.
2026-05-31 14:23:49 +02:00
iris
68c2b3284e tabs.js + docs: drop rebuild-queue cookies (#712 batch 10)
#437 (multi-step progress on rebuild_queue entries — closed) ×2:
in tabs.js current-step comment + in docs/web-ui.md prose
referencing the phase annotation.

#501 (PR — merged for #437) ×1: paired with #437 in the same
tabs.js current-step comment.

#575 (X button to cancel pending builds — closed) ×2: in tabs.js
cancel-form comment + in docs/web-ui.md cancel-button prose.

#436 (route approval execution through rebuild_queue — closed)
×1 in docs/web-ui.md: scrubbed the parenthetical "; #436" next
to the approval source-chip description (small freebie since I
was editing that paragraph anyway). The approval-as-source-chip
is the implementation of #436; the cookie was just history.

Pre-scanned docs for matching cookies; caught all 3 doc cookies
in the same PR.
2026-05-31 14:16:30 +02:00
iris
c527f739ec tabs.js: drop scheduled-prompts cookies (#712 batch 9)
#459 (dashboard tab + creation form for scheduled prompts —
closed) ×2 (section header + tab-activation re-fetch comment) +
#444 (make it possible to schedule prompts — closed) ×1 (Backend
endpoint enumeration preamble) + #535 (schedules-as-table —
closed) ×1 (renderSchedulesList layout rationale). The
section-header comment now reads as a pure mile-marker; the
backend-endpoints preamble enumerates what the API surface is
today; the table-layout comment describes the rendered shape.
No docs/web-ui.md matches for any of #444 / #459 / #535.
2026-05-31 14:12:44 +02:00
iris
272d8af620 dashboard: split queued vs running rebuild visual (#769)
Container row's pending highlight fired for both queued + running
rebuild-queue entries, so a long queue painted half the SW4RM tab
amber. Mara on #769: don't highlight while queued, show running as
spinner on agent icon.

- tabs.js: derive opRunning (transient OR op.state === 'running')
  separately from pending; add a pending-running row class.
  Operator-initiated transients still count as running so the
  rebuild-now-clicked → backend-picks-it-up window stays visually
  consistent.
- dashboard.css: move the amber border + tint from .pending to
  .pending-running (queued rows now keep their normal look, with
  the badge text still saying "rebuild queued"). Add a 1s linear
  rotating amber arc as .pending-running > .container-icon::after,
  with overflow:visible so the ring sits just outside the icon and
  composes with the mauve selected ring.
- docs/web-ui.md::Container row: describe the queued vs running
  split in prose. Drop the orphaned (#398) cookie while I'm at it
  — small #712 freebie.
2026-05-31 14:10:37 +02:00
atlas
b106bb8c87 nix/hive-matrix: argus 🟡 on #764 — empty-gatewayHost assertion + federation port doc
Folds both 🟡 notes from argus's #764 review:

1. **Empty-string assertion on `cfg.gatewayHost`**: same footgun as
   the forge.domain rejection from #754 — empty would render `.<hive>`
   shaped garbage in both nginx server_name (wildcard catch-all,
   surprising) and /etc/hosts (invalid entry). Fail loud at toplevel
   build with a message pointing at `null` as the right opt-out.

2. **Federation port-8448 caveat in `gatewayHost` docs**: when the
   gateway listens on 80, `.well-known/matrix/server` advertises
   `${gatewayHost}` with no port suffix → matrix federation spec
   falls back to port 8448 → no listener on 8448 → cross-hive
   federation requires either `_matrix._tcp.${gatewayHost}` SRV
   record OR `services.hyperhive.matrix.openFirewall = true`.
   Hyperhive is mostly closed/internal so this rarely bites, but
   the option docs now flag it for the federation-curious operator.

Verified: `gatewayHost = ""` triggers the new assertion at toplevel
build with the expected message; default still resolves to
`matrix.<hive>` cleanly.
2026-05-31 14:08:40 +02:00
atlas
01cc664d76 nix/hive-{gateway,matrix}: matrix vhost at matrix.<hive-domain> + .well-known delegation (#747)
mara on #747:9722: "this still seems to be an issue in current version"
(after #751 closed without merge). Mirroring the forge sub-domain
pattern just merged as #754 for matrix per mara's #749:9609 verdict
(sub-domain over sub-path for forge + matrix, "not user-visible for
matrix because the .well-known/matrix/{client,server} redirect routes
clients through automatically").

## Mechanics

**New `services.hyperhive.matrix.gatewayHost`** — nullable str, defaults
to `matrix.<services.hyperhive.domain>` when hive-domain set, else
null. Full hostname (`matrix.darkest.space`, `homeserver.internal.lan`)
for bespoke shapes per mara's #754:9684 "specify full domain in
options instead" pattern.

**Gateway:** new `server { server_name = matrixCfg.gatewayHost; }`
block proxying `/_matrix/...` → `http://127.0.0.1:<httpPort>/_matrix/...`
with matrix-spec CORS + tuned for long-poll `/sync` (1h timeout) +
typical media uploads (50M body cap). `/` returns 404 — nothing
else lives at the matrix vhost. Matches the forge vhost shape from #754.

**`.well-known/matrix/{client,server}`** (already served at bare hive-
domain since #660): now points at `matrixCfg.gatewayHost` (no port
suffix when gateway is on the canonical port 80) instead of the
direct `<hive-domain>:<httpPort>` shape. Falls back to direct shape
when `gatewayHost = null` (no hive-domain, or operator nulled it).

**`localHostsEntry` extension**: `/etc/hosts` (when set) now adds the
matrix sub-domain → 127.0.0.1 alongside hive-domain + forge.domain.
`lib.unique` collapses any duplicate (edge case if operator sets
gatewayHost equal to hive-domain).

## Verified via `nix eval`

```
vhosts:         ["_", "forge.test.local", "matrix.test.local"]
gatewayHost:    "matrix.test.local"
client wellknown: m.homeserver.base_url = "http://matrix.test.local"
server wellknown: m.server = "matrix.test.local"
/etc/hosts:     ["test.local", "forge.test.local", "matrix.test.local"]
```

## What this fixes for #747

mara's HAR showed `GET /.well-known/matrix/client` and
`GET /_matrix/client/versions` both failing on `pr1ma.darkest.space`:

1. **`.well-known/matrix/client`** was advertising
   `http://pr1ma.darkest.space:8008` — that URL only works if tuwunel's
   port 8008 is firewall-open to the operator's browser (it isn't by
   default — `services.hyperhive.matrix.openFirewall` defaults to false
   since #651). Now advertises `http://matrix.pr1ma.darkest.space/`
   which goes through the gateway on the (already-open) port 80.

2. **`/_matrix/client/versions`** was hitting the bare-domain `"_"`
   vhost, which has no `/_matrix/` location — fell through to `/` →
   c0re's dashboard upstream → 404. Now hits the new `matrix.<hive>`
   vhost which proxies the request to tuwunel cleanly.

server_name + serverName unaffected — matrix identifiers (`@alice:<hive>`)
still embed the bare hive-domain per #660; only the wire-level transport
URL moves to the sub-domain.

## Risk

Medium. Existing matrix tokens / sessions stay valid because:
- `serverName` (the identifier domain) doesn't change
- tuwunel's `/_matrix/` endpoints serve the same requests, just reached
  via the new sub-domain instead of the direct port

Operators with `services.hyperhive.matrix.openFirewall = true` and
external clients reaching `:8008` directly keep working too — the
sub-domain vhost is additive, doesn't take away the direct port.

## Sequencing

This is a parallel matrix-side mirror of #754 (forge). Both follow
the same mara-verdict pattern; once both have soaked, the gateway-
behind-everything story is done for v0.

Closes #747.
2026-05-31 14:08:40 +02:00