The browser console was the only place Atlas wrote to, and it dies with
the tab: a reader who saw "get_code_snippet returned no source" had
nothing to hand over, and an agent looking into it had nothing to read.
The page now keeps a second copy of everything it logs, on the server,
next to the daemon's own records.
Server (src/ui/http_server.c):
- POST /api/ui-log takes {page, session, entries[]} and appends one JSON
line per entry to <cache_dir>/logs/ui.log (received, page, session,
seq, ts, level, source, message, detail, stack, url, line, col).
Bounds: 64 KiB per post, 100 entries per post, 4096 characters per
text field cut on a UTF-8 boundary and marked, the level normalised to
a known word, every string escaped again on the way out. The file
rotates once to ui.log.1 at CBM_UI_LOG_ROTATE_BYTES (default 5 MiB).
Warnings and errors are mirrored into the ring GET /api/logs serves,
so both sides of one failure sit in one list; the chatter stays in
the file. The reply names the file and says whether it could be
written.
- GET /api/ui-log?lines=N tails the file (default 200, at most 1000,
from the last 256 KiB) and names the file and its rotated
predecessor; `partial` says when the lines do not start at the top.
- cbm_ui_log_file_path() is the one place the path is built.
Frontend (graph-ui):
- app/ui-log.ts: a bounded buffer (200 entries, oldest dropped first
and the loss named at the head of the next batch), batched posts
(25 entries or 1.5 s), backoff on a refused post, never writes to the
console itself. app/ui-log-transport.ts posts with fetch, and with a
beacon when the page is going away.
- app/ui-log-install.ts wraps the five console methods (they still
print; the copy is taken after), listens to window error and
unhandledrejection, and flushes on pagehide. Installed first in
main.tsx, so nothing can fail before the copy exists.
- provider/error-observer.ts is the seam for failures the panels catch
and show as a sentence: the /rpc transport and the /api client
announce every failed call there before throwing, with a level that
tells a tool the server does not offer (info) from a tool that said
no (warn) from a broken or unreachable server (error). The reader
announces a file the index does not know.
- The projects panel shows the tail under "This server" as sentences,
names the file to attach to a report, and reloads it with the rest.
- README (both) and INVENTAR.md name the file and the routes.
Tests: three httpd cases (JSON lines and tail, refused bodies and
dropped entries, rotation); vitest for the buffer, the wiring in jsdom,
the transport, the seam through both clients, the API method, the model
and the panel. Verified live against a real project: the reader's
failure appears in the file and in the panel within two seconds.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>