@burdenoff/microfe-vibecontrols 2026.608.1 → 2026.608.2

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.
@@ -12,9 +12,16 @@ function t(e, t) {
12
12
  id: "new-session",
13
13
  keysDisplay: "⌘/Ctrl + T",
14
14
  category: "Actions",
15
- description: t("sessions.sc.newSession", "New session"),
15
+ description: t("sessions.sc.newSession", "New terminal (quick)"),
16
16
  run: e.newSession
17
17
  },
18
+ {
19
+ id: "new-session-options",
20
+ keysDisplay: "⌘/Ctrl + Shift + T",
21
+ category: "Actions",
22
+ description: t("sessions.sc.newSessionOptions", "New session with options…"),
23
+ run: e.newSessionWithOptions
24
+ },
18
25
  {
19
26
  id: "open-palette",
20
27
  keysDisplay: "⌘/Ctrl + K",
@@ -29,6 +36,20 @@ function t(e, t) {
29
36
  description: t("sessions.sc.reconnect", "Reconnect active terminal"),
30
37
  run: e.reconnectActive
31
38
  },
39
+ {
40
+ id: "rename-tab",
41
+ keysDisplay: "F2",
42
+ category: "Tabs",
43
+ description: t("sessions.sc.renameTab", "Rename active tab"),
44
+ run: e.renameActiveTab
45
+ },
46
+ {
47
+ id: "open-tab-new",
48
+ keysDisplay: "⌘/Ctrl + O",
49
+ category: "Tabs",
50
+ description: t("sessions.sc.openTabNew", "Open active tab in new browser tab"),
51
+ run: e.openActiveInNewTab
52
+ },
32
53
  {
33
54
  id: "close-tab",
34
55
  keysDisplay: "⌘/Ctrl + W",
@@ -38,13 +59,13 @@ function t(e, t) {
38
59
  },
39
60
  {
40
61
  id: "next-tab",
41
- keysDisplay: "Ctrl + Tab",
62
+ keysDisplay: "Ctrl + Tab · ⌘/Ctrl + Shift + ]",
42
63
  category: "Tabs",
43
64
  description: t("sessions.sc.nextTab", "Next tab")
44
65
  },
45
66
  {
46
67
  id: "prev-tab",
47
- keysDisplay: "Ctrl + Shift + Tab",
68
+ keysDisplay: "Ctrl + Shift + Tab · ⌘/Ctrl + Shift + [",
48
69
  category: "Tabs",
49
70
  description: t("sessions.sc.prevTab", "Previous tab")
50
71
  },
@@ -131,6 +152,12 @@ function t(e, t) {
131
152
  description: t("sessions.sc.openTarget", "Open active session's target"),
132
153
  run: e.openActiveTarget
133
154
  },
155
+ {
156
+ id: "terminal-select-copy",
157
+ keysDisplay: "⇧ Shift + drag",
158
+ category: "Help",
159
+ description: t("sessions.sc.selectCopy", "Select & copy text in the terminal (iTerm2-style, even in tmux)")
160
+ },
134
161
  {
135
162
  id: "open-cheatsheet",
136
163
  keysDisplay: "?",
@@ -1 +1 @@
1
- {"version":3,"file":"sessionShortcuts.js","names":[],"sources":["../../../src/components/sessions/sessionShortcuts.ts"],"sourcesContent":["/**\n * Single source of truth for the Sessions page keyboard shortcuts.\n *\n * The same registry drives (a) the keydown handler, (b) the cheatsheet\n * overlay, and (c) the command palette — so a shortcut and its help text\n * can never drift apart. Each entry carries a display string (platform\n * neutral) plus the action to run.\n */\nexport type ShortcutCategory = 'Tabs' | 'View' | 'Navigation' | 'Actions' | 'Help';\n\nexport const SHORTCUT_CATEGORY_ORDER: ShortcutCategory[] = [\n 'Actions',\n 'Tabs',\n 'View',\n 'Navigation',\n 'Help',\n];\n\nexport interface SessionShortcut {\n id: string;\n /** Platform-neutral key display, e.g. \"⌘/Ctrl + T\". */\n keysDisplay: string;\n description: string;\n category: ShortcutCategory;\n /** Action to run. Display-only entries (e.g. tab digits) omit it. */\n run?: () => void;\n}\n\nexport interface SessionShortcutHandlers {\n newSession: () => void;\n closeActiveTab: () => void;\n openPalette: () => void;\n openCheatsheet: () => void;\n focusSearch: () => void;\n cycleView: () => void;\n groupByNone: () => void;\n groupByAgent: () => void;\n groupByTarget: () => void;\n groupByVibe: () => void;\n groupByStatus: () => void;\n reconnectActive: () => void;\n toggleFullscreen: () => void;\n toggleInfoPanel: () => void;\n openActiveAgent: () => void;\n openActiveTarget: () => void;\n}\n\ntype Tr = (key: string, fallback: string) => string;\n\n/**\n * Build the shortcut registry. Key-event → id matching lives in the page's\n * keydown handler; this provides the canonical id/description/action set.\n */\nexport function buildSessionShortcuts(h: SessionShortcutHandlers, tr: Tr): SessionShortcut[] {\n return [\n // Actions\n {\n id: 'new-session',\n keysDisplay: '⌘/Ctrl + T',\n category: 'Actions',\n description: tr('sessions.sc.newSession', 'New session'),\n run: h.newSession,\n },\n {\n id: 'open-palette',\n keysDisplay: '⌘/Ctrl + K',\n category: 'Actions',\n description: tr('sessions.sc.palette', 'Command palette'),\n run: h.openPalette,\n },\n {\n id: 'reconnect-active',\n keysDisplay: '⌘/Ctrl + R',\n category: 'Actions',\n description: tr('sessions.sc.reconnect', 'Reconnect active terminal'),\n run: h.reconnectActive,\n },\n // Tabs\n {\n id: 'close-tab',\n keysDisplay: '⌘/Ctrl + W',\n category: 'Tabs',\n description: tr('sessions.sc.closeTab', 'Close active tab'),\n run: h.closeActiveTab,\n },\n {\n id: 'next-tab',\n keysDisplay: 'Ctrl + Tab',\n category: 'Tabs',\n description: tr('sessions.sc.nextTab', 'Next tab'),\n },\n {\n id: 'prev-tab',\n keysDisplay: 'Ctrl + Shift + Tab',\n category: 'Tabs',\n description: tr('sessions.sc.prevTab', 'Previous tab'),\n },\n {\n id: 'jump-tab',\n keysDisplay: '⌘/Ctrl + 1…9',\n category: 'Tabs',\n description: tr('sessions.sc.jumpTab', 'Jump to tab N'),\n },\n // View\n {\n id: 'focus-search',\n keysDisplay: '/',\n category: 'View',\n description: tr('sessions.sc.focusSearch', 'Focus search'),\n run: h.focusSearch,\n },\n {\n id: 'cycle-view',\n keysDisplay: '⌘/Ctrl + \\\\',\n category: 'View',\n description: tr('sessions.sc.cycleView', 'Cycle terminal / grid / list'),\n run: h.cycleView,\n },\n {\n id: 'group-agent',\n keysDisplay: 'g a',\n category: 'View',\n description: tr('sessions.sc.groupAgent', 'Group by agent'),\n run: h.groupByAgent,\n },\n {\n id: 'group-target',\n keysDisplay: 'g t',\n category: 'View',\n description: tr('sessions.sc.groupTarget', 'Group by target'),\n run: h.groupByTarget,\n },\n {\n id: 'group-vibe',\n keysDisplay: 'g v',\n category: 'View',\n description: tr('sessions.sc.groupVibe', 'Group by vibe'),\n run: h.groupByVibe,\n },\n {\n id: 'group-status',\n keysDisplay: 'g s',\n category: 'View',\n description: tr('sessions.sc.groupStatus', 'Group by status'),\n run: h.groupByStatus,\n },\n {\n id: 'group-none',\n keysDisplay: 'g 0',\n category: 'View',\n description: tr('sessions.sc.groupNone', 'Clear grouping'),\n run: h.groupByNone,\n },\n {\n id: 'toggle-fullscreen',\n keysDisplay: 'f',\n category: 'View',\n description: tr('sessions.sc.fullscreen', 'Toggle terminal fullscreen'),\n run: h.toggleFullscreen,\n },\n {\n id: 'toggle-info',\n keysDisplay: 'i',\n category: 'View',\n description: tr('sessions.sc.infoPanel', 'Toggle session info panel'),\n run: h.toggleInfoPanel,\n },\n // Navigation\n {\n id: 'open-agent',\n keysDisplay: 'g shift+a',\n category: 'Navigation',\n description: tr('sessions.sc.openAgent', \"Open active session's agent\"),\n run: h.openActiveAgent,\n },\n {\n id: 'open-target',\n keysDisplay: 'g shift+t',\n category: 'Navigation',\n description: tr('sessions.sc.openTarget', \"Open active session's target\"),\n run: h.openActiveTarget,\n },\n // Help\n {\n id: 'open-cheatsheet',\n keysDisplay: '?',\n category: 'Help',\n description: tr('sessions.sc.cheatsheet', 'Keyboard shortcuts'),\n run: h.openCheatsheet,\n },\n ];\n}\n\n/** Group a registry into ordered [category, shortcuts] pairs for display. */\nexport function groupShortcutsByCategory(\n shortcuts: SessionShortcut[]\n): Array<[ShortcutCategory, SessionShortcut[]]> {\n return SHORTCUT_CATEGORY_ORDER.map(\n (category) =>\n [category, shortcuts.filter((s) => s.category === category)] as [\n ShortcutCategory,\n SessionShortcut[],\n ]\n ).filter(([, items]) => items.length > 0);\n}\n"],"mappings":";AAUA,IAAa,IAA8C;CACzD;CACA;CACA;CACA;CACA;CACD;AAqCD,SAAgB,EAAsB,GAA4B,GAA2B;AAC3F,QAAO;EAEL;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,0BAA0B,cAAc;GACxD,KAAK,EAAE;GACR;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,uBAAuB,kBAAkB;GACzD,KAAK,EAAE;GACR;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,yBAAyB,4BAA4B;GACrE,KAAK,EAAE;GACR;EAED;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,wBAAwB,mBAAmB;GAC3D,KAAK,EAAE;GACR;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,uBAAuB,WAAW;GACnD;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,uBAAuB,eAAe;GACvD;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,uBAAuB,gBAAgB;GACxD;EAED;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,2BAA2B,eAAe;GAC1D,KAAK,EAAE;GACR;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,yBAAyB,+BAA+B;GACxE,KAAK,EAAE;GACR;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,0BAA0B,iBAAiB;GAC3D,KAAK,EAAE;GACR;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,2BAA2B,kBAAkB;GAC7D,KAAK,EAAE;GACR;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,yBAAyB,gBAAgB;GACzD,KAAK,EAAE;GACR;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,2BAA2B,kBAAkB;GAC7D,KAAK,EAAE;GACR;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,yBAAyB,iBAAiB;GAC1D,KAAK,EAAE;GACR;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,0BAA0B,6BAA6B;GACvE,KAAK,EAAE;GACR;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,yBAAyB,4BAA4B;GACrE,KAAK,EAAE;GACR;EAED;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,yBAAyB,8BAA8B;GACvE,KAAK,EAAE;GACR;EACD;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,0BAA0B,+BAA+B;GACzE,KAAK,EAAE;GACR;EAED;GACE,IAAI;GACJ,aAAa;GACb,UAAU;GACV,aAAa,EAAG,0BAA0B,qBAAqB;GAC/D,KAAK,EAAE;GACR;EACF;;AAIH,SAAgB,EACd,GAC8C;AAC9C,QAAO,EAAwB,KAC5B,MACC,CAAC,GAAU,EAAU,QAAQ,MAAM,EAAE,aAAa,EAAS,CAAC,CAI/D,CAAC,QAAQ,GAAG,OAAW,EAAM,SAAS,EAAE"}
1
+ {"version":3,"file":"sessionShortcuts.js","names":[],"sources":["../../../src/components/sessions/sessionShortcuts.ts"],"sourcesContent":["/**\n * Single source of truth for the Sessions page keyboard shortcuts.\n *\n * The same registry drives (a) the keydown handler, (b) the cheatsheet\n * overlay, and (c) the command palette — so a shortcut and its help text\n * can never drift apart. Each entry carries a display string (platform\n * neutral) plus the action to run.\n */\nexport type ShortcutCategory = 'Tabs' | 'View' | 'Navigation' | 'Actions' | 'Help';\n\nexport const SHORTCUT_CATEGORY_ORDER: ShortcutCategory[] = [\n 'Actions',\n 'Tabs',\n 'View',\n 'Navigation',\n 'Help',\n];\n\nexport interface SessionShortcut {\n id: string;\n /** Platform-neutral key display, e.g. \"⌘/Ctrl + T\". */\n keysDisplay: string;\n description: string;\n category: ShortcutCategory;\n /** Action to run. Display-only entries (e.g. tab digits) omit it. */\n run?: () => void;\n}\n\nexport interface SessionShortcutHandlers {\n newSession: () => void;\n newSessionWithOptions: () => void;\n renameActiveTab: () => void;\n openActiveInNewTab: () => void;\n closeActiveTab: () => void;\n openPalette: () => void;\n openCheatsheet: () => void;\n focusSearch: () => void;\n cycleView: () => void;\n groupByNone: () => void;\n groupByAgent: () => void;\n groupByTarget: () => void;\n groupByVibe: () => void;\n groupByStatus: () => void;\n reconnectActive: () => void;\n toggleFullscreen: () => void;\n toggleInfoPanel: () => void;\n openActiveAgent: () => void;\n openActiveTarget: () => void;\n}\n\ntype Tr = (key: string, fallback: string) => string;\n\n/**\n * Build the shortcut registry. Key-event → id matching lives in the page's\n * keydown handler; this provides the canonical id/description/action set.\n */\nexport function buildSessionShortcuts(h: SessionShortcutHandlers, tr: Tr): SessionShortcut[] {\n return [\n // Actions\n {\n id: 'new-session',\n keysDisplay: '⌘/Ctrl + T',\n category: 'Actions',\n description: tr('sessions.sc.newSession', 'New terminal (quick)'),\n run: h.newSession,\n },\n {\n id: 'new-session-options',\n keysDisplay: '⌘/Ctrl + Shift + T',\n category: 'Actions',\n description: tr('sessions.sc.newSessionOptions', 'New session with options…'),\n run: h.newSessionWithOptions,\n },\n {\n id: 'open-palette',\n keysDisplay: '⌘/Ctrl + K',\n category: 'Actions',\n description: tr('sessions.sc.palette', 'Command palette'),\n run: h.openPalette,\n },\n {\n id: 'reconnect-active',\n keysDisplay: '⌘/Ctrl + R',\n category: 'Actions',\n description: tr('sessions.sc.reconnect', 'Reconnect active terminal'),\n run: h.reconnectActive,\n },\n // Tabs\n {\n id: 'rename-tab',\n keysDisplay: 'F2',\n category: 'Tabs',\n description: tr('sessions.sc.renameTab', 'Rename active tab'),\n run: h.renameActiveTab,\n },\n {\n id: 'open-tab-new',\n keysDisplay: '⌘/Ctrl + O',\n category: 'Tabs',\n description: tr('sessions.sc.openTabNew', 'Open active tab in new browser tab'),\n run: h.openActiveInNewTab,\n },\n {\n id: 'close-tab',\n keysDisplay: '⌘/Ctrl + W',\n category: 'Tabs',\n description: tr('sessions.sc.closeTab', 'Close active tab'),\n run: h.closeActiveTab,\n },\n {\n id: 'next-tab',\n keysDisplay: 'Ctrl + Tab · ⌘/Ctrl + Shift + ]',\n category: 'Tabs',\n description: tr('sessions.sc.nextTab', 'Next tab'),\n },\n {\n id: 'prev-tab',\n keysDisplay: 'Ctrl + Shift + Tab · ⌘/Ctrl + Shift + [',\n category: 'Tabs',\n description: tr('sessions.sc.prevTab', 'Previous tab'),\n },\n {\n id: 'jump-tab',\n keysDisplay: '⌘/Ctrl + 1…9',\n category: 'Tabs',\n description: tr('sessions.sc.jumpTab', 'Jump to tab N'),\n },\n // View\n {\n id: 'focus-search',\n keysDisplay: '/',\n category: 'View',\n description: tr('sessions.sc.focusSearch', 'Focus search'),\n run: h.focusSearch,\n },\n {\n id: 'cycle-view',\n keysDisplay: '⌘/Ctrl + \\\\',\n category: 'View',\n description: tr('sessions.sc.cycleView', 'Cycle terminal / grid / list'),\n run: h.cycleView,\n },\n {\n id: 'group-agent',\n keysDisplay: 'g a',\n category: 'View',\n description: tr('sessions.sc.groupAgent', 'Group by agent'),\n run: h.groupByAgent,\n },\n {\n id: 'group-target',\n keysDisplay: 'g t',\n category: 'View',\n description: tr('sessions.sc.groupTarget', 'Group by target'),\n run: h.groupByTarget,\n },\n {\n id: 'group-vibe',\n keysDisplay: 'g v',\n category: 'View',\n description: tr('sessions.sc.groupVibe', 'Group by vibe'),\n run: h.groupByVibe,\n },\n {\n id: 'group-status',\n keysDisplay: 'g s',\n category: 'View',\n description: tr('sessions.sc.groupStatus', 'Group by status'),\n run: h.groupByStatus,\n },\n {\n id: 'group-none',\n keysDisplay: 'g 0',\n category: 'View',\n description: tr('sessions.sc.groupNone', 'Clear grouping'),\n run: h.groupByNone,\n },\n {\n id: 'toggle-fullscreen',\n keysDisplay: 'f',\n category: 'View',\n description: tr('sessions.sc.fullscreen', 'Toggle terminal fullscreen'),\n run: h.toggleFullscreen,\n },\n {\n id: 'toggle-info',\n keysDisplay: 'i',\n category: 'View',\n description: tr('sessions.sc.infoPanel', 'Toggle session info panel'),\n run: h.toggleInfoPanel,\n },\n // Navigation\n {\n id: 'open-agent',\n keysDisplay: 'g shift+a',\n category: 'Navigation',\n description: tr('sessions.sc.openAgent', \"Open active session's agent\"),\n run: h.openActiveAgent,\n },\n {\n id: 'open-target',\n keysDisplay: 'g shift+t',\n category: 'Navigation',\n description: tr('sessions.sc.openTarget', \"Open active session's target\"),\n run: h.openActiveTarget,\n },\n // Help\n {\n id: 'terminal-select-copy',\n keysDisplay: '⇧ Shift + drag',\n category: 'Help',\n description: tr(\n 'sessions.sc.selectCopy',\n 'Select & copy text in the terminal (iTerm2-style, even in tmux)'\n ),\n },\n {\n id: 'open-cheatsheet',\n keysDisplay: '?',\n category: 'Help',\n description: tr('sessions.sc.cheatsheet', 'Keyboard shortcuts'),\n run: h.openCheatsheet,\n },\n ];\n}\n\n/** Group a registry into ordered [category, shortcuts] pairs for display. */\nexport function groupShortcutsByCategory(\n shortcuts: SessionShortcut[]\n): Array<[ShortcutCategory, SessionShortcut[]]> {\n return SHORTCUT_CATEGORY_ORDER.map(\n (category) =>\n [category, shortcuts.filter((s) => s.category === category)] as [\n ShortcutCategory,\n SessionShortcut[],\n ]\n ).filter(([, items]) => items.length > 0);\n}\n"],"mappings":"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"}
@@ -181,53 +181,60 @@ function T({ notification: e, onDismiss: t, autoDismiss: n = !0, autoDismissDela
181
181
  ]
182
182
  });
183
183
  }
184
- var E = 50;
185
- function D({ maxVisible: o = 3, autoDismiss: s = !1, autoDismissDelay: c = 8e3, showConnectionStatus: l = !0 }) {
186
- let u = e(), { notifications: d, connectionStatus: f, reconnect: p, agentId: m } = t(), [h, g] = a([]), b = i(/* @__PURE__ */ new Set());
184
+ function E() {
185
+ let { connectionStatus: e, reconnect: n, agentId: r } = t();
186
+ return r ? /* @__PURE__ */ _(y, {
187
+ status: e,
188
+ onReconnect: n
189
+ }) : null;
190
+ }
191
+ var D = 50;
192
+ function O({ maxVisible: o = 3, autoDismiss: s = !1, autoDismissDelay: c = 8e3, showConnectionStatus: l = !0, compact: u = !1 }) {
193
+ let d = e(), { notifications: f, connectionStatus: p, reconnect: m, agentId: h } = t(), [g, b] = a([]), x = i(/* @__PURE__ */ new Set());
187
194
  r(() => {
188
195
  let e = [];
189
- for (let t of d) b.current.has(t.id) || (b.current.add(t.id), e.push(t));
190
- e.length > 0 && g((t) => [...e, ...t].slice(0, E));
191
- }, [d]);
192
- let x = n((e) => {
193
- g((t) => t.filter((t) => t.id !== e));
194
- }, []), S = h.slice(0, o), C = Math.max(0, h.length - o);
195
- return !m && h.length === 0 ? null : /* @__PURE__ */ v("div", {
196
- className: "mb-4 space-y-3",
196
+ for (let t of f) x.current.has(t.id) || (x.current.add(t.id), e.push(t));
197
+ e.length > 0 && b((t) => [...e, ...t].slice(0, D));
198
+ }, [f]);
199
+ let S = n((e) => {
200
+ b((t) => t.filter((t) => t.id !== e));
201
+ }, []), C = g.slice(0, o), w = Math.max(0, g.length - o);
202
+ return !h && g.length === 0 || u && g.length === 0 ? null : /* @__PURE__ */ v("div", {
203
+ className: u ? "space-y-2" : "mb-4 space-y-3",
197
204
  "data-testid": "session-notifications-container",
198
205
  children: [
199
- l && m && /* @__PURE__ */ v("div", {
206
+ !u && l && h && /* @__PURE__ */ v("div", {
200
207
  className: "flex items-center justify-between",
201
208
  children: [/* @__PURE__ */ _("div", {
202
209
  className: "text-xs text-text-muted",
203
- children: u("vibecontrols.shared.sessionNotificationBanner.agentNotifications", "Agent Notifications")
210
+ children: d("vibecontrols.shared.sessionNotificationBanner.agentNotifications", "Agent Notifications")
204
211
  }), /* @__PURE__ */ _(y, {
205
- status: f,
206
- onReconnect: p
212
+ status: p,
213
+ onReconnect: m
207
214
  })]
208
215
  }),
209
216
  /* @__PURE__ */ _("div", {
210
217
  className: "space-y-2",
211
- children: S.map((e) => /* @__PURE__ */ _(T, {
218
+ children: C.map((e) => /* @__PURE__ */ _(T, {
212
219
  notification: e,
213
- onDismiss: x,
220
+ onDismiss: S,
214
221
  autoDismiss: s,
215
222
  autoDismissDelay: c
216
223
  }, e.id))
217
224
  }),
218
- C > 0 && /* @__PURE__ */ v("div", {
225
+ w > 0 && /* @__PURE__ */ v("div", {
219
226
  className: "text-xs text-text-muted text-center py-1",
220
227
  children: [
221
228
  "+",
222
- C,
229
+ w,
223
230
  " ",
224
- u("vibecontrols.shared.sessionNotificationBanner.moreNotifications", "more notification(s)")
231
+ d("vibecontrols.shared.sessionNotificationBanner.moreNotifications", "more notification(s)")
225
232
  ]
226
233
  })
227
234
  ]
228
235
  });
229
236
  }
230
237
  //#endregion
231
- export { y as ConnectionStatusIndicator, D as default };
238
+ export { y as ConnectionStatusIndicator, E as SessionConnectionStatus, O as default };
232
239
 
233
240
  //# sourceMappingURL=SessionNotificationBanner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SessionNotificationBanner.js","names":[],"sources":["../../../src/components/shared/SessionNotificationBanner.tsx"],"sourcesContent":["/**\n * SessionNotificationBanner\n *\n * Displays AI tool notifications contextually within the Sessions page,\n * positioned above the session list for immediate visibility.\n *\n * Features:\n * - Source-specific styling (OpenCode, Claude Code, Gemini, Cursor)\n * - Event-type icons (success, error, info)\n * - Separate notification state from global toasts (no auto-dismiss interference)\n * - Manual dismiss button\n * - Smooth enter/exit animations\n *\n * Architecture:\n * - SessionNotifications maintains its OWN notification state separate from the global toast system\n * - It subscribes to the same WebSocket events but stores notifications independently\n * - This allows global toasts to auto-dismiss without affecting session banners\n */\nimport { useEffect, useState, useCallback, useRef } from 'react';\nimport {\n Bell,\n CheckCircle,\n AlertCircle,\n Info,\n X,\n Sparkles,\n Terminal,\n Zap,\n Wifi,\n WifiOff,\n} from 'lucide-react';\n\nimport type { AiToolNotification, ConnectionStatus } from '../../hooks/useAgentEvents';\nimport { useAgentEventsContext } from '../../providers/AgentEventsProvider';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Props for the connection status indicator\n */\ninterface ConnectionStatusIndicatorProps {\n status: ConnectionStatus;\n onReconnect: () => void;\n}\n\n/**\n * Connection status indicator showing WebSocket state\n */\nexport function ConnectionStatusIndicator({ status, onReconnect }: ConnectionStatusIndicatorProps) {\n const tr = useTr();\n\n if (status === 'disconnected') {\n return null;\n }\n\n return (\n <div className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-secondary border border-border-primary text-sm\">\n {status === 'connected' && (\n <>\n <Wifi className=\"size-4 text-status-success-text\" />\n <span className=\"text-status-success-text font-medium\">\n {tr('vibecontrols.shared.sessionNotificationBanner.live', 'Live')}\n </span>\n </>\n )}\n {status === 'connecting' && (\n <>\n <Wifi className=\"size-4 text-status-warning-text animate-pulse\" />\n <span className=\"text-status-warning-text\">\n {tr('vibecontrols.shared.sessionNotificationBanner.connecting', 'Connecting...')}\n </span>\n </>\n )}\n {status === 'error' && (\n <>\n <WifiOff className=\"size-4 text-status-error-text\" />\n <span className=\"text-status-error-text\">\n {tr('vibecontrols.shared.sessionNotificationBanner.disconnected', 'Disconnected')}\n </span>\n <button\n type=\"button\"\n onClick={onReconnect}\n className=\"ml-2 px-2 py-0.5 text-xs rounded bg-accent-primary/10 text-accent-primary hover:bg-accent-primary/20 transition-colors\"\n >\n {tr('vibecontrols.shared.sessionNotificationBanner.reconnect', 'Reconnect')}\n </button>\n </>\n )}\n </div>\n );\n}\n\n/**\n * Props for a single notification banner\n */\ninterface NotificationBannerProps {\n notification: AiToolNotification;\n onDismiss: (id: string) => void;\n autoDismiss?: boolean;\n autoDismissDelay?: number;\n}\n\n/**\n * Get the icon for a source tool\n */\nfunction getSourceIcon(source: string) {\n const iconClass = 'size-5';\n\n switch (source.toLowerCase()) {\n case 'opencode':\n return <Terminal className={iconClass} />;\n case 'claude-code':\n case 'claudecode':\n return <Sparkles className={iconClass} />;\n case 'gemini-cli':\n case 'gemini':\n return <Zap className={iconClass} />;\n case 'cursor':\n return <Terminal className={iconClass} />;\n default:\n return <Bell className={iconClass} />;\n }\n}\n\n/**\n * Get the icon for an event type\n */\nfunction getEventIcon(eventType: string) {\n const iconClass = 'size-4';\n\n if (\n eventType.includes('completed') ||\n eventType.includes('success') ||\n eventType.includes('idle')\n ) {\n return <CheckCircle className={`${iconClass} text-status-success-text`} />;\n }\n if (eventType.includes('failed') || eventType.includes('error')) {\n return <AlertCircle className={`${iconClass} text-status-error-text`} />;\n }\n if (eventType.includes('question') || eventType.includes('permission')) {\n return <AlertCircle className={`${iconClass} text-status-warning-text`} />;\n }\n return <Info className={`${iconClass} text-status-info-text`} />;\n}\n\n/**\n * Get display name for a source tool\n */\nfunction getSourceDisplayName(source: string): string {\n const names: Record<string, string> = {\n opencode: 'OpenCode',\n 'opencode-vibe-webhook': 'OpenCode',\n 'claude-code': 'Claude Code',\n claudecode: 'Claude Code',\n 'gemini-cli': 'Gemini CLI',\n gemini: 'Gemini CLI',\n cursor: 'Cursor',\n };\n return names[source.toLowerCase()] || source;\n}\n\n/**\n * Get background color based on source\n */\nfunction getSourceStyles(source: string): { bg: string; border: string; text: string } {\n switch (source.toLowerCase()) {\n case 'opencode':\n case 'opencode-vibe-webhook':\n return {\n bg: 'bg-status-info-bg/10',\n border: 'border-status-info-border',\n text: 'text-status-info-text',\n };\n case 'claude-code':\n case 'claudecode':\n return {\n bg: 'bg-status-warning-bg/10',\n border: 'border-status-warning-border',\n text: 'text-status-warning-text',\n };\n case 'gemini-cli':\n case 'gemini':\n return {\n bg: 'bg-status-info-bg/10',\n border: 'border-status-info-border',\n text: 'text-status-info-text',\n };\n case 'cursor':\n return {\n bg: 'bg-status-success-bg/10',\n border: 'border-status-success-border',\n text: 'text-status-success-text',\n };\n default:\n return {\n bg: 'bg-bg-secondary',\n border: 'border-border-primary',\n text: 'text-text-secondary',\n };\n }\n}\n\n/**\n * Format timestamp for display\n */\nfunction formatTimestamp(timestamp: string): string {\n try {\n const date = new Date(timestamp);\n return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n } catch {\n return '';\n }\n}\n\n/**\n * Single notification banner\n */\nfunction NotificationBanner({\n notification,\n onDismiss,\n autoDismiss = true,\n autoDismissDelay = 8000,\n}: NotificationBannerProps) {\n const [isExiting, setIsExiting] = useState(false);\n const [progress, setProgress] = useState(100);\n const styles = getSourceStyles(notification.source);\n\n useEffect(() => {\n if (!autoDismiss) return;\n\n // Progress countdown\n const startTime = Date.now();\n const interval = setInterval(() => {\n const elapsed = Date.now() - startTime;\n const remaining = Math.max(0, 100 - (elapsed / autoDismissDelay) * 100);\n setProgress(remaining);\n }, 50);\n\n // Auto-dismiss timer\n const timer = setTimeout(() => {\n setIsExiting(true);\n setTimeout(() => onDismiss(notification.id), 300);\n }, autoDismissDelay);\n\n return () => {\n clearInterval(interval);\n clearTimeout(timer);\n };\n }, [autoDismiss, autoDismissDelay, notification.id, onDismiss]);\n\n const handleDismiss = () => {\n setIsExiting(true);\n setTimeout(() => onDismiss(notification.id), 300);\n };\n\n return (\n <div\n className={`\n relative flex items-start gap-3 p-4 rounded-lg border shadow-lg backdrop-blur-sm\n ${styles.bg} ${styles.border}\n transform transition-all duration-300 ease-out\n ${isExiting ? 'opacity-0 -translate-y-2 scale-95' : 'opacity-100 translate-y-0 scale-100'}\n animate-slide-in-down\n `}\n role=\"alert\"\n data-testid=\"session-notification-banner\"\n >\n {/* Source icon */}\n <div className={`flex-shrink-0 p-2 rounded-lg bg-bg-tertiary/50 ${styles.text}`}>\n {getSourceIcon(notification.source)}\n </div>\n\n {/* Content */}\n <div className=\"flex-1 min-w-0\">\n {/* Header */}\n <div className=\"flex items-center gap-2 mb-1\">\n <span className={`text-xs font-semibold uppercase tracking-wide ${styles.text}`}>\n {getSourceDisplayName(notification.source)}\n </span>\n {getEventIcon(notification.eventType)}\n <span className=\"text-xs text-text-muted\">{formatTimestamp(notification.timestamp)}</span>\n </div>\n\n {/* Title */}\n <p className=\"text-sm font-medium text-text-primary\">{notification.title}</p>\n\n {/* Message */}\n <p className=\"text-xs text-text-secondary mt-0.5\">{notification.message}</p>\n </div>\n\n {/* Dismiss button */}\n <button\n type=\"button\"\n onClick={handleDismiss}\n className=\"flex-shrink-0 p-1 rounded-md hover:bg-bg-tertiary/50 text-text-muted hover:text-text-secondary transition-colors\"\n aria-label=\"Dismiss notification\"\n >\n <X className=\"size-4\" />\n </button>\n\n {/* Progress bar for auto-dismiss */}\n {autoDismiss && (\n <div className=\"absolute bottom-0 left-0 right-0 h-0.5 bg-bg-tertiary/30 overflow-hidden rounded-b-lg\">\n <div\n className={`h-full ${styles.text.replace('text-', 'bg-')}/50 transition-all duration-50 ease-linear`}\n style={{ width: `${progress}%` }}\n />\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Props for the session notifications container\n */\nexport interface SessionNotificationsProps {\n /**\n * Maximum number of notifications to display\n */\n maxVisible?: number;\n\n /**\n * Whether notifications should auto-dismiss (default: false - requires manual close)\n */\n autoDismiss?: boolean;\n\n /**\n * Auto-dismiss delay in milliseconds (only used if autoDismiss is true)\n */\n autoDismissDelay?: number;\n\n /**\n * Whether to show the connection status indicator\n */\n showConnectionStatus?: boolean;\n}\n\n/**\n * Maximum notifications to keep in session banner state\n */\nconst MAX_SESSION_NOTIFICATIONS = 50;\n\n/**\n * Container component for session-contextual notifications\n * Displays notifications above the session list with connection status\n *\n * IMPORTANT: This component maintains its OWN notification state separate from the global\n * toast system. It subscribes to the same WebSocket events via the context but stores\n * notifications independently. This allows:\n * - Global toasts to auto-dismiss without affecting session banners\n * - Session banners to persist until manually dismissed by the user\n */\nexport function SessionNotifications({\n maxVisible = 3,\n autoDismiss = false,\n autoDismissDelay = 8000,\n showConnectionStatus = true,\n}: SessionNotificationsProps) {\n const tr = useTr();\n const {\n notifications: globalNotifications,\n connectionStatus,\n reconnect,\n agentId,\n } = useAgentEventsContext();\n\n // Maintain separate notification state for session banners\n const [sessionNotifications, setSessionNotifications] = useState<AiToolNotification[]>([]);\n const seenIdsRef = useRef<Set<string>>(new Set());\n\n // Sync new notifications from global state to session state\n // We only ADD new notifications, never remove them (except via manual dismiss)\n useEffect(() => {\n const newNotifications: AiToolNotification[] = [];\n\n for (const notification of globalNotifications) {\n if (!seenIdsRef.current.has(notification.id)) {\n seenIdsRef.current.add(notification.id);\n newNotifications.push(notification);\n }\n }\n\n if (newNotifications.length > 0) {\n setSessionNotifications((prev) => {\n const updated = [...newNotifications, ...prev];\n return updated.slice(0, MAX_SESSION_NOTIFICATIONS);\n });\n }\n }, [globalNotifications]);\n\n // Handle manual dismiss - only affects session banner state\n const handleDismiss = useCallback((id: string) => {\n setSessionNotifications((prev) => prev.filter((n) => n.id !== id));\n }, []);\n\n const visibleNotifications = sessionNotifications.slice(0, maxVisible);\n const hiddenCount = Math.max(0, sessionNotifications.length - maxVisible);\n\n // Don't render anything if no agent is configured\n if (!agentId && sessionNotifications.length === 0) {\n return null;\n }\n\n return (\n <div className=\"mb-4 space-y-3\" data-testid=\"session-notifications-container\">\n {/* Connection status indicator */}\n {showConnectionStatus && agentId && (\n <div className=\"flex items-center justify-between\">\n <div className=\"text-xs text-text-muted\">\n {tr(\n 'vibecontrols.shared.sessionNotificationBanner.agentNotifications',\n 'Agent Notifications'\n )}\n </div>\n <ConnectionStatusIndicator status={connectionStatus} onReconnect={reconnect} />\n </div>\n )}\n\n {/* Notification banners */}\n <div className=\"space-y-2\">\n {visibleNotifications.map((notification) => (\n <NotificationBanner\n key={notification.id}\n notification={notification}\n onDismiss={handleDismiss}\n autoDismiss={autoDismiss}\n autoDismissDelay={autoDismissDelay}\n />\n ))}\n </div>\n\n {/* Hidden count indicator */}\n {hiddenCount > 0 && (\n <div className=\"text-xs text-text-muted text-center py-1\">\n +{hiddenCount}{' '}\n {tr(\n 'vibecontrols.shared.sessionNotificationBanner.moreNotifications',\n 'more notification(s)'\n )}\n </div>\n )}\n </div>\n );\n}\n\nexport default SessionNotifications;\n"],"mappings":"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"}
1
+ {"version":3,"file":"SessionNotificationBanner.js","names":[],"sources":["../../../src/components/shared/SessionNotificationBanner.tsx"],"sourcesContent":["/**\n * SessionNotificationBanner\n *\n * Displays AI tool notifications contextually within the Sessions page,\n * positioned above the session list for immediate visibility.\n *\n * Features:\n * - Source-specific styling (OpenCode, Claude Code, Gemini, Cursor)\n * - Event-type icons (success, error, info)\n * - Separate notification state from global toasts (no auto-dismiss interference)\n * - Manual dismiss button\n * - Smooth enter/exit animations\n *\n * Architecture:\n * - SessionNotifications maintains its OWN notification state separate from the global toast system\n * - It subscribes to the same WebSocket events but stores notifications independently\n * - This allows global toasts to auto-dismiss without affecting session banners\n */\nimport { useEffect, useState, useCallback, useRef } from 'react';\nimport {\n Bell,\n CheckCircle,\n AlertCircle,\n Info,\n X,\n Sparkles,\n Terminal,\n Zap,\n Wifi,\n WifiOff,\n} from 'lucide-react';\n\nimport type { AiToolNotification, ConnectionStatus } from '../../hooks/useAgentEvents';\nimport { useAgentEventsContext } from '../../providers/AgentEventsProvider';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Props for the connection status indicator\n */\ninterface ConnectionStatusIndicatorProps {\n status: ConnectionStatus;\n onReconnect: () => void;\n}\n\n/**\n * Connection status indicator showing WebSocket state\n */\nexport function ConnectionStatusIndicator({ status, onReconnect }: ConnectionStatusIndicatorProps) {\n const tr = useTr();\n\n if (status === 'disconnected') {\n return null;\n }\n\n return (\n <div className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-secondary border border-border-primary text-sm\">\n {status === 'connected' && (\n <>\n <Wifi className=\"size-4 text-status-success-text\" />\n <span className=\"text-status-success-text font-medium\">\n {tr('vibecontrols.shared.sessionNotificationBanner.live', 'Live')}\n </span>\n </>\n )}\n {status === 'connecting' && (\n <>\n <Wifi className=\"size-4 text-status-warning-text animate-pulse\" />\n <span className=\"text-status-warning-text\">\n {tr('vibecontrols.shared.sessionNotificationBanner.connecting', 'Connecting...')}\n </span>\n </>\n )}\n {status === 'error' && (\n <>\n <WifiOff className=\"size-4 text-status-error-text\" />\n <span className=\"text-status-error-text\">\n {tr('vibecontrols.shared.sessionNotificationBanner.disconnected', 'Disconnected')}\n </span>\n <button\n type=\"button\"\n onClick={onReconnect}\n className=\"ml-2 px-2 py-0.5 text-xs rounded bg-accent-primary/10 text-accent-primary hover:bg-accent-primary/20 transition-colors\"\n >\n {tr('vibecontrols.shared.sessionNotificationBanner.reconnect', 'Reconnect')}\n </button>\n </>\n )}\n </div>\n );\n}\n\n/**\n * Props for a single notification banner\n */\ninterface NotificationBannerProps {\n notification: AiToolNotification;\n onDismiss: (id: string) => void;\n autoDismiss?: boolean;\n autoDismissDelay?: number;\n}\n\n/**\n * Get the icon for a source tool\n */\nfunction getSourceIcon(source: string) {\n const iconClass = 'size-5';\n\n switch (source.toLowerCase()) {\n case 'opencode':\n return <Terminal className={iconClass} />;\n case 'claude-code':\n case 'claudecode':\n return <Sparkles className={iconClass} />;\n case 'gemini-cli':\n case 'gemini':\n return <Zap className={iconClass} />;\n case 'cursor':\n return <Terminal className={iconClass} />;\n default:\n return <Bell className={iconClass} />;\n }\n}\n\n/**\n * Get the icon for an event type\n */\nfunction getEventIcon(eventType: string) {\n const iconClass = 'size-4';\n\n if (\n eventType.includes('completed') ||\n eventType.includes('success') ||\n eventType.includes('idle')\n ) {\n return <CheckCircle className={`${iconClass} text-status-success-text`} />;\n }\n if (eventType.includes('failed') || eventType.includes('error')) {\n return <AlertCircle className={`${iconClass} text-status-error-text`} />;\n }\n if (eventType.includes('question') || eventType.includes('permission')) {\n return <AlertCircle className={`${iconClass} text-status-warning-text`} />;\n }\n return <Info className={`${iconClass} text-status-info-text`} />;\n}\n\n/**\n * Get display name for a source tool\n */\nfunction getSourceDisplayName(source: string): string {\n const names: Record<string, string> = {\n opencode: 'OpenCode',\n 'opencode-vibe-webhook': 'OpenCode',\n 'claude-code': 'Claude Code',\n claudecode: 'Claude Code',\n 'gemini-cli': 'Gemini CLI',\n gemini: 'Gemini CLI',\n cursor: 'Cursor',\n };\n return names[source.toLowerCase()] || source;\n}\n\n/**\n * Get background color based on source\n */\nfunction getSourceStyles(source: string): { bg: string; border: string; text: string } {\n switch (source.toLowerCase()) {\n case 'opencode':\n case 'opencode-vibe-webhook':\n return {\n bg: 'bg-status-info-bg/10',\n border: 'border-status-info-border',\n text: 'text-status-info-text',\n };\n case 'claude-code':\n case 'claudecode':\n return {\n bg: 'bg-status-warning-bg/10',\n border: 'border-status-warning-border',\n text: 'text-status-warning-text',\n };\n case 'gemini-cli':\n case 'gemini':\n return {\n bg: 'bg-status-info-bg/10',\n border: 'border-status-info-border',\n text: 'text-status-info-text',\n };\n case 'cursor':\n return {\n bg: 'bg-status-success-bg/10',\n border: 'border-status-success-border',\n text: 'text-status-success-text',\n };\n default:\n return {\n bg: 'bg-bg-secondary',\n border: 'border-border-primary',\n text: 'text-text-secondary',\n };\n }\n}\n\n/**\n * Format timestamp for display\n */\nfunction formatTimestamp(timestamp: string): string {\n try {\n const date = new Date(timestamp);\n return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n } catch {\n return '';\n }\n}\n\n/**\n * Single notification banner\n */\nfunction NotificationBanner({\n notification,\n onDismiss,\n autoDismiss = true,\n autoDismissDelay = 8000,\n}: NotificationBannerProps) {\n const [isExiting, setIsExiting] = useState(false);\n const [progress, setProgress] = useState(100);\n const styles = getSourceStyles(notification.source);\n\n useEffect(() => {\n if (!autoDismiss) return;\n\n // Progress countdown\n const startTime = Date.now();\n const interval = setInterval(() => {\n const elapsed = Date.now() - startTime;\n const remaining = Math.max(0, 100 - (elapsed / autoDismissDelay) * 100);\n setProgress(remaining);\n }, 50);\n\n // Auto-dismiss timer\n const timer = setTimeout(() => {\n setIsExiting(true);\n setTimeout(() => onDismiss(notification.id), 300);\n }, autoDismissDelay);\n\n return () => {\n clearInterval(interval);\n clearTimeout(timer);\n };\n }, [autoDismiss, autoDismissDelay, notification.id, onDismiss]);\n\n const handleDismiss = () => {\n setIsExiting(true);\n setTimeout(() => onDismiss(notification.id), 300);\n };\n\n return (\n <div\n className={`\n relative flex items-start gap-3 p-4 rounded-lg border shadow-lg backdrop-blur-sm\n ${styles.bg} ${styles.border}\n transform transition-all duration-300 ease-out\n ${isExiting ? 'opacity-0 -translate-y-2 scale-95' : 'opacity-100 translate-y-0 scale-100'}\n animate-slide-in-down\n `}\n role=\"alert\"\n data-testid=\"session-notification-banner\"\n >\n {/* Source icon */}\n <div className={`flex-shrink-0 p-2 rounded-lg bg-bg-tertiary/50 ${styles.text}`}>\n {getSourceIcon(notification.source)}\n </div>\n\n {/* Content */}\n <div className=\"flex-1 min-w-0\">\n {/* Header */}\n <div className=\"flex items-center gap-2 mb-1\">\n <span className={`text-xs font-semibold uppercase tracking-wide ${styles.text}`}>\n {getSourceDisplayName(notification.source)}\n </span>\n {getEventIcon(notification.eventType)}\n <span className=\"text-xs text-text-muted\">{formatTimestamp(notification.timestamp)}</span>\n </div>\n\n {/* Title */}\n <p className=\"text-sm font-medium text-text-primary\">{notification.title}</p>\n\n {/* Message */}\n <p className=\"text-xs text-text-secondary mt-0.5\">{notification.message}</p>\n </div>\n\n {/* Dismiss button */}\n <button\n type=\"button\"\n onClick={handleDismiss}\n className=\"flex-shrink-0 p-1 rounded-md hover:bg-bg-tertiary/50 text-text-muted hover:text-text-secondary transition-colors\"\n aria-label=\"Dismiss notification\"\n >\n <X className=\"size-4\" />\n </button>\n\n {/* Progress bar for auto-dismiss */}\n {autoDismiss && (\n <div className=\"absolute bottom-0 left-0 right-0 h-0.5 bg-bg-tertiary/30 overflow-hidden rounded-b-lg\">\n <div\n className={`h-full ${styles.text.replace('text-', 'bg-')}/50 transition-all duration-50 ease-linear`}\n style={{ width: `${progress}%` }}\n />\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Props for the session notifications container\n */\nexport interface SessionNotificationsProps {\n /**\n * Maximum number of notifications to display\n */\n maxVisible?: number;\n\n /**\n * Whether notifications should auto-dismiss (default: false - requires manual close)\n */\n autoDismiss?: boolean;\n\n /**\n * Auto-dismiss delay in milliseconds (only used if autoDismiss is true)\n */\n autoDismissDelay?: number;\n\n /**\n * Whether to show the connection status indicator\n */\n showConnectionStatus?: boolean;\n\n /**\n * Compact mode: drop the standalone \"Agent Notifications\" header row and\n * render nothing at all when there are no notifications, so the banner\n * stops reserving vertical space. The connection status is expected to be\n * rendered in the page header instead (see `SessionConnectionStatus`).\n */\n compact?: boolean;\n}\n\n/**\n * Standalone agent connection-status chip for placement in a page header.\n * Reclaims the row the notification banner used to spend on a dedicated\n * \"Agent Notifications\" + status line. Renders nothing when no agent is\n * configured for the current context.\n */\nexport function SessionConnectionStatus() {\n const { connectionStatus, reconnect, agentId } = useAgentEventsContext();\n if (!agentId) return null;\n return <ConnectionStatusIndicator status={connectionStatus} onReconnect={reconnect} />;\n}\n\n/**\n * Maximum notifications to keep in session banner state\n */\nconst MAX_SESSION_NOTIFICATIONS = 50;\n\n/**\n * Container component for session-contextual notifications\n * Displays notifications above the session list with connection status\n *\n * IMPORTANT: This component maintains its OWN notification state separate from the global\n * toast system. It subscribes to the same WebSocket events via the context but stores\n * notifications independently. This allows:\n * - Global toasts to auto-dismiss without affecting session banners\n * - Session banners to persist until manually dismissed by the user\n */\nexport function SessionNotifications({\n maxVisible = 3,\n autoDismiss = false,\n autoDismissDelay = 8000,\n showConnectionStatus = true,\n compact = false,\n}: SessionNotificationsProps) {\n const tr = useTr();\n const {\n notifications: globalNotifications,\n connectionStatus,\n reconnect,\n agentId,\n } = useAgentEventsContext();\n\n // Maintain separate notification state for session banners\n const [sessionNotifications, setSessionNotifications] = useState<AiToolNotification[]>([]);\n const seenIdsRef = useRef<Set<string>>(new Set());\n\n // Sync new notifications from global state to session state\n // We only ADD new notifications, never remove them (except via manual dismiss)\n useEffect(() => {\n const newNotifications: AiToolNotification[] = [];\n\n for (const notification of globalNotifications) {\n if (!seenIdsRef.current.has(notification.id)) {\n seenIdsRef.current.add(notification.id);\n newNotifications.push(notification);\n }\n }\n\n if (newNotifications.length > 0) {\n setSessionNotifications((prev) => {\n const updated = [...newNotifications, ...prev];\n return updated.slice(0, MAX_SESSION_NOTIFICATIONS);\n });\n }\n }, [globalNotifications]);\n\n // Handle manual dismiss - only affects session banner state\n const handleDismiss = useCallback((id: string) => {\n setSessionNotifications((prev) => prev.filter((n) => n.id !== id));\n }, []);\n\n const visibleNotifications = sessionNotifications.slice(0, maxVisible);\n const hiddenCount = Math.max(0, sessionNotifications.length - maxVisible);\n\n // Don't render anything if no agent is configured. In compact mode we also\n // render nothing while there are no notifications so the banner stops\n // reserving a wasted row (the connection status lives in the page header).\n if (!agentId && sessionNotifications.length === 0) {\n return null;\n }\n if (compact && sessionNotifications.length === 0) {\n return null;\n }\n\n return (\n <div\n className={compact ? 'space-y-2' : 'mb-4 space-y-3'}\n data-testid=\"session-notifications-container\"\n >\n {/* Connection status indicator — header row only in full mode. In\n compact mode the status is rendered in the page header instead. */}\n {!compact && showConnectionStatus && agentId && (\n <div className=\"flex items-center justify-between\">\n <div className=\"text-xs text-text-muted\">\n {tr(\n 'vibecontrols.shared.sessionNotificationBanner.agentNotifications',\n 'Agent Notifications'\n )}\n </div>\n <ConnectionStatusIndicator status={connectionStatus} onReconnect={reconnect} />\n </div>\n )}\n\n {/* Notification banners */}\n <div className=\"space-y-2\">\n {visibleNotifications.map((notification) => (\n <NotificationBanner\n key={notification.id}\n notification={notification}\n onDismiss={handleDismiss}\n autoDismiss={autoDismiss}\n autoDismissDelay={autoDismissDelay}\n />\n ))}\n </div>\n\n {/* Hidden count indicator */}\n {hiddenCount > 0 && (\n <div className=\"text-xs text-text-muted text-center py-1\">\n +{hiddenCount}{' '}\n {tr(\n 'vibecontrols.shared.sessionNotificationBanner.moreNotifications',\n 'more notification(s)'\n )}\n </div>\n )}\n </div>\n );\n}\n\nexport default SessionNotifications;\n"],"mappings":"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"}