Uh oh!
There was an error while loading. Please reload this page.
fix(app-shell): arm the create deep link on the create action, not on any toolbar action (#4123) - #4166
Merged
Conversation
… any toolbar action (#4123) `EnvironmentListToolbar` armed `useAutoRunCreate` from `toolbarActions.length > 0`. Consumption of `?runAction=create_environment` is modelled as stripping the param, so arming is destructive: a toolbar with any other action stripped the deep link and triggered nothing (measured with the real action:bar + action:button runner: urlParam=null execute=0), leaving the intent unrecoverable on reload. Arming now keys on the create action's presence, and that presence is read from a list built with BOTH predicates action:bar applies to what it renders -- `actionRendersAt` plus the ADR-0066 D4 capability gate (`useCapabilityGate`). The toolbar previously filtered placement only, so a create action the caller may not invoke was counted here and dropped there; one list means arming and rendering cannot disagree by construction. The loading skeleton and the plan-locked upgrade CTA, both stand-ins for the create button, follow the same list. The #3803-verified consumption ordering is untouched and re-pinned: the runner consumes autoTrigger before the parent strips the param. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 10, 2026 15:25
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 10, 2026
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 freeto 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.
Fixes#4123
Premise check
Still valid at
origin/main(6b6721ccf).EnvironmentListToolbar.tsxarmed ontoolbarActions.length > 0 ? ctaKind : null, unchanged since #3803 closed, and #3803's verified consumption ordering (runner executes before the parent strips) is untouched by this PR and re-pinned by a test.Phase 1 — the open reachability question, answered and measured
The card asked whether
toolbarActionsand the listaction:baractually renders can genuinely diverge, with the ADR-0066 D4 capability gate as the candidate path. They can, and the divergence is by construction:a?.locations?.includes('list_toolbar');action:baradditionally applies the D4 capability gate to what it renders (action-bar.tsx:actions.filter(a => mayInvoke(a?.requiredPermissions)), thenactionRendersAt).So a create action declaring
requiredPermissionsthe caller lacks is counted by the toolbar and dropped by the bar. Measured with the real renderer +action:bar+action:button(the sibling test file stubsSchemaRenderer, which is precisely why this class was invisible to it):Both reproduce the card's exact measurement. Note this needs no change to cloud metadata to be wrong — the arming condition is simply keyed on the wrong thing, which is what the triage comment said when it promoted the card.
Phase 2 — the fix
One predicate over one list. The toolbar now builds its action list with both of the predicates the bar applies downstream —
actionRendersAtplususeCapabilityGate— and arms on the create action's presence in that list:useCapabilityGateis the shared hook that exists exactly for surfaces filtering their own action lists instead of routing throughActionEngine.getActionsForLocation("one hook so all three surfaces gate identically and can't drift apart"). This toolbar was a fourth such surface that bypassed it. The bar re-applies both predicates to the list it receives, which is idempotent on an already-filtered list — so arming and rendering cannot disagree by construction rather than by agreement of two copies.The mechanism is NOT generic, so it was not generalized. The dispatch asked to measure before generalizing
?runAction=Xto any action.runActionis read in exactly one place, hard-keyed toCREATE_ACTION; the only generic part isautoTriggeron the action schema, which is the transport, not the URL contract. Generalizing would be inventing a URL-to-action surface with no producer — declined per the startup-focus principle.The chosen degradation, and why it is the honest one. When arming is unsatisfiable the URL is left alone. Consumption is the strip, so the alternative (strip anyway) destroys a one-shot user intent that nothing can recreate. Leaving it means the next mount that can act on it still does — a reload, or the action arriving with fresh metadata — which is pinned as its own test. The cost is a param that lingers when no create action ever appears; that is cosmetic and, unlike the current behaviour, recoverable.
Consequences of the one list. Two other affordances derive from it and previously disagreed with the bar in the same way:
environment-add-upgradeCTA stands in for the create action so a free-plan click opens the upgrade prompt instead of POSTing into a 403. With no create action on the bar there is nothing to stand in for, and it used to render for a toolbar that had no create action at all.Both now read
hasCreateAction. This is deliberately in scope: leaving them on the old list would re-create the two-lists bug the fix exists to remove.Tests
New file
EnvironmentListToolbar.deepLinkArming.test.tsx— 10 cases over the realaction:bar+action:button+ runner (handlers: { api }counts executions, ahistory.replaceStatespy records the strip).Pins, per the card:
execute=0, and a later mount with the create action still runs it (recoverability);?runAction=create_environment的 autoTrigger 可能被无关 re-render 抹掉 #3803 ordering re-pinned — instrumented events equal["runner:execute","parent:consumed+strip"];Reverse verification, direction predicted before running: reverting only the arming predicate (keeping the gated list) turns exactly 3 pins red, each reproducing the card's signature —
['parent:consumed+strip']logged withexecutenever called, andrunActionParam()back tonull. The other 3 stay green because they are held by the list, not the predicate; that split is the attribution working correctly.Gates (repo root, per AGENTS.md §怎么跑测试):
The residual
react-hooks/refswarning atreturn shouldRun;is pre-existing and left untouched, as #3803's closing note ruled (render-purity smell with no constructible divergence).Out of scope
Filed #4162 (observation-class, measured):
autoTriggeris consumed only byaction:button, so an auto-triggered action that spills pastaction:bar'smaxVisibleis rendered byaction:menu, which ignores the flag —urlParam=null execute=0again, one layer deeper, and not closed by this PR (the create action is in the post-gate list there, so arming is correct by this predicate; the bar then hands it to a renderer that drops it). The right shape is a design question with at least three candidate answers, so it is filed rather than patched here.Generated by Claude Code