things-api 0.13.0 → 0.15.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 +3 -3
- package/dist/cli/commands/area.d.ts +5 -5
- package/dist/cli/commands/area.js +13 -8
- package/dist/cli/commands/area.js.map +1 -1
- package/dist/cli/commands/project.js +2 -2
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/reads.js +94 -16
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/show.js +6 -2
- package/dist/cli/commands/show.js.map +1 -1
- package/dist/cli/commands/todo.js +2 -2
- package/dist/cli/commands/todo.js.map +1 -1
- package/dist/cli/commands/writes.js +136 -70
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/glyphs.d.ts +9 -2
- package/dist/cli/glyphs.js +20 -15
- package/dist/cli/glyphs.js.map +1 -1
- package/dist/cli/help.js +4 -2
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/read-driver.d.ts +45 -9
- package/dist/cli/read-driver.js +39 -16
- package/dist/cli/read-driver.js.map +1 -1
- package/dist/cli/render.d.ts +57 -27
- package/dist/cli/render.js +117 -65
- package/dist/cli/render.js.map +1 -1
- package/dist/client.d.ts +127 -38
- package/dist/client.js +86 -22
- package/dist/client.js.map +1 -1
- package/dist/contracts.d.ts +35 -34
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +212 -148
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/dates.d.ts +26 -0
- package/dist/model/dates.js +46 -4
- package/dist/model/dates.js.map +1 -1
- package/dist/model/entities.d.ts +61 -39
- package/dist/model/entities.js +0 -4
- package/dist/model/entities.js.map +1 -1
- package/dist/model/mappers.js +24 -43
- package/dist/model/mappers.js.map +1 -1
- package/dist/read/area-filter.d.ts +1 -1
- package/dist/read/area-filter.js +5 -4
- package/dist/read/area-filter.js.map +1 -1
- package/dist/read/area-view.d.ts +0 -11
- package/dist/read/area-view.js +7 -11
- package/dist/read/area-view.js.map +1 -1
- package/dist/read/detail.js +2 -2
- package/dist/read/detail.js.map +1 -1
- package/dist/read/filter-contract.d.ts +1 -1
- package/dist/read/filter-contract.js +3 -0
- package/dist/read/filter-contract.js.map +1 -1
- package/dist/read/log-boundary.d.ts +46 -2
- package/dist/read/log-boundary.js +43 -2
- package/dist/read/log-boundary.js.map +1 -1
- package/dist/read/predicates.d.ts +13 -0
- package/dist/read/predicates.js +13 -0
- package/dist/read/predicates.js.map +1 -1
- package/dist/read/project-view.d.ts +34 -19
- package/dist/read/project-view.js +31 -25
- package/dist/read/project-view.js.map +1 -1
- package/dist/read/scope.d.ts +1 -1
- package/dist/read/scope.js +5 -4
- package/dist/read/scope.js.map +1 -1
- package/dist/read/search-rank.js +2 -2
- package/dist/read/search-rank.js.map +1 -1
- package/dist/read/shape.d.ts +78 -18
- package/dist/read/shape.js +496 -185
- package/dist/read/shape.js.map +1 -1
- package/dist/read/snapshot.js +2 -2
- package/dist/read/snapshot.js.map +1 -1
- package/dist/read/stage.d.ts +31 -13
- package/dist/read/stage.js +28 -11
- package/dist/read/stage.js.map +1 -1
- package/dist/read/truncation.d.ts +75 -2
- package/dist/read/truncation.js +69 -46
- package/dist/read/truncation.js.map +1 -1
- package/dist/read/views.d.ts +81 -18
- package/dist/read/views.js +136 -17
- package/dist/read/views.js.map +1 -1
- package/dist/surface-copy.d.ts +6 -0
- package/dist/surface-copy.js +6 -0
- package/dist/surface-copy.js.map +1 -1
- package/dist/write/batch.js +0 -1
- package/dist/write/batch.js.map +1 -1
- package/dist/write/clear-reminder.js +3 -1
- package/dist/write/clear-reminder.js.map +1 -1
- package/dist/write/commands.d.ts +25 -1
- package/dist/write/commands.js +350 -134
- package/dist/write/commands.js.map +1 -1
- package/dist/write/guards.js +36 -16
- package/dist/write/guards.js.map +1 -1
- package/dist/write/move.d.ts +26 -5
- package/dist/write/move.js +358 -2
- package/dist/write/move.js.map +1 -1
- package/dist/write/operations.d.ts +50 -19
- package/dist/write/operations.js +3 -2
- package/dist/write/operations.js.map +1 -1
- package/dist/write/pipeline.js +28 -7
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +89 -28
- package/dist/write/pre-state.js +197 -19
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/reorder.js +52 -5
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/resolution-timestamps.d.ts +34 -0
- package/dist/write/resolution-timestamps.js +286 -0
- package/dist/write/resolution-timestamps.js.map +1 -0
- package/dist/write/reversibility.js +10 -5
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/scope-guard.js +2 -5
- package/dist/write/scope-guard.js.map +1 -1
- package/dist/write/undo.d.ts +1 -7
- package/dist/write/undo.js +63 -16
- package/dist/write/undo.js.map +1 -1
- package/dist/write/vectors/applescript.js +23 -4
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/simulator.js +210 -9
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/url-scheme.js +0 -8
- package/dist/write/vectors/url-scheme.js.map +1 -1
- package/dist/write/verify/delta.d.ts +71 -12
- package/dist/write/verify/delta.js +94 -12
- package/dist/write/verify/delta.js.map +1 -1
- package/package.json +1 -1
- package/schema/envelope.schema.json +19 -70
- package/skills/things-cli/SKILL.md +13 -9
- package/skills/things-cli/references/contracts.md +3 -4
- package/skills/things-cli/references/{model.md → data-model.md} +10 -8
- package/skills/things-cli/references/ordering.md +18 -7
package/dist/write/commands.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { decodeReminderTime, encodeReminderTime, localToday, reminderUrlToken, } from "../model/dates.js";
|
|
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
|
-
|
|
84
|
-
|
|
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: "
|
|
110
|
+
{ field: "evening", equals: true },
|
|
91
111
|
];
|
|
92
112
|
case "anytime":
|
|
93
113
|
return [
|
|
@@ -144,6 +164,68 @@ function nonHeadingTitle(pre) {
|
|
|
144
164
|
const target = pre.target;
|
|
145
165
|
return target !== null && target.type !== "heading" ? target.title : "";
|
|
146
166
|
}
|
|
167
|
+
/** True when an add carries a resolution-timestamp flag (routes it through json). */
|
|
168
|
+
function addHasTimestamps(p) {
|
|
169
|
+
return p.createdAt !== undefined || p.completedAt !== undefined;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Validate the resolution-timestamp flags on an add (shape, chronology, and the
|
|
173
|
+
* schedule contradiction). Instant ordering is zone-independent (both values
|
|
174
|
+
* share one effective zone), so the chronology check resolves against the host
|
|
175
|
+
* zone. Throws RangeError on a bad combination.
|
|
176
|
+
*/
|
|
177
|
+
function assertAddTimestamps(p) {
|
|
178
|
+
if (p.createdAt !== undefined)
|
|
179
|
+
resolveResolutionInstant(p.createdAt);
|
|
180
|
+
if (p.completedAt !== undefined)
|
|
181
|
+
resolveResolutionInstant(p.completedAt);
|
|
182
|
+
if (p.createdAt !== undefined &&
|
|
183
|
+
p.completedAt !== undefined &&
|
|
184
|
+
resolveResolutionInstant(p.createdAt).getTime() >
|
|
185
|
+
resolveResolutionInstant(p.completedAt).getTime()) {
|
|
186
|
+
throw new RangeError("--created-at must not be after --completed-at");
|
|
187
|
+
}
|
|
188
|
+
if (p.completedAt !== undefined && (p.when !== undefined || p.reminder !== undefined)) {
|
|
189
|
+
throw new RangeError("--completed-at creates a resolved (Logbook) item, which has no active schedule — drop --when/--reminder");
|
|
190
|
+
}
|
|
191
|
+
if (p.reminder !== undefined && p.createdAt !== undefined) {
|
|
192
|
+
throw new RangeError("--reminder is not available with --created-at");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/** A `things:///json` import payload for a single created to-do/project. */
|
|
196
|
+
function addJsonUrl(type, attributes, token) {
|
|
197
|
+
return thingsUrl("json", { data: JSON.stringify([{ type, attributes }]) }, token);
|
|
198
|
+
}
|
|
199
|
+
/** The completion/creation json attributes for a timestamped add. */
|
|
200
|
+
function addDateAttributes(p, zone) {
|
|
201
|
+
const attrs = {};
|
|
202
|
+
if (p.createdAt !== undefined) {
|
|
203
|
+
attrs["creation-date"] = jsonTimestamp(resolveResolutionInstant(p.createdAt, zone));
|
|
204
|
+
}
|
|
205
|
+
if (p.completedAt !== undefined) {
|
|
206
|
+
attrs["completed"] = true;
|
|
207
|
+
attrs["completion-date"] = jsonTimestamp(resolveResolutionInstant(p.completedAt, zone));
|
|
208
|
+
}
|
|
209
|
+
return attrs;
|
|
210
|
+
}
|
|
211
|
+
/** The completion/creation delta assertions for a timestamped add. */
|
|
212
|
+
function addDateAssertions(p, zone) {
|
|
213
|
+
const assert = [];
|
|
214
|
+
if (p.completedAt !== undefined) {
|
|
215
|
+
assert.push({ field: "status", equals: "completed" });
|
|
216
|
+
assert.push({
|
|
217
|
+
field: "stoppedDate",
|
|
218
|
+
equals: hostLocalDate(resolveResolutionInstant(p.completedAt, zone)),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (p.createdAt !== undefined) {
|
|
222
|
+
assert.push({
|
|
223
|
+
field: "createdDate",
|
|
224
|
+
equals: hostLocalDate(resolveResolutionInstant(p.createdAt, zone)),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return assert;
|
|
228
|
+
}
|
|
147
229
|
// ----------------------------------------------------------------- commands
|
|
148
230
|
const todoAdd = {
|
|
149
231
|
op: "todo.add",
|
|
@@ -161,6 +243,7 @@ const todoAdd = {
|
|
|
161
243
|
if (params.heading !== undefined && !containerGiven(params.project)) {
|
|
162
244
|
throw new RangeError("heading requires a project destination");
|
|
163
245
|
}
|
|
246
|
+
assertAddTimestamps(params);
|
|
164
247
|
const pre = emptyPreState();
|
|
165
248
|
if (containerGiven(params.project)) {
|
|
166
249
|
pre.destProject = resolveProject(db, params.project);
|
|
@@ -182,13 +265,18 @@ const todoAdd = {
|
|
|
182
265
|
const assert = [];
|
|
183
266
|
if (params.notes !== undefined)
|
|
184
267
|
assert.push({ field: "notes", equals: params.notes });
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
268
|
+
// A born-resolved (completedAt) item has no active schedule — skip when/reminder.
|
|
269
|
+
if (params.completedAt === undefined) {
|
|
270
|
+
if (params.when !== undefined)
|
|
271
|
+
assert.push(...whenAssertions(params.when, ctx.todayIso));
|
|
272
|
+
if (params.reminder !== undefined) {
|
|
273
|
+
assert.push({ field: "reminder", equals: normalizeReminder(params.reminder) });
|
|
274
|
+
}
|
|
275
|
+
if (params.deadline !== undefined) {
|
|
276
|
+
assert.push({ field: "deadline", equals: params.deadline });
|
|
277
|
+
}
|
|
189
278
|
}
|
|
190
|
-
|
|
191
|
-
assert.push({ field: "deadline", equals: params.deadline });
|
|
279
|
+
assert.push(...addDateAssertions(params, ctx.zone));
|
|
192
280
|
if (params.tags !== undefined) {
|
|
193
281
|
assert.push({ field: "tags", equals: sortedTags(pre.resolvedTagTitles) });
|
|
194
282
|
}
|
|
@@ -221,6 +309,33 @@ const todoAdd = {
|
|
|
221
309
|
if (vector !== "url-scheme")
|
|
222
310
|
unsupportedVector(this.op, vector);
|
|
223
311
|
const container = pre.destProject?.resolved ?? pre.destArea?.resolved;
|
|
312
|
+
// Timestamped add: the only at-creation backdating surface is things:///json
|
|
313
|
+
// (the plain add URL drops date params — oddity 2g); route through it.
|
|
314
|
+
if (addHasTimestamps(params)) {
|
|
315
|
+
const attrs = { title: params.title };
|
|
316
|
+
if (params.notes !== undefined)
|
|
317
|
+
attrs["notes"] = params.notes;
|
|
318
|
+
Object.assign(attrs, addDateAttributes(params, ctx.zone));
|
|
319
|
+
if (params.completedAt === undefined) {
|
|
320
|
+
if (params.when !== undefined)
|
|
321
|
+
attrs["when"] = params.when;
|
|
322
|
+
if (params.deadline !== undefined)
|
|
323
|
+
attrs["deadline"] = params.deadline;
|
|
324
|
+
}
|
|
325
|
+
if (params.tags !== undefined)
|
|
326
|
+
attrs["tags"] = pre.resolvedTagTitles;
|
|
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
|
+
}
|
|
332
|
+
if (container?.uuid !== undefined)
|
|
333
|
+
attrs["list-id"] = container.uuid;
|
|
334
|
+
if (pre.destHeading?.resolved?.title !== undefined) {
|
|
335
|
+
attrs["heading"] = pre.destHeading.resolved.title;
|
|
336
|
+
}
|
|
337
|
+
return addJsonUrl("to-do", attrs, ctx.token);
|
|
338
|
+
}
|
|
224
339
|
return thingsUrl("add", {
|
|
225
340
|
title: params.title,
|
|
226
341
|
notes: params.notes,
|
|
@@ -261,7 +376,12 @@ function effectiveReminder(pre, params) {
|
|
|
261
376
|
return null;
|
|
262
377
|
if (!reminderIsLive(target.startDate, pre.todayIso))
|
|
263
378
|
return null;
|
|
264
|
-
|
|
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;
|
|
265
385
|
}
|
|
266
386
|
function assertNotesModesExclusive(params) {
|
|
267
387
|
if (params.notes !== undefined &&
|
|
@@ -304,7 +424,7 @@ const todoUpdate = {
|
|
|
304
424
|
if (joined !== undefined)
|
|
305
425
|
assert.push({ field: "notes", equals: joined });
|
|
306
426
|
if (params.when !== undefined) {
|
|
307
|
-
assert.push(...whenAssertions(params.when, ctx.todayIso));
|
|
427
|
+
assert.push(...whenAssertions(params.when, ctx.todayIso, { mode: "update" }));
|
|
308
428
|
const reminder = effectiveReminder(pre, params);
|
|
309
429
|
assert.push({
|
|
310
430
|
field: "reminder",
|
|
@@ -509,6 +629,21 @@ const todoSetTags = {
|
|
|
509
629
|
},
|
|
510
630
|
};
|
|
511
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
|
+
}
|
|
512
647
|
function checklistSpecs(items) {
|
|
513
648
|
const specs = items.map((i) => typeof i === "string"
|
|
514
649
|
? { title: i, completed: false }
|
|
@@ -555,10 +690,7 @@ const todoReplaceChecklist = {
|
|
|
555
690
|
operation: "update",
|
|
556
691
|
id: params.uuid,
|
|
557
692
|
attributes: {
|
|
558
|
-
"checklist-items": specs
|
|
559
|
-
type: "checklist-item",
|
|
560
|
-
attributes: { title: s.title, completed: s.completed },
|
|
561
|
-
})),
|
|
693
|
+
"checklist-items": checklistItemsJsonAttr(specs),
|
|
562
694
|
},
|
|
563
695
|
},
|
|
564
696
|
]);
|
|
@@ -604,6 +736,14 @@ const projectAdd = {
|
|
|
604
736
|
op: "project.add",
|
|
605
737
|
hazards: ["H-UNKNOWN-DESTINATION"],
|
|
606
738
|
preRead(db, params) {
|
|
739
|
+
assertAddTimestamps(params);
|
|
740
|
+
// §5b (B-PROJ-JSON.2): a completed-project json import silently reverts to
|
|
741
|
+
// OPEN if any child spec is unresolved. Seed to-dos are always open, so a
|
|
742
|
+
// born-resolved project cannot carry them.
|
|
743
|
+
if (params.completedAt !== undefined && params.todos !== undefined && params.todos.length > 0) {
|
|
744
|
+
throw new RangeError("--completed-at cannot seed child to-dos: a completed-project import reverts to open " +
|
|
745
|
+
"unless every child is resolved (§5b) — create the project resolved, then add logged children");
|
|
746
|
+
}
|
|
607
747
|
const pre = emptyPreState();
|
|
608
748
|
if (containerGiven(params.area))
|
|
609
749
|
pre.destArea = resolveArea(db, params.area);
|
|
@@ -614,10 +754,14 @@ const projectAdd = {
|
|
|
614
754
|
const assert = [];
|
|
615
755
|
if (params.notes !== undefined)
|
|
616
756
|
assert.push({ field: "notes", equals: params.notes });
|
|
617
|
-
if (params.
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
757
|
+
if (params.completedAt === undefined) {
|
|
758
|
+
if (params.when !== undefined)
|
|
759
|
+
assert.push(...whenAssertions(params.when, ctx.todayIso));
|
|
760
|
+
if (params.deadline !== undefined) {
|
|
761
|
+
assert.push({ field: "deadline", equals: params.deadline });
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
assert.push(...addDateAssertions(params, ctx.zone));
|
|
621
765
|
const area = pre.destArea?.resolved;
|
|
622
766
|
if (area !== undefined && area !== null)
|
|
623
767
|
assert.push({ field: "area.uuid", equals: area.uuid });
|
|
@@ -635,6 +779,24 @@ const projectAdd = {
|
|
|
635
779
|
compile(params, vector, pre, ctx) {
|
|
636
780
|
if (vector !== "url-scheme")
|
|
637
781
|
unsupportedVector(this.op, vector);
|
|
782
|
+
if (addHasTimestamps(params)) {
|
|
783
|
+
const attrs = { title: params.title };
|
|
784
|
+
if (params.notes !== undefined)
|
|
785
|
+
attrs["notes"] = params.notes;
|
|
786
|
+
Object.assign(attrs, addDateAttributes(params, ctx.zone));
|
|
787
|
+
if (params.completedAt === undefined) {
|
|
788
|
+
if (params.when !== undefined)
|
|
789
|
+
attrs["when"] = params.when;
|
|
790
|
+
if (params.deadline !== undefined)
|
|
791
|
+
attrs["deadline"] = params.deadline;
|
|
792
|
+
if (params.todos !== undefined) {
|
|
793
|
+
attrs["items"] = params.todos.map((t) => ({ type: "to-do", attributes: { title: t } }));
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (pre.destArea?.resolved?.uuid !== undefined)
|
|
797
|
+
attrs["area-id"] = pre.destArea.resolved.uuid;
|
|
798
|
+
return addJsonUrl("project", attrs, ctx.token);
|
|
799
|
+
}
|
|
638
800
|
return thingsUrl("add-project", {
|
|
639
801
|
title: params.title,
|
|
640
802
|
notes: params.notes,
|
|
@@ -665,7 +827,7 @@ const projectUpdate = {
|
|
|
665
827
|
if (joined !== undefined)
|
|
666
828
|
assert.push({ field: "notes", equals: joined });
|
|
667
829
|
if (params.when !== undefined) {
|
|
668
|
-
assert.push(...whenAssertions(params.when, ctx.todayIso));
|
|
830
|
+
assert.push(...whenAssertions(params.when, ctx.todayIso, { mode: "update" }));
|
|
669
831
|
// Projects carry the same reminderTime codec as to-dos (A3); a bare
|
|
670
832
|
// when= clears an existing reminder unless auto-preserved.
|
|
671
833
|
const reminder = effectiveReminder(pre, params);
|
|
@@ -1229,7 +1391,7 @@ const tagUpdate = {
|
|
|
1229
1391
|
const reorder = {
|
|
1230
1392
|
op: "reorder",
|
|
1231
1393
|
hazards: ["H-UNKNOWN-DESTINATION", "H-REORDER-SCOPE"],
|
|
1232
|
-
preRead(db, params, now) {
|
|
1394
|
+
preRead(db, params, now, zone) {
|
|
1233
1395
|
const pre = emptyPreState();
|
|
1234
1396
|
let containerUuid = null;
|
|
1235
1397
|
if (params.scope === "project") {
|
|
@@ -1254,18 +1416,46 @@ const reorder = {
|
|
|
1254
1416
|
containerUuid = pre.destArea.resolved?.uuid ?? null;
|
|
1255
1417
|
}
|
|
1256
1418
|
}
|
|
1257
|
-
|
|
1419
|
+
// The `reorder` op IS the pure-native private index wire — the ONE surface
|
|
1420
|
+
// runReorder dispatches here (strategy === "native"). So the LOGSORT ORD-13
|
|
1421
|
+
// permit is unconditionally offered (`admitResolved: true`); computeReorderPre
|
|
1422
|
+
// gates it internally on `key === "index"`, so day-axis native re-ranks
|
|
1423
|
+
// (today/container-day/tomorrow → todayIndex) still refuse resolved movees.
|
|
1424
|
+
// Every bounce/move/day-axis orchestrator calls computeReorderPre WITHOUT the
|
|
1425
|
+
// flag, so a resolved movee reaching an uncertified protocol stays refused.
|
|
1426
|
+
pre.reorder = computeReorderPre(db, params, containerUuid, now, { admitResolved: true, zone });
|
|
1258
1427
|
return pre;
|
|
1259
1428
|
},
|
|
1260
1429
|
expectedDelta(pre, params) {
|
|
1261
1430
|
// Verify the REQUESTED sequence (strictly ascending ranks). The wire
|
|
1262
1431
|
// list pins the unrequested tail too, but the caller's contract is the
|
|
1263
1432
|
// requested prefix; tail members are covered by pre-rank tripwires.
|
|
1433
|
+
// TODWIRE: on the `today` scope the wire is now MINIMAL — only the named
|
|
1434
|
+
// block gets fresh (front, monotonic) `todayIndex`; the unnamed tail keeps
|
|
1435
|
+
// its cohort-interleaved (non-monotonic) `todayIndex`, so the FULL
|
|
1436
|
+
// `params.uuids` is NOT strictly-ascending after the write. Verify the
|
|
1437
|
+
// `todayWire` (the named block) instead — it lands at the visible front in
|
|
1438
|
+
// wire order with fresh ascending values, and the untouched tail rides the
|
|
1439
|
+
// pre-rank tripwires. Every other scope verifies the requested prefix.
|
|
1440
|
+
// LOGSORT ORD-13 byte-lock: any admitted UNSWEPT-resolved movee must read
|
|
1441
|
+
// back index-only — status still closed, stoppedDate intact, umd unbumped
|
|
1442
|
+
// (a reopen would flip all three). Frozen assertions carry that into verify.
|
|
1443
|
+
const frozen = (pre.reorder?.resolvedMembers ?? []).map((m) => ({
|
|
1444
|
+
uuid: m.uuid,
|
|
1445
|
+
assert: [
|
|
1446
|
+
{ field: "status", equals: m.status },
|
|
1447
|
+
{ field: "stoppedDate", equals: m.stoppedDate },
|
|
1448
|
+
],
|
|
1449
|
+
}));
|
|
1450
|
+
const sequence = params.scope === "today" && pre.reorder?.todayWire != null
|
|
1451
|
+
? pre.reorder.todayWire
|
|
1452
|
+
: params.uuids;
|
|
1264
1453
|
return {
|
|
1265
1454
|
mode: "ordering",
|
|
1266
1455
|
key: pre.reorder?.key ??
|
|
1267
1456
|
(params.scope === "today" || params.scope === "evening" ? "todayIndex" : "index"),
|
|
1268
|
-
sequence
|
|
1457
|
+
sequence,
|
|
1458
|
+
...(frozen.length > 0 && { frozen }),
|
|
1269
1459
|
};
|
|
1270
1460
|
},
|
|
1271
1461
|
compile(params, vector, pre) {
|
|
@@ -1289,7 +1479,14 @@ const reorder = {
|
|
|
1289
1479
|
: params.scope === "upcoming"
|
|
1290
1480
|
? `list "Upcoming"`
|
|
1291
1481
|
: `list "Today"`;
|
|
1292
|
-
|
|
1482
|
+
// TODWIRE — the `today` scope sends the MINIMAL visible-order wire (names only
|
|
1483
|
+
// what must move; unnamed rows keep their entry cohorts + visible positions),
|
|
1484
|
+
// not the OLD full `wireList` that fused every cohort (MOVPLC/ORD-20). Every
|
|
1485
|
+
// other native scope keeps the full wire (their `index`/day axes do not
|
|
1486
|
+
// re-stamp `tiRef`, so a full re-rank is non-damaging).
|
|
1487
|
+
const wire = (params.scope === "today" ? pre.reorder?.todayWire : undefined) ??
|
|
1488
|
+
pre.reorder?.wireList ??
|
|
1489
|
+
params.uuids;
|
|
1293
1490
|
if (params.scope === "someday") {
|
|
1294
1491
|
// The Someday handler STACKS each sent id above the list's current top
|
|
1295
1492
|
// (the current top itself never moves), with OPPOSITE stack directions
|
|
@@ -1311,117 +1508,108 @@ const reorder = {
|
|
|
1311
1508
|
return osa(`${PRIVATE_REORDER_COMMAND} ${specifier} with ids ${q(wire.join(","))}`);
|
|
1312
1509
|
},
|
|
1313
1510
|
};
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1511
|
+
// ---- resolution-timestamp normalization (§5 of the plan) ------------------
|
|
1512
|
+
/**
|
|
1513
|
+
* Parse an ISO date (`2025-01-15`) OR datetime (`2025-01-15T09:30[:ss]`) and
|
|
1514
|
+
* resolve it to a single UTC instant in the effective `zone`. A date-only value
|
|
1515
|
+
* lands at NOON in that zone (B-DATEONLY: noon decodes to the intended calendar
|
|
1516
|
+
* date in every zone; midnight can slip a day). A datetime is read as wall-clock
|
|
1517
|
+
* time in the effective zone. `zone` undefined = the process-local (app host)
|
|
1518
|
+
* zone, which for a local CLI run IS the app's own zone.
|
|
1519
|
+
*/
|
|
1520
|
+
export function resolveResolutionInstant(input, zone) {
|
|
1521
|
+
const dateOnly = /^(\d{4}-\d{2}-\d{2})$/.exec(input);
|
|
1522
|
+
if (dateOnly !== null)
|
|
1523
|
+
return zonedWallInstant(dateOnly[1], 12, 0, 0, zone);
|
|
1524
|
+
const dt = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(input);
|
|
1525
|
+
if (dt !== null) {
|
|
1526
|
+
return zonedWallInstant(dt[1], Number(dt[2]), Number(dt[3]), dt[4] === undefined ? 0 : Number(dt[4]), zone);
|
|
1527
|
+
}
|
|
1528
|
+
throw new RangeError(`invalid timestamp "${input}" — expected an ISO date (YYYY-MM-DD) or datetime (YYYY-MM-DDTHH:mm)`);
|
|
1529
|
+
}
|
|
1530
|
+
const pad2 = (n) => String(n).padStart(2, "0");
|
|
1531
|
+
/** The host-local calendar date (`YYYY-MM-DD`) an instant renders as — what the verify layer observes for stopped/created. */
|
|
1532
|
+
function hostLocalDate(instant) {
|
|
1533
|
+
return `${instant.getFullYear()}-${pad2(instant.getMonth() + 1)}-${pad2(instant.getDate())}`;
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* The host-local calendar date a resolution-timestamp value resolves to — the
|
|
1537
|
+
* value the verify layer observes for `stoppedDate`/`createdDate`. Shared with
|
|
1538
|
+
* the multi-leg orchestrators so their synthesized deltas match the op's.
|
|
1539
|
+
*/
|
|
1540
|
+
export function resolutionDeltaDate(input, zone) {
|
|
1541
|
+
return hostLocalDate(resolveResolutionInstant(input, zone));
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* An instant as a second-precision UTC timestamp (`…Thh:mm:ssZ`) for the json
|
|
1545
|
+
* import. WITHOUT milliseconds: the app's json date parser rejects fractional
|
|
1546
|
+
* seconds — a `.000Z` stamp fails the whole command (oddity 2h; P4d's validated
|
|
1547
|
+
* shape was second-precision).
|
|
1548
|
+
*/
|
|
1549
|
+
function jsonTimestamp(instant) {
|
|
1550
|
+
return instant.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* A locale-proof AppleScript date literal built from an instant's HOST-LOCAL
|
|
1554
|
+
* wall-clock components — AS `current date` mutation lives in the host zone, so
|
|
1555
|
+
* setting those components reproduces exactly this instant regardless of the
|
|
1556
|
+
* effective zone the noon was computed in.
|
|
1557
|
+
*/
|
|
1558
|
+
function asDateBlockFromInstant(varName, instant) {
|
|
1317
1559
|
return [
|
|
1318
1560
|
`set ${varName} to current date`,
|
|
1319
|
-
`set time of ${varName} to
|
|
1561
|
+
`set time of ${varName} to ${instant.getHours()} * hours + ${instant.getMinutes()} * minutes + ${instant.getSeconds()}`,
|
|
1320
1562
|
`set day of ${varName} to 1`,
|
|
1321
|
-
`set year of ${varName} to ${
|
|
1322
|
-
`set month of ${varName} to ${
|
|
1323
|
-
`set day of ${varName} to ${
|
|
1563
|
+
`set year of ${varName} to ${instant.getFullYear()}`,
|
|
1564
|
+
`set month of ${varName} to ${instant.getMonth() + 1}`,
|
|
1565
|
+
`set day of ${varName} to ${instant.getDate()}`,
|
|
1324
1566
|
];
|
|
1325
1567
|
}
|
|
1326
|
-
/**
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1568
|
+
/** Shared spec for the kind-agnostic `set-dates` op (to-do / project). */
|
|
1569
|
+
function setDatesSpec(op, addressor) {
|
|
1570
|
+
return {
|
|
1571
|
+
op,
|
|
1572
|
+
hazards: ["H-UNKNOWN-DESTINATION", "H-BACKDATE-OPEN"],
|
|
1573
|
+
preRead(db, params) {
|
|
1574
|
+
if (params.completedAt === undefined && params.createdAt === undefined) {
|
|
1575
|
+
throw new RangeError("nothing to set: give completedAt and/or createdAt");
|
|
1576
|
+
}
|
|
1577
|
+
const pre = emptyPreState();
|
|
1578
|
+
pre.target = loadTarget(db, params.uuid);
|
|
1579
|
+
return pre;
|
|
1580
|
+
},
|
|
1581
|
+
expectedDelta(_pre, params, ctx) {
|
|
1582
|
+
const assert = [];
|
|
1583
|
+
if (params.completedAt !== undefined) {
|
|
1584
|
+
assert.push({
|
|
1585
|
+
field: "stoppedDate",
|
|
1586
|
+
equals: hostLocalDate(resolveResolutionInstant(params.completedAt, ctx.zone)),
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
if (params.createdAt !== undefined) {
|
|
1590
|
+
assert.push({
|
|
1591
|
+
field: "createdDate",
|
|
1592
|
+
equals: hostLocalDate(resolveResolutionInstant(params.createdAt, ctx.zone)),
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
return { mode: "update", uuid: params.uuid, assert };
|
|
1596
|
+
},
|
|
1597
|
+
compile(params, vector, _pre, ctx) {
|
|
1598
|
+
if (vector !== "applescript")
|
|
1599
|
+
unsupportedVector(this.op, vector);
|
|
1600
|
+
const statements = [];
|
|
1601
|
+
if (params.completedAt !== undefined) {
|
|
1602
|
+
statements.push(...asDateBlockFromInstant("compDate", resolveResolutionInstant(params.completedAt, ctx.zone)), `set completion date of ${addressor} id ${q(params.uuid)} to compDate`);
|
|
1603
|
+
}
|
|
1604
|
+
if (params.createdAt !== undefined) {
|
|
1605
|
+
statements.push(...asDateBlockFromInstant("createDate", resolveResolutionInstant(params.createdAt, ctx.zone)), `set creation date of ${addressor} id ${q(params.uuid)} to createDate`);
|
|
1606
|
+
}
|
|
1607
|
+
return osaBlock(statements);
|
|
1608
|
+
},
|
|
1609
|
+
};
|
|
1337
1610
|
}
|
|
1338
|
-
const
|
|
1339
|
-
|
|
1340
|
-
hazards: ["H-UNKNOWN-DESTINATION", "H-BACKDATE-OPEN"],
|
|
1341
|
-
preRead(db, params) {
|
|
1342
|
-
if (params.completionDate === undefined && params.creationDate === undefined) {
|
|
1343
|
-
throw new RangeError("nothing to backdate: give completionDate and/or creationDate");
|
|
1344
|
-
}
|
|
1345
|
-
const pre = emptyPreState();
|
|
1346
|
-
pre.target = loadTarget(db, params.uuid);
|
|
1347
|
-
return pre;
|
|
1348
|
-
},
|
|
1349
|
-
expectedDelta(_pre, params) {
|
|
1350
|
-
const assert = [];
|
|
1351
|
-
if (params.completionDate !== undefined) {
|
|
1352
|
-
assert.push({ field: "stoppedDate", equals: params.completionDate });
|
|
1353
|
-
}
|
|
1354
|
-
if (params.creationDate !== undefined) {
|
|
1355
|
-
assert.push({ field: "createdDate", equals: params.creationDate });
|
|
1356
|
-
}
|
|
1357
|
-
return { mode: "update", uuid: params.uuid, assert };
|
|
1358
|
-
},
|
|
1359
|
-
compile(params, vector) {
|
|
1360
|
-
if (vector !== "applescript")
|
|
1361
|
-
unsupportedVector(this.op, vector);
|
|
1362
|
-
const statements = [];
|
|
1363
|
-
if (params.completionDate !== undefined) {
|
|
1364
|
-
statements.push(...asDateBlock("compDate", params.completionDate), `set completion date of to do id ${q(params.uuid)} to compDate`);
|
|
1365
|
-
}
|
|
1366
|
-
if (params.creationDate !== undefined) {
|
|
1367
|
-
statements.push(...asDateBlock("createDate", params.creationDate), `set creation date of to do id ${q(params.uuid)} to createDate`);
|
|
1368
|
-
}
|
|
1369
|
-
return osaBlock(statements);
|
|
1370
|
-
},
|
|
1371
|
-
};
|
|
1372
|
-
const todoAddLogged = {
|
|
1373
|
-
op: "todo.add-logged",
|
|
1374
|
-
hazards: [],
|
|
1375
|
-
preRead(db, params) {
|
|
1376
|
-
if (params.creationDate !== undefined &&
|
|
1377
|
-
params.creationDate.localeCompare(params.completionDate) > 0) {
|
|
1378
|
-
throw new RangeError("creationDate must not be after completionDate");
|
|
1379
|
-
}
|
|
1380
|
-
const pre = emptyPreState();
|
|
1381
|
-
pre.sameTitleUuids = sameTitleTaskUuids(db, params.title, "to-do");
|
|
1382
|
-
return pre;
|
|
1383
|
-
},
|
|
1384
|
-
expectedDelta(pre, params) {
|
|
1385
|
-
const assert = [
|
|
1386
|
-
{ field: "status", equals: "completed" },
|
|
1387
|
-
{ field: "stoppedDate", equals: params.completionDate },
|
|
1388
|
-
];
|
|
1389
|
-
if (params.creationDate !== undefined) {
|
|
1390
|
-
assert.push({ field: "createdDate", equals: params.creationDate });
|
|
1391
|
-
}
|
|
1392
|
-
if (params.notes !== undefined)
|
|
1393
|
-
assert.push({ field: "notes", equals: params.notes });
|
|
1394
|
-
return {
|
|
1395
|
-
mode: "create",
|
|
1396
|
-
probe: {
|
|
1397
|
-
title: params.title,
|
|
1398
|
-
type: "to-do",
|
|
1399
|
-
sinceEpoch: 0,
|
|
1400
|
-
excludeUuids: pre.sameTitleUuids,
|
|
1401
|
-
},
|
|
1402
|
-
assert,
|
|
1403
|
-
};
|
|
1404
|
-
},
|
|
1405
|
-
compile(params, vector, _pre, ctx) {
|
|
1406
|
-
if (vector !== "url-scheme")
|
|
1407
|
-
unsupportedVector(this.op, vector);
|
|
1408
|
-
const payload = JSON.stringify([
|
|
1409
|
-
{
|
|
1410
|
-
type: "to-do",
|
|
1411
|
-
attributes: {
|
|
1412
|
-
title: params.title,
|
|
1413
|
-
...(params.notes !== undefined && { notes: params.notes }),
|
|
1414
|
-
completed: true,
|
|
1415
|
-
"completion-date": utcNoon(params.completionDate),
|
|
1416
|
-
...(params.creationDate !== undefined && {
|
|
1417
|
-
"creation-date": utcNoon(params.creationDate),
|
|
1418
|
-
}),
|
|
1419
|
-
},
|
|
1420
|
-
},
|
|
1421
|
-
]);
|
|
1422
|
-
return thingsUrl("json", { data: payload }, ctx.token);
|
|
1423
|
-
},
|
|
1424
|
-
};
|
|
1611
|
+
const todoSetDates = setDatesSpec("todo.set-dates", "to do");
|
|
1612
|
+
const projectSetDates = setDatesSpec("project.set-dates", "project");
|
|
1425
1613
|
/** Children of a heading (open ones drive the archive policies). */
|
|
1426
1614
|
function headingChildren(db, headingUuid) {
|
|
1427
1615
|
const rows = db
|
|
@@ -1589,7 +1777,11 @@ const projectMoveHeading = {
|
|
|
1589
1777
|
// fed the computed full order.
|
|
1590
1778
|
if (vector !== "applescript")
|
|
1591
1779
|
unsupportedVector(this.op, vector);
|
|
1592
|
-
|
|
1780
|
+
// #V11: send the MINIMAL front-cluster wire, not the full order — an archived
|
|
1781
|
+
// heading that need not move stays out of it (untouched, HEADSORT H-UNSWEPT);
|
|
1782
|
+
// any archived heading the target FORCES in (pre.headingMove.reopened) is
|
|
1783
|
+
// reopened and disclosed. NEVER put un-targeted rows into the wire.
|
|
1784
|
+
const order = pre.headingMove?.wire ?? [];
|
|
1593
1785
|
return osa(`${PRIVATE_REORDER_COMMAND} project id ${q(pre.destProject?.resolved?.uuid ?? "")} ` +
|
|
1594
1786
|
`with ids ${q(order.join(","))}`);
|
|
1595
1787
|
},
|
|
@@ -2221,6 +2413,29 @@ const trashEmpty = {
|
|
|
2221
2413
|
return osa("empty trash");
|
|
2222
2414
|
},
|
|
2223
2415
|
};
|
|
2416
|
+
const logNow = {
|
|
2417
|
+
op: "log-now",
|
|
2418
|
+
hazards: [],
|
|
2419
|
+
preRead(db, _params, now, zone) {
|
|
2420
|
+
const pre = emptyPreState();
|
|
2421
|
+
// The resolved-but-unlogged census the verb will move (the disclosed count),
|
|
2422
|
+
// and the pre-op boundary stamp the delta compares against.
|
|
2423
|
+
pre.logNow = {
|
|
2424
|
+
pending: pendingLogCount(db, now, zone),
|
|
2425
|
+
manualLogDatePre: manualLogDateEpoch(db),
|
|
2426
|
+
};
|
|
2427
|
+
return pre;
|
|
2428
|
+
},
|
|
2429
|
+
expectedDelta(pre) {
|
|
2430
|
+
const ln = pre.logNow ?? { pending: 0, manualLogDatePre: null };
|
|
2431
|
+
return { mode: "logged-now", pending: ln.pending, manualLogDatePre: ln.manualLogDatePre };
|
|
2432
|
+
},
|
|
2433
|
+
compile(_params, vector) {
|
|
2434
|
+
if (vector !== "applescript")
|
|
2435
|
+
unsupportedVector(this.op, vector);
|
|
2436
|
+
return osa("log completed now");
|
|
2437
|
+
},
|
|
2438
|
+
};
|
|
2224
2439
|
export const COMMANDS = {
|
|
2225
2440
|
"todo.add": todoAdd,
|
|
2226
2441
|
"todo.update": todoUpdate,
|
|
@@ -2252,8 +2467,6 @@ export const COMMANDS = {
|
|
|
2252
2467
|
"project.reopen": projectReopen,
|
|
2253
2468
|
"project.restore": projectRestore,
|
|
2254
2469
|
"project.set-tags": projectSetTags,
|
|
2255
|
-
"todo.backdate": todoBackdate,
|
|
2256
|
-
"todo.add-logged": todoAddLogged,
|
|
2257
2470
|
"project.add-heading": headingAdd,
|
|
2258
2471
|
"project.rename-heading": headingRename,
|
|
2259
2472
|
"project.archive-heading": headingArchive,
|
|
@@ -2271,8 +2484,11 @@ export const COMMANDS = {
|
|
|
2271
2484
|
"project.reschedule-repeat": projectRescheduleRepeat,
|
|
2272
2485
|
"project.pause-repeat": projectPauseRepeat,
|
|
2273
2486
|
"project.resume-repeat": projectResumeRepeat,
|
|
2487
|
+
"todo.set-dates": todoSetDates,
|
|
2488
|
+
"project.set-dates": projectSetDates,
|
|
2274
2489
|
"area.reorder": areaReorderSidebar,
|
|
2275
2490
|
"project.make-repeating": projectMakeRepeating,
|
|
2276
2491
|
"project.add-repeating": projectAddRepeating,
|
|
2492
|
+
"log-now": logNow,
|
|
2277
2493
|
};
|
|
2278
2494
|
//# sourceMappingURL=commands.js.map
|