- {visible.map((img, i) => (
+ {visible.map((img, index) => (
})
))}
@@ -515,6 +523,63 @@ function ThumbnailStack({ images }: { images: AttentionDetailImage[] }) {
);
}
+/**
+ * Larger image gallery shown when a row is expanded (PAP-13544). Shows the
+ * first three screenshots at a readable size; if more exist, an "n more" tile
+ * links through to the issue where the full set lives.
+ */
+function ExpandedImages({ images, issueHref }: { images: AttentionDetailImage[]; issueHref: string | null }) {
+ const visible = images.slice(0, 3);
+ const extra = images.length - visible.length;
+ return (
+
+ {visible.map((img, index) => {
+ const src = attentionImageUrl(img.assetId);
+ const key = `${img.assetId}-${index}`;
+ const image = (
+

+ );
+ return issueHref ? (
+
e.stopPropagation()}
+ >
+ {image}
+
+ ) : (
+
+ {image}
+
+ );
+ })}
+ {extra > 0 && (issueHref ? (
+
e.stopPropagation()}
+ className="flex h-32 w-24 flex-col items-center justify-center rounded-md border border-dashed border-border bg-muted/40 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-ring focus-visible:ring-(length:--rad-3) focus-visible:outline-none"
+ >
+
{extra} more
+
+ View issue
+
+
+
+ ) : (
+
+ {extra} more
+
+ ))}
+
+ );
+}
+
/** Snooze submenu: presets + a custom date-time (plan §6). */
function SnoozeSubmenu({ onSnooze }: { onSnooze: (snoozedUntil: string) => void }) {
const [customValue, setCustomValue] = useState("");
diff --git a/ui/storybook/stories/what-needs-me.stories.tsx b/ui/storybook/stories/what-needs-me.stories.tsx
index 05bc618b14..81745ddbb0 100644
--- a/ui/storybook/stories/what-needs-me.stories.tsx
+++ b/ui/storybook/stories/what-needs-me.stories.tsx
@@ -279,6 +279,38 @@ const SHOWCASE: AttentionItem[] = [
},
];
+// Image variations (PAP-13544): a row with images can be expanded by clicking
+// its thumbnails; when expanded it shows the first three larger plus an
+// "n more" link to the issue. These rows exercise every image count boundary.
+const IMAGE_ROWS: AttentionItem[] = [
+ {
+ ...item("img-review", "review", "medium", "PR ready for review: attention feed endpoint", "In-review issue is waiting on a human reviewer.", {
+ inlineResolvable: false,
+ project: { id: "proj-beta", name: "Beta", urlKey: "beta", color: "#7c3aed", icon: "layers" },
+ }),
+ activityAt: new Date(NOW - 30 * 60 * 1000).toISOString(),
+ detail: { kind: "generic", summaryExcerpt: "5 files changed · +212 / −41", images: [IMAGES[0], IMAGES[1], IMAGES[2], IMAGES[3], IMAGES[0]] },
+ },
+ {
+ ...item("img-questions", "issue_thread_interaction", "medium", "Answer 2 questions on rollout", "Questions need answers.", {
+ inlineResolvable: true,
+ subject: { kind: "interaction", id: "intx-img", companyId, title: "Answer 2 questions on rollout", identifier: null, status: "pending", href: "/PAP/issues/PAP-1000#qs", metadata: { kind: "ask_user_questions", issueId: "issue-1000" } },
+ decisionVerbs: [{ id: "respond", label: "Answer", description: null }],
+ project: { id: "proj-alpha", name: "Alpha", urlKey: "alpha", color: "#0f766e", icon: "rocket" },
+ }),
+ activityAt: new Date(NOW - 90 * 60 * 1000).toISOString(),
+ detail: { kind: "questions", questionCount: 2, firstQuestionText: "Which auth provider should we standardize on?", images: [IMAGES[0], IMAGES[2], IMAGES[3]] },
+ },
+ {
+ ...item("img-failed", "failed_run", "high", "Deploy pipeline failed after 3 retries", "Retries exhausted.", {
+ inlineResolvable: false,
+ relatedIssue: null,
+ }),
+ activityAt: new Date(NOW - 3 * HOUR).toISOString(),
+ detail: { kind: "failed_run", agentName: "Deployer", failureReasonExcerpt: "exit code 1 running migrate", images: [IMAGES[3]] },
+ },
+];
+
const SNOOZED: AttentionItem[] = [
{
...item("snz-1", "review", "medium", "Design review: settings redesign", "Snoozed until this afternoon."),
@@ -314,6 +346,7 @@ function Queue({
snoozed = [],
dismissed = [],
openCurtains = false,
+ initialExpandedId,
}: {
items: AttentionItem[];
groupBy?: AttentionGroupBy;
@@ -321,9 +354,11 @@ function Queue({
snoozed?: AttentionItem[];
dismissed?: AttentionItem[];
openCurtains?: boolean;
+ /** Pre-expand a specific row (e.g. to show the larger image gallery). */
+ initialExpandedId?: string;
}) {
const firstInline = items.find((i) => i.inlineResolvable && (i.sourceKind === "approval" || i.sourceKind === "join_request"));
- const [expandedId, setExpandedId] = useState
(firstInline?.id ?? null);
+ const [expandedId, setExpandedId] = useState(initialExpandedId ?? firstInline?.id ?? null);
const [cleared, setCleared] = useState>(new Set());
const visible = items.filter((i) => !cleared.has(i.id));
@@ -472,6 +507,25 @@ export const TypeColorsAndDetail: Story = {
args: { items: SHOWCASE, groupBy: "type" },
};
+/**
+ * Collapsed image rows (PAP-13544). Each row shows up to three small
+ * thumbnails plus a "+n" chip; clicking the thumbnails expands the row. Covers
+ * a 5-image review row, a 3-image (no "+n") questions row, and a single-image
+ * failed-run row.
+ */
+export const ImageThumbnails: Story = {
+ args: { items: IMAGE_ROWS },
+};
+
+/**
+ * An expanded image row (PAP-13544). The 5-image review row is pre-expanded so
+ * the first three screenshots render larger with a "2 more" tile that links to
+ * the issue.
+ */
+export const ImageGalleryExpanded: Story = {
+ args: { items: IMAGE_ROWS, initialExpandedId: "img-review" },
+};
+
/** The ~8s undo toast shown after dismissing a row (plan §6). */
function DismissUndoDemo() {
const { pushToast } = useToastActions();