From 504fcd922048322994dd7711b1ea7bd174bdd501 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Thu, 12 Nov 2026 18:31:21 -0600 Subject: [PATCH] fix(webui): pack preview reads credential lines as {service, scope} objects (#1873 SP6) The bus sends each hire's credential lines as {service, scope} (scope absent when a definition names a service without scopes). The picker destructured them as tuples, so a real preview threw and never rendered. The browser test fixture now uses the producer's shape. --- packages/webui/src/public/packs.js | 2 +- packages/webui/tests/packs-workflows-browser.test.mjs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/webui/src/public/packs.js b/packages/webui/src/public/packs.js index 04eac991e..9c6009563 100644 --- a/packages/webui/src/public/packs.js +++ b/packages/webui/src/public/packs.js @@ -67,7 +67,7 @@ ? `

Flagged lines, which only the owner approves:

` : '

No flagged lines.

'; const creds = Array.isArray(h.credentialLines) && h.credentialLines.length - ? `

Credential lines:

` + ? `

Credential lines:

` : '

No credential lines.

'; const mark = h.mark === 'dropped' ? 'Not proposed: an agent or open hire decision already has this name.' : 'Recorded on apply for the chief to raise as a hire decision.'; const noHire = h.noHire ? '

On this organization\'s no-hire list: the decision will say so.

' : ''; diff --git a/packages/webui/tests/packs-workflows-browser.test.mjs b/packages/webui/tests/packs-workflows-browser.test.mjs index 6da622de7..016daeda7 100644 --- a/packages/webui/tests/packs-workflows-browser.test.mjs +++ b/packages/webui/tests/packs-workflows-browser.test.mjs @@ -28,7 +28,7 @@ const PREVIEW = { { item: 'workflow', name: 'ship', target: 'ship', path: 'workflows.ship', mark: 'removed' }, ], hires: [ - { name: 'lead-reviewer', definition: 'org:reviewer', mark: 'propose', pin: null, flagged: [{ field: 'authority.withinRole', value: 'decision.resolve.technical', reasons: ['above-chief', 'resolve-right'] }], credentialLines: [['gitea', 'repo:write']], noHire: true }, + { name: 'lead-reviewer', definition: 'org:reviewer', mark: 'propose', pin: null, flagged: [{ field: 'authority.withinRole', value: 'decision.resolve.technical', reasons: ['above-chief', 'resolve-right'] }], credentialLines: [{ service: 'gitea', scope: 'repo:write' }, { service: 'matrix' }], noHire: true }, { name: 'builder', definition: 'coder', mark: 'dropped', pin: null, flagged: null, credentialLines: [], noHire: false }, ], skills: { notEnabled: ['lint-fix'] }, chief: true, plan: {}, digest: REV('d'), @@ -106,7 +106,9 @@ test('SP6 pack picker: list, preview with marks, flagged, credential and noHire ]); const lead = '[data-pack-hire="lead-reviewer"]'; assert.match(await b.evaluate(`document.querySelector('${lead} [data-pack-flagged]').textContent`), /authority\.withinRole decision\.resolve\.technical above the chief, a resolve right/); - assert.equal(await b.evaluate(`document.querySelector('${lead} [data-pack-credentials]').textContent`), 'gitea repo:write'); + // The bus sends each credential line as {service, scope} (hire-decisions credentialLines); scope is absent + // when the definition names a service without scopes. + assert.deepEqual(await b.evaluate(`[...document.querySelectorAll('${lead} [data-pack-credentials] li')].map(l=>l.textContent)`), ['gitea repo:write', 'matrix']); assert.match(await b.evaluate(`document.querySelector('${lead} [data-pack-nohire]').textContent`), /no-hire list/); assert.equal(await b.evaluate(`document.querySelector('[data-pack-hire="builder"] [data-pack-nohire]')`), null); assert.ok(await b.evaluate(`document.querySelector('[data-pack-hire="builder"] [data-pack-noflag]')!==null`));