fix(actions): defuse the three action-visibility traps (#2358) — clean rebase, replaces #2575#2611
Merged
Merged
Conversation
Trap 1 — os.user identity alias: the spec's canonical CEL identity scope
is os.user.* (server formula/validation/sharing), but client predicate
scopes only bound current_user / user / ctx.user, so a predicate authored
against the server dialect threw and was fail-closed hidden for everyone.
ExpressionProvider, ActionRunner (and thus ActionEngine), and the
page:header evaluator now all expose an os.user alias derived from the
signed-in user, preserving any consumer-provided os namespace keys.
Trap 2 — record_more never rendered: actions declaring only
locations: ['record_more'] were filtered out of the record page header
entirely. Per the spec, record_more is "the overflow menu under the ⋯
button on a record" — RecordDetailView and PageHeaderRenderer now accept
it, and record_more-only actions are always routed into the ⋯ overflow
menu (never an inline slot).
Unified diagnostics — no more silent fail-closed hide: a throwing
visible/hidden predicate still hides the action, but now warns once per
(action, predicate) with the action name, the predicate source, and the
failure reason (page:header evaluator + useCondition({ throwOnError })
consumers action:button / action:menu / action:bar).
Trap 3 heuristic — predicates referencing record.<field> keys absent
from a non-empty record payload warn once, pointing at hidden:true
fields being stripped from detail payloads server-side (the stripping
itself is framework-territory and needs confirmation there).
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
July 16, 2026 13:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2358. Replaces #2575.
Why a new PR
#2575's conflict-resolution PR (#2608) was squash-merged into its head branch, which discarded main's commit history while keeping its content. That pushed the merge-base back to the branch's creation point, so every subsequent commit on main re-conflicted against the branch — including phantom conflicts in files the PR never touched — and polluted #2575's diff to 206 files (+7889/−4327) instead of the real 13 files. This PR is the original commit (
758559e) cherry-picked onto latest main (b4d6ee3) with conflicts resolved once, restoring a clean, reviewable diff.What (unchanged from #2575)
All three action-visibility traps share one root cause:
visibleevaluation failure is fail-closed silently. This PR fixes the two client-side traps outright, adds a heuristic warning for the third, and makes every fail-closed hide diagnosable:os.useridentity alias (spec parity):ExpressionProvider,ActionRunner/ActionEngine, and thepage:headerevaluator now expose anos.useralias derived from the signed-in user, so predicates authored against the server CEL dialect (os.user.*) no longer throw and fail-closed hide for everyone. Consumer-providedoskeys are preserved;updateContextre-derives the alias.record_morenever rendered:RecordDetailViewandPageHeaderRendereracceptrecord_morealongsiderecord_header;record_more-only actions are always routed into the ⋯ overflow menu and never occupy an inline slot.visible/hiddenpredicate still hides the action but now warns once per (action, predicate) with the action name, predicate source, failure reason, and scope shape — covering thepage:headerevaluator anduseCondition({ throwOnError })consumers via a newlabeloption.hidden: truefields; only FLS masking removes fields. The header evaluator warns once when a predicate referencesrecord.keys absent from a non-empty payload; present-but-nullfields don't trigger it.Conflict resolutions vs latest main
containers.tsx: kept [components] 记录详情页头只允许一个主按钮(INLINE_MAX=1),无法并排多主按钮 #2361's metadata-drivenmaxVisible/action:menusplit (which replaced the old fixedINLINE_MAXsplit this branch originally patched) and layered therecord_morerule on top — overflow order: past-maxVisiblebuttonables,action:menu-pinned, thenrecord_more-only. Also kept feat(detail+fields+components+app-shell): record inline-edit polish — expanded-value passthrough, approval-lock preflight, numeric inputs, edit-CTA gate, keyboard shortcuts (#2572) #2604'sisActionDisabledinline-edit gating.page-header-actions.test.tsx: kept all appended suites — the [app-shell] 动作 visible 三陷阱:身份变量 user≠os.user、record_more 不入页头 ⋯、引用 hidden 字段 fail-closed 静默隐藏 #2358 trap suite plus main's [components] 记录详情页头只允许一个主按钮(INLINE_MAX=1),无法并排多主按钮 #2361 split and Record-level inline edit polish (follow-up to #2407): expanded-value passthrough, approval-lock preflight, numeric inputs, edit-CTA state, keyboard shortcuts #2572 inline-edit gate suites.Tests
page-header-actions.test.tsx+ActionRunner.test.ts+ActionEngine.visibility.test.ts+useExpression.test.ts: 127/127 pass on the rebased tree.packages/componentstsc --noEmit: clean.Bug fix — no changeset per AGENTS.md.
🤖 Generated with Claude Code
https://claude.ai/code/session_01SswcPC6fzV3qHomg1DFGUE
Generated by Claude Code