#418 · Separated tracks draw relationship arcs, within and between tracks (#415)
- timeline-app
- pull request
Closes #415 A timeline separated by tags, or by **Separate related** / **Separate all connected**, now keeps its relationship arcs. Before, separated tracks drew none. ## What it looks like - **A link within one track** arcs above that track's axis, as on the whole timeline. - **A link between the tracks** crosses the gap as an S curve from one event's marker to the other's. It leaves and arrives vertically, so it can't be mistaken for an in-track arc, and it never runs along an axis. When the ends are far apart in time, the part in view crosses almost level, halfway between the tracks, and runs off toward both ends. Like `arcPath`, only the part over the stage is drawn (`crossArcPath`), so an end far off screen never puts a coordinate millions of pixels away. - **Arrowheads** from the Relationship arrows plugin still apply, on both kinds of arc (`crossArrowheadPath` puts the tip on the curve 12 px from the end, pointing into it). - **Hover text, limits and notice:** hover text works as before. The 256-arcs-a-window limit and its notice still apply, and so does hiding arcs shorter than the grouping distance. Screenshots from the live test (light and dark) are in the session's scratchpad; see the report. ## How ends are placed on tracks Arcs come from the **whole timeline's** arc index, never from the tracks' filtered views. The upper track's overview answers carry every arc touching the window. Each end is marked with whether it is on that track (`aIn`, `bIn`); the lower track is everything else, so an end that isn't on the upper track is on the lower one. The lower track's overview queries send `arcs: false` and get none, so the work is done once. The arc index doesn't know tags or relationship reach, so each backend answers "is this end on the upper track?" for at most 512 ends a window (256 arcs) without reading the whole timeline. The upper view already holds exactly that side's entities, so the cheapest correct test is to look each end up there: | Backend | Arcs from | Which track an end is on | |---|---|---| | Server (PostgreSQL) | The timeline's own `oc_edge_nodes` walk (the `walk` helper now takes `own`, so a view's query still reads a fork's arc tree through its lineage) | Primary-key lookups in the view's `oc_view_moments`, and in `oc_view_duration_nodes` by duration ID (new index `oc_view_duration_nodes_id` on `(timeline_id, band->>'id')`); two `= ANY($2)` queries | | Desktop `.och` | The open file's own `edge_window` | One SQLite query of primary-key lookups (`json_each` over the ends) in the view's derived copy (`events`, `durations`) | | In-memory | `separationArcs`: one arc tree of the whole document, built when separating | The separation's own membership test (`filterMembership`: the tags, or the related set found by BFS once) | This works the same for tag and relationship separations, including "all connected", whose membership would otherwise need a graph walk. ## Changes - `src/relationships.ts`: `crossArcPath`, `crossArrowheadPath`; `EdgeBand.aIn` and `EdgeBand.bIn`. - `src/tags.ts`: `separationArcs` (in-memory). - `src/comparison.ts`: arcs pass through `mapFrame` and `join` with their track's key. A local source can supply its own arcs (`ComparisonSource.arcs`). - `src/app.ts`: separation sources ask for arcs only on the upper track. `renderEdgeArcs` places each end on its track's axis and draws within-track or cross-track curves (`.edge-arc-between`). Arrowheads and hover names read entity metadata and titles from the tracks' frames (track keys stripped). Other comparisons still draw no arcs. - `server/store.mjs`, `server/http.mjs`, `server/schema.sql`: filtered overviews return marked arcs, and `arcs: false` turns them off. `arcs` is validated as a boolean. - `native-store/src/workspace.rs`: the same for desktop (`Query.arcs`, `Snapshot::arcs`, `mark_ends`). - OpenAPI (`arcs`, `aIn`, `bIn`). Docs: `relationships.md` (new "Arcs between tracks" section with the per-backend table), `tags.md`, `guide.md`,…