@@ -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' ;
2627import type { Project } from '../store/types' ;
28+ import type { TaskAttentionState } from '../store/store' ;
2729import { computeGroupedTasks } from '../store/sidebar-order' ;
2830import { ConnectPhoneModal } from './ConnectPhoneModal' ;
2931import { ConfirmDialog } from './ConfirmDialog' ;
@@ -41,29 +43,40 @@ const SIDEBAR_MIN_WIDTH = 160;
4143const SIDEBAR_MAX_WIDTH = 480 ;
4244const 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
6982export function Sidebar ( ) {
@@ -753,18 +766,17 @@ function CurrentBranchBadge(props: { branchName: string }) {
753766}
754767
755768function 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
777789function 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 {
847858function 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
0 commit comments