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.
- package/README.md +13 -1
- package/dist/cli/commands/area.d.ts +14 -0
- package/dist/cli/commands/area.js +112 -33
- package/dist/cli/commands/area.js.map +1 -1
- package/dist/cli/commands/doctor.js +8 -0
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/project.d.ts +14 -0
- package/dist/cli/commands/project.js +123 -34
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/reads.d.ts +111 -10
- package/dist/cli/commands/reads.js +567 -73
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/setup.d.ts +2 -0
- package/dist/cli/commands/setup.js +91 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/show.d.ts +9 -0
- package/dist/cli/commands/show.js +41 -0
- package/dist/cli/commands/show.js.map +1 -0
- package/dist/cli/commands/todo.d.ts +9 -0
- package/dist/cli/commands/todo.js +89 -15
- package/dist/cli/commands/todo.js.map +1 -1
- package/dist/cli/commands/writes.js +55 -1
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/glyphs.d.ts +72 -0
- package/dist/cli/glyphs.js +169 -0
- package/dist/cli/glyphs.js.map +1 -0
- package/dist/cli/main.js +4 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/style.d.ts +6 -0
- package/dist/cli/style.js +6 -0
- package/dist/cli/style.js.map +1 -1
- package/dist/client.d.ts +35 -37
- package/dist/client.js +10 -74
- package/dist/client.js.map +1 -1
- package/dist/contracts.d.ts +1 -1
- package/dist/contracts.js +1 -1
- package/dist/db/baselines/db-v26.js +7 -5
- package/dist/db/baselines/db-v26.js.map +1 -1
- package/dist/db/schema.d.ts +1 -1
- package/dist/db/schema.js +7 -1
- package/dist/db/schema.js.map +1 -1
- package/dist/diagnose.d.ts +19 -0
- package/dist/diagnose.js +42 -0
- package/dist/diagnose.js.map +1 -1
- package/dist/mcp/server.js +20 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/entities.d.ts +14 -0
- package/dist/model/entities.js.map +1 -1
- package/dist/model/mappers.js +8 -0
- package/dist/model/mappers.js.map +1 -1
- package/dist/model/occurrences.js +4 -1
- package/dist/model/occurrences.js.map +1 -1
- package/dist/model/recurrence.d.ts +23 -2
- package/dist/model/recurrence.js +33 -1
- package/dist/model/recurrence.js.map +1 -1
- package/dist/read/area-view.js +21 -5
- package/dist/read/area-view.js.map +1 -1
- package/dist/read/detail.js +12 -2
- package/dist/read/detail.js.map +1 -1
- package/dist/read/log-boundary.d.ts +24 -0
- package/dist/read/log-boundary.js +38 -0
- package/dist/read/log-boundary.js.map +1 -0
- package/dist/read/project-view.js +28 -3
- package/dist/read/project-view.js.map +1 -1
- package/dist/read/queries.d.ts +14 -1
- package/dist/read/queries.js +25 -2
- package/dist/read/queries.js.map +1 -1
- package/dist/read/show-target.d.ts +16 -0
- package/dist/read/show-target.js +32 -0
- package/dist/read/show-target.js.map +1 -0
- package/dist/read/snapshot.js +2 -0
- package/dist/read/snapshot.js.map +1 -1
- package/dist/read/views.d.ts +25 -3
- package/dist/read/views.js +176 -21
- package/dist/read/views.js.map +1 -1
- package/dist/write/automation-probe.js +14 -6
- package/dist/write/automation-probe.js.map +1 -1
- package/dist/write/availability.d.ts +28 -0
- package/dist/write/availability.js +105 -0
- package/dist/write/availability.js.map +1 -0
- package/dist/write/capabilities.d.ts +3 -0
- package/dist/write/capabilities.js +2 -0
- package/dist/write/capabilities.js.map +1 -1
- package/dist/write/checklist.d.ts +50 -0
- package/dist/write/checklist.js +61 -0
- package/dist/write/checklist.js.map +1 -0
- package/dist/write/clear-reminder.d.ts +3 -0
- package/dist/write/clear-reminder.js +205 -0
- package/dist/write/clear-reminder.js.map +1 -0
- package/dist/write/commands.js +129 -9
- package/dist/write/commands.js.map +1 -1
- package/dist/write/edit-checklist.d.ts +3 -0
- package/dist/write/edit-checklist.js +184 -0
- package/dist/write/edit-checklist.js.map +1 -0
- package/dist/write/failure-hints.d.ts +14 -1
- package/dist/write/failure-hints.js +31 -6
- package/dist/write/failure-hints.js.map +1 -1
- package/dist/write/guards.d.ts +1 -1
- package/dist/write/guards.js +16 -0
- package/dist/write/guards.js.map +1 -1
- package/dist/write/operations.d.ts +32 -1
- package/dist/write/operations.js +3 -0
- package/dist/write/operations.js.map +1 -1
- package/dist/write/pipeline.d.ts +5 -0
- package/dist/write/pipeline.js +24 -1
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/reversibility.d.ts +46 -0
- package/dist/write/reversibility.js +164 -0
- package/dist/write/reversibility.js.map +1 -0
- package/dist/write/undo.d.ts +29 -2
- package/dist/write/undo.js +356 -9
- package/dist/write/undo.js.map +1 -1
- package/dist/write/vectors/registry.js +2 -1
- package/dist/write/vectors/registry.js.map +1 -1
- package/dist/write/vectors/shortcuts.d.ts +9 -0
- package/dist/write/vectors/shortcuts.js +90 -0
- package/dist/write/vectors/shortcuts.js.map +1 -0
- package/dist/write/vectors/types.d.ts +11 -3
- package/dist/write/vectors/url-scheme.js +16 -0
- package/dist/write/vectors/url-scheme.js.map +1 -1
- package/dist/write/verify/delta.d.ts +1 -1
- package/dist/write/verify/delta.js +1 -1
- package/dist/write/verify/delta.js.map +1 -1
- package/package.json +3 -2
- package/shortcuts/things-proxy-create-heading.shortcut +0 -0
- package/shortcuts/things-proxy-delete-items-permanently.shortcut +0 -0
- package/shortcuts/things-proxy-delete-items.shortcut +0 -0
- package/shortcuts/things-proxy-edit-title.shortcut +0 -0
- package/shortcuts/things-proxy-find-items.shortcut +0 -0
- 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,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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"}
|
package/dist/write/commands.js
CHANGED
|
@@ -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
|
|
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)
|
|
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
|