From eae7d85d6dd0aa44b6bab6edeb6cb076dd74a9a1 Mon Sep 17 00:00:00 2001 From: Laure Date: Mon, 14 Sep 2026 02:31:55 +0200 Subject: [PATCH] Add id:/needs: task dependencies --- src/action.rs | 2 ++ src/app/palette.rs | 6 ++++ src/app/prefs.rs | 7 ++++ src/app/visibility.rs | 15 ++++++++ src/config.rs | 7 ++++ src/main.rs | 9 +++++ src/todo.rs | 15 ++++++++ src/ui/archive.rs | 1 + src/ui/help.rs | 1 + src/ui/list.rs | 12 +++++++ src/ui/settings.rs | 4 +++ src/ui/task_row.rs | 36 ++++++++++++++++++- .../snapshots__help_overlay_styled.snap | 22 ++++++------ .../snapshots__help_overlay_text.snap | 22 ++++++------ .../snapshots__settings_overlay_styled.snap | 2 +- .../snapshots__settings_overlay_text.snap | 2 +- 16 files changed, 138 insertions(+), 25 deletions(-) diff --git a/src/action.rs b/src/action.rs index f20c4fbe..cf957fb6 100644 --- a/src/action.rs +++ b/src/action.rs @@ -49,6 +49,7 @@ pub enum Action { ToggleLineNum, ToggleShowDone, ToggleShowFuture, + ToggleShowBlocked, CopyLine, CopyBody, OpenNote, @@ -108,6 +109,7 @@ impl Action { "toggle_line_num" | "toggle_line_numbers" => Some(Self::ToggleLineNum), "toggle_show_done" => Some(Self::ToggleShowDone), "toggle_show_future" => Some(Self::ToggleShowFuture), + "toggle_show_blocked" => Some(Self::ToggleShowBlocked), "copy_line" => Some(Self::CopyLine), "copy_body" => Some(Self::CopyBody), "open_note" | "note" => Some(Self::OpenNote), diff --git a/src/app/palette.rs b/src/app/palette.rs index c2f5bec9..815fafef 100644 --- a/src/app/palette.rs +++ b/src/app/palette.rs @@ -187,6 +187,11 @@ pub const ENTRIES: &[PaletteEntry] = &[ keys: "F", action: Action::ToggleShowFuture, }, + PaletteEntry { + label: "show blocked in list", + keys: "B", + action: Action::ToggleShowBlocked, + }, PaletteEntry { label: "toggle filter pane", keys: "[", @@ -553,6 +558,7 @@ mod tests { Action::ToggleLineNum, Action::ToggleShowDone, Action::ToggleShowFuture, + Action::ToggleShowBlocked, Action::CopyLine, Action::CopyBody, Action::EscapeStack, diff --git a/src/app/prefs.rs b/src/app/prefs.rs index debf62e4..7c940bf4 100644 --- a/src/app/prefs.rs +++ b/src/app/prefs.rs @@ -35,6 +35,7 @@ pub struct Prefs { pub layout: Layout, pub show_done: bool, pub show_future: bool, + pub show_blocked: bool, /// Metadata keys whose `key:value` tokens are hidden from task rows. /// Config-only (no in-app toggle); see `Config::hidden_keys`. pub hidden_keys: Vec, @@ -64,6 +65,7 @@ impl Prefs { }, show_done: cfg.show_done.unwrap_or(false), show_future: cfg.show_future.unwrap_or(false), + show_blocked: cfg.show_blocked.unwrap_or(true), hidden_keys: cfg.hidden_keys, week_start: cfg.week_start.unwrap_or(WeekStart::Sunday), recurrence_builder: cfg.recurrence_builder.unwrap_or(true), @@ -133,6 +135,10 @@ impl Prefs { self.show_future = !self.show_future; } + pub fn toggle_show_blocked(&mut self) { + self.show_blocked = !self.show_blocked; + } + pub fn cycle_week_start(&mut self) -> String { self.week_start = match self.week_start { WeekStart::Sunday => WeekStart::Monday, @@ -160,6 +166,7 @@ impl Prefs { cfg.show_status_bar = Some(self.layout.status_bar); cfg.show_done = Some(self.show_done); cfg.show_future = Some(self.show_future); + cfg.show_blocked = Some(self.show_blocked); cfg.hidden_keys = self.hidden_keys.clone(); cfg.save() } diff --git a/src/app/visibility.rs b/src/app/visibility.rs index 308e41a4..c688e2ca 100644 --- a/src/app/visibility.rs +++ b/src/app/visibility.rs @@ -1,6 +1,7 @@ use super::App; use super::types::{Sort, View}; use crate::core::filter::{self, ListDueBucket}; +use crate::todo::Task; /// One entry per visible row, parallel to `visible_cache`. Renderers detect /// group transitions by comparing successive entries; under `Sort::File` every @@ -44,8 +45,22 @@ impl App { let needle = (!self.filter.search.is_empty()) .then(|| filter::resolve_needle(&self.filter.search, today)); + let undone_ids: std::collections::HashSet<&str> = tasks + .iter() + .filter(|t| !t.done) + .filter_map(|t| t.id.as_deref()) + .collect(); + let is_blocked = |t: &Task| { + t.needs + .as_deref() + .is_some_and(|dep| undone_ids.contains(dep)) + }; + let mut idxs: Vec = (0..tasks.len()) .filter(|&i| { + if !self.prefs.show_blocked && is_blocked(&tasks[i]) { + return false; + } filter::list_predicate( &tasks[i], self.prefs.show_done, diff --git a/src/config.rs b/src/config.rs index c235dbed..5c50e5da 100644 --- a/src/config.rs +++ b/src/config.rs @@ -26,6 +26,7 @@ pub struct Config { pub show_status_bar: Option, pub show_done: Option, pub show_future: Option, + pub show_blocked: Option, /// 64-character lowercase-hex token gating the in-TUI capture server. /// Persisted across sessions so phone bookmarks survive a relaunch. /// Stored on disk; only meaningful for LAN access, but flagged here @@ -154,6 +155,7 @@ fn parse(s: &str) -> Config { "show_status_bar" => c.show_status_bar = parse_bool(v), "show_done" => c.show_done = parse_bool(v), "show_future" => c.show_future = parse_bool(v), + "show_blocked" => c.show_blocked = parse_bool(v), // Reject anything that isn't a valid hex token so we don't // carry forward a corrupt value that the server would later // refuse to compare against. @@ -226,6 +228,9 @@ fn serialize(c: &Config) -> String { if let Some(v) = c.show_future { let _ = writeln!(out, "show_future = {v}"); } + if let Some(v) = c.show_blocked { + let _ = writeln!(out, "show_blocked = {v}"); + } if let Some(v) = &c.share_token { let _ = writeln!(out, "share_token = {v}"); } @@ -283,6 +288,7 @@ mod tests { show_status_bar: Some(true), show_done: Some(true), show_future: Some(true), + show_blocked: Some(true), share_token: Some("a".repeat(64)), share_port: Some(18080), filters: vec![ @@ -443,6 +449,7 @@ mod tests { show_status_bar: Some(false), show_done: Some(true), show_future: Some(false), + show_blocked: Some(false), share_token: None, share_port: None, filters: vec![("errand".into(), "@errand".into())], diff --git a/src/main.rs b/src/main.rs index 401ac736..f08efaf3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -843,6 +843,7 @@ fn handle_settings(app: &mut App, key: KeyEvent) { KeyCode::Char(']') => apply_action(app, Action::ToggleRightPane), KeyCode::Char('H') => apply_action(app, Action::ToggleShowDone), KeyCode::Char('F') => apply_action(app, Action::ToggleShowFuture), + KeyCode::Char('B') => apply_action(app, Action::ToggleShowBlocked), KeyCode::Char('S') => apply_action(app, Action::CycleSort), _ => {} } @@ -1092,6 +1093,7 @@ fn resolve_normal_key(app: &mut App, key: KeyEvent, keybinds: &KeyBindings) -> O KeyCode::Char('L') => Action::ToggleLineNum, KeyCode::Char('H') => Action::ToggleShowDone, KeyCode::Char('F') => Action::ToggleShowFuture, + KeyCode::Char('B') => Action::ToggleShowBlocked, KeyCode::Esc => Action::EscapeStack, KeyCode::Char('W') => Action::ChangeWeekStart, _ => return None, @@ -1136,6 +1138,7 @@ fn apply_action(app: &mut App, action: Action) { | Action::CycleSort | Action::ToggleShowDone | Action::ToggleShowFuture + | Action::ToggleShowBlocked | Action::Undo => { app.flash("read-only in archive"); return; @@ -1307,6 +1310,12 @@ fn apply_action(app: &mut App, action: Action) { app.recompute_visible(); app.save_prefs(); } + Action::ToggleShowBlocked => { + app.prefs.toggle_show_blocked(); + app.cursor = 0; + app.recompute_visible(); + app.save_prefs(); + } Action::CopyLine => copy_current_task(app, false), Action::CopyBody => copy_current_task(app, true), Action::OpenNote => app.open_note_for_current(), diff --git a/src/todo.rs b/src/todo.rs index 893a7604..940921bc 100644 --- a/src/todo.rs +++ b/src/todo.rs @@ -56,6 +56,10 @@ pub struct Task { /// filter parses it on demand via `crate::threshold`. pub threshold: Option, pub notes: Vec, + /// Raw value of the `id:` tag if present. + pub id: Option, + /// Raw value of the `needs:` tag if present. + pub needs: Option, } pub fn parse_line(raw: &str) -> Result { @@ -94,6 +98,8 @@ pub fn parse_line(raw: &str) -> Result { let rec = find_kv(rest, "rec"); let threshold = find_kv(rest, "t"); let notes = find_quoted_kv(rest, "note"); + let id = find_kv(rest, "id"); + let needs = find_kv(rest, "needs"); let clean_raw = body_after_quoted_kv(line); Ok(Task { @@ -109,6 +115,8 @@ pub fn parse_line(raw: &str) -> Result { rec, threshold, notes, + id, + needs, }) } @@ -673,4 +681,11 @@ mod tests { assert_eq!(a.raw, b.raw); } } + + #[test] + fn parses_id_and_needs() { + let t = parse_line("Build wall id:wall needs:foundation").unwrap(); + assert_eq!(t.id.as_deref(), Some("wall")); + assert_eq!(t.needs.as_deref(), Some("foundation")); + } } diff --git a/src/ui/archive.rs b/src/ui/archive.rs index 77be08f8..f20ceb5c 100644 --- a/src/ui/archive.rs +++ b/src/ui/archive.rs @@ -98,6 +98,7 @@ pub fn render(frame: &mut Frame, area: Rect, app: &App) { match_term: None, today: app.today(), hidden_keys: &app.prefs.hidden_keys, + blocked: false, }; if i == app.cursor { cursor_line = Some(lines.len()); diff --git a/src/ui/help.rs b/src/ui/help.rs index 6c3659ec..a772f619 100644 --- a/src/ui/help.rs +++ b/src/ui/help.rs @@ -62,6 +62,7 @@ const VIEW: Section = ( ("A", "archive completed"), ("H", "show done in list"), ("F", "show future in list"), + ("B", "show blocked in list"), ("[ / ]", "toggle filter / detail"), ("T", "theme picker"), ("D", "cycle density"), diff --git a/src/ui/list.rs b/src/ui/list.rs index 9dbb205d..66dba8f4 100644 --- a/src/ui/list.rs +++ b/src/ui/list.rs @@ -68,6 +68,13 @@ pub fn render(frame: &mut Frame, area: Rect, app: &App) { let last = visible.len().saturating_sub(1); let mut last_group: Option<&GroupKey> = None; + let undone_ids: std::collections::HashSet<&str> = app + .tasks() + .iter() + .filter(|t| !t.done) + .filter_map(|t| t.id.as_deref()) + .collect(); + for (i, (&abs, gk)) in visible.iter().zip(groups.iter()).enumerate() { // Emit a section header on group transitions. `GroupKey::None` // means the active sort is `Sort::File`; we never render a header @@ -81,6 +88,10 @@ pub fn render(frame: &mut Frame, area: Rect, app: &App) { } let task = &app.tasks()[abs]; + let blocked = task + .needs + .as_deref() + .is_some_and(|dep| undone_ids.contains(dep)); let opts = task_row::RowOpts { idx_label: i, cursor: i == app.cursor && app.mode != Mode::Help && app.mode != Mode::Settings, @@ -91,6 +102,7 @@ pub fn render(frame: &mut Frame, area: Rect, app: &App) { match_term, today: app.today(), hidden_keys: &app.prefs.hidden_keys, + blocked, }; if i == app.cursor { cursor_line = Some(lines.len()); diff --git a/src/ui/settings.rs b/src/ui/settings.rs index 8cbf66f4..15775975 100644 --- a/src/ui/settings.rs +++ b/src/ui/settings.rs @@ -75,6 +75,10 @@ pub fn render(frame: &mut Frame, area: Rect, app: &App) { " show future in list", Some(format!("{} (F to toggle)", on(app.prefs.show_future))), ), + ( + " show blocked in list", + Some(format!("{} (B to toggle)", on(app.prefs.show_blocked))), + ), ("", Some("".into())), ("BEHAVIOR", None), ( diff --git a/src/ui/task_row.rs b/src/ui/task_row.rs index bbf38b81..525e023f 100644 --- a/src/ui/task_row.rs +++ b/src/ui/task_row.rs @@ -19,6 +19,9 @@ pub struct RowOpts<'a> { /// rendered body. Empty (the common case) means render everything, /// byte-for-byte as before. pub hidden_keys: &'a [String], + /// True when this task has a `needs:` reference pointing to an existing + /// undone task. + pub blocked: bool, } pub fn build_line<'a>(task: &'a Task, opts: RowOpts<'a>, theme: &Theme) -> Line<'a> { @@ -141,7 +144,11 @@ pub fn build_line<'a>(task: &'a Task, opts: RowOpts<'a>, theme: &Theme) -> Line< } else { Style::default() }; - Line::from(spans).style(line_style) + Line::from(spans).style(line_style.add_modifier(if opts.blocked { + Modifier::DIM + } else { + Modifier::empty() + })) } fn push_token_spans<'a>( @@ -389,6 +396,7 @@ mod tests { match_term: Some("a"), today: "2026-05-06", hidden_keys: &[], + blocked: false, }; // Build must not panic; we don't assert on the rendered spans. let _ = build_line(&task, opts, &MUTED); @@ -410,6 +418,7 @@ mod tests { match_term: Some("cade"), today: "2026-05-06", hidden_keys: &[], + blocked: false, }; let line = build_line(&task, opts, &MUTED); let highlight_bg = MUTED.matched; @@ -438,6 +447,7 @@ mod tests { match_term: None, today: "2026-05-06", hidden_keys: hidden, + blocked: false, }; let line = build_line(&task, opts, &MUTED); line.spans @@ -505,6 +515,7 @@ mod tests { match_term: None, today: "2026-05-06", hidden_keys: &[], + blocked: false, }; let line = build_line(&task, opts, &MUTED); let url_span = line @@ -536,6 +547,7 @@ mod tests { match_term: None, today: "2026-05-06", hidden_keys: &[], + blocked: false, }; let line = build_line(&task, opts, &MUTED); let url_span = line @@ -559,4 +571,26 @@ mod tests { "Pay rent due:2026-05-15", ); } + + #[test] + fn blocked_row_is_dimmed() { + let task = parse_line("Blocked task +project").unwrap(); + let opts = RowOpts { + idx_label: 0, + cursor: false, + multi_mode: false, + multi_checked: false, + selected: false, + show_line_num: false, + match_term: None, + today: "2026-05-06", + hidden_keys: &[], + blocked: true, + }; + let line = build_line(&task, opts, &MUTED); + assert!( + line.style.add_modifier.contains(Modifier::DIM), + "blocked row line style must carry DIM" + ); + } } diff --git a/tests/snapshots/snapshots__help_overlay_styled.snap b/tests/snapshots/snapshots__help_overlay_styled.snap index 5003caa2..26737d13 100644 --- a/tests/snapshots/snapshots__help_overlay_styled.snap +++ b/tests/snapshots/snapshots__help_overlay_styled.snap @@ -15,17 +15,17 @@ expression: buffer_to_styled(&buf) {fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}r {/}{fg=#c8ccd4 bg=#1f232b}reschedule task {/}{fg=#c89a6e bg=#1f232b mod=bold}A {/}{fg=#c8ccd4 bg=#1f232b}archive completed {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b}rd {/} {fg=#c89a6e bg=#1f232b mod=bold} CONT{/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}x {/}{fg=#c8ccd4 bg=#1f232b}toggle complete {/}{fg=#c89a6e bg=#1f232b mod=bold}H {/}{fg=#c8ccd4 bg=#1f232b}show done in list {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} {fg=#c8ccd4 bg=#1f232b} {/}{fg=#8aa9c9 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b}@e{/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}dd {/}{fg=#c8ccd4 bg=#1f232b}delete task {/}{fg=#c89a6e bg=#1f232b mod=bold}F {/}{fg=#c8ccd4 bg=#1f232b}show future in list {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} -{fg=#c8ccd4 bg=#1f232b} {/}{fg=#8aa9c9 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b}@h{/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}p {/}{fg=#c8ccd4 bg=#1f232b}cycle priority A→B→C→· {/}{fg=#c89a6e bg=#1f232b mod=bold}[ / ] {/}{fg=#c8ccd4 bg=#1f232b}toggle filter / detail{/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} -{fg=#c8ccd4 bg=#1f232b} {/}{fg=#8aa9c9 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b}@l{/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}J / K {/}{fg=#c8ccd4 bg=#1f232b}move task down / up {/}{fg=#c89a6e bg=#1f232b mod=bold}T {/}{fg=#c8ccd4 bg=#1f232b}theme picker {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} -{fg=#c8ccd4 bg=#1f232b} {/}{fg=#8aa9c9 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b}@p{/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}c {/}{fg=#c8ccd4 bg=#1f232b}add/remove context {/}{fg=#c89a6e bg=#1f232b mod=bold}D {/}{fg=#c8ccd4 bg=#1f232b}cycle density {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} -{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}+ {/}{fg=#c8ccd4 bg=#1f232b}add project {/}{fg=#c89a6e bg=#1f232b mod=bold}L {/}{fg=#c8ccd4 bg=#1f232b}toggle line numbers {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} -{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}yy / yb {/}{fg=#c8ccd4 bg=#1f232b}copy line / body {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} -{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}u {/}{fg=#c8ccd4 bg=#1f232b}undo {/}{fg=#8aa9c9 bg=#1f232b mod=bold}SYSTEM{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} -{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}: / Ctrl-P {/}{fg=#c8ccd4 bg=#1f232b}command palette {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} -{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#8aa9c9 bg=#1f232b mod=bold}RECURRENCE (rec:){/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}s {/}{fg=#c8ccd4 bg=#1f232b}share capture QR {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} -{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}j / k / Tab {/}{fg=#c8ccd4 bg=#1f232b}next / prev field {/}{fg=#c89a6e bg=#1f232b mod=bold}? / , {/}{fg=#c8ccd4 bg=#1f232b}help / settings {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} -{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}h / l / + / - {/}{fg=#c8ccd4 bg=#1f232b}change value {/}{fg=#c89a6e bg=#1f232b mod=bold}q / Ctrl-c {/}{fg=#c8ccd4 bg=#1f232b}quit {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} -{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}Enter / Esc {/}{fg=#c8ccd4 bg=#1f232b}save / cancel {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} +{fg=#c8ccd4 bg=#1f232b} {/}{fg=#8aa9c9 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b}@h{/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}p {/}{fg=#c8ccd4 bg=#1f232b}cycle priority A→B→C→· {/}{fg=#c89a6e bg=#1f232b mod=bold}B {/}{fg=#c8ccd4 bg=#1f232b}show blocked in list {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} +{fg=#c8ccd4 bg=#1f232b} {/}{fg=#8aa9c9 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b}@l{/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}J / K {/}{fg=#c8ccd4 bg=#1f232b}move task down / up {/}{fg=#c89a6e bg=#1f232b mod=bold}[ / ] {/}{fg=#c8ccd4 bg=#1f232b}toggle filter / detail{/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} +{fg=#c8ccd4 bg=#1f232b} {/}{fg=#8aa9c9 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b}@p{/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}c {/}{fg=#c8ccd4 bg=#1f232b}add/remove context {/}{fg=#c89a6e bg=#1f232b mod=bold}T {/}{fg=#c8ccd4 bg=#1f232b}theme picker {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} +{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}+ {/}{fg=#c8ccd4 bg=#1f232b}add project {/}{fg=#c89a6e bg=#1f232b mod=bold}D {/}{fg=#c8ccd4 bg=#1f232b}cycle density {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} +{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}yy / yb {/}{fg=#c8ccd4 bg=#1f232b}copy line / body {/}{fg=#c89a6e bg=#1f232b mod=bold}L {/}{fg=#c8ccd4 bg=#1f232b}toggle line numbers {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} +{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}u {/}{fg=#c8ccd4 bg=#1f232b}undo {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} +{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#8aa9c9 bg=#1f232b mod=bold}SYSTEM{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} +{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#8aa9c9 bg=#1f232b mod=bold}RECURRENCE (rec:){/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}: / Ctrl-P {/}{fg=#c8ccd4 bg=#1f232b}command palette {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} +{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}j / k / Tab {/}{fg=#c8ccd4 bg=#1f232b}next / prev field {/}{fg=#c89a6e bg=#1f232b mod=bold}s {/}{fg=#c8ccd4 bg=#1f232b}share capture QR {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} +{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}h / l / + / - {/}{fg=#c8ccd4 bg=#1f232b}change value {/}{fg=#c89a6e bg=#1f232b mod=bold}? / , {/}{fg=#c8ccd4 bg=#1f232b}help / settings {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} +{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}Enter / Esc {/}{fg=#c8ccd4 bg=#1f232b}save / cancel {/}{fg=#c89a6e bg=#1f232b mod=bold}q / Ctrl-c {/}{fg=#c8ccd4 bg=#1f232b}quit {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} {fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│────────────────────────────────────────────────────────────────────────────────────────│{/}{fg=#c8ccd4 bg=#1f232b} {/} {fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#8aa9c9 bg=#1f232b mod=bold}FORMAT{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} {fg=#c8ccd4 bg=#1f232b} {/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/}{fg=#c89a6e bg=#1f232b mod=bold}(A) {/}{fg=#c8ccd4 bg=#1f232b}priority A-Z {/}{fg=#c89a6e bg=#1f232b mod=bold}due:YYYY-MM-DD {/}{fg=#c8ccd4 bg=#1f232b}due date (+1w = range){/}{fg=#2a2f38 bg=#1f232b}│{/}{fg=#c8ccd4 bg=#1f232b} {/} diff --git a/tests/snapshots/snapshots__help_overlay_text.snap b/tests/snapshots/snapshots__help_overlay_text.snap index d8010e2b..a0da1563 100644 --- a/tests/snapshots/snapshots__help_overlay_text.snap +++ b/tests/snapshots/snapshots__help_overlay_text.snap @@ -15,17 +15,17 @@ expression: buffer_to_text(&buf) │ r reschedule task A archive completed │rd CONT│ x toggle complete H show done in list │ @e│ dd delete task F show future in list │ - @h│ p cycle priority A→B→C→· [ / ] toggle filter / detail│ - @l│ J / K move task down / up T theme picker │ - @p│ c add/remove context D cycle density │ - │ + add project L toggle line numbers │ - │ yy / yb copy line / body │ - │ u undo SYSTEM │ - │ : / Ctrl-P command palette │ - │ RECURRENCE (rec:) s share capture QR │ - │ j / k / Tab next / prev field ? / , help / settings │ - │ h / l / + / - change value q / Ctrl-c quit │ - │ Enter / Esc save / cancel │ + @h│ p cycle priority A→B→C→· B show blocked in list │ + @l│ J / K move task down / up [ / ] toggle filter / detail│ + @p│ c add/remove context T theme picker │ + │ + add project D cycle density │ + │ yy / yb copy line / body L toggle line numbers │ + │ u undo │ + │ SYSTEM │ + │ RECURRENCE (rec:) : / Ctrl-P command palette │ + │ j / k / Tab next / prev field s share capture QR │ + │ h / l / + / - change value ? / , help / settings │ + │ Enter / Esc save / cancel q / Ctrl-c quit │ │────────────────────────────────────────────────────────────────────────────────────────│ │ FORMAT │ │ (A) priority A-Z due:YYYY-MM-DD due date (+1w = range)│ diff --git a/tests/snapshots/snapshots__settings_overlay_styled.snap b/tests/snapshots/snapshots__settings_overlay_styled.snap index 9b8f5723..a2cca3cd 100644 --- a/tests/snapshots/snapshots__settings_overlay_styled.snap +++ b/tests/snapshots/snapshots__settings_overlay_styled.snap @@ -17,6 +17,7 @@ expression: buffer_to_styled(&buf) {fg=#c8ccd4 bg=#1a1d23} detail sidebar {/}{fg=#6b7280 bg=#1a1d23}on (] to toggle){/}{fg=#c8ccd4 bg=#1a1d23} {/} {fg=#c8ccd4 bg=#1a1d23} show done in list {/}{fg=#6b7280 bg=#1a1d23}off (H to toggle){/}{fg=#c8ccd4 bg=#1a1d23} {/} {fg=#c8ccd4 bg=#1a1d23} show future in list {/}{fg=#6b7280 bg=#1a1d23}off (F to toggle){/}{fg=#c8ccd4 bg=#1a1d23} {/} +{fg=#c8ccd4 bg=#1a1d23} show blocked in list {/}{fg=#6b7280 bg=#1a1d23}on (B to toggle){/}{fg=#c8ccd4 bg=#1a1d23} {/} {fg=#c8ccd4 bg=#1a1d23} {/} {fg=#c8ccd4 bg=#1a1d23} {/}{fg=#8aa9c9 bg=#1a1d23 mod=bold}BEHAVIOR{/}{fg=#c8ccd4 bg=#1a1d23} {/} {fg=#c8ccd4 bg=#1a1d23} default sort {/}{fg=#6b7280 bg=#1a1d23}priority (S to cycle){/}{fg=#c8ccd4 bg=#1a1d23} {/} @@ -32,5 +33,4 @@ expression: buffer_to_styled(&buf) {fg=#c8ccd4 bg=#1a1d23} {/} {fg=#c8ccd4 bg=#1a1d23} {/} {fg=#c8ccd4 bg=#1a1d23} {/} -{fg=#c8ccd4 bg=#1a1d23} {/} {fg=#1a1d23 bg=#8aa9c9 mod=bold} SETTINGS {/}{bg=#252a33} {/}{fg=#a8b0bc bg=#252a33}Esc back{/}{bg=#252a33} {/}{fg=#6b7280 bg=#252a33}15 open · 2026-05-06 · tuxedo 2026.8.1 {/} diff --git a/tests/snapshots/snapshots__settings_overlay_text.snap b/tests/snapshots/snapshots__settings_overlay_text.snap index 78d77c0b..9a6d3565 100644 --- a/tests/snapshots/snapshots__settings_overlay_text.snap +++ b/tests/snapshots/snapshots__settings_overlay_text.snap @@ -17,6 +17,7 @@ expression: buffer_to_text(&buf) detail sidebar on (] to toggle) show done in list off (H to toggle) show future in list off (F to toggle) + show blocked in list on (B to toggle) BEHAVIOR default sort priority (S to cycle) @@ -32,5 +33,4 @@ expression: buffer_to_text(&buf) - SETTINGS Esc back 15 open · 2026-05-06 · tuxedo 2026.8.1