ADR 0025 - The hub UI is the observability console; ide/ is an unshipped prototype¶
- Status: Accepted
- Date: 2026-08-20
- Decision owner: MAC fleet owner
- Supersedes: the "canonical browser UI" decision in ADR 0010 (Fleet IDE cut-over and parity matrix)
- Related: ADR 0018 (the task view is a graph under progressive disclosure), ADR 0024 (the dashboard streams the bus, not just its counts)
Context¶
The tree carried two browser UIs and pointed operators at the wrong one.
| source | build output | who serves it | |
|---|---|---|---|
| Observability console | observe/ |
src/mac/ui/console/ (committed) |
the hub, at /ui |
| Fleet IDE | ide/ |
ide/dist |
nothing |
Measured against a live hub on 2026-08-20: GET /ui returns
<title>MAC - fleet observability</title> — the console. api.py mounts
Path(__file__).with_name("ui") at /ui/assets and every /ui route returns
ui_dir/"console"/index.html. No route, mount, deploy step, or packaging step
has ever placed ide/dist on a hub.
Meanwhile the Makefile said the opposite. run-gui: ide-run ## Run the
canonical Fleet IDE development server., make help advertised "Run the Fleet
IDE using the active mac login profile", and install-gui / build-gui /
package-gui all built ide/. So an operator who ran make run-gui to "look
at the hub UI" got a different application, wired to a hub only through a
local Vite proxy, and reported it as broken. That report was correct: it was
not the thing the hub runs.
Two UIs is survivable. Labelling the undeployed one canonical while shipping the other is not — it makes the build system's own documentation the least reliable description of the product.
ADR 0010 declared ide/ canonical in June 2026, when the comparison was
against the legacy src/mac/ui/ dashboard. That comparison is stale: the
legacy dashboard was retired from /ui and the console took the front door.
The parity matrix in ADR 0010 measures the IDE against a surface that no
longer holds the position it was competing for.
Decision¶
The observability console is the hub UI. observe/ is its source,
src/mac/ui/console/ is its committed bundle, the hub serves it at /ui, and
every generic GUI target builds exactly that:
make run-guiruns the console's own dev server against a hub.make install-gui/make build-guibuildobserve/intosrc/mac/ui/console/.make package-guipackages that bundle (dist/mac-hub-ui.tar.gz).
ide/ is an unshipped local prototype, and is named that everywhere. It
keeps its own ide-* targets so it stays runnable and type-checked, it is no
longer reachable through any target whose name implies "the GUI", and no
install/build/package path produces it. ADR 0010's canonical claim is
superseded; its parity matrix stays as the historical record of what the
prototype set out to do.
A test holds the two halves together.
tests/ui/test_hub_ui_is_one_tree.py walks the actual chain — observe/'s
vite.config.ts outDir → the directory api.py serves at /ui → what
make -n run-gui executes — and fails if any link stops naming the same tree,
or if run-gui reaches into ide/ again.
Alternatives considered¶
Make the hub serve the Fleet IDE. This is the other coherent answer, and it
is a much larger change: the IDE mutates (/dispatch/tick, /agents/bulk,
/roles/seed, /notifier/deliver, /secrets), so serving it from /ui also
re-opens the read-only guarantee that observe/tests/readonly.test.ts asserts
and that the console was created to provide. It would need an auth story, a
build-and-commit story for a 14-file bundle, and a decision about what /ui
means. Not a rename — a project.
Delete ide/. Rejected here, deliberately. ide/ is the only consumer of
src/mac/ide_launcher.py, the /dashboard/state non-ide contract, the
Electron wrapper in desktop/, and tests/ui/test_fleet_ide_api_contracts.py.
Deleting it deletes the last caller of several hub routes, which is an
irreversible decision about the hub API and not this one. Naming it honestly
removes the operator-facing harm today and leaves that call to be made on its
own evidence.
Consequences¶
make run-guishows the operator the application the hub is actually running. The bug report "the GUI is broken" stops being generated by the build system.make buildandmake installno longer buildide/. Anyone who wants the prototype asks for it by name:make ide-install,make ide-run,make ide-build,make ide-package.dist/mac-ide-web.tar.gzis now produced only bymake ide-package;make package-guiwritesdist/mac-hub-ui.tar.gz.- The deploy banner sends operators to
<hub>/uiover the tunnel instead of telling them to launch the prototype. - If someone repoints
run-guiatide/, or moves the console's build output,tests/ui/test_hub_ui_is_one_tree.pyfails. The two cannot drift apart silently again.