fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182) - #7381

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids
Sep 2, 2026
Merged

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182)#7381
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids

Conversation

@os-project-manager

@os-project-manageros-project-manager commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7182

Ruling implemented

Maintainer ruling 2026-09-02 (issue comment 5509039514, decision batch #7, verbatim 「7189 A 其他同意」 — "其他同意" adopts this card's recommendation C): an actions array on page:header or record:quick_actions is either all action ids or all inline ActionDef objects; a mixed ['convert', { … }] array is refused loudly, naming the offending index, not tolerated by one renderer and half-drawn by the other. One pure resolveDeclaredActionIds(elements, registeredActions) lives in @object-ui/types beside actionRendersAt and both renderers call it; the whole-array switch in record-quick-actions.tsx and the per-element normalisation in containers.tsx are both gone.

Clause-② is YES per the ruling, so this PR is a draft carrying the reviewable contract increment and is labelled needs:contract-review. Contract review returned PASS with two text amendments and one surface question; all three are addressed in the patch round below. Not flipped ready, not enqueued — the PM does that after re-reading the changeset.

What changed

  • packages/types/src/ui-action.tsresolveDeclaredActionIds(elements, registeredActions): resolution by name in authored order, first registration wins on a duplicate name; ids that name nothing come back as index/id pairs for the caller to warn about once its own lookup has settled. Called with registeredActionsundefined (no registry yet) it returns the registry-independent verdict — kind and ids final, everything unresolved — which is what both renderers read before deciding whether to request a metadata read at all. Result types DeclaredActionsResolution and DeclaredActionsRefusal. The shape classifier and its shape type are module-internal (patch round, see below). Zero-dependency, typed on unknown[] for the same reason actionRendersAt types locations as plain strings. The element rule is closed: a string is an id, a non-null non-array object is an inline definition, anything else (null, a number, a boolean, a nested array) is refused at its index too. An empty array passes through as an empty object list.
  • packages/types/src/index.ts — barrel exports the one function (beside actionRendersAt) and the two result types. One-authority gate: none of the names had a prior declaration in packages/, scripts/__tests__/one-authority-per-exported-name-6273.test.ts is green on HEAD, KNOWN_COLLISIONS untouched.
  • packages/components/src/renderers/layout/containers.tsx (page:header) — headerActionIds and the per-element loop are replaced by the shared function (once with no registry for the hook-order question, once resolved). A refused array is reported once via console.error (surface, index, the function's message) and none of its authored actions is drawn; host-injected system actions still render. The unresolved-id warning keeps its wording and stays gated on the lookup having settled.
  • packages/plugin-detail/src/renderers/record-quick-actions.tsx (record:quick_actions) — the every(string) whole-array switch is replaced by the shared function, for both actionNames (the spec spelling) and host actions. A refused array is reported once via console.error and the empty-state placeholder reads … actions refused at index N (see console) instead of "no actions configured".
  • Three further behaviour changes ride on the one rule, all on published packages and now named in the changeset:page:header no longer trims a padded id (' convert ' used to resolve; ids are now compared exactly as authored); page:header reports a blank '' id through the unresolved-id warning instead of silently skipping it; record:quick_actions renders nothing (its ordinary empty placeholder) for an all-id actions array with no object bound instead of handing the bare strings to the action engine as action definitions.
  • Tests — packages/types/src/__tests__/declared-action-ids-7182.test.ts (15 function cases, including the registry-independent verdict and a pin that the classifier is NOT on the barrel); packages/components/src/__tests__/page-header-action-ids.test.tsx (the mixed-array tolerance case is REPLACED by the refusal pin — it pinned the branch this PR deletes; the all-object case also asserts no metadata read); packages/plugin-detail/src/renderers/__tests__/record-quick-actions.declared-action-ids-7182.test.tsx (id/object equivalence with the object render as live control, actionNames, mixed refusal under both keys, typo-vs-refusal control); packages/plugin-detail/src/__tests__/declaredActionIds.twoRenderers-7182.test.tsx (both renderers in one process).
  • .changeset/7182-declared-action-ids-one-rule.mdminor on @object-ui/types, @object-ui/components, @object-ui/plugin-detail; headline in the ruling's own wording; the three behaviour changes above named in a closing paragraph.
  • content/docs/guide/slotted-pages.md — the paragraph that taught the mixed-array tolerance now states the rule.

Validation layer — measured (dispatch ZONE 2, assumption 3), corrected in the patch round

Measured on @objectstack/spec 17.2.0 with ComponentPropsMap safeParse:

  • page:header: PageHeaderProps.actions is z.array(z.string()). A mixed array is refused at path actions, 1 (invalid_type: expected string, received object); an ALL-object array is refused too, at actions, 0. The spec is stricter than C.
  • record:quick_actions: RecordQuickActionsProps is strict and declares actionNames (ids) only. Any actions key is refused as unrecognized regardless of shape; a mixed actionNames is refused at actionNames, 1. The actions array reaches this renderer only through host channels (layout:page-header delegation, the page synthesizer).

Correction (contract review, re-measured on origin/main): an earlier version of this body said Studio's clientValidation reads that map. It does not. clientValidation validates a page through PageSchema, whose component properties is z.record(z.string(), z.unknown()) — an open bag — and ComponentPropsMap has zero code consumers in objectui (0 non-test hits under packages/*/src and apps/*/src; validateComponentProps appears only in comments). Consequence, stated plainly: for a Studio-authored page, this PR's runtime refusal is the only refusal there is. The spec-layer refusal exists as a contract statement and reaches an author only where something parses ComponentPropsMap, which nothing in this repo does today. The refusal is a structured return from the pure function, reported by each renderer; no renderer throws.

Pins — each named, red then green

Method for the red legs (run on the original commit 7d692df; the patch round changed only the pre-lookup call site and the exports, no resolution or refusal logic), both from the committed tree with an EXIT/INT/TERM trap restoring by absolute path, restore verified by git diff HEAD empty and blob hashes equal to HEAD's: leg A = both renderers restored from BASE d53e472 (pre-fix) under the new function and the new pins; leg B = the ids arm of resolveDeclaredActionIds ablated (byName.get(id) replaced by undefined, marker ABLATION_7182_MARKER; on-disk confirmed grep -c marker = 1, original line = 0). The reviewer's independent leg C (objects arm removed, 7 red) confirms pin 3 is a genuine control. Tests resolve @object-ui/types from src through the root vitest alias, so the src mutation is what the tests read; the built components bundle is measured separately by the dist pin.

  1. Mixed array refused, message naming the offending index — leg A: 4 red (header refusal pin; bar refusal pins under actions and actionNames; two-renderer refusal pin). The pre-fix bar failed inside ActionEngine.registerAction on the bare string — the live defect, measured. HEAD: green.
  2. All-id array resolves through the shared function in BOTH renderers — leg B: red in both (header: 7 id-path cases; bar: id equivalence and the actionNames case; two-renderer id case; plus 3 function cases = 13 red). HEAD: green.
  3. All-object array passes through in BOTH — green in leg A, leg B and HEAD; red in the reviewer's leg C. Pre-existing behaviour, the live control.
  4. Same buttons for the same authored array, object render as live controldeclaredActionIds.twoRenderers-7182.test.tsx: header control equals bar control (population inside the intersection of the two chains), header ids equal header control, bar ids equal bar control, header ids equal bar ids. Red in leg B, green on HEAD.

Patch round (contract review PASS with amendments)

  • Required 1 — changeset now names the three behaviour changes (padded id, blank id, unbound all-id array).
  • Required 2 — the clientValidation claim corrected above, with the consequence recorded.
  • Surface question — measured answer: there is no need for classifyDeclaredActions or DeclaredActionsShape to be public. The renderers' only pre-lookup need is kind and ids, which resolveDeclaredActionIds(elements, undefined) returns; both renderers now call that, the classifier and its type are module-internal, the barrel exports one function and two types, and the types test pins the classifier's absence from the barrel.
  • Base synced by merge (origin/main1688986, merge commit b1d670b; none of the 19 files overlap this PR's files) — merge, not rebase, per the repo's no-force-push rule.

Verification on HEAD 189f71c (union run after the patch commit)

  • Dependency closure built first: turbo run build --filter='@object-ui/plugin-detail^...', 11/11 — packages/types/dist rebuilt WITHOUT the classifier (0 hits for classifyDeclaredActions in dist/index.d.ts and dist/index.js), packages/components/dist/index.js carries the actions refused at index marker.
  • vitest from the repo root under the shared verify lock: the 4 pin files + scripts/__tests__/one-authority-per-exported-name-6273.test.tsTest Files 5 passed (5), Tests 42 passed (42), exit 0.
  • dist pin: OBJECTUI_DIST_PINS=1 vitest run --project distTest Files 1 passed (1), Tests 3 passed (3), exit 0.
  • type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-detail — exit 0 each, script lines echoed; the types test program lists the test file (--listFiles, 1 hit).
  • eslint per package on every touched file — 0 errors (warnings are pre-existing anys).
  • check:control-bytes ✅ · check:phantom-deps ✅ · check-changeset-presence ✅ (8 source files of 3 released packages, 1 changeset) · check-changeset-no-major ✅. Earlier round on 7d692df, unchanged by this diff: check:self-import ✅ · check:doc-fences ✅ · check:doc-types ✅ · check:action-forward-parity ✅.
  • NOT MEASURED locally, precondition not met by the gates' own wording: check:readme-exports (unbuilt packages, population collapsed), check:doc-snippets (needs the full snippet build; the doc change is prose only), check:eager-closure (needs the console vite build; no new package edge). Repo-wide pnpm lint and the full test farm are CI's.

Not in this PR

  • hotcrm#1279 (the consumer rewrite of four record_header arrays to ids) remains pm:blocked on the published artefact — not a rider.
  • Retiring the inline-object tolerance itself once hotcrm is all ids — the ruling's Restart-when, its own future card.
  • objectui#7382 — record:alert's single-id CTA lookup, a third hand-written copy of the by-name mechanism at arity one; filed as a finding, not touched here.

Implemented by the objectui#7182 dev seat, session https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC (written in prose because a PR edit degrades the footer form).

🤖 Generated with Claude Code


Generated by Claude Code

…ays, mixed id/object refused
Maintainer ruling 2026-09-02 (objectui#7182, option C): an `actions` array on
`page:header` or `record:quick_actions` is either all action ids or all inline
ActionDef objects; a mixed array is refused loudly, naming the offending index.
- `@object-ui/types`: pure `resolveDeclaredActionIds(elements, registered)`
beside `actionRendersAt`, with its shape half `classifyDeclaredActions` and
the `DeclaredActionsShape` / `DeclaredActionsResolution` /
`DeclaredActionsRefusal` result types.
- `page:header` (containers.tsx): the per-element normalisation is gone; the
header calls the shared function, refuses a mixed array (console.error once,
nothing authored drawn), warns on unresolved ids as before.
- `record:quick_actions`: the whole-array `every(string)` switch is gone; the
bar calls the shared function for both `actionNames` and host `actions`,
refuses a mixed array (console.error once, placeholder names the index).
- Pins: function cases in types; the header's mixed-array tolerance case is
replaced by a refusal pin; a quick_actions id/object equivalence + refusal
pin; a two-renderer pin proving both surfaces draw the same buttons for the
same id-authored array and refuse the same mixed array.
- Changeset (minor, types/components/plugin-detail) and the slotted-pages
guide paragraph.
Refs: objectui#7182 (ruling comment 5509039514), objectstack#11592.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BCevB0zE.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.90KB117.51KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.22KB64.19KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.77KB2.26KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.00KB3.28KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…ngeset names three behaviour changes
Contract review on PR #7381 (objectui#7182), patch round:
- `classifyDeclaredActions` and `DeclaredActionsShape` are no longer exported:
the renderers' pre-lookup need (kind + ids before the registry exists) is
served by `resolveDeclaredActionIds(elements, undefined)`, so both renderers
now call that; a second public function and third public type would be a
permanent surface for a need the first already serves. The types test pins
the classifier's absence from the barrel.
- Changeset names the three behaviour changes the review found unannounced:
page:header no longer trims a padded id, warns on a blank id instead of
skipping it; record:quick_actions renders nothing for an all-id array with
no object bound instead of handing bare strings to the engine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-rqzKpJmU.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.20KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(components/plugin-detail): the action-id → ActionDef lookup now exists twice, and the two copies disagree about mixed arrays

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182) - #7381

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids
Sep 2, 2026
Merged

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182)#7381
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids

Conversation

@os-project-manager

@os-project-manageros-project-manager commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7182

Ruling implemented

Maintainer ruling 2026-09-02 (issue comment 5509039514, decision batch #7, verbatim 「7189 A 其他同意」 — "其他同意" adopts this card's recommendation C): an actions array on page:header or record:quick_actions is either all action ids or all inline ActionDef objects; a mixed ['convert', { … }] array is refused loudly, naming the offending index, not tolerated by one renderer and half-drawn by the other. One pure resolveDeclaredActionIds(elements, registeredActions) lives in @object-ui/types beside actionRendersAt and both renderers call it; the whole-array switch in record-quick-actions.tsx and the per-element normalisation in containers.tsx are both gone.

Clause-② is YES per the ruling, so this PR is a draft carrying the reviewable contract increment and is labelled needs:contract-review. Contract review returned PASS with two text amendments and one surface question; all three are addressed in the patch round below. Not flipped ready, not enqueued — the PM does that after re-reading the changeset.

What changed

  • packages/types/src/ui-action.tsresolveDeclaredActionIds(elements, registeredActions): resolution by name in authored order, first registration wins on a duplicate name; ids that name nothing come back as index/id pairs for the caller to warn about once its own lookup has settled. Called with registeredActionsundefined (no registry yet) it returns the registry-independent verdict — kind and ids final, everything unresolved — which is what both renderers read before deciding whether to request a metadata read at all. Result types DeclaredActionsResolution and DeclaredActionsRefusal. The shape classifier and its shape type are module-internal (patch round, see below). Zero-dependency, typed on unknown[] for the same reason actionRendersAt types locations as plain strings. The element rule is closed: a string is an id, a non-null non-array object is an inline definition, anything else (null, a number, a boolean, a nested array) is refused at its index too. An empty array passes through as an empty object list.
  • packages/types/src/index.ts — barrel exports the one function (beside actionRendersAt) and the two result types. One-authority gate: none of the names had a prior declaration in packages/, scripts/__tests__/one-authority-per-exported-name-6273.test.ts is green on HEAD, KNOWN_COLLISIONS untouched.
  • packages/components/src/renderers/layout/containers.tsx (page:header) — headerActionIds and the per-element loop are replaced by the shared function (once with no registry for the hook-order question, once resolved). A refused array is reported once via console.error (surface, index, the function's message) and none of its authored actions is drawn; host-injected system actions still render. The unresolved-id warning keeps its wording and stays gated on the lookup having settled.
  • packages/plugin-detail/src/renderers/record-quick-actions.tsx (record:quick_actions) — the every(string) whole-array switch is replaced by the shared function, for both actionNames (the spec spelling) and host actions. A refused array is reported once via console.error and the empty-state placeholder reads … actions refused at index N (see console) instead of "no actions configured".
  • Three further behaviour changes ride on the one rule, all on published packages and now named in the changeset:page:header no longer trims a padded id (' convert ' used to resolve; ids are now compared exactly as authored); page:header reports a blank '' id through the unresolved-id warning instead of silently skipping it; record:quick_actions renders nothing (its ordinary empty placeholder) for an all-id actions array with no object bound instead of handing the bare strings to the action engine as action definitions.
  • Tests — packages/types/src/__tests__/declared-action-ids-7182.test.ts (15 function cases, including the registry-independent verdict and a pin that the classifier is NOT on the barrel); packages/components/src/__tests__/page-header-action-ids.test.tsx (the mixed-array tolerance case is REPLACED by the refusal pin — it pinned the branch this PR deletes; the all-object case also asserts no metadata read); packages/plugin-detail/src/renderers/__tests__/record-quick-actions.declared-action-ids-7182.test.tsx (id/object equivalence with the object render as live control, actionNames, mixed refusal under both keys, typo-vs-refusal control); packages/plugin-detail/src/__tests__/declaredActionIds.twoRenderers-7182.test.tsx (both renderers in one process).
  • .changeset/7182-declared-action-ids-one-rule.mdminor on @object-ui/types, @object-ui/components, @object-ui/plugin-detail; headline in the ruling's own wording; the three behaviour changes above named in a closing paragraph.
  • content/docs/guide/slotted-pages.md — the paragraph that taught the mixed-array tolerance now states the rule.

Validation layer — measured (dispatch ZONE 2, assumption 3), corrected in the patch round

Measured on @objectstack/spec 17.2.0 with ComponentPropsMap safeParse:

  • page:header: PageHeaderProps.actions is z.array(z.string()). A mixed array is refused at path actions, 1 (invalid_type: expected string, received object); an ALL-object array is refused too, at actions, 0. The spec is stricter than C.
  • record:quick_actions: RecordQuickActionsProps is strict and declares actionNames (ids) only. Any actions key is refused as unrecognized regardless of shape; a mixed actionNames is refused at actionNames, 1. The actions array reaches this renderer only through host channels (layout:page-header delegation, the page synthesizer).

Correction (contract review, re-measured on origin/main): an earlier version of this body said Studio's clientValidation reads that map. It does not. clientValidation validates a page through PageSchema, whose component properties is z.record(z.string(), z.unknown()) — an open bag — and ComponentPropsMap has zero code consumers in objectui (0 non-test hits under packages/*/src and apps/*/src; validateComponentProps appears only in comments). Consequence, stated plainly: for a Studio-authored page, this PR's runtime refusal is the only refusal there is. The spec-layer refusal exists as a contract statement and reaches an author only where something parses ComponentPropsMap, which nothing in this repo does today. The refusal is a structured return from the pure function, reported by each renderer; no renderer throws.

Pins — each named, red then green

Method for the red legs (run on the original commit 7d692df; the patch round changed only the pre-lookup call site and the exports, no resolution or refusal logic), both from the committed tree with an EXIT/INT/TERM trap restoring by absolute path, restore verified by git diff HEAD empty and blob hashes equal to HEAD's: leg A = both renderers restored from BASE d53e472 (pre-fix) under the new function and the new pins; leg B = the ids arm of resolveDeclaredActionIds ablated (byName.get(id) replaced by undefined, marker ABLATION_7182_MARKER; on-disk confirmed grep -c marker = 1, original line = 0). The reviewer's independent leg C (objects arm removed, 7 red) confirms pin 3 is a genuine control. Tests resolve @object-ui/types from src through the root vitest alias, so the src mutation is what the tests read; the built components bundle is measured separately by the dist pin.

  1. Mixed array refused, message naming the offending index — leg A: 4 red (header refusal pin; bar refusal pins under actions and actionNames; two-renderer refusal pin). The pre-fix bar failed inside ActionEngine.registerAction on the bare string — the live defect, measured. HEAD: green.
  2. All-id array resolves through the shared function in BOTH renderers — leg B: red in both (header: 7 id-path cases; bar: id equivalence and the actionNames case; two-renderer id case; plus 3 function cases = 13 red). HEAD: green.
  3. All-object array passes through in BOTH — green in leg A, leg B and HEAD; red in the reviewer's leg C. Pre-existing behaviour, the live control.
  4. Same buttons for the same authored array, object render as live controldeclaredActionIds.twoRenderers-7182.test.tsx: header control equals bar control (population inside the intersection of the two chains), header ids equal header control, bar ids equal bar control, header ids equal bar ids. Red in leg B, green on HEAD.

Patch round (contract review PASS with amendments)

  • Required 1 — changeset now names the three behaviour changes (padded id, blank id, unbound all-id array).
  • Required 2 — the clientValidation claim corrected above, with the consequence recorded.
  • Surface question — measured answer: there is no need for classifyDeclaredActions or DeclaredActionsShape to be public. The renderers' only pre-lookup need is kind and ids, which resolveDeclaredActionIds(elements, undefined) returns; both renderers now call that, the classifier and its type are module-internal, the barrel exports one function and two types, and the types test pins the classifier's absence from the barrel.
  • Base synced by merge (origin/main1688986, merge commit b1d670b; none of the 19 files overlap this PR's files) — merge, not rebase, per the repo's no-force-push rule.

Verification on HEAD 189f71c (union run after the patch commit)

  • Dependency closure built first: turbo run build --filter='@object-ui/plugin-detail^...', 11/11 — packages/types/dist rebuilt WITHOUT the classifier (0 hits for classifyDeclaredActions in dist/index.d.ts and dist/index.js), packages/components/dist/index.js carries the actions refused at index marker.
  • vitest from the repo root under the shared verify lock: the 4 pin files + scripts/__tests__/one-authority-per-exported-name-6273.test.tsTest Files 5 passed (5), Tests 42 passed (42), exit 0.
  • dist pin: OBJECTUI_DIST_PINS=1 vitest run --project distTest Files 1 passed (1), Tests 3 passed (3), exit 0.
  • type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-detail — exit 0 each, script lines echoed; the types test program lists the test file (--listFiles, 1 hit).
  • eslint per package on every touched file — 0 errors (warnings are pre-existing anys).
  • check:control-bytes ✅ · check:phantom-deps ✅ · check-changeset-presence ✅ (8 source files of 3 released packages, 1 changeset) · check-changeset-no-major ✅. Earlier round on 7d692df, unchanged by this diff: check:self-import ✅ · check:doc-fences ✅ · check:doc-types ✅ · check:action-forward-parity ✅.
  • NOT MEASURED locally, precondition not met by the gates' own wording: check:readme-exports (unbuilt packages, population collapsed), check:doc-snippets (needs the full snippet build; the doc change is prose only), check:eager-closure (needs the console vite build; no new package edge). Repo-wide pnpm lint and the full test farm are CI's.

Not in this PR

  • hotcrm#1279 (the consumer rewrite of four record_header arrays to ids) remains pm:blocked on the published artefact — not a rider.
  • Retiring the inline-object tolerance itself once hotcrm is all ids — the ruling's Restart-when, its own future card.
  • objectui#7382 — record:alert's single-id CTA lookup, a third hand-written copy of the by-name mechanism at arity one; filed as a finding, not touched here.

Implemented by the objectui#7182 dev seat, session https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC (written in prose because a PR edit degrades the footer form).

🤖 Generated with Claude Code


Generated by Claude Code

…ays, mixed id/object refused
Maintainer ruling 2026-09-02 (objectui#7182, option C): an `actions` array on
`page:header` or `record:quick_actions` is either all action ids or all inline
ActionDef objects; a mixed array is refused loudly, naming the offending index.
- `@object-ui/types`: pure `resolveDeclaredActionIds(elements, registered)`
beside `actionRendersAt`, with its shape half `classifyDeclaredActions` and
the `DeclaredActionsShape` / `DeclaredActionsResolution` /
`DeclaredActionsRefusal` result types.
- `page:header` (containers.tsx): the per-element normalisation is gone; the
header calls the shared function, refuses a mixed array (console.error once,
nothing authored drawn), warns on unresolved ids as before.
- `record:quick_actions`: the whole-array `every(string)` switch is gone; the
bar calls the shared function for both `actionNames` and host `actions`,
refuses a mixed array (console.error once, placeholder names the index).
- Pins: function cases in types; the header's mixed-array tolerance case is
replaced by a refusal pin; a quick_actions id/object equivalence + refusal
pin; a two-renderer pin proving both surfaces draw the same buttons for the
same id-authored array and refuse the same mixed array.
- Changeset (minor, types/components/plugin-detail) and the slotted-pages
guide paragraph.
Refs: objectui#7182 (ruling comment 5509039514), objectstack#11592.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BCevB0zE.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.90KB117.51KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.22KB64.19KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.77KB2.26KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.00KB3.28KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…ngeset names three behaviour changes
Contract review on PR #7381 (objectui#7182), patch round:
- `classifyDeclaredActions` and `DeclaredActionsShape` are no longer exported:
the renderers' pre-lookup need (kind + ids before the registry exists) is
served by `resolveDeclaredActionIds(elements, undefined)`, so both renderers
now call that; a second public function and third public type would be a
permanent surface for a need the first already serves. The types test pins
the classifier's absence from the barrel.
- Changeset names the three behaviour changes the review found unannounced:
page:header no longer trims a padded id, warns on a blank id instead of
skipping it; record:quick_actions renders nothing for an all-id array with
no object bound instead of handing bare strings to the engine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-rqzKpJmU.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.20KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(components/plugin-detail): the action-id → ActionDef lookup now exists twice, and the two copies disagree about mixed arrays

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182) - #7381

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids
Sep 2, 2026
Merged

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182)#7381
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids

Conversation

@os-project-manager

@os-project-manageros-project-manager commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7182

Ruling implemented

Maintainer ruling 2026-09-02 (issue comment 5509039514, decision batch #7, verbatim 「7189 A 其他同意」 — "其他同意" adopts this card's recommendation C): an actions array on page:header or record:quick_actions is either all action ids or all inline ActionDef objects; a mixed ['convert', { … }] array is refused loudly, naming the offending index, not tolerated by one renderer and half-drawn by the other. One pure resolveDeclaredActionIds(elements, registeredActions) lives in @object-ui/types beside actionRendersAt and both renderers call it; the whole-array switch in record-quick-actions.tsx and the per-element normalisation in containers.tsx are both gone.

Clause-② is YES per the ruling, so this PR is a draft carrying the reviewable contract increment and is labelled needs:contract-review. Contract review returned PASS with two text amendments and one surface question; all three are addressed in the patch round below. Not flipped ready, not enqueued — the PM does that after re-reading the changeset.

What changed

  • packages/types/src/ui-action.tsresolveDeclaredActionIds(elements, registeredActions): resolution by name in authored order, first registration wins on a duplicate name; ids that name nothing come back as index/id pairs for the caller to warn about once its own lookup has settled. Called with registeredActionsundefined (no registry yet) it returns the registry-independent verdict — kind and ids final, everything unresolved — which is what both renderers read before deciding whether to request a metadata read at all. Result types DeclaredActionsResolution and DeclaredActionsRefusal. The shape classifier and its shape type are module-internal (patch round, see below). Zero-dependency, typed on unknown[] for the same reason actionRendersAt types locations as plain strings. The element rule is closed: a string is an id, a non-null non-array object is an inline definition, anything else (null, a number, a boolean, a nested array) is refused at its index too. An empty array passes through as an empty object list.
  • packages/types/src/index.ts — barrel exports the one function (beside actionRendersAt) and the two result types. One-authority gate: none of the names had a prior declaration in packages/, scripts/__tests__/one-authority-per-exported-name-6273.test.ts is green on HEAD, KNOWN_COLLISIONS untouched.
  • packages/components/src/renderers/layout/containers.tsx (page:header) — headerActionIds and the per-element loop are replaced by the shared function (once with no registry for the hook-order question, once resolved). A refused array is reported once via console.error (surface, index, the function's message) and none of its authored actions is drawn; host-injected system actions still render. The unresolved-id warning keeps its wording and stays gated on the lookup having settled.
  • packages/plugin-detail/src/renderers/record-quick-actions.tsx (record:quick_actions) — the every(string) whole-array switch is replaced by the shared function, for both actionNames (the spec spelling) and host actions. A refused array is reported once via console.error and the empty-state placeholder reads … actions refused at index N (see console) instead of "no actions configured".
  • Three further behaviour changes ride on the one rule, all on published packages and now named in the changeset:page:header no longer trims a padded id (' convert ' used to resolve; ids are now compared exactly as authored); page:header reports a blank '' id through the unresolved-id warning instead of silently skipping it; record:quick_actions renders nothing (its ordinary empty placeholder) for an all-id actions array with no object bound instead of handing the bare strings to the action engine as action definitions.
  • Tests — packages/types/src/__tests__/declared-action-ids-7182.test.ts (15 function cases, including the registry-independent verdict and a pin that the classifier is NOT on the barrel); packages/components/src/__tests__/page-header-action-ids.test.tsx (the mixed-array tolerance case is REPLACED by the refusal pin — it pinned the branch this PR deletes; the all-object case also asserts no metadata read); packages/plugin-detail/src/renderers/__tests__/record-quick-actions.declared-action-ids-7182.test.tsx (id/object equivalence with the object render as live control, actionNames, mixed refusal under both keys, typo-vs-refusal control); packages/plugin-detail/src/__tests__/declaredActionIds.twoRenderers-7182.test.tsx (both renderers in one process).
  • .changeset/7182-declared-action-ids-one-rule.mdminor on @object-ui/types, @object-ui/components, @object-ui/plugin-detail; headline in the ruling's own wording; the three behaviour changes above named in a closing paragraph.
  • content/docs/guide/slotted-pages.md — the paragraph that taught the mixed-array tolerance now states the rule.

Validation layer — measured (dispatch ZONE 2, assumption 3), corrected in the patch round

Measured on @objectstack/spec 17.2.0 with ComponentPropsMap safeParse:

  • page:header: PageHeaderProps.actions is z.array(z.string()). A mixed array is refused at path actions, 1 (invalid_type: expected string, received object); an ALL-object array is refused too, at actions, 0. The spec is stricter than C.
  • record:quick_actions: RecordQuickActionsProps is strict and declares actionNames (ids) only. Any actions key is refused as unrecognized regardless of shape; a mixed actionNames is refused at actionNames, 1. The actions array reaches this renderer only through host channels (layout:page-header delegation, the page synthesizer).

Correction (contract review, re-measured on origin/main): an earlier version of this body said Studio's clientValidation reads that map. It does not. clientValidation validates a page through PageSchema, whose component properties is z.record(z.string(), z.unknown()) — an open bag — and ComponentPropsMap has zero code consumers in objectui (0 non-test hits under packages/*/src and apps/*/src; validateComponentProps appears only in comments). Consequence, stated plainly: for a Studio-authored page, this PR's runtime refusal is the only refusal there is. The spec-layer refusal exists as a contract statement and reaches an author only where something parses ComponentPropsMap, which nothing in this repo does today. The refusal is a structured return from the pure function, reported by each renderer; no renderer throws.

Pins — each named, red then green

Method for the red legs (run on the original commit 7d692df; the patch round changed only the pre-lookup call site and the exports, no resolution or refusal logic), both from the committed tree with an EXIT/INT/TERM trap restoring by absolute path, restore verified by git diff HEAD empty and blob hashes equal to HEAD's: leg A = both renderers restored from BASE d53e472 (pre-fix) under the new function and the new pins; leg B = the ids arm of resolveDeclaredActionIds ablated (byName.get(id) replaced by undefined, marker ABLATION_7182_MARKER; on-disk confirmed grep -c marker = 1, original line = 0). The reviewer's independent leg C (objects arm removed, 7 red) confirms pin 3 is a genuine control. Tests resolve @object-ui/types from src through the root vitest alias, so the src mutation is what the tests read; the built components bundle is measured separately by the dist pin.

  1. Mixed array refused, message naming the offending index — leg A: 4 red (header refusal pin; bar refusal pins under actions and actionNames; two-renderer refusal pin). The pre-fix bar failed inside ActionEngine.registerAction on the bare string — the live defect, measured. HEAD: green.
  2. All-id array resolves through the shared function in BOTH renderers — leg B: red in both (header: 7 id-path cases; bar: id equivalence and the actionNames case; two-renderer id case; plus 3 function cases = 13 red). HEAD: green.
  3. All-object array passes through in BOTH — green in leg A, leg B and HEAD; red in the reviewer's leg C. Pre-existing behaviour, the live control.
  4. Same buttons for the same authored array, object render as live controldeclaredActionIds.twoRenderers-7182.test.tsx: header control equals bar control (population inside the intersection of the two chains), header ids equal header control, bar ids equal bar control, header ids equal bar ids. Red in leg B, green on HEAD.

Patch round (contract review PASS with amendments)

  • Required 1 — changeset now names the three behaviour changes (padded id, blank id, unbound all-id array).
  • Required 2 — the clientValidation claim corrected above, with the consequence recorded.
  • Surface question — measured answer: there is no need for classifyDeclaredActions or DeclaredActionsShape to be public. The renderers' only pre-lookup need is kind and ids, which resolveDeclaredActionIds(elements, undefined) returns; both renderers now call that, the classifier and its type are module-internal, the barrel exports one function and two types, and the types test pins the classifier's absence from the barrel.
  • Base synced by merge (origin/main1688986, merge commit b1d670b; none of the 19 files overlap this PR's files) — merge, not rebase, per the repo's no-force-push rule.

Verification on HEAD 189f71c (union run after the patch commit)

  • Dependency closure built first: turbo run build --filter='@object-ui/plugin-detail^...', 11/11 — packages/types/dist rebuilt WITHOUT the classifier (0 hits for classifyDeclaredActions in dist/index.d.ts and dist/index.js), packages/components/dist/index.js carries the actions refused at index marker.
  • vitest from the repo root under the shared verify lock: the 4 pin files + scripts/__tests__/one-authority-per-exported-name-6273.test.tsTest Files 5 passed (5), Tests 42 passed (42), exit 0.
  • dist pin: OBJECTUI_DIST_PINS=1 vitest run --project distTest Files 1 passed (1), Tests 3 passed (3), exit 0.
  • type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-detail — exit 0 each, script lines echoed; the types test program lists the test file (--listFiles, 1 hit).
  • eslint per package on every touched file — 0 errors (warnings are pre-existing anys).
  • check:control-bytes ✅ · check:phantom-deps ✅ · check-changeset-presence ✅ (8 source files of 3 released packages, 1 changeset) · check-changeset-no-major ✅. Earlier round on 7d692df, unchanged by this diff: check:self-import ✅ · check:doc-fences ✅ · check:doc-types ✅ · check:action-forward-parity ✅.
  • NOT MEASURED locally, precondition not met by the gates' own wording: check:readme-exports (unbuilt packages, population collapsed), check:doc-snippets (needs the full snippet build; the doc change is prose only), check:eager-closure (needs the console vite build; no new package edge). Repo-wide pnpm lint and the full test farm are CI's.

Not in this PR

  • hotcrm#1279 (the consumer rewrite of four record_header arrays to ids) remains pm:blocked on the published artefact — not a rider.
  • Retiring the inline-object tolerance itself once hotcrm is all ids — the ruling's Restart-when, its own future card.
  • objectui#7382 — record:alert's single-id CTA lookup, a third hand-written copy of the by-name mechanism at arity one; filed as a finding, not touched here.

Implemented by the objectui#7182 dev seat, session https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC (written in prose because a PR edit degrades the footer form).

🤖 Generated with Claude Code


Generated by Claude Code

…ays, mixed id/object refused
Maintainer ruling 2026-09-02 (objectui#7182, option C): an `actions` array on
`page:header` or `record:quick_actions` is either all action ids or all inline
ActionDef objects; a mixed array is refused loudly, naming the offending index.
- `@object-ui/types`: pure `resolveDeclaredActionIds(elements, registered)`
beside `actionRendersAt`, with its shape half `classifyDeclaredActions` and
the `DeclaredActionsShape` / `DeclaredActionsResolution` /
`DeclaredActionsRefusal` result types.
- `page:header` (containers.tsx): the per-element normalisation is gone; the
header calls the shared function, refuses a mixed array (console.error once,
nothing authored drawn), warns on unresolved ids as before.
- `record:quick_actions`: the whole-array `every(string)` switch is gone; the
bar calls the shared function for both `actionNames` and host `actions`,
refuses a mixed array (console.error once, placeholder names the index).
- Pins: function cases in types; the header's mixed-array tolerance case is
replaced by a refusal pin; a quick_actions id/object equivalence + refusal
pin; a two-renderer pin proving both surfaces draw the same buttons for the
same id-authored array and refuse the same mixed array.
- Changeset (minor, types/components/plugin-detail) and the slotted-pages
guide paragraph.
Refs: objectui#7182 (ruling comment 5509039514), objectstack#11592.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BCevB0zE.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.90KB117.51KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.22KB64.19KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.77KB2.26KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.00KB3.28KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…ngeset names three behaviour changes
Contract review on PR #7381 (objectui#7182), patch round:
- `classifyDeclaredActions` and `DeclaredActionsShape` are no longer exported:
the renderers' pre-lookup need (kind + ids before the registry exists) is
served by `resolveDeclaredActionIds(elements, undefined)`, so both renderers
now call that; a second public function and third public type would be a
permanent surface for a need the first already serves. The types test pins
the classifier's absence from the barrel.
- Changeset names the three behaviour changes the review found unannounced:
page:header no longer trims a padded id, warns on a blank id instead of
skipping it; record:quick_actions renders nothing for an all-id array with
no object bound instead of handing bare strings to the engine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-rqzKpJmU.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.20KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(components/plugin-detail): the action-id → ActionDef lookup now exists twice, and the two copies disagree about mixed arrays

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182) - #7381

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids
Sep 2, 2026
Merged

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182)#7381
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids

Conversation

@os-project-manager

@os-project-manageros-project-manager commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7182

Ruling implemented

Maintainer ruling 2026-09-02 (issue comment 5509039514, decision batch #7, verbatim 「7189 A 其他同意」 — "其他同意" adopts this card's recommendation C): an actions array on page:header or record:quick_actions is either all action ids or all inline ActionDef objects; a mixed ['convert', { … }] array is refused loudly, naming the offending index, not tolerated by one renderer and half-drawn by the other. One pure resolveDeclaredActionIds(elements, registeredActions) lives in @object-ui/types beside actionRendersAt and both renderers call it; the whole-array switch in record-quick-actions.tsx and the per-element normalisation in containers.tsx are both gone.

Clause-② is YES per the ruling, so this PR is a draft carrying the reviewable contract increment and is labelled needs:contract-review. Contract review returned PASS with two text amendments and one surface question; all three are addressed in the patch round below. Not flipped ready, not enqueued — the PM does that after re-reading the changeset.

What changed

  • packages/types/src/ui-action.tsresolveDeclaredActionIds(elements, registeredActions): resolution by name in authored order, first registration wins on a duplicate name; ids that name nothing come back as index/id pairs for the caller to warn about once its own lookup has settled. Called with registeredActionsundefined (no registry yet) it returns the registry-independent verdict — kind and ids final, everything unresolved — which is what both renderers read before deciding whether to request a metadata read at all. Result types DeclaredActionsResolution and DeclaredActionsRefusal. The shape classifier and its shape type are module-internal (patch round, see below). Zero-dependency, typed on unknown[] for the same reason actionRendersAt types locations as plain strings. The element rule is closed: a string is an id, a non-null non-array object is an inline definition, anything else (null, a number, a boolean, a nested array) is refused at its index too. An empty array passes through as an empty object list.
  • packages/types/src/index.ts — barrel exports the one function (beside actionRendersAt) and the two result types. One-authority gate: none of the names had a prior declaration in packages/, scripts/__tests__/one-authority-per-exported-name-6273.test.ts is green on HEAD, KNOWN_COLLISIONS untouched.
  • packages/components/src/renderers/layout/containers.tsx (page:header) — headerActionIds and the per-element loop are replaced by the shared function (once with no registry for the hook-order question, once resolved). A refused array is reported once via console.error (surface, index, the function's message) and none of its authored actions is drawn; host-injected system actions still render. The unresolved-id warning keeps its wording and stays gated on the lookup having settled.
  • packages/plugin-detail/src/renderers/record-quick-actions.tsx (record:quick_actions) — the every(string) whole-array switch is replaced by the shared function, for both actionNames (the spec spelling) and host actions. A refused array is reported once via console.error and the empty-state placeholder reads … actions refused at index N (see console) instead of "no actions configured".
  • Three further behaviour changes ride on the one rule, all on published packages and now named in the changeset:page:header no longer trims a padded id (' convert ' used to resolve; ids are now compared exactly as authored); page:header reports a blank '' id through the unresolved-id warning instead of silently skipping it; record:quick_actions renders nothing (its ordinary empty placeholder) for an all-id actions array with no object bound instead of handing the bare strings to the action engine as action definitions.
  • Tests — packages/types/src/__tests__/declared-action-ids-7182.test.ts (15 function cases, including the registry-independent verdict and a pin that the classifier is NOT on the barrel); packages/components/src/__tests__/page-header-action-ids.test.tsx (the mixed-array tolerance case is REPLACED by the refusal pin — it pinned the branch this PR deletes; the all-object case also asserts no metadata read); packages/plugin-detail/src/renderers/__tests__/record-quick-actions.declared-action-ids-7182.test.tsx (id/object equivalence with the object render as live control, actionNames, mixed refusal under both keys, typo-vs-refusal control); packages/plugin-detail/src/__tests__/declaredActionIds.twoRenderers-7182.test.tsx (both renderers in one process).
  • .changeset/7182-declared-action-ids-one-rule.mdminor on @object-ui/types, @object-ui/components, @object-ui/plugin-detail; headline in the ruling's own wording; the three behaviour changes above named in a closing paragraph.
  • content/docs/guide/slotted-pages.md — the paragraph that taught the mixed-array tolerance now states the rule.

Validation layer — measured (dispatch ZONE 2, assumption 3), corrected in the patch round

Measured on @objectstack/spec 17.2.0 with ComponentPropsMap safeParse:

  • page:header: PageHeaderProps.actions is z.array(z.string()). A mixed array is refused at path actions, 1 (invalid_type: expected string, received object); an ALL-object array is refused too, at actions, 0. The spec is stricter than C.
  • record:quick_actions: RecordQuickActionsProps is strict and declares actionNames (ids) only. Any actions key is refused as unrecognized regardless of shape; a mixed actionNames is refused at actionNames, 1. The actions array reaches this renderer only through host channels (layout:page-header delegation, the page synthesizer).

Correction (contract review, re-measured on origin/main): an earlier version of this body said Studio's clientValidation reads that map. It does not. clientValidation validates a page through PageSchema, whose component properties is z.record(z.string(), z.unknown()) — an open bag — and ComponentPropsMap has zero code consumers in objectui (0 non-test hits under packages/*/src and apps/*/src; validateComponentProps appears only in comments). Consequence, stated plainly: for a Studio-authored page, this PR's runtime refusal is the only refusal there is. The spec-layer refusal exists as a contract statement and reaches an author only where something parses ComponentPropsMap, which nothing in this repo does today. The refusal is a structured return from the pure function, reported by each renderer; no renderer throws.

Pins — each named, red then green

Method for the red legs (run on the original commit 7d692df; the patch round changed only the pre-lookup call site and the exports, no resolution or refusal logic), both from the committed tree with an EXIT/INT/TERM trap restoring by absolute path, restore verified by git diff HEAD empty and blob hashes equal to HEAD's: leg A = both renderers restored from BASE d53e472 (pre-fix) under the new function and the new pins; leg B = the ids arm of resolveDeclaredActionIds ablated (byName.get(id) replaced by undefined, marker ABLATION_7182_MARKER; on-disk confirmed grep -c marker = 1, original line = 0). The reviewer's independent leg C (objects arm removed, 7 red) confirms pin 3 is a genuine control. Tests resolve @object-ui/types from src through the root vitest alias, so the src mutation is what the tests read; the built components bundle is measured separately by the dist pin.

  1. Mixed array refused, message naming the offending index — leg A: 4 red (header refusal pin; bar refusal pins under actions and actionNames; two-renderer refusal pin). The pre-fix bar failed inside ActionEngine.registerAction on the bare string — the live defect, measured. HEAD: green.
  2. All-id array resolves through the shared function in BOTH renderers — leg B: red in both (header: 7 id-path cases; bar: id equivalence and the actionNames case; two-renderer id case; plus 3 function cases = 13 red). HEAD: green.
  3. All-object array passes through in BOTH — green in leg A, leg B and HEAD; red in the reviewer's leg C. Pre-existing behaviour, the live control.
  4. Same buttons for the same authored array, object render as live controldeclaredActionIds.twoRenderers-7182.test.tsx: header control equals bar control (population inside the intersection of the two chains), header ids equal header control, bar ids equal bar control, header ids equal bar ids. Red in leg B, green on HEAD.

Patch round (contract review PASS with amendments)

  • Required 1 — changeset now names the three behaviour changes (padded id, blank id, unbound all-id array).
  • Required 2 — the clientValidation claim corrected above, with the consequence recorded.
  • Surface question — measured answer: there is no need for classifyDeclaredActions or DeclaredActionsShape to be public. The renderers' only pre-lookup need is kind and ids, which resolveDeclaredActionIds(elements, undefined) returns; both renderers now call that, the classifier and its type are module-internal, the barrel exports one function and two types, and the types test pins the classifier's absence from the barrel.
  • Base synced by merge (origin/main1688986, merge commit b1d670b; none of the 19 files overlap this PR's files) — merge, not rebase, per the repo's no-force-push rule.

Verification on HEAD 189f71c (union run after the patch commit)

  • Dependency closure built first: turbo run build --filter='@object-ui/plugin-detail^...', 11/11 — packages/types/dist rebuilt WITHOUT the classifier (0 hits for classifyDeclaredActions in dist/index.d.ts and dist/index.js), packages/components/dist/index.js carries the actions refused at index marker.
  • vitest from the repo root under the shared verify lock: the 4 pin files + scripts/__tests__/one-authority-per-exported-name-6273.test.tsTest Files 5 passed (5), Tests 42 passed (42), exit 0.
  • dist pin: OBJECTUI_DIST_PINS=1 vitest run --project distTest Files 1 passed (1), Tests 3 passed (3), exit 0.
  • type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-detail — exit 0 each, script lines echoed; the types test program lists the test file (--listFiles, 1 hit).
  • eslint per package on every touched file — 0 errors (warnings are pre-existing anys).
  • check:control-bytes ✅ · check:phantom-deps ✅ · check-changeset-presence ✅ (8 source files of 3 released packages, 1 changeset) · check-changeset-no-major ✅. Earlier round on 7d692df, unchanged by this diff: check:self-import ✅ · check:doc-fences ✅ · check:doc-types ✅ · check:action-forward-parity ✅.
  • NOT MEASURED locally, precondition not met by the gates' own wording: check:readme-exports (unbuilt packages, population collapsed), check:doc-snippets (needs the full snippet build; the doc change is prose only), check:eager-closure (needs the console vite build; no new package edge). Repo-wide pnpm lint and the full test farm are CI's.

Not in this PR

  • hotcrm#1279 (the consumer rewrite of four record_header arrays to ids) remains pm:blocked on the published artefact — not a rider.
  • Retiring the inline-object tolerance itself once hotcrm is all ids — the ruling's Restart-when, its own future card.
  • objectui#7382 — record:alert's single-id CTA lookup, a third hand-written copy of the by-name mechanism at arity one; filed as a finding, not touched here.

Implemented by the objectui#7182 dev seat, session https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC (written in prose because a PR edit degrades the footer form).

🤖 Generated with Claude Code


Generated by Claude Code

…ays, mixed id/object refused
Maintainer ruling 2026-09-02 (objectui#7182, option C): an `actions` array on
`page:header` or `record:quick_actions` is either all action ids or all inline
ActionDef objects; a mixed array is refused loudly, naming the offending index.
- `@object-ui/types`: pure `resolveDeclaredActionIds(elements, registered)`
beside `actionRendersAt`, with its shape half `classifyDeclaredActions` and
the `DeclaredActionsShape` / `DeclaredActionsResolution` /
`DeclaredActionsRefusal` result types.
- `page:header` (containers.tsx): the per-element normalisation is gone; the
header calls the shared function, refuses a mixed array (console.error once,
nothing authored drawn), warns on unresolved ids as before.
- `record:quick_actions`: the whole-array `every(string)` switch is gone; the
bar calls the shared function for both `actionNames` and host `actions`,
refuses a mixed array (console.error once, placeholder names the index).
- Pins: function cases in types; the header's mixed-array tolerance case is
replaced by a refusal pin; a quick_actions id/object equivalence + refusal
pin; a two-renderer pin proving both surfaces draw the same buttons for the
same id-authored array and refuse the same mixed array.
- Changeset (minor, types/components/plugin-detail) and the slotted-pages
guide paragraph.
Refs: objectui#7182 (ruling comment 5509039514), objectstack#11592.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BCevB0zE.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.90KB117.51KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.22KB64.19KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.77KB2.26KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.00KB3.28KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…ngeset names three behaviour changes
Contract review on PR #7381 (objectui#7182), patch round:
- `classifyDeclaredActions` and `DeclaredActionsShape` are no longer exported:
the renderers' pre-lookup need (kind + ids before the registry exists) is
served by `resolveDeclaredActionIds(elements, undefined)`, so both renderers
now call that; a second public function and third public type would be a
permanent surface for a need the first already serves. The types test pins
the classifier's absence from the barrel.
- Changeset names the three behaviour changes the review found unannounced:
page:header no longer trims a padded id, warns on a blank id instead of
skipping it; record:quick_actions renders nothing for an all-id array with
no object bound instead of handing bare strings to the engine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-rqzKpJmU.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.20KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(components/plugin-detail): the action-id → ActionDef lookup now exists twice, and the two copies disagree about mixed arrays

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182) - #7381

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids
Sep 2, 2026
Merged

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182)#7381
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids

Conversation

@os-project-manager

@os-project-manageros-project-manager commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7182

Ruling implemented

Maintainer ruling 2026-09-02 (issue comment 5509039514, decision batch #7, verbatim 「7189 A 其他同意」 — "其他同意" adopts this card's recommendation C): an actions array on page:header or record:quick_actions is either all action ids or all inline ActionDef objects; a mixed ['convert', { … }] array is refused loudly, naming the offending index, not tolerated by one renderer and half-drawn by the other. One pure resolveDeclaredActionIds(elements, registeredActions) lives in @object-ui/types beside actionRendersAt and both renderers call it; the whole-array switch in record-quick-actions.tsx and the per-element normalisation in containers.tsx are both gone.

Clause-② is YES per the ruling, so this PR is a draft carrying the reviewable contract increment and is labelled needs:contract-review. Contract review returned PASS with two text amendments and one surface question; all three are addressed in the patch round below. Not flipped ready, not enqueued — the PM does that after re-reading the changeset.

What changed

  • packages/types/src/ui-action.tsresolveDeclaredActionIds(elements, registeredActions): resolution by name in authored order, first registration wins on a duplicate name; ids that name nothing come back as index/id pairs for the caller to warn about once its own lookup has settled. Called with registeredActionsundefined (no registry yet) it returns the registry-independent verdict — kind and ids final, everything unresolved — which is what both renderers read before deciding whether to request a metadata read at all. Result types DeclaredActionsResolution and DeclaredActionsRefusal. The shape classifier and its shape type are module-internal (patch round, see below). Zero-dependency, typed on unknown[] for the same reason actionRendersAt types locations as plain strings. The element rule is closed: a string is an id, a non-null non-array object is an inline definition, anything else (null, a number, a boolean, a nested array) is refused at its index too. An empty array passes through as an empty object list.
  • packages/types/src/index.ts — barrel exports the one function (beside actionRendersAt) and the two result types. One-authority gate: none of the names had a prior declaration in packages/, scripts/__tests__/one-authority-per-exported-name-6273.test.ts is green on HEAD, KNOWN_COLLISIONS untouched.
  • packages/components/src/renderers/layout/containers.tsx (page:header) — headerActionIds and the per-element loop are replaced by the shared function (once with no registry for the hook-order question, once resolved). A refused array is reported once via console.error (surface, index, the function's message) and none of its authored actions is drawn; host-injected system actions still render. The unresolved-id warning keeps its wording and stays gated on the lookup having settled.
  • packages/plugin-detail/src/renderers/record-quick-actions.tsx (record:quick_actions) — the every(string) whole-array switch is replaced by the shared function, for both actionNames (the spec spelling) and host actions. A refused array is reported once via console.error and the empty-state placeholder reads … actions refused at index N (see console) instead of "no actions configured".
  • Three further behaviour changes ride on the one rule, all on published packages and now named in the changeset:page:header no longer trims a padded id (' convert ' used to resolve; ids are now compared exactly as authored); page:header reports a blank '' id through the unresolved-id warning instead of silently skipping it; record:quick_actions renders nothing (its ordinary empty placeholder) for an all-id actions array with no object bound instead of handing the bare strings to the action engine as action definitions.
  • Tests — packages/types/src/__tests__/declared-action-ids-7182.test.ts (15 function cases, including the registry-independent verdict and a pin that the classifier is NOT on the barrel); packages/components/src/__tests__/page-header-action-ids.test.tsx (the mixed-array tolerance case is REPLACED by the refusal pin — it pinned the branch this PR deletes; the all-object case also asserts no metadata read); packages/plugin-detail/src/renderers/__tests__/record-quick-actions.declared-action-ids-7182.test.tsx (id/object equivalence with the object render as live control, actionNames, mixed refusal under both keys, typo-vs-refusal control); packages/plugin-detail/src/__tests__/declaredActionIds.twoRenderers-7182.test.tsx (both renderers in one process).
  • .changeset/7182-declared-action-ids-one-rule.mdminor on @object-ui/types, @object-ui/components, @object-ui/plugin-detail; headline in the ruling's own wording; the three behaviour changes above named in a closing paragraph.
  • content/docs/guide/slotted-pages.md — the paragraph that taught the mixed-array tolerance now states the rule.

Validation layer — measured (dispatch ZONE 2, assumption 3), corrected in the patch round

Measured on @objectstack/spec 17.2.0 with ComponentPropsMap safeParse:

  • page:header: PageHeaderProps.actions is z.array(z.string()). A mixed array is refused at path actions, 1 (invalid_type: expected string, received object); an ALL-object array is refused too, at actions, 0. The spec is stricter than C.
  • record:quick_actions: RecordQuickActionsProps is strict and declares actionNames (ids) only. Any actions key is refused as unrecognized regardless of shape; a mixed actionNames is refused at actionNames, 1. The actions array reaches this renderer only through host channels (layout:page-header delegation, the page synthesizer).

Correction (contract review, re-measured on origin/main): an earlier version of this body said Studio's clientValidation reads that map. It does not. clientValidation validates a page through PageSchema, whose component properties is z.record(z.string(), z.unknown()) — an open bag — and ComponentPropsMap has zero code consumers in objectui (0 non-test hits under packages/*/src and apps/*/src; validateComponentProps appears only in comments). Consequence, stated plainly: for a Studio-authored page, this PR's runtime refusal is the only refusal there is. The spec-layer refusal exists as a contract statement and reaches an author only where something parses ComponentPropsMap, which nothing in this repo does today. The refusal is a structured return from the pure function, reported by each renderer; no renderer throws.

Pins — each named, red then green

Method for the red legs (run on the original commit 7d692df; the patch round changed only the pre-lookup call site and the exports, no resolution or refusal logic), both from the committed tree with an EXIT/INT/TERM trap restoring by absolute path, restore verified by git diff HEAD empty and blob hashes equal to HEAD's: leg A = both renderers restored from BASE d53e472 (pre-fix) under the new function and the new pins; leg B = the ids arm of resolveDeclaredActionIds ablated (byName.get(id) replaced by undefined, marker ABLATION_7182_MARKER; on-disk confirmed grep -c marker = 1, original line = 0). The reviewer's independent leg C (objects arm removed, 7 red) confirms pin 3 is a genuine control. Tests resolve @object-ui/types from src through the root vitest alias, so the src mutation is what the tests read; the built components bundle is measured separately by the dist pin.

  1. Mixed array refused, message naming the offending index — leg A: 4 red (header refusal pin; bar refusal pins under actions and actionNames; two-renderer refusal pin). The pre-fix bar failed inside ActionEngine.registerAction on the bare string — the live defect, measured. HEAD: green.
  2. All-id array resolves through the shared function in BOTH renderers — leg B: red in both (header: 7 id-path cases; bar: id equivalence and the actionNames case; two-renderer id case; plus 3 function cases = 13 red). HEAD: green.
  3. All-object array passes through in BOTH — green in leg A, leg B and HEAD; red in the reviewer's leg C. Pre-existing behaviour, the live control.
  4. Same buttons for the same authored array, object render as live controldeclaredActionIds.twoRenderers-7182.test.tsx: header control equals bar control (population inside the intersection of the two chains), header ids equal header control, bar ids equal bar control, header ids equal bar ids. Red in leg B, green on HEAD.

Patch round (contract review PASS with amendments)

  • Required 1 — changeset now names the three behaviour changes (padded id, blank id, unbound all-id array).
  • Required 2 — the clientValidation claim corrected above, with the consequence recorded.
  • Surface question — measured answer: there is no need for classifyDeclaredActions or DeclaredActionsShape to be public. The renderers' only pre-lookup need is kind and ids, which resolveDeclaredActionIds(elements, undefined) returns; both renderers now call that, the classifier and its type are module-internal, the barrel exports one function and two types, and the types test pins the classifier's absence from the barrel.
  • Base synced by merge (origin/main1688986, merge commit b1d670b; none of the 19 files overlap this PR's files) — merge, not rebase, per the repo's no-force-push rule.

Verification on HEAD 189f71c (union run after the patch commit)

  • Dependency closure built first: turbo run build --filter='@object-ui/plugin-detail^...', 11/11 — packages/types/dist rebuilt WITHOUT the classifier (0 hits for classifyDeclaredActions in dist/index.d.ts and dist/index.js), packages/components/dist/index.js carries the actions refused at index marker.
  • vitest from the repo root under the shared verify lock: the 4 pin files + scripts/__tests__/one-authority-per-exported-name-6273.test.tsTest Files 5 passed (5), Tests 42 passed (42), exit 0.
  • dist pin: OBJECTUI_DIST_PINS=1 vitest run --project distTest Files 1 passed (1), Tests 3 passed (3), exit 0.
  • type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-detail — exit 0 each, script lines echoed; the types test program lists the test file (--listFiles, 1 hit).
  • eslint per package on every touched file — 0 errors (warnings are pre-existing anys).
  • check:control-bytes ✅ · check:phantom-deps ✅ · check-changeset-presence ✅ (8 source files of 3 released packages, 1 changeset) · check-changeset-no-major ✅. Earlier round on 7d692df, unchanged by this diff: check:self-import ✅ · check:doc-fences ✅ · check:doc-types ✅ · check:action-forward-parity ✅.
  • NOT MEASURED locally, precondition not met by the gates' own wording: check:readme-exports (unbuilt packages, population collapsed), check:doc-snippets (needs the full snippet build; the doc change is prose only), check:eager-closure (needs the console vite build; no new package edge). Repo-wide pnpm lint and the full test farm are CI's.

Not in this PR

  • hotcrm#1279 (the consumer rewrite of four record_header arrays to ids) remains pm:blocked on the published artefact — not a rider.
  • Retiring the inline-object tolerance itself once hotcrm is all ids — the ruling's Restart-when, its own future card.
  • objectui#7382 — record:alert's single-id CTA lookup, a third hand-written copy of the by-name mechanism at arity one; filed as a finding, not touched here.

Implemented by the objectui#7182 dev seat, session https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC (written in prose because a PR edit degrades the footer form).

🤖 Generated with Claude Code


Generated by Claude Code

…ays, mixed id/object refused
Maintainer ruling 2026-09-02 (objectui#7182, option C): an `actions` array on
`page:header` or `record:quick_actions` is either all action ids or all inline
ActionDef objects; a mixed array is refused loudly, naming the offending index.
- `@object-ui/types`: pure `resolveDeclaredActionIds(elements, registered)`
beside `actionRendersAt`, with its shape half `classifyDeclaredActions` and
the `DeclaredActionsShape` / `DeclaredActionsResolution` /
`DeclaredActionsRefusal` result types.
- `page:header` (containers.tsx): the per-element normalisation is gone; the
header calls the shared function, refuses a mixed array (console.error once,
nothing authored drawn), warns on unresolved ids as before.
- `record:quick_actions`: the whole-array `every(string)` switch is gone; the
bar calls the shared function for both `actionNames` and host `actions`,
refuses a mixed array (console.error once, placeholder names the index).
- Pins: function cases in types; the header's mixed-array tolerance case is
replaced by a refusal pin; a quick_actions id/object equivalence + refusal
pin; a two-renderer pin proving both surfaces draw the same buttons for the
same id-authored array and refuse the same mixed array.
- Changeset (minor, types/components/plugin-detail) and the slotted-pages
guide paragraph.
Refs: objectui#7182 (ruling comment 5509039514), objectstack#11592.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BCevB0zE.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.90KB117.51KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.22KB64.19KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.77KB2.26KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.00KB3.28KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…ngeset names three behaviour changes
Contract review on PR #7381 (objectui#7182), patch round:
- `classifyDeclaredActions` and `DeclaredActionsShape` are no longer exported:
the renderers' pre-lookup need (kind + ids before the registry exists) is
served by `resolveDeclaredActionIds(elements, undefined)`, so both renderers
now call that; a second public function and third public type would be a
permanent surface for a need the first already serves. The types test pins
the classifier's absence from the barrel.
- Changeset names the three behaviour changes the review found unannounced:
page:header no longer trims a padded id, warns on a blank id instead of
skipping it; record:quick_actions renders nothing for an all-id array with
no object bound instead of handing bare strings to the engine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-rqzKpJmU.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.20KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(components/plugin-detail): the action-id → ActionDef lookup now exists twice, and the two copies disagree about mixed arrays

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182) - #7381

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids
Sep 2, 2026
Merged

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182)#7381
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids

Conversation

@os-project-manager

@os-project-manageros-project-manager commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7182

Ruling implemented

Maintainer ruling 2026-09-02 (issue comment 5509039514, decision batch #7, verbatim 「7189 A 其他同意」 — "其他同意" adopts this card's recommendation C): an actions array on page:header or record:quick_actions is either all action ids or all inline ActionDef objects; a mixed ['convert', { … }] array is refused loudly, naming the offending index, not tolerated by one renderer and half-drawn by the other. One pure resolveDeclaredActionIds(elements, registeredActions) lives in @object-ui/types beside actionRendersAt and both renderers call it; the whole-array switch in record-quick-actions.tsx and the per-element normalisation in containers.tsx are both gone.

Clause-② is YES per the ruling, so this PR is a draft carrying the reviewable contract increment and is labelled needs:contract-review. Contract review returned PASS with two text amendments and one surface question; all three are addressed in the patch round below. Not flipped ready, not enqueued — the PM does that after re-reading the changeset.

What changed

  • packages/types/src/ui-action.tsresolveDeclaredActionIds(elements, registeredActions): resolution by name in authored order, first registration wins on a duplicate name; ids that name nothing come back as index/id pairs for the caller to warn about once its own lookup has settled. Called with registeredActionsundefined (no registry yet) it returns the registry-independent verdict — kind and ids final, everything unresolved — which is what both renderers read before deciding whether to request a metadata read at all. Result types DeclaredActionsResolution and DeclaredActionsRefusal. The shape classifier and its shape type are module-internal (patch round, see below). Zero-dependency, typed on unknown[] for the same reason actionRendersAt types locations as plain strings. The element rule is closed: a string is an id, a non-null non-array object is an inline definition, anything else (null, a number, a boolean, a nested array) is refused at its index too. An empty array passes through as an empty object list.
  • packages/types/src/index.ts — barrel exports the one function (beside actionRendersAt) and the two result types. One-authority gate: none of the names had a prior declaration in packages/, scripts/__tests__/one-authority-per-exported-name-6273.test.ts is green on HEAD, KNOWN_COLLISIONS untouched.
  • packages/components/src/renderers/layout/containers.tsx (page:header) — headerActionIds and the per-element loop are replaced by the shared function (once with no registry for the hook-order question, once resolved). A refused array is reported once via console.error (surface, index, the function's message) and none of its authored actions is drawn; host-injected system actions still render. The unresolved-id warning keeps its wording and stays gated on the lookup having settled.
  • packages/plugin-detail/src/renderers/record-quick-actions.tsx (record:quick_actions) — the every(string) whole-array switch is replaced by the shared function, for both actionNames (the spec spelling) and host actions. A refused array is reported once via console.error and the empty-state placeholder reads … actions refused at index N (see console) instead of "no actions configured".
  • Three further behaviour changes ride on the one rule, all on published packages and now named in the changeset:page:header no longer trims a padded id (' convert ' used to resolve; ids are now compared exactly as authored); page:header reports a blank '' id through the unresolved-id warning instead of silently skipping it; record:quick_actions renders nothing (its ordinary empty placeholder) for an all-id actions array with no object bound instead of handing the bare strings to the action engine as action definitions.
  • Tests — packages/types/src/__tests__/declared-action-ids-7182.test.ts (15 function cases, including the registry-independent verdict and a pin that the classifier is NOT on the barrel); packages/components/src/__tests__/page-header-action-ids.test.tsx (the mixed-array tolerance case is REPLACED by the refusal pin — it pinned the branch this PR deletes; the all-object case also asserts no metadata read); packages/plugin-detail/src/renderers/__tests__/record-quick-actions.declared-action-ids-7182.test.tsx (id/object equivalence with the object render as live control, actionNames, mixed refusal under both keys, typo-vs-refusal control); packages/plugin-detail/src/__tests__/declaredActionIds.twoRenderers-7182.test.tsx (both renderers in one process).
  • .changeset/7182-declared-action-ids-one-rule.mdminor on @object-ui/types, @object-ui/components, @object-ui/plugin-detail; headline in the ruling's own wording; the three behaviour changes above named in a closing paragraph.
  • content/docs/guide/slotted-pages.md — the paragraph that taught the mixed-array tolerance now states the rule.

Validation layer — measured (dispatch ZONE 2, assumption 3), corrected in the patch round

Measured on @objectstack/spec 17.2.0 with ComponentPropsMap safeParse:

  • page:header: PageHeaderProps.actions is z.array(z.string()). A mixed array is refused at path actions, 1 (invalid_type: expected string, received object); an ALL-object array is refused too, at actions, 0. The spec is stricter than C.
  • record:quick_actions: RecordQuickActionsProps is strict and declares actionNames (ids) only. Any actions key is refused as unrecognized regardless of shape; a mixed actionNames is refused at actionNames, 1. The actions array reaches this renderer only through host channels (layout:page-header delegation, the page synthesizer).

Correction (contract review, re-measured on origin/main): an earlier version of this body said Studio's clientValidation reads that map. It does not. clientValidation validates a page through PageSchema, whose component properties is z.record(z.string(), z.unknown()) — an open bag — and ComponentPropsMap has zero code consumers in objectui (0 non-test hits under packages/*/src and apps/*/src; validateComponentProps appears only in comments). Consequence, stated plainly: for a Studio-authored page, this PR's runtime refusal is the only refusal there is. The spec-layer refusal exists as a contract statement and reaches an author only where something parses ComponentPropsMap, which nothing in this repo does today. The refusal is a structured return from the pure function, reported by each renderer; no renderer throws.

Pins — each named, red then green

Method for the red legs (run on the original commit 7d692df; the patch round changed only the pre-lookup call site and the exports, no resolution or refusal logic), both from the committed tree with an EXIT/INT/TERM trap restoring by absolute path, restore verified by git diff HEAD empty and blob hashes equal to HEAD's: leg A = both renderers restored from BASE d53e472 (pre-fix) under the new function and the new pins; leg B = the ids arm of resolveDeclaredActionIds ablated (byName.get(id) replaced by undefined, marker ABLATION_7182_MARKER; on-disk confirmed grep -c marker = 1, original line = 0). The reviewer's independent leg C (objects arm removed, 7 red) confirms pin 3 is a genuine control. Tests resolve @object-ui/types from src through the root vitest alias, so the src mutation is what the tests read; the built components bundle is measured separately by the dist pin.

  1. Mixed array refused, message naming the offending index — leg A: 4 red (header refusal pin; bar refusal pins under actions and actionNames; two-renderer refusal pin). The pre-fix bar failed inside ActionEngine.registerAction on the bare string — the live defect, measured. HEAD: green.
  2. All-id array resolves through the shared function in BOTH renderers — leg B: red in both (header: 7 id-path cases; bar: id equivalence and the actionNames case; two-renderer id case; plus 3 function cases = 13 red). HEAD: green.
  3. All-object array passes through in BOTH — green in leg A, leg B and HEAD; red in the reviewer's leg C. Pre-existing behaviour, the live control.
  4. Same buttons for the same authored array, object render as live controldeclaredActionIds.twoRenderers-7182.test.tsx: header control equals bar control (population inside the intersection of the two chains), header ids equal header control, bar ids equal bar control, header ids equal bar ids. Red in leg B, green on HEAD.

Patch round (contract review PASS with amendments)

  • Required 1 — changeset now names the three behaviour changes (padded id, blank id, unbound all-id array).
  • Required 2 — the clientValidation claim corrected above, with the consequence recorded.
  • Surface question — measured answer: there is no need for classifyDeclaredActions or DeclaredActionsShape to be public. The renderers' only pre-lookup need is kind and ids, which resolveDeclaredActionIds(elements, undefined) returns; both renderers now call that, the classifier and its type are module-internal, the barrel exports one function and two types, and the types test pins the classifier's absence from the barrel.
  • Base synced by merge (origin/main1688986, merge commit b1d670b; none of the 19 files overlap this PR's files) — merge, not rebase, per the repo's no-force-push rule.

Verification on HEAD 189f71c (union run after the patch commit)

  • Dependency closure built first: turbo run build --filter='@object-ui/plugin-detail^...', 11/11 — packages/types/dist rebuilt WITHOUT the classifier (0 hits for classifyDeclaredActions in dist/index.d.ts and dist/index.js), packages/components/dist/index.js carries the actions refused at index marker.
  • vitest from the repo root under the shared verify lock: the 4 pin files + scripts/__tests__/one-authority-per-exported-name-6273.test.tsTest Files 5 passed (5), Tests 42 passed (42), exit 0.
  • dist pin: OBJECTUI_DIST_PINS=1 vitest run --project distTest Files 1 passed (1), Tests 3 passed (3), exit 0.
  • type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-detail — exit 0 each, script lines echoed; the types test program lists the test file (--listFiles, 1 hit).
  • eslint per package on every touched file — 0 errors (warnings are pre-existing anys).
  • check:control-bytes ✅ · check:phantom-deps ✅ · check-changeset-presence ✅ (8 source files of 3 released packages, 1 changeset) · check-changeset-no-major ✅. Earlier round on 7d692df, unchanged by this diff: check:self-import ✅ · check:doc-fences ✅ · check:doc-types ✅ · check:action-forward-parity ✅.
  • NOT MEASURED locally, precondition not met by the gates' own wording: check:readme-exports (unbuilt packages, population collapsed), check:doc-snippets (needs the full snippet build; the doc change is prose only), check:eager-closure (needs the console vite build; no new package edge). Repo-wide pnpm lint and the full test farm are CI's.

Not in this PR

  • hotcrm#1279 (the consumer rewrite of four record_header arrays to ids) remains pm:blocked on the published artefact — not a rider.
  • Retiring the inline-object tolerance itself once hotcrm is all ids — the ruling's Restart-when, its own future card.
  • objectui#7382 — record:alert's single-id CTA lookup, a third hand-written copy of the by-name mechanism at arity one; filed as a finding, not touched here.

Implemented by the objectui#7182 dev seat, session https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC (written in prose because a PR edit degrades the footer form).

🤖 Generated with Claude Code


Generated by Claude Code

…ays, mixed id/object refused
Maintainer ruling 2026-09-02 (objectui#7182, option C): an `actions` array on
`page:header` or `record:quick_actions` is either all action ids or all inline
ActionDef objects; a mixed array is refused loudly, naming the offending index.
- `@object-ui/types`: pure `resolveDeclaredActionIds(elements, registered)`
beside `actionRendersAt`, with its shape half `classifyDeclaredActions` and
the `DeclaredActionsShape` / `DeclaredActionsResolution` /
`DeclaredActionsRefusal` result types.
- `page:header` (containers.tsx): the per-element normalisation is gone; the
header calls the shared function, refuses a mixed array (console.error once,
nothing authored drawn), warns on unresolved ids as before.
- `record:quick_actions`: the whole-array `every(string)` switch is gone; the
bar calls the shared function for both `actionNames` and host `actions`,
refuses a mixed array (console.error once, placeholder names the index).
- Pins: function cases in types; the header's mixed-array tolerance case is
replaced by a refusal pin; a quick_actions id/object equivalence + refusal
pin; a two-renderer pin proving both surfaces draw the same buttons for the
same id-authored array and refuse the same mixed array.
- Changeset (minor, types/components/plugin-detail) and the slotted-pages
guide paragraph.
Refs: objectui#7182 (ruling comment 5509039514), objectstack#11592.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BCevB0zE.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.90KB117.51KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.22KB64.19KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.77KB2.26KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.00KB3.28KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…ngeset names three behaviour changes
Contract review on PR #7381 (objectui#7182), patch round:
- `classifyDeclaredActions` and `DeclaredActionsShape` are no longer exported:
the renderers' pre-lookup need (kind + ids before the registry exists) is
served by `resolveDeclaredActionIds(elements, undefined)`, so both renderers
now call that; a second public function and third public type would be a
permanent surface for a need the first already serves. The types test pins
the classifier's absence from the barrel.
- Changeset names the three behaviour changes the review found unannounced:
page:header no longer trims a padded id, warns on a blank id instead of
skipping it; record:quick_actions renders nothing for an all-id array with
no object bound instead of handing bare strings to the engine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-rqzKpJmU.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.20KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(components/plugin-detail): the action-id → ActionDef lookup now exists twice, and the two copies disagree about mixed arrays

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182) - #7381

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids
Sep 2, 2026
Merged

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182)#7381
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids

Conversation

@os-project-manager

@os-project-manageros-project-manager commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7182

Ruling implemented

Maintainer ruling 2026-09-02 (issue comment 5509039514, decision batch #7, verbatim 「7189 A 其他同意」 — "其他同意" adopts this card's recommendation C): an actions array on page:header or record:quick_actions is either all action ids or all inline ActionDef objects; a mixed ['convert', { … }] array is refused loudly, naming the offending index, not tolerated by one renderer and half-drawn by the other. One pure resolveDeclaredActionIds(elements, registeredActions) lives in @object-ui/types beside actionRendersAt and both renderers call it; the whole-array switch in record-quick-actions.tsx and the per-element normalisation in containers.tsx are both gone.

Clause-② is YES per the ruling, so this PR is a draft carrying the reviewable contract increment and is labelled needs:contract-review. Contract review returned PASS with two text amendments and one surface question; all three are addressed in the patch round below. Not flipped ready, not enqueued — the PM does that after re-reading the changeset.

What changed

  • packages/types/src/ui-action.tsresolveDeclaredActionIds(elements, registeredActions): resolution by name in authored order, first registration wins on a duplicate name; ids that name nothing come back as index/id pairs for the caller to warn about once its own lookup has settled. Called with registeredActionsundefined (no registry yet) it returns the registry-independent verdict — kind and ids final, everything unresolved — which is what both renderers read before deciding whether to request a metadata read at all. Result types DeclaredActionsResolution and DeclaredActionsRefusal. The shape classifier and its shape type are module-internal (patch round, see below). Zero-dependency, typed on unknown[] for the same reason actionRendersAt types locations as plain strings. The element rule is closed: a string is an id, a non-null non-array object is an inline definition, anything else (null, a number, a boolean, a nested array) is refused at its index too. An empty array passes through as an empty object list.
  • packages/types/src/index.ts — barrel exports the one function (beside actionRendersAt) and the two result types. One-authority gate: none of the names had a prior declaration in packages/, scripts/__tests__/one-authority-per-exported-name-6273.test.ts is green on HEAD, KNOWN_COLLISIONS untouched.
  • packages/components/src/renderers/layout/containers.tsx (page:header) — headerActionIds and the per-element loop are replaced by the shared function (once with no registry for the hook-order question, once resolved). A refused array is reported once via console.error (surface, index, the function's message) and none of its authored actions is drawn; host-injected system actions still render. The unresolved-id warning keeps its wording and stays gated on the lookup having settled.
  • packages/plugin-detail/src/renderers/record-quick-actions.tsx (record:quick_actions) — the every(string) whole-array switch is replaced by the shared function, for both actionNames (the spec spelling) and host actions. A refused array is reported once via console.error and the empty-state placeholder reads … actions refused at index N (see console) instead of "no actions configured".
  • Three further behaviour changes ride on the one rule, all on published packages and now named in the changeset:page:header no longer trims a padded id (' convert ' used to resolve; ids are now compared exactly as authored); page:header reports a blank '' id through the unresolved-id warning instead of silently skipping it; record:quick_actions renders nothing (its ordinary empty placeholder) for an all-id actions array with no object bound instead of handing the bare strings to the action engine as action definitions.
  • Tests — packages/types/src/__tests__/declared-action-ids-7182.test.ts (15 function cases, including the registry-independent verdict and a pin that the classifier is NOT on the barrel); packages/components/src/__tests__/page-header-action-ids.test.tsx (the mixed-array tolerance case is REPLACED by the refusal pin — it pinned the branch this PR deletes; the all-object case also asserts no metadata read); packages/plugin-detail/src/renderers/__tests__/record-quick-actions.declared-action-ids-7182.test.tsx (id/object equivalence with the object render as live control, actionNames, mixed refusal under both keys, typo-vs-refusal control); packages/plugin-detail/src/__tests__/declaredActionIds.twoRenderers-7182.test.tsx (both renderers in one process).
  • .changeset/7182-declared-action-ids-one-rule.mdminor on @object-ui/types, @object-ui/components, @object-ui/plugin-detail; headline in the ruling's own wording; the three behaviour changes above named in a closing paragraph.
  • content/docs/guide/slotted-pages.md — the paragraph that taught the mixed-array tolerance now states the rule.

Validation layer — measured (dispatch ZONE 2, assumption 3), corrected in the patch round

Measured on @objectstack/spec 17.2.0 with ComponentPropsMap safeParse:

  • page:header: PageHeaderProps.actions is z.array(z.string()). A mixed array is refused at path actions, 1 (invalid_type: expected string, received object); an ALL-object array is refused too, at actions, 0. The spec is stricter than C.
  • record:quick_actions: RecordQuickActionsProps is strict and declares actionNames (ids) only. Any actions key is refused as unrecognized regardless of shape; a mixed actionNames is refused at actionNames, 1. The actions array reaches this renderer only through host channels (layout:page-header delegation, the page synthesizer).

Correction (contract review, re-measured on origin/main): an earlier version of this body said Studio's clientValidation reads that map. It does not. clientValidation validates a page through PageSchema, whose component properties is z.record(z.string(), z.unknown()) — an open bag — and ComponentPropsMap has zero code consumers in objectui (0 non-test hits under packages/*/src and apps/*/src; validateComponentProps appears only in comments). Consequence, stated plainly: for a Studio-authored page, this PR's runtime refusal is the only refusal there is. The spec-layer refusal exists as a contract statement and reaches an author only where something parses ComponentPropsMap, which nothing in this repo does today. The refusal is a structured return from the pure function, reported by each renderer; no renderer throws.

Pins — each named, red then green

Method for the red legs (run on the original commit 7d692df; the patch round changed only the pre-lookup call site and the exports, no resolution or refusal logic), both from the committed tree with an EXIT/INT/TERM trap restoring by absolute path, restore verified by git diff HEAD empty and blob hashes equal to HEAD's: leg A = both renderers restored from BASE d53e472 (pre-fix) under the new function and the new pins; leg B = the ids arm of resolveDeclaredActionIds ablated (byName.get(id) replaced by undefined, marker ABLATION_7182_MARKER; on-disk confirmed grep -c marker = 1, original line = 0). The reviewer's independent leg C (objects arm removed, 7 red) confirms pin 3 is a genuine control. Tests resolve @object-ui/types from src through the root vitest alias, so the src mutation is what the tests read; the built components bundle is measured separately by the dist pin.

  1. Mixed array refused, message naming the offending index — leg A: 4 red (header refusal pin; bar refusal pins under actions and actionNames; two-renderer refusal pin). The pre-fix bar failed inside ActionEngine.registerAction on the bare string — the live defect, measured. HEAD: green.
  2. All-id array resolves through the shared function in BOTH renderers — leg B: red in both (header: 7 id-path cases; bar: id equivalence and the actionNames case; two-renderer id case; plus 3 function cases = 13 red). HEAD: green.
  3. All-object array passes through in BOTH — green in leg A, leg B and HEAD; red in the reviewer's leg C. Pre-existing behaviour, the live control.
  4. Same buttons for the same authored array, object render as live controldeclaredActionIds.twoRenderers-7182.test.tsx: header control equals bar control (population inside the intersection of the two chains), header ids equal header control, bar ids equal bar control, header ids equal bar ids. Red in leg B, green on HEAD.

Patch round (contract review PASS with amendments)

  • Required 1 — changeset now names the three behaviour changes (padded id, blank id, unbound all-id array).
  • Required 2 — the clientValidation claim corrected above, with the consequence recorded.
  • Surface question — measured answer: there is no need for classifyDeclaredActions or DeclaredActionsShape to be public. The renderers' only pre-lookup need is kind and ids, which resolveDeclaredActionIds(elements, undefined) returns; both renderers now call that, the classifier and its type are module-internal, the barrel exports one function and two types, and the types test pins the classifier's absence from the barrel.
  • Base synced by merge (origin/main1688986, merge commit b1d670b; none of the 19 files overlap this PR's files) — merge, not rebase, per the repo's no-force-push rule.

Verification on HEAD 189f71c (union run after the patch commit)

  • Dependency closure built first: turbo run build --filter='@object-ui/plugin-detail^...', 11/11 — packages/types/dist rebuilt WITHOUT the classifier (0 hits for classifyDeclaredActions in dist/index.d.ts and dist/index.js), packages/components/dist/index.js carries the actions refused at index marker.
  • vitest from the repo root under the shared verify lock: the 4 pin files + scripts/__tests__/one-authority-per-exported-name-6273.test.tsTest Files 5 passed (5), Tests 42 passed (42), exit 0.
  • dist pin: OBJECTUI_DIST_PINS=1 vitest run --project distTest Files 1 passed (1), Tests 3 passed (3), exit 0.
  • type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-detail — exit 0 each, script lines echoed; the types test program lists the test file (--listFiles, 1 hit).
  • eslint per package on every touched file — 0 errors (warnings are pre-existing anys).
  • check:control-bytes ✅ · check:phantom-deps ✅ · check-changeset-presence ✅ (8 source files of 3 released packages, 1 changeset) · check-changeset-no-major ✅. Earlier round on 7d692df, unchanged by this diff: check:self-import ✅ · check:doc-fences ✅ · check:doc-types ✅ · check:action-forward-parity ✅.
  • NOT MEASURED locally, precondition not met by the gates' own wording: check:readme-exports (unbuilt packages, population collapsed), check:doc-snippets (needs the full snippet build; the doc change is prose only), check:eager-closure (needs the console vite build; no new package edge). Repo-wide pnpm lint and the full test farm are CI's.

Not in this PR

  • hotcrm#1279 (the consumer rewrite of four record_header arrays to ids) remains pm:blocked on the published artefact — not a rider.
  • Retiring the inline-object tolerance itself once hotcrm is all ids — the ruling's Restart-when, its own future card.
  • objectui#7382 — record:alert's single-id CTA lookup, a third hand-written copy of the by-name mechanism at arity one; filed as a finding, not touched here.

Implemented by the objectui#7182 dev seat, session https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC (written in prose because a PR edit degrades the footer form).

🤖 Generated with Claude Code


Generated by Claude Code

…ays, mixed id/object refused
Maintainer ruling 2026-09-02 (objectui#7182, option C): an `actions` array on
`page:header` or `record:quick_actions` is either all action ids or all inline
ActionDef objects; a mixed array is refused loudly, naming the offending index.
- `@object-ui/types`: pure `resolveDeclaredActionIds(elements, registered)`
beside `actionRendersAt`, with its shape half `classifyDeclaredActions` and
the `DeclaredActionsShape` / `DeclaredActionsResolution` /
`DeclaredActionsRefusal` result types.
- `page:header` (containers.tsx): the per-element normalisation is gone; the
header calls the shared function, refuses a mixed array (console.error once,
nothing authored drawn), warns on unresolved ids as before.
- `record:quick_actions`: the whole-array `every(string)` switch is gone; the
bar calls the shared function for both `actionNames` and host `actions`,
refuses a mixed array (console.error once, placeholder names the index).
- Pins: function cases in types; the header's mixed-array tolerance case is
replaced by a refusal pin; a quick_actions id/object equivalence + refusal
pin; a two-renderer pin proving both surfaces draw the same buttons for the
same id-authored array and refuse the same mixed array.
- Changeset (minor, types/components/plugin-detail) and the slotted-pages
guide paragraph.
Refs: objectui#7182 (ruling comment 5509039514), objectstack#11592.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BCevB0zE.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.90KB117.51KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.22KB64.19KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.77KB2.26KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.00KB3.28KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…ngeset names three behaviour changes
Contract review on PR #7381 (objectui#7182), patch round:
- `classifyDeclaredActions` and `DeclaredActionsShape` are no longer exported:
the renderers' pre-lookup need (kind + ids before the registry exists) is
served by `resolveDeclaredActionIds(elements, undefined)`, so both renderers
now call that; a second public function and third public type would be a
permanent surface for a need the first already serves. The types test pins
the classifier's absence from the barrel.
- Changeset names the three behaviour changes the review found unannounced:
page:header no longer trims a padded id, warns on a blank id instead of
skipping it; record:quick_actions renders nothing for an all-id array with
no object bound instead of handing bare strings to the engine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-rqzKpJmU.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.20KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(components/plugin-detail): the action-id → ActionDef lookup now exists twice, and the two copies disagree about mixed arrays

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182) - #7381

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids
Sep 2, 2026
Merged

fix: one shared rule for declared action arrays; mixed id/object arrays refused (objectui#7182)#7381
os-project-manager merged 3 commits into
mainfrom
claude/issue-7182-declared-action-ids

Conversation

@os-project-manager

@os-project-manageros-project-manager commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7182

Ruling implemented

Maintainer ruling 2026-09-02 (issue comment 5509039514, decision batch #7, verbatim 「7189 A 其他同意」 — "其他同意" adopts this card's recommendation C): an actions array on page:header or record:quick_actions is either all action ids or all inline ActionDef objects; a mixed ['convert', { … }] array is refused loudly, naming the offending index, not tolerated by one renderer and half-drawn by the other. One pure resolveDeclaredActionIds(elements, registeredActions) lives in @object-ui/types beside actionRendersAt and both renderers call it; the whole-array switch in record-quick-actions.tsx and the per-element normalisation in containers.tsx are both gone.

Clause-② is YES per the ruling, so this PR is a draft carrying the reviewable contract increment and is labelled needs:contract-review. Contract review returned PASS with two text amendments and one surface question; all three are addressed in the patch round below. Not flipped ready, not enqueued — the PM does that after re-reading the changeset.

What changed

  • packages/types/src/ui-action.tsresolveDeclaredActionIds(elements, registeredActions): resolution by name in authored order, first registration wins on a duplicate name; ids that name nothing come back as index/id pairs for the caller to warn about once its own lookup has settled. Called with registeredActionsundefined (no registry yet) it returns the registry-independent verdict — kind and ids final, everything unresolved — which is what both renderers read before deciding whether to request a metadata read at all. Result types DeclaredActionsResolution and DeclaredActionsRefusal. The shape classifier and its shape type are module-internal (patch round, see below). Zero-dependency, typed on unknown[] for the same reason actionRendersAt types locations as plain strings. The element rule is closed: a string is an id, a non-null non-array object is an inline definition, anything else (null, a number, a boolean, a nested array) is refused at its index too. An empty array passes through as an empty object list.
  • packages/types/src/index.ts — barrel exports the one function (beside actionRendersAt) and the two result types. One-authority gate: none of the names had a prior declaration in packages/, scripts/__tests__/one-authority-per-exported-name-6273.test.ts is green on HEAD, KNOWN_COLLISIONS untouched.
  • packages/components/src/renderers/layout/containers.tsx (page:header) — headerActionIds and the per-element loop are replaced by the shared function (once with no registry for the hook-order question, once resolved). A refused array is reported once via console.error (surface, index, the function's message) and none of its authored actions is drawn; host-injected system actions still render. The unresolved-id warning keeps its wording and stays gated on the lookup having settled.
  • packages/plugin-detail/src/renderers/record-quick-actions.tsx (record:quick_actions) — the every(string) whole-array switch is replaced by the shared function, for both actionNames (the spec spelling) and host actions. A refused array is reported once via console.error and the empty-state placeholder reads … actions refused at index N (see console) instead of "no actions configured".
  • Three further behaviour changes ride on the one rule, all on published packages and now named in the changeset:page:header no longer trims a padded id (' convert ' used to resolve; ids are now compared exactly as authored); page:header reports a blank '' id through the unresolved-id warning instead of silently skipping it; record:quick_actions renders nothing (its ordinary empty placeholder) for an all-id actions array with no object bound instead of handing the bare strings to the action engine as action definitions.
  • Tests — packages/types/src/__tests__/declared-action-ids-7182.test.ts (15 function cases, including the registry-independent verdict and a pin that the classifier is NOT on the barrel); packages/components/src/__tests__/page-header-action-ids.test.tsx (the mixed-array tolerance case is REPLACED by the refusal pin — it pinned the branch this PR deletes; the all-object case also asserts no metadata read); packages/plugin-detail/src/renderers/__tests__/record-quick-actions.declared-action-ids-7182.test.tsx (id/object equivalence with the object render as live control, actionNames, mixed refusal under both keys, typo-vs-refusal control); packages/plugin-detail/src/__tests__/declaredActionIds.twoRenderers-7182.test.tsx (both renderers in one process).
  • .changeset/7182-declared-action-ids-one-rule.mdminor on @object-ui/types, @object-ui/components, @object-ui/plugin-detail; headline in the ruling's own wording; the three behaviour changes above named in a closing paragraph.
  • content/docs/guide/slotted-pages.md — the paragraph that taught the mixed-array tolerance now states the rule.

Validation layer — measured (dispatch ZONE 2, assumption 3), corrected in the patch round

Measured on @objectstack/spec 17.2.0 with ComponentPropsMap safeParse:

  • page:header: PageHeaderProps.actions is z.array(z.string()). A mixed array is refused at path actions, 1 (invalid_type: expected string, received object); an ALL-object array is refused too, at actions, 0. The spec is stricter than C.
  • record:quick_actions: RecordQuickActionsProps is strict and declares actionNames (ids) only. Any actions key is refused as unrecognized regardless of shape; a mixed actionNames is refused at actionNames, 1. The actions array reaches this renderer only through host channels (layout:page-header delegation, the page synthesizer).

Correction (contract review, re-measured on origin/main): an earlier version of this body said Studio's clientValidation reads that map. It does not. clientValidation validates a page through PageSchema, whose component properties is z.record(z.string(), z.unknown()) — an open bag — and ComponentPropsMap has zero code consumers in objectui (0 non-test hits under packages/*/src and apps/*/src; validateComponentProps appears only in comments). Consequence, stated plainly: for a Studio-authored page, this PR's runtime refusal is the only refusal there is. The spec-layer refusal exists as a contract statement and reaches an author only where something parses ComponentPropsMap, which nothing in this repo does today. The refusal is a structured return from the pure function, reported by each renderer; no renderer throws.

Pins — each named, red then green

Method for the red legs (run on the original commit 7d692df; the patch round changed only the pre-lookup call site and the exports, no resolution or refusal logic), both from the committed tree with an EXIT/INT/TERM trap restoring by absolute path, restore verified by git diff HEAD empty and blob hashes equal to HEAD's: leg A = both renderers restored from BASE d53e472 (pre-fix) under the new function and the new pins; leg B = the ids arm of resolveDeclaredActionIds ablated (byName.get(id) replaced by undefined, marker ABLATION_7182_MARKER; on-disk confirmed grep -c marker = 1, original line = 0). The reviewer's independent leg C (objects arm removed, 7 red) confirms pin 3 is a genuine control. Tests resolve @object-ui/types from src through the root vitest alias, so the src mutation is what the tests read; the built components bundle is measured separately by the dist pin.

  1. Mixed array refused, message naming the offending index — leg A: 4 red (header refusal pin; bar refusal pins under actions and actionNames; two-renderer refusal pin). The pre-fix bar failed inside ActionEngine.registerAction on the bare string — the live defect, measured. HEAD: green.
  2. All-id array resolves through the shared function in BOTH renderers — leg B: red in both (header: 7 id-path cases; bar: id equivalence and the actionNames case; two-renderer id case; plus 3 function cases = 13 red). HEAD: green.
  3. All-object array passes through in BOTH — green in leg A, leg B and HEAD; red in the reviewer's leg C. Pre-existing behaviour, the live control.
  4. Same buttons for the same authored array, object render as live controldeclaredActionIds.twoRenderers-7182.test.tsx: header control equals bar control (population inside the intersection of the two chains), header ids equal header control, bar ids equal bar control, header ids equal bar ids. Red in leg B, green on HEAD.

Patch round (contract review PASS with amendments)

  • Required 1 — changeset now names the three behaviour changes (padded id, blank id, unbound all-id array).
  • Required 2 — the clientValidation claim corrected above, with the consequence recorded.
  • Surface question — measured answer: there is no need for classifyDeclaredActions or DeclaredActionsShape to be public. The renderers' only pre-lookup need is kind and ids, which resolveDeclaredActionIds(elements, undefined) returns; both renderers now call that, the classifier and its type are module-internal, the barrel exports one function and two types, and the types test pins the classifier's absence from the barrel.
  • Base synced by merge (origin/main1688986, merge commit b1d670b; none of the 19 files overlap this PR's files) — merge, not rebase, per the repo's no-force-push rule.

Verification on HEAD 189f71c (union run after the patch commit)

  • Dependency closure built first: turbo run build --filter='@object-ui/plugin-detail^...', 11/11 — packages/types/dist rebuilt WITHOUT the classifier (0 hits for classifyDeclaredActions in dist/index.d.ts and dist/index.js), packages/components/dist/index.js carries the actions refused at index marker.
  • vitest from the repo root under the shared verify lock: the 4 pin files + scripts/__tests__/one-authority-per-exported-name-6273.test.tsTest Files 5 passed (5), Tests 42 passed (42), exit 0.
  • dist pin: OBJECTUI_DIST_PINS=1 vitest run --project distTest Files 1 passed (1), Tests 3 passed (3), exit 0.
  • type-check for @object-ui/types, @object-ui/components, @object-ui/plugin-detail — exit 0 each, script lines echoed; the types test program lists the test file (--listFiles, 1 hit).
  • eslint per package on every touched file — 0 errors (warnings are pre-existing anys).
  • check:control-bytes ✅ · check:phantom-deps ✅ · check-changeset-presence ✅ (8 source files of 3 released packages, 1 changeset) · check-changeset-no-major ✅. Earlier round on 7d692df, unchanged by this diff: check:self-import ✅ · check:doc-fences ✅ · check:doc-types ✅ · check:action-forward-parity ✅.
  • NOT MEASURED locally, precondition not met by the gates' own wording: check:readme-exports (unbuilt packages, population collapsed), check:doc-snippets (needs the full snippet build; the doc change is prose only), check:eager-closure (needs the console vite build; no new package edge). Repo-wide pnpm lint and the full test farm are CI's.

Not in this PR

  • hotcrm#1279 (the consumer rewrite of four record_header arrays to ids) remains pm:blocked on the published artefact — not a rider.
  • Retiring the inline-object tolerance itself once hotcrm is all ids — the ruling's Restart-when, its own future card.
  • objectui#7382 — record:alert's single-id CTA lookup, a third hand-written copy of the by-name mechanism at arity one; filed as a finding, not touched here.

Implemented by the objectui#7182 dev seat, session https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC (written in prose because a PR edit degrades the footer form).

🤖 Generated with Claude Code


Generated by Claude Code

…ays, mixed id/object refused
Maintainer ruling 2026-09-02 (objectui#7182, option C): an `actions` array on
`page:header` or `record:quick_actions` is either all action ids or all inline
ActionDef objects; a mixed array is refused loudly, naming the offending index.
- `@object-ui/types`: pure `resolveDeclaredActionIds(elements, registered)`
beside `actionRendersAt`, with its shape half `classifyDeclaredActions` and
the `DeclaredActionsShape` / `DeclaredActionsResolution` /
`DeclaredActionsRefusal` result types.
- `page:header` (containers.tsx): the per-element normalisation is gone; the
header calls the shared function, refuses a mixed array (console.error once,
nothing authored drawn), warns on unresolved ids as before.
- `record:quick_actions`: the whole-array `every(string)` switch is gone; the
bar calls the shared function for both `actionNames` and host `actions`,
refuses a mixed array (console.error once, placeholder names the index).
- Pins: function cases in types; the header's mixed-array tolerance case is
replaced by a refusal pin; a quick_actions id/object equivalence + refusal
pin; a two-renderer pin proving both surfaces draw the same buttons for the
same id-authored array and refuse the same mixed array.
- Changeset (minor, types/components/plugin-detail) and the slotted-pages
guide paragraph.
Refs: objectui#7182 (ruling comment 5509039514), objectstack#11592.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-BCevB0zE.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.90KB117.51KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.22KB64.19KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.77KB2.26KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.00KB3.28KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…ngeset names three behaviour changes
Contract review on PR #7381 (objectui#7182), patch round:
- `classifyDeclaredActions` and `DeclaredActionsShape` are no longer exported:
the renderers' pre-lookup need (kind + ids before the registry exists) is
served by `resolveDeclaredActionIds(elements, undefined)`, so both renderers
now call that; a second public function and third public type would be a
permanent surface for a need the first already serves. The types test pins
the classifier's absence from the barrel.
- Changeset names the three behaviour changes the review found unannounced:
page:header no longer trims a padded id, warns on a blank id instead of
skipping it; record:quick_actions renders nothing for an all-id array with
no object bound instead of handing bare strings to the engine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3176.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-rqzKpJmU.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.87KB117.50KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.20KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)209.10KB56.65KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.21KB8.66KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(components/plugin-detail): the action-id → ActionDef lookup now exists twice, and the two copies disagree about mixed arrays

2 participants

@os-project-manager@claude