From eaf91ab1d492d43729891b0f41c82af494921577 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Tue, 2 Jun 2026 16:37:01 +0300 Subject: [PATCH] feat: refine mobile session status bar header Remove the per-row agent color dot (and its now-unused helpers), keeping only the status indicator. Add a "+" button to the panel header that opens a new session draft targeting the filtered project (or the most recently active session's project under "All", falling back to the default target), and restyle the close button to match it. Show the context token usage next to the percentage (e.g. 5.1% 20.4K/400.0K) at a larger, consistent size, and compute the percentage with a fraction instead of the store's integer-rounded value. --- .../chat/MobileSessionStatusBar.tsx | 94 ++++++++++++------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/packages/ui/src/components/chat/MobileSessionStatusBar.tsx b/packages/ui/src/components/chat/MobileSessionStatusBar.tsx index 4ec54224..630cfd86 100644 --- a/packages/ui/src/components/chat/MobileSessionStatusBar.tsx +++ b/packages/ui/src/components/chat/MobileSessionStatusBar.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { useSessionUIStore } from '@/sync/session-ui-store'; -import { useSelectionStore } from '@/sync/selection-store'; import { useAllSessionStatuses, useAllLiveSessions } from '@/sync/sync-context'; import { useGlobalSessionsStore, ensureGlobalSessionsLoaded, refreshGlobalSessions } from '@/stores/useGlobalSessionsStore'; import { useConfigStore } from '@/stores/useConfigStore'; @@ -9,7 +8,6 @@ import { useProjectsStore } from '@/stores/useProjectsStore'; import type { Session } from '@opencode-ai/sdk/v2'; import type { ProjectEntry } from '@/lib/api/types'; import { cn, formatDirectoryName } from '@/lib/utils'; -import { getAgentColor } from '@/lib/agentColors'; import type { SessionContextUsage } from '@/stores/types/sessionTypes'; import { PROJECT_ICON_MAP, PROJECT_COLOR_MAP, ProjectIconImage } from '@/lib/projectMeta'; import { useDirectoryStore } from '@/stores/useDirectoryStore'; @@ -172,17 +170,7 @@ function useSessionGrouping( return { sessions: processedSessions, totalRunning, totalUnread, totalCount: processedSessions.length }; } -function useSessionHelpers(agents: Array<{ name: string }>) { - const getSessionAgentName = React.useCallback((session: Session): string => { - const agent = (session as { agent?: string }).agent; - if (agent) return agent; - - const sessionAgentSelection = useSelectionStore.getState().getSessionAgentSelection(session.id); - if (sessionAgentSelection) return sessionAgentSelection; - - return agents[0]?.name ?? 'agent'; - }, [agents]); - +function useSessionHelpers() { const getSessionTitle = React.useCallback((session: Session): string => { const title = session.title; if (title && title.trim()) return title; @@ -194,7 +182,7 @@ function useSessionHelpers(agents: Array<{ name: string }>) { return (unseenCounts[sessionId] ?? 0) > 0; }, [unseenCounts]); - return { getSessionAgentName, getSessionTitle, needsAttention }; + return { getSessionTitle, needsAttention }; } // Per-project status indicators (running / unread) for the filter chips. @@ -300,14 +288,29 @@ function UnreadIndicator({ count }: { count: number }) { function TokenUsageIndicator({ contextUsage }: { contextUsage: SessionContextUsage | null }) { if (!contextUsage || contextUsage.totalTokens === 0) return null; - const percentage = Math.min(contextUsage.percentage, 999); + // Recompute with a fraction: contextUsage.percentage is rounded to an integer + // in the store, which would always render as "X.0%". + const percentage = contextUsage.contextLimit > 0 + ? Math.min((contextUsage.totalTokens / contextUsage.contextLimit) * 100, 999) + : 0; const colorClass = percentage >= 90 ? 'text-[var(--status-error)]' : percentage >= 75 ? 'text-[var(--status-warning)]' : 'text-[var(--status-success)]'; + const formatTokens = (value: number): string => { + if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`; + if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`; + return String(value); + }; + + const tokens = contextUsage.contextLimit > 0 + ? `${formatTokens(contextUsage.totalTokens)}/${formatTokens(contextUsage.contextLimit)}` + : formatTokens(contextUsage.totalTokens); + return ( - - {percentage.toFixed(1)}% + + {percentage.toFixed(1)}% + {tokens} ); } @@ -316,20 +319,16 @@ function TokenUsageIndicator({ contextUsage }: { contextUsage: SessionContextUsa function SessionItem({ session, isCurrent, - getSessionAgentName, getSessionTitle, onClick, needsAttention, }: { session: SessionWithStatus; isCurrent: boolean; - getSessionAgentName: (s: Session) => string; getSessionTitle: (s: Session) => string; onClick: () => void; needsAttention: (sessionId: string) => boolean; }) { - const agentName = getSessionAgentName(session); - const agentColor = getAgentColor(agentName); const attention = needsAttention(session.id); return ( @@ -346,11 +345,6 @@ function SessionItem({ - - = ({ const currentSessionId = useSessionUIStore((state) => state.currentSessionId); const sessionStatus = useAllSessionStatuses(); const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession); + const openNewSessionDraft = useSessionUIStore((state) => state.openNewSessionDraft); const getContextUsage = useSessionUIStore((state) => state.getContextUsage); - const agents = useConfigStore((state) => state.agents); const getCurrentModel = useConfigStore((state) => state.getCurrentModel); const isMobile = useUIStore((state) => state.isMobile); const showMobileSessionStatusBar = useUIStore((state) => state.showMobileSessionStatusBar); @@ -512,7 +506,7 @@ export const MobileSessionStatusBar: React.FC = ({ const homeDirectory = useDirectoryStore((state) => state.homeDirectory); const { sessions: sortedSessions, totalRunning, totalUnread } = useSessionGrouping(sessions, sessionStatus); - const { getSessionAgentName, getSessionTitle, needsAttention } = useSessionHelpers(agents); + const { getSessionTitle, needsAttention } = useSessionHelpers(); const getProjectStatus = useProjectStatus(sessions, sessionStatus, currentSessionId); const resolveProjectRoots = useProjectRootsResolver(); @@ -572,7 +566,28 @@ export const MobileSessionStatusBar: React.FC = ({ setOpen(false); }; - const renderHeader = React.useCallback((closeButton: React.ReactNode) => ( + // "+" — start a new session draft. Target the project selected in the filter; + // for "All", use the most recently active session's directory, falling back to + // the store's own default target when there are no sessions. + const handleNewChat = React.useCallback(() => { + setOpen(false); + if (filterProjectId) { + const project = projects.find((p) => p.id === filterProjectId); + if (project) { + openNewSessionDraft({ selectedProjectId: project.id, directoryOverride: project.path }); + return; + } + } + const mostRecent = [...sessions].sort((a, b) => { + const aTime = (a as { time?: { updated?: number } }).time?.updated ?? 0; + const bTime = (b as { time?: { updated?: number } }).time?.updated ?? 0; + return bTime - aTime; + })[0]; + const directory = mostRecent ? sessionDirectory(mostRecent) : ''; + openNewSessionDraft(directory ? { directoryOverride: directory } : undefined); + }, [filterProjectId, projects, sessions, openNewSessionDraft, setOpen]); + + const renderHeader = React.useCallback(() => (
@@ -586,7 +601,23 @@ export const MobileSessionStatusBar: React.FC = ({ - {closeButton} + +
@@ -620,7 +651,7 @@ export const MobileSessionStatusBar: React.FC = ({
)} - ), [t, totalRunning, totalUnread, contextUsage, projects, filterProjectId, setFilterProjectId, formatProjectLabel, currentTheme, getProjectStatus]); + ), [t, totalRunning, totalUnread, contextUsage, projects, filterProjectId, setFilterProjectId, formatProjectLabel, currentTheme, getProjectStatus, handleNewChat, setOpen]); if (!isMobile || !showMobileSessionStatusBar) { return null; @@ -646,7 +677,6 @@ export const MobileSessionStatusBar: React.FC = ({ key={session.id} session={session} isCurrent={session.id === currentSessionId} - getSessionAgentName={getSessionAgentName} getSessionTitle={getSessionTitle} onClick={() => handleSessionClick(session)} needsAttention={needsAttention}