-
diff --git a/ui/src/components/SidebarShell.test.tsx b/ui/src/components/SidebarShell.test.tsx
index 264ecc1fac8..edfb77b67fb 100644
--- a/ui/src/components/SidebarShell.test.tsx
+++ b/ui/src/components/SidebarShell.test.tsx
@@ -167,7 +167,7 @@ describe("SidebarShell", () => {
expect(panel().className).toContain("z-30");
});
- it("drops the width transition for reduced-motion users", () => {
+ it("opens and closes instantly with no width transition (PAP-10676)", () => {
act(() => {
root.render(
@@ -176,7 +176,9 @@ describe("SidebarShell", () => {
);
});
- expect(panel().className).toContain("transition-[width]");
- expect(panel().className).toContain("motion-reduce:transition-none");
+ // Open/close must be instant: the panel never animates its width, so neither
+ // the transition nor its reduced-motion fallback should be present.
+ expect(panel().className).not.toContain("transition-[width]");
+ expect(panel().className).not.toContain("motion-reduce:transition-none");
});
});
diff --git a/ui/src/components/SidebarShell.tsx b/ui/src/components/SidebarShell.tsx
index 41db0fc31a8..a964aa19d86 100644
--- a/ui/src/components/SidebarShell.tsx
+++ b/ui/src/components/SidebarShell.tsx
@@ -79,8 +79,12 @@ type SidebarShellProps = {
* is wider than the reserved spacer.
*
* The icon-alignment guarantee comes for free: collapsing is a pure width
- * animation with `overflow-hidden` clipping the labels; item padding/icon markup
+ * change with `overflow-hidden` clipping the labels; item padding/icon markup
* are never touched, so the left-aligned icon stays pixel-identical.
+ *
+ * Opening/closing is intentionally instant (no width transition): the pin toggle
+ * and peek snap between rail and expanded widths so the content never appears to
+ * slide. Resizing the drag handle is likewise direct.
*/
export function SidebarShell({
children,
@@ -193,8 +197,8 @@ export function SidebarShell({
{
return typeof value === "object" && value !== null && !Array.isArray(value)
? value as Record
diff --git a/ui/storybook/stories/navigation-layout.stories.tsx b/ui/storybook/stories/navigation-layout.stories.tsx
index 71989c0b108..3b9ac4c0985 100644
--- a/ui/storybook/stories/navigation-layout.stories.tsx
+++ b/ui/storybook/stories/navigation-layout.stories.tsx
@@ -16,6 +16,7 @@ import { KeyboardShortcutsCheatsheetContent } from "@/components/KeyboardShortcu
import { MobileBottomNav } from "@/components/MobileBottomNav";
import { PageTabBar } from "@/components/PageTabBar";
import { Sidebar } from "@/components/Sidebar";
+import { PluginLauncherProvider } from "@/plugins/launchers";
import { SidebarAccountMenu } from "@/components/SidebarAccountMenu";
import { SidebarCompanyMenu } from "@/components/SidebarCompanyMenu";
import { StatusBadge } from "@/components/StatusBadge";
@@ -349,3 +350,30 @@ export default meta;
type Story = StoryObj;
export const BoardChromeMatrix: Story = {};
+
+// PAP-10676 verification harness: renders the real Sidebar at a fixed width so a
+// screenshot of the expanded state and a screenshot of the pinned-collapsed rail
+// can be overlaid. The icon column must be pixel-identical between the two — the
+// only difference should be the labels (sr-only in the rail). Playwright toggles
+// collapse via the in-sidebar control, so this exercises the real context path.
+function SidebarIconAlignmentHarness() {
+ return (
+
+
+
+ );
+}
+
+export const SidebarIconAlignment: Story = {
+ render: () => ,
+};