TASK-conversation-console-workflow settle Fleet deep links after initial render

This commit is contained in:
thePR0M3TH3AN committed 2026-10-01 08:42:54 -04:00
1 parent a97a878e1f
commit 88ed1ed545
4 files changed
+101 -18

No files matched your search

+5 -5
View File
@@ -2599,9 +2599,9 @@ test_integrity_note:
when: "The reader opens either direct link and uses browser Back and Forward"
then: "Exactly the matching view is selected and the actual secondary target is focused inside the viewport"
- id: SCN-console-operational-deeplink-fleet
given: "Operations, resources, schedules, providers, decisions, context, and manager-wakes are grouped in the Fleet workspace"
when: "The reader loads #manager-wakes directly, follows its existing attention link, or returns to it through Back and Forward"
then: "Fleet is selected, the Operations disclosure opens, manager-wakes receives focus inside the viewport, and the entire Operations section is hidden on unrelated views"
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"
- 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,12 +2614,12 @@ 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 across direct navigation and browser history; 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 preserves the user's view and scroll; 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:
- "The existing isolated sample Console, fixed Playwright clock, and fixed desktop/mobile viewports"
- "Manager-wakes routing uses the existing isolated sample Console and browser fragment history without changing project state"
- "Manager-wakes routing uses the existing isolated sample Console, a deterministic first-generation completion marker, and browser fragment history without changing project state"
- "Newer evidence is introduced through the sample's supported preview/confirm API before an explicit refresh"
- "The browser test advances only the isolated sample API; it never submits the priority form or mutates a live project"
- "No network, live project mutation, retry, sleep, timeout, golden update, or direct fixture-source change"
+1
View File
@@ -231,6 +231,7 @@ try {
await page.locator('#owner-briefing .briefing-detail').first().locator('summary').click();
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false, 'expanded briefing evidence must fit mobile width');
await page.locator('#reset-demo').click();
await selectView(page, 'flow-watch');
await page.locator('#approval-request-count').filter({ hasText: '1 open' }).waitFor();
assert.equal(await page.locator('.priority-urgent').count(), 0);
assert.equal(await page.locator('.agent-row').count(), 6);
+47 -13
View File
@@ -18,6 +18,7 @@ function installConsoleViewRouting() {
if (!nav || !sections.length || !links.length) return { sync: () => 'overview' };
let lastHash = null;
const initialHash = globalThis.location.hash;
const decodeHash = (hash) => {
try { return decodeURIComponent(String(hash ?? '').replace(/^#/, '')); } catch { return ''; }
};
@@ -44,6 +45,29 @@ function installConsoleViewRouting() {
}
}
function positionNestedTarget(target, targetId, view) {
const targetView = target?.closest('[data-console-view]')?.dataset.consoleView;
if (!target || targetId === view || targetView !== view) return;
for (let ancestor = target; ancestor; ancestor = ancestor.parentElement) {
if (ancestor instanceof HTMLDetailsElement) ancestor.open = true;
}
accountForStickyNavigation(target);
if (!target.hasAttribute('tabindex') && !target.matches('a[href], button, input, select, textarea, summary')) {
target.tabIndex = -1;
}
target.scrollIntoView?.({ block: 'start', behavior: 'instant' });
target.focus?.({ preventScroll: true });
}
const initialFragment = decodeHash(initialHash);
let initialPositionPending = Boolean(initialFragment && !consoleViewDefinitions[initialFragment]
&& document.getElementById(initialFragment));
const abandonInitialPosition = () => { initialPositionPending = false; };
const onNavigation = () => {
abandonInitialPosition();
sync({ focus: true });
};
function sync({ focus = false, force = false } = {}) {
const hash = globalThis.location.hash || '#overview';
if (!force && hash === lastHash) return resolveView(hash);
@@ -68,15 +92,7 @@ function installConsoleViewRouting() {
const target = document.getElementById(targetId);
const targetView = target?.closest('[data-console-view]')?.dataset.consoleView;
if (target && targetId !== view && targetView === view) {
for (let ancestor = target; ancestor; ancestor = ancestor.parentElement) {
if (ancestor instanceof HTMLDetailsElement) ancestor.open = true;
}
accountForStickyNavigation(target);
if (!target.hasAttribute('tabindex') && !target.matches('a[href], button, input, select, textarea, summary')) {
target.tabIndex = -1;
}
target.scrollIntoView?.({ block: 'start', behavior: 'instant' });
target.focus?.({ preventScroll: true });
positionNestedTarget(target, targetId, view);
} else {
const labelledBy = visible[0]?.getAttribute('aria-labelledby');
const heading = (labelledBy && document.getElementById(labelledBy))
@@ -96,11 +112,28 @@ function installConsoleViewRouting() {
return view;
}
globalThis.addEventListener('hashchange', () => sync({ focus: true }));
globalThis.addEventListener('popstate', () => sync({ focus: true }));
const initialFragment = decodeHash(globalThis.location.hash);
globalThis.addEventListener('hashchange', onNavigation);
globalThis.addEventListener('popstate', onNavigation);
globalThis.addEventListener('wheel', abandonInitialPosition, { passive: true });
globalThis.addEventListener('touchstart', abandonInitialPosition, { passive: true });
globalThis.addEventListener('pointerdown', abandonInitialPosition, { passive: true });
globalThis.addEventListener('keydown', (event) => {
if (['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp', 'Home', 'End', ' '].includes(event.key)) {
abandonInitialPosition();
}
});
sync({ focus: Boolean(initialFragment && !consoleViewDefinitions[initialFragment]) });
return { sync };
return {
sync,
afterInitialLayout() {
if (!initialPositionPending) return;
initialPositionPending = false;
if (globalThis.location.hash !== initialHash) return;
const targetId = decodeHash(initialHash);
const view = resolveView(initialHash);
positionNestedTarget(document.getElementById(targetId), targetId, view);
},
};
}
const $ = (selector) => document.querySelector(selector);
@@ -1118,6 +1151,7 @@ const refreshScheduler = globalThis.TorchLiveRefresh.createScheduler({
const status = $('#live-refresh-status');
status.dataset.generation = String(++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 (appliedRefreshes === 1) consoleViewRouter.afterInitialLayout();
},
onError: (caught) => {
const error = $('#console-error');
@@ -105,6 +105,54 @@ test('SCN-console-secondary-deeplink-focus-and-history: direct fragments and his
await assertTarget('approval-request-list', 'flow-watch', 'Flow watch');
});
test('SCN-console-operational-deeplink-layout-history-and-refresh: Fleet deep links settle after first render without stealing later navigation', async (t) => {
for (const viewport of [{ width: 1280, height: 900 }, { width: 390, height: 844 }]) {
const { page } = await openDemo(t, viewport);
const base = `http://127.0.0.1:${new URL(page.url()).port}/console?demo=1`;
const assertManagerWakes = async () => {
await assertSelectedView(page, 'fleet', 'Fleet');
const state = await page.locator('#manager-wakes').evaluate((target) => {
const bounds = target.getBoundingClientRect();
return {
activeId: document.activeElement?.id,
operationsOpen: target.closest('#fleet-operations')?.open,
top: bounds.top,
bottom: bounds.bottom,
height: innerHeight,
};
});
assert.equal(state.operationsOpen, true);
assert.equal(state.activeId, 'manager-wakes');
assert.ok(state.top >= 0 && state.bottom <= state.height,
`manager-wakes must be fully inside ${viewport.width}x${viewport.height}: ${JSON.stringify(state)}`);
};
await page.goto(`${base}#manager-wakes`);
await page.locator('#live-refresh-status[data-generation="1"]').waitFor();
await assertManagerWakes();
await page.goto(`${base}#overview`);
await page.locator('#live-refresh-status[data-generation="1"]').waitFor();
await page.locator('#attention-list a[href="#manager-wakes"]').click();
await assertManagerWakes();
await page.goBack();
await assertSelectedView(page, 'overview', 'Overview');
await page.goForward();
await assertManagerWakes();
await page.locator('.console-rail a[href="#work"]').click();
await page.mouse.wheel(0, 180);
const beforeRefresh = await page.evaluate(() => scrollY);
await page.locator('#refresh-console').click();
await page.locator('#live-refresh-status[data-generation="2"]').waitFor();
assert.equal(new URL(page.url()).hash, '#work');
await assertSelectedView(page, 'work', 'Work');
assert.notEqual(await page.evaluate(() => document.activeElement?.id), 'manager-wakes');
assert.equal(await page.evaluate(() => scrollY), beforeRefresh,
'a later refresh must retain the user’s scrolled position');
}
});
test('SCN-console-priority-preview-invalidates-on-newer-evidence: refresh preserves editable drafts and rejects the old preview', async (t) => {
const { page } = await openDemo(t, { width: 1280, height: 900 });
const form = page.locator('.priority-change-form[data-task-id="API-08"]');