From c6177aff5d0753c1469f79c66af53ed2eb17d402 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 18:04:01 +0000 Subject: [PATCH 01/10] chore(objectui): bump the console pin to ab1879721595 (scripts/bump-objectui.sh --no-commit output) Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w Co-authored-by: Claude --- .changeset/console-ab1879721595.md | 119 +++++++++++++++++++++++++++++ .objectui-sha | 2 +- 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 .changeset/console-ab1879721595.md diff --git a/.changeset/console-ab1879721595.md b/.changeset/console-ab1879721595.md new file mode 100644 index 00000000000..e22c0b5987b --- /dev/null +++ b/.changeset/console-ab1879721595.md @@ -0,0 +1,119 @@ +--- +"@objectstack/console": minor +--- + +Console (objectui) refreshed to `ab1879721595`. Frontend changes in this range: + +Derived from the changesets objectui declared over the range — 111 releasing of 119 changesets added across 65 non-merge commits; omitted: 8 release-nothing changesets, 1 commit carrying no changeset (they ship no package code). + +- **minor** — Studio reaches the organization's own flows that belong to no package (objectui#11553). (objectui `f624f278d`) +- **minor** — `record:line_items` now publishes every key of its `@objectstack/spec` 17.6.0 row (objectui#11536). Each key was decided by measuring it through `SchemaRenderer` and the block's r… (objectui `072b7e843`) +- **minor** — The import wizard's "Download template" button now downloads the server's import template, an Excel workbook, instead of building a CSV of every field (objectui#9600). (objectui `b253c4e28`) +- **minor** — The Studio dataset filter builder writes "Is empty" / "Is not empty" as the spec's `{ FIELD: { $empty: true } }` / `{ FIELD: { $empty: false } }` instead of `$exists` (objectui#10… (objectui `d0c0c7fe9`) +- **minor** — `FilterConditionField` writes "Is empty" / "Is not empty" as the spec's one 「is empty」 operator, `{ FIELD: { $empty: true } }` / `{ FIELD: { $empty: false } }` (objectui#10813). (objectui `d0c0c7fe9`) +- **minor** — The list view's live query sends "Is empty" / "Is not empty" as the spec's `isempty` / `isnotempty` instead of an equality to `null` (objectui#10813). `@objectstack/spec` 17.6.0 a… (objectui `d0c0c7fe9`) +- **minor** — An `object-grid` honours `keyboardNavigation`: arrow-key cell navigation on the WAI-ARIA grid pattern (objectui#11068). `@objectstack/spec` 17.6.0 declares the key on its `object-… (objectui `154075ab1`) +- **minor** — The action success toast is composed from the action's `outcomeMessages`, then its `successMessage`, then the runner's default text. A `message` in the server's answer is no longe… (objectui `c476be0e0`) +- **minor** — The drill `filter[...]` URL dialect can spell "is empty" (objectui#11547). (objectui `7121221fa`) +- **minor** — `ValueDataSource` executes the empty pair `is_empty` / `is_not_empty` and the `$empty` operator, and `convertFiltersToAST` lowers `$empty` (objectui#11094). (objectui `6158e4c93`) +- **minor** — An `object-grid` publishes `description` and `emptyState` now that `@objectstack/spec` 17.6.0 declares them on its `object-grid` row, and `emptyState.title` / `.message` take an i… (objectui `6158e4c93`) +- **minor** — **BREAKING for authors of `object-grid` and `list-view` row rules, released as `minor`.** `conditionalFormatting` on `ObjectGridSchema` (and so on the `object-view` `table` slot b… (objectui `6f5719e1c`) +- **minor** — **BREAKING** — **A dashboard metric widget bound inline to an object now shows the retired-format prompt instead of its number (objectui#11525).** This follows the maintainer's ruling C on objec… (objectui `160c6c6ea`) +- **minor** — **BREAKING for authors of `object-kanban` card rules, released as `minor`.** `object-kanban`'s `conditionalFormatting` takes ONE rule dialect, the spec list view's `{ condition, s… (objectui `c73cdb569`) +- **minor** — `DashboardRenderer`'s `onWidgetsReorder` hands back the slot's own array type, `DashboardComponentSchema['widgets']`, instead of `DashboardWidgetSchema[]`; the dashboard's `object… (objectui `9d7419b91`) +- **minor** — `DashboardWidgetSchema['type']` names the widget vocabulary only, `DashboardWidgetTypeName`: it drops `DashboardComponentWidgetType` (objectui#11514). This narrows the TypeScript… (objectui `9d7419b91`) +- **minor** — fix(plugin-charts): an `object-chart` whose `specType` is a single-value or tabular spec family draws its routed form, not a silent bar chart; the renderer has no default family (… (objectui `06634afe7`) +- **minor** — **BREAKING** — `DrillDownConfig.report`'s `{ name }` reference arm is retired on the TypeScript face, the tolerant zod face and the strict authoring face, and both zod faces refuse it by name (o… (objectui `9ed8d0f1c`) +- **minor** — **BREAKING** — The drill-down drawer scopes a dataset-bound drill report by `runtimeFilter`, and the pre-9.0 object-bound drill report is retired (objectui#11506). (objectui `8366accd1`) +- **minor** — `@object-ui/types` declares TypeScript types for three node types its zod face already validates: `DetailSectionNodeSchema` (`detail-section`), `AppSchemaRendererNodeSchema` (`app… (objectui `fc7db059f`) +- **minor** — feat(types): `ObjectChartSchema.chartType` declares the `@objectstack/spec` chart families plugin-charts draws (objectui#11513) (objectui `95e58a3b2`) +- **minor** — **BREAKING** — A `grid` node sets a column count per breakpoint in one way: the breakpoint object of `columns`. The flat `smColumns`, `mdColumns`, `lgColumns` and `xlColumns` keys are retired, w… (objectui `2d576e46e`) +- **minor** — **BREAKING (`@object-ui/cli`):** four commands are retired: `objectui create`, `objectui lint`, `objectui test` and `objectui studio`. There is no alias window and no placeholder:… (objectui `37268aae9`) +- **minor** — **BREAKING** — chore(console)!: drop the lazy `spec-report` stub (objectui#11440) (objectui `9d9ed5495`) +- **minor** — **BREAKING** — chore(cli)!: the generated known-types list drops `spec-report`, which objectui#11440 retired (objectui `9d9ed5495`) +- **minor** — feat(types): the `report` node declares the `report` member that wraps a spec report (objectui#11440) (objectui `9d9ed5495`) +- **minor** — **BREAKING** — refactor(plugin-report)!: retire the `spec-report` node type key; a spec report is embedded as `{ "type": "report", "report": { … } }` (objectui#11440) (objectui `9d9ed5495`) +- **minor** — **BREAKING** — An `app-schema-renderer` node draws the app document it carries under `schema` (objectui#11494, triage ruling A). (objectui `fcdc8ec91`) +- **minor** — `AppSchemaRendererNodeSchema` declares the `app-schema-renderer` node's `schema` input: it is the app document, `AppComponentSchema` itself, by reference, and optional (objectui#1… (objectui `fcdc8ec91`) +- **minor** — **BREAKING** — The `columns` of a `grid` node is one of the counts its renderer maps, 1 to 12, as the bare number and at every breakpoint of the object form. Any other count is refused at valida… (objectui `aea682a31`) +- **minor** — **BREAKING (`@object-ui/cli`):** the `objectui add` command is retired, and so are `objectui analyze`'s two flags, `--render-performance` and `--bundle-size`. There is no alias wi… (objectui `ea3914139`) +- **minor** — A `metric-card` in a dashboard's widget slot parses only with its `value` (objectui#11483). This narrows a published accept set on both validator faces, and it narrows the widget… (objectui `f68e0a080`) +- **minor** — **BREAKING (`@object-ui/cli`):** `objectui generate` no longer accepts `--from`. The flag is retired, with no alias window and no placeholder, and the CLI now refuses it as an unk… (objectui `1fe05ff37`) +- **minor** — **BREAKING** — feat(types): `ObjectGridSchema`'s zod mirror declares ten members its TypeScript twin declares (objectui#6152, round 6) (objectui `0d723a33f`) +- **minor** — `safeValidateSchema` — and so `objectui validate` — accepts seven more registered node types: the spec page kinds `record`, `home` and `utility`, and `app-schema-renderer`, `objec… (objectui `00ccdf742`) +- **minor** — The `object-pivot` registration publishes `drillDown` as an input (objectui#11440). (objectui `00ccdf742`) +- **minor** — **BREAKING** — The `gap` of a `stack`, a `flex` and a `grid` node is one of the steps its renderer maps. Any other number is refused at validation, with the set named (objectui#11474). (objectui `4abc0aafa`) +- **minor** — Dashboard percent faces read the storage they are told, never a storage guessed from the value (objectui#11475). (objectui `f560ded15`) +- **minor** — **BREAKING: a percentage is scaled at the storage its field declares, never at a storage guessed from the value (objectui#11475)** (objectui `f560ded15`) +- **minor** — **BREAKING (`@object-ui/cli`):** `objectui generate` no longer accepts `--output`. The flag is retired, with no alias window, and the CLI now refuses it as an unknown option (obje… (objectui `9de0b3483`) +- **minor** — A `metric-card` in a dashboard's widget slot is checked against the props `MetricCard` renders, on the TypeScript face and in the validator (objectui#11467). This narrows a publis… (objectui `401611b21`) +- **minor** — `AnySchema` now includes the node types this package declares and exported outside it, so `SchemaByType` and narrowing on `type` reach them (objectui#11478). (objectui `2b188faa3`) +- **minor** — **BREAKING** — feat(types)!: `SidebarSchema` declares what the `sidebar` node draws — nine unread keys are retired on both faces, and `variant` takes the registration's enum (objectui#11465) (objectui `ca3de7272`) +- **minor** — The authored `properties`-bag carriers outside the spec's public blocks have a TypeScript authoring type, and `AuthoringNode` includes them, so `SchemaRenderer`'s `schema` prop ac… (objectui `2c0ddf226`) +- **minor** — **BREAKING** — chore(cli)!: the generated known-types list drops the ten `sidebar-*` node type keys objectui#10859 batch 8 phase 2d retired (objectui `1c8403692`) +- **minor** — **BREAKING** — refactor(components)!: retire the ten `sidebar-*` node type keys; the `sidebar` node supplies its own provider (objectui#10859, batch 8 phase 2d) (objectui `1c8403692`) +- **minor** — feat(components): the `sidebar` node mounts a `SidebarProvider` only when none is above it, and honours the boolean `collapsible` (objectui#10859, batch 8 phase 2d) (objectui `1c8403692`) +- **minor** — `input-otp` draws the separator its docs page teaches (objectui#11365). The "With Separator" example on the `input-otp` docs page and two catalog entries (`with-visual-separator`,… (objectui `e46ee770b`) +- **minor** — **BREAKING** — A `container` node's `padding` is one of the twelve steps its renderer maps: 0 to 8, 10, 12 and 16. Any other number is refused at validation, with the set named (objectui#11424). (objectui `3f6efd640`) +- **minor** — **BREAKING** — chore(cli)!: the generated known-types list drops the two node type keys objectui#11441 retired (objectui `9d1c0bff9`) +- **minor** — **BREAKING** — refactor(layout)!: retire the `navigation-renderer` and `responsive-grid` node type keys; navigation is application metadata, and the breakpoint grid is `grid` (objectui#11441) (objectui `9d1c0bff9`) +- **minor** — **BREAKING** — The page and report designers, and all three canvases, draw the members their node declarations always carried and they never read. Every designer registration's `inputs` now list… (objectui `5988b6b53`) +- **minor** — **BREAKING (authoring)** — a report element's `properties.field` is refused by name on the zod face; the element's binding is its declared `dataBinding` (objectui#11434, ADR-0049). (objectui `5988b6b53`) +- **minor** — **BREAKING** — chore(console)!: drop the lazy `pie-chart`, `donut-chart` and `radar-chart` stubs (objectui#10859, batch 8 phase 2c) (objectui `ad1785c1d`) +- **minor** — **BREAKING** — chore(cli)!: the generated known-types list drops the four node type keys objectui#10859 batch 8 phase 2c retired (objectui `ad1785c1d`) +- **minor** — **BREAKING** — refactor(components)!: drop `page-header` from the opt-in protocol placeholders (objectui#10859, batch 8 phase 2c) (objectui `ad1785c1d`) +- **minor** — **BREAKING** — refactor(plugin-charts)!: retire the `pie-chart`, `donut-chart` and `radar-chart` node type keys; the families are `chart` + `chartType` (objectui#10859, batch 8 phase 2c) (objectui `ad1785c1d`) +- **minor** — **BREAKING** — refactor(layout)!: retire the `page-header` node type key; the header node is `page:header` (objectui#10859, batch 8 phase 2c) (objectui `ad1785c1d`) +- **minor** — **BREAKING (authoring)** — `DataModelRelationship.onDelete` is respelled `deleteBehavior`, in `@objectstack/spec`'s vocabulary, on both faces; and the process designer's last two… (objectui `0e9058b95`) +- **minor** — The data-model and process designers draw the members their node declarations always carried and they never read (objectui#11434). (objectui `0e9058b95`) +- **minor** — **BREAKING** — chore(console)!: drop the lazy `scatter-chart` and `dashboard-grid` stubs, and stop the `metric` / `metric-card` stubs claiming the bare key (objectui#10859, batch 8 phase 2b) (objectui `37140f4f5`) +- **minor** — **BREAKING** — refactor(plugin-dashboard)!: retire the `dashboard-grid` node type key, and register the `metric` / `metric-card` node keys without a bare fallback (objectui#10859, batch 8 phase… (objectui `37140f4f5`) +- **minor** — **BREAKING** — chore(cli)!: the generated known-types list drops the twelve node type keys objectui#10859 batch 8 phase 2b retired (objectui `37140f4f5`) +- **minor** — **BREAKING** — refactor(plugin-designer)!: retire four builder-chrome node type keys (objectui#10859, batch 8 phase 2b) (objectui `37140f4f5`) +- **minor** — **BREAKING** — refactor(plugin-form)!: retire the `form-analytics` node type key (objectui#10859, batch 8 phase 2b) (objectui `37140f4f5`) +- **minor** — **BREAKING** — refactor(plugin-grid)!: retire the `import-wizard` node type key; the `ImportWizard` export stays (objectui#10859, batch 8 phase 2b) (objectui `37140f4f5`) +- **minor** — **BREAKING** — refactor(plugin-detail)!: retire the `related-list` node type key; the related-list block is `record:related_list` (objectui#10859, batch 8 phase 2b) (objectui `37140f4f5`) +- **minor** — **BREAKING** — refactor(plugin-charts)!: retire the `scatter-chart` node type key; scatter is `chart` + `chartType: 'scatter'` (objectui#10859, batch 8 phase 2b) (objectui `37140f4f5`) +- **minor** — **BREAKING** — refactor(plugin-view)!: retire the `shared-view-link` node type key (objectui#10859, batch 8 phase 2b) (objectui `37140f4f5`) +- **minor** — **BREAKING (authoring)** — seven members of the `@object-ui/plugin-designer` node declarations and their record types are retired on both faces, and two element types leave the pa… (objectui `c4ab6d09a`) +- **minor** — **BREAKING (TypeScript props)** — `DataModelDesignerProps.autoLayout` and `ReportDesignerProps.previewMode` are removed, and the `data-model-designer` registration no longer publi… (objectui `c4ab6d09a`) +- **patch** — `record:details` edit mode edits a `textarea` field in a multi-line textarea, so saving it keeps its line breaks (objectui#11562). (objectui `ab1879721`) +- **patch** — The top-level `fields` input descriptions of `object-form`, `view:form`, `embeddable-form` and `object-master-detail-form`, and the `console.warn` a top-level `fields` member that… (objectui `dbd108166`) +- **patch** — A grouped `object-grid` labels its group headers from the object field's `options` only; a column's `options` no longer relabels them (objectui#11544). (objectui `b0bf413ca`) +- **patch** — `record:details` edit mode gives a `markdown` field an editor: a multi-line textarea (objectui#11541). (objectui `b34cc148e`) +- **patch** — The record dialog now draws a `form.sections[].group` section (objectui#11542). (objectui `584eecae8`) +- **patch** — fix(app-shell): create and edit no longer render a container's first named form when it declares no default form (objectui `9bfd0b36e`) +- **patch** — Raise `@object-ui/types`' declared `@objectstack/spec` floor from `^17.5.0` to `^17.6.0` (objectui#11227). The package's published types now read `EmptyState` from `@objectstack/s… (objectui `6158e4c93`) +- **patch** — objectui now resolves `@objectstack/*` 17.6.0 (objectui#11438). One declared range moves: `@object-ui/types` raises its `@objectstack/spec` floor to `^17.6.0` in objectui#11227's… (objectui `6158e4c93`) +- **patch** — fix(plugin-charts): an `object-chart` whose family is on `specType` gets `compareTo` exactly as the same family on `chartType` does, so a `specType: scatter` chart with `compareTo… (objectui `6837bfa85`) +- **patch** — fix(plugin-dashboard): an `object-metric` with a structured `groupBy` and a rule-list `filter` draws its number (objectui `8bfc0012e`) +- **patch** — The metadata-admin dashboard preview's reorder handler takes `DashboardComponentSchema['widgets']`, the array `DashboardRenderer`'s `onWidgetsReorder` now hands back (objectui#115… (objectui `9d7419b91`) +- **patch** — The dashboard editor reads a `widgets[]` entry by the slot's element type, `DashboardComponentSchema['widgets'][number]`, in its widget card, its property panel, its preview and i… (objectui `9d7419b91`) +- **patch** — The README's Activity tab authors `record:activity`'s declared inputs in `properties` (`{ limit: 20, showCompleted: false }`) instead of a host feed in `items` (objectui#11515). (objectui `fc7db059f`) +- **patch** — The VS Code extension's **Export to React** command types the `schema` constant it emits as `SchemaRendererProps['schema']`, imported type-only from `@object-ui/react` beside `Sch… (objectui `fc7db059f`) +- **patch** — The generated plugin's `jsdom` devDependency is now `^29.1.1` instead of `^30.0.1` (objectui#11366). (objectui `059bf1b59`) +- **patch** — The drill-down drawer renders a `drillDown.report` as a `report` node, `{ type: 'report', report }` (objectui#11440). (objectui `9d9ed5495`) +- **patch** — `toRenderableSchema` declares its parameter as `SchemaNode` (objectui#11479). (objectui `b654d4ed5`) +- **patch** — The record header's percent summary chip, text and bar, scales at the storage its field declares (`percentCellScale`, the spec's `percentScaleOf`: a fraction unless the field decl… (objectui `f560ded15`) +- **patch** — The gantt tooltip's `percent` row scales at the storage the field declares (`percentCellScale`, the spec's `percentScaleOf`), the answer the list cell reads. A fraction-stored `1`… (objectui `f560ded15`) +- **patch** — Grid percent faces read the field's declared storage (objectui#11475). (objectui `f560ded15`) +- **patch** — The gallery card's field bag now carries the field's `max`, the storage statement the percent cell reads through the spec's `percentScaleOf` (objectui#11475). Without it, a whole-… (objectui `f560ded15`) +- **patch** — fix(components): an expanded `offcanvas` sidebar is drawn inside the viewport (objectui#11464) (objectui `50c73fed0`) +- **patch** — `objectui generate page NAME` writes a page that draws its title (objectui#11450). (objectui `5429e6c74`) +- **patch** — The runner drops a fallback welcome page that could never render (objectui#11450). (objectui `5429e6c74`) +- **patch** — fix(fields): a read-only percent or currency field shows its value the way its table cell does, and a whole currency amount keeps its minor units (objectui#11444) (objectui `6007dd4e4`) +- **patch** — fix(approvals): the console names a position approver `position:manager`, the spelling the server stores — not `role:manager` (objectui#11455) (objectui `d93e53f5d`) +- **patch** — The search results line and the object view's record-count footer read the right noun form in every language at every count (objectui#11445). Each passes `count` to one i18next co… (objectui `4a1adb76e`) +- **patch** — The comment thread's header, its reaction tooltip and the presence stack's labels read the right noun form in every language at every count (objectui#11445). Each passes `count` t… (objectui `4a1adb76e`) +- **patch** — The data table's "N rows modified" line and the `page:tabs` count badge's accessible name read the right noun form in every language at every count (objectui#11445): both are i18n… (objectui `4a1adb76e`) +- …and 11 more releasing changesets in this range (list capped at 100; see the objectui range below). + +⚠️ 43 of these carry a breaking change: 43 by the author's own breaking annotation in the changeset body — objectui declares no `major` inside a launch window (`scripts/check-changeset-no-major.mjs`). Each is marked **BREAKING** in the list above — read them before compiling the release record. + +**In this console build, declared nowhere** — objectui merged 1 commit in this range with no `.changeset/*.md`. The code is inside the pin above and ships here, but nothing upstream declared it, so it appears in no objectui CHANGELOG and in no entry above. Listed by subject rather than counted, because a count cannot tell a dependency bump from a form-behaviour change (objectstack#6174); the upstream gate that would prevent this is objectui#3387. + +- _(no changeset)_ docs(plugin-dashboard): the charts example authors a line widget bound to a dataset; the card widget with nested children retires (objectui#11484) (#11523) (objectui `6903eafbc`) + + + +objectui range: `89cad75d5570...ab1879721595` diff --git a/.objectui-sha b/.objectui-sha index e8693cff406..3638e699372 100644 --- a/.objectui-sha +++ b/.objectui-sha @@ -1 +1 @@ -89cad75d55702cc4f267bead5bf267de575d5842 +ab187972159583b595facdcae3c73b50f6f312e9 From 663ac47818339c195d3b1112f44511f0751f0ba6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 18:13:33 +0000 Subject: [PATCH 02/10] wip(spec): re-measure objectui pin citations at ab1879721 (part 1) Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w Co-authored-by: Claude --- packages/spec/src/ui/component.test.ts | 74 ++++++++++-- packages/spec/src/ui/component.zod.ts | 156 ++++++++++++++++--------- 2 files changed, 165 insertions(+), 65 deletions(-) diff --git a/packages/spec/src/ui/component.test.ts b/packages/spec/src/ui/component.test.ts index b7704d84dca..b562456be0a 100644 --- a/packages/spec/src/ui/component.test.ts +++ b/packages/spec/src/ui/component.test.ts @@ -284,11 +284,20 @@ describe('PageAccordionProps variant (#6776)', () => { // sweep once read as declared-but-unenforced. It has a live cross-repo consumer: // objectui's `PageAccordionRenderer` renders `{item.icon && }` inside the `AccordionTrigger` -// (`packages/components/src/renderers/layout/containers.tsx:1171-1177`), and the +// (`packages/components/src/renderers/layout/containers.tsx:1170-1176`), and the // same file's `ComponentRegistry.register('accordion', …)` publishes the key to -// the Studio block designer at `:1220` (the `items` input, documented as +// the Studio block designer at `:1219` (the `items` input, documented as // `[{ label, icon?, collapsed?, children }]`). Measured at the pin this repo -// builds against — `.objectui-sha` = `89cad75d5`. Re-derived at that pin +// builds against — `.objectui-sha` = `ab1879721`. Re-derived at that pin +// 2026-10-03: `containers.tsx` changed across the hop from `89cad75d5` (23 +// insertions, 27 deletions: objectui#11445's i18next count family for the +// `page:tabs` count badge, and objectui#11438 dropping the `page:header` +// registration's inert `breadcrumb` input), so both anchors were re-READ rather +// than carried, and BOTH MOVED up one line with their text byte-identical (the +// tab-count translation table above them lost a net line): the icon block +// `1171-1177` -> `1170-1176`, still inside `PageAccordionRenderer`'s +// `AccordionTrigger`, and the input `1220` -> `1219`, still inside the +// `register('accordion', …)` inputs. At `89cad75d5`, re-derived at that pin // 2026-10-02: `containers.tsx` is byte-identical across the hop from // `31971ff1e` (`git diff --quiet`), and both anchors were re-READ in place and // still say what this block says: the icon block `1171-1177`, inside @@ -409,11 +418,20 @@ describe('PageTabsProps items[].value / items[].count (#5775)', () => { // same bare declaration a liveness sweep reads as declared-but-unenforced. // objectui's `PageTabsRenderer` renders `{item.icon && }` inside the `TabsTrigger` -// (`packages/components/src/renderers/layout/containers.tsx:946-952`), and the +// (`packages/components/src/renderers/layout/containers.tsx:945-951`), and the // same file's `ComponentRegistry.register('tabs', …)` publishes the key to the -// Studio block designer at `:1005` (the `items` input, documented as +// Studio block designer at `:1004` (the `items` input, documented as // `[{ label, value?, icon?, count?, visibleWhen?, children }]`). Measured at -// the pin this repo builds against — `.objectui-sha` = `89cad75d5`. Re-derived at +// the pin this repo builds against — `.objectui-sha` = `ab1879721`. Re-derived at +// that pin 2026-10-03: `containers.tsx` changed across the hop from `89cad75d5` +// (23 insertions, 27 deletions: objectui#11445's i18next count family, which +// rewrote the tab-count translation table above both anchors and the count +// badge's `aria-label` between them, and objectui#11438 dropping the +// `page:header` registration's inert `breadcrumb` input below them), so both +// anchors were re-READ rather than carried, and BOTH MOVED up one line with +// their text byte-identical: the icon block `946-952` -> `945-951`, still +// inside `PageTabsRenderer`'s `TabsTrigger`, and the input `1005` -> `1004`, +// still inside the `register('tabs', …)` inputs. At `89cad75d5`, re-derived at // that pin 2026-10-02: `containers.tsx` is byte-identical across the hop from // `31971ff1e` (`git diff --quiet`), and both anchors were re-READ in place and // still say what this block says: the icon block `946-952`, inside @@ -3608,7 +3626,20 @@ describe('#7751 — object-* block props schemas', () => { // #16503 — the spec half of objectui#8172 (decision batch #68, 2026-09-07, // option A: the contract declares the capability that already ships, is // documented and is in use). Measured at the objectui pin this repo builds -// against (`.objectui-sha` = `89cad75d5`; re-measured there 2026-10-02 — +// against (`.objectui-sha` = `ab1879721`; re-measured there 2026-10-03 — +// `plugin-kanban/src/types.ts`, `ObjectKanban.tsx` and `plugin-kanban.mdx` are +// byte-identical to `89cad75d5` (`git diff --quiet`), so `types.ts` still +// declares no `limit`, `:722` / `:97` did not move and were re-read in place, +// and the mdx still teaches `limit: 250` with its Properties row; `index.tsx` +// changed only below the mapping (objectui#11438's 17.6.0 re-citation in the +// `ObjectKanbanRenderer` docblock and objectui#11522's `{ condition, style }` +// description on the `conditionalFormatting` input), so `487-491` did not +// move and still maps `limit: 'limit'`; `objectql.ts` MOVED the member `4661` +// -> `4720` byte-identical, still inside `ObjectKanbanSchema` (the 59 lines +// are declarations the range changed above it: the spec `{ condition, style }` +// rule types of objectui#11533 / objectui#11522, `ObjectGridSchema`'s +// `keyboardNavigation` of objectui#11068 and its 17.6.0 members of +// objectui#11438 among them). At `89cad75d5`, re-measured there 2026-10-02 — // `plugin-kanban/src/types.ts` and `ObjectKanban.tsx` are byte-identical to // `31971ff1e` (`git diff --quiet`), so `types.ts` still declares no `limit` // and `:722` / `:97` did not move and were re-read in place; `index.tsx` @@ -3677,7 +3708,7 @@ describe('#7751 — object-* block props schemas', () => { // `OBJECT_KANBAN_DATA_SOURCE`, ⚠️ `KanbanSchema` is RETIRED at this pin and // `plugin-kanban/src/types.ts` declares the member no more — the published // twin is `ObjectKanbanSchema`, declaring `limit?: number` at -// `packages/types/src/objectql.ts:4661` — and `content/docs/plugins/plugin-kanban.mdx` +// `packages/types/src/objectql.ts:4720` — and `content/docs/plugins/plugin-kanban.mdx` // teaches `limit: 250` with a Properties row. The strict map refused the key by // name — the same `unrecognized_keys` verdict as the `bogusProp` control — so an // author following the published docs wrote a node the save gate rejected. @@ -3739,7 +3770,15 @@ describe('ObjectKanbanPropsSchema limit — the row cap four objectui faces alre // since retired that block, objectui#8257). Unlike `limit` above — a key four // objectui faces already implemented, so the spec was the half that was wrong // — `quickAdd` was FORWARDED and never read: at the pin this repo builds -// against (`.objectui-sha` = `89cad75d5`; re-measured there 2026-10-02 — +// against (`.objectui-sha` = `ab1879721`; re-measured there 2026-10-03 — +// `KanbanBoardCore.tsx` and `ObjectKanban.tsx` are byte-identical to +// `89cad75d5` (`git diff --quiet`), so `:78`, `:111-112` and the spread +// `:1639` did not move and were re-read in place, and the counts re-read the +// same, 2 / 2 / 11; `KanbanImpl.tsx` changed only in two comments above the +// gate (objectui#11522: card formatting is the spec `{ condition, style }` +// rule alone, +8/-5), so both gate lines MOVED by 3 byte-identical, `621` -> +// `624` and `634` -> `637`, and still read `quickAdd && onQuickAdd`. At +// `89cad75d5`, re-measured there 2026-10-02 — // `KanbanImpl.tsx`, `KanbanBoardCore.tsx` and `ObjectKanban.tsx` are // byte-identical to `31971ff1e` (`git diff --quiet`), so `:621`, `:634`, // `:78`, `:111-112` and the spread `:1639` did not move and were re-read in @@ -3782,7 +3821,7 @@ describe('ObjectKanbanPropsSchema limit — the row cap four objectui faces alre // spreads the // authored bag into `KanbanBoardCore` (into `KanbanRenderer` until // objectui#11234, which reads the pair off it no more) and `KanbanImpl` gates the affordance on -// `quickAdd && onQuickAdd` (`KanbanImpl.tsx:621`, `:634` — the file is spelled +// `quickAdd && onQuickAdd` (`KanbanImpl.tsx:624`, `:637` — the file is spelled // here because those two ranges are NOT in `ObjectKanban.tsx`), while // `onQuickAdd` is a // host-supplied FUNCTION no producer puts on an `object-kanban` node @@ -3881,7 +3920,11 @@ describe('ObjectKanbanPropsSchema quickAdd is retired (#17260)', () => { // #9881 and commit 60e0f900a recorded the accordion and tab items; these two close the set. // // The button record re-measured at the pin this repo builds against — -// `.objectui-sha` = `89cad75d5`, re-derived there 2026-10-02: `button.tsx`, +// `.objectui-sha` = `ab1879721`, re-derived there 2026-10-03: `button.tsx`, +// `lazy-icon.tsx`, `resolve-icon.ts` and the generated +// `lucide-record-icon-names.ts` are byte-identical to `89cad75d5` +// (`git diff --quiet`), so every anchor below holds unmoved and was re-read in +// place. At `89cad75d5`, 2026-10-02: `button.tsx`, // `lazy-icon.tsx`, `resolve-icon.ts` and the generated // `lucide-record-icon-names.ts` are byte-identical to `31971ff1e` // (`git diff --quiet`), so every anchor below holds unmoved and was re-read in @@ -4035,7 +4078,14 @@ describe('ObjectMetricPropsSchema icon liveness (#10053)', () => { // // The acceptance the card names, pinned: each row's KEY SET is the one the // renderer's read points support at the pin this repo builds against -// (`.objectui-sha` = `89cad75d5`; re-measured there 2026-10-02 — all four +// (`.objectui-sha` = `ab1879721`; re-measured there 2026-10-03 — +// `ObjectMap.tsx`, `ObjectTree.tsx` and `record-source.ts` are byte-identical +// across the hop off `89cad75d5` (`git diff --quiet`), so every anchor in them +// held unmoved; `ObjectGantt.tsx` +8/-0 gained one import line and, in the +// tooltip's `percent` row, a `percentCellScale(def)` argument scaling at the +// field definition's declared storage (objectui#11475) — a field-definition +// read, not a node key — so the set of `schema.*` keys each renderer reads is +// the same at both pins. At `89cad75d5`, 2026-10-02 — all four // cited files changed on the hop off `31971ff1e`, so each was re-READ, and // the set of `schema.*` keys each renderer reads is the same at both pins. // `ObjectMap.tsx` +11/-4: the shadowed-flat-key warning reads `schema[key]` diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index 2594178cfc5..a2415435227 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -803,10 +803,17 @@ export const PageTabsProps = strictObject({ * false candidate a component over). * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `89cad75d5`; re-derived at that pin 2026-10-02 — - * `containers.tsx` is byte-identical across the hop from `31971ff1e` - * (`git diff --quiet`), so both anchors were re-READ in place, each still - * the read point this record names: `946-952` and `1005`. At `31971ff1e` + * (`.objectui-sha` = `ab1879721`; re-derived at that pin 2026-10-03 — + * `containers.tsx` changed across the hop from `89cad75d5` (23 insertions, + * 27 deletions: objectui#11445's i18next count family, which rewrote the + * tab-count translation table above both anchors and the count badge's + * `aria-label` between them, and objectui#11438 dropping the `page:header` + * registration's inert `breadcrumb` input below them), so both anchors were + * re-READ rather than carried, and BOTH MOVED up one line with their text + * byte-identical: `946-952` -> `945-951`, `1005` -> `1004`. At `89cad75d5` + * (2026-10-02) `containers.tsx` was byte-identical across the hop from + * `31971ff1e` (`git diff --quiet`), so both anchors were re-READ in place, + * each still the read point this record names: `946-952` and `1005`. At `31971ff1e` * (2026-10-01) `containers.tsx` was byte-identical across the hop from * `e420df310` (`git diff --quiet`), so both anchors were re-READ in place, * each still the read point this record names: `946-952` and `1005`. At `e420df310` @@ -837,12 +844,12 @@ export const PageTabsProps = strictObject({ * lines byte-identical and the registration input `789` -> `912` with its * LINE rewritten — it declares `of: 'object'` and carries a longer * description — while the member list this record cites stayed - * unchanged): `containers.tsx:946-952` + * unchanged): `containers.tsx:945-951` * renders * `{item.icon && }` inside the * `TabsTrigger`, left of the label span (`mr-1.5 h-3.5 w-3.5 shrink-0 * opacity-70`, `aria-hidden`), and the renderer's registration publishes - * the key to the Studio block designer at `:1005` (the `items` input, + * the key to the Studio block designer at `:1004` (the `items` input, * documented as `[{ label, value?, icon?, count?, visibleWhen?, children * }]`). * @@ -1004,7 +1011,10 @@ export const PageCardProps = strictObject({ * declarations identical. * * Each clause is read off the objectui pin this repo builds against - * (`.objectui-sha` = `89cad75d5570`, re-read there 2026-10-02: across the hop + * (`.objectui-sha` = `ab1879721595`, re-read there 2026-10-03: across the hop + * from `89cad75d5570` all four `record-*.tsx` renderers and the three + * `permissions` files are byte-identical (`git diff --quiet`), so NO anchor + * below moved, and each was re-read in place. At `89cad75d5570`, 2026-10-02: across the hop * from `31971ff1e28f` `record-details.tsx`, `record-highlights.tsx`, * `record-quick-actions.tsx` and the three `permissions` files are * byte-identical, so NO anchor in those six moved; `record-related-list.tsx` @@ -2286,10 +2296,18 @@ export const PageAccordionProps = strictObject({ * re-derive the same false candidate). * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `89cad75d5`; re-derived at that pin 2026-10-02 — - * `containers.tsx` is byte-identical across the hop from `31971ff1e` - * (`git diff --quiet`), so both anchors were re-READ in place, each still - * the read point this record names: `1171-1177` and `1220`. At `31971ff1e` + * (`.objectui-sha` = `ab1879721`; re-derived at that pin 2026-10-03 — + * `containers.tsx` changed across the hop from `89cad75d5` (23 insertions, + * 27 deletions: objectui#11445's i18next count family for the `page:tabs` + * count badge, and objectui#11438 dropping the `page:header` + * registration's inert `breadcrumb` input below both anchors), so both + * anchors were re-READ rather than carried, and BOTH MOVED up one line with + * their text byte-identical (the tab-count translation table above them + * lost a net line): `1171-1177` -> `1170-1176`, `1220` -> `1219`. At + * `89cad75d5` (2026-10-02) `containers.tsx` was byte-identical across the + * hop from `31971ff1e` (`git diff --quiet`), so both anchors were re-READ in + * place, each still the read point this record names: `1171-1177` and + * `1220`. At `31971ff1e` * (2026-10-01) `containers.tsx` was byte-identical across the hop from * `e420df310` (`git diff --quiet`), so both anchors were re-READ in place, * each still the read point this record names: `1171-1177` and `1220`. At `e420df310` @@ -2320,12 +2338,12 @@ export const PageAccordionProps = strictObject({ * lines byte-identical and the registration input `966` -> `1116` with its * LINE rewritten — it declares `of: 'object'` and carries a longer * description — while the member list this record cites stayed - * unchanged): `containers.tsx:1171-1177` + * unchanged): `containers.tsx:1170-1176` * renders * `{item.icon && }` inside the * `AccordionTrigger`, grouped with the label in the trigger's one wrapping * span, and the renderer's registration publishes the key to the Studio - * block designer at `:1220` (the `items` input, documented as + * block designer at `:1219` (the `items` input, documented as * `[{ label, icon?, collapsed?, children }]`). * * Vocabulary is Lucide, resolved through objectui's `LazyIcon` @@ -2567,11 +2585,12 @@ export const ElementButtonPropsSchema = lazySchema(() => strictObject({ * the button. * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `89cad75d5`; re-derived at that pin 2026-10-02 — + * (`.objectui-sha` = `ab1879721`; re-derived at that pin 2026-10-03 — * `button.tsx`, `lib/lazy-icon.tsx`, `renderers/action/resolve-icon.ts` and the - * generated `lucide-record-icon-names.ts` are all byte-identical to `31971ff1e` + * generated `lucide-record-icon-names.ts` are all byte-identical to `89cad75d5` * across the hop onto this pin, so every anchor below holds unmoved and was - * re-checked in place; they were byte-identical to `e420df310` across the hop + * re-checked in place; they were byte-identical to `31971ff1e` across the hop + * onto `89cad75d5` (2026-10-02), byte-identical to `e420df310` across the hop * onto `31971ff1e` (2026-10-01), byte-identical to `db11afd49` across the hop * onto `e420df310` (2026-09-30), and byte-identical to `dd3f7e1be` across the hop * onto `db11afd49` (2026-09-29) as well. At `dd3f7e1be` (2026-09-28) @@ -3042,12 +3061,27 @@ export const ElementTextInputPropsSchema = lazySchema(() => strictObject({ * had no row for — `action:button`, `action:group`, `action:menu`, * `action:icon`, `element:definition-list`, `element:repeater` * (`core/src/registry/public-blocks.ts:117-122` at the pin this repo builds - * against, `.objectui-sha` = `89cad75d5`; first measured at `.objectui-sha` + * against, `.objectui-sha` = `ab1879721`; first measured at `.objectui-sha` * pin `f8a9d0fb0`, every read point below re-derived at the current pin - * 2026-10-02: `public-blocks.ts`, `auto-trigger.ts`, `static-params.ts`, all - * four `action-*.tsx` renderers, `basic/data-list.tsx` and `ui/button.tsx` - * are byte-identical to `31971ff1e` (`git diff --quiet`), so every anchor - * below held unmoved and was re-read in place. At `31971ff1e` (2026-10-01): + * 2026-10-03: `public-blocks.ts`, `auto-trigger.ts`, `static-params.ts`, + * `basic/data-list.tsx` and `ui/button.tsx` are byte-identical to `89cad75d5` + * (`git diff --quiet`), so their anchors held unmoved and were re-read in + * place, and all four `action-*.tsx` renderers changed in one way: + * objectui#11344 forwards the spec's `outcomeMessages` to the runner beside + * `successMessage` (+7 in `action-button.tsx`'s forward, +3 in each of the + * other three), and `action-button.tsx` / `action-icon.tsx` record in a + * registration comment why the input stays unpublished, and reword the + * `successMessage` input's description (+14/-1 and +7/-2 in all). So every + * anchor below the inserted forward line MOVED with its cited text + * byte-identical — by 7 then 13 in `action-button.tsx`, by 3 then 5 in + * `action-icon.tsx`, by 3 in `action-group.tsx` and `action-menu.tsx` — and + * every anchor above it held; each is cited below at this pin, and the one + * read the range added, `outcomeMessages`, is recorded on the rows. At + * `89cad75d5` (2026-10-02): `public-blocks.ts`, `auto-trigger.ts`, + * `static-params.ts`, all four `action-*.tsx` renderers, + * `basic/data-list.tsx` and `ui/button.tsx` are byte-identical to `31971ff1e` + * (`git diff --quiet`), so every anchor below held unmoved and was re-read in + * place. At `31971ff1e` (2026-10-01): * `public-blocks.ts`, `auto-trigger.ts`, `static-params.ts`, * `action-group.tsx`, `action-menu.tsx` and `action-icon.tsx` are * byte-identical to `e420df310`, so their anchors held unmoved, and @@ -3119,10 +3153,10 @@ export const ElementTextInputPropsSchema = lazySchema(() => strictObject({ * * `objectName` IS declared on `action:button` / `action:icon`: absent at the * first measurement, it is forwarded to the runner at the current pin - * (`action-button.tsx:311`, `action-icon.tsx:208`), and the console resolves + * (`action-button.tsx:318`, `action-icon.tsx:211`), and the console resolves * its dispatch target as `action.objectName || `. On * `action:group` / `action:menu` the forward is the MEMBER's - * (`action-group.tsx:378`, `action-menu.tsx:324`), so it rides each member + * (`action-group.tsx:381`, `action-menu.tsx:327`), so it rides each member * object and the container rows gain no key. */ @@ -3206,14 +3240,14 @@ const ACTION_NODE_GUIDANCE = { * page button is not a registered object action, and the runner takes a * nameless action on its `type` leg. `UIActionSchema` declares it required; * the read does not. - * - `label` — `:387`, placed as a React child (so a literal string: an inline + * - `label` — `:394`, placed as a React child (so a literal string: an inline * locale map is not resolved on this path; the translation bundle's * `components..label` is the localization channel); also `:123`, `:220`. * - `icon` — `:138`, through the shared `resolveIcon`. * - `actionType` — `:215`, forwarded as the runner's `type`. * - `variant` / `size` — `:141` / `:142`: `primary` → the primitive's * `default` and `md` → `default`, everything else handed to `Button` as-is. - * - `visible` / `disabled` — `:121` + `:339` / `:134` + `:375`, evaluated + * - `visible` / `disabled` — `:121` + `:346` / `:134` + `:382`, evaluated * against the row the host binds (`usePredicateRecordContext`). * - `params` — `:180-183`. An array is the input list, forwarded as * `actionParams`; the static values are read off `properties.params` @@ -3222,11 +3256,22 @@ const ACTION_NODE_GUIDANCE = { * hoist makes `schema.params` the same object, so an array here is the input * list and an object is the static values. (A node-level object `params` * outside `properties` is ignored with a development warning.) - * - Forwarded to the runner (`:215-311`): `description`, `target`, `openIn`, + * - Forwarded to the runner (`:215-318`): `description`, `target`, `openIn`, * `method`, `bodyExtra`, `bodyShape`, `operation`, `patch`, * `confirmText`, `successMessage`, `errorMessage`, `refreshAfter`, * `undoable`, `recordIdField`, `locations`, `toast`, `resultDialog`, - * `onSuccess`, `objectName`. + * `onSuccess`, `objectName` — and, since objectui#11344, `outcomeMessages`. + * + * ⚠️ New at this pin, and NOT a key of this row: objectui#11344 forwards + * `outcomeMessages` (`:261`), the success copy per handler outcome that + * `ActionSchema` declares, because an `action:bar` member is spread onto an + * `action:button` node whole and a registered action's map would otherwise + * never reach the success toast. objectui leaves the input unpublished + * (`:422-426`) until this row declares it. So the key reaches the renderer + * only on that member path; written on an `action:button` node, this strict + * row refuses it at save. The row stays as measured from the block's own + * authorable reads: declaring the key is a contract decision, not a pin + * re-measure. * * NOT declared, though forwarded: `endpoint`. Both console runtimes register * their own `api` handler, which reads `action.target || action.name` and @@ -3235,12 +3280,12 @@ const ACTION_NODE_GUIDANCE = { * `target` (`ACTION_NODE_ALIASES` above) — one concept, one spelling, one * verdict on the action and on the block that runs it. * - * The registration's `inputs` (`:414-559`) publish twenty-seven of the + * The registration's `inputs` (`:427-572`) publish twenty-seven of the * twenty-nine keys the renderer forwards or reads since objectui#11168 slice 1 * (at `db11afd49` they published seven, `:395-416`), and since slice 2 the * `size` input publishes the same five values this row declares (`default`, * `sm`, `md`, `lg`, `icon`); the two left unpublished are `endpoint` (refused - * here, above) and `undoable`, each still forwarded (`:215-311`), on + * here, above) and `undoable`, each still forwarded (`:215-318`), on * objectui's measurement that the console's own `api` handler reads `target` * and never `endpoint`, and that the runner offers Undo only with a host row * stash this block never writes. It also publishes `className`, which is read @@ -3312,23 +3357,27 @@ export type ActionButtonPropsParsed = z.infer; * * - `size` is NOT read — `:120` pins the primitive's `icon` size — so it is * not declared (the registration does not publish it either). - * - `undoable` and `recordIdField` are NOT forwarded (`:147-209` lists the + * - `undoable` and `recordIdField` are NOT forwarded (`:147-212` lists the * rest of `action:button`'s forward and not these two), so not declared. * - `label` is read five times at this pin (four at `db11afd49`): * `:109` (the visibility-diagnostic label, `schema.name ?? schema.label`, * new with objectui#11212's fail-closed `visible`), `:165` (forwarded), - * `:278` (`aria-label`, falling back to `name`), `:287` (its first letter - * when no icon resolves) and `:293` / `:299` (the tooltip). `description` - * is the tooltip's fallback (`:299`) as well as forwarded (`:166`). - * - `visible` / `disabled` — `:107` + `:242` / `:115` + `:271`; `visible` + * `:281` (`aria-label`, falling back to `name`), `:290` (its first letter + * when no icon resolves) and `:296` / `:302` (the tooltip). `description` + * is the tooltip's fallback (`:302`) as well as forwarded (`:166`). + * - `visible` / `disabled` — `:107` + `:245` / `:115` + `:274`; `visible` * fails CLOSED on a faulting predicate since objectui#11212, as on * `action:button`. `variant` — `:119`, `primary` mapped to `default`, * renderer default `ghost`. * - `params` — `:143-146`, routed exactly as on `action:button`. - * - Forwarded (`:160-208`): `actionType`, `name`, `target`, `openIn`, + * - Forwarded (`:160-211`): `actionType`, `name`, `target`, `openIn`, * `method`, `bodyExtra`, `bodyShape`, `operation`, `patch`, * `confirmText`, `successMessage`, `errorMessage`, `refreshAfter`, * `locations`, `toast`, `resultDialog`, `onSuccess`, `objectName`. + * - `outcomeMessages` is forwarded too since objectui#11344 (`:189`) and NOT + * declared, for `action:button`'s reason: it is an `ActionSchema` key that + * reaches this renderer on the `action:bar` member path, and objectui + * leaves its input unpublished until the row declares it. * - `endpoint` is forwarded too and NOT declared, for `action:button`'s * reason: no console `api` handler reads it. Refused with `ActionSchema`'s * rename onto `target`, through the same `ACTION_NODE_ALIASES` (#21005). @@ -3424,23 +3473,23 @@ const actionMemberList = () => z.array(z.record(z.string(), z.unknown())); * - `actions` — `:303`, then filtered by `location` through `actionRendersAt` * (`:304`); members are read at `:114-171` (inline), `:205-243` (dropdown), * both through the shared `useMemberVisible` for `visible` (`:81-86`), and - * forwarded at `:329-379` (static values `:329-335`, `objectName` - * `:378`). - * - `display` — `:401`, `inline` unless it is `dropdown`. - * - `label` / `icon` — `:421` / `:405`: the DROPDOWN trigger's text (default + * forwarded at `:329-382` (static values `:329-335`, `objectName` + * `:381`; the member's `outcomeMessages` too since objectui#11344). + * - `display` — `:404`, `inline` unless it is `dropdown`. + * - `label` / `icon` — `:424` / `:408`: the DROPDOWN trigger's text (default * `Actions`) and icon. Inline mode renders neither. - * - `variant` — `:411` (dropdown trigger) and `:453` (each inline member's + * - `variant` — `:414` (dropdown trigger) and `:456` (each inline member's * fallback); no `primary` mapping at group level, so the primitive's six. - * - `size` — `:412` maps `md` → `default` for the dropdown trigger, but `:454` + * - `size` — `:415` maps `md` → `default` for the dropdown trigger, but `:457` * hands the group size to each inline member raw, and `:124` maps only a * member's OWN `md`. So `md` renders only in dropdown mode, and in the * default inline mode reaches the Button primitive, which has no `md`. * Declared: the primitive's four sizes. (Since objectui#11168 the - * registration publishes those same four, `:484-525`; through `db11afd49` + * registration publishes those same four, `:487-528`; through `db11afd49` * it published `sm` / `md` / `lg`. A stored `md` is still mapped in * dropdown mode only, which objectui records as a back-compat read.) - * - `visible` — `:290` + `:398`; `:290` fails CLOSED on a faulting predicate - * since objectui#11212. `:398` tests the raw value's truthiness, so a + * - `visible` — `:290` + `:401`; `:290` fails CLOSED on a faulting predicate + * since objectui#11212. `:401` tests the raw value's truthiness, so a * literal `false` is honoured by `SchemaRenderer`'s node gate, which also * evaluates the hoisted value, rather than by this check. * @@ -3448,7 +3497,7 @@ const actionMemberList = () => z.array(z.record(z.string(), z.unknown())); * consume the host's EVALUATED `disabled` verdict by name (`:274`), the one * `SchemaRenderer`'s generic enablement gate computes from a hoisted * `disabled` on any node, and apply it to every inline member (`:159`) or to - * the dropdown trigger (`:415`). So a `properties.disabled` on this block now + * the dropdown trigger (`:418`). So a `properties.disabled` on this block now * greys it out at render while this strict row refuses the key at save. The * row stays as measured from the block's own reads: declaring the key is a * contract decision, not a pin re-measure. @@ -3503,23 +3552,24 @@ export type ActionGroupPropsParsed = z.infer; * (`components/src/renderers/action/action-menu.tsx` at the pin). Read * points, per key: * - * - `actions` — `:339`; members are read at `:80`, `:108-147` and `:405`, run - * through `ActionAutoTrigger` (`:360-367`), and forwarded at `:264-325` - * (static values `:264-270`, `objectName` `:324`). - * - `label` — `:386` (`aria-label`, default: the translated "More actions") - * and `:395-396` (trigger text; icon-only when omitted). + * - `actions` — `:342`; members are read at `:80`, `:108-147` and `:408`, run + * through `ActionAutoTrigger` (`:363-370`), and forwarded at `:264-328` + * (static values `:264-270`, `objectName` `:327`; the member's + * `outcomeMessages` too since objectui#11344). + * - `label` — `:389` (`aria-label`, default: the translated "More actions") + * and `:398-399` (trigger text; icon-only when omitted). * - `icon` — `:240`, default the `MoreHorizontal` glyph. * - `variant` / `size` — `:241` / `:242`, handed to the Button primitive * unmapped (defaults `ghost` / `icon`): no `primary`, no `md` here. - * - `visible` — `:235` + `:333`, fail-closed; the same truthiness note as + * - `visible` — `:235` + `:336`, fail-closed; the same truthiness note as * `action:group`'s applies to a literal `false`. * - * The registration's `inputs` (`:432-460`) publish `label`, `icon`, + * The registration's `inputs` (`:435-463`) publish `label`, `icon`, * `actions`, `variant`, `size`, `visible` and `className` since objectui#11168 * slice 1, every key this row declares; through `db11afd49` (`:410-419` * there) `size` and `visible` were read and unpublished. ⚠️ As on * `action:group`, objectui#11182 has the trigger consume the host's evaluated - * `disabled` verdict (`:219`, `:385`) — at `db11afd49` it already reached the + * `disabled` verdict (`:219`, `:388`) — at `db11afd49` it already reached the * trigger through the `...rest` spread — so a `properties.disabled` here greys * the trigger out while this row refuses it; recorded, not declared. */ From e57e2ca42f419c9b151469280bf509c76d827bcb Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 18:17:31 +0000 Subject: [PATCH 03/10] chore(objectui): regenerate the SDUI manifest and the sdui-parser lockstep at ab1879721595; re-measure more pin citations (wip) Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w Co-authored-by: Claude --- packages/sdui-parser/objectui-lockstep.json | 6 +- packages/spec/src/ui/component.zod.ts | 172 +++++++--- scripts/sdui-manifest.record.json | 8 +- sdui.manifest.json | 355 +++++++++++++++++--- 4 files changed, 449 insertions(+), 92 deletions(-) diff --git a/packages/sdui-parser/objectui-lockstep.json b/packages/sdui-parser/objectui-lockstep.json index 9836b05b742..c3f4d102080 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": "89cad75d55702cc4f267bead5bf267de575d5842", - "revDate": "2026-10-02T06:25:01+00:00", + "rev": "ab187972159583b595facdcae3c73b50f6f312e9", + "revDate": "2026-10-03T17:12:55+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": "89cad75d55702cc4f267bead5bf267de575d5842", + "recordedAgainstPin": "ab187972159583b595facdcae3c73b50f6f312e9", "grammarRegion": { "file": "packages/sdui-parser/src/parse.ts", "delimiter": "/* ---------------------- the JS literal subset (#6614) ---------------------- */", diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index a2415435227..67730ef8bbc 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -4467,7 +4467,21 @@ export const ObjectMetricPropsSchema = lazySchema(() => strictObject({ * same record for the metric tile. * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `89cad75d5`; re-derived at that pin 2026-10-02 — + * (`.objectui-sha` = `ab1879721`; re-derived at that pin 2026-10-03 — + * `MetricCard.tsx` and `lazy-icon.tsx` are byte-identical to `89cad75d5`, + * so their anchors hold unmoved and were re-read in place; the other three + * files changed and every one of their anchors MOVED with its cited text + * byte-identical, no hunk naming `icon`. `plugin-dashboard/src/index.tsx` + * (+55/-22) marked the `metric` / `metric-card` registrations above the + * `object-metric` one `skipFallback` (objectui#10859 batch 8 phase 2b, + * the bare keys retired), so that registration's start MOVED `227` -> + * `244` and the icon input `252` -> `269`, still `{ name: 'icon', type: + * 'string' }`; `ObjectMetricWidget.tsx` (+108/-14: objectui#11526's + * structured `groupBy` with a rule-list `filter`, objectui#11444's currency + * and objectui#11475's percent faces) MOVED the destructure `255` -> `304` + * and the forward `659` -> `753`; `MetricWidget.tsx` (+30/-10, the same + * currency and percent faces) MOVED the resolve `351-360` -> `371-380` and + * the draw `412-421` -> `432-441`. At `89cad75d5` (2026-10-02) * `plugin-dashboard/src/index.tsx`, `MetricWidget.tsx`, `MetricCard.tsx` * and `lazy-icon.tsx` are byte-identical to `31971ff1e`, so their anchors * hold unmoved and were re-read in place; `ObjectMetricWidget.tsx` changed @@ -4513,12 +4527,12 @@ export const ObjectMetricPropsSchema = lazySchema(() => strictObject({ * `{ name: 'icon', type: 'string' }`. The four render-path anchors moved * with their cited text byte-identical), and the chain * runs three files: - * `plugin-dashboard/src/index.tsx:252` publishes it as a designer input + * `plugin-dashboard/src/index.tsx:269` publishes it as a designer input * on the registered `object-metric` block; - * `ObjectMetricWidget.tsx:255` destructures it and forwards it at `:659` to - * `MetricWidget`; `MetricWidget.tsx:351-360` resolves it via + * `ObjectMetricWidget.tsx:304` destructures it and forwards it at `:753` to + * `MetricWidget`; `MetricWidget.tsx:371-380` resolves it via * `getLazyIcon(icon)` — guarded on `typeof icon === 'string'`, because the - * React prop also accepts a ready-made node — and `:412-421` draws it in the + * React prop also accepts a ready-made node — and `:432-441` draws it in the * tinted square whose colour comes from `colorVariant`. * * ⚠️ Do not re-anchor this to `MetricCard.tsx`. That sibling calls @@ -4649,10 +4663,12 @@ const ObjectKanbanLaneSchema = lazySchema(() => strictObject({ * DESIGNER's spelling with * zero read points (#7973 class) — aliased to the `groupBy` the board reads. * `limit` (#16503) was measured later, at the pin this repo builds against - * (`.objectui-sha` = `89cad75d5`; re-measured there 2026-10-02 — - * `ObjectKanban.tsx` is byte-identical across the hop from `31971ff1e` + * (`.objectui-sha` = `ab1879721`; re-measured there 2026-10-03 — + * `ObjectKanban.tsx` is byte-identical across the hop from `89cad75d5` * (`git diff --quiet`), so the anchor did not move and was re-read in place. - * At `31971ff1e` (2026-10-01) + * At `89cad75d5` (2026-10-02) `ObjectKanban.tsx` was byte-identical across + * the hop from `31971ff1e` (`git diff --quiet`), so the anchor did not move + * and was re-read in place. At `31971ff1e` (2026-10-01) * `ObjectKanban.tsx` changed on that hop only in the comment above its * `navigation` read (+12/-14, objectui#8652: the key is now declared on * `ObjectKanbanSchema`), far below the anchor, which did not move and was @@ -4736,8 +4752,20 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * Row cap (#16503 — the spec half of objectui#8172; decision batch #68, * 2026-09-07, option A: the contract declares the capability that already * ships, is documented and is in use). Measured at the objectui pin this - * repo builds against (`.objectui-sha` = `89cad75d5`; re-measured there - * 2026-10-02 — `ObjectKanban.tsx`, `plugin-kanban/src/types.ts` (still no + * repo builds against (`.objectui-sha` = `ab1879721`; re-measured there + * 2026-10-03 — `ObjectKanban.tsx`, `plugin-kanban/src/types.ts` (still no + * row-cap member), `ElementDataSourceGate.tsx`, `element-data-source.ts` + * and `plugin-kanban.mdx` are byte-identical to `89cad75d5`, so the query + * `715-725`, the default `:97`, the `queryFilter` resolution `:584-585`, + * the import `:10`, `:218-220`, `:437-456` and `:268-272` did not move and + * were re-read in place, and the `limit` row and the `limit: 250` snippet + * did not change; `plugin-kanban/src/index.tsx` changed only below the + * mapping (+3/-2: objectui#11438's 17.6.0 re-citation in the + * `ObjectKanbanRenderer` docblock and objectui#11522's `{ condition, style }` + * description on the `conditionalFormatting` input), which did not move + * from `487-491`; and `objectql.ts` MOVED the member `4661` -> `4720` + * byte-identical, still inside `ObjectKanbanSchema`. At `89cad75d5`, + * re-measured there 2026-10-02 — `ObjectKanban.tsx`, `plugin-kanban/src/types.ts` (still no * row-cap member), `ElementDataSourceGate.tsx` and `element-data-source.ts` * are byte-identical to `31971ff1e`, so the query `715-725`, the default * `:97`, the `queryFilter` resolution `:584-585`, the import `:10`, @@ -4839,7 +4867,7 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * through is `ObjectKanbanSchema` — ⚠️ `KanbanSchema` was RETIRED on this hop * (maintainer ruling 2026-09-09) and `plugin-kanban/src/types.ts` no longer * declares the member at all — imported at `ObjectKanban.tsx:10` and - * declaring `limit?: number` at `packages/types/src/objectql.ts:4661`, + * declaring `limit?: number` at `packages/types/src/objectql.ts:4720`, * and `content/docs/plugins/plugin-kanban.mdx` * teaches it with a typed snippet (`limit: 250`) plus a Properties row. So * an author following the published docs wrote a node the save gate @@ -4934,7 +4962,17 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * quick-add control and no block a document can name offers one either). * * Measured at the objectui pin this repo builds against - * (`.objectui-sha` = `89cad75d5`; re-measured there 2026-10-02 — + * (`.objectui-sha` = `ab1879721`; re-measured there 2026-10-03 — + * `KanbanBoardCore.tsx` and `ObjectKanban.tsx` are byte-identical to + * `89cad75d5`, so `:78` and `:1639` did not move and were re-read in place; + * `KanbanImpl.tsx` changed only in two comments above the gate + * (objectui#11522: card formatting is the spec `{ condition, style }` rule + * alone, +8/-5), so both gate lines MOVED by 3 byte-identical, `621` -> + * `624` and `634` -> `637`; `plugin-kanban/src/index.tsx` changed only at + * `:532` and below (+3/-2, objectui#11438's 17.6.0 re-citation and + * objectui#11522's `conditionalFormatting` input description), so the + * pass-through did not move from `345-346` and was re-read in place. At + * `89cad75d5` (2026-10-02) — * `KanbanImpl.tsx`, `KanbanBoardCore.tsx` and `ObjectKanban.tsx` are * byte-identical to `31971ff1e`, so `:621`, `:634`, `:78` and `:1639` did * not move and were re-read in place; `plugin-kanban/src/index.tsx` changed @@ -4988,12 +5026,12 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * which passes * `quickAdd={schema.quickAdd}` and `onQuickAdd={schema.onQuickAdd}` * (`plugin-kanban/src/index.tsx:357-358` there, `:345-346` at this pin) — but the affordance is gated on - * BOTH (`KanbanImpl.tsx:621` and `:634`), and `onQuickAdd` is a + * BOTH (`KanbanImpl.tsx:624` and `:637`), and `onQuickAdd` is a * host-supplied FUNCTION that JSON cannot carry and no producer puts on an * `object-kanban` node. `ObjectKanban.tsx` names neither half of the pair * in code (0 occurrences each, against 11 for the sibling `onCardClick` in the same * file — re-counted at `db11afd49`, at `f8a9d0fb0` and at `62597c588`; at - * `e420df310`, at `31971ff1e` and again at this pin 2 each, all four inside the two + * `e420df310`, at `31971ff1e`, at `89cad75d5` and again at this pin 2 each, all four inside the two * objectui#11234 comments that record the cut, and still 11 for * `onCardClick`; this record said 6, which the identical * file at `87af769e9` does not reproduce either, so the control was @@ -5013,7 +5051,7 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * renders directly and can hand the runtime function to. ⚠️ Re-read at * `e420df310`: objectui retired the schema-only `kanban-ui` registration * long before this pin (objectui#8257; 0 registrations at `dd3f7e1be`, - * `db11afd49`, `e420df310`, `31971ff1e` and this pin), and the pair now lives on the + * `db11afd49`, `e420df310`, `31971ff1e`, `89cad75d5` and this pin), and the pair now lives on the * exported `KanbanRenderer` React component a host mounts directly * (`plugin-kanban/src/index.tsx:345-346`), not on any block a document can * name. ⛔ So the tombstone below prescribes "delete the key" and names no @@ -5040,7 +5078,12 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * element schemas). * * Measured at the pin this repo builds against (`.objectui-sha` = - * `89cad75d5`; re-measured there 2026-10-02 — `ObjectKanban.tsx` is + * `ab1879721`; re-measured there 2026-10-03 — `ObjectKanban.tsx` and + * `useNavigationOverlay.ts` are byte-identical to `89cad75d5` + * (`git diff --quiet`), so every anchor held unmoved and was re-read in + * place: the read `1280`, the hand-off `1288-1289`, the overlay render + * `1538` and its `NavigationOverlay` `1554-1567`, and the card click + * `1663`. At `89cad75d5` (2026-10-02) `ObjectKanban.tsx` was * byte-identical to `31971ff1e` (`git diff --quiet`), so every anchor held * unmoved and was re-read in place: the read `1280`, the hand-off * `1288-1289`, the overlay render `1538` and its `NavigationOverlay` @@ -5220,7 +5263,13 @@ export const ObjectCalendarPropsSchema = lazySchema(() => strictObject({ * `object-kanban`'s above. * * Measured at the pin this repo builds against (`.objectui-sha` = - * `89cad75d5`; re-measured there 2026-10-02 — `ObjectCalendar.tsx` is + * `ab1879721`; re-measured there 2026-10-03 — `ObjectCalendar.tsx` changed + * across the hop from `89cad75d5` in one comment line only, line for line + * (`:1250`, objectui#11438 re-citing the installed spec as 17.6.0), and + * `useNavigationOverlay.ts` is byte-identical, so every anchor held unmoved + * and was re-read in place: the read `1018`, the hand-off `1020-1021`, the + * event click `1413`, the overlay render `1333` and its `NavigationOverlay` + * `1346-1359`. At `89cad75d5` (2026-10-02) `ObjectCalendar.tsx` was * byte-identical to `31971ff1e` (`git diff --quiet`), so every anchor held * unmoved and was re-read in place: the read `1018`, the hand-off * `1020-1021`, the event click `1413`, the overlay render `1333` and its @@ -5732,7 +5781,10 @@ const OBJECT_MAP_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ /** * `object-map` (objectui `plugin-map/src/ObjectMap.tsx` plus the registry shell * `plugin-map/src/index.tsx`, read at the pin this repo builds against — - * `.objectui-sha` = `89cad75d5`, re-measured there 2026-10-02: all three + * `.objectui-sha` = `ab1879721`, re-measured there 2026-10-03: `ObjectMap.tsx`, + * `index.tsx` and `core/src/utils/record-source.ts` are byte-identical across + * the hop from `89cad75d5` (`git diff --quiet`), so every anchor held unmoved + * and was re-read in place. At `89cad75d5` (2026-10-02) all three * files changed across the hop from `31971ff1e`, so every anchor was * re-mapped through the diff and re-READ. `ObjectMap.tsx` (+11/-4) changed * in two places: objectui#11355 typed the flat-key read in @@ -5895,8 +5947,11 @@ export const ObjectMapPropsSchema = lazySchema(() => strictObject({ * Base query filter — the `ViewFilterRule` ARRAY form, the one filter * orthography every `filter` door in this map shares (ui#6206-B reaching the * `object-*` family: #15449, decision batch #55, option A). Measured at the - * pin this repo builds against (`.objectui-sha` = `89cad75d5`, re-READ there - * 2026-10-02: `ObjectMap.tsx` changed above all three anchors + * pin this repo builds against (`.objectui-sha` = `ab1879721`, re-READ there + * 2026-10-03: `ObjectMap.tsx` and `plugin-map/src/index.tsx` are + * byte-identical to `89cad75d5` (`git diff --quiet`), so all three anchors + * and the `filter` input held unmoved and were re-read in place. At + * `89cad75d5`, re-READ there 2026-10-02: `ObjectMap.tsx` changed above all three anchors * (objectui#11168 slice 3's seven-line `mapStyle` precedence comment; * objectui#11355's in-place typing of the flat-key read is line-neutral), so * each MOVED down 7 with its text byte-identical, `:857` -> `:864`, `:951` @@ -5940,8 +5995,11 @@ export const ObjectMapPropsSchema = lazySchema(() => strictObject({ * Marker order — the `SortItem` ARRAY form, the one sort orthography every * DECLARED `sort` door on this platform carries (objectui#8221, decision batch * #77, option B). Measured at the pin this repo builds against - * (`.objectui-sha` = `89cad75d5`, re-READ there 2026-10-02 — `ObjectMap.tsx` - * changed above both anchors (objectui#11168 slice 3's seven-line `mapStyle` + * (`.objectui-sha` = `ab1879721`, re-READ there 2026-10-03 — `ObjectMap.tsx` + * and `plugin-map/src/index.tsx` are byte-identical to `89cad75d5` + * (`git diff --quiet`), so both anchors and the `sort` input held unmoved + * and were re-read in place. At `89cad75d5`, re-READ there 2026-10-02 — + * `ObjectMap.tsx` changed above both anchors (objectui#11168 slice 3's seven-line `mapStyle` * precedence comment; objectui#11355's in-place typing of the flat-key read * is line-neutral), so both MOVED down 7 with their text byte-identical, * `:952` -> `:959` and `:1031` -> `:1038`; and `plugin-map/src/index.tsx` @@ -6060,7 +6118,20 @@ const OBJECT_GANTT_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ /** * `object-gantt` (objectui `plugin-gantt/src/ObjectGantt.tsx` plus the registry * shell `plugin-gantt/src/index.tsx`, read at the pin this repo builds against - * — `.objectui-sha` = `89cad75d5`, re-measured there 2026-10-02: all three + * — `.objectui-sha` = `ab1879721`, re-measured there 2026-10-03: + * `plugin-gantt/src/index.tsx` and `record-source.ts` are byte-identical to + * `89cad75d5` (`git diff --quiet`), so their anchors held unmoved; and + * `ObjectGantt.tsx` +8/-0 gained one import line above `getGanttConfig` and, + * in the tooltip's `percent` row, objectui#11475's `percentCellScale(def)` + * argument with its comment (+7) — so every anchor through the tooltip MOVED + * by 1 (`:611-613` -> `:612-614`, `:723` -> `:724`, `:919` -> `:920`, + * `:1064`/`:1065` -> `:1065`/`:1066`, `:1073`/`:1075` -> `:1074`/`:1076`, + * `:886-889` -> `:887-890`, `:1079-1084` -> `:1080-1085`) and every anchor + * after it by 8 (`:1670`/`:1671` -> `:1678`/`:1679`, `:1822`/`:1824` -> + * `:1830`/`:1832`, `:1960` -> `:1968`, `:2027` -> `:2035`, `:2339` -> + * `:2347`, `:2497`-`:2505` -> `:2505`-`:2513`, `:2540-2546` -> `:2548-2554`), + * each with its cited text byte-identical; NONE changed content. At + * `89cad75d5` (2026-10-02) all three * files changed across the hop from `31971ff1e`, so every anchor was * re-mapped through the diff and re-READ; NONE changed content. * `ObjectGantt.tsx` +47/-10: one import added above `getGanttConfig` and one @@ -6131,46 +6202,46 @@ const OBJECT_GANTT_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * every number was re-derived from that tree, and the `label` one moved far * enough that the retired reading now lands in an unrelated callback.) * Read points per key: `data` (`resolveRecordSourceConfig` at - * `:723` — rung 1, `core/src/utils/record-source.ts:307`), `staticData` (rung 2, - * `record-source.ts:311`), `objectName` (rung 3; also `:1824` and `:2027` here), - * `filter` (`:919`, read once through `useResolvedFilter`, which resolves its - * context tokens — objectui#10666 — and `:1064` sends the held result to - * `$filter`; at `f8a9d0fb0` it was sent verbatim), `sort` (`:1065`, through - * `convertSortToQueryParams` to `$orderby`), `gantt` (`:611-613` — + * `:724` — rung 1, `core/src/utils/record-source.ts:307`), `staticData` (rung 2, + * `record-source.ts:311`), `objectName` (rung 3; also `:1832` and `:2035` here), + * `filter` (`:920`, read once through `useResolvedFilter`, which resolves its + * context tokens — objectui#10666 — and `:1065` sends the held result to + * `$filter`; at `f8a9d0fb0` it was sent verbatim), `sort` (`:1066`, through + * `convertSortToQueryParams` to `$orderby`), `gantt` (`:612-614` — * `getGanttConfig` branch 1, the author face and the registration's declared * `{ name: 'gantt', type: 'object' }` input (`plugin-gantt/src/index.tsx:179`), * validated there against this - * repo's own {@link GanttConfigSchema}), `navigation` (`:1960`), `label` - * (`:2542`, resolved through `resolveInlineI18nLabel` for the export file + * repo's own {@link GanttConfigSchema}), `navigation` (`:1968`), `label` + * (`:2550`, resolved through `resolveInlineI18nLabel` for the export file * name — this record said `resolveI18nLabel`, which the line did not read at * `f8a9d0fb0` either — * ⚠️ the retired reading `:1849` was the comment ABOVE that chain and at this * pin `87af769e9` was a BLANK line (nothing cites `:1849` now), two above a dependency-delete callback's own comment, * so it is re-READ here, not re-pointed), - * `skipWeekends` (`:1670`), `holidays` (`:1671`), `persistLayout` (`:1822`), - * `viewName` (`:1824`), `markers` (`:2497`), `criticalPath` (`:2500`), - * `showBaselines` (`:2503`), `readOnly` (`:2339` and `:2504`) and - * `mobileReadOnly` (`:2505`). + * `skipWeekends` (`:1678`), `holidays` (`:1679`), `persistLayout` (`:1830`), + * `viewName` (`:1832`), `markers` (`:2505`), `criticalPath` (`:2508`), + * `showBaselines` (`:2511`), `readOnly` (`:2347` and `:2512`) and + * `mobileReadOnly` (`:2513`). * * Measured and deliberately NOT declared: the flat `GanttConfig` spellings (the * flatten product — {@link OBJECT_GANTT_FLAT_CONFIG_GUIDANCE}); `title`, which * this renderer never reads (the export-name chain is `gantt.exportFileName` → - * `label` → the OBJECT's label → `objectName`, `:2540-2546`); a row cap — the + * `label` → the OBJECT's label → `objectName`, `:2548-2554`); a row cap — the * reload's `$top` is the platform ceiling, spread in as core's - * `nonGridRowCeilingQuery()` (`:1075`: one probe row past the ceiling, which + * `nonGridRowCeilingQuery()` (`:1076`: one probe row past the ceiling, which * `applyNonGridRowCeiling` slices back off; at `f8a9d0fb0` a bare * `$top: NON_GRID_ROW_CEILING_TOP`, until objectui#7508 homed the ceiling in - * core) and the renderer's own comment marks it "⛔ Not authorable" (`:1073`); and + * core) and the renderer's own comment marks it "⛔ Not authorable" (`:1074`); and * the `onTaskClick` / `onRowClick` / `onBeforeTaskUpdate` callbacks, which are * host props. ⚠️ New at `f8a9d0fb0` and recorded, not ruled: objectui#10250 * gave the renderer two more reads, `search` and `searchableFields` - * (`:886-889`, sent as `$search` / `$searchFields` at `:1079-1084`), which + * (`:887-890`, sent as `$search` / `$searchFields` at `:1080-1085`), which * `ListView.tsx` writes onto the node it generates from its toolbar Search * box — a host-generated key pair in the same position as the flatten * product, and not declared here. * * VALUE posture: `gantt` is the one config block in this family whose value - * contract is already the SPEC's — `ObjectGantt.tsx:613` validates it against + * contract is already the SPEC's — `ObjectGantt.tsx:614` validates it against * `GanttConfigSchema` imported from `@objectstack/spec/ui` — so the read point * names the schema and this door takes it rather than `z.unknown()`. The * scalars below are read as their coercions say: `!!schema.readOnly`, @@ -6258,10 +6329,19 @@ export type ObjectGanttPropsParsed = z.infer; /** * The flat `TreeConfig` spellings `getTreeConfig` reads ahead of the `tree` * block (`ObjectTree.tsx:281-294`) and that `ObjectView` / `ListView` EMIT when - * they flatten `options.tree` (`ListView.tsx:3756-3775`, `case 'tree'`: the + * they flatten `options.tree` (`ListView.tsx:3770-3789`, `case 'tree'`: the * product carries these keys, the EFFECTIVE `filter` objectui#10250 added, and * NO `tree` key). Both halves re-READ at the - * pin this repo builds against (`.objectui-sha` = `89cad75d5`) on 2026-10-02 — + * pin this repo builds against (`.objectui-sha` = `ab1879721`) on 2026-10-03 — + * `ObjectTree.tsx` and `plugin-tree/src/index.tsx` are byte-identical to + * `89cad75d5`, so `281-294` and `233-254` did not move and were re-read in + * place; `ListView.tsx` changed around its `case 'tree'` arm (+36/-18: + * above it objectui#10813's `isempty` / `isnotempty` filter operators and + * objectui#11445's record-count translation keys, below it objectui#11227's + * resolved empty-state labels and objectui#11445's count line), so the arm + * MOVED byte-identical `3756-3775` -> `3770-3789`, `:3770` -> `:3784` with + * it, and the flat key set each reads or emits did not move. At + * `89cad75d5`, 2026-10-02 — * `ListView.tsx` is byte-identical to `31971ff1e`, so `3756-3775` and `:3770` * did not move; `ObjectTree.tsx` changed (+8/-17, objectui#11168 slice 3 * replacing the cast comment on its `navigation` read with a typed read), @@ -6301,7 +6381,7 @@ export type ObjectGanttPropsParsed = z.infer; * * `titleField` is in the set although no `tree` block key is spelled that way: * `ListView`'s flatten resolves `treeCfg.titleField` into `labelField` before - * emitting (`ListView.tsx:3770`, `labelField: treeCfg.labelField || + * emitting (`ListView.tsx:3784`, `labelField: treeCfg.labelField || * treeCfg.titleField || 'name'`), so the author's intent is always the block's * `labelField`, and the prescription below says so. * @@ -6330,7 +6410,11 @@ const OBJECT_TREE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ /** * `object-tree` (objectui `plugin-tree/src/ObjectTree.tsx` plus the registry * shell `plugin-tree/src/index.tsx`, read at the pin this repo builds against - * — `.objectui-sha` = `89cad75d5`, re-measured there 2026-10-02: all three + * — `.objectui-sha` = `ab1879721`, re-measured there 2026-10-03: + * `plugin-tree/src/index.tsx`, `ObjectTree.tsx` and + * `core/src/utils/record-source.ts` are byte-identical to `89cad75d5` + * (`git diff --quiet`), so every anchor held unmoved and was re-read in + * place. At `89cad75d5` (2026-10-02) all three * cited files changed on the hop off `31971ff1e`, so every anchor was * re-mapped through the diff and re-READ. * `core/src/utils/record-source.ts` changed (+4/-5, objectui#10859 batch 8: diff --git a/scripts/sdui-manifest.record.json b/scripts/sdui-manifest.record.json index 6007241d1cc..ce050430ec4 100644 --- a/scripts/sdui-manifest.record.json +++ b/scripts/sdui-manifest.record.json @@ -11,12 +11,12 @@ "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": "89cad75d55702cc4f267bead5bf267de575d5842", + "objectuiSha": "ab187972159583b595facdcae3c73b50f6f312e9", "source": "built-tree", - "modulesRoot": ".cache/objectui-89cad75d5570/apps/console", + "modulesRoot": ".cache/objectui-ab1879721595/apps/console", "objectuiWorkspaceVersion": "17.6.0", "generator": "scripts/gen-sdui-manifest-node.mjs", - "generatedAt": "2026-10-02", - "sha256": "45341de35b91c4e624ecceeb244cce4a72c3571ccda6f2b93fd81e02faf4f5de", + "generatedAt": "2026-10-03", + "sha256": "0ead67c1111d73e6f48424510a002a626edd75d7dc5617d663bd022434106458", "components": 107 } diff --git a/sdui.manifest.json b/sdui.manifest.json index 40fe00fe0d0..3d83b85382e 100644 --- a/sdui.manifest.json +++ b/sdui.manifest.json @@ -27,6 +27,14 @@ ], "description": "Grid label, used as the table caption, as the export file title and in the record-detail overlay heading. The canonical spelling — the deprecated `title` is only read when this is absent. Accepts either a plain string or an inline per-locale map (`{ en: \"Accounts\", \"zh-CN\": \"客户\" }`) — the `I18nLabel` union the contract admits on this key — and the grid resolves the map against the display locale (the workspace's regional default when one is configured, otherwise the active UI language), falling back through base language, a region-qualified sibling, `default`, then `en`, and finally to any remaining entry." }, + { + "name": "description", + "type": [ + "string", + "object" + ], + "description": "One line of help text drawn above the grid's rows, in muted type. Accepts either a plain string or an inline per-locale map (`{ en: \"Everyone you work with\", \"zh-CN\": \"你的所有联系人\" }`) — the `I18nLabel` union the contract admits on this key — resolved against the display locale the way `label` is. A map with no usable entry draws no line." + }, { "name": "sort", "type": "array", @@ -88,7 +96,12 @@ { "name": "conditionalFormatting", "type": "array", - "description": "Row/cell styling rules. Accepts both the ObjectUI `{ field, operator, value }` form and the spec expression form `{ condition, style }`." + "description": "Row style rules, each `{ condition, style }` — a CEL `condition` over the row’s own `record.*` and a CSS `style` map, the rule a list view declares. The first matching rule styles that row. The native `{ field, operator, value }` rule, its `expression`, and a colour written beside `condition` instead of inside `style` are retired (objectui#11533)." + }, + { + "name": "emptyState", + "type": "object", + "description": "What the grid draws in place of an empty table, `{ title, message, icon }`: a Lucide `icon` name, a `title` (default: the table's own \"No results found\") and a `message` (default: none). `title` and `message` each accept a plain string or an inline per-locale map, resolved against the display locale. Not drawn when a term typed into the grid's own server-side search box is what emptied it — the table and its search box stay, so the term can be cleared. Leave it out and an empty grid draws the table's own empty row." }, { "name": "grouping", @@ -135,6 +148,11 @@ "type": "boolean", "description": "With `editable`, a single click opens the cell instead of a double-click. Has no effect on a non-editable grid." }, + { + "name": "keyboardNavigation", + "type": "boolean", + "description": "Arrow-key cell navigation on the WAI-ARIA grid pattern: the grid's data cells take one place in the Tab order instead of one each (a link or button inside a cell keeps its own), and the arrow keys move focus between them (Home / End to the ends of the row, Ctrl+Home / Ctrl+End to the ends of the page). Enter still opens an editable cell, and an edit ended with Enter or Escape returns focus to its cell. Defaults to on when the grid renders editable; a read-only grid keeps every cell its own Tab stop unless this is `true`, and `false` turns it off on an editable grid." + }, { "name": "navigation", "type": "object", @@ -245,7 +263,7 @@ { "name": "fields", "type": "array", - "description": "Bare field names to show, in order (each looked up in the object schema; `{ name }` is tolerated). NOT the same vocabulary as `sections[].fields`, which also accepts the spec `FormFieldSchema` object (identity key `field`, e.g. `{ field: \"note\", colSpan: 2 }`) — that shape resolves to no name HERE and is silently skipped (SimpleObjectForm in ObjectForm.tsx; buildFlatFields in flatFields.ts for the drawer/modal presentations)." + "description": "Bare field names to show, in order (each looked up in the object schema). NOT the same vocabulary as `sections[].fields`, which also accepts the spec `FormFieldSchema` object (identity key `field`, e.g. `{ field: \"note\", colSpan: 2 }`) — that shape resolves to no name HERE and is silently skipped (SimpleObjectForm in ObjectForm.tsx; buildFlatFields in flatFields.ts for the drawer/modal presentations)." }, { "name": "mode", @@ -513,7 +531,7 @@ { "name": "fields", "type": "array", - "description": "Bare field names to show, in order (each looked up in the object schema; `{ name }` is tolerated). NOT the same vocabulary as `sections[].fields`, which also accepts the spec `FormFieldSchema` object (identity key `field`, e.g. `{ field: \"note\", colSpan: 2 }`) — that shape resolves to no name HERE and is silently skipped (`EmbeddableForm` passes this array straight through to `` with no `sections`, so it renders through the same `SimpleObjectForm` as `object-form` above — see its `fields` description)." + "description": "Bare field names to show, in order (each looked up in the object schema). NOT the same vocabulary as `sections[].fields`, which also accepts the spec `FormFieldSchema` object (identity key `field`, e.g. `{ field: \"note\", colSpan: 2 }`) — that shape resolves to no name HERE and is silently skipped (`EmbeddableForm` passes this array straight through to `` with no `sections`, so it renders through the same `SimpleObjectForm` as `object-form` above — see its `fields` description)." }, { "name": "allowMultiple", @@ -570,7 +588,7 @@ { "name": "fields", "type": "array", - "description": "Which parent fields to show, in order — and it is NOT ignored when `sections` is given: the two INTERSECT. The parent field pool is built from this key first and every section then resolves its own members against that pool, so a section member this key does not list is dropped from the rendered form, and a section that loses EVERY member that way disappears with its heading. Each such drop is reported once via `console.warn` (objectui#9884); it is not repaired, because this key bounds what the form DRAWS and edits, not what Save writes: on a create, the submitted set is the drawn fields plus any parent value seeded through `initialValues` (or its alternate spelling `initialData`), drawn or not. A seed for an undeclared, server-owned, computed or read-only field is still stripped, as on any save. Author one or the other, or list every section member here too. Members are bare field names (`{ name }` tolerated); NOT the spec `FormFieldSchema` object `sections[].fields` accepts (identity key `field`) — that shape resolves to no name here and is silently skipped (the parent form renders through the same `ObjectForm` / `SimpleObjectForm` as `object-form` — see its `fields` description)." + "description": "Which parent fields to show, in order — and it is NOT ignored when `sections` is given: the two INTERSECT. The parent field pool is built from this key first and every section then resolves its own members against that pool, so a section member this key does not list is dropped from the rendered form, and a section that loses EVERY member that way disappears with its heading. Each such drop is reported once via `console.warn` (objectui#9884); it is not repaired, because this key bounds what the form DRAWS and edits, not what Save writes: on a create, the submitted set is the drawn fields plus any parent value seeded through `initialValues` (or its alternate spelling `initialData`), drawn or not. A seed for an undeclared, server-owned, computed or read-only field is still stripped, as on any save. Author one or the other, or list every section member here too. Members are bare field names; NOT the spec `FormFieldSchema` object `sections[].fields` accepts (identity key `field`) — that shape resolves to no name here and is silently skipped (the parent form renders through the same `ObjectForm` / `SimpleObjectForm` as `object-form` — see its `fields` description)." }, { "name": "title", @@ -689,7 +707,7 @@ { "name": "conditionalFormatting", "type": "array", - "description": "Per-card style rules, each evaluated against that card’s own record. Two member dialects are accepted: the native `{ field, operator, value, backgroundColor?, borderColor? }` and the spec CEL `{ condition, backgroundColor?, borderColor? }`. A matching rule colours that card alone. A rule comparing a relation field sees the stored foreign key rather than the expanded record." + "description": "Per-card style rules, each `{ condition, style }` — a CEL `condition` over the card’s own `record.*` and a CSS `style` map, the rule a list view declares. The first matching rule styles that card alone. A rule comparing a relation field sees the stored foreign key rather than the expanded record. The native `{ field, operator, value }` rule and a colour written beside `condition` instead of inside `style` are retired and refused by name (objectui#11522)." }, { "name": "navigation", @@ -1350,6 +1368,11 @@ "name": "format", "type": "string" }, + { + "name": "drillDown", + "type": "object", + "description": "Click-through config that opens the records behind a clicked cell, header or total — in a drawer, a dialog, the object’s list page (`target: \"navigate\"`), or an analytical report (`report`). The drilled list is this block’s `filter` narrowed by the clicked value. `drillDown.mode` does not apply: every click point on a pivot is an aggregated bucket, so it always drills through; `mode` belongs on `object-data-table`." + }, { "name": "dataSource", "type": "object", @@ -1594,6 +1617,58 @@ "name": "amountField", "type": "string" }, + { + "name": "parentObject", + "type": "string", + "description": "The PARENT object the line total is written to on Save, together with `totalField`. Defaults to the object of the record the page shows, and outranks it when set. With neither, Save writes the lines alone and no parent field." + }, + { + "name": "parentId", + "type": "string", + "description": "The parent record whose lines the panel lists and saves: it loads `childObject` rows whose `relationshipField` equals this id, and a line added in the grid is saved with this id in `relationshipField`. Outranks `recordId` and the record the page shows; leave it unset on a record page, where the record shown is the parent. With no parent id at all the panel asks the user to save the record first and loads nothing." + }, + { + "name": "recordId", + "type": "string", + "description": "The parent record id, read only when `parentId` is unset. It outranks the record the page shows." + }, + { + "name": "title", + "type": "string", + "description": "Heading of the panel. Defaults to \"Line Items\" in the active UI language. A plain string, drawn as authored." + }, + { + "name": "readonly", + "type": "boolean", + "description": "Shows the lines without editing: no Save button, no Add line / Duplicate row / Remove row actions, and the grid draws a read-only table." + }, + { + "name": "minRows", + "type": "number", + "description": "Fewest lines the user can leave: every line's Remove row action is disabled while the grid holds this many lines or fewer. It adds no blank lines to reach the number." + }, + { + "name": "maxRows", + "type": "number", + "description": "Most lines the user can hold: at this many lines Add line and Duplicate row are disabled and the trailing blank entry row is not drawn." + }, + { + "name": "filter", + "type": "array", + "of": "object", + "description": "Additional criteria for the lines, as spec `ViewFilterRule` entries (`[{ field, operator, value }]`). AND-combined with the parent relationship condition, never a replacement for it: it can only narrow this record's lines. Also the key a per-element `dataSource` binding's composed filter lands on." + }, + { + "name": "sort", + "type": "array", + "of": "object", + "description": "Load order for the lines, as `[{ field, order }]` entries applied in list order; `order` is `asc` or `desc`. Without it the lines arrive in storage order." + }, + { + "name": "limit", + "type": "number", + "description": "Most lines loaded (default 500). The grid has no pagination: every loaded line is editable and saved as one batch, and lines past the cap are not loaded. Must be a positive integer: any other value is ignored, with a console warning." + }, { "name": "dataSource", "type": "object", @@ -2348,7 +2423,7 @@ { "name": "successMessage", "type": "string", - "description": "Toast text when the action succeeds (a message the server returns takes precedence)" + "description": "Toast text when the action succeeds; a `${result.*}` token reads the handler's answer (e.g. `${result.id}`)" }, { "name": "errorMessage", @@ -2647,7 +2722,7 @@ { "name": "successMessage", "type": "string", - "description": "Toast text when the action succeeds (a message the server returns takes precedence)" + "description": "Toast text when the action succeeds; a `${result.*}` token reads the handler's answer (e.g. `${result.id}`)" }, { "name": "errorMessage", @@ -2727,8 +2802,46 @@ }, { "name": "gap", - "type": "number", - "description": "Gap between items (0-8)" + "type": "enum", + "enum": [ + { + "label": "0 (none)", + "value": 0 + }, + { + "label": "1", + "value": 1 + }, + { + "label": "2", + "value": 2 + }, + { + "label": "3", + "value": 3 + }, + { + "label": "4", + "value": 4 + }, + { + "label": "5", + "value": 5 + }, + { + "label": "6", + "value": 6 + }, + { + "label": "7", + "value": 7 + }, + { + "label": "8", + "value": 8 + } + ], + "description": "Gap step between items; 0 is none. Default 2." }, { "name": "wrap", @@ -2752,33 +2865,109 @@ "inputs": [ { "name": "columns", - "type": "number", - "description": "Number of columns on mobile devices" - }, - { - "name": "smColumns", - "type": "number", - "description": "Columns at sm breakpoint (>640px)" - }, - { - "name": "mdColumns", - "type": "number", - "description": "Columns at md breakpoint (>768px)" - }, - { - "name": "lgColumns", - "type": "number", - "description": "Columns at lg breakpoint (>1024px)" - }, - { - "name": "xlColumns", - "type": "number", - "description": "Columns at xl breakpoint (>1280px)" + "type": [ + "enum", + "object" + ], + "of": "enum", + "enum": [ + { + "label": "1", + "value": 1 + }, + { + "label": "2", + "value": 2 + }, + { + "label": "3", + "value": 3 + }, + { + "label": "4", + "value": 4 + }, + { + "label": "5", + "value": 5 + }, + { + "label": "6", + "value": 6 + }, + { + "label": "7", + "value": 7 + }, + { + "label": "8", + "value": 8 + }, + { + "label": "9", + "value": 9 + }, + { + "label": "10", + "value": 10 + }, + { + "label": "11", + "value": 11 + }, + { + "label": "12", + "value": 12 + } + ], + "description": "Column count, 1 to 12, or an object of such counts keyed by breakpoint (xs, sm, md, lg, xl, 2xl). Default 2." }, { "name": "gap", - "type": "number", - "description": "Gap between items (0-12)" + "type": "enum", + "enum": [ + { + "label": "0 (none)", + "value": 0 + }, + { + "label": "1", + "value": 1 + }, + { + "label": "2", + "value": 2 + }, + { + "label": "3", + "value": 3 + }, + { + "label": "4", + "value": 4 + }, + { + "label": "5", + "value": 5 + }, + { + "label": "6", + "value": 6 + }, + { + "label": "8", + "value": 8 + }, + { + "label": "10", + "value": 10 + }, + { + "label": "12", + "value": 12 + } + ], + "description": "Gap step between items; 0 is none. Default 4." }, { "name": "className", @@ -2807,7 +2996,46 @@ }, { "name": "gap", - "type": "number" + "type": "enum", + "enum": [ + { + "label": "0 (none)", + "value": 0 + }, + { + "label": "1", + "value": 1 + }, + { + "label": "2", + "value": 2 + }, + { + "label": "3", + "value": 3 + }, + { + "label": "4", + "value": 4 + }, + { + "label": "5", + "value": 5 + }, + { + "label": "6", + "value": 6 + }, + { + "label": "8", + "value": 8 + }, + { + "label": "10", + "value": 10 + } + ], + "description": "Gap step between items; 0 is none. Default 2." }, { "name": "align", @@ -2976,8 +3204,58 @@ }, { "name": "padding", - "type": "number", - "description": "Padding value (0, 1-8, 10, 12, 16)" + "type": "enum", + "enum": [ + { + "label": "0 (none)", + "value": 0 + }, + { + "label": "1", + "value": 1 + }, + { + "label": "2", + "value": 2 + }, + { + "label": "3", + "value": 3 + }, + { + "label": "4", + "value": 4 + }, + { + "label": "5", + "value": 5 + }, + { + "label": "6", + "value": 6 + }, + { + "label": "7", + "value": 7 + }, + { + "label": "8", + "value": 8 + }, + { + "label": "10", + "value": 10 + }, + { + "label": "12", + "value": 12 + }, + { + "label": "16", + "value": 16 + } + ], + "description": "Padding step on the container spacing scale; 0 is none. Default 4." }, { "name": "centered", @@ -3034,11 +3312,6 @@ "of": "string", "description": "Action IDS — the names of actions declared on the object's own metadata — rendered in the header before any host-injected system actions. An id whose action declares neither record_header nor record_more in its locations renders nowhere." }, - { - "name": "breadcrumb", - "type": "boolean", - "description": "Ignored: the header draws no breadcrumb, and the key is being retired from the contract. Leave it out." - }, { "name": "recordChrome", "type": "boolean", From fe5a5633cacfac865c618984fae67a845313d6e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 18:21:20 +0000 Subject: [PATCH 04/10] chore(spec): re-measure the remaining objectui pin citations at ab1879721 (wip) Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w Co-authored-by: Claude --- .../api-methods-batch-conformance.test.ts | 21 +++++++-- .../src/kernel/functional-completeness.ts | 37 +++++++++++++--- ...ck-and-hot-reload-durations-unit-in-key.ts | 6 +-- ...rnel-runtime-config-timeout-unit-in-key.ts | 6 +-- .../18.logging-durations-unit-in-key.ts | 6 +-- ...tem-metrics-jsdoc-durations-unit-in-key.ts | 9 ++-- ...ing-otel-exporter-durations-unit-in-key.ts | 11 ++--- .../18.tenant-schema-cache-ttl-unit-in-key.ts | 6 +-- packages/spec/src/migrations/registry.ts | 44 ++++++++++--------- packages/spec/src/ui/component.zod.ts | 41 ++++++++++++++--- packages/spec/src/ui/dataset.zod.ts | 27 ++++++++---- packages/spec/src/ui/view.zod.ts | 34 +++++++++++--- 12 files changed, 175 insertions(+), 73 deletions(-) diff --git a/packages/spec/src/data/api-methods-batch-conformance.test.ts b/packages/spec/src/data/api-methods-batch-conformance.test.ts index 8d4983771ac..2d3aca11af8 100644 --- a/packages/spec/src/data/api-methods-batch-conformance.test.ts +++ b/packages/spec/src/data/api-methods-batch-conformance.test.ts @@ -61,13 +61,26 @@ const SINGLE_RECORD_WRITE_ONLY: Record = { // `revoked` on ONE key. The multi-select surface this rule protects does not // exist for API keys, and the shape a future one would take does not need // `bulk` either — both read off the console build this release pins - // (`.objectui-sha` = `89cad75d5`, `packages/plugin-grid`; re-measured at - // that pin, 2026-10-02. On the hop off `31971ff1e`, `ObjectGrid.tsx` changed + // (`.objectui-sha` = `ab1879721`, `packages/plugin-grid`; re-measured at + // that pin, 2026-10-03. On the hop off `89cad75d5`, `ObjectGrid.tsx` changed + // again (74 insertions, 11 deletions: objectui#11544's group-header labels + // read from the object field's `options` only, objectui#11475's percent + // storage `max` copied onto the cell's field meta on three paths and one + // import edited in place, above the selection block; objectui#11068's + // `keyboardNavigation`, objectui#11475's mobile-card percent face and + // objectui#11227's resolved empty-state labels, below it), none of it inside + // the selection block, which only MOVED by +19: `ObjectGrid.tsx:4810-4837` + // here (`4791-4818` at `89cad75d5`), still hashing to + // `c88443302d40c2db739ddb235470bafa29056e2e` (hash-object of the block), + // re-READ with the same reading below; `hooks/useBulkExecutor.ts` is + // byte-identical to `89cad75d5` and its `:298-303` still hashes to + // `01083348330f10a201cdf1078b4c21c236402b6a`. At `89cad75d5`, 2026-10-02: + // on the hop off `31971ff1e`, `ObjectGrid.tsx` changed // again (42 insertions, 17 deletions: objectui#11322's built-in bulk-delete // gate asking `partitionRowsByPredicate` directly and objectui#11254's // mobile-card percent width, plus their two import lines, each edited in // place), every hunk either a same-length import edit or below the - // selection block, which did NOT move: `ObjectGrid.tsx:4791-4818` here as at + // selection block, which did NOT move: `ObjectGrid.tsx:4791-4818` there as at // `31971ff1e`, still hashing to `c88443302d40c2db739ddb235470bafa29056e2e` // (hash-object of the block), re-READ with the same reading below; // `hooks/useBulkExecutor.ts` is byte-identical to `31971ff1e` and its @@ -101,7 +114,7 @@ const SINGLE_RECORD_WRITE_ONLY: Record = { // re-READ with the same reading below; `hooks/useBulkExecutor.ts` is // byte-identical to `dd3f7e1be` and its `:298-303` still hashes to // `01083348330f10a201cdf1078b4c21c236402b6a`. Previously measured at - // `31971ff1e`, `e420df310`, `db11afd49`, `dd3f7e1be`, `f8a9d0fb0`, `62597c588`, + // `89cad75d5`, `31971ff1e`, `e420df310`, `db11afd49`, `dd3f7e1be`, `f8a9d0fb0`, `62597c588`, // `87af769e9`, `53ded82bf`, `a472b0716`, `00d3f09c5`, `67dadd602`, before // that at `d8ec8d6d4`, `9602dc820`, `190fbd01d`, `9a3daf8d3`, originally at // `6314e87f2`. On the hop off `f8a9d0fb0`, `ObjectGrid.tsx` changed hard diff --git a/packages/spec/src/kernel/functional-completeness.ts b/packages/spec/src/kernel/functional-completeness.ts index 788af993882..626a2825633 100644 --- a/packages/spec/src/kernel/functional-completeness.ts +++ b/packages/spec/src/kernel/functional-completeness.ts @@ -287,7 +287,17 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * English sentence. So the loss is total rather than partial — every * record, on every object — and the author is told at render time as well * as here. Both reads hold at the pin this repo builds against - * (`.objectui-sha` = `89cad75d5`, re-read 2026-10-02: on the hop off + * (`.objectui-sha` = `ab1879721`, re-read 2026-10-03: on the hop off + * `89cad75d5` `ObjectCalendar.tsx` changed in one comment line only, line + * for line (`:1250`, objectui#11438 re-citing the installed spec as + * 17.6.0), so `getCalendarConfig` `:294`, the `if (!calendarConfig)` arm + * `:1296` and its `tt(…)` lines `:1303-1304` did not move and are + * byte-identical; `ListView.tsx` changed (objectui#10813's `isempty` / + * `isnotempty` filter operators, objectui#11445's record-count family, + * objectui#11227's resolved empty-state labels), every `case` arm + * byte-identical and moved by 14; and the `en` / `zh` / `de` + * `calendar.configRequired` strings did not change. At `89cad75d5` + * (2026-10-02), on the hop off * `31971ff1e` `ObjectCalendar.tsx` and `ListView.tsx` are byte-identical, * so `getCalendarConfig` `:294`, the `if (!calendarConfig)` arm `:1296` and * its `tt(…)` lines `:1303-1304` did not move, every `case` arm is @@ -334,8 +344,14 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * This repo already records the same deletion one door over: the #13817 * check in `../ui/view.zod.ts` names objectui#7029 as its runtime half. * - `gantt` → NO fallback, and no silence [#19630]. Measured at the pin - * this repo builds against (`.objectui-sha` = `89cad75d5`, re-read - * 2026-10-02; on the hop off `31971ff1e` `ObjectGantt.tsx` changed (47 + * this repo builds against (`.objectui-sha` = `ab1879721`, re-read + * 2026-10-03; on the hop off `89cad75d5` `ObjectGantt.tsx` changed (8 + * insertions, 0 deletions: one import line and objectui#11475's storage + * argument on the tooltip's `percent` row), none of it in `getGanttConfig` + * or the refusal arm, which are byte-identical and moved `:608` → `:609` + * and `:2298` → `:2306`, and `ListView.tsx`'s `case 'gantt'` is + * byte-identical, moved by 14; at `89cad75d5` (2026-10-02), on the hop off + * `31971ff1e` `ObjectGantt.tsx` changed (47 * insertions, 10 deletions: objectui#11254's field-scale width on the * number and percent rows, objectui#11355's uncast shadowed-flat-key read, * one import line), none of it in `getGanttConfig` or the refusal arm, which @@ -382,7 +398,12 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * requires. So the view does not draw a blank chart: it refuses, by name. * - `timeline` → date axis: NO fallback [#19630]; title: `titleField || 'name'`, * which still stands. Measured at the same pin (`.objectui-sha` = - * `89cad75d5`; on the hop off `31971ff1e` `ListView.tsx` is byte-identical, + * `ab1879721`; on the hop off `89cad75d5` `ObjectTimeline.tsx` is + * byte-identical, so the start-date chain `:574-576` and the refusal arm + * `:911` did not move, and `ListView.tsx`'s `case 'timeline'` and + * `resolveTimelineDateBinding` are byte-identical, moved by 14 and by 9 + * (objectui#10813's filter operators above them). At `89cad75d5`, on the + * hop off `31971ff1e` `ListView.tsx` is byte-identical, * `case 'timeline'` and `resolveTimelineDateBinding` with it, and * `ObjectTimeline.tsx` changed only in two `navigation` comments * (objectui#11293 / objectui#11168 slice 5, 16 insertions, 7 deletions: @@ -423,8 +444,12 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * block does render; the warning still fires there, because the block the * view TYPE names is the one that is missing. Unchanged by this row. * - `map` → NO fallback, and no silence [#19630]. Measured at the same - * pin (`.objectui-sha` = `89cad75d5`; on the hop off `31971ff1e` - * `ListView.tsx` is byte-identical, `case 'map'` and `resolveListMapConfig` + * pin (`.objectui-sha` = `ab1879721`; on the hop off `89cad75d5` + * `ObjectMap.tsx` is byte-identical, so `getMapConfig` `:387`, + * `hasCoordinateBinding` `:493` and the refusal arm `:1291` did not move, + * and `ListView.tsx`'s `case 'map'` is byte-identical, moved by 14, with + * `resolveListMapConfig` unmoved at `:146`. At `89cad75d5`, on the hop off + * `31971ff1e` `ListView.tsx` is byte-identical, `case 'map'` and `resolveListMapConfig` * with it, and `ObjectMap.tsx` changed (11 insertions, 4 deletions) in * `warnOnShadowedFlatMapKeys`' cast (objectui#11355) and in `getMapConfig`'s * `style` precedence — `mapStyle` before `map.style` on the declared-block diff --git a/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts index 3c4f1d49635..fdc2f388168 100644 --- a/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts @@ -52,13 +52,13 @@ export const entry: SemanticMigration = { + 'the audience that does not parse. Measured on 884e8347d: the only in-repo readers are ' + 'packages/core/src/health-monitor.ts and packages/core/src/hot-reload.ts, both moved in ' + 'this same change; and the pinned objectui checkout — the pin this repo builds ' - + 'against, `.objectui-sha` = `89cad75d55702cc4f267bead5bf267de575d5842` — names ' + + 'against, `.objectui-sha` = `ab187972159583b595facdcae3c73b50f6f312e9` — names ' + 'neither def and neither key: all thirteen exports of plugin-lifecycle-advanced.zod.ts and ' - + 'the string debounceDelay each occur 0 times across its 10071 tracked files (0 across the 9912 at 31971ff1e, the 9800 at e420df310, the 9546 at db11afd49, the 9283 at dd3f7e1be, the ' + + 'the string debounceDelay each occur 0 times across its 10267 tracked files (0 across the 10071 at 89cad75d5, the 9912 at 31971ff1e, the 9800 at e420df310, the 9546 at db11afd49, the 9283 at dd3f7e1be, the ' + '8512 at f8a9d0fb0 and the 8303 at 62597c588 too), against lit ' + 'controls objectstack 12966 and @objectstack/spec 4997 on the same corpus at 87af769e9, ' + 'which re-count to 13125 and 5043 respectively at 62597c588, to 13347 and 5123 at ' - + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be, to 14704 and 5545 at db11afd49, to 15352 and 6024 at e420df310, to 15691 and 6206 at 31971ff1e and to 16044 and 6461 at this pin (git grep -o -F, the method that reproduces ' + + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be, to 14704 and 5545 at db11afd49, to 15352 and 6024 at e420df310, to 15691 and 6206 at 31971ff1e, to 16044 and 6461 at 89cad75d5 and to 16377 and 6665 at this pin (git grep -o -F, the method that reproduces ' + 'every earlier count).', acceptanceCriteria: 'Every producer and reader of a PluginHealthCheck spells intervalMs and timeoutMs, and every ' diff --git a/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts index 5a85a1dce15..401b8853500 100644 --- a/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts @@ -46,10 +46,10 @@ export const entry: SemanticMigration = { + 'spells timeout 0 times; outside the zod file and its test the only live occurrences are the ' + 'generated rows in content/docs/references/kernel/plugin-security-advanced.mdx, which this ' + 'rename regenerates. The pinned objectui checkout — this is the pin we build against, ' - + '`.objectui-sha` = `89cad75d55702cc4f267bead5bf267de575d5842`, re-read from this tree — ' + + '`.objectui-sha` = `ab187972159583b595facdcae3c73b50f6f312e9`, re-read from this tree — ' + 'spells resourceLimits.timeout 0 times across ' - + '10071 tracked files, against lit controls timeout 1331, RuntimeConfig 273 and resourceLimits ' - + '2 on the same corpus (0 across 9912, and 1303 / 273 / 2, at 31971ff1e; 0 across 9800, and 1293 / 273 / 2, at e420df310; 0 across 9546, and 1197 / 273 / 2, at db11afd49; 0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + + '10267 tracked files, against lit controls timeout 1348, RuntimeConfig 273 and resourceLimits ' + + '2 on the same corpus (0 across 10071, and 1331 / 273 / 2, at 89cad75d5; 0 across 9912, and 1303 / 273 / 2, at 31971ff1e; 0 across 9800, and 1293 / 273 / 2, at e420df310; 0 across 9546, and 1197 / 273 / 2, at db11afd49; 0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + '1086 / 240 / 2, at 62597c588); both resourceLimits hits are prose in packages/app-shell recording ' + 'that objectui\'s own AppShellRuntimeConfig shares not one key with the spec\'s ' + 'RuntimeConfig, so nothing there authors this key and no pin bump is owed. ADR-0087.', diff --git a/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts index 1941e15c83b..644fddbec7b 100644 --- a/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts @@ -47,10 +47,10 @@ export const entry: SemanticMigration = { + 'no in-repo runtime reads any of the four — outside `packages/spec/src/system/logging.zod.ts` ' + 'and its test the only occurrences are the generated rows in ' + '`content/docs/references/system/logging.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `89cad75d55702cc4f267bead5bf267de575d5842` — spells ' + + 'objectui checkout — `.objectui-sha` = `ab187972159583b595facdcae3c73b50f6f312e9` — spells ' + '`flushInterval` 0 times, `initialDelay` 0, `HttpDestinationConfig` 0 and `LoggingConfig` 0 ' - + 'across its 10071 tracked files, against lit controls `useState` 2470 and `timeout` 1331 on ' - + 'the same corpus (all four 0 across 9912, against 2469 and 1303, at 31971ff1e, 0 across 9800, against 2464 and 1293, at e420df310, 0 across 9546, against 2449 and 1197, at db11afd49, 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + + 'across its 10267 tracked files, against lit controls `useState` 2476 and `timeout` 1348 on ' + + 'the same corpus (all four 0 across 10071, against 2470 and 1331, at 89cad75d5, 0 across 9912, against 2469 and 1303, at 31971ff1e, 0 across 9800, against 2464 and 1293, at e420df310, 0 across 9546, against 2449 and 1197, at db11afd49, 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + 'against 2391 and 1096, at f8a9d0fb0, and 0 across ' + '8303, against 2389 and 1086, at 62597c588).', acceptanceCriteria: diff --git a/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts index a236d052fd6..d7beded6b88 100644 --- a/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts @@ -73,10 +73,11 @@ export const entry: SemanticMigration = { + 'against a lit control of 1195 defineStack occurrences on that same corpus at fc28c1d38 ' + '(1195 again at 9b62f54671); and the objectui ' + 'checkout this repo builds against — this is the pin, ' - + '`.objectui-sha` = `89cad75d55702cc4f267bead5bf267de575d5842`, re-read from this tree — ' - + 'spells all six metrics def names and both distinctive keys 0 times across 10071 tracked ' - + 'files at that sha, against lit controls window 4002, timeout 1331, period 231, ' - + 'interval 196 and metrics 355 on that same corpus and sha (0 across 9912, against 3916 / ' + + '`.objectui-sha` = `ab187972159583b595facdcae3c73b50f6f312e9`, re-read from this tree — ' + + 'spells all six metrics def names and both distinctive keys 0 times across 10267 tracked ' + + 'files at that sha, against lit controls window 4044, timeout 1348, period 231, ' + + 'interval 196 and metrics 354 on that same corpus and sha (0 across 10071, against 4002 / ' + + '1331 / 231 / 196 / 355, at 89cad75d5, 0 across 9912, against 3916 / ' + '1303 / 234 / 196 / 354, at 31971ff1e, 0 across 9800, against 3873 / ' + '1293 / 233 / 196 / 352, at e420df310, 0 across 9546, against 3772 / ' + '1197 / 228 / 196 / 341, at db11afd49, 0 across 9283, against 3681 / 1172 / 183 / 176 / 340, at dd3f7e1be, 0 across 8512, against 3581 / ' diff --git a/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts index 6475b66dd73..58eb991ac8c 100644 --- a/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts @@ -69,12 +69,13 @@ export const entry: SemanticMigration = { + 'dark control of 0; inside packages/spec the ' + 'only occurrences are tracing.zod.ts, its test, and the generated rows in ' + 'content/docs/references/system/tracing.mdx, which this rename regenerates. And the ' - + 'pinned objectui checkout — `.objectui-sha` = `89cad75d55702cc4f267bead5bf267de575d5842` — names none of it: all 37 exports of ' - + 'tracing.zod.ts and each of the four key names occur 0 times across the 10071 files ' - + 'tracked at that sha (the 489 Span and 56 SpanSchema hits are objectui\'s own HTML ' + + 'pinned objectui checkout — `.objectui-sha` = `ab187972159583b595facdcae3c73b50f6f312e9` — names none of it: all 37 exports of ' + + 'tracing.zod.ts and each of the four key names occur 0 times across the 10267 files ' + + 'tracked at that sha (the 491 Span and 56 SpanSchema hits are objectui\'s own HTML ' + 'text-span component, TextSpanSchema, an unrelated name, plus colSpan and prose), against ' - + 'two lit controls on that same corpus and sha: 16044 hits for the bare token objectstack, ' - + 'and 6461 for the package specifier @objectstack/spec (at 31971ff1e: 0 across 9912, Span 486, ' + + 'two lit controls on that same corpus and sha: 16377 hits for the bare token objectstack, ' + + 'and 6665 for the package specifier @objectstack/spec (at 89cad75d5: 0 across 10071, Span 489, ' + + '16044 and 6461; at 31971ff1e: 0 across 9912, Span 486, ' + '15691 and 6206; at e420df310: 0 across 9800, Span 486, ' + '15352 and 6024; at db11afd49: 0 across 9546, Span 486, ' + '14704 and 5545; at dd3f7e1be: 0 across 9283, Span 485, 13745 and 5466; at f8a9d0fb0: 0 across 8512, ' diff --git a/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts index f3bb632fd4a..6570ecafba4 100644 --- a/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts @@ -31,9 +31,9 @@ export const entry: SemanticMigration = { + 'bd25e897dc: no in-repo runtime reads the key — outside `packages/spec/src/system/tenant.zod.ts` ' + 'and its test the only occurrences are the four generated rows in ' + '`content/docs/references/system/tenant.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `89cad75d55702cc4f267bead5bf267de575d5842` — spells it 0 ' - + 'times across 10071 tracked files, against lit controls `TTL` 181 and `tenant` 1237 on the ' - + 'same corpus (0 across 9912, against 181 and 1237, at 31971ff1e; 0 across 9800, against 181 and 1235, at e420df310; 0 across 9546, against 181 and 1200, at db11afd49; 0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + + 'objectui checkout — `.objectui-sha` = `ab187972159583b595facdcae3c73b50f6f312e9` — spells it 0 ' + + 'times across 10267 tracked files, against lit controls `TTL` 180 and `tenant` 1238 on the ' + + 'same corpus (0 across 10071, against 181 and 1237, at 89cad75d5; 0 across 9912, against 181 and 1237, at 31971ff1e; 0 across 9800, against 181 and 1235, at e420df310; 0 across 9546, against 181 and 1200, at db11afd49; 0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + 'and 987, at 62597c588).', acceptanceCriteria: 'Every schema-level tenant isolation source spells `performance.schemaCacheTtlSeconds`; ' diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index 2aa79d5154c..a7e8472e87b 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -13883,13 +13883,13 @@ const step18: MigrationStep = { + 'the audience that does not parse. Measured on 884e8347d: the only in-repo readers are ' + 'packages/core/src/health-monitor.ts and packages/core/src/hot-reload.ts, both moved in ' + 'this same change; and the pinned objectui checkout — the pin this repo builds ' - + 'against, `.objectui-sha` = `89cad75d55702cc4f267bead5bf267de575d5842` — names ' + + 'against, `.objectui-sha` = `ab187972159583b595facdcae3c73b50f6f312e9` — names ' + 'neither def and neither key: all thirteen exports of plugin-lifecycle-advanced.zod.ts and ' - + 'the string debounceDelay each occur 0 times across its 10071 tracked files (0 across the 9912 at 31971ff1e, the 9800 at e420df310, the 9546 at db11afd49, the 9283 at dd3f7e1be, the ' + + 'the string debounceDelay each occur 0 times across its 10267 tracked files (0 across the 10071 at 89cad75d5, the 9912 at 31971ff1e, the 9800 at e420df310, the 9546 at db11afd49, the 9283 at dd3f7e1be, the ' + '8512 at f8a9d0fb0 and the 8303 at 62597c588 too), against lit ' + 'controls objectstack 12966 and @objectstack/spec 4997 on the same corpus at 87af769e9, ' + 'which re-count to 13125 and 5043 respectively at 62597c588, to 13347 and 5123 at ' - + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be, to 14704 and 5545 at db11afd49, to 15352 and 6024 at e420df310, to 15691 and 6206 at 31971ff1e and to 16044 and 6461 at this pin (git grep -o -F, the method that reproduces ' + + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be, to 14704 and 5545 at db11afd49, to 15352 and 6024 at e420df310, to 15691 and 6206 at 31971ff1e, to 16044 and 6461 at 89cad75d5 and to 16377 and 6665 at this pin (git grep -o -F, the method that reproduces ' + 'every earlier count).', acceptanceCriteria: 'Every producer and reader of a PluginHealthCheck spells intervalMs and timeoutMs, and every ' @@ -14094,10 +14094,10 @@ const step18: MigrationStep = { + 'spells timeout 0 times; outside the zod file and its test the only live occurrences are the ' + 'generated rows in content/docs/references/kernel/plugin-security-advanced.mdx, which this ' + 'rename regenerates. The pinned objectui checkout — this is the pin we build against, ' - + '`.objectui-sha` = `89cad75d55702cc4f267bead5bf267de575d5842`, re-read from this tree — ' + + '`.objectui-sha` = `ab187972159583b595facdcae3c73b50f6f312e9`, re-read from this tree — ' + 'spells resourceLimits.timeout 0 times across ' - + '10071 tracked files, against lit controls timeout 1331, RuntimeConfig 273 and resourceLimits ' - + '2 on the same corpus (0 across 9912, and 1303 / 273 / 2, at 31971ff1e; 0 across 9800, and 1293 / 273 / 2, at e420df310; 0 across 9546, and 1197 / 273 / 2, at db11afd49; 0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + + '10267 tracked files, against lit controls timeout 1348, RuntimeConfig 273 and resourceLimits ' + + '2 on the same corpus (0 across 10071, and 1331 / 273 / 2, at 89cad75d5; 0 across 9912, and 1303 / 273 / 2, at 31971ff1e; 0 across 9800, and 1293 / 273 / 2, at e420df310; 0 across 9546, and 1197 / 273 / 2, at db11afd49; 0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + '1086 / 240 / 2, at 62597c588); both resourceLimits hits are prose in packages/app-shell recording ' + 'that objectui\'s own AppShellRuntimeConfig shares not one key with the spec\'s ' + 'RuntimeConfig, so nothing there authors this key and no pin bump is owed. ADR-0087.', @@ -14333,10 +14333,10 @@ const step18: MigrationStep = { + 'no in-repo runtime reads any of the four — outside `packages/spec/src/system/logging.zod.ts` ' + 'and its test the only occurrences are the generated rows in ' + '`content/docs/references/system/logging.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `89cad75d55702cc4f267bead5bf267de575d5842` — spells ' + + 'objectui checkout — `.objectui-sha` = `ab187972159583b595facdcae3c73b50f6f312e9` — spells ' + '`flushInterval` 0 times, `initialDelay` 0, `HttpDestinationConfig` 0 and `LoggingConfig` 0 ' - + 'across its 10071 tracked files, against lit controls `useState` 2470 and `timeout` 1331 on ' - + 'the same corpus (all four 0 across 9912, against 2469 and 1303, at 31971ff1e, 0 across 9800, against 2464 and 1293, at e420df310, 0 across 9546, against 2449 and 1197, at db11afd49, 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + + 'across its 10267 tracked files, against lit controls `useState` 2476 and `timeout` 1348 on ' + + 'the same corpus (all four 0 across 10071, against 2470 and 1331, at 89cad75d5, 0 across 9912, against 2469 and 1303, at 31971ff1e, 0 across 9800, against 2464 and 1293, at e420df310, 0 across 9546, against 2449 and 1197, at db11afd49, 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + 'against 2391 and 1096, at f8a9d0fb0, and 0 across ' + '8303, against 2389 and 1086, at 62597c588).', acceptanceCriteria: @@ -18066,10 +18066,11 @@ const step18: MigrationStep = { + 'against a lit control of 1195 defineStack occurrences on that same corpus at fc28c1d38 ' + '(1195 again at 9b62f54671); and the objectui ' + 'checkout this repo builds against — this is the pin, ' - + '`.objectui-sha` = `89cad75d55702cc4f267bead5bf267de575d5842`, re-read from this tree — ' - + 'spells all six metrics def names and both distinctive keys 0 times across 10071 tracked ' - + 'files at that sha, against lit controls window 4002, timeout 1331, period 231, ' - + 'interval 196 and metrics 355 on that same corpus and sha (0 across 9912, against 3916 / ' + + '`.objectui-sha` = `ab187972159583b595facdcae3c73b50f6f312e9`, re-read from this tree — ' + + 'spells all six metrics def names and both distinctive keys 0 times across 10267 tracked ' + + 'files at that sha, against lit controls window 4044, timeout 1348, period 231, ' + + 'interval 196 and metrics 354 on that same corpus and sha (0 across 10071, against 4002 / ' + + '1331 / 231 / 196 / 355, at 89cad75d5, 0 across 9912, against 3916 / ' + '1303 / 234 / 196 / 354, at 31971ff1e, 0 across 9800, against 3873 / ' + '1293 / 233 / 196 / 352, at e420df310, 0 across 9546, against 3772 / ' + '1197 / 228 / 196 / 341, at db11afd49, 0 across 9283, against 3681 / 1172 / 183 / 176 / 340, at dd3f7e1be, 0 across 8512, against 3581 / ' @@ -18285,12 +18286,13 @@ const step18: MigrationStep = { + 'dark control of 0; inside packages/spec the ' + 'only occurrences are tracing.zod.ts, its test, and the generated rows in ' + 'content/docs/references/system/tracing.mdx, which this rename regenerates. And the ' - + 'pinned objectui checkout — `.objectui-sha` = `89cad75d55702cc4f267bead5bf267de575d5842` — names none of it: all 37 exports of ' - + 'tracing.zod.ts and each of the four key names occur 0 times across the 10071 files ' - + 'tracked at that sha (the 489 Span and 56 SpanSchema hits are objectui\'s own HTML ' + + 'pinned objectui checkout — `.objectui-sha` = `ab187972159583b595facdcae3c73b50f6f312e9` — names none of it: all 37 exports of ' + + 'tracing.zod.ts and each of the four key names occur 0 times across the 10267 files ' + + 'tracked at that sha (the 491 Span and 56 SpanSchema hits are objectui\'s own HTML ' + 'text-span component, TextSpanSchema, an unrelated name, plus colSpan and prose), against ' - + 'two lit controls on that same corpus and sha: 16044 hits for the bare token objectstack, ' - + 'and 6461 for the package specifier @objectstack/spec (at 31971ff1e: 0 across 9912, Span 486, ' + + 'two lit controls on that same corpus and sha: 16377 hits for the bare token objectstack, ' + + 'and 6665 for the package specifier @objectstack/spec (at 89cad75d5: 0 across 10071, Span 489, ' + + '16044 and 6461; at 31971ff1e: 0 across 9912, Span 486, ' + '15691 and 6206; at e420df310: 0 across 9800, Span 486, ' + '15352 and 6024; at db11afd49: 0 across 9546, Span 486, ' + '14704 and 5545; at dd3f7e1be: 0 across 9283, Span 485, 13745 and 5466; at f8a9d0fb0: 0 across 8512, ' @@ -18400,9 +18402,9 @@ const step18: MigrationStep = { + 'bd25e897dc: no in-repo runtime reads the key — outside `packages/spec/src/system/tenant.zod.ts` ' + 'and its test the only occurrences are the four generated rows in ' + '`content/docs/references/system/tenant.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `89cad75d55702cc4f267bead5bf267de575d5842` — spells it 0 ' - + 'times across 10071 tracked files, against lit controls `TTL` 181 and `tenant` 1237 on the ' - + 'same corpus (0 across 9912, against 181 and 1237, at 31971ff1e; 0 across 9800, against 181 and 1235, at e420df310; 0 across 9546, against 181 and 1200, at db11afd49; 0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + + 'objectui checkout — `.objectui-sha` = `ab187972159583b595facdcae3c73b50f6f312e9` — spells it 0 ' + + 'times across 10267 tracked files, against lit controls `TTL` 180 and `tenant` 1238 on the ' + + 'same corpus (0 across 10071, against 181 and 1237, at 89cad75d5; 0 across 9912, against 181 and 1237, at 31971ff1e; 0 across 9800, against 181 and 1235, at e420df310; 0 across 9546, against 181 and 1200, at db11afd49; 0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + 'and 987, at 62597c588).', acceptanceCriteria: 'Every schema-level tenant isolation source spells `performance.schemaCacheTtlSeconds`; ' diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index 67730ef8bbc..e8b9bd16088 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -6568,7 +6568,13 @@ export const ObjectTreePropsSchema = lazySchema(() => strictObject({ * holds for them because each registers through `ElementDataSourceGate`, * which lowers the spec binding onto `objectName` before the renderer sees * the node. `plugin-tree/src/index.tsx` does NOT: at the pin this repo - * builds against (`.objectui-sha` = `89cad75d5`, re-COUNTED there 2026-10-02 by + * builds against (`.objectui-sha` = `ab1879721`, re-COUNTED there 2026-10-03 by + * the same method — 0 for the tree, whose shell is byte-identical across the + * hop, 3 each for `plugin-gantt` and `plugin-calendar` (both byte-identical) + * and `plugin-grid` (changed on the hop: objectui#11068's + * `keyboardNavigation`, objectui#11533's rule dialect and objectui#11438's + * 17.6.0 inputs, its wiring unchanged), and 4 for `plugin-map` + * (byte-identical); re-COUNTED at `89cad75d5` 2026-10-02 by * the same method — 0 for the tree, whose shell changed on the hop * (objectui#11168 slice 3's published inputs, objectui#10859 batch 8's * retired bare `tree` registration) and still registers no gate, 3 each for @@ -6694,8 +6700,12 @@ const OBJECT_TIMELINE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * `object-timeline` (objectui `plugin-timeline/src/ObjectTimeline.tsx`, the * presentational `plugin-timeline/src/renderer.tsx` it composes into, and the * registry shell `plugin-timeline/src/index.tsx` — all read at the pin this - * repo builds against (`.objectui-sha` = `89cad75d5`), re-measured there - * 2026-10-02: `renderer.tsx` is byte-identical to `31971ff1e`, so its three + * repo builds against (`.objectui-sha` = `ab1879721`), re-measured there + * 2026-10-03: `ObjectTimeline.tsx`, `renderer.tsx` and `index.tsx` are + * byte-identical to `89cad75d5` (`git diff --quiet`), so every anchor held + * unmoved and was re-read in place, and the `schema.*` read set of all three + * is unchanged. At `89cad75d5` (2026-10-02) `renderer.tsx` was + * byte-identical to `31971ff1e`, so its three * anchors hold unmoved; `ObjectTimeline.tsx` changed (+16/-7, objectui#11168 * slice 5) in two COMMENTS only — the `navigation` member's docblock on the * component's props and the comment above the navigation read — every anchor @@ -6886,6 +6896,10 @@ export const ObjectTimelinePropsSchema = lazySchema(() => strictObject({ * first publishes a refusal for a key the renderer honours. * * Read points at the pin this repo builds against (`.objectui-sha` = + * `ab1879721`, re-READ there 2026-10-03 — the file, `SchemaRenderer.tsx` + * and `record-source.ts` are byte-identical across the hop from + * `89cad75d5` (`git diff --quiet`), so every anchor held unmoved and was + * re-read in place, and the prop channel above still carries the key. At * `89cad75d5`, re-READ there 2026-10-02 — the file changed across the hop * from `31971ff1e` (+16/-7, objectui#11168 slice 5) in two comments only, * both above or away from these reads, so every anchor MOVED by 6 with its @@ -7143,7 +7157,11 @@ export const ComponentPropsMap = { // set from this map's keys), on the three-part evidence that vocabulary's // string-arm ledger asks of a type admitted without an enum member — all // measured at the pin this repo builds against (`.objectui-sha` = - // `89cad75d5`; re-measured there 2026-10-02: `data-list.tsx`, + // `ab1879721`; re-measured there 2026-10-03: `data-list.tsx`, + // `public-blocks.ts`, `block-types.ts` and `block-config.ts` all + // byte-identical to `89cad75d5` (`git diff --quiet`), so every anchor below + // holds unmoved and was re-read in place, both registrations still in the + // `element` namespace. At `89cad75d5`, 2026-10-02: `data-list.tsx`, // `public-blocks.ts`, `block-types.ts` and `block-config.ts` all // byte-identical to `31971ff1e` (`git diff --quiet`), so every anchor below // holds unmoved and was re-read in place, both registrations still in the @@ -7201,7 +7219,13 @@ export const ComponentPropsMap = { // authority for map and gantt while tree's rung-1 `data` read stayed // undeclared on every face. Key sets measured from the renderers' read // points at the pin this repo builds against (`.objectui-sha` = - // `89cad75d5`), all three re-measured there 2026-10-02 (all three renderers + // `ab1879721`), all three re-measured there 2026-10-03 (`ObjectMap.tsx` and + // `ObjectTree.tsx` are byte-identical to `89cad75d5`; `ObjectGantt.tsx` + // (+8/-0) gained one import line and, in its tooltip's `percent` row, + // objectui#11475's scaling at the field definition's declared storage — a + // field-definition read, not a node key — and was re-READ. The `schema.*` + // read set of each is unchanged, so no declared key set moved and no read + // point died. At `89cad75d5`, 2026-10-02, all three renderers // changed on the hop off `31971ff1e` and were re-READ: `ObjectMap.tsx` // (+11/-4) types its shadowed-flat-key read, objectui#11355, and now reads // `mapStyle` ahead of `map.style` on the declared block's path too, @@ -7247,8 +7271,11 @@ export const ComponentPropsMap = { // it — so `object-timeline` was unjudged in both directions, a real key and // a typo riding through alike. Key set measured from the renderer's read // points at the pin this repo builds against (`.objectui-sha` = - // `89cad75d5`), re-measured there 2026-10-02 (`renderer.tsx` byte-identical - // to `31971ff1e`; `ObjectTimeline.tsx` changed in two comments only and + // `ab1879721`), re-measured there 2026-10-03 (`renderer.tsx`, + // `ObjectTimeline.tsx` and `index.tsx` byte-identical to `89cad75d5`, so + // every anchor held unmoved, the `schema.*` read set unchanged and no + // declared key set moved. At `89cad75d5`, 2026-10-02: `renderer.tsx` + // byte-identical to `31971ff1e`; `ObjectTimeline.tsx` changed in two comments only and // `index.tsx` for objectui#11168 slice 5 — one shared input list on both // registrations, publishing the ten further keys this row declares, with // `objectName` no longer required — both re-READ, the `schema.*` read set diff --git a/packages/spec/src/ui/dataset.zod.ts b/packages/spec/src/ui/dataset.zod.ts index 59baf54f097..370691ef0ed 100644 --- a/packages/spec/src/ui/dataset.zod.ts +++ b/packages/spec/src/ui/dataset.zod.ts @@ -283,9 +283,20 @@ export const DatasetMeasureSchema = lazySchema(() => strictObject({ * and this docblock and the `describe` beneath it both said so. * * Measured at the pin this repo builds against (`.objectui-sha` = - * `89cad75d5`; re-derived at that pin 2026-10-02 — `dataset-format.ts` and - * `date-display.ts` are byte-identical to `31971ff1e`, so every anchor below - * held unmoved and was re-READ in place. At `31971ff1e` (2026-10-01) both + * `ab1879721`; re-derived at that pin 2026-10-03 — `date-display.ts` is + * byte-identical to `89cad75d5`, so `formatDate` `445-480` and the ±7-day + * fallback `399` held unmoved and were re-READ in place; `dataset-format.ts` + * changed (+53/-36, objectui#11475: `scalePercent` scales at the storage + * its caller states, through the spec's `percentScaleOf`, instead of + * guessing from the value's magnitude), above `formatMeasureDate` in the + * percent-scaling helpers and below it in `formatMeasure`'s docblock (line + * for line) and its percent arm, so + * `formatMeasureDate` `:229-263` -> `:240-274`, its datetime arm `:259`-`:261` + * -> `:270`-`:272` and its call `:369` -> `:380` MOVED byte-identical, + * re-READ with the same reading below. At `89cad75d5` (2026-10-02) + * `dataset-format.ts` and `date-display.ts` were byte-identical to + * `31971ff1e`, so every anchor below held unmoved and was re-READ in place. + * At `31971ff1e` (2026-10-01) both * files were byte-identical to `e420df310`, so every anchor held unmoved and * was re-READ in place. At `e420df310` (2026-09-30) * `dataset-format.ts` was byte-identical to `db11afd49`, so `formatMeasureDate` `:229-263`, its call @@ -330,17 +341,17 @@ export const DatasetMeasureSchema = lazySchema(() => strictObject({ * into and widening that published signature was refused) in * objectui * `packages/core/src/utils/dataset-format.ts`: `formatMeasure` routes a - * non-numeric value through `formatMeasureDate` (`:229-263`, was `:229-264` - * and before that `:185-198`) at `:369`, + * non-numeric value through `formatMeasureDate` (`:240-274`, was `:229-263`, + * `:229-264` and before that `:185-198`) at `:380`, * whose date-only arm threads `format` into the STYLE parameter of * `formatDate` (`utils/date-display.ts:445-480`, was `:378-413`, `:355-390`, `:271-306`, * `:198-233` and before that `:131-164`, whose `relative` branch falls back to the absolute form * beyond ±7 days at `:399`, was `:332`, `:309`, `:225`, `:152` and `:117` — the fallback strips the style through * `absoluteFallbackOptions`), while its datetime arm answers `relative` with - * `formatRelativeDate` (`:259`), `short` with - * `formatDateTime(v, { locale, style: 'compact' })` (`:260`) and everything + * `formatRelativeDate` (`:270`), `short` with + * `formatDateTime(v, { locale, style: 'compact' })` (`:271`) and everything * else — a date PATTERN included — with the bare - * `formatDateTime(v, { locale })` (`:261`). + * `formatDateTime(v, { locale })` (`:272`). * Teaching * the shared path a pattern grammar would change every list cell that reads * it, so that gap is still DOCUMENTED here rather than closed (objectui#7178 diff --git a/packages/spec/src/ui/view.zod.ts b/packages/spec/src/ui/view.zod.ts index 57f48ae60c3..68d2bb9d652 100644 --- a/packages/spec/src/ui/view.zod.ts +++ b/packages/spec/src/ui/view.zod.ts @@ -2153,14 +2153,28 @@ export const TreeConfigSchema = lazySchema(() => strictObject({ * of the AUTHORED block, undeclared key and all, so the reading below holds; * only the `style` it writes over the spread changed precedence. The * `safeParse` line is byte-identical. `ObjectView.tsx` and `ListView.tsx` are - * byte-identical, so their anchors did not move. Each anchor quotes the line it was read at, + * byte-identical, so their anchors did not move. RE-READ again at pin + * `ab1879721` on 2026-10-03: that bump redded three anchors, and none of them + * changed what it reads. Two are in `objectql.zod.ts`, which grew above them + * again (+629/-88: objectui#11533 / objectui#11522's spec `{ condition, style }` + * rule schemas, objectui#6152 round 6's ten `ObjectGridSchema` members, + * objectui#11068's `keyboardNavigation` and objectui#11440's seven armed node + * types among others) — `ObjectMapConfigSchema` `2145` -> `2370`, its ten-line + * declaration byte-identical and still closed with `.strict()`, and + * `LIST_VIEW_LOCAL_OVERRIDES` `1160` -> `1419`, the whole list byte-identical + * and still without `map`. The third is `case 'map':` in `ObjectView.tsx`, + * `2293` -> `2299`, the whole arm byte-identical: objectui#11445 added six + * lines above it (the record-count line's i18next count family). + * `ObjectMap.tsx` is byte-identical and `ListView.tsx` changed only below + * `:146` and `:85` (objectui#10813, objectui#11445, objectui#11227), so their + * anchors did not move. Each anchor quotes the line it was read at, * so the next pin bump reds instead of rotting * (`check:objectui-pin-citations`): * * - **The block this face feeds is FLATTENED, not forwarded.** `ListView` * (`packages/plugin-list/src/ListView.tsx:146` first line * `function resolveListMapConfig(schema: { map?: unknown; options?: { map?: unknown } }): Record {`) - * and `ObjectView` (`packages/plugin-view/src/ObjectView.tsx:2293` first line + * and `ObjectView` (`packages/plugin-view/src/ObjectView.tsx:2299` first line * `case 'map':`) copy it through a HAND-LISTED whitelist * (`packages/plugin-list/src/ListView.tsx:85` first line * `export const FLAT_MAP_CONFIG_SPELLING = {`) — ⚠️ re-read at the new pin: @@ -2172,7 +2186,7 @@ export const TreeConfigSchema = lazySchema(() => strictObject({ * there, but by a whitelist and in SILENCE: no parse, no warning, no * diagnostic of any kind. * - **The renderer's own zod schema does not close the set.** - * `packages/types/src/zod/objectql.zod.ts:2145` first line + * `packages/types/src/zod/objectql.zod.ts:2370` first line * `export const ObjectMapConfigSchema = z.object({` — a plain `z.object` at `dd3f7e1be`, * NOT strict, so an undeclared key parses clean there: zero issues, no * warning. ⚠️ At `db11afd49` the declaration is closed with `.strict()` @@ -2195,7 +2209,7 @@ export const TreeConfigSchema = lazySchema(() => strictObject({ * `db11afd49` it is closed by `.strict()` and still warn-only). And this parse is * the only * place an author is told ANYWHERE: `map` is not in objectui's - * `LIST_VIEW_LOCAL_OVERRIDES` (`packages/types/src/zod/objectql.zod.ts:1160` + * `LIST_VIEW_LOCAL_OVERRIDES` (`packages/types/src/zod/objectql.zod.ts:1419` * first line `const LIST_VIEW_LOCAL_OVERRIDES = [`), so objectui's own * `ListViewSchema` imports THIS block by reference and the document check on * that side is this same schema. The two key sets MIRROR each other, key for @@ -3253,7 +3267,15 @@ const FormFieldBaseSchema = lazySchema(() => { * inside the `53ded82bf7...87af769e9` range, so the widest-tier-only * under-span this block used to record (#17328: one cell of two at * 720px) no longer reproduces at the pin this repo builds against - * (`.objectui-sha` = `89cad75d5`, re-read 2026-10-02: `autoLayout.ts`, + * (`.objectui-sha` = `ab1879721`, re-read 2026-10-03: `autoLayout.ts` and + * `form.tsx` are byte-identical to `89cad75d5`, so `resolveColSpan` `:154`, + * `WIDE_FIELD_TYPES` `:58-69`, `spanLadderFor` `:204-231` and its one call + * site `:2986` held unmoved and were re-READ in place, so it still emits the + * ladder; `fields`' `field-type-alias.ts` changed one mapping (+6/-1, + * objectui#11448: `json` now maps to `field:object` instead of + * `field:code`), neither of them a `WIDE_FIELD_TYPES` member, so the wide + * set is unchanged, and the `repeater` -> `field:grid` mapping is + * byte-identical. At `89cad75d5`, re-read 2026-10-02: `autoLayout.ts`, * `fields`' `field-type-alias.ts` and `form.tsx` are all byte-identical to * `31971ff1e`, so `resolveColSpan` `:154`, `WIDE_FIELD_TYPES` `:58-69`, the * `repeater` -> `field:grid` mapping, `spanLadderFor` `:204-231` and its one @@ -3292,7 +3314,7 @@ const FormFieldBaseSchema = lazySchema(() => { * had changed only in its registration's input list, objectui#9910's * `children` slot; at `62597c588` it was byte-identical to `87af769e9`). */ - span: z.enum(['auto', 'full']).default('auto').describe("Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `89cad75d5570`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest."), + span: z.enum(['auto', 'full']).default('auto').describe("Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `ab1879721595`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest."), /** Custom widget override — only needed when auto-inference is insufficient */ widget: z.string().optional().describe('Custom widget/component name (overrides type-based inference)'), From bc86a46108a2ed9c9ece2cfc264f3401aa10fb5f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 18:23:48 +0000 Subject: [PATCH 05/10] chore(objectui): answer the console changeset's ADR-0087 disposition (wip) Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w Co-authored-by: Claude --- .changeset/console-ab1879721595.md | 44 ++++++++++++++++++++++++++- packages/spec/src/ui/component.zod.ts | 8 ++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/.changeset/console-ab1879721595.md b/.changeset/console-ab1879721595.md index e22c0b5987b..e50a91e91dd 100644 --- a/.changeset/console-ab1879721595.md +++ b/.changeset/console-ab1879721595.md @@ -114,6 +114,48 @@ Derived from the changesets objectui declared over the range — 111 releasing o - _(no changeset)_ docs(plugin-dashboard): the charts example authors a line widget bound to a dataset; the card widget with nested children retires (objectui#11484) (#11523) (objectui `6903eafbc`) - + objectui range: `89cad75d5570...ab1879721595` diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index e8b9bd16088..3947017a18d 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -6537,10 +6537,10 @@ const OBJECT_TREE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * This was also the one block of the three whose type was absent from the * tracked `sdui.manifest.json`, so `check:react-declaration-parity` reported * it as missing from the registry rather than comparing it. ⚠️ Re-read at - * `89cad75d5`: the tracked manifest regenerated at this pin - * (`scripts/sdui-manifest.record.json` names `89cad75d5570`) carries an - * `object-tree` entry, as the ones at `31971ff1e`, `db11afd49` and - * `e420df310` did, now publishing `data`, `filter`, `navigation`, + * `ab1879721`: the tracked manifest regenerated at this pin + * (`scripts/sdui-manifest.record.json` names `ab1879721595`) carries an + * `object-tree` entry, as the ones at `89cad75d5`, `31971ff1e`, `db11afd49` + * and `e420df310` did, publishing the same `data`, `filter`, `navigation`, * `objectName`, `staticData` and `tree`; its source at this pin still * registers `object-tree`, and `core/src/registry/public-blocks.ts`, * byte-identical across the hop, still lists it — only the bare `tree` key, From 81853c7470b4d3dd946f0a556024f2fa5f0a6cab Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 18:24:31 +0000 Subject: [PATCH 06/10] chore(spec): changeset for the re-measured objectui pin citations (wip) Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w Co-authored-by: Claude --- .changeset/objectui-pin-citations-ab1879721595.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/objectui-pin-citations-ab1879721595.md diff --git a/.changeset/objectui-pin-citations-ab1879721595.md b/.changeset/objectui-pin-citations-ab1879721595.md new file mode 100644 index 00000000000..fdff674ed91 --- /dev/null +++ b/.changeset/objectui-pin-citations-ab1879721595.md @@ -0,0 +1,9 @@ +--- +'@objectstack/spec': patch +--- + +The spec's objectui citations, and the shipped description text that names the `.objectui-sha` pin (the `FormField.span` describe and six migration-entry descriptions), are re-measured against the new console pin, objectui `ab1879721595`. + +Clause-②: no + +One record was corrected rather than moved, because objectui changed what it describes on this hop: the four action renderers now forward `outcomeMessages` to the action runner (objectui#11344), so the `action:button` and `action:icon` rows record it as a key the renderer forwards and the row does not declare, and the `action:group` and `action:menu` rows record that each member's own `outcomeMessages` rides the member forward. Every other anchor either held on a byte-identical file or moved with its cited text byte-identical; the corpus counts in the six migration entries were re-taken with the method that reproduces the previous pin's numbers. No key, default, enum member or export moves. From 87d7c514242d37935fe45c6be18c2e673d0b6531 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 18:29:53 +0000 Subject: [PATCH 07/10] docs(references): regenerate ui/view.mdx for the re-measured FormField.span describe (wip) Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w Co-authored-by: Claude --- content/docs/references/ui/view.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/references/ui/view.mdx b/content/docs/references/ui/view.mdx index b2f7ca209a3..8daef70fdcd 100644 --- a/content/docs/references/ui/view.mdx +++ b/content/docs/references/ui/view.mdx @@ -213,7 +213,7 @@ Column footer summary configuration | **required** | `boolean` | optional | Required override | | **hidden** | `boolean` | optional | Hidden override | | **colSpan** | `integer` | optional | Absolute column span (1-4). The renderer clamps it to the form grid's current column count, so the cell starts at a real column boundary at every surface width and never overflows (`colSpan: 4` in a 3-column grid renders as 3); a `colSpan` within the column count renders as authored, and `colSpan: 1` emits no span class at all. | -| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `89cad75d5570`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | +| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `ab1879721595`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | | **widget** | `string` | optional | Custom widget/component name (overrides type-based inference) | | **language** | `string` | optional | Code editor language (for type=code) | | **keyField** | `{ field?: string; label?: string \| Record; placeholder?: string \| Record; helpText?: string \| Record; … }` | optional | Key column config for record-typed fields | @@ -343,7 +343,7 @@ Form-view select option — the object-field option shape minus the per-option ` | **required** | `boolean` | optional | Required override | | **hidden** | `boolean` | optional | Hidden override | | **colSpan** | `integer` | optional | Absolute column span (1-4). The renderer clamps it to the form grid's current column count, so the cell starts at a real column boundary at every surface width and never overflows (`colSpan: 4` in a 3-column grid renders as 3); a `colSpan` within the column count renders as authored, and `colSpan: 1` emits no span class at all. | -| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `89cad75d5570`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | +| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `ab1879721595`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | | **widget** | `string` | optional | Custom widget/component name (overrides type-based inference) | | **language** | `string` | optional | Code editor language (for type=code) | | **keyField** | `{ field?: string; label?: string \| Record; placeholder?: string \| Record; helpText?: string \| Record; … }` | optional | Key column config for record-typed fields | From 4f6df21d509aeeb3c57f8a037837156ee2020970 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 20:42:06 +0000 Subject: [PATCH 08/10] fix(spec): re-measure the object-grid keyboardNavigation and emptyState records and the outcomeMessages liveness row at objectui ab1879721 The console reader for each landed inside 89cad75d5570..ab1879721595 (objectui#11068, objectui#11227, objectui#11344), so the records that said no renderer reads them are rewritten to the measured read at the pin, and the keyboardNavigation describe drops its not-enforced marker. Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w Co-authored-by: Claude --- .../objectui-pin-citations-ab1879721595.md | 9 ++- packages/spec/liveness/action.json | 14 ++--- packages/spec/liveness/state-counts/action.md | 2 +- .../src/ui/action-outcome-messages.test.ts | 6 +- packages/spec/src/ui/action.zod.ts | 12 +++- packages/spec/src/ui/component.zod.ts | 60 +++++++++++++++---- 6 files changed, 76 insertions(+), 27 deletions(-) diff --git a/.changeset/objectui-pin-citations-ab1879721595.md b/.changeset/objectui-pin-citations-ab1879721595.md index fdff674ed91..af4572da4a6 100644 --- a/.changeset/objectui-pin-citations-ab1879721595.md +++ b/.changeset/objectui-pin-citations-ab1879721595.md @@ -6,4 +6,11 @@ The spec's objectui citations, and the shipped description text that names the ` Clause-②: no -One record was corrected rather than moved, because objectui changed what it describes on this hop: the four action renderers now forward `outcomeMessages` to the action runner (objectui#11344), so the `action:button` and `action:icon` rows record it as a key the renderer forwards and the row does not declare, and the `action:group` and `action:menu` rows record that each member's own `outcomeMessages` rides the member forward. Every other anchor either held on a byte-identical file or moved with its cited text byte-identical; the corpus counts in the six migration entries were re-taken with the method that reproduces the previous pin's numbers. No key, default, enum member or export moves. +Several records were corrected rather than moved, because objectui changed what they describe on this hop. + +- **`object-grid` `keyboardNavigation`.** The grid now reads the key (objectui#11068), so its describe drops the `[EXPERIMENTAL — not enforced]` marker and the sentence that said no renderer reads it and authoring it changes nothing. The describe now says what the grid does: the data cells become one Tab stop that the arrow keys, Home / End and Ctrl+Home / Ctrl+End move between. It is on by default when the grid renders editable, `true` turns it on for a read-only grid, and `false` turns it off on an editable one. +- **`object-grid` `emptyState`.** Its record now says the grid resolves `title` and `message` against the display locale (objectui#11227), so an inline locale map draws. +- **`ActionSchema.outcomeMessages`.** The console reader landed (objectui#11344). The key's liveness row is now `live`, and authoring it no longer draws the "the console does not show outcome copy yet" author warning. The `successMessage` row records that `${result.*}` is interpolated. +- **The four `action:*` rows.** The action renderers forward `outcomeMessages` to the action runner. The `action:button` and `action:icon` rows record it as a key the renderer forwards and the row does not declare. The `action:group` and `action:menu` rows record that each member's own `outcomeMessages` rides the member forward. + +Every other anchor either held on a byte-identical file or moved with its cited text byte-identical. The corpus counts in the six migration entries were re-taken with the method that reproduces the previous pin's numbers. No key, default, enum member or export moves. diff --git a/packages/spec/liveness/action.json b/packages/spec/liveness/action.json index 50656a3ce46..13d57f9572b 100644 --- a/packages/spec/liveness/action.json +++ b/packages/spec/liveness/action.json @@ -103,16 +103,14 @@ }, "successMessage": { "status": "live", - "note": "objectui toast. 2026-10-01 (#21095): the describe() now also declares that the message may interpolate ${result.*} on a type:'api' / type:'script' action (the scope `onSuccess.navigate` declares) and that it is the fallback when a declared `outcomeMessages` entry does not match the response's `outcome`. Those two halves are declared ahead of their reader, by ruling A on objectstack-ai/cloud#2315 (spec first, then the console, then cloud): measured at objectui @e420df31 packages/core/src/actions/ActionRunner.ts#handlePostExecution, the toast is `result.data.message || action.successMessage || runner default` with no interpolation, so a ${result.X} token renders literally until objectstack-ai/objectui#11344 lands (re-read unchanged at objectui @31971ff1). The KEY stays `live` — the toast reads it — and a row's verdict is per key, so the interpolation half is recorded here and on the `outcomeMessages` row rather than by re-grading a working key. Census when declared: zero successMessage values in this repo or objectui carry a `${` token, so nothing that already renders changes meaning." + "note": "objectui toast. 2026-10-01 (#21095): the describe() now also declares that the message may interpolate ${result.*} on a type:'api' / type:'script' action (the scope `onSuccess.navigate` declares) and that it is the fallback when a declared `outcomeMessages` entry does not match the response's `outcome`. Those two halves are declared ahead of their reader, by ruling A on objectstack-ai/cloud#2315 (spec first, then the console, then cloud): measured at objectui @e420df31 packages/core/src/actions/ActionRunner.ts#handlePostExecution, the toast is `result.data.message || action.successMessage || runner default` with no interpolation, so a ${result.X} token renders literally until objectstack-ai/objectui#11344 lands (re-read unchanged at objectui @31971ff1). The KEY stays `live` — the toast reads it — and a row's verdict is per key, so the interpolation half is recorded here and on the `outcomeMessages` row rather than by re-grading a working key. Census when declared: zero successMessage values in this repo or objectui carry a `${` token, so nothing that already renders changes meaning. Re-read at objectui @ab1879721595 (2026-10-03): objectui#11344 landed in the pin's range — packages/core/src/actions/ActionRunner.ts#composeSuccessMessage fills `${result.*}` in the message it shows and uses `successMessage` as the fallback after a matching `outcomeMessages` entry, so both halves declared ahead of their reader are read; `result.data.message` is no longer a rung." }, "outcomeMessages": { - "status": "planned", - "verifiedAt": "2026-10-01", - "evidenceScope": "in-repo", - "authorWarn": true, - "authorHint": "The console does not show outcome copy yet: until its reader ships, a successful action still toasts `successMessage` (or the server's own message), whatever `outcome` the handler returns. Keep the map — it is accepted, validated and translated today — and keep a `successMessage` as the fallback.", - "evidence": "packages/spec/src/ui/action.zod.ts#refuseInertOutcomeMessages (authoring-time: the map is refused on a type with no server response to report an outcome and beside `resultDialog`, which suppresses the toast; the declarative-update table refuses it beside `operation: 'update'`); packages/spec/src/system/i18n-resolver.ts#translateAction (the REST localization seam overlays each DECLARED outcome's translated copy at the action's one address: `_actions..outcomeMessages.` for a bound action, `globalActions` for an object-less one); packages/cli/src/utils/i18n-extract.ts#pushActionOutcomeMessages (one translation key per declared outcome, beside `successMessage`)", - "note": "PLANNED, carrier objectstack-ai/objectui#11344 (the console's success toast picks `outcomeMessages[result.outcome]`, interpolates ${result.*}, falls back to `successMessage`, then the runner default). Declared by ruling A on objectstack-ai/cloud#2315 (maintainer 2026-10-01), the mechanism half of ruling B there — the server returns facts, the console composes the message in the user's locale — landing spec+client first (#21095), the console second, cloud's producers LAST. Measured at objectui @e420df31: packages/core/src/actions/ActionRunner.ts#handlePostExecution composes the toast from `result.data.message || action.successMessage || runner default` and reads no `outcomeMessages`; re-read unchanged at objectui @31971ff1 (the `.objectui-sha` pin after the base merge), and objectui main 3c3ce15 has no reader either. The in-repo pieces cited in `evidence` validate, translate and extract the copy; none of them SELECTS or shows it, which is what a `live` verdict would claim. Flip to `live` on the commit that cites the console reader at the `.objectui-sha` pin — and drop `authorWarn` / `authorHint` in that same edit: a warned `live` row reds this gate (#21127)." + "status": "live", + "verifiedAt": "2026-10-03", + "evidenceScope": "cross-repo", + "evidence": "objectui @ab1879721595: packages/core/src/actions/ActionRunner.ts#composeSuccessMessage — the success toast's text: `outcomeMessages[outcome]` off the handler's return value, then `successMessage`, then the runner's default text, with `${result.*}` filled in whichever is shown (called from `handlePostExecution`); objectui @ab1879721595: packages/components/src/renderers/action/action-button.tsx#ActionButtonRenderer — forwards the key to the runner, and `action:bar` spreads a registered action onto `action:button` whole (`action-icon.tsx`, `action-group.tsx` and `action-menu.tsx` forward it too); packages/spec/src/ui/action.zod.ts#refuseInertOutcomeMessages (authoring-time: the map is refused on a type with no server response to report an outcome and beside `resultDialog`, which suppresses the toast; the declarative-update table refuses it beside `operation: 'update'`); packages/spec/src/system/i18n-resolver.ts#translateAction (the REST localization seam overlays each DECLARED outcome's translated copy at the action's one address: `_actions..outcomeMessages.` for a bound action, `globalActions` for an object-less one); packages/cli/src/utils/i18n-extract.ts#pushActionOutcomeMessages (one translation key per declared outcome, beside `successMessage`)", + "note": "LIVE from the `.objectui-sha` pin ab1879721595 (2026-10-03): the console reader objectstack-ai/objectui#11344 (objectui c476be0e0) is in the pin's range 89cad75d5570..ab1879721595, so the flip this row prescribed lands on the pin bump that carries it, with `authorWarn` / `authorHint` dropped in the same edit. At 89cad75d5570 no objectui source outside `packages/types` named the key (0 hit lines), and `handlePostExecution` toasted `result.data.message || action.successMessage || runner default`; at ab1879721595 the server-message rung is gone and `composeSuccessMessage` selects the outcome copy. An `outcome` reaches it only from a handler that returns one, which a `script` handler's author writes beside the map and an `api` target returns in its body. History: PLANNED, carrier objectstack-ai/objectui#11344 (the console's success toast picks `outcomeMessages[result.outcome]`, interpolates ${result.*}, falls back to `successMessage`, then the runner default). Declared by ruling A on objectstack-ai/cloud#2315 (maintainer 2026-10-01), the mechanism half of ruling B there — the server returns facts, the console composes the message in the user's locale — landing spec+client first (#21095), the console second, cloud's producers LAST. Measured at objectui @e420df31: packages/core/src/actions/ActionRunner.ts#handlePostExecution composes the toast from `result.data.message || action.successMessage || runner default` and reads no `outcomeMessages`; re-read unchanged at objectui @31971ff1 (the `.objectui-sha` pin after the base merge), and objectui main 3c3ce15 has no reader either. The in-repo pieces cited in `evidence` validate, translate and extract the copy; none of them SELECTS or shows it, which is what a `live` verdict would claim. Flip to `live` on the commit that cites the console reader at the `.objectui-sha` pin — and drop `authorWarn` / `authorHint` in that same edit: a warned `live` row reds this gate (#21127)." }, "errorMessage": { "status": "live", diff --git a/packages/spec/liveness/state-counts/action.md b/packages/spec/liveness/state-counts/action.md index 994c5fa8399..96423e673f4 100644 --- a/packages/spec/liveness/state-counts/action.md +++ b/packages/spec/liveness/state-counts/action.md @@ -12,4 +12,4 @@ committed anywhere: `check:liveness` sums the shards when it reads them. | Type | live | exp | elsewhere | dead | planned | classified | |---|---|---|---|---|---|---| -| `action` | 45 | 0 | 0 | 4 | 1 | 50 | +| `action` | 46 | 0 | 0 | 4 | 0 | 50 | diff --git a/packages/spec/src/ui/action-outcome-messages.test.ts b/packages/spec/src/ui/action-outcome-messages.test.ts index 377e22dd99f..3bbe14f758e 100644 --- a/packages/spec/src/ui/action-outcome-messages.test.ts +++ b/packages/spec/src/ui/action-outcome-messages.test.ts @@ -10,8 +10,10 @@ * * The SELECTION (`outcomeMessages[result.outcome]` → `successMessage` → the * runner default) and the `${result.*}` INTERPOLATION are the console's — the - * reader is objectstack-ai/objectui#11344, a later link of the same ruling, and - * the ledger row stays `planned` until it lands. So the "a handler returning + * reader is objectstack-ai/objectui#11344, a later link of the same ruling, + * carried from the pin this repo builds against (`.objectui-sha` = + * `ab1879721`; `ActionRunner.composeSuccessMessage`), where the ledger row + * turned `live`. Its behaviour is pinned on objectui's side, so the "a handler returning * `{ outcome: 'archived', … }` resolves to the archived copy, interpolated" * acceptance splits in two. HERE: the key is accepted on exactly the two types * that have a success payload, refused loudly everywhere it would be inert or diff --git a/packages/spec/src/ui/action.zod.ts b/packages/spec/src/ui/action.zod.ts index a5b3b65a1f0..d2c988a7d21 100644 --- a/packages/spec/src/ui/action.zod.ts +++ b/packages/spec/src/ui/action.zod.ts @@ -1362,8 +1362,16 @@ const actionObject = () => strictObject({ * `objects.._actions..outcomeMessages.`, and an * object-less action reads `globalActions..outcomeMessages.`. * - * Liveness `planned` until the console reader lands — the ledger row names - * its carrier. + * Liveness: the ledger row was `planned` until the console reader landed, + * and it is `live` from the pin this repo builds against (`.objectui-sha` = + * `ab1879721`, read 2026-10-03). The reader is objectui#11344 (objectui + * `c476be0e0`): `ActionRunner.composeSuccessMessage` + * (`core/src/actions/ActionRunner.ts:1497`) picks `outcomeMessages[outcome]` + * off the handler's return value, falls back to `successMessage` and then to + * the runner's default text, and fills `${result.*}` in whichever it shows. + * The four action renderers forward the key to the runner (`action:bar` + * spreads a registered action onto `action:button` whole). At `89cad75d5570` + * no objectui source outside its types named the key. */ outcomeMessages: z.record(SnakeCaseIdentifierSchema, I18nLabelSchema).optional().describe("Success copy per handler outcome, for type:'api' and type:'script' actions: keys are the snake_case `outcome` values the handler returns in its success payload (e.g. archived, already_archived), values the message shown for that outcome. Each message may interpolate ${result.*}, the scope `onSuccess.navigate` declares. An outcome with no entry here falls back to `successMessage`, then to the default text. Not allowed beside `resultDialog` (which suppresses the success toast) or `operation: 'update'` (no handler, so no outcome)."), // Runtime (ActionRunner) already honours this — declared here so authors can diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index f28135aa41a..ff167ae9edb 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -3951,12 +3951,20 @@ const GridOperationsSchema = lazySchema(() => strictObject({ * `description` (:5677, `resolveInlineI18nLabel(schema.description, * displayLocale)`, drawn above the rows at :5784 / :6303 / :6342) and * `emptyState` (:6225, drawn through `DataEmptyState` at :6233). The third, - * `keyboardNavigation`, has NO read point at that pin — zero hits under + * `keyboardNavigation`, had NO read point at that pin — zero hits under * objectui `packages/` and `apps/` outside tests, CHANGELOGs, READMEs and * `packages/types` (its type declaration and zod twin), against 3 hits for the - * control `schema.editable` in `ObjectGrid.tsx`. It is declared ahead of its - * reader on purpose (the BUILD objectui#11068 chose), and its describe carries - * the `[EXPERIMENTAL — not enforced]` marker that says so; see the member. + * control `schema.editable` in `ObjectGrid.tsx`. It was declared ahead of its + * reader on purpose (the BUILD objectui#11068 chose), and its describe carried + * the `[EXPERIMENTAL — not enforced]` marker that said so. Re-measured at the + * pin this repo builds against (`.objectui-sha` = `ab1879721`, 2026-10-03): + * the BUILD landed — objectui `154075ab1` (objectui#11068), inside + * `89cad75d5570..ab1879721595` — and the grid reads the key. The same method + * finds 0 hit lines at `89cad75d5570` and 15 at this pin, against 3 for the + * control `schema.editable` in `ObjectGrid.tsx` at both: `ObjectGrid.tsx:5458` + * hands `schema.keyboardNavigation ?? inlineEditable` to the `data-table` it + * renders, and `components/src/renderers/complex/data-table.tsx` acts on it. + * So the marker is gone, as the member's record prescribed; see the member. * * [#21445] Seven members re-measured at the `.objectui-sha` pin `89cad75d55`, * same file, and typed with the shape each read point takes — they were @@ -4029,6 +4037,16 @@ export const ObjectGridPropsSchema = lazySchema(() => strictObject({ * "Objects are not valid as a React child"). That is a renderer gap on the * objectui side, not a second shape here: the list view door declares the * same members, and objectui's grid follows this declaration. + * + * ⚠️ That gap is closed at the pin this repo builds against (`.objectui-sha` + * = `ab1879721`, re-measured 2026-10-03): objectui#11227 (carried by objectui + * `6158e4c93`) resolves both members against the display locale before they + * reach `DataEmptyState` — + * `resolveInlineI18nLabel(authoredEmptyState?.title, displayLocale)` and the + * same for `message` (`ObjectGrid.tsx:6468-6469`, drawn at `:6485-6486`) — so + * both `I18nLabel` forms draw, and a locale map with no usable entry keeps + * that member's default. At `89cad75d5570` the two still reached + * `DataEmptyState` raw. */ emptyState: EmptyStateSchema.optional() .describe('What the grid draws instead of an empty table: `{ title, message, icon }` — the list view\'s own empty-state shape'), @@ -4376,17 +4394,33 @@ export const ObjectGridPropsSchema = lazySchema(() => strictObject({ /** * [#20694] Declared AHEAD of its reader, deliberately: objectui#11068 chose * to BUILD arrow-key cell navigation for the grid, and this row is the spec - * half triage folded in. At the pin `db11afd4967c` nothing reads it (see the - * block docblock above for the measurement), so an authored value changes - * nothing yet. The `[EXPERIMENTAL — not enforced]` marker in the describe is - * the liveness ledger's own spelling for a declared-but-not-enforced key; - * the `page/regions` container that holds page components is undrilled in - * the ledger (`undrilled-containers.baseline.json`), so no ledger row exists - * for any `ComponentPropsMap` key and the marker is the record. When the - * BUILD lands and the grid reads it, drop the marker in the same change. + * half triage folded in. At the pin `db11afd4967c` nothing read it (see the + * block docblock above for the measurement), so an authored value changed + * nothing then, and the describe carried the `[EXPERIMENTAL — not enforced]` + * marker — the liveness ledger's own spelling for a declared-but-not-enforced + * key, and the record here because the `page/regions` container that holds + * page components is undrilled in the ledger + * (`undrilled-containers.baseline.json`), so no ledger row exists for any + * `ComponentPropsMap` key. This record said to drop the marker in the change + * that lands the BUILD at the pin, and it is dropped here. + * + * Read at the pin this repo builds against (`.objectui-sha` = `ab1879721`, + * re-measured 2026-10-03; the BUILD is objectui `154075ab1`, objectui#11068): + * `plugin-grid/src/ObjectGrid.tsx:5458` hands the data table + * `keyboardNavigation: schema.keyboardNavigation ?? inlineEditable`, where + * `inlineEditable` (`:1812`) is the authored `editable` AND the viewer's + * write verdict — so an absent key follows whether the grid RENDERS + * editable, `true` turns the navigation on for a read-only grid, and `false` + * turns it off on an editable one. `components/src/renderers/complex/data-table.tsx` + * (`keyboardNavigation = false` by default, `:787`) then gives the table + * `role="grid"` (`:2235`) and makes the data cells one roving Tab stop + * (`tabIndex` 0 on one cell and -1 on the rest, `:2700`) that the arrow + * keys, Home / End and Ctrl+Home / Ctrl+End move (`navigationTarget`, + * `:1964`; `handleCellKeyDown`, `:1990`). A widget a cell renders keeps its + * own Tab stop. Off, every data cell is its own Tab stop, as before. */ keyboardNavigation: z.boolean().optional() - .describe('[EXPERIMENTAL — not enforced] Arrow-key cell navigation on the WAI-ARIA grid pattern. Defaults to on when `editable` is set; a read-only grid keeps its Tab behaviour unless this is `true`. No renderer reads it yet: it is declared ahead of the grid\'s keyboard-navigation build, so authoring it changes nothing today'), + .describe('Arrow-key cell navigation on the WAI-ARIA grid pattern: the grid\'s data cells become one Tab stop that the arrow keys, Home / End and Ctrl+Home / Ctrl+End move between. Defaults to on when the grid renders editable — `editable` set and the viewer allowed to edit; a grid that renders read-only keeps every cell its own Tab stop unless this is `true`, and `false` turns it off on an editable grid'), resizable: z.boolean().optional().describe('Allow column resize (the renderer default is on)'), /** * REMOVED (#21445, ADR-0049 enforce-or-remove; objectui#6152 ruling A, From 5eadf35375dc406b7c7e027d9310ac8e9e491794 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 20:52:01 +0000 Subject: [PATCH 09/10] docs(references): regenerate ui/component.mdx for the re-measured keyboardNavigation describe Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w Co-authored-by: Claude --- content/docs/references/ui/component.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/references/ui/component.mdx b/content/docs/references/ui/component.mdx index 9030761053a..76a8242ba5a 100644 --- a/content/docs/references/ui/component.mdx +++ b/content/docs/references/ui/component.mdx @@ -735,7 +735,7 @@ Sort field and direction pair | **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; preventNavigation?: boolean; openNewTab?: boolean; size?: Enum<'auto' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'>; … }` | optional | Row-click navigation config — the same block `ListViewSchema.navigation` declares (`{ mode, size, openNewTab, preventNavigation }`) | | **editable** | `boolean` | optional | Enable inline cell editing | | **singleClickEdit** | `boolean` | optional | Enter cell edit on single click (default true when editable) | -| **keyboardNavigation** | `boolean` | optional | [EXPERIMENTAL — not enforced] Arrow-key cell navigation on the WAI-ARIA grid pattern. Defaults to on when `editable` is set; a read-only grid keeps its Tab behaviour unless this is `true`. No renderer reads it yet: it is declared ahead of the grid's keyboard-navigation build, so authoring it changes nothing today | +| **keyboardNavigation** | `boolean` | optional | Arrow-key cell navigation on the WAI-ARIA grid pattern: the grid's data cells become one Tab stop that the arrow keys, Home / End and Ctrl+Home / Ctrl+End move between. Defaults to on when the grid renders editable — `editable` set and the viewer allowed to edit; a grid that renders read-only keeps every cell its own Tab stop unless this is `true`, and `false` turns it off on an editable grid | | **resizable** | `boolean` | optional | Allow column resize (the renderer default is on) | | **resizableColumns** | `never` | optional | [REMOVED] `object-grid` property `resizableColumns` was removed in @objectstack/spec 17.7.0 (ADR-0049) — it was the legacy second spelling of `resizable`, read only when `resizable` was absent, so one switch had two spellings and a grid authoring both silently ignored this one. Use `resizable`. Rename the key; the value (a boolean) is unchanged. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | | **reorderableColumns** | `boolean` | optional | Allow column drag-reorder | From 4fc15b7e67f4d4f25e3886058e3eb507b2947bbc Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 21:40:05 +0000 Subject: [PATCH 10/10] docs(references): regenerate ui/component.mdx on the merged tree The os-regen driver kept this branch's side of the generated page in the merge with origin/main 5b5e83f446, which dropped main's object-metric aggregate and trend rows (3f1bc816a2). gen:docs on the merged tree puts them back. The page now differs from origin/main only by this branch's keyboardNavigation row. Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w Co-authored-by: Claude --- content/docs/references/ui/component.mdx | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/content/docs/references/ui/component.mdx b/content/docs/references/ui/component.mdx index 76a8242ba5a..ae8afe2f215 100644 --- a/content/docs/references/ui/component.mdx +++ b/content/docs/references/ui/component.mdx @@ -1106,7 +1106,7 @@ Sort field and direction pair | **title** | `string \| Record` | optional | Drill-down panel title; defaults to the metric label | | **icon** | `string` | optional | Lucide icon name drawn in the metric tile header, inside the `colorVariant`-tinted square. Read on this component — `ObjectMetricWidget` forwards it to `MetricWidget`, which resolves it with `getLazyIcon` (the `LazyIcon` module: kebab-case or PascalCase, degrading to the `Database` glyph on an unknown name). | | **colorVariant** | `Enum<'default' \| 'blue' \| 'teal' \| 'orange' \| 'purple' \| 'success' \| 'warning' \| 'danger'>` | optional | Icon container color variant | -| **aggregate** | `any` | optional | Aggregation config (`{ field, function, groupBy? }`) run against the object | +| **aggregate** | `{ field?: string; function: Enum<'count' \| 'sum' \| 'avg' \| 'min' \| 'max' \| 'count_distinct'>; groupBy?: string \| object }` | optional | Aggregation run against the object: `{ field?, function, groupBy? }` — `function` one of count / sum / avg / min / max / count_distinct (`field` needed for all but count), `groupBy` a field name or a `{ field, dateGranularity }` node, absent for one number over every row | | **filter** | `{ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Filter the aggregation is scoped by — the ViewFilterRule array form `[{ field, operator, value }, ...]`, the one filter orthography every `filter` door in this map shares. The MongoDB-style record form is refused — see migration `element-data-source-and-object-block-filter-rule-array` | | **format** | `string` | optional | Number format pattern (e.g. '0,0', '$0,0', '0%') | | **currency** | `string` | optional | ISO currency code (e.g. 'USD') — enables currency formatting | @@ -1115,10 +1115,18 @@ Sort field and direction pair | **invert** | `boolean` | optional | Display `1 - value` for opposite-signal gauges (compliance/uptime) | | **variant** | `Enum<'card' \| 'bare'>` | optional | Layout variant | | **fallbackValue** | `string \| number` | optional | Static value shown when no data source is available | -| **trend** | `any` | optional | Static trend info (`{ value, label, direction }`) | +| **trend** | `{ value: number; label?: string \| Record; direction?: Enum<'up' \| 'down' \| 'neutral'> }` | optional | Static trend badge `{ value, label?, direction? }` — `value` painted as a percentage, `direction` up / down / neutral. A `compareTo`-derived trend replaces it | | **drillDown** | `any` | optional | Click-through drill config — opens the underlying records | | **compareTo** | `any` | optional | Period-over-period comparison (`{ kind: 'previousPeriod' \| 'previousYear' }`) | +### Nested Shape: `ObjectMetricProps.aggregate` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **field** | `string` | optional | Field to aggregate — required for every function but `count`, which counts rows | +| **function** | `Enum<'count' \| 'sum' \| 'avg' \| 'min' \| 'max' \| 'count_distinct'>` | ✅ | Aggregation function — `count`, `sum`, `avg`, `min`, `max` or `count_distinct` | +| **groupBy** | `string \| { field: string; dateGranularity?: Enum<'day' \| 'week' \| 'month' \| 'quarter' \| 'year'>; alias?: string }` | optional | Field the rows are grouped by, or a `{ field, dateGranularity }` date-bucket node. Omit it for the one number over every row (`_all`) | + ### Nested Shape: `ObjectMetricProps.filter[number]` View filter rule @@ -1129,6 +1137,14 @@ View filter rule | **operator** | `Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>` | ✅ | Filter operator | | **value** | `string \| number \| boolean \| null \| (string \| number)[]` | optional | Filter value. The accepted SHAPE depends on the operator: `in` / `not_in` take an array (any length, including []), `between` takes exactly [min, max], every other operator takes a scalar. The unary operators (is_empty / is_not_empty / is_null / is_not_null) take their direction from the operator name and ignore this key. One operator bounds the VALUE as well as the shape: `icontains` takes a NON-EMPTY STRING, the comparand the Filter Protocol conformance table declares for it — an empty comparand constrains nothing and a non-string one would answer a query nobody wrote, and both are refused at the query path too. | +### Nested Shape: `ObjectMetricProps.trend` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **value** | `number` | ✅ | The change shown on the badge, painted as a percentage (`12` reads `12%`) | +| **label** | `string \| Record` | optional | Badge caption — a string or an inline locale map. The tile-level `description` outranks it in the one caption slot they share | +| **direction** | `Enum<'up' \| 'down' \| 'neutral'>` | optional | Arrow beside the value; omit it for no arrow | + ---