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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
rather than the workhorse. Agent templates gained a `{model}` placeholder and
a per-agent `model`/`model_deep`/`model_plan` map to fill it; an agent naming
no models, such as the built-in `codex`, ignores the flag entirely.
- `?` in the TUI opens the current screen's complete key map — actions,
navigation, and screen switching — including the keys no hint ever advertised
(`o` open in a viewer, `g` open the PR, `a` attach, `l` page the log, `J`/`K`
and the page keys, `ctrl-r`). Any key closes it again.

### Changed

Expand All @@ -66,6 +70,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
body, branch name, document title or project name containing `{task_id}`,
`{db}`, `{note}`, `{seed}`, `{branch}` or `{docs}` now reaches the agent
verbatim instead of being rewritten before it is read.
- The TUI's contextual key line is shorter and speaks one language. A lowercase
key and its shifted sibling now share a single slot labelled with the base
verb — `d/D dispatch`, `r/R refine`, `n/N new` — instead of three different
renderings of the same idea, and the line keeps only what changes a task's
state or destiny: `x` score, `h` history, `c` docs and `l` log still work but
are documented in the `?` map rather than on the line. No key was rebound.
- `projects.path` is gone. Existing databases convert in place: every project's
old path becomes its default repo, and existing tasks resolve to exactly the
checkouts they had before. `voro project add` and `voro project path` keep
Expand Down
26 changes: 14 additions & 12 deletions crates/voro/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ pub enum Mode {
editing: bool,
review_project: Option<i64>,
},
/// The current screen's full key map (DESIGN.md §9), opened with `?`. It is
/// a peek rather than a screen — any key dismisses it — and it carries no
/// state of its own, since the screen it describes is the App's.
KeyMap,
/// Picking `default_agent` or `default_viewer` from the configured set
/// (DESIGN.md §5), on the Config screen.
DefaultPicker {
Expand Down Expand Up @@ -953,6 +957,9 @@ impl App {
editing,
review_project,
} => self.key_viewer_form(key, name, cmd, on_cmd, editing, review_project),
// The key map is dismissed by any key, and `on_key` has already
// restored `Mode::Normal`, so there is nothing left to do.
Mode::KeyMap => {}
Mode::DefaultPicker {
kind,
names,
Expand All @@ -963,13 +970,18 @@ impl App {
}

fn key_normal(&mut self, key: KeyEvent) {
// Navigation shared by every screen: quit, tab cycling, and moving the
// selection.
// Navigation shared by every screen: quit, the key map, tab cycling,
// and moving the selection. `?` belongs here rather than in the
// trailing match, which the projects and Config screens never reach.
match key.code {
KeyCode::Char('q') => {
self.should_quit = true;
return;
}
KeyCode::Char('?') => {
self.mode = Mode::KeyMap;
return;
}
KeyCode::Tab => {
self.toggle_screen();
return;
Expand Down Expand Up @@ -1200,13 +1212,6 @@ impl App {
self.store.repos(project_id).map(|r| r.len()).unwrap_or(1)
}

/// The selected task's newest-session log path, whatever its state — what
/// gates the `l` key and its key-line hint.
pub fn selected_session_log(&self) -> Option<&str> {
let id = self.selected_task_id()?;
self.last_sessions.get(&id)?.log_path.as_deref()
}

/// Whether the selection is a review task, so it can be handed off with
/// `w` — what gates that key's key-line hint.
pub fn selected_can_hand_off(&self) -> bool {
Expand Down Expand Up @@ -3752,7 +3757,6 @@ mod tests {
assert_eq!(session.outcome, Some(voro_core::SessionOutcome::Failed));
assert!(session.ended_at.is_some());
assert_eq!(session.log_path.as_deref(), Some("/tmp/demo/s.log"));
assert_eq!(app.selected_session_log(), Some("/tmp/demo/s.log"));
}

/// `l` on a stalled task queues `$PAGER <log>` for main() to run with the
Expand Down Expand Up @@ -3797,7 +3801,6 @@ mod tests {
app.store.task(task.id).unwrap().state,
TaskState::NeedsInput
);
assert_eq!(app.selected_session_log(), Some("/tmp/demo/open.log"));
key(&mut app, KeyCode::Char('l'));

let request = app.pending_attach.clone().expect("a pager request");
Expand All @@ -3810,7 +3813,6 @@ mod tests {
#[test]
fn log_key_on_a_ready_task_reports_and_does_nothing() {
let mut app = app_with(&[TaskState::Ready]);
assert!(app.selected_session_log().is_none());
key(&mut app, KeyCode::Char('l'));

assert!(app.pending_attach.is_none());
Expand Down
Loading
Loading