Skip to content

fix(event-display): keep a cut's default range over clone and save/load - #1037

Merged
EdwardMoyse merged 1 commit into
HSF:mainfrom
Shivansh1205:fix/cut-defaults-lost-on-clone-and-fromjson
Sep 15, 2026
Merged

EdwardMoyse merged 1 commit into
HSF:mainfrom
Shivansh1205:fix/cut-defaults-lost-on-clone-and-fromjson

Conversation

@Shivansh1205

Copy link
Copy Markdown
Contributor

Fixes #1036

Problem

A Cut records the bounds it was constructed with as the values reset() restores. clone() and fromJSON() both rebuild a Cut through that constructor using its current bounds, so any narrowing the user applied silently becomes the new default.

fromJSON sits on the state-restore path — StateManager.restoreCutsFromJSON rebuilds every cut that way and hands the result to the menu, which is exactly what the Reset cuts button operates on:

  1. Narrow pT from 0–100 to 40–60, save the state, reload, load it back.
  2. The cut correctly reads 40–60.
  3. Press Reset cuts → it stays at 40–60. The configured range is gone and cannot be recovered without reloading the event.

Widening back out after narrowing in is the point of that button. Because the number shown is the one the user saved, it reads as the button being broken rather than the range having been lost.

Fix

CutJSON had nowhere to carry the defaults, so:

  • Added an optional defaults block (minValue, maxValue, minCutActive, maxCutActive) to CutJSON, populated by toJSON().
  • fromJSON() constructs from those defaults, then applies the saved active values on top — so the restored cut reads what the user saved but resets to the collection's real range.
  • clone() does the same, building from the source's defaults rather than its current values.

Backward compatibility

defaults is optional and falls back to the active values:

Number(defaults?.minValue ?? json.minValue)

State files written before this change load exactly as they do today — no migration, no error. There is a test pinning that.

On clone()

clone() is called once per collection at load time from cuts still at their configured values (phoenix-loader.ts#L253), so it does not misbehave today. I fixed it anyway because it is the same defect and would bite the first time a cut is cloned after adjustment — but flagging it so reviewers know only the fromJSON half is a live user-facing bug.

Tests

Added to cut.model.test.ts:

  • resets a clone to the original defaults, not the narrowed values — fails before
  • keeps the clone resettable even when cloned before any change — fails before
  • restores config defaults after a save/load round trip — fails before (the headline bug: reset() gives 40 where 0 is expected)
  • falls back to the active values for a state file without defaults — pins backward compatibility
  • round trips the active cut flags without changing their defaults

I also updated one existing assertion: toJSON had an exact-shape toEqual that now needs the defaults key. Worth noting that the neighbouring test asserting no private defaultMinValue / defaultMaxValue keys leak into the JSON still passes unchanged, since the defaults are nested under their own key rather than flattened onto the object.

Verification

  • phoenix-event-display: 44 suites / 399 tests passed, 0 failures.
  • phoenix-ng: 62 suites / 221 tests passed, 0 failures.
  • eslint clean on both changed files.
  • tsc --noEmit reports one error in src/tests/helpers/webgl-mock.ts (three.module has no default export). It is pre-existing — identical on a clean tree — and unrelated to this change.

A Cut captures the bounds it is constructed with as the values `reset()`
restores. Both `clone()` and `fromJSON()` rebuild a Cut through that same
constructor using its *current* bounds, so any narrowing the user has applied
silently becomes the new default.

For `fromJSON` this sits on the state-restore path: `restoreCutsFromJSON`
rebuilds every cut that way and hands the result to the menu, which is what the
"Reset cuts" button operates on. So after saving a state with pT narrowed to
40-60 and loading it back, "Reset cuts" returns to 40-60 rather than the
collection's configured 0-100, and the full range cannot be recovered without
reloading the event. Widening back out after narrowing in is the point of the
button, and since the value shown is the one the user saved, it reads as the
button being dead rather than the range having been lost.

`CutJSON` had nowhere to carry the defaults, so add an optional `defaults`
block and populate it in `toJSON`. `fromJSON` constructs from those defaults
and then applies the saved active values on top. The field is optional and
falls back to the active values, so state files written before this change
still load with exactly today's behaviour.

`clone()` gets the same treatment, building from the source's defaults rather
than its current values. It is called once per collection at load time from
pristine cuts, so it does not misbehave today, but it is the same defect.

Added tests for reset-after-clone, reset-after-round-trip, and the legacy
no-defaults fallback. The first three fail before this change. Updated the
existing exact-shape toJSON assertion for the new field; the neighbouring test
asserting no private `defaultMinValue` / `defaultMaxValue` keys leak still
holds, since the defaults are nested under their own key.

Fixes HSF#1036
@github-actions

Copy link
Copy Markdown

🚀 Preview deployed: http://phoenix-pr-1037.surge.sh

Built from 19ad5cd.

@EdwardMoyse
EdwardMoyse merged commit 9ae1430 into HSF:main Sep 15, 2026
4 checks passed

This branch was successfully deployed

1 active deployment
pull-request — 19ad5cd7 Deployed Sep 14, 2026 by github-actions[bot]
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.

"Reset cuts" cannot restore the full range after a state load: Cut.fromJSON and clone() overwrite the defaults

2 participants