Skip to content

fix(wework): resolve composer and dialog display bugs - #2078

Closed
sdadunderscoresdad wants to merge 6 commits into
wecode-ai:mainfrom
sdadunderscoresdad:fix/ui-display-bugs
Closed

sdadunderscoresdad wants to merge 6 commits into
wecode-ai:mainfrom
sdadunderscoresdad:fix/ui-display-bugs

Conversation

@sdadunderscoresdad

@sdadunderscoresdad sdadunderscoresdad commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes a set of UI display bugs in the wework desktop client:

  • Model selector layout: menu now opens to the left and stays inside the available viewport, respecting the right workspace panel boundary instead of being clipped by it.
  • Embedded browser occlusion: only occlude the embedded browser when the cloud connection dialog is open, keeping it visible under normal overlays.
  • Cloud-work error popover: rendered via portal so it layers correctly, and closes on an outside click.
  • Dialog theming: removed forced dark mode from the add-device and cloud-work dialogs so they follow the active theme.
  • macOS input arrow keys: swallow arrow-key events at native input boundaries to prevent unintended composer navigation.
  • Narrow composer toolbar: cap the model selector trigger width and collapse the quick phrase menu to icon-only when the toolbar is narrow, preventing overlap/overflow.

Test plan

  • Open the desktop model selector and verify the menu is left-aligned and does not cross the right workspace panel edge.
  • Open the cloud connection dialog and confirm the embedded browser is occluded; close the dialog and confirm the browser is visible again.
  • Trigger a cloud-work error popover and click outside; it should close.
  • Open add-device / cloud-work dialogs in light theme and confirm they render in light mode.
  • In a native text input on macOS, press arrow keys at the start/end of the text and verify the cursor stays in the input.
  • Resize the composer to a narrow width and verify the quick phrase button shows only an icon and the model selector does not expand past its capped width.

Summary by CodeRabbit

  • New Features

    • Composer controls now adapt to available space, using compact icons and constrained model-selector widths.
    • Model-selector menus position correctly alongside workspace panels.
    • Cloud connection error details open in a positioned popover and close when clicking outside.
    • Cloud connection dialogs now handle embedded-browser occlusion.
    • macOS desktop text inputs provide improved arrow-key behavior.
  • Style

    • Updated project and cloud-related dialogs to use consistent theme-based colors.
  • Bug Fixes

    • Workspace browser event handling now reflects label changes.

jiaqi62 and others added 5 commits July 20, 2026 18:15
Work around a WKWebView/Tauri bug (tauri-apps/tauri#5685) where pressing
ArrowLeft/ArrowRight at the start/end of a native text input leaks through
AppKit's insertText: path and inserts U+FFFC / tofu characters. The chat
composer uses ProseMirror and was not affected.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… occlusion to cloud dialog

Keep the embedded browser visible under normal overlays; only occlude it
when the cloud connection dialog is open so the dialog is not covered.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ialogs

Replace hardcoded dark colors in StandaloneProjectDialogs,
DeviceFolderPicker remoteDark variant, and AddCloudDeviceDialog
command block with semantic tokens so they follow the active theme.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…outside click

The cloud work unavailable error popover was clipped by the sidebar's
overflow-hidden wrapper. Render it through a portal with fixed positioning
anchored to the warning button so it displays fully outside the sidebar.

Also close the popover when clicking anywhere else on the page, while
keeping clicks on the trigger button or inside the popover from closing it.
Add tests for outside-click dismissal and inside-click persistence.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…arrow toolbar

Prevent the model selector trigger from expanding to 240px when opened in
a narrow composer toolbar by capping its width with maxClosedWidth. Also
switch the quick phrase menu to icon-only mode when the toolbar is narrow
so the right-side controls do not overlap or overflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds responsive chat composer layouts, workspace-aware model menu positioning, a portal-based cloud error popover, embedded-browser occlusion handling, a macOS input arrow-key guard, and theme-token styling updates across project dialogs.

Changes

Responsive composer UI

Layer / File(s) Summary
Toolbar width-driven rendering
wework/src/components/chat/composer/ComposerToolbar.tsx, wework/src/components/chat/composer/ComposerToolbar.test.tsx
Toolbar width measurement toggles compact quick-phrase rendering and constrains model-selector width.
Model selector sizing and positioning
wework/src/components/chat/composer/ModelSelector.tsx, wework/src/components/chat/composer/ModelSelectorTrigger.tsx, wework/src/components/chat/composer/model-selector-types.ts, wework/src/components/chat/composer/ModelSelector.test.tsx, wework/src/components/chat/ChatInput.test.tsx
Desktop menu placement respects the workspace shell, while trigger widths use configurable CSS-variable caps.
Quick-phrase trigger modes
wework/src/components/chat/composer/QuickPhraseMenu.tsx, wework/src/components/chat/composer/QuickPhraseMenu.test.tsx
QuickPhraseMenu supports icon-only and compact trigger layouts with corresponding tests.

Embedded-browser and cloud interaction handling

Layer / File(s) Summary
Embedded-browser occlusion lifecycle
wework/src/hooks/useEmbeddedBrowserOcclusion.ts, wework/src/features/cloud-connection/CloudConnectionDialog.tsx
Dialog visibility now synchronizes embedded-browser occlusion state.
Cloud error popover positioning
wework/src/features/cloud-connection/CloudConnectionSidebarButton.tsx, wework/src/components/layout/DesktopSidebar.test.tsx
The cloud error popover renders in a body portal, tracks position on resize, and closes only for outside pointer input.
Embedded listener lifecycle and test setup
wework/src/components/layout/workspace-panels/WorkspaceBrowserPanel.tsx, wework/src/components/layout/workspace-panels/WorkspaceBrowserPanel.test.tsx
Embedded-browser listeners refresh when label changes, with deterministic download-resume test setup.

macOS input arrow-key guard

Layer / File(s) Summary
Guard implementation and application installation
wework/src/lib/macosInputArrowKeyGuard.ts, wework/src/App.tsx
Tauri macOS boundary arrow keys in eligible native inputs are prevented through a cleaned-up capturing listener.
Guard behavior validation
wework/src/lib/macosInputArrowKeyGuard.test.ts
Tests cover platform gating, caret boundaries, composition, selections, targets, and input states.

Theme-token project and device styling

Layer / File(s) Summary
Project dialog theme tokens
wework/src/components/projects/DeviceFolderPicker.tsx, wework/src/components/projects/StandaloneProjectDialogs.tsx
Hard-coded dark-mode colors are replaced with theme-token classes across project folder and startup-script interfaces.
Cloud device command styling
wework/src/components/settings/AddCloudDeviceDialog.tsx
The Docker command panel uses theme-token classes for its container, controls, and text.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CloudConnectionSidebarButton
  participant DocumentBody
  participant CloudErrorPopover
  User->>CloudConnectionSidebarButton: Click error button
  CloudConnectionSidebarButton->>CloudConnectionSidebarButton: Calculate popover position
  CloudConnectionSidebarButton->>DocumentBody: Render portal
  DocumentBody->>CloudErrorPopover: Display positioned details
  User->>DocumentBody: Pointer down outside popover
  DocumentBody->>CloudConnectionSidebarButton: Close details
Loading

Possibly related PRs

Suggested reviewers: qdaxb

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main UI bug-fix theme, though it omits some secondary fixes like macOS input handling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
wework/src/lib/macosInputArrowKeyGuard.test.ts (1)

144-157: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a test case for inputs that throw on selection access.

To complement the try...catch fix for the InvalidStateError in macosInputArrowKeyGuard.ts, consider adding a test case that explicitly mocks and verifies this exception handling. This ensures the guard safely ignores unsupported input types without breaking.

♻️ Proposed test addition
   test('ignores disabled and read-only inputs', () => {
     isTauriRuntimeMock.mockReturnValue(true)
     userAgent = 'Macintosh; Intel Mac OS X 10_15_7'
     const dispose = installMacOSInputArrowKeyGuard()
     const disabledInput = createInput('hello', 5, 5)
     disabledInput.disabled = true
     expect(dispatchArrowKey(disabledInput, 'ArrowRight').defaultPrevented).toBe(false)
 
     const readOnlyInput = createInput('hello', 0, 0)
     readOnlyInput.readOnly = true
     expect(dispatchArrowKey(readOnlyInput, 'ArrowLeft').defaultPrevented).toBe(false)
     dispose()
   })
+
+  test('ignores inputs that throw on selection access (e.g., type="number")', () => {
+    isTauriRuntimeMock.mockReturnValue(true)
+    userAgent = 'Macintosh; Intel Mac OS X 10_15_7'
+    const dispose = installMacOSInputArrowKeyGuard()
+    const numberInput = createInput('123', 0, 0)
+    
+    // Simulate the InvalidStateError thrown by WebKit for certain input types
+    Object.defineProperty(numberInput, 'selectionStart', {
+      get() { throw new DOMException('Failed to read selectionStart', 'InvalidStateError') }
+    })
+    
+    const event = dispatchArrowKey(numberInput, 'ArrowLeft')
+    expect(event.defaultPrevented).toBe(false)
+    dispose()
+  })
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/lib/macosInputArrowKeyGuard.test.ts` around lines 144 - 157, Add a
test alongside the existing disabled/read-only case that makes selection access
throw an InvalidStateError and verifies dispatchArrowKey does not throw or
prevent the default action. Use the existing installMacOSInputArrowKeyGuard and
input helpers, and clean up the guard with dispose after the assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wework/src/features/cloud-connection/CloudConnectionSidebarButton.tsx`:
- Around line 140-152: Update CloudConnectionSidebarButton to add a containerRef
covering the entire sidebar item, exclude that container from the pointerdown
outside-click check, and replace the main button’s openErrorDetails() handler
with toggleErrorDetails() so clicking the row reliably toggles the popover
without flicker.

In `@wework/src/lib/macosInputArrowKeyGuard.ts`:
- Around line 32-38: Wrap the selectionStart/selectionEnd access and boundary
evaluation in the macOS arrow-key guard function with try/catch so
InvalidStateError from unsupported input types is safely ignored. Preserve the
existing false returns for unavailable or non-collapsed selections and the true
returns for left/right boundary keys; return false when the guarded access
throws.

---

Nitpick comments:
In `@wework/src/lib/macosInputArrowKeyGuard.test.ts`:
- Around line 144-157: Add a test alongside the existing disabled/read-only case
that makes selection access throw an InvalidStateError and verifies
dispatchArrowKey does not throw or prevent the default action. Use the existing
installMacOSInputArrowKeyGuard and input helpers, and clean up the guard with
dispose after the assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: edd3de5f-eff4-4b24-90e2-ce26bc764ce6

📥 Commits

Reviewing files that changed from the base of the PR and between b3e7292 and 38f52a9.

📒 Files selected for processing (21)
  • wework/src/App.tsx
  • wework/src/components/chat/ChatInput.test.tsx
  • wework/src/components/chat/composer/ComposerToolbar.test.tsx
  • wework/src/components/chat/composer/ComposerToolbar.tsx
  • wework/src/components/chat/composer/ModelSelector.test.tsx
  • wework/src/components/chat/composer/ModelSelector.tsx
  • wework/src/components/chat/composer/ModelSelectorTrigger.tsx
  • wework/src/components/chat/composer/QuickPhraseMenu.test.tsx
  • wework/src/components/chat/composer/QuickPhraseMenu.tsx
  • wework/src/components/chat/composer/model-selector-types.ts
  • wework/src/components/layout/DesktopSidebar.test.tsx
  • wework/src/components/layout/workspace-panels/WorkspaceBrowserPanel.test.tsx
  • wework/src/components/layout/workspace-panels/WorkspaceBrowserPanel.tsx
  • wework/src/components/projects/DeviceFolderPicker.tsx
  • wework/src/components/projects/StandaloneProjectDialogs.tsx
  • wework/src/components/settings/AddCloudDeviceDialog.tsx
  • wework/src/features/cloud-connection/CloudConnectionDialog.tsx
  • wework/src/features/cloud-connection/CloudConnectionSidebarButton.tsx
  • wework/src/hooks/useEmbeddedBrowserOcclusion.ts
  • wework/src/lib/macosInputArrowKeyGuard.test.ts
  • wework/src/lib/macosInputArrowKeyGuard.ts

Comment on lines +140 to +152
useEffect(() => {
if (!detailsOpen) return
const handlePointerDown = (event: PointerEvent) => {
const target = event.target as Node
if (errorButtonRef.current?.contains(target) || popoverRef.current?.contains(target)) {
return
}
setDetailsOpen(false)
setPopoverPosition(null)
}
document.addEventListener('pointerdown', handlePointerDown)
return () => document.removeEventListener('pointerdown', handlePointerDown)
}, [detailsOpen])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Prevent outside-click conflict when clicking the main button.

Currently, clicking the main button while the popover is open triggers the pointerdown outside-click listener (which closes the popover), followed immediately by the click event (which calls openErrorDetails(), reopening it). This causes a brief flicker and prevents the main button from functioning as a proper toggle.

To fix this, introduce a containerRef to wrap the entire sidebar item, exclude it from the outside-click check, and update the main button to use toggleErrorDetails() so the whole row behaves consistently.

🐛 Proposed fix
--- a/wework/src/features/cloud-connection/CloudConnectionSidebarButton.tsx
+++ b/wework/src/features/cloud-connection/CloudConnectionSidebarButton.tsx
@@ -39,2 +39,3 @@
   const [detailsOpen, setDetailsOpen] = useState(false)
+  const containerRef = useRef<HTMLDivElement>(null)
   const errorButtonRef = useRef<HTMLButtonElement>(null)
@@ -142,3 +143,3 @@
       const target = event.target as Node
-      if (errorButtonRef.current?.contains(target) || popoverRef.current?.contains(target)) {
+      if (containerRef.current?.contains(target) || popoverRef.current?.contains(target)) {
         return
@@ -175,3 +176,3 @@
     <>
-      <div className="group/cloud relative flex h-[30px] items-center rounded-[10px] hover:bg-[rgb(var(--color-sidebar-hover))]">
+      <div ref={containerRef} className="group/cloud relative flex h-[30px] items-center rounded-[10px] hover:bg-[rgb(var(--color-sidebar-hover))]">
         <div className="flex h-[30px] w-[30px] shrink-0 items-center justify-center">
@@ -215,3 +216,3 @@
               if (errorDetail) {
-                openErrorDetails()
+                toggleErrorDetails()
                 return
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (!detailsOpen) return
const handlePointerDown = (event: PointerEvent) => {
const target = event.target as Node
if (errorButtonRef.current?.contains(target) || popoverRef.current?.contains(target)) {
return
}
setDetailsOpen(false)
setPopoverPosition(null)
}
document.addEventListener('pointerdown', handlePointerDown)
return () => document.removeEventListener('pointerdown', handlePointerDown)
}, [detailsOpen])
useEffect(() => {
if (!detailsOpen) return
const handlePointerDown = (event: PointerEvent) => {
const target = event.target as Node
if (containerRef.current?.contains(target) || popoverRef.current?.contains(target)) {
return
}
setDetailsOpen(false)
setPopoverPosition(null)
}
document.addEventListener('pointerdown', handlePointerDown)
return () => document.removeEventListener('pointerdown', handlePointerDown)
}, [detailsOpen])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/cloud-connection/CloudConnectionSidebarButton.tsx` around
lines 140 - 152, Update CloudConnectionSidebarButton to add a containerRef
covering the entire sidebar item, exclude that container from the pointerdown
outside-click check, and replace the main button’s openErrorDetails() handler
with toggleErrorDetails() so clicking the row reliably toggles the popover
without flicker.

Comment on lines +32 to +38
const { selectionStart, selectionEnd, value } = input
if (selectionStart === null || selectionEnd === null) return false
if (selectionStart !== selectionEnd) return false

if (event.key === ARROW_LEFT && selectionStart === 0) return true
if (event.key === ARROW_RIGHT && selectionEnd === value.length) return true
return false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle InvalidStateError when accessing selectionStart on unsupported input types.

Accessing selectionStart or selectionEnd on certain <input> types (such as number, email, date, etc.) throws an InvalidStateError DOMException in WebKit/Safari. This uncaught exception will break the global keydown listener and could interfere with other event handlers in the application.

Wrap the selection property access and evaluation in a try...catch block to safely ignore these input types.

🐛 Proposed fix
-  const { selectionStart, selectionEnd, value } = input
-  if (selectionStart === null || selectionEnd === null) return false
-  if (selectionStart !== selectionEnd) return false
-
-  if (event.key === ARROW_LEFT && selectionStart === 0) return true
-  if (event.key === ARROW_RIGHT && selectionEnd === value.length) return true
-  return false
+  try {
+    const { selectionStart, selectionEnd, value } = input
+    if (selectionStart === null || selectionEnd === null) return false
+    if (selectionStart !== selectionEnd) return false
+
+    if (event.key === ARROW_LEFT && selectionStart === 0) return true
+    if (event.key === ARROW_RIGHT && selectionEnd === value.length) return true
+    return false
+  } catch {
+    // Accessing selectionStart on types like 'number' or 'email' throws InvalidStateError in WebKit.
+    return false
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { selectionStart, selectionEnd, value } = input
if (selectionStart === null || selectionEnd === null) return false
if (selectionStart !== selectionEnd) return false
if (event.key === ARROW_LEFT && selectionStart === 0) return true
if (event.key === ARROW_RIGHT && selectionEnd === value.length) return true
return false
try {
const { selectionStart, selectionEnd, value } = input
if (selectionStart === null || selectionEnd === null) return false
if (selectionStart !== selectionEnd) return false
if (event.key === ARROW_LEFT && selectionStart === 0) return true
if (event.key === ARROW_RIGHT && selectionEnd === value.length) return true
return false
} catch {
// Accessing selectionStart on types like 'number' or 'email' throws InvalidStateError in WebKit.
return false
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/lib/macosInputArrowKeyGuard.ts` around lines 32 - 38, Wrap the
selectionStart/selectionEnd access and boundary evaluation in the macOS
arrow-key guard function with try/catch so InvalidStateError from unsupported
input types is safely ignored. Preserve the existing false returns for
unavailable or non-collapsed selections and the true returns for left/right
boundary keys; return false when the guarded access throws.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant