From dac22ff8dfd385709048c84ad3f67668902e7a7c Mon Sep 17 00:00:00 2001 From: QPan Date: Tue, 16 Jun 2026 20:33:13 -0400 Subject: [PATCH 1/5] feat(data-app-shell): update breadcrumb labels in stories Replace the demo breadcrumb trail with generic placeholder labels (All Projects / Project Name / worksession name). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../composed/DataAppShell/DataAppShell.stories.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/composed/DataAppShell/DataAppShell.stories.tsx b/src/components/composed/DataAppShell/DataAppShell.stories.tsx index 190ff766..f0deb744 100644 --- a/src/components/composed/DataAppShell/DataAppShell.stories.tsx +++ b/src/components/composed/DataAppShell/DataAppShell.stories.tsx @@ -382,9 +382,8 @@ const htsWorkflowSteps: WorkflowStep[] = [ const htsBreadcrumbs = [ { label: "All Projects", onClick: () => console.log("All Projects") }, - { label: "DUX4", onClick: () => console.log("DUX4") }, - { label: "Primary Screening", onClick: () => console.log("Primary Screening") }, - { label: "Data Overview" }, + { label: "Project Name", onClick: () => console.log("Project Name") }, + { label: "worksession name" }, ]; // ============================================================================= @@ -585,9 +584,8 @@ const InteractiveShell = () => { userMenu={} breadcrumbs={[ { label: "All Projects", onClick: () => setActivePageId("explorer") }, - { label: "DUX4" }, - { label: "Primary Screening" }, - { label: activeStep?.label ?? "Data Overview" }, + { label: "Project Name" }, + { label: "worksession name" }, ]} headerActions={ isProjectPage && ( From 20f19ab3fde2e788a050d8e42439d0150c818adf Mon Sep 17 00:00:00 2001 From: QPan Date: Tue, 16 Jun 2026 20:35:02 -0400 Subject: [PATCH 2/5] feat(data-app-shell): add collapsible nav rail (navRailHidden) Add a `navRailHidden` prop to DataAppShell. When true, the desktop icon nav rail is not rendered, so collapsing the sidebar panel can hide both the rail and the panel, giving the content full width. Wired in the Default story to the panel's collapsed state, with a play-test asserting the rail is hidden when collapsed. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../composed/DataAppShell/DataAppShell.stories.tsx | 7 +++++++ src/components/composed/DataAppShell/DataAppShell.tsx | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/composed/DataAppShell/DataAppShell.stories.tsx b/src/components/composed/DataAppShell/DataAppShell.stories.tsx index f0deb744..2e0ab367 100644 --- a/src/components/composed/DataAppShell/DataAppShell.stories.tsx +++ b/src/components/composed/DataAppShell/DataAppShell.stories.tsx @@ -435,6 +435,7 @@ const DefaultShell = ({ initialCollapsed = false }: { initialCollapsed?: boolean } + navRailHidden={collapsed} sidebarPanel={} >
@@ -488,6 +489,12 @@ export const CollapsedWorkflow: Story = { expect(canvas.queryByText("Global Filtering")).not.toBeInTheDocument(); expect(canvas.queryByText("Workflow")).not.toBeInTheDocument(); }); + + await step("Collapsed workflow — app nav rail is hidden", async () => { + expect( + canvasElement.querySelector("[data-slot='data-app-sidebar-rail']") + ).not.toBeInTheDocument(); + }); }, parameters: { zephyr: { testCaseId: "SW-T4666" }, diff --git a/src/components/composed/DataAppShell/DataAppShell.tsx b/src/components/composed/DataAppShell/DataAppShell.tsx index a9714f64..34373b5b 100644 --- a/src/components/composed/DataAppShell/DataAppShell.tsx +++ b/src/components/composed/DataAppShell/DataAppShell.tsx @@ -104,6 +104,8 @@ export interface DataAppShellProps { // -- Shell -- /** Slot rendered between the icon rail and the content (e.g. WorkflowPanel) */ sidebarPanel?: React.ReactNode; + /** Hide the desktop icon nav rail (e.g. when the sidebar panel is collapsed) */ + navRailHidden?: boolean; /** Main content area */ children: React.ReactNode; /** Additional className for the root container */ @@ -492,6 +494,7 @@ function DataAppShell({ onHelpClick, headerActions, sidebarPanel, + navRailHidden = false, children, className, }: DataAppShellProps) { @@ -515,8 +518,8 @@ function DataAppShell({ data-slot="data-app-shell" className={cn("flex flex-row w-full h-screen overflow-hidden", className)} > - {/* Desktop icon rail (hidden on mobile) */} - + {/* Desktop icon rail (hidden on mobile, or when navRailHidden) */} + {!navRailHidden && } {/* Mobile sidebar Sheet */} Date: Tue, 16 Jun 2026 20:36:35 -0400 Subject: [PATCH 3/5] feat(data-app-shell): refine side nav rail + workflow step styling Side nav rail (component): - Narrow rail 60px -> 48px; icon-only nav buttons (label moved to aria-label + tooltip); button box 36px -> 30px; glyphs 20px -> 16px. - Remove divider under the app logo; nudge logo up 4px. - Unify breadcrumb text size to text-sm. Workflow step panel (story demo): - Rename demo steps to generic "Step N Name"; drop per-step counts. - Step card height -> 40px; step name uses the text-title-sm token; inactive steps lightened (font-light). - Remove the INPUT/OUTPUT data-count pills and dead helpers. - Add an outline Back button next to Next from step 2 onward. Play-tests updated to query nav buttons by accessible name and the new labels/sizes. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../DataAppShell/DataAppShell.stories.tsx | 159 ++++-------------- .../composed/DataAppShell/DataAppShell.tsx | 32 ++-- 2 files changed, 48 insertions(+), 143 deletions(-) diff --git a/src/components/composed/DataAppShell/DataAppShell.stories.tsx b/src/components/composed/DataAppShell/DataAppShell.stories.tsx index 2e0ab367..f1fc0ffb 100644 --- a/src/components/composed/DataAppShell/DataAppShell.stories.tsx +++ b/src/components/composed/DataAppShell/DataAppShell.stories.tsx @@ -85,7 +85,7 @@ function UserMenuButton({ name, userRole, expanded = false }: UserMenuButtonProp ) : ( ); @@ -282,62 +266,6 @@ function WorkflowPanel({ ); } -// ── Data count pills ───────────────────────────────────────────────────────── - -interface DataCount { - label: string; - count: number; - variant?: "default" | "outline" | "primary"; - onClick?: () => void; -} - -const countPillVariants = cva( - "inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium tabular-nums transition-colors", - { - variants: { - variant: { - default: "bg-muted text-muted-foreground", - outline: "bg-transparent border border-border text-foreground", - primary: "bg-primary/10 border border-primary/30 text-primary", - }, - clickable: { - true: "cursor-pointer hover:bg-primary/15", - false: "cursor-default", - }, - }, - defaultVariants: { variant: "default", clickable: false }, - }, -); - -function DataCountPills({ dataCounts }: { dataCounts: DataCount[] }) { - if (dataCounts.length === 0) return null; - return ( -
- {dataCounts.map((dc, i) => { - const pillClass = cn(countPillVariants({ variant: dc.variant ?? "outline", clickable: !!dc.onClick })); - const pillContent = ( - <> - {dc.label} - {dc.count.toLocaleString()} - - ); - return ( - - {i > 0 && {"\u2192"}} - {dc.onClick ? ( - - ) : ( -
{pillContent}
- )} -
- ); - })} -
- ); -} - // ============================================================================= // Meta // ============================================================================= @@ -368,16 +296,14 @@ const htsNavGroups: NavGroup[] = [ const htsWorkflowSteps: WorkflowStep[] = [ { id: "data-overview", - label: "Data Overview", + label: "Step 1 Name", icon: LayoutGrid, isActive: true, - inputCount: 649568, - outputCount: 645396, }, - { id: "global-filtering", label: "Global Filtering", icon: Filter, inputCount: 645396, outputCount: 4803 }, - { id: "explore-clusters", label: "Explore Clusters", icon: Library, inputCount: 3917, outputCount: 20 }, - { id: "review-compound", label: "Review Selection", icon: Search, inputCount: 20, outputCount: 15 }, - { id: "export-list", label: "Export Primary List", icon: Download, inputCount: 15 }, + { id: "global-filtering", label: "Step 2 Name", icon: Filter }, + { id: "explore-clusters", label: "Step 3 Name", icon: Library }, + { id: "review-compound", label: "Step 4 Name", icon: Search }, + { id: "export-list", label: "Step 5 Name", icon: Download }, ]; const htsBreadcrumbs = [ @@ -400,17 +326,9 @@ const DefaultShell = ({ initialCollapsed = false }: { initialCollapsed?: boolean onClick: () => setActiveStepId(s.id), })); - const activeStep = steps.find((s) => s.isActive); const activeStepIndex = steps.findIndex((s) => s.isActive); const isLastStep = activeStepIndex === steps.length - 1; - - const dataCounts: DataCount[] = - activeStep?.inputCount == null || activeStep?.outputCount == null - ? [] - : [ - { label: "INPUT", count: activeStep.inputCount, variant: "outline" }, - { label: "Output", count: activeStep.outputCount, variant: "primary" }, - ]; + const isFirstStep = activeStepIndex <= 0; return ( - + {!isFirstStep && ( + + )} ) @@ -627,7 +542,7 @@ export const Interactive: Story = { await step("Interactive shell renders", async () => { expect(canvas.getByText("HTS")).toBeInTheDocument(); - expect(canvas.getAllByText("Data Overview").length).toBeGreaterThan(0); + expect(canvas.getAllByText("Step 1 Name").length).toBeGreaterThan(0); }); }, parameters: { @@ -916,8 +831,8 @@ export const MultipleNavGroups: Story = { const canvas = within(canvasElement); await step("All pages from both groups are visible", async () => { - expect(canvas.getByText("Project")).toBeInTheDocument(); - expect(canvas.getByText("Explorer")).toBeInTheDocument(); + expect(canvas.getByRole("button", { name: "Project" })).toBeInTheDocument(); + expect(canvas.getByRole("button", { name: "Explorer" })).toBeInTheDocument(); expect(canvas.getAllByText("Filters").length).toBeGreaterThan(0); }); @@ -1228,16 +1143,16 @@ export const CompactProperty: Story = { await step("Compact icon rail renders on desktop (hidden on mobile)", async () => { const rail = canvasElement.querySelector("[data-slot='data-app-sidebar-rail']"); expect(rail).toBeInTheDocument(); - // Icon rail should have width of 60px - expect(rail).toHaveClass("w-[60px]"); + // Icon rail should have width of 48px + expect(rail).toHaveClass("w-12"); }); - await step("Icon rail displays icons and labels stacked vertically", async () => { + await step("Icon rail displays icon-only nav buttons labelled via aria-label", async () => { const rail = canvasElement.querySelector("[data-slot='data-app-sidebar-rail']"); - // Labels should be present - expect(within(rail!).getByText("Project")).toBeInTheDocument(); - expect(within(rail!).getByText("Explorer")).toBeInTheDocument(); - expect(within(rail!).getByText("Filters")).toBeInTheDocument(); + // Labels are exposed as accessible names (aria-label), not visible text + expect(within(rail!).getByRole("button", { name: "Project" })).toBeInTheDocument(); + expect(within(rail!).getByRole("button", { name: "Explorer" })).toBeInTheDocument(); + expect(within(rail!).getByRole("button", { name: "Filters" })).toBeInTheDocument(); }); await step("Group labels are hidden in compact icon rail", async () => { @@ -1249,7 +1164,7 @@ export const CompactProperty: Story = { await step("Active page has primary background highlight in compact mode", async () => { const rail = canvasElement.querySelector("[data-slot='data-app-sidebar-rail']"); - const projectBtn = within(rail!).getByText("Project").closest("button"); + const projectBtn = within(rail!).getByRole("button", { name: "Project" }); const iconDiv = projectBtn?.querySelector(".bg-primary\\/10"); expect(iconDiv).toBeInTheDocument(); }); diff --git a/src/components/composed/DataAppShell/DataAppShell.tsx b/src/components/composed/DataAppShell/DataAppShell.tsx index 34373b5b..748c6dd5 100644 --- a/src/components/composed/DataAppShell/DataAppShell.tsx +++ b/src/components/composed/DataAppShell/DataAppShell.tsx @@ -130,7 +130,7 @@ interface SidebarBodyProps | "userMenu" > { /** - * compact=true → narrow icon rail (60px): icons + tiny labels stacked, tooltips on hover + * compact=true → narrow icon rail (48px): icon-only nav buttons, tooltips on hover * compact=false → expanded mobile sheet (220px): icon + label side-by-side rows */ compact: boolean; @@ -147,7 +147,7 @@ const pageIconVariants = cva( false: "bg-transparent", }, compact: { - true: "w-9 h-9 hover:bg-muted", + true: "w-[30px] h-[30px] hover:bg-muted", false: "w-8 h-8", }, }, @@ -173,8 +173,8 @@ function SidebarBody({ {/* ── Header: app icon / name + version ──────────────────────────────── */}
@@ -189,8 +189,7 @@ function SidebarBody({ {/* Icon */} {appIcon ?? appName} @@ -291,7 +290,7 @@ function SidebarBody({ const iconEl = Icon ? ( @@ -315,22 +314,13 @@ function SidebarBody({ {page.label} @@ -387,7 +377,7 @@ function IconRailSidebar(props: Omit @@ -434,13 +424,13 @@ function TopNav({ ) : isClickable && item.onClick ? ( ) : ( - {item.label} + {item.label} )} From 62fb4b755a377b2367d670645c6563309edf4171 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 22:27:30 +0000 Subject: [PATCH 4/5] Initial plan From c5f20a33842b6ab89b33bc83f6d5a0ed74fb2afe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 22:34:47 +0000 Subject: [PATCH 5/5] fix(data-app-shell): fix 3 stale Storybook E2E test assertions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit a84f94b narrowed the sidebar rail (60px→48px), made nav buttons icon-only (labels moved to aria-label + tooltip), and removed count pills from WorkflowPanel — but left 3 play-test assertions stale: - WorkflowPanelInteractions: remove getByText("1K") — counts no longer rendered - MultipleNavGroups: use getByRole("button", {name:"Filters"}) instead of getByText("Filters") — nav items are icon-only with aria-label - CompactProperty: change expected rail width "60px" → "48px" --- .../composed/DataAppShell/DataAppShell.stories.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/composed/DataAppShell/DataAppShell.stories.tsx b/src/components/composed/DataAppShell/DataAppShell.stories.tsx index f1fc0ffb..7ab95557 100644 --- a/src/components/composed/DataAppShell/DataAppShell.stories.tsx +++ b/src/components/composed/DataAppShell/DataAppShell.stories.tsx @@ -751,11 +751,10 @@ export const WorkflowPanelInteractions: Story = { play: async ({ canvasElement, step }) => { const canvas = within(canvasElement); - await step("Expanded panel shows step labels and counts", async () => { + await step("Expanded panel shows step labels", async () => { expect(canvas.getByText("Step Alpha")).toBeInTheDocument(); expect(canvas.getByText("Step Beta")).toBeInTheDocument(); expect(canvas.getByText("Disabled")).toBeInTheDocument(); - expect(canvas.getByText("1K")).toBeInTheDocument(); }); await step("Clicking Step Beta makes it the active step", async () => { @@ -833,7 +832,7 @@ export const MultipleNavGroups: Story = { await step("All pages from both groups are visible", async () => { expect(canvas.getByRole("button", { name: "Project" })).toBeInTheDocument(); expect(canvas.getByRole("button", { name: "Explorer" })).toBeInTheDocument(); - expect(canvas.getAllByText("Filters").length).toBeGreaterThan(0); + expect(canvas.getByRole("button", { name: "Filters" })).toBeInTheDocument(); }); await step("A separator divides the two groups in the icon rail", async () => { @@ -846,7 +845,7 @@ export const MultipleNavGroups: Story = { await step("Active page icon has primary highlight", async () => { // Filters page is isActive — its icon container has bg-primary/10 const rail = canvasElement.querySelector("[data-slot='data-app-sidebar-rail']"); - const activePage = within(rail!).getByText("Filters").closest("button"); + const activePage = within(rail!).getByRole("button", { name: "Filters" }); const iconContainer = activePage?.querySelector(".bg-primary\\/10"); expect(iconContainer).toBeInTheDocument(); }); @@ -1183,7 +1182,7 @@ export const CompactProperty: Story = { // Icon rail has md:flex which means it's hidden on mobile expect(rail).toHaveClass("hidden", "md:flex"); const railStyles = window.getComputedStyle(rail!); - expect(railStyles.width).toBe("60px"); + expect(railStyles.width).toBe("48px"); }); await step("User menu is visible at bottom of icon rail", async () => {