Module: lash-tui
Dependencies: tasks.tui.md
Effort: 1–2 days
Priority: MEDIUM
Design doc: docs/live-tui-updates.md §"Status bar: activity sections"
Add two live-updated sections to the bottom status bar:
- In-progress: title of the currently in-progress task (icon
▶), if any. - Recently completed: rolling list of titles for tasks that transitioned
to Done/Waived within the last 5 minutes (icon
✓), newest first, up to 3.
Titles are truncated with … to fit terminal width. Updates flow from
StateDelta::TaskStatusChanged events — the same plumbing that will later
carry external changes once the Store and watcher land.
In Phase A (this file), updates are wired only to TUI-initiated transitions
via handle_toggle_status. The hook surface is designed so Phase C of the
live-updates work can drop external-change updates into the same ActivityState
without further refactor.
Priority: HIGH Effort: 0.5 day
Add ActivityState to crates/lash-tui/src/state.rs. Pure data, no IO,
fully unit-testable.
-
ActivityEntry { full_id, title, at } -
ActivityState { in_progress: Option<ActivityEntry>, recently_completed: VecDeque<ActivityEntry> } -
ActivityState::record_transition(full_id, title, old: TaskStatus, new: TaskStatus, now: Instant)- Open|Blocked|Waived → InProgress: set
in_progress - InProgress → Done|Waived: clear
in_progressif matches; push torecently_completed - Open|InProgress → Done|Waived: push to
recently_completed - Done|Waived → Open|InProgress: remove from
recently_completed(re-opened a recently-completed task should not still show as "recent")
- Open|Blocked|Waived → InProgress: set
-
ActivityState::prune(now)— droprecently_completedentries older than 5 minutes; cap to 3 newest - Add
pub activity: ActivityStatefield toAppState, initialize inwith_theme - Unit tests: each transition path, prune by age, cap by count, reopen removes from recent
- 100% branch coverage on
record_transitionandprune
Priority: MEDIUM Effort: 0.25 day Depends on: Task 1
On TUI startup, query the DB for any tasks with status = InProgress and
seed activity.in_progress with the first one (deterministic ordering by
full_id). Recently-completed stays empty — we don't backfill historical
completion times.
- Seed via existing
TaskRepository::find_by_status(InProgress)(already ordered byfull_id— no new query needed) - Seed
state.activity.in_progressinTuiApp::new_with_scheme - Test: DB with two in-progress tasks → smaller full_id wins (
crates/lash-tui/tests/activity_bar_tests.rs; the fixture listszuluabovealphaso row order and full_id order disagree)
Priority: HIGH Effort: 0.25 day Depends on: Task 1
After a successful status transition in app.rs::handle_toggle_status,
call state.activity.record_transition(...). The existing
set_success_message path is preserved — it overlays the activity bar
for its transient duration.
- Insert
record_transitioncall afterupdate_markdown_task_statussucceeds (primary toggle path) - Hook the three cascading handlers (
handle_confirm_cascading_complete,handle_confirm_linked_file_complete,handle_confirm_cascading_incomplete) — initiating task only, not cascade results - Existing tests for
handle_toggle_statusstill pass - New end-to-end test: toggling Open→InProgress sets
activity.in_progress; InProgress→Done clears it and pushes to recent (crates/lash-tui/tests/activity_bar_tests.rs, driven throughTestAppBuilderand real Space key events)
Priority: MEDIUM Effort: 0.1 day Depends on: Task 1
tick() already runs at ~10Hz. Call state.activity.prune(Instant::now())
alongside check_status_expiry(). Cheap (~O(3)).
- One-line addition in
tick() - No test needed beyond what Task 1 already covers (
pruneis unit-tested)
Priority: HIGH Effort: 0.75 day Depends on: Task 1
Extend render_default in crates/lash-tui/src/ui/status_bar.rs to render:
Tasks Files: 12 Tasks: 184 ▶ Implementing Store actor ✓ Add lash task files ✓ Survey TUI writes
- Compute available width after the existing pane/counts/filter prefix and the right-side "Press ? for help" suffix
- Allocate ~40% to in-progress section, remainder split evenly among up-to-3 recent entries
- Width-aware ellipsis: minimum 12 visible chars per entry; drop entries from the right if they don't fit
- Icons:
▶(in-progress) and✓(recent) styled with theme colors (task_in_progress/task_done) - When
status_messageis active, the existing message overlay continues to take over the whole bar - Unit tests using
ratatui::backend::TestBackend: assert the buffer contents
- Bar fits any terminal width ≥ 60 cols without panicking or wrapping
- Reasonable truncation behavior verified by buffer snapshot tests