Skip to content

docs(guide): quick-start teaches the action:button node, not the unread events bag - #7949

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7936-quickstart-events-bag
Sep 6, 2026
Merged

docs(guide): quick-start teaches the action:button node, not the unread events bag#7949
os-sam merged 1 commit into
mainfrom
claude/issue-7936-quickstart-events-bag

Conversation

@os-sam

@os-sam os-sam commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7936

Clause-②: no — docs example only: no key added, no zod touched, no gate predicate or scan population moved.

content/docs/guide/quick-start.md, Next Steps → Add Actions, declared the events bag as THE way to author actions ("Define them in schema events:") and showed a button node carrying events.onClick. Nothing reads that key. Prose and fence are replaced together — fixing only the fence would move the contradiction rather than remove it.

The four evidence items, reproduced on this worktree (base b8e3f8379)

A — SDUI read points: zero.

$ git grep -nE '\??\.events\b' -- 'packages/*/src' 'apps/*/src'
(no output, exit 1)
$ git grep -nE '\??\.events\b' -- 'packages/*/src' 'apps/*/src' | grep -vE '(\.test\.|\.spec\.|__tests__|/tests?/)' | wc -l
0

B — EventableSchema: exactly three sites, no extender / mirror / reader.

$ git grep -n 'EventableSchema' -- packages apps
packages/types/src/api-types.ts:278:export interface EventableSchema extends BaseSchema {
packages/types/src/api-types.ts:470:  | EventableSchema
packages/types/src/index.ts:729:  EventableSchema,

(The card recorded the re-export at index.ts:718; on this base it sits at :729. Substance identical — one declaration, one union member, one barrel re-export.)

⚠️ Wording, kept precise: EventableSchema is a TypeScript interface in api-types.ts, not a zod schema. An authored events bag survives safeParse because ButtonSchema / BaseSchema are .passthrough(). The accurate statement is declared on the TS face, exported from the barrel, read by nothing at runtime.

C — the DOM pass-through whitelist cites this very key as a leak.

$ sed -n '24p' packages/core/src/utils/dom-props.ts
 *      events="[object Object]" arialabel="Canary label"

D — the refusal message points at a node type, never at the bag. ButtonSchema (packages/types/src/zod/form.zod.ts:160) declares onClick: handlerKeyRefusal('onClick', 'runtime-slot', 'Click handler') (:174), whose remedy string in packages/types/src/zod/tombstone.zod.ts:118-120 reads:

Author behaviour as a NODE TYPE instead — e.g. { "type": "toast", ... } or an action:button node with a declared action, the spelling PR #6498 established.

Surface: exactly one file, and that is measured

$ git grep -n '"events"' -- content/docs
content/docs/api/schema-reference.md:683:      "content": { "type": "timeline", "events": [] }
content/docs/api/schema-reference.md:1144:      "content": { "type": "timeline", "events": [] }
content/docs/guide/quick-start.md:148:  "events": {

The two schema-reference.md hits are timeline's own events array, which has a real reader — not a defect, not touched (the card recorded them at :669,1092; same two occurrences, drifted line numbers). One file changed: content/docs/guide/quick-start.md, 7 insertions, 12 deletions.

The replacement spelling was measured against the BUILT artifacts

Not carried over from the ButtonSchema sweep — ActionSchema and action:button were measured on their own declarations.

Reading Artifact Result
the spec's action vocabulary node_modules/@objectstack/spec/dist/action.zod-CLgwKXBA.d.ts:275-282 ActionType = url | form | flow | script | api | modal
what the runner dispatches packages/types/dist/ui-action.d.ts:168,191,202 RunnableActionType = ActionType | ObjectUiLocalActionType, ObjectUiLocalActionType = 'navigation' — objectui's own alias of url; the source note says "Prefer type: 'url' + openIn in new metadata"
where the URL lives packages/types/dist/ui-action.d.ts, interface UIActionSchema target?: string — "the only handler slot"; openIn?: 'self' | 'new-tab'
what the executor reads packages/core/src/actions/ActionRunner.ts:1418 executeUrl reads action.target || action.redirectnot params.url
what the node forwards packages/components/src/renderers/action/action-button.tsx:160,166 type: schema.actionType, target: schema.target

So the intent of the old example (navigate to /users/ada) is authored as actionType: "url" + target: "/users/ada" on an action:button node — the dialect already landed in content/docs/guide/record-edit-modes.md:66-110.

Reverse verification (planted-failure direction, run against the built packages/types/dist):

$ cat probe/action-type-probe.ts
import type { RunnableActionType, UIActionSchema } from '../packages/types/dist/index.js';
export const good: RunnableActionType = 'url';
export const bad: RunnableActionType = 'navigate';
export const t: UIActionSchema['target'] = '/users/ada';

$ node_modules/.bin/tsc --noEmit --ignoreConfig --strict --moduleResolution bundler \
    --module esnext --target es2022 --skipLibCheck probe/action-type-probe.ts
probe/action-type-probe.ts(3,14): error TS2322: Type '"navigate"' is not assignable to type 'RunnableActionType'.

Exactly one error, the planted one: 'url' compiles, 'navigate' — the action name the old fence taught — does not, and target accepts the path. The probe file was deleted before the commit (git status clean, only the one doc file modified).

Gates — all run on the final commit ca586a664, git diff HEAD empty

Derived from the changed path by grep -rln "content/docs" scripts/*.mjs (every gate script whose population reads content/docs), plus the checks the dispatch named.

Gate Exit Verdict line
check:doc-types 0 ✅ Every documented component type is registered. (889 type literals, 772 registered, 117 exempted — identical to the pre-edit baseline)
check:doc-fences 0 ✅ check:doc-fences — every TypeScript block in 227 document(s) is fenced ts/tsx/typescript…
check-doc-links 0 Links are valid across 17 scan roots.
check-doc-expression-carriage 0 census unchanged before/after — see below
check:control-bytes 0 ✅ check-control-bytes: OK (scanned 6407 tracked text file(s); skipped 85 binary).
check:doc-snippets 0 Every covered documentation snippet compiles against the built types. (467/467 judged, 0 failed)
check:skill-examples 0 Every marked skill example holds up against the built types.
check:doc-example-readers 0 OK 80 documented symbol(s), 3947 call site(s)…
check:docs-route-closure 0 clean
check:shell-escape-residue 0 ✅ check-shell-escape-residue: OK (5/5 root(s) resolved…)
check:skills-paths 0 clean

check:doc-snippets and check:skill-examples both exit 2 (PREREQUISITE NOT MET, not a verdict) on an unbuilt tree; both were run after building this gate's own --build-filter closure — turbo run build … --concurrency=2, 34/34 tasks successful, through the container's shared verify lock (VERDICT command-exit 0 · held the lock 208s · waited 51s).

check-doc-expression-carriage census, before and after — identical, as expected (both fences are literals, outside its population):

Scanned 184 file(s) under content/docs: 204 json/jsonc fence(s), 204 parsed, 0 UNPARSED (1 parsed as an object body).
361 node(s) with a string `type`; 65 ${…} site(s) on those nodes, 61 of them carried.
⚠️  4 site(s) in 3 page(s) author ${…} on a key nothing evaluates

Changeset — quoted verbatim from node scripts/check-changeset-presence.mjs (exit 0), and obeyed:

✅ No source or published contract of a released package changed in this range, so no changeset is owed.

No skip-changeset label applied: in this repository that label is read by nothing, and the checker's verdict line is the authority.

Deliberately not touched

  • content/docs/api/schema-reference.mdtimeline's own events, real reader.
  • content/docs/core/enhanced-actions.mdx — its actionType: 'ajax' and api: are legal for ActionSchema, and that was measured rather than assumed: packages/types/src/zod/crud.zod.ts:64-71 declares type: z.literal('action') with actionType: z.enum(['button','link','dropdown','ajax','confirm','dialog']) and api: z.string().optional(). A different schema from ButtonSchema, with its own vocabulary. Not a defect; not changed.
  • The Learn the full action model in [Enhanced Actions] line below the fence — outside the declared surface.
  • No gateway built.

Out-of-scope finding filed

#7945 — the published skills/objectui skill (SKILL.md, guides/page-builder.md, rules/protocol.md) and AGENTS.md still teach the same events bag across 5 sites, one of them worded as a Rule on a customer-published surface. Both are governance surfaces this seat must not edit, so it is reported, not fixed. That issue is filed with no assignee and is not addressed here.


Generated by Claude Code

The Next Steps -> Add Actions section declared the `events` bag as THE way to
author actions ("Define them in schema events:") and showed a `button` node
carrying `events.onClick`. Nothing reads that key.

Measured on this tree:

  git grep -nE '\??\.events\b' -- 'packages/*/src' 'apps/*/src'   -> 0 hits
  git grep -n 'EventableSchema' -- packages apps                  -> exactly 3:
      packages/types/src/api-types.ts:278  own declaration (a TS interface)
      packages/types/src/api-types.ts:470  one union member
      packages/types/src/index.ts:729      barrel re-export
    no extender, no mirror, no reader.

`packages/core/src/utils/dom-props.ts:24` cites `events="[object Object]"` as an
example of the DOM leak its pass-through whitelist exists to close, and the
`handlerKeyRefusal` remedy string points authors at a NODE TYPE ("an
action:button node with a declared action"), never at the bag. The bag is an
authored key an interface declares and `.passthrough()` keeps, not a channel:
objectui#6182's 2026-08-25 Option A ruling re-priced it to an ADR-0049
retirement.

Prose and fence are replaced together, since fixing only the fence would move
the contradiction rather than remove it. The replacement is the landed dialect
(objectui#7898 / PR objectui#7931): an `action:button` node whose `actionType`
names the executor and whose `target` carries the location.

Spelling measured against the BUILT artifacts, not carried over from the
ButtonSchema sweep:

  node_modules/@objectstack/spec/dist/action.zod-CLgwKXBA.d.ts:275-282
      ActionType = url | form | flow | script | api | modal
  packages/types/dist/ui-action.d.ts:168,191,202
      RunnableActionType = ActionType | 'navigation'   ('navigation' is
      objectui's own alias of `url`; the source note prefers `url`)
  packages/types/dist/ui-action.d.ts, interface UIActionSchema
      target?: string   -- "the **only** handler slot"
  packages/core/src/actions/ActionRunner.ts:1418
      executeUrl reads `action.target || action.redirect` (not params.url)
  packages/components/src/renderers/action/action-button.tsx:160,166
      forwards `type: schema.actionType` and `target: schema.target`

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
@os-sam
os-sam marked this pull request as ready for review September 6, 2026 04:17
@os-sam
os-sam added this pull request to the merge queue Sep 6, 2026
Merged via the queue into main with commit 97c2a0b Sep 6, 2026
30 checks passed
@os-sam
os-sam deleted the claude/issue-7936-quickstart-events-bag branch September 6, 2026 04:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants