fix(list): include projects in trash - #216
Merged
Merged
Conversation
Closes #212 Trashing a project in Things puts the project row itself in the app's Trash, but the CLI's trash view pinned t.type = 0, so it never appeared. `things projects` filters trashed rows too, which left a trashed project 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, the same rule #205 and #209 applied to the other views. Headings 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 in the bin still list as they are stored. Verified against the live database with sqlite3 -readonly, compared by uuid against the app's own Trash membership via AppleScript: the app lists 162 items, the CLI listed 148 before and 165 after, and all 17 project rows gained are in the app's Trash with no false positives. inbox and deadlines stay to-do only; #213 covers deadlines.
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 #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.
ryanlewis
added a commit
that referenced
this pull request
Sep 10, 2026
Closes #217 ## What changed Three changes to the two views the app arranges differently from the CLI, all measured against it on 10 Sep 2026 by uuid. **`anytime` no longer lists project rows.** This is a revision of what #205 and #216 widened, for this view alone. Every active project is trivially "anytime", so listing them all buries the to-dos; the app uses the project as the group header above its own to-dos instead. Measured: the app's Anytime held **none** of the 23 active projects as a row, and all 107 of their to-dos, which the CLI already matched exactly. `today` keeps its project rows, and so do `upcoming` and `someday`, where a project has actually been put somewhere. **`anytime` is ordered to match that arrangement:** items filed nowhere lead, then areas in area order, and inside an area its own loose to-dos before those of its projects. It listed in bare `t."index"` order before, which scattered each project's to-dos and made the rendered group header repeat. **`upcoming` reads by date**, then by the within-day `todayIndex` the app also keys Today on. Bare `t."index"` order interleaved the dates. ## Getting the keys right took two corrections Worth recording, because both were wrong in ways that still produced plausible-looking output: 1. Sorting on the area index alone put the unfiled items **last**. Things writes its indexes negative, so the COALESCE default of 0 that stands for "not filed here" outranks every real area. 2. Sorting on the project index alone put each area's project to-dos **before** its own loose ones, for the same reason. The app does the opposite. Each key is therefore a CASE rather than a plain index. ## How verified By uuid against the app on live data: | view | rows | order vs the app | | --- | --- | --- | | anytime | 105 shared | identical for all 105 | | upcoming | 30 | identical for all 30, no membership difference either way | Plain output now renders the app's shape: unfiled items, then each area, then each project name once as a group header above its to-dos. ## Deliberately left alone `today` and the catch-all `--project`/`--area`/`--tag` ordering are byte-identical to before. An earlier revision of this branch had factored their ORDER BY into shared constants; I backed that out, because neither was asked to change and `today` is already at parity. Two ordering gaps are recorded in the comments rather than guessed at, both raised by `/code-review`: - A to-do inside a project that carries **no area** falls to the COALESCE default and sorts after every area. There is no such project in this data, so the app's answer could not be measured. - `upcoming` orders on a nullable `todayIndex`, and SQLite sorts NULL first. No upcoming row carries a NULL, and the `today` ordering has the same shape, so changing one and not the other would invent a rule rather than match one. One consequence worth a decision, not fixed here: because `upcoming` now orders by date, a project with to-dos on several days gets its group header re-printed per day, since the CLI has no day header to group under the way the app does. ## Docs The "which views carry projects" paragraph was rewritten once in `internal/skill/SKILL.md`, `docs/content/commands.md`, `docs/content/agents.md` and the README: every named view except `inbox` and `anytime`. Each says why `anytime` is the exception and points at `things projects` for sweeping projects themselves. `make test` and `make lint` are clean.
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 #212
What changed
trashnow 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, andthings projectsfilters 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 existingtodoOrProjectconstant. Nothing else about it moved: headings (type 2) stay out, andtrashkeeps the literal reading of the database it shares withlogbook, so the children of a trashed project and any repeating template that ends up in the bin still list as they are stored.inboxanddeadlinesstay to-do only. #213 coversdeadlines.Telling a project row from a to-do
No output change was needed.
printTasksalready 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.list trashbeforelist trashafterAll 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
viewsIncludingTrashedProjectsdecision from #155, and narrowing that is a separate question from this issue.Tests
Three tests in
internal/db/tasks_test.goover a newseedTrashedProjectshelper: trashed projects of both statuses are listed and carrymodel.TypeProject; headings and live rows of either kind stay out;--areamatches a trashed project through its own area while--projectcannot.TestTrashAndLogbookKeepTrashedProjectChildrenseeds 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.mdanddocs/content/agents.md: the sentence naming which views carry projects, and the repeating-template note that said a project template never reachestrash.