@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.
- package/CHANGELOG.md +4263 -0
- package/README.md +383 -42
- package/dist/SchemaRenderer.d.ts +64 -7
- package/dist/SchemaRenderer.js +1326 -106
- package/dist/context/ActionContext.js +40 -1
- package/dist/context/AppShellContext.d.ts +27 -0
- package/dist/context/DiscussionContext.d.ts +27 -0
- package/dist/context/DiscussionContext.js +4 -0
- package/dist/context/DrillNavigationContext.d.ts +13 -0
- package/dist/context/InlineEditContext.d.ts +151 -1
- package/dist/context/InlineEditContext.js +36 -2
- package/dist/context/PrintPageContext.d.ts +60 -0
- package/dist/context/PrintPageContext.js +32 -0
- package/dist/context/RecordContext.d.ts +48 -5
- package/dist/context/RecordContext.js +10 -1
- package/dist/context/RelatedRecordActionsContext.d.ts +72 -2
- package/dist/context/RelatedRecordActionsContext.js +4 -0
- package/dist/context/SchemaRendererContext.d.ts +61 -6
- package/dist/context/SchemaRendererContext.js +39 -6
- package/dist/context/ThemeContext.js +1 -1
- package/dist/context/actionRunnerTranslator.d.ts +27 -0
- package/dist/context/actionRunnerTranslator.js +37 -0
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.js +1 -0
- package/dist/element-data-source/ElementApiDataSourcePanel.d.ts +28 -0
- package/dist/element-data-source/ElementApiDataSourcePanel.js +10 -0
- package/dist/element-data-source/ElementDataSourceGate.d.ts +118 -3
- package/dist/element-data-source/ElementDataSourceGate.js +401 -19
- package/dist/element-data-source/useElementApiDataSource.d.ts +119 -0
- package/dist/element-data-source/useElementApiDataSource.js +198 -0
- package/dist/hooks/index.d.ts +3 -1
- package/dist/hooks/index.js +10 -1
- package/dist/hooks/useActionEngine.js +46 -3
- package/dist/hooks/useActionRunner.js +4 -0
- package/dist/hooks/useActionTextLocalizer.d.ts +35 -9
- package/dist/hooks/useActionTextLocalizer.js +70 -14
- package/dist/hooks/useConfigBagEvaluator.d.ts +31 -0
- package/dist/hooks/useConfigBagEvaluator.js +40 -0
- package/dist/hooks/useDatasetDimensionLabels.js +25 -10
- package/dist/hooks/useDensityMode.d.ts +8 -2
- package/dist/hooks/useDensityMode.js +8 -2
- package/dist/hooks/useDiscovery.d.ts +1 -10
- package/dist/hooks/useETagCache.js +25 -4
- package/dist/hooks/useElementDataSource.d.ts +15 -2
- package/dist/hooks/useElementDataSource.js +24 -7
- package/dist/hooks/useExpression.d.ts +52 -59
- package/dist/hooks/useExpression.js +58 -90
- package/dist/hooks/useFilterScope.d.ts +22 -2
- package/dist/hooks/useFilterScope.js +29 -4
- package/dist/hooks/useGlobalUndo.d.ts +10 -0
- package/dist/hooks/useGlobalUndo.js +112 -46
- package/dist/hooks/useNavigationOverlay.d.ts +76 -11
- package/dist/hooks/useNavigationOverlay.js +119 -9
- package/dist/hooks/useOffline.js +65 -11
- package/dist/hooks/usePageAssignment.d.ts +33 -7
- package/dist/hooks/usePageAssignment.js +85 -21
- package/dist/hooks/useRecordSearch.d.ts +34 -1
- package/dist/hooks/useRecordSearch.js +42 -7
- package/dist/hooks/useResolvedFilter.d.ts +46 -0
- package/dist/hooks/useResolvedFilter.js +130 -0
- package/dist/hooks/useSchemaPersistence.d.ts +7 -0
- package/dist/hooks/useSchemaPersistence.js +144 -5
- package/dist/hooks/useSettledSchema.d.ts +123 -0
- package/dist/hooks/useSettledSchema.js +186 -0
- package/dist/hooks/useViewData.d.ts +10 -3
- package/dist/hooks/useViewData.js +5 -14
- package/dist/index.d.ts +9 -1
- package/dist/index.js +48 -1
- package/dist/schema-input.d.ts +38 -8
- package/dist/schema-input.js +39 -7
- package/dist/utils/configBag.d.ts +68 -0
- package/dist/utils/configBag.js +70 -0
- package/dist/utils/error-message.d.ts +1 -1
- package/dist/utils/error-message.js +1 -1
- package/dist/utils/inlineAria.d.ts +61 -0
- package/dist/utils/inlineAria.js +70 -0
- package/dist/utils/localeMap.d.ts +27 -0
- package/dist/utils/localeMap.js +106 -0
- package/dist/utils/nonGridRowCeiling.d.ts +44 -0
- package/dist/utils/nonGridRowCeiling.js +78 -0
- package/dist/utils/paramsBag.d.ts +28 -0
- package/dist/utils/paramsBag.js +146 -0
- package/dist/utils/propsBagDiagnostic.d.ts +196 -0
- package/dist/utils/propsBagDiagnostic.js +422 -0
- package/dist/utils/refusedDataPropDiagnostic.d.ts +44 -0
- package/dist/utils/refusedDataPropDiagnostic.js +94 -0
- package/dist/utils/unevaluatedExpression.d.ts +44 -1
- package/dist/utils/unevaluatedExpression.js +92 -11
- package/dist/utils/visibilityDiagnostic.d.ts +282 -21
- package/dist/utils/visibilityDiagnostic.js +321 -46
- package/package.json +10 -10
- package/dist/hooks/useClientNotifications.d.ts +0 -47
- package/dist/hooks/useClientNotifications.js +0 -162
- package/dist/spec-bridge/SpecBridge.d.ts +0 -28
- package/dist/spec-bridge/SpecBridge.js +0 -56
- package/dist/spec-bridge/bridges/form-view.d.ts +0 -158
- package/dist/spec-bridge/bridges/form-view.js +0 -170
- package/dist/spec-bridge/bridges/list-view.d.ts +0 -20
- package/dist/spec-bridge/bridges/list-view.js +0 -268
- package/dist/spec-bridge/index.d.ts +0 -11
- package/dist/spec-bridge/index.js +0 -10
- package/dist/spec-bridge/types.d.ts +0 -55
- 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;
|