fix(frontend): each shared <style src> sheet sits at one block index everywhere, so vite build cannot depend on transform order (#4988)
CI & Build / Plugin hooks (push) Successful in 15s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m40s
CI & Build / Python tests (push) Successful in 2m37s
CI & Build / Build & push image (push) Successful in 56s

Run 8271 failed in the image build: "[vite:vue] Cannot read properties of undefined (reading 'scoped')" on moments-shared.css. plugin-vue caches one descriptor per src file and answers ?index=N from whichever component registered it last. RuleEditorSlideOver had moments-shared at block 2, while its other two importers have it at block 1. Step 2's new imports changed the transform order and exposed the bug.

- RuleEditorSlideOver: moments-shared is now loaded with an @import instead of a third <style src>.
- rules-shared.css sat at block 0 in the five panes and at block 1 in LessonDetailView, LessonEditorView and RuleEditorSlideOver, which is the same latent hazard. The three move to block 0.
- tests/test_frontend_shared_styles.py: a guard that every <style src> sheet sits at one index, plus a test that the guard can fail. vue-tsc cannot see this hazard, and vite build only runs after every test has passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 09:58:41 -04:00
co-authored by Claude Opus 5.5
parent 07d2542479
commit 1774ee3696
4 changed files with 77 additions and 13 deletions
@@ -414,6 +414,12 @@ watch(() => props.ruleId, load);
</div>
</template>
<!-- `.rule-chip` for the "suggested" marker on a lesson — the chip the rule
panes already use, loaded here because the slide-over can open with no
pane that loads it (a link straight to ?rule=N).
Block 0, as in every pane that loads it: a shared src must sit at
one index everywhere (tests/test_frontend_shared_styles.py). -->
<style src="@/assets/rules-shared.css" />
<style scoped>
.kind { border: 1px solid var(--fs-border-color); border-radius: var(--fs-radius-md); padding: var(--fs-space-3); margin: var(--fs-space-3) 0; }
.kind legend { font-size: var(--fs-size-tiny); text-transform: uppercase; letter-spacing: var(--fs-tracking-tiny); color: var(--fs-text-tertiary); padding: 0 var(--fs-space-2); }
@@ -518,8 +524,11 @@ legend { padding: 0 0.35rem; font-size: 0.8rem; color: var(--fs-text-tertiary);
.trash:hover, .close:hover { opacity: 1; }
</style>
<!-- `.rule-chip` for the "suggested" marker on a lesson — the chip the rule
panes already use, loaded here because the slide-over can open with no
pane that loads it (a link straight to ?rule=N). -->
<style src="@/assets/rules-shared.css" />
<style src="@/assets/moments-shared.css" />
<!-- An @import, not a second <style src>: plugin-vue caches ONE descriptor per
src file, and every other importer of moments-shared.css has it as style
block 1. Here it would be block 2, so whichever SFC registered last decided
the lookup and the production build failed with "reading 'scoped'" when
the transform order changed (tests/test_frontend_shared_styles.py). -->
<style>
@import "@/assets/moments-shared.css";
</style>