#25 · Summarize short durations with moments at every zoom level
- timeline-app
- pull request
Durations that become too short to see at the current zoom now collapse into summaries, just like moments. A collapsed duration also joins nearby moment summaries, and the work is done in the database. ### Semantics - **When a duration collapses:** a duration draws as a band only while its extent reaches the grouping distance, i.e. the same exact threshold that groups moments, `span × grouping px / width`. A shorter duration **collapses** and is summarized by its start. - **Step 1, clustering:** collapsed durations in the window are grouped among themselves with the anchored-span rule moments already use. - **Step 2, coalescing:** moment groups and duration clusters merge in time order while the merged group still spans less than the threshold. Moment groups are already maximal, so two moment groups never merge with each other. A cluster of short durations joins the moments it sits next to. - **Why not one stream of points:** I considered treating collapsed durations as extra points in a single anchored stream. That isn't decomposable over cached subtree summaries, because an extra point can shift group anchors and split an existing moment group. Exact results would mean visiting individual moments. The two-step rule stays exact and bounded, and needs only cached summaries. ### Results and UI - **New fields:** groups report moments (`count`, `distinct`) and collapsed durations (`durationCount`) separately. - **A single collapsed duration** (no moments) carries its duration and is drawn as a small capsule marker. It uses duration plugins (color, hover card), and clicking it opens the duration. - **Other summaries** read "3 moments · 2 durations". The group inspector lists the durations lying wholly inside the group, 25 at a time, through a new `durations` page query. - **Zooming in** turns a collapsed duration back into a band. ### Database-level computation - **Augmented tree:** the interval tree, ordered by start, now caches per subtree the largest start, the entry count, and the smallest and largest extents. A subtree whose durations all collapse and lie inside the window is consumed whole when its starts fit the open cluster. Subtrees with no collapsed duration are skipped. - **Where it runs:** PostgreSQL in the new `oc_duration_overview` (PL/pgSQL), desktop SQLite in the native query, and in-memory timelines in `TimelineIndex.frame`. Bands exclude collapsed durations in the database queries themselves. - **Unsaved edits:** server and desktop timelines overlay unsaved edits, so an edited duration moves between summaries or becomes a band before saving. - **Migration:** `npm run migrate` rebuilds duration indexes saved before the new columns. ### Tests - **PostgreSQL oracle** (`test/summaries-postgres.mjs`, added to CI): 150 random views must match the browser index exactly for groups, counts, clusters and bands. The views covered 312 mixed groups, 128 clusters, 215 lone collapsed durations and 1,751 bands. The test also checks that duration pages are complete and that migration rebuilds older indexes. - **Rust oracle:** desktop summaries match a brute-force reference over 48 random views. - **JS oracle:** the browser index matches the same brute-force reference over 160 random views. Further unit tests cover lone durations, cache coarsening with duration counts, and the sparse overlay moving an edited duration. - **Browser** (served and offline): capsule markers open durations, a mixed summary lists its duration, and zooming in restores the band. - **Passing locally:** all unit (171) and Rust (18) tests, the PostgreSQL suites (summaries, durations, search, core, versioning, browser forks, comparisons, Next.js), and the Chromium served, offline and desktop browser suites. Fixes #24
Sources
Open #25 · Summarize short durations with moments at every zoom level in the timeline