things-api 0.8.0 → 0.10.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 (238) hide show
  1. package/README.md +31 -11
  2. package/dist/audit/log.js +56 -2
  3. package/dist/audit/log.js.map +1 -1
  4. package/dist/audit/schema.d.ts +26 -8
  5. package/dist/audit/schema.js +42 -1
  6. package/dist/audit/schema.js.map +1 -1
  7. package/dist/cli/commands/area.d.ts +34 -2
  8. package/dist/cli/commands/area.js +140 -28
  9. package/dist/cli/commands/area.js.map +1 -1
  10. package/dist/cli/commands/doctor.d.ts +1 -1
  11. package/dist/cli/commands/doctor.js +44 -5
  12. package/dist/cli/commands/doctor.js.map +1 -1
  13. package/dist/cli/commands/mcp.js +23 -4
  14. package/dist/cli/commands/mcp.js.map +1 -1
  15. package/dist/cli/commands/project.d.ts +23 -1
  16. package/dist/cli/commands/project.js +82 -15
  17. package/dist/cli/commands/project.js.map +1 -1
  18. package/dist/cli/commands/reads.d.ts +6 -125
  19. package/dist/cli/commands/reads.js +649 -657
  20. package/dist/cli/commands/reads.js.map +1 -1
  21. package/dist/cli/commands/repeat-flags.d.ts +20 -0
  22. package/dist/cli/commands/repeat-flags.js +76 -0
  23. package/dist/cli/commands/repeat-flags.js.map +1 -0
  24. package/dist/cli/commands/setup.js +2 -3
  25. package/dist/cli/commands/setup.js.map +1 -1
  26. package/dist/cli/commands/show.d.ts +1 -1
  27. package/dist/cli/commands/show.js +169 -18
  28. package/dist/cli/commands/show.js.map +1 -1
  29. package/dist/cli/commands/snapshot.js +1 -1
  30. package/dist/cli/commands/snapshot.js.map +1 -1
  31. package/dist/cli/commands/todo.d.ts +1 -1
  32. package/dist/cli/commands/todo.js +21 -9
  33. package/dist/cli/commands/todo.js.map +1 -1
  34. package/dist/cli/commands/writes.js +378 -148
  35. package/dist/cli/commands/writes.js.map +1 -1
  36. package/dist/cli/did-you-mean.d.ts +29 -0
  37. package/dist/cli/did-you-mean.js +53 -0
  38. package/dist/cli/did-you-mean.js.map +1 -0
  39. package/dist/cli/excess-args.d.ts +15 -0
  40. package/dist/cli/excess-args.js +51 -0
  41. package/dist/cli/excess-args.js.map +1 -0
  42. package/dist/cli/glyphs.d.ts +67 -7
  43. package/dist/cli/glyphs.js +202 -19
  44. package/dist/cli/glyphs.js.map +1 -1
  45. package/dist/cli/help.d.ts +54 -0
  46. package/dist/cli/help.js +355 -0
  47. package/dist/cli/help.js.map +1 -0
  48. package/dist/cli/main.js +36 -21
  49. package/dist/cli/main.js.map +1 -1
  50. package/dist/cli/period.d.ts +39 -0
  51. package/dist/cli/period.js +141 -0
  52. package/dist/cli/period.js.map +1 -0
  53. package/dist/cli/read-driver.d.ts +83 -0
  54. package/dist/cli/read-driver.js +215 -0
  55. package/dist/cli/read-driver.js.map +1 -0
  56. package/dist/cli/render.d.ts +246 -0
  57. package/dist/cli/render.js +806 -0
  58. package/dist/cli/render.js.map +1 -0
  59. package/dist/cli/resolve-invocation.d.ts +116 -0
  60. package/dist/cli/resolve-invocation.js +261 -0
  61. package/dist/cli/resolve-invocation.js.map +1 -0
  62. package/dist/cli/shell-quote.d.ts +9 -0
  63. package/dist/cli/shell-quote.js +12 -0
  64. package/dist/cli/shell-quote.js.map +1 -0
  65. package/dist/cli/tag-filters.d.ts +59 -0
  66. package/dist/cli/tag-filters.js +57 -0
  67. package/dist/cli/tag-filters.js.map +1 -0
  68. package/dist/cli/verb-hint.d.ts +25 -0
  69. package/dist/cli/verb-hint.js +138 -0
  70. package/dist/cli/verb-hint.js.map +1 -0
  71. package/dist/cli/width.d.ts +135 -0
  72. package/dist/cli/width.js +313 -0
  73. package/dist/cli/width.js.map +1 -0
  74. package/dist/client.d.ts +146 -24
  75. package/dist/client.js +106 -16
  76. package/dist/client.js.map +1 -1
  77. package/dist/config.d.ts +11 -0
  78. package/dist/config.js +3 -0
  79. package/dist/config.js.map +1 -1
  80. package/dist/contracts.d.ts +136 -1
  81. package/dist/contracts.js +44 -1
  82. package/dist/contracts.js.map +1 -1
  83. package/dist/db/fingerprint.d.ts +12 -0
  84. package/dist/db/fingerprint.js +15 -1
  85. package/dist/db/fingerprint.js.map +1 -1
  86. package/dist/db/locate.js +1 -1
  87. package/dist/db/locate.js.map +1 -1
  88. package/dist/diagnose.d.ts +63 -0
  89. package/dist/diagnose.js +38 -1
  90. package/dist/diagnose.js.map +1 -1
  91. package/dist/index.d.ts +30 -4
  92. package/dist/index.js +35 -2
  93. package/dist/index.js.map +1 -1
  94. package/dist/mcp/server.d.ts +9 -1
  95. package/dist/mcp/server.js +850 -128
  96. package/dist/mcp/server.js.map +1 -1
  97. package/dist/model/entities.d.ts +40 -7
  98. package/dist/model/entities.js.map +1 -1
  99. package/dist/model/mappers.d.ts +7 -0
  100. package/dist/model/mappers.js +20 -3
  101. package/dist/model/mappers.js.map +1 -1
  102. package/dist/model/occurrences.d.ts +6 -1
  103. package/dist/model/occurrences.js +9 -5
  104. package/dist/model/occurrences.js.map +1 -1
  105. package/dist/model/recurrence.d.ts +17 -8
  106. package/dist/model/recurrence.js.map +1 -1
  107. package/dist/model/serialize.d.ts +28 -0
  108. package/dist/model/serialize.js +80 -0
  109. package/dist/model/serialize.js.map +1 -0
  110. package/dist/model/when-sugar.d.ts +47 -0
  111. package/dist/model/when-sugar.js +45 -0
  112. package/dist/model/when-sugar.js.map +1 -0
  113. package/dist/read/area-view.d.ts +8 -1
  114. package/dist/read/area-view.js +49 -13
  115. package/dist/read/area-view.js.map +1 -1
  116. package/dist/read/caps.d.ts +14 -0
  117. package/dist/read/caps.js +21 -0
  118. package/dist/read/caps.js.map +1 -0
  119. package/dist/read/filter-contract.d.ts +117 -0
  120. package/dist/read/filter-contract.js +78 -0
  121. package/dist/read/filter-contract.js.map +1 -0
  122. package/dist/read/log-boundary.d.ts +9 -4
  123. package/dist/read/log-boundary.js.map +1 -1
  124. package/dist/read/predicates.d.ts +61 -0
  125. package/dist/read/predicates.js +78 -0
  126. package/dist/read/predicates.js.map +1 -0
  127. package/dist/read/project-view.d.ts +12 -1
  128. package/dist/read/project-view.js +39 -8
  129. package/dist/read/project-view.js.map +1 -1
  130. package/dist/read/queries.d.ts +156 -8
  131. package/dist/read/queries.js +261 -38
  132. package/dist/read/queries.js.map +1 -1
  133. package/dist/read/search-rank.d.ts +36 -0
  134. package/dist/read/search-rank.js +31 -0
  135. package/dist/read/search-rank.js.map +1 -0
  136. package/dist/read/sections.d.ts +51 -0
  137. package/dist/read/sections.js +37 -0
  138. package/dist/read/sections.js.map +1 -0
  139. package/dist/read/show-target.d.ts +13 -4
  140. package/dist/read/show-target.js +11 -2
  141. package/dist/read/show-target.js.map +1 -1
  142. package/dist/read/sidebar-order.d.ts +19 -0
  143. package/dist/read/sidebar-order.js +93 -0
  144. package/dist/read/sidebar-order.js.map +1 -0
  145. package/dist/read/tags.d.ts +27 -3
  146. package/dist/read/tags.js +83 -12
  147. package/dist/read/tags.js.map +1 -1
  148. package/dist/read/truncation.d.ts +71 -0
  149. package/dist/read/truncation.js +217 -0
  150. package/dist/read/truncation.js.map +1 -0
  151. package/dist/read/views.d.ts +177 -19
  152. package/dist/read/views.js +390 -213
  153. package/dist/read/views.js.map +1 -1
  154. package/dist/surface-copy.d.ts +42 -0
  155. package/dist/surface-copy.js +55 -0
  156. package/dist/surface-copy.js.map +1 -1
  157. package/dist/sync-health.d.ts +78 -0
  158. package/dist/sync-health.js +312 -0
  159. package/dist/sync-health.js.map +1 -0
  160. package/dist/write/accessibility-probe.d.ts +12 -0
  161. package/dist/write/accessibility-probe.js +63 -0
  162. package/dist/write/accessibility-probe.js.map +1 -0
  163. package/dist/write/automation-probe.d.ts +7 -0
  164. package/dist/write/automation-probe.js +8 -1
  165. package/dist/write/automation-probe.js.map +1 -1
  166. package/dist/write/batch.js +4 -2
  167. package/dist/write/batch.js.map +1 -1
  168. package/dist/write/capabilities.d.ts +8 -0
  169. package/dist/write/capabilities.js +13 -5
  170. package/dist/write/capabilities.js.map +1 -1
  171. package/dist/write/commands.d.ts +2 -0
  172. package/dist/write/commands.js +562 -48
  173. package/dist/write/commands.js.map +1 -1
  174. package/dist/write/edit-checklist.js +3 -2
  175. package/dist/write/edit-checklist.js.map +1 -1
  176. package/dist/write/guards.d.ts +2 -7
  177. package/dist/write/guards.js +64 -6
  178. package/dist/write/guards.js.map +1 -1
  179. package/dist/write/heading.js +2 -0
  180. package/dist/write/heading.js.map +1 -1
  181. package/dist/write/lock.d.ts +40 -2
  182. package/dist/write/lock.js +91 -14
  183. package/dist/write/lock.js.map +1 -1
  184. package/dist/write/make-repeating-project.d.ts +4 -0
  185. package/dist/write/make-repeating-project.js +253 -0
  186. package/dist/write/make-repeating-project.js.map +1 -0
  187. package/dist/write/operations.d.ts +145 -1
  188. package/dist/write/operations.js +48 -0
  189. package/dist/write/operations.js.map +1 -1
  190. package/dist/write/pipeline.d.ts +19 -0
  191. package/dist/write/pipeline.js +109 -26
  192. package/dist/write/pipeline.js.map +1 -1
  193. package/dist/write/pre-state.d.ts +68 -3
  194. package/dist/write/pre-state.js +102 -3
  195. package/dist/write/pre-state.js.map +1 -1
  196. package/dist/write/reopen.js +3 -2
  197. package/dist/write/reopen.js.map +1 -1
  198. package/dist/write/reorder.js +27 -17
  199. package/dist/write/reorder.js.map +1 -1
  200. package/dist/write/repeat-rule.d.ts +22 -0
  201. package/dist/write/repeat-rule.js +258 -0
  202. package/dist/write/repeat-rule.js.map +1 -0
  203. package/dist/write/reversibility.js +49 -0
  204. package/dist/write/reversibility.js.map +1 -1
  205. package/dist/write/tag-refs.d.ts +47 -0
  206. package/dist/write/tag-refs.js +126 -0
  207. package/dist/write/tag-refs.js.map +1 -0
  208. package/dist/write/undo.d.ts +74 -5
  209. package/dist/write/undo.js +494 -80
  210. package/dist/write/undo.js.map +1 -1
  211. package/dist/write/vectors/registry.d.ts +17 -1
  212. package/dist/write/vectors/registry.js +18 -2
  213. package/dist/write/vectors/registry.js.map +1 -1
  214. package/dist/write/vectors/types.d.ts +119 -3
  215. package/dist/write/vectors/ui-certification.d.ts +48 -0
  216. package/dist/write/vectors/ui-certification.js +46 -0
  217. package/dist/write/vectors/ui-certification.js.map +1 -0
  218. package/dist/write/vectors/ui-drag.d.ts +188 -0
  219. package/dist/write/vectors/ui-drag.js +1095 -0
  220. package/dist/write/vectors/ui-drag.js.map +1 -0
  221. package/dist/write/vectors/ui-recipes.d.ts +70 -0
  222. package/dist/write/vectors/ui-recipes.js +558 -0
  223. package/dist/write/vectors/ui-recipes.js.map +1 -0
  224. package/dist/write/vectors/ui.d.ts +137 -0
  225. package/dist/write/vectors/ui.js +656 -0
  226. package/dist/write/vectors/ui.js.map +1 -0
  227. package/dist/write/verify/delta.d.ts +31 -3
  228. package/dist/write/verify/delta.js +22 -6
  229. package/dist/write/verify/delta.js.map +1 -1
  230. package/dist/write/verify/poller.js +1 -0
  231. package/dist/write/verify/poller.js.map +1 -1
  232. package/package.json +1 -1
  233. package/dist/cli/exit-codes.d.ts +0 -26
  234. package/dist/cli/exit-codes.js +0 -26
  235. package/dist/cli/exit-codes.js.map +0 -1
  236. package/dist/cli/output.d.ts +0 -42
  237. package/dist/cli/output.js +0 -16
  238. package/dist/cli/output.js.map +0 -1
@@ -0,0 +1,61 @@
1
+ /** Real to-dos/projects (type IN 0,1), untrashed, excluding repeating templates. */
2
+ export declare const LIVE = "t.type IN (0, 1) AND t.trashed = 0 AND (t.rt1_recurrenceRule IS NULL AND t.repeater IS NULL)";
3
+ /** {@link LIVE} restricted to open (status = 0) rows. */
4
+ export declare const OPEN = "t.type IN (0, 1) AND t.trashed = 0 AND (t.rt1_recurrenceRule IS NULL AND t.repeater IS NULL) AND t.status = 0";
5
+ /**
6
+ * The GUI-visible set: open rows PLUS closed (completed/canceled) rows the
7
+ * periodic log-move sweep has NOT yet carried to the Logbook — completion ≠
8
+ * logged (src/read/log-boundary.ts). A closed row keeps its slot in its list
9
+ * until the sweep passes it, exactly as the GUI keeps it checked in place; it
10
+ * leaves when the sweep boundary advances past its stopDate (boundary-relative,
11
+ * no new state). This is {@link LIVE} widened to that set: swept iff
12
+ * status IN (2,3) AND stopDate IS NOT NULL AND stopDate <= boundary — the
13
+ * negation of the {@link markLogged} `logged` predicate, so an included closed
14
+ * row always materializes with `logged=false`. Mirrors the project/area card
15
+ * precedent (project-view.ts / area-view.ts keep a closed-but-unswept row
16
+ * checked in place). ONE bind: the log boundary as epoch seconds
17
+ * (logBoundary(db, now).getTime() / 1000). Ruling 2026-07-14 (Mike): GUI parity
18
+ * — `today` and `anytime` show checked-but-unswept rows.
19
+ */
20
+ export declare const OPEN_OR_UNSWEPT = "t.type IN (0, 1) AND t.trashed = 0 AND (t.rt1_recurrenceRule IS NULL AND t.repeater IS NULL) AND NOT (t.status IN (2, 3) AND t.stopDate IS NOT NULL AND t.stopDate <= ?)";
21
+ /**
22
+ * The `--overdue` content scope: OPEN items whose deadline falls strictly
23
+ * BEFORE today. Due-TODAY is deliberately NOT overdue (`<`, not `<=`) —
24
+ * mirroring the app's own Today badge split, where a deadline EQUAL to today
25
+ * reads as "due" and only an EARLIER deadline reads as "overdue". One bind:
26
+ * today as a packed-date int (encodePackedDate(localToday(now))), so the
27
+ * boundary rides the same injected clock every other view uses — never a
28
+ * hardcoded date. The `t.status = 0` clause re-tightens the OPEN_OR_UNSWEPT
29
+ * views (today/anytime) so a checked-but-unswept row that happens to sit past
30
+ * a deadline is excluded — overdue is remaining, OPEN work. Trash exclusion is
31
+ * the hosting view's job (every view this composes into already drops trashed
32
+ * and derived-trashed rows), so it is not repeated here.
33
+ */
34
+ export declare const OVERDUE = "t.deadline IS NOT NULL AND t.deadline < ? AND t.status = 0";
35
+ /** An item's own anytime membership: unscheduled-active, or dated <= today. */
36
+ export declare const ANYTIME_SELF: (col: string) => string;
37
+ /**
38
+ * The item's effective project: its own link, or its heading's project for
39
+ * headed children (heading rows carry the project link).
40
+ */
41
+ export declare const EFF_PROJECT = "COALESCE(t.project, (SELECT h.project FROM TMTask h WHERE h.uuid = t.heading))";
42
+ /**
43
+ * Container cascade (live-verified against the UI, 2026-07-09): a to-do
44
+ * inside a project that is NOT itself anytime-visible (someday or
45
+ * future-scheduled, logged, or trashed) is absent from Anytime regardless of
46
+ * the to-do's own start state — the project row alone represents it.
47
+ * Projects and container-less to-dos pass through. Two binds (packedToday ×2).
48
+ */
49
+ export declare const PROJECT_ANYTIME_ACTIVE: string;
50
+ /**
51
+ * DERIVED-trash exclusion: project deletion is SHALLOW (A24B — only the
52
+ * project row flips trashed=1; children keep trashed=0 and their links, so
53
+ * their Trash membership is derived through the container chain). Every live
54
+ * view must therefore check the chain, not just the row's own flag: the
55
+ * heading (if any) and the effective project (direct or via heading) must
56
+ * both be untrashed. Areas cannot be trashed (they delete permanently), so
57
+ * the chain is at most heading → project. Trash-adjacent surfaces stay
58
+ * exempt on purpose: `things trash` lists directly-flagged rows, and a
59
+ * trashed project's OWN view shows its would-be-recovered children.
60
+ */
61
+ export declare const CONTAINER_UNTRASHED = "(t.heading IS NULL OR EXISTS (\n SELECT 1 FROM TMTask hh WHERE hh.uuid = t.heading AND hh.trashed = 0))\n AND (COALESCE(t.project, (SELECT h.project FROM TMTask h WHERE h.uuid = t.heading)) IS NULL OR EXISTS (\n SELECT 1 FROM TMTask cc WHERE cc.uuid = COALESCE(t.project, (SELECT h.project FROM TMTask h WHERE h.uuid = t.heading)) AND cc.trashed = 0))";
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Shared SQL predicate fragments for the list views (src/read/views.ts).
3
+ * Every live view composes these so the membership rules — untrashed, open,
4
+ * anytime-self, the effective-project resolution, and the derived-trash
5
+ * container checks — are written once and cannot drift between views.
6
+ *
7
+ * Derivation and live-probe provenance are documented at each view's call
8
+ * site in views.ts and in docs/atlas/schema-v26.md.
9
+ */
10
+ import { NOT_TEMPLATE } from "./queries.js";
11
+ /** Real to-dos/projects (type IN 0,1), untrashed, excluding repeating templates. */
12
+ export const LIVE = `t.type IN (0, 1) AND t.trashed = 0 AND ${NOT_TEMPLATE}`;
13
+ /** {@link LIVE} restricted to open (status = 0) rows. */
14
+ export const OPEN = `${LIVE} AND t.status = 0`;
15
+ /**
16
+ * The GUI-visible set: open rows PLUS closed (completed/canceled) rows the
17
+ * periodic log-move sweep has NOT yet carried to the Logbook — completion ≠
18
+ * logged (src/read/log-boundary.ts). A closed row keeps its slot in its list
19
+ * until the sweep passes it, exactly as the GUI keeps it checked in place; it
20
+ * leaves when the sweep boundary advances past its stopDate (boundary-relative,
21
+ * no new state). This is {@link LIVE} widened to that set: swept iff
22
+ * status IN (2,3) AND stopDate IS NOT NULL AND stopDate <= boundary — the
23
+ * negation of the {@link markLogged} `logged` predicate, so an included closed
24
+ * row always materializes with `logged=false`. Mirrors the project/area card
25
+ * precedent (project-view.ts / area-view.ts keep a closed-but-unswept row
26
+ * checked in place). ONE bind: the log boundary as epoch seconds
27
+ * (logBoundary(db, now).getTime() / 1000). Ruling 2026-07-14 (Mike): GUI parity
28
+ * — `today` and `anytime` show checked-but-unswept rows.
29
+ */
30
+ export const OPEN_OR_UNSWEPT = `${LIVE} AND NOT (t.status IN (2, 3) AND t.stopDate IS NOT NULL AND t.stopDate <= ?)`;
31
+ /**
32
+ * The `--overdue` content scope: OPEN items whose deadline falls strictly
33
+ * BEFORE today. Due-TODAY is deliberately NOT overdue (`<`, not `<=`) —
34
+ * mirroring the app's own Today badge split, where a deadline EQUAL to today
35
+ * reads as "due" and only an EARLIER deadline reads as "overdue". One bind:
36
+ * today as a packed-date int (encodePackedDate(localToday(now))), so the
37
+ * boundary rides the same injected clock every other view uses — never a
38
+ * hardcoded date. The `t.status = 0` clause re-tightens the OPEN_OR_UNSWEPT
39
+ * views (today/anytime) so a checked-but-unswept row that happens to sit past
40
+ * a deadline is excluded — overdue is remaining, OPEN work. Trash exclusion is
41
+ * the hosting view's job (every view this composes into already drops trashed
42
+ * and derived-trashed rows), so it is not repeated here.
43
+ */
44
+ export const OVERDUE = `t.deadline IS NOT NULL AND t.deadline < ? AND t.status = 0`;
45
+ /** An item's own anytime membership: unscheduled-active, or dated <= today. */
46
+ export const ANYTIME_SELF = (col) => `((${col}.start = 1 AND (${col}.startDate IS NULL OR ${col}.startDate <= ?))
47
+ OR (${col}.start = 2 AND ${col}.startDate IS NOT NULL AND ${col}.startDate <= ?))`;
48
+ /**
49
+ * The item's effective project: its own link, or its heading's project for
50
+ * headed children (heading rows carry the project link).
51
+ */
52
+ export const EFF_PROJECT = `COALESCE(t.project, (SELECT h.project FROM TMTask h WHERE h.uuid = t.heading))`;
53
+ /**
54
+ * Container cascade (live-verified against the UI, 2026-07-09): a to-do
55
+ * inside a project that is NOT itself anytime-visible (someday or
56
+ * future-scheduled, logged, or trashed) is absent from Anytime regardless of
57
+ * the to-do's own start state — the project row alone represents it.
58
+ * Projects and container-less to-dos pass through. Two binds (packedToday ×2).
59
+ */
60
+ export const PROJECT_ANYTIME_ACTIVE = `(${EFF_PROJECT} IS NULL OR EXISTS (
61
+ SELECT 1 FROM TMTask p WHERE p.uuid = ${EFF_PROJECT}
62
+ AND p.trashed = 0 AND p.status = 0 AND ${ANYTIME_SELF("p")}))`;
63
+ /**
64
+ * DERIVED-trash exclusion: project deletion is SHALLOW (A24B — only the
65
+ * project row flips trashed=1; children keep trashed=0 and their links, so
66
+ * their Trash membership is derived through the container chain). Every live
67
+ * view must therefore check the chain, not just the row's own flag: the
68
+ * heading (if any) and the effective project (direct or via heading) must
69
+ * both be untrashed. Areas cannot be trashed (they delete permanently), so
70
+ * the chain is at most heading → project. Trash-adjacent surfaces stay
71
+ * exempt on purpose: `things trash` lists directly-flagged rows, and a
72
+ * trashed project's OWN view shows its would-be-recovered children.
73
+ */
74
+ export const CONTAINER_UNTRASHED = `(t.heading IS NULL OR EXISTS (
75
+ SELECT 1 FROM TMTask hh WHERE hh.uuid = t.heading AND hh.trashed = 0))
76
+ AND (${EFF_PROJECT} IS NULL OR EXISTS (
77
+ SELECT 1 FROM TMTask cc WHERE cc.uuid = ${EFF_PROJECT} AND cc.trashed = 0))`;
78
+ //# sourceMappingURL=predicates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predicates.js","sourceRoot":"","sources":["../../src/read/predicates.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,oFAAoF;AACpF,MAAM,CAAC,MAAM,IAAI,GAAG,0CAA0C,YAAY,EAAE,CAAC;AAE7E,yDAAyD;AACzD,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,IAAI,mBAAmB,CAAC;AAE/C;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,IAAI,8EAA8E,CAAC;AAErH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,4DAA4D,CAAC;AAEpF,+EAA+E;AAC/E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAW,EAAU,EAAE,CAClD,KAAK,GAAG,mBAAmB,GAAG,yBAAyB,GAAG;UAClD,GAAG,kBAAkB,GAAG,8BAA8B,GAAG,mBAAmB,CAAC;AAEvF;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,gFAAgF,CAAC;AAE5G;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,WAAW;6CACR,WAAW;8CACV,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;AAEpE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;QAE3B,WAAW;+CAC4B,WAAW,uBAAuB,CAAC"}
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import type { DatabaseSync } from "node:sqlite";
11
11
  import type { Heading, IsoDateGroup, Project, Todo } from "../model/entities.ts";
12
+ import { type ViewFilter } from "./views.ts";
12
13
  export interface ProjectView {
13
14
  project: Project;
14
15
  /** Open, unscheduled/current children not under a heading, by index. */
@@ -27,8 +28,18 @@ export interface ProjectView {
27
28
  };
28
29
  logged: Todo[];
29
30
  trashed: Todo[];
31
+ /**
32
+ * PLOG1 (additive): count of untrashed OPEN (status=0) children this project
33
+ * still holds while it is ITSELF completed or canceled — including once swept
34
+ * to the Logbook. Such items are invisible in every live app view (Today /
35
+ * Anytime / Inbox / Upcoming), reachable only by drilling into this card
36
+ * (the app's GUI "Put Back" into a completed parent can strand them —
37
+ * docs/lab/plog1-research.md). 0 when the project is open, or holds no such
38
+ * child.
39
+ */
40
+ openChildrenWhileResolved: number;
30
41
  }
31
42
  export declare class ProjectNotFoundError extends Error {
32
43
  constructor(uuid: string);
33
44
  }
34
- export declare function projectView(db: DatabaseSync, uuid: string, now?: Date): ProjectView;
45
+ export declare function projectView(db: DatabaseSync, uuid: string, now?: Date, filter?: ViewFilter): ProjectView;
@@ -2,14 +2,17 @@ import { encodePackedDate, localToday, decodePackedDate } from "../model/dates.j
2
2
  import { mapHeading, mapProject, mapTodo } from "../model/mappers.js";
3
3
  import { fetchTagsForTasks, fetchTaskByUuid, fetchTaskRows, makeRefResolver } from "./queries.js";
4
4
  import { logBoundary, markLogged } from "./log-boundary.js";
5
+ import { OVERDUE } from "./predicates.js";
5
6
  import { inheritedTagsFor } from "./tags.js";
7
+ import { tagFilter } from "./views.js";
6
8
  export class ProjectNotFoundError extends Error {
7
9
  constructor(uuid) {
8
10
  super(`no project with uuid ${uuid}`);
9
11
  this.name = "ProjectNotFoundError";
10
12
  }
11
13
  }
12
- export function projectView(db, uuid, now) {
14
+ export function projectView(db, uuid, now, filter = {}) {
15
+ const overdue = filter.overdue === true;
13
16
  const projectRow = fetchTaskByUuid(db, uuid);
14
17
  if (!projectRow || projectRow.type !== 1)
15
18
  throw new ProjectNotFoundError(uuid);
@@ -21,10 +24,26 @@ export function projectView(db, uuid, now) {
21
24
  project.inheritedTags = inheritedTagsFor(db, projectRow);
22
25
  const headingRows = fetchTaskRows(db, `t.type = 2 AND t.project = ? AND t.trashed = 0 ORDER BY t."index" ASC`, [uuid]);
23
26
  const headings = headingRows.map((h) => mapHeading(h, refs));
27
+ // OWN-DEADLINE UNIFORM: `--overdue` narrows the child TO-DOS to those whose
28
+ // OWN deadline is overdue (open, strictly before today) via the shared
29
+ // OVERDUE predicate — NO recursion into anything, and the project header
30
+ // itself always renders. Headings that keep no surviving child collapse
31
+ // (below). Its single packed-today bind trails the two uuid binds.
32
+ const overdueSql = overdue ? ` AND ${OVERDUE}` : "";
33
+ const overdueBinds = overdue ? [encodePackedDate(localToday(now))] : [];
34
+ // Tag scope (§9a): the child to-dos are filtered by a tag carried DIRECTLY on
35
+ // the row — the container semantics. The project's own tags are inherited by
36
+ // EVERY child, so an inheritance-inclusive `--tag` would be vacuous here;
37
+ // suppressing the container hop makes `--tag` mean "children with this tag on
38
+ // themselves" (still descendant-expanded), and `--untagged` "children with no
39
+ // direct tag". No recursion; the header always renders. The tag binds trail
40
+ // the two uuid binds and lead the overdue bind, matching their left-to-right
41
+ // order in the SQL.
42
+ const tf = tagFilter(db, filter, { container: true });
24
43
  const childRows = fetchTaskRows(db, `t.type = 0 AND (t.project = ? OR t.heading IN (
25
44
  SELECT uuid FROM TMTask WHERE type = 2 AND project = ?
26
- ))
27
- ORDER BY t."index" ASC`, [uuid, uuid]);
45
+ ))${tf.sql}${overdueSql}
46
+ ORDER BY t."index" ASC`, [uuid, uuid, ...tf.binds, ...overdueBinds]);
28
47
  const childTags = tagsOf(childRows);
29
48
  const boundary = logBoundary(db, now);
30
49
  const todos = childRows.map((r) => ({
@@ -40,6 +59,10 @@ export function projectView(db, uuid, now) {
40
59
  const someday = [];
41
60
  const logged = [];
42
61
  const trashed = [];
62
+ // Every untrashed, non-template OPEN child, regardless of which bucket it
63
+ // lands in below — surfaced (only when the project is itself resolved) as
64
+ // the PLOG1 stranded-open-child count.
65
+ let openChildren = 0;
43
66
  for (const { row, todo } of todos) {
44
67
  if (row.trashed === 1) {
45
68
  trashed.push(todo);
@@ -49,6 +72,8 @@ export function projectView(db, uuid, now) {
49
72
  repeating.push(todo);
50
73
  continue;
51
74
  }
75
+ if (row.status === 0)
76
+ openChildren += 1;
52
77
  if (row.status !== 0) {
53
78
  // Completion ≠ logged: closed items the log-move sweep has not
54
79
  // passed stay checked IN PLACE (their heading / the active block),
@@ -90,23 +115,29 @@ export function projectView(db, uuid, now) {
90
115
  logged.sort((a, b) => (b.stopped?.getTime() ?? 0) - (a.stopped?.getTime() ?? 0));
91
116
  const scheduled = [];
92
117
  // Within a day the UI sorts by todayIndex ASC (Upcoming drag order).
93
- for (const { date, todo } of scheduledRows.sort((a, b) => a.date.localeCompare(b.date) || a.ti - b.ti)) {
118
+ for (const { date, todo } of scheduledRows.toSorted((a, b) => a.date.localeCompare(b.date) || a.ti - b.ti)) {
94
119
  const last = scheduled[scheduled.length - 1];
95
120
  if (last && last.date === date)
96
121
  last.items.push(todo);
97
122
  else
98
123
  scheduled.push({ date, items: [todo] });
99
124
  }
125
+ // Under any content scope (`--overdue` or a tag filter), a heading whose
126
+ // children were all filtered out collapses rather than rendering an empty
127
+ // section; a heading with a surviving child is kept. With no scope active
128
+ // every heading renders (its own empty state).
129
+ const contentScoped = overdue || tf.sql !== "";
130
+ const headingGroups = headings
131
+ .map((heading) => ({ heading, items: byHeading.get(heading.uuid) ?? [] }))
132
+ .filter((g) => !contentScoped || g.items.length > 0);
100
133
  return {
101
134
  project,
102
135
  active,
103
- headings: headings.map((heading) => ({
104
- heading,
105
- items: byHeading.get(heading.uuid) ?? [],
106
- })),
136
+ headings: headingGroups,
107
137
  later: { scheduled, repeating, someday },
108
138
  logged,
109
139
  trashed,
140
+ openChildrenWhileResolved: project.status === "open" ? 0 : openChildren,
110
141
  };
111
142
  }
112
143
  //# sourceMappingURL=project-view.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-view.js","sourceRoot":"","sources":["../../src/read/project-view.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEnF,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAgB,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAClG,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAmB7C,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,IAAY;QACtB,KAAK,CAAC,wBAAwB,IAAI,EAAE,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,MAAM,UAAU,WAAW,CAAC,EAAgB,EAAE,IAAY,EAAE,GAAU;IACpE,MAAM,UAAU,GAAG,eAAe,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC;QAAE,MAAM,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAE/E,MAAM,IAAI,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,CAAC,IAAe,EAAE,EAAE,CACjC,iBAAiB,CACf,EAAE,EACF,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CACxB,CAAC;IACJ,MAAM,WAAW,GAAG,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,UAAU,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACrF,gFAAgF;IAChF,OAAO,CAAC,aAAa,GAAG,gBAAgB,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;IAEzD,MAAM,WAAW,GAAG,aAAa,CAC/B,EAAE,EACF,uEAAuE,EACvE,CAAC,IAAI,CAAC,CACP,CAAC;IACF,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAE7D,MAAM,SAAS,GAAG,aAAa,CAC7B,EAAE,EACF;;;4BAGwB,EACxB,CAAC,IAAI,EAAE,IAAI,CAAC,CACb,CAAC;IACF,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,WAAW,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAClC,GAAG,EAAE,CAAC;QACN,IAAI,EAAE,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;KACpD,CAAC,CAAC,CAAC;IACJ,UAAU,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAE7D,MAAM,WAAW,GAAG,gBAAgB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,MAAM,MAAM,GAAW,EAAE,CAAC;IAC1B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC5C,MAAM,aAAa,GAAoD,EAAE,CAAC;IAC1E,MAAM,SAAS,GAAW,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAW,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAW,EAAE,CAAC;IAC1B,MAAM,OAAO,GAAW,EAAE,CAAC;IAE3B,KAAK,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QAClC,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC;YAC9B,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,+DAA+D;YAC/D,mEAAmE;YACnE,mEAAmE;YACnE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAClB,SAAS;YACX,CAAC;YACD,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBAChB,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;gBAC9C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAChB,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACnC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC;YACD,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;YAC9C,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI,IAAI,GAAG,CAAC,SAAS,GAAG,WAAW,EAAE,CAAC;YAC1D,aAAa,CAAC,IAAI,CAAC;gBACjB,IAAI,EAAE,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE;gBAC3C,EAAE,EAAE,GAAG,CAAC,UAAU,IAAI,CAAC;gBACvB,IAAI;aACL,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAC9C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChB,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACjC,SAAS;QACX,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACjF,MAAM,SAAS,GAAyB,EAAE,CAAC;IAC3C,qEAAqE;IACrE,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,aAAa,CAAC,IAAI,CAC7C,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CACtD,EAAE,CAAC;QACF,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC7C,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;YAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;YACjD,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO;QACL,OAAO;QACP,MAAM;QACN,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACnC,OAAO;YACP,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE;SACzC,CAAC,CAAC;QACH,KAAK,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE;QACxC,MAAM;QACN,OAAO;KACR,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"project-view.js","sourceRoot":"","sources":["../../src/read/project-view.ts"],"names":[],"mappings":"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"}
@@ -9,13 +9,51 @@ import type { ChecklistRow, TaskRow } from "../model/mappers.ts";
9
9
  export declare const NOT_TEMPLATE = "(t.rt1_recurrenceRule IS NULL AND t.repeater IS NULL)";
10
10
  /**
11
11
  * UI-faithful tag membership for list filtering: direct tag, or inherited
12
- * through the ancestor chain heading → project → area (T18/U18/A13 — the
13
- * same chain inheritedTagsFor() walks). Takes a SET of tag uuids (the target
14
- * plus its hierarchy descendants) each of the six clauses gets the full
15
- * set, so callers bind `uuids.length * 6` values via tagScopeBinds().
12
+ * through the ancestor chain heading → project → area — the OR of every
13
+ * {@link INHERITANCE_CLAUSES} hop. Takes a SET of tag uuids (the target plus its
14
+ * hierarchy descendants); each hop gets the full set, so callers bind
15
+ * `uuids.length * INHERITANCE_CLAUSES.length` values via {@link tagScopeBinds}.
16
16
  */
17
17
  export declare function tagScopeSql(uuidCount: number): string;
18
+ /**
19
+ * The bind list for {@link tagScopeSql}: the uuid set repeated once per hop, in
20
+ * clause order. Derived from the clause count so the bind multiplicity can never
21
+ * drift from the number of hops the SQL actually emits.
22
+ */
18
23
  export declare function tagScopeBinds(uuids: string[]): string[];
24
+ /**
25
+ * The DIRECT-ONLY projection of {@link tagScopeSql}: {@link DIRECT_TAG_CLAUSE}
26
+ * alone — the item's own `TMTaskTag` assignments — WITHOUT the five container-
27
+ * inheritance hops (project/area/heading). This is the SQL behind the CONTAINER
28
+ * `--tag` (the `project show` / `area show` / `projects` list views): every
29
+ * child inherits its container's tags, so the inheritance-inclusive relation is
30
+ * vacuous there — matching a DIRECT assignment is the useful, GUI-faithful
31
+ * behavior. It keeps tag-hierarchy descendant expansion (the uuid SET is still
32
+ * the tag plus its descendants, OR-matched) but drops container inheritance, so
33
+ * an item matches only when it is DIRECTLY tagged. Takes the uuid set once, so
34
+ * callers bind `uuids` exactly one time (not `× 6`).
35
+ */
36
+ export declare function directTagScopeSql(uuidCount: number): string;
37
+ /**
38
+ * The negation of tag membership — the SQL behind the `untagged` filter (the
39
+ * GUI's "No Tag"). It negates the SAME {@link INHERITANCE_CLAUSES} relation with
40
+ * the tag-set restriction dropped: "carries ANY tag by any hop", wrapped in NOT.
41
+ * An item is untagged iff NO possible `--tag X` could ever match it — so this
42
+ * negates the whole membership relation, not merely the row's own direct
43
+ * assignments. Takes no binds.
44
+ */
45
+ export declare function untaggedScopeSql(): string;
46
+ /**
47
+ * The DIRECT-ONLY counterpart of {@link untaggedScopeSql} — the SQL behind the
48
+ * CONTAINER `--untagged` (the GUI's in-context "No Tag" inside a project/area
49
+ * card). It negates only {@link DIRECT_TAG_CLAUSE} (the item's OWN direct
50
+ * assignments), leaving container inheritance untouched: an item qualifies when
51
+ * it carries no DIRECT tag, even if it inherits one from its project/area/
52
+ * heading. Every child inherits the container's tags, so the whole-relation
53
+ * {@link untaggedScopeSql} would exclude every row there — direct-only is the
54
+ * useful negation. Takes no binds.
55
+ */
56
+ export declare function directUntaggedScopeSql(): string;
19
57
  /**
20
58
  * A tag plus every hierarchy descendant. Filtering by a parent tag matches
21
59
  * child-tagged items — DOCUMENTED app behavior (the UI's tag filter works
@@ -39,7 +77,43 @@ export declare function tagWithDescendants(db: DatabaseSync, uuid: string): stri
39
77
  * input passes through untouched (after trimming).
40
78
  */
41
79
  export declare function stripThingsUri(ref: string): string;
42
- export declare function resolveTaskUuidPrefix(db: DatabaseSync, refRaw: string): string;
80
+ /**
81
+ * The single source for uuid-miss not-found copy. Reused by the read-side
82
+ * resolvers, the write guards, and the MCP item lookups so a uuid/partial-uuid
83
+ * that matches nothing reads identically wherever the miss is reported.
84
+ */
85
+ export declare function noUuidMatch(entity: string, ref: string): string;
86
+ /** A disambiguation candidate for a reference-resolution failure. */
87
+ export interface RefCandidate {
88
+ uuid: string;
89
+ title: string;
90
+ /** Optional context that distinguishes same-named candidates (area for a project, parent path for a tag). */
91
+ context?: string;
92
+ }
93
+ /**
94
+ * A reference (uuid / partial-uuid / name) that did not resolve to exactly one
95
+ * entity. Extends RangeError so every existing `instanceof RangeError` handler
96
+ * keeps treating it as a usage-class failure — but the surfaces that know about
97
+ * it (CLI --json envelope, MCP tool result) additionally lift the structured
98
+ * `candidates` onto `error.details.candidates` so an agent can self-correct
99
+ * without re-parsing the prose message. `code` mirrors the envelope error code.
100
+ *
101
+ * PUBLIC API — exported from src/index.ts. This is the one error the consumer
102
+ * surfaces catch to render structured disambiguation; its `code`
103
+ * ("not-found" | "ambiguous") and `candidates` ({@link RefCandidate}[]) are the
104
+ * documented machine shape (docs/design/architecture.md, Consumer boundary).
105
+ */
106
+ export declare class ReferenceResolutionError extends RangeError {
107
+ readonly code: "not-found" | "ambiguous";
108
+ readonly ref: string;
109
+ readonly candidates: RefCandidate[];
110
+ constructor(message: string, opts: {
111
+ code: "not-found" | "ambiguous";
112
+ ref: string;
113
+ candidates?: RefCandidate[];
114
+ });
115
+ }
116
+ export declare function resolveTaskUuidPrefix(db: DatabaseSync, refRaw: string, entity?: string): string;
43
117
  /**
44
118
  * Fold a name to its match key: NFC + case-fold + strip all whitespace and
45
119
  * dashes/hyphens (ASCII hyphen, the U+2010–2015 dash block, U+2212 minus).
@@ -54,6 +128,15 @@ export interface NamedResolution {
54
128
  } | null;
55
129
  /** 0 = not found, 1 = ok, >1 = ambiguous at the deciding tier. */
56
130
  matches: number;
131
+ /**
132
+ * The rows at the deciding tier when it was ambiguous (matches > 1) — the
133
+ * candidates a fail-closed resolver lists so the caller can disambiguate by
134
+ * uuid. Absent when resolved or not-found.
135
+ */
136
+ candidates?: {
137
+ uuid: string;
138
+ title: string;
139
+ }[];
57
140
  }
58
141
  /**
59
142
  * Tiered reference resolution (docs/design/reference-resolution.md): exact
@@ -62,7 +145,25 @@ export interface NamedResolution {
62
145
  * ambiguous; no tier is not-found. Shared by the read-side `resolve*Uuid`
63
146
  * throwers and the write-side `resolve*` (ContainerResolution) helpers.
64
147
  */
65
- export declare function resolveNamedRef(db: DatabaseSync, table: string, extraWhere: string, extraBinds: (string | number)[], refRaw: string): NamedResolution;
148
+ export declare function resolveNamedRef(db: DatabaseSync, table: string, extraWhere: string, extraBinds: (string | number)[], refRaw: string, options?: {
149
+ prefixTier?: boolean;
150
+ }): NamedResolution;
151
+ /**
152
+ * Resolve a PROJECT write target from a uuid, partial-uuid, or unique name.
153
+ * Project write verbs (`things project update <ref>`, etc.) accept names
154
+ * through this; to-do and heading write targets stay uuid-only.
155
+ *
156
+ * A uuid / unique uuid-prefix resolves FIRST over every task (reusing
157
+ * {@link resolveTaskUuidPrefix}), so a wrong-TYPE id — a to-do uuid handed to a
158
+ * project verb — passes through to the op's own guard, which reports it with a
159
+ * targeted "that is a to-do, not a project" message rather than a misleading
160
+ * not-found. Otherwise the ref resolves as a project NAME through the SAME
161
+ * tiered {@link resolveNamedRef} matching the read side uses (shared core, not
162
+ * a fork) across projects (trashed included, for `project restore`), fail-
163
+ * closed with a candidate listing on an ambiguous name so a duplicated project
164
+ * title is disambiguated by uuid rather than guessed.
165
+ */
166
+ export declare function resolveProjectWriteTarget(db: DatabaseSync, refRaw: string): string;
66
167
  export declare function resolveTagUuid(db: DatabaseSync, ref: string): string;
67
168
  /**
68
169
  * Write destinations stay strict (a trashed project is not a valid target);
@@ -71,12 +172,59 @@ export declare function resolveTagUuid(db: DatabaseSync, ref: string): string;
71
172
  */
72
173
  export declare function resolveProjectUuid(db: DatabaseSync, ref: string, options?: {
73
174
  trashed?: boolean;
175
+ prefixTier?: boolean;
74
176
  }): string;
75
- export declare function resolveAreaUuid(db: DatabaseSync, ref: string): string;
177
+ export declare function resolveAreaUuid(db: DatabaseSync, ref: string, options?: {
178
+ prefixTier?: boolean;
179
+ }): string;
180
+ /**
181
+ * A row's EFFECTIVE area: its own `area` link, else the area of its project,
182
+ * else the area of its heading's project. To-dos nested in a project (or under a
183
+ * heading) carry `area = NULL` in the DB — the area lives on the container — so
184
+ * this resolves the nearest area walking the SAME chain the tag-inheritance SQL
185
+ * uses (t.area → t.project's area → t.heading's project's area). Projects carry
186
+ * their area directly (project/heading are NULL), so COALESCE returns `t.area`
187
+ * unchanged for them — areas are not inherited. Surfaced as the entity's `area`
188
+ * Ref (mappers.ts); whether it is direct vs effective stays derivable from
189
+ * whether `project`/`heading` is set. Emitted as the extra `effectiveArea`
190
+ * column so the raw `t.area` (which tag inheritance and the write layer read)
191
+ * stays available.
192
+ */
193
+ export declare const EFFECTIVE_AREA = "COALESCE(\n t.area,\n (SELECT p.area FROM TMTask p WHERE p.uuid = t.project),\n (SELECT hp.area FROM TMTask h JOIN TMTask hp ON hp.uuid = h.project WHERE h.uuid = t.heading)\n)";
76
194
  export declare function fetchTaskRows(db: DatabaseSync, where: string, params?: unknown[]): TaskRow[];
77
195
  export declare function fetchTaskByUuid(db: DatabaseSync, uuid: string): TaskRow | null;
78
196
  export declare function fetchChecklistRows(db: DatabaseSync, taskUuid: string): ChecklistRow[];
79
- /** Direct tags for a set of tasks, in one query. Returns uuid -> Ref[] (sorted by tag title). */
197
+ /**
198
+ * Direct tags for a set of tasks, in one query. Returns uuid -> Ref[] in the
199
+ * app's CANONICAL tag order.
200
+ *
201
+ * CANONICAL ORDER (ratified 2026-07-14; tiebreak corrected 2026-07-15 by
202
+ * TAGORD1): `TMTag."index"` (INTEGER, often negative) is the user-draggable
203
+ * order from the app's Tags window, and the GUI renders every multi-tag pill row
204
+ * in ascending `index`. Live oracle: the `Replace CPAP mask & air filter` to-do
205
+ * shows `#recurring #home #housekeeping`, matching the tags' indexes, NOT their
206
+ * alphabetical order.
207
+ *
208
+ * TIEBREAK = `uuid`, NOT `title` (TAGORD1 lab oracle, docs/lab/taglab-probes.md).
209
+ * Never-dragged tags ubiquitously tie at `index = 0`; the app breaks that tie by
210
+ * the tag's UUID (ascending ASCII), NOT alphabetically. Proven across three
211
+ * surfaces in a VM (Tags window, a to-do's multi-tag pill row — input-order
212
+ * independent, and the list filter-bar chips): 8 tags seeded reverse-alpha all
213
+ * tied at 0 displayed in exact uuid order, and `ORDER BY "index", uuid`
214
+ * reproduced the whole Tags-window order byte-for-byte where `ORDER BY "index",
215
+ * title` diverged. TMTag has no creation-date column, so creation order is not
216
+ * even a candidate comparator.
217
+ *
218
+ * NESTED-TAG CAVEAT (open question, deliberately unsolved): child tags' indexes
219
+ * interleave globally with top-level ones — CONFIRMED by TAGORD1: `TMTag."index"`
220
+ * is a single GLOBAL space, not per-parent (a seeded child landed at -378 among
221
+ * root tags at 0/-35/-67). So a flat-index sort can place a child BEFORE its
222
+ * parent in a multi-tag row. No live item carries a nested tag alongside another
223
+ * tag, so there is no GUI oracle for the interleaved case — flat ascending
224
+ * `index` is the ratified comparator, isolated HERE. If a GUI oracle ever
225
+ * contradicts it, the fix is a DFS-rank swap in this one ORDER BY (rank children
226
+ * after parents).
227
+ */
80
228
  export declare function fetchTagsForTasks(db: DatabaseSync, taskUuids: string[]): Map<string, Ref[]>;
81
229
  /**
82
230
  * Lazy heading-uuid -> owning-project Ref resolver, cached per instance.