diff --git a/docs/pr-screenshots/pap-9963/_briefing-desktop.html b/docs/pr-screenshots/pap-9963/_briefing-desktop.html new file mode 100644 index 00000000000..3f3b5dd4d17 --- /dev/null +++ b/docs/pr-screenshots/pap-9963/_briefing-desktop.html @@ -0,0 +1,76 @@ + + + + + Briefing — desktop + + + +
+
+

Briefing

+
8 active · 2 pinned · refreshed just now
+
+ + +
+
+

Durable cards for areas of work that involve you. Pin the ones you always want to see.

+ +
+
+

Needs your attention

+ 3 + +
+
+ +
+
+ +
+
+

Live and in review

+ 3 + +
+
+

Release readiness

In review
PAP-94102 issues· in 4h

Release notes drafted; CTO review queued. CI is green and QA validation is done.

+
+
+ +
+
+

Recently done & stale

+ 2 + +
+
+

GA migration spike

Stale
PAP-70002 issues· 2w ago

Spike paused 11 days; reviewer paged but no follow-up activity.

+
+
+ +
+ + \ No newline at end of file diff --git a/docs/pr-screenshots/pap-9963/_briefing-empty.html b/docs/pr-screenshots/pap-9963/_briefing-empty.html new file mode 100644 index 00000000000..47d86bb67db --- /dev/null +++ b/docs/pr-screenshots/pap-9963/_briefing-empty.html @@ -0,0 +1,46 @@ + + + + + Briefing — desktop + + + +
+
+

Briefing

+
0 active · 0 pinned · refreshed just now
+
+ + +
+
+

Durable cards for areas of work that involve you. Pin the ones you always want to see.

+
+
No briefs yet
+
Cards appear here once the Briefing Analyst picks up recent work. Pinned cards never expire.
+
+
+ + \ No newline at end of file diff --git a/docs/pr-screenshots/pap-9963/_briefing-mobile.html b/docs/pr-screenshots/pap-9963/_briefing-mobile.html new file mode 100644 index 00000000000..78f0e22e617 --- /dev/null +++ b/docs/pr-screenshots/pap-9963/_briefing-mobile.html @@ -0,0 +1,76 @@ + + + + + Briefing — mobile + + + +
+
+

Briefing

+
8 active · 2 pinned · refreshed just now
+
+ + +
+
+

Durable cards for areas of work that involve you. Pin the ones you always want to see.

+ +
+
+

Needs your attention

+ 3 + +
+
+ +
+
+ +
+
+

Live and in review

+ 3 + +
+
+

Release readiness

In review
PAP-94102 issues· in 4h

Release notes drafted; CTO review queued. CI is green and QA validation is done.

+
+
+ +
+
+

Recently done & stale

+ 2 + +
+
+

GA migration spike

Stale
PAP-70002 issues· 2w ago

Spike paused 11 days; reviewer paged but no follow-up activity.

+
+
+ +
+ + \ No newline at end of file diff --git a/docs/pr-screenshots/pap-9963/briefing-desktop.png b/docs/pr-screenshots/pap-9963/briefing-desktop.png new file mode 100644 index 00000000000..3b35cc16615 Binary files /dev/null and b/docs/pr-screenshots/pap-9963/briefing-desktop.png differ diff --git a/docs/pr-screenshots/pap-9963/briefing-empty.png b/docs/pr-screenshots/pap-9963/briefing-empty.png new file mode 100644 index 00000000000..0b1e30b2fbb Binary files /dev/null and b/docs/pr-screenshots/pap-9963/briefing-empty.png differ diff --git a/docs/pr-screenshots/pap-9963/briefing-mobile.png b/docs/pr-screenshots/pap-9963/briefing-mobile.png new file mode 100644 index 00000000000..775e8e8e02b Binary files /dev/null and b/docs/pr-screenshots/pap-9963/briefing-mobile.png differ diff --git a/packages/plugins/plugin-briefs/package.json b/packages/plugins/plugin-briefs/package.json index 156de50f4f4..a0089c07cac 100644 --- a/packages/plugins/plugin-briefs/package.json +++ b/packages/plugins/plugin-briefs/package.json @@ -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", diff --git a/packages/plugins/plugin-briefs/tests/ui/screenshots.spec.tsx b/packages/plugins/plugin-briefs/tests/ui/screenshots.spec.tsx new file mode 100644 index 00000000000..08fd26bb476 --- /dev/null +++ b/packages/plugins/plugin-briefs/tests/ui/screenshots.spec.tsx @@ -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; 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 ` + + + + Briefing — ${isMobile ? "mobile" : "desktop"} + + + +
+
+

Briefing

+
${active.length} active · ${pinned} pinned · refreshed just now
+
+ + +
+
+

Durable cards for areas of work that involve you. Pin the ones you always want to see.

+ ${active.length === 0 + ? `
+
No briefs yet
+
Cards appear here once the Briefing Analyst picks up recent work. Pinned cards never expire.
+
` + : visibleSections.map((section) => section.cards.length === 0 ? "" : ` +
+
+

${section.label}

+ ${section.cards.length} + +
+
+ ${section.cards.map((card) => renderToStaticMarkup( {}} />)).join("")} +
+
+ `).join("")} +
+ + `; +} + +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 { + 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(); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27725ab082e..7fccd27fc1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: