◷ openchronology

Comparing timelines and live viewing

Select timeline cards in the dashboard and use Compare timelines. Selection persists across search pages in the current browser tab. Alternatively, Compare in an open editor presents a searchable list of timelines visible to you, including private timelines you own or have collaborator access to. Select two or more sources, up to your account's limit: by default four for guests and eight for signed-in users and administrators. Operators set these limits, from 2 to 8, with COMPARE_TIMELINES_GUEST, COMPARE_TIMELINES_USER and COMPARE_TIMELINES_ADMIN; a saved comparison holds at most as many sources as its owner may compare. The currently open working timeline can be included. JSON .ochx files can also be added, including in the standalone offline HTML build. Desktop can compare its current SQLite timeline with server timelines or JSON files.

The result is a read-only view: source titles, moments, metadata and plugin configurations cannot be edited, saved, or exported from this view. Exit comparison to return to the original timeline, including its unsaved edits and viewport. Alignment and display controls affect the comparison only.

Each track uses shared time = original time × scale + offset, with exact rational arithmetic. Scale must be positive to preserve chronology and accepts exact rationals or finite decimals. Offsets are time differences in the units of the comparison's time display: 2 years 3 days or -1 h 30 min with a Gregorian display, 2.5 million years with that unit; a plain number is in the display's unit, or in shared coordinates when it has none, never in screen pixels. The panel lists the units the display reads, and applying an offset reports it as the display prints it. Fit all includes transformed bounds. One horizontal viewport controls every track; vertical pan and Ctrl-scroll scaling remain available.

Stacked mode has separate axes and source labels. Combine into one timeline merges the visible summaries and groups nearby moments across sources; disabling it restores the tracks. Qualified moment IDs avoid collisions between sources. Moment details identify their source and original coordinate. Group inspection uses disposable 25-moment pages in either mode.

When source presentations differ, a dialog requires choosing a common printer/ parser before comparison starts. Source presets, including custom code, and standard presets are available. This converts their presentation, without changing source coordinates or guessing a correspondence between different unit or epoch conventions. Use scale/offset to define that correspondence explicitly. Time display settings may subsequently be adjusted on the comparison itself.

The plugin union preserves source selection order and deduplicates by plugin ID. A plugin enabled in any source is enabled in the view. If the same ID has different versions or code, the first source's definition is used and a visible notice lists the conflict. Distinct plugin IDs remain distinct. No plugin code runs on the server. Plugins continue to use the restricted interpreter and existing host components, with editing disabled in this view.

Each indexed source has its own bounded viewport cache (see architecture), without prefetching. Inverse affine transforms produce database query bounds and grouping thresholds in source coordinates. Changing alignment clears that source's cache. Viewport requests are debounced, serial, and discard stale results. Browser-created and imported JSON timelines remain in their existing RationalMap; comparison does not clone all their events or fetch complete server documents.

Live refresh

Web editors use one server-sent event stream for up to eight source IDs. Transactional PostgreSQL LISTEN/NOTIFY signals committed timeline and membership changes across application processes. Notifications carry IDs only. The stream checks current session and timeline access before sending revisions, and rechecks access on its 25-second heartbeat. The revision check reads only indexed ID and revision columns, not full-text data, settings or events. A connection's initial revision closes the race during subscription/reconnection.

Desktop and transports without the event stream poll the small /revision endpoint every five seconds. Hidden documents stop listening/polling; returning resumes refresh. A changed revision updates headers, invalidates only the affected viewport cache, and fetches bounded summaries at the current viewport. It does not jump the viewport back to Fit all. New and removed markers fade over 220 ms; reduced-motion preferences disable these fades. Retiring DOM nodes have a fixed budget and are removed after their animations.

Unsaved edits are never replaced by background updates. With edits pending on a server timeline, the editor moves to the latest revision underneath them, and saving merges them onto it (saving while others save). While a save is under way, or in a pull request's working copy, it shows Reload latest instead; reloading confirms replacement through the usual unsaved-work check. A working source with unsaved edits is likewise preserved during comparison while other tracks keep refreshing; exit, save or export that working timeline before reloading it. Comparison moment inspectors are cleared when refreshed source contents change, avoiding stale edits.

The standalone HTML build includes no live network module, event stream, or refresh timers. Existing CSP and no-network tests remain enforced.

After upgrading the server, run migrations to install the notification triggers. Compose's normal build/start command runs migrations automatically. Reverse proxies should support streaming responses; /api/live sets X-Accel-Buffering: no. If streaming is unavailable, metadata polling takes over.

Saved public comparisons

A platform timeline may contain comparison: {sources: [UUID, UUID], combined: false} in place of its own moments (events is empty). Its immutable checkpoint stores the definition, while oc_timelines.comparison supplies the current live view. Opening its normal /timelines/<id> route loads bounded windows from each source and subscribes to their revisions. Public comparisons require public ordinary sources; source ACLs are checked on access. Nested saved comparisons are rejected. Ad hoc selection expands saved comparisons and deduplicates sources (eight maximum). The dashboard labels these as comparisons and links to them alongside ordinary timelines. Source pages remain independently searchable, editable and forkable.

The seeded World War II comparison demonstrates this with the European and Pacific campaigns. Its source links and definition history appear above the viewer. View alignment is temporary; this version does not provide a UI to publish or edit comparison definitions. Exiting a named comparison returns to the dashboard. Exporting and forking operate on individual source timelines; a reference-only comparison cannot become an offline copy of its source data.

This page is docs/comparison.md in the source code, as this server runs it.