Skip to content

swimline view#1949

Draft
pjurewicz wants to merge 2 commits into
masterfrom
hotwire-browser-compare-streams
Draft

swimline view#1949
pjurewicz wants to merge 2 commits into
masterfrom
hotwire-browser-compare-streams

Conversation

@pjurewicz

@pjurewicz pjurewicz commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a "compare streams" view to ruby_event_store-browser: from a stream's page you can add other streams and see their events merged into one newest-first timeline table, one column per stream, with infinite scroll. An event linked into several compared streams renders once, as a link in each matching column.
  • The data layer lives entirely in the browser gem and composes each page from existing single-stream reads — no new repository/core API. Each compared stream is read in as_at order and the per-stream pages are k-way merged by event time, so the merge inputs are sorted by the merge key and the result is correct also for linked events and custom timestamps.
  • The timeline can be sorted along either axis of the bi-temporal model: created-at (as_at, default) or valid-at (as_of), toggled in the header. The whole pipeline — per-stream reads, merge key, cursor comparisons, group draining and the displayed time column — follows the chosen axis.
  • Pagination uses a plain timestamp cursor. Pages never split a group of events sharing one timestamp: a page runs to the end of its last timestamp (fetching the rest of the group with between(t..t) when the per-stream page got cut exactly there), so the next page asks for strictly older events and nothing is dropped or repeated. Pages can therefore be a little longer than the nominal count — and a mass of identical timestamps (e.g. a backfill with second precision) renders as one long page.
  • Fixes the in-memory repository applying limit before the time filters, unlike the SQL adapters where WHERE precedes LIMITlimit + older_than is exactly the combination this pagination relies on. A shared lint example pins the ordering for all adapters.

Test plan

  • ruby_event_store suite (incl. the new limit-vs-time-filter lint example) — green
  • ruby_event_store-active_record, contrib/ruby_event_store-sequel, contrib/ruby_event_store-rom suites — green (no adapter changes; lint example passes everywhere)
  • ruby_event_store-browser suite incl. compare view, JSON endpoint, UI/Capybara — green
  • Mutation testing at 100% for the browser gem diff and the in-memory fix

🤖 Generated with Claude Code

From a stream's page other streams can be added for comparison and
their events show up merged into one newest-first timeline table, one
column per stream, with infinite scroll.

The data layer composes each page in the browser gem from existing
single-stream reads: one as_at read per compared stream, k-way merged
by event time. The shared cursor is a timestamp and pages never split a
group of events sharing one - a page runs to the end of its last
timestamp (so it can be a little longer than the nominal count), which
lets the next page ask for strictly older events with nothing dropped
or repeated. An event linked into several compared streams is one row
with a link in each matching column; lanes come from which streams'
pages contained the event, so no extra membership queries are needed.

Fix the in-memory repository applying limit before time filters, unlike
the SQL adapters where WHERE precedes LIMIT - the combination is what
this pagination relies on. Pin the ordering with a shared lint example.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pjurewicz
pjurewicz force-pushed the hotwire-browser-compare-streams branch from f793cdd to cb260d0 Compare July 17, 2026 09:08
@pjurewicz pjurewicz changed the title Compare streams in the browser + native multi-stream reads Compare streams in the browser Jul 17, 2026
@pjurewicz pjurewicz changed the title Compare streams in the browser swimline view Jul 17, 2026
The timeline can now be ordered by either axis of the bi-temporal
model: created-at (as_at, the default) or valid-at (as_of), toggled
with a link in the header and carried through the compare and
compare/more urls.

The reader keeps one axis end to end: per-stream reads, the merge key,
the cursor comparisons and the group-draining between(t..t) all follow
the chosen sort, and the time column displays that axis so it stays
monotonic. Unknown sort values fall back to the default.
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