Skip to content

perf: back calculator getters with $derived (#689)#703

Open
grzanka wants to merge 1 commit into
masterfrom
claude/issue-689-hjT9Q
Open

perf: back calculator getters with $derived (#689)#703
grzanka wants to merge 1 commit into
masterfrom
claude/issue-689-hjT9Q

Conversation

@grzanka
Copy link
Copy Markdown
Contributor

@grzanka grzanka commented Jun 4, 2026

Closes #689.

What

The public getters rows, stpDisplayUnit, and validationSummary in src/lib/state/calculator.svelte.ts each called a compute*() / get*() function on every property access, rather than exposing memoized $derived values:

get rows() { return computeRows(); }                 // re-parses every row, every read
get stpDisplayUnit() { return getStpDisplayUnit(); }
get validationSummary() { return computeValidationSummary(); }

Templates read these several times per frame (table body, footer, validation banner, export wiring), so the per-row parse/convert/result-lookup work ran multiple times for a single render, and each read handed back a fresh object — a latent identity hazard for any === / capture-and-compare call site.

Change

  • Introduced module-level $derived values, matching the existing pattern in entity-availability.svelte.ts:
    • const rows = $derived(computeRows())
    • const stpDisplayUnit = $derived(getStpDisplayUnit())
    • const validationSummary = $derived.by(...) — now derives from the memoized rows value instead of re-running computeRows(), so one parse pass backs both the row view and the validation counts.
  • Public getter surface is unchanged (each getter returns its derived value), so no call sites change.
  • Confirmed the compute* / get* helpers are pure reads of reactive state (no mutation), so no untangling was needed.

Acceptance criteria

  • rows, validationSummary, stpDisplayUnit are backed by $derived.
  • Reading a getter twice without an input change returns the same memoized value (added a focused referential-stability test, plus a complementary test asserting the reference is invalidated after an input change).
  • No behavioural change in calculator output; existing calculator-state.test.ts passes unchanged.
  • pnpm check (0 errors), pnpm lint (clean), calculator-state.test.ts 59/59 pass.

Notes

The only full-suite failures are in guard-forbidden-files.test.ts, which fail because git commit signing is unavailable in the CI sandbox (the signing server returns HTTP 400 during the test's makeRepo). They are unrelated to this change.

https://claude.ai/code/session_01Tvdg51J44XeNxiwP9bnEjS


Generated by Claude Code

The public getters `rows`, `stpDisplayUnit`, and `validationSummary` in
`calculator.svelte.ts` each ran a `compute*()` / `get*()` function on every
property access, re-parsing every input row several times per frame and
returning a fresh object on each read. Replace them with module-level
`$derived` values (matching `entity-availability.svelte.ts`), so each
computation is memoized and only reruns when a tracked dependency changes.

`validationSummary` now derives from the memoized `rows` value instead of
calling `computeRows()` a second time, so one parse pass backs both views.
The public getter surface is unchanged; no call sites change.

Add referential-stability unit tests asserting a getter read twice without
an input change returns the same reference, and that the reference is
invalidated after an input change.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves calculator render-time performance and correctness-by-identity by memoizing hot public getters in createCalculatorState() with Svelte 5 $derived, avoiding repeated per-row parsing/conversion on each property access.

Changes:

  • Backed rows, stpDisplayUnit, and validationSummary getters with memoized $derived / $derived.by values (with validationSummary derived from the memoized rows).
  • Added unit tests asserting referential stability across repeated reads and invalidation after an input change.
  • Logged the AI session (changelog entry + detailed session log) per repo process.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/lib/state/calculator.svelte.ts Replaces recompute-on-access getters with $derived-memoized views; makes validationSummary depend on memoized rows to avoid duplicate row parsing.
src/tests/unit/calculator-state.test.ts Adds focused tests for derived getter memoization (stable references) and invalidation on input updates.
docs/ai-logs/README.md Adds the new session log entry to the index table.
docs/ai-logs/2026-06-04-issue-689-derived-getters.md Adds the detailed AI session narrative and task record for Issue #689.
CHANGELOG-AI.md Prepends a new bullet entry for this session with a link to the detailed log.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

calculator.svelte.ts: back recompute-on-access getters with $derived

3 participants