Skip to content

feat(api): /api/journal + /api/journal/stream (#323)#330

Merged
thinmintdev merged 1 commit into
mainfrom
feat/api-journal-stream
May 25, 2026
Merged

feat(api): /api/journal + /api/journal/stream (#323)#330
thinmintdev merged 1 commit into
mainfrom
feat/api-journal-stream

Conversation

@thinmintdev
Copy link
Copy Markdown
Contributor

Summary

Phase 1 of the journal panel rework (epic #322 / issue #323). Adds a backend surface that flattens the in-process EventBus and a new LemondLogRing into one JournalEntry shape so the dashboard journal panel can render both sources through a single component. The lemond ring is fed by a lifespan-owned bridge task that wraps LemonadeClient.stream_logs() with exponential-backoff reconnect (1s → 30s), so a lemond restart never permanently silences the panel.

Endpoints

  • GET /api/journal — backfill with source (hal0 | lemond | merged | all), level (info | warn | error), q (case-insensitive substring on msg), since, limit ≤ 500. Returns { entries: JournalEntry[], next_since }.
  • GET /api/journal/stream — SSE: 50-entry replay then live multiplex across both subscriber queues, 15s keep-alive frames, clean disconnect on client close.

Both endpoints are open (no auth) per ADR-0012 — same first-run rationale as /api/events.

Files changed

  • src/hal0/journal/__init__.py — new LemondLogRing (deque(maxlen=500) + per-subscriber asyncio.Queue with drop-oldest overflow) + start_lemond_bridge background-task helper.
  • src/hal0/api/routes/journal.pyJournalEntry Pydantic model + the two endpoints + the source-specific mappers.
  • src/hal0/api/__init__.py — wires the router under /api/journal, constructs the ring on app.state.lemond_log_ring, spawns the bridge task in the lifespan, cancels it cleanly on shutdown.
  • tests/api/test_journal_routes.py — 10 tests: HTTP empty/shape/source/level/q/since cases + SSE handshake + replay + live-emit. The bridge is stubbed in tests so we never try to open a real WebSocket.

Test plan

  • pytest tests/api/test_journal_routes.py -x → 10 passed in 0.9s
  • pytest tests/api/ -x (full sweep) → 374 passed, 3 pre-existing skipped, no regressions
  • ruff check src/ tests/ → All checks passed
  • ruff format --check src/ tests/ → 298 files already formatted

Closes #323
Refs #322

Phase 1 of the journal panel rework (epic #322). Adds a unified backend
surface that flattens the in-process EventBus and a new lemond log ring
into one JournalEntry shape, so the dashboard journal panel can render
both sources through a single component without per-source branching.

New module hal0.journal.LemondLogRing mirrors EventBus's bounded ring +
per-subscriber fan-out for lemond log lines, fed by a lifespan-owned
background bridge that wraps LemonadeClient.stream_logs() with
exponential-backoff reconnect so a lemond restart doesn't permanently
silence the panel.

GET /api/journal: backfill with source / level / q / since / limit
filters; returns sorted merged page + next_since cursor.
GET /api/journal/stream: SSE with 50-entry replay then live multiplex
across both subscriber queues; 15s keep-alive frames + clean
disconnect on client close. Both endpoints are open (no auth) per
ADR-0012; same first-run rationale as /api/events.

Closes #323
Refs #322

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thinmintdev thinmintdev merged commit cb2aa99 into main May 25, 2026
4 checks passed
@thinmintdev thinmintdev deleted the feat/api-journal-stream branch May 25, 2026 22:14
thinmintdev added a commit that referenced this pull request May 25, 2026
Phase 3 of epic #322 — replace two silent fallbacks in the dashboard
footer with live backend wiring:

* Journal pane streams /api/journal/stream (PR #330) instead of
  rendering HAL0_DATA.journal as a fallback. Source chip rebuilds the
  SSE with ?source=hal0|lemond|merged (debounced ~200ms); search box
  filters the in-memory ring client-side. Empty ring renders
  "No events yet" instead of mock prose like "loaded model
  'qwen3.6-27b-mtp' via llamacpp:rocm".
* Footer update chip reads useUpdateState() directly. The hardcoded
  "hal0 v0.2.2 available" literal is gone; chip composes the version
  string from the live `available` field and self-hides when there is
  no update (or current === available). The updateAvailable prop
  thread from main.jsx is dropped — Phase 2's UpdateBanner owns its
  own dismiss state and the chip is allowed to keep nagging until a
  new release lands.

Other touched surfaces:
* useLogs.ts hook rewritten against /api/journal* with source/level/q
  filter params + SSE reconnect-on-change + exponential backoff on
  EventSource error. JournalEntry replaces the loose LogEntry shape.
* LogsView (extras.jsx) updated to consume the new envelope
  ({entries, next_since}) and pass server-side filter params.
* HAL0_DATA.journal block deleted; mock.ts buildLogs replaced with
  buildJournal that returns an empty envelope (no synthetic copy).
* mock.ts: window.__hal0UpdateStateOverride seam added so Playwright
  specs can drive forced-mock update state — same seam Phase 2's
  PR #329 adds (identical content; will trivially merge).
* Two new e2e specs (10 tests):
  - footer-journal-pane-v3.spec.ts — pane closed cold load, SSE
    connects on expand, hal0 chip narrows source + client-side
    residue filter, search filters case-insensitively, empty ring
    renders "No events yet", deleted journal prose absent from bundle.
  - footer-update-chip-v3.spec.ts — chip hidden when no update or
    current === available, chip renders live `${available}` string,
    chrome.jsx no longer hardcodes "v0.2.2".

Closes #325
Refs #322

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
thinmintdev added a commit that referenced this pull request May 28, 2026
…lease-manifest (#389)

Add docs/internal/v0.3-state.md as the canonical v0.3 ground-truth doc
(repo HEAD, five-stream status with PR/issue citations, terminology
canon, stale-memory list, open blockers, ADR index). Writers cite this
to stop triangulating PLAN.md + auto-memory + GH issues per word.

Add ADR-0015 (Draft, alpha.2 target) — hal0 is an MCP host platform.
Generalises ADR-0013's per-agent allow-list pattern to third-party MCP
*servers*: registry at /etc/hal0/mcp/servers/<name>.toml, systemd
template hal0-mcp@<name>.service, slot-style lifecycle states, curated
catalog at installer/manifests/mcp-catalog.toml. Closes the gap the
hal0_mcp_host_platform auto-memory calls out and the design behind
issue #224's placeholder install-from-URL UI.

Add ADR-0017 (Accepted) — bell+inbox approval UX for destructive MCP
calls. Documents the contract shipped via Epic #322 (PRs #321 #328
#329 #330 #332): every MCP tool is classified READ-ONLY or
DESTRUCTIVE via MCP annotations, unclassified defaults to DESTRUCTIVE,
no per-agent trust override, pending forever. Third-party MCPs per
ADR-0015 inherit the contract.

Refresh release-manifest.md against v0.2/v0.3 reality. The runtime is
no longer v0.1.x toolbox containers — it's the Lemonade embeddable
tarball + the FastFlowLM .deb. Add optional `lemonade` and `flm`
manifest blocks mirroring the installer's LEMONADE_* / FLM_* pins.
Mark `toolbox_images` as historical (retained for out-of-tree
consumers). Switch CF Pages references to Vercel per the actual
deploy path.

Light fix in api-errors.md: the 401 / auth.required example was
documented as enforced by ADR-0001's FastAPI auth layer, which
ADR-0012 removed entirely in v0.3.0-alpha.1. Reframe as a shape
contract for any future re-introduced auth and for the MCP identity
middleware, not a live request path.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
thinmintdev added a commit that referenced this pull request May 28, 2026
…rough + gut installer auth section (#390)

- docs/operate/lemonade.md (new, .md canonical): operator reference for
  the v0.2 Lemonade runtime — what it is, where state lives, the /v1/*
  proxy + dispatcher fallthrough (PRs #248/#277), slot ↔ Lemonade
  model mapping (PRs #281/#282), max_loaded_models = 8 LRU cap (PR
  #283), per-type LRU eviction per ADR-0008 (supersedes nuclear-evict
  ADR-0007), OFFLINE-on-eviction (PR #276), and the three known v0.3
  caveats (Vulkan KV gauge missing, whisper RUNPATH workaround, GPU
  cleanup unload hang).

- docs/dashboard/v3.md (new, .md canonical, new docs/dashboard/ dir):
  page-by-page tour of the v3 React dashboard shipped in
  v0.3.0-alpha.1 (PR #235). Covers the shell + Mock-badge convention,
  /dashboard (system overview after #356), /chat (real surface per
  #309/#314/#315/#351), /slots (sidebar mirror per #357 + #344 UX
  sweep), /models (#313/#319/#353), /mcp (#304/#300), /agents (Peers
  per #299), /memory (graph #297, throughput #308), Settings (no Auth
  tab post-ADR-0012), and the footer journal (Epic #322 — PRs
  #321/#328/#329/#330/#332). Mock-fallback issues linked via the
  dashboard-v3 label, not enumerated.

- installer/README.md: gut ~95 lines of stale auth prose (Caddy,
  Bearer-token mint/use/revoke, first-run OTP claim wizard,
  HAL0_AUTH_ENABLED/HAL0_AUTH_DISABLED, password recovery, basic_auth
  upgrade path, the TLS recipe). Replace with one paragraph pointing
  at docs/operate/auth.mdx for the reverse-proxy recipe and
  docs/agents/identity.md for the X-hal0-Agent identity model. Auth
  was removed in v0.3.0-alpha.1 per ADR-0012; the README hadn't
  caught up.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 1 / #322: backend — /api/journal + /api/journal/stream

1 participant