Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

- **CHANGE (breaking, ALPHA) — the `area show` / `get_area` view dissolves its direct-to-do buckets into ONE flat `items[]`; `projects[]` is kept.** Fourth of five PRs implementing the read-shape doctrine (`docs/design/read-shape-doctrine.md` §3.13). An area has TWO order axes (project sidebar rank, direct-to-do index), so it keeps two flat lists.
- **`data.view.items`** — ONE flat list of every direct to-do in area `index` order, each row carrying `stage` (KEPT — the list is stage-mixed) and `when`, `area` dropped (the card states it). The `anytime` / `upcoming` / `someday` direct-to-do buckets are GONE. Area direct to-dos are never headed nor project-nested, so they are fully self-describing.
- **`data.view.projects`** — UNCHANGED (the area's child-project rows in sidebar order, mixed-stage, someday/scheduled projects included). This is the DISTINCT order axis; the `projects` / someday-projects / direct / upcoming / loose render variants collapse to TTY projections of `projects[]` + `items[]`.
- **Truncation / caps unchanged:** the per-block caps still apply (`project_limit` on project rows, `area_limit` on the open/current direct to-dos); the `area` block reports the open/current direct-to-dos hidden, and the flat `items[]` drops exactly those (the later scheduled/someday rows always ride `items[]` — the `--show-later` toggle is TTY-only, never gated the wire).
- **TTY + truncation byte-stable** — verified byte-identical (incl. the `area_limit` cut) on a rich fixture + the full render/loose suites; the library retains its structured direct-to-do grouping for the GUI projection, only the wire flattened. Removed the now-dead `rebucketChildren` card-bucketing helper (no wire surface has stage/date sub-buckets anymore). (`src/read/area-view.ts` adds `items`, `src/read/truncation.ts` `capAreaSections`, `src/read/shape.ts` `shapeAreaView`.) Tests: `test/unit/{shape,truncation}.test.ts`, `test/cli/{e2e,loose-pseudo-area,render}.test.ts`, `test/mcp/server.test.ts`. Docs: `docs/design/read-shape-doctrine.md`, `docs/contract.md`, `docs/design/contracts.md`, MCP `get_area`/`get_project` descriptions. No new `apiVersion`; no new error code.

- **CHANGE (breaking, ALPHA) — the `project show` / `get_project` `headings[]` becomes a flat catalog and `logbookHeadings` dissolves into the flat `logbook`.** Third of five PRs implementing the read-shape doctrine (`docs/design/read-shape-doctrine.md` §3.12, rulings #C3/#C3a/#C4).
- **`headings[]` is now the flat catalog** `[{uuid,title,archived?}]` — EVERY heading (live AND swept archived) in project `index` order (the order axis). The PR-2 `{heading: node}` wrapper is unwrapped. A swept archived heading is now a catalog entry carrying `archived` (it was previously only inside `logbookHeadings`). Under a content scope (`--overdue`/`--tag`) a heading whose children all filtered out collapses. A heading catalog entry carries only `archived` — never `stage`/`status`; sweptness is TTY-derived, not on the wire (#C3a).
- **`logbookHeadings` is GONE.** Its archived-heading children fold into the single flat **`logbook`** bucket — now ONE `stopDate DESC` list of ALL swept children (of open headings, un-headed, AND archived headings), each carrying its `heading` ref (flat title + project-scoped `headingUuid`). An odd OPEN child a Put-Back stranded under an archived heading (HEADARC2-C) rides here too with its live `stage` kept — so the flat `logbook` now **KEEPS `stage`** (the bucket is stage-mixed; it previously dropped `stage` as pure). Reconstruct an archived heading's logged children by filtering `logbook` on `heading` + reading `archived` off the catalog.
Expand Down
2 changes: 1 addition & 1 deletion docs/contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The contract reserves a small vocabulary, and each word means exactly one thing
| `candidates` | `error.detail.candidates` | The disambiguation list a not-found / ambiguous resolution (or a `show`/bare-noun did-you-mean) offers, so a caller self-corrects without another round-trip. Each entry is the ONE FIXED candidate shape — **invariant across request flags** (`--full` / `--all` never widen it; an error payload is the most determinism-critical surface) and presence-keyed like the rest of the wire: `uuid` and `title` always; `type` (`"project"` \| `"heading"` \| `"area"` \| `"tag"`) present for those kinds only — **absent `type` = to-do**, the same convention the item wire uses; a container hint `area` and/or `project` as a TITLE string only when set; and the lifecycle words `stage` / `when` (same derivations as the item wire) only for a to-do/project candidate whose row carries them. NOTHING else — no notes, counts, dates, or raw entity fields. A trashed/logged candidate needs no boolean: `stage` already reads `"trash"` / `"logbook"`. The list is capped at **8**; when more matched, the `message` states the total (e.g. "matches 12 projects … 4 more"). The POOL is domain-scoped: by default candidates are LIVE rows only (a trashed/logged row never appears); a trash/logbook-domain op (`project restore`) widens its own pool to its domain. When a name matches ZERO live rows but DEAD rows exist, the `message` gains an honest tail ("1 trashed item matches this name — see `things trash`") and `candidates` stays `[]` — no dangling-ref invitation. `not-found` carries `candidates: []`. On a TTY the human candidate LINES render the fused ref form `Title [8charPrefix]` (a duplicate-titled project/to-do candidate also gets a `· started YYYY-MM-DD` tail) — TTY-only presentation; the structured `candidates[]` payload above is unchanged. That fused string is a valid DECORATED input ref: every `<ref>` slot accepts `Title [ref]` (the bracketed uuid/partial-uuid resolves, the title is an ignored comment; a literal bracketed title wins via exact-title). |
| `scope` | `meta.scope` | The container jail a response was confined to (an area or project), and where the jail came from (`flag` \| `env` \| `config`). |
| `filter` | `meta.filter` | The content filter a response was scoped to — currently the `--area` view filter (resolved uuid + title). |
| `loose` | reference argument (READ surfaces only) | The reserved, case-insensitive ref that addresses the NULL area — the area-less items — as a pseudo-area: `area show loose`, `areas loose`, `show loose`, and `projects --area loose`. It renders the composite null-area view (`kind: "area-view"` with `area: null`): area-less projects first, then loose direct to-dos (`--show-later` adds the Upcoming/Someday blocks). As an `--area` FILTER value it selects the area-less rows on `today`/`anytime`/`someday`/`upcoming`/`logbook`/`search` (`--area loose`) — the effective-area-null inversion of a real `--area` (a row whose own area is null AND, for a project child, whose project carries no area either). It ALWAYS wins over a real area that happens to be named "Loose" (reach that one by uuid; a resolution disclosure names it under `meta.warnings`). READ-ONLY: `open` and every WRITE verb refuse it by name — the detach verbs stay `--loose` (a to-do) / `--no-area` (a project), and `--to-area loose` is refused. |
| `loose` | reference argument (READ surfaces only) | The reserved, case-insensitive ref that addresses the NULL area — the area-less items — as a pseudo-area: `area show loose`, `areas loose`, `show loose`, and `projects --area loose`. It renders the composite null-area view (`kind: "area-view"` with `area: null`): area-less projects (`projects[]`) plus the loose direct to-dos as one flat `items[]` (`--show-later` is TTY-only — it toggles the terminal's Upcoming/Someday render sections, not the wire, which always carries every direct to-do in `items[]`). As an `--area` FILTER value it selects the area-less rows on `today`/`anytime`/`someday`/`upcoming`/`logbook`/`search` (`--area loose`) — the effective-area-null inversion of a real `--area` (a row whose own area is null AND, for a project child, whose project carries no area either). It ALWAYS wins over a real area that happens to be named "Loose" (reach that one by uuid; a resolution disclosure names it under `meta.warnings`). READ-ONLY: `open` and every WRITE verb refuse it by name — the detach verbs stay `--loose` (a to-do) / `--no-area` (a project), and `--to-area loose` is refused. |
| `uuid` | throughout | A Things object's stable identity. The one durable way to name an item across calls. |
| `stage` | inside a to-do/project | The ONE derived lifecycle word: `"inbox"` \| `"upcoming"` \| `"anytime"` \| `"someday"` \| `"logbook"` \| `"trash"`. It replaces the former `start` / `logged` / `trashed` wire fields (all three deleted). Precedence: trashed → `trash` (wins over everything, incl. logged); logged (past the logbook boundary) → `logbook`; else `inbox` (start=0); else `upcoming` (a repeating template, OR a STRICTLY-FUTURE `startDate` — Upcoming membership is `COALESCE(startDate, deadline) > today`, UPC1); else `anytime` (undated-active, OR an ARRIVED `startDate` — `startDate <= today` — which is a Today + Anytime member, reading `when: "today"`, NOT Upcoming); else `someday` (undated, deferred). A completed/canceled row not YET past the logbook boundary keeps its live stage. A deadline-PULLED undated row (an unsuppressed due/overdue deadline, no When-date) derives `anytime` — R13 (BANNER1b): the GUI re-files a pulled Inbox/Someday row into Anytime + Today at pull time. So EVERY Today member derives `anytime`, and the today view is stage-PURE. Dropped only where the enclosing view/section PROVABLY states it — the stage-pure flat catalogues (`inbox`, `anytime`, `someday`, `logbook`, `trash`), the `today` view (R13), and the card sub-buckets — and KEPT everywhere it is not implied: the stage-MIXED `upcoming` catalogue (also holds deadline-forecast `anytime`/`someday` rows), plus search, changes, the projects/areas listings, and detail. Distinct from `status` (the open/canceled/completed axis). |
| `when` | inside a to-do/project | The ONE derived, presence-keyed TIME-AXIS position (R12): `"today"` \| `"evening"` \| a FUTURE ISO date (`YYYY-MM-DD`), or absent. The doctrine line: **`stage` enumerates the sidebar BUCKETS an item lives in; `when` enumerates its TIME POSITIONS (today \| evening \| a future date).** `"evening"` is the This-Evening sub-bucket of Today (implies today); `"today"` is Today membership by ANY arm (an arrived `startDate <= today`, OR an undated due/overdue deadline that is not suppressed — the same predicate the star renders, so `when` can never disagree with it); a FUTURE date is a strictly-future scheduled row (`startDate > today`) or a repeating TEMPLATE's projected next occurrence; absent means unscheduled and not in Today (an unprojected template — paused / after-completion — and every logged/trashed row have none). It REPLACES the former `today` / `evening` markers (both deleted) and, for a template, the former `repeating.nextOccurrence` (same fact, one word). Someday is deliberately NOT a `when` value — it is a bucket → `stage`. A deadline-pulled Someday/Inbox row is re-filed into Anytime at pull time (R13/BANNER1b), so it reads `stage: "anytime"` + `when: "today"` — NOT `stage: "someday"`/`"inbox"` (that combination is unreachable; the `stage` derivation makes every Today member `anytime`). Dropped where the enclosing node PROVABLY states the position — inside a card `upcoming` date-group whose key equals it — and KEPT everywhere else it is present, including the `today` view's flat `items[]` (it interleaves Today-proper and This-Evening members, so each row carries which) and the flat `upcoming`/`anytime`/`inbox`/`someday`/search/changes surfaces. NOTE the read/write asymmetry: the write grammar's `--when` input mirrors the app's When control (incl. `--when someday`); reads DECOMPOSE the resulting state onto the two axes (`stage` + `when`). |
Expand Down
Loading
Loading