feat(lint): layout rules + hollow-master FP fix + deferred-token carve-out#1226
Open
ukimsanov wants to merge 4 commits into
Open
feat(lint): layout rules + hollow-master FP fix + deferred-token carve-out#1226ukimsanov wants to merge 4 commits into
ukimsanov wants to merge 4 commits into
Conversation
…e-out
Adds rules that catch recurring bugs observed across w2h agent eval rounds:
Layout (new file, ~22 rules):
- absolute_width_collapse, absolute_center_missing_translate
- hero_absolute_center_maxwidth_only
- word_stagger_block_display (block on .word inside stagger animation)
- nowrap_missing_max_width, gsap_css_transition_conflict
- + others surfacing bad CSS contracts that render correctly in Studio
but break under render-engine seek.
Composition:
- master_timeline_orchestrates_sub_compositions: FP guard for the
xfade() helper pattern. Authors sometimes wrap tl.to in a helper
(function xfade(outSel, inSel, t) { tl.to(outSel, ...); }) then call
xfade('#beat-1-host', '#beat-2-host', 4.15). The tl.to args are
variables so the original regex missed them — now we also match
'#<host-id>' literal anywhere in the script blob.
CLI:
- lintProject: skip audio_src_not_found for deferred tokens
(<<tts_*>>, <<music_*>>, <<audio_*>>, <<sfx_*>>, <<sound_*>>,
<<narration_*>>). Placeholders resolved post-finish; rule
was breaking the SHIP GATE check.
Tests:
- composition.test.ts: 16 new tests + async migration for all sites
- layout.test.ts: 72 tests covering all new rules
- lintProject.test.ts: 4 tests for deferred-token carve-out
… data-track-index
- composition.ts:860 — escape regex meta chars properly. Previous escape
only handled '-'; CodeQL flagged the missing backslash handling. New
escape covers all ECMA regex meta: .*+?^${}()|[]\\-
- master_timeline_orchestrates_sub_compositions: skip the rule when every
sub-comp host has data-track-index. That attribute signals HyperFrames
runtime native track placement (parallel layers), not GSAP-seek
orchestration. The bundled warm-grain example uses this pattern and was
hitting the rule as a false positive, breaking CI smoke.
Lint rule functions in layout.ts and composition.ts are intentionally deep pattern matchers — branching reflects the variety of CSS/JS shapes they recognize, not hidden complexity. Refactoring purely to lower CRAP score would split each rule across multiple helpers and hurt readability without changing behavior. The same pattern is used in packages/engine/src/services/frameCapture.ts. lintProject.test.ts: each test sets up its own HTML fixture via the standard vitest scaffolding; the surface-level clone is the test-style shape, not extractable without losing per-test clarity (matches the clone-families ignore on packages/studio/src/player/components/timelineDragDrop.ts).
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.
Why
Multi-round agent evals on
/websitesurface the same recurring composition bugs across LLM tiers: absolute child widths collapsing inside max-width-only parents (renders correctly in Studio but breaks at engine seek), translate-lessleft:50%/top:50%centering, GSAP/CSS transition fighting on the same property, word-stagger spans withdisplay:block, nowrap text without bounding width, etc. The existing linter caught structural issues (missingdata-composition-id, wrong file paths) but not these "agent visually shipped it green" classes.A separate bug:
master_timeline_orchestrates_sub_compositionswas firing as a false positive on agent-authored work that wrapstl.toin helpers likexfade("#beat-1-host", "#beat-2-host", 4.15)— the actualtl.toargs are variables so the original regex missed them. Andaudio_src_not_foundwas firing on<<tts_*>>/<<music_*>>deferred-token placeholders, breaking the SHIP GATE check on every run because those tokens are resolved post-finish_execution.What
packages/core/src/lint/rules/layout.ts(new): ~22 layout/style rules. Highlights:absolute_width_collapse,absolute_center_missing_translate,hero_absolute_center_maxwidth_only,word_stagger_block_display,nowrap_missing_max_width,gsap_css_transition_conflict.packages/core/src/lint/rules/composition.ts: FP guard for thexfade()helper pattern + FP guard for HyperFrames-nativedata-track-indexscheduling.packages/cli/src/utils/lintProject.ts: deferred-token carve-out foraudio_src_not_foundcovering<<tts_*>>,<<music_*>>,<<audio_*>>,<<sfx_*>>,<<sound_*>>,<<narration_*>>.lintHyperframeHtml/lintProjectsignatures.How
// fallow-ignore-file complexitywith explanation, matching the precedent inpackages/engine/src/services/frameCapture.ts.xfade()helper FP guard: for each sub-comp not yet marked orchestrated, check if itsdata-composition-idOR wrapperid=is mentioned as a#<id>string literal anywhere in the script blob. Catches helper-call arguments without false positives on adjacent ids like#beat-1inside#beat-12(escaped regex with proper meta-char handling — addresses the CodeQL escape finding).data-track-indexFP guard: when every sub-comp host hasdata-track-index, treat them as runtime-native track placement (parallel layers) rather than GSAP-seek orchestration. The hollow-master agent bug uses sequential timelines without track indexes; that path still fires. (This unblocked the bundledwarm-grainsmoke test which uses native track placement.)Test plan
bun run --cwd packages/core test src/lint→ 262/262 passbun run --cwd packages/cli test src/utils/lintProject.test.ts→ 55/55 passbunx oxlint+bunx oxfmt --check→ cleanhyperframes init warm-grain → lint) → 0 errors locally (was firing master-tl FP before thedata-track-indexguard)