22
33import { memo , useMemo , useState , useEffect , useCallback , useRef } from 'react' ;
44import type { ContextEntry , StoreEvent } from '@/types' ;
5- import { cn } from '@/lib/utils' ;
5+ import { cn , formatTimestamp } from '@/lib/utils' ;
66import { Database , GitBranch , GitFork , ChevronRight , Folder , User , Tag } from './icons' ;
77import { PresenceIndicator , LiveTimestamp } from './live' ;
88import type { PresenceState } from './live' ;
@@ -22,6 +22,47 @@ function getTagColor(tag: string) {
2222 return TAG_COLORS [ tag . toLowerCase ( ) ] || DEFAULT_TAG_COLOR ;
2323}
2424
25+ function getContextBadgeLabel ( context : ContextEntry ) : string | null {
26+ const provenance = context . provenance ;
27+ const username =
28+ provenance ?. on_behalf_of ||
29+ provenance ?. process_owner ||
30+ provenance ?. on_behalf_of_email ||
31+ null ;
32+ const hostname = provenance ?. host_name || null ;
33+
34+ if ( username && hostname ) {
35+ return `${ username } @${ hostname } ` ;
36+ }
37+ if ( username ) {
38+ return username ;
39+ }
40+ if ( hostname ) {
41+ return hostname ;
42+ }
43+ return context . client_tag || null ;
44+ }
45+
46+ function basename ( path ?: string ) : string | null {
47+ if ( ! path ) return null ;
48+ const trimmed = path . replace ( / \/ + $ / , '' ) ;
49+ const segments = trimmed . split ( '/' ) . filter ( Boolean ) ;
50+ return segments . length > 0 ? segments [ segments . length - 1 ] : null ;
51+ }
52+
53+ function getContextTitleLabel ( context : ContextEntry ) : string | null {
54+ const provider = context . client_tag ?. split ( '/' ) . pop ( ) || null ;
55+ const worktreeName = basename ( context . provenance ?. env ?. PWD ) ;
56+ const timestampSource = context . provenance ?. captured_at ?? context . created_at_unix_ms ;
57+ const timestamp = timestampSource ? formatTimestamp ( timestampSource ) : null ;
58+
59+ if ( provider && worktreeName && timestamp ) {
60+ return `${ provider } : ${ worktreeName } ${ timestamp } ` ;
61+ }
62+
63+ return context . title || null ;
64+ }
65+
2566interface ContextListProps {
2667 contexts : ContextEntry [ ] ;
2768 selectedId ?: string ;
@@ -73,6 +114,8 @@ const ContextListItem = memo(function ContextListItem({
73114 const hasParent = ! ! ( provenance ?. parent_context_id ) ;
74115 const onBehalfOf = provenance ?. on_behalf_of || provenance ?. on_behalf_of_email ;
75116 const sourceStyle = provenance ?. on_behalf_of_source ? getSourceStyle ( provenance . on_behalf_of_source ) : null ;
117+ const badgeLabel = getContextBadgeLabel ( context ) ;
118+ const titleLabel = getContextTitleLabel ( context ) ;
76119
77120 return (
78121 < button
@@ -92,13 +135,13 @@ const ContextListItem = memo(function ContextListItem({
92135 ) }
93136 >
94137 { /* Title row (if available) */ }
95- { context . title && (
138+ { titleLabel && (
96139 < div className = "flex items-center gap-1.5 mb-1" >
97140 < span className = { cn (
98141 'text-sm font-medium truncate' ,
99142 isSelected ? 'text-theme-text' : 'text-theme-text-secondary'
100143 ) } >
101- { context . title }
144+ { titleLabel }
102145 </ span >
103146 </ div >
104147 ) }
@@ -123,14 +166,16 @@ const ContextListItem = memo(function ContextListItem({
123166 ) } >
124167 { context . context_id }
125168 </ span >
126- { /* Client tag badge */ }
127- { context . client_tag && (
169+ { /* Context provenance badge */ }
170+ { badgeLabel && (
128171 < span className = { cn (
129172 'px-1.5 py-0.5 rounded text-[10px] font-medium truncate' ,
130- getTagColor ( context . client_tag ) . bg ,
131- getTagColor ( context . client_tag ) . text
132- ) } >
133- { context . client_tag }
173+ getTagColor ( context . client_tag || 'default' ) . bg ,
174+ getTagColor ( context . client_tag || 'default' ) . text
175+ ) }
176+ title = { badgeLabel }
177+ >
178+ { badgeLabel }
134179 </ span >
135180 ) }
136181 { /* Filesystem snapshot indicator */ }
0 commit comments