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.
Files changed (224) hide show
  1. package/README.md +18 -9
  2. package/deputy/AppIcon.icns +0 -0
  3. package/deputy/VERSION +1 -1
  4. package/deputy/helpers-Info.plist +4 -0
  5. package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
  6. package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/Info.plist +3 -1
  7. package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/MacOS/things-reader +0 -0
  8. package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/Resources/AppIcon.icns +0 -0
  9. package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/_CodeSignature/CodeResources +15 -2
  10. package/deputy/prebuilt/Things API Helper.app/Contents/Info.plist +5 -1
  11. package/deputy/prebuilt/Things API Helper.app/Contents/MacOS/things-deputy +0 -0
  12. package/deputy/prebuilt/Things API Helper.app/Contents/Resources/AppIcon.icns +0 -0
  13. package/deputy/prebuilt/Things API Helper.app/Contents/_CodeSignature/CodeResources +14 -2
  14. package/deputy/reader/Info.plist +2 -0
  15. package/deputy/reader/entitlements.plist +30 -0
  16. package/deputy/reader/main.swift +129 -40
  17. package/deputy/src/server.swift +25 -2
  18. package/deputy/src/tcc.swift +58 -0
  19. package/dist/audit/schema.d.ts +30 -1
  20. package/dist/audit/schema.js.map +1 -1
  21. package/dist/capability.d.ts +322 -0
  22. package/dist/capability.js +706 -0
  23. package/dist/capability.js.map +1 -0
  24. package/dist/cli/commands/doctor.d.ts +13 -1
  25. package/dist/cli/commands/doctor.js +141 -5
  26. package/dist/cli/commands/doctor.js.map +1 -1
  27. package/dist/cli/commands/helpers.js +189 -51
  28. package/dist/cli/commands/helpers.js.map +1 -1
  29. package/dist/cli/commands/mcp.js +8 -0
  30. package/dist/cli/commands/mcp.js.map +1 -1
  31. package/dist/cli/commands/op-result.js +6 -0
  32. package/dist/cli/commands/op-result.js.map +1 -1
  33. package/dist/cli/commands/reads.js +47 -1
  34. package/dist/cli/commands/reads.js.map +1 -1
  35. package/dist/cli/commands/repeat-flags.js +34 -2
  36. package/dist/cli/commands/repeat-flags.js.map +1 -1
  37. package/dist/cli/commands/setup.d.ts +9 -0
  38. package/dist/cli/commands/setup.js +76 -83
  39. package/dist/cli/commands/setup.js.map +1 -1
  40. package/dist/cli/commands/writes.d.ts +1 -1
  41. package/dist/cli/commands/writes.js +311 -142
  42. package/dist/cli/commands/writes.js.map +1 -1
  43. package/dist/cli/dry-run.d.ts +1 -1
  44. package/dist/cli/dry-run.js +1 -1
  45. package/dist/cli/glyphs.d.ts +23 -4
  46. package/dist/cli/glyphs.js +46 -19
  47. package/dist/cli/glyphs.js.map +1 -1
  48. package/dist/cli/help.d.ts +1 -1
  49. package/dist/cli/help.js +49 -27
  50. package/dist/cli/help.js.map +1 -1
  51. package/dist/cli/interrupt.d.ts +54 -6
  52. package/dist/cli/interrupt.js +78 -12
  53. package/dist/cli/interrupt.js.map +1 -1
  54. package/dist/cli/main.js +8 -5
  55. package/dist/cli/main.js.map +1 -1
  56. package/dist/cli/period.d.ts +3 -1
  57. package/dist/cli/period.js +1 -1
  58. package/dist/cli/period.js.map +1 -1
  59. package/dist/cli/read-driver.js +26 -1
  60. package/dist/cli/read-driver.js.map +1 -1
  61. package/dist/cli/render.d.ts +31 -4
  62. package/dist/cli/render.js +127 -15
  63. package/dist/cli/render.js.map +1 -1
  64. package/dist/client.d.ts +23 -3
  65. package/dist/client.js +36 -13
  66. package/dist/client.js.map +1 -1
  67. package/dist/contracts.d.ts +8 -4
  68. package/dist/contracts.js +1 -1
  69. package/dist/contracts.js.map +1 -1
  70. package/dist/deputy/install.d.ts +234 -6
  71. package/dist/deputy/install.js +843 -31
  72. package/dist/deputy/install.js.map +1 -1
  73. package/dist/deputy/notices.js +2 -2
  74. package/dist/deputy/notices.js.map +1 -1
  75. package/dist/deputy/protocol.d.ts +59 -5
  76. package/dist/deputy/protocol.js +48 -11
  77. package/dist/deputy/protocol.js.map +1 -1
  78. package/dist/deputy/routing.d.ts +37 -0
  79. package/dist/deputy/routing.js +181 -27
  80. package/dist/deputy/routing.js.map +1 -1
  81. package/dist/deputy/wake.d.ts +61 -0
  82. package/dist/deputy/wake.js +149 -0
  83. package/dist/deputy/wake.js.map +1 -0
  84. package/dist/diagnose.d.ts +19 -4
  85. package/dist/diagnose.js +64 -8
  86. package/dist/diagnose.js.map +1 -1
  87. package/dist/direct-setup.d.ts +104 -0
  88. package/dist/direct-setup.js +658 -0
  89. package/dist/direct-setup.js.map +1 -0
  90. package/dist/host-access.d.ts +75 -0
  91. package/dist/host-access.js +177 -0
  92. package/dist/host-access.js.map +1 -0
  93. package/dist/index.d.ts +20 -5
  94. package/dist/index.js +14 -1
  95. package/dist/index.js.map +1 -1
  96. package/dist/mcp/server.d.ts +27 -1
  97. package/dist/mcp/server.js +308 -109
  98. package/dist/mcp/server.js.map +1 -1
  99. package/dist/op-result.d.ts +9 -0
  100. package/dist/op-result.js +13 -1
  101. package/dist/op-result.js.map +1 -1
  102. package/dist/read/filter-contract.d.ts +1 -1
  103. package/dist/read/filter-contract.js +4 -0
  104. package/dist/read/filter-contract.js.map +1 -1
  105. package/dist/read/shape.js +4 -0
  106. package/dist/read/shape.js.map +1 -1
  107. package/dist/read/views.d.ts +29 -0
  108. package/dist/read/views.js +60 -0
  109. package/dist/read/views.js.map +1 -1
  110. package/dist/session-grant.d.ts +48 -0
  111. package/dist/session-grant.js +185 -0
  112. package/dist/session-grant.js.map +1 -0
  113. package/dist/surface-copy.d.ts +19 -3
  114. package/dist/surface-copy.js +19 -3
  115. package/dist/surface-copy.js.map +1 -1
  116. package/dist/wizard.d.ts +58 -0
  117. package/dist/wizard.js +174 -0
  118. package/dist/wizard.js.map +1 -0
  119. package/dist/write/availability.d.ts +1 -16
  120. package/dist/write/availability.js +10 -65
  121. package/dist/write/availability.js.map +1 -1
  122. package/dist/write/batch.d.ts +20 -0
  123. package/dist/write/batch.js +111 -21
  124. package/dist/write/batch.js.map +1 -1
  125. package/dist/write/capabilities.d.ts +9 -0
  126. package/dist/write/capabilities.js +2 -0
  127. package/dist/write/capabilities.js.map +1 -1
  128. package/dist/write/clear-reminder.js +1 -1
  129. package/dist/write/clear-reminder.js.map +1 -1
  130. package/dist/write/commands.d.ts +22 -7
  131. package/dist/write/commands.js +231 -254
  132. package/dist/write/commands.js.map +1 -1
  133. package/dist/write/failure-hints.d.ts +32 -7
  134. package/dist/write/failure-hints.js +9 -14
  135. package/dist/write/failure-hints.js.map +1 -1
  136. package/dist/write/guards.js +22 -4
  137. package/dist/write/guards.js.map +1 -1
  138. package/dist/write/lock.d.ts +16 -13
  139. package/dist/write/lock.js +55 -0
  140. package/dist/write/lock.js.map +1 -1
  141. package/dist/write/make-repeating-project.js +9 -13
  142. package/dist/write/make-repeating-project.js.map +1 -1
  143. package/dist/write/move.js +9 -12
  144. package/dist/write/move.js.map +1 -1
  145. package/dist/write/operations.d.ts +51 -29
  146. package/dist/write/operations.js +16 -0
  147. package/dist/write/operations.js.map +1 -1
  148. package/dist/write/opid.d.ts +72 -14
  149. package/dist/write/opid.js +152 -8
  150. package/dist/write/opid.js.map +1 -1
  151. package/dist/write/param-schema.d.ts +80 -0
  152. package/dist/write/param-schema.js +754 -0
  153. package/dist/write/param-schema.js.map +1 -0
  154. package/dist/write/pipeline.d.ts +110 -5
  155. package/dist/write/pipeline.js +288 -14
  156. package/dist/write/pipeline.js.map +1 -1
  157. package/dist/write/pre-state.d.ts +38 -16
  158. package/dist/write/pre-state.js +117 -22
  159. package/dist/write/pre-state.js.map +1 -1
  160. package/dist/write/promote-clone.js +658 -444
  161. package/dist/write/promote-clone.js.map +1 -1
  162. package/dist/write/reorder.js +25 -8
  163. package/dist/write/reorder.js.map +1 -1
  164. package/dist/write/repeat-collateral.d.ts +31 -0
  165. package/dist/write/repeat-collateral.js +153 -0
  166. package/dist/write/repeat-collateral.js.map +1 -0
  167. package/dist/write/repeat-rule.d.ts +28 -1
  168. package/dist/write/repeat-rule.js +54 -4
  169. package/dist/write/repeat-rule.js.map +1 -1
  170. package/dist/write/resolution-timestamps.d.ts +11 -0
  171. package/dist/write/resolution-timestamps.js +108 -40
  172. package/dist/write/resolution-timestamps.js.map +1 -1
  173. package/dist/write/reversibility.js +4 -0
  174. package/dist/write/reversibility.js.map +1 -1
  175. package/dist/write/template-mutation.d.ts +137 -0
  176. package/dist/write/template-mutation.js +598 -0
  177. package/dist/write/template-mutation.js.map +1 -0
  178. package/dist/write/undo.js +10 -20
  179. package/dist/write/undo.js.map +1 -1
  180. package/dist/write/update-fields.d.ts +151 -0
  181. package/dist/write/update-fields.js +427 -0
  182. package/dist/write/update-fields.js.map +1 -0
  183. package/dist/write/vectors/applescript.js +6 -10
  184. package/dist/write/vectors/applescript.js.map +1 -1
  185. package/dist/write/vectors/registry.d.ts +1 -1
  186. package/dist/write/vectors/registry.js +10 -2
  187. package/dist/write/vectors/registry.js.map +1 -1
  188. package/dist/write/vectors/shortcuts.js +2 -2
  189. package/dist/write/vectors/shortcuts.js.map +1 -1
  190. package/dist/write/vectors/simulator.js +15 -4
  191. package/dist/write/vectors/simulator.js.map +1 -1
  192. package/dist/write/vectors/types.d.ts +268 -1
  193. package/dist/write/vectors/ui-certification.js +63 -13
  194. package/dist/write/vectors/ui-certification.js.map +1 -1
  195. package/dist/write/vectors/ui-chord.d.ts +144 -0
  196. package/dist/write/vectors/ui-chord.js +428 -0
  197. package/dist/write/vectors/ui-chord.js.map +1 -0
  198. package/dist/write/vectors/ui-drag.d.ts +8 -4
  199. package/dist/write/vectors/ui-drag.js +2 -0
  200. package/dist/write/vectors/ui-drag.js.map +1 -1
  201. package/dist/write/vectors/ui-recipes.d.ts +70 -3
  202. package/dist/write/vectors/ui-recipes.js +482 -44
  203. package/dist/write/vectors/ui-recipes.js.map +1 -1
  204. package/dist/write/vectors/ui.d.ts +280 -10
  205. package/dist/write/vectors/ui.js +1068 -77
  206. package/dist/write/vectors/ui.js.map +1 -1
  207. package/dist/write/vectors/url-scheme.js +3 -0
  208. package/dist/write/vectors/url-scheme.js.map +1 -1
  209. package/dist/write/verify/delta.d.ts +62 -0
  210. package/dist/write/verify/delta.js +36 -2
  211. package/dist/write/verify/delta.js.map +1 -1
  212. package/dist/write/verify/poller.d.ts +4 -1
  213. package/dist/write/verify/poller.js +14 -0
  214. package/dist/write/verify/poller.js.map +1 -1
  215. package/package.json +2 -1
  216. package/schema/envelope.schema.json +6 -4
  217. package/scripts/build-helpers.sh +5 -0
  218. package/skills/things-cli/SKILL.md +5 -3
  219. package/skills/things-cli/references/contracts.md +1 -0
  220. package/skills/things-cli/references/data-model.md +3 -0
  221. package/skills/things-cli/references/errors.md +1 -1
  222. package/skills/things-cli/references/gui.md +1 -0
  223. package/skills/things-cli/references/repeating.md +6 -0
  224. 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. The sidebar
34
- * drag driver asserts the database between hops; the client wires `areaState`
35
- * to the open connection. Absent (e.g. the capabilities surface, which never
36
- * executes), a drag op refuses cleanly.
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`)