From edbea736f0ae9e1d455e6d043da335fc2aeae99b Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Thu, 1 Oct 2026 09:05:50 -0400 Subject: [PATCH] TASK-conversation-console-workflow settle and preserve routed viewport after render --- docs/TEST_INTEGRITY.md | 4 ++-- site/console.js | 20 ++++++++++++++++---- test/fleet/console-view-navigation.test.mjs | 4 ++-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/TEST_INTEGRITY.md b/docs/TEST_INTEGRITY.md index 0e97c64..b13368a 100644 --- a/docs/TEST_INTEGRITY.md +++ b/docs/TEST_INTEGRITY.md @@ -2601,7 +2601,7 @@ test_integrity_note: - id: SCN-console-operational-deeplink-fleet given: "Operations, resources, schedules, providers, decisions, context, and manager-wakes are grouped in the Fleet workspace at 1280x900 and 390x844" when: "The reader loads #manager-wakes directly, follows its existing attention link, or returns to it through Back and Forward, then later navigates, scrolls, and refreshes" - then: "After the first deterministic data/layout application Fleet is selected, the Operations disclosure opens, manager-wakes receives focus fully inside the viewport, the later refresh preserves the reader's view and scroll, and Operations is hidden on unrelated views" + then: "After the first deterministic data/layout application Fleet is selected, the Operations disclosure opens, manager-wakes receives focus fully inside the viewport, a later refresh retains the user's selected view and scrolled position without refocusing manager-wakes, and Operations is hidden on unrelated views" - id: SCN-console-view-drafts given: "A task draft and an owner approval preview are present" when: "The reader changes views and refreshes the sample Console" @@ -2614,7 +2614,7 @@ test_integrity_note: - "An unchanged current-revision preview, confirmation boundary, and draft retained after refresh" - "A regenerated preview token and displayed revision bound to N+1" - "Exact Overview and contextual view titles, plus focused in-viewport secondary targets across direct loads and browser history" - - "The manager-wakes route selects Fleet and opens/focuses its nested target fully inside desktop and mobile viewports after first render and across browser history; later refresh preserves the user's view and scroll; operations stay hidden outside Fleet" + - "The manager-wakes route selects Fleet and opens/focuses its nested target fully inside desktop and mobile viewports after first render and across browser history; later refresh retains the user's selected view and scrolled position without refocusing manager-wakes; operations stay hidden outside Fleet" - "Visible recorded-evidence limitation beside owner briefing content while exact provenance remains compact and collapsed" - "No browser requests to live project APIs and no changes to tracked screenshot expectations" determinism_controls: diff --git a/site/console.js b/site/console.js index 4f3f170..117bbc5 100644 --- a/site/console.js +++ b/site/console.js @@ -62,7 +62,11 @@ function installConsoleViewRouting() { const initialFragment = decodeHash(initialHash); let initialPositionPending = Boolean(initialFragment && !consoleViewDefinitions[initialFragment] && document.getElementById(initialFragment)); - const abandonInitialPosition = () => { initialPositionPending = false; }; + let userIntentVersion = 0; + const abandonInitialPosition = () => { + initialPositionPending = false; + userIntentVersion += 1; + }; const onNavigation = () => { abandonInitialPosition(); sync({ focus: true }); @@ -133,6 +137,7 @@ function installConsoleViewRouting() { const view = resolveView(initialHash); positionNestedTarget(document.getElementById(targetId), targetId, view); }, + userIntentVersion: () => userIntentVersion, }; } @@ -1143,6 +1148,7 @@ const refreshScheduler = globalThis.TorchLiveRefresh.createScheduler({ apply: (snapshot) => { const viewportBeforeRender = appliedRefreshes > 0 ? { left: globalThis.scrollX, top: globalThis.scrollY } : null; + const userIntentAtRender = consoleViewRouter.userIntentVersion(); const editedBeforeRender = refreshProtection.dirty(); heldPanels = 0; renderingSnapshot = true; @@ -1151,10 +1157,16 @@ const refreshScheduler = globalThis.TorchLiveRefresh.createScheduler({ refreshProtection.remember({ preserve: editedBeforeRender }); $('#console-error').hidden = true; const status = $('#live-refresh-status'); - status.dataset.generation = String(++appliedRefreshes); + const renderGeneration = ++appliedRefreshes; status.textContent = `${liveUpdatesPaused ? 'Automatic updates paused.' : 'Updates every 15 seconds while visible.'}${heldPanels ? ' Edited forms and active previews retained; their panels may show older state.' : ''}`; - if (viewportBeforeRender) globalThis.scrollTo?.({ ...viewportBeforeRender, behavior: 'instant' }); - if (appliedRefreshes === 1) consoleViewRouter.afterInitialLayout(); + globalThis.requestAnimationFrame(() => { + if (renderGeneration !== appliedRefreshes) return; + if (viewportBeforeRender && consoleViewRouter.userIntentVersion() === userIntentAtRender) { + globalThis.scrollTo?.({ ...viewportBeforeRender, behavior: 'instant' }); + } + if (renderGeneration === 1) consoleViewRouter.afterInitialLayout(); + status.dataset.generation = String(renderGeneration); + }); }, onError: (caught) => { const error = $('#console-error'); diff --git a/test/fleet/console-view-navigation.test.mjs b/test/fleet/console-view-navigation.test.mjs index 58be904..0bd547e 100644 --- a/test/fleet/console-view-navigation.test.mjs +++ b/test/fleet/console-view-navigation.test.mjs @@ -142,9 +142,9 @@ test('SCN-console-operational-deeplink-layout-history-and-refresh: Fleet deep li await page.locator('.console-rail a[href="#work"]').click(); await page.mouse.wheel(0, 180); - const beforeRefreshRender = await page.locator('#refresh-console').evaluate((button) => { + const beforeRefreshRender = await page.evaluate(() => scrollY); + await page.locator('#refresh-console').evaluate((button) => { button.dispatchEvent(new MouseEvent('click', { bubbles: true })); - return scrollY; }); await page.locator('#live-refresh-status[data-generation="2"]').waitFor(); assert.equal(new URL(page.url()).hash, '#work');