Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions openspec/changes/archive/2026-08-02-add-live-kanban/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
## 1. Change probe in client

- [x] 1.1 Add `OpenSpecClient.has_changed() -> bool` that stats the mtimes of `<root>/openspec/changes/` and each change's `tasks.md` (and the specs dir for dashboard), compares against a cached snapshot kept on the client, and returns whether any mtime advanced; updates the cache when invoked
- [x] 1.2 Add a `reset_change_cache()` helper (or reset on `reload()`) so a forced refresh re-baselines the cache and doesn't immediately re-fire

## 2. Live-refresh state on the app

- [x] 2.1 Add ephemeral `live_refresh: bool` (default `True`) and the mtime cache to `OpenSpecUIApp`; do not persist either
- [x] 2.2 Add a helper to detect "foreground busy": app is in `suspend()` or a modal/non-`reload`-able screen is atop the stack
- [x] 2.3 Add `toggle_live_refresh()` and expose current state for screens to read

## 3. Polling on kanban

- [x] 3.1 In `KanbanScreen`, start a `set_interval(~1s)` timer on mount that, when `live_refresh` and not foreground-busy, calls `client.has_changed()` and only calls `reload()` when it returns true
- [x] 3.2 Skip ticks when the screen is not the active/top screen or when foreground-busy
- [x] 3.3 Show `● live` / `○ paused` in the kanban title; update it on toggle and on reload
- [x] 3.4 Bind `p` to toggle live refresh; keep `r` as a forced immediate reload that works in both live and paused states

## 4. Polling on dashboard

- [x] 4.1 Mirror the timer logic on `DashboardScreen`, gating `list_changes()` + `list_specs()` behind `has_changed()`
- [x] 4.2 Skip ticks when foreground-busy or not the top screen
- [x] 4.3 Show the live/paused indicator in the dashboard title; bind `p` and keep `r`

## 5. Editor/modal race safety

- [x] 5.1 Confirm the foreground-busy check suspends polling for the full `$EDITOR` session in `DetailScreen.action_edit` (via `app.suspend()`) and resumes only after the editor exits — *implemented: `_in_editor` flag set in a try/finally around `app.suspend()`; verified `foreground_busy()` is True during and False after*
- [x] 5.2 Confirm modal screens (confirm, new-change, palette) suppress polling ticks while atop the stack — *verified via Textual `run_test()`: opening the command palette makes the dashboard non-top, and its `_tick` early-returns without probing (the `self is not self.app.screen` guard)*

## 6. Verification

- [x] 6.1 Manual: open kanban, flip a task checkbox in another window, confirm the card moves to the matching column within ~1s with no keypress — *verified via `run_test()`: touching a change's `tasks.md` then calling `_tick()` triggers a reload that re-populates the kanban columns*
- [x] 6.2 Manual: open dashboard, add/remove a change externally, confirm counts and listings update within the polling interval — *verified via `run_test()`: external `tasks.md` mtime bump → dashboard `_tick()` reloads and re-populates `_changes`*
- [x] 6.3 Manual: press `p` and confirm the indicator switches to paused and ticks stop; press `p` again to resume — *verified: `p` flips `live_refresh` True↔False and the title glyph `● live`↔`○ paused`; `_tick` early-returns when paused*
- [x] 6.4 Manual: while paused, press `r` and confirm an immediate forced reload — *verified: `r` calls `reload()` + `reset_change_cache()` and works in both live and paused states*
- [x] 6.5 Manual: invoke `e` to edit an artifact and confirm no poll fires mid-edit and the view reloads cleanly after the editor exits — *verified: `_in_editor` flag + the "am I top" guard suppress polling during the suspended `$EDITOR` session; flag clears in `finally`*
- [x] 6.6 Run `openspec validate add-live-kanban` and resolve any reported issues — *valid*
- [x] 6.7 Commit on a `feat/live-kanban` branch with Conventional Commit messages and open a PR referencing the change — *PR #8, CI build-check passed*
57 changes: 55 additions & 2 deletions openspec/specs/openspec-tui/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The application SHALL be launchable as a single console command (`openspec-ui`)

### Requirement: Dashboard view of changes and specs

The application SHALL provide an interactive dashboard listing changes and specifications for the resolved root, grouped by status (draft, active, completed), with task progress indicators.
The application SHALL provide an interactive dashboard listing changes and specifications for the resolved root, grouped by status (draft, active, completed), with task progress indicators. While the dashboard is open and live refresh is not paused, the view SHALL periodically re-read state from disk and re-render so counts and progress bars stay current without a manual keypress.

#### Scenario: Active changes shown with progress

Expand All @@ -35,9 +35,14 @@ The application SHALL provide an interactive dashboard listing changes and speci
- **WHEN** the resolved root contains no changes and no specs
- **THEN** the dashboard SHALL render an empty state that tells the user no changes or specs exist, without erroring

#### Scenario: External change reflected while dashboard is open

- **WHEN** the dashboard is open and live refresh is active, and an external process modifies a change's tasks or adds/removes a change or spec
- **THEN** the dashboard SHALL, within its polling interval, re-read from disk and update counts, progress bars, and listings to match the current state, without any user keypress

### Requirement: Kanban pipeline view of changes

The application SHALL provide a kanban-style pipeline view that arranges changes into columns by pipeline stage, so the user can see the distribution of work at a glance.
The application SHALL provide a kanban-style pipeline view that arranges changes into columns by pipeline stage, so the user can see the distribution of work at a glance. While the kanban view is open and live refresh is not paused, the view SHALL periodically re-read state from disk and re-render so that cards move between columns as their task progress changes, without requiring the user to press a key.

#### Scenario: Changes distributed across stages

Expand All @@ -49,6 +54,16 @@ The application SHALL provide a kanban-style pipeline view that arranges changes
- **WHEN** the user selects a change card in the kanban view
- **THEN** the application SHALL allow the user to open that change's detail view

#### Scenario: External change reflected while view is open

- **WHEN** the kanban view is open and live refresh is active, and an external process (e.g. an agent or editor in another window) modifies a change's tasks so its completed-task count changes
- **THEN** the kanban view SHALL, within its polling interval, re-read from disk and move the affected card to the column matching the new stage, without any user keypress

#### Scenario: Polling cost between changes

- **WHEN** the kanban view is open and live refresh is active, and no file under the `openspec/` root has changed since the last poll
- **THEN** the application SHALL NOT spawn the `openspec` CLI subprocess on that poll tick, instead gating the expensive re-read on a cheap change probe

### Requirement: Read-only artifact detail view

The application SHALL provide a detail view that renders the markdown artifacts of a change or spec (`proposal.md`, `design.md`, `specs/**`, `tasks.md`) read-only, preserving monospace and ASCII diagram formatting.
Expand Down Expand Up @@ -105,3 +120,41 @@ The application SHALL NOT persist or own any state of its own. All reads SHALL c

- **WHEN** the user views, edits, or creates changes through the application
- **THEN** the application SHALL NOT write any state of its own anywhere except by invoking the openspec CLI or `$EDITOR` on files within the `openspec/` root

### Requirement: Live refresh with pause and indicator

The application SHALL periodically refresh open views (kanban and dashboard) from disk while live refresh is active, SHALL let the user pause and resume live refresh, and SHALL display a visible indicator of whether live refresh is currently active or paused.

#### Scenario: Live refresh on by default

- **WHEN** the user opens the kanban or dashboard view
- **THEN** live refresh SHALL be active by default, periodically reloading the view from disk

#### Scenario: User pauses live refresh

- **WHEN** the user invokes the pause-live-refresh action
- **THEN** the application SHALL stop periodic reloading and SHALL display an indicator that live refresh is paused

#### Scenario: User resumes live refresh

- **WHEN** the user invokes the resume-live-refresh action while paused
- **THEN** the application SHALL resume periodic reloading and SHALL display an indicator that live refresh is active

#### Scenario: Manual refresh always available

- **WHEN** the user presses the manual refresh key while live refresh is either active or paused
- **THEN** the application SHALL immediately reload the current view from disk

### Requirement: Polling pauses during foreground edits

The application SHALL NOT poll for external changes while the user is editing in `$EDITOR` or while a modal/suspended screen is in the foreground, so live refresh never reads a half-saved file or competes with a foreground operation.

#### Scenario: Editing an artifact suspends polling

- **WHEN** the user invokes the edit action and the application suspends into `$EDITOR`
- **THEN** the application SHALL pause live refresh for the duration of the editor session and SHALL resume it only after the editor exits and the view reloads

#### Scenario: Modal screen suspends polling

- **WHEN** a modal or suspended screen is in the foreground
- **THEN** the application SHALL skip polling ticks until that screen is dismissed
Loading