@cynodia/axiom-core 0.6.0-alpha.1 → 0.6.2-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,13 +21,13 @@ builders, `Presentation`, `Theme`, `resolvePresentationMap`, `validateGraph`,
21
21
  ## Installation
22
22
 
23
23
  ```bash
24
- npm install @cynodia/axiom-core@alpha
24
+ npm install @cynodia/axiom-core
25
25
  ```
26
26
 
27
27
  Most applications should install the facade package instead, which re-exports this one:
28
28
 
29
29
  ```bash
30
- npm install @cynodia/axiom@alpha
30
+ npm install @cynodia/axiom
31
31
  ```
32
32
 
33
33
 
@@ -71,5 +71,6 @@ export declare const VALIDATION_CODES: {
71
71
  readonly principalReferenceOnClient: "PRINCIPAL_REFERENCE_ON_CLIENT";
72
72
  readonly serverOnlyStateObserved: "SERVER_ONLY_STATE_OBSERVED";
73
73
  readonly invalidPrincipalEntity: "INVALID_PRINCIPAL_ENTITY";
74
+ readonly missingActionArgument: "MISSING_ACTION_ARGUMENT";
74
75
  };
75
76
  //# sourceMappingURL=diagnostics.d.ts.map
@@ -58,4 +58,5 @@ export const VALIDATION_CODES = {
58
58
  principalReferenceOnClient: 'PRINCIPAL_REFERENCE_ON_CLIENT',
59
59
  serverOnlyStateObserved: 'SERVER_ONLY_STATE_OBSERVED',
60
60
  invalidPrincipalEntity: 'INVALID_PRINCIPAL_ENTITY',
61
+ missingActionArgument: 'MISSING_ACTION_ARGUMENT',
61
62
  };
package/dist/graph.js CHANGED
@@ -23,7 +23,7 @@ export class ApplicationGraph {
23
23
  /** Bumped by every change, so the derived edge index can never serve stale data. */
24
24
  revision = 0;
25
25
  semanticIndex;
26
- constructor(id, name, version = '0.6.0') {
26
+ constructor(id, name, version = '0.6.2') {
27
27
  this.data = { id, name, version, nodes: {}, edges: {} };
28
28
  }
29
29
  get id() {
@@ -1,4 +1,4 @@
1
- import { isUINode, uiChildIds } from './ui.js';
1
+ import { formSubmitActionId, isUINode, uiChildIds } from './ui.js';
2
2
  import { TEXT_ROLE_HEADING_LEVELS, normalizeDensity, normalizeLayout, normalizePadding, normalizeRole, } from './presentation.js';
3
3
  import { DEFAULT_THEME } from './theme.js';
4
4
  /**
@@ -249,7 +249,10 @@ function semanticLayer(node, index) {
249
249
  const layer = { origin: 'inferred' };
250
250
  const formId = index.formOf.get(node.id);
251
251
  const form = formId ? index.nodes.get(formId) : undefined;
252
- if (form && form.kind === 'form' && form.submitActionId === node.actionId) {
252
+ const submitActionId = form && form.kind === 'form'
253
+ ? formSubmitActionId(form, (id) => index.nodes.get(id))
254
+ : undefined;
255
+ if (submitActionId !== undefined && submitActionId === node.actionId) {
253
256
  layer.role = 'primary';
254
257
  layer.emphasis = 'strong';
255
258
  layer.uxRole = 'primary-action';
package/dist/ui.d.ts CHANGED
@@ -198,6 +198,18 @@ export declare function isUINode(node: {
198
198
  * content that never appears at the same time.
199
199
  */
200
200
  export declare function primaryChildIds(node: UINode): NodeId[];
201
+ /**
202
+ * The action a form submits, however it was declared.
203
+ *
204
+ * A form may name the action directly (`submitActionId`) or name a declared button that
205
+ * invokes it (`submitButtonId`). Every layer — execution, validation, presentation
206
+ * inference, `AgentAPI` — must resolve it the same way, or a form's primary action means
207
+ * one thing to the renderer and another to an agent.
208
+ */
209
+ export declare function formSubmitActionId(form: FormNode, lookup: (id: NodeId) => {
210
+ kind: string;
211
+ actionId?: NodeId;
212
+ } | undefined): NodeId | undefined;
201
213
  /** Child ids declared by a UI node, in render order. */
202
214
  export declare function uiChildIds(node: UINode): NodeId[];
203
215
  //# sourceMappingURL=ui.d.ts.map
package/dist/ui.js CHANGED
@@ -32,6 +32,24 @@ export function primaryChildIds(node) {
32
32
  return uiChildIds(node);
33
33
  }
34
34
  }
35
+ /**
36
+ * The action a form submits, however it was declared.
37
+ *
38
+ * A form may name the action directly (`submitActionId`) or name a declared button that
39
+ * invokes it (`submitButtonId`). Every layer — execution, validation, presentation
40
+ * inference, `AgentAPI` — must resolve it the same way, or a form's primary action means
41
+ * one thing to the renderer and another to an agent.
42
+ */
43
+ export function formSubmitActionId(form, lookup) {
44
+ if (form.submitActionId) {
45
+ return form.submitActionId;
46
+ }
47
+ if (!form.submitButtonId) {
48
+ return undefined;
49
+ }
50
+ const button = lookup(form.submitButtonId);
51
+ return button?.kind === 'button' ? button.actionId : undefined;
52
+ }
35
53
  /** Child ids declared by a UI node, in render order. */
36
54
  export function uiChildIds(node) {
37
55
  switch (node.kind) {
@@ -2,7 +2,7 @@ import { VALIDATION_CODES } from './diagnostics.js';
2
2
  import { ACTION_UX_ROLES, ALIGNMENTS, BOUNDED_SIZES, CONTROL_VARIANTS, DENSITIES, DEVICE_CLASSES, EMPHASIS_LEVELS, ICON_NAMES, JUSTIFICATIONS, LAYOUT_KINDS, PRESENTATION_ROLES, SIZING_VALUES, SPACING_TOKENS, SURFACE_ROLES, TEXT_ROLES, TREATMENTS, HEADING_LEVELS, UX_ROLES, VALUE_FORMAT_KINDS, } from './presentation.js';
3
3
  import { isDestructiveAction } from './resolve-presentation.js';
4
4
  import { APPEARANCES, RADIUS_TOKENS, SEMANTIC_COLOR_ROLES } from './theme.js';
5
- import { isUINode, primaryChildIds, uiChildIds } from './ui.js';
5
+ import { formSubmitActionId, isUINode, primaryChildIds, uiChildIds } from './ui.js';
6
6
  /** More than this many controls side by side stops being a group and becomes a wall. */
7
7
  const HORIZONTAL_ACTION_LIMIT = 5;
8
8
  /** Below this a non-wrapping row is not yet a responsive problem. */
@@ -666,8 +666,11 @@ function checkPrimaryActions(bag, index, resolved) {
666
666
  continue;
667
667
  }
668
668
  const primary = new Set();
669
- if (isForm && node.kind === 'form' && node.submitActionId) {
670
- primary.add(node.submitActionId);
669
+ if (isForm && node.kind === 'form') {
670
+ const submitActionId = formSubmitActionId(node, (id) => index.nodes.get(id));
671
+ if (submitActionId) {
672
+ primary.add(submitActionId);
673
+ }
671
674
  }
672
675
  for (const child of descendants(index, node.id)) {
673
676
  if (child.kind === 'button' && resolved[child.id]?.uxRole === 'primary-action') {
package/dist/validate.js CHANGED
@@ -449,6 +449,13 @@ function validateUiNode(node, context) {
449
449
  if (node.submitActionId) {
450
450
  requireKind(node.submitActionId, 'action', node.id, context, VALIDATION_CODES.invalidActionRef);
451
451
  }
452
+ if (!node.submitButtonId && node.submitActionId) {
453
+ // A generated submit button carries no arguments, so an action needing any of them
454
+ // could never be satisfied. Silent omission is what produced ARGUMENT_TYPE_MISMATCH
455
+ // at run time; it is rejected here instead.
456
+ requireArguments(node.submitActionId, {}, node.id, context, `Form ${node.id} submits ${node.submitActionId}, whose generated button cannot supply arguments;` +
457
+ ' declare a submit button with submitButtonId and give it');
458
+ }
452
459
  if (node.submitButtonId) {
453
460
  const button = context.nodes.get(node.submitButtonId);
454
461
  if (button?.kind !== 'button') {
@@ -497,6 +504,7 @@ function validateUiNode(node, context) {
497
504
  return;
498
505
  case 'button': {
499
506
  requireKind(node.actionId, 'action', node.id, context, VALIDATION_CODES.invalidActionRef);
507
+ requireArguments(node.actionId, node.arguments ?? {}, node.id, context, `Button ${node.id}`);
500
508
  if (typeof node.label !== 'string') {
501
509
  validateExpression(node.label, node.id, context, new Set());
502
510
  }
@@ -522,6 +530,28 @@ function validateUiNode(node, context) {
522
530
  default:
523
531
  }
524
532
  }
533
+ /**
534
+ * Every required parameter of an action must have an argument. A missing one is statically
535
+ * knowable, and would otherwise surface as a refusal at invocation time — on the authority,
536
+ * for a remote action.
537
+ */
538
+ function requireArguments(actionId, args, ownerId, context, where) {
539
+ const action = context.nodes.get(actionId);
540
+ if (action?.kind !== 'action') {
541
+ return;
542
+ }
543
+ const missing = (action.parameters ?? [])
544
+ .filter((parameter) => parameter.required && !(String(parameter.id) in args))
545
+ .map((parameter) => String(parameter.id));
546
+ if (missing.length > 0) {
547
+ context.errors.push({
548
+ code: VALIDATION_CODES.missingActionArgument,
549
+ message: `${where} ${missing.length === 1 ? 'no argument for' : 'no arguments for'} ${missing.join(', ')}`,
550
+ nodeId: ownerId,
551
+ details: { actionId, missing },
552
+ });
553
+ }
554
+ }
525
555
  /** Every UI node beneath this one, for checks that must know what a subtree contains. */
526
556
  function uiDescendants(id, context) {
527
557
  const found = new Set();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cynodia/axiom-core",
3
- "version": "0.6.0-alpha.1",
3
+ "version": "0.6.2-alpha.1",
4
4
  "description": "Application Graph, semantic types, locations and validation for Axiom.",
5
5
  "license": "MIT",
6
6
  "author": "AskTech AS",