From c5019fd566da5861dafc932d4ca9792cf33b6e46 Mon Sep 17 00:00:00 2001 From: Peter Peirce Date: Mon, 9 Feb 2026 15:14:26 -0500 Subject: [PATCH] feat: add `open` filter to show all incomplete reminders The existing filters don't cover incomplete reminders without due dates. `upcoming` requires a due date, so undated items (e.g. grocery lists) are only visible via `all` which includes completed items too. `remindctl open` returns all uncompleted reminders regardless of due date status. Co-Authored-By: Claude Opus 4.6 --- README.md | 1 + Sources/RemindCore/ReminderFilter.swift | 5 +++++ Sources/remindctl/Commands/ShowCommand.swift | 4 ++-- Tests/RemindCoreTests/ReminderFilterParseTests.swift | 1 + Tests/RemindCoreTests/ReminderFilteringTests.swift | 10 ++++++++++ docs/manual-tests.md | 2 +- 6 files changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7b6a444..7f6ec33 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ remindctl tomorrow # show tomorrow remindctl week # show this week remindctl overdue # overdue remindctl upcoming # upcoming +remindctl open # all incomplete reminders remindctl completed # completed remindctl all # all reminders remindctl 2026-01-03 # specific date diff --git a/Sources/RemindCore/ReminderFilter.swift b/Sources/RemindCore/ReminderFilter.swift index d602373..b65b953 100644 --- a/Sources/RemindCore/ReminderFilter.swift +++ b/Sources/RemindCore/ReminderFilter.swift @@ -6,6 +6,7 @@ public enum ReminderFilter: Equatable, Sendable { case week case overdue case upcoming + case open case completed case date(Date) case all @@ -25,6 +26,8 @@ public enum ReminderFiltering { return .overdue case "upcoming", "u": return .upcoming + case "open": + return .open case "completed", "done", "c": return .completed case "all", "a": @@ -76,6 +79,8 @@ public enum ReminderFiltering { return reminders.filter { reminder in !reminder.isCompleted && reminder.dueDate != nil } + case .open: + return reminders.filter { !$0.isCompleted } case .completed: return reminders.filter { $0.isCompleted } case .date(let date): diff --git a/Sources/remindctl/Commands/ShowCommand.swift b/Sources/remindctl/Commands/ShowCommand.swift index c484634..04c4033 100644 --- a/Sources/remindctl/Commands/ShowCommand.swift +++ b/Sources/remindctl/Commands/ShowCommand.swift @@ -7,13 +7,13 @@ enum ShowCommand { CommandSpec( name: "show", abstract: "Show reminders", - discussion: "Filters: today, tomorrow, week, overdue, upcoming, completed, all, or a date string.", + discussion: "Filters: today, tomorrow, week, overdue, upcoming, open, completed, all, or a date string.", signature: CommandSignatures.withRuntimeFlags( CommandSignature( arguments: [ .make( label: "filter", - help: "today|tomorrow|week|overdue|upcoming|completed|all|", + help: "today|tomorrow|week|overdue|upcoming|open|completed|all|", isOptional: true ) ], diff --git a/Tests/RemindCoreTests/ReminderFilterParseTests.swift b/Tests/RemindCoreTests/ReminderFilterParseTests.swift index 3519a2c..a644716 100644 --- a/Tests/RemindCoreTests/ReminderFilterParseTests.swift +++ b/Tests/RemindCoreTests/ReminderFilterParseTests.swift @@ -10,6 +10,7 @@ struct ReminderFilterParseTests { #expect(ReminderFiltering.parse("w") == .week) #expect(ReminderFiltering.parse("o") == .overdue) #expect(ReminderFiltering.parse("u") == .upcoming) + #expect(ReminderFiltering.parse("open") == .open) #expect(ReminderFiltering.parse("done") == .completed) #expect(ReminderFiltering.parse("all") == .all) } diff --git a/Tests/RemindCoreTests/ReminderFilteringTests.swift b/Tests/RemindCoreTests/ReminderFilteringTests.swift index 35d45f2..bc53c3d 100644 --- a/Tests/RemindCoreTests/ReminderFilteringTests.swift +++ b/Tests/RemindCoreTests/ReminderFilteringTests.swift @@ -108,6 +108,16 @@ struct ReminderFilteringTests { #expect(result.count == 3) } + @Test("Open filter includes no due date and excludes completed") + func openFilter() { + let now = Date(timeIntervalSince1970: 1_700_000_000) + let items = reminders(now: now) + let result = ReminderFiltering.apply(items, filter: .open, now: now, calendar: calendar) + #expect(result.count == 4) + #expect(result.contains(where: { $0.title == "No Due" })) + #expect(result.allSatisfy { !$0.isCompleted }) + } + @Test("Date filter") func dateFilter() { let now = Date(timeIntervalSince1970: 1_700_000_000) diff --git a/docs/manual-tests.md b/docs/manual-tests.md index 45499ef..c4d2967 100644 --- a/docs/manual-tests.md +++ b/docs/manual-tests.md @@ -16,7 +16,7 @@ Run on a local GUI session (not SSH-only) so the Reminders permission prompt can - list lists: `remindctl list` - list list contents: `remindctl list "remindctl-manual-YYYYMMDD"` - add reminders (3 variants) -- show filters: `today`, `tomorrow`, `week`, `overdue`, `upcoming`, `completed`, `all` +- show filters: `today`, `tomorrow`, `week`, `overdue`, `upcoming`, `open`, `completed`, `all` - edit: update title/notes/priority/due date - complete: mark one reminder complete - delete: remove reminders, then delete list