feat(briefs): capture briefing page screenshots and pin playwright

Adds a screenshot-capture spec gated behind BRIEFS_CAPTURE_SCREENSHOTS=1
that renders the Briefing page fixture set and writes desktop, mobile,
and empty-state PNGs into docs/pr-screenshots/pap-9963/ for UX handoff.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-05-24 02:28:36 +00:00
1 parent 0f84cb9aed
commit 3a0f9b5c83
9 files changed
+398

No files matched your search

@@ -0,0 +1,76 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Briefing — desktop</title>
<style>
:root {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--border: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--accent: oklch(0.269 0 0);
}
html, body { background: var(--background); color: var(--foreground); margin: 0; min-height: 100vh; }
body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; }
a { color: inherit; }
@media (max-width: 700px) {
[data-briefs-mobile-tabs] { display: flex !important; }
[data-briefs-section] > header { display: none; }
[data-briefs-section][data-mobile-hidden="true"] { display: none; }
[data-briefs-grid] { grid-template-columns: 1fr !important; }
}
</style>
</head>
<body>
<div style="font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; color: var(--foreground); padding: 20px clamp(12px, 4vw, 32px); max-width: 1280px; margin: 0 auto; min-height: 100vh;">
<header style="display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px; margin-bottom: 6px;">
<h1 style="margin: 0; font-size: 22px; font-weight: 600;">Briefing</h1>
<div style="flex: 1; min-width: 0; font-size: 12px; color: var(--muted-foreground);">8 active · 2 pinned · refreshed just now</div>
<div style="display: flex; gap: 6px; align-items: center;">
<button style="padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--card); color: var(--foreground); font-size: 12px;">Preferences</button>
<button style="padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--card); color: var(--foreground); font-size: 12px;">Refresh</button>
</div>
</header>
<p style="margin: 0; margin-bottom: 18px; font-size: 13px; color: var(--muted-foreground);">Durable cards for areas of work that involve you. Pin the ones you always want to see.</p>
<section data-briefs-section="attention" style="display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px;">
<header style="display: flex; align-items: center; gap: 10px;">
<h2 style="margin: 0; font-size: 12px; letter-spacing: 0.6px; color: var(--muted-foreground); text-transform: uppercase; font-weight: 600;">Needs your attention</h2>
<span style="font-size: 12px; color: var(--muted-foreground);">3</span>
<span style="flex: 1; border-bottom: 1px dashed var(--border);"></span>
</header>
<div data-briefs-grid style="display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr)); gap: 12px;">
<article data-briefs-card="true" data-state="waiting-user" data-summary-status="ok" data-pinned="true" style="position:relative;background:var(--card, oklch(0.205 0 0));border:1px solid var(--border, oklch(0.269 0 0));border-radius:10px;padding:12px 14px 12px 18px;display:flex;flex-direction:column;gap:8px;min-width:0;opacity:1"><span aria-hidden="true" style="position:absolute;top:8px;bottom:8px;left:6px;width:3px;border-radius:2px;background:oklch(0.72 0.15 70)"></span><header style="display:flex;align-items:flex-start;gap:8px;min-width:0"><button type="button" aria-pressed="true" aria-label="Unpin card" style="width:22px;height:22px;flex-shrink:0;border-radius:6px;border:1px solid oklch(0.8 0.13 80);background:oklch(0.4 0.13 80);color:oklch(0.95 0.1 80);font-size:12px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center">★</button><h3 style="margin:0;flex:1;font-size:14px;font-weight:600;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">External-adapter plugin: spec review</h3><span data-briefs-state-badge="waiting-user" style="flex-shrink:0;padding:2px 8px;font-size:11px;font-weight:600;border-radius:999px;background:oklch(0.27 0.07 70);color:oklch(0.85 0.11 70);white-space:nowrap">Waiting on you</span></header><div style="display:flex;align-items:center;flex-wrap:wrap;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));min-width:0"><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0))">PAP-9020</span><span>2 issues</span><span>· 14h ago</span></div><p data-briefs-summary="true" style="margin:0;font-size:13px;line-height:1.45;color:var(--foreground, oklch(0.985 0 0));overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3">External-adapter sandbox spec is waiting on your reply about credential scoping before publish.</p><ul data-briefs-source-rows="true" style="list-style:none;margin:0;padding:0;display:flex;flex-direction:column"><li style="display:flex;align-items:center;gap:6px;padding:6px 0;border-top:none;font-size:12px;min-width:0"><span aria-hidden="true" style="color:var(--muted-foreground, oklch(0.708 0 0));width:12px;text-align:center">?</span><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0));font-size:11px">PAP-9020</span><a href="/PAP/issues/PAP-9020#interaction-1" title="Confirm credential scoping" style="flex:1;color:var(--foreground, oklch(0.985 0 0));text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Confirm credential scoping</a><span data-briefs-row-tag="warning" style="flex-shrink:0;font-size:10px;padding:1px 6px;border-radius:999px;background:oklch(0.27 0.07 70);color:oklch(0.85 0.11 70);white-space:nowrap">asked you</span></li><li style="display:flex;align-items:center;gap:6px;padding:6px 0;border-top:1px dashed var(--border, oklch(0.269 0 0));font-size:12px;min-width:0"><span aria-hidden="true" style="color:var(--muted-foreground, oklch(0.708 0 0));width:12px;text-align:center">■</span><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0));font-size:11px">PAP-9001</span><a href="/PAP/issues/PAP-9963" title="Adapter spec draft" style="flex:1;color:var(--foreground, oklch(0.985 0 0));text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Adapter spec draft</a><span data-briefs-row-tag="violet" style="flex-shrink:0;font-size:10px;padding:1px 6px;border-radius:999px;background:oklch(0.27 0.07 305);color:oklch(0.84 0.11 305);white-space:nowrap">in_review</span></li></ul><footer style="display:flex;align-items:center;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));padding-top:4px;margin-top:auto"><span aria-hidden="true"></span><span style="flex:1"></span><span>14h ago</span><a href="/PAP/issues/PAP-9963" style="color:var(--foreground, oklch(0.985 0 0));text-decoration:none;font-size:11px">Open tree →</a></footer></article><article data-briefs-card="true" data-state="error" data-summary-status="ok" data-pinned="false" style="position:relative;background:var(--card, oklch(0.205 0 0));border:1px solid var(--border, oklch(0.269 0 0));border-radius:10px;padding:12px 14px 12px 18px;display:flex;flex-direction:column;gap:8px;min-width:0;opacity:1"><span aria-hidden="true" style="position:absolute;top:8px;bottom:8px;left:6px;width:3px;border-radius:2px;background:oklch(0.62 0.21 25)"></span><header style="display:flex;align-items:flex-start;gap:8px;min-width:0"><button type="button" aria-pressed="false" aria-label="Pin card" style="width:22px;height:22px;flex-shrink:0;border-radius:6px;border:1px solid var(--border, oklch(0.269 0 0));background:transparent;color:var(--muted-foreground, oklch(0.708 0 0));font-size:12px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:cenLine truncated
</div>
</section>
<section data-briefs-section="live" style="display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px;">
<header style="display: flex; align-items: center; gap: 10px;">
<h2 style="margin: 0; font-size: 12px; letter-spacing: 0.6px; color: var(--muted-foreground); text-transform: uppercase; font-weight: 600;">Live and in review</h2>
<span style="font-size: 12px; color: var(--muted-foreground);">3</span>
<span style="flex: 1; border-bottom: 1px dashed var(--border);"></span>
</header>
<div data-briefs-grid style="display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr)); gap: 12px;">
<article data-briefs-card="true" data-state="live" data-summary-status="ok" data-pinned="true" style="position:relative;background:var(--card, oklch(0.205 0 0));border:1px solid var(--border, oklch(0.269 0 0));border-radius:10px;padding:12px 14px 12px 18px;display:flex;flex-direction:column;gap:8px;min-width:0;opacity:1"><span aria-hidden="true" style="position:absolute;top:8px;bottom:8px;left:6px;width:3px;border-radius:2px;background:oklch(0.7 0.13 200)"></span><header style="display:flex;align-items:flex-start;gap:8px;min-width:0"><button type="button" aria-pressed="true" aria-label="Unpin card" style="width:22px;height:22px;flex-shrink:0;border-radius:6px;border:1px solid oklch(0.8 0.13 80);background:oklch(0.4 0.13 80);color:oklch(0.95 0.1 80);font-size:12px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center">★</button><h3 style="margin:0;flex:1;font-size:14px;font-weight:600;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">Briefs plugin planning</h3><span data-briefs-state-badge="live" style="flex-shrink:0;padding:2px 8px;font-size:11px;font-weight:600;border-radius:999px;background:oklch(0.27 0.06 200);color:oklch(0.84 0.11 200);white-space:nowrap">Live</span></header><div style="display:flex;align-items:center;flex-wrap:wrap;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));min-width:0"><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0))">PAP-9963</span><span>2 issues</span><span>· in 7h</span></div><p data-briefs-summary="true" style="margin:0;font-size:13px;line-height:1.45;color:var(--foreground, oklch(0.985 0 0));overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3">Phase 5 page UI in flight on PAP-9963; deterministic data is done and routines are paused for board approval.</p><ul data-briefs-source-rows="true" style="list-style:none;margin:0;padding:0;display:flex;flex-direction:column"><li style="display:flex;align-items:center;gap:6px;padding:6px 0;border-top:none;font-size:12px;min-width:0"><span aria-hidden="true" style="color:var(--muted-foreground, oklch(0.708 0 0));width:12px;text-align:center">■</span><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0));font-size:11px">PAP-9963</span><a href="/PAP/issues/PAP-9963" title="Wire briefing page UI" style="flex:1;color:var(--foreground, oklch(0.985 0 0));text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Wire briefing page UI</a><span data-briefs-row-tag="cyan" style="flex-shrink:0;font-size:10px;padding:1px 6px;border-radius:999px;background:oklch(0.27 0.06 200);color:oklch(0.84 0.11 200);white-space:nowrap">in_progress</span></li><li style="display:flex;align-items:center;gap:6px;padding:6px 0;border-top:1px dashed var(--border, oklch(0.269 0 0));font-size:12px;min-width:0"><span aria-hidden="true" style="color:var(--muted-foreground, oklch(0.708 0 0));width:12px;text-align:center">■</span><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0));font-size:11px">PAP-9961</span><a href="/PAP/issues/PAP-9963" title="Deterministic card service" style="flex:1;color:var(--foreground, oklch(0.985 0 0));text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Deterministic card service</a><span data-briefs-row-tag="green" style="flex-shrink:0;font-size:10px;padding:1px 6px;border-radius:999px;background:oklch(0.27 0.06 145);color:oklch(0.84 0.1 145);white-space:nowrap">done</span></li><li style="display:flex;align-items:center;gap:6px;padding:6px 0;border-top:1px dashed var(--border, oklch(0.269 0 0));font-size:12px;min-width:0"><span aria-hidden="true" style="color:var(--muted-foreground, oklch(0.708 0 0));width:12px;text-align:center">■</span><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0));font-size:11px">PAP-9962</span><a href="/PAP/issues/PAP-9963" title="Managed agent + routines" style="flex:1;color:var(--foreground, oklch(0.985 0 0));text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Managed agent + routines</a><span data-briefs-row-tag="muted" style="flex-shrink:0;font-size:10px;padding:1px 6px;border-radius:999px;background:oklch(0.25 0 0);color:oklch(0.75 0 0);white-space:nowrap">paused</span></li></ul><footer style="display:flex;align-items:center;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));padding-top:4px;margin-top:auto"><span>+3 more in tree</span><span style="flex:1"></span><span>in 7h</span><a href="/PAP/issues/PAP-9963" style="color:var(--foreground, oklch(0.985 0 0));text-decoration:none;font-size:11px">Open tree →</a></footer></article><article data-briefs-card="true" data-state="live" data-summary-status="fallback" data-pinned="faLine truncated
</div>
</section>
<section data-briefs-section="settled" style="display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px;">
<header style="display: flex; align-items: center; gap: 10px;">
<h2 style="margin: 0; font-size: 12px; letter-spacing: 0.6px; color: var(--muted-foreground); text-transform: uppercase; font-weight: 600;">Recently done & stale</h2>
<span style="font-size: 12px; color: var(--muted-foreground);">2</span>
<span style="flex: 1; border-bottom: 1px dashed var(--border);"></span>
</header>
<div data-briefs-grid style="display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr)); gap: 12px;">
<article data-briefs-card="true" data-state="done" data-summary-status="ok" data-pinned="false" style="position:relative;background:var(--card, oklch(0.205 0 0));border:1px solid var(--border, oklch(0.269 0 0));border-radius:10px;padding:12px 14px 12px 18px;display:flex;flex-direction:column;gap:8px;min-width:0;opacity:0.78"><span aria-hidden="true" style="position:absolute;top:8px;bottom:8px;left:6px;width:3px;border-radius:2px;background:oklch(0.65 0.16 145)"></span><header style="display:flex;align-items:flex-start;gap:8px;min-width:0"><button type="button" aria-pressed="false" aria-label="Pin card" style="width:22px;height:22px;flex-shrink:0;border-radius:6px;border:1px solid var(--border, oklch(0.269 0 0));background:transparent;color:var(--muted-foreground, oklch(0.708 0 0));font-size:12px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center">☆</button><h3 style="margin:0;flex:1;font-size:14px;font-weight:600;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">Sidebar plugin slot hardening</h3><span data-briefs-state-badge="done" style="flex-shrink:0;padding:2px 8px;font-size:11px;font-weight:600;border-radius:999px;background:oklch(0.27 0.06 145);color:oklch(0.84 0.1 145);white-space:nowrap">Recently done</span></header><div style="display:flex;align-items:center;flex-wrap:wrap;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));min-width:0"><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0))">PAP-8990</span><span>2 issues</span><span>· 5h ago</span></div><p data-briefs-summary="true" style="margin:0;font-size:13px;line-height:1.45;color:var(--foreground, oklch(0.985 0 0));overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3">Plugin sidebar slot order locked in; rolling tests green.</p><ul data-briefs-source-rows="true" style="list-style:none;margin:0;padding:0;display:flex;flex-direction:column"><li style="display:flex;align-items:center;gap:6px;padding:6px 0;border-top:none;font-size:12px;min-width:0"><span aria-hidden="true" style="color:var(--muted-foreground, oklch(0.708 0 0));width:12px;text-align:center">■</span><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0));font-size:11px">PAP-8990</span><a href="/PAP/issues/PAP-9963" title="Sidebar plugin slot hardening" style="flex:1;color:var(--foreground, oklch(0.985 0 0));text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Sidebar plugin slot hardening</a><span data-briefs-row-tag="green" style="flex-shrink:0;font-size:10px;padding:1px 6px;border-radius:999px;background:oklch(0.27 0.06 145);color:oklch(0.84 0.1 145);white-space:nowrap">done</span></li></ul><footer style="display:flex;align-items:center;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));padding-top:4px;margin-top:auto"><span aria-hidden="true"></span><span style="flex:1"></span><span>5h ago</span><a href="/PAP/issues/PAP-9963" style="color:var(--foreground, oklch(0.985 0 0));text-decoration:none;font-size:11px">Open tree →</a></footer></article><article data-briefs-card="true" data-state="stale" data-summary-status="ok" data-pinned="false" style="position:relative;background:var(--card, oklch(0.205 0 0));border:1px solid var(--border, oklch(0.269 0 0));border-radius:10px;padding:12px 14px 12px 18px;display:flex;flex-direction:column;gap:8px;min-width:0;opacity:0.78"><span aria-hidden="true" style="position:absolute;top:8px;bottom:8px;left:6px;width:3px;border-radius:2px;background:oklch(0.5 0 0)"></span><header style="display:flex;align-items:flex-start;gap:8px;min-width:0"><button type="button" aria-pressed="false" aria-label="Pin card" style="width:22px;height:22px;flex-shrink:0;border-radius:6px;border:1px solid var(--border, oklch(0.269 0 0));background:transparent;color:var(--muted-foreground, oklch(0.708 0 0));font-size:12px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center">☆</button><h3 style="margin:0;flex:1;font-size:14px;font-weight:600;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">GA migration spike</h3><span data-briefs-state-badge="stale" style="flex-shrink:0;padding:2px 8px;font-size:11px;font-weight:600;border-radius:999px;background:oklch(0.25 0 0);color:oklch(0.75 0 0);white-space:nowrap">Stale</span></header><div style="display:flex;align-items:center;flex-wrap:wrap;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));min-width:0"><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0))">PAP-7000</span><span>2 issues</span><span>· 2w ago</span></div><p data-briefs-summary="true" style="margin:0;font-size:13px;line-height:1.45;color:var(--foreground, oklch(0.985 0 0));overflow:hidden;display:-webkit-box;-webkit-box-orient:vertiLine truncated
</div>
</section>
</div>
</body>
</html>
@@ -0,0 +1,46 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Briefing — desktop</title>
<style>
:root {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--border: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--accent: oklch(0.269 0 0);
}
html, body { background: var(--background); color: var(--foreground); margin: 0; min-height: 100vh; }
body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; }
a { color: inherit; }
@media (max-width: 700px) {
[data-briefs-mobile-tabs] { display: flex !important; }
[data-briefs-section] > header { display: none; }
[data-briefs-section][data-mobile-hidden="true"] { display: none; }
[data-briefs-grid] { grid-template-columns: 1fr !important; }
}
</style>
</head>
<body>
<div style="font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; color: var(--foreground); padding: 20px clamp(12px, 4vw, 32px); max-width: 1280px; margin: 0 auto; min-height: 100vh;">
<header style="display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px; margin-bottom: 6px;">
<h1 style="margin: 0; font-size: 22px; font-weight: 600;">Briefing</h1>
<div style="flex: 1; min-width: 0; font-size: 12px; color: var(--muted-foreground);">0 active · 0 pinned · refreshed just now</div>
<div style="display: flex; gap: 6px; align-items: center;">
<button style="padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--card); color: var(--foreground); font-size: 12px;">Preferences</button>
<button style="padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--card); color: var(--foreground); font-size: 12px;">Refresh</button>
</div>
</header>
<p style="margin: 0; margin-bottom: 18px; font-size: 13px; color: var(--muted-foreground);">Durable cards for areas of work that involve you. Pin the ones you always want to see.</p>
<div data-briefs-empty style="padding: 40px 24px; border: 1px dashed var(--border); border-radius: 10px; text-align: center; color: var(--muted-foreground);">
<div style="font-size: 15px; color: var(--foreground); font-weight: 600; margin-bottom: 4px;">No briefs yet</div>
<div style="font-size: 13px;">Cards appear here once the Briefing Analyst picks up recent work. Pinned cards never expire.</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,76 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Briefing — mobile</title>
<style>
:root {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--border: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--accent: oklch(0.269 0 0);
}
html, body { background: var(--background); color: var(--foreground); margin: 0; min-height: 100vh; }
body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; }
a { color: inherit; }
@media (max-width: 700px) {
[data-briefs-mobile-tabs] { display: flex !important; }
[data-briefs-section] > header { display: none; }
[data-briefs-section][data-mobile-hidden="true"] { display: none; }
[data-briefs-grid] { grid-template-columns: 1fr !important; }
}
</style>
</head>
<body>
<div style="font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; color: var(--foreground); padding: 20px clamp(12px, 4vw, 32px); max-width: 1280px; margin: 0 auto; min-height: 100vh;">
<header style="display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px; margin-bottom: 6px;">
<h1 style="margin: 0; font-size: 22px; font-weight: 600;">Briefing</h1>
<div style="flex: 1; min-width: 0; font-size: 12px; color: var(--muted-foreground);">8 active · 2 pinned · refreshed just now</div>
<div style="display: flex; gap: 6px; align-items: center;">
<button style="padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--card); color: var(--foreground); font-size: 12px;">Preferences</button>
<button style="padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--card); color: var(--foreground); font-size: 12px;">Refresh</button>
</div>
</header>
<p style="margin: 0; margin-bottom: 18px; font-size: 13px; color: var(--muted-foreground);">Durable cards for areas of work that involve you. Pin the ones you always want to see.</p>
<section data-briefs-section="attention" style="display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px;">
<header style="display: flex; align-items: center; gap: 10px;">
<h2 style="margin: 0; font-size: 12px; letter-spacing: 0.6px; color: var(--muted-foreground); text-transform: uppercase; font-weight: 600;">Needs your attention</h2>
<span style="font-size: 12px; color: var(--muted-foreground);">3</span>
<span style="flex: 1; border-bottom: 1px dashed var(--border);"></span>
</header>
<div data-briefs-grid style="display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr)); gap: 12px;">
<article data-briefs-card="true" data-state="waiting-user" data-summary-status="ok" data-pinned="true" style="position:relative;background:var(--card, oklch(0.205 0 0));border:1px solid var(--border, oklch(0.269 0 0));border-radius:10px;padding:12px 14px 12px 18px;display:flex;flex-direction:column;gap:8px;min-width:0;opacity:1"><span aria-hidden="true" style="position:absolute;top:8px;bottom:8px;left:6px;width:3px;border-radius:2px;background:oklch(0.72 0.15 70)"></span><header style="display:flex;align-items:flex-start;gap:8px;min-width:0"><button type="button" aria-pressed="true" aria-label="Unpin card" style="width:22px;height:22px;flex-shrink:0;border-radius:6px;border:1px solid oklch(0.8 0.13 80);background:oklch(0.4 0.13 80);color:oklch(0.95 0.1 80);font-size:12px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center">★</button><h3 style="margin:0;flex:1;font-size:14px;font-weight:600;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">External-adapter plugin: spec review</h3><span data-briefs-state-badge="waiting-user" style="flex-shrink:0;padding:2px 8px;font-size:11px;font-weight:600;border-radius:999px;background:oklch(0.27 0.07 70);color:oklch(0.85 0.11 70);white-space:nowrap">Waiting on you</span></header><div style="display:flex;align-items:center;flex-wrap:wrap;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));min-width:0"><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0))">PAP-9020</span><span>2 issues</span><span>· 14h ago</span></div><p data-briefs-summary="true" style="margin:0;font-size:13px;line-height:1.45;color:var(--foreground, oklch(0.985 0 0));overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3">External-adapter sandbox spec is waiting on your reply about credential scoping before publish.</p><ul data-briefs-source-rows="true" style="list-style:none;margin:0;padding:0;display:flex;flex-direction:column"><li style="display:flex;align-items:center;gap:6px;padding:6px 0;border-top:none;font-size:12px;min-width:0"><span aria-hidden="true" style="color:var(--muted-foreground, oklch(0.708 0 0));width:12px;text-align:center">?</span><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0));font-size:11px">PAP-9020</span><a href="/PAP/issues/PAP-9020#interaction-1" title="Confirm credential scoping" style="flex:1;color:var(--foreground, oklch(0.985 0 0));text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Confirm credential scoping</a><span data-briefs-row-tag="warning" style="flex-shrink:0;font-size:10px;padding:1px 6px;border-radius:999px;background:oklch(0.27 0.07 70);color:oklch(0.85 0.11 70);white-space:nowrap">asked you</span></li><li style="display:flex;align-items:center;gap:6px;padding:6px 0;border-top:1px dashed var(--border, oklch(0.269 0 0));font-size:12px;min-width:0"><span aria-hidden="true" style="color:var(--muted-foreground, oklch(0.708 0 0));width:12px;text-align:center">■</span><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0));font-size:11px">PAP-9001</span><a href="/PAP/issues/PAP-9963" title="Adapter spec draft" style="flex:1;color:var(--foreground, oklch(0.985 0 0));text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Adapter spec draft</a><span data-briefs-row-tag="violet" style="flex-shrink:0;font-size:10px;padding:1px 6px;border-radius:999px;background:oklch(0.27 0.07 305);color:oklch(0.84 0.11 305);white-space:nowrap">in_review</span></li></ul><footer style="display:flex;align-items:center;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));padding-top:4px;margin-top:auto"><span aria-hidden="true"></span><span style="flex:1"></span><span>14h ago</span><a href="/PAP/issues/PAP-9963" style="color:var(--foreground, oklch(0.985 0 0));text-decoration:none;font-size:11px">Open tree →</a></footer></article><article data-briefs-card="true" data-state="error" data-summary-status="ok" data-pinned="false" style="position:relative;background:var(--card, oklch(0.205 0 0));border:1px solid var(--border, oklch(0.269 0 0));border-radius:10px;padding:12px 14px 12px 18px;display:flex;flex-direction:column;gap:8px;min-width:0;opacity:1"><span aria-hidden="true" style="position:absolute;top:8px;bottom:8px;left:6px;width:3px;border-radius:2px;background:oklch(0.62 0.21 25)"></span><header style="display:flex;align-items:flex-start;gap:8px;min-width:0"><button type="button" aria-pressed="false" aria-label="Pin card" style="width:22px;height:22px;flex-shrink:0;border-radius:6px;border:1px solid var(--border, oklch(0.269 0 0));background:transparent;color:var(--muted-foreground, oklch(0.708 0 0));font-size:12px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:cenLine truncated
</div>
</section>
<section data-briefs-section="live" style="display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px;">
<header style="display: flex; align-items: center; gap: 10px;">
<h2 style="margin: 0; font-size: 12px; letter-spacing: 0.6px; color: var(--muted-foreground); text-transform: uppercase; font-weight: 600;">Live and in review</h2>
<span style="font-size: 12px; color: var(--muted-foreground);">3</span>
<span style="flex: 1; border-bottom: 1px dashed var(--border);"></span>
</header>
<div data-briefs-grid style="display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr)); gap: 12px;">
<article data-briefs-card="true" data-state="live" data-summary-status="ok" data-pinned="true" style="position:relative;background:var(--card, oklch(0.205 0 0));border:1px solid var(--border, oklch(0.269 0 0));border-radius:10px;padding:12px 14px 12px 18px;display:flex;flex-direction:column;gap:8px;min-width:0;opacity:1"><span aria-hidden="true" style="position:absolute;top:8px;bottom:8px;left:6px;width:3px;border-radius:2px;background:oklch(0.7 0.13 200)"></span><header style="display:flex;align-items:flex-start;gap:8px;min-width:0"><button type="button" aria-pressed="true" aria-label="Unpin card" style="width:22px;height:22px;flex-shrink:0;border-radius:6px;border:1px solid oklch(0.8 0.13 80);background:oklch(0.4 0.13 80);color:oklch(0.95 0.1 80);font-size:12px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center">★</button><h3 style="margin:0;flex:1;font-size:14px;font-weight:600;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">Briefs plugin planning</h3><span data-briefs-state-badge="live" style="flex-shrink:0;padding:2px 8px;font-size:11px;font-weight:600;border-radius:999px;background:oklch(0.27 0.06 200);color:oklch(0.84 0.11 200);white-space:nowrap">Live</span></header><div style="display:flex;align-items:center;flex-wrap:wrap;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));min-width:0"><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0))">PAP-9963</span><span>2 issues</span><span>· in 7h</span></div><p data-briefs-summary="true" style="margin:0;font-size:13px;line-height:1.45;color:var(--foreground, oklch(0.985 0 0));overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3">Phase 5 page UI in flight on PAP-9963; deterministic data is done and routines are paused for board approval.</p><ul data-briefs-source-rows="true" style="list-style:none;margin:0;padding:0;display:flex;flex-direction:column"><li style="display:flex;align-items:center;gap:6px;padding:6px 0;border-top:none;font-size:12px;min-width:0"><span aria-hidden="true" style="color:var(--muted-foreground, oklch(0.708 0 0));width:12px;text-align:center">■</span><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0));font-size:11px">PAP-9963</span><a href="/PAP/issues/PAP-9963" title="Wire briefing page UI" style="flex:1;color:var(--foreground, oklch(0.985 0 0));text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Wire briefing page UI</a><span data-briefs-row-tag="cyan" style="flex-shrink:0;font-size:10px;padding:1px 6px;border-radius:999px;background:oklch(0.27 0.06 200);color:oklch(0.84 0.11 200);white-space:nowrap">in_progress</span></li><li style="display:flex;align-items:center;gap:6px;padding:6px 0;border-top:1px dashed var(--border, oklch(0.269 0 0));font-size:12px;min-width:0"><span aria-hidden="true" style="color:var(--muted-foreground, oklch(0.708 0 0));width:12px;text-align:center">■</span><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0));font-size:11px">PAP-9961</span><a href="/PAP/issues/PAP-9963" title="Deterministic card service" style="flex:1;color:var(--foreground, oklch(0.985 0 0));text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Deterministic card service</a><span data-briefs-row-tag="green" style="flex-shrink:0;font-size:10px;padding:1px 6px;border-radius:999px;background:oklch(0.27 0.06 145);color:oklch(0.84 0.1 145);white-space:nowrap">done</span></li><li style="display:flex;align-items:center;gap:6px;padding:6px 0;border-top:1px dashed var(--border, oklch(0.269 0 0));font-size:12px;min-width:0"><span aria-hidden="true" style="color:var(--muted-foreground, oklch(0.708 0 0));width:12px;text-align:center">■</span><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0));font-size:11px">PAP-9962</span><a href="/PAP/issues/PAP-9963" title="Managed agent + routines" style="flex:1;color:var(--foreground, oklch(0.985 0 0));text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Managed agent + routines</a><span data-briefs-row-tag="muted" style="flex-shrink:0;font-size:10px;padding:1px 6px;border-radius:999px;background:oklch(0.25 0 0);color:oklch(0.75 0 0);white-space:nowrap">paused</span></li></ul><footer style="display:flex;align-items:center;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));padding-top:4px;margin-top:auto"><span>+3 more in tree</span><span style="flex:1"></span><span>in 7h</span><a href="/PAP/issues/PAP-9963" style="color:var(--foreground, oklch(0.985 0 0));text-decoration:none;font-size:11px">Open tree →</a></footer></article><article data-briefs-card="true" data-state="live" data-summary-status="fallback" data-pinned="faLine truncated
</div>
</section>
<section data-briefs-section="settled" style="display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px;">
<header style="display: flex; align-items: center; gap: 10px;">
<h2 style="margin: 0; font-size: 12px; letter-spacing: 0.6px; color: var(--muted-foreground); text-transform: uppercase; font-weight: 600;">Recently done & stale</h2>
<span style="font-size: 12px; color: var(--muted-foreground);">2</span>
<span style="flex: 1; border-bottom: 1px dashed var(--border);"></span>
</header>
<div data-briefs-grid style="display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr)); gap: 12px;">
<article data-briefs-card="true" data-state="done" data-summary-status="ok" data-pinned="false" style="position:relative;background:var(--card, oklch(0.205 0 0));border:1px solid var(--border, oklch(0.269 0 0));border-radius:10px;padding:12px 14px 12px 18px;display:flex;flex-direction:column;gap:8px;min-width:0;opacity:0.78"><span aria-hidden="true" style="position:absolute;top:8px;bottom:8px;left:6px;width:3px;border-radius:2px;background:oklch(0.65 0.16 145)"></span><header style="display:flex;align-items:flex-start;gap:8px;min-width:0"><button type="button" aria-pressed="false" aria-label="Pin card" style="width:22px;height:22px;flex-shrink:0;border-radius:6px;border:1px solid var(--border, oklch(0.269 0 0));background:transparent;color:var(--muted-foreground, oklch(0.708 0 0));font-size:12px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center">☆</button><h3 style="margin:0;flex:1;font-size:14px;font-weight:600;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">Sidebar plugin slot hardening</h3><span data-briefs-state-badge="done" style="flex-shrink:0;padding:2px 8px;font-size:11px;font-weight:600;border-radius:999px;background:oklch(0.27 0.06 145);color:oklch(0.84 0.1 145);white-space:nowrap">Recently done</span></header><div style="display:flex;align-items:center;flex-wrap:wrap;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));min-width:0"><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0))">PAP-8990</span><span>2 issues</span><span>· 5h ago</span></div><p data-briefs-summary="true" style="margin:0;font-size:13px;line-height:1.45;color:var(--foreground, oklch(0.985 0 0));overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3">Plugin sidebar slot order locked in; rolling tests green.</p><ul data-briefs-source-rows="true" style="list-style:none;margin:0;padding:0;display:flex;flex-direction:column"><li style="display:flex;align-items:center;gap:6px;padding:6px 0;border-top:none;font-size:12px;min-width:0"><span aria-hidden="true" style="color:var(--muted-foreground, oklch(0.708 0 0));width:12px;text-align:center">■</span><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0));font-size:11px">PAP-8990</span><a href="/PAP/issues/PAP-9963" title="Sidebar plugin slot hardening" style="flex:1;color:var(--foreground, oklch(0.985 0 0));text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Sidebar plugin slot hardening</a><span data-briefs-row-tag="green" style="flex-shrink:0;font-size:10px;padding:1px 6px;border-radius:999px;background:oklch(0.27 0.06 145);color:oklch(0.84 0.1 145);white-space:nowrap">done</span></li></ul><footer style="display:flex;align-items:center;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));padding-top:4px;margin-top:auto"><span aria-hidden="true"></span><span style="flex:1"></span><span>5h ago</span><a href="/PAP/issues/PAP-9963" style="color:var(--foreground, oklch(0.985 0 0));text-decoration:none;font-size:11px">Open tree →</a></footer></article><article data-briefs-card="true" data-state="stale" data-summary-status="ok" data-pinned="false" style="position:relative;background:var(--card, oklch(0.205 0 0));border:1px solid var(--border, oklch(0.269 0 0));border-radius:10px;padding:12px 14px 12px 18px;display:flex;flex-direction:column;gap:8px;min-width:0;opacity:0.78"><span aria-hidden="true" style="position:absolute;top:8px;bottom:8px;left:6px;width:3px;border-radius:2px;background:oklch(0.5 0 0)"></span><header style="display:flex;align-items:flex-start;gap:8px;min-width:0"><button type="button" aria-pressed="false" aria-label="Pin card" style="width:22px;height:22px;flex-shrink:0;border-radius:6px;border:1px solid var(--border, oklch(0.269 0 0));background:transparent;color:var(--muted-foreground, oklch(0.708 0 0));font-size:12px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center">☆</button><h3 style="margin:0;flex:1;font-size:14px;font-weight:600;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">GA migration spike</h3><span data-briefs-state-badge="stale" style="flex-shrink:0;padding:2px 8px;font-size:11px;font-weight:600;border-radius:999px;background:oklch(0.25 0 0);color:oklch(0.75 0 0);white-space:nowrap">Stale</span></header><div style="display:flex;align-items:center;flex-wrap:wrap;gap:8px;font-size:11px;color:var(--muted-foreground, oklch(0.708 0 0));min-width:0"><span style="font-family:ui-monospace, SFMono-Regular, monospace;color:var(--foreground, oklch(0.985 0 0))">PAP-7000</span><span>2 issues</span><span>· 2w ago</span></div><p data-briefs-summary="true" style="margin:0;font-size:13px;line-height:1.45;color:var(--foreground, oklch(0.985 0 0));overflow:hidden;display:-webkit-box;-webkit-box-orient:vertiLine truncated
</div>
</section>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

@@ -60,6 +60,7 @@
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"esbuild": "^0.27.3",
"playwright": "^1.58.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"typescript": "^5.7.3",
@@ -0,0 +1,165 @@
/**
* Renders the Briefing page UI with a curated fixture set and writes
* desktop + mobile + empty-state PNG snapshots into
* `docs/pr-screenshots/pap-9963/`.
*
* Skipped by default — set `BRIEFS_CAPTURE_SCREENSHOTS=1` to opt in.
*
* The flag avoids dragging Playwright into the regular fast unit-test run while
* still letting CI / agents capture deterministic fixtures from the same view
* components the host renders.
*/
import path from "node:path";
import fs from "node:fs/promises";
import { fileURLToPath, pathToFileURL } from "node:url";
import { afterAll, describe, expect, it, vi } from "vitest";
vi.mock("@paperclipai/plugin-sdk/ui", () => {
return {
useHostNavigation: () => ({
resolveHref: (to: string) => to,
navigate: () => {},
linkProps: (to: string) => ({ href: to, onClick: () => {} }),
}),
usePluginAction: () => vi.fn(async () => ({ ok: true })),
usePluginData: () => ({ data: null, loading: false, error: null, refresh: () => {} }),
usePluginToast: () => vi.fn(),
useHostLocation: () => ({ pathname: "/PAP/briefs", search: "", hash: "" }),
usePluginStream: () => ({ events: [], lastEvent: null, connecting: false, connected: false, error: null, close: () => {} }),
};
});
import { renderToStaticMarkup } from "react-dom/server";
import { BriefCardView } from "../../src/ui/app.js";
import { groupCardsIntoSections } from "../../src/ui/view-model.js";
import { gallery } from "./fixtures.js";
const ENABLED = process.env.BRIEFS_CAPTURE_SCREENSHOTS === "1";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const repoRoot = path.resolve(__dirname, "../../../../..");
const outDir = path.resolve(repoRoot, "docs/pr-screenshots/pap-9963");
const desktopWidth = 1440;
const mobileWidth = 390;
function staticPage({ cards, viewportWidth }: { cards: ReturnType<typeof gallery>; viewportWidth: number }) {
const isMobile = viewportWidth < 700;
const sections = groupCardsIntoSections(cards);
const visibleSections = isMobile
? sections.filter((section) => section.cards.length > 0).slice(0, 1).concat(sections.slice(1))
: sections;
const active = cards.filter((c) => !c.hidden);
const pinned = active.filter((c) => c.pinned).length;
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Briefing — ${isMobile ? "mobile" : "desktop"}</title>
<style>
:root {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--border: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--accent: oklch(0.269 0 0);
}
html, body { background: var(--background); color: var(--foreground); margin: 0; min-height: 100vh; }
body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; }
a { color: inherit; }
@media (max-width: 700px) {
[data-briefs-mobile-tabs] { display: flex !important; }
[data-briefs-section] > header { display: none; }
[data-briefs-section][data-mobile-hidden="true"] { display: none; }
[data-briefs-grid] { grid-template-columns: 1fr !important; }
}
</style>
</head>
<body>
<div style="font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; color: var(--foreground); padding: 20px clamp(12px, 4vw, 32px); max-width: 1280px; margin: 0 auto; min-height: 100vh;">
<header style="display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px; margin-bottom: 6px;">
<h1 style="margin: 0; font-size: 22px; font-weight: 600;">Briefing</h1>
<div style="flex: 1; min-width: 0; font-size: 12px; color: var(--muted-foreground);">${active.length} active · ${pinned} pinned · refreshed just now</div>
<div style="display: flex; gap: 6px; align-items: center;">
<button style="padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--card); color: var(--foreground); font-size: 12px;">Preferences</button>
<button style="padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--card); color: var(--foreground); font-size: 12px;">Refresh</button>
</div>
</header>
<p style="margin: 0; margin-bottom: 18px; font-size: 13px; color: var(--muted-foreground);">Durable cards for areas of work that involve you. Pin the ones you always want to see.</p>
${active.length === 0
? `<div data-briefs-empty style="padding: 40px 24px; border: 1px dashed var(--border); border-radius: 10px; text-align: center; color: var(--muted-foreground);">
<div style="font-size: 15px; color: var(--foreground); font-weight: 600; margin-bottom: 4px;">No briefs yet</div>
<div style="font-size: 13px;">Cards appear here once the Briefing Analyst picks up recent work. Pinned cards never expire.</div>
</div>`
: visibleSections.map((section) => section.cards.length === 0 ? "" : `
<section data-briefs-section="${section.key}" style="display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px;">
<header style="display: flex; align-items: center; gap: 10px;">
<h2 style="margin: 0; font-size: 12px; letter-spacing: 0.6px; color: var(--muted-foreground); text-transform: uppercase; font-weight: 600;">${section.label}</h2>
<span style="font-size: 12px; color: var(--muted-foreground);">${section.cards.length}</span>
<span style="flex: 1; border-bottom: 1px dashed var(--border);"></span>
</header>
<div data-briefs-grid style="display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr)); gap: 12px;">
${section.cards.map((card) => renderToStaticMarkup(<BriefCardView card={card} onChanged={() => {}} />)).join("")}
</div>
</section>
`).join("")}
</div>
</body>
</html>`;
}
describe.skipIf(!ENABLED)("Briefs screenshots", () => {
let browser: import("playwright").Browser | null = null;
async function getBrowser() {
if (browser) return browser;
const playwright = await import("playwright");
browser = await playwright.chromium.launch({ headless: true });
return browser;
}
afterAll(async () => {
if (browser) await browser.close();
});
it("captures desktop, mobile, and empty-state Briefing snapshots", async () => {
await fs.mkdir(outDir, { recursive: true });
const cards = gallery();
const desktopHtml = staticPage({ cards, viewportWidth: desktopWidth });
const mobileHtml = staticPage({ cards, viewportWidth: mobileWidth });
const emptyHtml = staticPage({ cards: [], viewportWidth: desktopWidth });
const desktopFile = path.join(outDir, "_briefing-desktop.html");
const mobileFile = path.join(outDir, "_briefing-mobile.html");
const emptyFile = path.join(outDir, "_briefing-empty.html");
await fs.writeFile(desktopFile, desktopHtml);
await fs.writeFile(mobileFile, mobileHtml);
await fs.writeFile(emptyFile, emptyHtml);
const browser = await getBrowser();
await snap(browser, desktopFile, { width: desktopWidth, height: 900 }, path.join(outDir, "briefing-desktop.png"));
await snap(browser, mobileFile, { width: mobileWidth, height: 844 }, path.join(outDir, "briefing-mobile.png"));
await snap(browser, emptyFile, { width: desktopWidth, height: 700 }, path.join(outDir, "briefing-empty.png"));
for (const name of ["briefing-desktop.png", "briefing-mobile.png", "briefing-empty.png"]) {
const stats = await fs.stat(path.join(outDir, name));
expect(stats.size).toBeGreaterThan(1024);
}
}, 60_000);
});
async function snap(browser: import("playwright").Browser, htmlPath: string, viewport: { width: number; height: number }, out: string): Promise<void> {
const context = await browser.newContext({ viewport, colorScheme: "dark" });
const page = await context.newPage();
await page.goto(pathToFileURL(htmlPath).href, { waitUntil: "domcontentloaded" });
await page.waitForTimeout(150);
await page.screenshot({ path: out, fullPage: true });
await context.close();
}
+34
View File
@@ -513,6 +513,40 @@ importers:
specifier: ^3.2.4
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.12.0)(jiti@2.6.1)(jsdom@28.1.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(tsx@4.21.0)
packages/plugins/plugin-briefs:
dependencies:
'@paperclipai/plugin-sdk':
specifier: workspace:*
version: link:../sdk
devDependencies:
'@types/node':
specifier: ^24.6.0
version: 24.12.0
'@types/react':
specifier: ^19.0.8
version: 19.2.14
'@types/react-dom':
specifier: ^19.0.3
version: 19.2.3(@types/react@19.2.14)
esbuild:
specifier: ^0.27.3
version: 0.27.3
playwright:
specifier: ^1.58.2
version: 1.58.2
react:
specifier: ^19.0.0
version: 19.2.4
react-dom:
specifier: ^19.0.0
version: 19.2.4(react@19.2.4)
typescript:
specifier: ^5.7.3
version: 5.9.3
vitest:
specifier: ^3.0.5
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.12.0)(jiti@2.6.1)(jsdom@28.1.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(tsx@4.21.0)
packages/plugins/plugin-llm-wiki:
dependencies:
react: