@object-ui/core 17.3.0 → 17.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +2018 -0
  2. package/README.md +38 -34
  3. package/dist/actions/ActionEngine.d.ts +43 -26
  4. package/dist/actions/ActionEngine.js +67 -54
  5. package/dist/actions/ActionRunner.d.ts +281 -31
  6. package/dist/actions/ActionRunner.js +458 -36
  7. package/dist/actions/actionKeys.d.ts +163 -27
  8. package/dist/actions/actionKeys.js +238 -27
  9. package/dist/actions/actionResponse.js +1 -1
  10. package/dist/actions/index.d.ts +1 -0
  11. package/dist/actions/index.js +1 -0
  12. package/dist/actions/recordIdParam.d.ts +77 -0
  13. package/dist/actions/recordIdParam.js +37 -0
  14. package/dist/actions/serverActionHandler.d.ts +1 -1
  15. package/dist/actions/serverActionHandler.js +1 -1
  16. package/dist/data-scope/ViewDataProvider.d.ts +37 -12
  17. package/dist/data-scope/ViewDataProvider.js +83 -5
  18. package/dist/data-scope/element-data-source.d.ts +113 -0
  19. package/dist/data-scope/element-data-source.js +216 -0
  20. package/dist/data-scope/index.d.ts +2 -1
  21. package/dist/data-scope/index.js +1 -0
  22. package/dist/evaluator/ExpressionEvaluator.d.ts +42 -1
  23. package/dist/evaluator/ExpressionEvaluator.js +42 -1
  24. package/dist/evaluator/SafeExpressionParser.js +0 -4
  25. package/dist/evaluator/declaredPredicate.d.ts +119 -0
  26. package/dist/evaluator/declaredPredicate.js +152 -0
  27. package/dist/evaluator/fieldRules.d.ts +35 -0
  28. package/dist/evaluator/fieldRules.js +79 -20
  29. package/dist/evaluator/index.d.ts +2 -0
  30. package/dist/evaluator/index.js +2 -0
  31. package/dist/evaluator/listConditional.d.ts +54 -4
  32. package/dist/evaluator/listConditional.js +125 -19
  33. package/dist/evaluator/optionRules.d.ts +55 -0
  34. package/dist/evaluator/optionRules.js +60 -0
  35. package/dist/evaluator/predicateInput.d.ts +51 -3
  36. package/dist/evaluator/predicateInput.js +62 -5
  37. package/dist/evaluator/rowPredicateCanon.d.ts +67 -0
  38. package/dist/evaluator/rowPredicateCanon.js +208 -0
  39. package/dist/index.d.ts +8 -1
  40. package/dist/index.js +31 -1
  41. package/dist/protocols/DndProtocol.d.ts +42 -1
  42. package/dist/protocols/KeyboardProtocol.d.ts +31 -1
  43. package/dist/protocols/SharingProtocol.d.ts +11 -1
  44. package/dist/registry/Registry.d.ts +62 -16
  45. package/dist/theme/ThemeEngine.d.ts +0 -8
  46. package/dist/theme/ThemeEngine.js +22 -103
  47. package/dist/theme/index.d.ts +1 -1
  48. package/dist/theme/index.js +7 -1
  49. package/dist/types/index.d.ts +125 -12
  50. package/dist/utils/chart-presentation.d.ts +205 -0
  51. package/dist/utils/chart-presentation.js +280 -0
  52. package/dist/utils/chart-series.d.ts +390 -23
  53. package/dist/utils/chart-series.js +776 -32
  54. package/dist/utils/column-identity.d.ts +43 -0
  55. package/dist/utils/column-identity.js +47 -1
  56. package/dist/utils/compare-to.d.ts +40 -13
  57. package/dist/utils/compare-to.js +4 -32
  58. package/dist/utils/dashboard-filters.d.ts +54 -8
  59. package/dist/utils/dashboard-filters.js +261 -12
  60. package/dist/utils/dataset-format.d.ts +73 -14
  61. package/dist/utils/dataset-format.js +142 -9
  62. package/dist/utils/dataset-pivot.d.ts +28 -7
  63. package/dist/utils/dataset-pivot.js +27 -6
  64. package/dist/utils/debug.js +7 -0
  65. package/dist/utils/dom-props.d.ts +137 -0
  66. package/dist/utils/dom-props.js +153 -0
  67. package/dist/utils/expand-fields.d.ts +97 -7
  68. package/dist/utils/expand-fields.js +97 -7
  69. package/dist/utils/filter-tokens.js +0 -1
  70. package/dist/utils/freeze-schema.d.ts +38 -1
  71. package/dist/utils/freeze-schema.js +10 -0
  72. package/dist/utils/humanize-label.d.ts +60 -0
  73. package/dist/utils/humanize-label.js +62 -0
  74. package/dist/utils/managedBy.d.ts +27 -2
  75. package/dist/utils/normalize-list-view.d.ts +36 -6
  76. package/dist/utils/normalize-list-view.js +93 -9
  77. package/dist/utils/number-display.d.ts +155 -0
  78. package/dist/utils/number-display.js +71 -0
  79. package/dist/utils/predicate-fields.d.ts +29 -4
  80. package/dist/utils/predicate-fields.js +44 -4
  81. package/dist/utils/resolve-view-id.d.ts +31 -0
  82. package/dist/utils/resolve-view-id.js +45 -0
  83. package/dist/utils/retired-field-types.d.ts +90 -0
  84. package/dist/utils/retired-field-types.js +124 -0
  85. package/dist/utils/sort-query.d.ts +57 -0
  86. package/dist/utils/sort-query.js +35 -0
  87. package/dist/utils/unmaterialized-fields.d.ts +52 -0
  88. package/dist/utils/unmaterialized-fields.js +57 -0
  89. package/dist/validation/index.d.ts +1 -0
  90. package/dist/validation/index.js +1 -0
  91. package/dist/validation/schema-validator.js +105 -0
  92. package/dist/validation/server-owned-value.d.ts +62 -0
  93. package/dist/validation/server-owned-value.js +99 -0
  94. package/dist/validation/validators/object-validation-engine.js +0 -2
  95. package/package.json +5 -5
  96. package/dist/__benchmarks__/core.bench.d.ts +0 -8
  97. package/dist/__benchmarks__/core.bench.js +0 -53
  98. package/dist/utils/merge-views-into-objects.d.ts +0 -19
  99. package/dist/utils/merge-views-into-objects.js +0 -39
@@ -5,9 +5,24 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
- import type { ActionInput as SpecActionInput } from '@objectstack/spec/ui';
9
- import { ExpressionEvaluator } from '../evaluator/ExpressionEvaluator';
10
- import { type UndoableOperation } from './UndoManager';
8
+ /**
9
+ * @object-ui/core - Action Runner
10
+ *
11
+ * Executes actions defined in ActionSchema and EventHandler.
12
+ *
13
+ * Dispatches every `@objectstack/spec` `ActionType` — `script`, `url`, `modal`,
14
+ * `flow`, `api`, `form` — plus the one declared renderer-local alias,
15
+ * `navigation` (see `ObjectUiLocalActionType`). The table that does it is typed
16
+ * against that vocabulary, so this list cannot go stale the way the old comment
17
+ * had (it still claimed five types after `form` shipped).
18
+ *
19
+ * Features: conditional execution, confirmation, toast notifications,
20
+ * redirect handling, action chaining, custom handler registration.
21
+ */
22
+ import type { UIActionSchema } from '@object-ui/types';
23
+ import type { Action as SpecActionInput } from '@objectstack/spec/ui';
24
+ import { ExpressionEvaluator } from '../evaluator/ExpressionEvaluator.js';
25
+ import { type UndoableOperation } from './UndoManager.js';
11
26
  export interface ActionResult {
12
27
  success: boolean;
13
28
  data?: any;
@@ -61,6 +76,30 @@ export interface ApiConfig {
61
76
  /**
62
77
  * Action definition accepted by the runner.
63
78
  * Compatible with both UIActionSchema (spec v2.0.1) and legacy crud.ts ActionSchema.
79
+ *
80
+ * ── CLOSED since objectstack#4075 step 3 ──────────────────────────────────────
81
+ *
82
+ * This interface used to end with `[key: string]: any`, so it accepted any key
83
+ * of any type: deleting `execute` from it produced ZERO compile errors, a typo
84
+ * (`targt`, `exectue`) type-checked, and a tombstoned key sailed through to a
85
+ * runner that then silently did nothing — the objectstack#2169 "Mark Done does
86
+ * nothing" shape. The index signature is gone. Every key this type accepts is
87
+ * now written down below, and `tsc` rejects the rest at any site that AUTHORS an
88
+ * action literal in code.
89
+ *
90
+ * What it does NOT reach, and what still covers that: stored `sys_metadata` rows
91
+ * are rehydrated UNPARSED (objectstack#3903), so authored metadata arrives at
92
+ * `runner.execute()` as a plain object the compiler never saw. The dev-mode
93
+ * `warnOnUnknownActionKeys` shim from step 1 therefore STAYS — it is not
94
+ * redundant with the closed type, it is the other half of the same surface, and
95
+ * the two halves catch disjoint populations (code-authored literals vs. stored
96
+ * rows).
97
+ *
98
+ * {@link ActionContext} keeps ITS index signature, deliberately. It is a runtime
99
+ * data bag whose keys are genuinely open (`data`, `record`, `pageVariables`,
100
+ * `user`, plus whatever a host passes); this is a declared metadata contract
101
+ * mirroring `@objectstack/spec`'s `ActionSchema`. Open key set on a data bag is
102
+ * correct; open key set on a contract is the bug above.
64
103
  */
65
104
  export interface ActionDef {
66
105
  /** Action type identifier — a `RunnableActionType` (the spec's six plus the
@@ -77,19 +116,65 @@ export interface ActionDef {
77
116
  name?: string;
78
117
  /** Display label */
79
118
  label?: string;
80
- /** Confirmation text — shows a confirm dialog before executing */
119
+ /**
120
+ * Human description of the action, shown as the param-collection dialog's
121
+ * subtitle (`useConsoleActionRuntime` reads it beside `label`).
122
+ *
123
+ * objectui vocabulary, not spec: `@objectstack/spec`'s `ActionSchema` has no
124
+ * `description`, so this derives from `@object-ui/types`' renderer view of an
125
+ * action (`UIActionSchema`, `ui-action.ts`) — the same "authorable for a
126
+ * declared surface" source `check:action-forward-parity` derives its owed set
127
+ * from, which is why that gate already requires all four action renderers to
128
+ * forward this key (objectui#4192: `action:menu`'s dialog titled itself
129
+ * "Action parameters" instead of naming the action).
130
+ *
131
+ * Promoted by step 3 (objectstack#4075): the key was authorable, forwarded
132
+ * and read, but `ActionDef` never declared it, so it reached its reader only
133
+ * through `[key: string]: any`. Deleting the index signature is what surfaced
134
+ * it — the two TS2353s the deletion produced repo-wide were both this key.
135
+ */
136
+ description?: UIActionSchema['description'];
137
+ /**
138
+ * Confirmation text — shows a confirm dialog before executing. The ONE
139
+ * confirm spelling: `@objectstack/spec`'s action surface and the
140
+ * TranslationBundle key (`{ns}.objects.{obj}._actions.{name}.confirmText`)
141
+ * both stand on it.
142
+ */
81
143
  confirmText?: string;
82
- /** Structured confirmation (from crud.ts) */
83
- confirm?: {
84
- title?: string;
85
- message?: string;
86
- confirmText?: string;
87
- cancelText?: string;
88
- };
89
- /** Condition expression — if falsy, skip action */
90
- condition?: string;
91
- /** Disabled expression — if truthy, skip action */
92
- disabled?: string | boolean;
144
+ /**
145
+ * RETIRED (objectui#4314): the structured confirm object used to sit here
146
+ * and its `message` OUTRANKED `confirmText` — untranslated, since no bundle
147
+ * key exists for it. `?: never` so constructing an action with it is a tsc
148
+ * error rather than a silently-ignored key; the authorable twin
149
+ * (`@object-ui/types` `crud.ts`) carries the same tombstone.
150
+ * @deprecated Retired — author `confirmText` instead.
151
+ */
152
+ confirm?: never;
153
+ /**
154
+ * Condition predicate — the action executes only while it holds.
155
+ *
156
+ * A boolean, a bare CEL string, a `${…}` template, or the normalized
157
+ * `{ dialect, source }` envelope `objectstack build` emits. Declared and
158
+ * FALSE skips the action; not declared at all (absent / empty predicate)
159
+ * executes. `boolean` is spelled out here because the execution gate now
160
+ * honours `condition: false` as the verdict it plainly is (objectui#3872) —
161
+ * the same widening `disabled` already carries below.
162
+ */
163
+ condition?: string | boolean;
164
+ /**
165
+ * Disabled predicate — the control renders inert while it holds.
166
+ *
167
+ * Same three arms as {@link ActionDef.visible}, derived from the same spec
168
+ * field, because the 2026-08-06 maintainer ruling on objectstack#4075 gave
169
+ * both keys ONE shape: `boolean | string(CEL) | { dialect, source }` —
170
+ * "boolean = 条件的退化形字面量,string = CEL 简写,信封 = 完整形". The
171
+ * envelope arm arrived in `@objectstack/spec` 17.0.0-rc.6 (objectstack#5970,
172
+ * PR objectstack#6450); until then this was a hand-written `string | boolean`
173
+ * that could not describe the envelope, which is why `DeclaredActionsBar`
174
+ * read it through an `(action as any).disabled` cast. Derived, not restated,
175
+ * so the two keys cannot drift apart again.
176
+ */
177
+ disabled?: SpecActionInput['disabled'];
93
178
  /**
94
179
  * API endpoint (string URL or complex config).
95
180
  *
@@ -121,6 +206,19 @@ export interface ActionDef {
121
206
  * today (objectstack#4075 step 2).
122
207
  */
123
208
  navigate?: any;
209
+ /** `navigation` alias: the target URL. `executeNavigation` resolves
210
+ * `to || target || redirect`, so a flat spec-style `target` also works. */
211
+ to?: string;
212
+ /** `navigation` alias: force the external-URL treatment that the scheme
213
+ * heuristic would otherwise decide. */
214
+ external?: boolean;
215
+ /** `navigation` alias: open in a new tab. The first-class spec switch is
216
+ * `openIn: 'new-tab'`, which WINS over this one — prefer it in new metadata.
217
+ * Unrelated to the retired `params.newTab` read (objectui#4097). */
218
+ newTab?: boolean;
219
+ /** `navigation` alias: replace the current history entry instead of pushing
220
+ * a new one. No `ActionSchema` counterpart in any spelling. */
221
+ replace?: boolean;
124
222
  /** onClick callback (legacy) */
125
223
  onClick?: () => void | Promise<void>;
126
224
  /** Whether to reload data after success */
@@ -213,16 +311,23 @@ export interface ActionDef {
213
311
  /**
214
312
  * Visibility predicate, evaluated against the runner's context.
215
313
  *
216
- * The `boolean` arm is objectui's own, deliberately wider than the spec:
217
- * `ActionSchema.visible` admits only a CEL string or a `{ dialect, source }`
218
- * envelope, while `ActionEngine.getActionsForLocation` also honours a literal
219
- * `visible: true` / `false` (pinned by `ActionEngine.visibility.test.ts`).
220
- * Declared rather than left to the index signature so the tolerance is
221
- * auditable instead of invisible — but it IS a dialect, and objectstack#4075
222
- * step 3 has to decide whether the spec adopts the boolean or objectui drops
223
- * it. Do not widen this further.
224
- */
225
- visible?: SpecActionInput['visible'] | boolean;
314
+ * Three arms — `boolean | string(CEL) | { dialect, source }` — and all three
315
+ * now come from the spec. Step 2 had to write `SpecActionInput['visible'] |
316
+ * boolean` because the literal `visible: true` / `false` that
317
+ * `ActionEngine.getActionsForLocation` honours (pinned by
318
+ * `ActionEngine.visibility.test.ts`) was objectui tolerance the spec did not
319
+ * share, and it left the open question step 3 was blocked on: does the spec
320
+ * adopt the boolean, or does objectui drop it?
321
+ *
322
+ * The maintainer ruled ADOPT on 2026-08-06 (objectstack#4075): "统一形状,
323
+ * spec 采纳 —— `visible` / `disabled` 两键在 spec 侧统一收敛为
324
+ * `boolean | string(CEL) | {dialect, source}`(boolean = 条件的退化形字面量,
325
+ * string = CEL 简写,信封 = 完整形)". `@objectstack/spec` 17.0.0-rc.6 carries
326
+ * it (objectstack#5970, PR objectstack#6450), so the local `| boolean` is no
327
+ * longer a tolerance to declare — it is part of the derived type, and adding
328
+ * it back would restate a spec arm rather than widen anything.
329
+ */
330
+ visible?: SpecActionInput['visible'];
226
331
  /** System permissions the caller must ALL hold for the action to be offered
227
332
  * (ADR-0066 D4 UI half — the server enforces the source of truth). */
228
333
  requiredPermissions?: SpecActionInput['requiredPermissions'];
@@ -268,8 +373,6 @@ export interface ActionDef {
268
373
  * remains available to a HOST passing it explicitly.
269
374
  */
270
375
  bulkEnabled?: SpecActionInput['bulkEnabled'];
271
- /** Any additional properties */
272
- [key: string]: any;
273
376
  }
274
377
  /**
275
378
  * A handler registered with {@link ActionRunner} — the client-side action
@@ -286,6 +389,29 @@ export type ActionRunnerHandler = (action: ActionDef, context: ActionContext) =>
286
389
  /**
287
390
  * Confirmation handler — replaces window.confirm.
288
391
  * Consumers can provide an async implementation (e.g., Shadcn AlertDialog).
392
+ *
393
+ * `options` is LIVE, and its producer is not in this file — searching the
394
+ * runner alone will tell you otherwise. The only call site here passes one
395
+ * argument (the structured `confirm` arm that used to forward a bag was
396
+ * retired, objectui#4314), so a runner-scoped sweep reads this parameter as
397
+ * declared-but-never-produced and files it for removal. That has already
398
+ * happened once: objectui#5205, ruled KEEP on 2026-08-22.
399
+ *
400
+ * The producer bypasses the runner entirely. `handleDeleteView` in
401
+ * `packages/app-shell/src/views/ObjectView.tsx` — the admin "delete saved
402
+ * view" confirmation — invokes a `ConfirmationHandler` with all three fields
403
+ * populated and localized (`console.objectView.deleteViewTitle` / `.delete` /
404
+ * `.cancel`), and `app-shell`'s `ActionConfirmDialog.tsx` renders them in
405
+ * place of its generic `actionConfirm.*` fallbacks. Line numbers are left out
406
+ * on purpose: #5205's pointer had already drifted by the time it was executed.
407
+ *
408
+ * So re-measure across the repo, not this package: grep for two-argument
409
+ * calls of `ConfirmationHandler`-typed values (`confirmHandler(`, `onConfirm(`)
410
+ * in `app-shell` as well as here. Dropping `options` costs that dialog its
411
+ * title and both button labels, and narrows a published `.d.ts` against the
412
+ * natural `(message, options) => …` spelling — which TypeScript rejects
413
+ * against a one-parameter target unless the second parameter is written
414
+ * optional.
289
415
  */
290
416
  export type ConfirmationHandler = (message: string, options?: {
291
417
  title?: string;
@@ -351,6 +477,31 @@ export interface ResultDialogSpec {
351
477
  * cleanup purposes (the action already succeeded).
352
478
  */
353
479
  export type ResultDialogHandler = (spec: ResultDialogSpec, data: unknown, action?: ActionDef) => Promise<void>;
480
+ /**
481
+ * One entry of a param's option list, as the RESOLVED param carries it.
482
+ *
483
+ * `label` / `value` are the two keys this layer itself reads and rewrites (i18n
484
+ * translation of the label, value matching). Everything else an option declares
485
+ * rides along in the catch-all and reaches the widgets untouched: per-option
486
+ * `visibleWhen` (the CEL predicate `resolveVisibleOptions()` filters on —
487
+ * ADR-0058 / #2284), `color`, `icon`, `disabled`.
488
+ *
489
+ * The catch-all is deliberate rather than a closed key list (objectui#3559).
490
+ * A field's option vocabulary is owned by the field metadata (`@objectstack/spec`'s
491
+ * `SelectOptionSchema`) and read by the option widgets; a param's option list is
492
+ * only a CONDUIT between the two. When this type restated that vocabulary as
493
+ * `{ label, value }`, the resolver dutifully rebuilt every inherited option to
494
+ * match and a field's `visibleWhen` stopped narrowing the dialog's list — the
495
+ * same field metadata behaving two ways on two surfaces. Naming the two keys
496
+ * this layer uses and passing the rest through is what keeps that from
497
+ * recurring; it is NOT an invitation to author new resolved-only option keys
498
+ * (the spec's schema is the authoring gate, and it is `.strict()`).
499
+ */
500
+ export type ActionParamOption = {
501
+ label: string;
502
+ value: string;
503
+ [key: string]: unknown;
504
+ };
354
505
  /**
355
506
  * ActionParam definition accepted by the runner.
356
507
  * Compatible with @objectstack/spec ActionParam.
@@ -373,10 +524,7 @@ export interface ActionParamDef {
373
524
  label: string;
374
525
  type: string;
375
526
  required?: boolean;
376
- options?: Array<{
377
- label: string;
378
- value: string;
379
- }>;
527
+ options?: ActionParamOption[];
380
528
  defaultValue?: unknown;
381
529
  helpText?: string;
382
530
  placeholder?: string;
@@ -486,6 +634,30 @@ export declare class ActionRunner {
486
634
  * Post-execution: emit toast notifications, handle chaining, callbacks.
487
635
  */
488
636
  private handlePostExecution;
637
+ /**
638
+ * Perform the `ActionSchema.onSuccess` hop for an action that just succeeded.
639
+ *
640
+ * **The router is the app's own.** `navigationHandler` is the seam every
641
+ * navigator in this file already goes through, and the console wires it to
642
+ * react-router's `navigate` — so `openIn: 'self'` is a real SPA route hop,
643
+ * in place, immune to popup blocking. No `window.location` / `window.open`
644
+ * mechanism is introduced here; the no-handler fallback is the same
645
+ * `result.redirect` hand-off `navigateTo` already uses.
646
+ *
647
+ * **No default is written here.** `openIn` is a materialised
648
+ * `.default('self')` in the spec, so parse output always carries a resolved
649
+ * member; this reads the ONE member that changes the branch and leaves the
650
+ * other implicit. A `?? 'self'` would be a second source of truth for a
651
+ * default the producer already resolved.
652
+ *
653
+ * **The two `openIn` spellings never cross.** This reads
654
+ * `onSuccess.openIn` (`'self' | 'newTab'`) and nothing else — never the
655
+ * top-level `type: 'url'` switch, which spells its new-tab member
656
+ * `'new-tab'`. Spec refuses each crossover spelling with a keyed error; a
657
+ * renderer that accepted either would be a second, looser contract than the
658
+ * one authors are validated against.
659
+ */
660
+ private navigateOnSuccess;
489
661
  /**
490
662
  * Execute script action — evaluates client-side expression via ExpressionEvaluator.
491
663
  * Supports ${} template expressions referencing data, record, user context.
@@ -543,6 +715,37 @@ export declare class ActionRunner {
543
715
  * through to `executeActionSchema` and silently no-opped (the "Log Time does
544
716
  * nothing" report). The current record id is forwarded as `?recordId=` for
545
717
  * hosts that support it; the form route ignores unknown query params.
718
+ *
719
+ * ## An id never travels without its object (objectui#4292)
720
+ *
721
+ * `?recordId=` names an id and nothing else, so the consumer has to pick an
722
+ * object to resolve it against, and the only one it can pick is the FormView's
723
+ * own target object (`/forms/:name` does exactly that since objectui#4278).
724
+ * That is right whenever the action fired from a record OF that object, and
725
+ * nothing here used to check that it did: `target: 'showcase_task.edit'` on an
726
+ * Account record header is publishable metadata, and with per-table integer
727
+ * keys `GET /data/showcase_task/42` cheerfully answers for Account 42. Before
728
+ * #4278 the damage stopped at a duplicate insert; now that the route honours
729
+ * the param, the same mis-scoped action PATCHes a different object's record
730
+ * with no error anywhere. So the id is forwarded only when the two objects
731
+ * MATCH, and the object identity rides along as the consumer's second half of
732
+ * the check ({@link FORM_RECORD_OBJECT_PARAM}).
733
+ *
734
+ * The three outcomes, and why the third preserves rather than refuses:
735
+ *
736
+ * - **match** — forward `?recordId=&recordObject=`. The #4278 edit path,
737
+ * plus the identity the consumer re-checks against its own view metadata;
738
+ * - **mismatch** — forward NEITHER, i.e. the bare `/forms/:name`. A "log
739
+ * time" action fired from a Task at another object's create view is not a
740
+ * broken edit, it is a CREATE that happens to be launched from a record —
741
+ * which is exactly what this route did before #4278 taught it to read the
742
+ * param. Refusing here would break a shape that works;
743
+ * - **not comparable** — either end unknown ⇒ forward as before. A host that
744
+ * publishes no `objectName`, or an unqualified target name, gives this
745
+ * runner nothing to compare; tightening on absent information would turn
746
+ * working edit flows into creates on every such host. The guard only ever
747
+ * fires on information it actually has, and the consumer half applies the
748
+ * same rule to a missing `recordObject`.
546
749
  */
547
750
  private executeForm;
548
751
  /**
@@ -556,6 +759,35 @@ export declare class ActionRunner {
556
759
  * drift apart again. Prefer `type: 'url'` + `openIn` in new metadata.
557
760
  */
558
761
  private executeNavigation;
762
+ /**
763
+ * Assemble the request body for the runner's OWN `executeAPI` — the path taken
764
+ * when no host registered an `api` handler.
765
+ *
766
+ * `bodyExtra` is merged LAST, which is the spec's documented semantics for the
767
+ * key ("Static request-body fields for a `type:'api'` action, merged last
768
+ * (overrides user params)") and what the console's `apiHandler` already does on
769
+ * both of its branches. Before objectstack#6837 this path read `action.params`
770
+ * alone and never looked at `bodyExtra` at all, so an action whose payload lives
771
+ * entirely in `bodyExtra` — the shape the ADR-0087 conversion produces from an
772
+ * old object-form `params` page — POSTed an empty body here.
773
+ *
774
+ * `params` contributes only in its DEPRECATED non-array form (the compat window
775
+ * #5777 opened; see `warnOnDeprecatedObjectParams`). An ARRAY `params` is a
776
+ * parameter DEFINITION list, not a payload: it reaches this method unconsumed
777
+ * only when no `paramCollectionHandler` is mounted, and POSTing the definitions
778
+ * as the request body was never a payload any endpoint wanted. It now falls
779
+ * through to the context record like an absent `params` does.
780
+ *
781
+ * `bodyShape` then decides how that payload is WRAPPED, and the wrap covers the
782
+ * collected payload ONLY — `bodyExtra` stays at the top level. That is the
783
+ * spec's own wording for the key ("`{ wrap: 'data' }` nests the user-collected
784
+ * params under that key, while `recordIdParam` and other top-level keys stay
785
+ * flat") and what both console read-sites already do, so the key means one
786
+ * thing on every path that honours it. Until objectstack#6938 this path did not
787
+ * read it at all: the console `apiHandler` wrapped and the runner's own fallback
788
+ * did not, so the same action changed body shape with the host it ran under.
789
+ */
790
+ private buildApiRequestBody;
559
791
  /**
560
792
  * Execute API action — supports both simple string endpoint and complex ApiConfig.
561
793
  */
@@ -609,3 +841,21 @@ export declare class ActionRunner {
609
841
  * Convenience function to execute a single action with a one-off runner.
610
842
  */
611
843
  export declare function executeAction(action: ActionDef, context?: ActionContext): Promise<ActionResult>;
844
+ /** The `ActionSchema.onSuccess` block, as the pinned spec declares it. */
845
+ export interface OnSuccessNavigation {
846
+ navigate: string;
847
+ /** `'self' | 'newTab'` — read as data, so an off-contract value cannot widen the branch. */
848
+ openIn?: unknown;
849
+ }
850
+ /**
851
+ * Is this `onSuccess` the SPEC's navigation block, or this runner's older
852
+ * chained-callback channel (`ActionDef | ActionDef[]`)?
853
+ *
854
+ * The test IS the spec's declaration: a non-array object carrying a STRING
855
+ * `navigate`. Nothing else can produce that shape — the spec object is strict
856
+ * with `navigate: z.string()` required, and on a callback `ActionDef`,
857
+ * `navigate` is the deprecated nested navigation ENVELOPE that
858
+ * `executeNavigation` reads `to`/`target`/`redirect` off, so a bare string
859
+ * there has never been runnable.
860
+ */
861
+ export declare function readOnSuccessNavigation(value: unknown): OnSuccessNavigation | null;