Repository navigation
fix(ladder): dispose the timeline chart when leaving its section - #1357
Conversation
The Chart.js instance behind the ladder planned-purchase timeline, with its ResizeObserver and listeners, was only destroyed when the timeline was re-initialized, so it stayed alive after the user navigated away from Settings > Purchasing. Visiting the General sub-tab also rebuilt it inside the hidden Purchasing panel, because loadGlobalSettings runs the ladder init for both sub-tabs. navigation.ts now calls disposeLadderTimeline() when switching to any top-level tab other than Settings and to any settings sub-tab other than Purchasing. Dispose destroys the chart and drops in-flight loads. initLadderTimeline skips building while its section is hidden, and the existing loadGlobalSettings call re-initializes the timeline when the Purchasing sub-tab is shown again. The other items in the issue (409 reload, focus restore after save, no tab stop on the canvas) were shipped in LeanerCloud#688. Refs LeanerCloud#667 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 5 billable files and costs up to $1.25. Or wait 59 minutes for your next included review. View limit details
Comment |
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE Blocking:
|
|
Local verification at
|
|
Marked ready and merged by IAmJSD's agent, at IAmJSD's request (IAmJSD reviewed the before/after screenshots; cristim is traveling), after the independent review and local verification above, all at |
Conflicts: - frontend/src/ladder-timeline.ts: both sides changed the disposeTimeline closure. Combined them: it now clears chart (main, LeanerCloud#1357, so a disposed chart is not reused) and updateTimeline (this branch, LeanerCloud#728, so refreshLadderTimeline is a no-op once the timeline is torn down). Main's disposeLadderTimeline export and hidden-section early return are kept; refreshLadderTimeline simply does nothing when the hidden-section path skipped mounting. - frontend/src/__tests__/ladder-timeline.test.ts: import line now brings in both refreshLadderTimeline and disposeLadderTimeline; kept this branch's refresh test and main's chart-teardown describe block side by side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: - frontend/src/__tests__/ladder-timeline.test.ts: both sides appended a describe block at the end of the file. Kept both: this branch's "planned purchases layout and UTC date filters (issue LeanerCloud#753)" followed by main's "chart teardown on navigation (issue LeanerCloud#667)" (LeanerCloud#1357). ladder-timeline.ts merged cleanly: main's disposeLadderTimeline and the hidden-section guard sit before this branch's card template. The moved #ladder-purchase-timeline container is still inside the Purchasing panel, so the root.closest('.hidden') guard keeps working. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ladder planned-purchase timeline's Chart.js instance (with its ResizeObserver and listeners) stayed alive after leaving Settings > Purchasing, and opening the General sub-tab even built a new chart inside the hidden Purchasing panel. This PR disposes the chart when navigating away and rebuilds it on return. It is the last open item of the issue: the 409 reload action, focus restore after save and the canvas tab stop shipped in #688, as the maintainer's comment on the issue notes.
Closes #667
What changed
frontend/src/ladder-timeline.ts: new exporteddisposeLadderTimeline()destroys the chart, clears the reference and bumps the load generation so pending loads cannot recreate it.initLadderTimelineuses it, and returns early without fetching or building when its root is inside a.hiddensection.frontend/src/navigation.ts:switchTabcallsdisposeLadderTimeline()for any target other than Settings, after the unsaved-changes guard, so a cancelled leave keeps the chart.switchSettingsSubTabcalls it for any sub-tab other than Purchasing.loadGlobalSettings->initLadderingSettings->initLadderTimeline.frontend/src/__tests__/navigation.test.tsandfrontend/src/__tests__/ladder-timeline.test.ts; newfrontend/tests-e2e/ladder-timeline-teardown.spec.ts.438b140e fix(ladder): dispose the planned-purchase chart when leaving its section.Why this approach
switchTabthe issue comment asks for, instead of a general per-section lifecycle registry, which would be a larger refactor.Screenshots
No visible change: disposing the chart does not change what is rendered. The evidence is the live ResizeObserver count in the new e2e test.
Verification
Run on this branch's head
438b140eon top of maina14dc8f2:--randomizeon navigation + ladder-timeline: 107 passed.navigation.test.ts"ladder timeline teardown" (leaving Settings disposes the chart, but not while the unsaved-changes dialog is pending; General/Accounts/Users sub-tabs dispose it; Purchasing reloads settings).ladder-timeline.test.ts"chart teardown on navigation" (dispose destroys the chart and a pending reload resolving afterwards does not rebuild it; init after dispose builds a new chart; a hidden section fetches nothing and destroys the previous chart).ladder-timeline-teardown.spec.tstracks live ResizeObserver targets inside#ladder-purchase-timelinethrough Purchasing, General (0 live, no tranche refetch), Purchasing (rebuilt), Plans (0) and browser Back (rebuilt). Against main's build it fails at the General step (1 live observer, expected 0); it passes on this branch.e2e-local.sh): 158 passed.scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.Notes for review
Overlaps
ladder-timeline.ts. The source merges cleanly; only the appended tests at the end ofladder-timeline.test.tsconflict (keep both).ladder-timeline.tson thedisposeTimelineline. Keep both:disposed = true; generation++; chart?.destroy(); chart = undefined; updateTimeline = undefined;. Its test file has an append-only conflict.navigation.tsbut merges cleanly.Note for the UI owner
This changes UI code that @cristim owns. IAmJSD reviewed the before/after screenshots and asked for it to be merged; any design point you disagree with can be a follow-up.
🤖 Generated with Claude Code