Skip to content

Commit 6cbee1b

Browse files
author
IM.codes
committed
Refine footer thinking status icons
1 parent 0e7fd53 commit 6cbee1b

3 files changed

Lines changed: 49 additions & 9 deletions

File tree

web/src/components/UsageFooter.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ export function UsageFooter({ usage, sessionName, sessionState, agentType, model
9595
const monthlyCost = sessionCost > 0 ? getMonthlyCost() : 0;
9696
const modelLabel = shortModelLabel(displayModel);
9797
const inlineQuotaText = displayQuotaLabel;
98-
const liveStatusMode = sessionState === 'running' ? 'running' : sessionState === 'idle' ? 'idle' : null;
98+
const liveStatusMode = sessionState === 'running'
99+
? (statusText ? 'tool' : activeThinkingTs ? 'thinking' : 'running')
100+
: sessionState === 'idle' ? 'idle' : null;
99101
const liveStatusText = useMemo(() => {
100102
if (sessionState === 'running') {
101103
if (statusText) return statusText;
@@ -105,6 +107,7 @@ export function UsageFooter({ usage, sessionName, sessionState, agentType, model
105107
if (sessionState === 'idle') return 'Agent idle — waiting for input';
106108
return null;
107109
}, [activeThinkingTs, now, sessionState, statusText, t]);
110+
const showInlineStatusText = liveStatusMode === 'running' || liveStatusMode === 'thinking' || liveStatusMode === 'tool';
108111
const codexQuotaLines = (agentType === 'codex' || agentType === 'codex-sdk')
109112
? (displayQuotaLabel ?? '').split(' · ').filter(Boolean)
110113
: [];
@@ -128,9 +131,11 @@ export function UsageFooter({ usage, sessionName, sessionState, agentType, model
128131
{showLiveStatus && liveStatusText && liveStatusMode && (
129132
<span class={`session-live-status-inline ${liveStatusMode}`} title={liveStatusText} aria-label={liveStatusText}>
130133
<span class="session-live-status-emoji robot">🤖</span>
131-
{liveStatusMode === 'running'
132-
? <span class="session-live-status-emoji gear">⚙️</span>
133-
: <span class="session-live-status-emoji sleep">💤</span>}
134+
{liveStatusMode === 'running' && <span class="session-live-status-emoji gear">⚙️</span>}
135+
{liveStatusMode === 'thinking' && <span class="session-live-status-emoji thought">💭</span>}
136+
{liveStatusMode === 'tool' && <span class="session-live-status-emoji tool">🔍</span>}
137+
{liveStatusMode === 'idle' && <span class="session-live-status-emoji sleep">💤</span>}
138+
{showInlineStatusText && <span class="session-live-status-text">{liveStatusText}</span>}
134139
</span>
135140
)}
136141
<span style={{ marginLeft: 'auto', display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap', justifyContent: 'flex-end' }}>

web/src/styles.css

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,17 +858,29 @@ body {
858858
.session-ctx-input { position: absolute; left: 0; top: 0; height: 100%; background: #34d399; border-radius: 3px; }
859859
.session-ctx-cache { position: absolute; left: 0; top: 0; height: 100%; background: #818cf8; border-radius: 3px; }
860860
.session-usage-stats { display: flex; justify-content: space-between; font-size: 10px; color: #475569; }
861-
.session-live-status-inline { display: inline-flex; align-items: center; justify-content: center; gap: 1px; min-width: 20px; color: #818cf8; }
861+
.session-live-status-inline { display: inline-flex; align-items: center; justify-content: center; gap: 1px; min-width: 20px; color: #818cf8; min-width: 0; max-width: min(42vw, 240px); }
862862
.session-live-status-emoji { display: inline-block; font-size: 12px; line-height: 1; filter: saturate(1.1); }
863863
.session-live-status-emoji.robot { transform: translateY(0.2px); }
864+
.session-live-status-text { color: #818cf8; font-size: 10px; line-height: 1.1; margin-left: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; opacity: 0.92; }
864865
.session-live-status-inline.running .session-live-status-emoji.gear { animation: status-gear-spin 0.8s linear infinite; transform-origin: 50% 50%; }
866+
.session-live-status-inline.thinking .session-live-status-emoji.thought { font-size: 10px; transform: translateY(-2px) translateX(-1px); opacity: 0.94; animation: status-thought-breathe 1.8s ease-in-out infinite; transform-origin: 50% 100%; }
867+
.session-live-status-inline.tool .session-live-status-emoji.tool { animation: status-tool-peek 1.15s ease-in-out infinite; transform-origin: 50% 50%; }
865868
.session-live-status-inline.idle .session-live-status-emoji.sleep { font-size: 9px; transform: translateY(-3px) translateX(-1px); opacity: 0.9; animation: status-sleep-breathe 1.8s ease-in-out infinite; transform-origin: 50% 100%; }
866869
.session-usage-model { color: #a78bfa; font-size: 10px; font-weight: 500; margin-right: 6px; }
867870
.session-usage-tokens { color: #64748b; }
868871
.session-usage-badge { color: #93c5fd; border: 1px solid #1d4ed8; border-radius: 999px; padding: 1px 6px; line-height: 1.4; }
869872
.session-usage-quota-inline { color: #64748b; font-size: 9px; line-height: 1.4; white-space: nowrap; }
870873
.session-usage-cost { color: #94a3b8; }
871874
@keyframes status-gear-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
875+
@keyframes status-thought-breathe {
876+
0%, 100% { transform: translateY(-2px) translateX(-1px) scale(0.9); opacity: 0.72; }
877+
50% { transform: translateY(-3px) translateX(-1px) scale(1.06); opacity: 1; }
878+
}
879+
@keyframes status-tool-peek {
880+
0%, 100% { transform: translateX(0) rotate(0deg) scale(0.98); }
881+
35% { transform: translateX(0.5px) rotate(-8deg) scale(1.03); }
882+
65% { transform: translateX(0.5px) rotate(8deg) scale(1.03); }
883+
}
872884
@keyframes status-sleep-breathe {
873885
0%, 100% { transform: translateY(-3px) translateX(-1px) scale(0.88); opacity: 0.72; }
874886
50% { transform: translateY(-4px) translateX(-1px) scale(1.08); opacity: 1; }

web/test/usage-footer.test.tsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ afterEach(() => {
3434
});
3535

3636
describe('UsageFooter', () => {
37-
it('renders emoji live status inline with footer stats and only animates while running', () => {
37+
it('renders idle, thinking, and running live status inline with footer stats', () => {
3838
const { container, rerender } = render(
3939
<UsageFooter
4040
usage={{
@@ -55,6 +55,7 @@ describe('UsageFooter', () => {
5555
expect(idleStatus?.getAttribute('aria-label')).toContain('Agent idle');
5656
expect(container.querySelector('.chat-thinking-dots')).toBeNull();
5757
expect(container.querySelector('.session-live-status-inline.idle .session-live-status-emoji.sleep')).toBeTruthy();
58+
expect(container.querySelector('.session-live-status-text')).toBeNull();
5859

5960
rerender(
6061
<UsageFooter
@@ -72,13 +73,32 @@ describe('UsageFooter', () => {
7273

7374
const runningStatus = container.querySelector('.session-live-status-inline') as HTMLSpanElement | null;
7475
expect(runningStatus?.textContent).toContain('🤖');
75-
expect(runningStatus?.textContent).toContain('⚙️');
76+
expect(runningStatus?.textContent).toContain('💭');
7677
expect(runningStatus?.getAttribute('aria-label')).toContain('thinking');
77-
expect(container.querySelector('.session-live-status-inline.running')).toBeTruthy();
78+
expect(container.querySelector('.session-live-status-inline.thinking')).toBeTruthy();
79+
expect(container.querySelector('.session-live-status-inline.thinking .session-live-status-emoji.thought')).toBeTruthy();
80+
expect(container.querySelector('.session-live-status-text')?.textContent).toContain('thinking');
81+
82+
rerender(
83+
<UsageFooter
84+
usage={{
85+
inputTokens: 0,
86+
cacheTokens: 0,
87+
contextWindow: 1_000_000,
88+
model: 'coder-model',
89+
}}
90+
sessionName="deck_test_brain"
91+
sessionState="running"
92+
/>,
93+
);
94+
95+
const plainRunningStatus = container.querySelector('.session-live-status-inline') as HTMLSpanElement | null;
96+
expect(plainRunningStatus?.textContent).toContain('🤖');
97+
expect(plainRunningStatus?.textContent).toContain('⚙️');
7898
expect(container.querySelector('.session-live-status-inline.running .session-live-status-emoji.gear')).toBeTruthy();
7999
});
80100

81-
it('prefers explicit running status text in the live status row', () => {
101+
it('shows tool-call icon when explicit running status text is present', () => {
82102
const { container } = render(
83103
<UsageFooter
84104
usage={{
@@ -93,6 +113,9 @@ describe('UsageFooter', () => {
93113
/>,
94114
);
95115

116+
expect((container.querySelector('.session-live-status-inline') as HTMLSpanElement | null)?.textContent).toContain('🔍');
117+
expect(container.querySelector('.session-live-status-inline.tool .session-live-status-emoji.tool')).toBeTruthy();
118+
expect(container.querySelector('.session-live-status-text')?.textContent).toBe('Reading file...');
96119
expect((container.querySelector('.session-live-status-inline') as HTMLSpanElement | null)?.getAttribute('aria-label')).toBe('Reading file...');
97120
});
98121

0 commit comments

Comments
 (0)