Wire the active branch's [origin_event_id, head_event_id] window into
every user-facing event/memory reader so switching branches actually
changes what dialogue and memories the user sees. Phase 4 T89/T94
shipped branches as metadata-only — this closes the loop.
Helper:
- chat/state/branches.py: add `active_branch_event_ids(conn)` returning
the active branch's id range, with two defensive fall-throughs to
`(0, BIG_INT)`: (a) no active branch row at all, and (b) the
bootstrap "main" sentinel (name="main", origin=0, head=0). Production
never bumps main's head_event_id today, so this preserves existing
reader behaviour for every test that doesn't explicitly switch.
Readers updated (all user-facing dialogue / retrieval surfaces):
- chat/services/turn_common.py::read_recent_dialogue — chat-history
prompt context + the chat-view template path (via web/turns.py +
web/chat.py).
- chat/services/scene_summarize.py::_read_recent_dialogue — scene-close
per-POV summary input.
- chat/state/memory.py::search_memories — FTS leg filters via
m.event_id (T109's column); legacy NULL event_id rows are *included*
unconditionally so the filter doesn't break pre-0014 retrieval. The
fused (FTS + RRF + vector) path also drops vector hits whose
event_id falls outside the branch window.
- chat/web/meanwhile.py::_read_recent_meanwhile_dialogue — meanwhile
prompt context.
Projector queries (chat/state/world.py et al.) and admin/management
surfaces (drawer hide-panel, cross-chat search, regenerate's row
lookups by id) are intentionally NOT branch-filtered: projection must
see the full log to build state correctly, and the admin surfaces
operate across branches by design.
Tests (10 new, 446 total):
- tests/test_branches_state.py: 3 tests for `active_branch_event_ids`
itself (bootstrap-main, no-active-branch, non-main literal range).
- tests/test_branching.py: 7 cross-feature tests covering the spec's
five required scenarios plus scene_summarize and meanwhile readers.