Skip to content

feat: add Diary — passive read-only view of heartbeat cycles#126

Merged
luokerenx4 merged 4 commits intomasterfrom
dev
Apr 16, 2026
Merged

feat: add Diary — passive read-only view of heartbeat cycles#126
luokerenx4 merged 4 commits intomasterfrom
dev

Conversation

@luokerenx4
Copy link
Copy Markdown
Contributor

Summary

Adds a dedicated Diary page that exposes Alice's heartbeat session as a passive, read-only feed — separating "what she's been thinking" from "what she has to tell you" (which stays in Chat).

  • New backend route /api/diary/history reading SessionStore('heartbeat') + joining heartbeat events from the event log
  • New /diary page in the UI, nav entry promoted to the top section alongside Chat / Portfolio / News
  • Card-based visual layout (no chat bubbles, no avatars) with sticky date dividers grouping Today / Yesterday / older days
  • Polling-only (no SSE) — heartbeat fires ~every 30min, a persistent subscription would be dead weight
  • Extracted MarkdownContent from ChatMessage for reuse across surfaces

Also bundled: earlier README hero redesign commit (627b984) that was sitting on dev.

Design notes

  • Chat stays the "notification" surface, Diary is the "status" surface. ConnectorCenter and WebConnector are untouched — CHAT_YES heartbeat replies continue to push to Chat as before.
  • Reuses the existing heartbeat session file; no new storage.
  • Heartbeat events (heartbeat.done/skip/error) are read from disk (eventLog.read()) rather than the in-memory ring buffer — the ring buffer gets saturated by high-frequency events (snapshot.skipped, account.health) and evicts the ~30min-spaced heartbeat entries.

Test plan

  • npx tsc --noEmit passes
  • pnpm test — all 1015 tests pass; 15 new tests in src/connectors/web/__tests__/diary.spec.ts cover event → cycle projection
  • Manual: trigger a heartbeat cycle via POST /api/heartbeat/trigger, verify it appears in /diary within the 60s poll window and that Chat behavior is unchanged
  • Manual: verify sticky date dividers render correctly across days and that slim-row cycles (duplicate / empty / outside-hours) render correctly without cards

🤖 Generated with Claude Code

Ame and others added 4 commits April 17, 2026 00:56
Restructure the top section into a proper centered hero: logo (140px)
above an h1 title, tagline split into bold one-liner + descriptive
subtitle, badges row, then preview image. Move feature bullets below
the hero so the visual identity reads cleanly before content.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…s feed

Heartbeat already writes every cycle (prompt, reasoning, tool calls, reply) to
an isolated SessionStore('heartbeat'), but that record was never surfaced to
the UI. The Chat window received only the subset of cycles that produced
CHAT_YES notifications, conflating "Alice has something to tell you"
(notification) with "what Alice has been thinking" (status).

This adds a dedicated /activity page — read-only, one-speaker, passive — that
renders the heartbeat session joined with heartbeat.{done,skip,error} event
outcomes. Chat behavior is untouched; notifications still flow there as before.

Backend uses a single polling endpoint (GET /api/activity/history?afterSeq=)
rather than SSE. Heartbeat fires ~every 30min, so a persistent subscription
would be engineering weight for no user-visible benefit. The UI polls on
mount, window focus, and a 60s interval gated on document visibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The page was poorly named — "activity" evokes a machine operation log, but
the intent is to expose Alice's heartbeat-cycle thoughts, which is closer to
a diary than a system feed. Promotes the nav entry out of "Agent" and up to
the top section alongside Chat / Portfolio / News, matching its importance.

Visual redesign drops Chat's two-speaker bubble metaphor. Each heartbeat
cycle renders as a self-contained card (timestamp · outcome chip · body)
with sticky date dividers grouping Today / Yesterday / older days. Cycles
with no interesting body (duplicate / empty / outside-hours) collapse to
slim divider rows so they don't crowd the real content. Extracted
MarkdownContent from ChatMessage for reuse without inheriting chat chrome.

Also fixes a data-fetch bug: the route was reading heartbeat events from
the in-memory ring buffer, which gets saturated by high-frequency events
(snapshot.skipped, account.health) and evicts the ~30min-spaced heartbeat
entries we care about. Switched to a disk read with in-memory type
filtering — cheaper than three separate recent() calls, and correct
regardless of buffer churn.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The UI's tsc -b (used by CI) runs with a stricter project configuration that
doesn't expose the global JSX namespace. Switching to ReactNode also reads
more naturally for return-type annotations in modern React.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@luokerenx4 luokerenx4 merged commit 832f06c into master Apr 16, 2026
2 checks passed
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.

1 participant