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 @@ -29,9 +29,9 @@ import { stateMachineManager } from '@/flow_chat/state-machine';
import { SessionExecutionState } from '@/flow_chat/state-machine/types';
import './SessionsSection.scss';

const MAX_VISIBLE_SESSIONS = 8;
const INACTIVE_WORKSPACE_COLLAPSED_SESSIONS = 3;
const INACTIVE_WORKSPACE_EXPANDED_SESSIONS = 7;
/** Top-level parent sessions shown at each expand step (children still nest under visible parents). */
const SESSIONS_LEVEL_0 = 5;
const SESSIONS_LEVEL_1 = 10;
const log = createLogger('SessionsSection');
const AGENT_SCENE: SceneTabId = 'session';

Expand Down Expand Up @@ -71,7 +71,7 @@ const SessionsSection: React.FC<SessionsSectionProps> = ({
);
const [editingSessionId, setEditingSessionId] = useState<string | null>(null);
const [editingTitle, setEditingTitle] = useState('');
const [showAll, setShowAll] = useState(false);
const [expandLevel, setExpandLevel] = useState<0 | 1 | 2>(0);
const [openMenuSessionId, setOpenMenuSessionId] = useState<string | null>(null);
const [sessionMenuPosition, setSessionMenuPosition] = useState<{ top: number; left: number } | null>(null);
const [runningSessionIds, setRunningSessionIds] = useState<Set<string>>(new Set());
Expand Down Expand Up @@ -111,8 +111,8 @@ const SessionsSection: React.FC<SessionsSectionProps> = ({
}, [editingSessionId]);

useEffect(() => {
setShowAll(false);
}, [workspaceId, workspacePath, isActiveWorkspace]);
setExpandLevel(0);
}, [workspaceId, workspacePath]);

useEffect(() => {
if (!openMenuSessionId) return;
Expand Down Expand Up @@ -167,16 +167,11 @@ const SessionsSection: React.FC<SessionsSectionProps> = ({
}, [sessions]);

const sessionDisplayLimit = useMemo(() => {
if (isActiveWorkspace) {
return showAll || topLevelSessions.length <= MAX_VISIBLE_SESSIONS
? topLevelSessions.length
: MAX_VISIBLE_SESSIONS;
}

return showAll
? Math.min(topLevelSessions.length, INACTIVE_WORKSPACE_EXPANDED_SESSIONS)
: Math.min(topLevelSessions.length, INACTIVE_WORKSPACE_COLLAPSED_SESSIONS);
}, [isActiveWorkspace, topLevelSessions.length, showAll]);
const total = topLevelSessions.length;
if (expandLevel === 2 || total <= SESSIONS_LEVEL_0) return total;
if (expandLevel === 1) return Math.min(total, SESSIONS_LEVEL_1);
return SESSIONS_LEVEL_0;
}, [topLevelSessions.length, expandLevel]);

const visibleItems = useMemo(() => {
const visibleParents = topLevelSessions.slice(0, sessionDisplayLimit);
Expand All @@ -189,11 +184,6 @@ const SessionsSection: React.FC<SessionsSectionProps> = ({
return out;
}, [childrenByParent, sessionDisplayLimit, topLevelSessions]);

const toggleThreshold = isActiveWorkspace
? MAX_VISIBLE_SESSIONS
: INACTIVE_WORKSPACE_COLLAPSED_SESSIONS;
const hiddenCount = Math.max(0, topLevelSessions.length - toggleThreshold);

const activeSessionId = flowChatState.activeSessionId;

const handleSwitch = useCallback(
Expand Down Expand Up @@ -493,19 +483,43 @@ const SessionsSection: React.FC<SessionsSectionProps> = ({
})
)}

{topLevelSessions.length > toggleThreshold && (
{topLevelSessions.length > SESSIONS_LEVEL_0 && (
<button
type="button"
className="bitfun-nav-panel__inline-toggle"
onClick={() => setShowAll(prev => !prev)}
onClick={() => {
const total = topLevelSessions.length;
if (expandLevel === 0) {
setExpandLevel(1);
return;
}
if (expandLevel === 1 && total > SESSIONS_LEVEL_1) {
setExpandLevel(2);
return;
}
setExpandLevel(0);
}}
>
{showAll ? (
<span>{t('nav.sessions.showLess')}</span>
) : (
{expandLevel === 0 ? (
<>
<span className="bitfun-nav-panel__inline-toggle-dots">···</span>
<span>{t('nav.sessions.showMore', { count: hiddenCount })}</span>
<span>
{t('nav.sessions.showMore', {
count: topLevelSessions.length - SESSIONS_LEVEL_0,
})}
</span>
</>
) : expandLevel === 1 && topLevelSessions.length > SESSIONS_LEVEL_1 ? (
<>
<span className="bitfun-nav-panel__inline-toggle-dots">···</span>
<span>
{t('nav.sessions.showAll', {
count: topLevelSessions.length - SESSIONS_LEVEL_1,
})}
</span>
</>
) : (
<span>{t('nav.sessions.showLess')}</span>
)}
</button>
)}
Expand Down
26 changes: 10 additions & 16 deletions src/web-ui/src/app/layout/WorkspaceBody.scss
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ $_nav-collapsed-width: 80px;
height: 40px;
}

// ── Nav divider ────────────────────────────────────────

.bitfun-workspace-body__nav-divider {
position: absolute;
top: 0;
Expand All @@ -76,28 +78,20 @@ $_nav-collapsed-width: 80px;
height: 100%;
cursor: ew-resize;
z-index: 10;
background: transparent;

&::after {
content: '';
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 100%;
background: var(--color-primary);
opacity: 0;
border-radius: 1px;
transition: opacity $motion-fast $easing-standard;
}

&:hover::after {
opacity: 0.35;
content: none;
}
}

// Active resizing state — keep line visible while dragging
.bitfun-is-resizing-nav .bitfun-workspace-body__nav-divider::after {
opacity: 0.6;
content: '';
opacity: 1;
width: 3px;
background: var(--color-accent-500);
box-shadow: 0 0 10px rgba(96, 165, 250, 0.50);
}

// ── Right column: rounded scene card (SceneBar + SceneViewport) ───
Expand Down
16 changes: 15 additions & 1 deletion src/web-ui/src/app/layout/WorkspaceBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ const WorkspaceBody: React.FC<WorkspaceBodyProps> = ({
const { state, toggleLeftPanel } = useApp();
const isNavCollapsed = state.layout.leftPanelCollapsed;
const [navWidth, setNavWidth] = useState(NAV_DEFAULT_WIDTH);
const [isDividerHovered, setIsDividerHovered] = useState(false);

const handleDividerMouseEnter = useCallback(() => {
setIsDividerHovered(true);
document.body.classList.add('bitfun-divider-hovered');
}, []);

const handleDividerMouseLeave = useCallback(() => {
setIsDividerHovered(false);
document.body.classList.remove('bitfun-divider-hovered');
}, []);

const handleNavCollapseDragStart = useCallback((event: React.MouseEvent<HTMLDivElement>) => {
if (event.button !== 0 || isNavCollapsed) return;
Expand All @@ -64,6 +75,7 @@ const WorkspaceBody: React.FC<WorkspaceBodyProps> = ({
const cleanup = () => {
document.body.classList.remove('bitfun-is-dragging-nav-collapse');
document.body.classList.remove('bitfun-is-resizing-nav');
document.body.classList.remove('bitfun-divider-hovered');
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
};
Expand Down Expand Up @@ -91,7 +103,7 @@ const WorkspaceBody: React.FC<WorkspaceBodyProps> = ({
}, [isNavCollapsed, navWidth, toggleLeftPanel]);

return (
<div className={`bitfun-workspace-body${isEntering ? ' is-entering' : ''}${isExiting ? ' is-exiting' : ''} ${className}`}>
<div className={`bitfun-workspace-body${isEntering ? ' is-entering' : ''}${isExiting ? ' is-exiting' : ''}${isDividerHovered ? ' is-divider-hovered' : ''} ${className}`}>
{isNavCollapsed && (
<div className="bitfun-workspace-body__collapsed-nav">
<NavBar isCollapsed onExpandNav={toggleLeftPanel} onMaximize={onMaximize} />
Expand All @@ -113,6 +125,8 @@ const WorkspaceBody: React.FC<WorkspaceBodyProps> = ({
className="bitfun-workspace-body__nav-divider"
style={{ '--nav-width': `${navWidth}px` } as React.CSSProperties}
onMouseDown={handleNavCollapseDragStart}
onMouseEnter={handleDividerMouseEnter}
onMouseLeave={handleDividerMouseLeave}
role="separator"
aria-hidden="true"
/>
Expand Down
27 changes: 27 additions & 0 deletions src/web-ui/src/app/scenes/SceneViewport.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
border-radius: $size-radius-base;
border: 1px solid var(--border-subtle);
background: var(--color-bg-scene);
transition:
border-color 240ms cubic-bezier(0.25, 1, 0.5, 1),
box-shadow 240ms cubic-bezier(0.25, 1, 0.5, 1);

// ── Welcome overlay (app start) ──────────────────────

Expand Down Expand Up @@ -49,3 +52,27 @@
}
}
}

// ── Drag handle hover / active glow effect ──────────────────────────────

body.bitfun-divider-hovered .bitfun-scene-viewport {
border-color: var(--border-accent-strong);
box-shadow:
inset 6px 0 32px -4px rgba(96, 165, 250, 0.22),
inset 0 0 60px -16px rgba(96, 165, 250, 0.10),
-3px 0 20px -4px rgba(96, 165, 250, 0.30);
}

body.bitfun-is-resizing-nav .bitfun-scene-viewport {
border-color: var(--border-accent-strong);
box-shadow:
inset 4px 0 24px -4px rgba(96, 165, 250, 0.20),
inset 0 0 48px -16px rgba(96, 165, 250, 0.08),
-2px 0 18px -4px rgba(96, 165, 250, 0.28);
}

@media (prefers-reduced-motion: reduce) {
.bitfun-scene-viewport {
transition: none;
}
}
14 changes: 0 additions & 14 deletions src/web-ui/src/app/scenes/agents/AgentsScene.scss
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
@use '../../../component-library/styles/tokens' as *;

.bitfun-agents-scene {
.gallery-page-header__title {
margin: 0;
font-size: clamp(24px, 3vw, 32px);
line-height: 1.08;
letter-spacing: -0.03em;
}

.gallery-page-header__subtitle {
margin-top: $size-gap-2;
max-width: 720px;
font-size: $font-size-sm;
color: var(--color-text-secondary);
}

.gallery-zone__subtitle {
font-size: $font-size-sm;
color: var(--color-text-secondary);
Expand Down
Loading
Loading