@burdenoff/microfe-vibecontrols 2026.601.5 → 2026.601.7

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.
Files changed (45) hide show
  1. package/dist/components/sessions/EntityInfoPopover.js +183 -0
  2. package/dist/components/sessions/EntityInfoPopover.js.map +1 -0
  3. package/dist/components/sessions/SessionCard.js +80 -72
  4. package/dist/components/sessions/SessionCard.js.map +1 -1
  5. package/dist/components/sessions/SessionGroup.js +49 -0
  6. package/dist/components/sessions/SessionGroup.js.map +1 -0
  7. package/dist/components/sessions/SessionInfoPanel.js +128 -0
  8. package/dist/components/sessions/SessionInfoPanel.js.map +1 -0
  9. package/dist/components/sessions/SessionList.js +109 -96
  10. package/dist/components/sessions/SessionList.js.map +1 -1
  11. package/dist/components/sessions/SessionsCommandPalette.js +36 -0
  12. package/dist/components/sessions/SessionsCommandPalette.js.map +1 -0
  13. package/dist/components/sessions/ShortcutCheatsheet.js +73 -0
  14. package/dist/components/sessions/ShortcutCheatsheet.js.map +1 -0
  15. package/dist/components/sessions/TerminalPanel.js +640 -537
  16. package/dist/components/sessions/TerminalPanel.js.map +1 -1
  17. package/dist/components/sessions/groupSessions.js +46 -0
  18. package/dist/components/sessions/groupSessions.js.map +1 -0
  19. package/dist/components/sessions/index.js +7 -0
  20. package/dist/components/sessions/sessionShortcuts.js +149 -0
  21. package/dist/components/sessions/sessionShortcuts.js.map +1 -0
  22. package/dist/components/shared/FilterDropdown.js +14 -13
  23. package/dist/components/shared/FilterDropdown.js.map +1 -1
  24. package/dist/components/shared/LinkToVibePanel.js.map +1 -1
  25. package/dist/components/vibe/VibeLinkedEntitiesPanel.js.map +1 -1
  26. package/dist/generated/wspace-operations.js +21 -0
  27. package/dist/generated/wspace-operations.js.map +1 -1
  28. package/dist/node_modules/@apollo/client/utilities/internal/getMainDefinition.js +17 -0
  29. package/dist/node_modules/@apollo/client/utilities/internal/getMainDefinition.js.map +1 -0
  30. package/dist/pages/AgentGraphPage.js +65 -65
  31. package/dist/pages/AgentGraphPage.js.map +1 -1
  32. package/dist/pages/SessionsPage.js +1189 -920
  33. package/dist/pages/SessionsPage.js.map +1 -1
  34. package/dist/pages/TargetsPage.js +105 -92
  35. package/dist/pages/TargetsPage.js.map +1 -1
  36. package/dist/pages/VibeCalendarPage.js +2 -1
  37. package/dist/pages/VibeCalendarPage.js.map +1 -1
  38. package/dist/pages/ai/AgentManagerPage.js.map +1 -1
  39. package/dist/providers/VibeControlsProvider.js +147 -113
  40. package/dist/providers/VibeControlsProvider.js.map +1 -1
  41. package/dist/store/vibeControlsStore.js +16 -1
  42. package/dist/store/vibeControlsStore.js.map +1 -1
  43. package/dist/translations/en.js +97 -2
  44. package/dist/translations/en.js.map +1 -1
  45. package/package.json +1 -1
@@ -0,0 +1,183 @@
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { useVibeNavigate as t } from "../../providers/VibeControlsProvider.js";
3
+ import { getAgentStatus as n } from "../agents/agentStatus.js";
4
+ import { Clock as r, Cpu as i, ExternalLink as a, MonitorSmartphone as o, Network as s, Server as c, Wifi as l } from "lucide-react";
5
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
6
+ import { Popover as f, PopoverContent as p, PopoverTrigger as m } from "@burdenoff/fe-libs/ui";
7
+ //#region src/components/sessions/EntityInfoPopover.tsx
8
+ function h(e) {
9
+ if (!e) return "—";
10
+ let t = Date.now() - new Date(e).getTime();
11
+ return Number.isFinite(t) ? t < 6e4 ? "just now" : t < 36e5 ? `${Math.floor(t / 6e4)}m ago` : t < 864e5 ? `${Math.floor(t / 36e5)}h ago` : new Date(e).toLocaleDateString() : "—";
12
+ }
13
+ var g = {
14
+ ACTIVE: "bg-status-success-text",
15
+ DEGRADED: "bg-status-warning-text",
16
+ OFFLINE: "bg-status-error-text"
17
+ }, _ = {
18
+ DIRECT: "bg-bg-sunken text-text-secondary",
19
+ SSH: "bg-status-success-bg text-status-success-text",
20
+ RDP: "bg-status-warning-bg text-status-warning-text",
21
+ VNC: "bg-action-primary-bg/15 text-action-primary-text"
22
+ };
23
+ function v({ icon: e, label: t, value: n }) {
24
+ return /* @__PURE__ */ d("div", {
25
+ className: "flex items-center justify-between gap-3 py-1 text-xs",
26
+ children: [/* @__PURE__ */ d("span", {
27
+ className: "flex items-center gap-1.5 text-text-tertiary",
28
+ children: [e, t]
29
+ }), /* @__PURE__ */ u("span", {
30
+ className: "font-medium text-text-primary truncate max-w-[160px]",
31
+ title: n,
32
+ children: n
33
+ })]
34
+ });
35
+ }
36
+ function y({ agent: o, children: s, className: _ = "" }) {
37
+ let y = e(), b = t(), x = n(o);
38
+ return /* @__PURE__ */ d(f, { children: [/* @__PURE__ */ u(m, {
39
+ asChild: !0,
40
+ children: s ?? /* @__PURE__ */ d("button", {
41
+ type: "button",
42
+ onClick: (e) => e.stopPropagation(),
43
+ "data-testid": "session-agent-chip",
44
+ className: `inline-flex items-center gap-1.5 px-1.5 py-0.5 rounded-md text-xs text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors max-w-[140px] ${_}`,
45
+ title: o.name,
46
+ children: [
47
+ /* @__PURE__ */ u("span", {
48
+ className: `size-1.5 rounded-full shrink-0 ${g[x] ?? "bg-text-muted"}`,
49
+ "aria-hidden": !0
50
+ }),
51
+ /* @__PURE__ */ u(c, { className: "size-3 shrink-0" }),
52
+ /* @__PURE__ */ u("span", {
53
+ className: "truncate",
54
+ children: o.name
55
+ })
56
+ ]
57
+ })
58
+ }), /* @__PURE__ */ d(p, {
59
+ align: "start",
60
+ className: "w-64 p-3",
61
+ onClick: (e) => e.stopPropagation(),
62
+ "data-testid": "session-agent-popover",
63
+ children: [
64
+ /* @__PURE__ */ d("div", {
65
+ className: "flex items-center gap-2 mb-2",
66
+ children: [
67
+ /* @__PURE__ */ u("span", {
68
+ className: `size-2 rounded-full shrink-0 ${g[x] ?? "bg-text-muted"}`,
69
+ "aria-hidden": !0
70
+ }),
71
+ /* @__PURE__ */ u("span", {
72
+ className: "text-sm font-semibold text-text-primary truncate",
73
+ children: o.name
74
+ }),
75
+ /* @__PURE__ */ u("span", {
76
+ className: "ml-auto text-[10px] uppercase tracking-wide text-text-tertiary",
77
+ children: x
78
+ })
79
+ ]
80
+ }),
81
+ /* @__PURE__ */ d("div", {
82
+ className: "border-t border-border-subtle pt-1",
83
+ children: [
84
+ /* @__PURE__ */ u(v, {
85
+ icon: /* @__PURE__ */ u(c, { className: "size-3" }),
86
+ label: y("sessions.info.host", "Host"),
87
+ value: o.hostname || "—"
88
+ }),
89
+ /* @__PURE__ */ u(v, {
90
+ icon: /* @__PURE__ */ u(i, { className: "size-3" }),
91
+ label: y("sessions.info.platform", "Platform"),
92
+ value: o.platform || "—"
93
+ }),
94
+ /* @__PURE__ */ u(v, {
95
+ icon: /* @__PURE__ */ u(l, { className: "size-3" }),
96
+ label: y("sessions.info.tunnel", "Tunnel"),
97
+ value: o.tunnelStatus || (o.tunnelUrl ? "set" : "—")
98
+ }),
99
+ /* @__PURE__ */ u(v, {
100
+ icon: /* @__PURE__ */ u(r, { className: "size-3" }),
101
+ label: y("sessions.info.heartbeat", "Heartbeat"),
102
+ value: h(o.lastHeartbeat)
103
+ })
104
+ ]
105
+ }),
106
+ /* @__PURE__ */ d("button", {
107
+ type: "button",
108
+ onClick: () => b(`/agents/${o.id}`),
109
+ "data-testid": "session-agent-view",
110
+ className: "mt-2 w-full inline-flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors",
111
+ children: [/* @__PURE__ */ u(a, { className: "size-3" }), y("sessions.info.viewAgent", "View agent")]
112
+ })
113
+ ]
114
+ })] });
115
+ }
116
+ function b({ target: n, children: r, className: i = "" }) {
117
+ let c = e(), h = t();
118
+ return /* @__PURE__ */ d(f, { children: [/* @__PURE__ */ u(m, {
119
+ asChild: !0,
120
+ children: r ?? /* @__PURE__ */ d("button", {
121
+ type: "button",
122
+ onClick: (e) => e.stopPropagation(),
123
+ "data-testid": "session-target-chip",
124
+ className: `inline-flex items-center gap-1.5 px-1.5 py-0.5 rounded-md text-xs text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors max-w-[150px] ${i}`,
125
+ title: `${n.name} · ${n.type}`,
126
+ children: [
127
+ /* @__PURE__ */ u(o, { className: "size-3 shrink-0" }),
128
+ /* @__PURE__ */ u("span", {
129
+ className: "truncate",
130
+ children: n.name
131
+ }),
132
+ /* @__PURE__ */ u("span", {
133
+ className: `shrink-0 px-1 py-0 rounded text-[9px] font-semibold ${_[n.type] ?? "bg-bg-sunken text-text-secondary"}`,
134
+ children: n.type
135
+ })
136
+ ]
137
+ })
138
+ }), /* @__PURE__ */ d(p, {
139
+ align: "start",
140
+ className: "w-60 p-3",
141
+ onClick: (e) => e.stopPropagation(),
142
+ "data-testid": "session-target-popover",
143
+ children: [
144
+ /* @__PURE__ */ d("div", {
145
+ className: "flex items-center gap-2 mb-2",
146
+ children: [
147
+ /* @__PURE__ */ u(o, { className: "size-3.5 text-text-secondary shrink-0" }),
148
+ /* @__PURE__ */ u("span", {
149
+ className: "text-sm font-semibold text-text-primary truncate",
150
+ children: n.name
151
+ }),
152
+ /* @__PURE__ */ u("span", {
153
+ className: `ml-auto shrink-0 px-1.5 py-0.5 rounded text-[10px] font-semibold ${_[n.type] ?? "bg-bg-sunken text-text-secondary"}`,
154
+ children: n.type
155
+ })
156
+ ]
157
+ }),
158
+ /* @__PURE__ */ d("div", {
159
+ className: "border-t border-border-subtle pt-1",
160
+ children: [/* @__PURE__ */ u(v, {
161
+ icon: /* @__PURE__ */ u(l, { className: "size-3" }),
162
+ label: c("sessions.info.status", "Status"),
163
+ value: n.status || "—"
164
+ }), /* @__PURE__ */ u(v, {
165
+ icon: /* @__PURE__ */ u(s, { className: "size-3" }),
166
+ label: c("sessions.info.address", "Address"),
167
+ value: n.host ? `${n.host}${n.port ? `:${n.port}` : ""}` : "—"
168
+ })]
169
+ }),
170
+ /* @__PURE__ */ d("button", {
171
+ type: "button",
172
+ onClick: () => h(`/targets/${n.id}`),
173
+ "data-testid": "session-target-view",
174
+ className: "mt-2 w-full inline-flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors",
175
+ children: [/* @__PURE__ */ u(a, { className: "size-3" }), c("sessions.info.viewTarget", "View target")]
176
+ })
177
+ ]
178
+ })] });
179
+ }
180
+ //#endregion
181
+ export { y as AgentInfoPopover, b as TargetInfoPopover };
182
+
183
+ //# sourceMappingURL=EntityInfoPopover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityInfoPopover.js","names":[],"sources":["../../../src/components/sessions/EntityInfoPopover.tsx"],"sourcesContent":["/**\n * EntityInfoPopover — quick inline detail for the agent / target a\n * session runs on. Anchored on the agent/target chips in the session\n * list, cards and the terminal status bar so operators can confirm\n * \"which machine / is it healthy\" without leaving the page, with a\n * one-click jump to the full AgentDetails / TargetDetails page.\n */\nimport { Popover, PopoverTrigger, PopoverContent } from '@burdenoff/fe-libs/ui';\nimport { Server, MonitorSmartphone, ExternalLink, Cpu, Clock, Wifi, Network } from 'lucide-react';\nimport { getAgentStatus } from '@/components/agents/agentStatus';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useTr } from '../../shared/hooks/useTr';\nimport type { Session, SessionTarget, TargetType } from './types';\n\ntype SessionAgent = NonNullable<Session['agent']>;\n\n/** Relative \"x ago\" for a heartbeat / timestamp. */\nfunction relativeTime(ts: string | null | undefined): string {\n if (!ts) return '—';\n const diff = Date.now() - new Date(ts).getTime();\n if (!Number.isFinite(diff)) return '—';\n if (diff < 60_000) return 'just now';\n if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;\n if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;\n return new Date(ts).toLocaleDateString();\n}\n\nconst statusDotClass: Record<string, string> = {\n ACTIVE: 'bg-status-success-text',\n DEGRADED: 'bg-status-warning-text',\n OFFLINE: 'bg-status-error-text',\n};\n\nconst targetTypeBadgeClass: Record<TargetType, string> = {\n DIRECT: 'bg-bg-sunken text-text-secondary',\n SSH: 'bg-status-success-bg text-status-success-text',\n RDP: 'bg-status-warning-bg text-status-warning-text',\n VNC: 'bg-action-primary-bg/15 text-action-primary-text',\n};\n\nfunction InfoRow({ icon, label, value }: { icon: React.ReactNode; label: string; value: string }) {\n return (\n <div className=\"flex items-center justify-between gap-3 py-1 text-xs\">\n <span className=\"flex items-center gap-1.5 text-text-tertiary\">\n {icon}\n {label}\n </span>\n <span className=\"font-medium text-text-primary truncate max-w-[160px]\" title={value}>\n {value}\n </span>\n </div>\n );\n}\n\nexport interface AgentInfoPopoverProps {\n agent: SessionAgent;\n /** Optional custom trigger; defaults to a chip with the agent name + status dot. */\n children?: React.ReactNode;\n className?: string;\n}\n\n/** Popover summarising an agent's health + a link to its detail page. */\nexport function AgentInfoPopover({ agent, children, className = '' }: AgentInfoPopoverProps) {\n const tr = useTr();\n const navigate = useVibeNavigate();\n const status = getAgentStatus(agent);\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n {children ?? (\n <button\n type=\"button\"\n onClick={(e) => e.stopPropagation()}\n data-testid=\"session-agent-chip\"\n className={`inline-flex items-center gap-1.5 px-1.5 py-0.5 rounded-md text-xs text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors max-w-[140px] ${className}`}\n title={agent.name}\n >\n <span\n className={`size-1.5 rounded-full shrink-0 ${statusDotClass[status] ?? 'bg-text-muted'}`}\n aria-hidden\n />\n <Server className=\"size-3 shrink-0\" />\n <span className=\"truncate\">{agent.name}</span>\n </button>\n )}\n </PopoverTrigger>\n <PopoverContent\n align=\"start\"\n className=\"w-64 p-3\"\n onClick={(e) => e.stopPropagation()}\n data-testid=\"session-agent-popover\"\n >\n <div className=\"flex items-center gap-2 mb-2\">\n <span\n className={`size-2 rounded-full shrink-0 ${statusDotClass[status] ?? 'bg-text-muted'}`}\n aria-hidden\n />\n <span className=\"text-sm font-semibold text-text-primary truncate\">{agent.name}</span>\n <span className=\"ml-auto text-[10px] uppercase tracking-wide text-text-tertiary\">\n {status}\n </span>\n </div>\n <div className=\"border-t border-border-subtle pt-1\">\n <InfoRow\n icon={<Server className=\"size-3\" />}\n label={tr('sessions.info.host', 'Host')}\n value={agent.hostname || '—'}\n />\n <InfoRow\n icon={<Cpu className=\"size-3\" />}\n label={tr('sessions.info.platform', 'Platform')}\n value={agent.platform || '—'}\n />\n <InfoRow\n icon={<Wifi className=\"size-3\" />}\n label={tr('sessions.info.tunnel', 'Tunnel')}\n value={agent.tunnelStatus || (agent.tunnelUrl ? 'set' : '—')}\n />\n <InfoRow\n icon={<Clock className=\"size-3\" />}\n label={tr('sessions.info.heartbeat', 'Heartbeat')}\n value={relativeTime(agent.lastHeartbeat)}\n />\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/agents/${agent.id}`)}\n data-testid=\"session-agent-view\"\n className=\"mt-2 w-full inline-flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <ExternalLink className=\"size-3\" />\n {tr('sessions.info.viewAgent', 'View agent')}\n </button>\n </PopoverContent>\n </Popover>\n );\n}\n\nexport interface TargetInfoPopoverProps {\n target: SessionTarget;\n children?: React.ReactNode;\n className?: string;\n}\n\n/** Popover summarising the target machine + a link to its detail page. */\nexport function TargetInfoPopover({ target, children, className = '' }: TargetInfoPopoverProps) {\n const tr = useTr();\n const navigate = useVibeNavigate();\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n {children ?? (\n <button\n type=\"button\"\n onClick={(e) => e.stopPropagation()}\n data-testid=\"session-target-chip\"\n className={`inline-flex items-center gap-1.5 px-1.5 py-0.5 rounded-md text-xs text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors max-w-[150px] ${className}`}\n title={`${target.name} · ${target.type}`}\n >\n <MonitorSmartphone className=\"size-3 shrink-0\" />\n <span className=\"truncate\">{target.name}</span>\n <span\n className={`shrink-0 px-1 py-0 rounded text-[9px] font-semibold ${targetTypeBadgeClass[target.type] ?? 'bg-bg-sunken text-text-secondary'}`}\n >\n {target.type}\n </span>\n </button>\n )}\n </PopoverTrigger>\n <PopoverContent\n align=\"start\"\n className=\"w-60 p-3\"\n onClick={(e) => e.stopPropagation()}\n data-testid=\"session-target-popover\"\n >\n <div className=\"flex items-center gap-2 mb-2\">\n <MonitorSmartphone className=\"size-3.5 text-text-secondary shrink-0\" />\n <span className=\"text-sm font-semibold text-text-primary truncate\">{target.name}</span>\n <span\n className={`ml-auto shrink-0 px-1.5 py-0.5 rounded text-[10px] font-semibold ${targetTypeBadgeClass[target.type] ?? 'bg-bg-sunken text-text-secondary'}`}\n >\n {target.type}\n </span>\n </div>\n <div className=\"border-t border-border-subtle pt-1\">\n <InfoRow\n icon={<Wifi className=\"size-3\" />}\n label={tr('sessions.info.status', 'Status')}\n value={target.status || '—'}\n />\n <InfoRow\n icon={<Network className=\"size-3\" />}\n label={tr('sessions.info.address', 'Address')}\n value={target.host ? `${target.host}${target.port ? `:${target.port}` : ''}` : '—'}\n />\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/targets/${target.id}`)}\n data-testid=\"session-target-view\"\n className=\"mt-2 w-full inline-flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <ExternalLink className=\"size-3\" />\n {tr('sessions.info.viewTarget', 'View target')}\n </button>\n </PopoverContent>\n </Popover>\n );\n}\n"],"mappings":"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"}
@@ -1,23 +1,24 @@
1
1
  import { useTr as e } from "../../shared/hooks/useTr.js";
2
2
  import { SessionStatusBadge as t } from "./SessionStatusBadge.js";
3
- import { useState as n } from "react";
4
- import { Clock as r, MoreVertical as i, Play as a, RotateCcw as o, Share2 as s, Square as c, Terminal as l, Trash2 as u } from "lucide-react";
5
- import { AnimatePresence as d, motion as f } from "framer-motion";
6
- import { jsx as p, jsxs as m } from "react/jsx-runtime";
3
+ import { AgentInfoPopover as n, TargetInfoPopover as r } from "./EntityInfoPopover.js";
4
+ import { useState as i } from "react";
5
+ import { Clock as a, MoreVertical as o, Play as s, RotateCcw as c, Share2 as l, Square as u, Terminal as d, Trash2 as f } from "lucide-react";
6
+ import { AnimatePresence as p, motion as m } from "framer-motion";
7
+ import { jsx as h, jsxs as g } from "react/jsx-runtime";
7
8
  //#region src/components/sessions/SessionCard.tsx
8
- var h = {
9
+ var _ = {
9
10
  TMUX: "🖥️",
10
11
  SSH: "🔐",
11
12
  TERMINAL: "💻",
12
13
  SCRIPT: "📜",
13
14
  CUSTOM: "⚙️"
14
- }, g = ({ session: g, onOpen: _, onStart: v, onStop: y, onRestart: b, onTerminate: x, onOpenTerminal: S, onShare: C, selected: w = !1, onToggleSelect: T, className: E = "" }) => {
15
- let D = e(), [O, k] = n(!1), A = (e) => {
15
+ }, v = ({ session: v, onOpen: y, onStart: b, onStop: x, onRestart: S, onTerminate: C, onOpenTerminal: w, onShare: T, selected: E = !1, onToggleSelect: D, className: O = "" }) => {
16
+ let k = e(), [A, j] = i(!1), M = (e) => {
16
17
  if (!e) return "Never";
17
18
  let t = Date.now() - new Date(e).getTime();
18
19
  return t < 6e4 ? "Just now" : t < 36e5 ? `${Math.floor(t / 6e4)}m ago` : t < 864e5 ? `${Math.floor(t / 36e5)}h ago` : new Date(e).toLocaleDateString();
19
- }, j = g.status === "RUNNING", M = g.status === "STOPPED" || g.status === "TERMINATED";
20
- return /* @__PURE__ */ m(f.div, {
20
+ }, N = v.status === "RUNNING", P = v.status === "STOPPED" || v.status === "TERMINATED";
21
+ return /* @__PURE__ */ g(m.div, {
21
22
  whileHover: {
22
23
  y: -2,
23
24
  transition: {
@@ -31,60 +32,67 @@ var h = {
31
32
  }
32
33
  },
33
34
  whileTap: { scale: .98 },
34
- className: `bg-bg-surface border rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${w ? "border-action-primary-bg ring-1 ring-action-primary-bg/30" : "border-border-default"} ${E}`,
35
- onClick: () => _?.(g),
35
+ className: `bg-bg-surface border rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${E ? "border-action-primary-bg ring-1 ring-action-primary-bg/30" : "border-border-default"} ${O}`,
36
+ onClick: () => y?.(v),
36
37
  children: [
37
- /* @__PURE__ */ m("div", {
38
+ /* @__PURE__ */ g("div", {
38
39
  className: "flex items-start justify-between gap-2",
39
- children: [/* @__PURE__ */ m("div", {
40
+ children: [/* @__PURE__ */ g("div", {
40
41
  className: "flex items-start gap-2 sm:gap-3 min-w-0 flex-1",
41
42
  children: [
42
- T && /* @__PURE__ */ p("div", {
43
+ D && /* @__PURE__ */ h("div", {
43
44
  className: "flex-shrink-0 pt-0.5",
44
- children: /* @__PURE__ */ p("input", {
45
+ children: /* @__PURE__ */ h("input", {
45
46
  "aria-label": "control",
46
47
  type: "checkbox",
47
- checked: w,
48
+ checked: E,
48
49
  onChange: (e) => {
49
- e.stopPropagation(), T();
50
+ e.stopPropagation(), D();
50
51
  },
51
52
  onClick: (e) => e.stopPropagation(),
52
53
  className: "size-4 rounded border-border-default cursor-pointer"
53
54
  })
54
55
  }),
55
- /* @__PURE__ */ p("div", {
56
+ /* @__PURE__ */ h("div", {
56
57
  className: "flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center",
57
- children: /* @__PURE__ */ p(l, { className: "size-4 sm:w-5 sm:h-5 text-text-secondary" })
58
+ children: /* @__PURE__ */ h(d, { className: "size-4 sm:w-5 sm:h-5 text-text-secondary" })
58
59
  }),
59
- /* @__PURE__ */ m("div", {
60
+ /* @__PURE__ */ g("div", {
60
61
  className: "min-w-0 flex-1",
61
- children: [/* @__PURE__ */ m("div", {
62
- className: "flex items-center gap-1.5 sm:gap-2 flex-wrap",
63
- children: [/* @__PURE__ */ p("h3", {
64
- className: "font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base",
65
- children: g.name
66
- }), /* @__PURE__ */ p("span", {
67
- className: "text-base sm:text-lg flex-shrink-0",
68
- children: h[g.type] || "💻"
69
- })]
70
- }), /* @__PURE__ */ p("p", {
71
- className: "text-xs sm:text-sm text-text-secondary",
72
- children: g.type
73
- })]
62
+ children: [
63
+ /* @__PURE__ */ g("div", {
64
+ className: "flex items-center gap-1.5 sm:gap-2 flex-wrap",
65
+ children: [/* @__PURE__ */ h("h3", {
66
+ className: "font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base",
67
+ children: v.name
68
+ }), /* @__PURE__ */ h("span", {
69
+ className: "text-base sm:text-lg flex-shrink-0",
70
+ children: _[v.type] || "💻"
71
+ })]
72
+ }),
73
+ /* @__PURE__ */ h("p", {
74
+ className: "text-xs sm:text-sm text-text-secondary",
75
+ children: v.type
76
+ }),
77
+ v.agent && /* @__PURE__ */ g("div", {
78
+ className: "mt-1.5 flex items-center gap-1 flex-wrap",
79
+ children: [/* @__PURE__ */ h(n, { agent: v.agent }), v.agent.target && /* @__PURE__ */ h(r, { target: v.agent.target })]
80
+ })
81
+ ]
74
82
  })
75
83
  ]
76
- }), /* @__PURE__ */ m("div", {
84
+ }), /* @__PURE__ */ g("div", {
77
85
  className: "flex items-center gap-1 sm:gap-2 flex-shrink-0",
78
- children: [/* @__PURE__ */ p(t, { status: g.status }), (v || y || b || x) && /* @__PURE__ */ m("div", {
86
+ children: [/* @__PURE__ */ h(t, { status: v.status }), (b || x || S || C) && /* @__PURE__ */ g("div", {
79
87
  className: "relative",
80
- children: [/* @__PURE__ */ p("button", {
88
+ children: [/* @__PURE__ */ h("button", {
81
89
  type: "button",
82
90
  onClick: (e) => {
83
- e.stopPropagation(), k(!O);
91
+ e.stopPropagation(), j(!A);
84
92
  },
85
93
  className: "p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors",
86
- children: /* @__PURE__ */ p(i, { className: "size-4 text-text-secondary" })
87
- }), /* @__PURE__ */ p(d, { children: O && /* @__PURE__ */ m(f.div, {
94
+ children: /* @__PURE__ */ h(o, { className: "size-4 text-text-secondary" })
95
+ }), /* @__PURE__ */ h(p, { children: A && /* @__PURE__ */ g(m.div, {
88
96
  initial: {
89
97
  opacity: 0,
90
98
  scale: .95,
@@ -103,53 +111,53 @@ var h = {
103
111
  transition: { duration: .15 },
104
112
  className: "absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10",
105
113
  children: [
106
- M && v && /* @__PURE__ */ m(f.button, {
114
+ P && b && /* @__PURE__ */ g(m.button, {
107
115
  whileTap: { scale: .97 },
108
116
  onClick: (e) => {
109
- e.stopPropagation(), v(g), k(!1);
117
+ e.stopPropagation(), b(v), j(!1);
110
118
  },
111
119
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
112
- children: [/* @__PURE__ */ p(a, { className: "size-4" }), D("vibecontrols.sessions.card.start", "Start")]
120
+ children: [/* @__PURE__ */ h(s, { className: "size-4" }), k("vibecontrols.sessions.card.start", "Start")]
113
121
  }),
114
- j && y && /* @__PURE__ */ m(f.button, {
122
+ N && x && /* @__PURE__ */ g(m.button, {
115
123
  whileTap: { scale: .97 },
116
124
  onClick: (e) => {
117
- e.stopPropagation(), y(g), k(!1);
125
+ e.stopPropagation(), x(v), j(!1);
118
126
  },
119
127
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
120
- children: [/* @__PURE__ */ p(c, { className: "size-4" }), D("vibecontrols.sessions.card.stop", "Stop")]
128
+ children: [/* @__PURE__ */ h(u, { className: "size-4" }), k("vibecontrols.sessions.card.stop", "Stop")]
121
129
  }),
122
- j && b && /* @__PURE__ */ m(f.button, {
130
+ N && S && /* @__PURE__ */ g(m.button, {
123
131
  whileTap: { scale: .97 },
124
132
  onClick: (e) => {
125
- e.stopPropagation(), b(g), k(!1);
133
+ e.stopPropagation(), S(v), j(!1);
126
134
  },
127
135
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
128
- children: [/* @__PURE__ */ p(o, { className: "size-4" }), D("vibecontrols.sessions.card.restart", "Restart")]
136
+ children: [/* @__PURE__ */ h(c, { className: "size-4" }), k("vibecontrols.sessions.card.restart", "Restart")]
129
137
  }),
130
- j && S && /* @__PURE__ */ m(f.button, {
138
+ N && w && /* @__PURE__ */ g(m.button, {
131
139
  whileTap: { scale: .97 },
132
140
  onClick: (e) => {
133
- e.stopPropagation(), S(g), k(!1);
141
+ e.stopPropagation(), w(v), j(!1);
134
142
  },
135
143
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
136
- children: [/* @__PURE__ */ p(l, { className: "size-4" }), D("vibecontrols.sessions.card.openTerminal", "Open Terminal")]
144
+ children: [/* @__PURE__ */ h(d, { className: "size-4" }), k("vibecontrols.sessions.card.openTerminal", "Open Terminal")]
137
145
  }),
138
- j && C && /* @__PURE__ */ m(f.button, {
146
+ N && T && /* @__PURE__ */ g(m.button, {
139
147
  whileTap: { scale: .97 },
140
148
  onClick: (e) => {
141
- e.stopPropagation(), C(g), k(!1);
149
+ e.stopPropagation(), T(v), j(!1);
142
150
  },
143
151
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
144
- children: [/* @__PURE__ */ p(s, { className: "size-4" }), D("vibecontrols.sessions.card.shareSession", "Share Session")]
152
+ children: [/* @__PURE__ */ h(l, { className: "size-4" }), k("vibecontrols.sessions.card.shareSession", "Share Session")]
145
153
  }),
146
- x && /* @__PURE__ */ m(f.button, {
154
+ C && /* @__PURE__ */ g(m.button, {
147
155
  whileTap: { scale: .97 },
148
156
  onClick: (e) => {
149
- e.stopPropagation(), x(g), k(!1);
157
+ e.stopPropagation(), C(v), j(!1);
150
158
  },
151
159
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg",
152
- children: [/* @__PURE__ */ p(u, { className: "size-4" }), D("vibecontrols.sessions.card.terminate", "Terminate")]
160
+ children: [/* @__PURE__ */ h(f, { className: "size-4" }), k("vibecontrols.sessions.card.terminate", "Terminate")]
153
161
  })
154
162
  ]
155
163
  }) })]
@@ -157,42 +165,42 @@ var h = {
157
165
  })]
158
166
  }),
159
167
  (() => {
160
- let e = g.metadata || {}, t = typeof e.tmuxSessionName == "string" && e.tmuxSessionName || typeof e.weztermWorkspace == "string" && e.weztermWorkspace || typeof e.zellijSessionName == "string" && e.zellijSessionName || typeof e.externalSessionName == "string" && e.externalSessionName || g.providerSessionName || null;
161
- return t ? /* @__PURE__ */ m("div", {
168
+ let e = v.metadata || {}, t = typeof e.tmuxSessionName == "string" && e.tmuxSessionName || typeof e.weztermWorkspace == "string" && e.weztermWorkspace || typeof e.zellijSessionName == "string" && e.zellijSessionName || typeof e.externalSessionName == "string" && e.externalSessionName || v.providerSessionName || null;
169
+ return t ? /* @__PURE__ */ g("div", {
162
170
  className: "mt-2 text-[10px] font-mono text-text-tertiary",
163
171
  title: `Provider session: ${t}`,
164
172
  "data-testid": "session-card-external-name",
165
- children: [/* @__PURE__ */ m("span", {
173
+ children: [/* @__PURE__ */ g("span", {
166
174
  className: "opacity-70",
167
- children: [g.type.toLowerCase(), ": "]
168
- }), /* @__PURE__ */ p("span", {
175
+ children: [v.type.toLowerCase(), ": "]
176
+ }), /* @__PURE__ */ h("span", {
169
177
  className: "break-all",
170
178
  children: t
171
179
  })]
172
180
  }) : null;
173
181
  })(),
174
- g.command && /* @__PURE__ */ m("div", {
182
+ v.command && /* @__PURE__ */ g("div", {
175
183
  className: "mt-3 text-xs font-mono bg-bg-sunken p-2 rounded truncate text-text-secondary",
176
- children: ["$ ", g.command]
184
+ children: ["$ ", v.command]
177
185
  }),
178
- g.lastOutput && /* @__PURE__ */ p("div", {
186
+ v.lastOutput && /* @__PURE__ */ h("div", {
179
187
  className: "mt-2 text-xs font-mono bg-bg-sunken text-status-success-text p-2 rounded truncate",
180
- children: g.lastOutput
188
+ children: v.lastOutput
181
189
  }),
182
- /* @__PURE__ */ m("div", {
190
+ /* @__PURE__ */ g("div", {
183
191
  className: "mt-3 flex items-center justify-between text-sm text-text-secondary",
184
- children: [/* @__PURE__ */ m("div", {
192
+ children: [/* @__PURE__ */ g("div", {
185
193
  className: "flex items-center gap-2",
186
- children: [/* @__PURE__ */ p(r, { className: "size-4" }), /* @__PURE__ */ p("span", { children: A(g.updatedAt) })]
187
- }), g.pid && /* @__PURE__ */ m("span", {
194
+ children: [/* @__PURE__ */ h(a, { className: "size-4" }), /* @__PURE__ */ h("span", { children: M(v.updatedAt) })]
195
+ }), v.pid && /* @__PURE__ */ g("span", {
188
196
  className: "text-xs font-mono",
189
- children: ["PID: ", g.pid]
197
+ children: ["PID: ", v.pid]
190
198
  })]
191
199
  })
192
200
  ]
193
201
  });
194
202
  };
195
203
  //#endregion
196
- export { g as SessionCard };
204
+ export { v as SessionCard };
197
205
 
198
206
  //# sourceMappingURL=SessionCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/components/sessions/SessionCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Terminal,\n Clock,\n MoreVertical,\n Play,\n Square,\n RotateCcw,\n Trash2,\n Share2,\n} from 'lucide-react';\nimport { SessionStatusBadge } from './SessionStatusBadge';\nimport type { Session } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface SessionCardProps {\n session: Session;\n onOpen?: (session: Session) => void;\n onStart?: (session: Session) => void;\n onStop?: (session: Session) => void;\n onRestart?: (session: Session) => void;\n onTerminate?: (session: Session) => void;\n onOpenTerminal?: (session: Session) => void;\n /** Callback to open the share dialog */\n onShare?: (session: Session) => void;\n /** Whether this card is selected for bulk operations */\n selected?: boolean;\n /** Toggle selection for bulk operations */\n onToggleSelect?: () => void;\n className?: string;\n}\n\nconst sessionTypeIcons: Record<string, string> = {\n TMUX: '🖥️',\n SSH: '🔐',\n TERMINAL: '💻',\n SCRIPT: '📜',\n CUSTOM: '⚙️',\n};\n\n/**\n * SessionCard - Display terminal session information\n */\nexport const SessionCard: FC<SessionCardProps> = ({\n session,\n onOpen,\n onStart,\n onStop,\n onRestart,\n onTerminate,\n onOpenTerminal,\n onShare,\n selected = false,\n onToggleSelect,\n className = '',\n}) => {\n const tr = useTr();\n const [showMenu, setShowMenu] = useState(false);\n\n const formatTime = (timestamp: string | undefined | null) => {\n if (!timestamp) return 'Never';\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return 'Just now';\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return new Date(timestamp).toLocaleDateString();\n };\n\n const isRunning = session.status === 'RUNNING';\n const isStopped = session.status === 'STOPPED' || session.status === 'TERMINATED';\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className={`bg-bg-surface border rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${selected ? 'border-action-primary-bg ring-1 ring-action-primary-bg/30' : 'border-border-default'} ${className}`}\n onClick={() => onOpen?.(session)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {onToggleSelect && (\n <div className=\"flex-shrink-0 pt-0.5\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selected}\n onChange={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n onClick={(e) => e.stopPropagation()}\n className=\"size-4 rounded border-border-default cursor-pointer\"\n />\n </div>\n )}\n <div className=\"flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Terminal className=\"size-4 sm:w-5 sm:h-5 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base\">\n {session.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">\n {sessionTypeIcons[session.type] || '💻'}\n </span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary\">{session.type}</p>\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <SessionStatusBadge status={session.status} />\n {(onStart || onStop || onRestart || onTerminate) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {isStopped && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Play className=\"size-4\" />\n {tr('vibecontrols.sessions.card.start', 'Start')}\n </motion.button>\n )}\n {isRunning && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Square className=\"size-4\" />\n {tr('vibecontrols.sessions.card.stop', 'Stop')}\n </motion.button>\n )}\n {isRunning && onRestart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onRestart(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <RotateCcw className=\"size-4\" />\n {tr('vibecontrols.sessions.card.restart', 'Restart')}\n </motion.button>\n )}\n {isRunning && onOpenTerminal && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onOpenTerminal(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Terminal className=\"size-4\" />\n {tr('vibecontrols.sessions.card.openTerminal', 'Open Terminal')}\n </motion.button>\n )}\n {isRunning && onShare && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.shareSession', 'Share Session')}\n </motion.button>\n )}\n {onTerminate && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onTerminate(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.terminate', 'Terminate')}\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n {/* Provider-native session name (e.g. tmux session name like\n `vibe-051c9c62`). Surfaced so users can recognise their session\n if they ever need to attach from a CLI (`tmux attach -t …`)\n and so reconnect-after-restart flows are obvious. */}\n {(() => {\n const meta = (session.metadata as Record<string, unknown> | null) || {};\n const externalName =\n (typeof meta.tmuxSessionName === 'string' && meta.tmuxSessionName) ||\n (typeof meta.weztermWorkspace === 'string' && meta.weztermWorkspace) ||\n (typeof meta.zellijSessionName === 'string' && meta.zellijSessionName) ||\n (typeof meta.externalSessionName === 'string' && meta.externalSessionName) ||\n session.providerSessionName ||\n null;\n return externalName ? (\n <div\n className=\"mt-2 text-[10px] font-mono text-text-tertiary\"\n title={`Provider session: ${externalName}`}\n data-testid=\"session-card-external-name\"\n >\n <span className=\"opacity-70\">{session.type.toLowerCase()}: </span>\n <span className=\"break-all\">{externalName}</span>\n </div>\n ) : null;\n })()}\n\n {session.command && (\n <div className=\"mt-3 text-xs font-mono bg-bg-sunken p-2 rounded truncate text-text-secondary\">\n $ {session.command}\n </div>\n )}\n\n {session.lastOutput && (\n <div className=\"mt-2 text-xs font-mono bg-bg-sunken text-status-success-text p-2 rounded truncate\">\n {session.lastOutput}\n </div>\n )}\n\n <div className=\"mt-3 flex items-center justify-between text-sm text-text-secondary\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-4\" />\n <span>{formatTime(session.updatedAt)}</span>\n </div>\n {session.pid && <span className=\"text-xs font-mono\">PID: {session.pid}</span>}\n </div>\n </motion.div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/components/sessions/SessionCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Terminal,\n Clock,\n MoreVertical,\n Play,\n Square,\n RotateCcw,\n Trash2,\n Share2,\n} from 'lucide-react';\nimport { SessionStatusBadge } from './SessionStatusBadge';\nimport { AgentInfoPopover, TargetInfoPopover } from './EntityInfoPopover';\nimport type { Session } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface SessionCardProps {\n session: Session;\n onOpen?: (session: Session) => void;\n onStart?: (session: Session) => void;\n onStop?: (session: Session) => void;\n onRestart?: (session: Session) => void;\n onTerminate?: (session: Session) => void;\n onOpenTerminal?: (session: Session) => void;\n /** Callback to open the share dialog */\n onShare?: (session: Session) => void;\n /** Whether this card is selected for bulk operations */\n selected?: boolean;\n /** Toggle selection for bulk operations */\n onToggleSelect?: () => void;\n className?: string;\n}\n\nconst sessionTypeIcons: Record<string, string> = {\n TMUX: '🖥️',\n SSH: '🔐',\n TERMINAL: '💻',\n SCRIPT: '📜',\n CUSTOM: '⚙️',\n};\n\n/**\n * SessionCard - Display terminal session information\n */\nexport const SessionCard: FC<SessionCardProps> = ({\n session,\n onOpen,\n onStart,\n onStop,\n onRestart,\n onTerminate,\n onOpenTerminal,\n onShare,\n selected = false,\n onToggleSelect,\n className = '',\n}) => {\n const tr = useTr();\n const [showMenu, setShowMenu] = useState(false);\n\n const formatTime = (timestamp: string | undefined | null) => {\n if (!timestamp) return 'Never';\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return 'Just now';\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return new Date(timestamp).toLocaleDateString();\n };\n\n const isRunning = session.status === 'RUNNING';\n const isStopped = session.status === 'STOPPED' || session.status === 'TERMINATED';\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className={`bg-bg-surface border rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${selected ? 'border-action-primary-bg ring-1 ring-action-primary-bg/30' : 'border-border-default'} ${className}`}\n onClick={() => onOpen?.(session)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {onToggleSelect && (\n <div className=\"flex-shrink-0 pt-0.5\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selected}\n onChange={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n onClick={(e) => e.stopPropagation()}\n className=\"size-4 rounded border-border-default cursor-pointer\"\n />\n </div>\n )}\n <div className=\"flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Terminal className=\"size-4 sm:w-5 sm:h-5 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base\">\n {session.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">\n {sessionTypeIcons[session.type] || '💻'}\n </span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary\">{session.type}</p>\n {session.agent && (\n <div className=\"mt-1.5 flex items-center gap-1 flex-wrap\">\n <AgentInfoPopover agent={session.agent} />\n {session.agent.target && <TargetInfoPopover target={session.agent.target} />}\n </div>\n )}\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <SessionStatusBadge status={session.status} />\n {(onStart || onStop || onRestart || onTerminate) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {isStopped && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Play className=\"size-4\" />\n {tr('vibecontrols.sessions.card.start', 'Start')}\n </motion.button>\n )}\n {isRunning && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Square className=\"size-4\" />\n {tr('vibecontrols.sessions.card.stop', 'Stop')}\n </motion.button>\n )}\n {isRunning && onRestart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onRestart(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <RotateCcw className=\"size-4\" />\n {tr('vibecontrols.sessions.card.restart', 'Restart')}\n </motion.button>\n )}\n {isRunning && onOpenTerminal && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onOpenTerminal(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Terminal className=\"size-4\" />\n {tr('vibecontrols.sessions.card.openTerminal', 'Open Terminal')}\n </motion.button>\n )}\n {isRunning && onShare && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.shareSession', 'Share Session')}\n </motion.button>\n )}\n {onTerminate && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onTerminate(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.terminate', 'Terminate')}\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n {/* Provider-native session name (e.g. tmux session name like\n `vibe-051c9c62`). Surfaced so users can recognise their session\n if they ever need to attach from a CLI (`tmux attach -t …`)\n and so reconnect-after-restart flows are obvious. */}\n {(() => {\n const meta = (session.metadata as Record<string, unknown> | null) || {};\n const externalName =\n (typeof meta.tmuxSessionName === 'string' && meta.tmuxSessionName) ||\n (typeof meta.weztermWorkspace === 'string' && meta.weztermWorkspace) ||\n (typeof meta.zellijSessionName === 'string' && meta.zellijSessionName) ||\n (typeof meta.externalSessionName === 'string' && meta.externalSessionName) ||\n session.providerSessionName ||\n null;\n return externalName ? (\n <div\n className=\"mt-2 text-[10px] font-mono text-text-tertiary\"\n title={`Provider session: ${externalName}`}\n data-testid=\"session-card-external-name\"\n >\n <span className=\"opacity-70\">{session.type.toLowerCase()}: </span>\n <span className=\"break-all\">{externalName}</span>\n </div>\n ) : null;\n })()}\n\n {session.command && (\n <div className=\"mt-3 text-xs font-mono bg-bg-sunken p-2 rounded truncate text-text-secondary\">\n $ {session.command}\n </div>\n )}\n\n {session.lastOutput && (\n <div className=\"mt-2 text-xs font-mono bg-bg-sunken text-status-success-text p-2 rounded truncate\">\n {session.lastOutput}\n </div>\n )}\n\n <div className=\"mt-3 flex items-center justify-between text-sm text-text-secondary\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-4\" />\n <span>{formatTime(session.updatedAt)}</span>\n </div>\n {session.pid && <span className=\"text-xs font-mono\">PID: {session.pid}</span>}\n </div>\n </motion.div>\n );\n};\n"],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { ChevronDown as t, ChevronRight as n } from "lucide-react";
3
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
4
+ //#region src/components/sessions/SessionGroup.tsx
5
+ function a({ label: a, count: o, collapsed: s, onToggleCollapse: c, allSelected: l, someSelected: u, onToggleSelectGroup: d, showSelect: f = !0, children: p }) {
6
+ let m = e();
7
+ return /* @__PURE__ */ i("div", {
8
+ "data-testid": "session-group",
9
+ "data-group-label": a,
10
+ children: [/* @__PURE__ */ i("div", {
11
+ className: "flex items-center gap-2 px-1 py-1.5 sticky top-0 z-[1] bg-bg-surface/95 backdrop-blur-sm",
12
+ children: [f && /* @__PURE__ */ r("input", {
13
+ "aria-label": m("sessions.group.selectGroup", "Select group"),
14
+ type: "checkbox",
15
+ checked: l,
16
+ ref: (e) => {
17
+ e && (e.indeterminate = !l && u);
18
+ },
19
+ onChange: (e) => {
20
+ e.stopPropagation(), d();
21
+ },
22
+ onClick: (e) => e.stopPropagation(),
23
+ className: "size-3.5 rounded border-border-default text-action-primary-bg focus:ring-action-primary-bg"
24
+ }), /* @__PURE__ */ i("button", {
25
+ type: "button",
26
+ onClick: c,
27
+ className: "flex items-center gap-1.5 text-sm font-semibold text-text-primary hover:text-text-link transition-colors min-w-0",
28
+ children: [
29
+ r(s ? n : t, { className: "size-3.5 text-text-tertiary shrink-0" }),
30
+ /* @__PURE__ */ r("span", {
31
+ className: "truncate",
32
+ children: a
33
+ }),
34
+ /* @__PURE__ */ r("span", {
35
+ className: "ml-1 inline-flex items-center justify-center min-w-[1.25rem] px-1.5 py-0.5 text-[10px] font-medium rounded-full bg-bg-sunken text-text-secondary",
36
+ children: o
37
+ })
38
+ ]
39
+ })]
40
+ }), !s && /* @__PURE__ */ r("div", {
41
+ className: "pb-2",
42
+ children: p
43
+ })]
44
+ });
45
+ }
46
+ //#endregion
47
+ export { a as SessionGroup };
48
+
49
+ //# sourceMappingURL=SessionGroup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionGroup.js","names":[],"sources":["../../../src/components/sessions/SessionGroup.tsx"],"sourcesContent":["/**\n * SessionGroup — a collapsible section wrapping one group of sessions in\n * the grid/list views. The header carries the group label, a count, and a\n * tri-state select-all checkbox that toggles only this group's ids in the\n * page-level selection Set (so the global bulk-action bar keeps working).\n */\nimport type { ReactNode } from 'react';\nimport { ChevronDown, ChevronRight } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface SessionGroupProps {\n label: string;\n count: number;\n collapsed: boolean;\n onToggleCollapse: () => void;\n /** All sessions in this group are selected. */\n allSelected: boolean;\n /** Some (but not all) sessions in this group are selected. */\n someSelected: boolean;\n /** Toggle selection of every session in this group. */\n onToggleSelectGroup: () => void;\n /** Whether to show the per-group select checkbox (bulk mode). */\n showSelect?: boolean;\n children: ReactNode;\n}\n\nexport function SessionGroup({\n label,\n count,\n collapsed,\n onToggleCollapse,\n allSelected,\n someSelected,\n onToggleSelectGroup,\n showSelect = true,\n children,\n}: SessionGroupProps) {\n const tr = useTr();\n return (\n <div data-testid=\"session-group\" data-group-label={label}>\n <div className=\"flex items-center gap-2 px-1 py-1.5 sticky top-0 z-[1] bg-bg-surface/95 backdrop-blur-sm\">\n {showSelect && (\n <input\n aria-label={tr('sessions.group.selectGroup', 'Select group')}\n type=\"checkbox\"\n checked={allSelected}\n ref={(el) => {\n if (el) el.indeterminate = !allSelected && someSelected;\n }}\n onChange={(e) => {\n e.stopPropagation();\n onToggleSelectGroup();\n }}\n onClick={(e) => e.stopPropagation()}\n className=\"size-3.5 rounded border-border-default text-action-primary-bg focus:ring-action-primary-bg\"\n />\n )}\n <button\n type=\"button\"\n onClick={onToggleCollapse}\n className=\"flex items-center gap-1.5 text-sm font-semibold text-text-primary hover:text-text-link transition-colors min-w-0\"\n >\n {collapsed ? (\n <ChevronRight className=\"size-3.5 text-text-tertiary shrink-0\" />\n ) : (\n <ChevronDown className=\"size-3.5 text-text-tertiary shrink-0\" />\n )}\n <span className=\"truncate\">{label}</span>\n <span className=\"ml-1 inline-flex items-center justify-center min-w-[1.25rem] px-1.5 py-0.5 text-[10px] font-medium rounded-full bg-bg-sunken text-text-secondary\">\n {count}\n </span>\n </button>\n </div>\n {!collapsed && <div className=\"pb-2\">{children}</div>}\n </div>\n );\n}\n"],"mappings":";;;;AA0BA,SAAgB,EAAa,EAC3B,UACA,UACA,cACA,qBACA,gBACA,iBACA,wBACA,gBAAa,IACb,eACoB;CACpB,IAAM,IAAK,GAAO;AAClB,QACE,kBAAC,OAAD;EAAK,eAAY;EAAgB,oBAAkB;YAAnD,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,KACC,kBAAC,SAAD;IACE,cAAY,EAAG,8BAA8B,eAAe;IAC5D,MAAK;IACL,SAAS;IACT,MAAM,MAAO;AACX,KAAI,MAAI,EAAG,gBAAgB,CAAC,KAAe;;IAE7C,WAAW,MAAM;AAEf,KADA,EAAE,iBAAiB,EACnB,GAAqB;;IAEvB,UAAU,MAAM,EAAE,iBAAiB;IACnC,WAAU;IACV,CAAA,EAEJ,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;cAHZ;KAMI,EADD,IACE,IAEA,GAFD,EAAc,WAAU,wCAAyC,CAED;KAElE,kBAAC,QAAD;MAAM,WAAU;gBAAY;MAAa,CAAA;KACzC,kBAAC,QAAD;MAAM,WAAU;gBACb;MACI,CAAA;KACA;MACL;MACL,CAAC,KAAa,kBAAC,OAAD;GAAK,WAAU;GAAQ;GAAe,CAAA,CACjD"}