mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-11-02 19:01:55 +00:00
TASK-conversation-console-workflow: preserve dashboard captures across views
This commit is contained in:
1 parent
b5b5f10cb2
commit
5d43b2deb3
2 files changed
+139
No files matched your search
@@ -2559,3 +2559,49 @@ test_integrity_note:
|
||||
did_relax_any_assertion: false
|
||||
if_true_explain_spec_basis: ""
|
||||
```
|
||||
|
||||
# Console dashboard navigation and preview freshness (2026-10-01)
|
||||
|
||||
```yaml
|
||||
test_integrity_note:
|
||||
change_type: [new_tests, refactor_tests]
|
||||
scenarios:
|
||||
- id: SCN-dashboard-fragment-navigation-preserves-existing-captures
|
||||
given: "The existing dashboard scenarios and eleven screenshot destinations run in their established order"
|
||||
when: "The driver selects each target section's supported fragment immediately before its unchanged checks and captures"
|
||||
then: "Each original assertion and capture executes against the selected view without changing capture order, viewport coverage, artifact destination, or artifact safeguards"
|
||||
- id: SCN-dashboard-draft-preview-invalidates-on-newer-evidence
|
||||
given: "A task priority preview is created from task evidence revision N"
|
||||
when: "A separate confirmed sample action advances that task to authoritative revision N+1, the owner navigates away and back, and refresh loads the newer evidence"
|
||||
then: "The revision N token, preview, and confirmation control are absent; only a newly generated preview bound to revision N+1 can be presented"
|
||||
- id: SCN-console-view-navigation
|
||||
given: "The Console has addressable work, fleet, evidence, and flow-watch views"
|
||||
when: "A reader opens fragments, follows navigation, and uses browser Back and Forward"
|
||||
then: "Exactly one matching view is visible with the matching title, accessible current-page link, and persistent project and freshness context"
|
||||
- id: SCN-console-mobile-navigation
|
||||
given: "The Console is rendered at a 390x844 viewport"
|
||||
when: "Each supported destination is selected"
|
||||
then: "Every navigation destination remains visible and the document and navigation have no horizontal overflow"
|
||||
- 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"
|
||||
then: "The unsent draft and same-revision preview remain available with their existing explicit owner confirmation boundary"
|
||||
observable_outcomes:
|
||||
- "Selected fragment, visible view, original assertion results, and the unchanged ordered screenshot set"
|
||||
- "Revision N+1 displayed after refresh, with the old preview token and confirmation control unavailable"
|
||||
- "A regenerated preview token and displayed revision bound to N+1"
|
||||
- "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"
|
||||
- "Newer evidence is introduced through the sample's supported preview/confirm API before an explicit refresh"
|
||||
- "No network, live project mutation, retry, sleep, timeout, golden update, or direct fixture-source change"
|
||||
anti_cheat_rationale:
|
||||
prevents:
|
||||
- "Capturing a view other than the one named by the scenario while retaining a passing screenshot call"
|
||||
- "Reusing a revision N preview after authoritative task evidence advances to N+1"
|
||||
- "Treating an unchanged preview on refresh as proof of stale-preview invalidation"
|
||||
- "Passing because of changed screenshot expectations, weakened assertions, or bypassed artifact provenance"
|
||||
relaxation:
|
||||
did_relax_any_assertion: false
|
||||
if_true_explain_spec_basis: ""
|
||||
```
|
||||
@@ -21,6 +21,20 @@ function screenshotPath(name) {
|
||||
return screenshotPaths[name];
|
||||
}
|
||||
|
||||
async function selectView(page, view) {
|
||||
const link = page.locator(`.console-rail a[href="#${view}"]`);
|
||||
assert.equal(await link.count(), 1, `the ${view} view must have one supported fragment link`);
|
||||
if (await link.getAttribute('aria-current') !== 'page') await link.click();
|
||||
await page.locator(`.console-workspace > [data-console-view="${view}"]`).waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
async function selectViewFor(page, selector) {
|
||||
const view = await page.locator(selector).first().evaluate((element) =>
|
||||
element.closest('[data-console-view]')?.dataset.consoleView);
|
||||
assert.ok(view, `${selector} must belong to a fragment-addressable view`);
|
||||
await selectView(page, view);
|
||||
}
|
||||
|
||||
const server = createConsoleServer();
|
||||
server.listen(0, '127.0.0.1');
|
||||
await once(server, 'listening');
|
||||
@@ -43,20 +57,27 @@ try {
|
||||
const page = await popup;
|
||||
await page.waitForLoadState();
|
||||
await page.locator('#project-name').filter({ hasText: 'Northstar' }).waitFor();
|
||||
await selectView(page, 'overview');
|
||||
assert.equal(await page.title(), 'TORCH Dashboard');
|
||||
assert.equal(await page.locator('.agent-row').count(), 6);
|
||||
assert.equal(await page.locator('.task-ticket').count(), 5);
|
||||
assert.equal(await page.getByRole('region', { name: 'Queue', exact: true }).locator('.task-ticket').count(), 2);
|
||||
assert.equal(await page.locator('#demo-banner').isVisible(), true);
|
||||
await selectViewFor(page, '#operation-outcomes');
|
||||
assert.match(await page.locator('#operation-outcomes').innerText(), /Outcome unknown — review required/);
|
||||
assert.equal(await page.locator('#operation-outcomes button').count(), 0);
|
||||
await selectViewFor(page, '#operation-outcomes');
|
||||
await page.locator('#operation-outcomes .check-evidence').last().locator('summary').click();
|
||||
assert.match(await page.locator('#operation-outcomes').innerText(), /transient failure.*effects unknown/s);
|
||||
await selectViewFor(page, '#operation-outcomes');
|
||||
await page.locator('#operation-outcomes').screenshot({ path: screenshotPath('torch-dashboard-operation-outcomes.png') });
|
||||
await selectViewFor(page, '#activity-review');
|
||||
await page.locator('#activity-review > summary').click();
|
||||
assert.match(await page.locator('#activity-review').innerText(), /4 days without a named commit/);
|
||||
assert.match(await page.locator('#activity-review').innerText(), /waiting may be expected/);
|
||||
await selectViewFor(page, '#activity-review');
|
||||
await page.locator('#activity-review').screenshot({ path: screenshotPath('torch-dashboard-activity-review.png') });
|
||||
await selectViewFor(page, '#check-list');
|
||||
assert.match(await page.locator('#check-list').innerText(), /Running — completion unconfirmed/);
|
||||
await page.locator('#check-list .check-evidence').filter({ hasText: 'interrupted-browser' }).locator('summary').click();
|
||||
assert.match(await page.locator('#check-list').innerText(), /Abandoned — not qualified/);
|
||||
@@ -66,12 +87,16 @@ try {
|
||||
await page.locator('#check-list .check-evidence').filter({ hasText: 'recovery-scenario' }).locator('summary').click();
|
||||
assert.match(await page.locator('#check-list').innerText(), /clockMoving.*true/s);
|
||||
assert.match(await page.locator('#check-list').innerText(), /not independent sensor truth/);
|
||||
await selectViewFor(page, '#check-list');
|
||||
await page.locator('#check-list').screenshot({ path: screenshotPath('torch-dashboard-check-evidence.png') });
|
||||
await selectViewFor(page, '#owner-briefing');
|
||||
assert.match(await page.locator('#owner-briefing').innerText(), /Choose the project home layout/);
|
||||
assert.match(await page.locator('#owner-briefing').innerText(), /not independent live verification/);
|
||||
await page.locator('#owner-briefing .briefing-detail').first().locator('summary').click();
|
||||
assert.match(await page.locator('#owner-briefing').innerText(), /Landed — not necessarily deployed/);
|
||||
await selectViewFor(page, '#owner-briefing');
|
||||
await page.locator('#owner-briefing').screenshot({ path: screenshotPath('torch-dashboard-owner-briefing-desktop.png') });
|
||||
await selectViewFor(page, '#attention-list');
|
||||
assert.match(await page.locator('#attention-list').innerText(), /manager launch needs inspection/);
|
||||
await page.locator('#attention-list a[href="#manager-wakes"]').click();
|
||||
assert.equal(await page.locator('#manager-wakes').isVisible(), true);
|
||||
@@ -80,6 +105,7 @@ try {
|
||||
assert.equal(await page.locator('#manager-wake-list button').count(), 0,
|
||||
'uncertain launches must not offer automatic restart or unlocking');
|
||||
const pilotPanel = page.locator('.organization-proposal').filter({ hasText: 'Coordination pilot' }).locator('.pilot-review-history');
|
||||
await selectViewFor(page, '.organization-proposal');
|
||||
await pilotPanel.locator(':scope > summary').click();
|
||||
assert.match(await pilotPanel.innerText(), /Recommendation: inconclusive/);
|
||||
assert.match(await pilotPanel.innerText(), /reviewer-reported, not independently verified/);
|
||||
@@ -106,11 +132,13 @@ try {
|
||||
await conclusion.locator('[data-review-confirm]').waitFor();
|
||||
await conclusion.getByText('Exact proposal and evidence in this decision', { exact: true }).click();
|
||||
assert.match(await conclusion.locator('[data-review-scope]').innerText(), /schedulesRetained/);
|
||||
await selectViewFor(page, '[data-organization-review][data-type="hierarchy-conclusion"]');
|
||||
await conclusion.screenshot({ path: screenshotPath('torch-dashboard-pilot-conclusion.png') });
|
||||
await conclusion.locator('[data-review-confirm]').click();
|
||||
await page.locator('#organization-action-status').filter({ hasText: 'recorded as reversed' }).waitFor();
|
||||
assert.equal(await page.locator('.agent-row').count(), 6);
|
||||
|
||||
await selectViewFor(page, '.task-ticket');
|
||||
const ticket = page.locator('.task-ticket').filter({ hasText: 'API-08' });
|
||||
await ticket.locator('.priority-change > summary').click();
|
||||
await ticket.locator('[name="priority"]').selectOption('urgent');
|
||||
@@ -121,6 +149,7 @@ try {
|
||||
await ticket.locator('[data-priority-confirm]').click();
|
||||
await ticket.locator('.priority-urgent').waitFor();
|
||||
|
||||
await selectViewFor(page, '.owner-request-composer');
|
||||
await page.locator('.owner-request-composer > summary').click();
|
||||
await page.locator('#owner-request-recipient').selectOption('qa');
|
||||
await page.locator('#owner-request-body').fill('Please review the home layout.');
|
||||
@@ -129,6 +158,7 @@ try {
|
||||
await page.locator('[data-owner-request-confirm]').click();
|
||||
await page.locator('#message-list .conversation-item').filter({ hasText: 'Please review the home layout.' }).waitFor();
|
||||
|
||||
await selectViewFor(page, '.artifact-feedback-form');
|
||||
const feedback = page.locator('.artifact-feedback-form').first();
|
||||
await feedback.locator('textarea').fill('Give active work more room.');
|
||||
await feedback.getByRole('button', { name: 'Review feedback' }).click();
|
||||
@@ -136,6 +166,7 @@ try {
|
||||
await feedback.locator('[data-feedback-confirm]').click();
|
||||
await page.locator('.artifact-feedback blockquote').filter({ hasText: 'Give active work more room.' }).waitFor();
|
||||
|
||||
await selectViewFor(page, '.runtime-profile-tools');
|
||||
await page.locator('.runtime-profile-tools > summary').click();
|
||||
await page.locator('#runtime-profile-area').selectOption('interface');
|
||||
await page.locator('#runtime-profile-runtime').selectOption('claude');
|
||||
@@ -145,14 +176,17 @@ try {
|
||||
await page.locator('[data-runtime-profile-confirm]').click();
|
||||
await page.locator('.agent-row').filter({ hasText: 'interface · claude / sonnet' }).waitFor();
|
||||
|
||||
await selectViewFor(page, '.approval-decision-form');
|
||||
await page.locator('.approval-decision-form [name="decision"]').selectOption('approved');
|
||||
await page.locator('[data-approval-preview]').click();
|
||||
await page.locator('[data-approval-confirm]').waitFor();
|
||||
await page.locator('[data-approval-confirm]').click();
|
||||
await page.getByText('No open approval requests.', { exact: true }).waitFor();
|
||||
await selectViewFor(page, '#owner-briefing');
|
||||
assert.match(await page.locator('#owner-briefing').innerText(), /Choose the project home layout/,
|
||||
'published briefing is historical even when live approval state changes');
|
||||
|
||||
await selectViewFor(page, '.organization-review');
|
||||
await page.locator('.organization-review > summary').click();
|
||||
await page.locator('[data-organization-review] [name="action"]').selectOption('approve');
|
||||
await page.locator('[data-review-preview]').click();
|
||||
@@ -161,10 +195,15 @@ try {
|
||||
await page.locator('[data-review-confirm]').click();
|
||||
await page.locator('#organization-action-status').filter({ hasText: 'recorded as approved' }).waitFor();
|
||||
|
||||
await selectViewFor(page, '.pilot-review-history');
|
||||
await pilotPanel.locator(':scope > summary').click();
|
||||
await selectViewFor(page, '.pilot-review-history');
|
||||
await pilotPanel.screenshot({ path: screenshotPath('torch-dashboard-pilot-review.png') });
|
||||
await selectViewFor(page, '#manager-wakes');
|
||||
await page.locator('#manager-wakes').screenshot({ path: screenshotPath('torch-dashboard-manager-wakes.png') });
|
||||
await selectView(page, 'overview');
|
||||
await page.evaluate(() => scrollTo(0, 0));
|
||||
await selectView(page, 'overview');
|
||||
await page.screenshot({ path: screenshotPath('torch-dashboard-demo-desktop.png') });
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
// Let responsive styles and layout finish before measuring the new viewport.
|
||||
@@ -176,7 +215,9 @@ try {
|
||||
const bounds = element.getBoundingClientRect();
|
||||
return { tag: element.tagName, id: element.id, className: element.className, right: bounds.right, width: bounds.width };
|
||||
}).filter((element) => element.right > innerWidth + 1).slice(-30) }))));
|
||||
await selectView(page, 'overview');
|
||||
await page.screenshot({ path: screenshotPath('torch-dashboard-demo-mobile.png') });
|
||||
await selectView(page, 'owner-briefing');
|
||||
await page.locator('.console-rail a[href="#owner-briefing"]').click();
|
||||
const briefingHeading = await page.locator('#owner-briefing-title').boundingBox();
|
||||
const stickyRail = await page.locator('.console-rail').boundingBox();
|
||||
@@ -194,6 +235,7 @@ try {
|
||||
await livePage.clock.install({ time: new Date('2026-09-30T12:00:00Z') });
|
||||
await livePage.goto(base + '/console.html?demo=1');
|
||||
await livePage.locator('#live-refresh-status[data-generation="1"]').waitFor();
|
||||
await selectView(livePage, 'fleet');
|
||||
for (const model of ['external-model-1', 'external-model-2']) {
|
||||
await livePage.evaluate(async (model) => {
|
||||
const payload = { areaId: document.querySelector('#runtime-profile-area').value, runtime: 'codex', model, reasoning: 'high' };
|
||||
@@ -205,10 +247,13 @@ try {
|
||||
}
|
||||
await livePage.reload();
|
||||
await livePage.locator('#live-refresh-status[data-generation="1"]').waitFor();
|
||||
await selectView(livePage, 'fleet');
|
||||
await livePage.locator('.runtime-profile-tools > summary').click();
|
||||
await livePage.locator('#runtime-profile-model').fill('owner-draft-model');
|
||||
await selectViewFor(livePage, '#task-create-form');
|
||||
await livePage.locator('#task-create-form').locator('..').locator(':scope > summary').click();
|
||||
await livePage.locator('#task-create-form [name="title"]').fill('My unfinished task');
|
||||
await selectViewFor(livePage, '.approval-decision-form');
|
||||
await livePage.locator('.approval-decision-form [name="decision"]').selectOption('approved');
|
||||
await livePage.locator('[data-approval-preview]').click();
|
||||
await livePage.locator('[data-approval-confirm]').waitFor();
|
||||
@@ -241,14 +286,62 @@ try {
|
||||
await livePage.locator('#live-refresh-status[data-generation="5"]').waitFor();
|
||||
await livePage.clock.runFor(15_000);
|
||||
await livePage.locator('#live-refresh-status[data-generation="6"]').waitFor();
|
||||
await selectViewFor(livePage, '.approval-decision-form');
|
||||
await livePage.locator('[data-approval-confirm]').click();
|
||||
await livePage.locator('#approval-request-count').filter({ hasText: '0 open' }).waitFor();
|
||||
assert.equal(await livePage.locator('#runtime-profile-model').inputValue(), 'owner-draft-model', 'completing an approval must not clear another form');
|
||||
assert.equal(await livePage.locator('#task-create-form [name="title"]').inputValue(), 'My unfinished task');
|
||||
await livePage.locator('#refresh-console').click();
|
||||
assert.equal(await livePage.locator('#runtime-profile-model').inputValue(), 'owner-draft-model');
|
||||
await selectViewFor(livePage, '.approval-decision-form');
|
||||
await livePage.screenshot({ path: screenshotPath('torch-dashboard-live-refresh.png') });
|
||||
await livePage.close();
|
||||
|
||||
const stalePage = await context.newPage();
|
||||
await stalePage.clock.install({ time: new Date('2026-10-01T12:00:00.000Z') });
|
||||
await stalePage.goto(base + '/console.html?demo=1#work');
|
||||
await stalePage.locator('#live-refresh-status[data-generation="1"]').waitFor();
|
||||
await selectView(stalePage, 'work');
|
||||
const staleTicket = stalePage.locator('.task-ticket').filter({ hasText: 'API-08' });
|
||||
const staleForm = staleTicket.locator('.priority-change-form');
|
||||
const revisionN = Number(await staleForm.getAttribute('data-revision'));
|
||||
await staleTicket.locator('.priority-change > summary').click();
|
||||
await staleForm.locator('[name="priority"]').selectOption('urgent');
|
||||
await staleForm.locator('[name="reason"]').fill('Review this change against the current task evidence.');
|
||||
await staleForm.getByRole('button', { name: 'Review change', exact: true }).click();
|
||||
await staleForm.locator('[data-priority-confirm]').waitFor();
|
||||
assert.equal(await staleForm.locator('[data-priority-preview]').locator('dd').nth(2).innerText(), String(revisionN));
|
||||
const staleToken = await staleForm.getAttribute('data-preview-token');
|
||||
const revisionN1 = await stalePage.evaluate(async (expectedRevision) => {
|
||||
const path = '/api/backlog/tasks/API-08/priority';
|
||||
const change = { priority: 'low', reason: 'A separate confirmed sample change establishes newer task evidence.', expectedRevision };
|
||||
const preview = await globalThis.TorchConsoleDemo.request(`${path}/preview`, change);
|
||||
const applied = await globalThis.TorchConsoleDemo.request(path, { ...change, token: preview.token, planHash: preview.planHash });
|
||||
return applied.task.revision;
|
||||
}, revisionN);
|
||||
assert.equal(revisionN1, revisionN + 1, 'new authoritative task evidence must advance from revision N to N+1');
|
||||
await selectView(stalePage, 'overview');
|
||||
await selectView(stalePage, 'work');
|
||||
await stalePage.locator('#refresh-console').click();
|
||||
await stalePage.locator('#live-refresh-status[data-generation="2"]').waitFor();
|
||||
const refreshedTicket = stalePage.locator('.task-ticket').filter({ hasText: 'API-08' });
|
||||
const refreshedForm = refreshedTicket.locator('.priority-change-form');
|
||||
assert.equal(Number(await refreshedForm.getAttribute('data-revision')), revisionN1);
|
||||
assert.equal(await refreshedTicket.locator('.priority').textContent(), 'low');
|
||||
assert.equal(await refreshedForm.getAttribute('data-preview-token'), null, 'revision N preview token must be discarded');
|
||||
assert.equal(await refreshedForm.locator('[data-priority-preview]').isVisible(), false,
|
||||
'revision N preview must not remain presented after revision N+1 is refreshed');
|
||||
assert.equal(await refreshedForm.locator('[data-priority-confirm]').count(), 0,
|
||||
'the stale revision N preview cannot be confirmed as current');
|
||||
await refreshedTicket.locator('.priority-change > summary').click();
|
||||
await refreshedForm.locator('[name="priority"]').selectOption('urgent');
|
||||
await refreshedForm.locator('[name="reason"]').fill('Review again using the refreshed task evidence.');
|
||||
await refreshedForm.getByRole('button', { name: 'Review change', exact: true }).click();
|
||||
await refreshedForm.locator('[data-priority-confirm]').waitFor();
|
||||
assert.equal(await refreshedForm.locator('[data-priority-preview]').locator('dd').nth(2).innerText(), String(revisionN1));
|
||||
assert.notEqual(await refreshedForm.getAttribute('data-preview-token'), staleToken,
|
||||
'a current preview must be regenerated from revision N+1');
|
||||
await stalePage.close();
|
||||
assert.deepEqual(errors, []);
|
||||
assert.deepEqual(apiRequests, [], 'demo must never contact live project APIs');
|
||||
console.log('PASS: real Console demo; separate tab; priority, messages, feedback, profiles, approval and organization review; reset; mobile layout; zero live API calls.');
|
||||
|
||||
Reference in new issue
Block a user