mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:28:46 +01:00
fix(evals): display existing campaign summary inline
This commit is contained in:
1 parent
b3c77ff304
commit
2f68f4ff3c
3 files changed
+29
-1
No files matched your search
@@ -175,6 +175,7 @@ export async function regenerateRunnerDashboard(input: {
|
||||
entries,
|
||||
campaign,
|
||||
summaryAvailable: summary !== undefined,
|
||||
summaryText: summary,
|
||||
history,
|
||||
});
|
||||
const upgraded = {
|
||||
|
||||
@@ -22,11 +22,32 @@ it("regeneration derives link availability from the actual summary file without
|
||||
await regenerateRunnerDashboard({ bundle: root, historyFile: null });
|
||||
const html = await readFile(path.join(root, "index.html"), "utf8");
|
||||
expect(html).toContain('href="summary.md"'); expect(html).toContain(before.source.workflowRunUrl);
|
||||
expect(html).toContain('<summary>Campaign notes and coverage</summary>');
|
||||
expect(html).toContain('Six incomplete or blocked journeys.');
|
||||
const after = JSON.parse(await readFile(path.join(root, "normalized-results.json"), "utf8"));
|
||||
expect(after.results).toEqual(before.results); expect(after.source).toEqual(before.source);
|
||||
expect(after.selected).toBe(before.selected);
|
||||
await rm(path.join(root, "summary.md"));
|
||||
await regenerateRunnerDashboard({ bundle: root, historyFile: null });
|
||||
expect(await readFile(path.join(root, "index.html"), "utf8")).not.toContain('href="summary.md"');
|
||||
const withoutSummary = await readFile(path.join(root, "index.html"), "utf8");
|
||||
expect(withoutSummary).not.toContain('href="summary.md"');
|
||||
expect(withoutSummary).not.toContain('<summary>Campaign notes and coverage</summary>');
|
||||
} finally { await rm(root, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
it("keeps coverage prose separate from measured counts and escapes untrusted summary markup", () => {
|
||||
const summaryText = '# Current coverage\n0 passes, 1 failed initial, 243 unrun\n\n- tasks.create-backlog\n<script>alert(1)</script>\n[unsafe](javascript:alert(1))\n<img src=x onerror=alert(2)> & private-looking text';
|
||||
const input = { title: "Campaign", generatedAt: "2026-10-27T00:00:00Z", expected: [], catalog: [], entries: [], campaign: campaign(), summaryText };
|
||||
const output = renderRunnerE2EDashboard(input);
|
||||
expect(output).toContain('0 passes, 1 failed initial, 243 unrun');
|
||||
expect(output).toContain('tasks.create-backlog');
|
||||
expect(output).toContain('<script>alert(1)</script>');
|
||||
expect(output).toContain('<img src=x onerror=alert(2)> & private-looking text');
|
||||
expect(output).not.toContain('<script>alert(1)</script>');
|
||||
expect(output).not.toContain('<img src=x');
|
||||
expect(output).not.toContain('href="javascript:');
|
||||
expect(output).toContain('<strong>0/0</strong>');
|
||||
expect(input.campaign.selected).toBe(0);
|
||||
expect(renderRunnerE2EDashboard({ ...input, campaign: undefined })).not.toContain('<summary>Campaign notes and coverage</summary>');
|
||||
expect(renderRunnerE2EDashboard({ ...input, summaryText: " " })).not.toContain('<summary>Campaign notes and coverage</summary>');
|
||||
});
|
||||
@@ -46,6 +46,8 @@ export interface RunnerDashboardInput {
|
||||
campaign?: RunnerE2ECampaign;
|
||||
/** The bundle contains its existing relative summary.md document. */
|
||||
summaryAvailable?: boolean;
|
||||
/** Existing public summary.md content, rendered only as escaped readable text. */
|
||||
summaryText?: string;
|
||||
history?: RunnerE2EHistoryIndex;
|
||||
}
|
||||
|
||||
@@ -532,6 +534,9 @@ export function renderRunnerE2EDashboard(input: RunnerDashboardInput) {
|
||||
h1 { max-width: 760px; margin: 0; font-size: clamp(32px, 4vw, 56px); font-weight: 650; line-height: 1.03; letter-spacing: -.04em; }
|
||||
.lede { max-width: 720px; margin: 16px 0 0; color: var(--muted-foreground); font-size: 16px; }
|
||||
.report-actions { display: flex; align-items: stretch; gap: 8px; }
|
||||
.campaign-notes { margin: 0 0 24px; }
|
||||
.campaign-notes > summary { cursor: pointer; font-weight: 600; }
|
||||
.campaign-notes pre { white-space: pre-wrap; overflow-wrap: anywhere; font: inherit; line-height: 1.6; }
|
||||
.summary { display: flex; border: 1px solid var(--border); border-radius: var(--radius); background: var(--card); }
|
||||
.metric { min-width: 108px; padding: 13px 16px; border-right: 1px solid var(--border); }
|
||||
.metric:last-child { border-right: 0; }
|
||||
@@ -849,6 +854,7 @@ export function renderRunnerE2EDashboard(input: RunnerDashboardInput) {
|
||||
<button class="gallery-launch" type="button" data-gallery-open ${screenshotCount === 0 ? "disabled" : ""}>${screenshotCount === 0 ? "Visual evidence · workflow artifact only" : `View gallery · ${screenshotCount}`}</button>
|
||||
</div>
|
||||
</header>
|
||||
${input.campaign && input.summaryText?.trim() ? `<details class="campaign-notes" open><summary>Campaign notes and coverage</summary><pre>${html(input.summaryText)}</pre></details>` : ""}
|
||||
${renderCampaignSuiteSummary(input.campaign?.suites ?? [], catalog, "", false)}
|
||||
<details class="campaign-billing-details"><summary>Campaign usage and spend</summary>
|
||||
<section class="billing-overview" aria-label="Campaign billing summary">
|
||||
|
||||
Reference in new issue
Block a user