From b12c6c0dfd5935f8c884cb5d668f7389cbe8fed5 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 12:43:29 +0000 Subject: [PATCH 1/2] fix(spec): a dataset dimension's nullLabel is live; move the objectui pin past its reader The console now draws a dataset chart's null bucket under the name its dimension declares. objectstack#25402 carries the name on the answer's column descriptor as `fields[].nullLabel`, and objectui#13354 reads it, so: - `.objectui-sha` moves 5fef032b1ada -> 1d178e06ac5e (objectui#13354's merge), with the regenerated SDUI manifest record and the console changeset the bump writes. - `liveness/dataset.json` re-grades `dimensions[].nullLabel` planned -> live, measured at that pin: evidence objectui DatasetWidget#declaredNullCategoryLabel, producer service-analytics enrichResultColumns. `authorWarn` goes with the flip. - The dataset form's Dimensions repeater offers the Null Label column, and the reconciliation ledger's planned-only omit row for it is deleted, as that ledger prescribes once the verdict moves. Claude-Session: https://claude.ai/code/session_016qAuAwFXMw94v5vFqTVcw2 Co-authored-by: Claude --- .changeset/25330-null-label-live.md | 13 +++++++++++++ .changeset/console-1d178e06ac5e.md | 18 ++++++++++++++++++ .objectui-sha | 2 +- packages/spec/liveness/dataset.json | 8 ++++---- .../metadata-form-zod-reconciliation.test.ts | 11 ----------- packages/spec/src/ui/dataset.form.ts | 1 + scripts/sdui-manifest.record.json | 4 ++-- 7 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 .changeset/25330-null-label-live.md create mode 100644 .changeset/console-1d178e06ac5e.md diff --git a/.changeset/25330-null-label-live.md b/.changeset/25330-null-label-live.md new file mode 100644 index 00000000000..cb50fc096af --- /dev/null +++ b/.changeset/25330-null-label-live.md @@ -0,0 +1,13 @@ +--- +'@objectstack/spec': patch +--- + +fix(spec): a dataset dimension's `nullLabel` is live, so `os lint` stops warning that nothing draws it, and the dataset form offers it + +Clause-②: no (a liveness-ledger re-grade plus a form column for a key the schema already accepts: no schema, accept set, served body or error code moves) +Metadata-protocol: none + +The console now draws a dataset chart's null bucket under the name its dimension declares: the server carries it on the answer's column descriptor as `fields[].nullLabel` (objectstack#25402), and the dashboard's dataset chart reads it (objectui#13354, at the `.objectui-sha` pin this release builds). So: + +- **Liveness:** `liveness/dataset.json` re-grades `dimensions[].nullLabel` from `planned` to `live`, citing the console's reader at the pin and the server's carry as its producer. Its `authorWarn` goes with it, so the advisory lint no longer tells an author who sets `nullLabel` that no renderer draws the bucket under it. +- **Dataset form:** the Dimensions repeater gains a Null Label column, the schema's own `.meta({ title })`. The form had left the key out only while nothing drew it. That reason went with the `planned` verdict, so its row in the form ↔ Zod reconciliation ledger is gone. diff --git a/.changeset/console-1d178e06ac5e.md b/.changeset/console-1d178e06ac5e.md new file mode 100644 index 00000000000..6bb59ab409a --- /dev/null +++ b/.changeset/console-1d178e06ac5e.md @@ -0,0 +1,18 @@ +--- +"@objectstack/console": patch +--- + +Console (objectui) refreshed to `1d178e06ac5e`. Frontend changes in this range: + +Derived from the changesets objectui declared over the range — 6 releasing of 6 changesets added across 5 non-merge commits. + +- **minor** — Setup → Positions now says who holds each position and which permission sets it grants, and a click anywhere on a row opens the item (objectstack#25333). (objectui `a14737b14`) +- **patch** — types: a node derived from an `@objectstack/spec` schema keeps the spec's `credential` registry key (`'value' | 'map'`, objectstack#25036, which marks the webhook's `secret` and `… (objectui `1d178e06a`) +- **patch** — dashboard: a dataset chart draws its null bucket under the name the dataset declares for it (`fields[].nullLabel`, the dimension's `nullLabel` resolved by the server), over the gl… (objectui `1d178e06a`) +- **patch** — Setup's Permission Sets and Positions lists ask before the Active switch turns an item off, and a toast confirms the change either way (objectui#13338). (objectui `fcbf1b280`) +- **patch** — A member who may read none of the objects an app names now lands on one page that says they need access and whom to ask, instead of the app's dashboard drawn as a grid of "You don… (objectui `2c4e5f5af`) +- **patch** — A single-series bar chart draws every bar in its series colour (objectstack#25330). (objectui `56b65c0ce`) + +Metadata-protocol: none (the console refresh changes no key, value or type a metadata author writes) + +objectui range: `5fef032b1ada...1d178e06ac5e` diff --git a/.objectui-sha b/.objectui-sha index 4f191d22fa3..b597742ebe8 100644 --- a/.objectui-sha +++ b/.objectui-sha @@ -1 +1 @@ -5fef032b1ada5b86bad640637e7331e26ee5da6e +1d178e06ac5eda824af06f7d68eaccdcf38dcf63 diff --git a/packages/spec/liveness/dataset.json b/packages/spec/liveness/dataset.json index c23b5a83014..8164d63bc5a 100644 --- a/packages/spec/liveness/dataset.json +++ b/packages/spec/liveness/dataset.json @@ -70,12 +70,12 @@ "verifiedAt": "2026-08-28" }, "nullLabel": { - "status": "planned", + "status": "live", "verifiedAt": "2026-10-23", "evidenceScope": "cross-repo", - "authorWarn": true, - "authorHint": "Keep it, but nothing reads it yet: no chart renderer draws a dimension's null bucket under its `nullLabel`, so the bucket still shows the renderer's own default label until the renderer half ships.", - "note": "PLANNED RECORD (#25391, 2026-10-23): declared spec-first by ruling 6358843349 on #25330 (letter A), whose consumer half follows under #25330 — the objectui chart renderer reads `nullLabel` over its global `chart.nullCategory` default, and `packages/platform-objects` `system.datasets.ts` sets `user_id`'s null label. Display copy in the `label` slot's own type (I18nLabelSchema); it changes no grouping. No reader on either side today: objectstack — the dataset compiler (packages/services/service-analytics/src/dataset-compiler.ts#compileDataset) copies `name` / `label` / `field` / `type` / `dateGranularity` into the Cube dimension and nothing else, and a dataset response's dimension descriptor is `{ name, type, label }`; objectui @0d12e953 (the `.objectui-sha` pin) and main @a14737b — the only `nullLabel` spellings are the local parameter of objectui: packages/core/src/utils/chart-series.ts#pivotSeriesBuckets, fed from `ChartSeriesOptions.nullCategoryLabel ?? NULL_CATEGORY_LABEL`, never from a dataset dimension. Which channel carries the key to the renderer (the served dataset document or the response's dimension descriptor) is the consumer half's decision, not this row's." + "evidence": "objectui @1d178e06ac5e (the `.objectui-sha` pin, objectui#13354) packages/plugin-dashboard/src/DatasetWidget.tsx#declaredNullCategoryLabel reads the category dimension's `fields[].nullLabel` off the dataset answer, and the widget passes it as the chart's `nullCategoryLabel` over its global `chart.nullCategory` default, so the null bucket is drawn, and drilled, under the declared name. On a two-dimension pivot it applies only when both dimensions declare the same name (`buildChartSeries` takes one null label for both arms) — re-read 2026-10-23", + "producer": "packages/services/service-analytics/src/analytics-service.ts#enrichResultColumns — the column-description pass shared by the live query and the draft-data preview resolves the dimension's `nullLabel` at the request locale (resolveI18nLabel, the `label` resolver) and writes it onto the dimension column's descriptor as `fields[].nullLabel` (objectstack#25402), the channel the renderer reads; a measure column never carries it", + "note": "FLIPPED planned → live 2026-10-23 (#25330, ruling 6358843349 letter A), the `print` (#23710) and `runAction` (#10068) shape: declared spec-first by #25391 with `authorWarn`, its consumer landed in three steps — objectstack#25402 carries the name on the answer's column descriptor (the producer), objectui#13354 draws the bucket under it, and this change moves `.objectui-sha` past that merge and re-measures at the pin. `authorWarn` goes with the flip. Display copy only: grouping is unchanged. First author: `packages/platform-objects` `system.datasets.ts`, whose `user_id` dimension names System Overview's null user bucket \"System\". Readers that still use only the global default (noted, not carried): objectui's `DatasetReportRenderer` and `ObjectChart`, and the flat table, which draws a null dimension value as `—`." } } }, diff --git a/packages/spec/src/system/metadata-form-zod-reconciliation.test.ts b/packages/spec/src/system/metadata-form-zod-reconciliation.test.ts index aaa41c31fe4..a4b4384222c 100644 --- a/packages/spec/src/system/metadata-form-zod-reconciliation.test.ts +++ b/packages/spec/src/system/metadata-form-zod-reconciliation.test.ts @@ -365,17 +365,6 @@ const LEDGER: ReadonlyArray = [ key: 'conditionalFormatting', why: "planned, not yet rendered — a field's cell formatting rules (`[{ condition, style }]`, the list view's own rule element) are validated at parse and their CEL is judged at the authoring doors (it parses, it reads only `value` and `record`), but no renderer styles a cell from them until objectui's cell renderers read them, after the spec publishes. A form control offered today would let an author set a style nothing paints", }, - // #25391 — a dataset dimension's null-bucket label lands spec-first the same - // way: `planned` in the liveness ledger, read by no chart renderer until the - // consumer half (ruling 6358843349 on #25330) ships. One list down, on the - // `dimensions` repeater rather than the root. - { - kind: 'omit', - type: 'dataset', - path: 'dimensions', - key: 'nullLabel', - why: "planned, not yet rendered — the label of a dimension's null bucket is validated at parse (an I18nLabel), but no chart renderer draws the bucket under it until objectui reads it over its own default, after the spec publishes. A repeater column offered today would let an author set a label nothing shows", - }, { kind: 'omit', type: 'view', diff --git a/packages/spec/src/ui/dataset.form.ts b/packages/spec/src/ui/dataset.form.ts index c0701a46100..b09b2b3e878 100644 --- a/packages/spec/src/ui/dataset.form.ts +++ b/packages/spec/src/ui/dataset.form.ts @@ -63,6 +63,7 @@ export const datasetForm = defineForm({ { field: 'field', label: 'Field' }, { field: 'type', label: 'Type' }, { field: 'dateGranularity', label: 'Date Granularity' }, + { field: 'nullLabel', label: 'Null Label' }, ], }, ], diff --git a/scripts/sdui-manifest.record.json b/scripts/sdui-manifest.record.json index 5b7f7a2bd65..5fac33647b1 100644 --- a/scripts/sdui-manifest.record.json +++ b/scripts/sdui-manifest.record.json @@ -11,9 +11,9 @@ "a pin bump without regeneration goes RED there — that is the anti-rot half of the freshness gate.", "Regenerate + re-record: pnpm objectui:build && node scripts/gen-sdui-manifest-node.mjs" ], - "objectuiSha": "5fef032b1ada5b86bad640637e7331e26ee5da6e", + "objectuiSha": "1d178e06ac5eda824af06f7d68eaccdcf38dcf63", "source": "built-tree", - "modulesRoot": ".cache/objectui-5fef032b1ada/apps/console", + "modulesRoot": ".cache/objectui-1d178e06ac5e/apps/console", "objectuiWorkspaceVersion": "18.0.0", "generator": "scripts/gen-sdui-manifest-node.mjs", "generatedAt": "2026-10-23", From 78302ab05f8b1c5841514020b67133d87c0e7d41 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 13:23:10 +0000 Subject: [PATCH 2/2] chore: regenerate what the pin move and the nullLabel flip owe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - packages/sdui-parser/objectui-lockstep.json re-recorded at the new pin 1d178e06ac5e (check:sdui-lockstep). - liveness/state-counts/dataset.md regenerated: dataset is 28 live, 0 planned. - The dataset form's new Null Label row label enters the metadata-form catalogs (en from the source; zh-CN 空值标签, ja-JP 空値ラベル, es-ES Etiqueta de nulos), and the two catalog census pins count it: 677 -> 678 translated leaves, 37 -> 38 dataset-form leaves. Claude-Session: https://claude.ai/code/session_016qAuAwFXMw94v5vFqTVcw2 Co-authored-by: Claude --- .../src/apps/translations/en.metadata-forms.generated.ts | 3 +++ .../src/apps/translations/es-ES.metadata-forms.generated.ts | 3 +++ .../src/apps/translations/ja-JP.metadata-forms.generated.ts | 3 +++ .../object-lifecycle-panel-echo-decisions.test.ts | 5 ++++- .../apps/translations/report-form-echo-decisions.test.ts | 5 +++-- .../src/apps/translations/zh-CN.metadata-forms.generated.ts | 3 +++ packages/sdui-parser/objectui-lockstep.json | 6 +++--- packages/spec/liveness/state-counts/dataset.md | 2 +- 8 files changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts index da4b54354dd..8f36470a4ad 100644 --- a/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts +++ b/packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts @@ -2039,6 +2039,9 @@ export const enMetadataForms: NonNullable = { "dimensions.dateGranularity": { label: "Date Granularity" }, + "dimensions.nullLabel": { + label: "Null Label" + }, measures: { label: "Measures", helpText: "Each: name, aggregate, field (optional for count), and display format/currency" diff --git a/packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts index cc8eb941495..b7065bdde9f 100644 --- a/packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts +++ b/packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts @@ -2039,6 +2039,9 @@ export const esESMetadataForms: NonNullable = "dimensions.dateGranularity": { label: "Granularidad de fecha" }, + "dimensions.nullLabel": { + label: "Etiqueta de nulos" + }, measures: { label: "Medidas", helpText: "Cada medida: nombre, agregación, campo (opcional para count) y formato de visualización/moneda" diff --git a/packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts index fcc00141656..c6141147be8 100644 --- a/packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts +++ b/packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts @@ -2039,6 +2039,9 @@ export const jaJPMetadataForms: NonNullable = "dimensions.dateGranularity": { label: "日付の粒度" }, + "dimensions.nullLabel": { + label: "空値ラベル" + }, measures: { label: "メジャー", helpText: "各メジャー:名前、集計関数、フィールド(count の場合は省略可)、表示形式/通貨" diff --git a/packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts b/packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts index 1b6e8479bd3..0dfe36cfa70 100644 --- a/packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts +++ b/packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts @@ -1212,7 +1212,10 @@ describe('#19403 round 10 — the verdicts, on the live bundles', () => { // composites spell out their two live sub-rows (`enabled`, `position`; // `mode` / `formView` were retired), four new row labels authored in all // three locales. - expect(translated.length, `${locale} positive control`).toBe(677); + // 678 since #25330: the dataset form's dimensions repeater offers + // `nullLabel` once its liveness verdict went live — one row label, + // authored in all three locales. + expect(translated.length, `${locale} positive control`).toBe(678); } // ⭐ DARK — the blindness, executable. On a synthetic two-locale catalog the // all-three predicate returns 0 while the per-locale one returns 1, so the diff --git a/packages/platform-objects/src/apps/translations/report-form-echo-decisions.test.ts b/packages/platform-objects/src/apps/translations/report-form-echo-decisions.test.ts index 5e4be812491..02cd8e88391 100644 --- a/packages/platform-objects/src/apps/translations/report-form-echo-decisions.test.ts +++ b/packages/platform-objects/src/apps/translations/report-form-echo-decisions.test.ts @@ -59,7 +59,7 @@ // echoes in two locales TODAY. It is OUT of this population, asserted by // name — a walk that over-reached into the object form would sweep in 32 // undecided leaves, and the assertion is what notices. `datasetForm` — -// the sibling semantic-layer panel, 37 leaves, 0 echoing — is excluded +// the sibling semantic-layer panel, 38 leaves, 0 echoing — is excluded // too, and that exclusion is asserted rather than trusted precisely // because a wrong sweep of it would NOT go red. // (2) INWARD, AUTHORED. 38 of this population's own 44 leaves come back @@ -888,7 +888,8 @@ describe('#19403 round 9 — the population, DERIVED from `reportForm`', () => { // …and `datasetForm`, the sibling semantic-layer panel, is excluded too. // It is fully authored, so a wrong sweep of it would NOT go red — which is // exactly why the exclusion is asserted instead of trusted. - expect(DATASET_LEAVES.length).toBe(37); + // 38 since #25330: the dimensions repeater offers `nullLabel`. + expect(DATASET_LEAVES.length).toBe(38); const datasetEchoes = DATASET_LEAVES.filter((l) => TRANSLATED_LOCALES.some(([, forms]) => forms.dataset?.[l.kind === 'section' ? 'sections' : 'fields']?.[l.path]?.[l.prop] === l.en), ); diff --git a/packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts b/packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts index 5ca32b8ca98..f125a76cf7a 100644 --- a/packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts +++ b/packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts @@ -2039,6 +2039,9 @@ export const zhCNMetadataForms: NonNullable = "dimensions.dateGranularity": { label: "日期粒度" }, + "dimensions.nullLabel": { + label: "空值标签" + }, measures: { label: "度量", helpText: "每个度量:名称、聚合函数、字段(count 时可省略)、显示格式/货币" diff --git a/packages/sdui-parser/objectui-lockstep.json b/packages/sdui-parser/objectui-lockstep.json index 6a4470be251..b9b9836c72c 100644 --- a/packages/sdui-parser/objectui-lockstep.json +++ b/packages/sdui-parser/objectui-lockstep.json @@ -6,8 +6,8 @@ ], "objectui": { "repo": "https://github.com/objectstack-ai/objectui.git", - "rev": "5fef032b1ada5b86bad640637e7331e26ee5da6e", - "revDate": "2026-10-22T18:39:55+00:00", + "rev": "1d178e06ac5eda824af06f7d68eaccdcf38dcf63", + "revDate": "2026-10-23T12:16:26+00:00", "source": "packages/sdui-parser/src", "files": [ "packages/sdui-parser/src/body-dialect.ts", @@ -21,7 +21,7 @@ "packages/sdui-parser/src/validate.ts" ] }, - "recordedAgainstPin": "5fef032b1ada5b86bad640637e7331e26ee5da6e", + "recordedAgainstPin": "1d178e06ac5eda824af06f7d68eaccdcf38dcf63", "grammarRegion": { "file": "packages/sdui-parser/src/parse.ts", "delimiter": "/* ---------------------- the JS literal subset (#6614) ---------------------- */", diff --git a/packages/spec/liveness/state-counts/dataset.md b/packages/spec/liveness/state-counts/dataset.md index 3cffdd5c36c..4cc522e6e84 100644 --- a/packages/spec/liveness/state-counts/dataset.md +++ b/packages/spec/liveness/state-counts/dataset.md @@ -12,4 +12,4 @@ committed anywhere: `check:liveness` sums the shards when it reads them. | Type | live | exp | elsewhere | dead | planned | classified | |---|---|---|---|---|---|---| -| `dataset` | 27 | 0 | 0 | 0 | 1 | 28 | +| `dataset` | 28 | 0 | 0 | 0 | 0 | 28 |