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) {