TASK-conversation-console-workflow: add focused Console views

This commit is contained in:
thePR0M3TH3AN committed 2026-10-01 06:26:25 -04:00
1 parent cb145e56cb
commit 2752caab1c
4 files changed
+271 -21

No files matched your search

+17 -12
View File
@@ -43,14 +43,19 @@
<a href="#organization">Organization</a>
<a href="#evidence">Evidence</a>
<a href="#delivery">Release gates</a>
<p class="rail-note">Local project view<br><span id="project-commit">—</span></p>
<details class="rail-provenance">
<summary>Snapshot provenance</summary>
<span>Commit <code id="project-commit">—</code></span>
<span>Branch <code id="project-branch-detail">—</code></span>
</details>
</nav>
<div class="console-workspace">
<p class="console-error" id="console-error" role="alert" hidden></p>
<p id="live-refresh-status" role="status" aria-live="polite">Waiting for the first snapshot.</p>
<p id="console-view-status" class="screen-reader-only" role="status" aria-live="polite"></p>
<section id="overview" class="pulse-zone" aria-labelledby="pulse-title">
<section id="overview" class="pulse-zone" data-console-view="overview" aria-labelledby="pulse-title">
<div class="pulse-main">
<div class="pulse-heading"><div><p class="section-kicker">Project pulse</p><h1 id="pulse-title">Work in motion</h1></div><span id="fleet-health" class="health-flag">Observing</span></div>
<p id="project-summary" class="pulse-summary">TORCH is gathering the latest repository and fleet evidence.</p>
@@ -67,12 +72,12 @@
</aside>
</section>
<section id="owner-briefing" class="console-section owner-briefing" aria-labelledby="owner-briefing-title">
<section id="owner-briefing" class="console-section owner-briefing" data-console-view="owner-briefing" aria-labelledby="owner-briefing-title">
<div class="section-heading"><h2 id="owner-briefing-title">Owner briefing</h2><span>Published evidence</span></div>
<div id="owner-briefing-content"><p>Reading the latest local publication…</p></div>
</section>
<section id="flow-watch" class="console-section flow-watch-section" aria-labelledby="flow-watch-title">
<section id="flow-watch" class="console-section flow-watch-section" data-console-view="flow-watch" aria-labelledby="flow-watch-title">
<div class="section-heading"><div><p class="section-kicker">Approvals and reporting lines</p><h2 id="flow-watch-title">Flow watch</h2></div><span>Owner decisions gated</span></div>
<p class="flow-watch-note">See which teams need attention and who owns each decision. A configured cadence does not confirm that a system timer is installed.</p>
<div class="flow-watch-grid">
@@ -88,7 +93,7 @@
</div>
</section>
<section id="work" class="board-section" aria-labelledby="work-title">
<section id="work" class="board-section" data-console-view="work" aria-labelledby="work-title">
<div class="section-heading"><div><p class="section-kicker">Authoritative backlog</p><h2 id="work-title">Work board</h2></div><span id="task-count">0 active</span></div>
<div class="work-view-tools" role="group" aria-label="Backlog views and filters">
<div class="work-filter-row">
@@ -161,13 +166,13 @@
<p id="task-create-action-status" class="priority-action-status" role="status" aria-live="polite" hidden></p>
</section>
<section id="initiative-progress" class="console-section initiative-section" aria-labelledby="initiative-title">
<section id="initiative-progress" class="console-section initiative-section" data-console-view="initiative-progress" aria-labelledby="initiative-title">
<div class="section-heading"><div><p class="section-kicker">Derived from the single backlog</p><h2 id="initiative-title">Feature and milestone progress</h2></div><span>Task states only · gates remain separate</span></div>
<p class="initiative-note">Progress is calculated from each linked task's current state. It does not mean checks passed, integration landed, or a release is ready.</p>
<div id="initiative-rollup" class="initiative-rollup" aria-live="polite"></div>
</section>
<section id="fleet" class="console-section fleet-section" aria-labelledby="fleet-title">
<section id="fleet" class="console-section fleet-section" data-console-view="fleet" aria-labelledby="fleet-title">
<div class="section-heading"><div><p class="section-kicker">Persistent identities</p><h2 id="fleet-title">Fleet and ownership</h2></div><span id="agent-health-note">Session state from latest observation</span></div>
<div class="fleet-columns">
<article class="work-surface"><div class="panel-heading"><h3>Sessions</h3><span>One context per identity</span></div><div id="agent-list" class="agent-list"></div>
@@ -196,19 +201,19 @@
</div>
</section>
<section id="organization" class="console-section organization-section" aria-labelledby="organization-title">
<section id="organization" class="console-section organization-section" data-console-view="organization" aria-labelledby="organization-title">
<div class="section-heading"><div><p class="section-kicker">Current approved structure</p><h2 id="organization-title">Organization map</h2></div><span id="organization-revision">No active graph</span></div>
<div id="organization-map" class="organization-map" aria-live="polite"></div>
<div id="organization-lifecycle" class="organization-lifecycle" aria-live="polite"></div>
</section>
<section id="organization-proposals" class="console-section" aria-labelledby="proposal-title">
<section id="organization-proposals" class="console-section" data-console-view="organization" aria-labelledby="proposal-title">
<div class="section-heading"><div><p class="section-kicker">Owner review</p><h2 id="proposal-title">Organization proposals</h2></div><span id="change-count">0 pending</span></div>
<p id="organization-action-status" class="organization-action-status" role="status" aria-live="polite" hidden></p>
<div id="change-list" class="console-list"></div>
</section>
<section id="communications" class="console-section communication-section" aria-labelledby="communications-title">
<section id="communications" class="console-section communication-section" data-console-view="communications" aria-labelledby="communications-title">
<div class="section-heading"><div><p class="section-kicker">Durable coordination</p><h2 id="communications-title">Conversations</h2></div><span id="message-count">0 unacknowledged</span></div>
<details class="owner-request-composer">
<summary>Request work from an agent</summary>
@@ -244,7 +249,7 @@
<div id="message-list" class="conversation-stream"></div>
</section>
<section id="evidence" class="console-section evidence-section" aria-labelledby="evidence-title">
<section id="evidence" class="console-section evidence-section" data-console-view="evidence" aria-labelledby="evidence-title">
<div class="section-heading"><div><p class="section-kicker">Provenance first</p><h2 id="evidence-title">Evidence and review</h2></div><span>Exact commit receipts</span></div>
<div class="evidence-columns">
<article class="work-surface"><div class="panel-heading"><h3>Checks</h3><span>Passes are commit-bound</span></div><div id="check-list" class="console-list"></div></article>
@@ -252,7 +257,7 @@
</div>
</section>
<section id="delivery" class="console-section delivery-section" aria-labelledby="delivery-title">
<section id="delivery" class="console-section delivery-section" data-console-view="delivery" aria-labelledby="delivery-title">
<div class="section-heading"><div><p class="section-kicker">Separate authority gates</p><h2 id="delivery-title">Integration and release</h2></div><span>Integration is not deployment</span></div>
<div class="delivery-columns">
<article class="work-surface"><div class="panel-heading"><h3>Integration</h3><span>FIFO queue · source commit</span></div><div id="integration-list" class="console-list"></div></article>
+124 -2
View File
@@ -1,4 +1,83 @@
const consoleViewDefinitions = Object.freeze({
overview: { title: 'Overview' },
'owner-briefing': { title: 'Briefing' },
'flow-watch': { title: 'Flow watch' },
work: { title: 'Work' },
'initiative-progress': { title: 'Progress' },
fleet: { title: 'Fleet' },
communications: { title: 'Conversations' },
organization: { title: 'Organization' },
evidence: { title: 'Evidence' },
delivery: { title: 'Release gates' },
});
function installConsoleViewRouting() {
const nav = document.querySelector('.console-rail');
const sections = [...document.querySelectorAll('.console-workspace > [data-console-view]')];
const links = [...(nav?.querySelectorAll('a[href^="#"]') ?? [])];
if (!nav || !sections.length || !links.length) return { sync: () => 'overview' };
let lastHash = null;
const decodeHash = (hash) => {
try { return decodeURIComponent(String(hash ?? '').replace(/^#/, '')); } catch { return ''; }
};
function resolveView(hash) {
const fragment = decodeHash(hash);
if (consoleViewDefinitions[fragment]) return fragment;
const target = document.getElementById(fragment);
const containingView = target?.closest('[data-console-view]')?.dataset.consoleView;
return consoleViewDefinitions[containingView] ? containingView : 'overview';
}
function sync({ focus = false, force = false } = {}) {
const hash = globalThis.location.hash || '#overview';
if (!force && hash === lastHash) return resolveView(hash);
lastHash = hash;
const view = resolveView(hash);
const definition = consoleViewDefinitions[view];
const visible = sections.filter((section) => section.dataset.consoleView === view);
sections.forEach((section) => { section.hidden = section.dataset.consoleView !== view; });
links.forEach((link) => {
const route = resolveView(link.getAttribute('href'));
if (route === view) link.setAttribute('aria-current', 'page');
else link.removeAttribute('aria-current');
});
const workspace = document.querySelector('.console-workspace');
if (workspace) workspace.dataset.currentView = view;
document.title = `${definition.title} | TORCH Local Fleet Console`;
const announcement = document.querySelector('#console-view-status');
if (announcement) announcement.textContent = `Viewing ${definition.title}.`;
if (focus) {
const targetId = decodeHash(hash);
const target = document.getElementById(targetId);
if (target && target.dataset.consoleView !== view && target.closest('[data-console-view]')) {
target.scrollIntoView?.({ block: 'start' });
target.focus?.({ preventScroll: true });
} else {
const labelledBy = visible[0]?.getAttribute('aria-labelledby');
const heading = (labelledBy && document.getElementById(labelledBy))
?? visible[0]?.querySelector('h1, h2');
if (heading) {
heading.tabIndex = -1;
heading.focus({ preventScroll: true });
}
globalThis.scrollTo?.({ top: 0, behavior: 'instant' });
}
}
return view;
}
globalThis.addEventListener('hashchange', () => sync({ focus: true }));
globalThis.addEventListener('popstate', () => sync({ focus: true }));
const initialFragment = decodeHash(globalThis.location.hash);
sync({ focus: Boolean(initialFragment && !consoleViewDefinitions[initialFragment]) });
return { sync };
}
const $ = (selector) => document.querySelector(selector);
const consoleViewRouter = installConsoleViewRouting();
const demoWorkspace = globalThis.TorchConsoleDemo;
const workViews = globalThis.TorchWorkViews;
const workProgress = globalThis.TorchWorkProgress;
@@ -45,6 +124,46 @@ function setText(selector, value) {
if (element) element.textContent = value ?? '';
}
function compactOwnerBriefingProvenance() {
const publication = $('#owner-briefing-content')?.querySelector('.briefing-publication');
if (!publication) return;
const timestamp = publication.querySelector('p time');
const window = [...publication.querySelectorAll('.briefing-muted')]
.find((paragraph) => paragraph.textContent.trim().startsWith('Window:'));
if (timestamp || window) {
const disclosure = document.createElement('details');
disclosure.className = 'briefing-provenance';
const summary = document.createElement('summary');
summary.textContent = 'Publication time and reporting window';
disclosure.append(summary);
if (timestamp) {
const exactTime = timestamp.cloneNode(true);
exactTime.textContent = timestamp.getAttribute('datetime') || timestamp.textContent;
const published = document.createElement('p');
published.append('Published ', exactTime);
disclosure.append(published);
timestamp.textContent = ageLabel(timestamp.getAttribute('datetime'));
timestamp.title = exactTime.textContent;
}
if (window) disclosure.append(window);
publication.append(disclosure);
}
for (const code of publication.querySelectorAll('code')) {
const commit = code.textContent.trim();
if (!/^[a-f0-9]{40}$/i.test(commit)) continue;
const disclosure = document.createElement('details');
disclosure.className = 'briefing-provenance';
const summary = document.createElement('summary');
summary.textContent = `Commit ${shortCommit(commit)}`;
const exact = document.createElement('code');
exact.textContent = commit;
disclosure.append(summary, exact);
code.replaceWith(disclosure);
}
}
function shortCommit(value) {
return value ? String(value).slice(0, 9) : '—';
}
@@ -69,9 +188,10 @@ function taskCard(task, healthFindings = []) {
const staleEvidence = healthFindings.find((finding) => finding.taskId === task.id
&& ['BACKLOG_OBSERVED_COMMIT_STALE', 'BACKLOG_OBSERVED_COMMIT_MISSING'].includes(finding.code));
return `<article class="task-ticket">
<div class="ticket-topline"><code>${escapeHtml(task.id)}</code><span class="priority priority-${escapeHtml(task.priority ?? 'normal')}">${escapeHtml(task.priority ?? 'normal')}</span></div>
<div class="ticket-topline"><span class="priority priority-${escapeHtml(task.priority ?? 'normal')}">${escapeHtml(task.priority ?? 'normal')}</span></div>
<h4>${escapeHtml(task.title ?? 'Untitled task')}</h4>
<p>${escapeHtml(detail || 'No task description recorded.')}</p>
<details class="task-reference"><summary>Task reference</summary><code>${escapeHtml(task.id)}</code></details>
${staleEvidence ? `<div class="evidence-warning" role="status"><strong>Evidence needs refresh</strong><p>${escapeHtml(staleEvidence.recommendation)}</p></div>` : ''}
<dl class="ticket-meta">
<div><dt>Owner</dt><dd>${escapeHtml(task.owner ?? 'Unassigned')}</dd></div>
@@ -772,6 +892,7 @@ function render(snapshot) {
setText('#project-name', project?.name ?? project?.id ?? 'Not installed');
setText('#project-branch', branch);
setText('#project-branch-detail', branch);
setText('#project-commit', shortCommit(snapshot.repository?.head));
setText('#fleet-health', snapshot.mode === 'installed'
? (snapshot.doctor?.healthy ? 'Healthy' : 'Review findings') : 'Not installed');
@@ -794,6 +915,7 @@ function render(snapshot) {
renderRuntimeProfileEditor(agents, snapshot.runtimeAdapters ?? [], snapshot.mode === 'installed');
renderFlowWatch(snapshot);
setHtml('#owner-briefing-content', globalThis.TorchOwnerDigest.render(snapshot.ownerDigest));
compactOwnerBriefingProvenance();
renderAttention(attention);
@@ -1910,11 +2032,11 @@ globalThis.addEventListener('pageshow', (event) => {
if (event.persisted && !liveUpdatesPaused && document.visibilityState === 'visible') refresh();
});
if (demoWorkspace) {
document.title = 'TORCH Dashboard';
$('#demo-banner').hidden = false;
$('#reset-demo')?.addEventListener('click', () => {
demoWorkspace.reset();
globalThis.location.reload();
});
}
consoleViewRouter.sync({ force: true });
refresh();
+19 -7
View File
@@ -152,9 +152,19 @@ footer div { display: flex; gap: 1.5rem; }
.console-rail { position: sticky; top: 0; align-self: start; display: grid; gap: .2rem; padding: 1.5rem .75rem; border-right: 1px solid var(--line); }
.console-rail a { padding: .55rem .7rem; color: var(--muted); font-weight: 650; text-decoration: none; border-left: 3px solid transparent; }
.console-rail a:hover, .console-rail a:focus-visible { color: var(--ink); background: var(--paper); border-left-color: var(--route); }
.rail-note { margin: 2rem .7rem 0; color: var(--muted); font-size: .76rem; line-height: 1.7; }
.rail-note span { color: var(--ink); font-family: ui-monospace, monospace; }
.console-rail a[aria-current="page"] { color: var(--ink); background: var(--paper); border-left-color: var(--route); }
.rail-provenance { margin: 1.5rem .7rem 0; color: var(--muted); font-size: .72rem; line-height: 1.55; overflow-wrap: anywhere; }
.rail-provenance summary, .briefing-provenance summary, .task-reference summary { cursor: pointer; color: var(--route); font-weight: 650; }
.rail-provenance > span { display: block; margin-top: .35rem; }
.rail-provenance code { color: var(--ink); overflow-wrap: anywhere; }
.console-workspace { min-width: 0; padding: 0 clamp(1rem, 2.5vw, 2.5rem) 3rem; }
.console-workspace > [data-console-view][hidden] { display: none !important; }
.screen-reader-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
.briefing-provenance { margin-top: .4rem; color: var(--muted); font-size: .78rem; overflow-wrap: anywhere; }
.briefing-provenance p { margin: .3rem 0; }
.briefing-provenance code { display: inline-block; max-width: 100%; overflow-wrap: anywhere; }
.task-reference { margin-top: .55rem; color: var(--muted); font-size: .75rem; overflow-wrap: anywhere; }
.task-reference code { display: inline-block; margin-top: .3rem; color: var(--ink); }
.console-error { margin: 1.2rem 0 0; background: var(--warning-surface); color: var(--warning-ink); border-left: 4px solid var(--signal); padding: 1rem; }
.pulse-zone { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(270px, .8fr); border-bottom: 1px solid var(--ink); }
.pulse-main { min-width: 0; padding: 2rem 2rem 1.6rem 0; }
@@ -578,10 +588,10 @@ footer div { display: flex; gap: 1.5rem; }
.blueprint article { min-height: auto; border-right: 0; border-bottom: 1px solid var(--line); }
.blueprint-index { margin-bottom: 2rem; }
.console-layout { grid-template-columns: 1fr; }
.console-rail { position: sticky; z-index: 5; top: 0; display: flex; overflow-x: auto; padding: .35rem .6rem; border-right: 0; border-bottom: 1px solid var(--line); background: var(--paper); }
.console-rail a { flex: 0 0 auto; padding: .5rem .65rem; border-left: 0; border-bottom: 3px solid transparent; }
.console-rail a:hover, .console-rail a:focus-visible { border-left-color: transparent; border-bottom-color: var(--route); }
.rail-note { display: none; }
.console-rail { position: sticky; z-index: 5; top: 0; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: .25rem; padding: .4rem .6rem; border-right: 0; border-bottom: 1px solid var(--line); background: var(--paper); }
.console-rail a { min-width: 0; padding: .45rem .5rem; border-left: 0; border-bottom: 3px solid transparent; overflow-wrap: anywhere; }
.console-rail a:hover, .console-rail a:focus-visible, .console-rail a[aria-current="page"] { border-left-color: transparent; border-bottom-color: var(--route); }
.rail-provenance { grid-column: 1 / -1; margin: .25rem .5rem; }
.console-workspace { padding-inline: clamp(1rem, 3vw, 2rem); }
.pulse-zone { grid-template-columns: minmax(0, 1.15fr) minmax(240px, .85fr); }
.work-filter-row { grid-template-columns: repeat(auto-fit, minmax(135px, 1fr)); }
@@ -611,11 +621,13 @@ footer div { display: flex; gap: 1.5rem; }
.console-nav { min-height: 62px; gap: .7rem; }
.console-project { margin-right: 0; }
.console-project span { display: none; }
.console-tools > span { display: none; }
.console-tools { flex-wrap: wrap; gap: .5rem; }
.console-tools > span { display: inline-flex; flex-basis: 100%; }
.console-rail { top: 0; }
.section-heading { flex-wrap: wrap; }
.section-heading > div, .section-heading > span { min-width: 0; overflow-wrap: anywhere; }
.console-workspace { padding-inline: .8rem; }
.console-rail { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pulse-zone, .fleet-columns, .evidence-columns, .delivery-columns, .flow-watch-grid { grid-template-columns: 1fr; }
.owner-request-fields, .runtime-profile-fields { grid-template-columns: 1fr; }
.schedule-launcher-actions { align-items: stretch; flex-direction: column; }
+111
View File
@@ -0,0 +1,111 @@
import assert from 'node:assert/strict';
import { once } from 'node:events';
import test from 'node:test';
import { chromium } from '@playwright/test';
import { createConsoleServer } from '../../src/console/server.mjs';
const views = [
['overview', 'Overview'],
['owner-briefing', 'Briefing'],
['flow-watch', 'Flow watch'],
['work', 'Work'],
['initiative-progress', 'Progress'],
['fleet', 'Fleet'],
['communications', 'Conversations'],
['organization', 'Organization'],
['evidence', 'Evidence'],
['delivery', 'Release gates'],
];
async function openDemo(t, viewport) {
const server = createConsoleServer();
server.listen(0, '127.0.0.1');
await once(server, 'listening');
let browser;
t.after(async () => {
await browser?.close();
await new Promise((resolve) => server.close(resolve));
});
browser = await chromium.launch({ headless: true });
const context = await browser.newContext({ viewport });
const page = await context.newPage();
await page.clock.install({ time: new Date('2026-10-01T12:00:00.000Z') });
await page.goto(`http://127.0.0.1:${server.address().port}/console?demo=1#work`);
await page.locator('#live-refresh-status[data-generation="1"]').waitFor();
return { page, context };
}
async function assertSelectedView(page, route, title) {
const selected = await page.locator('.console-workspace > [data-console-view]:not([hidden])').evaluateAll((sections) =>
[...new Set(sections.map((section) => section.dataset.consoleView))]);
assert.deepEqual(selected, [route], `only the ${route} view should be visible`);
assert.equal(await page.title(), `${title} | TORCH Local Fleet Console`);
assert.equal(await page.locator(`.console-rail a[aria-current="page"][href="#${route}"]`).count(), 1);
assert.equal(await page.locator('#project-name').isVisible(), true);
assert.equal(await page.locator('#snapshot-time').isVisible(), true);
}
test('SCN-console-view-navigation: fragments, direct loads, Back and Forward select one addressable view', async (t) => {
const { page } = await openDemo(t, { width: 1280, height: 900 });
await assertSelectedView(page, 'work', 'Work');
await page.locator('.console-rail a[href="#fleet"]').click();
await assertSelectedView(page, 'fleet', 'Fleet');
await page.locator('.console-rail a[href="#evidence"]').click();
await assertSelectedView(page, 'evidence', 'Evidence');
await page.goBack();
await assertSelectedView(page, 'fleet', 'Fleet');
await page.goForward();
await assertSelectedView(page, 'evidence', 'Evidence');
await page.goto(`http://127.0.0.1:${new URL(page.url()).port}/console?demo=1#approval-request-list`);
await page.locator('#live-refresh-status[data-generation="1"]').waitFor();
await assertSelectedView(page, 'flow-watch', 'Flow watch');
});
test('SCN-console-mobile-navigation: every destination is visible at 390x844 without horizontal hunting', async (t) => {
const { page } = await openDemo(t, { width: 390, height: 844 });
assert.equal(await page.locator('.console-rail a').count(), views.length);
for (const [route, title] of views) {
const link = page.locator(`.console-rail a[href="#${route}"]`);
assert.equal(await link.isVisible(), true, `${title} navigation must remain visible`);
await link.click();
await assertSelectedView(page, route, title);
const widths = await page.evaluate(() => ({
viewport: innerWidth,
document: document.documentElement.scrollWidth,
navigation: document.querySelector('.console-rail').scrollWidth,
navigationClient: document.querySelector('.console-rail').clientWidth,
}));
assert.ok(widths.document <= widths.viewport, `document overflows horizontally: ${JSON.stringify(widths)}`);
assert.ok(widths.navigation <= widths.navigationClient, `navigation requires horizontal scrolling: ${JSON.stringify(widths)}`);
}
});
test('SCN-console-view-drafts: navigation and refresh retain unsent edits and the explicit owner preview boundary', async (t) => {
const { page } = await openDemo(t, { width: 1280, height: 900 });
const titleInput = page.locator('#task-create-form [name="title"]');
await page.locator('.task-create-panel > summary').click();
await titleInput.fill('Unsent route-change draft');
await page.locator('.console-rail a[href="#flow-watch"]').click();
const approval = page.locator('.approval-decision-form').first();
await approval.locator('[name="decision"]').selectOption('approved');
await approval.locator('[name="note"]').fill('Review remains explicit after navigation.');
await approval.locator('[data-approval-preview]').click();
await approval.locator('[data-approval-confirm]:visible').waitFor();
const previewToken = await approval.getAttribute('data-preview-token');
await page.locator('.console-rail a[href="#work"]').click();
await page.locator('#refresh-console').click();
await page.locator('#live-refresh-status[data-generation="2"]').waitFor();
assert.equal(await titleInput.inputValue(), 'Unsent route-change draft');
await page.locator('.console-rail a[href="#flow-watch"]').click();
assert.equal(await approval.getAttribute('data-preview-token'), previewToken);
assert.equal(await approval.locator('[data-approval-confirm]').isVisible(), true);
assert.equal(await approval.locator('[name="decision"]').isDisabled(), true);
assert.equal(await approval.locator('[data-approval-edit]').isVisible(), true);
assert.match(await approval.locator('.approval-decision-status').innerText(), /bound to this request revision/);
assert.equal(await page.locator('#approval-request-count').innerText(), '1 open');
});