@object-ui/react 17.6.0 → 18.0.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 (103) hide show
  1. package/CHANGELOG.md +4263 -0
  2. package/README.md +383 -42
  3. package/dist/SchemaRenderer.d.ts +64 -7
  4. package/dist/SchemaRenderer.js +1326 -106
  5. package/dist/context/ActionContext.js +40 -1
  6. package/dist/context/AppShellContext.d.ts +27 -0
  7. package/dist/context/DiscussionContext.d.ts +27 -0
  8. package/dist/context/DiscussionContext.js +4 -0
  9. package/dist/context/DrillNavigationContext.d.ts +13 -0
  10. package/dist/context/InlineEditContext.d.ts +151 -1
  11. package/dist/context/InlineEditContext.js +36 -2
  12. package/dist/context/PrintPageContext.d.ts +60 -0
  13. package/dist/context/PrintPageContext.js +32 -0
  14. package/dist/context/RecordContext.d.ts +48 -5
  15. package/dist/context/RecordContext.js +10 -1
  16. package/dist/context/RelatedRecordActionsContext.d.ts +72 -2
  17. package/dist/context/RelatedRecordActionsContext.js +4 -0
  18. package/dist/context/SchemaRendererContext.d.ts +61 -6
  19. package/dist/context/SchemaRendererContext.js +39 -6
  20. package/dist/context/ThemeContext.js +1 -1
  21. package/dist/context/actionRunnerTranslator.d.ts +27 -0
  22. package/dist/context/actionRunnerTranslator.js +37 -0
  23. package/dist/context/index.d.ts +1 -0
  24. package/dist/context/index.js +1 -0
  25. package/dist/element-data-source/ElementApiDataSourcePanel.d.ts +28 -0
  26. package/dist/element-data-source/ElementApiDataSourcePanel.js +10 -0
  27. package/dist/element-data-source/ElementDataSourceGate.d.ts +118 -3
  28. package/dist/element-data-source/ElementDataSourceGate.js +401 -19
  29. package/dist/element-data-source/useElementApiDataSource.d.ts +119 -0
  30. package/dist/element-data-source/useElementApiDataSource.js +198 -0
  31. package/dist/hooks/index.d.ts +3 -1
  32. package/dist/hooks/index.js +10 -1
  33. package/dist/hooks/useActionEngine.js +46 -3
  34. package/dist/hooks/useActionRunner.js +4 -0
  35. package/dist/hooks/useActionTextLocalizer.d.ts +35 -9
  36. package/dist/hooks/useActionTextLocalizer.js +70 -14
  37. package/dist/hooks/useConfigBagEvaluator.d.ts +31 -0
  38. package/dist/hooks/useConfigBagEvaluator.js +40 -0
  39. package/dist/hooks/useDatasetDimensionLabels.js +25 -10
  40. package/dist/hooks/useDensityMode.d.ts +8 -2
  41. package/dist/hooks/useDensityMode.js +8 -2
  42. package/dist/hooks/useDiscovery.d.ts +1 -10
  43. package/dist/hooks/useETagCache.js +25 -4
  44. package/dist/hooks/useElementDataSource.d.ts +15 -2
  45. package/dist/hooks/useElementDataSource.js +24 -7
  46. package/dist/hooks/useExpression.d.ts +52 -59
  47. package/dist/hooks/useExpression.js +58 -90
  48. package/dist/hooks/useFilterScope.d.ts +22 -2
  49. package/dist/hooks/useFilterScope.js +29 -4
  50. package/dist/hooks/useGlobalUndo.d.ts +10 -0
  51. package/dist/hooks/useGlobalUndo.js +112 -46
  52. package/dist/hooks/useNavigationOverlay.d.ts +76 -11
  53. package/dist/hooks/useNavigationOverlay.js +119 -9
  54. package/dist/hooks/useOffline.js +65 -11
  55. package/dist/hooks/usePageAssignment.d.ts +33 -7
  56. package/dist/hooks/usePageAssignment.js +85 -21
  57. package/dist/hooks/useRecordSearch.d.ts +34 -1
  58. package/dist/hooks/useRecordSearch.js +42 -7
  59. package/dist/hooks/useResolvedFilter.d.ts +46 -0
  60. package/dist/hooks/useResolvedFilter.js +130 -0
  61. package/dist/hooks/useSchemaPersistence.d.ts +7 -0
  62. package/dist/hooks/useSchemaPersistence.js +144 -5
  63. package/dist/hooks/useSettledSchema.d.ts +123 -0
  64. package/dist/hooks/useSettledSchema.js +186 -0
  65. package/dist/hooks/useViewData.d.ts +10 -3
  66. package/dist/hooks/useViewData.js +5 -14
  67. package/dist/index.d.ts +9 -1
  68. package/dist/index.js +48 -1
  69. package/dist/schema-input.d.ts +38 -8
  70. package/dist/schema-input.js +39 -7
  71. package/dist/utils/configBag.d.ts +68 -0
  72. package/dist/utils/configBag.js +70 -0
  73. package/dist/utils/error-message.d.ts +1 -1
  74. package/dist/utils/error-message.js +1 -1
  75. package/dist/utils/inlineAria.d.ts +61 -0
  76. package/dist/utils/inlineAria.js +70 -0
  77. package/dist/utils/localeMap.d.ts +27 -0
  78. package/dist/utils/localeMap.js +106 -0
  79. package/dist/utils/nonGridRowCeiling.d.ts +44 -0
  80. package/dist/utils/nonGridRowCeiling.js +78 -0
  81. package/dist/utils/paramsBag.d.ts +28 -0
  82. package/dist/utils/paramsBag.js +146 -0
  83. package/dist/utils/propsBagDiagnostic.d.ts +196 -0
  84. package/dist/utils/propsBagDiagnostic.js +422 -0
  85. package/dist/utils/refusedDataPropDiagnostic.d.ts +44 -0
  86. package/dist/utils/refusedDataPropDiagnostic.js +94 -0
  87. package/dist/utils/unevaluatedExpression.d.ts +44 -1
  88. package/dist/utils/unevaluatedExpression.js +92 -11
  89. package/dist/utils/visibilityDiagnostic.d.ts +282 -21
  90. package/dist/utils/visibilityDiagnostic.js +321 -46
  91. package/package.json +10 -10
  92. package/dist/hooks/useClientNotifications.d.ts +0 -47
  93. package/dist/hooks/useClientNotifications.js +0 -162
  94. package/dist/spec-bridge/SpecBridge.d.ts +0 -28
  95. package/dist/spec-bridge/SpecBridge.js +0 -56
  96. package/dist/spec-bridge/bridges/form-view.d.ts +0 -158
  97. package/dist/spec-bridge/bridges/form-view.js +0 -170
  98. package/dist/spec-bridge/bridges/list-view.d.ts +0 -20
  99. package/dist/spec-bridge/bridges/list-view.js +0 -268
  100. package/dist/spec-bridge/index.d.ts +0 -11
  101. package/dist/spec-bridge/index.js +0 -10
  102. package/dist/spec-bridge/types.d.ts +0 -55
  103. package/dist/spec-bridge/types.js +0 -8
@@ -0,0 +1,44 @@
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
+ import React from 'react';
9
+ import type { NonGridCeilingResult } from '@object-ui/core';
10
+ /**
11
+ * The loud footnote a non-grid view shows when it drew only the first
12
+ * `NON_GRID_ROW_CEILING` rows of a larger result set (objectui#7210).
13
+ *
14
+ * Placement and tone follow objectui#7148's chart footnote — a `role="note"`
15
+ * line in muted small type directly under the visualisation, naming BOTH
16
+ * numbers, because the count is the half a reader cannot recover from the
17
+ * picture. A truncated schedule renders as a healthy, confident schedule of a
18
+ * fraction of itself; "some rows are missing" leaves it indistinguishable from
19
+ * a complete one, and `2,000 of 40,000` is the bit that was missing.
20
+ *
21
+ * Two sentences because there are two conditions: a known total states the
22
+ * fact with both numbers; an adapter that reported no `total` still gets a
23
+ * DEFINITE sentence (the probe row proves more rows exist), it simply cannot
24
+ * name how many.
25
+ *
26
+ * ## It takes the RESULT, and nothing else (objectui#7508, ruling A′)
27
+ *
28
+ * The only prop is the `NonGridCeilingResult` that `applyNonGridRowCeiling`
29
+ * returned. Every number the note prints is read off that one object — the
30
+ * drawn count is `result.rows.length`, the total is `result.total`, the
31
+ * verdict is `result.truncated` — so a footnote whose numbers disagree with
32
+ * the rows actually drawn is not something a caller can write. The earlier
33
+ * shape took `drawn` / `total` / `truncated` as three loose props, and every
34
+ * call site hand-wrote `drawn={NON_GRID_ROW_CEILING}` next to a `total` from
35
+ * somewhere else: an incoherent pair was expressible and nothing refused it.
36
+ * ⛔ No deprecated path keeps those props; there were no released consumers.
37
+ *
38
+ * Renders `null` when nothing was truncated, so a caller holding a result can
39
+ * mount it unconditionally and gains no wrapper element on the healthy path.
40
+ */
41
+ export declare function NonGridRowCeilingNote({ result, }: {
42
+ /** What `applyNonGridRowCeiling` returned for the rows being drawn. */
43
+ result: NonGridCeilingResult;
44
+ }): React.JSX.Element | null;
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useObjectTranslation, en } from '@object-ui/i18n';
3
+ // The non-React half of the non-grid row ceiling — `NON_GRID_ROW_CEILING`,
4
+ // `nonGridRowCeilingQuery`, `applyNonGridRowCeiling` and `NonGridCeilingResult`
5
+ // — lives in `@object-ui/core` beside the `extractRecords` it wraps
6
+ // (objectui#7508, ruling A′). Only the footnote is React, so only the footnote
7
+ // is here.
8
+ /**
9
+ * The loud footnote a non-grid view shows when it drew only the first
10
+ * `NON_GRID_ROW_CEILING` rows of a larger result set (objectui#7210).
11
+ *
12
+ * Placement and tone follow objectui#7148's chart footnote — a `role="note"`
13
+ * line in muted small type directly under the visualisation, naming BOTH
14
+ * numbers, because the count is the half a reader cannot recover from the
15
+ * picture. A truncated schedule renders as a healthy, confident schedule of a
16
+ * fraction of itself; "some rows are missing" leaves it indistinguishable from
17
+ * a complete one, and `2,000 of 40,000` is the bit that was missing.
18
+ *
19
+ * Two sentences because there are two conditions: a known total states the
20
+ * fact with both numbers; an adapter that reported no `total` still gets a
21
+ * DEFINITE sentence (the probe row proves more rows exist), it simply cannot
22
+ * name how many.
23
+ *
24
+ * ## It takes the RESULT, and nothing else (objectui#7508, ruling A′)
25
+ *
26
+ * The only prop is the `NonGridCeilingResult` that `applyNonGridRowCeiling`
27
+ * returned. Every number the note prints is read off that one object — the
28
+ * drawn count is `result.rows.length`, the total is `result.total`, the
29
+ * verdict is `result.truncated` — so a footnote whose numbers disagree with
30
+ * the rows actually drawn is not something a caller can write. The earlier
31
+ * shape took `drawn` / `total` / `truncated` as three loose props, and every
32
+ * call site hand-wrote `drawn={NON_GRID_ROW_CEILING}` next to a `total` from
33
+ * somewhere else: an incoherent pair was expressible and nothing refused it.
34
+ * ⛔ No deprecated path keeps those props; there were no released consumers.
35
+ *
36
+ * Renders `null` when nothing was truncated, so a caller holding a result can
37
+ * mount it unconditionally and gains no wrapper element on the healthy path.
38
+ */
39
+ export function NonGridRowCeilingNote({ result, }) {
40
+ // ⚠️ A HOOK AT RENDER, never a module-scope `createSafeTranslation(...)`
41
+ // factory, and that is a correctness constraint rather than taste. This
42
+ // module is re-exported from `@object-ui/react`'s entry, so anything at its
43
+ // module scope runs on IMPORT for every consumer that touches the barrel —
44
+ // and a factory call there throws inside any test that partially mocks
45
+ // `@object-ui/i18n` with an object literal instead of `importOriginal`,
46
+ // before a single assertion runs. Two such files went red in CI on exactly
47
+ // that, and the population is repo-wide rather than knowable from here, so
48
+ // the call moved to where it cannot fire at import time. `en` is likewise
49
+ // dereferenced HERE, at render, not in a module-scope constant.
50
+ //
51
+ // `useObjectTranslation` is the one that interpolates on the provider-less
52
+ // path too (objectui#6219), so `{{shown}}` / `{{total}}` are filled whether
53
+ // or not the host mounted an `I18nProvider`. The `defaultValue` is read from
54
+ // the `en` pack rather than retyped: both sit in the same eagerly-loaded
55
+ // chunk, so a hand copy would ship identical bytes twice, and reading the
56
+ // pack makes an inline default that disagrees with `en` unrepresentable
57
+ // instead of merely policed.
58
+ const { t } = useObjectTranslation();
59
+ if (!result.truncated)
60
+ return null;
61
+ const shown = result.rows.length;
62
+ const text = typeof result.total === 'number'
63
+ ? t('common.rowCeilingNote', {
64
+ shown,
65
+ total: result.total,
66
+ defaultValue: en.common.rowCeilingNote,
67
+ })
68
+ : t('common.rowCeilingNoteUnknownTotal', {
69
+ shown,
70
+ defaultValue: en.common.rowCeilingNoteUnknownTotal,
71
+ });
72
+ // `shrink-0` is the note's own, not a caller's: under a `flex-1` view pane in
73
+ // a fixed-height host a plain sibling can be clipped out of view — the
74
+ // construction objectui#7148's `ChartFootnote` measured, and the reason the
75
+ // gantt used to pass this exact class in. Outside a flex container it does
76
+ // nothing, so all four views take it and no caller needs a styling prop.
77
+ return (_jsx("p", { role: "note", "data-row-ceiling-note": "non-grid", className: "shrink-0 px-1 py-1 text-xs text-muted-foreground", children: text }));
78
+ }
@@ -0,0 +1,28 @@
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
+ /** The key this rule is about. One spelling, shared by both readers. */
9
+ export declare const PARAMS_KEY = "params";
10
+ /**
11
+ * Is this value a `params` BAG this rule walks? A plain object only - see the
12
+ * module header for why an array `params` (the definition list) is not.
13
+ */
14
+ export declare function isParamsBag(value: unknown): value is Record<string, unknown>;
15
+ /**
16
+ * Visit one string leaf. `path` spells where it sits the way an author would
17
+ * search for it: `params.target.id`, `params.ids[0]`.
18
+ */
19
+ export type ParamsLeafVisitor = (leaf: string, path: string) => unknown;
20
+ /**
21
+ * Map every string leaf of a `params` bag through `visit`, at any depth, and
22
+ * return the result - the bag itself when nothing changed (copy on write).
23
+ *
24
+ * The evaluation memo passes the evaluator; the dev diagnostic passes a visitor
25
+ * that records and returns the leaf unchanged, which is what makes the two
26
+ * radii one radius by construction.
27
+ */
28
+ export declare function mapParamsLeaves(bag: Record<string, unknown>, visit: ParamsLeafVisitor, basePath?: string): Record<string, unknown>;
@@ -0,0 +1,146 @@
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
+ * An action's `params` values are templates, evaluated where `properties` are
10
+ * (objectui#7867, ruling A, maintainer 「其他同意」 2026-09-20).
11
+ *
12
+ * ## The rule
13
+ *
14
+ * Every STRING LEAF of a node's `params` bag - the node-level `params` and
15
+ * `properties.params` alike, at any depth - is template-evaluated by the
16
+ * `SchemaRenderer` evaluation memo, with the same evaluator and the same
17
+ * scope (`record`, `current_user`, `page`, the host's ambient roots) that
18
+ * already evaluates `properties`. Before it, `params` was the one argument bag
19
+ * an `action:button` hands the runner, and nothing walked it: the node-level
20
+ * bag was never evaluated at all, and `properties.params` was ONE value of a
21
+ * per-value, shallow loop, so a template one level inside it was never
22
+ * visited. A metadata-authored `navigate_edit` could not name the record it
23
+ * sits on.
24
+ *
25
+ * ## Why this module exists, and why it is not in `SchemaRenderer.tsx`
26
+ *
27
+ * Two readers must agree on exactly one radius: the memo that EVALUATES the
28
+ * leaves, and the dev diagnostic (`unevaluatedExpression.ts`) that REPORTS the
29
+ * leaves still carrying `${…}` afterwards. That diagnostic's header states its
30
+ * own rule - "Depth: exactly the evaluator's own radius, and not one level
31
+ * more" - so a second copy of this walk would be a second definition of that
32
+ * radius, free to drift. Both import this one. It lives in its own module
33
+ * rather than in the diagnostic's because the memo runs in production and the
34
+ * diagnostic module is meant to be dead code there.
35
+ *
36
+ * ## What counts as a bag, a container and a leaf
37
+ *
38
+ * - **The bag** is a PLAIN object: an object literal or JSON object, whose
39
+ * prototype is `Object.prototype` or `null`. An ARRAY `params` is not a bag:
40
+ * it is the `ActionParam[]` definition list `action:button` routes to
41
+ * `actionParams` for the params dialog, whose members carry predicates and
42
+ * formats (`visible`, `titleFormat`, option `visibleWhen`) that are evaluated
43
+ * later, against another scope. It is left untouched, as before.
44
+ * - **Containers** inside the bag - plain objects and arrays - are walked.
45
+ * - **String leaves** are handed to the visitor. Everything else is passed
46
+ * through by IDENTITY and never looked inside: numbers, booleans, `null`,
47
+ * `undefined`, functions, and non-plain objects (`Date`, `Map`, class
48
+ * instances). Those are host-built values, not authored templates.
49
+ * - **Keys are never visited**, only values. A key that looks like a template
50
+ * stays exactly as written.
51
+ * - **An expression envelope** (`{ dialect, source }`) is a plain object like
52
+ * any other: it is walked, and its `source` string is a leaf. It is never
53
+ * collapsed to its `source` the way a bare top-level config value is.
54
+ * - **Cycles** end the walk: a container already on the current path is passed
55
+ * through unwalked. JSON metadata cannot carry one; a host-built schema can.
56
+ *
57
+ * ## Copy on write
58
+ *
59
+ * A container is copied only when one of its descendants changed, so a bag
60
+ * with no template in it comes back as the very object that went in, and a
61
+ * changed bag never mutates the authored one (the memo's input is the
62
+ * caller's schema). Nothing may depend on that identity (AGENTS.md #10); it is
63
+ * there so that a template-free bag stays byte-for-byte and reference-for-
64
+ * reference what it was.
65
+ */
66
+ import { isConfigBag } from './configBag.js';
67
+ /** The key this rule is about. One spelling, shared by both readers. */
68
+ export const PARAMS_KEY = 'params';
69
+ /**
70
+ * A plain object: an object literal / JSON object, and nothing more exotic.
71
+ *
72
+ * Built ON the package's one "is this a config bag?" answer (objectui#6761)
73
+ * and narrowed by prototype, rather than re-spelling the object/array test:
74
+ * this rule asks that question plus one more (a `Date` or class instance is a
75
+ * bag to {@link isConfigBag}, and is not something this walk looks inside).
76
+ */
77
+ function isPlainObject(value) {
78
+ if (!isConfigBag(value))
79
+ return false;
80
+ const proto = Object.getPrototypeOf(value);
81
+ return proto === Object.prototype || proto === null;
82
+ }
83
+ /**
84
+ * Is this value a `params` BAG this rule walks? A plain object only - see the
85
+ * module header for why an array `params` (the definition list) is not.
86
+ */
87
+ export function isParamsBag(value) {
88
+ return isPlainObject(value);
89
+ }
90
+ function walk(value, path, visit, onPath) {
91
+ if (typeof value === 'string')
92
+ return visit(value, path);
93
+ if (Array.isArray(value)) {
94
+ if (onPath.has(value))
95
+ return value;
96
+ onPath.add(value);
97
+ let copy;
98
+ for (let i = 0; i < value.length; i++) {
99
+ const next = walk(value[i], path + '[' + i + ']', visit, onPath);
100
+ if (next !== value[i]) {
101
+ if (!copy)
102
+ copy = value.slice();
103
+ copy[i] = next;
104
+ }
105
+ }
106
+ onPath.delete(value);
107
+ return copy ?? value;
108
+ }
109
+ if (isPlainObject(value)) {
110
+ if (onPath.has(value))
111
+ return value;
112
+ onPath.add(value);
113
+ let copy;
114
+ for (const key of Object.keys(value)) {
115
+ const leaf = value[key];
116
+ const next = walk(leaf, path + '.' + key, visit, onPath);
117
+ if (next !== leaf) {
118
+ if (!copy)
119
+ copy = { ...value };
120
+ // `defineProperty`, not assignment: an own `__proto__` key (which
121
+ // `JSON.parse` does produce) must stay an own data property rather than
122
+ // reach the prototype setter.
123
+ Object.defineProperty(copy, key, {
124
+ value: next,
125
+ writable: true,
126
+ enumerable: true,
127
+ configurable: true,
128
+ });
129
+ }
130
+ }
131
+ onPath.delete(value);
132
+ return copy ?? value;
133
+ }
134
+ return value;
135
+ }
136
+ /**
137
+ * Map every string leaf of a `params` bag through `visit`, at any depth, and
138
+ * return the result - the bag itself when nothing changed (copy on write).
139
+ *
140
+ * The evaluation memo passes the evaluator; the dev diagnostic passes a visitor
141
+ * that records and returns the leaf unchanged, which is what makes the two
142
+ * radii one radius by construction.
143
+ */
144
+ export function mapParamsLeaves(bag, visit, basePath = PARAMS_KEY) {
145
+ return walk(bag, basePath, visit, new Set());
146
+ }
@@ -0,0 +1,196 @@
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
+ * Does this node's renderer read the `props` bag as a config bag?
10
+ *
11
+ * Exported so the report and its pins cannot drift apart: one predicate, two
12
+ * readers. A non-string `type` answers `false` — an untyped node cannot be in
13
+ * a family — and the caller has already resolved a component for it, so the
14
+ * unknown-type box is not in play.
15
+ */
16
+ export declare function readsPropsBag(type: unknown): boolean;
17
+ /** Prefix every line starts with — the handle tests, greps and log filters hold. */
18
+ export declare const DROPPED_PROPS_BAG_PREFIX = "[ObjectUI] A `props` config bag was not read";
19
+ /**
20
+ * Build the message. Separate from the emit so a test can assert the words a
21
+ * developer is going to read, not merely that something was logged.
22
+ */
23
+ export declare function formatDroppedPropsBagMessage(type: unknown, id: unknown, droppedKeys: readonly string[]): string;
24
+ /**
25
+ * Test-only reset for the dedupe above. Without it the second test to assert
26
+ * the same warning reads the first test's dedupe entry and sees silence — a
27
+ * green run that checked nothing.
28
+ */
29
+ export declare function __resetDroppedPropsBagWarnings(): void;
30
+ /**
31
+ * Which keys of the OUTGOING props bag are dropped by a `schema`-reading
32
+ * renderer, or `null` when there is nothing to say.
33
+ *
34
+ * Two bags are read, and needing both is a MEASUREMENT rather than caution.
35
+ *
36
+ * `outgoingPropsBag` is the bag `SchemaRenderer` actually spreads — the value
37
+ * of `propsWithoutCanonicalKeys(schema.props, schema.properties)`, passed in
38
+ * rather than re-derived. That is what makes the message honest about the two
39
+ * cases it must not confuse:
40
+ *
41
+ * - a key BOTH bags declare has already been subtracted by that function
42
+ * (objectui#5123: `properties` wins on both channels), so the author is
43
+ * getting the canonical answer and nothing was silently dropped;
44
+ * - a key only `props` declares survives into that bag, and it is exactly the
45
+ * key that reaches no `schema` reader.
46
+ *
47
+ * `authoredPropsBag` is what the AUTHOR wrote, read off the original schema
48
+ * before the evaluation memo touched it. It is needed because that memo
49
+ * rebuilds the bag with `{ ...newSchema.props }` under a bare truthiness guard,
50
+ * so a degenerate `props: 'not-a-bag'` arrives at the spread site as
51
+ * `{ '0': 'n', '1': 'o', … }` — measured, not supposed. Reading only the
52
+ * outgoing bag would therefore report nine dropped keys named `0` … `8` and
53
+ * tell the author that `schema.0` is undefined, which is true and useless. A
54
+ * string is not a config bag; there is nothing to point at, and the shape-level
55
+ * defect it represents is a different question from this one.
56
+ *
57
+ * Deliberately narrow, and each exclusion is a reading rather than an omission:
58
+ *
59
+ * - an EMPTY bag says nothing was authored and nothing was lost;
60
+ * - a non-object authored `props` is the degenerate case above;
61
+ * - a node in the {@link readsPropsBag} family is silent by the ruling.
62
+ */
63
+ export declare function collectDroppedPropsKeys(type: unknown, authoredPropsBag: unknown, outgoingPropsBag: unknown): string[] | null;
64
+ /**
65
+ * Dev-build only. Reports once per distinct message via `console.warn`, and
66
+ * returns the message it emitted (or `null`) so a caller or a test can read the
67
+ * decision rather than infer it from a spy.
68
+ *
69
+ * The caller applies the production gate, so this stays a single dev-only
70
+ * branch at the call site and the whole module is dead code in a production
71
+ * build.
72
+ */
73
+ export declare function reportDroppedPropsBag(type: unknown, id: unknown, authoredPropsBag: unknown, outgoingPropsBag: unknown): string | null;
74
+ /**
75
+ * Refusal: a NODE-GATE PREDICATE was parked under `props` (objectui#9108,
76
+ * maintainer ruling 2026-09-13, verbatim 「同意」 on the `domain:spec` seat's
77
+ * recommendation - REFUSE).
78
+ *
79
+ * ## The defect this refuses
80
+ *
81
+ * Measured at node level on `1e0e46af9`, four rows, both spellings and both
82
+ * polarities:
83
+ *
84
+ * { type: 'card', props: { visible: false } } -> the node RENDERED
85
+ * { type: 'card', props: { hidden: true } } -> the node RENDERED
86
+ * { type: 'card', properties: { visible: false } } -> correctly hidden
87
+ * { type: 'card', properties: { hidden: true } } -> correctly hidden
88
+ *
89
+ * `SchemaRenderer` hoists `properties.*` onto the node; NOTHING copies
90
+ * `props.*`. Both node gates read the post-hoist node, so a predicate that
91
+ * arrived under the alias is never one of the keys either gate can see. It is
92
+ * fail-OPEN and silent by construction - a gate that never bit renders exactly
93
+ * like a gate that said yes - so no user, no screenshot and no snapshot can
94
+ * find it. Only counting can.
95
+ *
96
+ * ## Why REFUSE and not HONOUR - this is a ruling, not a preference
97
+ *
98
+ * The opposite arm was implemented and CLOSED (PR objectui#9144). The ruling's
99
+ * decisive axis: honouring the alias would have made
100
+ * *"8 predicate keys work while the rest stayed silently dropped - and partly
101
+ * working is harder to learn from than not working"*, because the author
102
+ * generalises "`props` is fine" and collides again on the next key with the
103
+ * cause now further away. The three measured readings behind it, each with a
104
+ * control that fires: `@objectstack/spec` already refuses `props` by name
105
+ * (`unrecognized_keys ["props"]`); `skills/objectui/rules/protocol.md` teaches
106
+ * it as an ERROR rather than as a gap; and the producer census found ZERO
107
+ * authored `props` bags across `examples/**` and `content/docs/**` (controls:
108
+ * `visibleWhen` 9 files, `type` 488 files), so there is no capability to cut.
109
+ *
110
+ * ## What it deliberately does NOT do
111
+ *
112
+ * It changes NO verdict and NO rendered byte. Every gate answers exactly what
113
+ * it answered before, nothing is hoisted, `schema.<KEY>` stays undefined for a
114
+ * renderer declared as `({ schema })`, and the bag reaches the element with the
115
+ * same keys it always did. The trap stops being SILENT; it does not stop being
116
+ * a trap, which is the same posture objectui#6708 took one card earlier and the
117
+ * only posture a diagnostic can take without becoming the honour arm the ruling
118
+ * refused.
119
+ *
120
+ * ## Why `console.error`, and why NOT dev-only
121
+ *
122
+ * Two established postures in this tree, and this leg needs a leg of each:
123
+ *
124
+ * - SEVERITY from `unevaluatedExpression.ts`, which is this repo's REFUSAL
125
+ * tier. Its neighbour {@link reportDroppedPropsBag} warns because it
126
+ * reports a value that was dropped; the ruling's word here is *refused*,
127
+ * and the sibling gate diagnostic states the split explicitly ("not the
128
+ * refusal `reportUnevaluatedExpressions` emits").
129
+ * - ALWAYS-ON from `visibilityDiagnostic.ts`'s unresolvable-predicate leg,
130
+ * which the maintainer took out of `__DEV__` in objectui#6038 (ruling
131
+ * 2026-08-25, option B: "the silence is no longer an accepted property")
132
+ * on the ground that a node gate which has stopped biting in production is
133
+ * a class-1 defect that must not be able to sit live and undiscovered.
134
+ * This is that class in its widest form - the gate never bit at all, on
135
+ * every render - and the ruling's own confidence gap is about exactly the
136
+ * population a `__DEV__` gate would silence: *"The producer census covers
137
+ * this repository's `examples/**`, `content/docs/**` and skills corpus.
138
+ * It does NOT measure authored metadata in production. If stored documents
139
+ * out there do carry `props`, those authors are already suffering the
140
+ * silent drop today."*
141
+ *
142
+ * The rate limit that makes an always-on console line affordable is the same
143
+ * one objectui#6038 required: the dedupe below is keyed on the MESSAGE, so the
144
+ * ceiling is one line per distinct authoring bug for the lifetime of the page,
145
+ * not one line per render and not one per node.
146
+ */
147
+ export declare const REFUSED_PROPS_PREDICATE_PREFIX = "[ObjectUI] A node-gate predicate under `props` is REFUSED";
148
+ /**
149
+ * Build the refusal message. Separate from the emit so a test can assert the
150
+ * words a developer is going to read, not merely that something was logged.
151
+ *
152
+ * Carries the MIGRATION LINE the ruling requires by name - *"the implementing
153
+ * round should pair the refusal with a named migration line in the diagnostic,
154
+ * not let half the keys quietly start working"*. It names the keys it refused
155
+ * and the two spellings that do work, and it names nothing else: there is no
156
+ * per-key canonical-alias table here, because inventing one would be a second
157
+ * declaration of ADR-0089's normalization competing with the spec's own.
158
+ */
159
+ export declare function formatRefusedPropsPredicateMessage(type: unknown, id: unknown, refusedKeys: readonly string[]): string;
160
+ /**
161
+ * Which node-gate predicate keys of the PARKED `props` bag are refused, or
162
+ * `null` when there is nothing to say.
163
+ *
164
+ * `nodeGateKeys` is passed IN rather than declared here: `SchemaRenderer` owns
165
+ * the two chain declarations and derives this union from them, so a leg added
166
+ * to either chain is covered by the same edit that adds it and this module
167
+ * never grows a twin list to drift from them (AGENTS.md #9).
168
+ *
169
+ * `parkedPropsBag` is the value of
170
+ * `propsWithoutCanonicalKeys(schema.props, schema.properties)` - the same
171
+ * subtraction the outgoing bag and the objectui#6708 diagnostic already use, so
172
+ * the two cases stay un-confused exactly as they are there: a key BOTH bags
173
+ * declare has already been subtracted (objectui#5123: `properties` wins, the
174
+ * author is getting the canonical answer and nothing is parked), while a key
175
+ * only `props` declares survives into that bag and is precisely the one no gate
176
+ * can see. A degenerate `props` contributes no keys through that same function,
177
+ * so `props: 'not-a-bag'` refuses nothing here - its defect is a different
178
+ * question (objectui#6752).
179
+ */
180
+ export declare function collectRefusedPropsPredicateKeys(type: unknown, nodeGateKeys: ReadonlySet<string>, parkedPropsBag: unknown): string[] | null;
181
+ /**
182
+ * Test-only reset for the dedupe above. Without it the second test to assert
183
+ * the same refusal reads the first test's dedupe entry and sees silence - a
184
+ * green run that checked nothing.
185
+ */
186
+ export declare function __resetRefusedPropsPredicateWarnings(): void;
187
+ /**
188
+ * Reports a node-gate predicate parked under `props`, in DEVELOPMENT AND IN
189
+ * PRODUCTION - read {@link REFUSED_PROPS_PREDICATE_PREFIX}'s docblock for why
190
+ * this leg carries neither the `__DEV__` gate nor the `console.warn` severity
191
+ * of its neighbour in this module.
192
+ *
193
+ * Returns the message it emitted (or `null`) so a caller or a test can read the
194
+ * decision rather than infer it from a spy.
195
+ */
196
+ export declare function reportRefusedPropsPredicate(type: unknown, id: unknown, nodeGateKeys: ReadonlySet<string>, parkedPropsBag: unknown): string | null;