fix(webui): sp7 review round, each pane links its thread's full view in one plain click, a requirement's Open conversation keeps the organization's own view, the gated-pill assertion matches its text (#1873, lane sp7-polish)
This commit is contained in:
1 parent
69ac78b97e
commit
8296d33100
5 files changed
+33
-3
No files matched your search
@@ -132,6 +132,7 @@
|
||||
<div class="assist-pane-h">
|
||||
<h2 id="assist-p${n}-title" data-assist-title>Reading…</h2>
|
||||
<span class="assist-chips" data-assist-chips></span>
|
||||
<a class="pane-tool assist-msgs" data-assist-pane-full data-full-view href="#/orgs/${enc(p.org)}/projects/threads/${enc(p.id)}">Full view</a>
|
||||
<a class="pane-tool assist-msgs" data-assist-pane-messages href="#/orgs/${enc(p.org)}/messages" aria-label="Messages in ${esc(p.org)}">Messages</a>
|
||||
<button type="button" class="pane-tool" data-assist-close aria-label="Close thread">${CLOSE}</button>
|
||||
</div>
|
||||
@@ -344,7 +345,9 @@
|
||||
document.addEventListener('click', e => {
|
||||
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
||||
const a = e.target.closest?.('a[href]');
|
||||
if (!a || a.hasAttribute('download') || (a.target && a.target !== '_self')) return;
|
||||
// A link marked data-full-view (a pane's Full view, a requirement's "Open conversation") goes to the
|
||||
// organization's own thread view, which has Request details, sessions and sharing.
|
||||
if (!a || a.hasAttribute('download') || a.hasAttribute('data-full-view') || (a.target && a.target !== '_self')) return;
|
||||
const m = THREAD_LINK.exec(a.getAttribute('href') || '');
|
||||
const org = m && (m[1] || window.MosaicOrg?.id);
|
||||
if (!m || !org || !ORG.test(org)) return;
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
const candidate=d?.candidate, expanded=r && expandedSet.has(project.id+':'+r.id);
|
||||
const docPath=candidate?.prd_path, docLink=project.repo && typeof docPath==='string' && /^docs\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(docPath) ? `#/docs/${encodeURIComponent(project.repo)}/${encodeURIComponent(docPath)}` : null;
|
||||
const rows=candidate ? [['PRD version',candidate.prd_version],['Approved snapshot',d.snapshot.id || 'None'],['PRD file',docPath],['PRD blob',candidate.prd_blob],['Records commit',candidate.records_commit],['Confirmed intent commit',candidate.spec_commit],['Mode revision',candidate.mode_revision]].map(([label,value])=>`<div class="project-provenance-row"><span>${label}</span>${label==='PRD file' && docLink?`<a href="${docLink}"><code>${clean(value)}</code></a>`:`<code>${clean(value)}</code>`}</div>`).join('') : '<p>Unavailable until a PRD is read.</p>';
|
||||
return `${r?`<div class="project-panel project-source">${select || `Request <code>${clean(r.id)}</code>`} · ${chip(r.state)} · from ${clean(r.thread.name)} <a href="${conversationLink(r.thread)}">Open conversation</a></div>`:select}<div class="project-columns"><section><h2>Project direction</h2><h3 id="project-north-star">North star</h3>${north!==null?`<div data-north-star></div><a class="project-text-link" href="#/docs/${encodeURIComponent(project.repo)}/${encodeURIComponent('spec/north-star.md')}">Open north star in Docs</a>`:data.northRecordsMissing?`<p>${window.MosaicRecords.markup(project.repo)}</p>`:data.northMissing?'<p>No north star yet.</p>':project.repo?'<p>North star unavailable. Docs could not read this project’s repository.</p>':`<p>This project has no linked repository. The north star and project documents live in a repository. Linking one to an existing project isn't available yet.</p>`}<h3 id="project-prd">${candidate?`PRD ${clean(candidate.prd_version)}`:'PRD'} ${candidate?`<span class="project-chip ${d.snapshot.id?'success':''}">${d.snapshot.id?'Approved':'Draft'}</span>`:''}</h3>${d?`<p>${d.snapshot.id?'Approved snapshot '+clean(d.snapshot.id):'This candidate has no active approved snapshot.'}</p><div class="project-panel project-prd-panel"><div class="project-prd-preview${expanded?' expanded':''}" data-prd></div><button type="button" class="btn project-prd-toggle" aria-expanded="${!!expanded}">${expanded?'Show less':'Show full PRD'}</button></div><h3>Requirements <span class="project-count">${d.requirements.length}</span></h3><div class="project-panel">${d.requirements.map((req,i)=>`<details data-restore-key="req:${clean(r.id)}:${clean(req.id)}"><summary>${clean(req.id)} · ${clean(req.title)}</summary><p>${clean(req.text)}</p><p>Requirement identity: <code>${clean(candidate.requirements[i]?.path)}</code> · <code>${clean(candidate.requirements[i]?.blob)}</code></p></details>`).join('')}</div>`:`<p>${(data.failed || data.requests.some(x=>x.materialFailed))?'Request or PRD unavailable. Refresh to try again.':'No current PRD candidate or approved snapshot. Waiting for a PRD submission.'}</p>`}${d && data.requests.some(x=>x.materialFailed)?'<p role="status">Some request PRDs could not be read. Refresh to try again.</p>':''}</section><aside class="project-panel project-provenance"><details data-restore-key="provenance"${matchMedia('(min-width:700px)').matches?' open':''}><summary>Version and provenance</summary>${rows}${candidate?`<p>Approval and revocation live in the conversation's Request details.</p><a href="${conversationLink(r.thread)}">Open conversation</a>`:''}</details></aside></div>`;
|
||||
return `${r?`<div class="project-panel project-source">${select || `Request <code>${clean(r.id)}</code>`} · ${chip(r.state)} · from ${clean(r.thread.name)} <a data-full-view href="${conversationLink(r.thread)}">Open conversation</a></div>`:select}<div class="project-columns"><section><h2>Project direction</h2><h3 id="project-north-star">North star</h3>${north!==null?`<div data-north-star></div><a class="project-text-link" href="#/docs/${encodeURIComponent(project.repo)}/${encodeURIComponent('spec/north-star.md')}">Open north star in Docs</a>`:data.northRecordsMissing?`<p>${window.MosaicRecords.markup(project.repo)}</p>`:data.northMissing?'<p>No north star yet.</p>':project.repo?'<p>North star unavailable. Docs could not read this project’s repository.</p>':`<p>This project has no linked repository. The north star and project documents live in a repository. Linking one to an existing project isn't available yet.</p>`}<h3 id="project-prd">${candidate?`PRD ${clean(candidate.prd_version)}`:'PRD'} ${candidate?`<span class="project-chip ${d.snapshot.id?'success':''}">${d.snapshot.id?'Approved':'Draft'}</span>`:''}</h3>${d?`<p>${d.snapshot.id?'Approved snapshot '+clean(d.snapshot.id):'This candidate has no active approved snapshot.'}</p><div class="project-panel project-prd-panel"><div class="project-prd-preview${expanded?' expanded':''}" data-prd></div><button type="button" class="btn project-prd-toggle" aria-expanded="${!!expanded}">${expanded?'Show less':'Show full PRD'}</button></div><h3>Requirements <span class="project-count">${d.requirements.length}</span></h3><div class="project-panel">${d.requirements.map((req,i)=>`<details data-restore-key="req:${clean(r.id)}:${clean(req.id)}"><summary>${clean(req.id)} · ${clean(req.title)}</summary><p>${clean(req.text)}</p><p>Requirement identity: <code>${clean(candidate.requirements[i]?.path)}</code> · <code>${clean(candidate.requirements[i]?.blob)}</code></p></details>`).join('')}</div>`:`<p>${(data.failed || data.requests.some(x=>x.materialFailed))?'Request or PRD unavailable. Refresh to try again.':'No current PRD candidate or approved snapshot. Waiting for a PRD submission.'}</p>`}${d && data.requests.some(x=>x.materialFailed)?'<p role="status">Some request PRDs could not be read. Refresh to try again.</p>':''}</section><aside class="project-panel project-provenance"><details data-restore-key="provenance"${matchMedia('(min-width:700px)').matches?' open':''}><summary>Version and provenance</summary>${rows}${candidate?`<p>Approval and revocation live in the conversation's Request details.</p><a data-full-view href="${conversationLink(r.thread)}">Open conversation</a>`:''}</details></aside></div>`;
|
||||
}
|
||||
function overview(data,project,canSend,receipt='') {
|
||||
const active=data.requests.filter(r=>r.state && !ended(r.state)), waiting=active.filter(r=>needs(r.state)), ordered=[...data.requests].sort(requestOrder), first=ordered[0], threads=[...data.threads];
|
||||
|
||||
@@ -350,3 +350,27 @@ test('SP7: each pane names its own organization\'s Messages page, and the bar no
|
||||
[['#/orgs/alpha/messages', 'Messages in Alpha', 'Messages'], ['#/orgs/beta/messages', 'Messages in Beta', 'Messages']]);
|
||||
assert.doesNotMatch(await b.evaluate(`document.querySelector('.assist-bar').innerText`), /Messages/);
|
||||
});
|
||||
|
||||
// SP7 review: a pane has no Request details, sessions or sharing, so each pane links its thread's full
|
||||
// view, and a link marked data-full-view is not turned into a pane.
|
||||
test('SP7: each pane links its thread\'s full view, and one plain click reaches it', async t => {
|
||||
const { b, wait } = await open(t, { hash: `#/assistant?open=alpha:${T.a1},beta:${T.b1}` });
|
||||
await wait(`${ready(`alpha:${T.a1}`)} && ${ready(`beta:${T.b1}`)}`);
|
||||
assert.deepEqual(await b.evaluate(`[...document.querySelectorAll('[data-assist-pane]')].map(p => { const a = p.querySelector('.assist-pane-h a[data-assist-pane-full]'); return a && [a.getAttribute('href'), a.textContent.trim(), a.hasAttribute('data-full-view')]; })`),
|
||||
[[`#/orgs/alpha/projects/threads/${T.a1}`, 'Full view', true], [`#/orgs/beta/projects/threads/${T.b1}`, 'Full view', true]]);
|
||||
await b.evaluate(`${PANE(`beta:${T.b1}`)}.querySelector('a[data-assist-pane-full]').click()`);
|
||||
await wait(`!document.querySelector('[data-assistant]') && /Beta tokens rotate nightly/.test(document.getElementById('bus-view').innerText)`);
|
||||
assert.equal(await b.evaluate('location.hash'), `#/orgs/beta/projects/threads/${T.b1}`);
|
||||
});
|
||||
|
||||
test('SP7: a link marked as the full view stays in the organization\'s own view, from any page', async t => {
|
||||
const { b, wait } = await open(t, { hash: `#/assistant?open=alpha:${T.a1}` });
|
||||
await wait(ready(`alpha:${T.a1}`));
|
||||
// On the Assistant page itself: no pane is added, the browser follows the link.
|
||||
await b.evaluate(`(() => { const a = document.createElement('a'); a.href = '#/orgs/beta/projects/threads/${T.b1}'; a.setAttribute('data-full-view', ''); a.textContent = 'thread'; document.body.append(a); a.click(); a.remove(); })()`);
|
||||
await wait(`location.hash === '#/orgs/beta/projects/threads/${T.b1}'`);
|
||||
await wait(`!document.querySelector('[data-assistant]') && /Beta tokens rotate nightly/.test(document.getElementById('bus-view').innerText)`);
|
||||
// From another page: the same.
|
||||
await b.evaluate(`(() => { const a = document.createElement('a'); a.href = '#/orgs/alpha/projects/threads/${T.a1}'; a.setAttribute('data-full-view', ''); a.textContent = 'thread'; document.body.append(a); a.click(); a.remove(); })()`);
|
||||
await wait(`location.hash === '#/orgs/alpha/projects/threads/${T.a1}' && !document.querySelector('[data-assistant]')`);
|
||||
});
|
||||
@@ -165,3 +165,6 @@ test('S4 fix stale project gather cannot enrich a newer request revision', async
|
||||
const x=await b.evaluate(`({revision:document.querySelector('.project-request-intro').dataset.requestRevision,prd:document.querySelector('[data-work-prd]').textContent,files:document.querySelector('[data-work-files]').textContent})`);
|
||||
assert.equal(x.revision,'2');assert.doesNotMatch(x.prd,/v3|Approved/);assert.doesNotMatch(x.files,/4 draft/);
|
||||
});
|
||||
// SP7 review: the requirement view sends the person to the conversation's Request details, which a pane
|
||||
// does not have, so both of its "Open conversation" links are marked as the full view.
|
||||
test('SP7 requirement view links the conversation\'s full view, not a pane',async t=>{const {b,wait}=await setup(t,'/projects/'+P+'/requirements');await wait(`document.querySelector('[data-prd]')`);const links=await b.evaluate(`[...document.querySelectorAll('[data-project-content] a')].filter(a=>a.textContent.trim()==='Open conversation').map(a=>a.hasAttribute('data-full-view'))`);assert.deepEqual(links,[true,true]);});
|
||||
@@ -188,7 +188,7 @@ test('Inbox lists both organizations, copies a command with --org and the full i
|
||||
assert.deepEqual(items.map(([k]) => k).sort(), [`acme:${ACME_DECISION}`, `beta:${BETA_DECISION}`]);
|
||||
// Each row names its organization, project and session.
|
||||
const acme = items.find(([k]) => k.startsWith('acme'))[1];
|
||||
assert.match(acme, /Acme Studio/); assert.match(acme, /Website/); assert.match(acme, /coder/); assert.match(acme, /gated/);
|
||||
assert.match(acme, /Acme Studio/); assert.match(acme, /Website/); assert.match(acme, /coder/); assert.match(acme, /Gated/);
|
||||
const beta = items.find(([k]) => k.startsWith('beta'))[1];
|
||||
assert.match(beta, /Beta Works/); assert.match(beta, /pm/);
|
||||
// SP3: a project chip only where the item has a project.
|
||||
|
||||
Reference in new issue
Block a user