imsg-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.js","sources":["../src/url-schemes.ts","../src/tui/themes/ThemeContext.tsx","../src/tui/components/DatePicker.tsx","../src/tui/components/DateJumpModal.tsx","../src/tui/components/DevStats.tsx","../src/tui/components/ExportModal.tsx","../src/tui/components/HelpBar.tsx","../src/tui/themes/glyphs.ts","../src/tui/themes/color.ts","../src/tui/themes/palette.ts","../src/tui/theme.ts","../src/tui/components/MessageDrawer.tsx","../src/tui/components/SendViaModal.tsx","../src/tui/components/ConversationItem.tsx","../src/tui/components/Sidebar.tsx","../src/tui/components/StatusBar.tsx","../src/tui/components/ComposeBar.tsx","../src/tui/components/MessageBubble.tsx","../src/tui/components/ThreadPane.tsx","../src/tui/hooks/useDevStats.ts","../src/tui/messageCache.ts","../src/tui/hooks/useImsg.ts","../src/tui/hooks/useMouse.ts","../src/tui/types.ts","../src/tui/App.tsx","../src/tui/index.tsx"],"sourcesContent":["/**\n * macOS URL-scheme dispatch helpers.\n *\n * Detects which chat/comms apps are installed in /Applications and builds\n * deep-link URIs that, when passed to `open <uri>`, launch the target app\n * focused on a specific contact. Designed for the TUI's `O` (open in\n * Messages) and `S` (send-via external app) keybinds.\n *\n * Body-carrying support varies by app:\n * - sms:, whatsapp:, tg:, mailto: → body/text param honored\n * - imessage:, sgnl:, facetime: → recipient only, body stripped\n */\n\nimport { existsSync } from \"node:fs\";\n\nexport interface ChatAppDef {\n /** Display name shown in TUI list. */\n name: string;\n /** /Applications/ bundle to probe. */\n appPath: string;\n /** Build the URL given a handle + optional body text. Returns null if the\n * handle isn't compatible (e.g. Signal can't take an email). */\n buildUri: (handle: string, body?: string) => string | null;\n /** True if `body` is honored by the URL scheme. */\n supportsBody: boolean;\n}\n\nconst APPS: ChatAppDef[] = [\n {\n name: \"Messages\",\n appPath: \"/System/Applications/Messages.app\",\n buildUri: (handle) => `imessage://${encodeURIComponent(handle)}`,\n supportsBody: false,\n },\n {\n name: \"FaceTime\",\n appPath: \"/System/Applications/FaceTime.app\",\n buildUri: (handle) => `facetime://${encodeURIComponent(handle)}`,\n supportsBody: false,\n },\n {\n name: \"FaceTime Audio\",\n appPath: \"/System/Applications/FaceTime.app\",\n buildUri: (handle) => `facetime-audio://${encodeURIComponent(handle)}`,\n supportsBody: false,\n },\n {\n name: \"Signal\",\n appPath: \"/Applications/Signal.app\",\n buildUri: (handle) => {\n // Signal only accepts phone numbers.\n const phone = handle.replace(/[^\\d+]/g, \"\");\n if (!phone || !phone.startsWith(\"+\")) return null;\n return `sgnl://send?phone=${encodeURIComponent(phone)}`;\n },\n supportsBody: false,\n },\n {\n name: \"WhatsApp\",\n appPath: \"/Applications/WhatsApp.app\",\n buildUri: (handle, body) => {\n const phone = handle.replace(/[^\\d+]/g, \"\").replace(/^\\+/, \"\");\n if (!phone) return null;\n const base = `whatsapp://send?phone=${encodeURIComponent(phone)}`;\n return body ? `${base}&text=${encodeURIComponent(body)}` : base;\n },\n supportsBody: true,\n },\n {\n name: \"Telegram\",\n appPath: \"/Applications/Telegram.app\",\n buildUri: (handle, body) => {\n const phone = handle.replace(/[^\\d+]/g, \"\");\n if (!phone) return null;\n const base = `tg://resolve?phone=${encodeURIComponent(phone)}`;\n return body ? `${base}&text=${encodeURIComponent(body)}` : base;\n },\n supportsBody: true,\n },\n {\n name: \"SMS\",\n appPath: \"/System/Applications/Messages.app\",\n buildUri: (handle, body) => {\n // sms: works for both numbers and email (Messages.app fallback).\n const base = `sms:${encodeURIComponent(handle)}`;\n return body ? `${base}&body=${encodeURIComponent(body)}` : base;\n },\n supportsBody: true,\n },\n];\n\n/**\n * Return the apps installed on this Mac, in the order they're declared.\n * Messages and FaceTime are typically always present; third-party apps\n * (Signal, WhatsApp, Telegram) require the app to be installed.\n */\nexport function getInstalledChatApps(): ChatAppDef[] {\n return APPS.filter((a) => existsSync(a.appPath));\n}\n\n/**\n * Build the right URI for a specific app by name (case-insensitive). Returns\n * null if the app is not installed OR the handle isn't compatible with the\n * app's URL scheme.\n */\nexport function buildChatAppUri(\n appName: string,\n handle: string,\n body?: string,\n): { uri: string; supportsBody: boolean } | null {\n const lower = appName.toLowerCase();\n const app = APPS.find((a) => a.name.toLowerCase() === lower);\n if (!app) return null;\n if (!existsSync(app.appPath)) return null;\n const uri = app.buildUri(handle, body);\n if (!uri) return null;\n return { uri, supportsBody: app.supportsBody };\n}\n","/**\n * React context that delivers a fully-resolved Theme to every TUI\n * component. Replaces the legacy pattern of `import { theme } from \"../theme\"`\n * — components now `const theme = useTheme()` so the same component tree\n * can render with different accents / glyph presets.\n *\n * Lookup outside a provider throws — TypeScript would let it slide\n * silently otherwise (default value), and a missing provider is a\n * developer error we want to surface during the first render.\n */\n\nimport { createContext, type ReactNode, useContext } from \"react\";\nimport type { Theme } from \"../theme.js\";\n\nconst ThemeCtx = createContext<Theme | null>(null);\n\nexport function ThemeProvider({ value, children }: { value: Theme; children: ReactNode }) {\n return <ThemeCtx.Provider value={value}>{children}</ThemeCtx.Provider>;\n}\n\nexport function useTheme(): Theme {\n const t = useContext(ThemeCtx);\n if (!t) {\n throw new Error(\"useTheme(): no <ThemeProvider> in the component tree above this hook\");\n }\n return t;\n}\n","/**\n * Three-field date picker (YYYY / MM / DD) with arrow-key navigation.\n *\n * ←/→ cycle active field\n * ↑/↓ increment/decrement value\n * 0–9 type digit (right-shifts onto the field; full field auto-advances)\n * Bksp delete last digit of active field\n * Enter submit\n *\n * Returns the picked date as an ISO string (\"YYYY-MM-DD\") to keep the modal's\n * existing parser path unchanged.\n */\nimport { Box, Text, useInput } from \"ink\";\nimport { useState } from \"react\";\nimport { useTheme } from \"../themes/ThemeContext.js\";\n\ntype Field = \"year\" | \"month\" | \"day\";\nconst ORDER: Field[] = [\"year\", \"month\", \"day\"];\n\ninterface Props {\n initial?: Date;\n focused: boolean;\n onSubmit: (isoDate: string) => void;\n onCancel: () => void;\n}\n\nconst MIN_YEAR = 1900;\nconst MAX_YEAR = 2100;\n\nfunction daysInMonth(year: number, monthOneBased: number): number {\n return new Date(year, monthOneBased, 0).getDate();\n}\n\nfunction clamp(n: number, lo: number, hi: number): number {\n return Math.min(Math.max(n, lo), hi);\n}\n\nexport function DatePicker({ initial, focused, onSubmit, onCancel }: Props) {\n const theme = useTheme();\n const now = initial ?? new Date();\n const [year, setYear] = useState(now.getFullYear());\n const [month, setMonth] = useState(now.getMonth() + 1);\n const [day, setDay] = useState(now.getDate());\n const [active, setActive] = useState<Field>(\"year\");\n\n const fieldValue = (f: Field) => (f === \"year\" ? year : f === \"month\" ? month : day);\n\n const setField = (f: Field, v: number) => {\n if (f === \"year\") {\n const y = clamp(v, MIN_YEAR, MAX_YEAR);\n setYear(y);\n // Re-clamp day to month/year (Feb 29 → Feb 28 on non-leap)\n setDay((d) => clamp(d, 1, daysInMonth(y, month)));\n } else if (f === \"month\") {\n const m = clamp(v, 1, 12);\n setMonth(m);\n setDay((d) => clamp(d, 1, daysInMonth(year, m)));\n } else {\n setDay(clamp(v, 1, daysInMonth(year, month)));\n }\n };\n\n useInput(\n (input, key) => {\n if (!focused) return;\n if (key.escape) {\n onCancel();\n return;\n }\n if (key.return) {\n const iso = `${String(year).padStart(4, \"0\")}-${String(month).padStart(2, \"0\")}-${String(day).padStart(2, \"0\")}`;\n onSubmit(iso);\n return;\n }\n if (key.leftArrow) {\n const i = ORDER.indexOf(active);\n setActive(ORDER[(i + ORDER.length - 1) % ORDER.length]);\n return;\n }\n if (key.rightArrow) {\n const i = ORDER.indexOf(active);\n setActive(ORDER[(i + 1) % ORDER.length]);\n return;\n }\n if (key.upArrow) {\n setField(active, fieldValue(active) + 1);\n return;\n }\n if (key.downArrow) {\n setField(active, fieldValue(active) - 1);\n return;\n }\n if (input && /^[0-9]$/.test(input)) {\n const digit = Number.parseInt(input, 10);\n if (active === \"year\") {\n // Shift left and append. Year field is 4 digits.\n const next = (year % 1000) * 10 + digit;\n setField(\"year\", next);\n } else if (active === \"month\") {\n // 2-digit shift\n const next = (month % 10) * 10 + digit;\n setField(\"month\", next < 1 ? 1 : next);\n } else {\n const next = (day % 10) * 10 + digit;\n setField(\"day\", next < 1 ? 1 : next);\n }\n return;\n }\n if (key.backspace || key.delete) {\n if (active === \"year\") setField(\"year\", Math.floor(year / 10) || MIN_YEAR);\n else if (active === \"month\") setField(\"month\", Math.floor(month / 10) || 1);\n else setField(\"day\", Math.floor(day / 10) || 1);\n }\n },\n { isActive: focused },\n );\n\n const fieldStr = (f: Field, width: number) => String(fieldValue(f)).padStart(width, \"0\");\n\n const renderField = (f: Field, width: number) => {\n const isActive = active === f;\n return (\n <Text\n color={isActive ? theme.status.accent : theme.drawer.value}\n bold={isActive}\n inverse={isActive}\n >\n {fieldStr(f, width)}\n </Text>\n );\n };\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n {renderField(\"year\", 4)}\n <Text color={theme.help.desc}>-</Text>\n {renderField(\"month\", 2)}\n <Text color={theme.help.desc}>-</Text>\n {renderField(\"day\", 2)}\n </Box>\n <Box>\n <Text color={theme.help.desc}>←/→ field · ↑/↓ adjust · digits to type · Enter to jump</Text>\n </Box>\n </Box>\n );\n}\n","/**\n * Date-jump modal — two input modes:\n * - picker (default): three-field YYYY-MM-DD picker, arrow-keys for navigation.\n * - text: free-form text input, parsed via `parseUserDate` (handles ISO,\n * keywords like \"yesterday\", relative phrases like \"2 weeks ago\",\n * compact \"5d/2w/3m/1y\", etc).\n *\n * `Tab` (when the inner field doesn't claim it) toggles between modes. The\n * existing caller path is preserved: `onSubmit` always receives a string,\n * either a free-form expression (text mode) or a normalized ISO date\n * (picker mode). Both flow through `parseUserDate` downstream.\n */\nimport { TextInput } from \"@inkjs/ui\";\nimport { Box, Text, useInput } from \"ink\";\nimport { useState } from \"react\";\nimport { useTheme } from \"../themes/ThemeContext.js\";\nimport { DatePicker } from \"./DatePicker.js\";\n\ntype Mode = \"picker\" | \"text\";\n\ninterface Props {\n value: string;\n error: string;\n onChange: (v: string) => void;\n onSubmit: (v: string) => void;\n}\n\nexport function DateJumpModal({ value, error, onChange, onSubmit }: Props) {\n const theme = useTheme();\n const [mode, setMode] = useState<Mode>(\"picker\");\n\n // Tab swaps modes. Listen at the modal level so the swap fires regardless\n // of which inner widget has focus.\n useInput((_input, key) => {\n if (key.tab && !key.shift) {\n setMode((m) => (m === \"picker\" ? \"text\" : \"picker\"));\n }\n });\n\n return (\n <Box flexDirection=\"column\" borderStyle=\"double\" borderColor={theme.status.accent} paddingX={1}>\n <Box>\n <Text color={theme.status.accent} bold>\n Jump to date\n </Text>\n <Text color={theme.help.desc}>\n {\" \"}[{mode}] · Tab to switch\n </Text>\n </Box>\n\n {mode === \"picker\" ? (\n <DatePicker\n focused\n onSubmit={onSubmit}\n onCancel={() => {\n // Esc handled by the App's date-jump exit branch — DatePicker\n // forwards it via onCancel but we leave actual exit to the parent.\n }}\n />\n ) : (\n <Box flexDirection=\"column\">\n <Box>\n <Text color={theme.info.label}>Date: </Text>\n <TextInput\n defaultValue={value}\n onChange={onChange}\n onSubmit={onSubmit}\n placeholder=\"2024-03-15 | 3/15 | yesterday | 2 weeks ago | 1y\"\n />\n </Box>\n <Box>\n <Text color={theme.help.desc}>\n Formats: YYYY-MM-DD · M/D · today · yesterday · N days/weeks/months/years ago · 5d /\n 2w / 3m / 1y\n </Text>\n </Box>\n </Box>\n )}\n\n {error && (\n <Box>\n <Text color={theme.edited}>{error}</Text>\n </Box>\n )}\n\n <Box>\n <Text color={theme.help.desc}>Enter: jump · Esc: cancel · Tab: switch mode</Text>\n </Box>\n </Box>\n );\n}\n","import { Box, Text } from \"ink\";\nimport type { DevStatsData } from \"../hooks/useDevStats.js\";\nimport { useTheme } from \"../themes/ThemeContext.js\";\n\ninterface Props {\n stats: DevStatsData;\n width: number;\n}\n\nexport function DevStats({ stats, width }: Props) {\n const theme = useTheme();\n const engineColor = stats.engine.startsWith(\"Rust\") ? theme.rustEngine : theme.status.accent;\n const cpuColor =\n stats.cpuPercent > 50 ? theme.cpuHigh : stats.cpuPercent > 20 ? theme.edited : theme.info.value;\n\n return (\n <Box\n flexDirection=\"column\"\n width={width}\n borderStyle=\"single\"\n borderColor={theme.border}\n overflow=\"hidden\"\n >\n <Box paddingX={1} backgroundColor={theme.header.dim.bg}>\n <Text color={theme.header.dim.fg} bold>\n Stats\n </Text>\n </Box>\n <Box flexDirection=\"column\" paddingX={1}>\n <Box justifyContent=\"space-between\">\n <Text color={theme.info.label}>Engine</Text>\n <Text color={engineColor} bold>\n {stats.engine}\n </Text>\n </Box>\n <Box justifyContent=\"space-between\">\n <Text color={theme.info.label}>CPU</Text>\n <Text color={cpuColor}>{stats.cpuPercent}%</Text>\n </Box>\n <Box justifyContent=\"space-between\">\n <Text color={theme.info.label}>Mem</Text>\n <Text color={theme.info.value}>{stats.memMB}MB</Text>\n </Box>\n <Box justifyContent=\"space-between\">\n <Text color={theme.info.label}>PID</Text>\n <Text color={theme.info.value}>{stats.pid}</Text>\n </Box>\n <Box justifyContent=\"space-between\">\n <Text color={theme.info.label}>Up</Text>\n <Text color={theme.info.value}>{stats.uptime}</Text>\n </Box>\n {stats.lastQueryMs !== null && (\n <Box justifyContent=\"space-between\">\n <Text color={theme.info.label}>Query</Text>\n <Text color={stats.lastQueryMs > 500 ? theme.edited : theme.sms}>\n {stats.lastQueryMs}ms\n </Text>\n </Box>\n )}\n <Box justifyContent=\"space-between\">\n <Text color={theme.info.label}>Lag</Text>\n <Text\n color={\n stats.eventLoopP99Ms > 500\n ? theme.cpuHigh\n : stats.eventLoopP99Ms > 100\n ? theme.edited\n : theme.info.value\n }\n >\n {stats.eventLoopP99Ms}ms\n </Text>\n </Box>\n <Box justifyContent=\"space-between\">\n <Text color={theme.info.label}>Active</Text>\n <Text color={theme.info.value}>{stats.lastActivityAgo}</Text>\n </Box>\n </Box>\n </Box>\n );\n}\n\n/** Compact inline stats for the status bar (when full panel is hidden) */\nexport function CompactStats({ stats }: { stats: DevStatsData }) {\n const theme = useTheme();\n const engineColor = stats.engine.startsWith(\"Rust\") ? theme.rustEngine : theme.status.accent;\n return (\n <Box gap={1}>\n <Text color={engineColor}>{stats.engine}</Text>\n <Text color={theme.info.label}>{stats.cpuPercent}%</Text>\n <Text color={theme.info.label}>{stats.memMB}MB</Text>\n <Text color={theme.info.label}>PID:{stats.pid}</Text>\n </Box>\n );\n}\n","import { TextInput } from \"@inkjs/ui\";\nimport { Box, Text } from \"ink\";\nimport { useTheme } from \"../themes/ThemeContext.js\";\n\ninterface Props {\n format: \"markdown\" | \"csv\" | \"json\";\n path: string;\n rangeSummary: string;\n onChangePath: (p: string) => void;\n onSubmit: () => void;\n}\n\n/**\n * Export modal — full-width banner at the top of the body area.\n * The format is cycled via Tab in App.tsx (which dispatches SET_EXPORT_FORMAT\n * before rendering this). The path is the only inline-editable field here.\n */\nexport function ExportModal({ format, path, rangeSummary, onChangePath, onSubmit }: Props) {\n const theme = useTheme();\n return (\n <Box flexDirection=\"column\" borderStyle=\"double\" borderColor={theme.status.accent} paddingX={1}>\n <Box>\n <Text color={theme.status.accent} bold>\n Export messages\n </Text>\n </Box>\n <Box>\n <Text color={theme.info.label}>Range: </Text>\n <Text color={theme.info.value}>{rangeSummary}</Text>\n </Box>\n <Box>\n <Text color={theme.info.label}>Format: </Text>\n <Text\n color={format === \"markdown\" ? theme.status.accent : theme.info.value}\n bold={format === \"markdown\"}\n >\n [Markdown]\n </Text>\n <Text> </Text>\n <Text\n color={format === \"csv\" ? theme.status.accent : theme.info.value}\n bold={format === \"csv\"}\n >\n [CSV]\n </Text>\n <Text> </Text>\n <Text\n color={format === \"json\" ? theme.status.accent : theme.info.value}\n bold={format === \"json\"}\n >\n [JSON]\n </Text>\n <Text color={theme.help.desc}> (Tab cycles)</Text>\n </Box>\n <Box>\n <Text color={theme.info.label}>Path: </Text>\n <TextInput\n defaultValue={path}\n onChange={onChangePath}\n onSubmit={onSubmit}\n placeholder=\"/absolute/path/to/file\"\n />\n </Box>\n <Box>\n <Text color={theme.help.desc}>Enter: save Esc: cancel Tab: cycle format</Text>\n </Box>\n </Box>\n );\n}\n","import { Box, Text } from \"ink\";\nimport { useTheme } from \"../themes/ThemeContext.js\";\nimport type { FocusPane, Mode } from \"../types.js\";\n\ninterface Props {\n mode: Mode;\n focus?: FocusPane;\n}\n\nconst SIDEBAR_KEYS = [\n [\"j/k\", \"move\"],\n [\"#j/k\", \"jump\"],\n [\"gg/G\", \"top/btm\"],\n [\"^d/u\", \"½page\"],\n [\"y\", \"copy slug\"],\n [\"Tab\", \"→msgs\"],\n [\"/\", \"filter\"],\n [\"d\", \"stats\"],\n [\"r\", \"refresh\"],\n [\"q\", \"quit\"],\n];\n\nconst THREAD_KEYS = [\n [\"j/k\", \"move\"],\n [\"#j/k\", \"jump\"],\n [\"{/}\", \"grp jump\"],\n [\"gg/G\", \"top/btm\"],\n [\"^d/u\", \"½page\"],\n [\"V\", \"select\"],\n [\":\", \"date jump\"],\n [\"Enter\", \"details\"],\n [\"o\", \"open att\"],\n [\"O\", \"open in Msgs\"],\n [\"S\", \"send-via\"],\n [\"c\", \"compose\"],\n [\"d\", \"stats\"],\n [\"Tab\", \"→list\"],\n];\n\nconst SEND_VIA_KEYS = [\n [\"1-9\", \"pick app\"],\n [\"Esc\", \"cancel\"],\n];\n\nconst SELECT_KEYS = [\n [\"j/k\", \"extend\"],\n [\"{/}\", \"grp\"],\n [\"gg/G\", \"top/btm\"],\n [\"^d/u\", \"½page\"],\n [\"e\", \"export\"],\n [\"y\", \"copy text\"],\n [\"Esc\", \"exit select\"],\n];\n\nconst EXPORT_KEYS = [\n [\"Tab\", \"fmt: md/csv/json\"],\n [\"Enter\", \"save\"],\n [\"Esc\", \"cancel\"],\n];\n\nconst DATE_JUMP_KEYS = [\n [\"Tab\", \"picker↔text\"],\n [\"←/→\", \"field\"],\n [\"↑/↓\", \"adjust\"],\n [\"Enter\", \"jump\"],\n [\"Esc\", \"cancel\"],\n];\n\nconst COMPOSE_KEYS = [\n [\"Enter\", \"send\"],\n [\"Esc\", \"cancel\"],\n];\n\nconst FILTER_KEYS = [[\"Enter/Esc\", \"exit filter\"]];\n\nconst DRAWER_KEYS = [\n [\"j/k\", \"scroll\"],\n [\"o\", \"open attachment\"],\n [\"Esc/q\", \"close\"],\n];\n\nexport function HelpBar({ mode, focus }: Props) {\n const theme = useTheme();\n let keys: string[][];\n if (mode === \"compose\" || mode === \"confirm\") keys = COMPOSE_KEYS;\n else if (mode === \"filter\") keys = FILTER_KEYS;\n else if (mode === \"drawer\") keys = DRAWER_KEYS;\n else if (mode === \"select\") keys = SELECT_KEYS;\n else if (mode === \"export\") keys = EXPORT_KEYS;\n else if (mode === \"date-jump\") keys = DATE_JUMP_KEYS;\n else if (mode === \"send-via\") keys = SEND_VIA_KEYS;\n else keys = focus === \"thread\" ? THREAD_KEYS : SIDEBAR_KEYS;\n\n return (\n <Box paddingX={1} height={1} gap={1}>\n {keys.map(([key, desc]) => (\n <Box key={key}>\n <Text color={theme.help.key}>{key}</Text>\n <Text color={theme.help.desc}>:{desc} </Text>\n </Box>\n ))}\n </Box>\n );\n}\n","/**\n * Glyph presets — the user-selectable \"theme\" axis.\n *\n * - \"safe\" (default): only universally-renderable glyphs. Geometric\n * Shapes (▶ ◀ ●), ASCII separators (─), emoji where every modern OS\n * font has them (✉ 💬 📱). No Powerline private-use codepoints, no\n * Nerd Font icons. Renders correctly in Apple Terminal, Warp, iTerm2,\n * VS Code's integrated terminal, Alacritty defaults, kitty defaults.\n *\n * - \"powerline\": Powerline arrows + Nerd Font icons. Looks better but\n * requires the user's terminal font to be a Nerd-Font-patched build\n * (FiraCode Nerd Font, JetBrainsMono Nerd Font, etc.). The README\n * warns about this. `imsg config show` flags it too.\n *\n * Categories — the consumer (theme.ts → makeTheme) just spreads one of\n * these into the produced Theme object.\n */\n\nexport interface GlyphSet {\n /** Powerline arrow separators. Only used when the user opts in. */\n arrowRight: string;\n arrowRightThin: string;\n arrowLeft: string;\n arrowLeftThin: string;\n\n /** Direction indicators on each message row. Always single-cell-wide. */\n sent: string;\n received: string;\n\n /** Status indicators */\n unreadDot: string;\n envelope: string;\n\n /** Service icons (iMessage / SMS) */\n iMessage: string;\n sms: string;\n\n /** Misc UI */\n paperclip: string;\n group: string;\n search: string;\n pencil: string;\n refresh: string;\n separator: string;\n}\n\nconst SAFE: GlyphSet = {\n // Powerline arrows aren't actually safe — for the safe preset we\n // substitute simple ASCII triangles. Components that draw arrow\n // separators between segments fall back to vertical bar `│`.\n arrowRight: \"│\",\n arrowRightThin: \"│\",\n arrowLeft: \"│\",\n arrowLeftThin: \"│\",\n // Geometric Shapes — fixed-width East-Asian range, every font has them.\n sent: \"▶\", // ▶\n received: \"◀\", // ◀\n unreadDot: \"●\",\n envelope: \"✉\",\n iMessage: \"💬\",\n sms: \"📱\",\n paperclip: \"📎\",\n group: \"☰\",\n search: \"⌕\",\n pencil: \"✎\",\n refresh: \"↻\",\n separator: \"─\",\n};\n\nconst POWERLINE: GlyphSet = {\n // Powerline private-use range (E0B0..E0B3).\n arrowRight: \"\",\n arrowRightThin: \"\",\n arrowLeft: \"\",\n arrowLeftThin: \"\",\n // Use the Powerline arrows as direction glyphs too — visually consistent.\n sent: \"\",\n received: \"\",\n unreadDot: \"●\",\n envelope: \"\", // Nerd Font envelope (FontAwesome)\n iMessage: \"\", // Nerd Font speech bubble\n sms: \"\", // Nerd Font phone\n paperclip: \"\", // Nerd Font paperclip\n group: \"\", // Nerd Font group\n search: \"\", // Nerd Font magnifier\n pencil: \"\", // Nerd Font pencil\n refresh: \"\", // Nerd Font refresh\n separator: \"─\",\n};\n\nexport const GLYPH_PRESETS = {\n safe: SAFE,\n powerline: POWERLINE,\n} as const;\n\nexport type GlyphPreset = keyof typeof GLYPH_PRESETS;\n\nexport const TAPBACK_EMOJI: Record<string, string> = {\n love: \"❤️\",\n like: \"👍\",\n dislike: \"👎\",\n laugh: \"😂\",\n emphasize: \"‼️\",\n question: \"❓\",\n};\n","/**\n * Pure color-space utilities. No dependencies, no side effects.\n *\n * The TUI palette derives its 30-odd hex colors from a single accent\n * hex; that derivation is HSL math (rotate hue, mix toward grey, push\n * lightness up or down). All of those operations live here so they can\n * be unit-tested without dragging in any rendering code.\n *\n * Hex format: always 7-char `#RRGGBB`. Output strings are lower-case.\n * HSL: h ∈ [0, 360), s ∈ [0, 1], l ∈ [0, 1].\n */\n\nexport interface Hsl {\n h: number;\n s: number;\n l: number;\n}\n\nconst HEX6_RE = /^#[0-9a-fA-F]{6}$/;\n\n/** Parse `#rrggbb` to {r,g,b} ∈ [0,255]. Throws on invalid input. */\nfunction hexToRgb(hex: string): { r: number; g: number; b: number } {\n if (!HEX6_RE.test(hex)) {\n throw new Error(`invalid hex color: ${hex} (expected #RRGGBB)`);\n }\n return {\n r: Number.parseInt(hex.slice(1, 3), 16),\n g: Number.parseInt(hex.slice(3, 5), 16),\n b: Number.parseInt(hex.slice(5, 7), 16),\n };\n}\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n const c = (n: number) =>\n Math.max(0, Math.min(255, Math.round(n)))\n .toString(16)\n .padStart(2, \"0\");\n return `#${c(r)}${c(g)}${c(b)}`;\n}\n\nexport function hexToHsl(hex: string): Hsl {\n const { r, g, b } = hexToRgb(hex);\n const rn = r / 255;\n const gn = g / 255;\n const bn = b / 255;\n const max = Math.max(rn, gn, bn);\n const min = Math.min(rn, gn, bn);\n const l = (max + min) / 2;\n const d = max - min;\n let h = 0;\n let s = 0;\n if (d !== 0) {\n s = d / (1 - Math.abs(2 * l - 1));\n if (max === rn) h = 60 * (((gn - bn) / d) % 6);\n else if (max === gn) h = 60 * ((bn - rn) / d + 2);\n else h = 60 * ((rn - gn) / d + 4);\n if (h < 0) h += 360;\n }\n return { h, s, l };\n}\n\nexport function hslToHex({ h, s, l }: Hsl): string {\n const hh = ((h % 360) + 360) % 360;\n const ss = Math.max(0, Math.min(1, s));\n const ll = Math.max(0, Math.min(1, l));\n const c = (1 - Math.abs(2 * ll - 1)) * ss;\n const x = c * (1 - Math.abs(((hh / 60) % 2) - 1));\n const m = ll - c / 2;\n let r1 = 0;\n let g1 = 0;\n let b1 = 0;\n if (hh < 60) [r1, g1, b1] = [c, x, 0];\n else if (hh < 120) [r1, g1, b1] = [x, c, 0];\n else if (hh < 180) [r1, g1, b1] = [0, c, x];\n else if (hh < 240) [r1, g1, b1] = [0, x, c];\n else if (hh < 300) [r1, g1, b1] = [x, 0, c];\n else [r1, g1, b1] = [c, 0, x];\n return rgbToHex((r1 + m) * 255, (g1 + m) * 255, (b1 + m) * 255);\n}\n\n/** Return `hex` with lightness set to `l` ∈ [0,1]. */\nexport function withL(hex: string, l: number): string {\n const hsl = hexToHsl(hex);\n return hslToHex({ ...hsl, l });\n}\n\n/** Return `hex` with saturation set to `s` ∈ [0,1]. s=0 produces grey at the same lightness. */\nexport function withS(hex: string, s: number): string {\n const hsl = hexToHsl(hex);\n return hslToHex({ ...hsl, s });\n}\n\n/** Rotate hue by `degrees`. Negative values supported. */\nexport function rotateHue(hex: string, degrees: number): string {\n const hsl = hexToHsl(hex);\n return hslToHex({ ...hsl, h: hsl.h + degrees });\n}\n\n/**\n * Produce a desaturated, dark-or-light-tinted variant of `accent`. Used\n * by the palette to make borders / backgrounds / dim text feel like they\n * belong to the accent's \"color family\" without screaming.\n *\n * @param l Target lightness ∈ [0,1]\n * @param satMul Multiplier on the original saturation (1 = keep, 0 = grey)\n */\nexport function tint(accent: string, l: number, satMul: number): string {\n const hsl = hexToHsl(accent);\n return hslToHex({ h: hsl.h, s: hsl.s * satMul, l });\n}\n\n/**\n * WCAG-approximate relative luminance of a hex color.\n * Used by tests to assert palette-pair contrast ratios.\n */\nexport function relativeLuminance(hex: string): number {\n const { r, g, b } = hexToRgb(hex);\n const channel = (c: number) => {\n const cs = c / 255;\n return cs <= 0.03928 ? cs / 12.92 : ((cs + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);\n}\n\n/** Contrast ratio between two hex colors per WCAG 2.0. Range [1, 21]. */\nexport function contrastRatio(a: string, b: string): number {\n const la = relativeLuminance(a);\n const lb = relativeLuminance(b);\n const [hi, lo] = la > lb ? [la, lb] : [lb, la];\n return (hi + 0.05) / (lo + 0.05);\n}\n","/**\n * Single-accent palette derivation.\n *\n * The user picks one hex (default `#1982FC` = iMessage blue) and the\n * accent-driven parts of the palette come out of it. Approach:\n *\n * - **Accent-driven**: backgrounds, borders, headers, sidebar selection,\n * sent-bubble fill, and the unread dot all use the accent's hue at\n * varying lightness × saturation. Move the accent to orange and the\n * whole UI shifts orange.\n * - **Neutral with accent tint**: text colors and dim backgrounds keep\n * the accent's *hue* but at very low saturation (~5–10%). They feel\n * like part of the family without competing with the accent itself.\n * - **Semantic colors stay fixed**: error red, sms green, attachment\n * warm-yellow, edited gold. These need to be recognizable regardless\n * of accent (a magenta accent shouldn't make SMS markers magenta).\n * They're hand-tuned to coexist with any accent.\n *\n * The exported `Palette` shape mirrors what the legacy `theme` object\n * exported — the hand-tuned iMessage-inspired values are the target\n * for `accent = #1982FC`.\n */\n\nimport { contrastRatio, hexToHsl, hslToHex, withL, withS } from \"./color.js\";\n\nexport interface Palette {\n sent: { bg: string; fg: string; border: string };\n received: { bg: string; fg: string; border: string };\n pending: { bg: string; fg: string; border: string };\n\n sentText: string;\n receivedText: string;\n senderName: string;\n replyContext: string;\n attachment: string;\n lineNum: string;\n\n groupBg: { sent: string; received: string };\n selectionBg: string;\n\n sidebar: {\n selected: string;\n selectedFg: string;\n unread: string;\n read: string;\n snippet: string;\n slug: string;\n slugBg: string;\n separator: string;\n time: string;\n };\n\n border: string;\n dot: string;\n header: { focused: { bg: string; fg: string }; dim: { bg: string; fg: string } };\n info: { label: string; value: string };\n timestamp: string;\n status: { bg: string; fg: string; accent: string };\n help: { key: string; desc: string };\n sms: string;\n edited: string;\n compose: { bg: string; fg: string; placeholder: string };\n dateSep: string;\n drawer: { bg: string; border: string; label: string; value: string };\n\n /** Diagnostic — DevStats indicators that previously hard-coded #FF6B35 / #FF4444. */\n rustEngine: string;\n cpuHigh: string;\n}\n\nexport const DEFAULT_ACCENT = \"#1982FC\";\n\nexport function derivePalette(accent: string): Palette {\n const { h } = hexToHsl(accent);\n // Lightness clamp helpers — accept any input accent, never produce\n // text that's invisible on the dark TUI background.\n const acc = (l: number) => hslToHex({ h, s: 0.95, l });\n // Near-grey, slight accent tint — for borders, dim text, neutral bgs.\n const tint = (l: number, s = 0.06) => hslToHex({ h, s, l });\n // Choose whichever of {near-black, near-white} produces better\n // contrast against `bg`. Used by sent bubbles + sidebar selection\n // where the bg is accent-driven and may swing across the lightness\n // spectrum (magenta at L=0.55 has just-medium luminance and needs\n // dark fg; iMessage blue at L=0.55 needs light fg).\n const dark = tint(0.05, 0.1);\n const light = tint(0.97, 0.05);\n const fgFor = (bg: string) => (contrastRatio(dark, bg) > contrastRatio(light, bg) ? dark : light);\n\n const sentBg = acc(0.55); // accent at full saturation, mid-light → \"iMessage blue\"\n const selectedBg = acc(0.27);\n return {\n sent: {\n bg: sentBg,\n fg: fgFor(sentBg),\n border: acc(0.42),\n },\n received: {\n // Light grey bubble, very slight accent tint — preserves the iMessage\n // \"received\" look. Stays the same shade across accents (it's mostly grey).\n bg: tint(0.91, 0.05),\n fg: tint(0.12, 0.1),\n border: tint(0.75, 0.05),\n },\n pending: {\n bg: tint(0.27, 0.06),\n fg: tint(0.74, 0.06),\n border: tint(0.35, 0.08),\n },\n\n sentText: acc(0.78), // light accent — for sent text shown outside a bubble\n receivedText: tint(0.85, 0.06),\n // Sender names: a fixed teal that reads as \"another person\" in every\n // accent. Keeping accent-derived would produce magenta sender names on\n // a magenta accent, defeating the visual cue.\n senderName: SEMANTIC.senderName,\n replyContext: tint(0.62, 0.1),\n attachment: SEMANTIC.attachment, // fixed warm yellow\n lineNum: tint(0.45, 0.05),\n\n groupBg: {\n sent: tint(0.16, 0.4), // dark, clearly accent-tinted (sent rows)\n received: hslToHex({ h: (h + 20) % 360, s: 0.18, l: 0.14 }), // close-but-not-equal\n },\n selectionBg: SEMANTIC.selection, // muted warm yellow — universal \"selected\"\n\n sidebar: {\n selected: selectedBg,\n selectedFg: fgFor(selectedBg),\n unread: tint(0.97, 0.05),\n read: tint(0.74, 0.06),\n snippet: tint(0.51, 0.05),\n slug: hslToHex({ h, s: 0.28, l: 0.55 }),\n slugBg: tint(0.1, 0.15),\n separator: tint(0.2, 0.05),\n time: tint(0.51, 0.05),\n },\n\n border: tint(0.25, 0.05),\n dot: acc(0.55),\n header: {\n focused: { bg: tint(0.2, 0.06), fg: tint(0.97, 0.05) },\n dim: { bg: tint(0.13, 0.06), fg: tint(0.51, 0.05) },\n },\n info: { label: tint(0.62, 0.05), value: tint(0.85, 0.05) },\n timestamp: tint(0.62, 0.1),\n status: { bg: tint(0.13, 0.06), fg: tint(0.74, 0.06), accent: acc(0.55) },\n help: { key: tint(0.74, 0.06), desc: tint(0.4, 0.05) },\n sms: SEMANTIC.sms, // green — universal \"SMS not iMessage\" cue\n edited: SEMANTIC.edited, // gold — universal \"this was edited\" cue\n compose: {\n bg: tint(0.18, 0.06),\n fg: tint(0.97, 0.05),\n placeholder: tint(0.4, 0.05),\n },\n dateSep: tint(0.32, 0.05),\n drawer: {\n bg: tint(0.13, 0.06),\n border: tint(0.25, 0.05),\n label: tint(0.62, 0.05),\n value: tint(0.85, 0.05),\n },\n\n rustEngine: SEMANTIC.rustEngine, // warm orange — distinct from any accent\n cpuHigh: SEMANTIC.cpuHigh, // red — universal \"elevated\"\n };\n}\n\n/** Hand-tuned constants kept outside the accent-derivation loop. */\nconst SEMANTIC = {\n senderName: \"#5AC8C8\", // teal\n attachment: \"#FFB347\", // orange (paperclip)\n selection: \"#3C3814\", // dim olive (visual select bg)\n sms: \"#5AC85A\", // green (SMS marker)\n edited: \"#96821E\", // gold (edited indicator)\n rustEngine: \"#FF6B35\", // warm orange (DevStats engine label)\n cpuHigh: \"#FF4444\", // red (DevStats elevated CPU)\n} as const;\n\n// Surface tiny helpers for tests / setup probes.\nexport { withL, withS };\n","/**\n * TUI theme — assembled from a glyph preset + an accent-derived palette.\n *\n * Components consume the resolved theme via `useTheme()` from\n * `./themes/ThemeContext.js`. This file's `makeTheme()` is the single\n * place where palette + glyphs combine into the final shape.\n *\n * Backwards-compat: a few non-component places still want a constant\n * `glyphs` / `TAPBACK_EMOJI`. Those remain re-exported here. The legacy\n * `theme` constant export is gone — components were swept to `useTheme()`\n * in the same change as this refactor.\n */\n\nimport { GLYPH_PRESETS, type GlyphPreset, type GlyphSet } from \"./themes/glyphs.js\";\nimport { DEFAULT_ACCENT, derivePalette, type Palette } from \"./themes/palette.js\";\n\nexport interface Theme extends Palette {\n glyphs: GlyphSet;\n}\n\nexport interface ThemeOptions {\n preset?: GlyphPreset;\n accent?: string;\n}\n\nexport function makeTheme({ preset = \"safe\", accent = DEFAULT_ACCENT }: ThemeOptions = {}): Theme {\n return {\n ...derivePalette(accent),\n glyphs: GLYPH_PRESETS[preset],\n };\n}\n\nexport { GLYPH_PRESETS, type GlyphPreset, type GlyphSet, TAPBACK_EMOJI } from \"./themes/glyphs.js\";\nexport { DEFAULT_ACCENT } from \"./themes/palette.js\";\n","import { Box, Text } from \"ink\";\nimport type { Message } from \"../../types.js\";\nimport { TAPBACK_EMOJI } from \"../theme.js\";\nimport { useTheme } from \"../themes/ThemeContext.js\";\n\ninterface Props {\n message: Message;\n width: number;\n height: number;\n}\n\nfunction formatFullDate(date: Date): string {\n return date.toLocaleString(\"en-US\", {\n weekday: \"short\",\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: true,\n });\n}\n\nfunction Label({ children }: { children: string }) {\n const theme = useTheme();\n return <Text color={theme.drawer.label}>{children}: </Text>;\n}\n\nexport function MessageDrawer({ message: m, width, height }: Props) {\n const theme = useTheme();\n const hasAttachments = m.attachments && m.attachments.length > 0;\n\n return (\n <Box\n flexDirection=\"column\"\n width={width}\n height={height}\n borderStyle=\"single\"\n borderColor={theme.drawer.border}\n overflow=\"hidden\"\n >\n {/* Header */}\n <Box paddingX={1} backgroundColor={theme.header.focused.bg}>\n <Text color={theme.header.focused.fg} bold>\n Message Details\n </Text>\n </Box>\n\n <Box flexDirection=\"column\" paddingX={1} paddingY={0} gap={0}>\n {/* Sender */}\n <Box>\n <Label>From</Label>\n <Text color={theme.drawer.value}>{m.isFromMe ? \"Me\" : (m.displayName ?? m.handle)}</Text>\n </Box>\n\n {/* Handle */}\n {!m.isFromMe && m.displayName && (\n <Box>\n <Label>Handle</Label>\n <Text color={theme.drawer.value}>{m.handle}</Text>\n </Box>\n )}\n\n {/* Date */}\n <Box>\n <Label>Sent</Label>\n <Text color={theme.drawer.value}>{formatFullDate(m.date)}</Text>\n </Box>\n\n {/* Read/Delivered */}\n {m.dateDelivered && (\n <Box>\n <Label>Delivered</Label>\n <Text color={theme.drawer.value}>{formatFullDate(m.dateDelivered)}</Text>\n </Box>\n )}\n {m.dateRead && (\n <Box>\n <Label>Read</Label>\n <Text color={theme.drawer.value}>{formatFullDate(m.dateRead)}</Text>\n </Box>\n )}\n\n {/* Service */}\n <Box>\n <Label>Service</Label>\n <Text color={m.service === \"SMS\" ? theme.sms : theme.info.label}>{m.service}</Text>\n </Box>\n\n {/* Chat ID */}\n <Box>\n <Label>Chat</Label>\n <Text color={theme.drawer.value}>{m.chatId}</Text>\n </Box>\n\n {/* GUID */}\n <Box>\n <Label>GUID</Label>\n <Text color={theme.drawer.value} wrap=\"truncate\">\n {m.guid}\n </Text>\n </Box>\n\n {/* Edited */}\n {m.isEdited && (\n <Box>\n <Label>Status</Label>\n <Text color={theme.edited}>Edited</Text>\n </Box>\n )}\n\n {/* Reactions */}\n {m.reactions && m.reactions.length > 0 && (\n <Box flexDirection=\"column\">\n <Text color={theme.drawer.label}>Reactions:</Text>\n {m.reactions\n .filter((r) => !r.isRemoval)\n .map((r) => (\n <Box\n key={`${r.fromHandle}-${r.type}-${r.emoji ?? \"\"}-${r.targetMessageGuid}-${r.targetMessagePart}`}\n paddingLeft={1}\n >\n <Text color={theme.drawer.value}>\n {r.emoji ?? TAPBACK_EMOJI[r.type] ?? r.type} {r.fromHandle ?? \"unknown\"}\n </Text>\n </Box>\n ))}\n </Box>\n )}\n\n {/* Attachments */}\n {hasAttachments && (\n <Box flexDirection=\"column\" marginTop={1}>\n <Text color={theme.attachment} bold>\n Attachments ({m.attachments!.length}):\n </Text>\n {m.attachments!.map((att) => {\n return (\n <Box\n key={`${att.filename}-${att.transferName ?? \"\"}-${att.mimeType ?? \"\"}-${att.totalBytes}`}\n flexDirection=\"column\"\n paddingLeft={1}\n >\n <Text color={theme.drawer.value} wrap=\"truncate\">\n {att.transferName ?? att.filename}\n </Text>\n <Text color={theme.drawer.label}>\n {att.mimeType ?? \"unknown\"} ·{\" \"}\n {att.totalBytes > 0 ? formatBytes(att.totalBytes) : \"?\"}\n <Text color={theme.senderName}> (press o to preview)</Text>\n </Text>\n </Box>\n );\n })}\n <Box marginTop={1}>\n <Text color={theme.help.key}>o</Text>\n <Text color={theme.help.desc}>: open attachment</Text>\n </Box>\n </Box>\n )}\n\n {/* Full message text */}\n <Box flexDirection=\"column\" marginTop={1}>\n <Text color={theme.drawer.label}>Full text:</Text>\n <Box borderStyle=\"single\" borderColor={theme.drawer.border} paddingX={1}>\n <Text color={theme.drawer.value} wrap=\"wrap\">\n {m.text ?? \"(no text)\"}\n </Text>\n </Box>\n </Box>\n\n {/* Reply context */}\n {m.isReply && m.replyTo && (\n <Box flexDirection=\"column\" marginTop={1}>\n <Text color={theme.drawer.label}>Reply to:</Text>\n <Text color={theme.replyContext} italic wrap=\"wrap\">\n {m.replyTo.replyToText ?? \"(unknown)\"}\n </Text>\n </Box>\n )}\n </Box>\n\n {/* Footer hint */}\n <Box flexGrow={1} />\n <Box paddingX={1}>\n <Text color={theme.help.desc}>Esc/q to close</Text>\n </Box>\n </Box>\n );\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes}B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\n}\n","/**\n * Send-via modal — pick an installed external chat app and launch its URL\n * scheme deep-link for the current thread's handle. Triggered by `S` in\n * thread pane (TUI handler in App.tsx).\n *\n * Renders a numbered list of installed apps. Press `1`–`9` to launch,\n * Esc to cancel. The TUI's mode-level `useInput` handler in App.tsx\n * actually does the launching; this component is presentation-only.\n */\nimport { Box, Text } from \"ink\";\nimport type { ChatAppDef } from \"../../url-schemes.js\";\nimport { useTheme } from \"../themes/ThemeContext.js\";\n\ninterface Props {\n handle: string;\n apps: ChatAppDef[];\n}\n\nexport function SendViaModal({ handle, apps }: Props) {\n const theme = useTheme();\n return (\n <Box flexDirection=\"column\" borderStyle=\"double\" borderColor={theme.status.accent} paddingX={1}>\n <Text color={theme.status.accent} bold>\n Send via external app\n </Text>\n <Text color={theme.help.desc}>Handle: {handle}</Text>\n <Box flexDirection=\"column\" marginTop={1}>\n {apps.length === 0 ? (\n <Text color={theme.help.desc}>No compatible apps installed.</Text>\n ) : (\n apps.map((a, i) => (\n <Box key={a.name}>\n <Text color={theme.help.key}>{i + 1}</Text>\n <Text color={theme.help.desc}>\n {\": \"}\n {a.name}\n {a.supportsBody ? \"\" : \" (no body support)\"}\n </Text>\n </Box>\n ))\n )}\n </Box>\n <Box marginTop={1}>\n <Text color={theme.help.desc}>1-9: launch · Esc: cancel</Text>\n </Box>\n </Box>\n );\n}\n","import { Box, Text } from \"ink\";\nimport type { Conversation } from \"../../types.js\";\nimport { useTheme } from \"../themes/ThemeContext.js\";\n\nfunction relativeDate(date: Date | null): string {\n if (!date) return \"\";\n const now = new Date();\n const time = date.toLocaleTimeString(\"en-US\", {\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true,\n });\n if (date.toDateString() === now.toDateString()) return time;\n const y = new Date(now);\n y.setDate(now.getDate() - 1);\n if (date.toDateString() === y.toDateString()) return \"Yest\";\n return `${date.getMonth() + 1}/${date.getDate()}`;\n}\n\ninterface Props {\n conversation: Conversation;\n selected: boolean;\n width: number;\n lineNum?: string;\n focused?: boolean;\n isLast?: boolean;\n}\n\n/**\n * Width budget for the conversation row. Returning a precise budget for the\n * name text prevents the time/icons from wrapping onto the next row and\n * obscuring the snippet below — the original symptom of the layout bug.\n *\n * Layout (single row):\n * [lineNum 4][cursor 2][envelope 2?][group 2?][NAME ...truncated...] (count?) icon time\n * ^^^^^^^^^^^^^^^^^^^^\n * this is what we budget\n */\nfunction nameBudget(\n width: number,\n hasUnread: boolean,\n unreadCount: number,\n isGroup: boolean,\n): number {\n const lineNumW = 4; // \"123 \"\n const cursorW = 2; // \"▸ \" or \" \"\n const envelopeW = hasUnread ? 2 : 0;\n const groupW = isGroup ? 2 : 0;\n const countW = hasUnread ? ` (${unreadCount})`.length : 0;\n const iconW = 2; // \" 💬\"\n const timeW = 9; // max \" 12:34 PM\"\n const padding = 2; // safety margin\n return Math.max(\n width - lineNumW - cursorW - envelopeW - groupW - countW - iconW - timeW - padding,\n 8,\n );\n}\n\nexport function ConversationItem({\n conversation: c,\n selected,\n width,\n lineNum,\n focused,\n isLast,\n}: Props) {\n const theme = useTheme();\n const hasUnread = c.unreadCount > 0;\n const name = c.displayName ?? c.chatIdentifier;\n const time = relativeDate(c.lastMessageDate);\n const snippet = c.lastMessageSnippet ?? \"\";\n const serviceIcon = c.serviceType === \"SMS\" ? theme.glyphs.sms : theme.glyphs.iMessage;\n\n const nameW = nameBudget(width, hasUnread, c.unreadCount, c.isGroupChat);\n const truncatedName = name.length > nameW ? `${name.slice(0, Math.max(nameW - 1, 1))}…` : name;\n const snippetW = Math.max(width - 6, 8); // -5 for left padding, -1 safety\n const truncatedSnippet = snippet.length > snippetW ? snippet.slice(0, snippetW) : snippet;\n\n return (\n <Box flexDirection=\"column\" width={width}>\n <Box\n flexDirection=\"column\"\n width={width}\n backgroundColor={selected ? theme.sidebar.selected : undefined}\n >\n {/* Name row — left+right split so time can't wrap */}\n <Box width={width} justifyContent=\"space-between\">\n {/* Left side */}\n <Box flexShrink={1}>\n {lineNum !== undefined && (\n <Text color={selected && focused ? theme.sent.bg : theme.lineNum}>\n {lineNum.padStart(3)}{\" \"}\n </Text>\n )}\n <Text color={selected && focused ? theme.sent.bg : undefined}>\n {selected && focused ? \"▸\" : \" \"}\n </Text>\n {hasUnread && <Text color={theme.dot}>{theme.glyphs.envelope} </Text>}\n {c.isGroupChat && <Text color={theme.info.label}>{theme.glyphs.group} </Text>}\n <Text\n color={\n selected\n ? theme.sidebar.selectedFg\n : hasUnread\n ? theme.sidebar.unread\n : theme.sidebar.read\n }\n bold={hasUnread}\n wrap=\"truncate\"\n >\n {truncatedName}\n </Text>\n </Box>\n\n {/* Right side: count + service icon + time, fixed-width */}\n <Box flexShrink={0}>\n {hasUnread && <Text color={theme.sidebar.unread}> ({c.unreadCount})</Text>}\n <Text color={c.serviceType === \"SMS\" ? theme.sms : theme.info.label}>\n {\" \"}\n {serviceIcon}\n </Text>\n <Text color={theme.sidebar.time}> {time}</Text>\n </Box>\n </Box>\n\n {/* Snippet row — explicit width so it can't bleed into adjacent rows */}\n <Box width={width} paddingLeft={5}>\n <Text color={theme.sidebar.snippet} wrap=\"truncate\">\n {truncatedSnippet}\n </Text>\n </Box>\n\n {/* Slug row — right-justified, italic, dim, subtle bg */}\n <Box\n width={width}\n justifyContent=\"flex-end\"\n paddingRight={1}\n backgroundColor={theme.sidebar.slugBg}\n >\n <Text color={theme.sidebar.slug} dimColor italic>\n ~{c.threadSlug}\n </Text>\n </Box>\n </Box>\n\n {/* Separator between items */}\n {!isLast && (\n <Box paddingX={1}>\n <Text color={theme.sidebar.separator} dimColor>\n {theme.glyphs.separator.repeat(Math.max(width - 4, 1))}\n </Text>\n </Box>\n )}\n </Box>\n );\n}\n\n// Exported for tests\nexport { nameBudget };\n","import { Box, Text } from \"ink\";\nimport { useMemo } from \"react\";\nimport type { Conversation } from \"../../types.js\";\nimport { useTheme } from \"../themes/ThemeContext.js\";\nimport { ConversationItem } from \"./ConversationItem.js\";\n\ninterface Props {\n conversations: Conversation[];\n selectedIdx: number;\n scrollOffset: number;\n filterQuery: string;\n focused: boolean;\n width: number;\n height: number;\n}\n\nexport function Sidebar({\n conversations,\n selectedIdx,\n scrollOffset,\n filterQuery,\n focused,\n width,\n height,\n}: Props) {\n const theme = useTheme();\n const filtered = useMemo(() => {\n if (!filterQuery) return conversations;\n const q = filterQuery.toLowerCase();\n return conversations.filter(\n (c) =>\n (c.displayName?.toLowerCase().includes(q) ?? false) ||\n c.chatIdentifier.toLowerCase().includes(q) ||\n c.threadSlug.toLowerCase().includes(q),\n );\n }, [conversations, filterQuery]);\n\n // Each conversation item takes ~4 rows (name, snippet, slug, separator)\n const itemHeight = 4;\n const headerH = 1 + (filterQuery ? 1 : 0);\n const borderH = 2;\n const visibleCount = Math.floor((height - headerH - borderH) / itemHeight);\n const visible = filtered.slice(scrollOffset, scrollOffset + visibleCount);\n\n return (\n <Box\n flexDirection=\"column\"\n width={width}\n height={height}\n borderStyle=\"single\"\n borderColor={focused ? theme.header.focused.fg : theme.border}\n overflow=\"hidden\"\n >\n <Box paddingX={1} backgroundColor={focused ? theme.header.focused.bg : theme.header.dim.bg}>\n <Text color={focused ? theme.header.focused.fg : theme.header.dim.fg} bold={focused}>\n Conversations ({filtered.length})\n </Text>\n </Box>\n\n {filterQuery && (\n <Box paddingX={1}>\n <Text color={theme.status.accent}>/ </Text>\n <Text color={theme.compose.fg}>{filterQuery}</Text>\n </Box>\n )}\n\n <Box flexDirection=\"column\" flexGrow={1} overflow=\"hidden\">\n {visible.length === 0 ? (\n <Box paddingX={1}>\n <Text color={theme.sidebar.snippet}>No conversations</Text>\n </Box>\n ) : (\n visible.map((conv, i) => {\n const realIdx = scrollOffset + i;\n const relNum =\n realIdx === selectedIdx ? `${realIdx}` : `${Math.abs(realIdx - selectedIdx)}`;\n\n return (\n <ConversationItem\n key={conv.threadSlug}\n conversation={conv}\n selected={realIdx === selectedIdx}\n width={width - 2}\n lineNum={relNum}\n focused={focused}\n isLast={i === visible.length - 1}\n />\n );\n })\n )}\n </Box>\n </Box>\n );\n}\n","import { Box, Text } from \"ink\";\nimport type React from \"react\";\nimport type { Conversation } from \"../../types.js\";\nimport { useTheme } from \"../themes/ThemeContext.js\";\n\ninterface Props {\n totalUnread: number;\n selected: Conversation | undefined;\n status: string;\n loading: boolean;\n children?: React.ReactNode;\n}\n\nexport function StatusBar({ totalUnread, selected, status, loading, children }: Props) {\n const theme = useTheme();\n return (\n <Box backgroundColor={theme.status.bg} paddingX={1} height={1} justifyContent=\"space-between\">\n <Box gap={2}>\n {totalUnread > 0 && (\n <Text color={theme.status.accent} bold>\n ● {totalUnread} unread\n </Text>\n )}\n {selected && (\n <Text color={theme.status.fg}>{selected.displayName ?? selected.chatIdentifier}</Text>\n )}\n {selected && (\n <Text color={selected.serviceType === \"SMS\" ? theme.sms : theme.info.label}>\n {selected.serviceType}\n </Text>\n )}\n </Box>\n <Box gap={2}>\n {children}\n <Text color={theme.status.fg}>{loading ? \"loading...\" : status}</Text>\n </Box>\n </Box>\n );\n}\n","import { TextInput } from \"@inkjs/ui\";\nimport { Box, Text } from \"ink\";\nimport { useTheme } from \"../themes/ThemeContext.js\";\nimport type { Mode } from \"../types.js\";\n\ninterface Props {\n mode: Mode;\n recipientName: string;\n onChangeText: (text: string) => void;\n onSubmit: (text: string) => void;\n}\n\nexport function ComposeBar({ mode, recipientName, onChangeText, onSubmit }: Props) {\n const theme = useTheme();\n if (mode === \"confirm\") {\n return (\n <Box backgroundColor={theme.compose.bg} paddingX={1} height={1}>\n <Text color={theme.status.accent} bold>\n Send to {recipientName}?{\" \"}\n </Text>\n <Text color={theme.compose.fg}>Enter: send Esc: cancel</Text>\n </Box>\n );\n }\n\n if (mode === \"compose\") {\n return (\n <Box backgroundColor={theme.compose.bg} paddingX={1} height={1}>\n <Text color={theme.compose.fg}>&gt; </Text>\n <TextInput onChange={onChangeText} onSubmit={onSubmit} placeholder=\"Type a message...\" />\n </Box>\n );\n }\n\n return null;\n}\n","import { Box, Text } from \"ink\";\nimport type { Message, Reaction } from \"../../types.js\";\nimport { TAPBACK_EMOJI } from \"../theme.js\";\nimport { useTheme } from \"../themes/ThemeContext.js\";\n\nfunction relativeDate(date: Date): string {\n const now = new Date();\n const time = date.toLocaleTimeString(\"en-US\", {\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true,\n });\n if (date.toDateString() === now.toDateString()) return time;\n const y = new Date(now);\n y.setDate(now.getDate() - 1);\n if (date.toDateString() === y.toDateString()) return `Yest ${time}`;\n return `${date.getMonth() + 1}/${date.getDate()} ${time}`;\n}\n\nfunction formatReactions(reactions: Reaction[]): string {\n const counts = new Map<string, number>();\n for (const r of reactions) {\n if (r.isRemoval) continue;\n const emoji = r.emoji ?? TAPBACK_EMOJI[r.type] ?? r.type;\n counts.set(emoji, (counts.get(emoji) ?? 0) + 1);\n }\n if (counts.size === 0) return \"\";\n return [...counts.entries()].map(([e, c]) => (c > 1 ? `${e}${c}` : e)).join(\"\");\n}\n\n/** Check if two dates are on different calendar days */\nexport function isDifferentDay(a: Date, b: Date): boolean {\n return a.toDateString() !== b.toDateString();\n}\n\n/** Format a date separator label */\nexport function dateSeparator(date: Date): string {\n const now = new Date();\n if (date.toDateString() === now.toDateString()) return \"Today\";\n const y = new Date(now);\n y.setDate(now.getDate() - 1);\n if (date.toDateString() === y.toDateString()) return \"Yesterday\";\n return date.toLocaleDateString(\"en-US\", { weekday: \"short\", month: \"short\", day: \"numeric\" });\n}\n\n/** Determine if a message is the first in a group from the same sender */\nexport function isGroupStart(msg: Message, prev: Message | undefined): boolean {\n if (!prev) return true;\n return msg.isFromMe !== prev.isFromMe || msg.handle !== prev.handle;\n}\n\n/** Determine if a message is the last in a group from the same sender */\nexport function isGroupEnd(msg: Message, next: Message | undefined): boolean {\n if (!next) return true;\n return msg.isFromMe !== next.isFromMe || msg.handle !== next.handle;\n}\n\ninterface Props {\n message: Message;\n maxWidth: number;\n showSender?: boolean;\n senderName?: string;\n selected?: boolean;\n lineNum?: string;\n isFirstInGroup?: boolean; // first message from this sender in a consecutive run\n isLastInGroup?: boolean; // last message from this sender in a consecutive run\n bgTint?: string; // alternating background for sender groups\n /**\n * Optional resolver for reply target text by GUID. When `m.isReply` is\n * true but `m.replyTo.replyToText` is null/undefined (the iMessage DB\n * sometimes leaves it unpopulated even when the link metadata exists),\n * we fall back to looking up the target message in the currently loaded\n * messages array via this callback. If still not found, render a\n * placeholder so the reader at least sees the reply indicator.\n */\n lookupReplyText?: (guid: string) => string | null;\n /** True when this message is inside an active visual selection range. */\n inSelection?: boolean;\n}\n\n/**\n * Compact message row — no borders, minimal vertical space.\n * Sender groups get alternating background tints.\n * First message in group shows sender name; subsequent ones are indented.\n */\nexport function MessageBubble({\n message: m,\n maxWidth,\n showSender,\n senderName,\n selected,\n lineNum,\n isFirstInGroup,\n isLastInGroup,\n bgTint,\n lookupReplyText,\n inSelection,\n}: Props) {\n const theme = useTheme();\n const isSent = m.isFromMe;\n const text = m.text ?? \"(attachment)\";\n const timestamp = relativeDate(m.date);\n const reactions = m.reactions ? formatReactions(m.reactions) : \"\";\n const hasAttachments = m.hasAttachments;\n\n // Cursor indicator\n const cursor = selected ? \"▸\" : \" \";\n const cursorColor = selected ? theme.sent.bg : undefined;\n\n // Only show sender on first message in a group\n const sender = isFirstInGroup\n ? showSender && !isSent && senderName\n ? senderName\n : isSent\n ? \"Me\"\n : undefined\n : undefined;\n\n // Group separator: thin line between groups\n const groupSepChar = isFirstInGroup ? \"┌\" : isLastInGroup ? \"└\" : \"│\";\n const groupColor = isSent ? theme.sent.border : theme.received.border;\n\n return (\n <Box\n flexDirection=\"column\"\n backgroundColor={selected ? theme.sidebar.selected : inSelection ? theme.selectionBg : bgTint}\n >\n <Box>\n {/* Line number */}\n {lineNum !== undefined && (\n <Text color={selected ? theme.sent.bg : theme.lineNum}>{lineNum.padStart(3)} </Text>\n )}\n\n {/* Cursor */}\n <Text color={cursorColor}>{cursor}</Text>\n\n {/* Group gutter — visual thread indicator */}\n <Text color={groupColor}>{groupSepChar} </Text>\n\n {/* Timestamp — only on first in group or if selected */}\n {isFirstInGroup ? (\n <Text color={theme.timestamp}>{timestamp.padEnd(13)}</Text>\n ) : (\n <Text color={theme.timestamp}>{\" \"}</Text>\n )}\n\n {/* Direction indicator + sender (only first in group) */}\n {isFirstInGroup ? (\n <>\n {isSent ? (\n <Text color={theme.sent.bg} bold>\n {theme.glyphs.sent}{\" \"}\n </Text>\n ) : (\n <Text color={theme.received.border} bold>\n {theme.glyphs.received}{\" \"}\n </Text>\n )}\n {sender && (\n <Text color={isSent ? theme.sent.bg : theme.senderName} bold>\n {sender.length > 12 ? `${sender.slice(0, 11)}…` : sender}\n {\": \"}\n </Text>\n )}\n </>\n ) : (\n // Continuation: indent to align with first message text\n <Text>\n {\" \"}\n {sender ? \" \" : \"\"}\n </Text>\n )}\n\n {/* Message text */}\n <Text color={isSent ? theme.sentText : theme.receivedText} wrap=\"truncate\">\n {text}\n </Text>\n\n {/* Reactions */}\n {reactions && <Text> {reactions}</Text>}\n\n {/* Attachment indicator */}\n {hasAttachments && <Text color={theme.attachment}> 📎</Text>}\n\n {/* Edited indicator */}\n {m.isEdited && <Text color={theme.edited}> ✎</Text>}\n </Box>\n\n {/* Reply context — always render the indicator when isReply, even if\n replyToText is missing. iMessage sometimes leaves the text NULL\n even when the GUID link is present, so we try a runtime lookup\n and fall back to a placeholder so the user knows it IS a reply. */}\n {m.isReply &&\n (() => {\n let replyText: string | null = m.replyTo?.replyToText ?? null;\n if (!replyText && m.replyTo?.replyToGuid && lookupReplyText) {\n replyText = lookupReplyText(m.replyTo.replyToGuid);\n }\n const display = replyText\n ? replyText.slice(0, maxWidth - 12)\n : \"(replied to earlier message)\";\n return (\n <Box>\n {lineNum !== undefined && <Text>{\" \"}</Text>}\n <Text>{\" \"}</Text>\n <Text color={theme.replyContext} italic>\n {\" ↩ \"}\n {display}\n </Text>\n </Box>\n );\n })()}\n </Box>\n );\n}\n\ninterface PendingBubbleProps {\n text: string;\n status: \"sending\" | \"sent\" | \"failed\";\n maxWidth: number;\n}\n\nexport function PendingBubble({ text, status }: PendingBubbleProps) {\n const theme = useTheme();\n const indicator = status === \"sending\" ? \"⏳\" : status === \"failed\" ? \"⚠\" : \"✓\";\n const color = status === \"failed\" ? theme.edited : theme.pending.fg;\n\n return (\n <Box backgroundColor={theme.groupBg.sent}>\n <Text>{\" \"}</Text>\n <Text color={color}>{indicator} </Text>\n <Text color={theme.timestamp}>{\"now\".padEnd(13)}</Text>\n <Text color={theme.sent.bg} bold>\n {`${theme.glyphs.sent} Me: `}\n </Text>\n <Text color={theme.pending.fg} wrap=\"truncate\">\n {text}\n </Text>\n </Box>\n );\n}\n","import { Box, Text } from \"ink\";\nimport React, { useMemo } from \"react\";\nimport type { Conversation, Message } from \"../../types.js\";\nimport { useTheme } from \"../themes/ThemeContext.js\";\nimport type { Mode, PendingMessage } from \"../types.js\";\nimport { ComposeBar } from \"./ComposeBar.js\";\nimport {\n dateSeparator,\n isDifferentDay,\n isGroupEnd,\n isGroupStart,\n MessageBubble,\n PendingBubble,\n} from \"./MessageBubble.js\";\n\ninterface Props {\n conversation: Conversation | undefined;\n messages: Message[];\n pending: PendingMessage[];\n resolvedNames: string[];\n scrollOffset: number;\n selectedMsgIdx: number;\n /** Anchor index for visual selection (set when V is pressed). null = no selection. */\n selectionAnchor: number | null;\n /** Eviction gap markers — placeholders showing \"N more messages — scroll to load\". */\n gapMarkers: Array<{ atIdx: number; oldestId: number; newestId: number; count: number }>;\n focused: boolean;\n width: number;\n height: number;\n mode: Mode;\n onChangeCompose: (text: string) => void;\n onSubmitCompose: (text: string) => void;\n}\n\nexport function ThreadPane({\n conversation,\n messages,\n pending,\n resolvedNames: _resolvedNames,\n scrollOffset: _scrollOffset,\n selectedMsgIdx,\n selectionAnchor,\n gapMarkers,\n focused,\n width,\n height,\n mode,\n onChangeCompose,\n onSubmitCompose,\n}: Props) {\n const theme = useTheme();\n const isGroup = conversation?.isGroupChat ?? false;\n const maxBubbleW = Math.max(width - 8, 20);\n const composing = mode === \"compose\" || mode === \"confirm\";\n\n // Available height for messages\n const headerH = 1;\n const composeH = composing ? 1 : 0;\n const borderH = 2;\n const msgAreaHeight = Math.max(height - headerH - composeH - borderH, 3);\n\n // Compute visible window anchored on selectedMsgIdx.\n // Account for multi-line items: replies take 2 lines, date separators take 1 extra line.\n const { visibleStart, visibleEnd } = useMemo(() => {\n const total = messages.length + pending.length;\n if (total === 0) return { visibleStart: 0, visibleEnd: 0 };\n\n let cursorIdx = selectedMsgIdx >= 0 ? selectedMsgIdx : total - 1;\n cursorIdx = Math.max(0, Math.min(cursorIdx, total - 1));\n\n // Bottom-anchor: when the cursor is at or near the last real message, walk\n // UP from the end accumulating real heights until we fill msgAreaHeight.\n // Fixes the clip where the last 1-3 messages run past Ink's overflow=\"hidden\"\n // because the downward fill loop never executes (end === total - 1 already).\n const NEAR_END = 2;\n if (cursorIdx >= messages.length - NEAR_END && pending.length === 0) {\n const end = messages.length - 1;\n let start = end;\n let totalLines = lineHeight(messages, end, maxBubbleW);\n while (\n start > 0 &&\n totalLines + lineHeight(messages, start - 1, maxBubbleW) <= msgAreaHeight\n ) {\n start--;\n totalLines += lineHeight(messages, start, maxBubbleW);\n }\n return { visibleStart: start, visibleEnd: end + 1 };\n }\n\n // Walk outward from cursor, counting actual lines consumed\n // Start by going upward from cursor\n let start = cursorIdx;\n let linesAbove = 0;\n const targetAbove = Math.floor(msgAreaHeight * 0.4);\n while (start > 0 && linesAbove < targetAbove) {\n start--;\n linesAbove += lineHeight(messages, start, maxBubbleW);\n }\n\n // Then go downward from cursor\n let end = cursorIdx;\n let totalLines = linesAbove + lineHeight(messages, cursorIdx, maxBubbleW);\n while (end < total - 1 && totalLines < msgAreaHeight) {\n end++;\n if (end < messages.length) {\n totalLines += lineHeight(messages, end, maxBubbleW);\n } else {\n totalLines += 1; // pending messages\n }\n }\n\n // If we have room, extend upward more\n while (start > 0 && totalLines < msgAreaHeight) {\n start--;\n totalLines += lineHeight(messages, start, maxBubbleW);\n }\n\n return { visibleStart: start, visibleEnd: end + 1 };\n // `messages` reference is preserved across non-messages reducer cases\n // (see types.ts reducer), so depending on length is sufficient for the\n // common fast path. Content-mutating actions (SET_MESSAGES /\n // PREPEND_MESSAGES) replace the whole array, which also flips length.\n }, [messages, pending.length, selectedMsgIdx, msgAreaHeight, maxBubbleW]);\n\n const visibleMessages = messages.slice(visibleStart, Math.min(visibleEnd, messages.length));\n\n // Build a GUID → text lookup so MessageBubble can resolve missing replyToText\n // from the loaded message set when iMessage didn't populate it. Memoized to\n // avoid rebuilding on every render.\n const messagesByGuid = useMemo(() => {\n const map = new Map<string, string>();\n for (const m of messages) {\n if (m.guid && m.text) map.set(m.guid, m.text);\n }\n return map;\n }, [messages]);\n\n const lookupReplyText = (guid: string): string | null => messagesByGuid.get(guid) ?? null;\n\n // Visual selection range — derived from anchor + cursor.\n const selRange: [number, number] | null =\n selectionAnchor != null && selectedMsgIdx >= 0\n ? [Math.min(selectionAnchor, selectedMsgIdx), Math.max(selectionAnchor, selectedMsgIdx)]\n : null;\n\n return (\n <Box\n flexDirection=\"column\"\n width={width}\n height={height}\n borderStyle=\"single\"\n borderColor={focused ? theme.header.focused.fg : theme.border}\n overflow=\"hidden\"\n >\n {/* Header */}\n <Box\n paddingX={1}\n backgroundColor={focused ? theme.header.focused.bg : theme.header.dim.bg}\n justifyContent=\"space-between\"\n >\n <Box>\n <Text color={focused ? theme.header.focused.fg : theme.header.dim.fg} bold={focused}>\n {conversation?.displayName ?? conversation?.chatIdentifier ?? \"Thread\"}\n </Text>\n {conversation && <Text color={theme.info.label}> ({messages.length} msgs)</Text>}\n </Box>\n {conversation && (\n <Box gap={1}>\n {conversation.displayName && (\n <Text color={theme.info.label}>{conversation.rawIdentifier}</Text>\n )}\n <Text color={conversation.serviceType === \"SMS\" ? theme.sms : theme.info.label}>\n {conversation.serviceType}\n </Text>\n {conversation.isGroupChat && <Text color={theme.info.label}>Group</Text>}\n </Box>\n )}\n </Box>\n\n {/* Messages — compact rows with sender grouping */}\n <Box flexDirection=\"column\" flexGrow={1} overflow=\"hidden\">\n {messages.length === 0 && pending.length === 0 ? (\n <Box paddingX={1}>\n <Text color={theme.sidebar.snippet}>No messages</Text>\n </Box>\n ) : (\n <>\n {/* Scroll indicator top */}\n {visibleStart > 0 && (\n <Box justifyContent=\"center\">\n <Text color={theme.dateSep}>── ↑ {visibleStart} more ──</Text>\n </Box>\n )}\n\n {visibleMessages.map((msg, i) => {\n const realIdx = visibleStart + i;\n const prevMsg = realIdx > 0 ? messages[realIdx - 1] : undefined;\n const nextMsg = realIdx < messages.length - 1 ? messages[realIdx + 1] : undefined;\n const showDateSep = !prevMsg || isDifferentDay(prevMsg.date, msg.date);\n const firstInGroup = isGroupStart(msg, prevMsg);\n const lastInGroup = isGroupEnd(msg, nextMsg);\n\n // Alternating background tint based on sender\n const bgTint = msg.isFromMe ? theme.groupBg.sent : theme.groupBg.received;\n\n // Relative line number: distance from cursor\n const relNum =\n selectedMsgIdx >= 0\n ? realIdx === selectedMsgIdx\n ? `${realIdx}`\n : `${Math.abs(realIdx - selectedMsgIdx)}`\n : `${realIdx}`;\n\n return (\n <React.Fragment key={msg.id}>\n {(() => {\n // Gap marker — show \"N more messages\" before the first\n // message after an evicted region.\n const gap = gapMarkers.find((g) => g.atIdx === realIdx);\n if (!gap) return null;\n return (\n <Box justifyContent=\"center\" marginTop={1} marginBottom={1}>\n <Text color={theme.edited}>\n ─── {gap.count.toLocaleString()} older messages evicted (scroll back to\n reload) ───\n </Text>\n </Box>\n );\n })()}\n {showDateSep && (\n // Always 1 row of breathing room above date separators so the\n // visual rhythm is consistent — without this, separators that\n // appear after a same-sender continuation feel cramped while\n // ones after a different-sender row feel fine.\n <Box justifyContent=\"center\" marginTop={realIdx === 0 ? 0 : 1}>\n <Text color={theme.dateSep}>─── {dateSeparator(msg.date)} ───</Text>\n </Box>\n )}\n <MessageBubble\n message={msg}\n maxWidth={maxBubbleW}\n showSender={isGroup}\n senderName={msg.displayName ?? msg.handle}\n selected={realIdx === selectedMsgIdx && focused}\n lineNum={relNum}\n isFirstInGroup={firstInGroup || showDateSep}\n isLastInGroup={lastInGroup}\n bgTint={bgTint}\n lookupReplyText={lookupReplyText}\n inSelection={\n selRange != null && realIdx >= selRange[0] && realIdx <= selRange[1]\n }\n />\n {/* Group separator line between different senders */}\n {lastInGroup && nextMsg && !isDifferentDay(msg.date, nextMsg.date) && (\n <Box height={0} />\n )}\n </React.Fragment>\n );\n })}\n\n {/* Pending messages */}\n {pending.map((pm) => (\n <PendingBubble\n key={pm.text}\n text={pm.text}\n status={pm.status}\n maxWidth={maxBubbleW}\n />\n ))}\n\n {/* Scroll indicator bottom */}\n {visibleEnd < messages.length && (\n <Box justifyContent=\"center\">\n <Text color={theme.dateSep}>── ↓ {messages.length - visibleEnd} more ──</Text>\n </Box>\n )}\n </>\n )}\n </Box>\n\n {/* Compose bar */}\n {composing && (\n <ComposeBar\n mode={mode}\n recipientName={conversation?.displayName ?? conversation?.chatIdentifier ?? \"\"}\n onChangeText={onChangeCompose}\n onSubmit={onSubmitCompose}\n />\n )}\n </Box>\n );\n}\n\n/**\n * Estimate how many terminal lines a message at index `i` will consume.\n * Conservative upper bound: under-counts trigger bottom-of-thread clipping\n * (Ink's overflow=\"hidden\" silently drops rows past the box edge), so we err\n * toward over-counting wrap rows from text length.\n */\nfunction lineHeight(messages: Message[], i: number, bubbleWidth: number): number {\n if (i < 0 || i >= messages.length) return 1;\n const msg = messages[i];\n // Wrap rows for the message body. Bubble inner width is roughly bubbleWidth\n // minus padding/prefix (sender name, line number). Use max(20) so very narrow\n // terminals don't divide-by-near-zero.\n const innerW = Math.max(20, bubbleWidth - 4);\n const text = msg.text ?? \"\";\n let h = Math.max(1, Math.ceil(text.length / innerW));\n // Reply preview row(s)\n if (msg.isReply) {\n const replyLen = msg.replyTo?.replyToText?.length ?? 0;\n h += Math.max(1, Math.ceil(replyLen / Math.max(20, innerW - 4)));\n }\n // Date separator. Rendered with marginTop={1} when realIdx > 0\n // (ThreadPane.tsx ~line 216), so it actually occupies TWO terminal rows in\n // that case: 1 blank margin + 1 separator content. Under-counting this\n // pushes the last message past the box edge.\n if (i === 0) {\n h += 1;\n } else if (isDifferentDay(messages[i - 1].date, msg.date)) {\n h += 2;\n }\n // Attachment indicator row (paperclip + filename) when present\n if (msg.attachments && msg.attachments.length > 0) h += 1;\n return h;\n}\n\n/**\n * Find the index of the next sender-group boundary from the given position.\n * A group boundary is where the sender changes (isFromMe flips or handle changes).\n */\nexport function nextGroupBoundary(messages: Message[], fromIdx: number): number {\n if (fromIdx >= messages.length - 1) return messages.length - 1;\n const current = messages[fromIdx];\n // Skip to end of current group\n let i = fromIdx + 1;\n while (i < messages.length) {\n const m = messages[i];\n if (m.isFromMe !== current.isFromMe || m.handle !== current.handle) {\n return i;\n }\n i++;\n }\n return messages.length - 1;\n}\n\n/**\n * Find the index of the previous sender-group boundary from the given position.\n */\nexport function prevGroupBoundary(messages: Message[], fromIdx: number): number {\n if (fromIdx <= 0) return 0;\n const current = messages[fromIdx];\n // If we're at the start of a group, go to start of previous group\n const prev = messages[fromIdx - 1];\n if (prev.isFromMe !== current.isFromMe || prev.handle !== current.handle) {\n // We're at a boundary — find start of previous group\n let i = fromIdx - 1;\n while (i > 0) {\n const m = messages[i - 1];\n if (m.isFromMe !== prev.isFromMe || m.handle !== prev.handle) {\n return i;\n }\n i--;\n }\n return 0;\n }\n // We're in the middle of a group — go to start of current group\n let i = fromIdx - 1;\n while (i > 0) {\n const m = messages[i - 1];\n if (m.isFromMe !== current.isFromMe || m.handle !== current.handle) {\n return i;\n }\n i--;\n }\n return 0;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { hasNativeModule } from \"../../native-bridge.js\";\nimport { readWatchdogState } from \"../../watchdog.js\";\n\nexport interface DevStatsData {\n engine: \"Rust parser + TS DB\" | \"TS\";\n cpuPercent: number;\n memMB: number;\n pid: number;\n uptime: string;\n lastQueryMs: number | null;\n eventLoopP99Ms: number;\n lastActivityAgo: string;\n}\n\nfunction formatUptime(seconds: number): string {\n if (seconds < 60) return `${Math.round(seconds)}s`;\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n return `${h}h${m}m`;\n}\n\nfunction formatAgo(ms: number): string {\n if (ms < 1_000) return \"now\";\n const sec = Math.floor(ms / 1_000);\n if (sec < 60) return `${sec}s`;\n if (sec < 3600) return `${Math.floor(sec / 60)}m`;\n return `${Math.floor(sec / 3600)}h`;\n}\n\nfunction engineLabel(): DevStatsData[\"engine\"] {\n return hasNativeModule() ? \"Rust parser + TS DB\" : \"TS\";\n}\n\nexport function useDevStats(visible: boolean): {\n stats: DevStatsData;\n recordQueryTime: (ms: number) => void;\n} {\n const [stats, setStats] = useState<DevStatsData>({\n engine: engineLabel(),\n cpuPercent: 0,\n memMB: 0,\n pid: process.pid,\n uptime: \"0s\",\n lastQueryMs: null,\n eventLoopP99Ms: 0,\n lastActivityAgo: \"now\",\n });\n\n const lastCpuRef = useRef(process.cpuUsage());\n const lastTimeRef = useRef(Date.now());\n const lastQueryMsRef = useRef<number | null>(null);\n\n useEffect(() => {\n if (!visible) return;\n const timer = setInterval(() => {\n const now = Date.now();\n const elapsed = now - lastTimeRef.current;\n if (elapsed === 0) return;\n\n const cpuNow = process.cpuUsage(lastCpuRef.current);\n // cpuUsage returns microseconds; convert to percentage of wall time\n const totalCpuUs = cpuNow.user + cpuNow.system;\n const cpuPercent = (totalCpuUs / 1000 / elapsed) * 100;\n\n lastCpuRef.current = process.cpuUsage();\n lastTimeRef.current = now;\n\n const { rss } = process.memoryUsage();\n const memMB = Math.round((rss / 1024 / 1024) * 10) / 10;\n\n const wd = readWatchdogState();\n setStats({\n engine: engineLabel(),\n cpuPercent: Math.round(cpuPercent * 10) / 10,\n memMB,\n pid: process.pid,\n uptime: formatUptime(process.uptime()),\n lastQueryMs: lastQueryMsRef.current,\n eventLoopP99Ms: Math.round(wd.eventLoopP99Ms * 10) / 10,\n lastActivityAgo: formatAgo(Date.now() - wd.lastActivityTs),\n });\n }, 2000);\n timer.unref();\n\n return () => clearInterval(timer);\n }, [visible]);\n\n const recordQueryTime = useCallback((ms: number) => {\n lastQueryMsRef.current = Math.round(ms * 10) / 10;\n }, []);\n\n return { stats, recordQueryTime };\n}\n","/**\n * In-memory cache of messages-per-chat for the TUI.\n *\n * Behavior:\n * - When the user re-enters a chat they've already viewed within\n * `STALE_MS`, return cached messages immediately (no DB round trip).\n * - Older entries get evicted on a TTL sweep every 60s.\n * - When heap pressure crosses `MEMORY_PRESSURE_MB` (sampled by the\n * watchdog), evict the LRU half of the cache until below threshold.\n *\n * Pure module — no React imports. Used by `useImsg.ts` and observable\n * via `cacheStats()` for the dev stats panel.\n */\n\nimport type { Message } from \"../types.js\";\nimport { onMemorySample } from \"../watchdog.js\";\n\ninterface CacheEntry {\n messages: Message[];\n oldestId: number; // for \"load older\" continuity\n loadedAt: number; // wall-clock ms\n lastAccess: number; // for LRU\n bytesEstimate: number;\n}\n\nfunction envNum(name: string, fallback: number): number {\n const raw = process.env[name];\n if (!raw) return fallback;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\nconst TTL_MS = envNum(\"IMSG_TUI_CACHE_TTL_MS\", 600_000); // 10 min\nconst STALE_MS = envNum(\"IMSG_TUI_CACHE_STALE_MS\", 30_000); // 30s\nconst MEMORY_PRESSURE_MB = envNum(\"IMSG_TUI_CACHE_MEM_PRESSURE_MB\", 200); // heap MB\n\nconst cache = new Map<string, CacheEntry>();\nlet sweepTimer: ReturnType<typeof setInterval> | null = null;\nlet unsubMemSample: (() => void) | null = null;\n\nfunction estimateBytes(messages: Message[]): number {\n // Rough — only matters for relative sizing during eviction\n let bytes = 0;\n for (const m of messages) {\n bytes += (m.text?.length ?? 0) * 2; // UTF-16\n bytes += (m.handle?.length ?? 0) * 2;\n bytes += 80; // fixed overhead per message (date, ids, flags)\n }\n return bytes;\n}\n\n/** Get cached entry; returns undefined if missing. Touches lastAccess. */\nexport function getCached(chatIdentifier: string): CacheEntry | undefined {\n const entry = cache.get(chatIdentifier);\n if (entry) entry.lastAccess = Date.now();\n return entry;\n}\n\n/** Returns true if the entry is fresh enough to skip a DB round-trip. */\nexport function isFresh(entry: CacheEntry, now = Date.now()): boolean {\n return now - entry.loadedAt < STALE_MS;\n}\n\n/** Replace (or insert) the cache entry for a chat. */\nexport function setCached(chatIdentifier: string, messages: Message[], oldestId: number): void {\n const now = Date.now();\n cache.set(chatIdentifier, {\n messages,\n oldestId,\n loadedAt: now,\n lastAccess: now,\n bytesEstimate: estimateBytes(messages),\n });\n}\n\n/** Prepend older messages to an existing entry (dedup by id). */\nexport function prependCached(chatIdentifier: string, olderMessages: Message[]): void {\n const entry = cache.get(chatIdentifier);\n if (!entry) return;\n const existingIds = new Set(entry.messages.map((m) => m.id));\n const fresh = olderMessages.filter((m) => !existingIds.has(m.id));\n if (fresh.length === 0) return;\n const merged = [...fresh, ...entry.messages].sort((a, b) => a.date.getTime() - b.date.getTime());\n entry.messages = merged;\n entry.oldestId = Math.min(entry.oldestId, ...fresh.map((m) => m.id));\n entry.lastAccess = Date.now();\n entry.bytesEstimate = estimateBytes(merged);\n}\n\n/** Clear all cached entries — used on shutdown / explicit refresh. */\nexport function clearCache(): void {\n cache.clear();\n}\n\n/** Number of cached chats and total estimated bytes. For dev stats display. */\nexport function cacheStats(): { entries: number; bytes: number } {\n let bytes = 0;\n for (const e of cache.values()) bytes += e.bytesEstimate;\n return { entries: cache.size, bytes };\n}\n\n/** TTL sweep: drop entries older than TTL_MS. Exported for tests. */\nexport function ttlSweep(now = Date.now()): number {\n let dropped = 0;\n for (const [k, v] of cache) {\n if (now - v.loadedAt > TTL_MS) {\n cache.delete(k);\n dropped++;\n }\n }\n return dropped;\n}\n\n/**\n * Memory-pressure eviction: when heap exceeds threshold, drop the LRU\n * half of the cache. Called from the watchdog memory sampler.\n */\nexport function evictUnderPressure(heapMb: number): number {\n if (heapMb < MEMORY_PRESSURE_MB) return 0;\n const sorted = [...cache.entries()].sort((a, b) => a[1].lastAccess - b[1].lastAccess);\n const half = Math.ceil(sorted.length / 2);\n for (let i = 0; i < half; i++) {\n cache.delete(sorted[i][0]);\n }\n return half;\n}\n\n/** Install TTL sweep + memory-pressure subscription. Idempotent. */\nexport function installCacheSweepers(): void {\n if (sweepTimer) return;\n\n sweepTimer = setInterval(() => {\n ttlSweep();\n }, 60_000);\n sweepTimer.unref();\n\n unsubMemSample = onMemorySample((_rss, heapMb) => {\n evictUnderPressure(heapMb);\n });\n}\n\n/** Stop sweepers. Used by tests + on shutdown. */\nexport function stopCacheSweepers(): void {\n if (sweepTimer) {\n clearInterval(sweepTimer);\n sweepTimer = null;\n }\n if (unsubMemSample) {\n unsubMemSample();\n unsubMemSample = null;\n }\n}\n","import { useCallback, useRef } from \"react\";\nimport { sendMessageAlt, sendToChat, sendToChatId } from \"../../applescript.js\";\nimport { getContactsDbPaths, getImsgDbPath, getSlugsDbPath } from \"../../config.js\";\nimport { IMessageDB } from \"../../imessage-db.js\";\nimport type { Conversation, Message } from \"../../types.js\";\nimport { getCached, isFresh, prependCached, setCached } from \"../messageCache.js\";\n\nexport function useImsg() {\n const dbRef = useRef<IMessageDB | null>(null);\n\n const getDb = useCallback(() => {\n if (!dbRef.current) {\n dbRef.current = new IMessageDB(getImsgDbPath(), getContactsDbPaths(), getSlugsDbPath());\n }\n return dbRef.current;\n }, []);\n\n const loadConversations = useCallback(\n async (limit = 200): Promise<Conversation[]> => {\n return getDb().listConversations(limit);\n },\n [getDb],\n );\n\n const loadMessages = useCallback(\n async (chatIdentifier: string): Promise<Message[]> => {\n // Read-through cache: return fresh entries directly without hitting the DB.\n const cached = getCached(chatIdentifier);\n if (cached && isFresh(cached)) return cached.messages;\n\n const messages = await getDb().getMessagesForChat(chatIdentifier, 200, {\n includeReactionDetails: true,\n });\n const oldestId = messages.length > 0 ? Math.min(...messages.map((m) => m.id)) : 0;\n setCached(chatIdentifier, messages, oldestId);\n return messages;\n },\n [getDb],\n );\n\n const loadOlderMessages = useCallback(\n async (chatIdentifier: string, beforeMessageId: number): Promise<Message[]> => {\n const older = await getDb().getMessagesForChat(chatIdentifier, 100, {\n includeReactionDetails: true,\n beforeMessageId,\n });\n // Merge into cache so subsequent re-entries see the full loaded history.\n if (older.length > 0) prependCached(chatIdentifier, older);\n return older;\n },\n [getDb],\n );\n\n const resolveNames = useCallback(\n (handles: string[]): string[] => {\n return getDb().resolveParticipantNames(handles);\n },\n [getDb],\n );\n\n const send = useCallback(\n async (threadSlug: string, text: string): Promise<{ success: boolean; error?: string }> => {\n const db = getDb();\n const slugRecord = db.getSlugRecord(threadSlug);\n if (!slugRecord) return { success: false, error: `Unknown slug: ${threadSlug}` };\n\n if (slugRecord.isGroup) {\n return slugRecord.displayName && !slugRecord.displayName.startsWith(\"chat\")\n ? sendToChat(slugRecord.displayName, text)\n : sendToChatId(slugRecord.chatGuid, text);\n }\n return sendMessageAlt(slugRecord.chatIdentifier, text);\n },\n [getDb],\n );\n\n const refresh = useCallback(() => {\n getDb().scheduleBackgroundRefresh();\n }, [getDb]);\n\n const close = useCallback(async () => {\n if (dbRef.current) {\n await dbRef.current.close();\n dbRef.current = null;\n }\n }, []);\n\n return { loadConversations, loadMessages, loadOlderMessages, resolveNames, send, refresh, close };\n}\n","import { useStdin } from \"ink\";\nimport { useEffect } from \"react\";\n\nexport interface MouseEvent {\n type: \"click\" | \"scroll-up\" | \"scroll-down\";\n x: number;\n y: number;\n button: number;\n}\n\n/**\n * Enable SGR mouse protocol and parse mouse events from stdin.\n * Calls `onEvent` for each click or scroll wheel event.\n */\nexport function useMouse(onEvent: (event: MouseEvent) => void) {\n const { stdin, setRawMode } = useStdin();\n\n useEffect(() => {\n // ?1000h = button-event tracking (press/release + scroll wheel only).\n // Do NOT use ?1003h (any-event tracking) — it fires for every pixel of\n // mouse motion, flooding stdin and pinning the event loop.\n // ?1006h = SGR extended coordinates so x/y aren't capped at 223.\n process.stdout.write(\"\\x1b[?1000h\\x1b[?1006h\");\n setRawMode(true);\n\n const handler = (data: Buffer) => {\n const str = data.toString();\n // SGR mouse format: \\x1b[<btn;x;y;M (press) or \\x1b[<btn;x;y;m (release)\n // biome-ignore lint/suspicious/noControlCharactersInRegex: ESC is the terminal mouse protocol prefix.\n const re = /\\x1b\\[<(\\d+);(\\d+);(\\d+)([Mm])/g;\n let match = re.exec(str);\n while (match !== null) {\n const btn = Number.parseInt(match[1], 10);\n const x = Number.parseInt(match[2], 10);\n const y = Number.parseInt(match[3], 10);\n const isPress = match[4] === \"M\";\n\n if (btn === 64 && isPress) {\n onEvent({ type: \"scroll-up\", x, y, button: btn });\n } else if (btn === 65 && isPress) {\n onEvent({ type: \"scroll-down\", x, y, button: btn });\n } else if (btn === 0 && isPress) {\n onEvent({ type: \"click\", x, y, button: btn });\n }\n match = re.exec(str);\n }\n };\n\n stdin.on(\"data\", handler);\n return () => {\n stdin.off(\"data\", handler);\n process.stdout.write(\"\\x1b[?1000l\\x1b[?1006l\");\n };\n }, [stdin, setRawMode, onEvent]);\n}\n","import type { Conversation, Message } from \"../types.js\";\n\nexport type FocusPane = \"sidebar\" | \"thread\";\nexport type Mode =\n | \"browse\"\n | \"compose\"\n | \"confirm\"\n | \"filter\"\n | \"drawer\"\n | \"select\"\n | \"export\"\n | \"date-jump\"\n | \"send-via\";\n\nexport interface PendingMessage {\n text: string;\n sentAt: Date;\n status: \"sending\" | \"sent\" | \"failed\";\n}\n\nexport interface AppState {\n conversations: Conversation[];\n messages: Message[];\n selectedIdx: number;\n selectedMsgIdx: number; // cursor position in messages list\n focus: FocusPane;\n mode: Mode;\n sidebarScroll: number;\n threadScroll: number;\n composeText: string;\n filterQuery: string;\n pending: PendingMessage[];\n loading: boolean;\n status: string;\n numBuffer: string; // for vim-style number prefix (e.g. \"12j\")\n showDevStats: boolean;\n\n // Lazy-loading bookkeeping\n conversationLoadedCount: number; // how many we've requested from the DB so far\n conversationLoadingMore: boolean; // true while a \"load more\" fetch is in-flight\n messageOldestLoadedId: number | null; // oldest message ROWID currently in `messages` (for \"load older\")\n messageLoadingOlder: boolean; // true while a \"load older\" fetch is in-flight\n\n // Bounded message window — gaps appear when middle of history is evicted\n gapMarkers: Array<{ atIdx: number; oldestId: number; newestId: number; count: number }>;\n\n // Visual selection (vim V)\n selectionAnchor: number | null; // where the user pressed V — selection extends from anchor to selectedMsgIdx\n\n // Export modal state\n exportFormat: \"markdown\" | \"csv\" | \"json\";\n exportPath: string;\n exportStatus: string; // last export status message (e.g. \"Exported 247 msgs\")\n\n // Date-jump modal state\n dateJumpInput: string;\n dateJumpError: string;\n}\n\nexport type Action =\n | { type: \"SET_CONVERSATIONS\"; data: Conversation[] }\n | { type: \"SET_MESSAGES\"; data: Message[] }\n | { type: \"SELECT\"; index: number; visibleCount?: number }\n | { type: \"SELECT_MSG\"; index: number }\n | { type: \"MOVE_MSG\"; delta: number }\n | { type: \"FOCUS\"; pane: FocusPane }\n | { type: \"SCROLL_SIDEBAR\"; delta: number }\n | { type: \"SCROLL_THREAD\"; delta: number }\n | { type: \"SCROLL_THREAD_TO\"; position: number }\n | { type: \"ENTER_COMPOSE\" }\n | { type: \"UPDATE_COMPOSE\"; text: string }\n | { type: \"CONFIRM_SEND\" }\n | { type: \"CANCEL_COMPOSE\" }\n | { type: \"ADD_PENDING\"; msg: PendingMessage }\n | { type: \"RESOLVE_PENDING\"; text: string }\n | { type: \"FAIL_PENDING\"; text: string }\n | { type: \"SET_LOADING\"; loading: boolean; status?: string }\n | { type: \"SET_STATUS\"; status: string }\n | { type: \"ENTER_FILTER\" }\n | { type: \"UPDATE_FILTER\"; query: string }\n | { type: \"EXIT_FILTER\" }\n | { type: \"OPEN_DRAWER\" }\n | { type: \"CLOSE_DRAWER\" }\n | { type: \"SET_NUM_BUFFER\"; value: string }\n | { type: \"TOGGLE_DEV_STATS\" }\n | { type: \"APPEND_CONVERSATIONS\"; data: Conversation[]; loadedCount: number }\n | { type: \"PREPEND_MESSAGES\"; data: Message[]; oldestId: number }\n | { type: \"SET_LOADING_OLDER\"; loading: boolean }\n | { type: \"ENTER_SELECT_MODE\" }\n | { type: \"EXIT_SELECT_MODE\" }\n | { type: \"ENTER_EXPORT_MODE\"; defaultPath: string }\n | { type: \"EXIT_EXPORT_MODE\" }\n | { type: \"SET_EXPORT_FORMAT\"; format: \"markdown\" | \"csv\" | \"json\" }\n | { type: \"SET_EXPORT_PATH\"; path: string }\n | { type: \"SET_EXPORT_STATUS\"; status: string }\n | { type: \"ENTER_DATE_JUMP\" }\n | { type: \"EXIT_DATE_JUMP\" }\n | { type: \"ENTER_SEND_VIA\" }\n | { type: \"EXIT_SEND_VIA\" }\n | { type: \"SET_DATE_JUMP_INPUT\"; value: string }\n | { type: \"SET_DATE_JUMP_ERROR\"; error: string };\n\nexport const initialState: AppState = {\n conversations: [],\n messages: [],\n selectedIdx: 0,\n selectedMsgIdx: -1,\n focus: \"sidebar\",\n mode: \"browse\",\n sidebarScroll: 0,\n threadScroll: 0,\n composeText: \"\",\n filterQuery: \"\",\n pending: [],\n loading: true,\n status: \"Loading...\",\n numBuffer: \"\",\n showDevStats: false,\n conversationLoadedCount: 0,\n conversationLoadingMore: false,\n messageOldestLoadedId: null,\n messageLoadingOlder: false,\n gapMarkers: [],\n selectionAnchor: null,\n exportFormat: \"markdown\",\n exportPath: \"\",\n exportStatus: \"\",\n dateJumpInput: \"\",\n dateJumpError: \"\",\n};\n\n/** Clamp message cursor and ensure it's visible by adjusting scroll */\nfunction clampMsg(state: AppState, idx: number): Partial<AppState> {\n const total = state.messages.length;\n if (total === 0) return { selectedMsgIdx: -1 };\n const clamped = Math.max(0, Math.min(idx, total - 1));\n return { selectedMsgIdx: clamped };\n}\n\n// ── Bounded message window ───────────────────────────────────────────────\n// When loaded messages exceed HARD_CAP, evict the middle of the array but\n// preserve two regions:\n// 1. ANCHOR_KEEP at the END (most recent messages — user can press G)\n// 2. WINDOW_BUFFER ± selectedMsgIdx (current viewing window)\n// The evicted block becomes a gap marker so the renderer can show \"N more\n// messages — scroll to load\" and the user can refill on demand.\n\nconst MESSAGES_HARD_CAP = Number.parseInt(process.env.IMSG_TUI_MSG_HARD_CAP ?? \"5000\", 10);\nconst ANCHOR_KEEP = 200;\nconst WINDOW_BUFFER = 300;\n\n/**\n * Apply the bounding policy to a fresh `messages` array. Returns the trimmed\n * array, an updated `selectedMsgIdx`, and any new gap markers. Pure function.\n */\nexport function boundMessagesIfNeeded(\n messages: Message[],\n selectedMsgIdx: number,\n existingGaps: AppState[\"gapMarkers\"],\n): {\n messages: Message[];\n selectedMsgIdx: number;\n gapMarkers: AppState[\"gapMarkers\"];\n} {\n if (messages.length <= MESSAGES_HARD_CAP) {\n return { messages, selectedMsgIdx, gapMarkers: existingGaps };\n }\n\n const total = messages.length;\n // Keep the LAST ANCHOR_KEEP messages (most recent — anchor for G)\n const anchorStart = Math.max(total - ANCHOR_KEEP, 0);\n // Keep WINDOW_BUFFER on each side of the cursor\n const cursorLo = Math.max(0, selectedMsgIdx - WINDOW_BUFFER);\n const cursorHi = Math.min(total - 1, selectedMsgIdx + WINDOW_BUFFER);\n\n // Build the kept-set as a sorted list of [start, end] ranges, merged\n type Range = [number, number];\n const ranges: Range[] = [\n [cursorLo, cursorHi],\n [anchorStart, total - 1],\n ];\n // Sort by start; merge overlapping/adjacent\n ranges.sort((a, b) => a[0] - b[0]);\n const merged: Range[] = [];\n for (const r of ranges) {\n if (merged.length === 0) {\n merged.push(r);\n continue;\n }\n const last = merged[merged.length - 1];\n if (r[0] <= last[1] + 1) {\n last[1] = Math.max(last[1], r[1]);\n } else {\n merged.push(r);\n }\n }\n\n // Slice + concat the kept ranges, recording gaps between them\n const kept: Message[] = [];\n const gapMarkers: AppState[\"gapMarkers\"] = [];\n for (let i = 0; i < merged.length; i++) {\n const [start, end] = merged[i];\n if (i > 0) {\n const prevEnd = merged[i - 1][1];\n const gapStart = prevEnd + 1;\n const gapEnd = start - 1;\n if (gapEnd >= gapStart) {\n gapMarkers.push({\n atIdx: kept.length,\n oldestId: messages[gapStart].id,\n newestId: messages[gapEnd].id,\n count: gapEnd - gapStart + 1,\n });\n }\n }\n for (let j = start; j <= end; j++) kept.push(messages[j]);\n }\n\n // Map old selectedMsgIdx to new index in `kept`\n let newCursor = -1;\n if (selectedMsgIdx >= 0) {\n let collapsedIdx = 0;\n for (const [start, end] of merged) {\n if (selectedMsgIdx >= start && selectedMsgIdx <= end) {\n newCursor = collapsedIdx + (selectedMsgIdx - start);\n break;\n }\n collapsedIdx += end - start + 1;\n }\n if (newCursor === -1) newCursor = 0; // cursor was in evicted region; clamp to start\n }\n\n // Note: existingGaps from before this trim aren't re-merged; we replace.\n // In practice gaps are always recomputed from the current array shape.\n return { messages: kept, selectedMsgIdx: newCursor, gapMarkers };\n}\n\n/**\n * Compute a `sidebarScroll` value that keeps `selectedIdx` inside the\n * visible window. Adds a 2-row buffer at each edge so the user sees what's\n * coming next when navigating with j/k. Pure function — used by reducer.\n *\n * @param selectedIdx the cursor's target index\n * @param currentScroll the current sidebar scroll offset\n * @param visibleCount how many items fit on-screen at once\n * @param totalCount total items available (for clamping)\n */\nexport function ensureVisibleScroll(\n selectedIdx: number,\n currentScroll: number,\n visibleCount: number,\n totalCount: number,\n): number {\n if (visibleCount <= 0 || totalCount <= 0) return 0;\n const buffer = Math.min(2, Math.floor(visibleCount / 4));\n // If cursor is above the visible window (with buffer), scroll up\n if (selectedIdx < currentScroll + buffer) {\n return Math.max(0, selectedIdx - buffer);\n }\n // If cursor is below the visible window (with buffer), scroll down\n const lastVisible = currentScroll + visibleCount - 1;\n if (selectedIdx > lastVisible - buffer) {\n return Math.min(\n Math.max(0, totalCount - visibleCount),\n selectedIdx - visibleCount + 1 + buffer,\n );\n }\n // Already visible — don't move\n return Math.max(0, Math.min(currentScroll, Math.max(0, totalCount - visibleCount)));\n}\n\nexport function reducer(state: AppState, action: Action): AppState {\n switch (action.type) {\n case \"SET_CONVERSATIONS\":\n return {\n ...state,\n conversations: action.data,\n conversationLoadedCount: action.data.length,\n conversationLoadingMore: false,\n };\n case \"APPEND_CONVERSATIONS\": {\n // Merge new entries by threadSlug (DB returns the same recent ones too)\n const existing = new Set(state.conversations.map((c) => c.threadSlug));\n const fresh = action.data.filter((c) => !existing.has(c.threadSlug));\n return {\n ...state,\n conversations: [...state.conversations, ...fresh],\n conversationLoadedCount: action.loadedCount,\n conversationLoadingMore: false,\n };\n }\n case \"SET_MESSAGES\": {\n // Scroll to bottom: set cursor to last message\n const msgs = action.data;\n const lastIdx = Math.max(0, msgs.length - 1);\n // Set threadScroll high so the view shows the bottom\n const scrollToEnd = Math.max(0, msgs.length);\n const oldestId = msgs.length > 0 ? Math.min(...msgs.map((m) => m.id)) : null;\n return {\n ...state,\n messages: msgs,\n pending: [],\n selectedMsgIdx: lastIdx,\n threadScroll: scrollToEnd,\n messageOldestLoadedId: oldestId,\n messageLoadingOlder: false,\n gapMarkers: [],\n };\n }\n case \"PREPEND_MESSAGES\": {\n // Merge older messages at the start; dedup by id; preserve cursor on the\n // same logical message by shifting selectedMsgIdx by the count added.\n const existingIds = new Set(state.messages.map((m) => m.id));\n const fresh = action.data.filter((m) => !existingIds.has(m.id));\n const merged = [...fresh, ...state.messages].sort(\n (a, b) => a.date.getTime() - b.date.getTime(),\n );\n const shift = fresh.length;\n const shiftedCursor =\n state.selectedMsgIdx >= 0 ? state.selectedMsgIdx + shift : state.selectedMsgIdx;\n\n // Apply bounded-window eviction if we've grown past the cap\n const bounded = boundMessagesIfNeeded(merged, shiftedCursor, state.gapMarkers);\n\n return {\n ...state,\n messages: bounded.messages,\n selectedMsgIdx: bounded.selectedMsgIdx,\n gapMarkers: bounded.gapMarkers,\n messageOldestLoadedId: action.oldestId,\n messageLoadingOlder: false,\n };\n }\n case \"SET_LOADING_OLDER\":\n return { ...state, messageLoadingOlder: action.loading };\n case \"ENTER_SELECT_MODE\":\n return { ...state, mode: \"select\", selectionAnchor: state.selectedMsgIdx };\n case \"EXIT_SELECT_MODE\":\n return { ...state, mode: \"browse\", selectionAnchor: null };\n case \"ENTER_EXPORT_MODE\":\n return { ...state, mode: \"export\", exportPath: action.defaultPath };\n case \"EXIT_EXPORT_MODE\":\n return { ...state, mode: state.selectionAnchor != null ? \"select\" : \"browse\" };\n case \"SET_EXPORT_FORMAT\":\n return { ...state, exportFormat: action.format };\n case \"SET_EXPORT_PATH\":\n return { ...state, exportPath: action.path };\n case \"SET_EXPORT_STATUS\":\n return { ...state, exportStatus: action.status };\n case \"ENTER_DATE_JUMP\":\n return { ...state, mode: \"date-jump\", dateJumpInput: \"\", dateJumpError: \"\" };\n case \"EXIT_DATE_JUMP\":\n return { ...state, mode: \"browse\", dateJumpInput: \"\", dateJumpError: \"\" };\n case \"ENTER_SEND_VIA\":\n return { ...state, mode: \"send-via\" };\n case \"EXIT_SEND_VIA\":\n return { ...state, mode: \"browse\" };\n case \"SET_DATE_JUMP_INPUT\":\n return { ...state, dateJumpInput: action.value, dateJumpError: \"\" };\n case \"SET_DATE_JUMP_ERROR\":\n return { ...state, dateJumpError: action.error };\n case \"SELECT\": {\n const idx = Math.max(0, Math.min(action.index, Math.max(0, state.conversations.length - 1)));\n const sidebarScroll = action.visibleCount\n ? ensureVisibleScroll(\n idx,\n state.sidebarScroll,\n action.visibleCount,\n state.conversations.length,\n )\n : state.sidebarScroll;\n return { ...state, selectedIdx: idx, sidebarScroll };\n }\n case \"SELECT_MSG\": {\n const c = clampMsg(state, action.index);\n return { ...state, ...c };\n }\n case \"MOVE_MSG\": {\n const c = clampMsg(state, state.selectedMsgIdx + action.delta);\n return { ...state, ...c };\n }\n case \"FOCUS\":\n return { ...state, focus: action.pane, numBuffer: \"\" };\n case \"SCROLL_SIDEBAR\":\n return { ...state, sidebarScroll: Math.max(0, state.sidebarScroll + action.delta) };\n case \"SCROLL_THREAD\":\n return { ...state, threadScroll: Math.max(0, state.threadScroll + action.delta) };\n case \"SCROLL_THREAD_TO\":\n return { ...state, threadScroll: Math.max(0, action.position) };\n case \"ENTER_COMPOSE\":\n return { ...state, mode: \"compose\", composeText: \"\", focus: \"thread\" };\n case \"UPDATE_COMPOSE\":\n return { ...state, composeText: action.text };\n case \"CONFIRM_SEND\":\n return { ...state, mode: \"confirm\" };\n case \"CANCEL_COMPOSE\":\n return { ...state, mode: \"browse\", composeText: \"\" };\n case \"ADD_PENDING\":\n return { ...state, mode: \"browse\", composeText: \"\", pending: [...state.pending, action.msg] };\n case \"RESOLVE_PENDING\":\n return { ...state, pending: state.pending.filter((p) => p.text !== action.text) };\n case \"FAIL_PENDING\":\n return {\n ...state,\n pending: state.pending.map((p) =>\n p.text === action.text ? { ...p, status: \"failed\" as const } : p,\n ),\n };\n case \"SET_LOADING\":\n return { ...state, loading: action.loading, status: action.status ?? state.status };\n case \"SET_STATUS\":\n return { ...state, status: action.status };\n case \"ENTER_FILTER\":\n return { ...state, mode: \"filter\", filterQuery: \"\", focus: \"sidebar\" };\n case \"UPDATE_FILTER\":\n return { ...state, filterQuery: action.query };\n case \"EXIT_FILTER\":\n return { ...state, mode: \"browse\", filterQuery: \"\" };\n case \"OPEN_DRAWER\":\n return { ...state, mode: \"drawer\" };\n case \"CLOSE_DRAWER\":\n return { ...state, mode: \"browse\" };\n case \"SET_NUM_BUFFER\":\n return { ...state, numBuffer: action.value };\n case \"TOGGLE_DEV_STATS\":\n return { ...state, showDevStats: !state.showDevStats };\n default:\n return state;\n }\n}\n","import { execSync } from \"node:child_process\";\nimport { writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { useScreenSize } from \"fullscreen-ink\";\nimport { Box, useApp, useInput } from \"ink\";\nimport { useCallback, useEffect, useReducer, useRef } from \"react\";\nimport { registerCleanup } from \"../shutdown.js\";\nimport { getInstalledChatApps } from \"../url-schemes.js\";\nimport { DateJumpModal } from \"./components/DateJumpModal.js\";\nimport { CompactStats, DevStats } from \"./components/DevStats.js\";\nimport { ExportModal } from \"./components/ExportModal.js\";\nimport { HelpBar } from \"./components/HelpBar.js\";\nimport { MessageDrawer } from \"./components/MessageDrawer.js\";\nimport { SendViaModal } from \"./components/SendViaModal.js\";\nimport { Sidebar } from \"./components/Sidebar.js\";\nimport { StatusBar } from \"./components/StatusBar.js\";\nimport { nextGroupBoundary, prevGroupBoundary, ThreadPane } from \"./components/ThreadPane.js\";\nimport { formatJumpTarget, parseUserDate } from \"./dateParse.js\";\nimport { extensionFor, toCSV, toJSON, toMarkdown } from \"./exportFormats.js\";\nimport { useDevStats } from \"./hooks/useDevStats.js\";\nimport { useImsg } from \"./hooks/useImsg.js\";\nimport { useMouse } from \"./hooks/useMouse.js\";\nimport { initialState, reducer } from \"./types.js\";\n\nexport function App() {\n const [state, dispatch] = useReducer(reducer, initialState);\n const { exit } = useApp();\n const { width: columns, height: rows } = useScreenSize();\n const imsg = useImsg();\n const { stats: devStats, recordQueryTime } = useDevStats(state.showDevStats);\n const pollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const moveDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const ggPendingRef = useRef(false); // tracks if 'g' was pressed, waiting for second 'g'\n const ggTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const drawerWidth = state.mode === \"drawer\" ? Math.min(Math.floor(columns * 0.35), 50) : 0;\n const devStatsWidth = state.showDevStats ? 20 : 0;\n const sidebarWidth = Math.max(Math.floor((columns - drawerWidth - devStatsWidth) * 0.32), 28);\n const threadWidth = Math.max(columns - sidebarWidth - drawerWidth - devStatsWidth, 20);\n const bodyHeight = rows - 2; // status + help\n\n // Sidebar item layout: each item = 4 rows (name + snippet + slug + separator)\n // Header row + filter row + borders subtract from available height.\n // Mirror the calculation in Sidebar.tsx so SELECT dispatches include the\n // right visible-count and the cursor stays on screen as the user navigates.\n const SIDEBAR_ITEM_HEIGHT = 4;\n const sidebarVisibleCount = Math.max(\n Math.floor((bodyHeight - 1 - (state.filterQuery ? 1 : 0) - 2) / SIDEBAR_ITEM_HEIGHT),\n 1,\n );\n\n const selected = state.conversations[state.selectedIdx];\n const totalUnread = state.conversations.reduce((s, c) => s + c.unreadCount, 0);\n const resolvedNames = selected ? imsg.resolveNames(selected.participants) : [];\n const selectedMsg = state.selectedMsgIdx >= 0 ? state.messages[state.selectedMsgIdx] : undefined;\n\n // ── Data loading ───────────────────────────────────────────────────\n\n const loadMessages = useCallback(\n async (idx: number) => {\n const conv = state.conversations[idx];\n if (!conv) return;\n dispatch({\n type: \"SET_LOADING\",\n loading: true,\n status: `Loading ${conv.displayName ?? conv.chatIdentifier}...`,\n });\n const t0 = performance.now();\n const msgs = await imsg.loadMessages(conv.chatIdentifier);\n recordQueryTime(performance.now() - t0);\n dispatch({ type: \"SET_MESSAGES\", data: msgs });\n dispatch({ type: \"SET_LOADING\", loading: false, status: \"\" });\n },\n [state.conversations, imsg, recordQueryTime],\n );\n\n const refreshAll = useCallback(async () => {\n dispatch({ type: \"SET_LOADING\", loading: true, status: \"Refreshing...\" });\n const convs = await imsg.loadConversations();\n dispatch({ type: \"SET_CONVERSATIONS\", data: convs });\n if (convs.length > 0) {\n const prevSlug = selected?.threadSlug;\n if (prevSlug) {\n const idx = convs.findIndex((c) => c.threadSlug === prevSlug);\n if (idx >= 0) dispatch({ type: \"SELECT\", index: idx, visibleCount: sidebarVisibleCount });\n }\n await loadMessages(state.selectedIdx);\n }\n imsg.refresh();\n dispatch({ type: \"SET_LOADING\", loading: false, status: \"\" });\n }, [imsg, loadMessages, selected?.threadSlug, state.selectedIdx, sidebarVisibleCount]);\n\n // ── Lazy-load more conversations when user nears the end ──────────────\n const NEAR_END_THRESHOLD = 20;\n const CONV_BATCH_SIZE = 100;\n\n const loadMoreConversations = useCallback(async () => {\n if (state.conversationLoadingMore) return;\n const targetCount = state.conversationLoadedCount + CONV_BATCH_SIZE;\n dispatch({ type: \"SET_STATUS\", status: `Loading more conversations (${targetCount})...` });\n const convs = await imsg.loadConversations(targetCount);\n dispatch({ type: \"APPEND_CONVERSATIONS\", data: convs, loadedCount: targetCount });\n dispatch({ type: \"SET_STATUS\", status: \"\" });\n }, [imsg, state.conversationLoadingMore, state.conversationLoadedCount]);\n\n // ── Lazy-load older messages when cursor nears the top ────────────────\n const NEAR_TOP_THRESHOLD = 10;\n const _MSG_BATCH_SIZE = 100;\n\n const loadOlderMessages = useCallback(async () => {\n if (!selected) return;\n if (state.messageLoadingOlder) return;\n if (state.messageOldestLoadedId == null) return;\n dispatch({ type: \"SET_LOADING_OLDER\", loading: true });\n const olderMsgs = await imsg.loadOlderMessages(\n selected.chatIdentifier,\n state.messageOldestLoadedId,\n );\n if (olderMsgs.length === 0) {\n // Chat history exhausted — keep flag clear, mark id at -1 sentinel so we\n // don't keep retrying on every cursor move at the top.\n dispatch({ type: \"PREPEND_MESSAGES\", data: [], oldestId: -1 });\n return;\n }\n const newOldestId = Math.min(...olderMsgs.map((m) => m.id));\n dispatch({ type: \"PREPEND_MESSAGES\", data: olderMsgs, oldestId: newOldestId });\n }, [imsg, selected, state.messageLoadingOlder, state.messageOldestLoadedId]);\n\n // Trigger when cursor approaches the start of the message list\n useEffect(() => {\n if (state.loading || state.messageLoadingOlder) return;\n if (state.messages.length === 0) return;\n if (state.messageOldestLoadedId === -1) return; // exhausted\n if (state.selectedMsgIdx >= 0 && state.selectedMsgIdx < NEAR_TOP_THRESHOLD) {\n loadOlderMessages();\n }\n }, [\n state.selectedMsgIdx,\n state.messages.length,\n state.messageOldestLoadedId,\n state.messageLoadingOlder,\n state.loading,\n loadOlderMessages,\n ]);\n\n // Trigger lazy-load when cursor or scroll is near the end of loaded items.\n // Important: only fire when we've actually grown — DB returns N when N requested,\n // so once `conversations.length === conversationLoadedCount` and we asked for\n // 200, asking for 300 may yield no new entries (chat history exhausted).\n useEffect(() => {\n if (state.loading || state.conversationLoadingMore) return;\n if (state.conversations.length === 0) return;\n const cursorNearEnd = state.selectedIdx >= state.conversations.length - NEAR_END_THRESHOLD;\n const scrollNearEnd =\n state.sidebarScroll + sidebarVisibleCount >= state.conversations.length - NEAR_END_THRESHOLD;\n if (cursorNearEnd || scrollNearEnd) {\n // Only ask for more if we haven't seen this chat-count plateau yet\n if (state.conversationLoadedCount === state.conversations.length) {\n loadMoreConversations();\n }\n }\n }, [\n state.selectedIdx,\n state.sidebarScroll,\n state.conversations.length,\n state.conversationLoadedCount,\n state.conversationLoadingMore,\n state.loading,\n sidebarVisibleCount,\n loadMoreConversations,\n ]);\n\n // Initial load + register cleanup\n // biome-ignore lint/correctness/useExhaustiveDependencies: we intentionally run this only on mount to prevent a render-loop\n useEffect(() => {\n registerCleanup(() => imsg.close());\n refreshAll();\n }, [imsg.close]);\n\n // ── Send logic ─────────────────────────────────────────────────────\n\n const sendMessage = useCallback(async () => {\n if (!selected || !state.composeText.trim()) return;\n const text = state.composeText.trim();\n dispatch({ type: \"ADD_PENDING\", msg: { text, sentAt: new Date(), status: \"sending\" } });\n\n const result = await imsg.send(selected.threadSlug, text);\n if (!result.success) {\n dispatch({ type: \"FAIL_PENDING\", text });\n return;\n }\n\n let attempt = 0;\n const poll = async () => {\n // Check if we are still polling for this request (unmounted or cancelled)\n if (!pollTimerRef.current) return;\n attempt++;\n const msgs = await imsg.loadMessages(selected.chatIdentifier);\n const found = msgs.some((m) => m.isFromMe && m.text?.includes(text));\n if (found) {\n dispatch({ type: \"SET_MESSAGES\", data: msgs });\n dispatch({ type: \"RESOLVE_PENDING\", text });\n } else if (attempt < 7) {\n pollTimerRef.current = setTimeout(poll, 1500);\n }\n };\n if (pollTimerRef.current) clearTimeout(pollTimerRef.current);\n pollTimerRef.current = setTimeout(poll, 1500);\n }, [selected, state.composeText, imsg]);\n\n // ── Vim number prefix helper ───────────────────────────────────────\n\n const getCount = useCallback((): number => {\n const n = state.numBuffer ? Number.parseInt(state.numBuffer, 10) : 1;\n dispatch({ type: \"SET_NUM_BUFFER\", value: \"\" });\n return Math.max(1, Math.min(n, 999));\n }, [state.numBuffer]);\n\n // ── Keyboard ───────────────────────────────────────────────────────\n\n useInput(async (input, key) => {\n // Ctrl-C always exits\n if (key.ctrl && input === \"c\") {\n await imsg.close();\n exit();\n return;\n }\n\n // Filter mode\n if (state.mode === \"filter\") {\n if (key.escape || key.return) {\n dispatch({ type: \"EXIT_FILTER\" });\n } else if (key.backspace || key.delete) {\n dispatch({ type: \"UPDATE_FILTER\", query: state.filterQuery.slice(0, -1) });\n } else if (input && !key.ctrl && !key.meta) {\n dispatch({ type: \"UPDATE_FILTER\", query: state.filterQuery + input });\n }\n return;\n }\n\n // Compose mode\n if (state.mode === \"compose\") {\n if (key.escape) {\n dispatch({ type: \"CANCEL_COMPOSE\" });\n } else if (key.return && state.composeText.trim()) {\n dispatch({ type: \"CONFIRM_SEND\" });\n }\n return;\n }\n\n // Confirm mode\n if (state.mode === \"confirm\") {\n if (key.return) {\n await sendMessage();\n } else {\n dispatch({ type: \"CANCEL_COMPOSE\" });\n }\n return;\n }\n\n // Drawer mode\n if (state.mode === \"drawer\") {\n if (key.escape || input === \"q\") {\n dispatch({ type: \"CLOSE_DRAWER\" });\n } else if (input === \"o\" && selectedMsg) {\n // Open attachment in external viewer\n openAttachment(selectedMsg);\n }\n return;\n }\n\n // Date-jump modal mode — Esc cancels; Enter handled by TextInput.onSubmit\n if (state.mode === \"date-jump\") {\n if (key.escape) {\n dispatch({ type: \"EXIT_DATE_JUMP\" });\n }\n return;\n }\n\n // Send-via modal: digit picks an app + launches its URL-scheme deep link.\n if (state.mode === \"send-via\") {\n if (key.escape) {\n dispatch({ type: \"EXIT_SEND_VIA\" });\n return;\n }\n if (input && /^[1-9]$/.test(input) && selected) {\n const apps = getInstalledChatApps();\n const idx = Number.parseInt(input, 10) - 1;\n const app = apps[idx];\n if (app) {\n const lastMsgText = state.messages.length\n ? (state.messages[state.messages.length - 1]?.text ?? undefined)\n : undefined;\n const built = app.buildUri(selected.chatIdentifier, lastMsgText);\n if (built) {\n (await import(\"node:child_process\"))\n .spawn(\"open\", [built], { detached: true, stdio: \"ignore\" })\n .unref();\n dispatch({ type: \"SET_STATUS\", status: `Launched ${app.name}` });\n } else {\n dispatch({\n type: \"SET_STATUS\",\n status: `${app.name}: handle not compatible with this scheme`,\n });\n }\n }\n dispatch({ type: \"EXIT_SEND_VIA\" });\n setTimeout(() => dispatch({ type: \"SET_STATUS\", status: \"\" }), 2500);\n }\n return;\n }\n\n // Export-modal mode — Tab cycles format, Esc cancels.\n // Path text input + Enter handled by the inline TextInput inside the modal.\n if (state.mode === \"export\") {\n if (key.escape) {\n dispatch({ type: \"EXIT_EXPORT_MODE\" });\n } else if (key.tab) {\n const order: Array<\"markdown\" | \"csv\" | \"json\"> = [\"markdown\", \"csv\", \"json\"];\n const next = order[(order.indexOf(state.exportFormat) + 1) % order.length];\n dispatch({ type: \"SET_EXPORT_FORMAT\", format: next });\n // Update path extension to match new format\n const stripped = state.exportPath.replace(/\\.(md|csv|json)$/, \"\");\n dispatch({ type: \"SET_EXPORT_PATH\", path: `${stripped}.${extensionFor(next)}` });\n }\n return;\n }\n\n // Select mode — V'd. j/k extend, Esc exits, e opens export modal,\n // y copies selected text to clipboard.\n if (state.mode === \"select\") {\n if (key.escape) {\n dispatch({ type: \"EXIT_SELECT_MODE\" });\n return;\n }\n if (input === \"e\") {\n const slug = selected?.threadSlug ?? \"messages\";\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\").slice(0, 19);\n const defaultPath = join(homedir(), `imsg-export-${slug}-${stamp}.md`);\n dispatch({ type: \"ENTER_EXPORT_MODE\", defaultPath });\n return;\n }\n if (input === \"y\" && state.selectionAnchor != null) {\n const [lo, hi] = [\n Math.min(state.selectionAnchor, state.selectedMsgIdx),\n Math.max(state.selectionAnchor, state.selectedMsgIdx),\n ];\n const text = state.messages\n .slice(lo, hi + 1)\n .map(\n (m) =>\n `[${m.date.toISOString()}] ${m.isFromMe ? \"Me\" : (m.displayName ?? m.handle)}: ${m.text ?? \"(no text)\"}`,\n )\n .join(\"\\n\");\n try {\n execSync(\"pbcopy\", { input: text });\n dispatch({ type: \"SET_STATUS\", status: `Copied ${hi - lo + 1} msgs` });\n } catch {\n dispatch({ type: \"SET_STATUS\", status: \"Copy failed\" });\n }\n setTimeout(() => dispatch({ type: \"SET_STATUS\", status: \"\" }), 2000);\n return;\n }\n // j/k/G/gg/Ctrl-d/Ctrl-u — fall through to browse-mode movement (anchor stays)\n }\n\n // Browse mode\n if (input === \"d\" && !key.ctrl && !key.meta && state.mode === \"browse\") {\n dispatch({ type: \"TOGGLE_DEV_STATS\" });\n return;\n }\n if (input === \"V\" && state.focus === \"thread\" && state.selectedMsgIdx >= 0) {\n dispatch({ type: \"ENTER_SELECT_MODE\" });\n return;\n }\n if (input === \":\" && state.focus === \"thread\" && state.mode === \"browse\") {\n dispatch({ type: \"ENTER_DATE_JUMP\" });\n return;\n }\n // O — open the current thread in Messages.app via the imessage:// URL scheme.\n // For 1:1 chats this focuses or composes that thread. Groups have no URL\n // scheme — we fall back to AppleScript activate.\n if (\n input === \"O\" &&\n !key.ctrl &&\n !key.meta &&\n state.focus === \"thread\" &&\n state.mode === \"browse\" &&\n selected\n ) {\n const handle = selected.chatIdentifier;\n const uri = `imessage://${encodeURIComponent(handle)}`;\n (await import(\"node:child_process\"))\n .spawn(\"open\", [uri], { detached: true, stdio: \"ignore\" })\n .unref();\n dispatch({ type: \"SET_STATUS\", status: `Opened ${handle} in Messages.app` });\n setTimeout(() => dispatch({ type: \"SET_STATUS\", status: \"\" }), 2500);\n return;\n }\n // S — send-via picker: list installed external chat apps and let the user\n // launch one. Body is the most recent message text in the thread (best-\n // effort context); the URI carries it only on schemes that support body.\n if (\n input === \"S\" &&\n !key.ctrl &&\n !key.meta &&\n state.focus === \"thread\" &&\n state.mode === \"browse\" &&\n selected\n ) {\n dispatch({ type: \"ENTER_SEND_VIA\" });\n return;\n }\n if (input === \"q\") {\n await imsg.close();\n exit();\n return;\n }\n if (input === \"r\") {\n await refreshAll();\n return;\n }\n if (input === \"c\" || (key.return && state.focus === \"thread\" && state.mode === \"browse\")) {\n if (state.focus === \"thread\" && key.return && state.selectedMsgIdx >= 0) {\n // Enter on a message opens drawer\n dispatch({ type: \"OPEN_DRAWER\" });\n return;\n }\n dispatch({ type: \"ENTER_COMPOSE\" });\n return;\n }\n if (input === \"/\" && state.mode === \"browse\") {\n dispatch({ type: \"ENTER_FILTER\" });\n return;\n }\n if (key.tab) {\n dispatch({ type: \"FOCUS\", pane: state.focus === \"sidebar\" ? \"thread\" : \"sidebar\" });\n return;\n }\n\n if (state.loading) return;\n\n // Number buffer for vim-style counts (e.g. \"12j\" to jump 12 lines)\n if (input && input >= \"0\" && input <= \"9\" && !key.ctrl && !key.meta) {\n // Don't buffer leading zeros unless building a number\n if (input === \"0\" && !state.numBuffer) {\n // '0' alone: go to first item (like vim)\n if (state.focus === \"sidebar\") {\n dispatch({ type: \"SELECT\", index: 0, visibleCount: sidebarVisibleCount });\n loadMessages(0);\n } else {\n dispatch({ type: \"SELECT_MSG\", index: 0 });\n }\n return;\n }\n dispatch({ type: \"SET_NUM_BUFFER\", value: state.numBuffer + input });\n return;\n }\n\n if (state.focus === \"sidebar\") {\n // Copy thread slug to clipboard\n if (input === \"y\" && selected) {\n try {\n execSync(\"pbcopy\", { input: `~${selected.threadSlug}` });\n dispatch({ type: \"SET_STATUS\", status: `Copied ~${selected.threadSlug}` });\n setTimeout(() => dispatch({ type: \"SET_STATUS\", status: \"\" }), 2000);\n } catch {\n dispatch({ type: \"SET_STATUS\", status: \"Failed to copy\" });\n }\n return;\n }\n\n const count = getCount();\n let next: number | null = null;\n\n if (input === \"j\" || key.downArrow) {\n next = Math.min(state.selectedIdx + count, state.conversations.length - 1);\n } else if (input === \"k\" || key.upArrow) {\n next = Math.max(state.selectedIdx - count, 0);\n } else if (input === \"G\") {\n next = state.conversations.length - 1;\n } else if (input === \"g\") {\n // Handle gg (go to top)\n if (ggPendingRef.current) {\n ggPendingRef.current = false;\n if (ggTimerRef.current) clearTimeout(ggTimerRef.current);\n next = 0;\n } else {\n ggPendingRef.current = true;\n ggTimerRef.current = setTimeout(() => {\n ggPendingRef.current = false;\n }, 500);\n return;\n }\n } else if (key.ctrl && input === \"d\") {\n // Half page down\n next = Math.min(\n state.selectedIdx + Math.floor(bodyHeight / 2),\n state.conversations.length - 1,\n );\n } else if (key.ctrl && input === \"u\") {\n // Half page up\n next = Math.max(state.selectedIdx - Math.floor(bodyHeight / 2), 0);\n } else if ((key.ctrl && input === \"f\") || key.pageDown) {\n next = Math.min(state.selectedIdx + bodyHeight, state.conversations.length - 1);\n } else if ((key.ctrl && input === \"b\") || key.pageUp) {\n next = Math.max(state.selectedIdx - bodyHeight, 0);\n } else if (input === \"H\") {\n // High - first visible\n next = state.sidebarScroll;\n } else if (input === \"M\") {\n // Middle\n next = Math.min(\n state.sidebarScroll + Math.floor(bodyHeight / 2),\n state.conversations.length - 1,\n );\n } else if (input === \"L\") {\n // Low - last visible\n next = Math.min(state.sidebarScroll + bodyHeight - 1, state.conversations.length - 1);\n }\n\n if (next !== null && next !== state.selectedIdx) {\n dispatch({ type: \"SELECT\", index: next, visibleCount: sidebarVisibleCount });\n // Debounce message loading\n if (moveDebounceRef.current) clearTimeout(moveDebounceRef.current);\n const target = next;\n moveDebounceRef.current = setTimeout(() => {\n moveDebounceRef.current = null;\n loadMessages(target);\n }, 80);\n }\n } else {\n // Thread focus — message cursor movement\n const count = getCount();\n\n if (input === \"j\" || key.downArrow) {\n dispatch({ type: \"MOVE_MSG\", delta: count });\n } else if (input === \"k\" || key.upArrow) {\n dispatch({ type: \"MOVE_MSG\", delta: -count });\n } else if (input === \"G\") {\n dispatch({ type: \"SELECT_MSG\", index: state.messages.length - 1 });\n } else if (input === \"g\") {\n if (ggPendingRef.current) {\n ggPendingRef.current = false;\n if (ggTimerRef.current) clearTimeout(ggTimerRef.current);\n dispatch({ type: \"SELECT_MSG\", index: 0 });\n } else {\n ggPendingRef.current = true;\n ggTimerRef.current = setTimeout(() => {\n ggPendingRef.current = false;\n }, 500);\n }\n } else if (key.ctrl && input === \"d\") {\n dispatch({ type: \"MOVE_MSG\", delta: Math.floor(bodyHeight / 2) });\n } else if (key.ctrl && input === \"u\") {\n dispatch({ type: \"MOVE_MSG\", delta: -Math.floor(bodyHeight / 2) });\n } else if ((key.ctrl && input === \"f\") || key.pageDown) {\n dispatch({ type: \"MOVE_MSG\", delta: bodyHeight });\n } else if ((key.ctrl && input === \"b\") || key.pageUp) {\n dispatch({ type: \"MOVE_MSG\", delta: -bodyHeight });\n } else if (input === \"H\") {\n // Jump to top of visible area (approximate)\n const visibleTop = Math.max(0, state.selectedMsgIdx - Math.floor(bodyHeight * 0.7));\n dispatch({ type: \"SELECT_MSG\", index: visibleTop });\n } else if (input === \"L\") {\n const visibleBottom = Math.min(\n state.messages.length - 1,\n state.selectedMsgIdx + Math.floor(bodyHeight * 0.3),\n );\n dispatch({ type: \"SELECT_MSG\", index: visibleBottom });\n } else if (input === \"M\") {\n // Stay at current (middle)\n } else if (input === \"}\" || input === \"]\") {\n // Jump to next sender group\n const next = nextGroupBoundary(state.messages, state.selectedMsgIdx);\n dispatch({ type: \"SELECT_MSG\", index: next });\n } else if (input === \"{\" || input === \"[\") {\n // Jump to previous sender group\n const prev = prevGroupBoundary(state.messages, state.selectedMsgIdx);\n dispatch({ type: \"SELECT_MSG\", index: prev });\n } else if (input === \"o\" && state.selectedMsgIdx >= 0) {\n // Open attachment for selected message\n openAttachment(state.messages[state.selectedMsgIdx]);\n }\n }\n });\n\n // ── Date jump ──────────────────────────────────────────────────────\n\n const MAX_JUMP_BATCHES = 100; // bounded loop: 100 × 100 = 10,000 messages\n\n const doDateJump = useCallback(\n async (input: string) => {\n const target = parseUserDate(input);\n if (!target) {\n dispatch({\n type: \"SET_DATE_JUMP_ERROR\",\n error: `Could not parse \"${input}\". Try YYYY-MM-DD or \"1 week ago\".`,\n });\n return;\n }\n if (!selected) {\n dispatch({ type: \"EXIT_DATE_JUMP\" });\n return;\n }\n let batches = 0;\n // Loop: load older messages until oldest <= target, or exhausted\n while (batches < MAX_JUMP_BATCHES) {\n const oldest = state.messages.length > 0 ? state.messages[0].date : new Date();\n if (oldest <= target) break;\n if (state.messageOldestLoadedId == null || state.messageOldestLoadedId === -1) break;\n const older = await imsg.loadOlderMessages(\n selected.chatIdentifier,\n state.messageOldestLoadedId,\n );\n if (older.length === 0) break;\n const newOldestId = Math.min(...older.map((m) => m.id));\n dispatch({ type: \"PREPEND_MESSAGES\", data: older, oldestId: newOldestId });\n batches++;\n // Yield to render between batches\n await new Promise((r) => setTimeout(r, 10));\n }\n // Find first message at or after target\n const idx = state.messages.findIndex((m) => m.date >= target);\n if (idx >= 0) {\n dispatch({ type: \"SELECT_MSG\", index: idx });\n }\n dispatch({ type: \"EXIT_DATE_JUMP\" });\n dispatch({\n type: \"SET_STATUS\",\n status:\n batches >= MAX_JUMP_BATCHES\n ? `Jumped (capped) — load more manually for older history`\n : `Jumped to ${formatJumpTarget(target)}`,\n });\n setTimeout(() => dispatch({ type: \"SET_STATUS\", status: \"\" }), 4000);\n },\n [imsg, selected, state.messages, state.messageOldestLoadedId],\n );\n\n // ── Export action ──────────────────────────────────────────────────\n\n const doExport = useCallback(() => {\n const messagesToExport =\n state.selectionAnchor != null\n ? state.messages.slice(\n Math.min(state.selectionAnchor, state.selectedMsgIdx),\n Math.max(state.selectionAnchor, state.selectedMsgIdx) + 1,\n )\n : state.messages;\n if (messagesToExport.length === 0) {\n dispatch({ type: \"SET_STATUS\", status: \"Nothing to export\" });\n return;\n }\n const expandedPath = state.exportPath.replace(/^~/, homedir());\n try {\n let content: string;\n const header = {\n thread: selected?.displayName ?? selected?.chatIdentifier ?? \"thread\",\n participants: selected?.participants ?? [],\n serviceType: selected?.serviceType,\n };\n switch (state.exportFormat) {\n case \"markdown\":\n content = toMarkdown(messagesToExport, header);\n break;\n case \"csv\":\n content = toCSV(messagesToExport);\n break;\n case \"json\":\n content = toJSON(messagesToExport, header);\n break;\n }\n writeFileSync(expandedPath, content, \"utf8\");\n dispatch({ type: \"EXIT_EXPORT_MODE\" });\n dispatch({ type: \"EXIT_SELECT_MODE\" });\n dispatch({\n type: \"SET_STATUS\",\n status: `Exported ${messagesToExport.length} msgs to ${expandedPath}`,\n });\n setTimeout(() => dispatch({ type: \"SET_STATUS\", status: \"\" }), 4000);\n } catch (err) {\n dispatch({\n type: \"SET_STATUS\",\n status: `Export failed: ${err instanceof Error ? err.message : String(err)}`,\n });\n }\n }, [\n state.exportFormat,\n state.exportPath,\n state.messages,\n state.selectedMsgIdx,\n state.selectionAnchor,\n selected,\n ]);\n\n // ── Open attachment ────────────────────────────────────────────────\n\n function openAttachment(msg: import(\"../types.js\").Message | undefined) {\n if (!msg?.attachments?.length) return;\n const att = msg.attachments[0];\n if (!att.filename) return;\n // Expand ~ to home directory\n const filepath = att.filename.replace(/^~/, process.env.HOME ?? \"~\");\n const mime = att.mimeType ?? \"\";\n\n // macOS Quick Look (qlmanage -p) handles images, PDFs, audio, docs, and\n // most archives natively — same UX as Finder spacebar preview. For video,\n // mpv is preferred when installed (better scrubbing); otherwise fall back\n // to Quick Look. All spawns are detached + unref'd so the TUI never blocks.\n import(\"node:child_process\").then(({ spawn }) => {\n const spawnQuickLook = () =>\n spawn(\"qlmanage\", [\"-p\", filepath], { detached: true, stdio: \"ignore\" }).unref();\n\n if (mime.startsWith(\"video/\")) {\n const child = spawn(\"mpv\", [filepath], { detached: true, stdio: \"ignore\" });\n child.on(\"error\", spawnQuickLook);\n child.unref();\n } else {\n spawnQuickLook();\n }\n });\n }\n\n // ── Mouse ──────────────────────────────────────────────────────────\n\n const handleMouse = useCallback(\n (event: { type: string; x: number; y: number }) => {\n if (event.type === \"click\") {\n if (event.x <= sidebarWidth) {\n dispatch({ type: \"FOCUS\", pane: \"sidebar\" });\n const convIdx = Math.floor((event.y - 2 + state.sidebarScroll * 3) / 3);\n if (convIdx >= 0 && convIdx < state.conversations.length) {\n dispatch({ type: \"SELECT\", index: convIdx, visibleCount: sidebarVisibleCount });\n loadMessages(convIdx);\n }\n } else {\n dispatch({ type: \"FOCUS\", pane: \"thread\" });\n }\n } else if (event.type === \"scroll-up\") {\n // Wheel delta: 1 line per event for fine-grained control. macOS / iTerm\n // typically batch 2-3 wheel events per touchpad swipe so this still feels\n // responsive in practice.\n if (event.x <= sidebarWidth) {\n dispatch({ type: \"SCROLL_SIDEBAR\", delta: -1 });\n } else {\n dispatch({ type: \"MOVE_MSG\", delta: -1 });\n }\n } else if (event.type === \"scroll-down\") {\n if (event.x <= sidebarWidth) {\n dispatch({ type: \"SCROLL_SIDEBAR\", delta: 1 });\n } else {\n dispatch({ type: \"MOVE_MSG\", delta: 1 });\n }\n }\n },\n [\n sidebarWidth,\n state.sidebarScroll,\n state.conversations.length,\n loadMessages,\n sidebarVisibleCount,\n ],\n );\n\n useMouse(handleMouse);\n\n // ── Cleanup ────────────────────────────────────────────────────────\n\n useEffect(() => {\n return () => {\n if (pollTimerRef.current) clearTimeout(pollTimerRef.current);\n if (moveDebounceRef.current) clearTimeout(moveDebounceRef.current);\n if (ggTimerRef.current) clearTimeout(ggTimerRef.current);\n };\n }, []);\n\n // ── Render ─────────────────────────────────────────────────────────\n\n return (\n <Box flexDirection=\"column\" height={rows} width={columns}>\n {/* Main layout */}\n <Box flexGrow={1} height={bodyHeight}>\n <Sidebar\n conversations={state.conversations}\n selectedIdx={state.selectedIdx}\n scrollOffset={state.sidebarScroll}\n filterQuery={state.filterQuery}\n focused={state.focus === \"sidebar\"}\n width={sidebarWidth}\n height={bodyHeight}\n />\n <ThreadPane\n conversation={selected}\n messages={state.messages}\n pending={state.pending}\n resolvedNames={resolvedNames}\n scrollOffset={state.threadScroll}\n selectedMsgIdx={state.selectedMsgIdx}\n selectionAnchor={state.selectionAnchor}\n gapMarkers={state.gapMarkers}\n focused={state.focus === \"thread\"}\n width={threadWidth}\n height={bodyHeight}\n mode={state.mode}\n onChangeCompose={(text) => dispatch({ type: \"UPDATE_COMPOSE\", text })}\n onSubmitCompose={(text) => text.trim() && dispatch({ type: \"CONFIRM_SEND\" })}\n />\n {state.mode === \"drawer\" && selectedMsg && (\n <MessageDrawer message={selectedMsg} width={drawerWidth} height={bodyHeight} />\n )}\n {state.showDevStats && <DevStats stats={devStats} width={devStatsWidth} />}\n </Box>\n\n {/* Date-jump modal */}\n {state.mode === \"date-jump\" && (\n <DateJumpModal\n value={state.dateJumpInput}\n error={state.dateJumpError}\n onChange={(v) => dispatch({ type: \"SET_DATE_JUMP_INPUT\", value: v })}\n onSubmit={(v) => doDateJump(v)}\n />\n )}\n\n {/* Send-via picker — launch external chat apps for the current thread */}\n {state.mode === \"send-via\" && selected && (\n <SendViaModal handle={selected.chatIdentifier} apps={getInstalledChatApps()} />\n )}\n\n {/* Export modal — overlays the bottom of the body when active */}\n {state.mode === \"export\" && (\n <ExportModal\n format={state.exportFormat}\n path={state.exportPath}\n rangeSummary={(() => {\n if (state.selectionAnchor != null) {\n const lo = Math.min(state.selectionAnchor, state.selectedMsgIdx);\n const hi = Math.max(state.selectionAnchor, state.selectedMsgIdx);\n return `${hi - lo + 1} selected messages`;\n }\n return `entire loaded thread (${state.messages.length} messages)`;\n })()}\n onChangePath={(p) => dispatch({ type: \"SET_EXPORT_PATH\", path: p })}\n onSubmit={doExport}\n />\n )}\n\n {/* Status + Help */}\n <StatusBar\n totalUnread={totalUnread}\n selected={selected}\n status={state.status}\n loading={state.loading}\n >\n {!state.showDevStats && <CompactStats stats={devStats} />}\n </StatusBar>\n <HelpBar mode={state.mode} focus={state.focus} />\n </Box>\n );\n}\n","import { parseArgs } from \"node:util\";\nimport { withFullScreen } from \"fullscreen-ink\";\nimport { checkLocalAccess, formatAccessReport } from \"../access-check.js\";\nimport { enableOrphanWatchdog, installShutdownHandlers, registerCleanup } from \"../shutdown.js\";\nimport { resolveTuiConfig } from \"../tui-config.js\";\nimport { installWatchdog } from \"../watchdog.js\";\nimport { App } from \"./App.js\";\nimport { clearCache, installCacheSweepers, stopCacheSweepers } from \"./messageCache.js\";\nimport { makeTheme } from \"./theme.js\";\nimport { ThemeProvider } from \"./themes/ThemeContext.js\";\n\n/** Parse `--theme` and `--accent` from the TUI command line. Unknown\n * flags don't raise — the TUI accepts no other options today. */\nfunction parseTuiCliArgs(): { theme?: string; accent?: string } {\n try {\n const { values } = parseArgs({\n args: process.argv.slice(2),\n options: {\n theme: { type: \"string\" },\n accent: { type: \"string\" },\n },\n strict: false,\n allowPositionals: true,\n });\n return {\n theme: typeof values.theme === \"string\" ? values.theme : undefined,\n accent: typeof values.accent === \"string\" ? values.accent : undefined,\n };\n } catch {\n return {};\n }\n}\n\nexport async function runTui(): Promise<void> {\n if (!process.stdin.isTTY || !process.stdout.isTTY) {\n throw new Error(\"The TUI requires an interactive terminal (TTY).\");\n }\n\n const report = await checkLocalAccess();\n if (!report.ok) {\n console.log(formatAccessReport(report));\n process.exit(1);\n }\n\n // Resolve theme / accent: CLI > env > ~/.config/imsg-mcp/config.json > defaults.\n const cli = parseTuiCliArgs();\n const cfg = resolveTuiConfig({ cliTheme: cli.theme, cliAccent: cli.accent });\n for (const w of cfg.warnings) console.error(`warn: ${w}`);\n const theme = makeTheme({ preset: cfg.theme, accent: cfg.accentColor });\n\n // Install shutdown handlers + watchdog before starting TUI\n installShutdownHandlers();\n enableOrphanWatchdog();\n installWatchdog();\n installCacheSweepers();\n\n registerCleanup(() => {\n stopCacheSweepers();\n clearCache();\n });\n\n const screen = withFullScreen(\n <ThemeProvider value={theme}>\n <App />\n </ThemeProvider>,\n );\n\n // Register screen cleanup so terminal is restored on any exit\n registerCleanup(() => {\n try {\n screen.instance.unmount();\n } catch {\n // Screen may already be unmounted\n }\n });\n\n await screen.start();\n await screen.waitUntilExit();\n}\n"],"names":["relativeDate","end","start","totalLines","i"],"mappings":"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"}