@@ -30,6 +30,61 @@ export type TabBadgesEnv = WaveEnvSubset<{
3030const BadgeMap = new Map < string , PrimitiveAtom < Badge > > ( ) ;
3131const TabBadgeAtomCache = new Map < string , Atom < Badge [ ] > > ( ) ;
3232
33+ type BlockBorder = {
34+ color : string ;
35+ } ;
36+
37+ const BorderMap = new Map < string , PrimitiveAtom < BlockBorder | null > > ( ) ;
38+
39+ function getBorderAtom ( oref : string ) : PrimitiveAtom < BlockBorder | null > {
40+ if ( oref == null ) {
41+ return NullAtom as PrimitiveAtom < BlockBorder | null > ;
42+ }
43+ let rtn = BorderMap . get ( oref ) ;
44+ if ( rtn == null ) {
45+ rtn = atom ( null ) as PrimitiveAtom < BlockBorder | null > ;
46+ BorderMap . set ( oref , rtn ) ;
47+ }
48+ return rtn ;
49+ }
50+
51+ function getBlockBorderAtom ( blockId : string ) : Atom < BlockBorder | null > {
52+ if ( blockId == null ) {
53+ return NullAtom as Atom < BlockBorder | null > ;
54+ }
55+ const oref = WOS . makeORef ( "block" , blockId ) ;
56+ return getBorderAtom ( oref ) ;
57+ }
58+
59+ const BUILTIN_SOUND_PRESETS = new Set ( [ "chime" , "ping" , "gentle" ] ) ;
60+
61+ function playBadgeSound ( sound : string ) : void {
62+ if ( ! sound ) {
63+ return ;
64+ }
65+ if ( sound === "system" ) {
66+ fireAndForget ( ( ) => RpcApi . ElectronSystemBellCommand ( TabRpcClient , { route : "electron" } ) ) ;
67+ return ;
68+ }
69+ let audioPath : string ;
70+ if ( BUILTIN_SOUND_PRESETS . has ( sound ) ) {
71+ audioPath = `/sounds/${ sound } .mp3` ;
72+ } else {
73+ // Custom sound from ~/.waveterm/sounds/
74+ const electronApi = ( window as any ) . api as ElectronApi ;
75+ const configDir = electronApi ?. getConfigDir ?.( ) ;
76+ if ( ! configDir ) {
77+ console . warn ( `[badge] cannot resolve config dir for custom sound: ${ sound } ` ) ;
78+ return ;
79+ }
80+ audioPath = `file://${ configDir } /sounds/${ sound } ` ;
81+ }
82+ const audio = new Audio ( audioPath ) ;
83+ audio . play ( ) . catch ( ( e ) => {
84+ console . warn ( `[badge] failed to play sound "${ sound } ":` , e ) ;
85+ } ) ;
86+ }
87+
3388function publishBadgeEvent ( eventData : WaveEvent , env ?: BadgeEnv ) {
3489 if ( env != null ) {
3590 fireAndForget ( ( ) => env . rpc . EventPublishCommand ( TabRpcClient , eventData ) ) ;
@@ -57,6 +112,11 @@ function clearBadgesForBlockOnFocus(blockId: string, env?: BadgeEnv) {
57112 if ( badge != null && ! badge . pidlinked ) {
58113 clearBadgeInternal ( oref , env ) ;
59114 }
115+ // Always clear border on focus
116+ const borderAtom = BorderMap . get ( oref ) ;
117+ if ( borderAtom != null && globalStore . get ( borderAtom ) != null ) {
118+ globalStore . set ( borderAtom , null ) ;
119+ }
60120}
61121
62122function clearBadgesForTabOnFocus ( tabId : string , env ?: BadgeEnv ) {
@@ -199,6 +259,9 @@ function setupBadgesSubscription() {
199259 for ( const atom of BadgeMap . values ( ) ) {
200260 globalStore . set ( atom , null ) ;
201261 }
262+ for ( const atom of BorderMap . values ( ) ) {
263+ globalStore . set ( atom , null ) ;
264+ }
202265 return ;
203266 }
204267 if ( data ?. oref == null ) {
@@ -214,14 +277,25 @@ function setupBadgesSubscription() {
214277 }
215278 if ( data . clear ) {
216279 globalStore . set ( curAtom , null ) ;
280+ const borderAtom = getBorderAtom ( data . oref ) ;
281+ globalStore . set ( borderAtom , null ) ;
217282 return ;
218283 }
219- if ( data . badge == null ) {
220- return ;
284+ if ( data . badge != null ) {
285+ const existing = globalStore . get ( curAtom ) ;
286+ if ( existing == null || cmpBadge ( data . badge , existing ) > 0 ) {
287+ globalStore . set ( curAtom , data . badge ) ;
288+ }
289+ }
290+ // Play sound if requested
291+ if ( data . sound ) {
292+ playBadgeSound ( data . sound ) ;
221293 }
222- const existing = globalStore . get ( curAtom ) ;
223- if ( existing == null || cmpBadge ( data . badge , existing ) > 0 ) {
224- globalStore . set ( curAtom , data . badge ) ;
294+ // Set border highlight if requested
295+ if ( data . border ) {
296+ const borderColor = data . bordercolor || data . badge ?. color || "#fbbf24" ;
297+ const borderAtom = getBorderAtom ( data . oref ) ;
298+ globalStore . set ( borderAtom , { color : borderColor } ) ;
225299 }
226300 } ,
227301 } ) ;
@@ -258,6 +332,7 @@ export {
258332 clearBadgesForTabOnFocus ,
259333 getBadgeAtom ,
260334 getBlockBadgeAtom ,
335+ getBlockBorderAtom ,
261336 getTabBadgeAtom ,
262337 loadBadges ,
263338 setBadge ,
0 commit comments