Skip to content

fix(list): include projects in someday and logbook - #209

Merged
ryanlewis merged 1 commit into
mainfrom
fix/issue-206-someday-logbook-projects
Sep 10, 2026
Merged

ryanlewis merged 1 commit into
mainfrom
fix/issue-206-someday-logbook-projects

Conversation

@ryanlewis

Copy link
Copy Markdown
Owner

Closes #206

What changed

someday and logbook now list projects alongside to-dos, the same rule #205 applied to today, upcoming and anytime. 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 to t.type = 0, so those project rows were excluded by construction, not by data.

Both views now select t.type IN (0, 1) through the existing todoOrProject constant. Nothing else about them moved: repeating templates and the to-dos inside them stay out of someday, trashed rows stay out of both, and headings (type 2) stay out. logbook keeps 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, deadlines and 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. printTasks already appends a dim (project) for type = 1 rows, 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 is L16wNJD3D84AEJwr14QWni "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-project is absent before the change and present after, with (project) in plain output. The six rows the app does show in Someday all have start = 2, startDate IS NULL, status = 0 and no parent project, which is the filter minus the type pin.

How tested

make test (race) and make lint (0 issues) both pass. New tests, all confirmed to fail with the filters reverted:

  • internal/db: a deferred project appears in someday and a completed project in logbook, both with type = model.TypeProject; a Someday-start repeating project template, the to-dos inside it, trashed projects and headings stay out; the Logbook order still keys on stopDate with a project in it; --project never matches a project row while --area does.
  • cmd/things: things someday and things logbook end to end — JSON carries the project row, plain text marks it (project) and does not mark to-dos.

No new internal/output test. The header-folding behaviour these views can hit is already covered by TestPrintTasksNoRepeatedProjectHeader, which is written against Print rather than a view.

Docs

internal/skill/SKILL.md (the "Output and --json" list and the things list block), docs/content/commands.md (Listing) and docs/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 trash and logbook are to-do lists, so a project template never shows in either; logbook now carries projects, so that is only true of trash. And "the other views stay to-do only" now names which ones, since repeating has carried project templates since #165.

The comment on the header fold in internal/output/output.go named 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:

  • The logbook filter is t.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 someday returns 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.

trash and deadlines are the two views left pinned to t.type = 0. Trashing a project in the app leaves no CLI route to see it, since things projects filters trashed rows and trash excludes projects. And things deadlines skips a project with a deadline, though things projects reports deadline since #204. Both are behaviour changes outside this issue and want their own tests.

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
@ryanlewis
ryanlewis merged commit 88ca167 into main Sep 10, 2026
10 checks passed
@ryanlewis
ryanlewis deleted the fix/issue-206-someday-logbook-projects branch September 10, 2026 10:32
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`.
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.
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: things list someday and logbook omit projects that Things.app shows there

1 participant