Follow-up to #3713 / #3742, filed per Prime Directive #10 rather than expanding that PR's scope.
What #3742 fixed, and what it didn't
#3742 made the target / execute precedence consistent across all three readers and had the ActionSchema transform drop the alias from its parsed output, so the "two different scripts for one button" divergence is now unrepresentable.
What it did not change: when an author declares both slots with different values, execute is discarded silently. That is now documented, tested and consistent — a big improvement on "the wrong code ran" — but the author still gets no signal that one of the two handlers they wrote is being thrown away.
Per Prime Directive #12 ("reject it at authoring/publish so the error surfaces loudly"), that deserves a warning.
Why it wasn't done in #3742
The natural home is the CLI compile lint pipeline, which already has the right shape for this — advisory findings that never fail the build (lintFlowPatterns, lintLivenessProperties, lintViewRefs in packages/cli/src/commands/compile.ts).
The problem is ordering. Every one of those lints runs on the parsed stack:
// packages/cli/src/commands/compile.ts
const lowering = lowerCallables(normalized); // ~L107 (pre-parse)
const result = ObjectStackDefinitionSchema.safeParse(lowering.lowered); // ~L136
…
const flowLint = lintFlowPatterns(result.data as Record<string, unknown>); // ~L405 (post-parse)
By the time the existing lints see the stack, the transform has already consumed execute — so a lint added alongside them is structurally blind to the conflict it is meant to report.
The string/string case (exactly the one #3713 describes: target: 'preferredHandler', execute: 'legacyHandler') is still visible at line ~136, because lowerCallables deliberately leaves a string pair alone for the spec transform to resolve. So the fix is available — it just needs a new pre-parse lint hook, not another entry in the post-parse block. That's a structural change to the compile pipeline, which is more than the P2 fix warranted.
Suggested shape
- Add a pre-parse lint pass in
compile.ts between lowering and safeParse, operating on lowering.lowered.
- First rule:
action.target and action.execute both present as strings with different values → advisory warning naming both, stating that target wins and execute is discarded, and pointing at the one-line fix (delete execute). Equal values are harmless — stay quiet.
- Advisory only, never fails the build, consistent with
lintFlowPatterns / lintLivenessProperties.
Worth considering while there: a pre-parse hook is the only place any "author wrote a deprecated alias that parse will consume" warning can live, so the pass is likely to earn its keep beyond this one rule.
Refs #3713, #3742, #1891.
Follow-up to #3713 / #3742, filed per Prime Directive #10 rather than expanding that PR's scope.
What #3742 fixed, and what it didn't
#3742 made the
target/executeprecedence consistent across all three readers and had theActionSchematransform drop the alias from its parsed output, so the "two different scripts for one button" divergence is now unrepresentable.What it did not change: when an author declares both slots with different values,
executeis discarded silently. That is now documented, tested and consistent — a big improvement on "the wrong code ran" — but the author still gets no signal that one of the two handlers they wrote is being thrown away.Per Prime Directive #12 ("reject it at authoring/publish so the error surfaces loudly"), that deserves a warning.
Why it wasn't done in #3742
The natural home is the CLI compile lint pipeline, which already has the right shape for this — advisory findings that never fail the build (
lintFlowPatterns,lintLivenessProperties,lintViewRefsinpackages/cli/src/commands/compile.ts).The problem is ordering. Every one of those lints runs on the parsed stack:
By the time the existing lints see the stack, the transform has already consumed
execute— so a lint added alongside them is structurally blind to the conflict it is meant to report.The string/string case (exactly the one #3713 describes:
target: 'preferredHandler', execute: 'legacyHandler') is still visible at line ~136, becauselowerCallablesdeliberately leaves a string pair alone for the spec transform to resolve. So the fix is available — it just needs a new pre-parse lint hook, not another entry in the post-parse block. That's a structural change to the compile pipeline, which is more than the P2 fix warranted.Suggested shape
compile.tsbetween lowering andsafeParse, operating onlowering.lowered.action.targetandaction.executeboth present as strings with different values → advisory warning naming both, stating thattargetwins andexecuteis discarded, and pointing at the one-line fix (deleteexecute). Equal values are harmless — stay quiet.lintFlowPatterns/lintLivenessProperties.Worth considering while there: a pre-parse hook is the only place any "author wrote a deprecated alias that parse will consume" warning can live, so the pass is likely to earn its keep beyond this one rule.
Refs #3713, #3742, #1891.