Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const DateTimePickerPopover: React.FC<DateTimePickerPopoverProps> = ({
onSelect,
onClose,
}) => {
const { t, formatDate } = useI18n('common');
const { t, formatDate, formatNumber } = useI18n('common');
const popoverRef = useRef<HTMLDivElement | null>(null);

const [focusedDayMs, setFocusedDayMs] = useState(() => {
Expand Down Expand Up @@ -239,7 +239,7 @@ const DateTimePickerPopover: React.FC<DateTimePickerPopoverProps> = ({
onFocus={() => setFocusedDayMs(day.getTime())}
onClick={() => selectDay(day)}
>
{formatDate(day, { day: 'numeric' })}
{formatNumber(day.getDate(), { useGrouping: false })}
</button>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}),
}));
Expand Down
11 changes: 9 additions & 2 deletions src/web-ui/src/app/layout/FloatingMiniChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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}>
<LauncherButton className="openbitfun-fmc__button" aria-expanded={dock.open} aria-label={t('dock.open')}
{!sessionVisible && <LauncherButton className="openbitfun-fmc__button" aria-expanded={dock.open} aria-label={t('dock.open')}
onClick={() => dock.setOpen(true)} onPointerDown={event => { if (event.button === 0) dock.setOpen(true); }}>
{live ? <Phone size={16} /> : tv('voiceCall.call.launcherCompactLabel')}
</LauncherButton>
</LauncherButton>}
<div ref={panelRef} role="dialog" aria-modal="false" aria-label={t('dock.open')} aria-hidden={!dock.open} data-motion="presence"
{...(!dock.open ? { inert: '' } : {})}
className={['openbitfun-fmc__panel', dock.open && 'openbitfun-fmc__panel--open', drop && 'openbitfun-fmc__panel--drop'].filter(Boolean).join(' ')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down
33 changes: 11 additions & 22 deletions src/web-ui/src/features/ssh-remote/WorkspaceSourceSelector.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -32,31 +31,21 @@ 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);
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;
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 <div className="workspace-source-selector" data-openbitfun-component="ssh-remote" data-openbitfun-part="sourceSelector">
<div className="workspace-source-selector__field" data-openbitfun-component="ssh-remote" data-openbitfun-part="sourceField">
<div className="workspace-source-selector__heading">
Expand All @@ -75,18 +64,18 @@ export function WorkspaceSourceSelector({
}} />
{locationActions && <div className="workspace-source-selector__actions">{locationActions}</div>}
</div>
{location === 'current' && <div className="workspace-source-selector__device" role="status"
{location === 'current' && peerActive && <div className="workspace-source-selector__device" role="status"
data-openbitfun-component="ssh-remote" data-openbitfun-part="sourceHint">
{deviceName && <DeviceSystemGlyph size="sm" device={{ kind: deviceKind === 'cli' ? 'execution-host' : 'desktop', name: deviceName, os: deviceOs ?? undefined, hostKind: deviceKind === 'cli' ? 'cli' : 'desktop' }} />}
<OverflowText>{deviceName || (deviceNameFailed ? t('workspaceSource.deviceNameUnavailable') : '')}</OverflowText>
<OverflowText>{deviceName}</OverflowText>
</div>}
{locationOptions}
</div>
{showEnvironment && <div className="workspace-source-selector__field" data-openbitfun-component="ssh-remote" data-openbitfun-part="sourceField">
<div className="workspace-source-selector__heading">
<span id={environmentId} className="workspace-source-selector__label" data-openbitfun-component="ssh-remote" data-openbitfun-part="sourceLabel">{t('workspaceSource.environment')}</span>
<SegmentedControl className="workspace-source-selector__control" size="sm" distribution="content" aria-labelledby={environmentId}
value={environment} disabled={disabled} aria-describedby={location === 'current' && !wslSupported ? wslHintId : undefined}
value={environment} disabled={disabled} aria-describedby={showWslHint ? wslHintId : undefined}
options={[
{ value: 'system', label: t('workspaceSource.system'), disabled: location === 'current' && !allowHostDirectory },
{ value: 'container', label: t('workspaceSource.container') },
Expand All @@ -95,9 +84,9 @@ export function WorkspaceSourceSelector({
onValueChange={value => onValueChange?.(workspaceSourceFor(location, value as WorkspaceEnvironment))} />
{environmentActions && <div className="workspace-source-selector__actions">{environmentActions}</div>}
</div>
{location === 'current' && !wslSupported && <div id={wslHintId} className="workspace-source-selector__device" role="status">
{showWslHint && <div id={wslHintId} className="workspace-source-selector__device" role="status">
<span>{t(wsl.status === 'checking' ? 'workspaceSource.checkingWsl'
: wsl.status === 'unsupported' ? 'ssh.remote.wslUnsupported' : 'ssh.remote.wslDiscoveryFailed')}</span>
: 'ssh.remote.wslDiscoveryFailed')}</span>
{wsl.status === 'unavailable' && <Button size="sm" variant="quiet" disabled={disabled} onClick={wsl.retry}>{t('workspaceSource.refreshOptions')}</Button>}
</div>}
{environmentOptions}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading