From f49a7c3310d2ed52455622b367105b1dad8f7625 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 22:03:05 +0000 Subject: [PATCH] docs(skills): teach an action param's widget key in the action dialog The objectui protocol rule lists `widget` among an inline param's widget config: a registered field widget draws the param instead of its `type` widget, an unregistered name keeps the `type` widget, the bulk dialog does not read it, and the server validates by `type`. The objectstack-ui actions rule says a field-backed param inherits the field's `widget` only while it keeps or restates the field's `type`. Paid inside both files' token ceilings by deleting restatements. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_016WV4cyNZ7aKsfF5vTBgKKn --- .../30549-skills-action-param-widget.md | 12 +++++++++++ skills/objectstack-ui/rules/actions.md | 11 +++++----- skills/objectui/rules/protocol.md | 20 +++++++++---------- 3 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 .changeset/30549-skills-action-param-widget.md diff --git a/.changeset/30549-skills-action-param-widget.md b/.changeset/30549-skills-action-param-widget.md new file mode 100644 index 00000000000..27975d6ee4c --- /dev/null +++ b/.changeset/30549-skills-action-param-widget.md @@ -0,0 +1,12 @@ +--- +'@objectstack/skills': patch +--- + +docs(skills): teach an action param's `widget` key in the action dialog + +Clause-②: no +Metadata-protocol: none + +- `objectui` (`rules/protocol.md`, inline params): the widget-config list gains `widget`. It names a registered field widget that draws the param instead of its `type` widget (`'permission-ref'` on a `text` param is the permission-set picker); an unregistered name keeps the `type` widget, and the selection bar's bulk dialog does not read it. The server still validates the submitted value by `type`. +- `objectstack-ui` (`rules/actions.md`, field-backed params): a field-backed param inherits the field's `widget` only while it keeps the field's `type` or restates it; a param that overrides `type` names its own `widget`. +- Paid inside the token ceilings by removing restatements in the same files. No schema, key or behaviour changes. diff --git a/skills/objectstack-ui/rules/actions.md b/skills/objectstack-ui/rules/actions.md index aa8afa3ead2..6f1520dbe08 100644 --- a/skills/objectstack-ui/rules/actions.md +++ b/skills/objectstack-ui/rules/actions.md @@ -115,8 +115,7 @@ A server-side `body` (or a registered `handler`) receives `ctx` with `input` (th the active organization as `ctx.user?.organizationId ?? ctx.session?.organizationId` — the blessed name (same axes and `undefined` cases as hooks, objectstack-data). Bodies execute **trusted** (`ctx.api` / -`ctx.engine` bypass RLS/FLS), so a body that must scope by org reads it from -`ctx` explicitly. `ctx.session.positions` (absent when the caller holds none) is +`ctx.engine` bypass RLS/FLS). `ctx.session.positions` (absent when the caller holds none) is **not an authorization input** — ask the security service for privilege (ADR-0095). ## Opening in a New Tab (`openIn` / `opensInNewTab` / `newTabUrl`) @@ -135,7 +134,7 @@ export const PrintA3Action = defineAction({ name: 'print_a3', label: 'Print Summary Sheet (A3)', type: 'url', - target: '/print/a3?id=${record.id}', // static template; interpolated at click + target: '/print/a3?id=${record.id}', // interpolated at click openIn: 'new-tab', locations: ['list_toolbar'], }); @@ -156,7 +155,7 @@ export const OpenInvoicePdfAction = defineAction({ label: 'Open PDF', objectName: 'invoice', type: 'script', - target: 'open_invoice_pdf', // registered handler; skipped while newTabUrl is set + target: 'open_invoice_pdf', // registered handler opensInNewTab: true, newTabUrl: '/api/v1/invoice/{recordId}/pdf', locations: ['record_header'], @@ -172,7 +171,9 @@ export const OpenInvoicePdfAction = defineAction({ ## Action Parameter Patterns Prefer **field-backed** params (`{ field: 'email' }`): label (i18n), type, -validation, options, placeholder and widget come from the object metadata; +validation, options, placeholder and widget come from the object metadata +(`widget` only while the param keeps or restates the field's `type`; a param +that overrides `type` names its own `widget`); `objectOverride` borrows a field of another object, and `defaultFromRow: true` pre-fills from the selected row in `list_item` contexts. diff --git a/skills/objectui/rules/protocol.md b/skills/objectui/rules/protocol.md index 1f4a601edb1..110644aff0e 100644 --- a/skills/objectui/rules/protocol.md +++ b/skills/objectui/rules/protocol.md @@ -31,9 +31,7 @@ These top-level schema fields are passed as raw strings: types that declare them** (see "Rule: Bindable Text Keys"); read raw everywhere else. **Do not "move them to `props`"** to make an expression work on a type that does not declare them: under `props` they are evaluated and - then discarded, so the component paints an empty frame instead. On an - undeclared type, resolve the value in the host before handing the schema to - `SchemaRenderer`, or carry it on a `text` node's `content`. + then discarded, so the component paints an empty frame instead. - `className` — always a static Tailwind class string - `id` — always a static string - `type` — component type identifier @@ -95,10 +93,9 @@ declaration rather than keeping its own copy. ``` On any other component type these four keys are read **raw**: the expression -reaches the screen as literal text. There is no way to opt a type in from -metadata — a new type/key pair is a change to the spec's declaration, not -something the renderer infers. Until then, resolve the value in the host, or -use a `text` node's `content`, which every type evaluates. +reaches the screen as literal text. Resolve the value in the host before +handing the schema to `SchemaRenderer`, or use a `text` node's `content`, +which every type evaluates. ## Rule: Keys Live on the Node, Not in a `props` Envelope @@ -247,8 +244,7 @@ nothing thrown, a console warning each (`[ObjectUI] DataTable data:`; the dev-build `props`-bag warning). **Do not read that empty table as "the provider has no data."** The route this skill teaches is the last row — the host resolves the array and puts it on the node — for the -reason given under "Rule: Keys Live on the Node": the third row works today, -but its channel is an open contract question, not a taught surface. +reason given under "Rule: Keys Live on the Node". ## Rule: Actions Are Node Types, Not An Event Bag @@ -307,7 +303,11 @@ form-supported type (`select`, `lookup`, `date`, `file`, `image`, `richtext`, ``` - Inline params: `name` + `type` + widget config (`options`, `multiple`, - `accept`, `maxSize`, `defaultValue`, `placeholder`, `helpText`). + `accept`, `maxSize`, `defaultValue`, `placeholder`, `helpText`, `widget`). + `widget` names a registered field widget that draws the param instead of + its `type` widget (`'permission-ref'` on a `text` param is the + permission-set picker); an unregistered name keeps the `type` widget, and + the selection bar's bulk dialog ignores it. The server validates by `type`. - Field-backed params: `field` (+ `objectOverride`) inherits label, type, options, lookup config, `multiple`/`accept`/`maxSize` from the object field; inline properties override.