things-api 0.6.0 → 0.8.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 (130) hide show
  1. package/README.md +13 -1
  2. package/dist/cli/commands/area.d.ts +14 -0
  3. package/dist/cli/commands/area.js +112 -33
  4. package/dist/cli/commands/area.js.map +1 -1
  5. package/dist/cli/commands/doctor.js +8 -0
  6. package/dist/cli/commands/doctor.js.map +1 -1
  7. package/dist/cli/commands/project.d.ts +14 -0
  8. package/dist/cli/commands/project.js +123 -34
  9. package/dist/cli/commands/project.js.map +1 -1
  10. package/dist/cli/commands/reads.d.ts +111 -10
  11. package/dist/cli/commands/reads.js +567 -73
  12. package/dist/cli/commands/reads.js.map +1 -1
  13. package/dist/cli/commands/setup.d.ts +2 -0
  14. package/dist/cli/commands/setup.js +91 -0
  15. package/dist/cli/commands/setup.js.map +1 -0
  16. package/dist/cli/commands/show.d.ts +9 -0
  17. package/dist/cli/commands/show.js +41 -0
  18. package/dist/cli/commands/show.js.map +1 -0
  19. package/dist/cli/commands/todo.d.ts +9 -0
  20. package/dist/cli/commands/todo.js +89 -15
  21. package/dist/cli/commands/todo.js.map +1 -1
  22. package/dist/cli/commands/writes.js +55 -1
  23. package/dist/cli/commands/writes.js.map +1 -1
  24. package/dist/cli/glyphs.d.ts +72 -0
  25. package/dist/cli/glyphs.js +169 -0
  26. package/dist/cli/glyphs.js.map +1 -0
  27. package/dist/cli/main.js +4 -0
  28. package/dist/cli/main.js.map +1 -1
  29. package/dist/cli/style.d.ts +6 -0
  30. package/dist/cli/style.js +6 -0
  31. package/dist/cli/style.js.map +1 -1
  32. package/dist/client.d.ts +35 -37
  33. package/dist/client.js +10 -74
  34. package/dist/client.js.map +1 -1
  35. package/dist/contracts.d.ts +1 -1
  36. package/dist/contracts.js +1 -1
  37. package/dist/db/baselines/db-v26.js +7 -5
  38. package/dist/db/baselines/db-v26.js.map +1 -1
  39. package/dist/db/schema.d.ts +1 -1
  40. package/dist/db/schema.js +7 -1
  41. package/dist/db/schema.js.map +1 -1
  42. package/dist/diagnose.d.ts +19 -0
  43. package/dist/diagnose.js +42 -0
  44. package/dist/diagnose.js.map +1 -1
  45. package/dist/mcp/server.js +20 -1
  46. package/dist/mcp/server.js.map +1 -1
  47. package/dist/model/entities.d.ts +14 -0
  48. package/dist/model/entities.js.map +1 -1
  49. package/dist/model/mappers.js +8 -0
  50. package/dist/model/mappers.js.map +1 -1
  51. package/dist/model/occurrences.js +4 -1
  52. package/dist/model/occurrences.js.map +1 -1
  53. package/dist/model/recurrence.d.ts +23 -2
  54. package/dist/model/recurrence.js +33 -1
  55. package/dist/model/recurrence.js.map +1 -1
  56. package/dist/read/area-view.js +21 -5
  57. package/dist/read/area-view.js.map +1 -1
  58. package/dist/read/detail.js +12 -2
  59. package/dist/read/detail.js.map +1 -1
  60. package/dist/read/log-boundary.d.ts +24 -0
  61. package/dist/read/log-boundary.js +38 -0
  62. package/dist/read/log-boundary.js.map +1 -0
  63. package/dist/read/project-view.js +28 -3
  64. package/dist/read/project-view.js.map +1 -1
  65. package/dist/read/queries.d.ts +14 -1
  66. package/dist/read/queries.js +25 -2
  67. package/dist/read/queries.js.map +1 -1
  68. package/dist/read/show-target.d.ts +16 -0
  69. package/dist/read/show-target.js +32 -0
  70. package/dist/read/show-target.js.map +1 -0
  71. package/dist/read/snapshot.js +2 -0
  72. package/dist/read/snapshot.js.map +1 -1
  73. package/dist/read/views.d.ts +25 -3
  74. package/dist/read/views.js +176 -21
  75. package/dist/read/views.js.map +1 -1
  76. package/dist/write/automation-probe.js +14 -6
  77. package/dist/write/automation-probe.js.map +1 -1
  78. package/dist/write/availability.d.ts +28 -0
  79. package/dist/write/availability.js +105 -0
  80. package/dist/write/availability.js.map +1 -0
  81. package/dist/write/capabilities.d.ts +3 -0
  82. package/dist/write/capabilities.js +2 -0
  83. package/dist/write/capabilities.js.map +1 -1
  84. package/dist/write/checklist.d.ts +50 -0
  85. package/dist/write/checklist.js +61 -0
  86. package/dist/write/checklist.js.map +1 -0
  87. package/dist/write/clear-reminder.d.ts +3 -0
  88. package/dist/write/clear-reminder.js +205 -0
  89. package/dist/write/clear-reminder.js.map +1 -0
  90. package/dist/write/commands.js +129 -9
  91. package/dist/write/commands.js.map +1 -1
  92. package/dist/write/edit-checklist.d.ts +3 -0
  93. package/dist/write/edit-checklist.js +184 -0
  94. package/dist/write/edit-checklist.js.map +1 -0
  95. package/dist/write/failure-hints.d.ts +14 -1
  96. package/dist/write/failure-hints.js +31 -6
  97. package/dist/write/failure-hints.js.map +1 -1
  98. package/dist/write/guards.d.ts +1 -1
  99. package/dist/write/guards.js +16 -0
  100. package/dist/write/guards.js.map +1 -1
  101. package/dist/write/operations.d.ts +32 -1
  102. package/dist/write/operations.js +3 -0
  103. package/dist/write/operations.js.map +1 -1
  104. package/dist/write/pipeline.d.ts +5 -0
  105. package/dist/write/pipeline.js +24 -1
  106. package/dist/write/pipeline.js.map +1 -1
  107. package/dist/write/reversibility.d.ts +46 -0
  108. package/dist/write/reversibility.js +164 -0
  109. package/dist/write/reversibility.js.map +1 -0
  110. package/dist/write/undo.d.ts +29 -2
  111. package/dist/write/undo.js +356 -9
  112. package/dist/write/undo.js.map +1 -1
  113. package/dist/write/vectors/registry.js +2 -1
  114. package/dist/write/vectors/registry.js.map +1 -1
  115. package/dist/write/vectors/shortcuts.d.ts +9 -0
  116. package/dist/write/vectors/shortcuts.js +90 -0
  117. package/dist/write/vectors/shortcuts.js.map +1 -0
  118. package/dist/write/vectors/types.d.ts +11 -3
  119. package/dist/write/vectors/url-scheme.js +16 -0
  120. package/dist/write/vectors/url-scheme.js.map +1 -1
  121. package/dist/write/verify/delta.d.ts +1 -1
  122. package/dist/write/verify/delta.js +1 -1
  123. package/dist/write/verify/delta.js.map +1 -1
  124. package/package.json +3 -2
  125. package/shortcuts/things-proxy-create-heading.shortcut +0 -0
  126. package/shortcuts/things-proxy-delete-items-permanently.shortcut +0 -0
  127. package/shortcuts/things-proxy-delete-items.shortcut +0 -0
  128. package/shortcuts/things-proxy-edit-title.shortcut +0 -0
  129. package/shortcuts/things-proxy-find-items.shortcut +0 -0
  130. package/shortcuts/things-proxy-set-detail.shortcut +0 -0
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Checklist edit primitives, shared by the client, the write-layer
3
+ * orchestrator (`edit-checklist.ts`), and undo (`undo.ts`).
4
+ *
5
+ * Things exposes NO item-level checklist write surface: every change is a
6
+ * wholesale rewrite (`todo.replace-checklist`), and item uuids regenerate on
7
+ * each rewrite (docs/design/reference-resolution.md). A granular edit is
8
+ * therefore expressed as ONE action against the CURRENT list, applied here in
9
+ * memory, and delivered as a full rewrite that preserves every other item's
10
+ * state. These helpers live in the write layer (not client.ts) so the
11
+ * orchestrator can reuse them without importing back through the client.
12
+ */
13
+ import type { ChecklistItemAction, ChecklistItemSpec } from "./operations.ts";
14
+ export type { ChecklistItemAction };
15
+ /**
16
+ * Target for an existing checklist item: by `item` (title) or `index`
17
+ * (1-based). Provide exactly one. Title matching is best-effort on duplicates
18
+ * (docs/design/reference-resolution.md); index is exact.
19
+ */
20
+ export interface ChecklistTarget {
21
+ item?: string;
22
+ /** 1-based position; overrides `item` when both are given. */
23
+ index?: number;
24
+ }
25
+ export type ChecklistEdit = {
26
+ action: "add";
27
+ title: string; /** 1-based insert position (default: append). */
28
+ at?: number;
29
+ } | ({
30
+ action: "remove";
31
+ } & ChecklistTarget) | ({
32
+ action: "check";
33
+ } & ChecklistTarget) | ({
34
+ action: "uncheck";
35
+ } & ChecklistTarget) | ({
36
+ action: "rename";
37
+ title: string;
38
+ } & ChecklistTarget) | ({
39
+ action: "move"; /** 1-based target position. */
40
+ to: number;
41
+ } & ChecklistTarget);
42
+ /**
43
+ * Resolve a checklist target to an array index. `index` (1-based) is exact.
44
+ * A title resolves best-effort: unique → that item; duplicates → the first on
45
+ * which the action is meaningful (check → first unchecked, uncheck → first
46
+ * checked, others → first match). Loud only when nothing matches / index is
47
+ * out of range.
48
+ */
49
+ export declare function checklistTarget(items: ChecklistItemSpec[], edit: ChecklistEdit & ChecklistTarget): number;
50
+ export declare function applyChecklistEdit(items: ChecklistItemSpec[], edit: ChecklistEdit): ChecklistItemSpec[];
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Resolve a checklist target to an array index. `index` (1-based) is exact.
3
+ * A title resolves best-effort: unique → that item; duplicates → the first on
4
+ * which the action is meaningful (check → first unchecked, uncheck → first
5
+ * checked, others → first match). Loud only when nothing matches / index is
6
+ * out of range.
7
+ */
8
+ export function checklistTarget(items, edit) {
9
+ if (edit.index !== undefined) {
10
+ const i = edit.index - 1;
11
+ if (i < 0 || i >= items.length) {
12
+ throw new RangeError(`checklist index ${edit.index} is out of range (1..${items.length})`);
13
+ }
14
+ return i;
15
+ }
16
+ const ref = edit.item;
17
+ if (ref === undefined)
18
+ throw new RangeError("give a checklist item title or 1-based index");
19
+ const matches = items.map((c, i) => ({ c, i })).filter(({ c }) => c.title === ref);
20
+ if (matches.length === 0)
21
+ throw new RangeError(`no checklist item titled "${ref}"`);
22
+ if (matches.length === 1)
23
+ return matches[0].i;
24
+ const meaningful = edit.action === "check"
25
+ ? matches.find(({ c }) => !c.completed)
26
+ : edit.action === "uncheck"
27
+ ? matches.find(({ c }) => c.completed)
28
+ : undefined;
29
+ return (meaningful ?? matches[0] ?? { i: 0 }).i;
30
+ }
31
+ export function applyChecklistEdit(items, edit) {
32
+ const next = items.map((c) => ({ ...c }));
33
+ switch (edit.action) {
34
+ case "add": {
35
+ const at = edit.at === undefined ? next.length : Math.max(0, Math.min(next.length, edit.at - 1));
36
+ next.splice(at, 0, { title: edit.title, completed: false });
37
+ return next;
38
+ }
39
+ case "remove":
40
+ next.splice(checklistTarget(next, edit), 1);
41
+ return next;
42
+ case "check":
43
+ case "uncheck": {
44
+ const target = next[checklistTarget(next, edit)];
45
+ target.completed = edit.action === "check";
46
+ return next;
47
+ }
48
+ case "rename": {
49
+ const target = next[checklistTarget(next, edit)];
50
+ target.title = edit.title;
51
+ return next;
52
+ }
53
+ case "move": {
54
+ const from = checklistTarget(next, edit);
55
+ const [moved] = next.splice(from, 1);
56
+ next.splice(Math.max(0, Math.min(next.length, edit.to - 1)), 0, moved);
57
+ return next;
58
+ }
59
+ }
60
+ }
61
+ //# sourceMappingURL=checklist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checklist.js","sourceRoot":"","sources":["../../src/write/checklist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { UuidParams } from "./operations.ts";
2
+ import { type MutationResult, type WriteDeps, type WriteOptions } from "./pipeline.ts";
3
+ export declare function runClearReminder(deps: WriteDeps, params: UuidParams, options?: WriteOptions): Promise<MutationResult>;
@@ -0,0 +1,205 @@
1
+ import { resolveTaskUuidPrefix } from "../read/queries.js";
2
+ import { readShortcutProxies } from "./availability.js";
3
+ import { COMMANDS } from "./commands.js";
4
+ import { evaluateGuards } from "./guards.js";
5
+ import { emptyPreState, isRepeatingTemplate, loadTarget } from "./pre-state.js";
6
+ import { fingerprintLabel, runMutation, } from "./pipeline.js";
7
+ const SET_DETAIL_PROXY = "things-proxy-set-detail";
8
+ export async function runClearReminder(deps, params, options = {}) {
9
+ const uuid = resolveTaskUuidPrefix(deps.db, params.uuid);
10
+ const forced = options.vector;
11
+ // Dry-run previews the primary (atomic Shortcuts) plan unless the caller
12
+ // forces the URL bounce — cheap and host-independent (no proxy probe).
13
+ if (options.dryRun === true && forced !== "url-scheme") {
14
+ return runMutation(deps, "todo.clear-dated-reminder", { uuid }, { ...options, vector: "shortcuts" });
15
+ }
16
+ // Uniform pre-checks (no reminder to clear / wrong target type) via the op's
17
+ // own hazard set — identical result whichever path we'd pick.
18
+ const target = loadTarget(deps.db, uuid);
19
+ const pre = emptyPreState();
20
+ pre.target = target;
21
+ const block = evaluateGuards(COMMANDS["todo.clear-dated-reminder"].hazards, {
22
+ op: "todo.clear-dated-reminder",
23
+ params: { uuid },
24
+ pre,
25
+ acks: {},
26
+ });
27
+ if (block !== null) {
28
+ appendClearAudit(deps, {
29
+ uuid,
30
+ startedAt: deps.now?.() ?? new Date(),
31
+ result: `blocked:${block.hazard}`,
32
+ });
33
+ return {
34
+ kind: "blocked",
35
+ op: "todo.clear-dated-reminder",
36
+ reason: "hazard",
37
+ hazard: block.hazard,
38
+ detail: block.detail,
39
+ remediation: block.remediation,
40
+ };
41
+ }
42
+ const repeating = isRepeatingTemplate(target);
43
+ if (forced === "url-scheme" && repeating) {
44
+ appendClearAudit(deps, {
45
+ uuid,
46
+ startedAt: deps.now?.() ?? new Date(),
47
+ result: "blocked:H-REPEAT-SCHEDULE",
48
+ });
49
+ return {
50
+ kind: "blocked",
51
+ op: "todo.clear-dated-reminder",
52
+ reason: "hazard",
53
+ hazard: "H-REPEAT-SCHEDULE",
54
+ detail: "the URL bounce is unusable on a repeating template — a URL when= re-schedule CRASHES " +
55
+ "Things (R09); only the atomic Shortcuts clear is safe here",
56
+ remediation: "install the proxies (`things setup shortcuts`) and omit --vector, or clear it in the app",
57
+ };
58
+ }
59
+ // Repeating items are Shortcuts-only: delegate to the atomic path, which
60
+ // either clears (proxy present) or blocks with the setup remediation
61
+ // (proxy absent) — exactly the desired repeating behavior.
62
+ const useShortcuts = forced === "shortcuts"
63
+ ? true
64
+ : forced === "url-scheme"
65
+ ? false
66
+ : repeating
67
+ ? true
68
+ : proxiesInstalled(deps);
69
+ if (useShortcuts) {
70
+ return runMutation(deps, "todo.clear-dated-reminder", { uuid }, { ...options, vector: "shortcuts" });
71
+ }
72
+ return runBounce(deps, uuid, target, options);
73
+ }
74
+ function proxiesInstalled(deps) {
75
+ const state = (deps.shortcutProxies ?? (() => readShortcutProxies()))();
76
+ return state.present.includes(SET_DETAIL_PROXY);
77
+ }
78
+ // ------------------------------------------------------------------- bounce
79
+ async function runBounce(deps, uuid, target, options) {
80
+ const startedAt = deps.now?.() ?? new Date();
81
+ const originalDate = target !== null && target.type === "to-do" ? target.startDate : null;
82
+ const preReminder = target !== null && target.type === "to-do" ? target.reminder : null;
83
+ if (originalDate === null || preReminder === null) {
84
+ // The guard above already ruled these out; defensive only.
85
+ return {
86
+ kind: "blocked",
87
+ op: "todo.clear-dated-reminder",
88
+ reason: "hazard",
89
+ hazard: "H-NO-REMINDER",
90
+ detail: "the item has no dated reminder to clear via the URL bounce",
91
+ remediation: "target a to-do with a date and a time-of-day reminder",
92
+ };
93
+ }
94
+ if (options.dryRun === true) {
95
+ return {
96
+ kind: "dry-run",
97
+ op: "todo.clear-dated-reminder",
98
+ plan: {
99
+ op: "todo.clear-dated-reminder",
100
+ vector: "url-scheme",
101
+ tier: 0,
102
+ invocation: `bounce: update?id=${uuid}&when=today (clears the reminder, R07) → verified → ` +
103
+ `update?id=${uuid}&when=${originalDate} (re-dates back, RC02) → verified`,
104
+ expectedDelta: {
105
+ mode: "update",
106
+ uuid,
107
+ assert: [
108
+ { field: "reminder", equals: null },
109
+ { field: "startDate", equals: originalDate },
110
+ ],
111
+ },
112
+ hazardsChecked: ["H-NO-REMINDER"],
113
+ },
114
+ };
115
+ }
116
+ const txnId = `txn-${startedAt.getTime().toString(36)}-${process.pid.toString(36)}`;
117
+ const legOptions = { vector: "url-scheme", txn: { id: txnId, role: "leg" } };
118
+ if (options.maxDisruption !== undefined)
119
+ legOptions.maxDisruption = options.maxDisruption;
120
+ if (options.verifyTimeoutMs !== undefined)
121
+ legOptions.verifyTimeoutMs = options.verifyTimeoutMs;
122
+ if (options.actor !== undefined)
123
+ legOptions.actor = options.actor;
124
+ const leg1 = await runMutation(deps, "todo.update", { uuid, when: "today", reminder: null }, legOptions);
125
+ if (leg1.kind !== "ok") {
126
+ appendClearAudit(deps, { uuid, startedAt, result: "verify-failed:mismatch" });
127
+ return {
128
+ kind: "verify-failed",
129
+ op: "todo.clear-dated-reminder",
130
+ reason: "mismatch",
131
+ expected: leg1.kind === "verify-failed" ? leg1.expected : { mode: "update", uuid, assert: [] },
132
+ observed: leg1.kind === "verify-failed" ? leg1.observed : null,
133
+ detail: "bounce leg 1 (when=today) failed — the item was NOT touched; its dated reminder is " +
134
+ "unchanged. Retry, or clear it via `things setup shortcuts`",
135
+ };
136
+ }
137
+ const leg2 = await runMutation(deps, "todo.update", { uuid, when: originalDate }, legOptions);
138
+ if (leg2.kind !== "ok") {
139
+ // Non-atomic residue: the reminder IS cleared but the item is stranded on
140
+ // Today. Record it (summary as verify-failed) and surface the exact state.
141
+ appendClearAudit(deps, {
142
+ uuid,
143
+ startedAt,
144
+ result: "verify-failed:mismatch",
145
+ observed: { reminder: null, startDate: null },
146
+ });
147
+ return {
148
+ kind: "verify-failed",
149
+ op: "todo.clear-dated-reminder",
150
+ reason: "mismatch",
151
+ expected: { mode: "update", uuid, assert: [{ field: "startDate", equals: originalDate }] },
152
+ observed: leg2.kind === "verify-failed" ? leg2.observed : null,
153
+ detail: `bounce leg 2 (when=${originalDate}) failed — the reminder IS cleared but the item is ` +
154
+ `stranded on TODAY (not its original date ${originalDate}). Re-schedule it ` +
155
+ `(\`things todo update ${uuid} --when ${originalDate}\`) to finish`,
156
+ };
157
+ }
158
+ // Both legs verified: write the single undoable SUMMARY record.
159
+ appendClearAudit(deps, {
160
+ uuid,
161
+ startedAt,
162
+ result: "ok",
163
+ pre: { reminder: preReminder, startDate: originalDate },
164
+ observed: { reminder: null, startDate: originalDate },
165
+ txn: { id: txnId, role: "summary" },
166
+ invocation: `bounce(clear-dated-reminder) id=${uuid}`,
167
+ });
168
+ return {
169
+ kind: "ok",
170
+ op: "todo.clear-dated-reminder",
171
+ uuid,
172
+ observed: { reminder: null, startDate: originalDate },
173
+ vector: "url-scheme",
174
+ tier: 0,
175
+ };
176
+ }
177
+ // --------------------------------------------------------------------- audit
178
+ function appendClearAudit(deps, args) {
179
+ const fp = deps.fingerprint();
180
+ const record = {
181
+ v: 1,
182
+ ts: args.startedAt.toISOString(),
183
+ actor: deps.config.actor,
184
+ host: deps.config.host,
185
+ op: "todo.clear-dated-reminder",
186
+ uuid: args.uuid,
187
+ vector: "url-scheme",
188
+ disruption: 0,
189
+ invocation: args.invocation ?? null,
190
+ requested: { uuid: args.uuid },
191
+ ...(args.txn !== undefined && { txn: args.txn }),
192
+ pre: args.pre ?? null,
193
+ observed: args.observed ?? null,
194
+ result: args.result,
195
+ verify: null,
196
+ durationMs: (deps.now?.() ?? new Date()).getTime() - args.startedAt.getTime(),
197
+ env: {
198
+ pkg: deps.pkgVersion ?? "0.0.1",
199
+ dbVersion: fp.observation.databaseVersion,
200
+ fingerprint: fingerprintLabel(fp, deps.config),
201
+ },
202
+ };
203
+ deps.audit.append(record);
204
+ }
205
+ //# sourceMappingURL=clear-reminder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clear-reminder.js","sourceRoot":"","sources":["../../src/write/clear-reminder.ts"],"names":[],"mappings":"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"}
@@ -28,6 +28,23 @@ function osa(script) {
28
28
  const payload = `tell application "Things3" to ${script}`;
29
29
  return { vector: "applescript", kind: "osascript", payload, redactedPayload: payload };
30
30
  }
31
+ /**
32
+ * A `shortcuts run <name>` invocation. The input dict is piped to the proxy
33
+ * as JSON (no secrets — the auth token is never sent to a shortcut), so the
34
+ * redacted rendering equals the payload. Key names come verbatim from the
35
+ * proxy input contracts in docs/lab/s-campaign-results.md.
36
+ */
37
+ function shortcutsRun(shortcut, input) {
38
+ const rendered = `shortcuts run ${shortcut} <- ${JSON.stringify(input)}`;
39
+ return {
40
+ vector: "shortcuts",
41
+ kind: "shortcuts-run",
42
+ payload: rendered,
43
+ redactedPayload: rendered,
44
+ shortcut,
45
+ input,
46
+ };
47
+ }
31
48
  /** Multi-statement `tell` block (one osascript invocation, several events). */
32
49
  function osaBlock(statements) {
33
50
  const payload = `tell application "Things3"\n ${statements.join("\n ")}\nend tell`;
@@ -40,6 +57,19 @@ function unsupportedVector(op, vector) {
40
57
  throw new Error(`${op} cannot be compiled for vector ${vector} (planner bug)`);
41
58
  }
42
59
  function whenAssertions(when, todayIso) {
60
+ // Strict shape check: an unvalidated string used to flow straight into the
61
+ // URL (e.g. "2026-07-20@09:30", the raw URL grammar) — the app would SET
62
+ // date+reminder while verification asserted the literal string as the date,
63
+ // reporting a false mismatch on a write that succeeded.
64
+ if (when !== "today" &&
65
+ when !== "evening" &&
66
+ when !== "anytime" &&
67
+ when !== "someday" &&
68
+ !/^\d{4}-\d{2}-\d{2}$/.test(when)) {
69
+ throw new RangeError(when.includes("@")
70
+ ? `invalid when "${when}" — a reminder time is a separate parameter (reminder: "HH:mm"; CLI --reminder), not an @ suffix`
71
+ : `invalid when "${when}" — expected today | evening | anytime | someday | YYYY-MM-DD`);
72
+ }
43
73
  switch (when) {
44
74
  case "today":
45
75
  return [
@@ -407,17 +437,16 @@ const todoReplaceChecklist = {
407
437
  return pre;
408
438
  },
409
439
  expectedDelta(_pre, params) {
410
- const { specs, needsJson } = checklistSpecs(params.items);
440
+ const { specs } = checklistSpecs(params.items);
441
+ // Always assert titles AND states: the non-json form recreates every item
442
+ // OPEN (T07) and the json form honors per-item `completed` (P18), so the
443
+ // resulting states are known either way. Asserting them (a) strengthens
444
+ // verification and (b) records the ordered states into `pre`/`observed`,
445
+ // which the wholesale undo needs to restore states and to precondition on.
411
446
  const assert = [
412
447
  { field: "checklistTitles", equals: specs.map((s) => s.title) },
448
+ { field: "checklistStates", equals: specs.map((s) => (s.completed ? "completed" : "open")) },
413
449
  ];
414
- if (needsJson) {
415
- // P18: the json form applies per-item completed states — verify them.
416
- assert.push({
417
- field: "checklistStates",
418
- equals: specs.map((s) => (s.completed ? "completed" : "open")),
419
- });
420
- }
421
450
  return { mode: "update", uuid: params.uuid, assert };
422
451
  },
423
452
  compile(params, vector, _pre, ctx) {
@@ -445,6 +474,24 @@ const todoReplaceChecklist = {
445
474
  return thingsUrl("json", { data: payload }, ctx.token);
446
475
  },
447
476
  };
477
+ const ORCHESTRATED_ONLY = "todo.edit-checklist-item is delivered by the runEditChecklist orchestrator (a targeted " +
478
+ "todo.replace-checklist rewrite that preserves every other item's state); it has no atomic " +
479
+ "surface and is never dispatched directly through the pipeline";
480
+ const todoEditChecklistItem = {
481
+ op: "todo.edit-checklist-item",
482
+ hazards: [],
483
+ preRead(db, params) {
484
+ const pre = emptyPreState();
485
+ pre.target = loadTarget(db, params.uuid);
486
+ return pre;
487
+ },
488
+ expectedDelta() {
489
+ throw new Error(ORCHESTRATED_ONLY);
490
+ },
491
+ compile() {
492
+ throw new Error(ORCHESTRATED_ONLY);
493
+ },
494
+ };
448
495
  const todoDelete = {
449
496
  op: "todo.delete",
450
497
  hazards: ["H-UNKNOWN-DESTINATION", "H-REPEAT-SCHEDULE"],
@@ -1205,9 +1252,15 @@ const todoAddLogged = {
1205
1252
  unsupportedVector(this.op, vector);
1206
1253
  // Local noon -> UTC instant, so the stored timestamp decodes back to the
1207
1254
  // requested local DATE in every timezone (P4d: json attrs honored exactly).
1255
+ // WITHOUT milliseconds: the app's json date parser rejects fractional
1256
+ // seconds — a `.000Z` timestamp fails the whole command (error modal, no
1257
+ // write; caught live by the e2e 2026-07-09 — P4d's validated shape was
1258
+ // second-precision).
1208
1259
  const utcNoon = (iso) => {
1209
1260
  const [y, m, d] = iso.split("-").map(Number);
1210
- return new Date(y ?? 0, (m ?? 1) - 1, d ?? 1, 12, 0, 0).toISOString();
1261
+ return new Date(y ?? 0, (m ?? 1) - 1, d ?? 1, 12, 0, 0)
1262
+ .toISOString()
1263
+ .replace(/\.\d{3}Z$/, "Z");
1211
1264
  };
1212
1265
  const payload = JSON.stringify([
1213
1266
  {
@@ -1336,6 +1389,70 @@ const headingUnarchive = {
1336
1389
  return osa(`set status of to do id ${q(params.uuid)} to open`);
1337
1390
  },
1338
1391
  };
1392
+ const headingCreate = {
1393
+ op: "heading.create",
1394
+ hazards: ["H-UNKNOWN-DESTINATION"],
1395
+ preRead(db, params) {
1396
+ const pre = emptyPreState();
1397
+ pre.destProject = resolveProject(db, params.project);
1398
+ return pre;
1399
+ },
1400
+ expectedDelta(pre, params, ctx) {
1401
+ const project = pre.destProject?.resolved;
1402
+ // A new type=2 row with this title under the project; disambiguate
1403
+ // duplicate titles by newest creationDate (findCreated orders DESC).
1404
+ return {
1405
+ mode: "create",
1406
+ probe: { title: params.title, type: "heading", sinceEpoch: ctx.nowEpoch - 2 },
1407
+ assert: project !== undefined && project !== null
1408
+ ? [{ field: "project.uuid", equals: project.uuid }]
1409
+ : [],
1410
+ };
1411
+ },
1412
+ compile(params, vector, pre) {
1413
+ if (vector !== "shortcuts")
1414
+ unsupportedVector(this.op, vector);
1415
+ // `things-proxy-create-heading` input: {"title": <str>, "project": <uuid>}.
1416
+ return shortcutsRun("things-proxy-create-heading", {
1417
+ title: params.title,
1418
+ project: pre.destProject?.resolved?.uuid ?? "",
1419
+ });
1420
+ },
1421
+ };
1422
+ const todoClearDatedReminder = {
1423
+ op: "todo.clear-dated-reminder",
1424
+ hazards: ["H-UNKNOWN-DESTINATION", "H-NO-REMINDER"],
1425
+ preRead(db, params) {
1426
+ const pre = emptyPreState();
1427
+ pre.target = loadTarget(db, params.uuid);
1428
+ return pre;
1429
+ },
1430
+ expectedDelta(pre, params) {
1431
+ // The reminder clears; the scheduled date is left untouched (P3b).
1432
+ const target = pre.target;
1433
+ const startDate = target !== null && target.type === "to-do" ? (target.startDate ?? null) : null;
1434
+ return {
1435
+ mode: "update",
1436
+ uuid: params.uuid,
1437
+ assert: [
1438
+ { field: "reminder", equals: null },
1439
+ { field: "startDate", equals: startDate },
1440
+ ],
1441
+ };
1442
+ },
1443
+ compile(params, vector) {
1444
+ if (vector !== "shortcuts")
1445
+ unsupportedVector(this.op, vector);
1446
+ // `things-proxy-set-detail` input: {"id": <uuid>, "detail": <Detail>, "value": <str>}.
1447
+ // Reminder Time = "" is the clear path (scf P3b); the Detail selector name
1448
+ // comes from the app's Edit Items action list.
1449
+ return shortcutsRun("things-proxy-set-detail", {
1450
+ id: params.uuid,
1451
+ detail: "Reminder Time",
1452
+ value: "",
1453
+ });
1454
+ },
1455
+ };
1339
1456
  const trashEmpty = {
1340
1457
  op: "trash.empty",
1341
1458
  hazards: ["H-PERMANENT-DELETE"],
@@ -1362,6 +1479,7 @@ export const COMMANDS = {
1362
1479
  "todo.move": todoMove,
1363
1480
  "todo.set-tags": todoSetTags,
1364
1481
  "todo.replace-checklist": todoReplaceChecklist,
1482
+ "todo.edit-checklist-item": todoEditChecklistItem,
1365
1483
  "todo.delete": todoDelete,
1366
1484
  "project.add": projectAdd,
1367
1485
  "project.update": projectUpdate,
@@ -1388,5 +1506,7 @@ export const COMMANDS = {
1388
1506
  "heading.rename": headingRename,
1389
1507
  "heading.archive": headingArchive,
1390
1508
  "heading.unarchive": headingUnarchive,
1509
+ "heading.create": headingCreate,
1510
+ "todo.clear-dated-reminder": todoClearDatedReminder,
1391
1511
  };
1392
1512
  //# sourceMappingURL=commands.js.map