fix(list): include projects in someday and logbook - #209
Merged
Merged
Conversation
The someday and logbook view filters were pinned to t.type = 0, so a project deferred to Someday, or a completed project in the Logbook, was excluded by construction while the app shows both. Both views now select t.type IN (0, 1) through the todoOrProject constant #205 introduced for today, upcoming and anytime. Nothing else about the two views moved: repeating templates and the to-dos inside them stay out of someday, trashed rows stay out of both, and headings stay out. inbox, trash, deadlines and the bare-filter view stay to-do only. Closes #206
This was referenced Sep 10, 2026
ryanlewis
added a commit
that referenced
this pull request
Sep 10, 2026
Closes #212 ## What changed `trash` now lists projects alongside to-dos, the same rule #205 and #209 applied to the other views. Trashing a project in Things puts the project row itself in the app's Trash, and `things projects` filters trashed rows, so a trashed project was visible nowhere in the CLI — excluded by construction, not by data. The view now selects `t.type IN (0, 1)` through the existing `todoOrProject` constant. Nothing else about it moved: headings (type 2) stay out, and `trash` keeps the literal reading of the database it shares with `logbook`, so the children of a trashed project and any repeating template that ends up in the bin still list as they are stored. `inbox` and `deadlines` stay to-do only. #213 covers `deadlines`. ## Telling a project row from a to-do No output change was needed. `printTasks` already appends a dim `(project)` for project rows, and since #214 the JSON carries `"type": "project"`. ## Verified against the real database Read the live database with `sqlite3 -readonly`, and compared against the app's own list membership via AppleScript (`id of to dos of list "Trash"`), by uuid rather than title. | | count | |---|---| | App's Trash list | 162 | | CLI `list trash` before | 148 | | CLI `list trash` after | 165 | | Trashed project rows gained | 17 | All 17 project rows the CLI now returns are in the app's Trash, and nothing the app shows is missing from the CLI. No false positives: the database holds no trashed headings and no trashed repeating templates, so neither could leak in. The 3 rows the CLI returns that the app does not are unchanged by this PR. All three are to-dos whose parent project is itself trashed; the app shows the trashed project as one row and folds its children into it. They were already returned before this change, under the `viewsIncludingTrashedProjects` decision from #155, and narrowing that is a separate question from this issue. ## Tests Three tests in `internal/db/tasks_test.go` over a new `seedTrashedProjects` helper: trashed projects of both statuses are listed and carry `model.TypeProject`; headings and live rows of either kind stay out; `--area` matches a trashed project through its own area while `--project` cannot. `TestTrashAndLogbookKeepTrashedProjectChildren` seeds a trashed project as the parent of its fixture, so its trash expectation gained the project row it was already seeding. Its subject, that the child is kept, is unchanged. Docs updated in `internal/skill/SKILL.md`, `docs/content/commands.md` and `docs/content/agents.md`: the sentence naming which views carry projects, and the repeating-template note that said a project template never reaches `trash`.
This was referenced Sep 10, 2026
ryanlewis
added a commit
that referenced
this pull request
Sep 10, 2026
Closes #213 ## What changed `deadlines` now lists projects alongside to-dos. A project takes a deadline exactly as a to-do does, `things projects -j` has reported it since #204, and agents.md advertises this view as the way to sweep what is due — so an agent following the docs missed every project deadline. The view was pinned to `t.type = 0`, so those rows were excluded by construction, not by data. The view now selects `t.type IN (0, 1)` through the existing `todoOrProject` constant, the same rule #205, #209 and #216 applied to the other views. Its `ORDER BY t.deadline ASC` is unchanged, so project rows fall in among the to-dos by date rather than forming a block of their own. `--on`, `--from` and `--to` already filter `t.deadline` on this view and now filter project rows by their own deadline. `inbox` is now the only named view that stays to-do only, which is a property of the Inbox rather than a limitation: an inbox item has not been filed anywhere yet, so it is never a project. A bare `-p`/`-a`/`-t` filter with no view named is the other exception, and the docs now say so explicitly — it routes to the internal catch-all view, which is still pinned to to-dos. ## Verification: negative only, and worth being plain about There is nothing on this database that exercises the change, and I would rather say so than imply a check I did not make. The app has no Deadlines list to compare against, so the fallback is `things projects -j`, which reports no project deadlines at all. Reading the live database with `sqlite3 -readonly` shows why. Seven projects carry a deadline, and every one is already excluded by the `status = 0 AND trashed = 0` clause this PR does not touch: | why excluded | count | |---|---| | completed or cancelled | 4 | | trashed | 3 | | would list | 0 | So `things deadlines` returns the same 13 to-do rows before and after this change, and the SQL the new filter generates selects those same 13. What I have verified is that no project is wrongly admitted and none is wrongly held back for a reason this PR introduces. What I have not verified is a project deadline appearing in the app and then in the CLI, because no such project exists here. The tests below cover what the live data cannot. ## Tests Six tests in `internal/db/tasks_test.go` over a new `seedDeadlines` helper. The fixture interleaves a project between two to-dos by deadline while running `"index"` against that order, so an ordering that fell back to `"index"` or grouped by type would fail: - projects with a deadline are listed and carry `model.TypeProject` - the project sorts between the two to-dos, by deadline - a project with no deadline, a completed one, a trashed one and a heading all stay out - `--on` matches a project row by its own deadline - `--area` finds a project through its own area; `--project` cannot match one - a repeating project template with a deadline, and the to-do inside it, stay out — the one exclusion path that comes from `viewsIncludingTemplates` rather than the view filter ## Docs `internal/skill/SKILL.md`, `docs/content/commands.md`, `docs/content/agents.md` and `README.md`: the view lists now say every named view except `inbox` carries projects, and name the bare-filter exception so an agent knows to name a view when project rows matter. The README gains the general rule it never stated, so its `deadlines` table row no longer reads as though that view is the only one carrying projects. `internal/output/output.go` had a comment enumerating the old set of views.
This was referenced Sep 10, 2026
ryanlewis
added a commit
that referenced
this pull request
Sep 10, 2026
Closes #210 ## What changed `logbook` now returns cancelled items alongside completed ones. The Logbook is where Things files everything *closed*, not just everything *finished*: cancelling a to-do or a project logs it under its stop date beside the completed rows, and the app shows both. The view filtered `t.status = 3` alone, so cancelled rows never appeared. The filter is now `t.status IN (2, 3)`. Nothing else about the view moved: it keeps `ORDER BY t.stopDate DESC`, so cancelled rows interleave with completed ones by date rather than forming a block, and it keeps the literal reading of the database it shares with `trash`. Callers tell the two apart by `status`, which needed no output change. JSON already carries `"completed"` or `"cancelled"`, and plain output already prints `[x]` and `[~]`. ## Verified against the real database Read the live database with `sqlite3 -readonly`, compared against the app's own Logbook membership via AppleScript (`id of to dos of list "Logbook"`), by uuid. | | count | |---|---| | App's Logbook | 910 | | CLI `list logbook` before | 1190 | | CLI `list logbook` after | 1334 | | Rows the app shows that the CLI was missing, before | 39 | | Rows the app shows that the CLI is missing, now | 0 | The CLI now returns every row the app's Logbook shows. The 39 recovered are 33 cancelled to-dos and 6 cancelled projects, and all 6 projects are in the app's list. Both uuids named in the issue, `3p5oMeS6jVjUTNCRhuPEuW` and `PtV3Uvf1rRB7326cFPYvEN`, are among them. Stop-date ordering stays monotonically non-increasing across the widened set, with cancelled rows interleaved rather than blocked. `--include-completed` is unaffected: it is rejected on any view but `today`, `logbook` included, and still is. ## What this does not fix The CLI returns 1334 rows where the app shows 910, and the 424-row difference is not what this issue is about. I measured where it comes from, because it would otherwise look like this PR made the gap worse: - **341 rows** are the children of a closed or trashed project. The app shows the closed project as one row and folds its contents into it; the CLI lists each child separately. 292 of those are completed and were already returned before this PR; 49 are cancelled and arrive with it. - **6 rows** were closed today. The app keeps them under Today until `manualLogDate` advances, which is the same rule `--include-completed` already implements for the today view. - The rest are the children of trashed projects and template rows, which `trash` and `logbook` report deliberately under the decisions in #155 and #209. None of these are cancelled-specific and all predate this change in kind. They are worth their own issues. ## Tests Five tests in `internal/db/tasks_test.go` over a new `seedLogbookCancelled` helper, whose stop dates interleave the two statuses while `"index"` runs against that order, so a view that grouped by status or fell back to `"index"` would fail: - cancelled to-dos and cancelled projects are listed, and `status` and `type` identify each - cancelled rows sort by stop date among the completed ones - open rows, a trashed cancelled row and a cancelled heading stay out - `--area` finds a cancelled row through its area; `--project` cannot match a project row - cancelled rows do not leak into `today`, `upcoming`, `anytime`, `someday` or the catch-all view, each seeded with a cancelled row shaped to land in that view but for its status, so only the `t.status = 0` clause keeps it out. Mutation-checked: dropping that clause from `someday` or `upcoming` fails the test. `TestListTasksViews` expected `logbook` to hold only `t-done`, while its own fixture comment already labelled `t-cancelled` as a logbook row. That expectation now matches the comment. ## Docs `internal/skill/SKILL.md`, `docs/content/commands.md`, `docs/content/agents.md` and `README.md`: the Logbook is described as everything closed rather than everything completed, with `status` named as the way to separate the two and a `jq` filter for agents that mean finished rather than closed. The review also caught that every `jq 'select(...)'` example in the agent-facing docs was broken, including ones this PR did not add. `-j` emits a JSON array, so `jq 'select(.status=="completed")'` aborts with `Cannot index array with string`. All of them now read `jq '.[] | select(...)'`, which I checked by running both forms. Those examples ship in-binary for agents to copy literally, so leaving the pre-existing ones wrong while fixing the new one would have been worse than fixing the set.
ryanlewis
added a commit
that referenced
this pull request
Sep 10, 2026
Closes #222 ## What changed The internal catch-all view — the one a bare `--project`/`--area`/`--tag` filter routes to when no view is named — was still pinned to `t.type = 0` while every named view except `inbox` had been widened to `todoOrProject` (#205, #209, #216, #220). It now uses the same set. `--project` is unaffected in practice: a project has no parent project of its own, so `p.uuid` never matches a project row and the filter still returns a project's contents. The visible change is for `--area` and `--tag`. ## Why An agent sweeping an area with `things --area Work -j` got none of that area's projects, which is exactly the miss #222 describes. Parity with what Things.app shows in the matching list is the project's stated goal. ## How verified Measured by uuid against Things.app on 10 Sep 2026. For one area the app reports four projects; the CLI returned zero project rows before this change and returns the same four uuids after. `things --project <uuid> -j` still returns zero project rows. Tests added in `internal/db/tasks_test.go` cover the widened set, the `--area`/`--tag`/`--project` split, and the exclusions that must not widen with it — headings, trashed projects, repeating project templates and the to-dos inside them. Four existing tests asserted the old to-do-only behaviour and now assert the project rows: two area filters, the catch-all grouping order, and the CLI-level default-view test. `make test` and `make lint` are clean. ## Docs The paragraph naming the bare-filter exception was rewritten once, in `internal/skill/SKILL.md`, `docs/content/commands.md`, `docs/content/agents.md` and the README. It now states the `--project` case rather than a whole-view exception.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #206
What changed
somedayandlogbooknow list projects alongside to-dos, the same rule #205 applied totoday,upcomingandanytime. A project is deferred exactly as a to-do is deferred, and completed exactly as a to-do is completed, so the app shows the project itself in Someday and in the Logbook. The CLI's two view filters were pinned tot.type = 0, so those project rows were excluded by construction, not by data.Both views now select
t.type IN (0, 1)through the existingtodoOrProjectconstant. Nothing else about them moved: repeating templates and the to-dos inside them stay out ofsomeday, trashed rows stay out of both, and headings (type 2) stay out.logbookkeeps its literal reading of the database — it is still one of the two views that report trashed-project children and templates as they are stored.inbox,trash,deadlinesand the bare-filter view stay to-do only.Telling a project row from a to-do
No new marker was needed, and no output change either.
printTasksalready appends a dim(project)fortype = 1rows, and the header-folding fix from #205 is view-agnostic, so a completed project sitting directly above its own completed to-dos in the Logbook prints its title once.Verified against the real database
Read from a checkpointed copy of the live Things database, compared against the app's own list membership via AppleScript (
id of to dos of list "Logbook"/list "Someday"), uuids not titles.logbook: 36 project rows appear that the previous build did not return, and all 36 are in the app's Logbook list. No false positives. Total rows go from 1154 to 1190. The newest isL16wNJD3D84AEJwr14QWni"OQ-11 — Salesforce to AWS threat model", which is also the first row of the app's Logbook.someday: no project currently sits in Someday, so this was checked by inserting one row into a copy of the real database.PROBE-someday-projectis absent before the change and present after, with(project)in plain output. The six rows the app does show in Someday all havestart = 2,startDate IS NULL,status = 0and no parent project, which is the filter minus the type pin.How tested
make test(race) andmake lint(0 issues) both pass. New tests, all confirmed to fail with the filters reverted:internal/db: a deferred project appears insomedayand a completed project inlogbook, both withtype = model.TypeProject; a Someday-start repeating project template, the to-dos inside it, trashed projects and headings stay out; the Logbook order still keys onstopDatewith a project in it;--projectnever matches a project row while--areadoes.cmd/things:things somedayandthings logbookend to end — JSON carries the project row, plain text marks it(project)and does not mark to-dos.No new
internal/outputtest. The header-folding behaviour these views can hit is already covered byTestPrintTasksNoRepeatedProjectHeader, which is written againstPrintrather than a view.Docs
internal/skill/SKILL.md(the "Output and--json" list and thethings listblock),docs/content/commands.md(Listing) anddocs/content/agents.md— extending the wording #205 added rather than adding a parallel paragraph.Two knock-on corrections in both SKILL.md and commands.md. They said
trashandlogbookare to-do lists, so a project template never shows in either;logbooknow carries projects, so that is only true oftrash. And "the other views stay to-do only" now names which ones, sincerepeatinghas carried project templates since #165.The comment on the header fold in
internal/output/output.gonamed only the three views from #205 and said a project's to-dos sort straight after it. It now names all five and says the fold applies where the view's order puts them there, which is what the code actually tests for.Not addressed here
Two Someday/Logbook parity gaps found while verifying, both pre-existing and unrelated to project rows:
logbookfilter ist.status = 3, so cancelled items never appear, but the app's Logbook shows them. 138 cancelled to-dos and 6 cancelled projects are missing.things somedayreturns 8 to-dos that the app's Someday list does not show. Each is a Someday to-do inside an Anytime project; the app keeps those inside the project rather than in the global Someday list.trashanddeadlinesare the two views left pinned tot.type = 0. Trashing a project in the app leaves no CLI route to see it, sincethings projectsfilters trashed rows andtrashexcludes projects. Andthings deadlinesskips a project with a deadline, thoughthings projectsreportsdeadlinesince #204. Both are behaviour changes outside this issue and want their own tests.