use-tab-status.ts
1 import { useContext, useEffect, useRef } from 'react' 2 import { 3 CLEAR_TAB_STATUS, 4 supportsTabStatus, 5 tabStatus, 6 wrapForMultiplexer, 7 } from '../termio/osc.js' 8 import type { Color } from '../termio/types.js' 9 import { TerminalWriteContext } from '../useTerminalNotification.js' 10 11 export type TabStatusKind = 'idle' | 'busy' | 'waiting' 12 13 const rgb = (r: number, g: number, b: number): Color => ({ 14 type: 'rgb', 15 r, 16 g, 17 b, 18 }) 19 20 // Per the OSC 21337 usage guide's suggested mapping. 21 const TAB_STATUS_PRESETS: Record< 22 TabStatusKind, 23 { indicator: Color; status: string; statusColor: Color } 24 > = { 25 idle: { 26 indicator: rgb(0, 215, 95), 27 status: 'Idle', 28 statusColor: rgb(136, 136, 136), 29 }, 30 busy: { 31 indicator: rgb(255, 149, 0), 32 status: 'Working…', 33 statusColor: rgb(255, 149, 0), 34 }, 35 waiting: { 36 indicator: rgb(95, 135, 255), 37 status: 'Waiting', 38 statusColor: rgb(95, 135, 255), 39 }, 40 } 41 42 /** 43 * Declaratively set the tab-status indicator (OSC 21337). 44 * 45 * Emits a colored dot + short status text to the tab sidebar. Terminals 46 * that don't support OSC 21337 discard the sequence silently, so this is 47 * safe to call unconditionally. Wrapped for tmux/screen passthrough. 48 * 49 * Pass `null` to opt out. If a status was previously set, transitioning to 50 * `null` emits CLEAR_TAB_STATUS so toggling off mid-session doesn't leave 51 * a stale dot. Process-exit cleanup is handled by ink.tsx's unmount path. 52 */ 53 export function useTabStatus(kind: TabStatusKind | null): void { 54 const writeRaw = useContext(TerminalWriteContext) 55 const prevKindRef = useRef<TabStatusKind | null>(null) 56 57 useEffect(() => { 58 // When kind transitions from non-null to null (e.g. user toggles off 59 // showStatusInTerminalTab mid-session), clear the stale dot. 60 if (kind === null) { 61 if (prevKindRef.current !== null && writeRaw && supportsTabStatus()) { 62 writeRaw(wrapForMultiplexer(CLEAR_TAB_STATUS)) 63 } 64 prevKindRef.current = null 65 return 66 } 67 68 prevKindRef.current = kind 69 if (!writeRaw || !supportsTabStatus()) return 70 writeRaw(wrapForMultiplexer(tabStatus(TAB_STATUS_PRESETS[kind]))) 71 }, [kind, writeRaw]) 72 }