From f64871bcd2331ef9cd06d07e7c9691113fe910fc Mon Sep 17 00:00:00 2001 From: DemonGatanjieu <1027066412@qq.com> Date: Mon, 3 Aug 2026 18:20:11 +0800 Subject: [PATCH] fix(ui): preserve model grid return position --- .agents/logs/ai_changelog.md | 14 ++++++++++++++ .agents/logs/ai_lessons.md | 4 ++++ ARCHITECTURE.md | 2 +- web/modules/locales.js | 4 ++-- web/modules/ui_detail.js | 20 ++++++++++++++++++++ 5 files changed, 41 insertions(+), 3 deletions(-) diff --git a/.agents/logs/ai_changelog.md b/.agents/logs/ai_changelog.md index d612c64..c1486bd 100644 --- a/.agents/logs/ai_changelog.md +++ b/.agents/logs/ai_changelog.md @@ -1,5 +1,19 @@ # AI Changelog +## [Snapshot] 2026-08-03 - Preserve model grid return position + +**Implemented** + +- Renamed the model-detail fallback action from "Back to grid" to the shorter "Back" label. +- Captured the active model folder plus grid `scrollTop` and `scrollLeft` before opening detail. +- Restored the saved grid viewport when returning from the root model detail view, while leaving recipe-detail return state isolated. + +**Validation** + +- `node --check web/modules/ui_detail.js web/modules/ui_grid.js web/modules/ui_recipe_detail.js` +- `node tests/recipe_parser_roundtrip.mjs` +- `python_embeded/python.exe tests/test_recipe_roundtrip.py` +- `git diff --check` ## [Snapshot] 2026-08-03 - Keep model navigation inside Overview **Implemented** diff --git a/.agents/logs/ai_lessons.md b/.agents/logs/ai_lessons.md index 244a61d..e89f892 100644 --- a/.agents/logs/ai_lessons.md +++ b/.agents/logs/ai_lessons.md @@ -5,6 +5,10 @@ This document serves as an architectural retrospective and UX diagnostic log for ## 56. UI Refactors Must Preserve Reachable Semantic Tabs When a visual refactor merges information into a compact overview, do not remove a semantic tab until every dependent interaction has an intentional replacement. Model preview loading, model navigation, and return-state restoration were still designed around a Models tab, so removing only the tab made those actions unreachable. Keep tab state, asynchronous loading conditions, and return paths aligned with the visible navigation. + +## 57. Detail Return Must Restore the List Viewport + +Opening a detail panel should capture the list's semantic location and viewport, not only the selected item. Restore the folder/type and `scrollTop`/`scrollLeft` when the user returns to the grid, while keeping recipe-detail return state separate from browser-grid state. This prevents a visually correct Back action from still feeling like a navigation reset. 这份文档旨在记录 Anomalous 插件开发过程中的架构演进、UX 设计教训以及核心 Bug 排查经验。 --- diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 0c585fc..5419ea3 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -4,7 +4,7 @@ This document provides a high-level overview of the Anomalous Model Browser plug ## Current recipe interaction correction (2026-08-03) -The recipe detail view keeps Overview, Parameters, and Versions tabs. The Overview contains the model composition blocks; each resolved model name or preview is an explicit entry into the browser model detail view. Model previews are demand-loaded when Overview opens, and returning from model detail restores the recipe detail view. List-card Append actions await the shared canvas transaction before reporting success or restoring the button state. Export confirmation uses three states (cancel, exclude, include); cancellation must stop the export flow rather than silently becoming an exclusion. +The recipe detail view keeps Overview, Parameters, and Versions tabs. The Overview contains the model composition blocks; each resolved model name or preview is an explicit entry into the browser model detail view. Model previews are demand-loaded when Overview opens, and returning from model detail restores the recipe detail view. The model browser stores the active grid folder and scroll coordinates before opening detail and restores them when returning to the grid. List-card Append actions await the shared canvas transaction before reporting success or restoring the button state. Export confirmation uses three states (cancel, exclude, include); cancellation must stop the export flow rather than silently becoming an exclusion. ## 1. Project Philosophy (设计理念) * **Zero Frameworks**: No React, Vue, or build tools. Everything is Vanilla JS and CSS for maximum compatibility, minimum overhead, and zero compilation steps. diff --git a/web/modules/locales.js b/web/modules/locales.js index 40c7365..b1979df 100644 --- a/web/modules/locales.js +++ b/web/modules/locales.js @@ -25,7 +25,7 @@ export const i18n = { noModels: '此文件夹中没有找到模型。', noPreview: '暂无预览图', clickScan: '点击扫描', - back: '⬅️ 返回网格', + back: '⬅️ 返回', delModel: '🗑️ 删除', delConfirm: '确定彻底删除', delConfirm2: '及其所有关联配置、预览图和缓存文件吗?此操作不可逆!', @@ -275,7 +275,7 @@ export const i18n = { noModels: 'No models found in this folder.', noPreview: 'No preview available', clickScan: 'Click Scan', - back: '⬅️ Back to grid', + back: '⬅️ Back', delModel: '🗑️ Delete', delConfirm: 'Are you sure you want to permanently delete', delConfirm2: ' and all its associated configs, previews, and cache files? This action is irreversible!', diff --git a/web/modules/ui_detail.js b/web/modules/ui_detail.js index 27cc38b..116176b 100644 --- a/web/modules/ui_detail.js +++ b/web/modules/ui_detail.js @@ -49,6 +49,15 @@ export function showDetail(model) { } if (this.detailMouseMoveHandler) window.removeEventListener('mousemove', this.detailMouseMoveHandler); if (this.detailMouseUpHandler) window.removeEventListener('mouseup', this.detailMouseUpHandler); + if (!this.recipeModelReturn && this.grid && this.grid.style.display !== 'none') { + this.gridReturnState = { + type: this.currentType, + pathIdx: this.currentPathIdx, + subfolder: this.currentSubfolder, + scrollTop: this.grid.scrollTop, + scrollLeft: this.grid.scrollLeft, + }; + } this.grid.style.display = 'none'; this.detailPanel.style.display = 'flex'; this.stopMediaInContainer(this.detailPanel); this.detailPanel.innerHTML = ''; @@ -126,6 +135,17 @@ export function showDetail(model) { this.detailPanel.style.display = 'none'; this.stopMediaInContainer(this.detailPanel); this.detailPanel.innerHTML = ''; this.grid.style.display = 'grid'; + const gridReturnState = this.gridReturnState; + this.gridReturnState = null; + if (gridReturnState + && gridReturnState.type === this.currentType + && gridReturnState.pathIdx === this.currentPathIdx + && gridReturnState.subfolder === this.currentSubfolder) { + requestAnimationFrame(() => { + this.grid.scrollTop = gridReturnState.scrollTop || 0; + this.grid.scrollLeft = gridReturnState.scrollLeft || 0; + }); + } } };