Skip to content

Commit 4891572

Browse files
committed
chore: simplify sidebar attention cleanup
1 parent e304fba commit 4891572

5 files changed

Lines changed: 53 additions & 45 deletions

File tree

src/components/Sidebar.tsx

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
reorderTask,
1111
getTaskDotStatus,
1212
getTaskAttentionState,
13+
taskNeedsAttention,
1314
getTaskViewportVisibility,
1415
registerFocusFn,
1516
unregisterFocusFn,
@@ -24,6 +25,7 @@ import {
2425
isProjectMissing,
2526
} from '../store/store';
2627
import type { Project } from '../store/types';
28+
import type { TaskAttentionState } from '../store/store';
2729
import { computeGroupedTasks } from '../store/sidebar-order';
2830
import { ConnectPhoneModal } from './ConnectPhoneModal';
2931
import { ConfirmDialog } from './ConfirmDialog';
@@ -41,29 +43,40 @@ const SIDEBAR_MIN_WIDTH = 160;
4143
const SIDEBAR_MAX_WIDTH = 480;
4244
const SIDEBAR_SIZE_KEY = 'sidebar:width';
4345

44-
function getAttentionColor(attention: string): string | null {
46+
function getAttentionColor(attention: TaskAttentionState): string | null {
4547
if (attention === 'active') return theme.accent;
4648
if (attention === 'needs_input') return theme.warning;
4749
if (attention === 'error') return theme.error;
4850
return null;
4951
}
5052

51-
function hasOffscreenAttention(taskId: string): boolean {
52-
const visibility = getTaskViewportVisibility(taskId);
53-
if (!visibility || visibility === 'visible') return false;
54-
const attention = getTaskAttentionState(taskId);
55-
return attention === 'active' || attention === 'needs_input' || attention === 'error';
53+
interface OffscreenAttentionInfo {
54+
attention: TaskAttentionState;
55+
color: string;
56+
label: string | null;
5657
}
5758

58-
function getOffscreenAttentionLabel(taskId: string): string | null {
59-
if (!hasOffscreenAttention(taskId)) return null;
59+
function getOffscreenAttentionInfo(taskId: string): OffscreenAttentionInfo | null {
6060
const visibility = getTaskViewportVisibility(taskId);
61+
if (!visibility || visibility === 'visible' || !taskNeedsAttention(taskId)) return null;
6162
const attention = getTaskAttentionState(taskId);
63+
const color = getAttentionColor(attention) ?? theme.accent;
6264
const side = visibility === 'offscreen-left' ? 'left' : 'right';
6365
const prefix = visibility === 'offscreen-left' ? '←' : '→';
64-
if (attention === 'needs_input') return `${prefix} input (${side})`;
65-
if (attention === 'error') return `${prefix} error (${side})`;
66-
return null;
66+
let label: string | null = null;
67+
if (attention === 'needs_input') label = `${prefix} input (${side})`;
68+
if (attention === 'error') label = `${prefix} error (${side})`;
69+
return { attention, color, label };
70+
}
71+
72+
function createOffscreenAttentionState(taskId: () => string) {
73+
const info = createMemo(() => getOffscreenAttentionInfo(taskId()));
74+
return {
75+
hasAttention: () => info() !== null,
76+
attention: () => info()?.attention,
77+
color: () => info()?.color ?? theme.accent,
78+
label: () => info()?.label ?? null,
79+
};
6780
}
6881

6982
export function Sidebar() {
@@ -753,18 +766,17 @@ function CurrentBranchBadge(props: { branchName: string }) {
753766
}
754767

755768
function OffscreenAttentionBadge(props: { taskId: string }) {
756-
const label = () => getOffscreenAttentionLabel(props.taskId);
757-
const color = () => getAttentionColor(getTaskAttentionState(props.taskId)) ?? theme.fgSubtle;
769+
const offscreenAttention = createOffscreenAttentionState(() => props.taskId);
758770
return (
759-
<Show when={label()}>
771+
<Show when={offscreenAttention.label()}>
760772
{(text) => (
761773
<span
762774
class="sidebar-offscreen-attention-badge"
763775
title={text()}
764776
style={{
765-
color: color(),
766-
border: `1px solid color-mix(in srgb, ${color()} 30%, transparent)`,
767-
background: `color-mix(in srgb, ${color()} 10%, transparent)`,
777+
color: offscreenAttention.color(),
778+
border: `1px solid color-mix(in srgb, ${offscreenAttention.color()} 30%, transparent)`,
779+
background: `color-mix(in srgb, ${offscreenAttention.color()} 10%, transparent)`,
768780
}}
769781
>
770782
{text()}
@@ -776,6 +788,7 @@ function OffscreenAttentionBadge(props: { taskId: string }) {
776788

777789
function CollapsedTaskRow(props: { taskId: string }) {
778790
const task = () => store.tasks[props.taskId];
791+
const offscreenAttention = createOffscreenAttentionState(() => props.taskId);
779792
return (
780793
<Show when={task()}>
781794
{(t) => (
@@ -795,34 +808,32 @@ function CollapsedTaskRow(props: { taskId: string }) {
795808
style={{
796809
padding: '7px 10px',
797810
'border-radius': '6px',
798-
background: hasOffscreenAttention(props.taskId)
799-
? `color-mix(in srgb, ${getAttentionColor(getTaskAttentionState(props.taskId)) ?? theme.accent} 10%, transparent)`
811+
background: offscreenAttention.hasAttention()
812+
? `color-mix(in srgb, ${offscreenAttention.color()} 10%, transparent)`
800813
: 'transparent',
801-
color: hasOffscreenAttention(props.taskId) ? theme.fg : theme.fgSubtle,
814+
color: offscreenAttention.hasAttention() ? theme.fg : theme.fgSubtle,
802815
'font-size': sf(12),
803816
'font-weight': '400',
804817
cursor: 'pointer',
805818
'white-space': 'nowrap',
806819
overflow: 'hidden',
807820
'text-overflow': 'ellipsis',
808-
opacity: hasOffscreenAttention(props.taskId) ? '1' : '0.6',
821+
opacity: offscreenAttention.hasAttention() ? '1' : '0.6',
809822
display: 'flex',
810823
'align-items': 'center',
811824
gap: '6px',
812825
border:
813826
store.sidebarFocused && store.sidebarFocusedTaskId === props.taskId
814827
? `1.5px solid var(--border-focus)`
815-
: hasOffscreenAttention(props.taskId)
816-
? `1.5px solid color-mix(in srgb, ${getAttentionColor(getTaskAttentionState(props.taskId)) ?? theme.accent} 38%, transparent)`
828+
: offscreenAttention.hasAttention()
829+
? `1.5px solid color-mix(in srgb, ${offscreenAttention.color()} 38%, transparent)`
817830
: '1.5px solid transparent',
818831
}}
819832
>
820833
<StatusDot
821834
status={getTaskDotStatus(props.taskId)}
822835
size="sm"
823-
attention={
824-
hasOffscreenAttention(props.taskId) ? getTaskAttentionState(props.taskId) : undefined
825-
}
836+
attention={offscreenAttention.attention()}
826837
/>
827838
<Show when={t().gitIsolation === 'direct'}>
828839
<CurrentBranchBadge branchName={t().branchName} />
@@ -847,6 +858,7 @@ interface TaskRowProps {
847858
function TaskRow(props: TaskRowProps) {
848859
const task = () => store.tasks[props.taskId];
849860
const idx = () => props.globalIndex(props.taskId);
861+
const offscreenAttention = createOffscreenAttentionState(() => props.taskId);
850862
return (
851863
<Show when={task()}>
852864
{(t) => (
@@ -864,16 +876,16 @@ function TaskRow(props: TaskRowProps) {
864876
style={{
865877
padding: '7px 10px',
866878
'border-radius': '6px',
867-
background: hasOffscreenAttention(props.taskId)
868-
? `color-mix(in srgb, ${getAttentionColor(getTaskAttentionState(props.taskId)) ?? theme.accent} 10%, transparent)`
879+
background: offscreenAttention.hasAttention()
880+
? `color-mix(in srgb, ${offscreenAttention.color()} 10%, transparent)`
869881
: 'transparent',
870882
color:
871-
store.activeTaskId === props.taskId || hasOffscreenAttention(props.taskId)
883+
store.activeTaskId === props.taskId || offscreenAttention.hasAttention()
872884
? theme.fg
873885
: theme.fgMuted,
874886
'font-size': sf(12),
875887
'font-weight':
876-
store.activeTaskId === props.taskId || hasOffscreenAttention(props.taskId)
888+
store.activeTaskId === props.taskId || offscreenAttention.hasAttention()
877889
? '500'
878890
: '400',
879891
cursor: props.dragFromIndex() !== null ? 'grabbing' : 'pointer',
@@ -887,19 +899,15 @@ function TaskRow(props: TaskRowProps) {
887899
border:
888900
store.sidebarFocused && store.sidebarFocusedTaskId === props.taskId
889901
? `1.5px solid var(--border-focus)`
890-
: hasOffscreenAttention(props.taskId)
891-
? `1.5px solid color-mix(in srgb, ${getAttentionColor(getTaskAttentionState(props.taskId)) ?? theme.accent} 38%, transparent)`
902+
: offscreenAttention.hasAttention()
903+
? `1.5px solid color-mix(in srgb, ${offscreenAttention.color()} 38%, transparent)`
892904
: '1.5px solid transparent',
893905
}}
894906
>
895907
<StatusDot
896908
status={getTaskDotStatus(props.taskId)}
897909
size="sm"
898-
attention={
899-
hasOffscreenAttention(props.taskId)
900-
? getTaskAttentionState(props.taskId)
901-
: undefined
902-
}
910+
attention={offscreenAttention.attention()}
903911
/>
904912
<Show when={t().gitIsolation === 'direct'}>
905913
<span

src/components/TerminalView.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { WebLinksAddon } from '@xterm/addon-web-links';
66
import { invoke, fireAndForget, Channel } from '../lib/ipc';
77
import { IPC } from '../../electron/ipc/channels';
88
import { getTerminalFontFamily } from '../lib/fonts';
9+
import { TERMINAL_SCROLLBACK_LINES } from '../lib/terminalConstants';
910
import { getTerminalTheme } from '../lib/theme';
1011
import { matchesGlobalShortcut } from '../lib/shortcuts';
1112
import { isMac } from '../lib/platform';
@@ -67,10 +68,6 @@ interface TerminalViewProps {
6768
// expensive full-chunk decoding during large terminal bursts.
6869
const STATUS_ANALYSIS_MAX_BYTES = 8 * 1024;
6970

70-
// Keep substantially more terminal history available for agent review/debugging
71-
// without turning xterm into a memory landfill.
72-
const TERMINAL_SCROLLBACK_LINES = 10_000;
73-
7471
export function TerminalView(props: TerminalViewProps) {
7572
let containerRef!: HTMLDivElement;
7673
let term: Terminal | undefined;

src/components/TilingLayout.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
pickAndAddProject,
1313
closeTerminal,
1414
setTaskViewportVisibility,
15-
getTaskAttentionState,
15+
taskNeedsAttention,
1616
} from '../store/store';
1717
import { closeTask } from '../store/tasks';
1818
import { ResizablePanel, type PanelChild, type ResizablePanelHandle } from './ResizablePanel';
@@ -88,8 +88,7 @@ export function TilingLayout() {
8888
if (!store.tasks[taskId]) continue;
8989
const visibility = store.taskViewportVisibility[taskId];
9090
if (!visibility || visibility === 'visible') continue;
91-
const attention = getTaskAttentionState(taskId);
92-
if (attention !== 'active' && attention !== 'needs_input' && attention !== 'error') continue;
91+
if (!taskNeedsAttention(taskId)) continue;
9392
if (visibility === 'offscreen-left') left = true;
9493
if (visibility === 'offscreen-right') right = true;
9594
if (left && right) break;

src/lib/terminalConstants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Keep substantially more terminal history available for agent review/debugging
2+
// without turning xterm into a memory landfill.
3+
export const TERMINAL_SCROLLBACK_LINES = 10_000;

src/remote/AgentDetail.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { onMount, onCleanup, createSignal, Show, For } from 'solid-js';
22
import { Terminal } from '@xterm/xterm';
33
import { FitAddon } from '@xterm/addon-fit';
4+
import { TERMINAL_SCROLLBACK_LINES } from '../lib/terminalConstants';
45
import {
56
subscribeAgent,
67
unsubscribeAgent,
@@ -95,7 +96,7 @@ export function AgentDetail(props: AgentDetailProps) {
9596
fontSize: 10,
9697
fontFamily: "'JetBrains Mono', 'Courier New', monospace",
9798
theme: { background: '#0b0f14' },
98-
scrollback: 10000,
99+
scrollback: TERMINAL_SCROLLBACK_LINES,
99100
cursorBlink: false,
100101
disableStdin: true,
101102
convertEol: false,

0 commit comments

Comments
 (0)