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
@@ -1,4 +1,4 @@
1
- import { resolveAreaUuid, resolveTaskUuidPrefix, stripThingsUri } from "./queries.js";
1
+ import { resolveAreaUuid, resolveProjectUuid, resolveTaskUuidPrefix, stripThingsUri, } from "./queries.js";
2
2
  export function classifyShowTarget(db, ref) {
3
3
  const stripped = stripThingsUri(ref);
4
4
  try {
@@ -23,9 +23,18 @@ export function classifyShowTarget(db, ref) {
23
23
  throw err;
24
24
  }
25
25
  try {
26
- return { kind: "area", uuid: resolveAreaUuid(db, stripped) };
26
+ return { kind: "area", uuid: resolveAreaUuid(db, stripped, { prefixTier: false }) };
27
27
  }
28
28
  catch {
29
+ // fall through to project-name resolution
30
+ }
31
+ try {
32
+ return { kind: "project", uuid: resolveProjectUuid(db, stripped, { prefixTier: false }) };
33
+ }
34
+ catch (err) {
35
+ // An ambiguous project name lists its candidates — surface that verbatim.
36
+ if (err instanceof RangeError && err.message.includes("ambiguous"))
37
+ throw err;
29
38
  throw new RangeError(`no to-do, project, or area matches "${ref}" (tags and checklist items have no show view)`);
30
39
  }
31
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"show-target.js","sourceRoot":"","sources":["../../src/read/show-target.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAStF,MAAM,UAAU,kBAAkB,CAAC,EAAgB,EAAE,GAAW;IAC9D,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,qBAAqB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,iDAAiD,CAAC,CAAC,GAAG,CAAC,IAAI,CAGjF,CAAC;QACF,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QACrD,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACnB,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI;gBACtB,MAAM,IAAI,UAAU,CAAC,WAAW,IAAI,oCAAoC,CAAC,CAAC;YAC5E,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;QAClE,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,oEAAoE;QACpE,sEAAsE;QACtE,oCAAoC;QACpC,IAAI,GAAG,YAAY,UAAU,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,MAAM,GAAG,CAAC;QAC9E,IAAI,CAAC,CAAC,GAAG,YAAY,UAAU,CAAC;YAAE,MAAM,GAAG,CAAC;IAC9C,CAAC;IACD,IAAI,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,EAAE,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,UAAU,CAClB,uCAAuC,GAAG,gDAAgD,CAC3F,CAAC;IACJ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"show-target.js","sourceRoot":"","sources":["../../src/read/show-target.ts"],"names":[],"mappings":"AAmBA,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AAStB,MAAM,UAAU,kBAAkB,CAAC,EAAgB,EAAE,GAAW;IAC9D,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,qBAAqB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,iDAAiD,CAAC,CAAC,GAAG,CAAC,IAAI,CAGjF,CAAC;QACF,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QACrD,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACnB,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI;gBACtB,MAAM,IAAI,UAAU,CAAC,WAAW,IAAI,oCAAoC,CAAC,CAAC;YAC5E,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;QAClE,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,oEAAoE;QACpE,sEAAsE;QACtE,oCAAoC;QACpC,IAAI,GAAG,YAAY,UAAU,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,MAAM,GAAG,CAAC;QAC9E,IAAI,CAAC,CAAC,GAAG,YAAY,UAAU,CAAC;YAAE,MAAM,GAAG,CAAC;IAC9C,CAAC;IACD,IAAI,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACtF,CAAC;IAAC,MAAM,CAAC;QACP,0CAA0C;IAC5C,CAAC;IACD,IAAI,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,kBAAkB,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAC5F,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,0EAA0E;QAC1E,IAAI,GAAG,YAAY,UAAU,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,MAAM,GAAG,CAAC;QAC9E,MAAM,IAAI,UAAU,CAClB,uCAAuC,GAAG,gDAAgD,CAC3F,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Sidebar-order arrangement for the grouped views (anytime/someday). Takes an
3
+ * already-materialized flat list and reproduces the UI's sidebar layout:
4
+ * the area-less block first, then each area by its sidebar index; inside a
5
+ * block, direct to-dos first, then each project followed by its members.
6
+ *
7
+ * Type-only imports from views.ts (ListItem, SidebarSection) are erased at
8
+ * runtime, so the sole runtime edge is views.ts → sidebar-order.ts.
9
+ */
10
+ import type { DatabaseSync } from "node:sqlite";
11
+ import type { ListItem, SidebarSection } from "./views.ts";
12
+ /**
13
+ * Arranges view members into the UI's flat sidebar-mirroring order: the
14
+ * area-less block first (direct to-dos, then each top-level project followed
15
+ * by its members), then each area by its sidebar index (direct to-dos, then
16
+ * its projects). Project and area order here mirrors the sidebar exactly —
17
+ * both read the same "index" columns.
18
+ */
19
+ export declare function groupBySidebar(db: DatabaseSync, items: ListItem[]): SidebarSection[];
@@ -0,0 +1,93 @@
1
+ /** `?, ?, …` SQL placeholder list of length n. */
2
+ const inList = (n) => Array.from({ length: n }, () => "?").join(", ");
3
+ /**
4
+ * Arranges view members into the UI's flat sidebar-mirroring order: the
5
+ * area-less block first (direct to-dos, then each top-level project followed
6
+ * by its members), then each area by its sidebar index (direct to-dos, then
7
+ * its projects). Project and area order here mirrors the sidebar exactly —
8
+ * both read the same "index" columns.
9
+ */
10
+ export function groupBySidebar(db, items) {
11
+ if (items.length === 0)
12
+ return [];
13
+ // Headed children: resolve heading -> project.
14
+ const headingUuids = [
15
+ ...new Set(items.flatMap((i) => (i.type === "to-do" && i.heading !== null ? [i.heading.uuid] : []))),
16
+ ];
17
+ const headingProject = new Map();
18
+ if (headingUuids.length > 0) {
19
+ for (const row of db
20
+ .prepare(`SELECT uuid, project FROM TMTask WHERE uuid IN (${inList(headingUuids.length)})`)
21
+ .all(...headingUuids)) {
22
+ headingProject.set(row.uuid, row.project);
23
+ }
24
+ }
25
+ const effProject = (i) => i.type !== "to-do"
26
+ ? null
27
+ : (i.project?.uuid ??
28
+ (i.heading !== null ? (headingProject.get(i.heading.uuid) ?? null) : null));
29
+ // Sidebar rank + title for areas; index + area for every referenced project
30
+ // (a tag-filtered list can contain a child whose project row didn't match).
31
+ const areaRows = db
32
+ .prepare(`SELECT uuid, title, "index" FROM TMArea ORDER BY "index" ASC, uuid ASC`)
33
+ .all();
34
+ const areaRank = new Map(areaRows.map((a, rank) => [a.uuid, rank]));
35
+ const areaTitle = new Map(areaRows.map((a) => [a.uuid, a.title ?? ""]));
36
+ const projectUuids = [
37
+ ...new Set([
38
+ ...items.flatMap((i) => (i.type === "project" ? [i.uuid] : [])),
39
+ ...items.flatMap((i) => {
40
+ const p = effProject(i);
41
+ return p === null ? [] : [p];
42
+ }),
43
+ ]),
44
+ ];
45
+ const projectMeta = new Map();
46
+ if (projectUuids.length > 0) {
47
+ for (const row of db
48
+ .prepare(`SELECT uuid, "index", area FROM TMTask WHERE uuid IN (${inList(projectUuids.length)})`)
49
+ .all(...projectUuids)) {
50
+ projectMeta.set(row.uuid, { index: row.index ?? 0, area: row.area });
51
+ }
52
+ }
53
+ // items arrive in SQL "index" order, so array position IS the per-container
54
+ // rank (the internal index is no longer exposed on the entity).
55
+ const sortKey = (i, pos) => {
56
+ const project = i.type === "project" ? i.uuid : effProject(i);
57
+ const meta = project === null ? undefined : projectMeta.get(project);
58
+ const area = i.area?.uuid ?? meta?.area ?? null;
59
+ return {
60
+ areaRank: area === null ? -1 : (areaRank.get(area) ?? areaRows.length),
61
+ area: area ?? "",
62
+ inProject: project === null ? 0 : 1,
63
+ projectIndex: meta?.index ?? 0,
64
+ project: project ?? "",
65
+ headerFirst: i.type === "project" ? 0 : 1,
66
+ pos,
67
+ uuid: i.uuid,
68
+ };
69
+ };
70
+ const keyed = items.map((item, pos) => ({ item, k: sortKey(item, pos) }));
71
+ keyed.sort((a, b) => a.k.areaRank - b.k.areaRank ||
72
+ a.k.area.localeCompare(b.k.area) ||
73
+ a.k.inProject - b.k.inProject ||
74
+ a.k.projectIndex - b.k.projectIndex ||
75
+ a.k.project.localeCompare(b.k.project) ||
76
+ a.k.headerFirst - b.k.headerFirst ||
77
+ a.k.pos - b.k.pos ||
78
+ a.k.uuid.localeCompare(b.k.uuid));
79
+ const sections = [];
80
+ for (const { item, k } of keyed) {
81
+ const areaUuid = k.area === "" ? null : k.area;
82
+ const last = sections.at(-1);
83
+ if (last === undefined || (last.area?.uuid ?? null) !== areaUuid) {
84
+ sections.push({
85
+ area: areaUuid === null ? null : { uuid: areaUuid, title: areaTitle.get(areaUuid) ?? "" },
86
+ items: [],
87
+ });
88
+ }
89
+ sections.at(-1)?.items.push(item);
90
+ }
91
+ return sections;
92
+ }
93
+ //# sourceMappingURL=sidebar-order.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebar-order.js","sourceRoot":"","sources":["../../src/read/sidebar-order.ts"],"names":[],"mappings":"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"}
@@ -8,10 +8,34 @@
8
8
  * direct tags.
9
9
  */
10
10
  import type { DatabaseSync } from "node:sqlite";
11
- import type { Area, Ref, Tag } from "../model/entities.ts";
11
+ import type { Area, Ref, Tag, TagRef } from "../model/entities.ts";
12
12
  import type { TaskRow } from "../model/mappers.ts";
13
+ /**
14
+ * The `things tags` listing: the tag TREE, depth-first, so a child always
15
+ * FOLLOWS its parent. Siblings (and roots) order by canonical `TMTag."index"`
16
+ * (the app's Tags-window rank), UUID tiebreak (TAGORD1 oracle — the Tags-window
17
+ * order for index-tied tags is uuid-ascending, not alphabetical; see
18
+ * fetchTagsForTasks).
19
+ *
20
+ * DFS is unambiguous here and matches the Tags window: this is a listing of the
21
+ * hierarchy itself, where the parent→child relationship is the structure. That
22
+ * is DISTINCT from a flat multi-tag pill row (fetchTagsForTasks / areaTags),
23
+ * where the ratified order is flat ascending `index` and the nested-tag
24
+ * interleave is an open question — see fetchTagsForTasks for that caveat.
25
+ */
13
26
  export declare function tagsView(db: DatabaseSync): Tag[];
14
27
  export declare function areasView(db: DatabaseSync): Area[];
15
28
  export declare function areaTags(db: DatabaseSync, areaUuid: string): Ref[];
16
- /** Tags inherited from ancestors (heading's project, project, area) — direct tags excluded. */
17
- export declare function inheritedTagsFor(db: DatabaseSync, row: TaskRow): Ref[];
29
+ /**
30
+ * Tags inherited from ancestors (the heading's project, the project, the area),
31
+ * surfaced by NAME as a plain list ({@link TagRef}) parallel to the item's own
32
+ * direct tags. The item's OWN direct tags are excluded (they render on the
33
+ * `tags:` line, not here). Which tags are inherited is still computed — a
34
+ * heading cannot be tagged (TAGINH1, VM-verified), so the chain is
35
+ * heading → project → area — but no container source/provenance is emitted.
36
+ *
37
+ * ORDER: the merged inherited set is returned in the app's CANONICAL tag order
38
+ * — `TMTag."index"`, uuid tiebreak (TAGORD1) — the SAME comparator the direct
39
+ * pill row uses (fetchTagsForTasks), applied across the project+area union.
40
+ */
41
+ export declare function inheritedTagsFor(db: DatabaseSync, row: TaskRow): TagRef[];
package/dist/read/tags.js CHANGED
@@ -1,19 +1,57 @@
1
1
  import { q, selectList } from "../db/schema.js";
2
2
  import { fetchTagsForTasks, fetchTaskByUuid } from "./queries.js";
3
+ /**
4
+ * The `things tags` listing: the tag TREE, depth-first, so a child always
5
+ * FOLLOWS its parent. Siblings (and roots) order by canonical `TMTag."index"`
6
+ * (the app's Tags-window rank), UUID tiebreak (TAGORD1 oracle — the Tags-window
7
+ * order for index-tied tags is uuid-ascending, not alphabetical; see
8
+ * fetchTagsForTasks).
9
+ *
10
+ * DFS is unambiguous here and matches the Tags window: this is a listing of the
11
+ * hierarchy itself, where the parent→child relationship is the structure. That
12
+ * is DISTINCT from a flat multi-tag pill row (fetchTagsForTasks / areaTags),
13
+ * where the ratified order is flat ascending `index` and the nested-tag
14
+ * interleave is an open question — see fetchTagsForTasks for that caveat.
15
+ */
3
16
  export function tagsView(db) {
4
17
  const rows = db
5
- .prepare(`SELECT ${selectList("TMTag")} FROM TMTag ORDER BY ${q("index")} ASC`)
18
+ .prepare(`SELECT ${selectList("TMTag")} FROM TMTag ORDER BY ${q("index")} ASC, uuid ASC`)
6
19
  .all();
7
20
  const byUuid = new Map(rows.map((r) => [r.uuid, r]));
8
- return rows.map((row) => {
21
+ // Children grouped under their parent uuid, each list already in index/title
22
+ // order (the SQL sort is stable and preserves the sibling ordering).
23
+ const childrenOf = new Map();
24
+ for (const row of rows) {
25
+ if (!row.parent)
26
+ continue;
27
+ (childrenOf.get(row.parent) ?? childrenOf.set(row.parent, []).get(row.parent)).push(row);
28
+ }
29
+ const out = [];
30
+ const seen = new Set();
31
+ const visit = (row) => {
32
+ if (seen.has(row.uuid))
33
+ return; // cycle guard (bad parent data can't loop)
34
+ seen.add(row.uuid);
9
35
  const parentRow = row.parent ? byUuid.get(row.parent) : undefined;
10
- return {
11
- uuid: row.uuid,
36
+ out.push({
12
37
  title: row.title ?? "",
13
38
  shortcut: row.shortcut,
14
- parent: parentRow ? { uuid: parentRow.uuid, title: parentRow.title ?? "" } : null,
15
- };
16
- });
39
+ // Parent tag's NAME (null for a root) the tree is reconstructable from
40
+ // names alone; tag uuids are never surfaced.
41
+ parent: parentRow ? (parentRow.title ?? "") : null,
42
+ });
43
+ for (const child of childrenOf.get(row.uuid) ?? [])
44
+ visit(child);
45
+ };
46
+ // Roots first (no parent, or an orphan whose parent uuid is absent), in index
47
+ // order; DFS pulls each subtree. A final pass emits any row a parent cycle
48
+ // left unreached, so the listing never silently drops a tag.
49
+ for (const row of rows)
50
+ if (!row.parent || !byUuid.has(row.parent))
51
+ visit(row);
52
+ for (const row of rows)
53
+ visit(row);
54
+ return out;
17
55
  }
18
56
  export function areasView(db) {
19
57
  const rows = db
@@ -23,23 +61,41 @@ export function areasView(db) {
23
61
  uuid: row.uuid,
24
62
  title: row.title ?? "",
25
63
  visible: row.visible !== 0,
26
- tags: areaTags(db, row.uuid),
64
+ // Surface area tags by NAME only (tag uuids are internal).
65
+ tags: areaTags(db, row.uuid).map((t) => ({ title: t.title })),
27
66
  }));
28
67
  }
29
68
  export function areaTags(db, areaUuid) {
69
+ // Canonical pill order: ascending TMTag."index" (the app's Tags-window rank),
70
+ // UUID tiebreak. Same comparator + nested-tag caveat as fetchTagsForTasks.
30
71
  const rows = db
31
72
  .prepare(`SELECT tg.uuid AS uuid, tg.title AS title
32
73
  FROM TMAreaTag at JOIN TMTag tg ON tg.uuid = at.tags
33
- WHERE at.areas = ? ORDER BY tg.title`)
74
+ WHERE at.areas = ? ORDER BY tg.${q("index")}, tg.uuid`)
34
75
  .all(areaUuid);
35
76
  return rows.map((r) => ({ uuid: r.uuid, title: r.title ?? "" }));
36
77
  }
37
- /** Tags inherited from ancestors (heading's project, project, area) — direct tags excluded. */
78
+ /**
79
+ * Tags inherited from ancestors (the heading's project, the project, the area),
80
+ * surfaced by NAME as a plain list ({@link TagRef}) parallel to the item's own
81
+ * direct tags. The item's OWN direct tags are excluded (they render on the
82
+ * `tags:` line, not here). Which tags are inherited is still computed — a
83
+ * heading cannot be tagged (TAGINH1, VM-verified), so the chain is
84
+ * heading → project → area — but no container source/provenance is emitted.
85
+ *
86
+ * ORDER: the merged inherited set is returned in the app's CANONICAL tag order
87
+ * — `TMTag."index"`, uuid tiebreak (TAGORD1) — the SAME comparator the direct
88
+ * pill row uses (fetchTagsForTasks), applied across the project+area union.
89
+ */
38
90
  export function inheritedTagsFor(db, row) {
91
+ // Work with uuid-carrying rows INTERNALLY (dedup + canonical ranking need the
92
+ // uuid); the surfaced TagRef carries the tag NAME only. First-seen wins on a
93
+ // duplicate — the nearer ancestor (project, collected before area) is kept.
39
94
  const collected = new Map();
40
95
  const addAll = (refs) => {
41
96
  for (const ref of refs)
42
- collected.set(ref.uuid, ref);
97
+ if (!collected.has(ref.uuid))
98
+ collected.set(ref.uuid, ref);
43
99
  };
44
100
  let projectUuid = row.project;
45
101
  if (!projectUuid && row.heading) {
@@ -57,6 +113,21 @@ export function inheritedTagsFor(db, row) {
57
113
  if (areaUuid)
58
114
  addAll(areaTags(db, areaUuid));
59
115
  const direct = new Set((fetchTagsForTasks(db, [row.uuid]).get(row.uuid) ?? []).map((t) => t.uuid));
60
- return [...collected.values()].filter((t) => !direct.has(t.uuid));
116
+ const result = [...collected.values()].filter((ref) => !direct.has(ref.uuid));
117
+ if (result.length > 1) {
118
+ // Re-rank the project+area union in one canonical `index, uuid` pass so the
119
+ // merged inherited chips match the GUI's tag order (the per-source lists
120
+ // arrive already ordered, but their concatenation is not globally sorted).
121
+ const rank = new Map();
122
+ const uuids = result.map((ref) => ref.uuid);
123
+ const rows = db
124
+ .prepare(`SELECT uuid FROM TMTag WHERE uuid IN (${uuids.map(() => "?").join(", ")})
125
+ ORDER BY ${q("index")}, uuid`)
126
+ .all(...uuids);
127
+ rows.forEach((r, i) => rank.set(r.uuid, i));
128
+ result.sort((a, b) => (rank.get(a.uuid) ?? 0) - (rank.get(b.uuid) ?? 0));
129
+ }
130
+ // Surface the tag NAME only (uuid stays internal).
131
+ return result.map((ref) => ({ title: ref.title }));
61
132
  }
62
133
  //# sourceMappingURL=tags.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tags.js","sourceRoot":"","sources":["../../src/read/tags.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tags.js","sourceRoot":"","sources":["../../src/read/tags.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Shared list-view truncation: turn a full, filtered result into the rows a
3
+ * surface actually shows (default 50) plus the exact {@link Truncation}
4
+ * metadata every surface reports so nothing is ever silently dropped. The
5
+ * limit counts ITEMS in render order and truncates mid-group; grouped shapes
6
+ * (the Today split, sidebar sections) drop the trailing groups that fall
7
+ * entirely past the cut so no empty header survives. `limit === null` means
8
+ * "all rows" (the caller passed --all / all: true).
9
+ */
10
+ import type { GroupedTruncation, Truncation } from "../contracts.ts";
11
+ import { type AreaView } from "./area-view.ts";
12
+ import { AREA_PREVIEW_LIMIT, DEFAULT_LIST_LIMIT, PROJECT_PREVIEW_LIMIT } from "../surface-copy.ts";
13
+ import type { SidebarSection, TodayView } from "./views.ts";
14
+ import { partitionSomedaySection, splitSectionBlocks, type GroupedLimits } from "./sections.ts";
15
+ export { AREA_PREVIEW_LIMIT, DEFAULT_LIST_LIMIT, PROJECT_PREVIEW_LIMIT };
16
+ export { partitionSomedaySection, splitSectionBlocks, type GroupedLimits };
17
+ /** Flat list: slice to the limit; total is the full filtered length. */
18
+ export declare function truncateList<T>(items: T[], limit: number | null): {
19
+ data: T[];
20
+ truncation: Truncation;
21
+ };
22
+ /**
23
+ * Today split: the cut runs across Today then This Evening in render order,
24
+ * so a limit smaller than the Today block trims Evening to nothing. The badge
25
+ * (a whole-view count summary) is preserved unchanged. The truncation carries
26
+ * a per-section (`today`/`evening`) shown/total breakdown so a renderer can
27
+ * keep This Evening honest under the single global cap without a pre-cap copy.
28
+ */
29
+ export declare function truncateToday(view: TodayView, limit: number | null): {
30
+ data: TodayView;
31
+ truncation: Truncation;
32
+ };
33
+ /**
34
+ * Anytime: the block skeleton is ALWAYS complete — every area header and
35
+ * every project row survives — and the caps apply INDEPENDENTLY to each
36
+ * innermost item list: `limits.area` to the loose block and each area's
37
+ * direct to-dos, `limits.project` to each project's to-dos. Returns the
38
+ * per-block-truncated sections (project rows retained) plus the per-block
39
+ * counts and a top-level `truncated` flag.
40
+ */
41
+ export declare function previewSections(sections: SidebarSection[], limits: GroupedLimits): {
42
+ data: SidebarSection[];
43
+ grouped: GroupedTruncation;
44
+ };
45
+ /**
46
+ * Someday preview: every group survives; `limits.area` (null = no cap)
47
+ * applies independently to each section's own block (project rows + direct
48
+ * to-dos are items alike there), `limits.project` to each active project's
49
+ * child group (the show-active-project-items toggle). Sections keep their
50
+ * capped children after the own block, still clustered per project.
51
+ */
52
+ export declare function previewSomedaySections(sections: SidebarSection[], limits: GroupedLimits): {
53
+ data: SidebarSection[];
54
+ grouped: GroupedTruncation;
55
+ };
56
+ /**
57
+ * Sectioned cap for the `area show` detail view: its sections are containers,
58
+ * so there is no strict total limit — instead `limits.project` bounds the
59
+ * ACTIVE project-ROWS section and `limits.area` the direct-to-dos section
60
+ * (null = uncapped). The cap is render-aware: only the area's ACTIVE project
61
+ * rows are capped, while its future-scheduled and someday project rows always
62
+ * survive (the card renders them under its uncapped Upcoming/Someday sections),
63
+ * so the human view derives entirely from this bounded shape. The toggled
64
+ * later/logged lists and the trashed bucket pass through untouched. Counts ride
65
+ * the same grouped-block shape the sidebar catalogues emit (kind "projects" =
66
+ * the active project-rows section). `now` classifies the schedule split.
67
+ */
68
+ export declare function capAreaSections(view: AreaView, limits: GroupedLimits, now?: Date): {
69
+ data: AreaView;
70
+ grouped: GroupedTruncation;
71
+ };
@@ -0,0 +1,217 @@
1
+ import { localToday } from "../model/dates.js";
2
+ import { isActiveProjectRow } from "./area-view.js";
3
+ import { AREA_PREVIEW_LIMIT, DEFAULT_LIST_LIMIT, PROJECT_PREVIEW_LIMIT } from "../surface-copy.js";
4
+ import { partitionSomedaySection, splitSectionBlocks } from "./sections.js";
5
+ // The per-block cap shape and the structural section splitters live in
6
+ // ./sections.ts (imported for capping here). Re-exported so existing importers
7
+ // — and the truncation unit test — keep one import site.
8
+ export { AREA_PREVIEW_LIMIT, DEFAULT_LIST_LIMIT, PROJECT_PREVIEW_LIMIT };
9
+ export { partitionSomedaySection, splitSectionBlocks };
10
+ const whole = (total, limit) => ({
11
+ shown: total,
12
+ total,
13
+ limit,
14
+ truncated: false,
15
+ });
16
+ /** Flat list: slice to the limit; total is the full filtered length. */
17
+ export function truncateList(items, limit) {
18
+ const total = items.length;
19
+ if (limit === null || total <= limit)
20
+ return { data: items, truncation: whole(total, limit) };
21
+ return {
22
+ data: items.slice(0, limit),
23
+ truncation: { shown: limit, total, limit, truncated: true },
24
+ };
25
+ }
26
+ /**
27
+ * Today split: the cut runs across Today then This Evening in render order,
28
+ * so a limit smaller than the Today block trims Evening to nothing. The badge
29
+ * (a whole-view count summary) is preserved unchanged. The truncation carries
30
+ * a per-section (`today`/`evening`) shown/total breakdown so a renderer can
31
+ * keep This Evening honest under the single global cap without a pre-cap copy.
32
+ */
33
+ export function truncateToday(view, limit) {
34
+ const todayTotal = view.today.length;
35
+ const eveningTotal = view.evening.length;
36
+ const total = todayTotal + eveningTotal;
37
+ const sections = (shownToday, shownEvening) => [
38
+ { key: "today", shown: shownToday, total: todayTotal },
39
+ { key: "evening", shown: shownEvening, total: eveningTotal },
40
+ ];
41
+ if (limit === null || total <= limit) {
42
+ return {
43
+ data: view,
44
+ truncation: { ...whole(total, limit), sections: sections(todayTotal, eveningTotal) },
45
+ };
46
+ }
47
+ const today = view.today.slice(0, limit);
48
+ const evening = view.evening.slice(0, Math.max(0, limit - today.length));
49
+ const shown = today.length + evening.length;
50
+ return {
51
+ data: { today, evening, badge: view.badge },
52
+ truncation: {
53
+ shown,
54
+ total,
55
+ limit,
56
+ truncated: true,
57
+ sections: sections(today.length, evening.length),
58
+ },
59
+ };
60
+ }
61
+ const takeUpTo = (items, limit) => limit === null ? items : items.slice(0, limit);
62
+ /**
63
+ * Anytime: the block skeleton is ALWAYS complete — every area header and
64
+ * every project row survives — and the caps apply INDEPENDENTLY to each
65
+ * innermost item list: `limits.area` to the loose block and each area's
66
+ * direct to-dos, `limits.project` to each project's to-dos. Returns the
67
+ * per-block-truncated sections (project rows retained) plus the per-block
68
+ * counts and a top-level `truncated` flag.
69
+ */
70
+ export function previewSections(sections, limits) {
71
+ const outSections = [];
72
+ const blocks = [];
73
+ let truncated = false;
74
+ for (const section of sections) {
75
+ const { direct, projects } = splitSectionBlocks(section);
76
+ const shownDirect = takeUpTo(direct, limits.area);
77
+ if (direct.length > shownDirect.length)
78
+ truncated = true;
79
+ // Project item-lists nest inside their area/loose block.
80
+ const children = [];
81
+ const items = [...shownDirect];
82
+ for (const { project, items: kids } of projects) {
83
+ const shownChildren = takeUpTo(kids, limits.project);
84
+ if (kids.length > 0) {
85
+ if (kids.length > shownChildren.length)
86
+ truncated = true;
87
+ children.push({
88
+ kind: "project",
89
+ ref: project.uuid,
90
+ title: project.title,
91
+ shown: shownChildren.length,
92
+ total: kids.length,
93
+ limit: limits.project,
94
+ });
95
+ }
96
+ items.push(project, ...shownChildren);
97
+ }
98
+ if (direct.length > 0 || children.length > 0) {
99
+ blocks.push({
100
+ kind: section.area === null ? "loose" : "area",
101
+ ref: section.area?.uuid ?? null,
102
+ title: section.area?.title ?? null,
103
+ shown: shownDirect.length,
104
+ total: direct.length,
105
+ limit: limits.area,
106
+ ...(children.length > 0 && { children }),
107
+ });
108
+ }
109
+ outSections.push({ area: section.area, items });
110
+ }
111
+ return { data: outSections, grouped: { truncated, blocks } };
112
+ }
113
+ /**
114
+ * Someday preview: every group survives; `limits.area` (null = no cap)
115
+ * applies independently to each section's own block (project rows + direct
116
+ * to-dos are items alike there), `limits.project` to each active project's
117
+ * child group (the show-active-project-items toggle). Sections keep their
118
+ * capped children after the own block, still clustered per project.
119
+ */
120
+ export function previewSomedaySections(sections, limits) {
121
+ const outSections = [];
122
+ const blocks = [];
123
+ let truncated = false;
124
+ for (const section of sections) {
125
+ const { own, children } = partitionSomedaySection(section);
126
+ const shownOwn = takeUpTo(own, limits.area);
127
+ if (own.length > shownOwn.length)
128
+ truncated = true;
129
+ // The active-project child groups nest inside this section's own block.
130
+ const childBlocks = [];
131
+ const items = [...shownOwn];
132
+ for (const group of children) {
133
+ const shown = takeUpTo(group.items, limits.project);
134
+ if (group.items.length > shown.length)
135
+ truncated = true;
136
+ childBlocks.push({
137
+ kind: "project",
138
+ ref: group.project.uuid,
139
+ title: group.project.title,
140
+ shown: shown.length,
141
+ total: group.items.length,
142
+ limit: limits.project,
143
+ });
144
+ items.push(...shown);
145
+ }
146
+ if (own.length > 0 || childBlocks.length > 0) {
147
+ const totalProjects = own.filter((i) => i.type === "project").length;
148
+ blocks.push({
149
+ kind: section.area === null ? "loose" : "area",
150
+ ref: section.area?.uuid ?? null,
151
+ title: section.area?.title ?? null,
152
+ shown: shownOwn.length,
153
+ total: own.length,
154
+ limit: limits.area,
155
+ totalProjects,
156
+ totalTodos: own.length - totalProjects,
157
+ ...(childBlocks.length > 0 && { children: childBlocks }),
158
+ });
159
+ }
160
+ outSections.push({ area: section.area, items });
161
+ }
162
+ return { data: outSections, grouped: { truncated, blocks } };
163
+ }
164
+ /**
165
+ * Sectioned cap for the `area show` detail view: its sections are containers,
166
+ * so there is no strict total limit — instead `limits.project` bounds the
167
+ * ACTIVE project-ROWS section and `limits.area` the direct-to-dos section
168
+ * (null = uncapped). The cap is render-aware: only the area's ACTIVE project
169
+ * rows are capped, while its future-scheduled and someday project rows always
170
+ * survive (the card renders them under its uncapped Upcoming/Someday sections),
171
+ * so the human view derives entirely from this bounded shape. The toggled
172
+ * later/logged lists and the trashed bucket pass through untouched. Counts ride
173
+ * the same grouped-block shape the sidebar catalogues emit (kind "projects" =
174
+ * the active project-rows section). `now` classifies the schedule split.
175
+ */
176
+ export function capAreaSections(view, limits, now) {
177
+ const todayIso = localToday(now);
178
+ const blocks = [];
179
+ let truncated = false;
180
+ // Cap the ACTIVE project rows in place; scheduled/someday rows always survive.
181
+ const activeTotal = view.projects.filter((p) => isActiveProjectRow(p, todayIso)).length;
182
+ const shownActive = limits.project === null ? activeTotal : Math.min(activeTotal, limits.project);
183
+ let activeSeen = 0;
184
+ const projects = view.projects.filter((p) => {
185
+ if (!isActiveProjectRow(p, todayIso))
186
+ return true;
187
+ activeSeen += 1;
188
+ return limits.project === null || activeSeen <= limits.project;
189
+ });
190
+ if (activeTotal > 0) {
191
+ if (shownActive < activeTotal)
192
+ truncated = true;
193
+ blocks.push({
194
+ kind: "projects",
195
+ ref: view.area.uuid,
196
+ title: view.area.title,
197
+ shown: shownActive,
198
+ total: activeTotal,
199
+ limit: limits.project,
200
+ });
201
+ }
202
+ const active = limits.area === null ? view.active : view.active.slice(0, limits.area);
203
+ if (view.active.length > 0) {
204
+ if (active.length < view.active.length)
205
+ truncated = true;
206
+ blocks.push({
207
+ kind: "area",
208
+ ref: view.area.uuid,
209
+ title: view.area.title,
210
+ shown: active.length,
211
+ total: view.active.length,
212
+ limit: limits.area,
213
+ });
214
+ }
215
+ return { data: { ...view, projects, active }, grouped: { truncated, blocks } };
216
+ }
217
+ //# sourceMappingURL=truncation.js.map