Skip to content

feat(studio): a page button created in Studio can be given an action (#2997) - #3028

Merged
os-zhuang merged 1 commit into
mainfrom
claude/element-button-action-inspector
Jul 30, 2026
Merged

feat(studio): a page button created in Studio can be given an action (#2997)#3028
os-zhuang merged 1 commit into
mainfrom
claude/element-button-action-inspector

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

The last piece of #2997. Independent of the spec work (objectstack-ai/objectstack#4135, merged) — this table is hand-written, not derived from the props schema.

The gap

element:button renders inert without an action, and Studio had no way to add one.

Two things had to both be true, and they were:

  1. The curated inspector entry (block-config.ts:216) listed label, variant, size, icon — no action.
  2. The generic "Advanced" section is not a fallback for that. It enumerates the keys the block already has:
const advancedKeys = Object.keys(blockProps).filter(
  (key) => !curatedNames.has(key) && !STRUCTURAL_PROP_KEYS.has(key),
);

So Advanced could edit an action that source had authored, and never add one to a button dragged from the palette. (This also corrects the framing in my own analysis on #2997, which said Studio "cannot author it" — it can edit an existing one; what it cannot do is create one.)

The change

A json field kind — the same InspectorJsonField editor Advanced already uses, made reachable for a property that does not exist yet — plus an action field on element:button:

{ name: 'action', label: 'Action', kind: 'json',
  placeholder: '{ "type": "url", "target": "/environments" }' }

The placeholder is not decoration. An empty JSON textarea is the entire affordance a raw-JSON editor offers, so placeholder is now threaded through to the <textarea> and asserted for every json field — a json field without one is a blank box, which is worse than no field.

Raw JSON rather than typed sub-fields, deliberately. The spec declares the prop as InlineActionSchema, and the inspector cannot render a nested schema as fields yet. A JSON box an author can actually use beats a curated form that models type and target and silently omits params, openIn, confirmText and the rest. The placeholder shows the canonical pair, which covers the common case.

The existing ratchet caught the new kind

block-config.test.ts asserts every field's kind is in an allowlist, and it failed on first run:

element:button.action kind=json … expected false to be true

That is the guard doing its job; json is added to the allowlist in the same change. Two new assertions go with it — element:button must expose an action field, and every json field must carry a placeholder.

Verification

  • app-shell metadata-admin: 968 tests across 111 files, green (12 in block-config.test.ts, up from 10).
  • tsc --noEmit: 0 errors with workspace deps built. (Without them the package reports 727, all Cannot find module '@object-ui/*' cascades — worth stating, because the 4 that land in PageBlockInspector.tsx unbuilt are of that class, not from this change.)
  • eslint: 0 errors on both touched files.

Refs #2997, objectstack-ai/objectstack#4135

🤖 Generated with Claude Code

…2997)

element:button renders inert without an `action`, and Studio had no way to
add one. The curated BLOCK_CONFIG entry listed label/variant/size/icon and no
`action`, and the generic "Advanced" section is not a fallback for that: it
enumerates the keys the block ALREADY has (Object.keys(blockProps)). So it
could edit an action authored in source and never add one to a button dragged
from the palette.

Adds a `json` field kind — the same InspectorJsonField editor Advanced uses,
reachable for a property that does not exist yet — plus an `action` field on
element:button whose placeholder shows the expected shape. An empty JSON
textarea is otherwise the entire affordance, so `placeholder` is threaded
through to the textarea and asserted for every json field.

Raw JSON rather than typed sub-fields deliberately: the spec declares the prop
as InlineActionSchema (objectstack#4135) and the inspector cannot render a
nested schema as fields yet. A JSON box the author can use beats a curated
form modelling a fraction of the shape.

The existing field-kind allowlist test caught the new kind on first run, which
is the ratchet working; `json` is added there too.

app-shell metadata-admin: 968 tests / 111 files green; tsc clean with deps
built.

Refs #2997

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Jul 30, 2026 11:34am

Request Review

@github-actions github-actions Bot added the tests label Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 27.9 KB 350 KB
Entry file index-DzsraTmn.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.20KB 2.97KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.25KB 1.01KB
auth (org-roles.js) 6.72KB 2.85KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 458.27KB 100.20KB
core (index.js) 2.16KB 0.78KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 134.67KB 34.24KB
fields (index.js) 222.07KB 54.35KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 25.17KB 5.80KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 38.45KB 10.67KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.05KB 1.53KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 6.84KB 2.42KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.90KB 12.35KB
plugin-charts (index.js) 60.52KB 17.11KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 111.59KB 28.74KB
plugin-designer (index.js) 210.56KB 42.56KB
plugin-detail (index.js) 216.65KB 53.02KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 104.83KB 25.30KB
plugin-gantt (index.js) 162.26KB 39.53KB
plugin-grid (index.js) 180.86KB 47.45KB
plugin-kanban (index.js) 47.82KB 13.18KB
plugin-list (index.js) 102.39KB 24.18KB
plugin-map (index.js) 16.80KB 5.24KB
plugin-markdown (index.js) 13.65KB 4.67KB
plugin-report (index.js) 40.32KB 10.53KB
plugin-timeline (index.js) 25.75KB 7.32KB
plugin-tree (index.js) 8.36KB 2.81KB
plugin-view (index.js) 85.91KB 20.99KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 3.47KB 1.54KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 0.77KB 0.41KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (index.js) 2.00KB 0.96KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.04KB 1.93KB
types (system-fields.js) 2.39KB 1.17KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 1.08KB 0.64KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@os-zhuang
os-zhuang merged commit 4a74ea6 into main Jul 30, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/element-button-action-inspector branch July 30, 2026 11:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant