Activity: the time range overlay of the TUI #282

Closed
opened 2026-10-17 08:23:57 +02:00 by agentic-developer · 3 comments
Collaborator

Part of #277. Needs #281.

Goal

t in the activity view opens an overlay to choose the range, in the text syntax or from quick picks, like the app's time range sheet. The sheet is shown below for reference only: its tabs and calendar are not part of the terminal version.

        ┌ TIME RANGE ─────────────────────────┐
        │ > 2026-10-03..2026-10-17█           │
        │   2026-10-03 → 2026-10-17           │
        │   15 days · to today                │
        │                                     │
        │   all · 2026 · 2026-10 · 2026-10-16 │
        │   a..b · last 30 days               │
        ├─────────────────────────────────────┤
        │   Today                             │
        │   Yesterday                         │
        │ ▶ Last 7 days                       │
        │   Last 30 days                      │
        │   This month                        │
        │   Last month                        │
        │   This year                         │
        │   Last year                         │
        │   All time                          │
        └─────────────────────────────────────┘
          ⏎ apply   ↑/↓ quick pick   Esc cancel

Behaviour

  • The field starts with the current range in the syntax (RangeSpec → text, a round trip of #279) and is parsed as it is typed (api::activity_range + resolve). Below it are the resolved dates and N days, plus · to today when the range ends today, or the parse error in the error colour. The grey line with the accepted forms stays and is labelled as examples. This answers open question 2 of the spec: typed input is parsed.
  • ↑/↓ (not j/k, which type) move through the quick picks of #279 and put the chosen one into the field. ⏎ applies a valid range, which starts a new root and clears the back stack (#281). Esc cancels. The view is modal while the overlay is open.
  • The overlay is a widget of commands/tui/widgets.rs style (centered), with its own small model that is tested purely.

Tests

Model: typing, the round trip of the current range, an invalid entry blocking ⏎, quick picks filling the field, Esc keeping the old range. A TestBackend drawing.

Docs

R36 (the keys of the view), README.

Part of #277. Needs #281. ## Goal `t` in the activity view opens an overlay to choose the range, in the text syntax or from quick picks, like the app's time range sheet. The sheet is shown below for reference only: its tabs and calendar are not part of the terminal version. <img src="https://git.konstantinfickel.de/attachments/c7127aea-ef60-4d29-a09f-72c1649254e6" width="260"> ``` ┌ TIME RANGE ─────────────────────────┐ │ > 2026-10-03..2026-10-17█ │ │ 2026-10-03 → 2026-10-17 │ │ 15 days · to today │ │ │ │ all · 2026 · 2026-10 · 2026-10-16 │ │ a..b · last 30 days │ ├─────────────────────────────────────┤ │ Today │ │ Yesterday │ │ ▶ Last 7 days │ │ Last 30 days │ │ This month │ │ Last month │ │ This year │ │ Last year │ │ All time │ └─────────────────────────────────────┘ ⏎ apply ↑/↓ quick pick Esc cancel ``` ## Behaviour - The field starts with the current range in the syntax (`RangeSpec` → text, a round trip of #279) and is **parsed as it is typed** (`api::activity_range` + `resolve`). Below it are the resolved dates and `N days`, plus `· to today` when the range ends today, or the parse error in the error colour. The grey line with the accepted forms stays and is labelled as examples. This answers open question 2 of the spec: typed input is parsed. - ↑/↓ (not j/k, which type) move through the quick picks of #279 and put the chosen one into the field. ⏎ applies a valid range, which starts a new root and clears the back stack (#281). Esc cancels. The view is modal while the overlay is open. - The overlay is a widget of `commands/tui/widgets.rs` style (`centered`), with its own small model that is tested purely. ## Tests Model: typing, the round trip of the current range, an invalid entry blocking ⏎, quick picks filling the field, Esc keeping the old range. A `TestBackend` drawing. ## Docs R36 (the keys of the view), README.
Author
Collaborator

Implementation plan

Dependency #281 is merged. No earlier plan or follow-up comments were present when refining this ticket.

Behaviour and defaults

  • Open a modal range overlay with t from calendar, grid, day or all-shards mode. Seed the field from the current Model.range.to_string(), preserving relative syntax such as last 7 days.
  • Keep edits pending until Enter. Parse after every change; show resolved ISO dates, the inclusive number of days and “· to today” exactly when the end is today. Show the core parser's error in the error colour and keep invalid Enter inside the overlay.
  • Offer the nine core quick picks in their existing order. Initially highlight a pick only if its RangeSpec equals the current range. Up/Down fill the field and select a pick, clamping at the ends; with no selection Down chooses the first and Up the last. Text changes clear the highlight.
  • Default proposed during refinement: normal cursor editing (Left/Right, Home/End, Backspace/Delete and character insertion). The clarification question received no answer before publication, so this remains an explicit implementation default. Keep string edits at character boundaries and cursor placement in display columns.
  • Printable keys such as j, k, q, t, x, ?, :, digits and brackets edit the field rather than invoking dashboard actions. Tab/BackTab do not switch commands. Esc cancels without changing range, stack, cursor, list selection, filter, scroll or mixed-day setting. Retain the front-end's existing global Ctrl-C quit.
  • Enter on a valid field closes the overlay, calls the existing set_root(range) and emits Effect::Recompute. This clears drill-down history and exits all-shards mode through the existing root transition; preserve the session's mixed-day setting. Applying the same range also starts a fresh root.

Code changes

  1. Add an activity-local interactive/range.rs (registered in interactive/mod.rs) with a small pure overlay model: text, insertion cursor, selected quick pick and a result such as Continue / Apply(RangeSpec) / Cancel. Supply today and tracking start explicitly from Snapshot.today / Snapshot.start; no clock or file reads.
  2. Reuse RangeSpec::parse, RangeSpec::resolve(today, start), DayRange::len and quick_picks(today), already re-exported by the CLI activity module. Do not implement a second parser or duplicate quick-pick dates. The CLI convention is to call the core modules directly. In particular, do not use api::resolve_activity_range for this preview: it lacks the stream's tracking start and resolves “all” to today alone.
  3. Add Option<RangeOverlay> to interactive/model.rs. Dispatch overlay keys before Pending prefixes, digit filters, help or normal dashboard keys; opening it clears pending prefixes. Replace the t placeholder. Cancellation just drops the pending model; acceptance uses the root/recompute path above. Ignore key-release events as the existing model does.
  4. In interactive/terminal.rs, make ActivityView::is_modal() true for help or the range overlay. The front-end already forwards modal keys, so no new global routing mechanism is needed.
  5. Draw the overlay after the dashboard using tui::centered, Clear, bordered Paragraph/List widgets and the shared title/dim/error styles. Include the field with a visible cursor, live preview or error, a labelled examples line, quick picks with selection, and “Enter apply · ↑/↓ quick pick · Esc cancel”. Keep the pending text/cursor visible horizontally; wrap preview/error/examples as needed and scroll the quick-pick viewport on short terminals. Clamp geometry and handle very small areas without panics. Keep parsing/resolution separate from drawing.
  6. Update the activity help, key bar/context as appropriate while modal, and remove “not available yet” references. Keep this activity-specific widget local; reuse shared layout/style helpers without introducing a general input framework.

Verification

Use fixed today/start fixtures, with no test reading the clock.

  • Pure overlay tests: every supported initial range round-trips; character insertion and cursor/deletion boundaries; edits immediately update preview/error; invalid Enter stays open; all nine quick picks and navigation endpoints; typing clears selection; ordinary dashboard shortcut characters become text.
  • Preview tests: “all” uses the earliest tracked day even when outside the displayed range; no tracking start means today alone; inclusive day count, month/year boundaries and “to today”; reversed ranges show the core error.
  • Dashboard model tests: opening from drilled and all-shards states; cancellation preserves navigation and mixed settings; valid acceptance emits Recompute, clears parents and resets root navigation; invalid acceptance changes no dashboard state.
  • Front-end integration test: while the actual activity overlay is open, Tab/BackTab/: cannot switch/open the command picker, q does not quit and Esc only dismisses the overlay; retain Ctrl-C behaviour.
  • TestBackend snapshots at normal and narrow widths for a valid preview, invalid field and quick-pick selection; cover a short/tiny viewport and long input/error without out-of-bounds cursor placement. Existing dashboard snapshots should change only where help/hints intentionally change.
  • During implementation run the targeted activity/front-end tests, then nix develop -c cargo test, nix develop -c cargo clippy --all-targets -- -D warnings, formatting checks, and finally nix flake check as required by AGENTS.md. Manually try typing, picks, cancel/apply and drill-down → new root in the TUI.

Documentation and completion

Update README.md's activity keys, REQUIREMENTS.md R36a's range-picker and modal behaviour, and AGENTS.md's activity architecture entry. Remove the placeholder wording from all three. This ticket changes the terminal overlay only; the Android range sheet remains #285.

Complete when arbitrary valid ranges and quick picks can be applied through t, errors remain editable, cancellation preserves the dashboard, and modal routing plus narrow-terminal rendering are verified.

## Implementation plan Dependency #281 is merged. No earlier plan or follow-up comments were present when refining this ticket. ### Behaviour and defaults - Open a modal range overlay with `t` from calendar, grid, day or all-shards mode. Seed the field from the current `Model.range.to_string()`, preserving relative syntax such as `last 7 days`. - Keep edits pending until Enter. Parse after every change; show resolved ISO dates, the inclusive number of days and “· to today” exactly when the end is today. Show the core parser's error in the error colour and keep invalid Enter inside the overlay. - Offer the nine core quick picks in their existing order. Initially highlight a pick only if its RangeSpec equals the current range. Up/Down fill the field and select a pick, clamping at the ends; with no selection Down chooses the first and Up the last. Text changes clear the highlight. - Default proposed during refinement: normal cursor editing (Left/Right, Home/End, Backspace/Delete and character insertion). The clarification question received no answer before publication, so this remains an explicit implementation default. Keep string edits at character boundaries and cursor placement in display columns. - Printable keys such as j, k, q, t, x, ?, :, digits and brackets edit the field rather than invoking dashboard actions. Tab/BackTab do not switch commands. Esc cancels without changing range, stack, cursor, list selection, filter, scroll or mixed-day setting. Retain the front-end's existing global Ctrl-C quit. - Enter on a valid field closes the overlay, calls the existing `set_root(range)` and emits `Effect::Recompute`. This clears drill-down history and exits all-shards mode through the existing root transition; preserve the session's mixed-day setting. Applying the same range also starts a fresh root. ### Code changes 1. Add an activity-local `interactive/range.rs` (registered in `interactive/mod.rs`) with a small pure overlay model: text, insertion cursor, selected quick pick and a result such as Continue / Apply(RangeSpec) / Cancel. Supply today and tracking start explicitly from `Snapshot.today` / `Snapshot.start`; no clock or file reads. 2. Reuse `RangeSpec::parse`, `RangeSpec::resolve(today, start)`, `DayRange::len` and `quick_picks(today)`, already re-exported by the CLI activity module. Do not implement a second parser or duplicate quick-pick dates. The CLI convention is to call the core modules directly. In particular, do not use `api::resolve_activity_range` for this preview: it lacks the stream's tracking start and resolves “all” to today alone. 3. Add `Option<RangeOverlay>` to `interactive/model.rs`. Dispatch overlay keys before Pending prefixes, digit filters, help or normal dashboard keys; opening it clears pending prefixes. Replace the `t` placeholder. Cancellation just drops the pending model; acceptance uses the root/recompute path above. Ignore key-release events as the existing model does. 4. In `interactive/terminal.rs`, make `ActivityView::is_modal()` true for help or the range overlay. The front-end already forwards modal keys, so no new global routing mechanism is needed. 5. Draw the overlay after the dashboard using `tui::centered`, Clear, bordered Paragraph/List widgets and the shared title/dim/error styles. Include the field with a visible cursor, live preview or error, a labelled examples line, quick picks with selection, and “Enter apply · ↑/↓ quick pick · Esc cancel”. Keep the pending text/cursor visible horizontally; wrap preview/error/examples as needed and scroll the quick-pick viewport on short terminals. Clamp geometry and handle very small areas without panics. Keep parsing/resolution separate from drawing. 6. Update the activity help, key bar/context as appropriate while modal, and remove “not available yet” references. Keep this activity-specific widget local; reuse shared layout/style helpers without introducing a general input framework. ### Verification Use fixed today/start fixtures, with no test reading the clock. - Pure overlay tests: every supported initial range round-trips; character insertion and cursor/deletion boundaries; edits immediately update preview/error; invalid Enter stays open; all nine quick picks and navigation endpoints; typing clears selection; ordinary dashboard shortcut characters become text. - Preview tests: “all” uses the earliest tracked day even when outside the displayed range; no tracking start means today alone; inclusive day count, month/year boundaries and “to today”; reversed ranges show the core error. - Dashboard model tests: opening from drilled and all-shards states; cancellation preserves navigation and mixed settings; valid acceptance emits Recompute, clears parents and resets root navigation; invalid acceptance changes no dashboard state. - Front-end integration test: while the actual activity overlay is open, Tab/BackTab/: cannot switch/open the command picker, q does not quit and Esc only dismisses the overlay; retain Ctrl-C behaviour. - TestBackend snapshots at normal and narrow widths for a valid preview, invalid field and quick-pick selection; cover a short/tiny viewport and long input/error without out-of-bounds cursor placement. Existing dashboard snapshots should change only where help/hints intentionally change. - During implementation run the targeted activity/front-end tests, then `nix develop -c cargo test`, `nix develop -c cargo clippy --all-targets -- -D warnings`, formatting checks, and finally `nix flake check` as required by AGENTS.md. Manually try typing, picks, cancel/apply and drill-down → new root in the TUI. ### Documentation and completion Update README.md's activity keys, REQUIREMENTS.md R36a's range-picker and modal behaviour, and AGENTS.md's activity architecture entry. Remove the placeholder wording from all three. This ticket changes the terminal overlay only; the Android range sheet remains #285. Complete when arbitrary valid ranges and quick picks can be applied through t, errors remain editable, cancellation preserves the dashboard, and modal routing plus narrow-terminal rendering are verified.
Author
Collaborator

Implementation status

Implemented locally on 282_activity-range-overlay, branched from the latest origin/main.

  • t opens a modal editor with normal cursor editing, live core range previews and quick picks.
  • “All time” resolves with the stream's tracking start. Invalid Enter stays in the overlay; Esc preserves dashboard navigation, and applying a valid range starts a new root.
  • Input follows the cursor horizontally; preview/error/examples and action hints wrap, and quick picks remain selectable in short terminals.
  • Added pure editor/navigation tests, actual front-end modal routing coverage, six normal/narrow snapshots and tiny-terminal cursor checks. Updated README.md, REQUIREMENTS.md and AGENTS.md.

Validation: nix flake check passed on x86_64-linux, including 1,708 Rust tests and the Kotlin bindings check. All-target Clippy with -D warnings, rustfmt and repository commit hooks passed. Terminal smoke-tested drill-down, invalid typing/Enter, cancellation, quick-pick application and clearing the parent stack.

Publishing is blocked: both GPG-signed commit attempts failed with gpg: signing failed: Operation cancelled after pinentry launched. No unsigned commit was created. The user has been asked to unlock the signing key; commits, push and PR remain pending.

Elapsed wall time to this status: approximately 81 minutes (started 2026-10-20 13:22 UTC). Exact token usage is not exposed by this session, so no measured token count is available.

## Implementation status Implemented locally on `282_activity-range-overlay`, branched from the latest `origin/main`. - `t` opens a modal editor with normal cursor editing, live core range previews and quick picks. - “All time” resolves with the stream's tracking start. Invalid Enter stays in the overlay; Esc preserves dashboard navigation, and applying a valid range starts a new root. - Input follows the cursor horizontally; preview/error/examples and action hints wrap, and quick picks remain selectable in short terminals. - Added pure editor/navigation tests, actual front-end modal routing coverage, six normal/narrow snapshots and tiny-terminal cursor checks. Updated README.md, REQUIREMENTS.md and AGENTS.md. Validation: `nix flake check` passed on x86_64-linux, including 1,708 Rust tests and the Kotlin bindings check. All-target Clippy with `-D warnings`, rustfmt and repository commit hooks passed. Terminal smoke-tested drill-down, invalid typing/Enter, cancellation, quick-pick application and clearing the parent stack. Publishing is blocked: both GPG-signed commit attempts failed with `gpg: signing failed: Operation cancelled` after pinentry launched. No unsigned commit was created. The user has been asked to unlock the signing key; commits, push and PR remain pending. Elapsed wall time to this status: approximately 81 minutes (started 2026-10-20 13:22 UTC). Exact token usage is not exposed by this session, so no measured token count is available.
Author
Collaborator

Implemented and published in PR #310: #310

The signing blocker is resolved. Both commits are GPG-signed (c5cdf75 feature, 7deb023 documentation), passed the applicable repository hooks, and are pushed. The working tree is clean.

The implementation and validation findings are in the preceding status comment: live core range parsing, tracking-aware all-time preview, modal input, cancel/apply semantics, narrow layouts, tests and documentation. nix flake check passed, including 1,708 Rust tests and Kotlin bindings; all-target Clippy and the terminal smoke test passed too.

Total elapsed wall time from the initial start at 13:22 UTC to publication at approximately 14:54 UTC: about 92 minutes, including the signing interruption. Exact token usage is unavailable in this session; no measured count can be reported.

Implemented and published in PR #310: https://git.konstantinfickel.de/kfickel/streamd/pulls/310 The signing blocker is resolved. Both commits are GPG-signed (`c5cdf75` feature, `7deb023` documentation), passed the applicable repository hooks, and are pushed. The working tree is clean. The implementation and validation findings are in the preceding status comment: live core range parsing, tracking-aware all-time preview, modal input, cancel/apply semantics, narrow layouts, tests and documentation. `nix flake check` passed, including 1,708 Rust tests and Kotlin bindings; all-target Clippy and the terminal smoke test passed too. Total elapsed wall time from the initial start at 13:22 UTC to publication at approximately 14:54 UTC: about 92 minutes, including the signing interruption. Exact token usage is unavailable in this session; no measured count can be reported.
Sign in to join this conversation.
No labels
android
cli
epic
planned
tui
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
kfickel/streamd#282
No description provided.