things-api 0.14.0 → 0.16.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 (140) hide show
  1. package/README.md +4 -4
  2. package/dist/audit/schema.d.ts +8 -0
  3. package/dist/audit/schema.js.map +1 -1
  4. package/dist/cli/commands/area.js +1 -1
  5. package/dist/cli/commands/install-skill.js +9 -2
  6. package/dist/cli/commands/install-skill.js.map +1 -1
  7. package/dist/cli/commands/project.js +1 -1
  8. package/dist/cli/commands/project.js.map +1 -1
  9. package/dist/cli/commands/reads.js +73 -4
  10. package/dist/cli/commands/reads.js.map +1 -1
  11. package/dist/cli/commands/repeat-flags.d.ts +11 -1
  12. package/dist/cli/commands/repeat-flags.js +23 -0
  13. package/dist/cli/commands/repeat-flags.js.map +1 -1
  14. package/dist/cli/commands/todo.js +50 -3
  15. package/dist/cli/commands/todo.js.map +1 -1
  16. package/dist/cli/commands/writes.js +202 -61
  17. package/dist/cli/commands/writes.js.map +1 -1
  18. package/dist/cli/glyphs.js +9 -10
  19. package/dist/cli/glyphs.js.map +1 -1
  20. package/dist/cli/help.js +18 -13
  21. package/dist/cli/help.js.map +1 -1
  22. package/dist/cli/read-driver.d.ts +9 -3
  23. package/dist/cli/read-driver.js +3 -1
  24. package/dist/cli/read-driver.js.map +1 -1
  25. package/dist/cli/render.d.ts +33 -2
  26. package/dist/cli/render.js +60 -9
  27. package/dist/cli/render.js.map +1 -1
  28. package/dist/cli/skill.d.ts +24 -0
  29. package/dist/cli/skill.js +40 -0
  30. package/dist/cli/skill.js.map +1 -1
  31. package/dist/client.d.ts +88 -15
  32. package/dist/client.js +31 -5
  33. package/dist/client.js.map +1 -1
  34. package/dist/contracts.d.ts +13 -2
  35. package/dist/contracts.js +1 -1
  36. package/dist/contracts.js.map +1 -1
  37. package/dist/index.d.ts +5 -4
  38. package/dist/index.js +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/mcp/server.js +187 -36
  41. package/dist/mcp/server.js.map +1 -1
  42. package/dist/model/entities.d.ts +97 -40
  43. package/dist/model/entities.js +0 -4
  44. package/dist/model/entities.js.map +1 -1
  45. package/dist/model/mappers.js +24 -43
  46. package/dist/model/mappers.js.map +1 -1
  47. package/dist/read/area-view.js +6 -6
  48. package/dist/read/area-view.js.map +1 -1
  49. package/dist/read/detail.js +51 -3
  50. package/dist/read/detail.js.map +1 -1
  51. package/dist/read/filter-contract.d.ts +1 -1
  52. package/dist/read/filter-contract.js +3 -0
  53. package/dist/read/filter-contract.js.map +1 -1
  54. package/dist/read/log-boundary.d.ts +46 -2
  55. package/dist/read/log-boundary.js +43 -2
  56. package/dist/read/log-boundary.js.map +1 -1
  57. package/dist/read/predicates.d.ts +13 -0
  58. package/dist/read/predicates.js +13 -0
  59. package/dist/read/predicates.js.map +1 -1
  60. package/dist/read/project-view.js +5 -5
  61. package/dist/read/project-view.js.map +1 -1
  62. package/dist/read/queries.d.ts +12 -0
  63. package/dist/read/queries.js +14 -0
  64. package/dist/read/queries.js.map +1 -1
  65. package/dist/read/search-rank.js +2 -2
  66. package/dist/read/search-rank.js.map +1 -1
  67. package/dist/read/shape.d.ts +30 -26
  68. package/dist/read/shape.js +130 -101
  69. package/dist/read/shape.js.map +1 -1
  70. package/dist/read/snapshot.js +2 -2
  71. package/dist/read/snapshot.js.map +1 -1
  72. package/dist/read/stage.d.ts +36 -18
  73. package/dist/read/stage.js +30 -13
  74. package/dist/read/stage.js.map +1 -1
  75. package/dist/read/views.d.ts +62 -0
  76. package/dist/read/views.js +134 -14
  77. package/dist/read/views.js.map +1 -1
  78. package/dist/surface-copy.d.ts +2 -2
  79. package/dist/surface-copy.js +2 -2
  80. package/dist/write/batch.d.ts +28 -3
  81. package/dist/write/batch.js +162 -46
  82. package/dist/write/batch.js.map +1 -1
  83. package/dist/write/clear-reminder.js +3 -1
  84. package/dist/write/clear-reminder.js.map +1 -1
  85. package/dist/write/clone.d.ts +4 -0
  86. package/dist/write/clone.js +531 -0
  87. package/dist/write/clone.js.map +1 -0
  88. package/dist/write/commands.js +196 -29
  89. package/dist/write/commands.js.map +1 -1
  90. package/dist/write/guards.d.ts +1 -1
  91. package/dist/write/guards.js +44 -12
  92. package/dist/write/guards.js.map +1 -1
  93. package/dist/write/make-repeating-project.d.ts +2 -3
  94. package/dist/write/make-repeating-project.js +2 -73
  95. package/dist/write/make-repeating-project.js.map +1 -1
  96. package/dist/write/move.js +53 -1
  97. package/dist/write/move.js.map +1 -1
  98. package/dist/write/operations.d.ts +134 -11
  99. package/dist/write/operations.js +5 -1
  100. package/dist/write/operations.js.map +1 -1
  101. package/dist/write/pipeline.d.ts +38 -0
  102. package/dist/write/pipeline.js +71 -6
  103. package/dist/write/pipeline.js.map +1 -1
  104. package/dist/write/pre-state.d.ts +41 -0
  105. package/dist/write/pre-state.js +71 -8
  106. package/dist/write/pre-state.js.map +1 -1
  107. package/dist/write/preserve-modified.d.ts +55 -0
  108. package/dist/write/preserve-modified.js +106 -0
  109. package/dist/write/preserve-modified.js.map +1 -0
  110. package/dist/write/promote-clone.d.ts +32 -0
  111. package/dist/write/promote-clone.js +620 -0
  112. package/dist/write/promote-clone.js.map +1 -0
  113. package/dist/write/reorder.js +49 -2
  114. package/dist/write/reorder.js.map +1 -1
  115. package/dist/write/resolution-timestamps.js +29 -7
  116. package/dist/write/resolution-timestamps.js.map +1 -1
  117. package/dist/write/reversibility.js +32 -14
  118. package/dist/write/reversibility.js.map +1 -1
  119. package/dist/write/scope-guard.js +2 -0
  120. package/dist/write/scope-guard.js.map +1 -1
  121. package/dist/write/undo.d.ts +12 -2
  122. package/dist/write/undo.js +145 -22
  123. package/dist/write/undo.js.map +1 -1
  124. package/dist/write/vectors/applescript.js +9 -0
  125. package/dist/write/vectors/applescript.js.map +1 -1
  126. package/dist/write/vectors/simulator.js +242 -10
  127. package/dist/write/vectors/simulator.js.map +1 -1
  128. package/dist/write/vectors/ui-certification.d.ts +1 -1
  129. package/dist/write/vectors/ui-certification.js +10 -3
  130. package/dist/write/vectors/ui-certification.js.map +1 -1
  131. package/dist/write/verify/delta.d.ts +55 -10
  132. package/dist/write/verify/delta.js +73 -10
  133. package/dist/write/verify/delta.js.map +1 -1
  134. package/package.json +1 -1
  135. package/schema/envelope.schema.json +25 -0
  136. package/skills/things-cli/SKILL.md +10 -7
  137. package/skills/things-cli/references/banner.md +1 -1
  138. package/skills/things-cli/references/contracts.md +17 -9
  139. package/skills/things-cli/references/{model.md → data-model.md} +5 -2
  140. package/skills/things-cli/references/ordering.md +2 -0
@@ -1,5 +1,6 @@
1
1
  import { decodeReminderTime, encodeReminderTime, localToday, reminderUrlToken, zonedWallInstant, } from "../model/dates.js";
2
2
  import { byUuid } from "../read/detail.js";
3
+ import { manualLogDateEpoch, pendingLogCount } from "../read/log-boundary.js";
3
4
  import { isLooseRef } from "../read/pseudo-area.js";
4
5
  import { reminderIsLive } from "../read/stage.js";
5
6
  import { areaMemberCounts, childTagTitles, classifyHeadingConvert, classifyHeadingDissolve, classifyHeadingMoveToProject, classifyProjectRepeat, computeHeadingMovePre, computeReorderPre, emptyPreState, loadTarget, projectChildren, projectStatus, projectSubtreeUuids, resolveArea, resolveHeading, resolveProject, resolveTag, sameTitleTaskUuids, trashedCount, } from "./pre-state.js";
@@ -62,7 +63,7 @@ function q(value) {
62
63
  function unsupportedVector(op, vector) {
63
64
  throw new Error(`${op} cannot be compiled for vector ${vector} (planner bug)`);
64
65
  }
65
- function whenAssertions(when, todayIso) {
66
+ function whenAssertions(when, todayIso, opts = { mode: "add" }) {
66
67
  // Strict shape check: an unvalidated string used to flow straight into the
67
68
  // URL (e.g. "2026-07-20@09:30", the raw URL grammar) — the app would SET
68
69
  // date+reminder while verification asserted the literal string as the date,
@@ -78,16 +79,35 @@ function whenAssertions(when, todayIso) {
78
79
  }
79
80
  switch (when) {
80
81
  case "today":
82
+ // Today's non-evening section: in Today (the `today` marker) AND NOT in the
83
+ // evening sub-bucket (the presence-keyed `evening` marker absent — asserted
84
+ // as null, which valuesEqual treats as absent). Gated to Today members under
85
+ // the verify clock exactly as the retired `todaySection` was.
86
+ //
87
+ // The `startDate` assertion differs by op (field bug §0½.8):
88
+ // - ADD mints a fresh Today item with no schedule history, so the app dates
89
+ // it EXACTLY today — exact equality is right.
90
+ // - UPDATE of an item ALREADY in Today whose `startDate` has already arrived
91
+ // (past or today): the app PRESERVES that historical date rather than
92
+ // rewriting the storage byte to today (arrived-date law). Assert the
93
+ // arrived-date PREDICATE (non-null and <= today) so a preserved historical
94
+ // date verifies, while an undated deadline-only pull (null startDate) is
95
+ // still rejected — the item's Today membership then rests only on a
96
+ // deadline, not on the requested schedule.
81
97
  return [
82
98
  { field: "start", equals: "active" },
83
- { field: "startDate", equals: todayIso },
84
- { field: "todaySection", equals: "today" },
99
+ opts.mode === "update"
100
+ ? { field: "startDate", satisfies: { predicate: "arrived-on-or-before", date: todayIso } }
101
+ : { field: "startDate", equals: todayIso },
102
+ { field: "today", equals: true },
103
+ { field: "evening", equals: null },
85
104
  ];
86
105
  case "evening":
106
+ // The This-Evening sub-bucket: the `evening` marker (which implies `today`).
87
107
  return [
88
108
  { field: "start", equals: "active" },
89
109
  { field: "startDate", equals: todayIso },
90
- { field: "todaySection", equals: "evening" },
110
+ { field: "evening", equals: true },
91
111
  ];
92
112
  case "anytime":
93
113
  return [
@@ -304,8 +324,11 @@ const todoAdd = {
304
324
  }
305
325
  if (params.tags !== undefined)
306
326
  attrs["tags"] = pre.resolvedTagTitles;
307
- if (params.checklistItems !== undefined)
308
- attrs["checklist-items"] = params.checklistItems;
327
+ if (params.checklistItems !== undefined) {
328
+ // §9y: things:///json rejects a bare STRING array wholesale (silent
329
+ // no-op) — emit the OBJECT-array shape the app accepts.
330
+ attrs["checklist-items"] = checklistItemsJsonAttr(params.checklistItems.map((title) => ({ title })));
331
+ }
309
332
  if (container?.uuid !== undefined)
310
333
  attrs["list-id"] = container.uuid;
311
334
  if (pre.destHeading?.resolved?.title !== undefined) {
@@ -353,7 +376,12 @@ function effectiveReminder(pre, params) {
353
376
  return null;
354
377
  if (!reminderIsLive(target.startDate, pre.todayIso))
355
378
  return null;
356
- return target.reminder;
379
+ // Read the RAW stored byte off the substrate (top-level `reminder` is
380
+ // live-gated under the LOAD clock, which can differ from `pre.todayIso` under a
381
+ // pinned THINGS_NOW). The `reminderIsLive` guard above applies liveness under
382
+ // the injected clock explicitly, so the raw byte + this gate reproduce the
383
+ // former semantics exactly.
384
+ return target.derived.reminder;
357
385
  }
358
386
  function assertNotesModesExclusive(params) {
359
387
  if (params.notes !== undefined &&
@@ -396,7 +424,7 @@ const todoUpdate = {
396
424
  if (joined !== undefined)
397
425
  assert.push({ field: "notes", equals: joined });
398
426
  if (params.when !== undefined) {
399
- assert.push(...whenAssertions(params.when, ctx.todayIso));
427
+ assert.push(...whenAssertions(params.when, ctx.todayIso, { mode: "update" }));
400
428
  const reminder = effectiveReminder(pre, params);
401
429
  assert.push({
402
430
  field: "reminder",
@@ -601,6 +629,48 @@ const todoSetTags = {
601
629
  },
602
630
  };
603
631
  /** Normalize the string | spec union; decide whether states force the json form. */
632
+ /**
633
+ * The `checklist-items` value `things:///json` accepts: an array of
634
+ * `{type:"checklist-item",attributes:{title[,completed]}}` OBJECTS. A bare
635
+ * STRING array is rejected wholesale — the whole import silently no-ops, no row,
636
+ * no error (§9y; RESID1 R-JSONPAR RAW-a..d). `completed` is emitted only when a
637
+ * spec carries it, so an at-creation add (all items open) yields the minimal
638
+ * `{title}` attributes the probe validated (OBJ/OBJ3), while the checklist
639
+ * replace path forwards its per-item state.
640
+ */
641
+ function checklistItemsJsonAttr(specs) {
642
+ return specs.map((s) => ({
643
+ type: "checklist-item",
644
+ attributes: s.completed === undefined ? { title: s.title } : { title: s.title, completed: s.completed },
645
+ }));
646
+ }
647
+ /**
648
+ * Build a project json import's `items` array from a structured
649
+ * {@link ProjectItemSpec} list (the clone / rich-import path). Order IS the
650
+ * layout: a `heading` node produces a `{type:"heading"}` row and every following
651
+ * `to-do` node inherits it positionally (A4); a `to-do` before the first heading
652
+ * is a project-root child. Children are born OPEN — no `completed` attribute.
653
+ */
654
+ function projectItemsJsonAttr(items) {
655
+ return items.map((it) => {
656
+ if (it.kind === "heading") {
657
+ return { type: "heading", attributes: { title: it.title } };
658
+ }
659
+ const attrs = { title: it.title };
660
+ if (it.notes !== undefined)
661
+ attrs["notes"] = it.notes;
662
+ if (it.when !== undefined)
663
+ attrs["when"] = it.when;
664
+ if (it.deadline !== undefined)
665
+ attrs["deadline"] = it.deadline;
666
+ if (it.tags !== undefined)
667
+ attrs["tags"] = it.tags;
668
+ if (it.checklistItems !== undefined) {
669
+ attrs["checklist-items"] = checklistItemsJsonAttr(it.checklistItems.map((title) => ({ title })));
670
+ }
671
+ return { type: "to-do", attributes: attrs };
672
+ });
673
+ }
604
674
  function checklistSpecs(items) {
605
675
  const specs = items.map((i) => typeof i === "string"
606
676
  ? { title: i, completed: false }
@@ -647,10 +717,7 @@ const todoReplaceChecklist = {
647
717
  operation: "update",
648
718
  id: params.uuid,
649
719
  attributes: {
650
- "checklist-items": specs.map((s) => ({
651
- type: "checklist-item",
652
- attributes: { title: s.title, completed: s.completed },
653
- })),
720
+ "checklist-items": checklistItemsJsonAttr(specs),
654
721
  },
655
722
  },
656
723
  ]);
@@ -675,6 +742,28 @@ const todoEditChecklistItem = {
675
742
  throw new Error(ORCHESTRATED_ONLY);
676
743
  },
677
744
  };
745
+ const CLONE_ORCHESTRATED_ONLY = "todo.clone / project.clone are delivered by the runCloneTodo / runCloneProject orchestrators (a " +
746
+ "compound over todo.add / project.add plus checklist / terminal-state follow-up legs); they have " +
747
+ "no atomic surface and are never dispatched directly through the pipeline";
748
+ function cloneStub(op) {
749
+ return {
750
+ op,
751
+ hazards: [],
752
+ preRead(db, params) {
753
+ const pre = emptyPreState();
754
+ pre.target = loadTarget(db, params.uuid);
755
+ return pre;
756
+ },
757
+ expectedDelta() {
758
+ throw new Error(CLONE_ORCHESTRATED_ONLY);
759
+ },
760
+ compile() {
761
+ throw new Error(CLONE_ORCHESTRATED_ONLY);
762
+ },
763
+ };
764
+ }
765
+ const todoClone = cloneStub("todo.clone");
766
+ const projectClone = cloneStub("project.clone");
678
767
  const todoDelete = {
679
768
  op: "todo.delete",
680
769
  hazards: ["H-UNKNOWN-DESTINATION", "H-REPEAT-SCHEDULE"],
@@ -704,6 +793,13 @@ const projectAdd = {
704
793
  throw new RangeError("--completed-at cannot seed child to-dos: a completed-project import reverts to open " +
705
794
  "unless every child is resolved (§5b) — create the project resolved, then add logged children");
706
795
  }
796
+ if (params.todos !== undefined && params.items !== undefined) {
797
+ throw new RangeError("project.add takes either `todos` or structured `items`, not both");
798
+ }
799
+ if (params.completedAt !== undefined && params.items !== undefined && params.items.length > 0) {
800
+ throw new RangeError("--completed-at cannot seed child items: a completed-project import reverts to open " +
801
+ "unless every child is resolved (§5b)");
802
+ }
707
803
  const pre = emptyPreState();
708
804
  if (containerGiven(params.area))
709
805
  pre.destArea = resolveArea(db, params.area);
@@ -739,7 +835,9 @@ const projectAdd = {
739
835
  compile(params, vector, pre, ctx) {
740
836
  if (vector !== "url-scheme")
741
837
  unsupportedVector(this.op, vector);
742
- if (addHasTimestamps(params)) {
838
+ // The json import is required for born-timestamps AND for structured `items`
839
+ // (headings / rich children) — the plain add-project URL carries neither.
840
+ if (addHasTimestamps(params) || params.items !== undefined) {
743
841
  const attrs = { title: params.title };
744
842
  if (params.notes !== undefined)
745
843
  attrs["notes"] = params.notes;
@@ -749,7 +847,10 @@ const projectAdd = {
749
847
  attrs["when"] = params.when;
750
848
  if (params.deadline !== undefined)
751
849
  attrs["deadline"] = params.deadline;
752
- if (params.todos !== undefined) {
850
+ if (params.items !== undefined) {
851
+ attrs["items"] = projectItemsJsonAttr(params.items);
852
+ }
853
+ else if (params.todos !== undefined) {
753
854
  attrs["items"] = params.todos.map((t) => ({ type: "to-do", attributes: { title: t } }));
754
855
  }
755
856
  }
@@ -787,7 +888,7 @@ const projectUpdate = {
787
888
  if (joined !== undefined)
788
889
  assert.push({ field: "notes", equals: joined });
789
890
  if (params.when !== undefined) {
790
- assert.push(...whenAssertions(params.when, ctx.todayIso));
891
+ assert.push(...whenAssertions(params.when, ctx.todayIso, { mode: "update" }));
791
892
  // Projects carry the same reminderTime codec as to-dos (A3); a bare
792
893
  // when= clears an existing reminder unless auto-preserved.
793
894
  const reminder = effectiveReminder(pre, params);
@@ -1046,7 +1147,10 @@ const projectReopen = {
1046
1147
  };
1047
1148
  const projectRestore = {
1048
1149
  op: "project.restore",
1049
- hazards: ["H-UNKNOWN-DESTINATION"],
1150
+ // H-REPEAT-SCHEDULE: a trashed repeating PROJECT template cannot be restored
1151
+ // headlessly (Put Back only) — refuse categorically instead of the raw AS-301
1152
+ // no-op. (The to-do path already carries it; kind-parity, 2026-08-13.)
1153
+ hazards: ["H-UNKNOWN-DESTINATION", "H-REPEAT-SCHEDULE"],
1050
1154
  preRead(db, params) {
1051
1155
  const pre = emptyPreState();
1052
1156
  pre.target = loadTarget(db, params.uuid);
@@ -1390,6 +1494,13 @@ const reorder = {
1390
1494
  // Verify the REQUESTED sequence (strictly ascending ranks). The wire
1391
1495
  // list pins the unrequested tail too, but the caller's contract is the
1392
1496
  // requested prefix; tail members are covered by pre-rank tripwires.
1497
+ // TODWIRE: on the `today` scope the wire is now MINIMAL — only the named
1498
+ // block gets fresh (front, monotonic) `todayIndex`; the unnamed tail keeps
1499
+ // its cohort-interleaved (non-monotonic) `todayIndex`, so the FULL
1500
+ // `params.uuids` is NOT strictly-ascending after the write. Verify the
1501
+ // `todayWire` (the named block) instead — it lands at the visible front in
1502
+ // wire order with fresh ascending values, and the untouched tail rides the
1503
+ // pre-rank tripwires. Every other scope verifies the requested prefix.
1393
1504
  // LOGSORT ORD-13 byte-lock: any admitted UNSWEPT-resolved movee must read
1394
1505
  // back index-only — status still closed, stoppedDate intact, umd unbumped
1395
1506
  // (a reopen would flip all three). Frozen assertions carry that into verify.
@@ -1400,11 +1511,14 @@ const reorder = {
1400
1511
  { field: "stoppedDate", equals: m.stoppedDate },
1401
1512
  ],
1402
1513
  }));
1514
+ const sequence = params.scope === "today" && pre.reorder?.todayWire != null
1515
+ ? pre.reorder.todayWire
1516
+ : params.uuids;
1403
1517
  return {
1404
1518
  mode: "ordering",
1405
1519
  key: pre.reorder?.key ??
1406
1520
  (params.scope === "today" || params.scope === "evening" ? "todayIndex" : "index"),
1407
- sequence: params.uuids,
1521
+ sequence,
1408
1522
  ...(frozen.length > 0 && { frozen }),
1409
1523
  };
1410
1524
  },
@@ -1429,7 +1543,14 @@ const reorder = {
1429
1543
  : params.scope === "upcoming"
1430
1544
  ? `list "Upcoming"`
1431
1545
  : `list "Today"`;
1432
- const wire = pre.reorder?.wireList ?? params.uuids;
1546
+ // TODWIRE the `today` scope sends the MINIMAL visible-order wire (names only
1547
+ // what must move; unnamed rows keep their entry cohorts + visible positions),
1548
+ // not the OLD full `wireList` that fused every cohort (MOVPLC/ORD-20). Every
1549
+ // other native scope keeps the full wire (their `index`/day axes do not
1550
+ // re-stamp `tiRef`, so a full re-rank is non-damaging).
1551
+ const wire = (params.scope === "today" ? pre.reorder?.todayWire : undefined) ??
1552
+ pre.reorder?.wireList ??
1553
+ params.uuids;
1433
1554
  if (params.scope === "someday") {
1434
1555
  // The Someday handler STACKS each sent id above the list's current top
1435
1556
  // (the current top itself never moves), with OPPOSITE stack directions
@@ -1514,8 +1635,8 @@ function setDatesSpec(op, addressor) {
1514
1635
  op,
1515
1636
  hazards: ["H-UNKNOWN-DESTINATION", "H-BACKDATE-OPEN"],
1516
1637
  preRead(db, params) {
1517
- if (params.completionDate === undefined && params.creationDate === undefined) {
1518
- throw new RangeError("nothing to set: give completionDate and/or creationDate");
1638
+ if (params.completedAt === undefined && params.createdAt === undefined) {
1639
+ throw new RangeError("nothing to set: give completedAt and/or createdAt");
1519
1640
  }
1520
1641
  const pre = emptyPreState();
1521
1642
  pre.target = loadTarget(db, params.uuid);
@@ -1523,16 +1644,16 @@ function setDatesSpec(op, addressor) {
1523
1644
  },
1524
1645
  expectedDelta(_pre, params, ctx) {
1525
1646
  const assert = [];
1526
- if (params.completionDate !== undefined) {
1647
+ if (params.completedAt !== undefined) {
1527
1648
  assert.push({
1528
1649
  field: "stoppedDate",
1529
- equals: hostLocalDate(resolveResolutionInstant(params.completionDate, ctx.zone)),
1650
+ equals: hostLocalDate(resolveResolutionInstant(params.completedAt, ctx.zone)),
1530
1651
  });
1531
1652
  }
1532
- if (params.creationDate !== undefined) {
1653
+ if (params.createdAt !== undefined) {
1533
1654
  assert.push({
1534
1655
  field: "createdDate",
1535
- equals: hostLocalDate(resolveResolutionInstant(params.creationDate, ctx.zone)),
1656
+ equals: hostLocalDate(resolveResolutionInstant(params.createdAt, ctx.zone)),
1536
1657
  });
1537
1658
  }
1538
1659
  return { mode: "update", uuid: params.uuid, assert };
@@ -1541,11 +1662,11 @@ function setDatesSpec(op, addressor) {
1541
1662
  if (vector !== "applescript")
1542
1663
  unsupportedVector(this.op, vector);
1543
1664
  const statements = [];
1544
- if (params.completionDate !== undefined) {
1545
- statements.push(...asDateBlockFromInstant("compDate", resolveResolutionInstant(params.completionDate, ctx.zone)), `set completion date of ${addressor} id ${q(params.uuid)} to compDate`);
1665
+ if (params.completedAt !== undefined) {
1666
+ statements.push(...asDateBlockFromInstant("compDate", resolveResolutionInstant(params.completedAt, ctx.zone)), `set completion date of ${addressor} id ${q(params.uuid)} to compDate`);
1546
1667
  }
1547
- if (params.creationDate !== undefined) {
1548
- statements.push(...asDateBlockFromInstant("createDate", resolveResolutionInstant(params.creationDate, ctx.zone)), `set creation date of ${addressor} id ${q(params.uuid)} to createDate`);
1668
+ if (params.createdAt !== undefined) {
1669
+ statements.push(...asDateBlockFromInstant("createDate", resolveResolutionInstant(params.createdAt, ctx.zone)), `set creation date of ${addressor} id ${q(params.uuid)} to createDate`);
1549
1670
  }
1550
1671
  return osaBlock(statements);
1551
1672
  },
@@ -2013,7 +2134,7 @@ const projectResumeRepeat = {
2013
2134
  };
2014
2135
  // project.make-repeating: the pure-AX row-selection op (UIC4). The command
2015
2136
  // spec drives the area / someday cases; the area-less-anytime coercion is done
2016
- // by the runMakeRepeatingProject orchestrator BEFORE this spec sees the target,
2137
+ // by the promoteProjectViaGui internal orchestrator BEFORE this spec sees the target,
2017
2138
  // so by drive time the taxonomy is always area or someday. A DIRECT dispatch on
2018
2139
  // an anytime project is refused by H-PROJECT-REPEAT (with the orchestrator as
2019
2140
  // the remediation).
@@ -2091,6 +2212,25 @@ const projectAddRepeating = {
2091
2212
  throw new Error(ADD_REPEATING_ORCHESTRATED);
2092
2213
  },
2093
2214
  };
2215
+ // todo.add-repeating is delivered by the runAddRepeatingTodo orchestrator
2216
+ // (todo.add THEN native promote); it has no atomic surface and is never
2217
+ // dispatched directly through the pipeline (promote-clone.ts).
2218
+ const TODO_ADD_REPEATING_ORCHESTRATED = "todo.add-repeating is delivered by the runAddRepeatingTodo orchestrator (create the to-do, " +
2219
+ "then promote it to a repeating series); it has no atomic surface and is never dispatched " +
2220
+ "directly through the pipeline";
2221
+ const todoAddRepeating = {
2222
+ op: "todo.add-repeating",
2223
+ hazards: [],
2224
+ preRead() {
2225
+ return emptyPreState();
2226
+ },
2227
+ expectedDelta() {
2228
+ throw new Error(TODO_ADD_REPEATING_ORCHESTRATED);
2229
+ },
2230
+ compile() {
2231
+ throw new Error(TODO_ADD_REPEATING_ORCHESTRATED);
2232
+ },
2233
+ };
2094
2234
  const todoConvertToProject = {
2095
2235
  op: "todo.convert-to-project",
2096
2236
  hazards: UI_HAZARDS,
@@ -2356,6 +2496,29 @@ const trashEmpty = {
2356
2496
  return osa("empty trash");
2357
2497
  },
2358
2498
  };
2499
+ const logNow = {
2500
+ op: "log-now",
2501
+ hazards: [],
2502
+ preRead(db, _params, now, zone) {
2503
+ const pre = emptyPreState();
2504
+ // The resolved-but-unlogged census the verb will move (the disclosed count),
2505
+ // and the pre-op boundary stamp the delta compares against.
2506
+ pre.logNow = {
2507
+ pending: pendingLogCount(db, now, zone),
2508
+ manualLogDatePre: manualLogDateEpoch(db),
2509
+ };
2510
+ return pre;
2511
+ },
2512
+ expectedDelta(pre) {
2513
+ const ln = pre.logNow ?? { pending: 0, manualLogDatePre: null };
2514
+ return { mode: "logged-now", pending: ln.pending, manualLogDatePre: ln.manualLogDatePre };
2515
+ },
2516
+ compile(_params, vector) {
2517
+ if (vector !== "applescript")
2518
+ unsupportedVector(this.op, vector);
2519
+ return osa("log completed now");
2520
+ },
2521
+ };
2359
2522
  export const COMMANDS = {
2360
2523
  "todo.add": todoAdd,
2361
2524
  "todo.update": todoUpdate,
@@ -2409,5 +2572,9 @@ export const COMMANDS = {
2409
2572
  "area.reorder": areaReorderSidebar,
2410
2573
  "project.make-repeating": projectMakeRepeating,
2411
2574
  "project.add-repeating": projectAddRepeating,
2575
+ "todo.add-repeating": todoAddRepeating,
2576
+ "todo.clone": todoClone,
2577
+ "project.clone": projectClone,
2578
+ "log-now": logNow,
2412
2579
  };
2413
2580
  //# sourceMappingURL=commands.js.map