things-api 0.19.2 → 0.19.4

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 (66) hide show
  1. package/README.md +1 -1
  2. package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
  3. package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/MacOS/things-reader +0 -0
  4. package/deputy/prebuilt/Things API Helper.app/Contents/MacOS/things-deputy +0 -0
  5. package/dist/audit/schema.d.ts +14 -0
  6. package/dist/audit/schema.js.map +1 -1
  7. package/dist/cli/commands/op-result.js +13 -1
  8. package/dist/cli/commands/op-result.js.map +1 -1
  9. package/dist/cli/commands/writes.js +51 -7
  10. package/dist/cli/commands/writes.js.map +1 -1
  11. package/dist/contracts.d.ts +1 -1
  12. package/dist/contracts.js +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/mcp/server.js +7 -0
  16. package/dist/mcp/server.js.map +1 -1
  17. package/dist/op-result.d.ts +9 -0
  18. package/dist/op-result.js +5 -0
  19. package/dist/op-result.js.map +1 -1
  20. package/dist/ui-state.d.ts +1 -1
  21. package/dist/ui-state.js +33 -8
  22. package/dist/ui-state.js.map +1 -1
  23. package/dist/write/batch.js +4 -1
  24. package/dist/write/batch.js.map +1 -1
  25. package/dist/write/clone.js +12 -6
  26. package/dist/write/clone.js.map +1 -1
  27. package/dist/write/disclosures.d.ts +263 -0
  28. package/dist/write/disclosures.js +265 -0
  29. package/dist/write/disclosures.js.map +1 -0
  30. package/dist/write/heading.js +4 -7
  31. package/dist/write/heading.js.map +1 -1
  32. package/dist/write/make-repeating-project.js +2 -1
  33. package/dist/write/make-repeating-project.js.map +1 -1
  34. package/dist/write/opid.js +5 -3
  35. package/dist/write/opid.js.map +1 -1
  36. package/dist/write/pipeline.d.ts +39 -1
  37. package/dist/write/pipeline.js +49 -23
  38. package/dist/write/pipeline.js.map +1 -1
  39. package/dist/write/promote-clone.js +46 -36
  40. package/dist/write/promote-clone.js.map +1 -1
  41. package/dist/write/reorder.js +45 -39
  42. package/dist/write/reorder.js.map +1 -1
  43. package/dist/write/resolution-timestamps.js +4 -1
  44. package/dist/write/resolution-timestamps.js.map +1 -1
  45. package/dist/write/spawn-expectation.d.ts +135 -0
  46. package/dist/write/spawn-expectation.js +237 -0
  47. package/dist/write/spawn-expectation.js.map +1 -0
  48. package/dist/write/template-mutation.js +20 -33
  49. package/dist/write/template-mutation.js.map +1 -1
  50. package/dist/write/vectors/types.d.ts +12 -0
  51. package/dist/write/vectors/ui-recipes.js +11 -1
  52. package/dist/write/vectors/ui-recipes.js.map +1 -1
  53. package/dist/write/vectors/ui-state.d.ts +136 -6
  54. package/dist/write/vectors/ui-state.js +372 -70
  55. package/dist/write/vectors/ui-state.js.map +1 -1
  56. package/dist/write/vectors/ui.d.ts +62 -2
  57. package/dist/write/vectors/ui.js +401 -151
  58. package/dist/write/vectors/ui.js.map +1 -1
  59. package/dist/write/verify/delta.d.ts +17 -2
  60. package/dist/write/verify/delta.js +46 -16
  61. package/dist/write/verify/delta.js.map +1 -1
  62. package/dist/write/verify/poller.d.ts +3 -2
  63. package/dist/write/verify/poller.js +3 -1
  64. package/dist/write/verify/poller.js.map +1 -1
  65. package/package.json +1 -1
  66. package/skills/things-cli/SKILL.md +1 -1
@@ -47,6 +47,65 @@ export const AX_DIALOG_SHELL_SNIPPET = ` set shellRef to missing value
47
47
  set sheetDepth to sheetDepth + 1
48
48
  end repeat
49
49
  end if`;
50
+ /** Probes whose absence makes the census unusable for a keystroke decision. */
51
+ const CRITICAL_PROBES = ["running", "frontmost", "dialog"];
52
+ /** Every probe name the census can report, for parsing its record back. */
53
+ const KNOWN_PROBES = new Set([
54
+ "running",
55
+ "frontmost",
56
+ "dialog",
57
+ "frontapp",
58
+ "focus",
59
+ ]);
60
+ /**
61
+ * Per-APPLE-EVENT budget for every read in the census (`with timeout of N
62
+ * seconds`). Without one, osascript's default is two MINUTES: a System Events
63
+ * call that does not come back is then bounded only by the caller's own process
64
+ * deadline, which is how issue #629's field incident spent ~15s per inspection
65
+ * and ~56s per drive discovering nothing. With one, a read that will not answer
66
+ * raises AppleScript error -1712 and the census carries on to the next probe —
67
+ * or stops, if the probe was one the caller decides on.
68
+ */
69
+ const PROBE_TIMEOUT_S = 2;
70
+ /**
71
+ * Wall-clock budget for the WHOLE census, checked between probes. `with
72
+ * timeout` bounds one Apple event, not a run of them, so a surface that is
73
+ * merely slow (rather than wedged) could still add up. Past this, the remaining
74
+ * probes are skipped and reported as stalled rather than waited on.
75
+ */
76
+ const CENSUS_BUDGET_S = 8;
77
+ /**
78
+ * The transport deadline for one census hop — the backstop under the in-script
79
+ * budgets, not the mechanism. Deliberately far below the 15s step timeout that
80
+ * bounded the old single-shot script: nothing here may take this long, and if
81
+ * it does the caller must hear about it while the drive can still be aborted
82
+ * cleanly rather than after four of them have gone by.
83
+ */
84
+ export const CENSUS_TIMEOUT_MS = 12_000;
85
+ /** Did a probe the caller has to DECIDE on fail to answer? */
86
+ export function censusUnverifiable(state) {
87
+ if (state === null)
88
+ return true;
89
+ return CRITICAL_PROBES.some((p) => state.stalledProbes.includes(p) || state.failedProbes.includes(p));
90
+ }
91
+ /** Name the probes that did not answer, for a diagnostic someone has to act on. */
92
+ export function describeUnprovenProbes(state) {
93
+ const label = {
94
+ running: "whether Things is running",
95
+ frontmost: "whether Things owns the screen",
96
+ dialog: "which dialog is open",
97
+ frontapp: "which application owns the screen",
98
+ focus: "which element has keyboard focus",
99
+ };
100
+ const parts = [];
101
+ if (state.stalledProbes.length > 0) {
102
+ parts.push(`did not answer in time: ${state.stalledProbes.map((p) => label[p]).join(", ")}`);
103
+ }
104
+ if (state.failedProbes.length > 0) {
105
+ parts.push(`could not be read: ${state.failedProbes.map((p) => label[p]).join(", ")}`);
106
+ }
107
+ return parts.join("; ");
108
+ }
50
109
  /** A recognizable token in the script so a test runner can key off the ui-state command. */
51
110
  export const UI_STATE_MARKER = "-- ui-state census (read-only)";
52
111
  /** The label every ui-state dispatch carries (one stable command shape). */
@@ -54,14 +113,51 @@ export const UI_STATE_LABEL = "read the window and focus state";
54
113
  /** The Things application's process name — the frontmost value that means "us". */
55
114
  export const THINGS_PROCESS = "Things3";
56
115
  /**
57
- * The census script. Every read is wrapped: a surface that will not answer
58
- * degrades that ONE field rather than failing the whole census, because the
59
- * cases this exists for — a foreign modal, an AX-blind session — are exactly
60
- * the cases where half the tree is unreadable.
116
+ * The census script ADDRESSED probes, each on its own Apple-event budget
117
+ * (issue #629).
118
+ *
119
+ * WHAT CHANGED, AND WHY IT HAD TO. The 0.19.2 census was one unbounded script
120
+ * that opened with two UNADDRESSED queries: `first application process whose
121
+ * frontmost is true` (the entire process table enumerated) and `value of
122
+ * attribute "AXFocusedUIElement"` on whatever that returned (a system-wide
123
+ * focused-element resolution). Everything else the ui vector runs — every step
124
+ * the field incident's log shows succeeding, inside the very sheet the census
125
+ * could not describe — is addressed: `tell process "Things3" to …`. So the one
126
+ * script that stalled was the one script that left the addressed style, and it
127
+ * stalled on the critical path of BOTH the per-step guard and the cleanup that
128
+ * was supposed to recover from it. Rebuilt here so that:
129
+ *
130
+ * - the decision-critical facts are ADDRESSED and are proven FIRST;
131
+ * - every read carries `with timeout of ${PROBE_TIMEOUT_S} seconds`, so a
132
+ * surface that will not answer costs seconds, not the caller's whole
133
+ * deadline;
134
+ * - a critical probe that does not answer STOPS the census then and there —
135
+ * the remaining probes cannot change what the caller must now do (abort and
136
+ * clean up), so waiting for them is pure latency;
137
+ * - the two unaddressed queries survive only as DECORATION, last, skipped
138
+ * entirely when the addressed probes already answered the question they
139
+ * were there to answer.
140
+ *
141
+ * Every probe still degrades one field rather than failing the census, and what
142
+ * could not be proven is NAMED (`stalled=` / `failed=`) instead of silently
143
+ * reading as a clean "nothing is open".
61
144
  */
62
145
  export function axUiStateScript() {
63
146
  return `${UI_STATE_MARKER}
64
- set frontName to ""
147
+ ${CENSUS_BODY}
148
+
149
+ return ${censusRecord("linefeed")}`;
150
+ }
151
+ /**
152
+ * The census BODY — every probe, leaving its verdict in the AppleScript variables
153
+ * the record below reads. Split out of {@link axUiStateScript} (DRVLAT1, issue
154
+ * #633) so the SAME probes can run as the in-script prelude of a keystroke hop
155
+ * ({@link axFocusGuardPrelude}) rather than as a separate osascript round-trip.
156
+ * Nothing about the probes, their budgets or their order differs between the two
157
+ * uses: it is one body, compiled into two scripts.
158
+ */
159
+ const CENSUS_BODY = `set frontName to ""
160
+ set frontIsThings to false
65
161
  set focusRole to ""
66
162
  set focusSub to ""
67
163
  set canInspect to true
@@ -70,70 +166,234 @@ set sheetForm to "none"
70
166
  set sheetKind to "none"
71
167
  set sheetDepth to 0
72
168
  set census to ""
73
- tell application "System Events"
169
+ set stalled to ""
170
+ set failed to ""
171
+ set halted to false
172
+ set t0 to (current date)
173
+
174
+ -- P1 running: a name-keyed lookup, no Accessibility round-trip.
175
+ try
176
+ with timeout of ${PROBE_TIMEOUT_S} seconds
177
+ tell application "System Events" to set thingsRunning to (exists application process "${THINGS_PROCESS}")
178
+ end timeout
179
+ on error errMsg number errNum
180
+ if errNum is -1712 then
181
+ set stalled to stalled & "running "
182
+ else
183
+ set failed to failed & "running "
184
+ end if
185
+ set halted to true
186
+ end try
187
+
188
+ -- P2 frontmost, ADDRESSED: the single fact the per-step input guard decides on.
189
+ if (not halted) and thingsRunning then
74
190
  try
75
- set fp to first application process whose frontmost is true
76
- set frontName to (name of fp) as text
77
- try
78
- set fe to value of attribute "AXFocusedUIElement" of fp
79
- set focusRole to (role of fe) as text
80
- try
81
- set focusSub to (subrole of fe) as text
82
- end try
83
- on error
84
- set canInspect to false
85
- end try
86
- on error
87
- set canInspect to false
191
+ with timeout of ${PROBE_TIMEOUT_S} seconds
192
+ tell application "System Events" to tell process "${THINGS_PROCESS}" to set frontIsThings to (frontmost as boolean)
193
+ end timeout
194
+ if frontIsThings then set frontName to "${THINGS_PROCESS}"
195
+ on error errMsg number errNum
196
+ if errNum is -1712 then
197
+ set stalled to stalled & "frontmost "
198
+ else
199
+ set failed to failed & "frontmost "
200
+ end if
201
+ set halted to true
88
202
  end try
203
+ end if
204
+
205
+ -- P3 dialog: shell, form, stack depth and control census — all addressed
206
+ -- inside the Things process, which is the shape every working drive step uses.
207
+ if (not halted) and thingsRunning then
89
208
  try
90
- if (exists application process "${THINGS_PROCESS}") then set thingsRunning to true
91
- end try
92
- end tell
93
- if thingsRunning then
94
- tell application "System Events" to tell process "${THINGS_PROCESS}"
209
+ with timeout of ${PROBE_TIMEOUT_S} seconds
210
+ tell application "System Events" to tell process "${THINGS_PROCESS}"
95
211
  ${AX_DIALOG_SHELL_SNIPPET}
96
- if shellRef is not missing value then
97
- set nCb to -1
98
- set nPu to -1
99
- set nBt to -1
100
- set nGp to -1
101
- set nTf to -1
102
- try
103
- set nCb to (count of checkboxes of shellRef)
104
- end try
105
- try
106
- set nPu to (count of pop up buttons of shellRef)
107
- end try
108
- try
109
- set nBt to (count of buttons of shellRef)
110
- end try
111
- try
112
- set nGp to (count of groups of shellRef)
113
- end try
114
- try
115
- set nTf to (count of text fields of shellRef)
116
- end try
117
- set census to "cb:" & nCb & " pu:" & nPu & " bt:" & nBt & " gp:" & nGp & " tf:" & nTf
118
- set winId to ""
212
+ if shellRef is not missing value then
213
+ set nCb to -1
214
+ set nPu to -1
215
+ set nBt to -1
216
+ set nGp to -1
217
+ set nTf to -1
218
+ try
219
+ set nCb to (count of checkboxes of shellRef)
220
+ end try
221
+ try
222
+ set nPu to (count of pop up buttons of shellRef)
223
+ end try
224
+ try
225
+ set nBt to (count of buttons of shellRef)
226
+ end try
227
+ try
228
+ set nGp to (count of groups of shellRef)
229
+ end try
230
+ try
231
+ set nTf to (count of text fields of shellRef)
232
+ end try
233
+ set census to "cb:" & nCb & " pu:" & nPu & " bt:" & nBt & " gp:" & nGp & " tf:" & nTf
234
+ set winId to ""
235
+ try
236
+ set winId to (value of attribute "AXIdentifier" of shellRef) as text
237
+ end try
238
+ if winId starts with "MovePopUpDialog-" then
239
+ set sheetKind to "move-picker"
240
+ else if nCb is 2 and nPu is 1 and nBt is 2 and nGp is 1 and nTf is 0 then
241
+ set groupOk to false
242
+ try
243
+ set g to group 1 of shellRef
244
+ if ((count of text fields of g) + (count of pop up buttons of g)) > 0 then set groupOk to true
245
+ end try
246
+ if groupOk then set sheetKind to "repeat"
247
+ end if
248
+ if sheetKind is "none" then set sheetKind to "other"
249
+ end if
250
+ end tell
251
+ end timeout
252
+ on error errMsg number errNum
253
+ if errNum is -1712 then
254
+ set stalled to stalled & "dialog "
255
+ else
256
+ set failed to failed & "dialog "
257
+ end if
258
+ set halted to true
259
+ end try
260
+ end if
261
+
262
+ -- P4 frontapp: the ONLY enumeration left, and it runs only when the addressed
263
+ -- probe has already said the screen is not ours — its whole job is naming the
264
+ -- application in the refusal sentence.
265
+ if (not halted) and (not frontIsThings) then
266
+ if ((current date) - t0) > ${CENSUS_BUDGET_S} then
267
+ set stalled to stalled & "frontapp "
268
+ else
269
+ try
270
+ with timeout of ${PROBE_TIMEOUT_S} seconds
271
+ tell application "System Events" to set frontName to (name of first application process whose frontmost is true) as text
272
+ end timeout
273
+ on error errMsg number errNum
274
+ if errNum is -1712 then
275
+ set stalled to stalled & "frontapp "
276
+ else
277
+ set failed to failed & "frontapp "
278
+ end if
279
+ end try
280
+ end if
281
+ end if
282
+
283
+ -- P5 focus: decoration, and the most expensive read in the census. Addressed at
284
+ -- the process that owns the screen rather than resolved system-wide, run last,
285
+ -- and skipped outright once the budget is spent. An ERROR here (as opposed to a
286
+ -- timeout) is the secure-system-modal signature: macOS exposes no tree for one.
287
+ if not halted then
288
+ if ((current date) - t0) > ${CENSUS_BUDGET_S} then
289
+ set stalled to stalled & "focus "
290
+ else
291
+ set focusTarget to frontName
292
+ if focusTarget is "" then
293
+ set stalled to stalled & "focus "
294
+ else
119
295
  try
120
- set winId to (value of attribute "AXIdentifier" of shellRef) as text
296
+ with timeout of ${PROBE_TIMEOUT_S} seconds
297
+ tell application "System Events" to tell process focusTarget
298
+ set fe to value of attribute "AXFocusedUIElement"
299
+ -- No focused element is a perfectly ordinary state (an app
300
+ -- with no key window); it is not an unreadable screen.
301
+ if fe is not missing value then
302
+ set focusRole to (role of fe) as text
303
+ try
304
+ set focusSub to (subrole of fe) as text
305
+ end try
306
+ end if
307
+ end tell
308
+ end timeout
309
+ on error errMsg number errNum
310
+ if errNum is -1712 then
311
+ set stalled to stalled & "focus "
312
+ else
313
+ set canInspect to false
314
+ set failed to failed & "focus "
315
+ end if
121
316
  end try
122
- if winId starts with "MovePopUpDialog-" then
123
- set sheetKind to "move-picker"
124
- else if nCb is 2 and nPu is 1 and nBt is 2 and nGp is 1 and nTf is 0 then
125
- set groupOk to false
126
- try
127
- set g to group 1 of shellRef
128
- if ((count of text fields of g) + (count of pop up buttons of g)) > 0 then set groupOk to true
129
- end try
130
- if groupOk then set sheetKind to "repeat"
131
- end if
132
- if sheetKind is "none" then set sheetKind to "other"
133
317
  end if
134
- end tell
135
- end if
136
- return "front=" & frontName & linefeed & "running=" & thingsRunning & linefeed & "form=" & sheetForm & linefeed & "depth=" & sheetDepth & linefeed & "kind=" & sheetKind & linefeed & "census=" & census & linefeed & "role=" & focusRole & linefeed & "subrole=" & focusSub & linefeed & "inspectable=" & canInspect`;
318
+ end if
319
+ end if`;
320
+ /**
321
+ * The census RECORD, joined by `sep` — `linefeed` for the stand-alone census
322
+ * script, a single-line separator for the guard prelude's `log` line (a logged
323
+ * record has to survive as ONE stderr line to be recovered).
324
+ */
325
+ function censusRecord(sep) {
326
+ return `"front=" & frontName & ${sep} & "isfront=" & frontIsThings & ${sep} & "running=" & thingsRunning & ${sep} & "form=" & sheetForm & ${sep} & "depth=" & sheetDepth & ${sep} & "kind=" & sheetKind & ${sep} & "census=" & census & ${sep} & "role=" & focusRole & ${sep} & "subrole=" & focusSub & ${sep} & "inspectable=" & canInspect & ${sep} & "stalled=" & stalled & ${sep} & "failed=" & failed`;
327
+ }
328
+ /** The stderr line prefix carrying a folded guard's census record. */
329
+ export const GUARD_LOG_PREFIX = "#FGCENSUS ";
330
+ /** The separator joining that record's fields on its single line. */
331
+ export const GUARD_LOG_SEP = " ~|~ ";
332
+ /**
333
+ * What a folded guard RAISES when it refuses. Deliberately a machine tag rather
334
+ * than a sentence: the refusal a caller reads is still built by
335
+ * {@link judgeFocusGuard} in TypeScript, from the census this same hop logged, so
336
+ * there is exactly ONE place the wording lives and the in-script judgement can
337
+ * never drift from it.
338
+ */
339
+ export const GUARD_REFUSED_TAG = "#FGREFUSE";
340
+ /**
341
+ * The census as the IN-SCRIPT PRELUDE of the hop it guards (DRVLAT1, issue #633).
342
+ *
343
+ * The per-step focus guard used to be its own osascript round-trip: census hop,
344
+ * then keystroke hop. That is both a hop of latency per typed control AND a
345
+ * TOCTOU window — the screen can change between the census that approved the
346
+ * keystroke and the keystroke itself. Prepending the census to the very script
347
+ * that types closes both: the probes and the input now run in one process, in
348
+ * order, with nothing dispatched in between.
349
+ *
350
+ * The prelude LOGS its census record (stderr, one line) and then judges it
351
+ * IN-SCRIPT, raising {@link GUARD_REFUSED_TAG} — a bare tag — when the input must
352
+ * not be sent. The caller recovers the logged record, parses it into the same
353
+ * {@link UiState} the stand-alone census yields, and asks {@link judgeFocusGuard}
354
+ * for the sentence. So the DECISION is made before the keystroke, in-script, and
355
+ * the WORDING is still single-sourced in TypeScript.
356
+ *
357
+ * `expectedSheet` is the dialog the drive has observed itself driving; pass null
358
+ * where no dialog invariant applies.
359
+ */
360
+ export function axFocusGuardPrelude(expectedSheet) {
361
+ const critical = CRITICAL_PROBES.map((p) => ` if stalled contains "${p} " then set fgBad to true
362
+ if failed contains "${p} " then set fgBad to true`).join("\n");
363
+ const sheetClause = expectedSheet === null
364
+ ? ""
365
+ : `\n if sheetKind is not "${expectedSheet}" then set fgBad to true`;
366
+ return `${UI_STATE_MARKER}
367
+ ${CENSUS_BODY}
368
+
369
+ log "${GUARD_LOG_PREFIX}" & ${censusRecord(`"${GUARD_LOG_SEP}"`)}
370
+ set fgBad to false
371
+ ${critical}
372
+ if not canInspect then set fgBad to true
373
+ if not frontIsThings then set fgBad to true${sheetClause}
374
+ if fgBad then error "${GUARD_REFUSED_TAG}"
375
+ `;
376
+ }
377
+ /**
378
+ * Recover a folded guard's census from the hop's stderr: the parsed state (null
379
+ * when no record was logged) and the stderr with that line removed, so a failure
380
+ * message a caller reads never carries the machinery.
381
+ */
382
+ export function parseGuardLog(stderr) {
383
+ const kept = [];
384
+ let record = null;
385
+ for (const line of stderr.split(/\r?\n/)) {
386
+ const at = line.indexOf(GUARD_LOG_PREFIX);
387
+ if (at >= 0) {
388
+ record = line.slice(at + GUARD_LOG_PREFIX.length);
389
+ continue;
390
+ }
391
+ kept.push(line);
392
+ }
393
+ return {
394
+ state: record === null ? null : parseUiState(record.split(GUARD_LOG_SEP).join("\n")),
395
+ stderr: kept.join("\n").trim(),
396
+ };
137
397
  }
138
398
  /**
139
399
  * Parse the census record into a {@link UiState}. Returns null when the output
@@ -150,6 +410,7 @@ export function parseUiState(stdout) {
150
410
  }
151
411
  if (!fields.has("kind") || !fields.has("inspectable"))
152
412
  return null;
413
+ const probes = (key) => (fields.get(key) ?? "").split(/\s+/).filter((p) => KNOWN_PROBES.has(p));
153
414
  const kindRaw = fields.get("kind") ?? "none";
154
415
  const sheetKind = kindRaw === "repeat" || kindRaw === "move-picker" || kindRaw === "other" ? kindRaw : "none";
155
416
  const formRaw = fields.get("form") ?? "none";
@@ -157,12 +418,17 @@ export function parseUiState(stdout) {
157
418
  const frontRaw = fields.get("front") ?? "";
158
419
  const frontmostApp = frontRaw === "" ? null : frontRaw;
159
420
  const role = fields.get("role") ?? "";
160
- const subrole = fields.get("subrole") ?? "";
421
+ // AppleScript coerces an absent attribute to the literal words "missing
422
+ // value"; that is "no subrole", not a subrole named that.
423
+ const subroleRaw = fields.get("subrole") ?? "";
424
+ const subrole = subroleRaw === "missing value" ? "" : subroleRaw;
161
425
  const census = fields.get("census") ?? "";
162
426
  const depth = Number(fields.get("depth") ?? "0");
163
427
  return {
164
428
  thingsRunning: fields.get("running") === "true",
165
- thingsFrontmost: frontmostApp === THINGS_PROCESS,
429
+ // Decided by the ADDRESSED probe, not by comparing a name the enumeration
430
+ // may never have been asked for (issue #629).
431
+ thingsFrontmost: fields.get("isfront") === "true",
166
432
  frontmostApp,
167
433
  sheetOpen: sheetKind !== "none",
168
434
  sheetKind,
@@ -171,14 +437,19 @@ export function parseUiState(stdout) {
171
437
  sheetControls: census === "" ? null : census,
172
438
  focusOwner: frontmostApp === null ? null : { app: frontmostApp, role, subrole: subrole || null },
173
439
  inspectable: fields.get("inspectable") === "true",
440
+ stalledProbes: probes("stalled"),
441
+ failedProbes: probes("failed"),
174
442
  };
175
443
  }
176
444
  /**
177
445
  * Read the census through the injected runner. Returns null on a transport
178
446
  * failure — an UNKNOWN state, which every caller treats fail-closed (the guard
179
447
  * refuses; the cleanup path does not send a blind Escape).
448
+ *
449
+ * `timeoutMs` defaults to {@link CENSUS_TIMEOUT_MS}: a census is not a drive
450
+ * step and must not borrow a drive step's patience (issue #629).
180
451
  */
181
- export async function readUiState(run, timeoutMs) {
452
+ export async function readUiState(run, timeoutMs = CENSUS_TIMEOUT_MS) {
182
453
  const res = await run({ primitive: "resolve", label: UI_STATE_LABEL, script: axUiStateScript() }, timeoutMs);
183
454
  if (!res.ok)
184
455
  return null;
@@ -194,6 +465,12 @@ export async function readUiState(run, timeoutMs) {
194
465
  export function describeFocusOwner(state) {
195
466
  if (state === null)
196
467
  return "the window state could not be read";
468
+ // #629: an inspection that did not come back says nothing about who owns the
469
+ // screen — and saying "an unidentified application is frontmost" would read
470
+ // as a measurement. Name the probe that stalled instead.
471
+ if (censusUnverifiable(state)) {
472
+ return `the window state inspection did not complete (${describeUnprovenProbes(state)})`;
473
+ }
197
474
  if (!state.inspectable) {
198
475
  return ("a system dialog owns the screen — macOS does not expose it to other apps, so it cannot be " +
199
476
  `identified from here${state.frontmostApp === null ? "" : ` (frontmost application: ${state.frontmostApp})`}`);
@@ -204,11 +481,36 @@ export function describeFocusOwner(state) {
204
481
  ? `${app} is frontmost`
205
482
  : `${app} is frontmost and keyboard focus is on a ${role}`;
206
483
  }
207
- /** A one-line human summary of the census, for a diagnostic line or a warning. */
484
+ /**
485
+ * A one-line human summary of the census, for a diagnostic line or a warning.
486
+ *
487
+ * Reports WHAT EACH PROBE PROVED. A stalled probe leaves its clause reading
488
+ * "could not be determined" and is named at the end — never omitted, and never
489
+ * rendered as its default (issue #629: a census that could not see the open
490
+ * Repeat sheet used to report a clean screen).
491
+ */
208
492
  export function describeUiState(state) {
209
- const front = state.thingsFrontmost
210
- ? "Things is frontmost"
211
- : `${state.frontmostApp ?? "an unidentified application"} is frontmost`;
493
+ const unproven = (p) => state.stalledProbes.includes(p) || state.failedProbes.includes(p);
494
+ const trailer = () => {
495
+ const unprovenText = describeUnprovenProbes(state);
496
+ return unprovenText === "" ? "" : ` — ${unprovenText}`;
497
+ };
498
+ if (unproven("running")) {
499
+ return `nothing about the screen could be established${trailer()}`;
500
+ }
501
+ if (!state.thingsRunning && !unproven("frontmost")) {
502
+ return `Things is not running${trailer()}`;
503
+ }
504
+ const front = unproven("frontmost")
505
+ ? "whether Things owns the screen could not be determined"
506
+ : state.thingsFrontmost
507
+ ? "Things is frontmost"
508
+ : unproven("frontapp")
509
+ ? "another application is frontmost (it could not be named)"
510
+ : `${state.frontmostApp ?? "an unidentified application"} is frontmost`;
511
+ if (unproven("dialog")) {
512
+ return `${front}; whether a dialog is open in Things could not be determined${trailer()}`;
513
+ }
212
514
  const sheet = state.sheetKind === "none"
213
515
  ? "no dialog is open in Things"
214
516
  : state.sheetKind === "repeat"
@@ -219,7 +521,7 @@ export function describeUiState(state) {
219
521
  // A stack means the thing in front is sitting ON another dialog, and each one
220
522
  // has to be dismissed in turn (MODALX1 §6).
221
523
  const stacked = state.sheetDepth > 1 ? `, on top of ${state.sheetDepth - 1} more` : "";
222
- return `${front}; ${sheet}${stacked}`;
524
+ return `${front}; ${sheet}${stacked}${trailer()}`;
223
525
  }
224
526
  /**
225
527
  * The warning an OPEN Things dialog earns, wherever it is reported.
@@ -1 +1 @@
1
- {"version":3,"file":"ui-state.js","sourceRoot":"","sources":["../../../src/write/vectors/ui-state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ui-state.js","sourceRoot":"","sources":["../../../src/write/vectors/ui-state.ts"],"names":[],"mappings":"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"}
@@ -63,6 +63,41 @@ export type UiRunner = (command: UiCommand, timeoutMs: number) => Promise<UiRunR
63
63
  export declare const AX_FOCUS_GUARD_HANDLERS = "on fgFrontApp()\n\tset frontName to \"\"\n\ttry\n\t\ttell application \"System Events\" to set frontName to (name of first application process whose frontmost is true) as text\n\tend try\n\treturn frontName\nend fgFrontApp\n\non fgAssertFront(what)\n\tset f to my fgFrontApp()\n\tif f is \"Things3\" then return true\n\tif f is \"\" then\n\t\terror \"refused to \" & what & \": the frontmost application could not be read, so there is no proof the keystrokes would reach Things \u2014 nothing was typed\"\n\tend if\n\terror \"refused to \" & what & \": \" & f & \" is frontmost, not Things \u2014 a keystroke goes to whatever owns the screen, so nothing was typed\"\nend fgAssertFront";
64
64
  /** resolve-element: does the element exist right now? Returns "true"/"false". */
65
65
  export declare function axResolveScript(path: string): string;
66
+ /**
67
+ * The AppleScript variable a folded candidate resolution binds the live element
68
+ * to. Every addressed script takes its target as `(<path>)`, so handing it this
69
+ * name — with {@link axCandidatePrelude} in front — makes the resolution and the
70
+ * action ONE hop instead of two (DRVLAT1, issue #633).
71
+ */
72
+ export declare const STEP_ELEMENT_REF = "fgStepEl";
73
+ /**
74
+ * The message a folded candidate resolution raises when NONE of a step's expected
75
+ * element shapes appeared. Byte-identical to the driver's own wording for the
76
+ * separate-hop resolution it replaces, so a report reads the same either way.
77
+ */
78
+ export declare const CANDIDATES_MISSED: string;
79
+ /**
80
+ * IN-SCRIPT CANDIDATE RESOLUTION (DRVLAT1, issue #633).
81
+ *
82
+ * A candidate-addressed step used to dispatch one `resolve` hop PER CANDIDATE PER
83
+ * POLL ROUND before the hop that acted on whichever answered — a process spawn
84
+ * each, on top of a 300ms JS-side poll floor. This prelude does the same work in
85
+ * the acting hop's own process: it polls the candidates in the SAME priority
86
+ * order, binds the first that exists to {@link STEP_ELEMENT_REF}, and fails closed
87
+ * with {@link CANDIDATES_MISSED} when the window elapses with none of them there.
88
+ *
89
+ * Collapsing it is also strictly better against TOCTOU: the element the script
90
+ * acts on is the one it just proved exists, with nothing dispatched in between.
91
+ */
92
+ export declare function axCandidatePrelude(paths: string[], timeoutMs?: number): string;
93
+ /**
94
+ * wait: poll for ANY of the awaited element shapes to appear, IN-SCRIPT (DRVLAT1).
95
+ * One hop for the whole wait instead of one per candidate per round — which is
96
+ * what a slow host paid most: the dialog the drive is waiting for is exactly the
97
+ * thing that is slow when the app is busy. Returns "true", or "false" when the
98
+ * window elapses with none of them present (the driver's abort path is unchanged).
99
+ */
100
+ export declare function axWaitAnyScript(paths: string[], timeoutMs: number): string;
66
101
  /** press: AXPress the element. */
67
102
  export declare function axPressScript(path: string): string;
68
103
  /**
@@ -458,7 +493,7 @@ export declare function axSelectHeadingRowScript(tablePath: string, ordinal: num
458
493
  * naming expected vs observed. Pure System Events + Things scripting, background-
459
494
  * capable. One stable command shape per primitive.
460
495
  */
461
- export declare function axAssertEligibleScript(targetUuid: string, menuItemPath: string): string;
496
+ export declare function axAssertEligibleScript(targetUuid: string, menuItemPath: string, settleMs?: number): string;
462
497
  /** activate: foreground Things (the fallback preamble step). */
463
498
  export declare function axActivateScript(): string;
464
499
  /**
@@ -558,6 +593,15 @@ export declare function axAbortScript(): string;
558
593
  * the census to decide that.
559
594
  */
560
595
  export declare function axCancelDialogScript(): string;
596
+ /**
597
+ * The Cancel button's on-screen FRAME, resolved through the same addressed
598
+ * dialog-shell path the AXPress dismissal uses (issue #629). Feeds the pointer
599
+ * fallback: if `AXPress` on the button reports success and the dialog is still
600
+ * standing, a real click at the button's own AX-resolved centre is the next
601
+ * thing to try before discarding the window wholesale. The frame comes from the
602
+ * tree, never from a remembered coordinate, so a moved dialog fails closed.
603
+ */
604
+ export declare function axCancelFrameScript(): string;
561
605
  /**
562
606
  * The PROVEN app-level clearance / relocation maneuver (SESSGATE, #480, live-host
563
607
  * recovery): close the front Things window — which takes an attached modal sheet
@@ -606,6 +650,13 @@ export interface ClearResult {
606
650
  sheetKind?: UiSheetKind;
607
651
  /** Who owned the screen when the cleanup started, when it was not Things. */
608
652
  focusOwner?: string;
653
+ /**
654
+ * True when the cleanup ran WITHOUT a working window-state inspection (issue
655
+ * #629) — the dismissal and its proof came from addressed reads alone, so the
656
+ * outcome is real but the identity of what was dismissed was taken from what
657
+ * this drive had already observed rather than re-confirmed.
658
+ */
659
+ unverified?: boolean;
609
660
  }
610
661
  /**
611
662
  * resolve-frame: read the element's on-screen frame (top-left origin, points)
@@ -693,7 +744,16 @@ export declare function judgeFocusGuard(state: UiState | null, expectedSheet: Ui
693
744
  * cannot see and would otherwise discover hours later on another device.
694
745
  */
695
746
  export declare function describeCleanup(clear: ClearResult): string;
696
- /** Compile one recipe step into its primitive command (no dispatch). */
747
+ /**
748
+ * Compile one recipe step into its primitive command (no dispatch).
749
+ *
750
+ * A CANDIDATE-ADDRESSED step compiles to ONE script that resolves its own element
751
+ * and then acts on it (DRVLAT1, issue #633): the addressed body is generated
752
+ * against {@link STEP_ELEMENT_REF} and {@link axCandidatePrelude} is prepended, so
753
+ * the resolution that used to be a separate `resolve` hop (or several) now rides
754
+ * the acting hop. Steps whose script is JXA, or that resolve their own target,
755
+ * are unaffected.
756
+ */
697
757
  export declare function commandForStep(step: UiStep, targetUuid: string): UiCommand;
698
758
  /**
699
759
  * The ui vector. Config-gated: when `ui.enabled` is false the matrix reports