From afe0fd120671d23aae3184f95fa8d39b501c5148 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Wed, 20 May 2026 08:13:55 -0400 Subject: [PATCH] fix(simple-editor): preserve scroll position across loadAll() re-renders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regression of an earlier fix. Operators reported the editor jumping to the top of the page on every step save / insert / remove / promote. Root cause: .o_fp_simple_editor is the overflow:auto scroll container. loadAll() replaces state.steps with a fresh JSONRPC payload — OWL tears down the t-foreach and rebuilds every row, which snaps scrollTop back to 0. Every author action (Save Step, Add Step, Remove, Promote, Demote, Reorder, Import Template) routes through loadAll, so the symptom hit everywhere. Fix: capture scrollTop before the RPC, restore in a double-rAF after the response settles. rAF (microtask runs before paint in OWL 2; we need the rebuilt DOM to exist). One choke point fix — every caller benefits without per-handler changes. Cheap: a single DOM lookup + an integer save/restore. No XML or state-shape changes. Module: fusion_plating 19.0.20.6.0 → 19.0.20.6.1. Co-Authored-By: Claude Opus 4.7 (1M context) --- fusion_plating/fusion_plating/__manifest__.py | 2 +- .../static/src/js/simple_recipe_editor.js | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/fusion_plating/fusion_plating/__manifest__.py b/fusion_plating/fusion_plating/__manifest__.py index dee9ea3b..d75f2e40 100644 --- a/fusion_plating/fusion_plating/__manifest__.py +++ b/fusion_plating/fusion_plating/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating', - 'version': '19.0.20.6.0', + 'version': '19.0.20.6.1', 'category': 'Manufacturing/Plating', 'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.', 'description': """ diff --git a/fusion_plating/fusion_plating/static/src/js/simple_recipe_editor.js b/fusion_plating/fusion_plating/static/src/js/simple_recipe_editor.js index 5b68f83a..9ff158d3 100644 --- a/fusion_plating/fusion_plating/static/src/js/simple_recipe_editor.js +++ b/fusion_plating/fusion_plating/static/src/js/simple_recipe_editor.js @@ -86,6 +86,20 @@ export class FpSimpleRecipeEditor extends Component { } async loadAll() { + // Preserve scroll position across the re-render. .o_fp_simple_editor + // is the overflow:auto scroll container — when `state.steps` is + // replaced with a fresh array, OWL tears down the t-foreach and + // rebuilds every row, which snaps scrollTop back to 0. Operators + // hate this: they save a step half-way down the recipe and the + // page jumps to the top. Capture the position before the RPC, + // restore it after the next paint. + // + // Regression note (2026-05-20): every save/insert/remove/promote + // handler calls loadAll, so this single choke point fixes scroll + // reset for the whole editor. + const scrollRoot = document.querySelector(".o_fp_simple_editor"); + const savedScrollTop = scrollRoot ? scrollRoot.scrollTop : 0; + this.state.loading = true; const [recipeData, libraryData, templateData] = await Promise.all([ rpc("/fp/simple_recipe/load", { recipe_id: this._recipeId }), @@ -97,6 +111,21 @@ export class FpSimpleRecipeEditor extends Component { this.state.library = libraryData.templates; this.state.templateOptions = templateData.templates; this.state.loading = false; + + // Restore AFTER OWL repaints. Microtask runs before paint in OWL 2; + // we need rAF (or two of them, defensively) so the rebuilt DOM + // exists when we set scrollTop. Without this the assignment fires + // against the pre-render DOM and gets discarded. + if (savedScrollTop > 0) { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + const el = document.querySelector(".o_fp_simple_editor"); + if (el) { + el.scrollTop = savedScrollTop; + } + }); + }); + } } async onSearchLibrary(ev) {