#28 · Tag moments and durations and separate tagged entities into a track
- timeline-app
- pull request
Moments and durations can now carry user-defined tags. **Separate by tags** pulls everything carrying the chosen tags into a track above the rest of the timeline, using the same layout as comparisons. ### Tagging - **Editing:** a **Tags** field (comma-separated) in moment details and the duration dialog. - **Storage:** `metadata.tags`, normalized like timeline tags (trimmed, lowercased, deduplicated, at most 40). Tags are kept out of the raw metadata JSON view, like title and notes. - **Reserved key:** `tags` is reserved for plugins. - **Older documents** that used a `tags` key for something else still open unchanged; that value is just ignored for tagging. - **Indexing:** tags are stored in the search index and listed with counts by a new `tags` query. ### Separating - **Choosing tags:** **Separate by tags** lists the timeline's tags with counts, plus a free-text field for others. - **Layout:** entities carrying **any** chosen tag move into one track above, and **everything else** stays below. - **View:** keeps its position, and both tracks pan and zoom together. - **Read-only** until **Rejoin**, which returns to the whole timeline. Nothing is changed or saved. - **Cross-track durations:** a duration that follows a moment on the other side keeps that moment's time. - **Saved vs unsaved:** server and desktop timelines show the saved version while separated (unsaved edits are kept and return on rejoin). In-memory timelines include unsaved edits. ### Derived indexes, built on demand Each side of a separation is a full index of the filtered saved document. Summaries, duration bands, collapsed-duration clusters, event pages and duration pages all use the normal index code and rules. - **PostgreSQL:** - **Tables:** new `oc_views` with `oc_view_nodes` and `oc_view_duration_nodes`, mirroring the main index tables. - **Building:** the first query for a filter builds the view under an advisory lock; later queries reuse it. - **Lifetime:** views belong to one saved revision. A save deletes them, and each timeline keeps its eight most recent. - **Functions:** `oc_view_overview`, `oc_view_events` and `oc_view_duration_overview` are **generated by the migration from the main functions' own definitions**, with only table names substituted. The summary algorithms therefore have a single implementation. - **Desktop SQLite:** a derived, disk-backed snapshot built from the open file's baseline on first use, cached (two most recent) inside `Snapshot`. Tauri needs no changes. - **In-memory:** two filtered indexes built in the browser. - **API:** `overview`, `events` and `durations` queries accept `filter: {tags, mode: "any" | "none"}` (validated). ### Tests - **PostgreSQL oracle** (`test/views-postgres.mjs`, added to CI): 48 random views across six separations match an in-memory index of the filtered document exactly. It also checks event and duration pages, tag counts, view caching, and invalidation on save. - **Rust:** both sides partition the moments, cross-side durations keep their placement, tag counts are correct, and invalid filters are rejected. - **Unit:** normalization (including custom `tags` metadata left untouched), filtering with pinned anchors, counts, filter keys, and the reserved plugin key. - **Browser** (served and offline): editing tags, normalized storage, the tag list with counts, tracks above and below, read-only mode, and rejoin. - **Desktop mock:** separation issues both filtered native queries and never loads the whole file into the webview. - **Passing locally:** 173 unit tests; 19 Rust tests; the PostgreSQL suites (views, summaries, durations, search, core, versioning, browser forks, comparisons, Next.js); and the served browser suite in Chromium and Firefox, plus the offline suite in Chromium. Fixes #26
Sources
Open #28 · Tag moments and durations and separate tagged entities into a track in the timeline