Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/30549-skills-action-param-widget.md
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 6 additions & 5 deletions skills/objectstack-ui/rules/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand All @@ -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'],
});
Expand All @@ -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'],
Expand All @@ -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.

Expand Down
20 changes: 10 additions & 10 deletions skills/objectui/rules/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
Loading