@object-ui/core 17.5.0 → 17.7.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 (157) hide show
  1. package/CHANGELOG.md +7404 -0
  2. package/README.md +127 -26
  3. package/dist/actions/ActionEngine.d.ts +5 -3
  4. package/dist/actions/ActionEngine.js +14 -10
  5. package/dist/actions/ActionRunner.d.ts +363 -30
  6. package/dist/actions/ActionRunner.js +544 -58
  7. package/dist/actions/TransactionManager.d.ts +7 -2
  8. package/dist/actions/actionKeys.d.ts +75 -13
  9. package/dist/actions/actionKeys.js +131 -16
  10. package/dist/actions/actionResponse.js +3 -3
  11. package/dist/actions/index.d.ts +2 -0
  12. package/dist/actions/index.js +2 -0
  13. package/dist/actions/recordDelete.d.ts +118 -0
  14. package/dist/actions/recordDelete.js +112 -0
  15. package/dist/actions/recordIdParam.d.ts +77 -0
  16. package/dist/actions/recordIdParam.js +37 -0
  17. package/dist/actions/serverActionHandler.d.ts +1 -1
  18. package/dist/actions/serverActionHandler.js +1 -1
  19. package/dist/adapters/ValueDataSource.d.ts +6 -1
  20. package/dist/adapters/ValueDataSource.js +1090 -98
  21. package/dist/adapters/batchTransaction.d.ts +1 -1
  22. package/dist/adapters/batchTransaction.js +1 -1
  23. package/dist/builder/schema-builder.d.ts +38 -81
  24. package/dist/builder/schema-builder.js +33 -152
  25. package/dist/data-scope/ViewDataProvider.d.ts +1 -1
  26. package/dist/data-scope/ViewDataProvider.js +21 -1
  27. package/dist/data-scope/element-data-source.d.ts +261 -7
  28. package/dist/data-scope/element-data-source.js +342 -52
  29. package/dist/data-scope/index.d.ts +9 -4
  30. package/dist/data-scope/index.js +9 -4
  31. package/dist/evaluator/ExpressionEvaluator.d.ts +128 -1
  32. package/dist/evaluator/ExpressionEvaluator.js +226 -13
  33. package/dist/evaluator/FormulaFunctions.d.ts +0 -10
  34. package/dist/evaluator/FormulaFunctions.js +166 -27
  35. package/dist/evaluator/SafeExpressionParser.js +0 -4
  36. package/dist/evaluator/declaredPredicate.d.ts +93 -9
  37. package/dist/evaluator/declaredPredicate.js +118 -17
  38. package/dist/evaluator/fieldRules.d.ts +107 -4
  39. package/dist/evaluator/fieldRules.js +231 -35
  40. package/dist/evaluator/index.d.ts +2 -0
  41. package/dist/evaluator/index.js +2 -0
  42. package/dist/evaluator/listConditional.d.ts +125 -9
  43. package/dist/evaluator/listConditional.js +167 -26
  44. package/dist/evaluator/optionRules.d.ts +126 -0
  45. package/dist/evaluator/optionRules.js +141 -4
  46. package/dist/evaluator/predicateInput.d.ts +15 -4
  47. package/dist/evaluator/predicateInput.js +35 -2
  48. package/dist/evaluator/rowPredicateCanon.d.ts +56 -0
  49. package/dist/evaluator/rowPredicateCanon.js +158 -0
  50. package/dist/evaluator/subjectPermissions.d.ts +76 -0
  51. package/dist/evaluator/subjectPermissions.js +44 -0
  52. package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
  53. package/dist/evaluator/unevaluablePredicate.js +70 -0
  54. package/dist/index.d.ts +18 -0
  55. package/dist/index.js +97 -2
  56. package/dist/protocols/index.d.ts +0 -1
  57. package/dist/protocols/index.js +9 -1
  58. package/dist/registry/Registry.d.ts +397 -48
  59. package/dist/registry/Registry.js +263 -11
  60. package/dist/registry/WidgetRegistry.js +7 -3
  61. package/dist/registry/html-tier-intrinsics.d.ts +90 -0
  62. package/dist/registry/html-tier-intrinsics.js +110 -0
  63. package/dist/registry/public-blocks.d.ts +8 -0
  64. package/dist/registry/public-blocks.js +57 -0
  65. package/dist/theme/ThemeEngine.js +1 -1
  66. package/dist/theme/index.d.ts +1 -1
  67. package/dist/theme/index.js +1 -1
  68. package/dist/types/index.d.ts +92 -13
  69. package/dist/utils/chart-category-key.d.ts +82 -0
  70. package/dist/utils/chart-category-key.js +84 -0
  71. package/dist/utils/chart-measure-key.d.ts +64 -0
  72. package/dist/utils/chart-measure-key.js +65 -0
  73. package/dist/utils/chart-presentation.d.ts +188 -0
  74. package/dist/utils/chart-presentation.js +345 -0
  75. package/dist/utils/chart-series.d.ts +271 -31
  76. package/dist/utils/chart-series.js +478 -55
  77. package/dist/utils/column-identity.d.ts +43 -0
  78. package/dist/utils/column-identity.js +47 -1
  79. package/dist/utils/column-sortability.d.ts +157 -0
  80. package/dist/utils/column-sortability.js +217 -0
  81. package/dist/utils/dashboard-filters.d.ts +38 -6
  82. package/dist/utils/dashboard-filters.js +107 -61
  83. package/dist/utils/dataset-format.d.ts +211 -30
  84. package/dist/utils/dataset-format.js +407 -52
  85. package/dist/utils/date-display.d.ts +354 -0
  86. package/dist/utils/date-display.js +463 -0
  87. package/dist/utils/date-macros.js +7 -0
  88. package/dist/utils/debug.js +7 -0
  89. package/dist/utils/drill-down.d.ts +78 -0
  90. package/dist/utils/drill-down.js +93 -0
  91. package/dist/utils/emptiness.d.ts +96 -0
  92. package/dist/utils/emptiness.js +101 -0
  93. package/dist/utils/expand-fields.d.ts +171 -11
  94. package/dist/utils/expand-fields.js +171 -11
  95. package/dist/utils/extract-records.d.ts +65 -5
  96. package/dist/utils/extract-records.js +65 -8
  97. package/dist/utils/filter-converter.d.ts +152 -4
  98. package/dist/utils/filter-converter.js +1092 -22
  99. package/dist/utils/filter-tokens.d.ts +49 -5
  100. package/dist/utils/filter-tokens.js +105 -34
  101. package/dist/utils/freeze-schema.d.ts +38 -1
  102. package/dist/utils/freeze-schema.js +10 -0
  103. package/dist/utils/grouping-fields.d.ts +61 -0
  104. package/dist/utils/grouping-fields.js +84 -0
  105. package/dist/utils/humanize-label.d.ts +60 -0
  106. package/dist/utils/humanize-label.js +62 -0
  107. package/dist/utils/managedBy.d.ts +30 -5
  108. package/dist/utils/managedBy.js +5 -5
  109. package/dist/utils/native-date-value.d.ts +48 -0
  110. package/dist/utils/native-date-value.js +167 -0
  111. package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
  112. package/dist/utils/non-grid-row-ceiling.js +141 -0
  113. package/dist/utils/normalize-list-view.d.ts +131 -1
  114. package/dist/utils/normalize-list-view.js +248 -6
  115. package/dist/utils/number-display.d.ts +70 -17
  116. package/dist/utils/number-display.js +34 -12
  117. package/dist/utils/object-aggregate-query.d.ts +113 -0
  118. package/dist/utils/object-aggregate-query.js +50 -0
  119. package/dist/utils/parent-scope.d.ts +123 -0
  120. package/dist/utils/parent-scope.js +140 -0
  121. package/dist/utils/predicate-fields.d.ts +62 -4
  122. package/dist/utils/predicate-fields.js +169 -4
  123. package/dist/utils/record-color.d.ts +75 -0
  124. package/dist/utils/record-color.js +65 -0
  125. package/dist/utils/record-source.d.ts +298 -0
  126. package/dist/utils/record-source.js +331 -0
  127. package/dist/utils/record-title.d.ts +148 -8
  128. package/dist/utils/record-title.js +220 -26
  129. package/dist/utils/reference-keys.d.ts +21 -22
  130. package/dist/utils/reference-keys.js +532 -29
  131. package/dist/utils/retired-field-types.d.ts +90 -0
  132. package/dist/utils/retired-field-types.js +124 -0
  133. package/dist/utils/sort-query.d.ts +128 -26
  134. package/dist/utils/sort-query.js +240 -13
  135. package/dist/utils/text-comparand.d.ts +92 -0
  136. package/dist/utils/text-comparand.js +99 -0
  137. package/dist/utils/unmaterialized-fields.d.ts +52 -0
  138. package/dist/utils/unmaterialized-fields.js +57 -0
  139. package/dist/utils/without-denied-fields.d.ts +49 -0
  140. package/dist/utils/without-denied-fields.js +52 -0
  141. package/dist/validation/index.d.ts +9 -4
  142. package/dist/validation/index.js +9 -4
  143. package/dist/validation/schema-validator.d.ts +1 -1
  144. package/dist/validation/schema-validator.js +305 -82
  145. package/dist/validation/server-owned-value.d.ts +62 -0
  146. package/dist/validation/server-owned-value.js +99 -0
  147. package/dist/validation/validators/object-validation-engine.d.ts +1 -1
  148. package/dist/validation/validators/object-validation-engine.js +14 -5
  149. package/package.json +8 -9
  150. package/dist/__benchmarks__/core.bench.d.ts +0 -8
  151. package/dist/__benchmarks__/core.bench.js +0 -53
  152. package/dist/data-scope/DataScopeManager.d.ts +0 -127
  153. package/dist/data-scope/DataScopeManager.js +0 -229
  154. package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
  155. package/dist/protocols/ResponsiveProtocol.js +0 -158
  156. package/dist/validation/validation-engine.d.ts +0 -88
  157. package/dist/validation/validation-engine.js +0 -434
@@ -21,6 +21,14 @@
21
21
  *
22
22
  * This is a single, reviewable source of truth for the public surface — prefer
23
23
  * editing this list over scattering `tier` flags across registration sites.
24
+ *
25
+ * ⛔ The html tier's intrinsic elements (`h1`, `p`, `a`, the sectioning tags, …)
26
+ * do NOT belong here. They reach the published manifest through the second
27
+ * roster beside this one, `HTML_TIER_INTRINSICS`, stamped `tier: 'html'`, so
28
+ * that the manifest's other reader — the objectstack gate that whitelists a
29
+ * `kind:'html'` page's tags — accepts them without this curated JSON-surface
30
+ * vocabulary growing (objectui#10735). Listing one here would widen the
31
+ * vocabulary silently; `html-tier-intrinsics.test.ts` pins the two disjoint.
24
32
  */
25
33
  export const PUBLIC_BLOCKS = [
26
34
  // ── Tier A — object-aware blocks (the contract core) ──────────────────────
@@ -34,6 +42,23 @@ export const PUBLIC_BLOCKS = [
34
42
  'object-gantt',
35
43
  'object-timeline',
36
44
  'object-map',
45
+ // Curated by objectui#10064, which is a NARROWING being undone rather than a
46
+ // widening: `@objectstack/spec` already declares `object-tree` on
47
+ // `ComponentPropsMap`, and @object-ui/plugin-tree has registered the renderer
48
+ // all along — this roster was the one face that withheld it, so the tier sat
49
+ // narrower than the declaration. Everything downstream reported that
50
+ // faithfully and could not fix it: `getPublicConfigs()` is what both manifest
51
+ // producers serialize, so the framework's tracked `sdui.manifest.json` could
52
+ // never carry a block the contract says exists, and moving the objectui pin
53
+ // could not help while the pinned source had nothing to carry
54
+ // (objectstack#18407, whose parity-gate half waits on this entry).
55
+ //
56
+ // The `tree` alias the same module registers is deliberately NOT here: it is
57
+ // a second spelling of this one block and @objectstack/spec declares no such
58
+ // key, so curating it would widen the vocabulary past the declaration — the
59
+ // opposite act — and leave an authoring model two names for one block, which
60
+ // is the ground `record:chatter` is held out on above.
61
+ 'object-tree',
37
62
  'object-metric',
38
63
  'object-chart',
39
64
  'dashboard',
@@ -102,6 +127,38 @@ export const PUBLIC_BLOCKS = [
102
127
  'tabs',
103
128
  'accordion',
104
129
  'container',
130
+ // The neutral block container, minted by the 2026-08-29 ruling as the JSON
131
+ // surface's replacement for the deprecated `div` (objectui#3965, PR #6878).
132
+ // It landed on every other declaration face — interface, zod mirror,
133
+ // `SchemaRegistry`, registration, docs page, and 27 catalog fixtures — and
134
+ // this list was the one face it missed, so the vocabulary taught a type the
135
+ // contract did not carry (`3619792bf`).
136
+ //
137
+ // MEASURED before it was added, because "add it and see" is how a gate
138
+ // discovers a new population at merge time. `registry-inputs-spec-parity`
139
+ // (apps/console) does NOT judge it, and not by tolerance: its coverage set is
140
+ // SPEC-shaped — `Object.keys(ComponentPropsMap)`, filtered to what this repo
141
+ // registers with inputs. It never reads this list, and membership here is
142
+ // neither necessary nor sufficient for being judged: `element:record_picker`
143
+ // is judged while absent from this list, and `flex` / `grid` / `stack` /
144
+ // `card` / `container` are all listed here and all unjudged, because
145
+ // `@objectstack/spec@17` carries no entry for ANY Tier B layout primitive. So
146
+ // `box` joins a spec-less population five types deep rather than opening a
147
+ // new one; it needs no spec entry, and no gate needed loosening to accept it.
148
+ //
149
+ // ⚠️ And "not on this list" never meant "not on an authoring surface". `box`
150
+ // has been validated all along through the OTHER consumer of the same
151
+ // `inputs`: `components/src/renderers/layout/page.tsx` builds the JSX-page
152
+ // compiler's manifest from `getKnownTypes()`, registry-wide. What this list
153
+ // gates is `getPublicConfigs()` — the curated AI-authoring vocabulary, and
154
+ // through `sdui-parser/scripts/gen-manifest.ts` the published
155
+ // `sdui.manifest.json` and `sdui-intrinsics.d.ts`. THAT is the surface `box`
156
+ // was missing from, which is a narrower and truer statement of the gap than
157
+ // "the contract does not know it".
158
+ //
159
+ // The measurement is pinned next to the roster census in `apps/console`, over
160
+ // the DERIVED container population rather than over this list.
161
+ 'box',
105
162
  'page:header',
106
163
  'text',
107
164
  'image',
@@ -124,7 +124,7 @@ export function generateColorVars(colors) {
124
124
  // (`fontSize` / `fontWeight` / `lineHeight` / `letterSpacing`, plus
125
125
  // `fontFamily.heading` / `fontFamily.mono`) became TOMBSTONES: the schema now
126
126
  // rejects them by name and their prescription points at `theme.customVars`, the
127
- // declared — and since #5021 the only — door for a custom property. A
127
+ // declared — and since objectstack-ai/objectstack#5021 the only — door for a custom property. A
128
128
  // `--z-modal` or a `--duration-fast` is authored there now, emitted verbatim as
129
129
  // `--<key>: <value>`.
130
130
  //
@@ -5,4 +5,4 @@
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
- export { hexToHSL, toCSSColor, generateColorVars, generateTypographyVars, generateBorderRadiusVars, generateShadowVars, generateThemeVars, mergeThemes, resolveThemeInheritance, resolveMode, contrastRatio, meetsContrastLevel, } from './ThemeEngine';
8
+ export { hexToHSL, toCSSColor, generateColorVars, generateTypographyVars, generateBorderRadiusVars, generateShadowVars, generateThemeVars, mergeThemes, resolveThemeInheritance, resolveMode, contrastRatio, meetsContrastLevel, } from './ThemeEngine.js';
@@ -11,4 +11,4 @@ export { hexToHSL, toCSSColor, generateColorVars, generateTypographyVars, genera
11
11
  // 17.0.0-rc.3 (objectstack#5021) — see ThemeEngine's RETIRED THEME BLOCKS
12
12
  // note. `theme.customVars` is the declared door for a `--duration-*` or
13
13
  // `--z-*` now.
14
- generateThemeVars, mergeThemes, resolveThemeInheritance, resolveMode, contrastRatio, meetsContrastLevel, } from './ThemeEngine';
14
+ generateThemeVars, mergeThemes, resolveThemeInheritance, resolveMode, contrastRatio, meetsContrastLevel, } from './ThemeEngine.js';
@@ -30,24 +30,103 @@
30
30
  * rather than restated, so there is exactly one declaration left to disagree
31
31
  * with — a structural copy would reproduce the defect the moment either side
32
32
  * moved. This package already depends on `@object-ui/types`, so the edge exists
33
- * and adds no cycle, and core's own entry surface is unchanged (`dist/index.d.ts`
34
- * is byte-identical across the change — measured, both rounds).
33
+ * and adds no cycle.
34
+ *
35
+ * ## The entry-surface sentence that used to close this block is WITHDRAWN
36
+ *
37
+ * It read: *core's own entry surface is unchanged (`dist/index.d.ts` is
38
+ * byte-identical across the change — measured, both rounds)*. The reading was
39
+ * real, and it certified nothing (objectui#5673).
40
+ *
41
+ * `core/dist/index.d.ts` is emitted from a barrel that only FORWARDS this
42
+ * symbol, and forwarding never restates a shape — not `export *`, and not the
43
+ * `export type { SchemaNode, … } from './types/index.js'` line that names it.
44
+ * Only the module that DECLARES the symbol can move. So that file is
45
+ * byte-identical under any change to a re-exported declaration's shape, naming
46
+ * the symbol or not, and a gauge that cannot fail for the change class it is
47
+ * quoted against has discharged nothing.
48
+ *
49
+ * ## The gauge that can fail — and how to prove it can, before believing it
50
+ *
51
+ * 1. Watch the emitted `.d.ts` of the module that DECLARES the symbol. For
52
+ * `SchemaNode` that is `@object-ui/types`' `dist/base.d.ts`, in another
53
+ * package — not core's entry, and not core's own `dist/types/index.d.ts`
54
+ * either, which forwards exactly as the entry does. For the shape claim
55
+ * itself, resolve the symbol through the TypeScript CHECKER from
56
+ * `core/dist/index.d.ts`.
57
+ * 2. Build both legs from a cleared `dist/` AND a cleared
58
+ * `tsconfig.tsbuildinfo` — the build info lives outside `dist/`, so
59
+ * composite `tsc` skips emit if it survives and two stale trees compare
60
+ * equal for free.
61
+ * 3. CALIBRATE: inject a probe key into the declaring module, rebuild, and
62
+ * require the hash you are watching to MOVE; drop the probe, rebuild, and
63
+ * require it to come back to its original value. A probe that leaves your
64
+ * hash unmoved means the gauge is not wired to this symbol — that is what
65
+ * failure looks like here, and no "byte-identical" sentence may be written
66
+ * from a gauge that just failed it.
67
+ *
68
+ * Run against this file's own claim — probe: one additive optional key on
69
+ * `BaseSchema`, the shape `SchemaNode` publishes — `@object-ui/types`'
70
+ * `dist/base.d.ts` moved and came back, while `core/dist/index.d.ts` and
71
+ * `core/dist/types/index.d.ts` held one hash across all three legs. The
72
+ * withdrawn sentence was watching the two files that cannot move.
35
73
  *
36
74
  * The collision is only observable from a package that resolves BOTH through
37
75
  * `node_modules`; the pin therefore lives in `@object-ui/react`
38
76
  * (`src/__tests__/SchemaNode.reconciliation.test.ts`), not here.
39
77
  */
40
78
  export type { SchemaNode } from '@object-ui/types';
41
- import type { SchemaNode } from '@object-ui/types';
42
79
  /**
43
- * ⛔ Deliberately NOT reconciled with `@object-ui/types`' `ComponentRendererProps`
44
- * (objectui#4594). The two declarations differ — types' is generic
45
- * (`< TSchema extends BaseSchema = BaseSchema >`), this one is not — but that
46
- * card measured **zero consumers** of this declaration, so reconciling it here
47
- * would be an unmeasured change riding a card that was scoped to `SchemaNode`.
48
- * It stays dual-declared until #4594 is dispatched on its own evidence.
80
+ * One `ComponentRendererProps` (objectui#4594).
81
+ *
82
+ * This package used to hand-declare `interface ComponentRendererProps
83
+ * { schema: SchemaNode; [key: string]: any }` here — two lines below `SchemaNode`
84
+ * and against `@object-ui/types`' generic
85
+ * `ComponentRendererProps< TSchema extends BaseSchema = BaseSchema >`. Same name,
86
+ * two packages the same consumers import together: the second such pair in this
87
+ * one file, and the third across this fault line after `SchemaNode` (#4580) and
88
+ * `ComponentInput` (#4972).
89
+ *
90
+ * **`@object-ui/types`' generic declaration wins**, and is RE-EXPORTED rather
91
+ * than restated, so there is exactly one declaration left to disagree with — a
92
+ * structural copy would reproduce the defect the moment either side moved.
93
+ *
94
+ * Reconciled now, at **zero consumers** — re-verified on the merged ref rather
95
+ * than inherited from the card: repo-wide, `ComponentRendererProps` occurred only
96
+ * at the two declarations, each package's own entry re-export, and one line of
97
+ * `packages/components/CHANGELOG.md` recording that nothing used it. Zero
98
+ * consumers is why this is cheap today and would not stay cheap: the day someone
99
+ * imports it, which declaration they get decides whether a primitive node is
100
+ * admissible.
101
+ *
102
+ * ## What the re-export moves, and how that was measured
103
+ *
104
+ * It is NOT surface-neutral, and the gauge that would have said so is vacuous.
105
+ * `core/dist/index.d.ts` is emitted from a barrel that only FORWARDS this
106
+ * symbol — its `export type { … }` line names it, and naming is not restating
107
+ * — so that file is byte-identical under ANY change to a re-exported
108
+ * declaration's shape and cannot fail for this change class (objectui#5673,
109
+ * where the `SchemaNode` block above withdraws the same reading and carries the
110
+ * probe calibration that tells a live gauge from this one).
111
+ *
112
+ * Measured instead by resolving the symbol through the TypeScript CHECKER from
113
+ * `core/dist/index.d.ts`, over a `dist/` + `tsconfig.tsbuildinfo` clean rebuild
114
+ * on both legs (the build info lives outside `dist/`, so composite `tsc` skips
115
+ * emit if it survives and two stale trees compare equal for free):
116
+ *
117
+ * ```
118
+ * before ComponentRendererProps schema: BaseSchema | string | number | boolean | null | undefined
119
+ * after ComponentRendererProps<TSchema> schema: TSchema (TSchema extends BaseSchema = BaseSchema)
120
+ * ```
121
+ *
122
+ * So `schema` NARROWS back to the object form — core's copy had silently widened
123
+ * when #4608 made `SchemaNode` a re-export of types' union, which is the interim
124
+ * state this card closes — and the type gains a parameter. Nothing downstream
125
+ * can observe either move, because nothing imports it.
126
+ *
127
+ * The collision is only observable from a package that resolves BOTH through
128
+ * `node_modules`; the pin therefore lives in `@object-ui/react`
129
+ * (`src/__tests__/ComponentRendererProps.reconciliation.test.ts`), not here —
130
+ * same position, same reason, as `SchemaNode`'s pin above.
49
131
  */
50
- export interface ComponentRendererProps {
51
- schema: SchemaNode;
52
- [key: string]: any;
53
- }
132
+ export type { ComponentRendererProps } from '@object-ui/types';
@@ -0,0 +1,82 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * chart-category-key — the ONE answer to "which result column carries the
10
+ * CATEGORY?" for an object-bound chart (objectui#8269).
11
+ *
12
+ * The mirror of `chart-measure-key` (objectui#8266) on the other axis. That
13
+ * card landed `chartMeasureKey` over the contract's `chartAggregateValueKey`;
14
+ * the contract publishes a CATEGORY sibling in the same module —
15
+ * `chartAggregateCategoryKey` — and nothing in this repository read it.
16
+ *
17
+ * ## The disagreement this replaces
18
+ *
19
+ * Both dashboard relays composed the category binding as a LITERAL FLOOR:
20
+ *
21
+ * const xAxisKey = options.xField || 'name';
22
+ *
23
+ * and handed it to the `object-chart` node without ever consulting the
24
+ * `aggregate` that decides it. An object-bound aggregate returns one row per
25
+ * group, keyed by the raw `groupBy` field, so a widget declaring
26
+ * `aggregate: { function: 'count', groupBy: 'status' }` and no `options.xField`
27
+ * bound `'name'` against rows keyed `'status'`.
28
+ *
29
+ * Unlike the measure half, this one is LOUD: `hasNoCategoryKey`
30
+ * (`plugin-charts/src/AdvancedChartImpl.tsx`, framework#4033) fires and the
31
+ * author reads "This chart cannot plot its category axis: no row has a `name`
32
+ * field." The diagnostic is wrong-CAUSE — it names a binding the author never
33
+ * wrote and never mentions the `groupBy` they did — so it points at the wrong
34
+ * layer. Being loud makes it a lesser harm than objectui#8266, not a
35
+ * non-defect: measured over the rows a fieldless count returns,
36
+ * `[{status:'open',count:2},{status:'paid',count:5}]`, an `xAxisKey` of
37
+ * `'name'` refuses under BOTH series bindings, i.e. objectui#8266's fix does
38
+ * not reach this authoring shape at all.
39
+ *
40
+ * ## Why the answer is delegated rather than restated
41
+ *
42
+ * Restating the rule here would make this file a second opinion of a question
43
+ * the contract already answers — the objectui#5042 / #7544 / #8193 / #8168
44
+ * drift shape that `chart-measure-key` exists to end. So the rule stays
45
+ * upstream in `@objectstack/spec/ui` and this is the seam objectui-side callers
46
+ * share.
47
+ *
48
+ * ## ⚠️ NOT the same function as `resolveChartCategoryField`
49
+ *
50
+ * `plugin-charts`' own `resolveChartCategoryField` (objectui#8168) reads
51
+ * `aggregate.groupBy` first too — which is exactly why `ObjectChart` does NOT
52
+ * refuse this shape at its own level, and then forwards `schema.xAxisKey`
53
+ * verbatim as the render binding anyway. But the two answer DIFFERENT
54
+ * questions and must not be collapsed:
55
+ *
56
+ * - `resolveChartCategoryField` answers "which FIELD is the category?" — its
57
+ * structured-`groupBy` leg returns `node.field`, because its two readers
58
+ * are the refusal (does the author name a category at all?) and the
59
+ * field-metadata probe that loads that field's option labels and colours.
60
+ * - this function answers "which COLUMN do the returned rows carry it
61
+ * under?" — `groupBy.alias ?? groupBy.field` per the contract, because an
62
+ * `alias` (admitted by `ChartGroupBySchema`) renames the projected column.
63
+ *
64
+ * They coincide whenever no alias is written, which is why the distinction is
65
+ * easy to miss; conflating them would either bind an axis to a column the rows
66
+ * do not carry, or probe field metadata for a field that does not exist.
67
+ */
68
+ import { type ChartAggregateLike } from '@objectstack/spec/ui';
69
+ /**
70
+ * The result column a chart's category axis / x-axis binding must name.
71
+ *
72
+ * `fallback` is the caller's own floor, used ONLY when the contract has no
73
+ * answer — a chart that declares no `aggregate` at all (its rows are raw
74
+ * records or authored literals, where the author's `xField` is the right key),
75
+ * an UNGROUPED aggregate (one row, no category column), or a `groupBy` shape
76
+ * `ChartGroupBySchema` already rejects. It is never a second opinion about an
77
+ * aggregate the contract CAN answer for.
78
+ *
79
+ * @param aggregate the chart's inline aggregate, or `undefined`
80
+ * @param fallback the key to bind when the contract has no answer
81
+ */
82
+ export declare function chartCategoryKey(aggregate: ChartAggregateLike | undefined, fallback: string): string;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * chart-category-key — the ONE answer to "which result column carries the
10
+ * CATEGORY?" for an object-bound chart (objectui#8269).
11
+ *
12
+ * The mirror of `chart-measure-key` (objectui#8266) on the other axis. That
13
+ * card landed `chartMeasureKey` over the contract's `chartAggregateValueKey`;
14
+ * the contract publishes a CATEGORY sibling in the same module —
15
+ * `chartAggregateCategoryKey` — and nothing in this repository read it.
16
+ *
17
+ * ## The disagreement this replaces
18
+ *
19
+ * Both dashboard relays composed the category binding as a LITERAL FLOOR:
20
+ *
21
+ * const xAxisKey = options.xField || 'name';
22
+ *
23
+ * and handed it to the `object-chart` node without ever consulting the
24
+ * `aggregate` that decides it. An object-bound aggregate returns one row per
25
+ * group, keyed by the raw `groupBy` field, so a widget declaring
26
+ * `aggregate: { function: 'count', groupBy: 'status' }` and no `options.xField`
27
+ * bound `'name'` against rows keyed `'status'`.
28
+ *
29
+ * Unlike the measure half, this one is LOUD: `hasNoCategoryKey`
30
+ * (`plugin-charts/src/AdvancedChartImpl.tsx`, framework#4033) fires and the
31
+ * author reads "This chart cannot plot its category axis: no row has a `name`
32
+ * field." The diagnostic is wrong-CAUSE — it names a binding the author never
33
+ * wrote and never mentions the `groupBy` they did — so it points at the wrong
34
+ * layer. Being loud makes it a lesser harm than objectui#8266, not a
35
+ * non-defect: measured over the rows a fieldless count returns,
36
+ * `[{status:'open',count:2},{status:'paid',count:5}]`, an `xAxisKey` of
37
+ * `'name'` refuses under BOTH series bindings, i.e. objectui#8266's fix does
38
+ * not reach this authoring shape at all.
39
+ *
40
+ * ## Why the answer is delegated rather than restated
41
+ *
42
+ * Restating the rule here would make this file a second opinion of a question
43
+ * the contract already answers — the objectui#5042 / #7544 / #8193 / #8168
44
+ * drift shape that `chart-measure-key` exists to end. So the rule stays
45
+ * upstream in `@objectstack/spec/ui` and this is the seam objectui-side callers
46
+ * share.
47
+ *
48
+ * ## ⚠️ NOT the same function as `resolveChartCategoryField`
49
+ *
50
+ * `plugin-charts`' own `resolveChartCategoryField` (objectui#8168) reads
51
+ * `aggregate.groupBy` first too — which is exactly why `ObjectChart` does NOT
52
+ * refuse this shape at its own level, and then forwards `schema.xAxisKey`
53
+ * verbatim as the render binding anyway. But the two answer DIFFERENT
54
+ * questions and must not be collapsed:
55
+ *
56
+ * - `resolveChartCategoryField` answers "which FIELD is the category?" — its
57
+ * structured-`groupBy` leg returns `node.field`, because its two readers
58
+ * are the refusal (does the author name a category at all?) and the
59
+ * field-metadata probe that loads that field's option labels and colours.
60
+ * - this function answers "which COLUMN do the returned rows carry it
61
+ * under?" — `groupBy.alias ?? groupBy.field` per the contract, because an
62
+ * `alias` (admitted by `ChartGroupBySchema`) renames the projected column.
63
+ *
64
+ * They coincide whenever no alias is written, which is why the distinction is
65
+ * easy to miss; conflating them would either bind an axis to a column the rows
66
+ * do not carry, or probe field metadata for a field that does not exist.
67
+ */
68
+ import { chartAggregateCategoryKey } from '@objectstack/spec/ui';
69
+ /**
70
+ * The result column a chart's category axis / x-axis binding must name.
71
+ *
72
+ * `fallback` is the caller's own floor, used ONLY when the contract has no
73
+ * answer — a chart that declares no `aggregate` at all (its rows are raw
74
+ * records or authored literals, where the author's `xField` is the right key),
75
+ * an UNGROUPED aggregate (one row, no category column), or a `groupBy` shape
76
+ * `ChartGroupBySchema` already rejects. It is never a second opinion about an
77
+ * aggregate the contract CAN answer for.
78
+ *
79
+ * @param aggregate the chart's inline aggregate, or `undefined`
80
+ * @param fallback the key to bind when the contract has no answer
81
+ */
82
+ export function chartCategoryKey(aggregate, fallback) {
83
+ return chartAggregateCategoryKey(aggregate) ?? fallback;
84
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * chart-measure-key — the ONE answer to "which result column carries the
10
+ * measure?" for an object-bound chart (objectui#8266).
11
+ *
12
+ * ## The disagreement this replaces
13
+ *
14
+ * The question was answered independently in three places, and for a FIELDLESS
15
+ * `count` — the normal way to author "how many records per status", and the
16
+ * most common dashboard chart there is — two of them answered differently:
17
+ *
18
+ * - the ROW PROJECTION (`aggregateValueKey` / `aggregateRecords` /
19
+ * `runAggregate`'s alias, all in `plugin-charts/src/ObjectChart.tsx`) keys
20
+ * the value column `'count'`, the alias the engine projects `COUNT(*)`
21
+ * under;
22
+ * - the SERIES BINDING, spelled twice in `plugin-dashboard`
23
+ * (`DashboardGridLayout.tsx` and `DashboardRenderer.tsx`), read
24
+ * `aggregate.field || (options.yField || 'value')` and so bound `'value'`.
25
+ *
26
+ * A `dataKey` naming a column no row carries plots nothing, and nothing says
27
+ * so: measured on `origin/main` `0fa7a9c83`, rows `[{status:'open',count:2},
28
+ * {status:'paid',count:5}]` with `series:[{dataKey:'value'}]` rendered a
29
+ * `.recharts-surface` with the category ticks "open"/"paid", **0 bars, 0
30
+ * rectangles**, no refusal and no empty state — the same rows under
31
+ * `dataKey:'count'` drew 2 rectangles. An empty chart reads as "no data yet",
32
+ * which is exactly what an author with a genuinely empty object also sees.
33
+ *
34
+ * ## Why the answer is delegated rather than restated
35
+ *
36
+ * `chartAggregateValueKey` in `@objectstack/spec/ui` is the CONTRACT's own
37
+ * derivation — "the VALUE column an object-bound aggregate produces … what a
38
+ * chart's series / y-axis binding must name". Producers and checkers already
39
+ * read it. Restating the rule here would make this file a fourth opinion of the
40
+ * same question, which is the shape (objectui#5042 / #7544 / #8193 / #8168)
41
+ * this module exists to end. So the rule lives upstream in the spec and this is
42
+ * the seam objectui-side callers share, exactly as `humanizeLabel` (objectui#5444)
43
+ * and `buildChartSeries` (ADR-0021) already are.
44
+ *
45
+ * ⛔ The inverse fix — making the row projection key a fieldless count
46
+ * `'value'` — is not available: it contradicts a pin the tree already carries
47
+ * (`ObjectChart.aggregateResultColumns.test.ts`) and renames the column every
48
+ * other consumer reads, including the engine's own `COUNT(*)` alias.
49
+ */
50
+ import { type ChartAggregateLike } from '@objectstack/spec/ui';
51
+ export type { ChartAggregateLike };
52
+ /**
53
+ * The result column a chart's series must bind to.
54
+ *
55
+ * `fallback` is the caller's own floor, used ONLY when the contract has no
56
+ * answer — a chart that declares no `aggregate` at all (its rows are raw
57
+ * records or authored literals, where the author's `yField` is the right key),
58
+ * or an aggregate shape `ChartAggregateSchema` already rejects. It is never a
59
+ * second opinion about an aggregate the contract CAN answer for.
60
+ *
61
+ * @param aggregate the chart's inline aggregate, or `undefined`
62
+ * @param fallback the key to bind when the contract has no answer
63
+ */
64
+ export declare function chartMeasureKey(aggregate: ChartAggregateLike | undefined, fallback: string): string;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * chart-measure-key — the ONE answer to "which result column carries the
10
+ * measure?" for an object-bound chart (objectui#8266).
11
+ *
12
+ * ## The disagreement this replaces
13
+ *
14
+ * The question was answered independently in three places, and for a FIELDLESS
15
+ * `count` — the normal way to author "how many records per status", and the
16
+ * most common dashboard chart there is — two of them answered differently:
17
+ *
18
+ * - the ROW PROJECTION (`aggregateValueKey` / `aggregateRecords` /
19
+ * `runAggregate`'s alias, all in `plugin-charts/src/ObjectChart.tsx`) keys
20
+ * the value column `'count'`, the alias the engine projects `COUNT(*)`
21
+ * under;
22
+ * - the SERIES BINDING, spelled twice in `plugin-dashboard`
23
+ * (`DashboardGridLayout.tsx` and `DashboardRenderer.tsx`), read
24
+ * `aggregate.field || (options.yField || 'value')` and so bound `'value'`.
25
+ *
26
+ * A `dataKey` naming a column no row carries plots nothing, and nothing says
27
+ * so: measured on `origin/main` `0fa7a9c83`, rows `[{status:'open',count:2},
28
+ * {status:'paid',count:5}]` with `series:[{dataKey:'value'}]` rendered a
29
+ * `.recharts-surface` with the category ticks "open"/"paid", **0 bars, 0
30
+ * rectangles**, no refusal and no empty state — the same rows under
31
+ * `dataKey:'count'` drew 2 rectangles. An empty chart reads as "no data yet",
32
+ * which is exactly what an author with a genuinely empty object also sees.
33
+ *
34
+ * ## Why the answer is delegated rather than restated
35
+ *
36
+ * `chartAggregateValueKey` in `@objectstack/spec/ui` is the CONTRACT's own
37
+ * derivation — "the VALUE column an object-bound aggregate produces … what a
38
+ * chart's series / y-axis binding must name". Producers and checkers already
39
+ * read it. Restating the rule here would make this file a fourth opinion of the
40
+ * same question, which is the shape (objectui#5042 / #7544 / #8193 / #8168)
41
+ * this module exists to end. So the rule lives upstream in the spec and this is
42
+ * the seam objectui-side callers share, exactly as `humanizeLabel` (objectui#5444)
43
+ * and `buildChartSeries` (ADR-0021) already are.
44
+ *
45
+ * ⛔ The inverse fix — making the row projection key a fieldless count
46
+ * `'value'` — is not available: it contradicts a pin the tree already carries
47
+ * (`ObjectChart.aggregateResultColumns.test.ts`) and renames the column every
48
+ * other consumer reads, including the engine's own `COUNT(*)` alias.
49
+ */
50
+ import { chartAggregateValueKey } from '@objectstack/spec/ui';
51
+ /**
52
+ * The result column a chart's series must bind to.
53
+ *
54
+ * `fallback` is the caller's own floor, used ONLY when the contract has no
55
+ * answer — a chart that declares no `aggregate` at all (its rows are raw
56
+ * records or authored literals, where the author's `yField` is the right key),
57
+ * or an aggregate shape `ChartAggregateSchema` already rejects. It is never a
58
+ * second opinion about an aggregate the contract CAN answer for.
59
+ *
60
+ * @param aggregate the chart's inline aggregate, or `undefined`
61
+ * @param fallback the key to bind when the contract has no answer
62
+ */
63
+ export function chartMeasureKey(aggregate, fallback) {
64
+ return chartAggregateValueKey(aggregate) ?? fallback;
65
+ }