Skip to content

feat: range timeline#501

Open
FelipeDefensor wants to merge 8 commits into
range-stack-basefrom
range-timeline
Open

feat: range timeline#501
FelipeDefensor wants to merge 8 commits into
range-stack-basefrom
range-timeline

Conversation

@FelipeDefensor

@FelipeDefensor FelipeDefensor commented May 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds the range timeline — a new timeline kind for annotating arbitrary time ranges, organised into rows. Ranges can be joined into chains, merged, split, and extended with hierarchy-style pre-start / post-end whiskers.

What's in scope

UI

  • Element / timeline / row context menus
  • Ribbon-style toolbar grouped into Ranges / Rows / Display sections, with split-mode dropdown
  • Inspector with label, start, end, length, pre-start/post-end, comments
  • Comments indicator on the body (visible when comments are non-empty and the body is wide enough)
  • Vertical arrow navigation; horizontal + vertical body drag (snaps to row)
  • Copy/paste, full undo/redo

CSV import (by-time + by-measure)

  • Required: start, end, row. Optional: label, color, comments, joined_with_next, start_fraction, end_fraction
  • Rows auto-created by name; joins validated after parsing
  • Replaces existing rows; restores >=1-row invariant on empty import

CLI

  • timelines add range [--row-height N]
  • timelines range row {add, set-height, list, rename, set-color, reset-color, reorder, remove}
  • timelines import range {by-time, by-measure}

File format

  • RangeTimeline.SERIALIZABLE: rows, default_row_height
  • Range adds row_id, label, color, comments, joined_right, pre_start, post_end, start_fraction, end_fraction
  • Per-row Row.height (optional override)
  • Label alignment is session-only (settings, not per-timeline)

Stack

This branch sits on top of the cleanup stack (#497, #498, #499, #500) plus the media-commands refactor (#387). When those land on dev, the merge commits drop on rebase via patch-id matching and the squash sits cleanly on top. No further history rewriting required here.

Tests

  • 407 range-specific tests across tests/timelines/range/, tests/ui/timelines/range/, tests/parsers/csv/test_range_from_csv.py, tests/ui/cli/timelines/test_cli_timelines_range.py and test_cli_timelines_import.py
  • Full suite: 1612 passed, 0 failed

@FelipeDefensor FelipeDefensor added this to the 0.7.0 milestone May 4, 2026
@FelipeDefensor FelipeDefensor changed the base branch from dev to range-stack-base May 5, 2026 09:33
Comment thread examples/range/by_measure_basic.csv Outdated
Comment thread tests/parsers/csv/test_range_from_csv.py Outdated
Comment thread tests/parsers/csv/test_range_from_csv.py Outdated
Comment thread tests/parsers/csv/test_range_from_csv.py Outdated
Comment thread tests/parsers/csv/test_range_from_csv.py Outdated
Comment thread tests/parsers/csv/test_range_from_csv.py Outdated
Comment thread tests/timelines/range/test_range_timeline.py
Comment thread tests/timelines/range/test_range_timeline.py
Comment thread tests/timelines/range/test_range_timeline.py Outdated
Comment thread tests/ui/cli/components/test_cli_components_add.py Outdated
Comment thread tests/ui/timelines/harmony/test_harmony_ui.py
Comment thread tests/ui/timelines/hierarchy/test_hierarchy_timeline_ui.py Outdated
Comment thread tilia/parsers/csv/range.py
Comment thread tests/ui/cli/timelines/test_cli_timelines_import.py Outdated
Comment thread tests/ui/cli/timelines/test_cli_timelines_import.py Outdated
Comment thread tilia/timelines/range/components.py
Comment thread tilia/timelines/range/timeline.py Outdated
Comment thread tilia/timelines/range/timeline.py
Comment thread tilia/timelines/range/timeline.py Outdated
Comment thread tilia/timelines/range/timeline.py Outdated
Comment thread tilia/timelines/range/timeline.py Outdated
Comment thread tilia/ui/cli/timelines/add.py
Comment thread tilia/ui/cli/timelines/imp.py
title: str,
prompt: str,
kind: type(Timeline) | list[type(Timeline)] | None = None,
kind: type[Timeline] | list[type[Timeline]] | None = None,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These bracket changes should be a separate commit.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude: Acknowledged — the type(Foo)type[Foo] modernization is in the original feat: range timeline commit. I'll split it out at final-squash time.

Comment thread tilia/ui/timelines/range/timeline.py
Comment thread tilia/ui/timelines/range/timeline.py
Comment thread tilia/ui/timelines/range/timeline.py Outdated
Comment thread tilia/ui/timelines/range/timeline.py Outdated
Comment thread tilia/ui/timelines/range/timeline.py Outdated
Comment thread tilia/ui/timelines/range/timeline.py Outdated
Comment thread tilia/ui/timelines/range/timeline.py Outdated
Comment thread tilia/ui/timelines/range/timeline.py Outdated
Comment thread tests/parsers/csv/test_range_from_csv.py Outdated
Comment thread tilia/timelines/range/timeline.py Outdated
Comment thread tilia/ui/timelines/hierarchy/timeline.py
Symmetric with the existing post() call in undo() — keeps listeners
that depend on undo/redo events firing in both directions.
The fixture is auto-applied through qtui and routes settings reads and
writes to a dedicated QSettings store, so production settings are
never touched and tests don't need per-test save/restore wrappers.
Tests should call settings.set(group, name, value) explicitly when
they depend on a particular value — settings persist across tests
within a module, so reading 'the current value' before mutating
returns whatever the previous test left behind, not the default.
These short-form aliases for argparse subcommands have been deprecated
for a while. Drop them so the help output stays focused on the canonical
forms.
…SELECTED

PEP 585 generic-alias syntax for the Timeline type annotations across
TimelineUIs. Also fires Post.TIMELINE_UI_SELECTED on
_add_to_timeline_ui_select_order / _send_to_top_of_select_order so
listeners can react when the active timeline changes, and rewrites
get_first_timeline_ui_in_select_order to use isinstance against the
timeline subclass instead of looking up the now-removed KIND attr.
Without blockSignals, setText("") in clear_widgets fires textChanged →
INSPECTOR_FIELD_EDITED → state record. The phantom record discards the
redo stack, so any subsequent edit.redo finds nothing to redo.

Mirrors the pattern already in set_widget_value.
Add range timeline: a new timeline kind for annotating arbitrary time
ranges organised into rows. Ranges can be joined into chains, merged,
split, and extended with hierarchy-style pre-start / post-end whiskers.

UI surface:
- Element, timeline, and row context menus
- Ribbon-style toolbar grouped into Ranges / Rows / Display sections,
  with split-mode dropdown
- Inspector with label, start, end, length, pre-start/post-end, comments
- Comments indicator on the body (visible when comments non-empty and
  body is wide enough)
- Vertical arrow navigation; horizontal + vertical body drag (snaps
  to row); copy/paste; full undo/redo
- Ctrl+Up / Ctrl+Down moves the selected range across rows; the same
  chord is shared with hierarchy increase_level / decrease_level via
  Post.TIMELINE_KEY_PRESS_CTRL_UP/DOWN

CSV import (by-time + by-measure):
- Required: start, end, row. Optional: label, color, comments,
  joined_with_next, start_fraction, end_fraction
- Rows auto-created by name; joins validated after parsing
- Replaces existing rows; restores >=1 row invariant on empty import

CLI:
- timelines add range [--row-height N]
- timelines range row {add, set-height, list, rename, set-color,
  reset-color, reorder, remove}
- timelines import range {by-time, by-measure}

File format:
- RangeTimeline.SERIALIZABLE: rows, default_row_height
- Range adds row_id, label, color, comments, joined_right, pre_start,
  post_end, start_fraction, end_fraction
- Per-row Row.height (optional override)
- Label alignment is session-only (settings, not per-timeline)

Sample CSV fixtures live in TimeLineAnnotator/testing under
csv/test_ranges_by_*.csv.

README updated to 8 timeline kinds.
@FelipeDefensor FelipeDefensor marked this pull request as ready for review May 6, 2026 14:44
@FelipeDefensor FelipeDefensor requested a review from azfoo May 6, 2026 14:46
@FelipeDefensor FelipeDefensor mentioned this pull request May 6, 2026
FelipeDefensor added a commit that referenced this pull request May 6, 2026
- Backend ↔ frontend split: spell out that frontend constants and visual
  settings live under tilia/ui/, not tilia/timelines/<kind>/.
- Code style: prefer ORDERING_ATTRS over redundant key= when sorting
  components; use logger.error + early return for stale-UI / invariant
  violations on production paths instead of raise.
- New "Commit hygiene" section: split unrelated changes into their own
  commits; for pre-existing bugs in unrelated code, prompt for opening a
  separate PR rather than burying the fix in the feature branch.
azfoo pushed a commit that referenced this pull request May 7, 2026
- Backend ↔ frontend split: spell out that frontend constants and visual
  settings live under tilia/ui/, not tilia/timelines/<kind>/.
- Code style: prefer ORDERING_ATTRS over redundant key= when sorting
  components; use logger.error + early return for stale-UI / invariant
  violations on production paths instead of raise.
- New "Commit hygiene" section: split unrelated changes into their own
  commits; for pre-existing bugs in unrelated code, prompt for opening a
  separate PR rather than burying the fix in the feature branch.
azfoo pushed a commit that referenced this pull request May 7, 2026
- Backend ↔ frontend split: spell out that frontend constants and visual
  settings live under tilia/ui/, not tilia/timelines/<kind>/.
- Code style: prefer ORDERING_ATTRS over redundant key= when sorting
  components; use logger.error + early return for stale-UI / invariant
  violations on production paths instead of raise.
- New "Commit hygiene" section: split unrelated changes into their own
  commits; for pre-existing bugs in unrelated code, prompt for opening a
  separate PR rather than burying the fix in the feature branch.
…kind

`s` (split) and `e` (merge) are bound to both range and hierarchy
commands; Qt fired both with an "Ambiguous shortcut overload" warning.

`commands.register` now tracks shortcut bindings; `commands.setup_shortcuts`
strips colliding QActions and installs one application-level QShortcut per
chord that posts SHARED_SHORTCUT_FIRED with the bound names. TimelineUIs
listens and fires the command whose kind matches the most-recently-clicked
timeline (walks `_select_order`, matches names by the
`timeline.{kind}.{action}` convention).

A backstop in handle_qt_log_message surfaces "Ambiguous shortcut overload"
as AMBIGUOUS_SHORTCUT in case anything bypasses commands.register, and
non-timeline collisions through the dispatcher surface as the same error
rather than dropping silently.
…licates

When every selected range has the same label (or comments), the merged
range adopts that single value rather than concatenating identical
copies with the configured separator.
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