You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/09/13 11:24:18 UTC

(maka) 01/01: fix(ui): open magnetic model pickers without shifting nearby controls

This is an automated email from the ASF dual-hosted git repository.

M4n5ter pushed a commit to branch fix/model-wheel-overlay
in repository https://gitbox.apache.org/repos/asf/maka.git

commit 2af253442faf2fc9a879938f41dc90a710274053
Author: M4n5ter <m4...@gmail.com>
AuthorDate: Sun Sep 13 19:21:22 2026 +0800

    fix(ui): open magnetic model pickers without shifting nearby controls
    
    Generated-by: Codex
---
 apps/desktop/e2e-budget.json                       |  2 +-
 apps/desktop/e2e/workhub-layout.spec.ts            | 36 +++++++++++---------
 .../renderer/features/workhub/ui/workhub-root.tsx  |  4 +--
 apps/desktop/src/renderer/styles/workhub.css       |  7 ++++
 .../stories/settings/settings-pages.stories.tsx    | 13 +++++++-
 apps/desktop/stories/workhub.stories.tsx           | 32 +++++++++++++++++-
 .../ui/src/__tests__/model-wheel-picker.test.tsx   |  2 ++
 packages/ui/src/model-wheel-picker.tsx             | 39 ++++++++++++++++------
 packages/ui/src/styles.css                         |  4 +++
 packages/ui/stories/model-picker.stories.tsx       |  2 +-
 scripts/storybook-visual-smoke.mjs                 |  1 +
 11 files changed, 111 insertions(+), 31 deletions(-)

diff --git a/apps/desktop/e2e-budget.json b/apps/desktop/e2e-budget.json
index d56aab798f..a86076bed8 100644
--- a/apps/desktop/e2e-budget.json
+++ b/apps/desktop/e2e-budget.json
@@ -79,7 +79,7 @@
     },
     "workhub-layout.spec.ts": {
       "tests": 2,
-      "electron": "WorkHub model configuration and attachment sending cross the Host boundary; native floating preserves macOS Dock visibility and pointer dragging verifies model-wheel scrolling and release snapping; a held-open Host Turn verifies immediate prompt visibility, durable transcript handoff and persistence after renderer reload"
+      "electron": "WorkHub model configuration and attachment sending cross the Host boundary; the compact native window grows to contain the model overlay without moving composer controls on screen, and pointer dragging scrolls the no-drag wheel instead of moving the frameless window; native floating preserves macOS Dock visibility; a held-open Host Turn verifies immediate prompt visibility, durable transcript handoff and persistence after renderer reload"
     },
     "workhub-reconstruction.spec.ts": {
       "tests": 1,
diff --git a/apps/desktop/e2e/workhub-layout.spec.ts b/apps/desktop/e2e/workhub-layout.spec.ts
index 97deb6089f..15158441d6 100644
--- a/apps/desktop/e2e/workhub-layout.spec.ts
+++ b/apps/desktop/e2e/workhub-layout.spec.ts
@@ -20,7 +20,10 @@
 import { FAKE_HOLD_OPEN_PROMPT } from '@maka/runtime/test-only/fake-backend';
 import { awaitSendReady, COMPOSER_INPUT, expect, test, getWorkHubPage } from './fixtures';
 
-test('WorkHub uses its coordination model and shared attachment composer', async ({ sessionLocalWindow: { page, app } }, testInfo) => {
+// The compact native window must contain the top-layer wheel while keeping
+// composer controls fixed on screen; native dragging must scroll the no-drag
+// wheel instead of moving the frameless window. A browser has neither boundary.
+test('WorkHub uses its coordination model and shared attachment composer', async ({ sessionLocalWindow: { page, app } }) => {
   await page.evaluate(async () => {
     const { connections } = await window.maka.connections.getSnapshot();
     const connection = connections.find((entry) => entry.slug === 'e2e')!;
@@ -191,6 +194,14 @@ test('WorkHub uses its coordination model and shared attachment composer', async
   await expect.poll(() => workhub.evaluate(() => innerHeight === Math.ceil(document.querySelector('.workHubComposerSurface')!.getBoundingClientRect().height))).toBe(true);
   await expect.poll(floatingBottom).toBe(anchoredBottom);
   const compactHeight = await workhub.evaluate(() => innerHeight);
+  const screenLayout = async () => {
+    const origin = await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows().find((window) => window.getTitle() === 'WorkHub')!.getContentBounds());
+    return workhub.evaluate(({ x, y }) => Array.from(document.querySelectorAll('.maka-composer-editor, .maka-composer button, .workHubExpandButton')).map((element) => {
+      const rect = element.getBoundingClientRect();
+      return { x: x + rect.x, y: y + rect.y, width: rect.width, height: rect.height };
+    }), origin);
+  };
+  const compactControls = await screenLayout();
   await model.click();
   const wheel = workhub.getByRole('listbox');
   await expect(wheel).toBeVisible();
@@ -200,6 +211,12 @@ test('WorkHub uses its coordination model and shared attachment composer', async
   await expect(workhub.locator('.workHubHistory')).toBeHidden();
   await expect.poll(() => workhub.evaluate(() => innerHeight)).toBeGreaterThan(compactHeight);
   await expect.poll(floatingBottom).toBe(anchoredBottom);
+  await expect.poll(screenLayout).toEqual(compactControls);
+  const expectWheelInsideWindow = () => expect.poll(() => wheel.evaluate((element) => {
+    const rect = element.getBoundingClientRect();
+    return rect.top >= 0 && rect.left >= 0 && rect.bottom <= innerHeight && rect.right <= innerWidth;
+  })).toBe(true);
+  await expectWheelInsideWindow();
   const expectSnappedSelection = async (index: number) => {
     const choice = modelChoices[index]!;
     const option = wheel.getByRole('option').nth(index);
@@ -217,16 +234,12 @@ test('WorkHub uses its coordination model and shared attachment composer', async
   await wheel.hover();
   await workhub.mouse.wheel(0, scrollDirection * 30);
   await expectSnappedSelection(initialIndex + scrollDirection);
-  await expect.poll(() => wheel.evaluate((element) => {
-    const selected = document.getElementById(element.getAttribute('aria-activedescendant')!)!.getBoundingClientRect();
-    const viewport = element.getBoundingClientRect();
-    return Math.abs((selected.top + selected.bottom - viewport.top - viewport.bottom) / 2);
-  })).toBeLessThanOrEqual(1);
   await wheel.press('Escape');
   await model.click();
   await expect(wheel.getByRole('option')).toHaveCount(3);
   await expect.poll(() => workhub.evaluate(() => innerHeight === Math.ceil(document.querySelector('.workHubComposerSurface')!.getBoundingClientRect().height))).toBe(true);
   const dragInitialTop = await wheel.evaluate((element) => element.scrollTop);
+  const beforeDrag = await screenLayout();
   const dragDistance = dragInitialTop > 0 ? 32 : -32;
   const wheelBounds = (await wheel.boundingBox())!;
   const dragX = wheelBounds.x + wheelBounds.width / 2;
@@ -236,6 +249,7 @@ test('WorkHub uses its coordination model and shared attachment composer', async
   await workhub.mouse.move(dragX, dragY + dragDistance, { steps: 8 });
   await expect.poll(() => wheel.evaluate((element) => element.scrollTop)).toBe(dragInitialTop - dragDistance);
   await workhub.mouse.up();
+  await expect.poll(screenLayout).toEqual(beforeDrag);
   await expect(wheel).toBeVisible();
   await expect.poll(() => wheel.evaluate((element) => element.scrollTop)).toBe(Math.round((dragInitialTop - dragDistance) / 44) * 44);
   const draggedIndex = Math.round((dragInitialTop - dragDistance) / 44);
@@ -250,15 +264,7 @@ test('WorkHub uses its coordination model and shared attachment composer', async
   const chooseLast = await options.first().getAttribute('aria-selected') === 'true';
   await wheel.press(chooseLast ? 'End' : 'Home');
   const selectedChoice = await expectSnappedSelection(chooseLast ? modelChoices.length - 1 : 0);
-  const appearance = await page.evaluate(async () => (await window.maka.settings.getClient()).appearance);
-  for (const theme of ['light', 'dark'] as const) {
-    await page.evaluate((theme) => window.maka.settings.updateClient({ appearance: { theme } }), theme);
-    await expect.poll(() => workhub.evaluate(() => document.documentElement.classList.contains('dark'))).toBe(theme === 'dark');
-    await expect(wheel).toBeFocused();
-    await expect(wheel).toHaveCSS('outline-style', 'solid');
-    await workhub.locator('.workHubComposerSurface').screenshot({ path: testInfo.outputPath(`model-wheel-${theme}.png`), animations: 'disabled' });
-  }
-  await page.evaluate((appearance) => window.maka.settings.updateClient({ appearance }), appearance);
+  await expectWheelInsideWindow();
   await wheel.press('Enter');
   await expect(wheel).toHaveCount(0);
   await expect(model).toBeFocused();
diff --git a/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx b/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx
index 3f45fbf277..f0fab04b92 100644
--- a/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx
+++ b/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx
@@ -296,7 +296,7 @@ function WorkHubContents() {
             {controller.activeQuestion && <UserQuestionPrompt key={controller.activeQuestion.requestId}
               request={controller.activeQuestion} onRespond={controller.respondToUserQuestion}
               onStop={controller.stop} stopPending={controller.stopPending} />}
-            <div hidden={Boolean(controller.activeQuestion || controller.activeForm)}>
+            <div className="workHubComposerContent" hidden={Boolean(controller.activeQuestion || controller.activeForm)}>
             <WorkHubComposer
               pendingMessages={controller.transientMessages}
               queuedMessages={controller.messageQueue.entries}
@@ -336,10 +336,10 @@ function WorkHubContents() {
                 </div>
               }
             />
-            </div>
             {!progress && floating && !conversationExpanded && (
               <IconButton className="workHubExpandButton" type="button" size="sm" variant="ghost" icon={<ChevronDown size={14} style={{ rotate: '180deg' }} />} label={t.expandConversation} aria-expanded={false} onClick={toggleConversation} />
             )}
+            </div>
           </div>
         }
       >
diff --git a/apps/desktop/src/renderer/styles/workhub.css b/apps/desktop/src/renderer/styles/workhub.css
index f1803f14b2..6d9d0d553a 100644
--- a/apps/desktop/src/renderer/styles/workhub.css
+++ b/apps/desktop/src/renderer/styles/workhub.css
@@ -155,6 +155,13 @@
   .workHubLiveWelcome p { color: var(--muted-foreground); }
   .workHubLiveError { padding: var(--space-3); }
   .workHubComposerSurface { position: relative; width: 100%; }
+  .workHubComposerContent { position: relative; }
+  /* Top-layer content still needs room inside the native window. Measuring
+     this leading space grows the bottom-anchored window upward, without
+     changing the composer's layout or its controls' screen positions. */
+  .workHubLive[data-placement='floating'][data-conversation-expanded='false'] .workHubComposerSurface:has(.maka-model-wheel-anchor[data-open='true'])::before {
+    content: ''; display: block; height: 136px;
+  }
   .workHubComposerActions { display: flex; align-items: center; gap: 2px; margin-left: auto; }
   .workHubRevealMark { position: absolute; inset: 32px 0 calc(var(--workhub-composer-height, 96px) + 12px); display: grid; place-items: center; z-index: 3; color: var(--foreground); opacity: 0; pointer-events: none; }
   .workHubRevealMark svg { filter: drop-shadow(0 2px 12px var(--background)); }
diff --git a/apps/desktop/stories/settings/settings-pages.stories.tsx b/apps/desktop/stories/settings/settings-pages.stories.tsx
index 64594b32d9..132bed64db 100644
--- a/apps/desktop/stories/settings/settings-pages.stories.tsx
+++ b/apps/desktop/stories/settings/settings-pages.stories.tsx
@@ -2089,7 +2089,7 @@ export const General: Story = {
   decorators: [withSettingsBridge],
   render: () => <SettingsStory section="general" />,
 };
-// Real path: 设置 → 通用 → 默认模型. Focus stays on the inline magnetic wheel;
+// Real path: 设置 → 通用 → 默认模型. Focus stays on the floating magnetic wheel;
 // the containing settings row must not add a second focus ring.
 export const GeneralPickerOpenFocusRing: Story = {
   decorators: [withSettingsBridge],
@@ -2097,6 +2097,12 @@ export const GeneralPickerOpenFocusRing: Story = {
   play: async ({ canvasElement }) => {
     const canvas = within(canvasElement);
     const trigger = await canvas.findByRole('button', { name: '默认模型' });
+    trigger.scrollIntoView({ block: 'center' });
+    const rows = () => Array.from(canvasElement.querySelectorAll('.astryx-item')).map((element) => {
+      const { x, y, width, height } = element.getBoundingClientRect();
+      return { x, y, width, height };
+    });
+    const before = rows();
     await userEvent.click(trigger);
     await waitFor(() => {
       const active = document.activeElement as HTMLElement | null;
@@ -2106,6 +2112,11 @@ export const GeneralPickerOpenFocusRing: Story = {
     const row = active.closest<HTMLElement>('.astryx-item');
     expect(row).not.toBeNull();
     expect(row ? getComputedStyle(row).outlineStyle : null).toBe('none');
+    expect(rows()).toEqual(before);
+    await userEvent.keyboard('{Escape}');
+    await waitFor(() => expect(trigger).toHaveFocus());
+    expect(rows()).toEqual(before);
+    await userEvent.click(trigger);
   },
 };
 
diff --git a/apps/desktop/stories/workhub.stories.tsx b/apps/desktop/stories/workhub.stories.tsx
index 5ac9f73e47..d119c8263b 100644
--- a/apps/desktop/stories/workhub.stories.tsx
+++ b/apps/desktop/stories/workhub.stories.tsx
@@ -169,19 +169,49 @@ export const FullConversationAndWorkIdentity: Story = {
   },
 };
 export const FullConversationNarrow: Story = { ...FullConversationAndWorkIdentity, parameters: { viewport: { defaultViewport: 'tablet' } } };
+// Real path: the docked WorkHub composer opens its model wheel before sending.
 export const StandardComposer: Story = {
   render: () => <Surface />,
   play: async ({ canvasElement }) => {
     Object.values(writes).forEach((spy) => spy.mockClear());
     const canvas = within(canvasElement); const page = within(canvasElement.ownerDocument.body);
     await waitFor(() => expect(canvas.getByRole('button', { name: /切换当前任务模型/ })).toBeEnabled());
-    await userEvent.click(canvas.getByRole('button', { name: /切换当前任务模型/ }));
+    const trigger = canvas.getByRole('button', { name: /切换当前任务模型/ });
+    const layout = () => Array.from(canvasElement.querySelectorAll('.maka-composer-editor, .maka-composer button')).map((element) => {
+      const { x, y, width, height } = element.getBoundingClientRect();
+      return { x, y, width, height };
+    });
+    const before = layout();
+    await userEvent.click(trigger);
     const wheel = canvas.getByRole('listbox', { name: /切换当前任务模型/ });
+    await waitFor(() => expect(wheel).toHaveFocus());
+    expect(layout()).toEqual(before);
     await expect(within(wheel).getByRole('option', { name: /model-a/, selected: true })).toBeInTheDocument();
     await userEvent.keyboard('{End}');
     await waitFor(() => expect(writes.model).toHaveBeenCalledWith(sessionId, expect.objectContaining({ expectedRevision: 1, modelTarget: expect.objectContaining({ model: 'model-b' }) })));
     await waitFor(() => expect(within(wheel).getByRole('option', { name: /model-b/, selected: true })).toBeInTheDocument());
+    expect(layout()).toEqual(before);
+    await waitFor(() => {
+      const selected = wheel.querySelector('[aria-selected="true"]')!.getBoundingClientRect();
+      const viewport = wheel.getBoundingClientRect();
+      expect(Math.abs((selected.top + selected.bottom - viewport.top - viewport.bottom) / 2)).toBeLessThanOrEqual(1);
+    });
+    const activeLabel = wheel.querySelector('[data-active="true"] .maka-model-wheel-label')!;
+    const otherLabel = wheel.querySelector('[data-active="false"] .maka-model-wheel-label')!;
+    await waitFor(() => expect(new DOMMatrix(getComputedStyle(activeLabel).transform).a).toBeGreaterThan(new DOMMatrix(getComputedStyle(otherLabel).transform).a));
+    expect(getComputedStyle(wheel).outlineStyle).toBe('solid');
     await userEvent.keyboard('{Escape}');
+    await waitFor(() => expect(trigger).toHaveFocus());
+    expect(layout()).toEqual(before);
+    await userEvent.click(trigger);
+    await waitFor(() => expect(canvas.getByRole('listbox')).toHaveFocus());
+    await userEvent.click(trigger);
+    await waitFor(() => expect(canvas.queryByRole('listbox')).not.toBeInTheDocument());
+    await userEvent.click(trigger);
+    await waitFor(() => expect(canvas.getByRole('listbox')).toHaveFocus());
+    await userEvent.tab();
+    await waitFor(() => expect(canvas.queryByRole('listbox')).not.toBeInTheDocument());
+    expect(canvasElement.ownerDocument.activeElement).not.toBe(canvasElement.ownerDocument.body);
     await userEvent.click(canvas.getByRole('button', { name: '添加上下文' }));
     await userEvent.click(page.getByRole('menuitem', { name: /添加文件/ }));
     const editor = canvasElement.querySelector('[contenteditable="true"]') as HTMLElement;
diff --git a/packages/ui/src/__tests__/model-wheel-picker.test.tsx b/packages/ui/src/__tests__/model-wheel-picker.test.tsx
index 3746bb21ad..800ba3df4e 100644
--- a/packages/ui/src/__tests__/model-wheel-picker.test.tsx
+++ b/packages/ui/src/__tests__/model-wheel-picker.test.tsx
@@ -33,6 +33,8 @@ test('the wheel applies settled selection once and restores the saved model on f
     }).IS_REACT_ACT_ENVIRONMENT,
   };
   const { document, window } = parseHTML('<div id="root"></div>');
+  window.getComputedStyle = () => ({ direction: 'ltr', writingMode: 'horizontal-tb',
+    getPropertyValue: () => '' }) as unknown as CSSStyleDeclaration;
   Object.assign(globalThis, { document, window, IS_REACT_ACT_ENVIRONMENT: true });
   const root = createRoot(document.querySelector('#root')!);
   const calls: string[] = [];
diff --git a/packages/ui/src/model-wheel-picker.tsx b/packages/ui/src/model-wheel-picker.tsx
index d36c31d4d5..3003633857 100644
--- a/packages/ui/src/model-wheel-picker.tsx
+++ b/packages/ui/src/model-wheel-picker.tsx
@@ -18,7 +18,7 @@
  */
 
 import { type ReactNode, useId, useLayoutEffect, useRef, useState } from 'react';
-import { Button } from '@astryxdesign/core';
+import { Button, useLayer } from '@astryxdesign/core';
 import { Check, ICON_SIZE } from './icons.js';
 
 export interface ModelWheelOption {
@@ -29,7 +29,7 @@ export interface ModelWheelOption {
   disabled?: boolean;
 }
 
-/** Shared inline picker: the model at the settled snap position takes effect. */
+/** Shared overlay picker: the model at the settled snap position takes effect. */
 export function ModelWheelPicker(props: {
   options: readonly ModelWheelOption[];
   value?: string;
@@ -46,25 +46,44 @@ export function ModelWheelPicker(props: {
 }) {
   const [internalOpen, setInternalOpen] = useState(false);
   const trigger = useRef<HTMLButtonElement>(null);
+  const anchor = useRef<HTMLSpanElement>(null);
   const open = props.open ?? internalOpen;
   const setOpen = (next: boolean) => {
     if (props.open === undefined) setInternalOpen(next);
     props.onOpenChange?.(next);
   };
-  return open ? <ModelWheel {...props} onClose={(restoreFocus) => {
-    setOpen(false);
-    if (restoreFocus) requestAnimationFrame(() => trigger.current?.focus({ preventScroll: true }));
-  }} /> : <Button ref={trigger} type="button" variant="ghost" size={props.size ?? 'sm'}
-    label={props.label} icon={props.icon} tooltip={props.tooltip}
+  const layer = useLayer({ mode: 'context', lazyMount: true, lightDismiss: true,
+    onHide: () => setOpen(false) });
+  useLayoutEffect(() => {
+    if (open) layer.show();
+    else layer.hide();
+  }, [open, layer.show, layer.hide]);
+  useLayoutEffect(() => {
+    const element = anchor.current;
+    if (!open || !element) return;
+    // Saving another model can change the label while the wheel is open.
+    // Keep its anchor's footprint stable throughout that interaction.
+    element.style.width = `${element.getBoundingClientRect().width}px`;
+    return () => { element.style.removeProperty('width'); };
+  }, [open]);
+  return <span ref={anchor} className="maka-model-wheel-anchor" data-open={open}>
+    <Button ref={(element) => { trigger.current = element; layer.ref(element); }} type="button" variant="ghost" size={props.size ?? 'sm'}
+    label={props.label} icon={props.icon} tooltip={open ? undefined : props.tooltip}
     isDisabled={props.disabled || props.options.length === 0}
     className={props.triggerClassName} aria-label={props.ariaLabel}
-    aria-haspopup="listbox" aria-expanded={false}
-    onClick={() => setOpen(true)}
+    aria-haspopup="listbox" aria-expanded={open} aria-controls={open ? layer.id : undefined}
+    onMouseDown={(event) => { if (open) event.preventDefault(); }}
+    onClick={() => setOpen(!open)}
     onKeyDown={(event) => {
       if (!props.disabled && props.options.length > 0 && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
         event.preventDefault(); event.stopPropagation(); setOpen(true);
       }
-    }} />;
+    }} />
+    {layer.render(open && layer.isOpen ? <ModelWheel {...props} onClose={(restoreFocus) => {
+    setOpen(false);
+    if (restoreFocus) requestAnimationFrame(() => trigger.current?.focus({ preventScroll: true }));
+    }} /> : null, { placement: 'above', alignment: 'start', offset: 4, className: 'maka-model-wheel-popup' })}
+  </span>;
 }
 
 const ROW_HEIGHT = 44;
diff --git a/packages/ui/src/styles.css b/packages/ui/src/styles.css
index d88ab05de1..0b8886dd7f 100644
--- a/packages/ui/src/styles.css
+++ b/packages/ui/src/styles.css
@@ -19,6 +19,10 @@
 
 @import 'katex/dist/katex.min.css';
 
+.maka-model-wheel-anchor { display: inline-flex; min-width: 0; max-width: 100%; }
+.maka-model-wheel-anchor > button { min-width: 0; max-width: 100%; }
+.maka-model-wheel-anchor[data-open='true'] > button { width: 100%; }
+.maka-model-wheel-popup { border-radius: var(--radius-container); background: var(--color-background-popover); box-shadow: var(--shadow-med); }
 .maka-model-wheel { position: relative; width: min(264px, 55vw); max-width: 100%; }
 .maka-model-wheel::before { content: ''; position: absolute; inset: 44px 0; border-radius: 10px; background: color-mix(in oklch, var(--foreground) 6%, transparent); pointer-events: none; }
 .maka-model-wheel-viewport { position: relative; box-sizing: border-box; overflow-y: auto; overscroll-behavior: contain; scroll-snap-type: y mandatory; scrollbar-width: none; mask-image: linear-gradient(transparent, black 32%, black 68%, transparent); outline: none; }
diff --git a/packages/ui/stories/model-picker.stories.tsx b/packages/ui/stories/model-picker.stories.tsx
index b1b62e5e73..e801c397b0 100644
--- a/packages/ui/stories/model-picker.stories.tsx
+++ b/packages/ui/stories/model-picker.stories.tsx
@@ -234,7 +234,7 @@ export const ExistingConversation: Story = {
     await waitFor(() => expect(announcement).toBeEmptyDOMElement());
     await expect(document.body.querySelector('.maka-model-switch-notice')).not.toBeInTheDocument();
 
-    // Closing replaces the wheel with a new trigger and restores focus next frame.
+    // Closing restores focus to the same trigger next frame.
     await waitFor(() => expect(within(canvasElement).getByRole('button', {
       name: /切换当前任务模型|Switch model for this task/,
     })).toHaveFocus());
diff --git a/scripts/storybook-visual-smoke.mjs b/scripts/storybook-visual-smoke.mjs
index e89465bd9f..f5388fcf34 100644
--- a/scripts/storybook-visual-smoke.mjs
+++ b/scripts/storybook-visual-smoke.mjs
@@ -71,6 +71,7 @@ const DARK_THEME_SENTINEL_STORY_IDS = new Set([
   'product-settings-pages--appearance',
   'product-settings-pages--bot-chat-needs-attention',
   'product-shell-official-appshell--default-layout',
+  'product-workhub--standard-composer',
 ]);
 const FORCED_COLORS_STORY_IDS = new Set([
   'product-settings-pages--general-forced-colors-focus-ring',


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org