Group by
{ARTIFACT_GROUP_OPTIONS.map(({ value, label }) => (
onGroupByChange(value)}
className="justify-between"
>
{label}
{groupBy === value ? : null}
))}
{ARTIFACT_KIND_FILTERS.map((filter) => (
))}
);
}
interface MockGroup {
id: string;
groupBy: Exclude;
issueIdentifier: string;
issueTitle: string;
count: number;
preview: CompanyArtifact;
updatedAt: string;
href: string;
}
/**
* Stack card mock for grouped views.
*
* Visual rules (matching ArtifactCard footprint):
* - Same border, radius (rounded-[8px]), border + bg-card surface, hover treatment.
* - Preview frame is the same `aspect-video` PreviewFrame as ArtifactCard.
* - Footer block has the same vertical rhythm.
* - When `count > 1`, render two stacked sibling layers behind the main card,
* offset by 4px / 8px on both axes, with reduced opacity. This produces a
* subtle "stack" without competing with the preview content.
* - Stack count badge sits in the top-right of the preview as a small pill.
* - The body line replaces the artifact title with the issue identifier and
* title; metadata line shows the artifact count and most-recent-edit time.
*/
function ArtifactStackCard({ group }: { group: MockGroup }) {
const isStacked = group.count > 1;
return (
);
},
};
/**
* Grouped by Task — the production default. Every stack is one issue's
* artifacts. Counts > 1 show the subtle stack effect; the lone `count = 1`
* stacks render flat to keep the grid honest about depth.
*/
export const GroupedByTask: Story = {
render: () => {
const [query, setQuery] = useState("");
const [kind, setKind] = useState("all");
const [groupBy, setGroupBy] = useState("task");
return (
{TASK_GROUPS.map((group) => (
))}
);
},
};
/**
* Grouped by Parent task — stacks cluster all descendants under the root
* issue identifier. Same visual contract as task grouping.
*/
export const GroupedByParentTask: Story = {
render: () => {
const [query, setQuery] = useState("");
const [kind, setKind] = useState("all");
const [groupBy, setGroupBy] = useState("parent_task");
return (
{PARENT_TASK_GROUPS.map((group) => (
))}
);
},
};
/**
* Selected stack — drilled into a single issue's artifacts. The header row
* is the back affordance plus the selected-group label; media filter and
* search remain available and still apply within the stack.
*/
export const SelectedStack: Story = {
render: () => {
const [query, setQuery] = useState("");
const [kind, setKind] = useState("all");
const [groupBy, setGroupBy] = useState("task");
return (