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
@@ -1,6 +1,6 @@
import React, { lazy, Suspense, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
import { createPortal } from 'react-dom';
import { Folder, FolderOpen, MoreHorizontal, FolderSearch, Plus, ChevronDown, Trash2, RotateCcw, Copy, FileText, Bot, Link2, ListChecks, Loader2, Clock3, ShieldCheck, Pencil } from 'lucide-react';
import { Folder, FolderOpen, MoreHorizontal, FolderSearch, Plus, ChevronDown, Trash2, RotateCcw, Copy, FileText, Bot, Link2, ListChecks, Loader2, Clock3, ShieldCheck, Pencil, Server } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { DotMatrixArrowRightIcon } from './DotMatrixArrowRightIcon';
import { Button, ConfirmDialog, InputDialog, Modal, Tooltip } from '@/component-library';
Expand Down Expand Up @@ -202,6 +202,35 @@ const WorkspaceItem: React.FC<WorkspaceItemProps> = ({
? sshContext.workspaceStatuses[workspace.connectionId]
: undefined;

const remoteMeta = useMemo(() => {
if (!workspaceIsRemote) {
return null;
}

const status = remoteConnStatus ?? 'unknown';
const statusLabel = t(`nav.workspaces.remote.status.${status}`, {
defaultValue: t('nav.workspaces.remote.status.unknown'),
});
const connectionLabel = workspace.connectionName?.trim() || workspace.sshHost?.trim() || '';

return {
status,
statusLabel,
connectionLabel,
/** A green dot already reads as "fine"; spell out only the states that need attention. */
showStatusText: status !== 'connected',
tooltip: t('nav.workspaces.remote.tooltip', {
connection: connectionLabel,
host: workspace.sshHost?.trim() || connectionLabel,
status: statusLabel,
}),
ariaLabel: t('nav.workspaces.remote.ariaLabel', {
connection: connectionLabel,
status: statusLabel,
}),
};
}, [remoteConnStatus, t, workspace.connectionName, workspace.sshHost, workspaceIsRemote]);

const searchIndexIndicator = useMemo(() => {
if (!canShowSearchIndex) {
return null;
Expand Down Expand Up @@ -1016,6 +1045,8 @@ const WorkspaceItem: React.FC<WorkspaceItemProps> = ({
<span className="bitfun-nav-panel__workspace-item-active-icon">
<DotMatrixArrowRightIcon size={14} />
</span>
) : workspaceIsRemote ? (
<Server size={14} />
) : (
<FolderOpen size={14} />
)}
Expand All @@ -1026,7 +1057,7 @@ const WorkspaceItem: React.FC<WorkspaceItemProps> = ({
</span>
</button>
<div className="bitfun-nav-panel__workspace-item-name-cluster">
<div className="bitfun-nav-panel__workspace-item-name-stack">
<div className={`bitfun-nav-panel__workspace-item-name-stack${remoteMeta ? ' is-remote' : ''}`}>
<div className="bitfun-nav-panel__workspace-item-name-row">
<Tooltip content={workspace.rootPath} placement="right" followCursor>
<button
Expand Down Expand Up @@ -1161,14 +1192,31 @@ const WorkspaceItem: React.FC<WorkspaceItemProps> = ({
</>
)}
</div>
{isRemoteWorkspace(workspace) && (
<span className="bitfun-nav-panel__workspace-item-subtitle">
<span
className={`bitfun-nav-panel__workspace-item-status-dot is-${remoteConnStatus ?? 'unknown'}`}
aria-label={remoteConnStatus ?? 'unknown'}
/>
<span>{workspace.connectionName}</span>
</span>
{remoteMeta && (
<div className="bitfun-nav-panel__workspace-item-subtitle">
<Tooltip content={remoteMeta.tooltip} placement="right" followCursor>
<span
className={`bitfun-nav-panel__workspace-item-remote is-${remoteMeta.status}`}
role="img"
aria-label={remoteMeta.ariaLabel}
data-testid="nav-workspace-remote-meta"
data-remote-status={remoteMeta.status}
>
<span
className={`bitfun-nav-panel__workspace-item-status-dot is-${remoteMeta.status}`}
aria-hidden="true"
/>
<span className="bitfun-nav-panel__workspace-item-remote-name">
{remoteMeta.connectionLabel}
</span>
{remoteMeta.showStatusText ? (
<span className="bitfun-nav-panel__workspace-item-remote-status">
{remoteMeta.statusLabel}
</span>
) : null}
</span>
</Tooltip>
</div>
)}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,29 @@
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: center;
gap: 1px;
flex: 1 1 0;
min-width: 0;
max-width: 100%;
padding: 0 4px;
/** Row actions appear on hover and widen the reserved gutter — glide instead of snapping. */
transition: padding-right $motion-fast $easing-standard;

/**
* Remote rows carry a second metadata line. Collapse the single-line 30px
* title block so both lines read as one centred unit at the same row rhythm
* as local workspaces instead of a top-heavy title with an orphaned subtitle.
*/
&.is-remote {
gap: 1px;
padding-top: 2px;
padding-bottom: 2px;

.bitfun-nav-panel__workspace-item-name-btn {
min-height: 17px;
}
}
}

&__workspace-item-name-row {
Expand Down Expand Up @@ -448,48 +466,83 @@
}

&__workspace-item-subtitle {
display: flex;
align-items: center;
min-width: 0;
max-width: 100%;
}

/**
* Remote connection chip. Shares the branch-pill language so remote metadata
* reads as a deliberate part of the row rather than a stray second line.
*/
&__workspace-item-remote {
display: inline-flex;
align-items: center;
gap: 4px;
gap: 5px;
min-width: 0;
max-width: 100%;
color: var(--color-text-muted);
padding: 0 6px 0 5px;
border-radius: 999px;
background: color-mix(in srgb, var(--element-bg-medium) 62%, transparent);
color: var(--color-text-secondary);
font-size: var(--font-size-xxs);
line-height: 1.2;
opacity: 0.75;
line-height: 1.6;
cursor: default;
transition: background $motion-fast $easing-standard,
color $motion-fast $easing-standard;

svg {
flex-shrink: 0;
color: var(--color-success);
&.is-error,
&.is-disconnected {
background: color-mix(in srgb, var(--color-error) 14%, transparent);
color: var(--color-error);
}

span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&.is-connecting {
background: color-mix(in srgb, var(--color-warning) 14%, transparent);
color: var(--color-warning);
}
}

&__workspace-item-remote-name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

&__workspace-item-remote-status {
flex-shrink: 0;
white-space: nowrap;
opacity: 0.85;

&::before {
content: '·';
margin-right: 4px;
opacity: 0.6;
}
}

&__workspace-item:not(.is-active) &__workspace-item-remote {
opacity: 0.8;
}

&__workspace-item-status-dot {
flex-shrink: 0;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--color-text-muted);

&.is-connected {
background: var(--color-success);
}

&.is-unknown,
&.is-error,
&.is-disconnected {
background: var(--color-error);
}

&.is-unknown {
background: var(--color-text-muted);
}

&.is-connecting {
background: var(--color-warning);
animation: bitfun-status-dot-pulse 1.4s ease-in-out infinite;
Expand Down
11 changes: 11 additions & 0 deletions src/web-ui/src/locales/en-US/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,17 @@
"projects": "Projects",
"assistants": "Personal Assistants"
},
"remote": {
"tooltip": "Remote workspace · {{connection}} ({{host}})\nConnection: {{status}}",
"ariaLabel": "Remote connection {{connection}}, {{status}}",
"status": {
"connected": "Connected",
"connecting": "Connecting",
"disconnected": "Disconnected",
"error": "Connection error",
"unknown": "Status unknown"
}
},
"actions": {
"more": "More actions",
"newSession": "New session",
Expand Down
11 changes: 11 additions & 0 deletions src/web-ui/src/locales/zh-CN/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,17 @@
"projects": "普通工作区",
"assistants": "个人助理"
},
"remote": {
"tooltip": "远程工作区 · {{connection}}({{host}})\n连接状态:{{status}}",
"ariaLabel": "远程连接 {{connection}},{{status}}",
"status": {
"connected": "已连接",
"connecting": "连接中",
"disconnected": "已断开",
"error": "连接异常",
"unknown": "状态未知"
}
},
"actions": {
"more": "更多操作",
"newSession": "新建会话",
Expand Down
11 changes: 11 additions & 0 deletions src/web-ui/src/locales/zh-TW/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,17 @@
"projects": "普通工作區",
"assistants": "個人助理"
},
"remote": {
"tooltip": "遠端工作區 · {{connection}}({{host}})\n連線狀態:{{status}}",
"ariaLabel": "遠端連線 {{connection}},{{status}}",
"status": {
"connected": "已連線",
"connecting": "連線中",
"disconnected": "已中斷",
"error": "連線異常",
"unknown": "狀態未知"
}
},
"actions": {
"more": "更多操作",
"newSession": "新增會話",
Expand Down