LanguageRunner: .prettierrc binding effects re-run on every Prettier option toggle even in beginner/lesson mode #80

Closed
opened 2026-05-06 05:01:30 +00:00 by spikerj · 1 comment
Owner

Observation (low-priority perf note)

The two .prettierrc bidirectional binding effects in LanguageRunner (language-runner.ts:452-507) are registered unconditionally (after the if (this.config.injectPrettierRc) config flag check at registration time). Inside each effect, the if (this.mode() !== "sandbox") return; if (this.effectiveSandboxLevel() !== "advanced") return; guards short-circuit when not in advanced sandbox mode — but the effect still RUNS on every signal change it reads (mode, effectiveSandboxLevel, files, AND prettierStore.options()).

In beginner mode, every visual-panel Prettier toggle re-fires the store→file effect, which immediately returns. Cost is O(1) per toggle and signals are fast, but multiplying by 9 options × N toggles × 2 effects adds up over a long session, especially on lower-end devices common among learners.

Suggested fix

Gate the effect creation behind a computed that becomes true only when injectPrettierRc && mode === "sandbox" && effectiveSandboxLevel === "advanced". Use Angular's effect() from inside another effect (or runInInjectionContext lazily) to only register the binding effects when that gate flips to true; tear them down when it flips to false.

Alternative (simpler): leave as-is. The cost is sub-microsecond per signal change and not measurable in practice. This issue exists primarily to flag the design decision so a future profiler-driven optimization knows the trade-off.

Owner pointers

  • libraries/tools/src/components/language-runner/language-runner.ts:432-508 (the two binding effects)
  • libraries/tools/src/components/language-runner/language-runner-prettierrc.spec.ts (the 12-spec test suite covering the seam — any restructure must keep these passing)

Related

Observation from code review of the Prettier integration + JS formatting options panel feature wave; not a bug.

## Observation (low-priority perf note) The two `.prettierrc` bidirectional binding effects in `LanguageRunner` (`language-runner.ts:452-507`) are registered unconditionally (after the `if (this.config.injectPrettierRc)` config flag check at registration time). Inside each effect, the `if (this.mode() !== "sandbox") return; if (this.effectiveSandboxLevel() !== "advanced") return;` guards short-circuit when not in advanced sandbox mode — but the effect still RUNS on every signal change it reads (`mode`, `effectiveSandboxLevel`, `files`, AND `prettierStore.options()`). In beginner mode, every visual-panel Prettier toggle re-fires the store→file effect, which immediately returns. Cost is O(1) per toggle and signals are fast, but multiplying by 9 options × N toggles × 2 effects adds up over a long session, especially on lower-end devices common among learners. ## Suggested fix Gate the effect creation behind a `computed` that becomes true only when `injectPrettierRc && mode === "sandbox" && effectiveSandboxLevel === "advanced"`. Use Angular's `effect()` from inside another effect (or `runInInjectionContext` lazily) to only register the binding effects when that gate flips to true; tear them down when it flips to false. Alternative (simpler): leave as-is. The cost is sub-microsecond per signal change and not measurable in practice. This issue exists primarily to flag the design decision so a future profiler-driven optimization knows the trade-off. ## Owner pointers - `libraries/tools/src/components/language-runner/language-runner.ts:432-508` (the two binding effects) - `libraries/tools/src/components/language-runner/language-runner-prettierrc.spec.ts` (the 12-spec test suite covering the seam — any restructure must keep these passing) ## Related Observation from code review of the Prettier integration + JS formatting options panel feature wave; not a bug.
Author
Owner

Investigated — the described per-toggle re-run does not actually occur, so no gating machinery is warranted. Angular effect() uses dynamic dependency tracking: an effect only re-runs when a signal it read during its last execution changes. Both .prettierrc binding effects read the cheap mode() / effectiveSandboxLevel() guards and early-return before ever reading prettierStore.toJson(). So in beginner/lesson mode they don't depend on the Prettier store at all, and a panel toggle does not re-fire them. The dependency is (re)established only while in advanced sandbox mode, which is exactly when we want it.

Added a comment in spikersoft-angular PR #56 documenting that the guard ordering is intentional for this reason. No functional change needed; implementing dynamic effect creation/teardown would be over-engineering for a cost that isn't incurred. Will close alongside PR #56 (which also fixes #81 in the same effect block). Reopen if you'd still prefer the explicit gate.

Investigated — the described per-toggle re-run does **not** actually occur, so no gating machinery is warranted. Angular `effect()` uses *dynamic* dependency tracking: an effect only re-runs when a signal it read during its **last** execution changes. Both `.prettierrc` binding effects read the cheap `mode()` / `effectiveSandboxLevel()` guards and early-return **before** ever reading `prettierStore.toJson()`. So in beginner/lesson mode they don't depend on the Prettier store at all, and a panel toggle does not re-fire them. The dependency is (re)established only while in advanced sandbox mode, which is exactly when we want it. Added a comment in `spikersoft-angular` PR #56 documenting that the guard ordering is intentional for this reason. No functional change needed; implementing dynamic effect creation/teardown would be over-engineering for a cost that isn't incurred. Will close alongside PR #56 (which also fixes #81 in the same effect block). Reopen if you'd still prefer the explicit gate.
Sign in to join this conversation.