From ae29d65b23bfe4295fc45246859aa7349cb76b3a Mon Sep 17 00:00:00 2001 From: Bob Lee Date: Sat, 24 Oct 2026 09:35:39 +0800 Subject: [PATCH 1/5] fix(ui): center the unused-session usage state --- src/web-ui/src/flow_chat/components/usage/SessionUsageModal.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/web-ui/src/flow_chat/components/usage/SessionUsageModal.scss b/src/web-ui/src/flow_chat/components/usage/SessionUsageModal.scss index 286b3292e8..4d04e48b39 100644 --- a/src/web-ui/src/flow_chat/components/usage/SessionUsageModal.scss +++ b/src/web-ui/src/flow_chat/components/usage/SessionUsageModal.scss @@ -5,6 +5,7 @@ .session-usage-dialog__content { display: flex; + justify-content: center; flex: 1 0 auto; min-inline-size: 0; container: session-usage / inline-size; From 8c8e37a7be2ed92506b2a894d075efc77428b3d5 Mon Sep 17 00:00:00 2001 From: Bob Lee Date: Sat, 24 Oct 2026 09:35:39 +0800 Subject: [PATCH 2/5] fix(calendar): show numeric day labels without date suffixes --- .../app/components/scheduled-jobs/DateTimePickerPopover.tsx | 4 ++-- .../components/scheduled-jobs/LocalizedDateTimeField.test.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/web-ui/src/app/components/scheduled-jobs/DateTimePickerPopover.tsx b/src/web-ui/src/app/components/scheduled-jobs/DateTimePickerPopover.tsx index a2bdd5e81b..0efa678065 100644 --- a/src/web-ui/src/app/components/scheduled-jobs/DateTimePickerPopover.tsx +++ b/src/web-ui/src/app/components/scheduled-jobs/DateTimePickerPopover.tsx @@ -58,7 +58,7 @@ const DateTimePickerPopover: React.FC = ({ onSelect, onClose, }) => { - const { t, formatDate } = useI18n('common'); + const { t, formatDate, formatNumber } = useI18n('common'); const popoverRef = useRef(null); const [focusedDayMs, setFocusedDayMs] = useState(() => { @@ -239,7 +239,7 @@ const DateTimePickerPopover: React.FC = ({ onFocus={() => setFocusedDayMs(day.getTime())} onClick={() => selectDay(day)} > - {formatDate(day, { day: 'numeric' })} + {formatNumber(day.getDate(), { useGrouping: false })} ); diff --git a/src/web-ui/src/app/components/scheduled-jobs/LocalizedDateTimeField.test.tsx b/src/web-ui/src/app/components/scheduled-jobs/LocalizedDateTimeField.test.tsx index c038593a55..215ce7604b 100644 --- a/src/web-ui/src/app/components/scheduled-jobs/LocalizedDateTimeField.test.tsx +++ b/src/web-ui/src/app/components/scheduled-jobs/LocalizedDateTimeField.test.tsx @@ -14,6 +14,7 @@ vi.mock('@/infrastructure/i18n', () => ({ useI18n: () => ({ currentLanguage: 'en-US', formatDate: (date: Date) => date.toISOString(), + formatNumber: (value: number) => String(value), t: (key: string) => key, }), })); From 5e7f1c83ca4229e29a7704243e0f0383e1f1158d Mon Sep 17 00:00:00 2001 From: Bob Lee Date: Sat, 24 Oct 2026 09:35:52 +0800 Subject: [PATCH 3/5] fix(workspaces): keep unsupported WSL disabled without a hint --- .../components/NewProjectDialog/NewProjectDialog.test.tsx | 2 +- .../src/features/ssh-remote/SSHConnectionDialog.test.tsx | 3 ++- .../src/features/ssh-remote/WorkspaceSourceSelector.tsx | 7 ++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/web-ui/src/app/components/NewProjectDialog/NewProjectDialog.test.tsx b/src/web-ui/src/app/components/NewProjectDialog/NewProjectDialog.test.tsx index aa7aa977ee..fa33bff0e6 100644 --- a/src/web-ui/src/app/components/NewProjectDialog/NewProjectDialog.test.tsx +++ b/src/web-ui/src/app/components/NewProjectDialog/NewProjectDialog.test.tsx @@ -83,7 +83,7 @@ describe('NewProjectDialog composition', () => { const onSelectRemoteHost = vi.fn(); await render({ onPickRemoteFolder: vi.fn(), onSelectRemoteHost }); expect(sourceOption(1, 'wsl').disabled).toBe(true); - expect(document.body.textContent).toContain('ssh.remote.wslUnsupported'); + expect(document.body.textContent).not.toContain('ssh.remote.wslUnsupported'); act(() => sourceOption(1, 'wsl').click()); expect(sourceOption(1, 'system').getAttribute('aria-checked')).toBe('true'); expect(onSelectRemoteHost).not.toHaveBeenCalled(); diff --git a/src/web-ui/src/features/ssh-remote/SSHConnectionDialog.test.tsx b/src/web-ui/src/features/ssh-remote/SSHConnectionDialog.test.tsx index 3e0c3591c7..690bc95e6a 100644 --- a/src/web-ui/src/features/ssh-remote/SSHConnectionDialog.test.tsx +++ b/src/web-ui/src/features/ssh-remote/SSHConnectionDialog.test.tsx @@ -526,7 +526,8 @@ describe('SSHConnectionDialog', () => { sshApiMock.listWslDistributions.mockResolvedValue(result); await renderDialog(); await selectWsl(); - expect(container.textContent).toContain(hint); + if (result.supported) expect(container.textContent).toContain(hint); + else expect(container.textContent).not.toContain(hint); expect(findConnectButton()?.disabled).toBe(true); expect(remoteContextMock.connect).not.toHaveBeenCalled(); }); diff --git a/src/web-ui/src/features/ssh-remote/WorkspaceSourceSelector.tsx b/src/web-ui/src/features/ssh-remote/WorkspaceSourceSelector.tsx index 23749a8a15..c584f2479d 100644 --- a/src/web-ui/src/features/ssh-remote/WorkspaceSourceSelector.tsx +++ b/src/web-ui/src/features/ssh-remote/WorkspaceSourceSelector.tsx @@ -39,6 +39,7 @@ export function WorkspaceSourceSelector({ const { location, environment } = workspaceSourceCoordinates(value); const wsl = useWslSupport(location === 'current' && showEnvironment); const wslSupported = wsl.status === 'supported'; + const showWslHint = location === 'current' && (wsl.status === 'checking' || wsl.status === 'unavailable'); useEffect(() => { onWslSupportChange?.(wslSupported); }, [onWslSupportChange, wslSupported]); const wslHintId = useId(); const peerActive = peer?.peerMode.active === true; @@ -86,7 +87,7 @@ export function WorkspaceSourceSelector({
{t('workspaceSource.environment')} onValueChange?.(workspaceSourceFor(location, value as WorkspaceEnvironment))} /> {environmentActions &&
{environmentActions}
}
- {location === 'current' && !wslSupported &&
+ {showWslHint &&
{t(wsl.status === 'checking' ? 'workspaceSource.checkingWsl' - : wsl.status === 'unsupported' ? 'ssh.remote.wslUnsupported' : 'ssh.remote.wslDiscoveryFailed')} + : 'ssh.remote.wslDiscoveryFailed')} {wsl.status === 'unavailable' && }
} {environmentOptions} From 3f69aed9078ff7a0bb17282d21a6e65963e79fe9 Mon Sep 17 00:00:00 2001 From: Bob Lee Date: Sat, 24 Oct 2026 09:35:52 +0800 Subject: [PATCH 4/5] fix(workspaces): omit redundant local host identity --- .../NewProjectDialog.test.tsx | 5 ++-- .../ssh-remote/WorkspaceSourceSelector.tsx | 26 +++++-------------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/web-ui/src/app/components/NewProjectDialog/NewProjectDialog.test.tsx b/src/web-ui/src/app/components/NewProjectDialog/NewProjectDialog.test.tsx index fa33bff0e6..9f3d629e76 100644 --- a/src/web-ui/src/app/components/NewProjectDialog/NewProjectDialog.test.tsx +++ b/src/web-ui/src/app/components/NewProjectDialog/NewProjectDialog.test.tsx @@ -224,11 +224,10 @@ describe('NewProjectDialog composition', () => { expect(pick).not.toHaveBeenCalled(); }); - it('shows the device name below the current-computer header without repeating generic hints', async () => { + it('does not repeat the device name below the current-computer header', async () => { await render({ onPickRemoteFolder: vi.fn() }); const row = document.querySelectorAll('[data-openbitfun-part="sourceField"]')[0]; - expect(row.querySelector('.workspace-source-selector__device')?.textContent).toBe('Workstation'); - expect(row.querySelector('.workspace-source-selector__device svg')?.getAttribute('data-system')).toBe('macos'); + expect(row.querySelector('.workspace-source-selector__device')).toBeNull(); expect(row.querySelector('.workspace-source-selector__heading')?.textContent).not.toContain('Workstation'); expect(document.body.textContent).not.toContain('workspaceSource.currentHostHint'); expect(document.body.textContent).not.toContain('newProject.sourceFoldersHint'); diff --git a/src/web-ui/src/features/ssh-remote/WorkspaceSourceSelector.tsx b/src/web-ui/src/features/ssh-remote/WorkspaceSourceSelector.tsx index c584f2479d..a0919804a7 100644 --- a/src/web-ui/src/features/ssh-remote/WorkspaceSourceSelector.tsx +++ b/src/web-ui/src/features/ssh-remote/WorkspaceSourceSelector.tsx @@ -1,9 +1,8 @@ -import { useEffect, useId, useState, type ReactNode } from 'react'; +import { useEffect, useId, type ReactNode } from 'react'; import { Button, OverflowText, SegmentedControl } from '@openbitfun/ui'; import { useI18n } from '@/infrastructure/i18n'; import { usePeerDeviceModeOptional } from '@/infrastructure/peer-device/peerDeviceContextState'; import { DeviceSystemGlyph } from '@/app/components/NavPanel/components/DeviceSystemGlyph'; -import { remoteConnectAPI } from '@/infrastructure/api/service-api/RemoteConnectAPI'; import { useDeviceDirectory, resolveDeviceNameFrom } from '@/infrastructure/account/deviceDirectory'; import { workspaceSourceCoordinates, workspaceSourceFor, @@ -32,8 +31,6 @@ export function WorkspaceSourceSelector({ const { t } = useI18n('common'); const peer = usePeerDeviceModeOptional(); const directory = useDeviceDirectory(); - const [localDevice, setLocalDevice] = useState<{ id: string; name: string; os?: string; kind?: string }>(); - const [deviceNameFailed, setDeviceNameFailed] = useState(false); const locationId = useId(); const environmentId = useId(); const { location, environment } = workspaceSourceCoordinates(value); @@ -43,21 +40,12 @@ export function WorkspaceSourceSelector({ useEffect(() => { onWslSupportChange?.(wslSupported); }, [onWslSupportChange, wslSupported]); const wslHintId = useId(); const peerActive = peer?.peerMode.active === true; - useEffect(() => { - if (peerActive || location !== 'current') return; - let cancelled = false; - setDeviceNameFailed(false); - void remoteConnectAPI.getDeviceInfo().then(info => { - if (!cancelled) setLocalDevice({ id: info.device_id, name: info.device_name, os: info.device_os ?? undefined, kind: info.device_kind ?? undefined }); - }).catch(() => { if (!cancelled) setDeviceNameFailed(true); }); - return () => { cancelled = true; }; - }, [peerActive, location]); - const deviceId = peer?.peerMode.active ? peer.peerMode.deviceId : directory.localId || localDevice?.id; + const deviceId = peer?.peerMode.active ? peer.peerMode.deviceId : undefined; const metadata = directory.devices.find(device => device.device_id === deviceId); - const fallbackName = peer?.peerMode.active ? peer.peerMode.deviceName : localDevice?.name || ''; + const fallbackName = peer?.peerMode.active ? peer.peerMode.deviceName : ''; const deviceName = deviceId ? resolveDeviceNameFrom(directory.devices, deviceId, fallbackName) : fallbackName; - const deviceKind = metadata?.device_kind ?? (peerActive ? undefined : localDevice?.kind); - const deviceOs = metadata?.device_os ?? (peerActive ? undefined : localDevice?.os); + const deviceKind = metadata?.device_kind; + const deviceOs = metadata?.device_os; return
@@ -76,10 +64,10 @@ export function WorkspaceSourceSelector({ }} /> {locationActions &&
{locationActions}
}
- {location === 'current' &&
{deviceName && } - {deviceName || (deviceNameFailed ? t('workspaceSource.deviceNameUnavailable') : '')} + {deviceName}
} {locationOptions}
From 217e168d3499287195f12468a3cc496fd185638a Mon Sep 17 00:00:00 2001 From: Bob Lee Date: Sat, 24 Oct 2026 09:35:52 +0800 Subject: [PATCH 5/5] fix(chat): hide the corner launcher while a conversation is visible --- src/web-ui/src/app/layout/FloatingMiniChat.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/web-ui/src/app/layout/FloatingMiniChat.tsx b/src/web-ui/src/app/layout/FloatingMiniChat.tsx index 9a696b5d70..665091f702 100644 --- a/src/web-ui/src/app/layout/FloatingMiniChat.tsx +++ b/src/web-ui/src/app/layout/FloatingMiniChat.tsx @@ -20,6 +20,9 @@ import { beginConversationTransfer, endConversationTransfer, isConversationTrans import { sessionSceneWorkspaceKey } from '../services/sessionSceneTarget'; import { useMiniAppStore, MINIAPP_COMPOSER_DRAFT_EVENT, MINIAPP_COMPOSER_FOCUS_EVENT, type MiniAppDraftEventDetail, type MiniAppFocusEventDetail } from '../scenes/miniapps/miniAppStore'; import { useSceneStore } from '../stores/sceneStore'; +import { isSessionSceneId } from '../components/SceneBar/types'; +import { selectedScene } from '../workbench/scenePaneModel'; +import { useSessionPaneLayout } from '../scenes/session/sessionPaneLayoutStore'; import { getMiniAppIdFromSceneId, getMiniAppSceneId } from '../scenes/miniapps/miniAppActivity'; import { followMiniAppConversation, openMiniAppConversation, openMiniAppFromConversation, resolveMiniAppConversation, syncMiniAppConversations } from '../scenes/miniapps/miniAppConversation'; import { postMiniAppComposerMessage } from '../scenes/miniapps/miniAppComposerMessages'; @@ -104,6 +107,10 @@ export function FloatingMiniChat() { const claims = useMiniAppStore(s => s.composerClaims); const apps = useMiniAppStore(s => s.apps); const activeSceneId = useSceneStore(s => s.activeTabId); + const paneSelections = useSceneStore(s => s.paneSelections); + const paneLayout = useSessionPaneLayout(); + const sessionVisible = (paneLayout.mode !== 'content-only' && isSessionSceneId(selectedScene(paneSelections.primary))) + || (paneLayout.mode !== 'chat-only' && isSessionSceneId(selectedScene(paneSelections.secondary))); const openScenes = useSceneStore(s => s.openTabs); const activeMiniAppId = activeSceneId ? getMiniAppIdFromSceneId(activeSceneId) : null; const activeMiniAppClaim = activeMiniAppId ? claims[activeMiniAppId] : undefined; @@ -262,10 +269,10 @@ export function FloatingMiniChat() { data-openbitfun-communication-mode={immersiveVoice ? 'voice' : 'chat'} onDragOver={event => { if (isConversationTransfer(event.dataTransfer)) { event.preventDefault(); event.dataTransfer.dropEffect = 'move'; setDrop(true); dock.setOpen(true); } }} onDragLeave={event => { if (!event.currentTarget.contains(event.relatedTarget as Node)) setDrop(false); }} onDrop={acceptDrop}> - dock.setOpen(true)} onPointerDown={event => { if (event.button === 0) dock.setOpen(true); }}> {live ? : tv('voiceCall.call.launcherCompactLabel')} - + }