From 733ae166f9444d6174363da256932cd79aee0dc8 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 24 Apr 2026 11:42:03 -0500 Subject: [PATCH] Add sub-issues workflow visual review harness. Provide a dedicated Storybook lab fixture and screenshot script so checklist polish can be validated across required viewport and theme combinations for UX sign-off. Co-Authored-By: Paperclip --- scripts/screenshot-subissues.mjs | 35 +++ .../stories/sub-issues-workflow.stories.tsx | 281 ++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 scripts/screenshot-subissues.mjs create mode 100644 ui/storybook/stories/sub-issues-workflow.stories.tsx diff --git a/scripts/screenshot-subissues.mjs b/scripts/screenshot-subissues.mjs new file mode 100644 index 00000000000..78acad66d7f --- /dev/null +++ b/scripts/screenshot-subissues.mjs @@ -0,0 +1,35 @@ +import { chromium } from "@playwright/test"; +import { mkdir } from "node:fs/promises"; +import { argv } from "node:process"; + +const outDir = argv[2] ?? "/tmp/claude/pap-2202-screens"; +const baseUrl = argv[3] ?? "http://localhost:6006"; +await mkdir(outDir, { recursive: true }); + +const id = "ux-labs-sub-issues-workflow-checklist--default"; +const runs = [ + { name: "desktop-1440x900-dark", w: 1440, h: 900, theme: "dark" }, + { name: "desktop-1440x900-light", w: 1440, h: 900, theme: "light" }, + { name: "mobile-390x844-dark", w: 390, h: 844, theme: "dark" }, + { name: "mobile-390x844-light", w: 390, h: 844, theme: "light" }, +]; + +const browser = await chromium.launch(); +try { + for (const run of runs) { + const url = `${baseUrl}/iframe.html?id=${id}&viewMode=story&globals=theme:${run.theme}`; + const context = await browser.newContext({ + viewport: { width: run.w, height: run.h }, + deviceScaleFactor: 2, + }); + const page = await context.newPage(); + await page.goto(url, { waitUntil: "networkidle", timeout: 30_000 }); + await page.waitForTimeout(1200); + const file = `${outDir}/${run.name}.png`; + await page.screenshot({ path: file, fullPage: true }); + console.log("wrote", file); + await context.close(); + } +} finally { + await browser.close(); +} diff --git a/ui/storybook/stories/sub-issues-workflow.stories.tsx b/ui/storybook/stories/sub-issues-workflow.stories.tsx new file mode 100644 index 00000000000..a15a66bb282 --- /dev/null +++ b/ui/storybook/stories/sub-issues-workflow.stories.tsx @@ -0,0 +1,281 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import type { Issue } from "@paperclipai/shared"; +import { useQueryClient } from "@tanstack/react-query"; +import { IssuesList } from "@/components/IssuesList"; +import { queryKeys } from "@/lib/queryKeys"; +import { + createIssue, + storybookAgents, + storybookAuthSession, + storybookCompanies, + storybookIssueLabels, + storybookProjects, +} from "../fixtures/paperclipData"; + +const companyId = "company-storybook"; +const parentId = "issue-pap-1953"; + +type BlockerRef = NonNullable[number]; + +function child(overrides: Partial): Issue { + return createIssue({ + parentId, + projectId: storybookProjects[0]!.id, + projectWorkspaceId: storybookProjects[0]!.workspaces[0]?.id ?? null, + goalId: null, + blockedBy: [], + blocks: [], + labelIds: [], + labels: [], + ...overrides, + }); +} + +const blockerRef = (issue: Issue): BlockerRef => ({ + id: issue.id, + identifier: issue.identifier, + title: issue.title, + status: issue.status, + priority: issue.priority, + assigneeAgentId: issue.assigneeAgentId, + assigneeUserId: issue.assigneeUserId, +}); + +const baseCreatedAt = new Date("2026-04-10T12:00:00.000Z").getTime(); +const createdAt = (offsetMinutes: number) => + new Date(baseCreatedAt + offsetMinutes * 60_000).toISOString(); + +// Mirrors the PAP-1953 topology called out in the PAP-2189 plan: +// 1954 Scoping (done) — root +// 1955 Security scoping (done) — root +// 1960 Phase 1 (done) → 1961 Phase 2 (done) +// 1962 Phase 3 (done) → 1963 Phase 4 (done) +// → 1964 Phase 5 (in_progress) +// → 1965 Phase 6 (blocked) +// → 1966 Phase 7 (blocked) + +const scoping = child({ + id: "issue-pap-1954", + identifier: "PAP-1954", + issueNumber: 1954, + title: "Scoping review", + status: "done", + priority: "medium", + completedAt: createdAt(120), + createdAt: createdAt(0), +}); + +const security = child({ + id: "issue-pap-1955", + identifier: "PAP-1955", + issueNumber: 1955, + title: "Security scoping", + status: "done", + priority: "medium", + completedAt: createdAt(180), + createdAt: createdAt(10), +}); + +const phase1 = child({ + id: "issue-pap-1960", + identifier: "PAP-1960", + issueNumber: 1960, + title: "Phase 1 — groundwork", + status: "done", + priority: "medium", + completedAt: createdAt(600), + createdAt: createdAt(20), +}); + +const phase2 = child({ + id: "issue-pap-1961", + identifier: "PAP-1961", + issueNumber: 1961, + title: "Phase 2 — integration", + status: "done", + priority: "medium", + completedAt: createdAt(720), + createdAt: createdAt(30), + blockedBy: [blockerRef(phase1)], +}); + +const phase3 = child({ + id: "issue-pap-1962", + identifier: "PAP-1962", + issueNumber: 1962, + title: "Phase 3 — data model", + status: "done", + priority: "medium", + completedAt: createdAt(800), + createdAt: createdAt(40), +}); + +const phase4 = child({ + id: "issue-pap-1963", + identifier: "PAP-1963", + issueNumber: 1963, + title: "Phase 4 — API surface", + status: "done", + priority: "medium", + completedAt: createdAt(900), + createdAt: createdAt(50), + blockedBy: [blockerRef(phase3)], +}); + +const phase5 = child({ + id: "issue-pap-1964", + identifier: "PAP-1964", + issueNumber: 1964, + title: "Phase 5 — UI polish", + status: "in_progress", + priority: "high", + createdAt: createdAt(60), + blockedBy: [blockerRef(phase4)], +}); + +const phase6 = child({ + id: "issue-pap-1965", + identifier: "PAP-1965", + issueNumber: 1965, + title: "Phase 6 — telemetry wiring", + status: "blocked", + priority: "medium", + createdAt: createdAt(70), + blockedBy: [blockerRef(phase5)], +}); + +const phase7 = child({ + id: "issue-pap-1966", + identifier: "PAP-1966", + issueNumber: 1966, + title: "Phase 7 — rollout", + status: "blocked", + priority: "medium", + createdAt: createdAt(80), + blockedBy: [blockerRef(phase6)], +}); + +const subIssues: Issue[] = [ + scoping, + security, + phase1, + phase2, + phase3, + phase4, + phase5, + phase6, + phase7, +]; + +const viewStateKey = "storybook:sub-issues-workflow:list"; +const scopedKey = `${viewStateKey}:${companyId}`; + +function hydrateQueries(client: ReturnType) { + client.setQueryData(queryKeys.companies.all, storybookCompanies); + client.setQueryData(queryKeys.auth.session, storybookAuthSession); + client.setQueryData(queryKeys.agents.list(companyId), storybookAgents); + client.setQueryData(queryKeys.projects.list(companyId), storybookProjects); + client.setQueryData(queryKeys.issues.labels(companyId), storybookIssueLabels); + client.setQueryData(queryKeys.issues.list(companyId), subIssues); + client.setQueryData(queryKeys.access.companyUserDirectory(companyId), { + users: [ + { + principalId: "user-board", + status: "active", + user: { + id: "user-board", + email: "riley@paperclip.local", + name: "Riley Board", + image: null, + }, + }, + ], + }); + client.setQueryData(queryKeys.instance.experimentalSettings, { + enableIsolatedWorkspaces: true, + enableRoutineTriggers: true, + }); +} + +function Hydrated({ children }: { children: React.ReactNode }) { + const queryClient = useQueryClient(); + const [ready] = useState(() => { + hydrateQueries(queryClient); + if (typeof window !== "undefined") { + window.localStorage.removeItem(scopedKey); + window.localStorage.removeItem(`${scopedKey}:issue-columns`); + } + return true; + }); + return ready ? children : null; +} + +function SubIssuesWorkflowPanel() { + return ( +
+
+
+
+
Issue Detail · Sub-issues
+

+ Workflow-sorted sub-issues with checklist affordances +

+

+ Fixture mirrors the PAP-1953 topology called out in the PAP-2189 + plan: two standalone scoping items, a Phase 1→2 pair, and a long + Phase 3→4→5→6→7 chain. The panel renders with + + defaultSortField="workflow" + + and + + showProgressSummary + + so reviewers see the full checklist surface in isolation. +

+
+
+ undefined} + createIssueLabel="Sub-issue" + /> +
+
+
+
+ ); +} + +const meta = { + title: "UX Labs/Sub-issues Workflow Checklist", + component: SubIssuesWorkflowPanel, + parameters: { + layout: "fullscreen", + docs: { + description: { + component: + "Review surface for the PAP-2189 checklist-style sub-issues work. Renders the IssuesList component with the Sub-issues panel props so the progress strip, workflow sort, step gutter, current marker, done de-emphasis, and blocker chips are all visible against a PAP-1953-like topology.", + }, + }, + }, + decorators: [ + (StoryRender) => ( + + + + ), + ], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {};