Skip to content

Commit fa3cef9

Browse files
committed
docs(skills): contract-review should-fixes on the fold — confirmText scope, lanes refusal, seriesBy pivot
- actions.md: the `confirmText`-beside-`params` refusal is the registered action's; a view's `bulkActionDefs` entry pairs the two by its own contract (action.zod.ts 2240-2262). Paid by trimming the ctx hooks cross-reference, one lead-in and a "two mechanisms above" sentence. - list-views.md: the unknown-lane refusal is judged against the group field's inline options (validate-list-view-field-refs.ts 370-394), so the note says "not one of the field's inline options". - dashboards.md: the `seriesBy` note names the pivot it replaces (a second `columns` dimension only pivots the table) instead of "not a matrix workaround" under a `type: 'matrix'` example. Paid by dropping the Looker / Power BI aside and the "drill are unified" restatement. Token ratchet: actions.md 2256/2259, list-views.md 3114/3129, dashboards.md 6248/6262. Review record 6550826945. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibz4G9FrLR4TRExx6gn
1 parent 162680e commit fa3cef9

3 files changed

Lines changed: 10 additions & 10 deletions

File tree

‎skills/objectstack-ui/rules/actions.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
- `disabled` — `boolean` **or** a CEL predicate; when true the action **shows but greys out**. Use this (not `visible`) when the action should stay discoverable but locked in the current state.
2020
- `disabledReason` — an I18nLabel (a sentence or an inline locale map) a greyed action shows as its hover title and accessible description. Display-only: never a predicate and never a gate (the hook or permission still refuses). Omit it to keep the console's default "Not available for this record"; a blank one is refused, and `disabledMessage` / `disabledTooltip` / `disabledHint` / `disabledText` are refused naming this key. The reason goes here, not in `description`.
2121
- `pressed` — a CEL predicate over the same scope as `visible` that marks the action as already done by this reader: while TRUE the record header draws it pressed (`aria-pressed`). Display-only, never a gate; a boolean literal is refused (a constant is a style, not a reader's state), and `active` / `activeWhen` / `pressedWhen` are refused naming this key. A per-reader fact (the reader's vote) is a block the object declares under `attachedOnRead`, filled per caller by a service or `afterFind` hook, read as `has(record.viewer) && record.viewer.vote == 'helpful'`.
22-
- `confirmText` — set for any destructive or irreversible operation; the confirm dialog of a param-less action, refused beside a non-empty `params` (put the question on `description`).
22+
- `confirmText` — set for any destructive or irreversible operation; the confirm dialog of a param-less action. A registered action is refused it beside a non-empty `params` (put the question on `description`); a view's `bulkActionDefs` entry pairs the two by its own contract.
2323
- `submitText` — the verb on the param dialog's submit button ("Send", "Log Call") in place of "Confirm": an I18nLabel, refused blank and refused without a non-empty `params` (no dialog opens); `submitLabel` / `confirmLabel` are refused naming this key.
2424
- `successMessage` / `errorMessage` — toast copy on success / failure. Set `successMessage` for non-obvious outcomes; without it the UI shows a generic "Action completed" toast.
2525
- Copy holes are `{{ }}`: `confirmText`, `description` (`'Choose the new owner of {{ record.name }}.'`) and a param's `helpText` (`'Why {{ record.name }} is paused'`) read `record`; `successMessage` and each `outcomeMessages` entry (`resent: 'Invitation resent to {{ result.email }}'`) read `result` (the handler's answer) and `record`; `errorMessage` takes none. A `bulkActionDefs` entry's `confirmText` reads `{{ count }}` and its params by bare name (`{{ tier }}`). In all of them `${record.name}`, `{name}`, logic in a hole and a blank sentence fail `os validate`.
@@ -72,7 +72,7 @@ export const ConvertLeadAction = defineAction({
7272
});
7373
```
7474

75-
**Script-typed action** (collect params, then execute server body):
75+
**Script-typed action** (params, then a server body):
7676

7777
<!-- os:check -->
7878
```typescript
@@ -113,8 +113,8 @@ export const AddToCampaignAction = defineAction({
113113
A server-side `body` (or a registered `handler`) receives `ctx` with `input` (the dialog params), `record` (the target row, when a
114114
`recordId` is in scope), `api` (scoped cross-object CRUD) and the caller. Read
115115
the active organization as `ctx.user?.organizationId ?? ctx.session?.organizationId`
116-
— the blessed name, with the same isolation axes and `undefined` cases as hooks
117-
(objectstack-data hooks reference). Bodies execute **trusted** (`ctx.api` /
116+
— the blessed name (same axes and `undefined` cases as hooks, objectstack-data).
117+
Bodies execute **trusted** (`ctx.api` /
118118
`ctx.engine` bypass RLS/FLS), so a body that must scope by org reads it from
119119
`ctx` explicitly. `ctx.session.positions` (absent when the caller holds none) is
120120
**not an authorization input** — ask the security service for privilege (ADR-0095).
@@ -167,7 +167,7 @@ export const OpenInvoicePdfAction = defineAction({
167167
168168
> ⚠️ **Never express new-tab behavior via `params`** — it is `ActionParam[]` for
169169
> **user input** only: `params: [{ name: 'newTab', type: 'checkbox' }]` builds and
170-
> draws a checkbox in the dialog. Use one of the two mechanisms above.
170+
> draws a checkbox in the dialog.
171171
172172
## Action Parameter Patterns
173173

‎skills/objectstack-ui/rules/dashboards.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -362,15 +362,14 @@ behind it; **drill-to-record** opens one record.
362362
object's *full* list page (sort / bulk-select / export / shareable URL),
363363
scoped by the same drill filter. The in-place drawer is the default (peek
364364
without losing the dashboard); the escape hatch escalates when the user wants
365-
the full surface — the Looker / Power BI "see records → open page" model.
365+
the full surface.
366366
* **`metric` / `chart` widgets are not click-drillable** in the dataset form
367367
(they render the aggregate only; `compareTo` still applies). Surface the detail
368368
through a `table` / `pivot` widget instead.
369369

370370
**Reports drill the same way.** A `summary` / `matrix` report (`drilldown`
371371
defaults `true`) opens the identical in-place drawer on row/cell click — peek the
372372
records, click a row to open one, or "Open in list →" for the full list page.
373-
Dashboard and report drill are unified.
374373

375374
---
376375

@@ -418,8 +417,9 @@ export const PipelineCoverageReport = defineReport({
418417
> base object's permission-set `allowExport`; a `joined` report takes neither
419418
> `dateRange` nor `exportOptions`.
420419
> **`seriesBy`** splits the one `yAxis` measure into a series per value of a second
421-
> dataset dimension — not a `matrix` workaround. It must name a declared dimension
422-
> (publish refuses an unknown name) other than `xAxis`, and is refused on the
420+
> dataset dimension: the chart's own split, where a second `columns` dimension
421+
> only pivots the table. It must name a declared dimension (publish refuses an
422+
> unknown name) other than `xAxis`, and is refused on the
423423
> single-series types `pie` / `donut` / `funnel` / `treemap` / `sankey`; with it,
424424
> `series[].name` names one of its values.
425425

‎skills/objectstack-ui/rules/list-views.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ top-level `groupBy`**. The top-level `columns` is required on every list view
279279
> `lanes` chooses the lanes and their order, not the records: a record whose
280280
> value is unlisted lands in `Uncategorized`, so filter the view to keep records
281281
> off the board. Omit it to draw every option in its own order; a value that is
282-
> not an option, or listed twice, fails `os validate`.
282+
> not one of the field's inline options, or listed twice, fails `os validate`.
283283
284284
---
285285

0 commit comments

Comments
 (0)