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,80 @@
1
+ /**
2
+ * The emit-time omit-empty transform for entity/data payloads (the "Omit-empty"
3
+ * contract in docs/design/contracts.md). Applied at the JSON emit boundary of
4
+ * the read surfaces ONLY — the CLI `--json` read envelope (src/cli/read-driver.ts)
5
+ * and the MCP read tool results (src/mcp/server.ts) — never to the in-memory
6
+ * entities the library returns, and never to the error envelope.
7
+ *
8
+ * The rule: an ENTITY object omits any optional field whose value is empty —
9
+ * null, undefined, "" (empty string), or [] (empty array). A consumer MUST
10
+ * read an absent key as unset/empty/default and MUST NOT distinguish absent
11
+ * from empty. Values that are semantically meaningful when "empty" are kept:
12
+ * booleans (a real `false`) and numbers (a `0` count) are never omitted, and
13
+ * the identity keys ({@link IDENTITY_KEYS}) are always present.
14
+ *
15
+ * Why entity-scoped, not a blanket deep prune: the same key means opposite
16
+ * things on different shapes. A to-do's `area: null` is "no area" (omit), but a
17
+ * sidebar section's `area: null` is the load-bearing "top-level/loose block"
18
+ * discriminant (keep). So pruning is gated to the recognized entity shapes;
19
+ * the view scaffolding that CARRIES entities (today/evening split, project/area
20
+ * card sections, grouped blocks) is structural and passes through untouched —
21
+ * only its nested entities are pruned.
22
+ */
23
+ /**
24
+ * Keys never omitted from an entity even when empty: the identity a consumer
25
+ * always keys on. `uuid`/`type` are never empty in practice; `title`/`name`
26
+ * can be "" (an untitled to-do) and MUST still be present.
27
+ */
28
+ const IDENTITY_KEYS = new Set(["uuid", "type", "title", "name"]);
29
+ /** A to-do/project/heading row: uuid + a task-type discriminant. */
30
+ function isTaskEntity(o) {
31
+ return (typeof o["uuid"] === "string" &&
32
+ (o["type"] === "to-do" || o["type"] === "project" || o["type"] === "heading"));
33
+ }
34
+ /** An area: uuid + the `visible` flag (distinguishes it from a bare Ref). */
35
+ function isAreaEntity(o) {
36
+ return typeof o["uuid"] === "string" && typeof o["visible"] === "boolean";
37
+ }
38
+ /** A taxonomy tag row (uuid-free): a title plus the parent/shortcut columns. */
39
+ function isTagEntity(o) {
40
+ return (o["uuid"] === undefined && typeof o["title"] === "string" && "parent" in o && "shortcut" in o);
41
+ }
42
+ /** Whether this object's empty optional fields should be omitted. */
43
+ function prunesEmpties(o) {
44
+ return isTaskEntity(o) || isAreaEntity(o) || isTagEntity(o);
45
+ }
46
+ /** null | undefined | "" | [] — the values an entity omits the key for. */
47
+ function isEmpty(v) {
48
+ return v === null || v === undefined || v === "" || (Array.isArray(v) && v.length === 0);
49
+ }
50
+ function prune(value) {
51
+ if (value === null || value === undefined)
52
+ return value;
53
+ // Dates serialize to ISO strings via JSON.stringify — never recurse into them.
54
+ if (value instanceof Date)
55
+ return value;
56
+ if (Array.isArray(value))
57
+ return value.map(prune);
58
+ if (typeof value === "object") {
59
+ const o = value;
60
+ const omitEmpties = prunesEmpties(o);
61
+ const out = {};
62
+ for (const key of Object.keys(o)) {
63
+ const v = o[key];
64
+ if (omitEmpties && !IDENTITY_KEYS.has(key) && isEmpty(v))
65
+ continue;
66
+ out[key] = prune(v);
67
+ }
68
+ return out;
69
+ }
70
+ return value;
71
+ }
72
+ /**
73
+ * Return a deep copy of `payload` with every entity's empty optional fields
74
+ * omitted (the "Omit-empty" contract). Pure — the input is never mutated, so
75
+ * the human-render path keeps the full entity beside the JSON emit.
76
+ */
77
+ export function omitEmpty(payload) {
78
+ return prune(payload);
79
+ }
80
+ //# sourceMappingURL=serialize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.js","sourceRoot":"","sources":["../../src/model/serialize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;;;;GAIG;AACH,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAItF,oEAAoE;AACpE,SAAS,YAAY,CAAC,CAAM;IAC1B,OAAO,CACL,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ;QAC7B,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC,CAC9E,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,SAAS,YAAY,CAAC,CAAM;IAC1B,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,SAAS,CAAC;AAC5E,CAAC;AAED,gFAAgF;AAChF,SAAS,WAAW,CAAC,CAAM;IACzB,OAAO,CACL,CAAC,CAAC,MAAM,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,QAAQ,IAAI,CAAC,IAAI,UAAU,IAAI,CAAC,CAC9F,CAAC;AACJ,CAAC;AAED,qEAAqE;AACrE,SAAS,aAAa,CAAC,CAAM;IAC3B,OAAO,YAAY,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,2EAA2E;AAC3E,SAAS,OAAO,CAAC,CAAU;IACzB,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,KAAK,CAAC,KAAc;IAC3B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACxD,+EAA+E;IAC/E,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,KAAY,CAAC;QACvB,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,GAAG,GAAQ,EAAE,CAAC;QACpB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;YACjB,IAAI,WAAW,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC;gBAAE,SAAS;YACnE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAI,OAAU;IACrC,OAAO,KAAK,CAAC,OAAO,CAAM,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The `<when>@<time>` scheduling sugar, shared by every consumer surface. A
3
+ * single string like `2026-07-05@09:00` (or `today@evening`-style keyword +
4
+ * time) carries BOTH the schedule and its reminder; this splits it into the
5
+ * separate `when` + `reminder` the write pipeline expects, or reports the one
6
+ * usage error. Extracted from the CLI so the CLI and MCP validate the suffix
7
+ * identically instead of each re-deriving it — see docs/design/architecture.md
8
+ * (Consumer boundary).
9
+ *
10
+ * Pure: it neither mutates its input nor validates the DATE/TIME values (the
11
+ * pipeline does that downstream) — it only owns the `@` split and the two
12
+ * usage messages. The flag/parameter LABELS are injected so each surface keeps
13
+ * its own vocabulary (`--when`/`--reminder` vs `when`/`reminder`).
14
+ */
15
+ /** The surface's spelling of the two parameters, interpolated into usage copy. */
16
+ export interface WhenSugarLabels {
17
+ when: string;
18
+ reminder: string;
19
+ }
20
+ /** CLI flag spellings — the default, since the sugar originated there. */
21
+ export declare const CLI_WHEN_LABELS: WhenSugarLabels;
22
+ /** MCP tool-parameter spellings. */
23
+ export declare const MCP_WHEN_LABELS: WhenSugarLabels;
24
+ /**
25
+ * The result of inspecting a `when` value for the `@time` suffix:
26
+ * - `unchanged` — no suffix; pass `when` through as-is.
27
+ * - `split` — a valid suffix; use the split `when` + `reminder`.
28
+ * - `error` — a malformed suffix, or a suffix given ALONGSIDE a separate
29
+ * reminder; emit `message` as a usage error.
30
+ */
31
+ export type WhenSugar = {
32
+ kind: "unchanged";
33
+ } | {
34
+ kind: "split";
35
+ when: string;
36
+ reminder: string;
37
+ } | {
38
+ kind: "error";
39
+ message: string;
40
+ };
41
+ /**
42
+ * Split a `when` value on its `@time` suffix. `reminderProvided` reflects
43
+ * whether the caller ALSO passed a separate reminder — a suffix plus an
44
+ * explicit reminder is ambiguous and rejected. A non-string or suffix-free
45
+ * `when` is `unchanged`.
46
+ */
47
+ export declare function splitWhenSugar(when: unknown, reminderProvided: boolean, labels?: WhenSugarLabels): WhenSugar;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The `<when>@<time>` scheduling sugar, shared by every consumer surface. A
3
+ * single string like `2026-07-05@09:00` (or `today@evening`-style keyword +
4
+ * time) carries BOTH the schedule and its reminder; this splits it into the
5
+ * separate `when` + `reminder` the write pipeline expects, or reports the one
6
+ * usage error. Extracted from the CLI so the CLI and MCP validate the suffix
7
+ * identically instead of each re-deriving it — see docs/design/architecture.md
8
+ * (Consumer boundary).
9
+ *
10
+ * Pure: it neither mutates its input nor validates the DATE/TIME values (the
11
+ * pipeline does that downstream) — it only owns the `@` split and the two
12
+ * usage messages. The flag/parameter LABELS are injected so each surface keeps
13
+ * its own vocabulary (`--when`/`--reminder` vs `when`/`reminder`).
14
+ */
15
+ /** CLI flag spellings — the default, since the sugar originated there. */
16
+ export const CLI_WHEN_LABELS = { when: "--when", reminder: "--reminder" };
17
+ /** MCP tool-parameter spellings. */
18
+ export const MCP_WHEN_LABELS = { when: "when", reminder: "reminder" };
19
+ /**
20
+ * Split a `when` value on its `@time` suffix. `reminderProvided` reflects
21
+ * whether the caller ALSO passed a separate reminder — a suffix plus an
22
+ * explicit reminder is ambiguous and rejected. A non-string or suffix-free
23
+ * `when` is `unchanged`.
24
+ */
25
+ export function splitWhenSugar(when, reminderProvided, labels = CLI_WHEN_LABELS) {
26
+ if (typeof when !== "string" || !when.includes("@"))
27
+ return { kind: "unchanged" };
28
+ const at = when.indexOf("@");
29
+ const date = when.slice(0, at);
30
+ const time = when.slice(at + 1);
31
+ if (date === "" || time === "" || time.includes("@")) {
32
+ return {
33
+ kind: "error",
34
+ message: `invalid ${labels.when} "${when}" — expected today | evening | anytime | someday | YYYY-MM-DD (set a reminder with ${labels.reminder} HH:mm)`,
35
+ };
36
+ }
37
+ if (reminderProvided) {
38
+ return {
39
+ kind: "error",
40
+ message: `${labels.when} "${when}" carries an @time suffix and ${labels.reminder} was also given — use one`,
41
+ };
42
+ }
43
+ return { kind: "split", when: date, reminder: time };
44
+ }
45
+ //# sourceMappingURL=when-sugar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"when-sugar.js","sourceRoot":"","sources":["../../src/model/when-sugar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,eAAe,GAAoB,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;AAE3F,oCAAoC;AACpC,MAAM,CAAC,MAAM,eAAe,GAAoB,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;AAcvF;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAa,EACb,gBAAyB,EACzB,SAA0B,eAAe;IAEzC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAClF,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAChC,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,OAAO;YACL,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,WAAW,MAAM,CAAC,IAAI,KAAK,IAAI,sFAAsF,MAAM,CAAC,QAAQ,SAAS;SACvJ,CAAC;IACJ,CAAC;IACD,IAAI,gBAAgB,EAAE,CAAC;QACrB,OAAO;YACL,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,GAAG,MAAM,CAAC,IAAI,KAAK,IAAI,iCAAiC,MAAM,CAAC,QAAQ,2BAA2B;SAC5G,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AACvD,CAAC"}
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import type { DatabaseSync } from "node:sqlite";
8
8
  import type { Area, IsoDateGroup, Project, Todo } from "../model/entities.ts";
9
+ import { type ViewFilter } from "./views.ts";
9
10
  export interface AreaView {
10
11
  area: Area;
11
12
  /** Open, unscheduled/current direct to-dos, by index. */
@@ -22,5 +23,11 @@ export interface AreaView {
22
23
  logged: Todo[];
23
24
  trashed: Todo[];
24
25
  }
26
+ /** A someday project row: open, incubated, no start date. */
27
+ export declare const isSomedayProjectRow: (p: Project) => boolean;
28
+ /** A future-scheduled project row (start date strictly after `todayIso`). */
29
+ export declare const isScheduledProjectRow: (p: Project, todayIso: string) => boolean;
30
+ /** An active project row — everything that is neither someday nor future-scheduled. */
31
+ export declare const isActiveProjectRow: (p: Project, todayIso: string) => boolean;
25
32
  /** Resolves by uuid or unique (case-insensitive) title; throws like the ref resolvers. */
26
- export declare function areaView(db: DatabaseSync, ref: string, now?: Date): AreaView;
33
+ export declare function areaView(db: DatabaseSync, ref: string, now?: Date, filter?: ViewFilter): AreaView;
@@ -2,9 +2,25 @@ import { encodePackedDate, localToday, decodePackedDate } from "../model/dates.j
2
2
  import { mapProject, mapTodo } from "../model/mappers.js";
3
3
  import { fetchTagsForTasks, fetchTaskRows, makeRefResolver, resolveAreaUuid } from "./queries.js";
4
4
  import { logBoundary, markLogged } from "./log-boundary.js";
5
+ import { OVERDUE } from "./predicates.js";
5
6
  import { areaTags } from "./tags.js";
7
+ import { tagFilter } from "./views.js";
8
+ // The area card splits its open projects into three display buckets by their
9
+ // own schedule (the card mirrors the GUI: active projects as rows, scheduled
10
+ // ones under Upcoming, someday ones under Someday). Closed-but-unswept projects
11
+ // stay "active" (checked in place) — start/startDate only classify OPEN rows.
12
+ // Shared by the bounding layer (capAreaSections keeps scheduled/someday whole
13
+ // while capping the active rows) and the human renderer, so the split is
14
+ // defined ONCE and the two never drift.
15
+ /** A someday project row: open, incubated, no start date. */
16
+ export const isSomedayProjectRow = (p) => p.status === "open" && p.start === "someday" && p.startDate === null;
17
+ /** A future-scheduled project row (start date strictly after `todayIso`). */
18
+ export const isScheduledProjectRow = (p, todayIso) => p.status === "open" && p.startDate !== null && p.startDate > todayIso;
19
+ /** An active project row — everything that is neither someday nor future-scheduled. */
20
+ export const isActiveProjectRow = (p, todayIso) => !isSomedayProjectRow(p) && !isScheduledProjectRow(p, todayIso);
6
21
  /** Resolves by uuid or unique (case-insensitive) title; throws like the ref resolvers. */
7
- export function areaView(db, ref, now) {
22
+ export function areaView(db, ref, now, filter = {}) {
23
+ const overdue = filter.overdue === true;
8
24
  const uuid = resolveAreaUuid(db, ref);
9
25
  const row = db
10
26
  .prepare(`SELECT uuid, title, visible, "index" FROM TMArea WHERE uuid = ?`)
@@ -15,18 +31,38 @@ export function areaView(db, ref, now) {
15
31
  uuid: row.uuid,
16
32
  title: row.title ?? "",
17
33
  visible: row.visible !== 0,
18
- tags: areaTags(db, uuid),
34
+ // Area tags by NAME only (tag uuids are internal).
35
+ tags: areaTags(db, uuid).map((t) => ({ title: t.title })),
19
36
  };
20
37
  const refs = makeRefResolver(db);
21
38
  const tagsOf = (rows) => fetchTagsForTasks(db, rows.map((r) => r.uuid));
22
39
  const boundary = logBoundary(db, now);
40
+ // OWN-DEADLINE UNIFORM: `--overdue` filters BOTH displayed row kinds by each
41
+ // row's OWN deadline (open, strictly before today) via the shared OVERDUE
42
+ // predicate — the area's child PROJECTS by the project's own deadline AND the
43
+ // loose to-dos by theirs. There is NO descent into project contents (an
44
+ // overdue to-do inside a non-overdue project never surfaces here — that is
45
+ // `project show --overdue`). Sections with no surviving rows collapse.
46
+ const overdueSql = overdue ? ` AND ${OVERDUE}` : "";
47
+ const overdueBinds = overdue ? [encodePackedDate(localToday(now))] : [];
48
+ // Tag scope (§9a): BOTH displayed row kinds — the child PROJECTS and the
49
+ // loose to-dos — are filtered by a tag carried DIRECTLY on the row (the
50
+ // container semantics). The area's own tags are inherited by every row it
51
+ // holds, so an inheritance-inclusive `--tag` would be vacuous; suppressing the
52
+ // container hop makes `--tag` mean "rows with this tag on themselves" (still
53
+ // descendant-expanded) and `--untagged` "rows with no direct tag". Computed
54
+ // once, spliced into both queries; NO descent into project contents (a child
55
+ // of a matching project is never inspected here). The tag binds trail each
56
+ // query's own binds.
57
+ const tf = tagFilter(db, filter, { container: true });
23
58
  // Open projects PLUS closed ones the log-move sweep has not passed — the
24
- // GUI keeps those checked in place (completion ≠ logged).
59
+ // GUI keeps those checked in place (completion ≠ logged). Under --overdue the
60
+ // OVERDUE predicate's own `status = 0` narrows this to open overdue projects.
25
61
  const projectRows = fetchTaskRows(db, `t.type = 1 AND t.area = ? AND t.trashed = 0
26
- AND (t.status = 0 OR t.stopDate > ?) ORDER BY t."index" ASC`, [uuid, boundary.getTime() / 1000]);
62
+ AND (t.status = 0 OR t.stopDate > ?)${overdueSql}${tf.sql} ORDER BY t."index" ASC`, [uuid, boundary.getTime() / 1000, ...overdueBinds, ...tf.binds]);
27
63
  const projectTags = tagsOf(projectRows);
28
64
  const projects = markLogged(projectRows.map((r) => mapProject(r, refs, projectTags.get(r.uuid) ?? [])), boundary);
29
- const todoRows = fetchTaskRows(db, `t.type = 0 AND t.area = ? ORDER BY t."index" ASC`, [uuid]);
65
+ const todoRows = fetchTaskRows(db, `t.type = 0 AND t.area = ?${overdueSql}${tf.sql} ORDER BY t."index" ASC`, [uuid, ...overdueBinds, ...tf.binds]);
30
66
  const todoTags = tagsOf(todoRows);
31
67
  const todos = todoRows.map((r) => ({
32
68
  row: r,
@@ -40,8 +76,8 @@ export function areaView(db, ref, now) {
40
76
  const someday = [];
41
77
  const logged = [];
42
78
  const trashed = [];
43
- for (const { row, todo } of todos) {
44
- if (row.trashed === 1) {
79
+ for (const { row: todoRow, todo } of todos) {
80
+ if (todoRow.trashed === 1) {
45
81
  trashed.push(todo);
46
82
  continue;
47
83
  }
@@ -49,7 +85,7 @@ export function areaView(db, ref, now) {
49
85
  repeating.push(todo);
50
86
  continue;
51
87
  }
52
- if (row.status !== 0) {
88
+ if (todoRow.status !== 0) {
53
89
  // Completion ≠ logged: closed-but-unswept items stay checked in the
54
90
  // active block, like the GUI.
55
91
  if (todo.logged)
@@ -58,14 +94,14 @@ export function areaView(db, ref, now) {
58
94
  active.push(todo);
59
95
  continue;
60
96
  }
61
- if (row.start === 2 && row.startDate === null) {
97
+ if (todoRow.start === 2 && todoRow.startDate === null) {
62
98
  someday.push(todo);
63
99
  continue;
64
100
  }
65
- if (row.startDate !== null && row.startDate > packedToday) {
101
+ if (todoRow.startDate !== null && todoRow.startDate > packedToday) {
66
102
  scheduledRows.push({
67
- date: decodePackedDate(row.startDate) ?? "",
68
- ti: row.todayIndex ?? 0,
103
+ date: decodePackedDate(todoRow.startDate) ?? "",
104
+ ti: todoRow.todayIndex ?? 0,
69
105
  todo,
70
106
  });
71
107
  continue;
@@ -75,7 +111,7 @@ export function areaView(db, ref, now) {
75
111
  logged.sort((a, b) => (b.stopped?.getTime() ?? 0) - (a.stopped?.getTime() ?? 0));
76
112
  const scheduled = [];
77
113
  // Within a day the UI sorts by todayIndex ASC (Upcoming drag order).
78
- for (const { date, todo } of scheduledRows.sort((a, b) => a.date.localeCompare(b.date) || a.ti - b.ti)) {
114
+ for (const { date, todo } of scheduledRows.toSorted((a, b) => a.date.localeCompare(b.date) || a.ti - b.ti)) {
79
115
  const last = scheduled[scheduled.length - 1];
80
116
  if (last && last.date === date)
81
117
  last.items.push(todo);
@@ -1 +1 @@
1
- {"version":3,"file":"area-view.js","sourceRoot":"","sources":["../../src/read/area-view.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"area-view.js","sourceRoot":"","sources":["../../src/read/area-view.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The one shared decision behind every surface's `--limit`/`--all` (CLI) and
3
+ * `limit`/`all` (MCP) cap resolution: given an explicit value, the `all`
4
+ * flag, and a default, decide the effective row cap. Each surface keeps its
5
+ * own input parsing and error emission (the CLI validates the string and
6
+ * writes a usage error; MCP validates via zod and returns a tool error) —
7
+ * only this pure conflict/default logic is shared.
8
+ */
9
+ /**
10
+ * Resolve one cap against `all` into a row cap: `null` = every row,
11
+ * `"conflict"` when an explicit value combines with `all: true`, otherwise
12
+ * the explicit value or the default.
13
+ */
14
+ export declare function resolveCap(value: number | undefined, all: boolean | undefined, defaultLimit: number): number | null | "conflict";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The one shared decision behind every surface's `--limit`/`--all` (CLI) and
3
+ * `limit`/`all` (MCP) cap resolution: given an explicit value, the `all`
4
+ * flag, and a default, decide the effective row cap. Each surface keeps its
5
+ * own input parsing and error emission (the CLI validates the string and
6
+ * writes a usage error; MCP validates via zod and returns a tool error) —
7
+ * only this pure conflict/default logic is shared.
8
+ */
9
+ /**
10
+ * Resolve one cap against `all` into a row cap: `null` = every row,
11
+ * `"conflict"` when an explicit value combines with `all: true`, otherwise
12
+ * the explicit value or the default.
13
+ */
14
+ export function resolveCap(value, all, defaultLimit) {
15
+ if (all === true && value !== undefined)
16
+ return "conflict";
17
+ if (all === true)
18
+ return null;
19
+ return value ?? defaultLimit;
20
+ }
21
+ //# sourceMappingURL=caps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caps.js","sourceRoot":"","sources":["../../src/read/caps.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,KAAyB,EACzB,GAAwB,EACxB,YAAoB;IAEpB,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,UAAU,CAAC;IAC3D,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9B,OAAO,KAAK,IAAI,YAAY,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The per-view filter contract — the ONE declarative statement of which read
3
+ * filters each view honors, plus the shared predicates that enforce them. Both
4
+ * consumer surfaces (CLI commands, MCP tools) derive their per-view guards from
5
+ * this table instead of hand-coding view lists, so a filter's applicability is
6
+ * stated once and can never drift between surfaces.
7
+ *
8
+ * The table says, per view: whether the `overdue` content scope applies; what
9
+ * tag semantics the view carries (inheritance-inclusive `inherited`, container-
10
+ * direct `direct`, or `rejected` where the view has no per-row tags to filter);
11
+ * the bound model the view truncates under (`flat` row cap, `grouped` per-block
12
+ * caps, or `none`); and whether the view offers the status-widening flags
13
+ * (search's logged/trashed/all) that `overdue` refuses to combine with.
14
+ *
15
+ * The exact usage-error COPY stays surface-owned (consumer voice differs per
16
+ * surface — CLI `--tag`, MCP `tag`; docs/design/surface-copy.md): a surface
17
+ * hands {@link validateViewArgs} its own phrasing via {@link FilterVocab}, and
18
+ * this module owns only the DECISION of which message applies.
19
+ */
20
+ import type { ViewFilter } from "./views.ts";
21
+ /** Every read view whose filter applicability the contract governs. */
22
+ export type ViewName = "today" | "inbox" | "anytime" | "someday" | "upcoming" | "logbook" | "trash" | "search" | "changes" | "projects" | "areas" | "project-show" | "area-show";
23
+ /**
24
+ * How a view's tag filter behaves:
25
+ * - `inherited` — FLAT views: a `--tag` matches direct OR container-inherited
26
+ * membership (plus hierarchy descendants).
27
+ * - `direct` — CONTAINER views (`project show`, `area show`): a `--tag` matches
28
+ * a tag carried DIRECTLY on the row (the container's own inherited tags are
29
+ * suppressed, since every child inherits them).
30
+ * - `rejected` — the view has no per-row tag list to filter (an area list row,
31
+ * a tag list row, the changes feed); tag flags do not apply.
32
+ */
33
+ export type TagSemantics = "inherited" | "direct" | "rejected";
34
+ /** The truncation model a view bounds under. */
35
+ export type BoundModel = "flat" | "grouped" | "none";
36
+ /** One row of {@link FILTER_CONTRACT}: a single view's filter applicability. */
37
+ export interface ViewFilterSpec {
38
+ /** Whether the `overdue` content scope (open, past-deadline) applies. */
39
+ readonly overdue: boolean;
40
+ /** The view's tag-filter semantics. */
41
+ readonly tag: TagSemantics;
42
+ /** The bound model the view truncates under. */
43
+ readonly bound: BoundModel;
44
+ /**
45
+ * Whether the view offers the status-widening flags (logged/trashed/all).
46
+ * Only `search` does; `overdue` refuses to combine with them there.
47
+ */
48
+ readonly statusWidening: boolean;
49
+ }
50
+ /**
51
+ * The single source of truth for read-filter applicability, one row per view.
52
+ * Consumed by both surfaces' per-view guards (CLI command actions, MCP tool
53
+ * handlers) so no surface hand-maintains its own view list.
54
+ */
55
+ export declare const FILTER_CONTRACT: Record<ViewName, ViewFilterSpec>;
56
+ /**
57
+ * The three universal tag-filter inputs in their canonical (CLI-flag) spelling.
58
+ * The CLI's parsed options match this verbatim; the MCP surface maps its
59
+ * snake_case inputs (`exact_tag`) onto it before calling the shared predicates.
60
+ */
61
+ export interface TagPresence {
62
+ tag?: readonly string[] | undefined;
63
+ exactTag?: boolean | undefined;
64
+ untagged?: boolean | undefined;
65
+ }
66
+ /** True when any tag-PRESENCE flag was passed (a positive filter, not a negation). */
67
+ export declare function hasTagPresence(a: TagPresence): boolean;
68
+ /**
69
+ * True when the tag flags are mutually incoherent: the `untagged` negation
70
+ * inverts a tag presence, so it does not combine with a tag-presence flag. A
71
+ * pure predicate — each surface emits its own usage error in its own voice.
72
+ */
73
+ export declare function tagFlagConflict(a: TagPresence): boolean;
74
+ /** Build the {@link ViewFilter} tag fields from the parsed flags (empty keys omitted). */
75
+ export declare function tagFilterFields(a: TagPresence): ViewFilter;
76
+ /** The parsed read-filter inputs {@link validateViewArgs} inspects. */
77
+ export interface FilterArgs extends TagPresence {
78
+ overdue?: boolean | undefined;
79
+ /** search only: the status-widening flags overdue refuses to combine with. */
80
+ logged?: boolean | undefined;
81
+ trashed?: boolean | undefined;
82
+ all?: boolean | undefined;
83
+ }
84
+ /**
85
+ * The surface's exact usage-error copy for each conflict {@link validateViewArgs}
86
+ * can detect. Built per call by the surface (it knows the view/kind and its own
87
+ * flag spellings), so the SHARED module decides WHICH message applies while the
88
+ * message text stays consumer-voiced per surface.
89
+ */
90
+ export interface FilterVocab {
91
+ /** `untagged` combined with a tag-presence flag. */
92
+ untaggedConflict: string;
93
+ /** `overdue` passed to a view that does not honor it. */
94
+ overdueRejected: string;
95
+ /** `overdue` combined with a status-widening flag (search). */
96
+ overdueStatusWiden: string;
97
+ }
98
+ /** {@link validateViewArgs} outcome: the built filter, or the usage message to emit. */
99
+ export type ViewValidation = {
100
+ ok: true;
101
+ filter: ViewFilter;
102
+ } | {
103
+ ok: false;
104
+ message: string;
105
+ };
106
+ /**
107
+ * Validate a view's filter arguments against {@link FILTER_CONTRACT} and, when
108
+ * coherent, build the {@link ViewFilter} tag+overdue portion. The checks (and
109
+ * their precedence) live here so both surfaces stay in lockstep; the surface
110
+ * supplies its own exact copy via {@link FilterVocab}.
111
+ *
112
+ * Does NOT enforce tag-REJECTION (a view refusing tag flags outright): that
113
+ * lives at the two call sites that currently reject — the areas/projects/tags
114
+ * collection listers — so this stays a pure no-op for the flat/container views
115
+ * that merely ignore tags on a sub-path (e.g. `trash`).
116
+ */
117
+ export declare function validateViewArgs(view: ViewName, args: FilterArgs, vocab: FilterVocab): ViewValidation;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The single source of truth for read-filter applicability, one row per view.
3
+ * Consumed by both surfaces' per-view guards (CLI command actions, MCP tool
4
+ * handlers) so no surface hand-maintains its own view list.
5
+ */
6
+ export const FILTER_CONTRACT = {
7
+ // Current-work flat views: overdue applies, tags are inheritance-inclusive.
8
+ today: { overdue: true, tag: "inherited", bound: "flat", statusWidening: false },
9
+ inbox: { overdue: true, tag: "inherited", bound: "flat", statusWidening: false },
10
+ // Grouped catalogues: overdue applies, per-block caps.
11
+ anytime: { overdue: true, tag: "inherited", bound: "grouped", statusWidening: false },
12
+ someday: { overdue: true, tag: "inherited", bound: "grouped", statusWidening: false },
13
+ // Forward-looking / closed-item flat views: overdue deliberately does NOT apply.
14
+ upcoming: { overdue: false, tag: "inherited", bound: "flat", statusWidening: false },
15
+ logbook: { overdue: false, tag: "inherited", bound: "flat", statusWidening: false },
16
+ trash: { overdue: false, tag: "rejected", bound: "flat", statusWidening: false },
17
+ // Search: overdue applies but refuses the status-widening flags.
18
+ search: { overdue: true, tag: "inherited", bound: "flat", statusWidening: true },
19
+ // Sync feed: neither overdue nor tags apply.
20
+ changes: { overdue: false, tag: "rejected", bound: "flat", statusWidening: false },
21
+ // Projects LIST is a FLAT view — each project row filtered by its own
22
+ // (inheritance-inclusive) tags; overdue keeps past-deadline projects.
23
+ projects: { overdue: true, tag: "inherited", bound: "none", statusWidening: false },
24
+ // Areas LIST rows carry no deadline and no per-row tag filter.
25
+ areas: { overdue: false, tag: "rejected", bound: "none", statusWidening: false },
26
+ // Single-container views: overdue and DIRECT-tag filtering apply to children.
27
+ "project-show": { overdue: true, tag: "direct", bound: "grouped", statusWidening: false },
28
+ "area-show": { overdue: true, tag: "direct", bound: "grouped", statusWidening: false },
29
+ };
30
+ /** True when any tag-PRESENCE flag was passed (a positive filter, not a negation). */
31
+ export function hasTagPresence(a) {
32
+ return (a.tag?.length ?? 0) > 0 || a.exactTag === true;
33
+ }
34
+ /**
35
+ * True when the tag flags are mutually incoherent: the `untagged` negation
36
+ * inverts a tag presence, so it does not combine with a tag-presence flag. A
37
+ * pure predicate — each surface emits its own usage error in its own voice.
38
+ */
39
+ export function tagFlagConflict(a) {
40
+ return a.untagged === true && hasTagPresence(a);
41
+ }
42
+ /** Build the {@link ViewFilter} tag fields from the parsed flags (empty keys omitted). */
43
+ export function tagFilterFields(a) {
44
+ return {
45
+ ...(a.tag !== undefined && a.tag.length > 0 && { tags: [...a.tag] }),
46
+ ...(a.exactTag === true && { exactTag: true }),
47
+ ...(a.untagged === true && { untagged: true }),
48
+ };
49
+ }
50
+ /**
51
+ * Validate a view's filter arguments against {@link FILTER_CONTRACT} and, when
52
+ * coherent, build the {@link ViewFilter} tag+overdue portion. The checks (and
53
+ * their precedence) live here so both surfaces stay in lockstep; the surface
54
+ * supplies its own exact copy via {@link FilterVocab}.
55
+ *
56
+ * Does NOT enforce tag-REJECTION (a view refusing tag flags outright): that
57
+ * lives at the two call sites that currently reject — the areas/projects/tags
58
+ * collection listers — so this stays a pure no-op for the flat/container views
59
+ * that merely ignore tags on a sub-path (e.g. `trash`).
60
+ */
61
+ export function validateViewArgs(view, args, vocab) {
62
+ const spec = FILTER_CONTRACT[view];
63
+ if (tagFlagConflict(args))
64
+ return { ok: false, message: vocab.untaggedConflict };
65
+ if (args.overdue === true && !spec.overdue) {
66
+ return { ok: false, message: vocab.overdueRejected };
67
+ }
68
+ if (args.overdue === true &&
69
+ spec.statusWidening &&
70
+ (args.logged === true || args.trashed === true || args.all === true)) {
71
+ return { ok: false, message: vocab.overdueStatusWiden };
72
+ }
73
+ return {
74
+ ok: true,
75
+ filter: { ...tagFilterFields(args), ...(args.overdue === true && { overdue: true }) },
76
+ };
77
+ }
78
+ //# sourceMappingURL=filter-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-contract.js","sourceRoot":"","sources":["../../src/read/filter-contract.ts"],"names":[],"mappings":"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"}
@@ -8,10 +8,15 @@
8
8
  * GUI Logbook share every TMTask column shape with logged history; only
9
9
  * the stopDate-vs-boundary relation differs).
10
10
  *
11
- * logInterval semantics: 0 = immediately, 1 = daily (VERIFIED live),
12
- * 2 = weekly, 3 = monthly (both assumed by analogy lab probe queued in
13
- * docs/lab/probe-backlog.md), anything else = manual-only. manualLogDate
14
- * is the user's last explicit "log now"; it can only move the boundary
11
+ * logInterval semantics (GUI enum VERIFIED live in a clone, 2026-07-12,
12
+ * s-campaign-results.md round 3): 0 = immediately, 1 = daily, 4 = manual
13
+ * ("When I choose"). Things 3.22.11's "Move completed items to Logbook"
14
+ * dropdown offers ONLY these three there is NO weekly or monthly option,
15
+ * so the `case 2`/`case 3` branches below are UNREACHABLE with the current
16
+ * app (kept as defensive analogues in case a future build adds them; the
17
+ * real manual value 4 falls to `default`, which is correct). manualLogDate
18
+ * is the user's last explicit "log now" (an AppleScript `log completed now`
19
+ * advances it to the current time — VERIFIED); it can only move the boundary
15
20
  * FORWARD past the interval's own edge.
16
21
  */
17
22
  import type { DatabaseSync } from "node:sqlite";
@@ -1 +1 @@
1
- {"version":3,"file":"log-boundary.js","sourceRoot":"","sources":["../../src/read/log-boundary.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,WAAW,CAAC,EAAgB,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE;IAC5D,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,mDAAmD,CAAC,CAAC,GAAG,EAElE,CAAC;IACd,MAAM,MAAM,GAAG,GAAG,EAAE,aAAa,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtF,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IACjC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAChC,IAAI,IAAU,CAAC;IACf,QAAQ,GAAG,EAAE,WAAW,IAAI,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC;YACJ,IAAI,GAAG,GAAG,CAAC;YACX,MAAM;QACR,KAAK,CAAC;YACJ,IAAI,GAAG,UAAU,CAAC;YAClB,MAAM;QACR,KAAK,CAAC,CAAC,CAAC,CAAC;YACP,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YACpC,IAAI,GAAG,CAAC,CAAC;YACT,MAAM;QACR,CAAC;QACD,KAAK,CAAC,CAAC,CAAC,CAAC;YACP,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACb,IAAI,GAAG,CAAC,CAAC;YACT,MAAM;QACR,CAAC;QACD;YACE,IAAI,GAAG,MAAM,IAAI,GAAG,CAAC;IACzB,CAAC;IACD,OAAO,MAAM,KAAK,IAAI,IAAI,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1D,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,UAAU,CACxB,KAAU,EACV,QAAc;IAEd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC5F,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"log-boundary.js","sourceRoot":"","sources":["../../src/read/log-boundary.ts"],"names":[],"mappings":"AAuBA,MAAM,UAAU,WAAW,CAAC,EAAgB,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE;IAC5D,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,mDAAmD,CAAC,CAAC,GAAG,EAElE,CAAC;IACd,MAAM,MAAM,GAAG,GAAG,EAAE,aAAa,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtF,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IACjC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAChC,IAAI,IAAU,CAAC;IACf,QAAQ,GAAG,EAAE,WAAW,IAAI,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC;YACJ,IAAI,GAAG,GAAG,CAAC;YACX,MAAM;QACR,KAAK,CAAC;YACJ,IAAI,GAAG,UAAU,CAAC;YAClB,MAAM;QACR,KAAK,CAAC,CAAC,CAAC,CAAC;YACP,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YACpC,IAAI,GAAG,CAAC,CAAC;YACT,MAAM;QACR,CAAC;QACD,KAAK,CAAC,CAAC,CAAC,CAAC;YACP,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACb,IAAI,GAAG,CAAC,CAAC;YACT,MAAM;QACR,CAAC;QACD;YACE,IAAI,GAAG,MAAM,IAAI,GAAG,CAAC;IACzB,CAAC;IACD,OAAO,MAAM,KAAK,IAAI,IAAI,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1D,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,UAAU,CACxB,KAAU,EACV,QAAc;IAEd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC5F,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}