things-api 0.19.0 → 0.19.2
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 -1
- package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
- 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/MacOS/things-deputy +0 -0
- package/dist/audit/schema.d.ts +19 -2
- package/dist/audit/schema.js.map +1 -1
- package/dist/capability.d.ts +166 -4
- package/dist/capability.js +303 -13
- package/dist/capability.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +13 -1
- package/dist/cli/commands/doctor.js +52 -22
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/helpers.js +14 -3
- 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/repeat-flags.js +34 -2
- package/dist/cli/commands/repeat-flags.js.map +1 -1
- package/dist/cli/commands/setup.js +16 -3
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/ui-state.d.ts +11 -0
- package/dist/cli/commands/ui-state.js +27 -0
- package/dist/cli/commands/ui-state.js.map +1 -0
- package/dist/cli/commands/writes.js +102 -67
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/help.js +2 -0
- 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 +10 -5
- package/dist/cli/main.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/contracts.d.ts +4 -4
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deputy/install.d.ts +15 -1
- package/dist/deputy/install.js +46 -6
- package/dist/deputy/install.js.map +1 -1
- package/dist/deputy/osa.d.ts +42 -2
- package/dist/deputy/osa.js +79 -5
- package/dist/deputy/osa.js.map +1 -1
- package/dist/deputy/routing.d.ts +34 -0
- package/dist/deputy/routing.js +95 -0
- 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 +6 -4
- package/dist/diagnose.js +20 -4
- package/dist/diagnose.js.map +1 -1
- package/dist/direct-setup.d.ts +31 -5
- package/dist/direct-setup.js +200 -26
- package/dist/direct-setup.js.map +1 -1
- package/dist/host-access.d.ts +12 -0
- package/dist/host-access.js +12 -0
- package/dist/host-access.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +23 -9
- package/dist/mcp/server.js.map +1 -1
- package/dist/op-result.js +8 -1
- package/dist/op-result.js.map +1 -1
- package/dist/session-grant.js +16 -1
- package/dist/session-grant.js.map +1 -1
- package/dist/surface-copy.d.ts +11 -3
- package/dist/surface-copy.js +11 -3
- package/dist/surface-copy.js.map +1 -1
- package/dist/ui-state.d.ts +46 -0
- package/dist/ui-state.js +82 -0
- package/dist/ui-state.js.map +1 -0
- package/dist/wizard.js +8 -1
- package/dist/wizard.js.map +1 -1
- package/dist/write/accessibility-probe.js +4 -1
- package/dist/write/accessibility-probe.js.map +1 -1
- package/dist/write/automation-probe.js +7 -1
- package/dist/write/automation-probe.js.map +1 -1
- package/dist/write/availability.d.ts +0 -15
- package/dist/write/availability.js +7 -62
- package/dist/write/availability.js.map +1 -1
- package/dist/write/batch.js +17 -6
- package/dist/write/batch.js.map +1 -1
- package/dist/write/commands.d.ts +10 -6
- package/dist/write/commands.js +120 -31
- package/dist/write/commands.js.map +1 -1
- package/dist/write/failure-hints.d.ts +36 -7
- package/dist/write/failure-hints.js +33 -14
- package/dist/write/failure-hints.js.map +1 -1
- package/dist/write/field-limits.d.ts +100 -0
- package/dist/write/field-limits.js +144 -0
- package/dist/write/field-limits.js.map +1 -0
- package/dist/write/guards.js +3 -2
- package/dist/write/guards.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 +15 -8
- package/dist/write/operations.js +9 -0
- package/dist/write/operations.js.map +1 -1
- package/dist/write/opid.d.ts +67 -23
- package/dist/write/opid.js +141 -19
- package/dist/write/opid.js.map +1 -1
- package/dist/write/param-schema.d.ts +17 -44
- package/dist/write/param-schema.js +74 -7
- package/dist/write/param-schema.js.map +1 -1
- package/dist/write/pipeline.d.ts +38 -7
- package/dist/write/pipeline.js +134 -18
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +30 -16
- package/dist/write/pre-state.js +88 -20
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/promote-clone.d.ts +26 -0
- package/dist/write/promote-clone.js +333 -41
- package/dist/write/promote-clone.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/resolution-timestamps.js +25 -2
- package/dist/write/resolution-timestamps.js.map +1 -1
- package/dist/write/template-mutation.js +163 -16
- package/dist/write/template-mutation.js.map +1 -1
- package/dist/write/vectors/applescript.js +3 -10
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/simulator.js +8 -2
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/types.d.ts +236 -4
- 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 +51 -3
- package/dist/write/vectors/ui-recipes.js +452 -44
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui-state.d.ts +157 -0
- package/dist/write/vectors/ui-state.js +237 -0
- package/dist/write/vectors/ui-state.js.map +1 -0
- package/dist/write/vectors/ui.d.ts +386 -13
- package/dist/write/vectors/ui.js +1543 -124
- 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 +41 -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 +1 -1
- package/schema/envelope.schema.json +2 -1
- package/skills/things-cli/SKILL.md +2 -2
|
@@ -60,7 +60,72 @@ export interface UiRecipe {
|
|
|
60
60
|
*/
|
|
61
61
|
needsWindowReachability?: boolean;
|
|
62
62
|
}
|
|
63
|
-
export type UiPrimitive = "reveal" | "activate" | "resolve" | "press" | "set-value"
|
|
63
|
+
export type UiPrimitive = "reveal" | "activate" | "resolve" | "press" | "set-value"
|
|
64
|
+
/**
|
|
65
|
+
* Set ONE of the Repeat dialog's two numeric fields — the cadence INTERVAL
|
|
66
|
+
* ("Every [n] days") or the ENDS-AFTER COUNT ("Ends: after [n] times") —
|
|
67
|
+
* addressed by the ROW it sits on rather than by its index among the group's
|
|
68
|
+
* text fields (HXPC1). Both used to be spelled `text field 1 of group 1`,
|
|
69
|
+
* which is only correct in the order the create path happens to drive them:
|
|
70
|
+
* the interval is the group's sole text field until an "Ends: after" bound is
|
|
71
|
+
* selected, and the count then takes index 1 with the interval displaced to 2
|
|
72
|
+
* (measured, Things 3.23 — docs/lab/hxpc1-picker-assert.md §A). A RESCHEDULE
|
|
73
|
+
* opens the dialog PRE-POPULATED, so a rule that already ends after N
|
|
74
|
+
* presents both fields from the first step and the index spelling wrote the
|
|
75
|
+
* requested interval into the count field. The row anchor is the group's
|
|
76
|
+
* `Ends:` static text: the count is the field sharing its row, the interval
|
|
77
|
+
* is the field that does not (and after-completion rules, which offer no ends
|
|
78
|
+
* bound at all, have only the interval). {@link UiStep.numberTarget} picks
|
|
79
|
+
* which. Drives with the same focus → select-all → type → Tab → read-back
|
|
80
|
+
* closed loop as set-value, and fails closed when the row anchor resolves
|
|
81
|
+
* anything other than exactly one field.
|
|
82
|
+
*/
|
|
83
|
+
| "set-group-number"
|
|
84
|
+
/**
|
|
85
|
+
* Set a Repeat-dialog text field addressed by the pinned English LABEL sharing
|
|
86
|
+
* its row — the same discrimination law as `set-group-number`, for a field that
|
|
87
|
+
* lives on the dialog SHELL rather than in the cadence group. Its one caller is
|
|
88
|
+
* the "and start [n] days earlier" offset the "Add deadlines" checkbox reveals,
|
|
89
|
+
* which shipped as `text field 1` of the shell: a value-bearing field picked by
|
|
90
|
+
* index out of a tree whose shape depends on that checkbox, and verified only by
|
|
91
|
+
* re-reading the same index it wrote — the HXPC1 error class exactly. It was
|
|
92
|
+
* right on Things 3.23 (measured: the shell carries 0 direct text fields with
|
|
93
|
+
* deadlines off and exactly 1 with them on, CGRD1 §B), but nothing in the address
|
|
94
|
+
* said so. {@link UiStep.rowLabel} names the anchor (`days earlier`); a missing
|
|
95
|
+
* label, or anything but exactly one field on its row, fails closed naming the
|
|
96
|
+
* shell's whole text-field inventory.
|
|
97
|
+
*/
|
|
98
|
+
| "set-row-field"
|
|
99
|
+
/**
|
|
100
|
+
* PRE-COMMIT FULL-DIALOG AUDIT — re-read every control this drive set, through
|
|
101
|
+
* each control's own discriminated address, and refuse to press OK if any of
|
|
102
|
+
* them does not hold the intended value (CGRD1).
|
|
103
|
+
*
|
|
104
|
+
* Every setter in this vocabulary confirms its own write by re-reading the
|
|
105
|
+
* element it addressed, which proves the keystrokes landed where they were
|
|
106
|
+
* aimed and nothing more; a wrong ADDRESS is invisible to it (#589: the interval
|
|
107
|
+
* drive typed into the ends-count field, then read that field back and found its
|
|
108
|
+
* own number). This step is the outside view. The plan is derived from the
|
|
109
|
+
* recipe's own step list ({@link UiStep.audit}), so a control the recipe drives
|
|
110
|
+
* cannot be left out of the audit by omission, and it compares the dialog's
|
|
111
|
+
* complete intended state — frequency, cadence unit, interval, ends bound and
|
|
112
|
+
* count, deadline/reminder checkboxes, start-days-earlier, the weekday set, the
|
|
113
|
+
* monthly/yearly anchors, the first occurrence and the ends date. A mismatch
|
|
114
|
+
* names every differing control with both values and aborts the drive
|
|
115
|
+
* fail-closed BEFORE the commit, through the usual clean-abort path.
|
|
116
|
+
*/
|
|
117
|
+
| "audit-dialog" | "select-popup" | "wait" | "key"
|
|
118
|
+
/**
|
|
119
|
+
* Type literal text into whatever control currently holds focus (HXPC1). Used
|
|
120
|
+
* for the Move… picker's filter field, which the picker focuses for itself the
|
|
121
|
+
* moment it opens: the field is not addressable as a direct child of the
|
|
122
|
+
* picker window, and the set-value primitive's select-all + Tab commit is
|
|
123
|
+
* wrong for a search field anyway (Tab has no next key view in a popover). The
|
|
124
|
+
* keystroke is not the verification — the `click-picker-row` step that follows
|
|
125
|
+
* resolves the intended destination row by name and fails closed if the filter
|
|
126
|
+
* did not produce it, so a keystroke that went astray can never be committed.
|
|
127
|
+
*/
|
|
128
|
+
| "type-text"
|
|
64
129
|
/**
|
|
65
130
|
* Synthesize a MOUSE click at an AX-resolved element's frame center (the
|
|
66
131
|
* NATIVE1 JXA ObjC-bridge / HID-tap primitive). Used ONLY for Things' custom
|
|
@@ -71,6 +136,28 @@ export type UiPrimitive = "reveal" | "activate" | "resolve" | "press" | "set-val
|
|
|
71
136
|
* foreground surface, NATIVE1-e), so a recipe using it must activate first.
|
|
72
137
|
*/
|
|
73
138
|
| "click-element"
|
|
139
|
+
/**
|
|
140
|
+
* Commit the Move… project picker by CLICKING the row that carries the
|
|
141
|
+
* destination project's exact title — never by pressing Return on whatever the
|
|
142
|
+
* filter happened to highlight (HXPC1). The picker exposes no selection,
|
|
143
|
+
* focus, or highlight attribute on any row, so there is nothing to read back
|
|
144
|
+
* from a keyboard commit; what it does expose is one `AXUnknown` per row whose
|
|
145
|
+
* `AXDescription` IS the project title, plus — whenever text has been typed —
|
|
146
|
+
* a `New Project "<typed text>"` row that CREATES a project when committed.
|
|
147
|
+
* The blind Return committed that row whenever the destination was absent from
|
|
148
|
+
* the picker, which is reachable from an ordinary database-resolved
|
|
149
|
+
* destination: a COMPLETED or CANCELED project is offered nowhere in the
|
|
150
|
+
* picker, so the drive silently created a second project of the same name and
|
|
151
|
+
* moved the heading into it (measured on Things 3.23,
|
|
152
|
+
* docs/lab/hxpc1-picker-assert.md §B4). Addressing the row by exact title
|
|
153
|
+
* cannot match the New-Project row (its description is the quoted form) and
|
|
154
|
+
* fails closed — naming every row the picker DID offer — when the intended one
|
|
155
|
+
* is absent, ambiguous, or scrolled out of its own scroll area (the CNCAC1
|
|
156
|
+
* off-screen-frame hazard). `path` is the picker WINDOW — the resolver reads its
|
|
157
|
+
* `AXIdentifier` to confirm identity before it looks at any row; `value` is the
|
|
158
|
+
* destination title.
|
|
159
|
+
*/
|
|
160
|
+
| "click-picker-row"
|
|
74
161
|
/**
|
|
75
162
|
* Synthesize a MOUSE DRAG that reorders a sidebar AREA row (the AXDRAG1
|
|
76
163
|
* primitive). The driver resolves the source row and the destination slot
|
|
@@ -80,6 +167,22 @@ export type UiPrimitive = "reveal" | "activate" | "resolve" | "press" | "set-val
|
|
|
80
167
|
* bound like click-element.
|
|
81
168
|
*/
|
|
82
169
|
| "drag-reorder"
|
|
170
|
+
/**
|
|
171
|
+
* Reorder a project's HEADINGS with the arrow chords (CHORDMH1). Things has no
|
|
172
|
+
* menu item, context-menu item or AX action for heading order — the affordance
|
|
173
|
+
* is four bare keybindings on a selected heading row: ⌘↑/⌘↓ move it one slot,
|
|
174
|
+
* ⌘⌥↑/⌘⌥↓ move it to the top/bottom (HEADORD1). The driver selects the row
|
|
175
|
+
* with the pure-AX positional primitive and posts one chord at a time straight
|
|
176
|
+
* to the Things process, reading the heading order back out of the DATABASE
|
|
177
|
+
* after each — the only oracle a bare keybinding offers. Every chord is
|
|
178
|
+
* computed from that read (never fired speculatively: a chord the app declines
|
|
179
|
+
* costs the user an alert beep), and every chord must move exactly the intended
|
|
180
|
+
* heading exactly one slot with no other row's `index` and no child row
|
|
181
|
+
* touched, or the drive stops. Background-capable with no focus steal — the
|
|
182
|
+
* whole gesture runs with Things behind the user's frontmost app. See
|
|
183
|
+
* src/write/vectors/ui-chord.ts.
|
|
184
|
+
*/
|
|
185
|
+
| "chord-reorder"
|
|
83
186
|
/**
|
|
84
187
|
* Select a PROJECT as a content-table ROW by matching its title, purely via
|
|
85
188
|
* AX (UIC4-a): the content table's `AXSelectedRows` is settable, so the
|
|
@@ -174,6 +277,18 @@ export type UiPrimitive = "reveal" | "activate" | "resolve" | "press" | "set-val
|
|
|
174
277
|
* the ≤3.22 `set-datetime next` drive wrote.
|
|
175
278
|
*/
|
|
176
279
|
| "select-next-occurrence"
|
|
280
|
+
/**
|
|
281
|
+
* Let the Things 3.23 `Next:` occurrence pop-up ABSORB the rule change the
|
|
282
|
+
* preceding steps made, before any further input reaches the dialog
|
|
283
|
+
* (NEXTPOP1). The dialog recomputes that control — its displayed first
|
|
284
|
+
* occurrence AND the menu of occurrences behind it — ASYNCHRONOUSLY, ~0.4s
|
|
285
|
+
* after a calendar-anchor change, and an input that lands inside that window
|
|
286
|
+
* CANCELS the recompute for good: the pop-up goes on describing the PREVIOUS
|
|
287
|
+
* rule indefinitely. The step polls the control until it moves (the common
|
|
288
|
+
* case, ~0.4s) or a bounded budget expires (nothing to absorb), so the next
|
|
289
|
+
* input never races it.
|
|
290
|
+
*/
|
|
291
|
+
| "settle-occurrences"
|
|
177
292
|
/**
|
|
178
293
|
* Converge the weekly dialog's weekday ROWS onto an exact target set through a
|
|
179
294
|
* deterministic closed loop (RDLG2, the RRD1 fix): read the live row count,
|
|
@@ -224,6 +339,42 @@ export interface UiStep {
|
|
|
224
339
|
keys?: string;
|
|
225
340
|
/** wait: how long to poll for the element before aborting. */
|
|
226
341
|
timeoutMs?: number;
|
|
342
|
+
/**
|
|
343
|
+
* set-group-number only: WHICH of the Repeat dialog's two numeric fields to
|
|
344
|
+
* drive. `interval` = the cadence field ("Every [n] …"); `ends-count` = the
|
|
345
|
+
* "Ends: after [n] times" field. Each is resolved by the ROW it sits on, so
|
|
346
|
+
* the drive no longer depends on the order the dialog's controls were touched
|
|
347
|
+
* or on whether a reschedule opened it pre-populated. Required for every
|
|
348
|
+
* set-group-number step.
|
|
349
|
+
*/
|
|
350
|
+
numberTarget?: "interval" | "ends-count";
|
|
351
|
+
/**
|
|
352
|
+
* set-row-field only: the pinned English static text whose ROW the target field
|
|
353
|
+
* shares (`days earlier` for the start-offset field). Locale fail-closed, like
|
|
354
|
+
* every other title-pinned selector here.
|
|
355
|
+
*/
|
|
356
|
+
rowLabel?: string;
|
|
357
|
+
/**
|
|
358
|
+
* audit-dialog only: the complete set of controls to re-read before the commit,
|
|
359
|
+
* with the value the drive intended for each. Built by the recipe from its own
|
|
360
|
+
* emitted steps, so the audit and the drive can never disagree about which
|
|
361
|
+
* controls were touched.
|
|
362
|
+
*/
|
|
363
|
+
audit?: DialogAuditPlan;
|
|
364
|
+
/**
|
|
365
|
+
* click-element only: resolve the click target by walking the addressed
|
|
366
|
+
* content TABLE's rows → cells → cell children for the element whose
|
|
367
|
+
* `AXDescription` equals this, instead of resolving `path` itself. The heading
|
|
368
|
+
* row's `…` button is the one control that needs it: it carries its heading's
|
|
369
|
+
* title in `AXDescription` ("More. <title>"), but it sits three levels below
|
|
370
|
+
* the table, and a `whose` clause on `UI elements of <table>` searches only
|
|
371
|
+
* the table's DIRECT children — the rows — so the shipped one-level spelling
|
|
372
|
+
* matched nothing and every ellipsis drive died at its own frame resolution
|
|
373
|
+
* (measured on Things 3.23, docs/lab/hxpc1-picker-assert.md §B0 — the same
|
|
374
|
+
* frame-resolution miss the timestamp cells recorded against the golden-v2
|
|
375
|
+
* rig). The walk is an exact match and fails closed naming what it sought.
|
|
376
|
+
*/
|
|
377
|
+
rowCellDescription?: string;
|
|
227
378
|
/**
|
|
228
379
|
* click-element only: the element expected to appear right AFTER the click
|
|
229
380
|
* (a popover opening, a sheet appearing). The driver polls for it and, on
|
|
@@ -253,6 +404,8 @@ export interface UiStep {
|
|
|
253
404
|
activateFallback?: boolean;
|
|
254
405
|
/** drag-reorder only: the sidebar move the drag driver performs. */
|
|
255
406
|
drag?: import("./ui-drag.ts").SidebarDragSpec;
|
|
407
|
+
/** chord-reorder only: the heading order the chord driver steps the project into. */
|
|
408
|
+
chord?: import("./ui-chord.ts").HeadingChordSpec;
|
|
256
409
|
/**
|
|
257
410
|
* set-datetime only: WHICH of the dialog's date areas to drive (ANCH2). The
|
|
258
411
|
* driver selects deterministically — `reminder` = the only time-bearing area;
|
|
@@ -287,6 +440,47 @@ export interface UiStep {
|
|
|
287
440
|
value?: string;
|
|
288
441
|
}>>;
|
|
289
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* ONE control of the pre-commit dialog audit, as the RECIPE declares it — before
|
|
445
|
+
* the driver has resolved which dialog shell is live or which shape the dialog is.
|
|
446
|
+
* Paths are per-shell candidate lists in the same order as {@link DialogAuditPlan.shells}.
|
|
447
|
+
*/
|
|
448
|
+
export interface DialogAuditControl {
|
|
449
|
+
/** Human name of the control, as the mismatch report should say it. */
|
|
450
|
+
label: string;
|
|
451
|
+
kind: "popup" | "checkbox" | "group-number" | "row-field" | "weekdays" | "occurrence-popup" | "date-area";
|
|
452
|
+
/** popup / checkbox / occurrence-popup: per-shell candidate paths. */
|
|
453
|
+
pathCandidates?: string[];
|
|
454
|
+
/** group-number: which of the cadence group's numeric fields. */
|
|
455
|
+
numberTarget?: "interval" | "ends-count";
|
|
456
|
+
/** row-field: the pinned English label sharing the field's row. */
|
|
457
|
+
rowLabel?: string;
|
|
458
|
+
/** weekdays: the group pop-up index of the first weekday row. */
|
|
459
|
+
weekdayBase?: number;
|
|
460
|
+
/** date-area: which of the dialog's `AXDateTimeArea` controls. */
|
|
461
|
+
dtTarget?: "next" | "ends" | "reminder";
|
|
462
|
+
/** date-area: the spec the drive wrote (`date:YYYY-MM-DD` / `time:HH:mm`). */
|
|
463
|
+
dtSpec?: string;
|
|
464
|
+
/** The accepted observed values — ANY one satisfies (the singular/plural pair). */
|
|
465
|
+
expected?: string[];
|
|
466
|
+
/** How the intended value should READ in the report ("checked", not "1"). */
|
|
467
|
+
expectedLabel?: string;
|
|
468
|
+
/** Check this control ONLY under the named dialog shape (the +1-index fork). */
|
|
469
|
+
onlyShape?: RepeatDialogShape;
|
|
470
|
+
/** Per-shape overrides, merged once the dialog shape is measured. */
|
|
471
|
+
shaped?: Partial<Record<RepeatDialogShape, {
|
|
472
|
+
pathCandidates?: string[];
|
|
473
|
+
weekdayBase?: number;
|
|
474
|
+
}>>;
|
|
475
|
+
}
|
|
476
|
+
/** The pre-commit audit an `audit-dialog` step carries. */
|
|
477
|
+
export interface DialogAuditPlan {
|
|
478
|
+
/** The dialog shells, in the SAME priority order the drive resolves them. */
|
|
479
|
+
shells: string[];
|
|
480
|
+
/** The cadence group inside each shell (same order as {@link shells}). */
|
|
481
|
+
groups: string[];
|
|
482
|
+
controls: DialogAuditControl[];
|
|
483
|
+
}
|
|
290
484
|
/**
|
|
291
485
|
* The structural SHAPE of the Repeat dialog, MEASURED live (never sniffed from
|
|
292
486
|
* the app version — a version string says nothing about the tree the driver has
|
|
@@ -325,6 +519,16 @@ export interface VectorSupport {
|
|
|
325
519
|
notes?: string;
|
|
326
520
|
}
|
|
327
521
|
export type VectorMatrix = Partial<Record<OperationKind, VectorSupport>>;
|
|
522
|
+
/**
|
|
523
|
+
* How the ui vector's audited cleanup ladder left an open dialog (issue #620):
|
|
524
|
+
* `none` — nothing was open; `dismissed` — closed, and a fresh read confirmed
|
|
525
|
+
* it closed; `cleared-blind` — cleared by the window close+reopen on an
|
|
526
|
+
* AX-blind session, so it cannot be confirmed; `foreign` — a dialog this drive
|
|
527
|
+
* did not open was found and deliberately LEFT ALONE; `may-remain` — ours, and
|
|
528
|
+
* nothing in the ladder would close it (the stranded case, which also holds
|
|
529
|
+
* Things Cloud sync until someone dismisses it).
|
|
530
|
+
*/
|
|
531
|
+
export type UiClearOutcome = "none" | "dismissed" | "cleared-blind" | "foreign" | "may-remain";
|
|
328
532
|
export interface ExecuteResult {
|
|
329
533
|
exitCode: number | null;
|
|
330
534
|
stdout: string;
|
|
@@ -358,8 +562,8 @@ export interface ExecuteResult {
|
|
|
358
562
|
elapsedMs: number;
|
|
359
563
|
/** The step the drive was about to run (or running) when the budget blew. */
|
|
360
564
|
lastStep: string;
|
|
361
|
-
/** How the open dialog was cleaned up (
|
|
362
|
-
clear:
|
|
565
|
+
/** How the open dialog was cleaned up (the audited cleanup ladder's outcome). */
|
|
566
|
+
clear: UiClearOutcome;
|
|
363
567
|
/** The local trace file reconstructing the timeline, when tracing is on. */
|
|
364
568
|
tracePath?: string | null;
|
|
365
569
|
};
|
|
@@ -387,7 +591,7 @@ export interface ExecuteResult {
|
|
|
387
591
|
*/
|
|
388
592
|
cause: "unreachable" | "unresponsive";
|
|
389
593
|
/** How a half-open sheet was cleaned up, when the drive had opened one. */
|
|
390
|
-
clear?:
|
|
594
|
+
clear?: UiClearOutcome;
|
|
391
595
|
/** What the caller does to make a retry work. */
|
|
392
596
|
remediation: string;
|
|
393
597
|
};
|
|
@@ -406,6 +610,18 @@ export interface WriteVector {
|
|
|
406
610
|
* vectors and the simulator, so a caller with no ui vector simply skips the gate.
|
|
407
611
|
*/
|
|
408
612
|
probeReachability?: () => Promise<import("./session-reachability.ts").ReachabilityVerdict>;
|
|
613
|
+
/**
|
|
614
|
+
* ui vector ONLY: read the window/focus census (read-only — no click, no
|
|
615
|
+
* keystroke, no activation), so a composite ORCHESTRATOR can refuse BEFORE it
|
|
616
|
+
* seeds a row when a dialog is already standing in Things (MODALX1, issue
|
|
617
|
+
* #620). Measured: the seed leg rides the URL scheme, which an open dialog
|
|
618
|
+
* does not touch, so the copy lands and then every AppleScript leg after it
|
|
619
|
+
* fails with `-1728` — the compound mutates for a reason it could have known
|
|
620
|
+
* up front. Null means the state could not be read; the caller proceeds (the
|
|
621
|
+
* drive's own precondition is the backstop). Absent on the real transport
|
|
622
|
+
* vectors and the simulator.
|
|
623
|
+
*/
|
|
624
|
+
probeUiState?: () => Promise<import("./ui-state.ts").UiState | null>;
|
|
409
625
|
/**
|
|
410
626
|
* The bench-harness SIMULATOR vector (src/write/vectors/simulator.ts). It
|
|
411
627
|
* presents under a real {@link VectorId} but applies mutations via SQL from
|
|
@@ -445,4 +661,20 @@ export interface WriteVector {
|
|
|
445
661
|
* imports the developer's own grants into CI).
|
|
446
662
|
*/
|
|
447
663
|
drivesGui?: boolean;
|
|
664
|
+
/**
|
|
665
|
+
* This vector delivers MUTATIONS through `things:///` URLs, so it is subject
|
|
666
|
+
* to Things' own in-app authorization — Settings ▸ General ▸ "Enable Things
|
|
667
|
+
* URLs" (URLEN1, #611). No macOS consent class is involved, but the two
|
|
668
|
+
* not-enabled states are both silent from the caller's side: an explicitly
|
|
669
|
+
* disabled app discards the command outright, and an app nobody has answered
|
|
670
|
+
* the first-use dialog for PARKS the command behind that dialog. Either way
|
|
671
|
+
* the write reports a silent no-op minutes later, so the pipeline reads the
|
|
672
|
+
* setting prompt-free and refuses before dispatch instead.
|
|
673
|
+
*
|
|
674
|
+
* Set ONLY by {@link import("./url-scheme.ts").createUrlSchemeVector}, for the
|
|
675
|
+
* same reason as its two siblings above: engine tests substitute fakes under
|
|
676
|
+
* the real ids, and a fake that opens no URL must not be gated on the state of
|
|
677
|
+
* the developer's own Things install.
|
|
678
|
+
*/
|
|
679
|
+
dispatchesUrls?: boolean;
|
|
448
680
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { UI_DRIVE_OPS } from "../operations.js";
|
|
2
2
|
/** The manifest profile — records the tier + Things build the suite certified. */
|
|
3
|
-
export const UI_CERTIFICATION_PROFILE = "UIC1 + UIC3 + AXDRAG2 + UIC5 + UIC6 + UIC7 + UIC7b + HEADCERT1 +
|
|
4
|
-
"UIC8 promote-via-clone compounds in-VM (golden-v2 / Things 3.22.12)
|
|
3
|
+
export const UI_CERTIFICATION_PROFILE = "UIC1 + UIC3 + AXDRAG2 + UIC5 + UIC6 + UIC7 + UIC7b + HEADCERT1 + HEADXPROJ + DISS1 in-VM (Things 3.22.11) + " +
|
|
4
|
+
"UIC8 promote-via-clone compounds in-VM (golden-v2 / Things 3.22.12) + RDLG2 recipe re-point + " +
|
|
5
|
+
"HXPC1 heading-ellipsis/Move-picker paths in-VM (golden-v4 / Things 3.23) + " +
|
|
6
|
+
"CHORDMH1 heading-order arrow chords in-VM (golden-v4 / Things 3.23) — on-device pending";
|
|
5
7
|
const CERTIFICATION = {
|
|
6
8
|
"todo.make-repeating": {
|
|
7
9
|
status: "lab-certified",
|
|
@@ -48,16 +50,59 @@ const CERTIFICATION = {
|
|
|
48
50
|
evidence: ["UI2-d", "UIC1-a", "HEADCERT1"],
|
|
49
51
|
},
|
|
50
52
|
"project.move-heading-to-project": {
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// type the destination → Return
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
53
|
+
// HEADXPROJ (2026-07-27, bjhx-lab, Things 3.22.11) established the flow and the
|
|
54
|
+
// DB oracle with a hand-written driver: HID-click the "More. <title>" button →
|
|
55
|
+
// Move… → type the destination → Return, and heading `HXH`'s project FK
|
|
56
|
+
// rewritten HX-PA → HX-PB with both children following via their intact heading
|
|
57
|
+
// FK (project NULL, heading=HXH) — a single-row change, no index churn.
|
|
58
|
+
//
|
|
59
|
+
// HXPC1 (2026-08-25, hxpc1-lab, Things 3.23 / golden-v4) certified the SHIPPED
|
|
60
|
+
// recipe for the first time and corrected three of the four provisional paths:
|
|
61
|
+
// the "More. <title>" button is three levels below the content table (so the
|
|
62
|
+
// shipped one-level `whose` clause matched nothing and the drive never got past
|
|
63
|
+
// its first click), the picker is a detached `MovePopUpDialog-` window rather
|
|
64
|
+
// than a sheet of the main window, and the commit is a CLICK on the row whose
|
|
65
|
+
// title matches exactly — the blind Return could take the picker's
|
|
66
|
+
// `New Project "<typed>"` row whenever the destination was absent from it,
|
|
67
|
+
// which a completed or canceled destination is. 7/7 through the production CLI
|
|
68
|
+
// (clean match, prefix collision, completed-destination refusal with zero
|
|
69
|
+
// mutation). docs/lab/hxpc1-picker-assert.md.
|
|
59
70
|
status: "lab-certified",
|
|
60
|
-
evidence: ["HXPC1"],
|
|
71
|
+
evidence: ["HEADXPROJ", "HXPC1"],
|
|
72
|
+
},
|
|
73
|
+
"project.move-heading": {
|
|
74
|
+
// HEADORD1 (2026-08-25, headord1-lab, Things 3.23 / golden-v4) discovered and
|
|
75
|
+
// characterised the affordance: ⌘↑/⌘↓ = ±1 slot, ⌘⌥↑/⌘⌥↓ = to top/bottom on a
|
|
76
|
+
// selected heading row; a single-row `index` rewrite with no sibling renumber
|
|
77
|
+
// and children untouched behind their FK; a chord with nowhere to go declined
|
|
78
|
+
// with zero delta and one alert beep; System Events modifiers frontmost-only,
|
|
79
|
+
// `CGEventPostToPid` background-capable.
|
|
80
|
+
//
|
|
81
|
+
// CHORDMH1 (2026-08-25, chordmh1-lab, Things 3.23 / golden-v4) certified the
|
|
82
|
+
// SHIPPED op. Its delivery gate re-measured the whole gesture with Things
|
|
83
|
+
// never activated at all — `open -g` reveal, pure-AX row select, pid-posted
|
|
84
|
+
// chord — and Finder frontmost at every stage, so the op ships BACKGROUND
|
|
85
|
+
// delivery with no `activate` step (the least-disruptive tier any ordering op
|
|
86
|
+
// has reached). The certification arms drove the production CLI over
|
|
87
|
+
// 3-heading fixtures: ±1 up, ±1 down, to-top, to-bottom, a multi-hop, the
|
|
88
|
+
// already-in-position no-op, the dry run, and the ungated call — children
|
|
89
|
+
// byte-identical on every arm, sibling indexes byte-identical, zero beeps on
|
|
90
|
+
// the normal paths. docs/lab/chordmh1-move-heading-build.md.
|
|
91
|
+
//
|
|
92
|
+
// CHORDMH2 (2026-08-25, chordmh2-lab, Things 3.23 / golden-v4) lifted CHORDMH1's
|
|
93
|
+
// one deliberate capability cut — the whole-project refusal on any archived
|
|
94
|
+
// heading — after CHORD2 cell 7a′ measured that an archived heading renders no
|
|
95
|
+
// content row, takes no ordinal in the walk, and is skipped by a live heading's
|
|
96
|
+
// ±1. The fence stood in for an ordinal mismatch, so the three reads that feed
|
|
97
|
+
// the plan, the driver and the bare-placement anchor now return the RENDERED
|
|
98
|
+
// (`status = 0`) order. Certified through the production CLI: one chord carried
|
|
99
|
+
// a heading past an archived row's slot, that heading was the only row
|
|
100
|
+
// rewritten, the archived row was byte-untouched on status/index/stopDate/umd,
|
|
101
|
+
// children intact, zero beeps; an archived heading named as a movee or an
|
|
102
|
+
// anchor still refuses with zero mutation.
|
|
103
|
+
// docs/lab/chordmh2-archived-fence-lift.md.
|
|
104
|
+
status: "lab-certified",
|
|
105
|
+
evidence: ["HEADORD1", "CHORDMH1", "CHORDMH2"],
|
|
61
106
|
},
|
|
62
107
|
"project.dissolve-heading": {
|
|
63
108
|
// DISS1 (2026-07-28, bjhx-lab, Things 3.22.11): the ellipsis Delete recipe ran
|
|
@@ -66,8 +111,13 @@ const CERTIFICATION = {
|
|
|
66
111
|
// TMTask), its 3 children re-homed as DIRECT project children (heading→NULL,
|
|
67
112
|
// project→parent, index preserved: c1<c2<c3, trashed=0). NO confirm sheet.
|
|
68
113
|
// Popover items are AX-description-enumerable (the recipe resolves Delete by
|
|
69
|
-
// description, scoped to the popover)
|
|
70
|
-
//
|
|
114
|
+
// description, scoped to the popover) — the one provisional path HXPC1 measured
|
|
115
|
+
// CORRECT. Its sibling was not: the "More. <title>" button this drive opens the
|
|
116
|
+
// popover with was addressed one level too shallow, so it resolved nothing and
|
|
117
|
+
// the drive died at its first click on every host. HXPC1 (2026-08-25, Things
|
|
118
|
+
// 3.23) fixed the walk and certified it through the move-heading sibling, but
|
|
119
|
+
// this op was NOT driven end to end there — its next sitting should carry a cell
|
|
120
|
+
// (docs/lab/hxpc1-picker-assert.md §B0/§D). The DB oracle + flow are certified.
|
|
71
121
|
status: "lab-certified",
|
|
72
122
|
evidence: ["DISS1"],
|
|
73
123
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-certification.js","sourceRoot":"","sources":["../../../src/write/vectors/ui-certification.ts"],"names":[],"mappings":"AAqCA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAYhD,kFAAkF;AAClF,MAAM,CAAC,MAAM,wBAAwB,GACnC,
|
|
1
|
+
{"version":3,"file":"ui-certification.js","sourceRoot":"","sources":["../../../src/write/vectors/ui-certification.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import type { UiCommand, UiRunner } from "./ui.ts";
|
|
3
|
+
/** The move a `chord-reorder` step performs, compiled from the pre-state. */
|
|
4
|
+
export interface HeadingChordSpec {
|
|
5
|
+
/** The project whose headings are being reordered. */
|
|
6
|
+
projectUuid: string;
|
|
7
|
+
/**
|
|
8
|
+
* Every LIVE (`status = 0`) heading of the project, in the order they must end
|
|
9
|
+
* up in — the full end state, not just the moved block. The driver places them
|
|
10
|
+
* left to right, so this doubles as the verification target. Archived headings
|
|
11
|
+
* are absent by construction: they render no row, so they are neither
|
|
12
|
+
* addressable nor displaced (CHORD2 cell 7a′).
|
|
13
|
+
*/
|
|
14
|
+
targetOrder: string[];
|
|
15
|
+
/**
|
|
16
|
+
* The headings the caller named. Only these may ever be chorded: if the
|
|
17
|
+
* driver's placement walk would have to move a heading the caller did not
|
|
18
|
+
* name, it refuses rather than touch a bystander.
|
|
19
|
+
*/
|
|
20
|
+
movees: string[];
|
|
21
|
+
/** The project view's content table (the recipe supplies the one path). */
|
|
22
|
+
tablePath: string;
|
|
23
|
+
}
|
|
24
|
+
/** The database read the driver asserts against between chords. */
|
|
25
|
+
export interface HeadingOrderState {
|
|
26
|
+
/**
|
|
27
|
+
* The project's LIVE (`status = 0`) headings in `index` order, with their raw
|
|
28
|
+
* index — the rows the project view actually renders, and therefore the rows
|
|
29
|
+
* the positional `select-heading-row` walk enumerates. An ARCHIVED heading is
|
|
30
|
+
* absent: it renders no row, takes no ordinal, and a live heading's ±1 chord
|
|
31
|
+
* skips its slot in one dispatch (CHORD2 cell 7a′). Filtering here is what
|
|
32
|
+
* keeps the driver's ordinals in step with the walk's.
|
|
33
|
+
*/
|
|
34
|
+
headings: {
|
|
35
|
+
uuid: string;
|
|
36
|
+
index: number;
|
|
37
|
+
}[];
|
|
38
|
+
/**
|
|
39
|
+
* Digest over every non-trashed child of those headings: `uuid:headingFK:index`.
|
|
40
|
+
* The chord law says children are untouched behind their FK — this is what
|
|
41
|
+
* proves it, and what catches the one measured hazard in the family (a row
|
|
42
|
+
* driven across a heading boundary has its heading FK rewritten, HEADORD1 §2
|
|
43
|
+
* cells 1h4/1i2; it cannot happen to a heading, and this asserts that).
|
|
44
|
+
*/
|
|
45
|
+
childDigest: string;
|
|
46
|
+
/** How many child rows the digest covers (reported in the failure text). */
|
|
47
|
+
childCount: number;
|
|
48
|
+
}
|
|
49
|
+
/** The database seam the chord driver reads its ground truth through. */
|
|
50
|
+
export type HeadingOrderReader = (projectUuid: string) => HeadingOrderState;
|
|
51
|
+
/**
|
|
52
|
+
* The client-side default: heading order + child containment, from the open DB.
|
|
53
|
+
*
|
|
54
|
+
* The heading read is filtered to `status = 0` so it returns the RENDERED order
|
|
55
|
+
* (CHORD2 cell 7a′). The child digest is deliberately NOT filtered — it covers
|
|
56
|
+
* every non-trashed child of every heading of the project, archived headings
|
|
57
|
+
* included, because no heading chord may disturb any of them.
|
|
58
|
+
*/
|
|
59
|
+
export declare function createHeadingOrderReader(db: DatabaseSync): HeadingOrderReader;
|
|
60
|
+
/**
|
|
61
|
+
* The four chords, as `(key code, CGEventFlags)` pairs. `126`/`125` are the
|
|
62
|
+
* arrow keys; `0x100000` is `kCGEventFlagMaskCommand` and `0x80000`
|
|
63
|
+
* `kCGEventFlagMaskAlternate`. Named rather than inlined so the recipe trace and
|
|
64
|
+
* the failure text can say which chord was posted.
|
|
65
|
+
*/
|
|
66
|
+
export type ChordId = "up-one" | "down-one" | "to-top" | "to-bottom";
|
|
67
|
+
export declare function chordGlyph(id: ChordId): string;
|
|
68
|
+
/**
|
|
69
|
+
* Post ONE modifier-bearing key event pair straight at the Things process
|
|
70
|
+
* (`CGEventPostToPid`), through the same JXA ObjC bridge every other synthetic
|
|
71
|
+
* input in this codebase rides.
|
|
72
|
+
*
|
|
73
|
+
* Why not System Events. The shipped `key` primitive emits `key code N` with no
|
|
74
|
+
* modifier support at all, and the System-Events spelling that DOES carry
|
|
75
|
+
* modifiers only lands while Things is FRONTMOST (HEADORD1 cell 1h2b: with
|
|
76
|
+
* Finder frontmost it produced no delta). `CGEventPostToPid` addresses the
|
|
77
|
+
* process rather than the focused surface, so the whole gesture — reveal,
|
|
78
|
+
* select, chord — runs with Things in the background and the user's focus
|
|
79
|
+
* untouched. Measured on Things 3.23 / golden-v4 (HEADORD1 1h2a; re-measured end
|
|
80
|
+
* to end, with Things never activated at all, by CHORDMH1's delivery gate).
|
|
81
|
+
*
|
|
82
|
+
* Note this is the KEYBOARD tap only. NATIVE1-e measured `CGEventPostToPid` as
|
|
83
|
+
* inert for Things' MOUSE hit-testing, which is why click synthesis still uses
|
|
84
|
+
* the HID tap and stays foreground-bound; keyboard events to the pid are a
|
|
85
|
+
* different path and do land.
|
|
86
|
+
*/
|
|
87
|
+
export declare function jxaChordScript(id: ChordId): string;
|
|
88
|
+
export declare function chordCommand(id: ChordId): UiCommand;
|
|
89
|
+
/** Select the Nth heading row of the revealed project view (the shipped primitive). */
|
|
90
|
+
export declare function chordSelectCommand(tablePath: string, ordinal: number, script: string): UiCommand;
|
|
91
|
+
export interface ChordDriveResult {
|
|
92
|
+
ok: boolean;
|
|
93
|
+
/** Human-readable outcome (chords posted) or the refusal reason. */
|
|
94
|
+
detail: string;
|
|
95
|
+
/** How many chords were posted before the outcome (partial-state honesty). */
|
|
96
|
+
chords: number;
|
|
97
|
+
}
|
|
98
|
+
/** One scheduled step: which heading to chord, with what, and where it must land. */
|
|
99
|
+
export interface ChordStep {
|
|
100
|
+
/** The heading to select and move — ALWAYS one the caller named. */
|
|
101
|
+
uuid: string;
|
|
102
|
+
chord: ChordId;
|
|
103
|
+
/** The position the heading must occupy afterwards (the progress assertion). */
|
|
104
|
+
landsAt: number;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The next chord that advances `order` towards `target`, or null when they
|
|
108
|
+
* already agree. `movees` is the only set of rows a chord may ever move.
|
|
109
|
+
*
|
|
110
|
+
* THE SCHEDULE, and why it is this one. The obvious walk — "put target[i] into
|
|
111
|
+
* slot i, left to right" — is WRONG here, because target[i] is very often a row
|
|
112
|
+
* the caller never named: asking to move heading A down one slot produces a
|
|
113
|
+
* target whose first element is the BYSTANDER B. Moving B is not an option (it
|
|
114
|
+
* would break the untouched-siblings law the whole vector rests on), and the
|
|
115
|
+
* equally obvious repair — "push whichever movee sits at slot i down" — thrashes
|
|
116
|
+
* forever when two movees sit side by side, each pushing the other back.
|
|
117
|
+
*
|
|
118
|
+
* So the rule fixes the first mismatched slot `i` from the row's own side:
|
|
119
|
+
*
|
|
120
|
+
* - target[i] IS a movee → it is necessarily BELOW slot i (everything above
|
|
121
|
+
* already matches), so step it UP; from slot 0 the one-dispatch ⌘⌥↑ takes it
|
|
122
|
+
* there directly.
|
|
123
|
+
* - target[i] is a bystander `R` → every row between slot i and R's current
|
|
124
|
+
* position is provably a movee (the non-movees keep their relative order, so
|
|
125
|
+
* R is the first non-movee at or below i), and pushing the ONE movee directly
|
|
126
|
+
* above R down past it moves R up by exactly one slot.
|
|
127
|
+
*
|
|
128
|
+
* Both branches strictly reduce a distance that cannot go below zero, so the
|
|
129
|
+
* schedule terminates, and every chord it emits moves a named heading by one
|
|
130
|
+
* slot. Two endpoint shortcuts run first, because they are the common single
|
|
131
|
+
* heading requests and each costs one dispatch instead of a walk: a movee whose
|
|
132
|
+
* target is the very top or the very bottom is sent there with ⌘⌥↑ / ⌘⌥↓.
|
|
133
|
+
*/
|
|
134
|
+
export declare function planChordStep(order: readonly string[], target: readonly string[], movees: ReadonlySet<string>): ChordStep | {
|
|
135
|
+
error: string;
|
|
136
|
+
} | null;
|
|
137
|
+
/**
|
|
138
|
+
* Drive a project's headings into `spec.targetOrder`, one verified chord at a
|
|
139
|
+
* time (see {@link planChordStep} for the schedule). Nothing here trusts the
|
|
140
|
+
* plan it made a moment ago: every step is re-derived from a fresh database
|
|
141
|
+
* read, and the step is only counted once the database says it landed where it
|
|
142
|
+
* was aimed with nothing else touched.
|
|
143
|
+
*/
|
|
144
|
+
export declare function driveHeadingChordReorder(spec: HeadingChordSpec, run: UiRunner, reader: HeadingOrderReader | undefined, selectScript: (ordinal: number) => string, sleep?: (ms: number) => Promise<void>): Promise<ChordDriveResult>;
|