@cynodia/axiom-core 0.4.1-alpha.1 → 0.5.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.
package/README.md CHANGED
@@ -9,6 +9,11 @@ The Application Graph and its semantic model: nodes, fields, structured types,
9
9
  expressions, **locations** (addressable writable positions), edge derivation,
10
10
  validation and type inference.
11
11
 
12
+ It also owns the presentation and UX layer — semantic roles, layout, spacing and sizing
13
+ tokens, device classes, value formats and the `Theme` — together with presentation
14
+ resolution and validation. Presentation lives here because it is part of the canonical
15
+ graph: it is intent, not styling, and it names no colour, length or CSS property.
16
+
12
17
  ## Installation
13
18
 
14
19
  ```bash
@@ -50,5 +50,20 @@ export declare const VALIDATION_CODES: {
50
50
  readonly initialValueInvalidEntity: "INITIAL_VALUE_INVALID_ENTITY";
51
51
  readonly scopeShadowing: "SCOPE_SHADOWING";
52
52
  readonly scopeCollidesWithNode: "SCOPE_COLLIDES_WITH_NODE";
53
+ readonly ephemeralStatePersisted: "EPHEMERAL_STATE_PERSISTED";
54
+ readonly unknownPresentationToken: "UNKNOWN_PRESENTATION_TOKEN";
55
+ readonly presentationSemanticConflict: "PRESENTATION_SEMANTIC_CONFLICT";
56
+ readonly multiplePrimaryActions: "MULTIPLE_PRIMARY_ACTIONS";
57
+ readonly formWithoutPrimaryAction: "FORM_WITHOUT_PRIMARY_ACTION";
58
+ readonly destructiveActionPresentedAsSuccess: "DESTRUCTIVE_ACTION_PRESENTED_AS_SUCCESS";
59
+ readonly destructiveActionUnmarked: "DESTRUCTIVE_ACTION_UNMARKED";
60
+ readonly excessiveHorizontalActions: "EXCESSIVE_HORIZONTAL_ACTIONS";
61
+ readonly emptyStateWithoutRecoveryAction: "EMPTY_STATE_WITHOUT_RECOVERY_ACTION";
62
+ readonly rigidHorizontalLayout: "RIGID_HORIZONTAL_LAYOUT";
63
+ readonly conflictingSizing: "CONFLICTING_SIZING";
64
+ readonly interactiveElementMissingLabel: "INTERACTIVE_ELEMENT_MISSING_LABEL";
65
+ readonly formInputMissingLabel: "FORM_INPUT_MISSING_LABEL";
66
+ readonly invalidHeadingStructure: "INVALID_HEADING_STRUCTURE";
67
+ readonly opaquePresentation: "OPAQUE_PRESENTATION";
53
68
  };
54
69
  //# sourceMappingURL=diagnostics.d.ts.map
@@ -33,4 +33,21 @@ export const VALIDATION_CODES = {
33
33
  initialValueInvalidEntity: 'INITIAL_VALUE_INVALID_ENTITY',
34
34
  scopeShadowing: 'SCOPE_SHADOWING',
35
35
  scopeCollidesWithNode: 'SCOPE_COLLIDES_WITH_NODE',
36
+ ephemeralStatePersisted: 'EPHEMERAL_STATE_PERSISTED',
37
+ // Presentation and UX. Everything here is a warning except an unknown token, which the
38
+ // renderer genuinely cannot act on.
39
+ unknownPresentationToken: 'UNKNOWN_PRESENTATION_TOKEN',
40
+ presentationSemanticConflict: 'PRESENTATION_SEMANTIC_CONFLICT',
41
+ multiplePrimaryActions: 'MULTIPLE_PRIMARY_ACTIONS',
42
+ formWithoutPrimaryAction: 'FORM_WITHOUT_PRIMARY_ACTION',
43
+ destructiveActionPresentedAsSuccess: 'DESTRUCTIVE_ACTION_PRESENTED_AS_SUCCESS',
44
+ destructiveActionUnmarked: 'DESTRUCTIVE_ACTION_UNMARKED',
45
+ excessiveHorizontalActions: 'EXCESSIVE_HORIZONTAL_ACTIONS',
46
+ emptyStateWithoutRecoveryAction: 'EMPTY_STATE_WITHOUT_RECOVERY_ACTION',
47
+ rigidHorizontalLayout: 'RIGID_HORIZONTAL_LAYOUT',
48
+ conflictingSizing: 'CONFLICTING_SIZING',
49
+ interactiveElementMissingLabel: 'INTERACTIVE_ELEMENT_MISSING_LABEL',
50
+ formInputMissingLabel: 'FORM_INPUT_MISSING_LABEL',
51
+ invalidHeadingStructure: 'INVALID_HEADING_STRUCTURE',
52
+ opaquePresentation: 'OPAQUE_PRESENTATION',
36
53
  };
package/dist/graph.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { EdgeId, FieldId, NodeId } from './ids.js';
2
2
  import type { EdgeKind, FieldDef, GraphEdge } from './nodes.js';
3
3
  import type { AnyNode, ApplicationGraphData, NodeInput, NodeKind, NodeOfKind } from './types.js';
4
+ import type { Theme, ThemeInput } from './theme.js';
4
5
  export interface FieldIndexEntry {
5
6
  entityId: NodeId;
6
7
  field: FieldDef;
@@ -25,6 +26,14 @@ export declare class ApplicationGraph {
25
26
  get id(): string;
26
27
  get name(): string;
27
28
  get version(): string;
29
+ /**
30
+ * The application's visual identity, completed against the default theme. A theme is
31
+ * presentation only: changing it cannot change an action, a constraint or a route.
32
+ */
33
+ get theme(): Theme;
34
+ /** Exactly what the application declared, before defaults were filled in. */
35
+ get declaredTheme(): ThemeInput | undefined;
36
+ setTheme(theme: ThemeInput | undefined): void;
28
37
  addNode<T extends AnyNode>(node: NodeInput<T>): NodeId;
29
38
  getNode<T extends AnyNode = AnyNode>(id: NodeId): T | undefined;
30
39
  hasNode(id: NodeId): boolean;
package/dist/graph.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { deriveEdges } from './derive-edges.js';
2
2
  import { createEdgeId, createNodeId } from './ids.js';
3
+ import { resolveTheme } from './theme.js';
3
4
  function clone(value) {
4
5
  return structuredClone(value);
5
6
  }
@@ -16,7 +17,7 @@ export class ApplicationGraph {
16
17
  /** Bumped by every change, so the derived edge index can never serve stale data. */
17
18
  revision = 0;
18
19
  semanticIndex;
19
- constructor(id, name, version = '0.4.1') {
20
+ constructor(id, name, version = '0.5.0') {
20
21
  this.data = { id, name, version, nodes: {}, edges: {} };
21
22
  }
22
23
  get id() {
@@ -28,6 +29,26 @@ export class ApplicationGraph {
28
29
  get version() {
29
30
  return this.data.version;
30
31
  }
32
+ /**
33
+ * The application's visual identity, completed against the default theme. A theme is
34
+ * presentation only: changing it cannot change an action, a constraint or a route.
35
+ */
36
+ get theme() {
37
+ return resolveTheme(this.data.theme);
38
+ }
39
+ /** Exactly what the application declared, before defaults were filled in. */
40
+ get declaredTheme() {
41
+ return this.data.theme ? structuredClone(this.data.theme) : undefined;
42
+ }
43
+ setTheme(theme) {
44
+ if (theme === undefined) {
45
+ delete this.data.theme;
46
+ }
47
+ else {
48
+ this.data.theme = structuredClone(theme);
49
+ }
50
+ this.revision += 1;
51
+ }
31
52
  addNode(node) {
32
53
  const id = (node.id ?? createNodeId(node.kind));
33
54
  if (this.data.nodes[id]) {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from './ids.js';
2
2
  export * from './diagnostics.js';
3
3
  export * from './location.js';
4
+ export * from './presentation.js';
5
+ export * from './theme.js';
4
6
  export * from './type-ref.js';
5
7
  export * from './expressions.js';
6
8
  export * from './nodes.js';
@@ -10,7 +12,9 @@ export * from './graph.js';
10
12
  export * from './infer.js';
11
13
  export * from './context.js';
12
14
  export * from './validate-location.js';
15
+ export * from './resolve-presentation.js';
13
16
  export * from './validate.js';
17
+ export * from './validate-presentation.js';
14
18
  export * from './derive-edges.js';
15
19
  export * from './ir.js';
16
20
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from './ids.js';
2
2
  export * from './diagnostics.js';
3
3
  export * from './location.js';
4
+ export * from './presentation.js';
5
+ export * from './theme.js';
4
6
  export * from './type-ref.js';
5
7
  export * from './expressions.js';
6
8
  export * from './nodes.js';
@@ -10,6 +12,8 @@ export * from './graph.js';
10
12
  export * from './infer.js';
11
13
  export * from './context.js';
12
14
  export * from './validate-location.js';
15
+ export * from './resolve-presentation.js';
13
16
  export * from './validate.js';
17
+ export * from './validate-presentation.js';
14
18
  export * from './derive-edges.js';
15
19
  export * from './ir.js';
package/dist/ir.d.ts CHANGED
@@ -4,6 +4,8 @@ import type { FieldIndexEntry } from './graph.js';
4
4
  import type { UINode } from './ui.js';
5
5
  import type { AnyNode } from './types.js';
6
6
  import type { TypeRef } from './type-ref.js';
7
+ import type { ResolvedPresentation } from './presentation.js';
8
+ import type { Theme } from './theme.js';
7
9
  export interface RouteSegment {
8
10
  kind: 'static' | 'parameter';
9
11
  value: string;
@@ -48,5 +50,21 @@ export interface ApplicationIR {
48
50
  * to canonical application state from a write to a draft.
49
51
  */
50
52
  locationRoots: Record<NodeId, NodeId>;
53
+ /**
54
+ * Whether the field each input addresses is declared required, resolved here so a
55
+ * renderer can mark it without re-deriving the model.
56
+ */
57
+ locationRequired: Record<NodeId, boolean>;
58
+ /** The application's visual identity, completed against the default theme. */
59
+ theme: Theme;
60
+ /**
61
+ * Presentation with every question already answered, per UI node: renderer defaults,
62
+ * theme, inheritance, semantic inference, node declaration and responsive overrides all
63
+ * resolved. A renderer reads this and needs to know nothing about how it was decided.
64
+ *
65
+ * It is deliberately still semantic — roles, tokens and device classes, not CSS — so a
66
+ * second renderer remains possible.
67
+ */
68
+ presentation: Record<NodeId, ResolvedPresentation>;
51
69
  }
52
70
  //# sourceMappingURL=ir.d.ts.map
package/dist/nodes.d.ts CHANGED
@@ -2,12 +2,7 @@ import type { Expression } from './expressions.js';
2
2
  import type { CollectionItemLocation, Location } from './location.js';
3
3
  import type { EdgeId, FieldId, NodeId } from './ids.js';
4
4
  import type { TypeRef } from './type-ref.js';
5
- /** Minimal, optional presentation hints. Styling is not a 0.2 research objective. */
6
- export interface PresentationHints {
7
- role?: 'primary' | 'secondary' | 'danger';
8
- density?: 'compact' | 'normal';
9
- emphasis?: 'normal' | 'strong';
10
- }
5
+ import type { ConfirmationPresentation } from './presentation.js';
11
6
  export interface NodeBase {
12
7
  id: NodeId;
13
8
  name?: string;
@@ -50,6 +45,16 @@ export interface StateDef extends NodeBase {
50
45
  * definition, so instance validation skips them until an action commits the value.
51
46
  */
52
47
  draft?: boolean;
48
+ /**
49
+ * Marks ephemeral presentation state — which panel is expanded, which tab is selected,
50
+ * whether a dialog is open. It is not canonical domain state: instance validation skips
51
+ * it, and it may not be persisted. Marking it says so in the graph instead of leaving an
52
+ * agent to guess which states are domain facts.
53
+ *
54
+ * It changes what a state *is*, never what is permitted: a write reaching domain state
55
+ * is governed exactly as before.
56
+ */
57
+ ephemeral?: boolean;
53
58
  persistence?: StatePersistence;
54
59
  }
55
60
  export interface ActionParameter {
@@ -80,6 +85,8 @@ export interface ActionDef extends NodeBase {
80
85
  destructive?: boolean;
81
86
  requiresConfirmation?: boolean;
82
87
  confirmationMessage?: string;
88
+ /** What the confirmation says, when a plain message is not enough. */
89
+ confirmation?: ConfirmationPresentation;
83
90
  }
84
91
  /**
85
92
  * The conditions an action checks, however they were written. `guards` pairs each
@@ -0,0 +1,270 @@
1
+ import type { NodeId } from './ids.js';
2
+ /**
3
+ * The presentation and UX vocabulary of 0.5.
4
+ *
5
+ * Everything here describes *intent*, never rendering. `role: 'destructive'` says what a
6
+ * control means; a theme and a renderer decide what that looks like. No entry in this
7
+ * module names a colour, a pixel, a font or a CSS property, and nothing in it can hold a
8
+ * function — presentation stays serializable and analyzable like the rest of the graph.
9
+ */
10
+ /** What a control or region *means*. A theme decides how each role is rendered. */
11
+ export type PresentationRole = 'primary' | 'secondary' | 'tertiary' | 'destructive' | 'success' | 'warning' | 'informational' | 'muted';
12
+ export declare const PRESENTATION_ROLES: readonly PresentationRole[];
13
+ /** 0.2 called the destructive role "danger". It is accepted and normalized, not an error. */
14
+ export type LegacyPresentationRole = 'danger';
15
+ export type Emphasis = 'subtle' | 'normal' | 'strong';
16
+ export declare const EMPHASIS_LEVELS: readonly Emphasis[];
17
+ export type Density = 'compact' | 'comfortable' | 'spacious';
18
+ export declare const DENSITIES: readonly Density[];
19
+ /** 0.2 called the comfortable density "normal". Accepted and normalized. */
20
+ export type LegacyDensity = 'normal';
21
+ /** Semantic spacing. A theme maps each token to a renderer value. */
22
+ export type SpacingToken = 'none' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
23
+ export declare const SPACING_TOKENS: readonly SpacingToken[];
24
+ /** How a node relates to the space it is given. */
25
+ export type SizingToken = 'fit' | 'fill' | 'content';
26
+ /** A bounded semantic size, used where a measurement would otherwise be needed. */
27
+ export type BoundedSize = 'narrow' | 'medium' | 'wide';
28
+ export type SizingValue = SizingToken | BoundedSize;
29
+ export declare const SIZING_TOKENS: readonly SizingToken[];
30
+ export declare const BOUNDED_SIZES: readonly BoundedSize[];
31
+ export declare const SIZING_VALUES: readonly SizingValue[];
32
+ export type Alignment = 'start' | 'center' | 'end' | 'stretch';
33
+ export declare const ALIGNMENTS: readonly Alignment[];
34
+ /** Distribution along the layout direction. `between` spreads the free space out. */
35
+ export type Justification = Alignment | 'between';
36
+ export declare const JUSTIFICATIONS: readonly Justification[];
37
+ export type LayoutKind = 'vertical' | 'horizontal' | 'grid' | 'stack';
38
+ export declare const LAYOUT_KINDS: readonly LayoutKind[];
39
+ /**
40
+ * Columns of a grid, stated semantically. `adaptive` says "as many columns of at least
41
+ * this width as fit", which is what a responsive card list actually means; a number says
42
+ * exactly how many. Neither form exposes a track template.
43
+ */
44
+ export type GridColumns = number | {
45
+ mode: 'adaptive';
46
+ minimum: BoundedSize;
47
+ };
48
+ export interface LayoutPresentation {
49
+ kind: LayoutKind;
50
+ gap?: SpacingToken;
51
+ /** Across the layout direction. */
52
+ align?: Alignment;
53
+ /** Along the layout direction. */
54
+ justify?: Justification;
55
+ wrap?: boolean;
56
+ columns?: GridColumns;
57
+ }
58
+ /** `layout: 'horizontal'` is shorthand for `layout: { kind: 'horizontal' }`. */
59
+ export type LayoutIntent = LayoutKind | LayoutPresentation;
60
+ export interface SizingPresentation {
61
+ width?: SizingValue;
62
+ height?: SizingValue;
63
+ minWidth?: BoundedSize;
64
+ maxWidth?: BoundedSize;
65
+ }
66
+ /** `padding: 'medium'` is shorthand for the same amount on every side. */
67
+ export type PaddingIntent = SpacingToken | {
68
+ horizontal?: SpacingToken;
69
+ vertical?: SpacingToken;
70
+ };
71
+ export type SurfaceRole = 'transparent' | 'base' | 'subtle' | 'raised' | 'inset';
72
+ export declare const SURFACE_ROLES: readonly SurfaceRole[];
73
+ /** Semantic text roles. A theme maps each to font metrics; the graph names no sizes. */
74
+ export type TextRole = 'body' | 'caption' | 'label' | 'heading' | 'title' | 'display';
75
+ export declare const TEXT_ROLES: readonly TextRole[];
76
+ /**
77
+ * Why a node exists in the interface. A UX role is high-information: `toolbar` implies a
78
+ * horizontal, wrapping, centre-aligned group, so an author states the role rather than
79
+ * six layout properties. It stays inspectable — nothing compiles it into an opaque
80
+ * component.
81
+ */
82
+ export type UxRole = 'primary-action' | 'secondary-action' | 'destructive-action' | 'navigation-action' | 'form-section' | 'action-group' | 'navigation-group' | 'empty-state' | 'error-state' | 'warning-state' | 'success-state' | 'informational-state' | 'toolbar' | 'sidebar' | 'content-region' | 'header-region' | 'footer-region';
83
+ export declare const UX_ROLES: readonly UxRole[];
84
+ /** UX roles that name an action's place in the hierarchy rather than a region. */
85
+ export declare const ACTION_UX_ROLES: readonly UxRole[];
86
+ /** UX roles that report a condition, and which a renderer may announce. */
87
+ export declare const STATUS_UX_ROLES: readonly UxRole[];
88
+ /** How a value is presented once it has been formatted. */
89
+ export type Treatment = 'plain' | 'badge' | 'pill';
90
+ export declare const TREATMENTS: readonly Treatment[];
91
+ /**
92
+ * The kind of control a value should be edited with, stated as intent. The renderer maps
93
+ * it onto whatever the platform offers; the graph never names an HTML input type.
94
+ */
95
+ export type ControlVariant = 'default' | 'switch' | 'checkbox' | 'radio-group' | 'select' | 'multiline' | 'stepper';
96
+ export declare const CONTROL_VARIANTS: readonly ControlVariant[];
97
+ /** Semantic icon names. A theme supplies the glyphs; the graph stores no artwork. */
98
+ export type IconName = 'add' | 'delete' | 'edit' | 'save' | 'close' | 'warning' | 'success' | 'error' | 'information' | 'navigation-back' | 'navigation-forward' | 'menu' | 'search' | 'refresh' | 'settings' | 'more';
99
+ export declare const ICON_NAMES: readonly IconName[];
100
+ /**
101
+ * Viewport classes, not pixels. An author says what should happen on a compact display;
102
+ * the renderer decides where compact begins.
103
+ */
104
+ export type DeviceClass = 'compact' | 'regular' | 'wide';
105
+ export declare const DEVICE_CLASSES: readonly DeviceClass[];
106
+ export type DateStyle = 'short' | 'medium' | 'long';
107
+ /**
108
+ * Structured value formatting. Formatting is presentation: the stored value is unchanged,
109
+ * and there is deliberately no way to supply a function.
110
+ */
111
+ export type ValueFormat = {
112
+ kind: 'text';
113
+ } | {
114
+ kind: 'number';
115
+ decimals?: number;
116
+ grouping?: boolean;
117
+ } | {
118
+ kind: 'currency';
119
+ currency: string;
120
+ decimals?: number;
121
+ } | {
122
+ kind: 'percentage';
123
+ decimals?: number;
124
+ scale?: 'fraction' | 'percent';
125
+ } | {
126
+ kind: 'boolean';
127
+ trueLabel?: string;
128
+ falseLabel?: string;
129
+ } | {
130
+ kind: 'date';
131
+ style?: DateStyle;
132
+ } | {
133
+ kind: 'datetime';
134
+ style?: DateStyle;
135
+ };
136
+ export type ValueFormatKind = ValueFormat['kind'];
137
+ export declare const VALUE_FORMAT_KINDS: readonly ValueFormatKind[];
138
+ /** What a device class changes. Anything absent keeps its resolved value. */
139
+ export interface ResponsiveOverride {
140
+ layout?: LayoutIntent;
141
+ sizing?: SizingPresentation;
142
+ padding?: PaddingIntent;
143
+ gap?: SpacingToken;
144
+ density?: Density | LegacyDensity;
145
+ hidden?: boolean;
146
+ }
147
+ export type ResponsivePresentation = Partial<Record<DeviceClass, ResponsiveOverride>>;
148
+ /**
149
+ * The escape hatch of §50. It is explicitly keyed by renderer, it is reported by
150
+ * `AgentAPI`, and semantic analysis makes no claim to understand it. Ordinary
151
+ * applications — including every acceptance fixture — use none of it.
152
+ */
153
+ export interface RendererOverride {
154
+ /** A renderer-specific class name. The renderer attaches it and nothing else. */
155
+ className?: string;
156
+ [key: string]: unknown;
157
+ }
158
+ /**
159
+ * Presentation and UX intent attached to a UI node. Every field is optional: defaults,
160
+ * inheritance, semantic inference and the theme are expected to do most of the work, and
161
+ * a graph with no presentation at all still renders as a usable application.
162
+ */
163
+ export interface Presentation {
164
+ role?: PresentationRole | LegacyPresentationRole;
165
+ emphasis?: Emphasis;
166
+ density?: Density | LegacyDensity;
167
+ textRole?: TextRole;
168
+ uxRole?: UxRole;
169
+ surface?: SurfaceRole;
170
+ treatment?: Treatment;
171
+ icon?: IconName;
172
+ /** An accessible name, for a control whose visible label is an icon or is empty. */
173
+ accessibleLabel?: string;
174
+ /** Help text. UX affordance, not business data. */
175
+ description?: string;
176
+ layout?: LayoutIntent;
177
+ sizing?: SizingPresentation;
178
+ padding?: PaddingIntent;
179
+ gap?: SpacingToken;
180
+ format?: ValueFormat;
181
+ control?: ControlVariant;
182
+ responsive?: ResponsivePresentation;
183
+ rendererOverrides?: Record<string, RendererOverride>;
184
+ }
185
+ /**
186
+ * The 0.2 name for presentation intent. Kept so 0.4 graphs compile unchanged.
187
+ *
188
+ * @deprecated Use `Presentation`.
189
+ */
190
+ export type PresentationHints = Presentation;
191
+ /**
192
+ * How a confirmation is presented. `ActionDef.requiresConfirmation` already states that a
193
+ * confirmation is required; this states what the person is asked. It carries no browser
194
+ * dialog markup, so a renderer with no dialogs can still present it.
195
+ */
196
+ export interface ConfirmationPresentation {
197
+ title?: string;
198
+ description?: string;
199
+ confirmLabel?: string;
200
+ cancelLabel?: string;
201
+ severity?: 'informational' | 'warning' | 'destructive';
202
+ }
203
+ /**
204
+ * Which layer of §40 gave a resolved property its value. Recording it is what makes
205
+ * precedence testable instead of a claim in a document.
206
+ */
207
+ export type PresentationOrigin = 'renderer-default' | 'theme' | 'inherited' | 'inferred' | 'node' | 'responsive';
208
+ export declare const PRESENTATION_ORIGINS: readonly PresentationOrigin[];
209
+ export interface ResolvedLayout {
210
+ kind: LayoutKind;
211
+ gap: SpacingToken;
212
+ align: Alignment;
213
+ justify: Justification;
214
+ wrap: boolean;
215
+ columns?: GridColumns;
216
+ }
217
+ export interface ResolvedSizing {
218
+ width: SizingValue;
219
+ height: SizingValue;
220
+ minWidth?: BoundedSize;
221
+ maxWidth?: BoundedSize;
222
+ }
223
+ export interface ResolvedPadding {
224
+ horizontal: SpacingToken;
225
+ vertical: SpacingToken;
226
+ }
227
+ export interface ResolvedResponsive {
228
+ layout?: ResolvedLayout;
229
+ sizing?: ResolvedSizing;
230
+ padding?: ResolvedPadding;
231
+ density?: Density;
232
+ hidden?: boolean;
233
+ }
234
+ /**
235
+ * Presentation with every question answered. The renderer reads this and nothing else,
236
+ * so a renderer needs no knowledge of themes, inheritance or inference.
237
+ */
238
+ export interface ResolvedPresentation {
239
+ nodeId: NodeId;
240
+ role?: PresentationRole;
241
+ emphasis: Emphasis;
242
+ density: Density;
243
+ textRole: TextRole;
244
+ uxRole?: UxRole;
245
+ surface: SurfaceRole;
246
+ treatment: Treatment;
247
+ icon?: IconName;
248
+ accessibleLabel?: string;
249
+ description?: string;
250
+ layout: ResolvedLayout;
251
+ sizing: ResolvedSizing;
252
+ padding: ResolvedPadding;
253
+ format?: ValueFormat;
254
+ control: ControlVariant;
255
+ responsive: Partial<Record<DeviceClass, ResolvedResponsive>>;
256
+ rendererOverrides?: Record<string, RendererOverride>;
257
+ /** True when the node carries renderer-specific presentation nothing here understands. */
258
+ opaque: boolean;
259
+ /** Property name → the layer that decided it. */
260
+ origins: Record<string, PresentationOrigin>;
261
+ }
262
+ /** Legacy role names, mapped onto the 0.5 vocabulary. */
263
+ export declare function normalizeRole(role: Presentation['role']): PresentationRole | undefined;
264
+ /** Legacy density names, mapped onto the 0.5 vocabulary. */
265
+ export declare function normalizeDensity(density: Presentation['density']): Density | undefined;
266
+ export declare function normalizeLayout(layout: LayoutIntent | undefined): LayoutPresentation | undefined;
267
+ export declare function normalizePadding(padding: PaddingIntent | undefined): ResolvedPadding | undefined;
268
+ /** True when a node carries renderer-specific presentation that cannot be analyzed. */
269
+ export declare function hasOpaquePresentation(presentation: Presentation | undefined): boolean;
270
+ //# sourceMappingURL=presentation.d.ts.map
@@ -0,0 +1,152 @@
1
+ export const PRESENTATION_ROLES = [
2
+ 'primary',
3
+ 'secondary',
4
+ 'tertiary',
5
+ 'destructive',
6
+ 'success',
7
+ 'warning',
8
+ 'informational',
9
+ 'muted',
10
+ ];
11
+ export const EMPHASIS_LEVELS = ['subtle', 'normal', 'strong'];
12
+ export const DENSITIES = ['compact', 'comfortable', 'spacious'];
13
+ export const SPACING_TOKENS = [
14
+ 'none',
15
+ 'xsmall',
16
+ 'small',
17
+ 'medium',
18
+ 'large',
19
+ 'xlarge',
20
+ ];
21
+ export const SIZING_TOKENS = ['fit', 'fill', 'content'];
22
+ export const BOUNDED_SIZES = ['narrow', 'medium', 'wide'];
23
+ export const SIZING_VALUES = [...SIZING_TOKENS, ...BOUNDED_SIZES];
24
+ export const ALIGNMENTS = ['start', 'center', 'end', 'stretch'];
25
+ export const JUSTIFICATIONS = [...ALIGNMENTS, 'between'];
26
+ export const LAYOUT_KINDS = ['vertical', 'horizontal', 'grid', 'stack'];
27
+ export const SURFACE_ROLES = ['transparent', 'base', 'subtle', 'raised', 'inset'];
28
+ export const TEXT_ROLES = ['body', 'caption', 'label', 'heading', 'title', 'display'];
29
+ export const UX_ROLES = [
30
+ 'primary-action',
31
+ 'secondary-action',
32
+ 'destructive-action',
33
+ 'navigation-action',
34
+ 'form-section',
35
+ 'action-group',
36
+ 'navigation-group',
37
+ 'empty-state',
38
+ 'error-state',
39
+ 'warning-state',
40
+ 'success-state',
41
+ 'informational-state',
42
+ 'toolbar',
43
+ 'sidebar',
44
+ 'content-region',
45
+ 'header-region',
46
+ 'footer-region',
47
+ ];
48
+ /** UX roles that name an action's place in the hierarchy rather than a region. */
49
+ export const ACTION_UX_ROLES = [
50
+ 'primary-action',
51
+ 'secondary-action',
52
+ 'destructive-action',
53
+ 'navigation-action',
54
+ ];
55
+ /** UX roles that report a condition, and which a renderer may announce. */
56
+ export const STATUS_UX_ROLES = [
57
+ 'empty-state',
58
+ 'error-state',
59
+ 'warning-state',
60
+ 'success-state',
61
+ 'informational-state',
62
+ ];
63
+ export const TREATMENTS = ['plain', 'badge', 'pill'];
64
+ export const CONTROL_VARIANTS = [
65
+ 'default',
66
+ 'switch',
67
+ 'checkbox',
68
+ 'radio-group',
69
+ 'select',
70
+ 'multiline',
71
+ 'stepper',
72
+ ];
73
+ export const ICON_NAMES = [
74
+ 'add',
75
+ 'delete',
76
+ 'edit',
77
+ 'save',
78
+ 'close',
79
+ 'warning',
80
+ 'success',
81
+ 'error',
82
+ 'information',
83
+ 'navigation-back',
84
+ 'navigation-forward',
85
+ 'menu',
86
+ 'search',
87
+ 'refresh',
88
+ 'settings',
89
+ 'more',
90
+ ];
91
+ export const DEVICE_CLASSES = ['compact', 'regular', 'wide'];
92
+ export const VALUE_FORMAT_KINDS = [
93
+ 'text',
94
+ 'number',
95
+ 'currency',
96
+ 'percentage',
97
+ 'boolean',
98
+ 'date',
99
+ 'datetime',
100
+ ];
101
+ export const PRESENTATION_ORIGINS = [
102
+ 'renderer-default',
103
+ 'theme',
104
+ 'inherited',
105
+ 'inferred',
106
+ 'node',
107
+ 'responsive',
108
+ ];
109
+ // --------------------------------------------------------------- normalization
110
+ /** Legacy role names, mapped onto the 0.5 vocabulary. */
111
+ export function normalizeRole(role) {
112
+ if (role === undefined) {
113
+ return undefined;
114
+ }
115
+ if (role === 'danger') {
116
+ return 'destructive';
117
+ }
118
+ return PRESENTATION_ROLES.includes(role) ? role : undefined;
119
+ }
120
+ /** Legacy density names, mapped onto the 0.5 vocabulary. */
121
+ export function normalizeDensity(density) {
122
+ if (density === undefined) {
123
+ return undefined;
124
+ }
125
+ if (density === 'normal') {
126
+ return 'comfortable';
127
+ }
128
+ return DENSITIES.includes(density) ? density : undefined;
129
+ }
130
+ export function normalizeLayout(layout) {
131
+ if (layout === undefined) {
132
+ return undefined;
133
+ }
134
+ return typeof layout === 'string' ? { kind: layout } : layout;
135
+ }
136
+ export function normalizePadding(padding) {
137
+ if (padding === undefined) {
138
+ return undefined;
139
+ }
140
+ if (typeof padding === 'string') {
141
+ return { horizontal: padding, vertical: padding };
142
+ }
143
+ if (padding.horizontal === undefined && padding.vertical === undefined) {
144
+ return undefined;
145
+ }
146
+ return { horizontal: padding.horizontal ?? 'none', vertical: padding.vertical ?? 'none' };
147
+ }
148
+ /** True when a node carries renderer-specific presentation that cannot be analyzed. */
149
+ export function hasOpaquePresentation(presentation) {
150
+ const overrides = presentation?.rendererOverrides;
151
+ return Boolean(overrides && Object.keys(overrides).length > 0);
152
+ }