Skip to content

fix(pages): author page:header actions as action ids, per the protocol - #1656

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-1653-header-actions-by-id
Sep 6, 2026
Merged

fix(pages): author page:header actions as action ids, per the protocol#1656
os-steve merged 1 commit into
mainfrom
claude/issue-1653-header-actions-by-id

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #1653

ui/PageHeaderProps:actions takes action id strings, not ActionDef objects. The four record pages authored objects there, and objectstack lint was reporting every entry correctly. This converts them, retires the exemptions that recorded the non-conformance, and pins that the ids resolve.

Re-measured on this branch's base

Base: 28bf0d998f0a93a4654e1117f2b64d965e4d758f (origin/main at cut time). The card warned its figures were PR #1605-era readings; all three were re-derived here and all three match.

figure card measured on base 28bf0d9 after
component-props-invalid 16 16 0
objectstack lint warnings, total 17 17 1
header KNOWN_UNCONFORMING lines 4 4 (of 5 entries) 0 (1 entry left)
hotcrm @object-ui/* dependencies 0 0 0

The one remaining warning is sales_home_page/ai_briefing :: description (component-props-unknown-key) — #1216, deliberately untouched, and its KNOWN_UNCONFORMING entry stays.

Baseline, verbatim from pnpm lint on the base:

  17 warning(s), 12 suggestion(s) (2298ms)

After:

  1 warning(s), 12 suggestion(s) (1310ms)

The contract, read off the pinned package

From node_modules/@objectstack/spec/dist/ui/index.js (17.3.0), inside PageHeaderProps:

actions: zod.z.array(zod.z.string()).optional().describe("Action IDs to show in header"),

The ledger agrees: packages/spec/authorable-surface/ui.json carries "ui/PageHeaderProps:actions" live, beside a retired sibling written "ui/PageHeaderProps:icon [RETIRED]".

Every id was resolved, and the resolver was shown refusing

The conversion trades a compile-checked import for a bare string, so a typo would swap a shape error for a dangling reference — the worse defect. All 16 ids were resolved against the app's own registry (31 registered actions, read from the built stack): an id resolves when some action carries that name and is reachable from the page's object, since the runtime registers a body action under OBJECTNAME:ACTIONNAME and the dispatcher probes the object name first.

page (object) ids all resolve
account_detail_page (crm_account) log_call log_meeting schedule_meeting yes
case_detail_page (crm_case) escalate_case close_case log_call yes
lead_detail_page (crm_lead) convert_lead schedule_followup log_call log_meeting schedule_meeting yes
opportunity_detail_page (crm_opportunity) generate_quote clone_opportunity log_call log_meeting schedule_meeting yes

All 16 also declare record_header in their own locations, so each is header-eligible by the protocol's placement vocabulary. That reading is reported, not gated on — this PR adds no rule the card did not ask for.

A one-off script proved nothing on its own, so the check is now a guard in test/action-references.test.ts, the file that already owns "action references resolve". It carries its own refusal leg: the same resolver must still reject a name no action carries, and a real name scoped to a different object.

Ablation — both legs mutated on disk, both restored by hash

Run against the committed tree, so the restore leg has something to restore to.

Leg 1 — one id mutated to convert_lead_typo; on-disk proof old-spelling=0 new-spelling=1; vitest exit = 1:

AssertionError: header actions that resolve to nothing:
  lead_detail_page/lead_header: "convert_lead_typo" names no action on "crm_lead"

Leg 2 — one KNOWN_UNCONFORMING header line put back; on-disk proof before=0 after=1; vitest exit = 1:

AssertionError: exemptions whose prop now conforms — delete these lines:

So the four lines genuinely had to go: leaving one would have failed the guard's own stale-exemption arm.

Restore proved by hash, not by exit code — after each leg, the file's git hash-object equals its HEAD blob (e3792acc… for the page, d5788796… for the guard) and git diff HEAD --name-only is empty. Control — the unmutated tree, same two files: 2 passed, 47 tests, exit 0.

Verify

pnpm verify green end to end (validate && typecheck && lint && lint:i18n-gate && hygiene && hygiene:tokens && build && test), at commit 05158b0:

  ✓ source hygiene clean
  ✓ interaction layer ~37,366 tokens (ceiling ~40,000; headroom ~2,634).
  ✓ authored total ~136,546 tokens (ceiling ~140,000; headroom ~3,454).
  ✓ Build complete (1427ms)
  ⚠ 5 author-time warning(s)   ← the #1216 key + 4 pre-existing approval suggestions
 Test Files  161 passed (161)
      Tests  3402 passed | 1 skipped (3403)

The token ratchet moved down on both anchored layers (interaction 37,572 → 37,366; total 136,751 → 136,546); no ceiling was raised, and business semantics is untouched, so the README's ~85k banner band is unaffected.

One declared deviation from the card

The card said to delete the four KNOWN_UNCONFORMING lines and leave the rest of test/metadata-references.test.ts byte-identical. No assertion, helper or fixture in that file changed — but one comment did: the docblock paragraph directly above the set, which existed to explain those four entries. It argued that the source should satisfy objectui's current page:header renderer rather than the spec, and stated that authoring ids "would delete every header button from four record pages". Leaving it would have kept the overturned reasoning in the tree, attached to an exemption that no longer exists, for the next agent to cite.

It is rewritten in the form this same file already uses for a retired exemption — see the sections[].collapsible paragraph two entries down, kept and re-headed "RESOLVED UPSTREAM, exemption removed" — and it now records the 2026-09-06 ruling, verbatim and untranslated, that settled which side moves. Flagging it here rather than choosing quietly; the diff on that file is the four lines plus that one paragraph, and nothing else.

Scope

Renderer behaviour is not reasoned about anywhere in this change. This repo declares zero @object-ui/* dependencies and cannot install them, so it cannot measure that surface — which is why it has no business in this repo's reasoning. Renderer-side id resolution is objectui#6252 / #7182, ruled 2026-09-02 (option C, "ids are the contract").

#1216 is not addressed here. #1279's unlock criteria still need rewriting against this card's conclusion — a record-keeping action, out of scope for this PR.


🤖 Generated with Claude Code

Generated by Claude Code


Generated by Claude Code

`PageHeaderProps.actions` is `z.array(z.string())` — "Action IDs to show in
header" (@objectstack/spec 17.3.0). The four record pages authored whole
`ActionDef` objects there instead, which `objectstack lint` reported as 16
`component-props-invalid` warnings. Convert all four to id arrays; the same
sixteen buttons are named on the same four headers, in the same order.

Every id was resolved against the app's own action registry: each names an
action that exists and is reachable from the page's object (the runtime keys
the registry on `<objectName>:<action.name>`). A new guard in
`test/action-references.test.ts` keeps that true — a bare string carries no
compile-time check, so the conversion would otherwise trade a shape error for
a dangling reference.

The four `KNOWN_UNCONFORMING` exemptions in `test/metadata-references.test.ts`
retire with the non-conformance they recorded. Their docblock paragraph, which
argued the source should follow objectui's current renderer, is replaced by the
2026-09-06 ruling that settled it: a metadata project does not depend on
`@object-ui/components`, and this repo declares no `@object-ui/*` dependency
at all. No assertion in that file changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
@vercel

vercel Bot commented Sep 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Sep 6, 2026 5:03am UTC

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

2 participants