From f3aff42b8ae8f6cb19544e0e8af779e1404f9360 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Tue, 16 Jun 2026 14:34:02 +0300 Subject: [PATCH] feat: show context usage as circular progress Replaces static context icons with live circular progress indicators Applies consistent context progress in desktop, mobile, VS Code, and mini-chat headers Keeps usage coloring tied to existing status thresholds --- packages/ui/src/apps/MobileApp.tsx | 60 +++++++++++++++++-- packages/ui/src/components/layout/Header.tsx | 2 +- .../ui/src/components/layout/VSCodeLayout.tsx | 2 +- .../components/mini-chat/MiniChatLayout.tsx | 2 +- .../src/components/ui/ContextUsageDisplay.tsx | 47 +++++++++++++-- 5 files changed, 101 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/apps/MobileApp.tsx b/packages/ui/src/apps/MobileApp.tsx index 1a2f89ca..1dc98318 100644 --- a/packages/ui/src/apps/MobileApp.tsx +++ b/packages/ui/src/apps/MobileApp.tsx @@ -26,7 +26,7 @@ import { opencodeClient } from '@/lib/opencode/client'; import type { ProjectEntry, RuntimeAPIs } from '@/lib/api/types'; import { useI18n } from '@/lib/i18n'; import { resolveProjectForDirectory, resolveProjectForSessionDirectory } from '@/lib/projectResolution'; -import { formatQuotaResetLabel, formatQuotaValueLabel, formatWindowLabel, QUOTA_PROVIDERS } from '@/lib/quota'; +import { clampPercent, formatQuotaResetLabel, formatQuotaValueLabel, formatWindowLabel, QUOTA_PROVIDERS, resolveUsageTone } from '@/lib/quota'; import { getDisplayModelName } from '@/lib/quota/model-families'; import { runtimeFetch } from '@/lib/runtime-fetch'; import { sessionEvents } from '@/lib/sessionEvents'; @@ -144,14 +144,61 @@ const getWindowValueClass = (window: UsageWindow): string => { return 'text-foreground'; }; +const ContextProgressIcon: React.FC<{ percentage: number }> = ({ percentage }) => { + const progressPct = clampPercent(percentage) ?? 0; + const tone = resolveUsageTone(percentage); + const progressColor = tone === 'critical' + ? 'var(--status-error)' + : tone === 'warn' + ? 'var(--status-warning)' + : 'var(--status-success)'; + const size = 18; + const stroke = 3; + const radius = (size - stroke) / 2; + const circumference = 2 * Math.PI * radius; + + return ( + + + + + ); +}; + const MetadataRow: React.FC<{ - icon: IconName; + icon?: IconName; + iconNode?: React.ReactNode; label: string; children: React.ReactNode; -}> = ({ icon, label, children }) => ( +}> = ({ icon, iconNode, label, children }) => (
- + {iconNode ?? (icon ? : null)} {label} @@ -244,7 +291,10 @@ const SessionMetadataOverlay: React.FC<{ {branchLabel} {contextDisplay ? ( - + } + label={t('mobile.header.metadata.context')} + > {contextDisplay.percentage.toFixed(1)}% {contextDisplay.tokens} diff --git a/packages/ui/src/components/layout/Header.tsx b/packages/ui/src/components/layout/Header.tsx index 9a76dff8..b227e023 100644 --- a/packages/ui/src/components/layout/Header.tsx +++ b/packages/ui/src/components/layout/Header.tsx @@ -2203,7 +2203,7 @@ export const Header: React.FC = ({ pressed={isContextPanelActive} className={!showMiniChatHeaderAction ? 'mr-3.5' : ''} valueClassName="typography-ui-label font-medium leading-none text-foreground" - percentIconClassName="h-5 w-5" + percentIconClassName="h-4.5 w-4.5" /> ) : null} {desktopChangesPanelAction} diff --git a/packages/ui/src/components/layout/VSCodeLayout.tsx b/packages/ui/src/components/layout/VSCodeLayout.tsx index 5ac2264b..528cc60c 100644 --- a/packages/ui/src/components/layout/VSCodeLayout.tsx +++ b/packages/ui/src/components/layout/VSCodeLayout.tsx @@ -1018,7 +1018,7 @@ const VSCodeHeader: React.FC = ({ title, showBack, onBack, on valueClassName="font-semibold leading-none" hideIcon showPercentIcon - percentIconClassName="h-5 w-5" + percentIconClassName="h-4.5 w-4.5" /> )}
diff --git a/packages/ui/src/components/mini-chat/MiniChatLayout.tsx b/packages/ui/src/components/mini-chat/MiniChatLayout.tsx index f38b6e6b..229d4cf9 100644 --- a/packages/ui/src/components/mini-chat/MiniChatLayout.tsx +++ b/packages/ui/src/components/mini-chat/MiniChatLayout.tsx @@ -293,7 +293,7 @@ const MiniChatHeader: React.FC<{ mode: MiniChatMode }> = ({ mode }) => { valueClassName="font-semibold leading-none" hideIcon showPercentIcon - percentIconClassName="h-5 w-5" + percentIconClassName="h-4.5 w-4.5" /> ) : null}