@cynodia/axiom-ui 0.7.0-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.
@@ -0,0 +1,55 @@
1
+ # Provenance
2
+
3
+ What a generated node remembers about where it came from.
4
+
5
+ ## It is authoring metadata
6
+
7
+ Provenance lives under core's reserved authoring key, `AUTHORING_METADATA_KEY`, which means
8
+ **the compiler strips it from every artifact by default** — client IR, server IR, the generated
9
+ page. It exists in the graph, for tools; it does not exist in anything that ships.
10
+
11
+ ```ts
12
+ compileToIR(graph); // no provenance
13
+ compileToIR(graph, { includeAuthoringMetadata: true }); // provenance, for a tool that asked
14
+ ```
15
+
16
+ The mechanism is core's, not the toolkit's: anything under that key is authoring metadata,
17
+ whoever wrote it. Semantic metadata beside it is untouched.
18
+
19
+ ## The record
20
+
21
+ ```ts
22
+ {
23
+ toolkit: '@cynodia/axiom-ui',
24
+ pattern: 'entity-list',
25
+ patternVersion: '0.7.0',
26
+ instance: 'product_list',
27
+ part: 'row-action',
28
+ parent: 'products', // the nearest generating pattern
29
+ ancestry: ['products'], // outermost to nearest
30
+ ownership: 'declaration',
31
+ }
32
+ ```
33
+
34
+ Everything in it is a stable string. No source location, no line number, no AST pointer —
35
+ nothing that changes when a file is reformatted.
36
+
37
+ ## What it is for
38
+
39
+ ```ts
40
+ provenanceOf(node); // which pattern generated this node?
41
+ instancesOfPattern(graph, 'entity-list'); // which entity-lists exist?
42
+ nodesOfInstance(graph, 'product_list'); // which nodes belong to this one?
43
+ axiomUi.inspect(graph, 'product_list'); // declaration, nodes, explanations
44
+ ```
45
+
46
+ ## What it is not for
47
+
48
+ Provenance records **origin, not ownership**, and it never affects execution. Removing it
49
+ changes node ids, semantic edges, validation, compiler output, presentation resolution and
50
+ runtime behaviour by exactly nothing — `expand(…, { model: 'macro' })` produces a
51
+ byte-identical graph.
52
+
53
+ **Nothing may branch on provenance at runtime.** A renderer, a constraint or an action that
54
+ behaved differently because a node was generated would make the toolkit part of the
55
+ application's semantics, which is the one thing this design exists to prevent.
@@ -0,0 +1,205 @@
1
+ # Axiom UI Toolkit — Agent Reference
2
+
3
+ `@cynodia/axiom-ui` 0.7.0-alpha.1. Semantic UI patterns for Axiom.
4
+
5
+ Read this plus [`PATTERN_CATALOG.json`](PATTERN_CATALOG.json) and the `.d.ts` declarations.
6
+ Nothing else should be necessary.
7
+
8
+ ## What a pattern is
9
+
10
+ A pattern is **not a component**. It has no runtime existence, renders nothing and owns no
11
+ state. It is a function from a declaration to ordinary Axiom UI nodes, run once at authoring
12
+ time.
13
+
14
+ ```
15
+ pattern declaration → expansion → canonical Axiom UI → presentation → renderer
16
+ ```
17
+
18
+ After expansion the application is an ordinary Axiom application. `validateGraph`,
19
+ `compileToIR`, `AgentAPI` and the runtime see nodes, not patterns. **Do not look for a
20
+ pattern at runtime; there is none.**
21
+
22
+ ## Using it
23
+
24
+ ```ts
25
+ import { axiomUi } from '@cynodia/axiom-ui';
26
+
27
+ const rootId = axiomUi.expand(graph, {
28
+ pattern: 'entity-list',
29
+ instance: 'product_list', // stable, yours, and the root of every generated id
30
+ source: STATE_PRODUCTS,
31
+ });
32
+ ```
33
+
34
+ `expand` returns the id of the root node it created. Put that id in a `view`'s children, or in
35
+ another pattern's `content` slot.
36
+
37
+ A declaration is **plain data**: no functions, no callbacks, no JSX. Slots take node ids or
38
+ nested declarations, never markup.
39
+
40
+ ## Discovering what exists
41
+
42
+ ```ts
43
+ import { axiomUi, listPatterns, describePattern } from '@cynodia/axiom-ui';
44
+
45
+ listPatterns(axiomUi); // ['action-bar','entity-form','entity-list','metric-grid','page']
46
+ describePattern(axiomUi, 'entity-list'); // required, optional, inputs, slots, produces, inferred
47
+ ```
48
+
49
+ `describePattern(...).inferred` tells you what a pattern works out for itself, and from what.
50
+ **Do not restate anything listed there.** The same content is in `PATTERN_CATALOG.json` if you
51
+ prefer to read it without running code.
52
+
53
+ ## What is inferred, and what is not
54
+
55
+ The rule: **the toolkit infers what the graph already says; you supply application-specific UX
56
+ choices.**
57
+
58
+ Inferred from the graph — do not repeat these:
59
+
60
+ | | from |
61
+ | --- | --- |
62
+ | the entity a collection holds | the state's `valueType` |
63
+ | a field's label | `FieldDef.name` |
64
+ | a field's control | `FieldDef.valueType` (`boolean` → checkbox, `number` → stepper, `enum` → select) |
65
+ | whether a control is required | `FieldDef.required` |
66
+ | field order | the entity's declaration order |
67
+ | number / boolean / date / datetime formats | `FieldDef.valueType` |
68
+ | destructive emphasis | `ActionDef.destructive` |
69
+ | the primary action of a form | its submit action |
70
+ | a metric's label | the `name` of the state its value reads |
71
+ | whether a form creates or edits | `draft` (create) or `target` (edit) — the two are different semantics, not a flag |
72
+
73
+ **Never inferred**, because nothing in the graph says it — supply these explicitly:
74
+
75
+ - **currency and percentage formats.** A `number` does not say which, and guessing from a field named `price` would be a heuristic you could not predict. Pass `formats`.
76
+ - **which fields matter in a list.** The default is every field but the identity; narrow it with `fields`.
77
+ - **where a form writes.** `draft: S` creates a record in a draft state; `target: { state, identity }` edits the member of a collection that `identity` selects. Exactly one is given, and the mode follows from which. A form that gave neither, or both, is refused at the declaration.
78
+ - **a choice drawn from application data.** A field whose value identifies another record takes an `options` source: `options: { [F_ORDER_PRODUCT]: { source: ref(STATE_PRODUCTS), scopeId, valueFieldId, labelFieldId } }`.
79
+
80
+ ## Creating and editing
81
+
82
+ ```ts
83
+ // Create: controls write into a draft state, which is not checked per keystroke, because a
84
+ // half-filled new record is incomplete by definition.
85
+ { pattern: 'entity-form', instance: 'new_product', draft: STATE_DRAFT, submit: ACTION_ADD }
86
+
87
+ // Edit: controls write into the addressed member of a collection. Every write is
88
+ // transactional against every hard invariant, and a value that breaks one is rolled back.
89
+ {
90
+ pattern: 'entity-form',
91
+ instance: 'edit_product',
92
+ target: { state: STATE_PRODUCTS, identity: ref(ROUTE_PARAM_CODE) },
93
+ submit: ACTION_SAVE,
94
+ }
95
+ ```
96
+
97
+ The identity field of the entity is what addresses the member; an entity without one cannot be
98
+ edited this way and says so (`NO_IDENTITY_FIELD`). An edit form omits the identity field from
99
+ its controls, because an identity is what addresses a record, not something to retype.
100
+
101
+ ## User-visible text can be an expression
102
+
103
+ A pattern input carrying user-visible **value** text takes `string | Expression`:
104
+
105
+ ```ts
106
+ { pattern: 'page', instance: 'detail', title: field(productInRoute(), F_PRODUCT_NAME) }
107
+ ```
108
+
109
+ `page.title`, `page.description`, a metric's `label` and `description`, and a form's `title`,
110
+ `description` and `submitLabel` all accept either. A caption that is the same on every record
111
+ is a string; a title that names the record on screen is an expression.
112
+
113
+ ## Composition
114
+
115
+ Patterns nest. `page` takes `content` and `actions`; `entity-list` takes `rowExtra`. A slot
116
+ accepts a node id or another declaration.
117
+
118
+ ```ts
119
+ axiomUi.expand(graph, {
120
+ pattern: 'page',
121
+ instance: 'products',
122
+ title: 'Products',
123
+ content: [
124
+ { pattern: 'entity-list', instance: 'product_list', source: STATE_PRODUCTS },
125
+ { pattern: 'entity-form', instance: 'new_product', draft: STATE_DRAFT, submit: ACTION_ADD },
126
+ ],
127
+ });
128
+ ```
129
+
130
+ ## When no pattern fits
131
+
132
+ **Compose. Do not wait for a pattern and do not reach for CSS.** Patterns emit ordinary Axiom
133
+ UI nodes, so ordinary nodes sit beside them. Build the part the toolkit does not know with
134
+ `container`, `text`, `conditional`, `field-display`, and put pattern expansions inside it.
135
+
136
+ The same applies to a requirement inside a pattern — a per-row badge, a warning marker. Put a
137
+ `conditional` node in the `rowExtra` slot. You do not need to reproduce the generated
138
+ structure, and there is no CSS or DOM escape hatch to reach for.
139
+
140
+ ## Referring to the current row
141
+
142
+ Inside `entity-list`, a row's fields and a row action's arguments are expressions evaluated in
143
+ the row's scope. Build them with the helpers, which derive the repeat's id from the instance:
144
+
145
+ ```ts
146
+ import { rowField } from '@cynodia/axiom-ui';
147
+
148
+ rowActions: [ACTION_DELETE_PRODUCT],
149
+ rowArguments: { [ACTION_DELETE_PRODUCT]: { [PARAM_PRODUCT]: rowField('product_list', F_PRODUCT_ID) } },
150
+ ```
151
+
152
+ ## Diagnostics
153
+
154
+ A declaration is checked against the graph **before** any node is created, and a mistake points
155
+ at the declaration:
156
+
157
+ ```
158
+ [SOURCE_NOT_A_COLLECTION] product_list.source: Low stock is not a collection, so it has no rows to list.
159
+ [FIELD_NOT_ON_ENTITY] product_list.fields[1]: field_order_total is not a field of Product.
160
+ [MISSING_ACTION_ARGUMENT] bar.actions[0]: Delete product requires param_product; supply it under arguments.
161
+ ```
162
+
163
+ A refused expansion creates nothing. `validateGraph` still runs afterwards and is still final —
164
+ toolkit checks are an earlier, better-located diagnostic, never a replacement.
165
+
166
+ ## Understanding what a pattern did
167
+
168
+ ```ts
169
+ const expansion = axiomUi.inspect(graph, 'product_list');
170
+ expansion.declaration; // what you wrote
171
+ expansion.nodeIds; // what it generated
172
+ expansion.explanations; // why it chose what it chose
173
+ ```
174
+
175
+ `explanations` is prose written by the pattern — which field got which format and from what,
176
+ why an action landed in the row action group, why an empty state exists.
177
+
178
+ ## Ownership
179
+
180
+ Generated nodes record who owns them. The default is `declaration`: **the declaration is the
181
+ source of truth, and editing a generated node is drift** that `detectDrift` reports and the
182
+ next build overwrites. To take ownership of the result instead, `materializePattern`. See
183
+ [`OWNERSHIP.md`](OWNERSHIP.md).
184
+
185
+ ## When not to add a pattern
186
+
187
+ A new pattern is justified by **recurring semantic UX intent**, not by recurring visual
188
+ structure. Before adding one, require evidence that it recurs, that its inference is
189
+ meaningful, that expansion removes semantic restatement, that customization stays composable,
190
+ and that canonical primitives alone are unnecessarily repetitive. A missing composition is not
191
+ a missing pattern.
192
+
193
+ Two things are never patterns:
194
+
195
+ - **Interaction behaviour.** A pattern can only emit nodes that already exist, so focus movement, containment, `Escape`, typeahead and active descendant are unreachable from here. They are canonical semantics — `dialog` is one, and `combobox` is classified as the next.
196
+ - **Anything that generates behaviour.** No pattern creates state, an action, a constraint or an authority. A pattern that generated an action would be hiding the part of an application that decides what happens.
197
+
198
+ ## Rules
199
+
200
+ - **MUST** give every declaration a stable, unique `instance`. Generated ids derive from it.
201
+ - **MUST NOT** put a function, closure or DOM node in a declaration.
202
+ - **MUST NOT** edit generated nodes under `declaration` ownership; use a slot, an option, or materialize.
203
+ - **MUST NOT** use `rendererOverrides` or any CSS mechanism to achieve a layout a pattern option or a semantic node can express.
204
+ - **SHOULD** compose patterns and canonical nodes when no pattern fits, rather than approximating with the wrong one.
205
+ - Patterns own UX structure — hierarchy, grouping, empty states, responsive intent. Themes own visual design. Neither owns behaviour: no pattern creates state, an action or a constraint.
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@cynodia/axiom-ui",
3
+ "version": "0.7.0-alpha.1",
4
+ "description": "Semantic UI authoring for Axiom: patterns that expand, at build time, into ordinary Axiom UI nodes.",
5
+ "license": "MIT",
6
+ "author": "AskTech AS",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ },
18
+ "./example": {
19
+ "types": "./dist/example/index.d.ts",
20
+ "import": "./dist/example/index.js"
21
+ },
22
+ "./catalog": "./docs/PATTERN_CATALOG.json"
23
+ },
24
+ "files": [
25
+ "dist/**/*.js",
26
+ "dist/**/*.d.ts",
27
+ "docs/*.md",
28
+ "docs/PATTERN_CATALOG.json",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "dependencies": {
33
+ "@cynodia/axiom-core": "0.7.0-alpha.1"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc -b tsconfig.json tsconfig.test.json",
37
+ "test": "node --test dist-test/**/*.test.js"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/cynodia/axiom.git"
42
+ },
43
+ "homepage": "https://github.com/cynodia/axiom",
44
+ "bugs": {
45
+ "url": "https://github.com/cynodia/axiom/issues"
46
+ }
47
+ }