things-api 0.18.0 → 0.19.1
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 +18 -9
- package/deputy/AppIcon.icns +0 -0
- package/deputy/VERSION +1 -1
- package/deputy/helpers-Info.plist +4 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/Info.plist +3 -1
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/MacOS/things-reader +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/Resources/AppIcon.icns +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/_CodeSignature/CodeResources +15 -2
- package/deputy/prebuilt/Things API Helper.app/Contents/Info.plist +5 -1
- package/deputy/prebuilt/Things API Helper.app/Contents/MacOS/things-deputy +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Resources/AppIcon.icns +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/_CodeSignature/CodeResources +14 -2
- package/deputy/reader/Info.plist +2 -0
- package/deputy/reader/entitlements.plist +30 -0
- package/deputy/reader/main.swift +129 -40
- package/deputy/src/server.swift +25 -2
- package/deputy/src/tcc.swift +58 -0
- package/dist/audit/schema.d.ts +30 -1
- package/dist/audit/schema.js.map +1 -1
- package/dist/capability.d.ts +322 -0
- package/dist/capability.js +706 -0
- package/dist/capability.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +13 -1
- package/dist/cli/commands/doctor.js +141 -5
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/helpers.js +189 -51
- package/dist/cli/commands/helpers.js.map +1 -1
- package/dist/cli/commands/mcp.js +8 -0
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/op-result.js +6 -0
- package/dist/cli/commands/op-result.js.map +1 -1
- package/dist/cli/commands/reads.js +47 -1
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/repeat-flags.js +34 -2
- package/dist/cli/commands/repeat-flags.js.map +1 -1
- package/dist/cli/commands/setup.d.ts +9 -0
- package/dist/cli/commands/setup.js +76 -83
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/writes.d.ts +1 -1
- package/dist/cli/commands/writes.js +311 -142
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/dry-run.d.ts +1 -1
- package/dist/cli/dry-run.js +1 -1
- package/dist/cli/glyphs.d.ts +23 -4
- package/dist/cli/glyphs.js +46 -19
- package/dist/cli/glyphs.js.map +1 -1
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +49 -27
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/interrupt.d.ts +54 -6
- package/dist/cli/interrupt.js +78 -12
- package/dist/cli/interrupt.js.map +1 -1
- package/dist/cli/main.js +8 -5
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/period.d.ts +3 -1
- package/dist/cli/period.js +1 -1
- package/dist/cli/period.js.map +1 -1
- package/dist/cli/read-driver.js +26 -1
- package/dist/cli/read-driver.js.map +1 -1
- package/dist/cli/render.d.ts +31 -4
- package/dist/cli/render.js +127 -15
- package/dist/cli/render.js.map +1 -1
- package/dist/client.d.ts +23 -3
- package/dist/client.js +36 -13
- package/dist/client.js.map +1 -1
- package/dist/contracts.d.ts +8 -4
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deputy/install.d.ts +234 -6
- package/dist/deputy/install.js +843 -31
- package/dist/deputy/install.js.map +1 -1
- package/dist/deputy/notices.js +2 -2
- package/dist/deputy/notices.js.map +1 -1
- package/dist/deputy/protocol.d.ts +59 -5
- package/dist/deputy/protocol.js +48 -11
- package/dist/deputy/protocol.js.map +1 -1
- package/dist/deputy/routing.d.ts +37 -0
- package/dist/deputy/routing.js +181 -27
- package/dist/deputy/routing.js.map +1 -1
- package/dist/deputy/wake.d.ts +61 -0
- package/dist/deputy/wake.js +149 -0
- package/dist/deputy/wake.js.map +1 -0
- package/dist/diagnose.d.ts +19 -4
- package/dist/diagnose.js +64 -8
- package/dist/diagnose.js.map +1 -1
- package/dist/direct-setup.d.ts +104 -0
- package/dist/direct-setup.js +658 -0
- package/dist/direct-setup.js.map +1 -0
- package/dist/host-access.d.ts +75 -0
- package/dist/host-access.js +177 -0
- package/dist/host-access.js.map +1 -0
- package/dist/index.d.ts +20 -5
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +27 -1
- package/dist/mcp/server.js +308 -109
- package/dist/mcp/server.js.map +1 -1
- package/dist/op-result.d.ts +9 -0
- package/dist/op-result.js +13 -1
- package/dist/op-result.js.map +1 -1
- package/dist/read/filter-contract.d.ts +1 -1
- package/dist/read/filter-contract.js +4 -0
- package/dist/read/filter-contract.js.map +1 -1
- package/dist/read/shape.js +4 -0
- package/dist/read/shape.js.map +1 -1
- package/dist/read/views.d.ts +29 -0
- package/dist/read/views.js +60 -0
- package/dist/read/views.js.map +1 -1
- package/dist/session-grant.d.ts +48 -0
- package/dist/session-grant.js +185 -0
- package/dist/session-grant.js.map +1 -0
- package/dist/surface-copy.d.ts +19 -3
- package/dist/surface-copy.js +19 -3
- package/dist/surface-copy.js.map +1 -1
- package/dist/wizard.d.ts +58 -0
- package/dist/wizard.js +174 -0
- package/dist/wizard.js.map +1 -0
- package/dist/write/availability.d.ts +1 -16
- package/dist/write/availability.js +10 -65
- package/dist/write/availability.js.map +1 -1
- package/dist/write/batch.d.ts +20 -0
- package/dist/write/batch.js +111 -21
- package/dist/write/batch.js.map +1 -1
- package/dist/write/capabilities.d.ts +9 -0
- package/dist/write/capabilities.js +2 -0
- package/dist/write/capabilities.js.map +1 -1
- package/dist/write/clear-reminder.js +1 -1
- package/dist/write/clear-reminder.js.map +1 -1
- package/dist/write/commands.d.ts +22 -7
- package/dist/write/commands.js +231 -254
- package/dist/write/commands.js.map +1 -1
- package/dist/write/failure-hints.d.ts +32 -7
- package/dist/write/failure-hints.js +9 -14
- package/dist/write/failure-hints.js.map +1 -1
- package/dist/write/guards.js +22 -4
- package/dist/write/guards.js.map +1 -1
- package/dist/write/lock.d.ts +16 -13
- package/dist/write/lock.js +55 -0
- package/dist/write/lock.js.map +1 -1
- package/dist/write/make-repeating-project.js +9 -13
- package/dist/write/make-repeating-project.js.map +1 -1
- package/dist/write/move.js +9 -12
- package/dist/write/move.js.map +1 -1
- package/dist/write/operations.d.ts +51 -29
- package/dist/write/operations.js +16 -0
- package/dist/write/operations.js.map +1 -1
- package/dist/write/opid.d.ts +72 -14
- package/dist/write/opid.js +152 -8
- package/dist/write/opid.js.map +1 -1
- package/dist/write/param-schema.d.ts +80 -0
- package/dist/write/param-schema.js +754 -0
- package/dist/write/param-schema.js.map +1 -0
- package/dist/write/pipeline.d.ts +110 -5
- package/dist/write/pipeline.js +288 -14
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +38 -16
- package/dist/write/pre-state.js +117 -22
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/promote-clone.js +658 -444
- package/dist/write/promote-clone.js.map +1 -1
- package/dist/write/reorder.js +25 -8
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/repeat-collateral.d.ts +31 -0
- package/dist/write/repeat-collateral.js +153 -0
- package/dist/write/repeat-collateral.js.map +1 -0
- package/dist/write/repeat-rule.d.ts +28 -1
- package/dist/write/repeat-rule.js +54 -4
- package/dist/write/repeat-rule.js.map +1 -1
- package/dist/write/resolution-timestamps.d.ts +11 -0
- package/dist/write/resolution-timestamps.js +108 -40
- package/dist/write/resolution-timestamps.js.map +1 -1
- package/dist/write/reversibility.js +4 -0
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/template-mutation.d.ts +137 -0
- package/dist/write/template-mutation.js +598 -0
- package/dist/write/template-mutation.js.map +1 -0
- package/dist/write/undo.js +10 -20
- package/dist/write/undo.js.map +1 -1
- package/dist/write/update-fields.d.ts +151 -0
- package/dist/write/update-fields.js +427 -0
- package/dist/write/update-fields.js.map +1 -0
- package/dist/write/vectors/applescript.js +6 -10
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/registry.d.ts +1 -1
- package/dist/write/vectors/registry.js +10 -2
- package/dist/write/vectors/registry.js.map +1 -1
- package/dist/write/vectors/shortcuts.js +2 -2
- package/dist/write/vectors/shortcuts.js.map +1 -1
- package/dist/write/vectors/simulator.js +15 -4
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/types.d.ts +268 -1
- package/dist/write/vectors/ui-certification.js +63 -13
- package/dist/write/vectors/ui-certification.js.map +1 -1
- package/dist/write/vectors/ui-chord.d.ts +144 -0
- package/dist/write/vectors/ui-chord.js +428 -0
- package/dist/write/vectors/ui-chord.js.map +1 -0
- package/dist/write/vectors/ui-drag.d.ts +8 -4
- package/dist/write/vectors/ui-drag.js +2 -0
- package/dist/write/vectors/ui-drag.js.map +1 -1
- package/dist/write/vectors/ui-recipes.d.ts +70 -3
- package/dist/write/vectors/ui-recipes.js +482 -44
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui.d.ts +280 -10
- package/dist/write/vectors/ui.js +1068 -77
- package/dist/write/vectors/ui.js.map +1 -1
- package/dist/write/vectors/url-scheme.js +3 -0
- package/dist/write/vectors/url-scheme.js.map +1 -1
- package/dist/write/verify/delta.d.ts +62 -0
- package/dist/write/verify/delta.js +36 -2
- package/dist/write/verify/delta.js.map +1 -1
- package/dist/write/verify/poller.d.ts +4 -1
- package/dist/write/verify/poller.js +14 -0
- package/dist/write/verify/poller.js.map +1 -1
- package/package.json +2 -1
- package/schema/envelope.schema.json +6 -4
- package/scripts/build-helpers.sh +5 -0
- package/skills/things-cli/SKILL.md +5 -3
- package/skills/things-cli/references/contracts.md +1 -0
- package/skills/things-cli/references/data-model.md +3 -0
- package/skills/things-cli/references/errors.md +1 -1
- package/skills/things-cli/references/gui.md +1 -0
- package/skills/things-cli/references/repeating.md +6 -0
- package/skills/things-cli/references/tag-cleanup.md +64 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Within-project HEADING ORDER on the arrow-chord vector (CHORDMH1, built on
|
|
3
|
+
* the HEADORD1 law — docs/lab/headord1-heading-order.md, docs/lab/chordmh1-move-heading-build.md).
|
|
4
|
+
*
|
|
5
|
+
* THE LAW THIS RIDES (measured, Things 3.23 / golden-v4, HEADORD1 cells 1e /
|
|
6
|
+
* 1g1–1g4 / 1h / 1i3):
|
|
7
|
+
*
|
|
8
|
+
* With a heading row selected in its project view, ⌘↑ / ⌘↓ move that heading
|
|
9
|
+
* one slot up / down and ⌘⌥↑ / ⌘⌥↓ move it to the top / bottom of the
|
|
10
|
+
* project's heading list. The move rewrites the MOVED heading's `index` and
|
|
11
|
+
* nothing else: no sibling heading, loose to-do or child row is renumbered,
|
|
12
|
+
* and the heading's children follow through their intact heading FK. A chord
|
|
13
|
+
* with nowhere to go is DECLINED — zero delta, one macOS alert beep. The
|
|
14
|
+
* chords carry no menu item and no AX action; they are bare keybindings.
|
|
15
|
+
*
|
|
16
|
+
* WHY THIS FILE IS A CLOSED LOOP AND NOT A KEYSTROKE MACRO. Two facts force it.
|
|
17
|
+
* A bare keybinding on an undocumented private surface has no contract at all
|
|
18
|
+
* (harness.md §AX-drive scrutiny), so nothing may be assumed to have happened
|
|
19
|
+
* because a chord was posted — the DATABASE is the only oracle. And a chord the
|
|
20
|
+
* app declines costs the user an audible error tone, so the move count must be
|
|
21
|
+
* COMPUTED from the pre-state rather than discovered by firing until nothing
|
|
22
|
+
* changes (HEADORD1 cell 1h5: six wasted chords, six beeps, all writes green).
|
|
23
|
+
*
|
|
24
|
+
* So each step is: read the order from the database → compute the ONE chord that
|
|
25
|
+
* advances it → post exactly that chord → read the order back and prove the
|
|
26
|
+
* intended heading moved exactly one slot (or reached the endpoint) with every
|
|
27
|
+
* other row's `index` byte-identical and every child still behind its heading FK.
|
|
28
|
+
* A step that produces NO delta stops the drive and names the boundary; it is
|
|
29
|
+
* never re-fired blind. A step that produces the WRONG delta stops the drive and
|
|
30
|
+
* reports what actually moved. Both are the over-caution direction the doctrine
|
|
31
|
+
* requires: a refused drive is a bug report, a mis-landed write is corruption.
|
|
32
|
+
*/
|
|
33
|
+
import { createHash } from "node:crypto";
|
|
34
|
+
/**
|
|
35
|
+
* The client-side default: heading order + child containment, from the open DB.
|
|
36
|
+
*
|
|
37
|
+
* The heading read is filtered to `status = 0` so it returns the RENDERED order
|
|
38
|
+
* (CHORD2 cell 7a′). The child digest is deliberately NOT filtered — it covers
|
|
39
|
+
* every non-trashed child of every heading of the project, archived headings
|
|
40
|
+
* included, because no heading chord may disturb any of them.
|
|
41
|
+
*/
|
|
42
|
+
export function createHeadingOrderReader(db) {
|
|
43
|
+
return (projectUuid) => {
|
|
44
|
+
const headings = db
|
|
45
|
+
.prepare(`SELECT uuid, "index" AS idx FROM TMTask
|
|
46
|
+
WHERE type = 2 AND trashed = 0 AND status = 0 AND project = ? ORDER BY "index", uuid`)
|
|
47
|
+
.all(projectUuid);
|
|
48
|
+
const children = db
|
|
49
|
+
.prepare(`SELECT c.uuid AS uuid, c.heading AS h, c."index" AS idx FROM TMTask c
|
|
50
|
+
JOIN TMTask hh ON c.heading = hh.uuid
|
|
51
|
+
WHERE hh.project = ? AND hh.type = 2 AND c.trashed = 0
|
|
52
|
+
ORDER BY c.uuid`)
|
|
53
|
+
.all(projectUuid);
|
|
54
|
+
const hash = createHash("sha256");
|
|
55
|
+
for (const row of children)
|
|
56
|
+
hash.update(`${row.uuid}:${row.h}:${row.idx}\n`);
|
|
57
|
+
return {
|
|
58
|
+
headings: headings.map((h) => ({ uuid: h.uuid, index: h.idx })),
|
|
59
|
+
childDigest: hash.digest("hex"),
|
|
60
|
+
childCount: children.length,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const CHORDS = {
|
|
65
|
+
"up-one": { code: 126, flags: 0x100000, glyph: "⌘↑" },
|
|
66
|
+
"down-one": { code: 125, flags: 0x100000, glyph: "⌘↓" },
|
|
67
|
+
"to-top": { code: 126, flags: 0x180000, glyph: "⌘⌥↑" },
|
|
68
|
+
"to-bottom": { code: 125, flags: 0x180000, glyph: "⌘⌥↓" },
|
|
69
|
+
};
|
|
70
|
+
export function chordGlyph(id) {
|
|
71
|
+
return CHORDS[id].glyph;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Post ONE modifier-bearing key event pair straight at the Things process
|
|
75
|
+
* (`CGEventPostToPid`), through the same JXA ObjC bridge every other synthetic
|
|
76
|
+
* input in this codebase rides.
|
|
77
|
+
*
|
|
78
|
+
* Why not System Events. The shipped `key` primitive emits `key code N` with no
|
|
79
|
+
* modifier support at all, and the System-Events spelling that DOES carry
|
|
80
|
+
* modifiers only lands while Things is FRONTMOST (HEADORD1 cell 1h2b: with
|
|
81
|
+
* Finder frontmost it produced no delta). `CGEventPostToPid` addresses the
|
|
82
|
+
* process rather than the focused surface, so the whole gesture — reveal,
|
|
83
|
+
* select, chord — runs with Things in the background and the user's focus
|
|
84
|
+
* untouched. Measured on Things 3.23 / golden-v4 (HEADORD1 1h2a; re-measured end
|
|
85
|
+
* to end, with Things never activated at all, by CHORDMH1's delivery gate).
|
|
86
|
+
*
|
|
87
|
+
* Note this is the KEYBOARD tap only. NATIVE1-e measured `CGEventPostToPid` as
|
|
88
|
+
* inert for Things' MOUSE hit-testing, which is why click synthesis still uses
|
|
89
|
+
* the HID tap and stays foreground-bound; keyboard events to the pid are a
|
|
90
|
+
* different path and do land.
|
|
91
|
+
*/
|
|
92
|
+
export function jxaChordScript(id) {
|
|
93
|
+
const { code, flags } = CHORDS[id];
|
|
94
|
+
return `ObjC.import('AppKit'); ObjC.import('ApplicationServices'); ObjC.import('CoreGraphics');
|
|
95
|
+
function pidOf(n){ return Application('System Events').processes.byName(n).unixId() }
|
|
96
|
+
function sleepMs(ms){ $.NSThread.sleepForTimeInterval(ms/1000) }
|
|
97
|
+
function run(){
|
|
98
|
+
var pid = pidOf('Things3');
|
|
99
|
+
if (!pid) return 'NOPID';
|
|
100
|
+
var d = $.CGEventCreateKeyboardEvent($(), ${code}, true);
|
|
101
|
+
var u = $.CGEventCreateKeyboardEvent($(), ${code}, false);
|
|
102
|
+
$.CGEventSetFlags(d, ${flags}); $.CGEventSetFlags(u, ${flags});
|
|
103
|
+
$.CGEventPostToPid(pid, d); sleepMs(70);
|
|
104
|
+
$.CGEventPostToPid(pid, u); sleepMs(70);
|
|
105
|
+
return 'POSTED';
|
|
106
|
+
}`;
|
|
107
|
+
}
|
|
108
|
+
export function chordCommand(id) {
|
|
109
|
+
return {
|
|
110
|
+
primitive: "chord-post",
|
|
111
|
+
label: `post ${CHORDS[id].glyph} to the Things process`,
|
|
112
|
+
lang: "javascript",
|
|
113
|
+
script: jxaChordScript(id),
|
|
114
|
+
meta: { chord: id, code: CHORDS[id].code, flags: CHORDS[id].flags },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Select the Nth heading row of the revealed project view (the shipped primitive). */
|
|
118
|
+
export function chordSelectCommand(tablePath, ordinal, script) {
|
|
119
|
+
return {
|
|
120
|
+
primitive: "select-heading-row",
|
|
121
|
+
label: `select the heading row at position ${ordinal + 1}`,
|
|
122
|
+
script,
|
|
123
|
+
meta: { ordinal },
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// -------------------------------------------------------------- the ladder
|
|
127
|
+
/**
|
|
128
|
+
* Absolute backstop on posted chords. The placement walk below is O(n²) hops in
|
|
129
|
+
* the worst case (every heading reversed), and the per-step progress guard
|
|
130
|
+
* already stops a drive that is not converging — this only bounds a pathological
|
|
131
|
+
* request. 200 chords at ~100ms is well inside the drive budget.
|
|
132
|
+
*/
|
|
133
|
+
const MAX_CHORDS_CEILING = 200;
|
|
134
|
+
/** DB assert poll after a chord: attempts × delay (Things writes `index` on the move). */
|
|
135
|
+
const ASSERT_ATTEMPTS = 12;
|
|
136
|
+
const ASSERT_DELAY_MS = 250;
|
|
137
|
+
const STEP_TIMEOUT_MS = 30_000;
|
|
138
|
+
async function runCmd(ctx, cmd) {
|
|
139
|
+
return ctx.run(cmd, STEP_TIMEOUT_MS);
|
|
140
|
+
}
|
|
141
|
+
function orderOf(state) {
|
|
142
|
+
return state.headings.map((h) => h.uuid);
|
|
143
|
+
}
|
|
144
|
+
function positionOf(state, uuid) {
|
|
145
|
+
return state.headings.findIndex((h) => h.uuid === uuid);
|
|
146
|
+
}
|
|
147
|
+
function indexOf(state, uuid) {
|
|
148
|
+
return state.headings.find((h) => h.uuid === uuid)?.index ?? null;
|
|
149
|
+
}
|
|
150
|
+
function sameOrder(a, b) {
|
|
151
|
+
return a.length === b.length && a.every((u, i) => u === b[i]);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The SINGLE-ROW-WRITE law, asserted per chord (RRF1's untouched-siblings law in
|
|
155
|
+
* the shape the app actually implements it). Returns a complaint, or null.
|
|
156
|
+
*
|
|
157
|
+
* HEADORD1 recorded the law as "the moved heading's `index` is rewritten and no
|
|
158
|
+
* sibling is renumbered", measured on a ⌘↑. CHORDMH1's certification found that
|
|
159
|
+
* is half of it: which of the two swapped rows gets the new number depends on
|
|
160
|
+
* the direction. Moving a heading UP rewrites the MOVER (it takes a value
|
|
161
|
+
* between its new neighbours: `C 0 → -343`, between `A -532` and `B -246`);
|
|
162
|
+
* moving one DOWN rewrites the SIBLING it passes (that sibling takes a value
|
|
163
|
+
* below the mover: `B -201 → -1152`, below `A -543`, which itself did not move).
|
|
164
|
+
* The ⌘⌥ endpoint chords rewrite the mover. In every measured case exactly ONE
|
|
165
|
+
* row is renumbered — the app picks whichever single write is cheapest — and it
|
|
166
|
+
* is always one of the rows the gesture actually passed over.
|
|
167
|
+
*
|
|
168
|
+
* So the assertion is: the order must be EXACTLY the order the chord was
|
|
169
|
+
* supposed to produce, exactly ONE heading's `index` may differ, and that
|
|
170
|
+
* heading must lie inside the span the move crossed. Anything else — two rows
|
|
171
|
+
* renumbered, a row outside the span renumbered, an unexpected order — is the
|
|
172
|
+
* signature of a chord that landed on a row the plan did not address, which is
|
|
173
|
+
* the one failure a positional heading selection cannot rule out by readback (a
|
|
174
|
+
* heading exposes no title to the Accessibility tree, so the readback proves
|
|
175
|
+
* only that A heading is selected, never WHICH — HEADCERT1).
|
|
176
|
+
*/
|
|
177
|
+
function singleRowWriteViolation(before, after, moved, landsAt) {
|
|
178
|
+
const beforeOrder = orderOf(before);
|
|
179
|
+
const from = beforeOrder.indexOf(moved);
|
|
180
|
+
const expected = beforeOrder.filter((u) => u !== moved);
|
|
181
|
+
expected.splice(landsAt, 0, moved);
|
|
182
|
+
const observed = orderOf(after);
|
|
183
|
+
if (!sameOrder(observed, expected)) {
|
|
184
|
+
return `the heading order after the chord is not the order the step aimed for (${observed.join(" < ")})`;
|
|
185
|
+
}
|
|
186
|
+
const lo = Math.min(from, landsAt);
|
|
187
|
+
const hi = Math.max(from, landsAt);
|
|
188
|
+
const spanned = new Set(beforeOrder.slice(lo, hi + 1));
|
|
189
|
+
const rewritten = [];
|
|
190
|
+
for (const row of before.headings) {
|
|
191
|
+
const now = after.headings.find((h) => h.uuid === row.uuid);
|
|
192
|
+
if (now === undefined)
|
|
193
|
+
return `heading ${row.uuid} disappeared from the project`;
|
|
194
|
+
if (now.index !== row.index)
|
|
195
|
+
rewritten.push(row.uuid);
|
|
196
|
+
}
|
|
197
|
+
const outside = rewritten.filter((u) => !spanned.has(u));
|
|
198
|
+
if (outside.length > 0) {
|
|
199
|
+
return `it also renumbered heading ${outside[0]}, which the move never passed over`;
|
|
200
|
+
}
|
|
201
|
+
if (rewritten.length > 1) {
|
|
202
|
+
return `it renumbered ${rewritten.length} headings at once (${rewritten.join(", ")}) — a chord rewrites exactly one row`;
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
/** Poll the database until `check` passes, or the attempts run out. */
|
|
207
|
+
async function pollState(ctx, check) {
|
|
208
|
+
for (let i = 0; i < ASSERT_ATTEMPTS; i++) {
|
|
209
|
+
const state = ctx.state();
|
|
210
|
+
if (check(state))
|
|
211
|
+
return state;
|
|
212
|
+
// polling the same database condition is inherently sequential
|
|
213
|
+
await ctx.sleep(ASSERT_DELAY_MS);
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The next chord that advances `order` towards `target`, or null when they
|
|
219
|
+
* already agree. `movees` is the only set of rows a chord may ever move.
|
|
220
|
+
*
|
|
221
|
+
* THE SCHEDULE, and why it is this one. The obvious walk — "put target[i] into
|
|
222
|
+
* slot i, left to right" — is WRONG here, because target[i] is very often a row
|
|
223
|
+
* the caller never named: asking to move heading A down one slot produces a
|
|
224
|
+
* target whose first element is the BYSTANDER B. Moving B is not an option (it
|
|
225
|
+
* would break the untouched-siblings law the whole vector rests on), and the
|
|
226
|
+
* equally obvious repair — "push whichever movee sits at slot i down" — thrashes
|
|
227
|
+
* forever when two movees sit side by side, each pushing the other back.
|
|
228
|
+
*
|
|
229
|
+
* So the rule fixes the first mismatched slot `i` from the row's own side:
|
|
230
|
+
*
|
|
231
|
+
* - target[i] IS a movee → it is necessarily BELOW slot i (everything above
|
|
232
|
+
* already matches), so step it UP; from slot 0 the one-dispatch ⌘⌥↑ takes it
|
|
233
|
+
* there directly.
|
|
234
|
+
* - target[i] is a bystander `R` → every row between slot i and R's current
|
|
235
|
+
* position is provably a movee (the non-movees keep their relative order, so
|
|
236
|
+
* R is the first non-movee at or below i), and pushing the ONE movee directly
|
|
237
|
+
* above R down past it moves R up by exactly one slot.
|
|
238
|
+
*
|
|
239
|
+
* Both branches strictly reduce a distance that cannot go below zero, so the
|
|
240
|
+
* schedule terminates, and every chord it emits moves a named heading by one
|
|
241
|
+
* slot. Two endpoint shortcuts run first, because they are the common single
|
|
242
|
+
* heading requests and each costs one dispatch instead of a walk: a movee whose
|
|
243
|
+
* target is the very top or the very bottom is sent there with ⌘⌥↑ / ⌘⌥↓.
|
|
244
|
+
*/
|
|
245
|
+
export function planChordStep(order, target, movees) {
|
|
246
|
+
const last = target.length - 1;
|
|
247
|
+
// Endpoint shortcuts, taken before the walk.
|
|
248
|
+
const top = target[0];
|
|
249
|
+
if (top !== undefined && movees.has(top) && order[0] !== top) {
|
|
250
|
+
return { uuid: top, chord: "to-top", landsAt: 0 };
|
|
251
|
+
}
|
|
252
|
+
const bottom = target[last];
|
|
253
|
+
if (bottom !== undefined && movees.has(bottom) && order[last] !== bottom) {
|
|
254
|
+
return { uuid: bottom, chord: "to-bottom", landsAt: last };
|
|
255
|
+
}
|
|
256
|
+
for (let i = 0; i <= last; i++) {
|
|
257
|
+
const want = target[i];
|
|
258
|
+
if (order[i] === want)
|
|
259
|
+
continue;
|
|
260
|
+
const at = order.indexOf(want);
|
|
261
|
+
if (at < 0)
|
|
262
|
+
return { error: `heading ${want} is no longer in the project` };
|
|
263
|
+
if (movees.has(want)) {
|
|
264
|
+
// Below slot i by construction — step it up one.
|
|
265
|
+
return { uuid: want, chord: "up-one", landsAt: at - 1 };
|
|
266
|
+
}
|
|
267
|
+
// A bystander has to rise: push the movee immediately above it down past it.
|
|
268
|
+
const pusher = order[at - 1];
|
|
269
|
+
if (pusher === undefined || !movees.has(pusher)) {
|
|
270
|
+
return {
|
|
271
|
+
error: `reaching the requested order would mean moving heading ${pusher ?? want}, which was ` +
|
|
272
|
+
"not one of the headings named in the move",
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return { uuid: pusher, chord: "down-one", landsAt: at };
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Drive a project's headings into `spec.targetOrder`, one verified chord at a
|
|
281
|
+
* time (see {@link planChordStep} for the schedule). Nothing here trusts the
|
|
282
|
+
* plan it made a moment ago: every step is re-derived from a fresh database
|
|
283
|
+
* read, and the step is only counted once the database says it landed where it
|
|
284
|
+
* was aimed with nothing else touched.
|
|
285
|
+
*/
|
|
286
|
+
export async function driveHeadingChordReorder(spec, run, reader, selectScript, sleep = (ms) => new Promise((r) => setTimeout(r, ms))) {
|
|
287
|
+
if (reader === undefined) {
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
chords: 0,
|
|
291
|
+
detail: "the heading-order driver has no database seam on this surface — this operation can " +
|
|
292
|
+
"only run through the full client",
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const ctx = {
|
|
296
|
+
run,
|
|
297
|
+
state: () => reader(spec.projectUuid),
|
|
298
|
+
sleep,
|
|
299
|
+
selectScript,
|
|
300
|
+
};
|
|
301
|
+
const pre = ctx.state();
|
|
302
|
+
const target = spec.targetOrder;
|
|
303
|
+
// The order was computed from a read taken before the GUI was touched; if the
|
|
304
|
+
// project's heading SET has changed since (another client, or the app), the
|
|
305
|
+
// whole plan — including every positional row address in it — is stale. Refuse.
|
|
306
|
+
const preOrder = orderOf(pre);
|
|
307
|
+
if (preOrder.length !== target.length ||
|
|
308
|
+
!preOrder.every((u) => target.includes(u)) ||
|
|
309
|
+
!target.every((u) => preOrder.includes(u))) {
|
|
310
|
+
return {
|
|
311
|
+
ok: false,
|
|
312
|
+
chords: 0,
|
|
313
|
+
detail: `the project's headings changed between planning and driving (planned for ` +
|
|
314
|
+
`${target.length}, found ${preOrder.length}) — nothing was moved; re-read the project ` +
|
|
315
|
+
"and reissue the move",
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (sameOrder(preOrder, target)) {
|
|
319
|
+
return { ok: true, chords: 0, detail: "already in the requested order — no chord was sent" };
|
|
320
|
+
}
|
|
321
|
+
const moveeSet = new Set(spec.movees);
|
|
322
|
+
const cap = Math.min(MAX_CHORDS_CEILING, target.length * target.length + target.length);
|
|
323
|
+
let chords = 0;
|
|
324
|
+
/** Which heading the last select landed on — the selection FOLLOWS the row it moves. */
|
|
325
|
+
let selected = null;
|
|
326
|
+
for (;;) {
|
|
327
|
+
const before = ctx.state();
|
|
328
|
+
const plan = planChordStep(orderOf(before), target, moveeSet);
|
|
329
|
+
if (plan === null)
|
|
330
|
+
break;
|
|
331
|
+
if ("error" in plan) {
|
|
332
|
+
return {
|
|
333
|
+
ok: false,
|
|
334
|
+
chords,
|
|
335
|
+
detail: `${plan.error} — refusing to reorder a bystander`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (chords >= cap) {
|
|
339
|
+
return {
|
|
340
|
+
ok: false,
|
|
341
|
+
chords,
|
|
342
|
+
detail: `the move exceeded its chord budget (${cap}) without reaching the requested order`,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
const want = plan.uuid;
|
|
346
|
+
const cur = positionOf(before, want);
|
|
347
|
+
// (1) Selection. The row a chord moves stays selected as it moves (HEADORD1
|
|
348
|
+
// cell 1h5 fired ten chords on one selection), so a run of hops on the
|
|
349
|
+
// same heading costs ONE positional walk — which is where essentially
|
|
350
|
+
// all the wall time goes (~0.25s per row the walk probes).
|
|
351
|
+
if (selected !== want) {
|
|
352
|
+
const sel = await runCmd(ctx, chordSelectCommand(spec.tablePath, cur, ctx.selectScript(cur)));
|
|
353
|
+
if (!sel.ok || sel.stdout.trim() !== "OK") {
|
|
354
|
+
return {
|
|
355
|
+
ok: false,
|
|
356
|
+
chords,
|
|
357
|
+
detail: `the project view exposed no selectable heading row at position ${cur + 1} ` +
|
|
358
|
+
`(${sel.ok ? sel.stdout.trim() || "no match" : sel.stderr.trim() || "the selection step failed"})` +
|
|
359
|
+
(chords > 0 ? ` — ${chords} chord(s) had already landed` : " — nothing was moved"),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
selected = want;
|
|
363
|
+
}
|
|
364
|
+
// (2) Exactly one chord, chosen from the database, never fired blind.
|
|
365
|
+
const post = await runCmd(ctx, chordCommand(plan.chord));
|
|
366
|
+
chords += 1;
|
|
367
|
+
if (!post.ok) {
|
|
368
|
+
return {
|
|
369
|
+
ok: false,
|
|
370
|
+
chords,
|
|
371
|
+
detail: `posting ${chordGlyph(plan.chord)} to Things failed (${post.stderr.trim() || "no detail"})`,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
// (3) The database is the oracle. STRICT progress: the heading must be at
|
|
375
|
+
// the slot the chord was supposed to put it in.
|
|
376
|
+
const after = await pollState(ctx, (s) => positionOf(s, want) === plan.landsAt);
|
|
377
|
+
if (after === null) {
|
|
378
|
+
const now = ctx.state();
|
|
379
|
+
const stalled = indexOf(now, want) === indexOf(before, want);
|
|
380
|
+
return {
|
|
381
|
+
ok: false,
|
|
382
|
+
chords,
|
|
383
|
+
detail: stalled
|
|
384
|
+
? `${chordGlyph(plan.chord)} did not move the heading — it is at position ` +
|
|
385
|
+
`${positionOf(now, want) + 1} of ${now.headings.length} and the app declined the ` +
|
|
386
|
+
"chord (a heading at the top declines ⌘↑ and one at the bottom declines ⌘↓), or the " +
|
|
387
|
+
"row selection was lost. The drive stopped rather than re-sending it" +
|
|
388
|
+
(chords > 1 ? `; ${chords - 1} earlier chord(s) did land` : "")
|
|
389
|
+
: `${chordGlyph(plan.chord)} moved the heading to position ` +
|
|
390
|
+
`${positionOf(now, want) + 1}, not the expected ${plan.landsAt + 1} — the drive ` +
|
|
391
|
+
"stopped rather than continue from a position it cannot vouch for",
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
// (4) The single-row-write law and the children law, per chord.
|
|
395
|
+
const violation = singleRowWriteViolation(before, after, want, plan.landsAt);
|
|
396
|
+
if (violation !== null) {
|
|
397
|
+
return {
|
|
398
|
+
ok: false,
|
|
399
|
+
chords,
|
|
400
|
+
detail: `${chordGlyph(plan.chord)} did not behave as a single-row move — ${violation}. The row ` +
|
|
401
|
+
"that was selected may not be the row the plan addressed; the drive stopped",
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if (after.childDigest !== before.childDigest) {
|
|
405
|
+
return {
|
|
406
|
+
ok: false,
|
|
407
|
+
chords,
|
|
408
|
+
detail: `${chordGlyph(plan.chord)} changed a to-do's heading or position (${before.childCount} ` +
|
|
409
|
+
`child row(s) before, ${after.childCount} after) — a heading move must leave every ` +
|
|
410
|
+
"child untouched behind its heading link. The drive stopped",
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const finalOrder = orderOf(ctx.state());
|
|
415
|
+
if (!sameOrder(finalOrder, target)) {
|
|
416
|
+
return {
|
|
417
|
+
ok: false,
|
|
418
|
+
chords,
|
|
419
|
+
detail: `the drive ended in an order that is not the requested one (after ${chords} chord(s))`,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
ok: true,
|
|
424
|
+
chords,
|
|
425
|
+
detail: `${chords} chord(s) posted`,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
//# sourceMappingURL=ui-chord.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-chord.js","sourceRoot":"","sources":["../../../src/write/vectors/ui-chord.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import { type HeadingOrderReader } from "./ui-chord.ts";
|
|
2
3
|
import type { UiRunner } from "./ui.ts";
|
|
3
4
|
export type SidebarPlacement = {
|
|
4
5
|
kind: "before";
|
|
@@ -30,13 +31,16 @@ export interface AreaSidebarState {
|
|
|
30
31
|
assignmentsDigest: string;
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
|
-
* Auxiliary seams the ui vector needs beyond osascript dispatch.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
* Auxiliary seams the ui vector needs beyond osascript dispatch. Both ordering
|
|
35
|
+
* drivers assert the database between gestures — the sidebar drag driver
|
|
36
|
+
* through `areaState`, the heading-chord driver through `headingOrder` — and
|
|
37
|
+
* the client wires each to the open connection. Absent (e.g. the capabilities
|
|
38
|
+
* surface, which never executes), those ops refuse cleanly.
|
|
37
39
|
*/
|
|
38
40
|
export interface UiDriveAux {
|
|
39
41
|
areaState?: () => AreaSidebarState;
|
|
42
|
+
/** Heading order + child containment for one project (the chord driver's oracle). */
|
|
43
|
+
headingOrder?: HeadingOrderReader;
|
|
40
44
|
}
|
|
41
45
|
/** The client-side default aux: reads area order + assignments from the DB. */
|
|
42
46
|
export declare function createUiDriveAux(db: DatabaseSync): UiDriveAux;
|
|
@@ -55,9 +55,11 @@
|
|
|
55
55
|
* RELATIVE positions, never index values.
|
|
56
56
|
*/
|
|
57
57
|
import { createHash } from "node:crypto";
|
|
58
|
+
import { createHeadingOrderReader } from "./ui-chord.js";
|
|
58
59
|
/** The client-side default aux: reads area order + assignments from the DB. */
|
|
59
60
|
export function createUiDriveAux(db) {
|
|
60
61
|
return {
|
|
62
|
+
headingOrder: createHeadingOrderReader(db),
|
|
61
63
|
areaState() {
|
|
62
64
|
const areas = db
|
|
63
65
|
.prepare(`SELECT uuid, title, "index" AS idx FROM TMArea ORDER BY "index", uuid`)
|