From 110c69928efc5b6597aae184777e9bd839f869f8 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Mon, 15 Jun 2026 04:30:43 +0500 Subject: [PATCH 1/2] fix(spec): render action `body` as composite, not a flat code editor An action's `body` is a discriminated union (HookBodySchema: `{ language, source, capabilities?, timeoutMs? }`), the same shape hooks use. The action form layout mapped the whole field to `{ widget: 'code' }`, so the inspector fed the union object to a single JS editor and rendered "[object Object]". Mirror the working hook.form.ts layout: a composite with a `language` select, a `source` code editor, and the L2-only capability/timeout knobs. `visibleOn: data.type == 'script'` is preserved. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/spec/src/ui/action.form.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/spec/src/ui/action.form.ts b/packages/spec/src/ui/action.form.ts index fe0e17199b..38bdbe6845 100644 --- a/packages/spec/src/ui/action.form.ts +++ b/packages/spec/src/ui/action.form.ts @@ -30,7 +30,26 @@ export const actionForm = defineForm({ fields: [ { field: 'target', visibleOn: "data.type != 'script'", helpText: 'URL, flow name, or API endpoint to call' }, { field: 'method', visibleOn: "data.type == 'api'", helpText: 'HTTP method (GET, POST, PUT, DELETE)' }, - { field: 'body', widget: 'code', visibleOn: "data.type == 'script'", helpText: 'JavaScript code to execute' }, + { + field: 'body', + type: 'composite', + visibleOn: "data.type == 'script'", + helpText: 'Either an L1 expression or an L2 sandboxed JS body', + // Mirrors hook.form.ts: `body` is a discriminated union + // (HookBodySchema) on `language`, not a bare string. A flat + // `widget: 'code'` fed the whole object to the editor and rendered + // "[object Object]". Render the union as language + source (+ the + // L2-only capability/timeout knobs). + fields: [ + { field: 'language', type: 'select', required: true, helpText: 'expression = pure formula; js = sandboxed JavaScript', options: [ + { label: 'Expression (L1)', value: 'expression' }, + { label: 'JavaScript (L2 sandboxed)', value: 'js' }, + ] }, + { field: 'source', type: 'code', language: 'javascript', required: true, helpText: 'Function body source — no top-level imports' }, + { field: 'capabilities', type: 'tags', helpText: 'Allowed ctx APIs (api.read, api.write, crypto.uuid, log, …)' }, + { field: 'timeoutMs', type: 'number', helpText: 'Per-invocation timeout (ms)' }, + ], + }, { field: 'params', type: 'repeater', helpText: 'User input parameters (show form before executing)' }, { field: 'confirmText', helpText: 'Confirmation message (e.g., "Are you sure?")' }, { field: 'successMessage', helpText: 'Success message after completion' }, From b9f894cdb6496ff640847e81831f90b44a59e707 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Mon, 15 Jun 2026 04:43:23 +0500 Subject: [PATCH 2/2] chore(changeset): action body composite fix Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/studio-action-body-composite.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/studio-action-body-composite.md diff --git a/.changeset/studio-action-body-composite.md b/.changeset/studio-action-body-composite.md new file mode 100644 index 0000000000..822ed9465d --- /dev/null +++ b/.changeset/studio-action-body-composite.md @@ -0,0 +1,7 @@ +--- +"@objectstack/spec": patch +--- + +fix(spec): render action `body` as a composite editor (language + source) instead of a flat code field + +An action's `body` is a discriminated union (`HookBodySchema`), the same shape hooks use, but `action.form.ts` mapped the whole field to `{ widget: 'code' }`, so the Studio inspector fed the union object to a single JS editor and rendered `[object Object]`. The layout now mirrors the working `hook.form.ts`: a composite with a `language` select, a `source` code editor, and the L2-only capability/timeout knobs.