things-api 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/canonical-ref.d.ts +21 -0
  3. package/dist/cli/canonical-ref.js +23 -0
  4. package/dist/cli/canonical-ref.js.map +1 -0
  5. package/dist/cli/commands/area.js +27 -11
  6. package/dist/cli/commands/area.js.map +1 -1
  7. package/dist/cli/commands/install-skill.d.ts +2 -2
  8. package/dist/cli/commands/install-skill.js +29 -0
  9. package/dist/cli/commands/install-skill.js.map +1 -1
  10. package/dist/cli/commands/mcp.js +12 -1
  11. package/dist/cli/commands/mcp.js.map +1 -1
  12. package/dist/cli/commands/project.js +73 -21
  13. package/dist/cli/commands/project.js.map +1 -1
  14. package/dist/cli/commands/reads.d.ts +5 -1
  15. package/dist/cli/commands/reads.js +10 -3
  16. package/dist/cli/commands/reads.js.map +1 -1
  17. package/dist/cli/commands/setup.js +11 -4
  18. package/dist/cli/commands/setup.js.map +1 -1
  19. package/dist/cli/commands/show.js +45 -30
  20. package/dist/cli/commands/show.js.map +1 -1
  21. package/dist/cli/commands/todo.js +1 -1
  22. package/dist/cli/commands/todo.js.map +1 -1
  23. package/dist/cli/commands/writes.js +31 -6
  24. package/dist/cli/commands/writes.js.map +1 -1
  25. package/dist/cli/did-you-mean.js +19 -5
  26. package/dist/cli/did-you-mean.js.map +1 -1
  27. package/dist/cli/dry-run.d.ts +55 -0
  28. package/dist/cli/dry-run.js +92 -0
  29. package/dist/cli/dry-run.js.map +1 -0
  30. package/dist/cli/help.js +7 -6
  31. package/dist/cli/help.js.map +1 -1
  32. package/dist/cli/main.js +7 -0
  33. package/dist/cli/main.js.map +1 -1
  34. package/dist/cli/read-driver.d.ts +16 -6
  35. package/dist/cli/read-driver.js +19 -18
  36. package/dist/cli/read-driver.js.map +1 -1
  37. package/dist/cli/ref-render.d.ts +14 -0
  38. package/dist/cli/ref-render.js +9 -0
  39. package/dist/cli/ref-render.js.map +1 -0
  40. package/dist/cli/render.d.ts +21 -6
  41. package/dist/cli/render.js +73 -34
  42. package/dist/cli/render.js.map +1 -1
  43. package/dist/cli/resolve-invocation.d.ts +5 -3
  44. package/dist/cli/resolve-invocation.js +13 -4
  45. package/dist/cli/resolve-invocation.js.map +1 -1
  46. package/dist/client.d.ts +18 -7
  47. package/dist/client.js +2 -1
  48. package/dist/client.js.map +1 -1
  49. package/dist/contracts.d.ts +12 -1
  50. package/dist/contracts.js +1 -1
  51. package/dist/contracts.js.map +1 -1
  52. package/dist/db/baselines/db-v26.js +4 -1
  53. package/dist/db/baselines/db-v26.js.map +1 -1
  54. package/dist/index.d.ts +3 -1
  55. package/dist/index.js +7 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/mcp/server.js +81 -42
  58. package/dist/mcp/server.js.map +1 -1
  59. package/dist/model/entities.d.ts +11 -1
  60. package/dist/model/entities.js.map +1 -1
  61. package/dist/model/mappers.js +3 -0
  62. package/dist/model/mappers.js.map +1 -1
  63. package/dist/model/serialize.js +15 -3
  64. package/dist/model/serialize.js.map +1 -1
  65. package/dist/read/area-filter.d.ts +2 -2
  66. package/dist/read/area-filter.js +5 -6
  67. package/dist/read/area-filter.js.map +1 -1
  68. package/dist/read/area-view.d.ts +11 -0
  69. package/dist/read/area-view.js +5 -1
  70. package/dist/read/area-view.js.map +1 -1
  71. package/dist/read/project-view.d.ts +61 -1
  72. package/dist/read/project-view.js +74 -9
  73. package/dist/read/project-view.js.map +1 -1
  74. package/dist/read/queries.d.ts +71 -2
  75. package/dist/read/queries.js +199 -10
  76. package/dist/read/queries.js.map +1 -1
  77. package/dist/read/scope.d.ts +2 -2
  78. package/dist/read/scope.js +5 -6
  79. package/dist/read/scope.js.map +1 -1
  80. package/dist/read/shape.d.ts +36 -17
  81. package/dist/read/shape.js +251 -200
  82. package/dist/read/shape.js.map +1 -1
  83. package/dist/read/show-target.js +52 -33
  84. package/dist/read/show-target.js.map +1 -1
  85. package/dist/read/truncation.d.ts +0 -7
  86. package/dist/read/truncation.js +24 -14
  87. package/dist/read/truncation.js.map +1 -1
  88. package/dist/read/views.d.ts +19 -7
  89. package/dist/read/views.js +13 -12
  90. package/dist/read/views.js.map +1 -1
  91. package/dist/surface-copy.d.ts +7 -0
  92. package/dist/surface-copy.js +7 -0
  93. package/dist/surface-copy.js.map +1 -1
  94. package/dist/write/commands.js +3 -1
  95. package/dist/write/commands.js.map +1 -1
  96. package/dist/write/move.d.ts +10 -6
  97. package/dist/write/move.js +417 -76
  98. package/dist/write/move.js.map +1 -1
  99. package/dist/write/operations.d.ts +1 -1
  100. package/dist/write/operations.js.map +1 -1
  101. package/dist/write/pre-state.d.ts +10 -0
  102. package/dist/write/pre-state.js +118 -41
  103. package/dist/write/pre-state.js.map +1 -1
  104. package/dist/write/reorder.js +263 -29
  105. package/dist/write/reorder.js.map +1 -1
  106. package/package.json +1 -1
  107. package/schema/envelope.schema.json +14 -0
  108. package/skills/things-cli/SKILL.md +6 -4
  109. package/skills/things-cli/references/contracts.md +3 -1
  110. package/skills/things-cli/references/errors.md +9 -1
  111. package/skills/things-cli/references/model.md +6 -4
  112. package/skills/things-cli/references/ordering.md +21 -4
@@ -31,11 +31,48 @@ export interface ProjectHeadingGroup {
31
31
  /** This heading's repeating template rows (invisible in list views). */
32
32
  repeating: Todo[];
33
33
  }
34
+ /**
35
+ * An ARCHIVED heading rendered inside the logged region (HEADARC2-A). Unlike a
36
+ * live {@link ProjectHeadingGroup}, an archived heading is itself a logged item,
37
+ * and its children — ALL of them, whatever their own state — nest beneath it as
38
+ * a grouped section (most-recently-completed first). That includes any odd OPEN
39
+ * child a GUI "Put Back" left stranded under the archived heading (HEADARC2-C):
40
+ * the group renders what is there, it does NOT filter to status=3.
41
+ */
42
+ export interface LoggedHeadingGroup {
43
+ /** The archived heading (status "completed"); the wire emits its `archived`. */
44
+ heading: Heading;
45
+ /** Every child of the archived heading, most-recently-completed first. */
46
+ items: Todo[];
47
+ }
34
48
  export interface ProjectView {
35
49
  project: Project;
50
+ /**
51
+ * ALL live (non-logbook, non-trash) children of the project — unheaded AND
52
+ * headed — in project `index` order, each headed row carrying its `heading`
53
+ * ref (and `headingProject`). This is the flat wire representation (the read
54
+ * doctrine's project-view `items[]`): membership in a heading is a per-row
55
+ * attribute, not a bucket. The structured buckets below (`active`, `headings`,
56
+ * `scheduled`, …) are the SAME children re-grouped into the GUI's render layout
57
+ * for the TTY projection — the library owns the clock + `todayIndex`, so the
58
+ * renderer reconstructs the exact GUI placement from them without re-deriving
59
+ * dates. The two are always consistent (built from one child fetch).
60
+ */
61
+ items: Todo[];
62
+ /**
63
+ * The full heading catalog — EVERY heading (live AND swept archived) in project
64
+ * `index` order (read-shape doctrine §3.12 / #C3). This is the flat wire
65
+ * representation of `headings[]` (`[{uuid,title,archived?}]`): which headings
66
+ * exist, in what order, and whether each is archived — the ORDER axis that
67
+ * earns the bucket its place. A live-group's row membership rides the flat
68
+ * `items[]` refs; a swept archived heading's children ride the flat `logbook`
69
+ * refs. The structured `headings` / `loggedHeadings` below re-group the SAME
70
+ * headings + children into the GUI render layout for the byte-stable TTY.
71
+ */
72
+ headingCatalog: Heading[];
36
73
  /** Open, unscheduled/current UNHEADED children, by index. */
37
74
  active: Todo[];
38
- /** Headings in project order, each with its own sub-buckets. */
75
+ /** OPEN headings in project order, each with its own sub-buckets. An archived heading never renders here. */
39
76
  headings: ProjectHeadingGroup[];
40
77
  /** UNHEADED future-dated children grouped by date ascending. */
41
78
  scheduled: IsoDateGroup<Todo>[];
@@ -43,7 +80,20 @@ export interface ProjectView {
43
80
  someday: Todo[];
44
81
  /** UNHEADED repeating template rows owned by this project (invisible in list views). */
45
82
  repeating: Todo[];
83
+ /**
84
+ * The flat logged region: swept children of OPEN headings (each carrying its
85
+ * heading ref) plus swept un-headed children, most-recently-completed first.
86
+ * Children of ARCHIVED headings are NOT here — they group under their heading
87
+ * in {@link ProjectView.loggedHeadings}.
88
+ */
46
89
  logged: Todo[];
90
+ /**
91
+ * The archived-heading GROUPS of the logged region (HEADARC2-A), in project
92
+ * (heading `index`) order — each an archived heading with its children nested.
93
+ * Rendered only under `--show-logged`, after the flat {@link ProjectView.logged}
94
+ * rows (the flat-then-grouped ordering is a defensible choice, not GUI-probed).
95
+ */
96
+ loggedHeadings: LoggedHeadingGroup[];
47
97
  /**
48
98
  * PLOG1 (additive): count of untrashed OPEN (status=0) children this project
49
99
  * still holds while it is ITSELF completed or canceled — including once swept
@@ -54,6 +104,16 @@ export interface ProjectView {
54
104
  * child.
55
105
  */
56
106
  openChildrenWhileResolved: number;
107
+ /**
108
+ * HEADARC2-C (additive): count of untrashed OPEN (status=0) children buried
109
+ * under an ARCHIVED heading of this project — the heading analog of the §6¾
110
+ * odd state. The GUI's "Put Back" of a trashed open child restores it in place
111
+ * under the archived heading WITHOUT reopening the heading, leaving an open,
112
+ * actionable to-do invisible to every live view (reachable only by expanding
113
+ * this card's logged region). Counted regardless of the PROJECT's own status.
114
+ * 0 when no archived heading holds an open child.
115
+ */
116
+ openChildrenUnderArchivedHeading: number;
57
117
  }
58
118
  export declare class ProjectNotFoundError extends Error {
59
119
  constructor(uuid: string);
@@ -72,30 +72,63 @@ export function projectView(db, uuid, now, filter = {}, zone) {
72
72
  todo: mapTodo(r, refs, childTags.get(r.uuid) ?? [], packedToday),
73
73
  }));
74
74
  markLogged([project, ...todos.map((t) => t.todo)], boundary);
75
+ // TRINARY heading split (the completion≠logged law, applied to headings): an
76
+ // OPEN heading — or an archived one NOT yet past the log-move boundary — stays
77
+ // a LIVE group IN PLACE (an archived-unswept heading carries `archived` yet
78
+ // renders among the live groups, exactly as a completed-unswept to-do row
79
+ // stays checked in place, §above / log-boundary.ts). Only a SWEPT archived
80
+ // heading moves into the logged region as a grouped section (HEADARC2-A).
81
+ const headingSwept = (h) => h.status !== "open" && h.stopped !== null && h.stopped <= boundary;
82
+ const liveHeadings = headings.filter((h) => !headingSwept(h));
83
+ const sweptHeadings = headings.filter((h) => headingSwept(h));
84
+ const liveHeadingSet = new Set(liveHeadings.map((h) => h.uuid));
85
+ const sweptHeadingSet = new Set(sweptHeadings.map((h) => h.uuid));
86
+ // The owning-project ref stamped on every headed child — so the heading ref a
87
+ // flat logged row carries promotes its `headingUuid` in the project's scope
88
+ // (the round-trip predicate is project-scoped), mirroring the list views.
89
+ const projectRef = { uuid: project.uuid, title: project.title };
75
90
  // Project-level (UNHEADED) buckets.
76
91
  const active = [];
77
92
  const scheduledRows = [];
78
93
  const repeating = [];
79
94
  const someday = [];
80
95
  const logged = [];
81
- // Per-heading sub-buckets, keyed by heading uuid (§9 fidelity fix): a headed
82
- // scheduled/someday/repeating child nests under its heading, NOT at the
96
+ // Children of a SWEPT archived heading grouped under their heading in the
97
+ // logged region (keyed by heading uuid), ALL of them regardless of state.
98
+ const sweptHeadingChildren = new Map();
99
+ // Per-heading LIVE sub-buckets, keyed by heading uuid (§9 fidelity fix): a
100
+ // headed scheduled/someday/repeating child nests under its heading, NOT at the
83
101
  // project level.
84
102
  const headingItems = new Map();
85
103
  const headingScheduled = new Map();
86
104
  const headingSomeday = new Map();
87
105
  const headingRepeating = new Map();
88
- // Known (fetched, untrashed) headings — a child whose heading FK is not one
89
- // of these falls back to the project-level (unheaded) buckets rather than
90
- // vanishing (mirrors the render's historical loose fallback).
91
- const headingSet = new Set(headings.map((h) => h.uuid));
92
- const headingUuidOf = (row) => row.heading !== null && headingSet.has(row.heading) ? row.heading : null;
106
+ // A child whose heading FK is not a LIVE heading (an unknown/trashed heading,
107
+ // OR a swept archived heading handled above) falls back to the project-level
108
+ // (unheaded) buckets rather than vanishing (mirrors the render's loose fallback).
109
+ const liveHeadingUuidOf = (row) => row.heading !== null && liveHeadingSet.has(row.heading) ? row.heading : null;
93
110
  // Every untrashed, non-template OPEN child, regardless of which bucket it
94
111
  // lands in below — surfaced (only when the project is itself resolved) as
95
112
  // the PLOG1 stranded-open-child count.
96
113
  let openChildren = 0;
114
+ // Open children buried under a SWEPT archived heading — the §6¾ odd state for
115
+ // headings (HEADARC2-C), surfaced regardless of the project's own status.
116
+ let openChildrenUnderArchivedHeading = 0;
97
117
  for (const { row, todo } of todos) {
98
- const h = headingUuidOf(row);
118
+ if (todo.heading !== null)
119
+ todo.headingProject = projectRef;
120
+ // A child of a SWEPT archived heading groups under it in the logged region,
121
+ // WHATEVER its own state — including the odd OPEN child a GUI Put-Back
122
+ // stranded (HEADARC2-C): render what is there, do NOT filter to status=3.
123
+ if (row.heading !== null && sweptHeadingSet.has(row.heading)) {
124
+ pushInto(sweptHeadingChildren, row.heading, todo);
125
+ if (row.status === 0 && !todo.repeating.isTemplate) {
126
+ openChildren += 1; // also stranded when the project itself is resolved
127
+ openChildrenUnderArchivedHeading += 1;
128
+ }
129
+ continue;
130
+ }
131
+ const h = liveHeadingUuidOf(row);
99
132
  if (todo.repeating.isTemplate) {
100
133
  if (h !== null)
101
134
  pushInto(headingRepeating, h, todo);
@@ -151,7 +184,7 @@ export function projectView(db, uuid, now, filter = {}, zone) {
151
184
  // section; a heading with any surviving child (in ANY sub-bucket) is kept.
152
185
  // With no scope active every heading renders (its own empty state).
153
186
  const contentScoped = overdue || tf.sql !== "";
154
- const headingGroups = headings
187
+ const headingGroups = liveHeadings
155
188
  .map((heading) => ({
156
189
  heading,
157
190
  items: headingItems.get(heading.uuid) ?? [],
@@ -164,15 +197,47 @@ export function projectView(db, uuid, now, filter = {}, zone) {
164
197
  g.scheduled.length > 0 ||
165
198
  g.someday.length > 0 ||
166
199
  g.repeating.length > 0);
200
+ // The logged-region archived-heading groups, in project (heading index) order;
201
+ // each group's children most-recently-completed first (open odd children, with
202
+ // no stopDate, sort last — the same null-last convention the flat logged list
203
+ // uses). Under a content scope (--overdue / --tag) an empty group collapses.
204
+ const loggedHeadings = sweptHeadings
205
+ .map((heading) => ({
206
+ heading,
207
+ items: (sweptHeadingChildren.get(heading.uuid) ?? []).toSorted((a, b) => (b.stopped?.getTime() ?? 0) - (a.stopped?.getTime() ?? 0)),
208
+ }))
209
+ .filter((g) => !contentScoped || g.items.length > 0);
210
+ // The flat wire `items[]`: every LIVE child in project index order (the
211
+ // `todos` fetch is already `ORDER BY index ASC`). Excludes swept logged rows
212
+ // (→ `logged`) and children of swept archived headings (→ `loggedHeadings`) —
213
+ // exactly the rows that leave the live region. Each headed row already carries
214
+ // its `heading` ref + `headingProject` (stamped in the loop above).
215
+ const items = todos
216
+ .map((t) => t.todo)
217
+ .filter((td) => !td.logged && !(td.heading !== null && sweptHeadingSet.has(td.heading.uuid)));
218
+ // The catalog: every heading (live + swept archived) in index order — but under
219
+ // a content scope (`--overdue` / `--tag`) a heading whose children were all
220
+ // filtered out COLLAPSES, exactly as it does for the live groups + logged groups
221
+ // and the TTY. With no scope every heading survives (empty ones included), so
222
+ // this is a no-op there. Built from the SAME survivors the render layout uses.
223
+ const survivingHeadings = new Set([
224
+ ...headingGroups.map((g) => g.heading.uuid),
225
+ ...loggedHeadings.map((g) => g.heading.uuid),
226
+ ]);
227
+ const headingCatalog = headings.filter((h) => survivingHeadings.has(h.uuid));
167
228
  return {
168
229
  project,
230
+ items,
231
+ headingCatalog,
169
232
  active,
170
233
  headings: headingGroups,
171
234
  scheduled,
172
235
  someday,
173
236
  repeating,
174
237
  logged,
238
+ loggedHeadings,
175
239
  openChildrenWhileResolved: project.status === "open" ? 0 : openChildren,
240
+ openChildrenUnderArchivedHeading,
176
241
  };
177
242
  }
178
243
  //# sourceMappingURL=project-view.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-view.js","sourceRoot":"","sources":["../../src/read/project-view.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"project-view.js","sourceRoot":"","sources":["../../src/read/project-view.ts"],"names":[],"mappings":"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"}
@@ -5,7 +5,7 @@
5
5
  import type { DatabaseSync } from "node:sqlite";
6
6
  import { type Ref } from "../model/entities.ts";
7
7
  import type { ChecklistRow, TaskRow } from "../model/mappers.ts";
8
- import { type CandidateRef } from "./shape.ts";
8
+ import { type CandidateRef, type RefKind, type RefPromoter } from "./shape.ts";
9
9
  /** Rows that repeat via a template are normal; template rows are invisible in list views. */
10
10
  export declare const NOT_TEMPLATE = "(t.rt1_recurrenceRule IS NULL AND t.repeater IS NULL)";
11
11
  /**
@@ -129,6 +129,9 @@ export declare function resolveTaskUuidPrefix(db: DatabaseSync, refRaw: string,
129
129
  * docs/design/reference-resolution.md.
130
130
  */
131
131
  export declare function normalizeNameKey(s: string): string;
132
+ /** The fused TTY ref form `Title [8charPrefix]` — the round-trippable decorated ref every candidate renders. */
133
+ export declare const REF_PREFIX_LEN = 8;
134
+ export declare function fusedRef(title: string, uuid: string): string;
132
135
  export interface NamedResolution {
133
136
  resolved: {
134
137
  uuid: string;
@@ -206,10 +209,49 @@ export declare function resolveProjectWriteTarget(db: DatabaseSync, refRaw: stri
206
209
  };
207
210
  }, includeTrashed?: boolean, includeNonOpen?: boolean): string;
208
211
  export declare function resolveTagUuid(db: DatabaseSync, ref: string): string;
212
+ /** Options shared by the read-side project resolvers (a name-tier narrowing subset). */
213
+ interface ReadProjectOptions {
214
+ prefixTier?: boolean;
215
+ scopeWhere?: string;
216
+ scopeBinds?: (string | number)[];
217
+ }
218
+ /**
219
+ * A read-side PROJECT NAME resolution verdict under the liveness law (never
220
+ * throws) — the shared core behind the throwing {@link resolveProjectUuid}
221
+ * (`project show`) and the cross-kind {@link classifyShowTarget} router.
222
+ *
223
+ * The uuid / partial-uuid tiers reach EVERY project row (explicit intent stays
224
+ * able to view a trashed project by id); the NAME tiers resolve against LIVE
225
+ * (untrashed) rows only, so a dead twin never shadows a live one nor inflates
226
+ * an ambiguity count. When the live pool is empty, the TRASHED pool is consulted
227
+ * for the reads-only ergonomic fallback: a UNIQUELY-named trashed project still
228
+ * resolves by name (the render discloses it — the card's own `(trashed)` marker
229
+ * / `stage: "trash"`), while several dead twins report honestly instead.
230
+ */
231
+ export interface ReadProjectVerdict {
232
+ /** The resolved winner — a live unique, a uuid/partial-uuid, or the unique-dead fallback; else null. */
233
+ resolved: {
234
+ uuid: string;
235
+ title: string;
236
+ } | null;
237
+ /** ALL live name-matching rows at the deciding tier (0, 1, or many). */
238
+ liveRows: {
239
+ uuid: string;
240
+ title: string;
241
+ }[];
242
+ /** 0 / 1 / >1 — the LIVE match count (the ambiguity count when > 1). */
243
+ liveMatches: number;
244
+ /** Count of TRASHED-name twins — feeds the disclosure tail / dead-hint. */
245
+ trashedMatches: number;
246
+ }
247
+ export declare function readProjectNameVerdict(db: DatabaseSync, ref: string, options?: ReadProjectOptions): ReadProjectVerdict;
209
248
  /**
210
249
  * Write destinations stay strict (a trashed project is not a valid target);
211
250
  * READ surfaces pass `trashed: true` so a project in the Trash can still be
212
- * viewed its would-be-recovered children are only visible there.
251
+ * viewed. Under `trashed: true` the read-side liveness law applies: an explicit
252
+ * uuid / partial-uuid reaches any project, but a NAME resolves against LIVE rows
253
+ * only (a dead same-name twin never shadows a live one), with the reads-only
254
+ * unique-dead fallback + trash disclosure ({@link readProjectNameVerdict}).
213
255
  */
214
256
  export declare function resolveProjectUuid(db: DatabaseSync, ref: string, options?: {
215
257
  trashed?: boolean;
@@ -217,6 +259,14 @@ export declare function resolveProjectUuid(db: DatabaseSync, ref: string, option
217
259
  scopeWhere?: string;
218
260
  scopeBinds?: (string | number)[];
219
261
  }): string;
262
+ /**
263
+ * The disclosure tail appended to a read-side project ambiguity when the
264
+ * uuid-reachable pool holds additional TRASHED twins of the ambiguous name. The
265
+ * ambiguity COUNT stays over live rows (coherent with the rendered candidate
266
+ * list); the dead ones are disclosed separately rather than inflating it. Empty
267
+ * when none are trashed.
268
+ */
269
+ export declare function trashDisclosureTail(deadCount: number): string;
220
270
  export declare function resolveAreaUuid(db: DatabaseSync, ref: string, options?: {
221
271
  prefixTier?: boolean;
222
272
  scopeWhere?: string;
@@ -245,6 +295,24 @@ export declare function resolveHeadingUuid(db: DatabaseSync, projectUuid: string
245
295
  uuid: string;
246
296
  title: string;
247
297
  };
298
+ /**
299
+ * Does a container ref's bare TITLE round-trip through its own resolver, in its
300
+ * own scope, back to THIS entity's uuid? The single predicate the emit boundary
301
+ * consults for the JSON round-trip law — true only when the sole resolution is
302
+ * `entityUuid` (not-found / ambiguous / a different entity all → false, i.e. the
303
+ * title must promote its uuid sibling). See {@link resolveTitleForRoundTrip}.
304
+ */
305
+ export declare function titleRoundTrips(db: DatabaseSync, kind: RefKind, title: string, entityUuid: string, projectUuid?: string): boolean;
306
+ /**
307
+ * Build the {@link RefPromoter} the read-shaping transform ({@link shapeReadPayload})
308
+ * consults to decide flat-ref uuid promotion, memoized per (kind, title, scope)
309
+ * for ONE response emission so a large view never re-runs a resolution it has
310
+ * already made. A fresh promoter (fresh memo) per response — the consumer
311
+ * surfaces build one via the client. The memo caches the RESOLVED uuid (or null),
312
+ * independent of the comparison target, so twin entities sharing a title share
313
+ * the one resolution.
314
+ */
315
+ export declare function makeRefPromoter(db: DatabaseSync): RefPromoter;
248
316
  /**
249
317
  * A row's EFFECTIVE area: its own `area` link, else the area of its project,
250
318
  * else the area of its heading's project. To-dos nested in a project (or under a
@@ -323,3 +391,4 @@ export declare function fetchTagsForTasks(db: DatabaseSync, taskUuids: string[])
323
391
  export declare function makeHeadingProjectResolver(db: DatabaseSync): (headingUuid: string) => Ref | null;
324
392
  /** Lazy uuid -> Ref resolver over TMTask + TMArea titles, cached per instance. */
325
393
  export declare function makeRefResolver(db: DatabaseSync): (uuid: string | null) => Ref | null;
394
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { q, selectList } from "../db/schema.js";
2
2
  import { TASK_TYPE_FROM_DB } from "../model/entities.js";
3
- import { candidateRef, CANDIDATE_CAP } from "./shape.js";
3
+ import { candidateRef, CANDIDATE_CAP, } from "./shape.js";
4
4
  /** Rows that repeat via a template are normal; template rows are invisible in list views. */
5
5
  export const NOT_TEMPLATE = "(t.rt1_recurrenceRule IS NULL AND t.repeater IS NULL)";
6
6
  /** ` AND col IN (…)` for the restricted form; empty for the tag-agnostic form. */
@@ -234,6 +234,27 @@ export function normalizeNameKey(s) {
234
234
  .replace(/[\s‐-―−-]+/gu, "");
235
235
  }
236
236
  const BASE62 = /^[0-9A-Za-z]+$/;
237
+ /**
238
+ * The DECORATED-REF form `Title [ref]` — a bare title with the machine-stable
239
+ * uuid/partial-uuid pinned in trailing brackets (the fused form every TTY
240
+ * candidate renders, `Title [8charPrefix]`). The title half is a COMMENT
241
+ * (ignored), so a stale copy still resolves after a rename; the bracket is the
242
+ * ref, resolved through the uuid/partial-uuid tier. Greedy `.*` so the LAST
243
+ * bracket wins (`"Family [Sub] [TC9yozLk]"` → `TC9yozLk`). The empty-title form
244
+ * `" [J2kPq9Ws]"` (a titleless heading) is legal — `(.*)` matches empty. Because
245
+ * `$`-anchored so the LAST bracket always wins. The space before `[` is OPTIONAL
246
+ * because {@link stripThingsUri} trims the ref first, so the empty-title form
247
+ * `" [J2kPq9Ws]"` (a titleless heading) arrives as the bare `[J2kPq9Ws]` — both
248
+ * resolve. The bracket is 4–22 base62 chars; a segment shorter than the 6-char
249
+ * partial-uuid floor simply fails to resolve (the FORM is recognized, the
250
+ * resolution is the real uuid/partial-uuid tier's).
251
+ */
252
+ const DECORATED_REF = /^(.*?) ?\[([0-9A-Za-z]{4,22})\]$/;
253
+ /** The fused TTY ref form `Title [8charPrefix]` — the round-trippable decorated ref every candidate renders. */
254
+ export const REF_PREFIX_LEN = 8;
255
+ export function fusedRef(title, uuid) {
256
+ return `${title} [${uuid.slice(0, REF_PREFIX_LEN)}]`;
257
+ }
237
258
  /**
238
259
  * Tiered reference resolution (docs/design/reference-resolution.md): exact
239
260
  * uuid → exact title → case-insensitive title → normalized title → uuid
@@ -287,12 +308,42 @@ export function resolveNamedRef(db, table, extraWhere, extraBinds, refRaw, optio
287
308
  if (rows.length > 1)
288
309
  return { resolved: null, matches: rows.length, candidates: rows };
289
310
  }
311
+ // The DECORATED-REF tier — LAST, after exact/case/normalized title and the
312
+ // uuid/partial-uuid tiers all miss. `Title [ref]`: the title is ignored and the
313
+ // bracketed segment resolves through the uuid/partial-uuid tier (uuid-exact,
314
+ // then a >=6-char partial-uuid prefix). Runs regardless of `prefixTier` — the
315
+ // bracket is EXPLICIT ref intent (a rendered fused ref), like a bare uuid. A
316
+ // literal title `Title [ref]` outranks this by construction (exact-title is an
317
+ // earlier tier). Scope/extraWhere apply (uuid-tier `sel`, not the name tiers).
318
+ const decorated = DECORATED_REF.exec(ref);
319
+ if (decorated !== null) {
320
+ const inner = decorated[2] ?? "";
321
+ const exactRows = sel("uuid = ?", [inner]);
322
+ if (exactRows.length === 1)
323
+ return { resolved: exactRows[0] ?? null, matches: 1 };
324
+ if (inner.length >= 6) {
325
+ const upper = inner.slice(0, -1) + String.fromCharCode(inner.charCodeAt(inner.length - 1) + 1);
326
+ const rows = sel("uuid >= ? AND uuid < ?", [inner, upper]);
327
+ if (rows.length === 1)
328
+ return { resolved: rows[0] ?? null, matches: 1 };
329
+ if (rows.length > 1)
330
+ return { resolved: null, matches: rows.length, candidates: rows };
331
+ }
332
+ }
290
333
  return { resolved: null, matches: 0 };
291
334
  }
292
335
  /** The accepted-forms clause for a name-accepting resolver's not-found copy. */
293
336
  function acceptedForms(prefixTier) {
294
337
  return prefixTier ? "tried uuid, partial-uuid, and name" : "tried uuid and name";
295
338
  }
339
+ /**
340
+ * The single-kind ambiguous-name refusal copy shared by the read resolvers
341
+ * (`"X" matches N projects — use the exact name or a uuid`). `kind` is the
342
+ * singular noun; the plural `s` is appended here so the count agrees.
343
+ */
344
+ function ambiguousNameMessage(ref, count, kind, capped) {
345
+ return `"${ref}" matches ${count} ${kind}s${capped ? `; first ${CANDIDATE_CAP} shown` : ""} — use the exact name or a uuid`;
346
+ }
296
347
  function resolveUuidOrThrow(db, table, extraWhere, ref, kind, listCmd, options) {
297
348
  const r = resolveNamedRef(db, table, extraWhere, [], ref, options);
298
349
  if (r.resolved !== null)
@@ -301,8 +352,7 @@ function resolveUuidOrThrow(db, table, extraWhere, ref, kind, listCmd, options)
301
352
  throw new ReferenceResolutionError(`no ${kind} matching "${ref}" — ${acceptedForms(options?.prefixTier !== false)} (list ${kind}s with \`${listCmd}\`)`, { code: "not-found", ref });
302
353
  }
303
354
  const all = r.candidates ?? [];
304
- const capped = all.length > CANDIDATE_CAP;
305
- throw new ReferenceResolutionError(`"${ref}" matches ${r.matches} ${kind}s${capped ? `; first ${CANDIDATE_CAP} shown` : ""} — use the exact name or a uuid`, {
355
+ throw new ReferenceResolutionError(ambiguousNameMessage(ref, r.matches, kind, all.length > CANDIDATE_CAP), {
306
356
  code: "ambiguous",
307
357
  ref,
308
358
  candidates: all.slice(0, CANDIDATE_CAP).map((c) => candidateRef(kind, c)),
@@ -402,11 +452,13 @@ export function resolveProjectWriteTarget(db, refRaw, scope, includeTrashed = fa
402
452
  }
403
453
  const all = describeProjectCandidates(db, r.candidates ?? []);
404
454
  const shown = all.slice(0, CANDIDATE_CAP);
455
+ // The fused ref form `Title [8charPrefix]` — the bracketed prefix pastes back
456
+ // as a decorated ref that resolves straight to this project.
405
457
  const lines = shown
406
- .map((c) => ` ${c.uuid.slice(0, 8)} — ${c.title}${c.area !== undefined ? ` (in ${c.area})` : ""}`)
458
+ .map((c) => ` ${fusedRef(c.title, c.uuid)}${c.area !== undefined ? ` (in ${c.area})` : ""}`)
407
459
  .join("\n");
408
460
  const more = all.length > CANDIDATE_CAP ? `\n … ${all.length - CANDIDATE_CAP} more` : "";
409
- throw new ReferenceResolutionError(`"${ref}" matches ${r.matches} projects — disambiguate with a uuid or partial-uuid:\n${lines}${more}`, { code: "ambiguous", ref, candidates: shown });
461
+ throw new ReferenceResolutionError(`"${ref}" matches ${r.matches} projects — disambiguate with a ref below:\n${lines}${more}`, { code: "ambiguous", ref, candidates: shown });
410
462
  }
411
463
  /** Fixed-shape candidates for an ambiguous project name, each with its area container hint. */
412
464
  function describeProjectCandidates(db, candidates) {
@@ -419,13 +471,82 @@ function describeProjectCandidates(db, candidates) {
419
471
  export function resolveTagUuid(db, ref) {
420
472
  return resolveUuidOrThrow(db, "TMTag", "1=1", ref, "tag", "things tags");
421
473
  }
474
+ /** Count (and, when unique, resolve) a NAME against the TRASHED project pool only. */
475
+ function trashedProjectNameMatches(db, ref, options) {
476
+ return resolveNamedRef(db, "TMTask", "type = 1 AND trashed = 1", [], ref, {
477
+ prefixTier: false,
478
+ ...(options?.scopeWhere !== undefined && {
479
+ scopeWhere: options.scopeWhere,
480
+ scopeBinds: options.scopeBinds,
481
+ }),
482
+ });
483
+ }
484
+ export function readProjectNameVerdict(db, ref, options) {
485
+ // uuid/partial-uuid tiers span every project (`type = 1`); name tiers narrow
486
+ // to LIVE rows via `nameExtraWhere` — the same split the write side uses.
487
+ const live = resolveNamedRef(db, "TMTask", "type = 1", [], ref, {
488
+ ...options,
489
+ nameExtraWhere: "trashed = 0",
490
+ });
491
+ if (live.resolved !== null)
492
+ return {
493
+ resolved: live.resolved,
494
+ liveRows: [live.resolved],
495
+ liveMatches: 1,
496
+ trashedMatches: 0,
497
+ };
498
+ if (live.matches > 1)
499
+ return {
500
+ resolved: null,
501
+ liveRows: live.candidates ?? [],
502
+ liveMatches: live.matches,
503
+ // Disclose extra trashed twins WITHOUT folding them into the count.
504
+ trashedMatches: trashedProjectNameMatches(db, ref, options).matches,
505
+ };
506
+ // Zero live name/uuid matches: the trashed pool decides the ergonomic fallback.
507
+ const dead = trashedProjectNameMatches(db, ref, options);
508
+ if (dead.resolved !== null)
509
+ return { resolved: dead.resolved, liveRows: [], liveMatches: 0, trashedMatches: dead.matches };
510
+ return { resolved: null, liveRows: [], liveMatches: 0, trashedMatches: dead.matches };
511
+ }
422
512
  /**
423
513
  * Write destinations stay strict (a trashed project is not a valid target);
424
514
  * READ surfaces pass `trashed: true` so a project in the Trash can still be
425
- * viewed its would-be-recovered children are only visible there.
515
+ * viewed. Under `trashed: true` the read-side liveness law applies: an explicit
516
+ * uuid / partial-uuid reaches any project, but a NAME resolves against LIVE rows
517
+ * only (a dead same-name twin never shadows a live one), with the reads-only
518
+ * unique-dead fallback + trash disclosure ({@link readProjectNameVerdict}).
426
519
  */
427
520
  export function resolveProjectUuid(db, ref, options) {
428
- return resolveUuidOrThrow(db, "TMTask", options?.trashed === true ? "type = 1" : "type = 1 AND trashed = 0", ref, "project", "things projects", options);
521
+ if (options?.trashed !== true) {
522
+ // The non-widening read pool: name AND uuid tiers are LIVE-only (a trashed
523
+ // project is invisible even by uuid here) — the filter/scope/write-target
524
+ // callers, unchanged.
525
+ return resolveUuidOrThrow(db, "TMTask", "type = 1 AND trashed = 0", ref, "project", "things projects", options);
526
+ }
527
+ const v = readProjectNameVerdict(db, ref, options);
528
+ if (v.resolved !== null)
529
+ return v.resolved.uuid;
530
+ if (v.liveMatches > 1) {
531
+ const shown = v.liveRows.slice(0, CANDIDATE_CAP);
532
+ throw new ReferenceResolutionError(ambiguousNameMessage(ref, v.liveMatches, "project", v.liveRows.length > CANDIDATE_CAP) +
533
+ trashDisclosureTail(v.trashedMatches), { code: "ambiguous", ref, candidates: shown.map((c) => candidateRef("project", c)) });
534
+ }
535
+ // Zero live matches (a unique dead row would have resolved above), so a
536
+ // non-empty trash count here is always the several-dead-twins case.
537
+ throw new ReferenceResolutionError(`no project matching "${ref}" — ${acceptedForms(options.prefixTier !== false)} (list projects with \`things projects\`)${deadNameMatchHint({ trashed: v.trashedMatches })}`, { code: "not-found", ref });
538
+ }
539
+ /**
540
+ * The disclosure tail appended to a read-side project ambiguity when the
541
+ * uuid-reachable pool holds additional TRASHED twins of the ambiguous name. The
542
+ * ambiguity COUNT stays over live rows (coherent with the rendered candidate
543
+ * list); the dead ones are disclosed separately rather than inflating it. Empty
544
+ * when none are trashed.
545
+ */
546
+ export function trashDisclosureTail(deadCount) {
547
+ return deadCount === 0
548
+ ? ""
549
+ : `; also matched: ${deadCount} in the trash — \`things trash\` lists them, a uuid reaches one directly`;
429
550
  }
430
551
  export function resolveAreaUuid(db, ref, options) {
431
552
  return resolveUuidOrThrow(db, "TMArea", "1=1", ref, "area", "things areas", options);
@@ -464,13 +585,81 @@ export function resolveHeadingUuid(db, projectUuid, refRaw) {
464
585
  throw new ReferenceResolutionError(`no heading matching ${label} in this project — a heading selector is an exact title or a uuid (list them with \`things project-view <project>\`)`, { code: "not-found", ref: refRaw });
465
586
  }
466
587
  const all = r.candidates ?? [];
467
- const capped = all.length > CANDIDATE_CAP;
468
- throw new ReferenceResolutionError(`${label} matches ${r.matches} headings in this project${capped ? `; first ${CANDIDATE_CAP} shown` : ""}disambiguate with a uuid`, {
588
+ const shown = all.slice(0, CANDIDATE_CAP);
589
+ // The fused ref form `Title [8charPrefix]` per candidate — a titleless heading
590
+ // reads ` [prefix]`, which pastes back as its decorated ref.
591
+ const lines = shown.map((c) => ` ${fusedRef(c.title, c.uuid)}`).join("\n");
592
+ const more = all.length > CANDIDATE_CAP ? `\n … ${all.length - CANDIDATE_CAP} more` : "";
593
+ throw new ReferenceResolutionError(`${label} matches ${r.matches} headings in this project — disambiguate with a ref below:\n${lines}${more}`, {
469
594
  code: "ambiguous",
470
595
  ref: refRaw,
471
- candidates: all.slice(0, CANDIDATE_CAP).map((c) => candidateRef("heading", c)),
596
+ candidates: shown.map((c) => candidateRef("heading", c)),
472
597
  });
473
598
  }
599
+ /**
600
+ * Resolve a container ref's bare TITLE through the REAL resolver for its kind,
601
+ * returning the sole resolved uuid or null (not-found OR ambiguous → null). This
602
+ * is the emit-side promotion judgment behind the JSON round-trip law (the flat
603
+ * `area`/`project`/`heading` refs promote a `*Uuid` sibling exactly when their
604
+ * bare title would NOT round-trip): it is the RESOLVER'S OWN judgment, not a
605
+ * re-derived uniqueness query, so it inherits every quirk of the write path —
606
+ * notably projects resolve through {@link resolveProjectWriteTarget} (the
607
+ * uuid-prefix tier runs FIRST, over the live+open write-target pool, so a title
608
+ * that is a valid unique uuid-prefix of ANOTHER task resolves to that task, not
609
+ * this project → does not round-trip → promotes; logged/trashed/completed
610
+ * same-titled twins never resolve by name, so they never trigger promotion).
611
+ * Areas resolve over all areas ({@link resolveAreaUuid}); headings within their
612
+ * project ({@link resolveHeadingRef}, `type = 2 AND trashed = 0 AND project = ?`).
613
+ * A thrown {@link ReferenceResolutionError} (not-found / ambiguous) is caught and
614
+ * read as "did not resolve" → null.
615
+ */
616
+ function resolveTitleForRoundTrip(db, kind, title, projectUuid) {
617
+ try {
618
+ if (kind === "area")
619
+ return resolveAreaUuid(db, title);
620
+ if (kind === "project")
621
+ return resolveProjectWriteTarget(db, title);
622
+ if (projectUuid === undefined)
623
+ return null; // heading round-trip needs its project scope
624
+ return resolveHeadingRef(db, projectUuid, title).resolved?.uuid ?? null;
625
+ }
626
+ catch {
627
+ return null;
628
+ }
629
+ }
630
+ /**
631
+ * Does a container ref's bare TITLE round-trip through its own resolver, in its
632
+ * own scope, back to THIS entity's uuid? The single predicate the emit boundary
633
+ * consults for the JSON round-trip law — true only when the sole resolution is
634
+ * `entityUuid` (not-found / ambiguous / a different entity all → false, i.e. the
635
+ * title must promote its uuid sibling). See {@link resolveTitleForRoundTrip}.
636
+ */
637
+ export function titleRoundTrips(db, kind, title, entityUuid, projectUuid) {
638
+ return resolveTitleForRoundTrip(db, kind, title, projectUuid) === entityUuid;
639
+ }
640
+ /**
641
+ * Build the {@link RefPromoter} the read-shaping transform ({@link shapeReadPayload})
642
+ * consults to decide flat-ref uuid promotion, memoized per (kind, title, scope)
643
+ * for ONE response emission so a large view never re-runs a resolution it has
644
+ * already made. A fresh promoter (fresh memo) per response — the consumer
645
+ * surfaces build one via the client. The memo caches the RESOLVED uuid (or null),
646
+ * independent of the comparison target, so twin entities sharing a title share
647
+ * the one resolution.
648
+ */
649
+ export function makeRefPromoter(db) {
650
+ const memo = new Map();
651
+ const resolve = (kind, title, projectUuid) => {
652
+ const key = `${kind}\x00${projectUuid ?? ""}\x00${title}`;
653
+ if (memo.has(key))
654
+ return memo.get(key) ?? null;
655
+ const uuid = resolveTitleForRoundTrip(db, kind, title, projectUuid);
656
+ memo.set(key, uuid);
657
+ return uuid;
658
+ };
659
+ return {
660
+ roundTrips: (kind, title, entityUuid, projectUuid) => resolve(kind, title, projectUuid) === entityUuid,
661
+ };
662
+ }
474
663
  /**
475
664
  * A row's EFFECTIVE area: its own `area` link, else the area of its project,
476
665
  * else the area of its heading's project. To-dos nested in a project (or under a