From 02ddae3a54d64cc6fa1f1ba3730e2c989c8428dc Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 10:20:55 +0000 Subject: [PATCH 1/4] chore(console): bump objectui pin 190fbd01 -> 9602dc82 (packaged-automation page) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat(app-shell): packaged actions on/off beside the packaged flows (ADR-0126 §8 item 2) (#6415) objectui@9602dc820450dda956843c6cfe5b329bcf88c757 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw --- .changeset/console-9602dc820450.md | 200 +++++++++++++++++++++++++++++ .objectui-sha | 2 +- 2 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 .changeset/console-9602dc820450.md diff --git a/.changeset/console-9602dc820450.md b/.changeset/console-9602dc820450.md new file mode 100644 index 0000000000..4c23a46aa8 --- /dev/null +++ b/.changeset/console-9602dc820450.md @@ -0,0 +1,200 @@ +--- +"@objectstack/console": minor +--- + +Console (objectui) refreshed to `9602dc820450`. Frontend changes in this range: + +Derived from the changesets objectui declared over the range — 188 releasing of 279 changesets added across 359 non-merge commits; omitted: 91 release-nothing changesets, 82 commits carrying no changeset (they ship no package code). + +- **minor** — Setup › **Packaged automation** gains a packaged **actions** section beside its flows one — the surface half of ADR-0126 §8 item 2 (objectui#6412; the engine, ledger and both disp… (objectui `9602dc820`) +- **minor** — Setup gains a **Packaged automation** page — the operational surface for the flows an installed package ships (ADR-0126 §7.4, objectui#6301). Reached the way every other framework… (objectui `b362c1b47`) +- **minor** — `TableColumn.type` now has ONE canonical value set across all three ends that disagreed (objectui#5853, maintainer ruling 2026-08-25, Option B: the 8-literal interface union is ca… (objectui `fc62bb490`) +- **minor** — `TimelineSchema` now declares the presentational keys the timeline renderer actually reads (objectui#6170, maintainer ruling 2026-08-25 — the same family rule adopted on objectui#… (objectui `e4559d1e0`) +- **minor** — `navigateOnSuccess` is relative-only, escapes the interpolated id, and is deprecated in favour of `submitBehavior` (objectui `636b2363f`) +- **minor** — `objectui check` recognises a schema by validating it, and reports broken ObjectUI files instead of filing them as foreign ones. (objectui `7dc08a3e7`) +- **minor** — The Field Designer no longer offers a formula-expression textarea, and no designer write path emits a `formula` key (objectui#6043). (objectui `864154e77`) +- **minor** — `ComponentMeta` at the registry is now DERIVED from the one declaration in `@object-ui/types` instead of restating it, and `tags` / `description` reach the registration surface (o… (objectui `44d075ba0`) +- **minor** — `StackSchema` now SHIPS the members it declares (objectui#6151). Its emitted declaration carried one property — `type` — where it was meant to carry twenty-five. (objectui `db3a6008e`) +- **minor** — ⚠️ **Behaviour change: `current_user` predicates that have been doing nothing on the console form routes and in the wizard's submit gate now TAKE EFFECT.** Read this before upgrad… (objectui `5173a5ece`) +- **minor** — **BREAKING** — Object-level metadata payloads no longer emit the three keys `ObjectSchema` refuses by name — **group**, **sortOrder** and **relationships** (objectui#6223). (objectui `d18a0d3af`) +- **minor** — `radio-group` now renders the `orientation` its own type has always declared (objectui#6158). (objectui `6fd2cf7a0`) +- **minor** — ⚠️ **Behaviour change: an authored `FormSection.visibleWhen` that has been doing nothing will now START HIDING SECTIONS.** Read this before upgrading if any of your metadata autho… (objectui `971d38748`) +- **minor** — **Breaking (shipped as `minor` per AGENTS.md §版本号策略).** The published `toSortItems` export now reads its sort direction from **`order`** only. The retired spelling is **`direction… (objectui `c5fbe0b99`) +- **minor** — **View configuration is explicitly org-wide, and its write path is now gated (objectstack#7494's ruling, maintainer 2026-08-12).** The `sort` / `hiddenFields` / `columnState` / `r… (objectui `41b7ce3ce`) +- **minor** — Retire `CRUDSchema` and the `type: 'crud'` node spelling (objectui#5373, maintainer ruling 2026-08-20, route 2) under ADR-0049 enforce-or-remove. (objectui `105f3c55c`) +- **minor** — The metadata designer states its package on the publish step, not only on the save (#5420) (objectui `b470e9150`) +- **minor** — Grid headers offer a sort click only on columns the PLATFORM says it will order by (objectui#5729 — the consumer leg of objectstack#10235, maintainer ruling A, 2026-08-23: the pla… (objectui `490d9a93a`) +- **minor** — **Retired the designer-surface dashboard `aria` pair — `DashboardConfig.aria` and `DashboardConfigSchema.aria`** (objectui#5852). (objectui `e7957ab87`) +- **minor** — The metadata designer's View **column list** now reads a column's identity — row label and bound field name — in the ObjectStack canonical spelling only: `field` and `label`. The… (objectui `f471b4f73`) +- **minor** — An auto-derived related list now orders its rows by the CHILD object's default list view `sort`, instead of falling to the server's primary-key order (objectui#5795). A task versi… (objectui `6d63cd029`) +- **minor** — **BREAKING** — `ObjectTrigger` and `ObjectRelationship` are removed from `@object-ui/types` — two hand-written interfaces orphaned by the `ObjectSchemaMetadata` derivation (objectui#5859, triage… (objectui `41df89320`) +- **minor** — `ComponentMeta` is now declared once and re-exported, and `PluginComponentMeta` is deprecated in favour of `ComponentMeta` (objectui#5893). (objectui `6d1c15536`) +- **minor** — `MetadataClient.layered()` validates the ADR-0010 protection envelope against the producer's own schema at the boundary, instead of casting ten wire fields through unchecked (obje… (objectui `94e2fa725`) +- **minor** — `ObjectGanttSchema` declares the ten gantt keys `ObjectGantt` actually reads (objectui#5903, triage 2026-08-24). Every one is a real, working, documented feature — `readOnly`, `mo… (objectui `00f3eb5b9`) +- **minor** — Route a `data-table`'s `emptyAction` node through `SchemaRenderer`, so a `visibleWhen` authored on it is actually evaluated (objectui#5926 gap 1). (objectui `f8cdbf2cc`) +- **minor** — **Behaviour change.** Form section and field `visibleWhen` predicates that silently failed OPEN now actually evaluate — and a rule that resolves FALSE now hides the field (objectu… (objectui `aefe42898`) +- **minor** — **`@object-ui/types/zod` now accepts seven spellings its own TypeScript declarations already declared** (objectui `69a2163d9`) +- **minor** — **Breaking (shipped as `minor` per AGENTS.md §版本号策略).** `ObjectViewProps.views[].sort` now spells its direction key **`order`**. The retired spelling is **`direction`** — named he… (objectui `53dc89db8`) +- **minor** — **BREAKING (authoring): `ui:icon` names its glyph with `icon`, not `name`** (objectui `d2fb6efb4`) +- **minor** — Close the dashboard widget `type` vocabulary, and admit `metric-card` as objectui's own component extension. (objectui `f7e34ca0b`) +- **minor** — Console telemetry can now be hard-disabled on an already-built artifact (objectui `26fbe09b0`) +- **minor** — **BREAKING** — Retire the `ActionCondition` `{ expression, then, else }` branch shape from `ActionSchema.condition` (objectui#3917, maintainer ruling 2026-08-09 route B, enforce-or-remove). (objectui `6a9158602`) +- **minor** — `ui:icon`: an unresolvable glyph now renders a visible placeholder instead of nothing (objectui `7cd398737`) +- **minor** — Classify `/analytics/query` failures by their ADR-0112 `code` rather than their HTTP status, so a chart is no longer answered from a different code path behind the wrong explanati… (objectui `b2065e711`) +- **minor** — **BREAKING** — Retire the `MobileOverrides` type and its `mobileOverrides` mount point (objectui#4919, maintainer ruling 2026-08-19, ADR-0049 enforce-or-remove). (objectui `2d7304d8a`) +- **minor** — Console half of `ActionSchema.onSuccess` post-success navigation. (objectui `053fdc8f9`) +- **minor** — `ObjectSchemaMetadata` is now derived from `@objectstack/spec`'s `ServiceObject` instead of being a hand-written copy (objectui#5362; maintainer ruling 2026-08-20: the object docu… (objectui `b2ea29717`) +- **minor** — **Published TS surface narrowed:** `DashboardComponentSchema` no longer declares the `aria` member (`{ ariaLabel?, ariaDescribedBy?, role? }`). Its doc comment claimed alignment w… (objectui `18a8e7db7`) +- **minor** — `record:path` stops looking like a control it cannot be. (objectui `0b12a3315`) +- **minor** — **Behaviour change:** the spec's view-level `map` block on a list view is now read at runtime. `ListMapConfigSchema` (objectstack#9340) has been authorable and validated since the… (objectui `e2e8e68f1`) +- **minor** — The console form now renders a refusal message the producer explicitly marked as user-facing, instead of always substituting a generic string (objectui#5210). (objectui `ae61ad4a2`) +- **minor** — The spec-bridge's form-view input now accepts everything `@objectstack/spec`'s FormViewSchema accepts — three key types had drifted narrower, and one of them had inverted (objectu… (objectui `cac64b3f1`) +- **minor** — The console record page and the `record:activity` block read ONE `sys_activity` type table, so a scheduled meeting no longer appears on one and vanishes on the other. (objectui `d06fba8d8`) +- **minor** — The Studio copilot tells the agent WHAT the user is discussing (cloud#1610 send half): `ChatPane` accepts a `surfaceContext` and sends it as `context.surface` on every turn (the t… (objectui `eddc1dd97`) +- **minor** — 设计⇄运行 on the Interfaces canvas (#5800): a two-state switch in the canvas header flips the SAME renderer between design (selection + inspector + design overlays) and an interactive… (objectui `1939c9610`) +- **minor** — The built-moment transition (#5799): a build conversation that has produced a WHOLE-APP build auto-routes to `/studio//interfaces` — on live completion and on reopening the c… (objectui `6bd343d29`) +- **minor** — Dashboard flat `table` widget: render the server's grand total as a `tfoot` row (objectui#5846). (objectui `9e725e06a`) +- **minor** — Dashboard `table` widget — a numeric **measure** column now sorts **descending on the first click** (objectui#5845). (objectui `1d9100eeb`) +- **minor** — Studio simplification, first cut (#5813): the Data pillar's object sub-tabs collapse to 记录/表单 with the five power panels (验证/钩子/操作/API/设置) behind one 「高级」 menu (the open panel's n… (objectui `19479a37d`) +- **minor** — One pending-drafts source of truth (#5801): `usePendingDrafts` / `fetchPendingDrafts` replace the five hand-rolled `GET /api/v1/meta/_drafts` copies behind the home banner, Studio… (objectui `469723471`) +- **minor** — Dashboard `table` widget (dataset-backed, flat grouped table) — four rendering fixes so it reads as a table rather than a raw dump (objectui#5827): (objectui `62c0fa164`) +- **minor** — **The page-editor palette now offers the canonical `record:discussion` instead of the `record:chatter` alias.** (objectui `8b1a8f38c`) +- **minor** — `dashboard` now publishes the authoring inputs its renderer already honours — `widgets`, `label`, `description`, `header`, `globalFilters`, `dateRange`, `refreshInterval` — so `va… (objectui `2e471a64c`) +- **minor** — **`viewMode` is now declared authoring surface on `ObjectGanttSchema`, and both gantt renderer branches honour it** (objectui#5074, maintainer ruling 2026-08-19: declare-and-wire;… (objectui `199d31bb9`) +- **minor** — Retire `ThemeSwitcherSchema` (`type: 'theme-switcher'`) and `ThemePreviewSchema` (`type: 'theme-preview'`) — the two remaining theme component kinds, which no renderer implemented… (objectui `b7543a9c9`) +- **minor** — **BREAKING** — Split the static `table` column type off the rich shared `TableColumn` (objectui#5474, maintainer ruling 2026-08-22: Option C), so declared = enforced holds per renderer. (objectui `3ccd9e8cb`) +- **minor** — Remove the inert `showRefresh` designer input from the `object-view` registration (objectui#5567). (objectui `4ef29f011`) +- **minor** — `'agenda'` leaves `defaultView` on all three of its declaration faces (objectui#5784 — the `defaultView` sibling of objectui#5740's retirement on `CalendarViewSchema.view`; ADR-00… (objectui `ed8df3e50`) +- **minor** — The 确认修改 (confirm changes) card now carries a UI-owned terminal state after approval (#5695): `detectReplayOutcome` lifts the confirm-replay envelope (`replay_*` tool results) int… (objectui `6ce89daae`) +- **minor** — `validateTree` now reports a dashboard widget `options` key that no renderer consumes as a `unconsumed-widget-option` **warning** naming the consumed set (objectui#5709 ruling). T… (objectui `8d58f46b4`) +- **minor** — **`ReferenceRailEntry` is now the spec's type, and the reference-rail `icon` key retires** (objectui#5494, maintainer ruling 2026-08-22). (objectui `23705b730`) +- **minor** — `'agenda'` leaves `CalendarViewMode` and the zod `CalendarViewModeSchema` (objectui#5740 — the value-level residue of objectui#5667's key-level convergence of `CalendarViewSchema`… (objectui `b55a34647`) +- **minor** — **BREAKING** — Retire `ActionParamDialog`: the `custom` barrel's second action-param dialog is removed, and the app-shell dialog is recorded as the surviving implementation (objectui#5685, maint… (objectui `72ffc3496`) +- **minor** — Localize the theme document types: `@object-ui/types` now owns `Theme`, `ThemeMode` and `ColorPalette` (objectui#5716 ruling, 2026-08-23). The spec retired its theme module (objec… (objectui `c9327c985`) +- **minor** — **BREAKING** — `CalendarViewSchema` (TS interface and zod mirror) converges on the registered `calendar-view` renderer's measured read set (objectui#5667, maintainer ruling option A — the render… (objectui `065bba746`) +- **minor** — Row predicates declare a canon: `record.*`. The bare shorthand and `data.*` now warn once, and are unchanged otherwise. (objectui `d1ab06f0f`) +- **minor** — The metadata designer's View column inspector now reads a column's identity — field key and label — in the ObjectStack canonical spelling only: `field` and `label`. The legacy Tan… (objectui `0e00da354`) +- **minor** — `AuthGuard`'s `requiredRoles` now matches against `user.positions` — the one published spelling (framework ADR-0090 D3) — keeping the `user.role` scalar fallback only for sessions… (objectui `686d5d92c`) +- **minor** — **BREAKING** — Stop showing an access-denied screen to a real administrator while their adminship is still resolving. (objectui `c8da8b326`) +- **patch** — Delete the dead `metadata-admin/previews/object-fields-bridge.ts` module, and the three prose references that still described it as wired. (objectui `dddb942d1`) +- **patch** — `appDesigner.fieldDesigner.formula` is retired — one row removed from each of the ten locale packs plus the designer defaults map, 11 lines, zero readers (objectui#6310). (objectui `00c665ef5`) +- **patch** — The form renderer no longer leaks `FormSchema.previousValues` onto the `
` DOM node (objectui#6396). (objectui `f24de8bbc`) +- **patch** — `@object-ui/plugin-grid` and `@object-ui/plugin-kanban` now publish a stylesheet — `"./style.css"`, mapped to `dist/index.css` and compiled at build time from the package's own so… (objectui `062943f86`) +- **patch** — `MasterDetailForm` shows a config hint naming `relationshipField` for a detail collection whose child schema **loaded fine but could not be derived from**, instead of a permanent… (objectui `fd814d6f0`) +- **patch** — `SimpleObjectForm`: consult a declared `submitHandler` before the inline-fields carve-out (objectui `43ca9d5ee`) +- **patch** — `ObjectViewSchema`'s `table` and `form` slots now ship the members they promise (objectui#6269). Both were declared by deriving from the schema they document — `table?: Partial + +objectui range: `190fbd01d061...9602dc820450` diff --git a/.objectui-sha b/.objectui-sha index 788ec64d9c..bd952a69cd 100644 --- a/.objectui-sha +++ b/.objectui-sha @@ -1 +1 @@ -190fbd01d0615e2e168faf9e08b8ad7844bc039d +9602dc820450dda956843c6cfe5b329bcf88c757 From 39b05bf821af235c8046064c9cd4e08764cecec8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 11:00:41 +0000 Subject: [PATCH 2/4] chore(spec): re-measure 6 objectui pin citations at 9602dc820450 Re-derived every cited anchor at the new pin rather than rewriting the sha, per check-objectui-pin-citations' own instruction. - ObjectGrid.tsx 2492-2507 -> 2586-2601 (block byte-identical, shifted) - plugin-dashboard/src/index.tsx 161 -> 204 (object-metric block) - useBulkExecutor.ts 284-288 -> 284-289 (span was truncated since written) - containers.tsx tabs 662-668 / :721 and accordion 851-857 / :898 unmoved, re-read at the new pin rather than inferred from byte-identity - MetricWidget / ObjectMetricWidget / MetricCard / lazy-icon anchors unmoved The two element:button records are deliberately NOT touched: that read point MOVED (objectui#5993) and its shipped .describe() text is now false. Reported rather than re-pointed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw --- .../api-methods-batch-conformance.test.ts | 18 ++++++++----- packages/spec/src/ui/component.test.ts | 15 ++++++----- packages/spec/src/ui/component.zod.ts | 25 +++++++++++-------- 3 files changed, 36 insertions(+), 22 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 38a781890b..b36e268441 100644 --- a/packages/spec/src/data/api-methods-batch-conformance.test.ts +++ b/packages/spec/src/data/api-methods-batch-conformance.test.ts @@ -61,12 +61,18 @@ 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` = `190fbd01d`, `packages/plugin-grid`; re-measured at - // that pin, 2026-08-22 — previously measured at `9a3daf8d3`, originally at - // `6314e87f2`. `ObjectGrid.tsx` DID change across that move, so both claims - // below were re-derived rather than carried over, and they now carry line - // anchors: `ObjectGrid.tsx:2492-2507` and `hooks/useBulkExecutor.ts:284-288`, - // the latter byte-identical at both pins): + // (`.objectui-sha` = `9602dc820`, `packages/plugin-grid`; re-measured at + // that pin, 2026-08-28 — previously measured at `190fbd01d`, before that at + // `9a3daf8d3`, originally at `6314e87f2`. `ObjectGrid.tsx` DID change across + // the move off `190fbd01d`, so both claims below were re-derived rather than + // carried over: `ObjectGrid.tsx:2586-2601`, whose block is byte-identical to + // the one cited at `190fbd01d:2492-2507` and shifted only by insertions above + // it, and `hooks/useBulkExecutor.ts:284-289`, in a file byte-identical at both + // pins. That second span is WIDENED BY ONE LINE from the `284-288` cited + // before: it stopped short of `label = 'bulk delete'`, truncating the second + // of the two branches it names — the #10274 class of anchor error, found here + // by re-reading rather than by the file changing, which is why byte-identity + // is never taken as proof an anchor is right): // // · No checkbox column is rendered. None of the object's four list views // declares `bulkActions` / `bulkActionDefs` / `selection`, and `ObjectGrid` diff --git a/packages/spec/src/ui/component.test.ts b/packages/spec/src/ui/component.test.ts index 82e721ce03..5055e367a6 100644 --- a/packages/spec/src/ui/component.test.ts +++ b/packages/spec/src/ui/component.test.ts @@ -278,9 +278,11 @@ describe('PageAccordionProps variant (#6776)', () => { // same file's `ComponentRegistry.register('accordion', …)` publishes the key to // the Studio block designer at `:898` (the `items` input, documented as // `[{ label, icon?, collapsed?, children }]`). Measured at the pin this repo -// builds against — `.objectui-sha` = `190fbd01d`. Re-derived at that pin -// 2026-08-22: `containers.tsx` changed across the move from `9a3daf8d3`, and -// both anchors above landed on the same lines when re-counted. +// builds against — `.objectui-sha` = `9602dc820`. Re-derived at that pin +// 2026-08-28: `containers.tsx` is byte-identical to the one at `190fbd01d`, so +// both anchors above are unmoved — and both were re-READ at the new pin rather +// than inferred from that identity, because identity preserves a wrong anchor +// as faithfully as a right one (#10274). // // #9397 spent a full dispatch cycle re-deriving that read point from scratch // after the sweep proposed retiring the key. This block plus the `.describe()` @@ -363,9 +365,10 @@ describe('PageTabsProps items[].value / items[].count (#5775)', () => { // same file's `ComponentRegistry.register('tabs', …)` publishes the key to the // Studio block designer at `:721` (the `items` input, documented as // `[{ label, value?, icon?, count?, visibleWhen?, children }]`). Measured at -// the pin this repo builds against — `.objectui-sha` = `190fbd01d`. Re-derived -// at that pin 2026-08-22: `containers.tsx` changed across the move from -// `9a3daf8d3`, and both anchors above landed on the same lines when re-counted. +// the pin this repo builds against — `.objectui-sha` = `9602dc820`. Re-derived +// at that pin 2026-08-28: `containers.tsx` is byte-identical to the one at +// `190fbd01d`, so both anchors above are unmoved — and both were re-READ at the +// new pin rather than inferred from that identity (#10274). // // #9397 spent a full dispatch cycle re-deriving the accordion's read point // after the sweep proposed retiring it. This block plus the `.describe()` it diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index f176f06178..2126de4f01 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -639,9 +639,10 @@ export const PageTabsProps = strictObject({ * false candidate a component over). * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `190fbd01d`; re-derived at that pin 2026-08-22 — - * `containers.tsx` changed across the move from `9a3daf8d3` but both - * anchors below landed on the same lines): `containers.tsx:662-668` renders + * (`.objectui-sha` = `9602dc820`; re-derived at that pin 2026-08-28 — + * `containers.tsx` is byte-identical to the one at `190fbd01d`, so both + * anchors below are unmoved, and both were re-READ there rather than + * inferred from that identity): `containers.tsx:662-668` 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 @@ -1646,9 +1647,10 @@ export const PageAccordionProps = strictObject({ * re-derive the same false candidate). * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `190fbd01d`; re-derived at that pin 2026-08-22 — - * `containers.tsx` changed across the move from `9a3daf8d3` but both - * anchors below landed on the same lines): `containers.tsx:851-857` renders + * (`.objectui-sha` = `9602dc820`; re-derived at that pin 2026-08-28 — + * `containers.tsx` is byte-identical to the one at `190fbd01d`, so both + * anchors below are unmoved, and both were re-READ there rather than + * inferred from that identity): `containers.tsx:851-857` 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 @@ -2390,11 +2392,14 @@ 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` = `190fbd01d`; re-derived at that pin 2026-08-22 — all - * four files in the chain below, plus `lazy-icon.tsx`, are byte-identical to - * the ones at `9a3daf8d3`, so every anchor is unmoved), and the chain runs + * (`.objectui-sha` = `9602dc820`; re-derived at that pin 2026-08-28 — of the + * files in the chain below, `index.tsx` DID change across the move off + * `190fbd01d` and its anchor moved `161` → `204`, while `ObjectMetricWidget + * .tsx`, `MetricWidget.tsx`, `MetricCard.tsx` and `lazy-icon.tsx` are + * byte-identical to the ones at `190fbd01d`; every anchor below was re-READ + * at the new pin rather than inferred from that identity), and the chain runs * three files: - * `plugin-dashboard/src/index.tsx:161` publishes it as a designer input + * `plugin-dashboard/src/index.tsx:204` publishes it as a designer input * (`Icon (Lucide name)`) on the registered `object-metric` block; * `ObjectMetricWidget.tsx:142` destructures it and forwards it at `:474` to * `MetricWidget`; `MetricWidget.tsx:312-321` resolves it via From d04c2d5221c36fef530306dfd9fab53b868a6592 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 17:18:33 +0000 Subject: [PATCH 3/4] wip(spec): re-measure the element:button icon record at objectui 9602dc820 --- packages/spec/src/ui/component.test.ts | 41 +++++++++++++++------- packages/spec/src/ui/component.zod.ts | 47 ++++++++++++++++---------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/packages/spec/src/ui/component.test.ts b/packages/spec/src/ui/component.test.ts index 6ac1b916ba..d29b9549db 100644 --- a/packages/spec/src/ui/component.test.ts +++ b/packages/spec/src/ui/component.test.ts @@ -2433,22 +2433,29 @@ describe('#7751 — object-* block props schemas', () => { // that sent #9397 on a full dispatch cycle re-deriving the accordion read point. // #9881 and #9972 recorded the accordion and tab items; these two close the set. // -// Both re-measured at the pin this repo builds against — -// `.objectui-sha` = `190fbd01d`, re-derived there 2026-08-22 when the pin moved -// off `9a3daf8d3`. All four records (these two plus #9881/#9972 above) were -// re-counted at the new pin in the same pass and agree on it, as they did at -// the previous two moves: #10137 moved the pin while #9881/#9972 still cited -// `82a94170c`, and #10274 re-measured those four onto `9a3daf8d3`. The button -// anchors have been unchanged across every one of those moves — `button.tsx` -// is byte-identical at `9a3daf8d3` and `190fbd01d`. +// The button record re-measured at the pin this repo builds against — +// `.objectui-sha` = `9602dc820`, re-derived there 2026-08-28. This is the first +// move that changed the button READ POINT and not merely its line numbers: +// objectui#5993 deleted `button.tsx`'s file-local `toPascalCase` + +// `iconNameMap` + `icons` index and routed the button through the SHARED +// `resolveIcon` that every `action:*` site already used, so the resolution +// anchor now hops into `renderers/action/resolve-icon.ts`. What an author sees +// did not move with it: an unknown name still resolves to `null` and draws +// nothing, which is still the `LazyIcon` contrast the third test below pins. +// The earlier moves were line-number drift only — #10137 moved the pin while +// #9881/#9972 still cited `82a94170c`, #10274 re-measured those four onto +// `9a3daf8d3`, and `button.tsx` was byte-identical at `9a3daf8d3` and +// `190fbd01d`. describe('ElementButtonPropsSchema icon liveness (#10053)', () => { const button = ComponentPropsMap['element:button']; it('accepts an icon on a button — the value objectui resolves through the lucide `icons` map', () => { - // objectui `packages/components/src/renderers/form/button.tsx:44-47` - // PascalCases the name, applies its own one-entry rename map, and looks it - // up in `icons` from `lucide-react`; `:69` / `:71` draw it either side of - // the label per `iconPosition`. + // objectui `packages/components/src/renderers/form/button.tsx:36` hands the + // name to the shared `resolveIcon` + // (`packages/components/src/renderers/action/resolve-icon.ts:30-35`), which + // PascalCases it and applies the one-entry rename map at `:14-24` before + // looking it up in `icons` from `lucide-react`; `button.tsx:57` / `:59` + // draw it either side of the label per `iconPosition`. const result = button.safeParse({ label: 'Save', icon: 'arrow-right' }); expect(result.success).toBe(true); const parsed = (result.success ? result.data : undefined) as { icon?: string } | undefined; @@ -2468,12 +2475,20 @@ describe('ElementButtonPropsSchema icon liveness (#10053)', () => { it('keeps a `.describe()` that names the consumer AND the non-LazyIcon path', () => { // The second half is load-bearing, not decoration: this slot is the one // authorable icon on the surface that does NOT go through `LazyIcon`, so an - // author who assumes the shared resolver gets silence instead of a glyph. + // author who assumes `LazyIcon`'s tolerant fallback gets silence instead of + // a glyph. const shape = (ElementButtonPropsSchema as unknown as { def: { shape: Record }; }).def.shape; expect(shape.icon?.description).toContain('lucide-react'); expect(shape.icon?.description).toContain('LazyIcon'); + // And that the path is the SHARED one. Naming `resolveIcon` is what stops + // the describe drifting back to "its own normaliser": that sentence was + // true when the button carried a private copy of the algorithm, survived + // the copy's deletion unchanged, and shipped false to authors until this + // record was re-measured. The prose has to name the function, not just the + // library, for a reader to be able to check it. + expect(shape.icon?.description).toContain('resolveIcon'); }); }); diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index f208800892..e86ff735c5 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -1822,33 +1822,46 @@ export const ElementButtonPropsSchema = lazySchema(() => strictObject({ * the button. * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `190fbd01d`; re-derived at that pin 2026-08-22 — - * `button.tsx` is byte-identical to the one at `9a3daf8d3`, so every anchor - * below is unmoved): `components/src/renderers/form/ - * button.tsx:44-47` resolves `schema.icon`, and `:69` / `:71` render it on - * either side of the label per `iconPosition` (`mr-2 h-4 w-4` left, - * `ml-2 h-4 w-4` right), both suppressed while `loading`. + * (`.objectui-sha` = `9602dc820`; re-derived at that pin 2026-08-28 — the + * read point MOVED rather than died, and the anchors below now hop into a + * second file): `components/src/renderers/form/ + * button.tsx:36` resolves `schema.icon` through the shared `resolveIcon`, + * and `:57` / `:59` render it on either side of the label per + * `iconPosition` (`mr-2 h-4 w-4` left, `ml-2 h-4 w-4` right), both + * suppressed while `loading`. * * ⚠️ The resolution path is NOT `LazyIcon`, the slot the container icons on - * this surface use — it is a second, older path with its own normaliser and - * its own rename map, and the two accept different spellings: - * - here: `toPascalCase` (splits on `-` only) → a one-entry rename map - * (`Home` → `House`) → `icons[name]` from `lucide-react` - * (`button.tsx:14-27`). An unknown name resolves to `undefined` and the - * button renders with NO icon and no diagnostic anywhere. + * this surface use — it is the `action:*` resolver, and the two accept + * different spellings: + * - here: `resolveIcon` + * (`components/src/renderers/action/resolve-icon.ts:30-35`) → + * `toPascalCase`, which splits on `-` only, then a one-entry rename map + * (`Home` → `House`), both at `:14-24` → `icons[name]` from + * `lucide-react`. An unknown name resolves to `null` and the button + * renders with NO icon and no diagnostic anywhere. * - `LazyIcon` / `getLazyIcon` (`components/src/lib/lazy-icon.tsx:66-92`): * normalises to kebab-case, checks the name against Lucide's own name * list, and degrades an unknown name to the `Database` glyph. * So a spelling that draws an icon in a tab trigger can draw nothing here. * + * That the resolver is SHARED is what this record most recently had to be + * corrected for: until objectui#5993 `button.tsx` carried its own + * `toPascalCase` + `iconNameMap` + `icons` index — the same algorithm, but + * not the same function, so a rename added to `resolve-icon.ts` to absorb a + * lucide retirement (objectui#5586, objectui#5622) reached every `action:*` + * site and silently missed this one. Removing the duplicate changed where + * the algorithm lives, not what an author may write: the accept/reject + * behaviour promised above is the same on both sides of that move. + * * Also measured at the same pin: the renderer's registration publishes no - * `icon` input (`button.tsx:82-98` lists `label`, `variant`, `size`, - * `className`), so the Studio block designer does not offer the key. - * Unpublished is not unread — the header `icon` above is refused for the - * second, not the first, and this docblock exists to hold them apart. + * `icon` input (`button.tsx:70-87` lists `label`, `variant`, `size`, + * `className`; `:88-92` is `defaultProps`), so the Studio block designer + * does not offer the key. Unpublished is not unread — the header `icon` + * above is refused for the second, not the first, and this docblock exists + * to hold them apart. */ icon: z.string().optional().describe( - 'Lucide icon name rendered inside the button, left or right of the label per `iconPosition`. Read on this component — the renderer resolves it through `lucide-react`\'s `icons` map using its own PascalCase normaliser and rename map, NOT the `LazyIcon` slot the container icons use; the two paths accept different spellings, and an unknown name here renders nothing rather than a fallback glyph.', + 'Lucide icon name rendered inside the button, left or right of the label per `iconPosition`. Read on this component — the renderer resolves it through `lucide-react`\'s `icons` map via the shared `resolveIcon` helper every `action:*` site uses (a PascalCase normaliser plus a one-entry rename map), NOT the `LazyIcon` slot the container icons use; the two paths accept different spellings, and an unknown name here renders nothing rather than a fallback glyph.', ), iconPosition: z.enum(['left', 'right']) .optional().default('left').describe('Icon position relative to label'), From 5593b54e3680285b20e02d89348ba647e1d0dfae Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 17:45:51 +0000 Subject: [PATCH 4/4] fix(spec): re-measure the element:button icon record at objectui 9602dc820 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `.describe()` on `ElementButtonPropsSchema.icon` told authors the renderer resolved the name "using its own PascalCase normaliser and rename map". That clause went false when objectui#5993 deleted `button.tsx`'s file-local `toPascalCase` + `iconNameMap` + `icons` index and routed the button through the shared `resolveIcon` every `action:*` site already used — the duplicate was itself the defect, since a rename added to `resolve-icon.ts` to absorb a lucide retirement reached every action site and silently missed `ui:button`. Correct the shipped prose, re-point both read-point records' anchors at the new pin (the resolution anchor now hops into `renderers/action/resolve-icon.ts`), and move exactly those two citations to `.objectui-sha` = `9602dc820`. Nothing an author may write changed: the key is still read, still rendered per `iconPosition`, still suppressed while `loading`, and an unknown name still resolves to nothing rather than a fallback glyph. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 --- .changeset/button-icon-describe-shared-resolver.md | 9 +++++++++ content/docs/references/ui/component.mdx | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .changeset/button-icon-describe-shared-resolver.md diff --git a/.changeset/button-icon-describe-shared-resolver.md b/.changeset/button-icon-describe-shared-resolver.md new file mode 100644 index 0000000000..cc45867d21 --- /dev/null +++ b/.changeset/button-icon-describe-shared-resolver.md @@ -0,0 +1,9 @@ +--- +"@objectstack/spec": patch +--- + +`element:button`'s `icon` description now names the shared icon resolver the renderer actually uses + +The `.describe()` on `ElementButtonPropsSchema.icon` — the sentence the docs site and the generated skill artifacts put in front of authors — said the renderer resolved the name "using its own PascalCase normaliser and rename map". That stopped being true when objectui moved the button off its file-local copy of that algorithm and onto the `resolveIcon` helper every `action:*` site already shared. The duplicate had been a defect in its own right: a rename added to the shared resolver to absorb a Lucide icon retirement reached every action site and silently missed `ui:button`. + +Nothing an author may write changed, and nothing about the platform's behaviour regressed. `icon` is still read, still rendered on either side of the label per `iconPosition`, still suppressed while `loading`, and an unknown name still resolves to nothing rather than degrading to a fallback glyph — which is still exactly what separates this slot from the `LazyIcon` path the container icons use. Only the sentence describing where the resolution lives had gone stale, and it is corrected here together with the cross-repo read-point anchors recorded beside it, re-measured at objectui `9602dc820450`. diff --git a/content/docs/references/ui/component.mdx b/content/docs/references/ui/component.mdx index d4fe79a5f8..1832baa7e3 100644 --- a/content/docs/references/ui/component.mdx +++ b/content/docs/references/ui/component.mdx @@ -54,7 +54,7 @@ const result = AIChatWindowProps.parse(data); | **label** | `string \| Record` | ✅ | Button display label | | **variant** | `Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'link'>` | optional (default: `"primary"`) | Button visual variant | | **size** | `Enum<'small' \| 'medium' \| 'large'>` | optional (default: `"medium"`) | Button size | -| **icon** | `string` | optional | Lucide icon name rendered inside the button, left or right of the label per `iconPosition`. Read on this component — the renderer resolves it through `lucide-react`'s `icons` map using its own PascalCase normaliser and rename map, NOT the `LazyIcon` slot the container icons use; the two paths accept different spellings, and an unknown name here renders nothing rather than a fallback glyph. | +| **icon** | `string` | optional | Lucide icon name rendered inside the button, left or right of the label per `iconPosition`. Read on this component — the renderer resolves it through `lucide-react`'s `icons` map via the shared `resolveIcon` helper every `action:*` site uses (a PascalCase normaliser plus a one-entry rename map), NOT the `LazyIcon` slot the container icons use; the two paths accept different spellings, and an unknown name here renders nothing rather than a fallback glyph. | | **iconPosition** | `Enum<'left' \| 'right'>` | optional (default: `"left"`) | Icon position relative to label | | **disabled** | `boolean` | optional (default: `false`) | Disable the button | | **action** | `{ type?: Enum<'script' \| 'url' \| 'modal' \| 'flow' \| 'api' \| 'form'>; name?: string; label?: string \| Record; target?: string; … }` | optional | Inline action executed on click |