Order by (ts, chunk start, chunk first event id, position) so the window does
not depend on its anchor. Each read asks for the held chunks at the edge in
addition, so a run of chunks at one start ms cannot fill a read with held ones;
the no-gain early stop is gone. Search and window print a body's line breaks as
\n. Tests pin ties, caps, dedupe, the unreached flag and non-chat points.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
sources::window resolves an event id to its chunk, then walks chunks of the
same room both ways (5 per read, at most 6 reads a side), dedupes by event id
and cuts to before/after with more_before/more_after. An id the index lacks is
a typed SelectionInvalid on event_id naming the three reasons; an id in two
chunks is an index fault. No embedder, no Synapse. ChatFilter gains event_id
and chunk-start bounds, chat_scroll gains an order, the wire gains ChatWindow
request/response, the CLI gains `huginn chat window`.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>