chat: add /app/chat messenger app (lode 3c)
New apps/chat/ Convey app that renders the chat stream as a
messenger-style transcript. Opts out of the universal bar via
app_bar: false (field added in 3b) so its own composer row fills
the space.
Routes (apps/chat/routes.py):
- GET /app/chat/ → redirects to today's day
- GET /app/chat/<YYYYMMDD> → renders day's transcript
- GET /app/chat/api/stats/<month> → month-picker counts
Partial apps/chat/_chat_event.html renders every chat-stream kind
server-side: owner_message + sol_message bubbles, notes exposed
via title, and talent_spawned/finished/errored as clickable cards
whose data-talent-use-id drives window.openTalentView from 3b.
Anchor ids use #event-<idx> (0-based line index in the day's
JSONL) so search results can jump deterministically without a new
endpoint — the existing /app/search/api/search?stream=chat already
returns (day, idx).
workspace.html handles: client-side time separators >20 min apart
via Intl.DateTimeFormat (user's local TZ), bubble author-side
decoration, today-only live chat subscription, today-only
composer row, past-day composer replaced by a "new messages go to
today" redirect link, and search input wired to /app/search.
Identity labels come from config via the same 3-line fallback used
by think/chat_formatter.py (identity.preferred → identity.name →
"Owner"; agent.name → "Sol") — no hardcoded names.
apps/chat/tests/test_routes.py covers: root redirect, empty-state,
rendered event kinds, anchor stability, invalid day → 404, composer
visibility today vs past.
CSS additions for bubbles, transcript, search, composer, and
talent cards live in convey/static/app.css to reuse the shared
theme variables.
make ci green (3749 tests). make test-app APP=chat green (8 tests).
make review fails on the pinchtab/browser harness in this
environment ("pinchtab failed to start" / screenshot 503s) —
pre-existing infra issue, not a regression from this change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>