@burdenoff/microfe-vibecontrols 2026.601.6 → 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 (35) 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/pages/SessionsPage.js +1189 -920
  29. package/dist/pages/SessionsPage.js.map +1 -1
  30. package/dist/pages/ai/AgentManagerPage.js.map +1 -1
  31. package/dist/store/vibeControlsStore.js +16 -1
  32. package/dist/store/vibeControlsStore.js.map +1 -1
  33. package/dist/translations/en.js +97 -2
  34. package/dist/translations/en.js.map +1 -1
  35. package/package.json +1 -1
@@ -0,0 +1,46 @@
1
+ //#region src/components/sessions/groupSessions.ts
2
+ var e = "__none__";
3
+ function t(e) {
4
+ return e && e.charAt(0).toUpperCase() + e.slice(1).toLowerCase();
5
+ }
6
+ function n(n, r) {
7
+ if (r === "none") return [{
8
+ key: "",
9
+ label: "",
10
+ sessions: n
11
+ }];
12
+ let i = /* @__PURE__ */ new Map(), a = [];
13
+ for (let o of n) {
14
+ let n, s;
15
+ switch (r) {
16
+ case "agent":
17
+ n = o.agent?.id ?? e, s = o.agent?.name ?? "No agent";
18
+ break;
19
+ case "target":
20
+ n = o.agent?.target?.id ?? e, s = o.agent?.target?.name ?? "No target";
21
+ break;
22
+ case "vibe":
23
+ n = o.vibeId ?? e, s = o.vibe?.name ?? "No vibe";
24
+ break;
25
+ case "status":
26
+ n = o.status, s = t(o.status);
27
+ break;
28
+ case "type":
29
+ n = o.type, s = o.type;
30
+ break;
31
+ default: n = e, s = "Other";
32
+ }
33
+ let c = i.get(n);
34
+ c || (c = {
35
+ key: n,
36
+ label: s,
37
+ sessions: []
38
+ }, i.set(n, c), a.push(n)), c.sessions.push(o);
39
+ }
40
+ let o = a.map((e) => i.get(e));
41
+ return o.sort((t, n) => t.key === e ? 1 : n.key === e ? -1 : t.label.localeCompare(n.label)), o;
42
+ }
43
+ //#endregion
44
+ export { n as groupSessions };
45
+
46
+ //# sourceMappingURL=groupSessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"groupSessions.js","names":[],"sources":["../../../src/components/sessions/groupSessions.ts"],"sourcesContent":["/**\n * Pure session grouping for the Sessions grid/list views.\n *\n * Mirrors the AgentManager sidebar's `groupSessions` precedent but typed\n * to the terminal `Session` model and the `SessionsGroupBy` union. Render\n * order is stable (alpha by label) with the \"No X\" bucket always last.\n * Grouping is purely a render-time partition — selection stays on the\n * page-level `Set<string>`, so this introduces no selection state.\n */\nimport type { Session, SessionsGroupBy } from './types';\n\nexport interface SessionGroupData {\n /** Stable group key (entity id, status, type, or sentinel). */\n key: string;\n /** Human-readable header label. */\n label: string;\n sessions: Session[];\n}\n\n/** Sentinel key for sessions missing the grouped attribute. */\nconst NO_KEY = '__none__';\n\nfunction titleCase(value: string): string {\n if (!value) return value;\n return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();\n}\n\nexport function groupSessions(sessions: Session[], groupBy: SessionsGroupBy): SessionGroupData[] {\n if (groupBy === 'none') {\n return [{ key: '', label: '', sessions }];\n }\n\n const groups = new Map<string, SessionGroupData>();\n const order: string[] = [];\n\n for (const session of sessions) {\n let key: string;\n let label: string;\n\n switch (groupBy) {\n case 'agent':\n key = session.agent?.id ?? NO_KEY;\n label = session.agent?.name ?? 'No agent';\n break;\n case 'target':\n key = session.agent?.target?.id ?? NO_KEY;\n label = session.agent?.target?.name ?? 'No target';\n break;\n case 'vibe':\n key = session.vibeId ?? NO_KEY;\n label = session.vibe?.name ?? 'No vibe';\n break;\n case 'status':\n key = session.status;\n label = titleCase(session.status);\n break;\n case 'type':\n key = session.type;\n label = session.type;\n break;\n default:\n key = NO_KEY;\n label = 'Other';\n }\n\n let group = groups.get(key);\n if (!group) {\n group = { key, label, sessions: [] };\n groups.set(key, group);\n order.push(key);\n }\n group.sessions.push(session);\n }\n\n const result = order.map((k) => groups.get(k) as SessionGroupData);\n result.sort((a, b) => {\n // \"No X\" bucket always sorts last regardless of alphabetical order.\n if (a.key === NO_KEY) return 1;\n if (b.key === NO_KEY) return -1;\n return a.label.localeCompare(b.label);\n });\n return result;\n}\n"],"mappings":";AAoBA,IAAM,IAAS;AAEf,SAAS,EAAU,GAAuB;AAExC,QADK,KACE,EAAM,OAAO,EAAE,CAAC,aAAa,GAAG,EAAM,MAAM,EAAE,CAAC,aAAa;;AAGrE,SAAgB,EAAc,GAAqB,GAA8C;AAC/F,KAAI,MAAY,OACd,QAAO,CAAC;EAAE,KAAK;EAAI,OAAO;EAAI;EAAU,CAAC;CAG3C,IAAM,oBAAS,IAAI,KAA+B,EAC5C,IAAkB,EAAE;AAE1B,MAAK,IAAM,KAAW,GAAU;EAC9B,IAAI,GACA;AAEJ,UAAQ,GAAR;GACE,KAAK;AAEH,IADA,IAAM,EAAQ,OAAO,MAAM,GAC3B,IAAQ,EAAQ,OAAO,QAAQ;AAC/B;GACF,KAAK;AAEH,IADA,IAAM,EAAQ,OAAO,QAAQ,MAAM,GACnC,IAAQ,EAAQ,OAAO,QAAQ,QAAQ;AACvC;GACF,KAAK;AAEH,IADA,IAAM,EAAQ,UAAU,GACxB,IAAQ,EAAQ,MAAM,QAAQ;AAC9B;GACF,KAAK;AAEH,IADA,IAAM,EAAQ,QACd,IAAQ,EAAU,EAAQ,OAAO;AACjC;GACF,KAAK;AAEH,IADA,IAAM,EAAQ,MACd,IAAQ,EAAQ;AAChB;GACF,QAEE,CADA,IAAM,GACN,IAAQ;;EAGZ,IAAI,IAAQ,EAAO,IAAI,EAAI;AAM3B,EALK,MACH,IAAQ;GAAE;GAAK;GAAO,UAAU,EAAE;GAAE,EACpC,EAAO,IAAI,GAAK,EAAM,EACtB,EAAM,KAAK,EAAI,GAEjB,EAAM,SAAS,KAAK,EAAQ;;CAG9B,IAAM,IAAS,EAAM,KAAK,MAAM,EAAO,IAAI,EAAE,CAAqB;AAOlE,QANA,EAAO,MAAM,GAAG,MAEV,EAAE,QAAQ,IAAe,IACzB,EAAE,QAAQ,IAAe,KACtB,EAAE,MAAM,cAAc,EAAE,MAAM,CACrC,EACK"}
@@ -1,9 +1,16 @@
1
1
  import "./SessionStatusBadge.js";
2
+ import "./EntityInfoPopover.js";
2
3
  import "./SessionCard.js";
3
4
  import "./SessionList.js";
4
5
  import "./SessionForm.js";
5
6
  import "./TerminalPanel.js";
6
7
  import "./SessionShareDialog.js";
8
+ import "./SessionGroup.js";
9
+ import "./groupSessions.js";
10
+ import "./sessionShortcuts.js";
11
+ import "./ShortcutCheatsheet.js";
12
+ import "./SessionsCommandPalette.js";
13
+ import "./SessionInfoPanel.js";
7
14
  import "./CollaboratorsList.js";
8
15
  import "./ControlStatusBar.js";
9
16
  import "./SharedTerminalPanel.js";
@@ -0,0 +1,149 @@
1
+ //#region src/components/sessions/sessionShortcuts.ts
2
+ var e = [
3
+ "Actions",
4
+ "Tabs",
5
+ "View",
6
+ "Navigation",
7
+ "Help"
8
+ ];
9
+ function t(e, t) {
10
+ return [
11
+ {
12
+ id: "new-session",
13
+ keysDisplay: "⌘/Ctrl + T",
14
+ category: "Actions",
15
+ description: t("sessions.sc.newSession", "New session"),
16
+ run: e.newSession
17
+ },
18
+ {
19
+ id: "open-palette",
20
+ keysDisplay: "⌘/Ctrl + K",
21
+ category: "Actions",
22
+ description: t("sessions.sc.palette", "Command palette"),
23
+ run: e.openPalette
24
+ },
25
+ {
26
+ id: "reconnect-active",
27
+ keysDisplay: "⌘/Ctrl + R",
28
+ category: "Actions",
29
+ description: t("sessions.sc.reconnect", "Reconnect active terminal"),
30
+ run: e.reconnectActive
31
+ },
32
+ {
33
+ id: "close-tab",
34
+ keysDisplay: "⌘/Ctrl + W",
35
+ category: "Tabs",
36
+ description: t("sessions.sc.closeTab", "Close active tab"),
37
+ run: e.closeActiveTab
38
+ },
39
+ {
40
+ id: "next-tab",
41
+ keysDisplay: "Ctrl + Tab",
42
+ category: "Tabs",
43
+ description: t("sessions.sc.nextTab", "Next tab")
44
+ },
45
+ {
46
+ id: "prev-tab",
47
+ keysDisplay: "Ctrl + Shift + Tab",
48
+ category: "Tabs",
49
+ description: t("sessions.sc.prevTab", "Previous tab")
50
+ },
51
+ {
52
+ id: "jump-tab",
53
+ keysDisplay: "⌘/Ctrl + 1…9",
54
+ category: "Tabs",
55
+ description: t("sessions.sc.jumpTab", "Jump to tab N")
56
+ },
57
+ {
58
+ id: "focus-search",
59
+ keysDisplay: "/",
60
+ category: "View",
61
+ description: t("sessions.sc.focusSearch", "Focus search"),
62
+ run: e.focusSearch
63
+ },
64
+ {
65
+ id: "cycle-view",
66
+ keysDisplay: "⌘/Ctrl + \\",
67
+ category: "View",
68
+ description: t("sessions.sc.cycleView", "Cycle terminal / grid / list"),
69
+ run: e.cycleView
70
+ },
71
+ {
72
+ id: "group-agent",
73
+ keysDisplay: "g a",
74
+ category: "View",
75
+ description: t("sessions.sc.groupAgent", "Group by agent"),
76
+ run: e.groupByAgent
77
+ },
78
+ {
79
+ id: "group-target",
80
+ keysDisplay: "g t",
81
+ category: "View",
82
+ description: t("sessions.sc.groupTarget", "Group by target"),
83
+ run: e.groupByTarget
84
+ },
85
+ {
86
+ id: "group-vibe",
87
+ keysDisplay: "g v",
88
+ category: "View",
89
+ description: t("sessions.sc.groupVibe", "Group by vibe"),
90
+ run: e.groupByVibe
91
+ },
92
+ {
93
+ id: "group-status",
94
+ keysDisplay: "g s",
95
+ category: "View",
96
+ description: t("sessions.sc.groupStatus", "Group by status"),
97
+ run: e.groupByStatus
98
+ },
99
+ {
100
+ id: "group-none",
101
+ keysDisplay: "g 0",
102
+ category: "View",
103
+ description: t("sessions.sc.groupNone", "Clear grouping"),
104
+ run: e.groupByNone
105
+ },
106
+ {
107
+ id: "toggle-fullscreen",
108
+ keysDisplay: "f",
109
+ category: "View",
110
+ description: t("sessions.sc.fullscreen", "Toggle terminal fullscreen"),
111
+ run: e.toggleFullscreen
112
+ },
113
+ {
114
+ id: "toggle-info",
115
+ keysDisplay: "i",
116
+ category: "View",
117
+ description: t("sessions.sc.infoPanel", "Toggle session info panel"),
118
+ run: e.toggleInfoPanel
119
+ },
120
+ {
121
+ id: "open-agent",
122
+ keysDisplay: "g shift+a",
123
+ category: "Navigation",
124
+ description: t("sessions.sc.openAgent", "Open active session's agent"),
125
+ run: e.openActiveAgent
126
+ },
127
+ {
128
+ id: "open-target",
129
+ keysDisplay: "g shift+t",
130
+ category: "Navigation",
131
+ description: t("sessions.sc.openTarget", "Open active session's target"),
132
+ run: e.openActiveTarget
133
+ },
134
+ {
135
+ id: "open-cheatsheet",
136
+ keysDisplay: "?",
137
+ category: "Help",
138
+ description: t("sessions.sc.cheatsheet", "Keyboard shortcuts"),
139
+ run: e.openCheatsheet
140
+ }
141
+ ];
142
+ }
143
+ function n(t) {
144
+ return e.map((e) => [e, t.filter((t) => t.category === e)]).filter(([, e]) => e.length > 0);
145
+ }
146
+ //#endregion
147
+ export { t as buildSessionShortcuts, n as groupShortcutsByCategory };
148
+
149
+ //# sourceMappingURL=sessionShortcuts.js.map
@@ -0,0 +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"}
@@ -4,34 +4,35 @@ import { Check as i, ChevronDown as a } from "lucide-react";
4
4
  import { AnimatePresence as o, motion as s } from "framer-motion";
5
5
  import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
6
  //#region src/components/shared/FilterDropdown.tsx
7
- function u({ label: u, options: d, value: f, onChange: p, className: m = "" }) {
8
- let h = e(), [g, _] = r(!1), v = n(null);
7
+ function u({ label: u, options: d, value: f, onChange: p, className: m = "", allValue: h = "all", allLabel: g, icon: _ }) {
8
+ let v = e(), [y, b] = r(!1), x = n(null), S = g ?? v("vibecontrols.shared.filterDropdown.all", "All");
9
9
  t(() => {
10
10
  function e(e) {
11
- v.current && !v.current.contains(e.target) && _(!1);
11
+ x.current && !x.current.contains(e.target) && b(!1);
12
12
  }
13
13
  return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
14
14
  }, []);
15
- let y = d.find((e) => e.value === f);
15
+ let C = d.find((e) => e.value === f);
16
16
  return /* @__PURE__ */ l("div", {
17
17
  className: `relative ${m}`,
18
- ref: v,
18
+ ref: x,
19
19
  children: [/* @__PURE__ */ l("button", {
20
20
  type: "button",
21
- onClick: () => _(!g),
21
+ onClick: () => b(!y),
22
22
  className: "inline-flex items-center gap-1.5 sm:gap-2 px-2.5 sm:px-3 py-1.5 sm:py-2 text-xs sm:text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary hover:bg-bg-elevated transition-colors whitespace-nowrap flex-shrink-0",
23
23
  children: [
24
+ _,
24
25
  /* @__PURE__ */ l("span", {
25
26
  className: "text-text-secondary",
26
27
  children: [u, ":"]
27
28
  }),
28
29
  /* @__PURE__ */ c("span", {
29
30
  className: "font-medium max-w-[80px] sm:max-w-none truncate",
30
- children: y?.label || h("vibecontrols.shared.filterDropdown.all", "All")
31
+ children: C?.label || S
31
32
  }),
32
- /* @__PURE__ */ c(a, { className: `size-3.5 sm:w-4 sm:h-4 transition-transform flex-shrink-0 ${g ? "rotate-180" : ""}` })
33
+ /* @__PURE__ */ c(a, { className: `size-3.5 sm:w-4 sm:h-4 transition-transform flex-shrink-0 ${y ? "rotate-180" : ""}` })
33
34
  ]
34
- }), /* @__PURE__ */ c(o, { children: g && /* @__PURE__ */ c(s.div, {
35
+ }), /* @__PURE__ */ c(o, { children: y && /* @__PURE__ */ c(s.div, {
35
36
  initial: {
36
37
  opacity: 0,
37
38
  y: -4,
@@ -62,14 +63,14 @@ function u({ label: u, options: d, value: f, onChange: p, className: m = "" }) {
62
63
  children: [/* @__PURE__ */ l("button", {
63
64
  type: "button",
64
65
  onClick: () => {
65
- p("all"), _(!1);
66
+ p(h), b(!1);
66
67
  },
67
68
  className: "flex items-center justify-between w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
68
- children: [h("vibecontrols.shared.filterDropdown.all", "All"), (f === null || f === "all") && /* @__PURE__ */ c(i, { className: "size-4 text-action-primary-bg" })]
69
- }), d.filter((e) => e.value !== "all").map((e) => /* @__PURE__ */ l("button", {
69
+ children: [S, (f === null || f === h) && /* @__PURE__ */ c(i, { className: "size-4 text-action-primary-bg" })]
70
+ }), d.filter((e) => e.value !== h).map((e) => /* @__PURE__ */ l("button", {
70
71
  type: "button",
71
72
  onClick: () => {
72
- p(e.value), _(!1);
73
+ p(e.value), b(!1);
73
74
  },
74
75
  className: "flex items-center justify-between w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
75
76
  children: [e.label, f === e.value && /* @__PURE__ */ c(i, { className: "size-4 text-action-primary-bg" })]
@@ -1 +1 @@
1
- {"version":3,"file":"FilterDropdown.js","names":[],"sources":["../../../src/components/shared/FilterDropdown.tsx"],"sourcesContent":["import { useState, useRef, useEffect } from 'react';\nimport { ChevronDown, Check } from 'lucide-react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface FilterOption {\n value: string;\n label: string;\n}\n\ninterface FilterDropdownProps {\n label: string;\n /**\n * Options to display after the hardcoded \"All\" button.\n * Any option with `value === 'all'` is automatically skipped — the component\n * renders its own \"All\" entry at the top, so callers may include or omit an\n * `{ value: 'all' }` entry without causing a duplicate.\n */\n options: FilterOption[];\n value: string | null;\n onChange: (value: string) => void;\n className?: string;\n}\n\nexport function FilterDropdown({\n label,\n options,\n value,\n onChange,\n className = '',\n}: FilterDropdownProps) {\n const tr = useTr();\n const [isOpen, setIsOpen] = useState(false);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {\n setIsOpen(false);\n }\n }\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, []);\n\n const selectedOption = options.find((opt) => opt.value === value);\n\n return (\n <div className={`relative ${className}`} ref={dropdownRef}>\n <button\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n className=\"inline-flex items-center gap-1.5 sm:gap-2 px-2.5 sm:px-3 py-1.5 sm:py-2 text-xs sm:text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary hover:bg-bg-elevated transition-colors whitespace-nowrap flex-shrink-0\"\n >\n <span className=\"text-text-secondary\">{label}:</span>\n <span className=\"font-medium max-w-[80px] sm:max-w-none truncate\">\n {selectedOption?.label || tr('vibecontrols.shared.filterDropdown.all', 'All')}\n </span>\n <ChevronDown\n className={`size-3.5 sm:w-4 sm:h-4 transition-transform flex-shrink-0 ${isOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n <AnimatePresence>\n {isOpen && (\n <motion.div\n initial={{ opacity: 0, y: -4, scale: 0.97 }}\n animate={{ opacity: 1, y: 0, scale: 1 }}\n exit={{ opacity: 0, y: -4, scale: 0.97 }}\n transition={{ duration: 0.15, ease: [0.4, 0, 0.2, 1] }}\n className=\"absolute z-50 mt-1 w-48 bg-bg-surface border border-border-default rounded-md shadow-lg\"\n >\n <div className=\"py-1\">\n <button\n type=\"button\"\n onClick={() => {\n onChange('all');\n setIsOpen(false);\n }}\n className=\"flex items-center justify-between w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n {tr('vibecontrols.shared.filterDropdown.all', 'All')}\n {(value === null || value === 'all') && (\n <Check className=\"size-4 text-action-primary-bg\" />\n )}\n </button>\n {options\n .filter((opt) => opt.value !== 'all')\n .map((option) => (\n <button\n type=\"button\"\n key={option.value}\n onClick={() => {\n onChange(option.value);\n setIsOpen(false);\n }}\n className=\"flex items-center justify-between w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n {option.label}\n {value === option.value && <Check className=\"size-4 text-action-primary-bg\" />}\n </button>\n ))}\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n );\n}\n"],"mappings":";;;;;;AAwBA,SAAgB,EAAe,EAC7B,UACA,YACA,UACA,aACA,eAAY,MACU;CACtB,IAAM,IAAK,GAAO,EACZ,CAAC,GAAQ,KAAa,EAAS,GAAM,EACrC,IAAc,EAAuB,KAAK;AAEhD,SAAgB;EACd,SAAS,EAAmB,GAAmB;AAC7C,GAAI,EAAY,WAAW,CAAC,EAAY,QAAQ,SAAS,EAAM,OAAe,IAC5E,EAAU,GAAM;;AAIpB,SADA,SAAS,iBAAiB,aAAa,EAAmB,QAC7C,SAAS,oBAAoB,aAAa,EAAmB;IACzE,EAAE,CAAC;CAEN,IAAM,IAAiB,EAAQ,MAAM,MAAQ,EAAI,UAAU,EAAM;AAEjE,QACE,kBAAC,OAAD;EAAK,WAAW,YAAY;EAAa,KAAK;YAA9C,CACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAU,CAAC,EAAO;GACjC,WAAU;aAHZ;IAKE,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAAuC,GAAM,IAAQ;;IACrD,kBAAC,QAAD;KAAM,WAAU;eACb,GAAgB,SAAS,EAAG,0CAA0C,MAAM;KACxE,CAAA;IACP,kBAAC,GAAD,EACE,WAAW,6DAA6D,IAAS,eAAe,MAChG,CAAA;IACK;MAET,kBAAC,GAAD,EAAA,UACG,KACC,kBAAC,EAAO,KAAR;GACE,SAAS;IAAE,SAAS;IAAG,GAAG;IAAI,OAAO;IAAM;GAC3C,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG,OAAO;IAAG;GACvC,MAAM;IAAE,SAAS;IAAG,GAAG;IAAI,OAAO;IAAM;GACxC,YAAY;IAAE,UAAU;IAAM,MAAM;KAAC;KAAK;KAAG;KAAK;KAAE;IAAE;GACtD,WAAU;aAEV,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe;AAEb,MADA,EAAS,MAAM,EACf,EAAU,GAAM;;KAElB,WAAU;eANZ,CAQG,EAAG,0CAA0C,MAAM,GAClD,MAAU,QAAQ,MAAU,UAC5B,kBAAC,GAAD,EAAO,WAAU,iCAAkC,CAAA,CAE9C;QACR,EACE,QAAQ,MAAQ,EAAI,UAAU,MAAM,CACpC,KAAK,MACJ,kBAAC,UAAD;KACE,MAAK;KAEL,eAAe;AAEb,MADA,EAAS,EAAO,MAAM,EACtB,EAAU,GAAM;;KAElB,WAAU;eAPZ,CASG,EAAO,OACP,MAAU,EAAO,SAAS,kBAAC,GAAD,EAAO,WAAU,iCAAkC,CAAA,CACvE;OATF,EAAO,MASL,CACT,CACA;;GACK,CAAA,EAEC,CAAA,CACd"}
1
+ {"version":3,"file":"FilterDropdown.js","names":[],"sources":["../../../src/components/shared/FilterDropdown.tsx"],"sourcesContent":["import { useState, useRef, useEffect } from 'react';\nimport { ChevronDown, Check } from 'lucide-react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface FilterOption {\n value: string;\n label: string;\n}\n\ninterface FilterDropdownProps {\n label: string;\n /**\n * Options to display after the sentinel \"All\" button.\n * Any option whose value equals `allValue` is automatically skipped — the\n * component renders its own sentinel entry at the top, so callers may\n * include or omit it without causing a duplicate.\n */\n options: FilterOption[];\n value: string | null;\n onChange: (value: string) => void;\n className?: string;\n /**\n * Value emitted by the sentinel \"All\" entry. Defaults to `'all'`.\n * Override (e.g. `'none'`) for controls where the reset state isn't \"all\".\n */\n allValue?: string;\n /** Label for the sentinel entry. Defaults to the shared \"All\" string. */\n allLabel?: string;\n /** Optional leading icon rendered before the label in the trigger. */\n icon?: React.ReactNode;\n}\n\nexport function FilterDropdown({\n label,\n options,\n value,\n onChange,\n className = '',\n allValue = 'all',\n allLabel,\n icon,\n}: FilterDropdownProps) {\n const tr = useTr();\n const [isOpen, setIsOpen] = useState(false);\n const dropdownRef = useRef<HTMLDivElement>(null);\n const sentinelLabel = allLabel ?? tr('vibecontrols.shared.filterDropdown.all', 'All');\n\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {\n setIsOpen(false);\n }\n }\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, []);\n\n const selectedOption = options.find((opt) => opt.value === value);\n\n return (\n <div className={`relative ${className}`} ref={dropdownRef}>\n <button\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n className=\"inline-flex items-center gap-1.5 sm:gap-2 px-2.5 sm:px-3 py-1.5 sm:py-2 text-xs sm:text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary hover:bg-bg-elevated transition-colors whitespace-nowrap flex-shrink-0\"\n >\n {icon}\n <span className=\"text-text-secondary\">{label}:</span>\n <span className=\"font-medium max-w-[80px] sm:max-w-none truncate\">\n {selectedOption?.label || sentinelLabel}\n </span>\n <ChevronDown\n className={`size-3.5 sm:w-4 sm:h-4 transition-transform flex-shrink-0 ${isOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n <AnimatePresence>\n {isOpen && (\n <motion.div\n initial={{ opacity: 0, y: -4, scale: 0.97 }}\n animate={{ opacity: 1, y: 0, scale: 1 }}\n exit={{ opacity: 0, y: -4, scale: 0.97 }}\n transition={{ duration: 0.15, ease: [0.4, 0, 0.2, 1] }}\n className=\"absolute z-50 mt-1 w-48 bg-bg-surface border border-border-default rounded-md shadow-lg\"\n >\n <div className=\"py-1\">\n <button\n type=\"button\"\n onClick={() => {\n onChange(allValue);\n setIsOpen(false);\n }}\n className=\"flex items-center justify-between w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n {sentinelLabel}\n {(value === null || value === allValue) && (\n <Check className=\"size-4 text-action-primary-bg\" />\n )}\n </button>\n {options\n .filter((opt) => opt.value !== allValue)\n .map((option) => (\n <button\n type=\"button\"\n key={option.value}\n onClick={() => {\n onChange(option.value);\n setIsOpen(false);\n }}\n className=\"flex items-center justify-between w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n {option.label}\n {value === option.value && <Check className=\"size-4 text-action-primary-bg\" />}\n </button>\n ))}\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n );\n}\n"],"mappings":";;;;;;AAiCA,SAAgB,EAAe,EAC7B,UACA,YACA,UACA,aACA,eAAY,IACZ,cAAW,OACX,aACA,WACsB;CACtB,IAAM,IAAK,GAAO,EACZ,CAAC,GAAQ,KAAa,EAAS,GAAM,EACrC,IAAc,EAAuB,KAAK,EAC1C,IAAgB,KAAY,EAAG,0CAA0C,MAAM;AAErF,SAAgB;EACd,SAAS,EAAmB,GAAmB;AAC7C,GAAI,EAAY,WAAW,CAAC,EAAY,QAAQ,SAAS,EAAM,OAAe,IAC5E,EAAU,GAAM;;AAIpB,SADA,SAAS,iBAAiB,aAAa,EAAmB,QAC7C,SAAS,oBAAoB,aAAa,EAAmB;IACzE,EAAE,CAAC;CAEN,IAAM,IAAiB,EAAQ,MAAM,MAAQ,EAAI,UAAU,EAAM;AAEjE,QACE,kBAAC,OAAD;EAAK,WAAW,YAAY;EAAa,KAAK;YAA9C,CACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAU,CAAC,EAAO;GACjC,WAAU;aAHZ;IAKG;IACD,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAAuC,GAAM,IAAQ;;IACrD,kBAAC,QAAD;KAAM,WAAU;eACb,GAAgB,SAAS;KACrB,CAAA;IACP,kBAAC,GAAD,EACE,WAAW,6DAA6D,IAAS,eAAe,MAChG,CAAA;IACK;MAET,kBAAC,GAAD,EAAA,UACG,KACC,kBAAC,EAAO,KAAR;GACE,SAAS;IAAE,SAAS;IAAG,GAAG;IAAI,OAAO;IAAM;GAC3C,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG,OAAO;IAAG;GACvC,MAAM;IAAE,SAAS;IAAG,GAAG;IAAI,OAAO;IAAM;GACxC,YAAY;IAAE,UAAU;IAAM,MAAM;KAAC;KAAK;KAAG;KAAK;KAAE;IAAE;GACtD,WAAU;aAEV,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe;AAEb,MADA,EAAS,EAAS,EAClB,EAAU,GAAM;;KAElB,WAAU;eANZ,CAQG,IACC,MAAU,QAAQ,MAAU,MAC5B,kBAAC,GAAD,EAAO,WAAU,iCAAkC,CAAA,CAE9C;QACR,EACE,QAAQ,MAAQ,EAAI,UAAU,EAAS,CACvC,KAAK,MACJ,kBAAC,UAAD;KACE,MAAK;KAEL,eAAe;AAEb,MADA,EAAS,EAAO,MAAM,EACtB,EAAU,GAAM;;KAElB,WAAU;eAPZ,CASG,EAAO,OACP,MAAU,EAAO,SAAS,kBAAC,GAAD,EAAO,WAAU,iCAAkC,CAAA,CACvE;OATF,EAAO,MASL,CACT,CACA;;GACK,CAAA,EAEC,CAAA,CACd"}
@@ -1 +1 @@
1
- {"version":3,"file":"LinkToVibePanel.js","names":[],"sources":["../../../src/components/shared/LinkToVibePanel.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useListVibesQuery } from '@/generated/wspace-operations';\nimport { VibeVisibility } from '@/generated/wspace-types';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Visibility for an entity that links to a vibe.\n *\n * - `PRIVATE`: only the creator can see it.\n * - `SHARED_WITH_VIBE`: anyone with access to the linked vibe can see it.\n *\n * Mirrors the proposed `EntityVisibility` enum on the wspace-vibecontrols-svc\n * schema. If the schema does not yet define this enum, callers should drop\n * the field from their mutation input (see the TODO in each form).\n */\nexport type LinkToVibeVisibility = 'PRIVATE' | 'SHARED_WITH_VIBE';\n\nexport interface LinkToVibeValue {\n vibeId: string | null;\n visibility: LinkToVibeVisibility;\n}\n\ninterface LinkToVibePanelProps {\n value: LinkToVibeValue;\n onChange: (value: LinkToVibeValue) => void;\n /** Optional label override */\n label?: string;\n /** Disable the panel entirely (e.g. while submitting). */\n disabled?: boolean;\n}\n\n/**\n * Optional \"Link to Vibe\" + visibility selector reusable across every entity\n * create form (VibeDeck / Action / Tunnel / Sandbox / AiPrompt / ScheduledJob).\n *\n * Forms thread the value into their mutation input as `vibeId` + `visibility`.\n * If the underlying schema does not yet define either field, the form should\n * omit it from the mutation; the resolver will pick a sensible default.\n */\nexport function LinkToVibePanel({\n value,\n onChange,\n label,\n disabled = false,\n}: LinkToVibePanelProps) {\n const tr = useTr();\n const { data, loading } = useListVibesQuery({\n variables: { pagination: { limit: 200 } },\n fetchPolicy: 'cache-first',\n });\n\n const vibeOptions = useMemo(() => {\n const items = data?.vibes?.items ?? [];\n return items.map((v: { id: string; name: string }) => ({ id: v.id, name: v.name }));\n }, [data?.vibes?.items]);\n\n const handleVibeChange = (vibeId: string | null) => {\n onChange({ vibeId, visibility: vibeId ? value.visibility : 'PRIVATE' });\n };\n\n const handleVisibilityChange = (visibility: LinkToVibeVisibility) => {\n onChange({ ...value, visibility });\n };\n\n return (\n <fieldset\n disabled={disabled}\n className=\"space-y-3 rounded-md border border-border-default bg-bg-surface p-3\"\n >\n <legend className=\"px-1 text-sm font-medium text-text-secondary\">\n {label || tr('vibecontrols.linkToVibe.title', 'Link to Vibe (optional)')}\n </legend>\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'vibecontrols.linkToVibe.description',\n 'Associate this entity with a vibe so it shows up under the vibe’s related tabs.'\n )}\n </p>\n\n <select\n value={value.vibeId ?? ''}\n onChange={(e) => handleVibeChange(e.target.value === '' ? null : e.target.value)}\n aria-label={tr('vibecontrols.linkToVibe.pickVibeAria', 'Pick a vibe to link')}\n className=\"w-full rounded-md border border-border-default bg-bg-sunken px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-action-primary-bg\"\n >\n <option value=\"\">\n {loading\n ? tr('vibecontrols.linkToVibe.loadingVibes', 'Loading vibes…')\n : tr('vibecontrols.linkToVibe.pickVibe', 'Pick a vibe to link…')}\n </option>\n {vibeOptions.map((opt: { id: string; name: string }) => (\n <option key={opt.id} value={opt.id}>\n {opt.name}\n </option>\n ))}\n </select>\n\n {value.vibeId && (\n <div className=\"space-y-2\">\n <p className=\"text-xs font-medium text-text-secondary\">\n {tr('vibecontrols.linkToVibe.visibilityLabel', 'Who can see it')}\n </p>\n <div\n role=\"radiogroup\"\n aria-label={tr('vibecontrols.linkToVibe.visibilityLabel', 'Who can see it')}\n className=\"space-y-1.5\"\n >\n <label className=\"flex cursor-pointer items-start gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"link-to-vibe-visibility\"\n value=\"PRIVATE\"\n checked={value.visibility === 'PRIVATE'}\n onChange={() => handleVisibilityChange('PRIVATE')}\n className=\"mt-1\"\n />\n <span className=\"text-text-primary\">\n {tr('vibecontrols.linkToVibe.private', 'Private to me')}\n </span>\n </label>\n <label className=\"flex cursor-pointer items-start gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"link-to-vibe-visibility\"\n value=\"SHARED_WITH_VIBE\"\n checked={value.visibility === 'SHARED_WITH_VIBE'}\n onChange={() => handleVisibilityChange('SHARED_WITH_VIBE')}\n className=\"mt-1\"\n />\n <span className=\"text-text-primary\">\n {tr(\n 'vibecontrols.linkToVibe.shared',\n 'Shared with everyone who has access to this vibe'\n )}\n </span>\n </label>\n </div>\n </div>\n )}\n </fieldset>\n );\n}\n\nexport const LINK_TO_VIBE_DEFAULT: LinkToVibeValue = {\n vibeId: null,\n visibility: 'PRIVATE',\n};\n\n/**\n * Maps the panel's string-union visibility onto the generated `VibeVisibility`\n * enum used by every mutation input that carries a vibe link. The string values\n * match the enum members 1:1, so this is a total, exhaustive mapping.\n */\nexport function toVibeVisibility(visibility: LinkToVibeVisibility): VibeVisibility {\n return visibility === 'SHARED_WITH_VIBE'\n ? VibeVisibility.SharedWithVibe\n : VibeVisibility.Private;\n}\n\n/**\n * Converts a `LinkToVibeValue` into the `{ vibeId, visibility }` fragment to\n * spread into a create/update mutation input. Returns an empty object when no\n * vibe is selected so the resolver applies its default (unlinked + PRIVATE),\n * avoiding a spurious `vibeId: null` in the request.\n */\nexport function linkToVibeInput(\n value: LinkToVibeValue\n): { vibeId?: string; visibility?: VibeVisibility } {\n if (!value.vibeId) return {};\n return { vibeId: value.vibeId, visibility: toVibeVisibility(value.visibility) };\n}\n"],"mappings":";;;;;;AAuCA,SAAgB,EAAgB,EAC9B,UACA,aACA,UACA,cAAW,MACY;CACvB,IAAM,IAAK,GAAO,EACZ,EAAE,SAAM,eAAY,EAAkB;EAC1C,WAAW,EAAE,YAAY,EAAE,OAAO,KAAK,EAAE;EACzC,aAAa;EACd,CAAC,EAEI,IAAc,SACJ,GAAM,OAAO,SAAS,EAAE,EACzB,KAAK,OAAqC;EAAE,IAAI,EAAE;EAAI,MAAM,EAAE;EAAM,EAAE,EAClF,CAAC,GAAM,OAAO,MAAM,CAAC,EAElB,KAAoB,MAA0B;AAClD,IAAS;GAAE;GAAQ,YAAY,IAAS,EAAM,aAAa;GAAW,CAAC;IAGnE,KAA0B,MAAqC;AACnE,IAAS;GAAE,GAAG;GAAO;GAAY,CAAC;;AAGpC,QACE,kBAAC,YAAD;EACY;EACV,WAAU;YAFZ;GAIE,kBAAC,UAAD;IAAQ,WAAU;cACf,KAAS,EAAG,iCAAiC,0BAA0B;IACjE,CAAA;GACT,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,uCACA,kFACD;IACC,CAAA;GAEJ,kBAAC,UAAD;IACE,OAAO,EAAM,UAAU;IACvB,WAAW,MAAM,EAAiB,EAAE,OAAO,UAAU,KAAK,OAAO,EAAE,OAAO,MAAM;IAChF,cAAY,EAAG,wCAAwC,sBAAsB;IAC7E,WAAU;cAJZ,CAME,kBAAC,UAAD;KAAQ,OAAM;eACX,IACG,EAAG,wCAAwC,iBAAiB,GAC5D,EAAG,oCAAoC,uBAAuB;KAC3D,CAAA,EACR,EAAY,KAAK,MAChB,kBAAC,UAAD;KAAqB,OAAO,EAAI;eAC7B,EAAI;KACE,EAFI,EAAI,GAER,CACT,CACK;;GAER,EAAM,UACL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,2CAA2C,iBAAiB;KAC9D,CAAA,EACJ,kBAAC,OAAD;KACE,MAAK;KACL,cAAY,EAAG,2CAA2C,iBAAiB;KAC3E,WAAU;eAHZ,CAKE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD;OACE,MAAK;OACL,MAAK;OACL,OAAM;OACN,SAAS,EAAM,eAAe;OAC9B,gBAAgB,EAAuB,UAAU;OACjD,WAAU;OACV,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,mCAAmC,gBAAgB;OAClD,CAAA,CACD;SACR,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD;OACE,MAAK;OACL,MAAK;OACL,OAAM;OACN,SAAS,EAAM,eAAe;OAC9B,gBAAgB,EAAuB,mBAAmB;OAC1D,WAAU;OACV,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EACC,kCACA,mDACD;OACI,CAAA,CACD;QACJ;OACF;;GAEC;;;AAIf,IAAa,IAAwC;CACnD,QAAQ;CACR,YAAY;CACb;AAOD,SAAgB,EAAiB,GAAkD;AACjF,QAAO,MAAe,qBAClB,EAAe,iBACf,EAAe;;AASrB,SAAgB,EACd,GACkD;AAElD,QADK,EAAM,SACJ;EAAE,QAAQ,EAAM;EAAQ,YAAY,EAAiB,EAAM,WAAW;EAAE,GADrD,EAAE"}
1
+ {"version":3,"file":"LinkToVibePanel.js","names":[],"sources":["../../../src/components/shared/LinkToVibePanel.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useListVibesQuery } from '@/generated/wspace-operations';\nimport { VibeVisibility } from '@/generated/wspace-types';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Visibility for an entity that links to a vibe.\n *\n * - `PRIVATE`: only the creator can see it.\n * - `SHARED_WITH_VIBE`: anyone with access to the linked vibe can see it.\n *\n * Mirrors the proposed `EntityVisibility` enum on the wspace-vibecontrols-svc\n * schema. If the schema does not yet define this enum, callers should drop\n * the field from their mutation input (see the TODO in each form).\n */\nexport type LinkToVibeVisibility = 'PRIVATE' | 'SHARED_WITH_VIBE';\n\nexport interface LinkToVibeValue {\n vibeId: string | null;\n visibility: LinkToVibeVisibility;\n}\n\ninterface LinkToVibePanelProps {\n value: LinkToVibeValue;\n onChange: (value: LinkToVibeValue) => void;\n /** Optional label override */\n label?: string;\n /** Disable the panel entirely (e.g. while submitting). */\n disabled?: boolean;\n}\n\n/**\n * Optional \"Link to Vibe\" + visibility selector reusable across every entity\n * create form (VibeDeck / Action / Tunnel / Sandbox / AiPrompt / ScheduledJob).\n *\n * Forms thread the value into their mutation input as `vibeId` + `visibility`.\n * If the underlying schema does not yet define either field, the form should\n * omit it from the mutation; the resolver will pick a sensible default.\n */\nexport function LinkToVibePanel({\n value,\n onChange,\n label,\n disabled = false,\n}: LinkToVibePanelProps) {\n const tr = useTr();\n const { data, loading } = useListVibesQuery({\n variables: { pagination: { limit: 200 } },\n fetchPolicy: 'cache-first',\n });\n\n const vibeOptions = useMemo(() => {\n const items = data?.vibes?.items ?? [];\n return items.map((v: { id: string; name: string }) => ({ id: v.id, name: v.name }));\n }, [data?.vibes?.items]);\n\n const handleVibeChange = (vibeId: string | null) => {\n onChange({ vibeId, visibility: vibeId ? value.visibility : 'PRIVATE' });\n };\n\n const handleVisibilityChange = (visibility: LinkToVibeVisibility) => {\n onChange({ ...value, visibility });\n };\n\n return (\n <fieldset\n disabled={disabled}\n className=\"space-y-3 rounded-md border border-border-default bg-bg-surface p-3\"\n >\n <legend className=\"px-1 text-sm font-medium text-text-secondary\">\n {label || tr('vibecontrols.linkToVibe.title', 'Link to Vibe (optional)')}\n </legend>\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'vibecontrols.linkToVibe.description',\n 'Associate this entity with a vibe so it shows up under the vibe’s related tabs.'\n )}\n </p>\n\n <select\n value={value.vibeId ?? ''}\n onChange={(e) => handleVibeChange(e.target.value === '' ? null : e.target.value)}\n aria-label={tr('vibecontrols.linkToVibe.pickVibeAria', 'Pick a vibe to link')}\n className=\"w-full rounded-md border border-border-default bg-bg-sunken px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-action-primary-bg\"\n >\n <option value=\"\">\n {loading\n ? tr('vibecontrols.linkToVibe.loadingVibes', 'Loading vibes…')\n : tr('vibecontrols.linkToVibe.pickVibe', 'Pick a vibe to link…')}\n </option>\n {vibeOptions.map((opt: { id: string; name: string }) => (\n <option key={opt.id} value={opt.id}>\n {opt.name}\n </option>\n ))}\n </select>\n\n {value.vibeId && (\n <div className=\"space-y-2\">\n <p className=\"text-xs font-medium text-text-secondary\">\n {tr('vibecontrols.linkToVibe.visibilityLabel', 'Who can see it')}\n </p>\n <div\n role=\"radiogroup\"\n aria-label={tr('vibecontrols.linkToVibe.visibilityLabel', 'Who can see it')}\n className=\"space-y-1.5\"\n >\n <label className=\"flex cursor-pointer items-start gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"link-to-vibe-visibility\"\n value=\"PRIVATE\"\n checked={value.visibility === 'PRIVATE'}\n onChange={() => handleVisibilityChange('PRIVATE')}\n className=\"mt-1\"\n />\n <span className=\"text-text-primary\">\n {tr('vibecontrols.linkToVibe.private', 'Private to me')}\n </span>\n </label>\n <label className=\"flex cursor-pointer items-start gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"link-to-vibe-visibility\"\n value=\"SHARED_WITH_VIBE\"\n checked={value.visibility === 'SHARED_WITH_VIBE'}\n onChange={() => handleVisibilityChange('SHARED_WITH_VIBE')}\n className=\"mt-1\"\n />\n <span className=\"text-text-primary\">\n {tr(\n 'vibecontrols.linkToVibe.shared',\n 'Shared with everyone who has access to this vibe'\n )}\n </span>\n </label>\n </div>\n </div>\n )}\n </fieldset>\n );\n}\n\nexport const LINK_TO_VIBE_DEFAULT: LinkToVibeValue = {\n vibeId: null,\n visibility: 'PRIVATE',\n};\n\n/**\n * Maps the panel's string-union visibility onto the generated `VibeVisibility`\n * enum used by every mutation input that carries a vibe link. The string values\n * match the enum members 1:1, so this is a total, exhaustive mapping.\n */\nexport function toVibeVisibility(visibility: LinkToVibeVisibility): VibeVisibility {\n return visibility === 'SHARED_WITH_VIBE' ? VibeVisibility.SharedWithVibe : VibeVisibility.Private;\n}\n\n/**\n * Converts a `LinkToVibeValue` into the `{ vibeId, visibility }` fragment to\n * spread into a create/update mutation input. Returns an empty object when no\n * vibe is selected so the resolver applies its default (unlinked + PRIVATE),\n * avoiding a spurious `vibeId: null` in the request.\n */\nexport function linkToVibeInput(value: LinkToVibeValue): {\n vibeId?: string;\n visibility?: VibeVisibility;\n} {\n if (!value.vibeId) return {};\n return { vibeId: value.vibeId, visibility: toVibeVisibility(value.visibility) };\n}\n"],"mappings":";;;;;;AAuCA,SAAgB,EAAgB,EAC9B,UACA,aACA,UACA,cAAW,MACY;CACvB,IAAM,IAAK,GAAO,EACZ,EAAE,SAAM,eAAY,EAAkB;EAC1C,WAAW,EAAE,YAAY,EAAE,OAAO,KAAK,EAAE;EACzC,aAAa;EACd,CAAC,EAEI,IAAc,SACJ,GAAM,OAAO,SAAS,EAAE,EACzB,KAAK,OAAqC;EAAE,IAAI,EAAE;EAAI,MAAM,EAAE;EAAM,EAAE,EAClF,CAAC,GAAM,OAAO,MAAM,CAAC,EAElB,KAAoB,MAA0B;AAClD,IAAS;GAAE;GAAQ,YAAY,IAAS,EAAM,aAAa;GAAW,CAAC;IAGnE,KAA0B,MAAqC;AACnE,IAAS;GAAE,GAAG;GAAO;GAAY,CAAC;;AAGpC,QACE,kBAAC,YAAD;EACY;EACV,WAAU;YAFZ;GAIE,kBAAC,UAAD;IAAQ,WAAU;cACf,KAAS,EAAG,iCAAiC,0BAA0B;IACjE,CAAA;GACT,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,uCACA,kFACD;IACC,CAAA;GAEJ,kBAAC,UAAD;IACE,OAAO,EAAM,UAAU;IACvB,WAAW,MAAM,EAAiB,EAAE,OAAO,UAAU,KAAK,OAAO,EAAE,OAAO,MAAM;IAChF,cAAY,EAAG,wCAAwC,sBAAsB;IAC7E,WAAU;cAJZ,CAME,kBAAC,UAAD;KAAQ,OAAM;eACX,IACG,EAAG,wCAAwC,iBAAiB,GAC5D,EAAG,oCAAoC,uBAAuB;KAC3D,CAAA,EACR,EAAY,KAAK,MAChB,kBAAC,UAAD;KAAqB,OAAO,EAAI;eAC7B,EAAI;KACE,EAFI,EAAI,GAER,CACT,CACK;;GAER,EAAM,UACL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,2CAA2C,iBAAiB;KAC9D,CAAA,EACJ,kBAAC,OAAD;KACE,MAAK;KACL,cAAY,EAAG,2CAA2C,iBAAiB;KAC3E,WAAU;eAHZ,CAKE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD;OACE,MAAK;OACL,MAAK;OACL,OAAM;OACN,SAAS,EAAM,eAAe;OAC9B,gBAAgB,EAAuB,UAAU;OACjD,WAAU;OACV,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,mCAAmC,gBAAgB;OAClD,CAAA,CACD;SACR,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD;OACE,MAAK;OACL,MAAK;OACL,OAAM;OACN,SAAS,EAAM,eAAe;OAC9B,gBAAgB,EAAuB,mBAAmB;OAC1D,WAAU;OACV,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EACC,kCACA,mDACD;OACI,CAAA,CACD;QACJ;OACF;;GAEC;;;AAIf,IAAa,IAAwC;CACnD,QAAQ;CACR,YAAY;CACb;AAOD,SAAgB,EAAiB,GAAkD;AACjF,QAAO,MAAe,qBAAqB,EAAe,iBAAiB,EAAe;;AAS5F,SAAgB,EAAgB,GAG9B;AAEA,QADK,EAAM,SACJ;EAAE,QAAQ,EAAM;EAAQ,YAAY,EAAiB,EAAM,WAAW;EAAE,GADrD,EAAE"}
@@ -1 +1 @@
1
- {"version":3,"file":"VibeLinkedEntitiesPanel.js","names":[],"sources":["../../../src/components/vibe/VibeLinkedEntitiesPanel.tsx"],"sourcesContent":["import { Link2 } from 'lucide-react';\nimport { EmptyState, LoadingState, ErrorState } from '@/components/shared';\nimport { useTr } from '@/shared/hooks/useTr';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\nimport {\n useGetVibeLinkedEntitiesQuery,\n useGetVibeLinkedSandboxesQuery,\n useGetVibeLinkedScheduledJobsQuery,\n type GetVibeLinkedEntitiesQuery,\n type GetVibeLinkedSandboxesQuery,\n type GetVibeLinkedScheduledJobsQuery,\n} from '@/generated/wspace-operations';\n\ntype LinkedSandbox = GetVibeLinkedSandboxesQuery['listSandboxes']['items'][number];\ntype LinkedScheduledJob = GetVibeLinkedScheduledJobsQuery['scheduledJobs']['jobs'][number];\n\ntype LinkedEntities = NonNullable<\n NonNullable<GetVibeLinkedEntitiesQuery['vibe']>['linkedEntities']\n>;\n\nexport type LinkedEntityKind =\n | 'vibedecks'\n | 'actions'\n | 'scheduledJobs'\n | 'sandboxes'\n | 'aiPrompts'\n | 'aiContexts'\n | 'instructionFiles'\n | 'tunnels';\n\ninterface VibeLinkedEntitiesPanelProps {\n vibeId: string;\n kind: LinkedEntityKind;\n}\n\nconst KIND_LABELS: Record<LinkedEntityKind, { singular: string; plural: string }> = {\n vibedecks: { singular: 'Vibe Deck', plural: 'Vibe Decks' },\n actions: { singular: 'Action', plural: 'Actions' },\n scheduledJobs: { singular: 'Scheduled Job', plural: 'Scheduled Jobs' },\n sandboxes: { singular: 'Sandbox', plural: 'Sandboxes' },\n aiPrompts: { singular: 'AI Prompt', plural: 'AI Prompts' },\n aiContexts: { singular: 'AI Context', plural: 'AI Contexts' },\n instructionFiles: { singular: 'Instruction File', plural: 'Instruction Files' },\n tunnels: { singular: 'Tunnel', plural: 'Tunnels' },\n};\n\n// Kinds backed by Vibe.linkedEntities on wspace-vibecontrols-svc. scheduledJobs +\n// sandboxes live in other subgraphs and are rendered by the dedicated\n// sub-panels below (listSandboxes / scheduledJobs filtered by vibeId).\nconst LOCAL_KINDS = new Set<LinkedEntityKind>([\n 'vibedecks',\n 'actions',\n 'aiPrompts',\n 'aiContexts',\n 'instructionFiles',\n 'tunnels',\n]);\n\ninterface LinkedRow {\n id: string;\n label: string;\n badge: string;\n visibility: string;\n}\n\nfunction visibilityLabel(visibility: string): string {\n return visibility === 'SHARED_WITH_VIBE' ? 'Shared' : 'Private';\n}\n\n/**\n * Shared presentational shell for a list of linked rows: handles loading,\n * error, empty, and the populated list. Keeps the three data sources\n * (linkedEntities, sandboxes, scheduledJobs) rendering identically.\n */\nfunction LinkedRowsView({\n labels,\n loading,\n hasData,\n error,\n rows,\n emptyDescription,\n}: {\n labels: { singular: string; plural: string };\n loading: boolean;\n hasData: boolean;\n error?: { message: string };\n rows: LinkedRow[];\n emptyDescription: string;\n}) {\n if (loading && !hasData) {\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-6\">\n <LoadingState message={`Loading ${labels.plural.toLowerCase()}…`} />\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-6\">\n <ErrorState title={`Failed to load ${labels.plural.toLowerCase()}`} message={error.message} />\n </div>\n );\n }\n\n if (rows.length === 0) {\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-6\">\n <EmptyState\n icon={<Link2 size={48} className=\"text-text-secondary\" />}\n title={`No ${labels.plural.toLowerCase()} linked yet`}\n description={emptyDescription}\n />\n </div>\n );\n }\n\n return (\n <div\n className=\"rounded-lg border border-border-default bg-bg-surface\"\n data-testid=\"vibe-linked-entities\"\n >\n <ul className=\"divide-y divide-border-default\">\n {rows.map((row) => (\n <li key={row.id} className=\"flex items-center justify-between gap-3 px-4 py-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"truncate text-sm font-medium text-text-primary\">{row.label}</span>\n <span className=\"flex-shrink-0 rounded bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {row.badge}\n </span>\n </div>\n <span\n className={`flex-shrink-0 rounded px-2 py-0.5 text-xs ${\n row.visibility === 'SHARED_WITH_VIBE'\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-tertiary'\n }`}\n >\n {visibilityLabel(row.visibility)}\n </span>\n </li>\n ))}\n </ul>\n </div>\n );\n}\n\n/** Sandboxes linked to the vibe — fetched from the sandbox subgraph. */\nfunction VibeLinkedSandboxes({ vibeId }: { vibeId: string }) {\n const tr = useTr();\n const labels = KIND_LABELS.sandboxes;\n const { workspaceId, projectId } = useVibeControls();\n\n const { data, loading, error } = useGetVibeLinkedSandboxesQuery({\n variables: {\n context: { workspaceId: workspaceId ?? undefined, projectId: projectId ?? undefined },\n vibeId,\n },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n const rows: LinkedRow[] = (data?.listSandboxes?.items ?? []).map((s: LinkedSandbox) => ({\n id: s.id,\n label: s.name,\n badge: String(s.status),\n visibility: s.visibility,\n }));\n\n return (\n <LinkedRowsView\n labels={labels}\n loading={loading}\n hasData={!!data}\n error={error}\n rows={rows}\n emptyDescription={tr(\n 'vibeDetails.linked.empty',\n `Link a ${labels.singular.toLowerCase()} to this vibe and set it to \"Shared with vibe\" to make it visible to everyone with access.`\n )}\n />\n );\n}\n\n/** Scheduled jobs linked to the vibe — fetched from the scheduler subgraph. */\nfunction VibeLinkedScheduledJobs({ vibeId }: { vibeId: string }) {\n const tr = useTr();\n const labels = KIND_LABELS.scheduledJobs;\n const { workspaceId } = useVibeControls();\n\n const { data, loading, error } = useGetVibeLinkedScheduledJobsQuery({\n variables: { workspaceId: workspaceId ?? '', vibeId },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n const rows: LinkedRow[] = (data?.scheduledJobs?.jobs ?? []).map((j: LinkedScheduledJob) => ({\n id: j.id,\n label: j.name ?? j.id,\n badge: String(j.status),\n visibility: j.visibility,\n }));\n\n return (\n <LinkedRowsView\n labels={labels}\n loading={loading}\n hasData={!!data}\n error={error}\n rows={rows}\n emptyDescription={tr(\n 'vibeDetails.linked.empty',\n `Link a ${labels.singular.toLowerCase()} to this vibe and set it to \"Shared with vibe\" to make it visible to everyone with access.`\n )}\n />\n );\n}\n\n/**\n * Renders the entities linked to a vibe (visibility-filtered server-side) for a\n * given kind. Local kinds come from `Vibe.linkedEntities`; sandboxes +\n * scheduledJobs are fetched from their own subgraphs via dedicated vibeId\n * filters. SHARED_WITH_VIBE entities are visible to every member with access to\n * the vibe; PRIVATE only to their owner.\n */\nexport function VibeLinkedEntitiesPanel({ vibeId, kind }: VibeLinkedEntitiesPanelProps) {\n const tr = useTr();\n const labels = KIND_LABELS[kind];\n const isLocal = LOCAL_KINDS.has(kind);\n\n const { data, loading, error } = useGetVibeLinkedEntitiesQuery({\n variables: { id: vibeId },\n skip: !isLocal,\n fetchPolicy: 'cache-and-network',\n });\n\n if (kind === 'sandboxes') {\n return <VibeLinkedSandboxes vibeId={vibeId} />;\n }\n if (kind === 'scheduledJobs') {\n return <VibeLinkedScheduledJobs vibeId={vibeId} />;\n }\n\n const linked: LinkedEntities | undefined = data?.vibe?.linkedEntities ?? undefined;\n const rows: LinkedRow[] = (() => {\n if (!linked) return [];\n switch (kind) {\n case 'vibedecks':\n return linked.vibeDecks.map((d) => ({\n id: d.id,\n label: d.name,\n badge: 'Deck',\n visibility: d.visibility,\n }));\n case 'actions':\n return linked.actions.map((a) => ({\n id: a.id,\n label: a.name,\n badge: 'Action',\n visibility: a.visibility,\n }));\n case 'aiPrompts':\n return linked.aiPrompts.map((p) => ({\n id: p.id,\n label: p.name,\n badge: String(p.category),\n visibility: p.visibility,\n }));\n case 'aiContexts':\n return linked.aiContexts.map((c) => ({\n id: c.id,\n label: c.name,\n badge: String(c.type),\n visibility: c.visibility,\n }));\n case 'instructionFiles':\n return linked.instructionFiles.map((f) => ({\n id: f.id,\n label: f.effectiveFilename,\n badge: String(f.scope),\n visibility: f.visibility,\n }));\n case 'tunnels':\n return linked.tunnels.map((t) => ({\n id: t.id,\n label: t.name,\n badge: 'Tunnel',\n visibility: t.visibility,\n }));\n default:\n return [];\n }\n })();\n\n return (\n <LinkedRowsView\n labels={labels}\n loading={loading}\n hasData={!!data}\n error={error}\n rows={rows}\n emptyDescription={tr(\n 'vibeDetails.linked.empty',\n `Link a ${labels.singular.toLowerCase()} to this vibe and set it to \"Shared with vibe\" to make it visible to everyone with access.`\n )}\n />\n );\n}\n"],"mappings":";;;;;;;;;;AAmCA,IAAM,IAA8E;CAClF,WAAW;EAAE,UAAU;EAAa,QAAQ;EAAc;CAC1D,SAAS;EAAE,UAAU;EAAU,QAAQ;EAAW;CAClD,eAAe;EAAE,UAAU;EAAiB,QAAQ;EAAkB;CACtE,WAAW;EAAE,UAAU;EAAW,QAAQ;EAAa;CACvD,WAAW;EAAE,UAAU;EAAa,QAAQ;EAAc;CAC1D,YAAY;EAAE,UAAU;EAAc,QAAQ;EAAe;CAC7D,kBAAkB;EAAE,UAAU;EAAoB,QAAQ;EAAqB;CAC/E,SAAS;EAAE,UAAU;EAAU,QAAQ;EAAW;CACnD,EAKK,IAAc,IAAI,IAAsB;CAC5C;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AASF,SAAS,EAAgB,GAA4B;AACnD,QAAO,MAAe,qBAAqB,WAAW;;AAQxD,SAAS,EAAe,EACtB,WACA,YACA,YACA,UACA,SACA,uBAQC;AA6BD,QA5BI,KAAW,CAAC,IAEZ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,WAAW,EAAO,OAAO,aAAa,CAAC,IAAM,CAAA;EAChE,CAAA,GAIN,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GAAY,OAAO,kBAAkB,EAAO,OAAO,aAAa;GAAI,SAAS,EAAM;GAAW,CAAA;EAC1F,CAAA,GAIN,EAAK,WAAW,IAEhB,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD;IAAO,MAAM;IAAI,WAAU;IAAwB,CAAA;GACzD,OAAO,MAAM,EAAO,OAAO,aAAa,CAAC;GACzC,aAAa;GACb,CAAA;EACE,CAAA,GAKR,kBAAC,OAAD;EACE,WAAU;EACV,eAAY;YAEZ,kBAAC,MAAD;GAAI,WAAU;aACX,EAAK,KAAK,MACT,kBAAC,MAAD;IAAiB,WAAU;cAA3B,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAkD,EAAI;MAAa,CAAA,EACnF,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAI;MACA,CAAA,CACH;QACN,kBAAC,QAAD;KACE,WAAW,6CACT,EAAI,eAAe,qBACf,kDACA;eAGL,EAAgB,EAAI,WAAW;KAC3B,CAAA,CACJ;MAhBI,EAAI,GAgBR,CACL;GACC,CAAA;EACD,CAAA;;AAKV,SAAS,EAAoB,EAAE,aAA8B;CAC3D,IAAM,IAAK,GAAO,EACZ,IAAS,EAAY,WACrB,EAAE,gBAAa,iBAAc,GAAiB,EAE9C,EAAE,SAAM,YAAS,aAAU,EAA+B;EAC9D,WAAW;GACT,SAAS;IAAE,aAAa,KAAe,KAAA;IAAW,WAAW,KAAa,KAAA;IAAW;GACrF;GACD;EACD,MAAM,CAAC;EACP,aAAa;EACd,CAAC,EAEI,KAAqB,GAAM,eAAe,SAAS,EAAE,EAAE,KAAK,OAAsB;EACtF,IAAI,EAAE;EACN,OAAO,EAAE;EACT,OAAO,OAAO,EAAE,OAAO;EACvB,YAAY,EAAE;EACf,EAAE;AAEH,QACE,kBAAC,GAAD;EACU;EACC;EACT,SAAS,CAAC,CAAC;EACJ;EACD;EACN,kBAAkB,EAChB,4BACA,UAAU,EAAO,SAAS,aAAa,CAAC,4FACzC;EACD,CAAA;;AAKN,SAAS,EAAwB,EAAE,aAA8B;CAC/D,IAAM,IAAK,GAAO,EACZ,IAAS,EAAY,eACrB,EAAE,mBAAgB,GAAiB,EAEnC,EAAE,SAAM,YAAS,aAAU,EAAmC;EAClE,WAAW;GAAE,aAAa,KAAe;GAAI;GAAQ;EACrD,MAAM,CAAC;EACP,aAAa;EACd,CAAC,EAEI,KAAqB,GAAM,eAAe,QAAQ,EAAE,EAAE,KAAK,OAA2B;EAC1F,IAAI,EAAE;EACN,OAAO,EAAE,QAAQ,EAAE;EACnB,OAAO,OAAO,EAAE,OAAO;EACvB,YAAY,EAAE;EACf,EAAE;AAEH,QACE,kBAAC,GAAD;EACU;EACC;EACT,SAAS,CAAC,CAAC;EACJ;EACD;EACN,kBAAkB,EAChB,4BACA,UAAU,EAAO,SAAS,aAAa,CAAC,4FACzC;EACD,CAAA;;AAWN,SAAgB,EAAwB,EAAE,WAAQ,WAAsC;CACtF,IAAM,IAAK,GAAO,EACZ,IAAS,EAAY,IACrB,IAAU,EAAY,IAAI,EAAK,EAE/B,EAAE,SAAM,YAAS,aAAU,EAA8B;EAC7D,WAAW,EAAE,IAAI,GAAQ;EACzB,MAAM,CAAC;EACP,aAAa;EACd,CAAC;AAEF,KAAI,MAAS,YACX,QAAO,kBAAC,GAAD,EAA6B,WAAU,CAAA;AAEhD,KAAI,MAAS,gBACX,QAAO,kBAAC,GAAD,EAAiC,WAAU,CAAA;CAGpD,IAAM,IAAqC,GAAM,MAAM,kBAAkB,KAAA,GACnE,WAA2B;AAC/B,MAAI,CAAC,EAAQ,QAAO,EAAE;AACtB,UAAQ,GAAR;GACE,KAAK,YACH,QAAO,EAAO,UAAU,KAAK,OAAO;IAClC,IAAI,EAAE;IACN,OAAO,EAAE;IACT,OAAO;IACP,YAAY,EAAE;IACf,EAAE;GACL,KAAK,UACH,QAAO,EAAO,QAAQ,KAAK,OAAO;IAChC,IAAI,EAAE;IACN,OAAO,EAAE;IACT,OAAO;IACP,YAAY,EAAE;IACf,EAAE;GACL,KAAK,YACH,QAAO,EAAO,UAAU,KAAK,OAAO;IAClC,IAAI,EAAE;IACN,OAAO,EAAE;IACT,OAAO,OAAO,EAAE,SAAS;IACzB,YAAY,EAAE;IACf,EAAE;GACL,KAAK,aACH,QAAO,EAAO,WAAW,KAAK,OAAO;IACnC,IAAI,EAAE;IACN,OAAO,EAAE;IACT,OAAO,OAAO,EAAE,KAAK;IACrB,YAAY,EAAE;IACf,EAAE;GACL,KAAK,mBACH,QAAO,EAAO,iBAAiB,KAAK,OAAO;IACzC,IAAI,EAAE;IACN,OAAO,EAAE;IACT,OAAO,OAAO,EAAE,MAAM;IACtB,YAAY,EAAE;IACf,EAAE;GACL,KAAK,UACH,QAAO,EAAO,QAAQ,KAAK,OAAO;IAChC,IAAI,EAAE;IACN,OAAO,EAAE;IACT,OAAO;IACP,YAAY,EAAE;IACf,EAAE;GACL,QACE,QAAO,EAAE;;KAEX;AAEJ,QACE,kBAAC,GAAD;EACU;EACC;EACT,SAAS,CAAC,CAAC;EACJ;EACD;EACN,kBAAkB,EAChB,4BACA,UAAU,EAAO,SAAS,aAAa,CAAC,4FACzC;EACD,CAAA"}
1
+ {"version":3,"file":"VibeLinkedEntitiesPanel.js","names":[],"sources":["../../../src/components/vibe/VibeLinkedEntitiesPanel.tsx"],"sourcesContent":["import { Link2 } from 'lucide-react';\nimport { EmptyState, LoadingState, ErrorState } from '@/components/shared';\nimport { useTr } from '@/shared/hooks/useTr';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\nimport {\n useGetVibeLinkedEntitiesQuery,\n useGetVibeLinkedSandboxesQuery,\n useGetVibeLinkedScheduledJobsQuery,\n type GetVibeLinkedEntitiesQuery,\n type GetVibeLinkedSandboxesQuery,\n type GetVibeLinkedScheduledJobsQuery,\n} from '@/generated/wspace-operations';\n\ntype LinkedSandbox = GetVibeLinkedSandboxesQuery['listSandboxes']['items'][number];\ntype LinkedScheduledJob = GetVibeLinkedScheduledJobsQuery['scheduledJobs']['jobs'][number];\n\ntype LinkedEntities = NonNullable<\n NonNullable<GetVibeLinkedEntitiesQuery['vibe']>['linkedEntities']\n>;\n\nexport type LinkedEntityKind =\n | 'vibedecks'\n | 'actions'\n | 'scheduledJobs'\n | 'sandboxes'\n | 'aiPrompts'\n | 'aiContexts'\n | 'instructionFiles'\n | 'tunnels';\n\ninterface VibeLinkedEntitiesPanelProps {\n vibeId: string;\n kind: LinkedEntityKind;\n}\n\nconst KIND_LABELS: Record<LinkedEntityKind, { singular: string; plural: string }> = {\n vibedecks: { singular: 'Vibe Deck', plural: 'Vibe Decks' },\n actions: { singular: 'Action', plural: 'Actions' },\n scheduledJobs: { singular: 'Scheduled Job', plural: 'Scheduled Jobs' },\n sandboxes: { singular: 'Sandbox', plural: 'Sandboxes' },\n aiPrompts: { singular: 'AI Prompt', plural: 'AI Prompts' },\n aiContexts: { singular: 'AI Context', plural: 'AI Contexts' },\n instructionFiles: { singular: 'Instruction File', plural: 'Instruction Files' },\n tunnels: { singular: 'Tunnel', plural: 'Tunnels' },\n};\n\n// Kinds backed by Vibe.linkedEntities on wspace-vibecontrols-svc. scheduledJobs +\n// sandboxes live in other subgraphs and are rendered by the dedicated\n// sub-panels below (listSandboxes / scheduledJobs filtered by vibeId).\nconst LOCAL_KINDS = new Set<LinkedEntityKind>([\n 'vibedecks',\n 'actions',\n 'aiPrompts',\n 'aiContexts',\n 'instructionFiles',\n 'tunnels',\n]);\n\ninterface LinkedRow {\n id: string;\n label: string;\n badge: string;\n visibility: string;\n}\n\nfunction visibilityLabel(visibility: string): string {\n return visibility === 'SHARED_WITH_VIBE' ? 'Shared' : 'Private';\n}\n\n/**\n * Shared presentational shell for a list of linked rows: handles loading,\n * error, empty, and the populated list. Keeps the three data sources\n * (linkedEntities, sandboxes, scheduledJobs) rendering identically.\n */\nfunction LinkedRowsView({\n labels,\n loading,\n hasData,\n error,\n rows,\n emptyDescription,\n}: {\n labels: { singular: string; plural: string };\n loading: boolean;\n hasData: boolean;\n error?: { message: string };\n rows: LinkedRow[];\n emptyDescription: string;\n}) {\n if (loading && !hasData) {\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-6\">\n <LoadingState message={`Loading ${labels.plural.toLowerCase()}…`} />\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-6\">\n <ErrorState\n title={`Failed to load ${labels.plural.toLowerCase()}`}\n message={error.message}\n />\n </div>\n );\n }\n\n if (rows.length === 0) {\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-6\">\n <EmptyState\n icon={<Link2 size={48} className=\"text-text-secondary\" />}\n title={`No ${labels.plural.toLowerCase()} linked yet`}\n description={emptyDescription}\n />\n </div>\n );\n }\n\n return (\n <div\n className=\"rounded-lg border border-border-default bg-bg-surface\"\n data-testid=\"vibe-linked-entities\"\n >\n <ul className=\"divide-y divide-border-default\">\n {rows.map((row) => (\n <li key={row.id} className=\"flex items-center justify-between gap-3 px-4 py-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"truncate text-sm font-medium text-text-primary\">{row.label}</span>\n <span className=\"flex-shrink-0 rounded bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {row.badge}\n </span>\n </div>\n <span\n className={`flex-shrink-0 rounded px-2 py-0.5 text-xs ${\n row.visibility === 'SHARED_WITH_VIBE'\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-tertiary'\n }`}\n >\n {visibilityLabel(row.visibility)}\n </span>\n </li>\n ))}\n </ul>\n </div>\n );\n}\n\n/** Sandboxes linked to the vibe — fetched from the sandbox subgraph. */\nfunction VibeLinkedSandboxes({ vibeId }: { vibeId: string }) {\n const tr = useTr();\n const labels = KIND_LABELS.sandboxes;\n const { workspaceId, projectId } = useVibeControls();\n\n const { data, loading, error } = useGetVibeLinkedSandboxesQuery({\n variables: {\n context: { workspaceId: workspaceId ?? undefined, projectId: projectId ?? undefined },\n vibeId,\n },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n const rows: LinkedRow[] = (data?.listSandboxes?.items ?? []).map((s: LinkedSandbox) => ({\n id: s.id,\n label: s.name,\n badge: String(s.status),\n visibility: s.visibility,\n }));\n\n return (\n <LinkedRowsView\n labels={labels}\n loading={loading}\n hasData={!!data}\n error={error}\n rows={rows}\n emptyDescription={tr(\n 'vibeDetails.linked.empty',\n `Link a ${labels.singular.toLowerCase()} to this vibe and set it to \"Shared with vibe\" to make it visible to everyone with access.`\n )}\n />\n );\n}\n\n/** Scheduled jobs linked to the vibe — fetched from the scheduler subgraph. */\nfunction VibeLinkedScheduledJobs({ vibeId }: { vibeId: string }) {\n const tr = useTr();\n const labels = KIND_LABELS.scheduledJobs;\n const { workspaceId } = useVibeControls();\n\n const { data, loading, error } = useGetVibeLinkedScheduledJobsQuery({\n variables: { workspaceId: workspaceId ?? '', vibeId },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n\n const rows: LinkedRow[] = (data?.scheduledJobs?.jobs ?? []).map((j: LinkedScheduledJob) => ({\n id: j.id,\n label: j.name ?? j.id,\n badge: String(j.status),\n visibility: j.visibility,\n }));\n\n return (\n <LinkedRowsView\n labels={labels}\n loading={loading}\n hasData={!!data}\n error={error}\n rows={rows}\n emptyDescription={tr(\n 'vibeDetails.linked.empty',\n `Link a ${labels.singular.toLowerCase()} to this vibe and set it to \"Shared with vibe\" to make it visible to everyone with access.`\n )}\n />\n );\n}\n\n/**\n * Renders the entities linked to a vibe (visibility-filtered server-side) for a\n * given kind. Local kinds come from `Vibe.linkedEntities`; sandboxes +\n * scheduledJobs are fetched from their own subgraphs via dedicated vibeId\n * filters. SHARED_WITH_VIBE entities are visible to every member with access to\n * the vibe; PRIVATE only to their owner.\n */\nexport function VibeLinkedEntitiesPanel({ vibeId, kind }: VibeLinkedEntitiesPanelProps) {\n const tr = useTr();\n const labels = KIND_LABELS[kind];\n const isLocal = LOCAL_KINDS.has(kind);\n\n const { data, loading, error } = useGetVibeLinkedEntitiesQuery({\n variables: { id: vibeId },\n skip: !isLocal,\n fetchPolicy: 'cache-and-network',\n });\n\n if (kind === 'sandboxes') {\n return <VibeLinkedSandboxes vibeId={vibeId} />;\n }\n if (kind === 'scheduledJobs') {\n return <VibeLinkedScheduledJobs vibeId={vibeId} />;\n }\n\n const linked: LinkedEntities | undefined = data?.vibe?.linkedEntities ?? undefined;\n const rows: LinkedRow[] = (() => {\n if (!linked) return [];\n switch (kind) {\n case 'vibedecks':\n return linked.vibeDecks.map((d) => ({\n id: d.id,\n label: d.name,\n badge: 'Deck',\n visibility: d.visibility,\n }));\n case 'actions':\n return linked.actions.map((a) => ({\n id: a.id,\n label: a.name,\n badge: 'Action',\n visibility: a.visibility,\n }));\n case 'aiPrompts':\n return linked.aiPrompts.map((p) => ({\n id: p.id,\n label: p.name,\n badge: String(p.category),\n visibility: p.visibility,\n }));\n case 'aiContexts':\n return linked.aiContexts.map((c) => ({\n id: c.id,\n label: c.name,\n badge: String(c.type),\n visibility: c.visibility,\n }));\n case 'instructionFiles':\n return linked.instructionFiles.map((f) => ({\n id: f.id,\n label: f.effectiveFilename,\n badge: String(f.scope),\n visibility: f.visibility,\n }));\n case 'tunnels':\n return linked.tunnels.map((t) => ({\n id: t.id,\n label: t.name,\n badge: 'Tunnel',\n visibility: t.visibility,\n }));\n default:\n return [];\n }\n })();\n\n return (\n <LinkedRowsView\n labels={labels}\n loading={loading}\n hasData={!!data}\n error={error}\n rows={rows}\n emptyDescription={tr(\n 'vibeDetails.linked.empty',\n `Link a ${labels.singular.toLowerCase()} to this vibe and set it to \"Shared with vibe\" to make it visible to everyone with access.`\n )}\n />\n );\n}\n"],"mappings":"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"}
@@ -7075,9 +7075,20 @@ var wp = e`
7075
7075
  agent {
7076
7076
  id
7077
7077
  name
7078
+ hostname
7079
+ platform
7078
7080
  isActive
7081
+ lastHeartbeat
7079
7082
  tunnelUrl
7080
7083
  tunnelStatus
7084
+ target {
7085
+ id
7086
+ name
7087
+ type
7088
+ status
7089
+ host
7090
+ port
7091
+ }
7081
7092
  }
7082
7093
  vibe {
7083
7094
  id
@@ -7119,9 +7130,19 @@ var Op = e`
7119
7130
  id
7120
7131
  name
7121
7132
  hostname
7133
+ platform
7122
7134
  isActive
7135
+ lastHeartbeat
7123
7136
  tunnelUrl
7124
7137
  tunnelStatus
7138
+ target {
7139
+ id
7140
+ name
7141
+ type
7142
+ status
7143
+ host
7144
+ port
7145
+ }
7125
7146
  }
7126
7147
  vibe {
7127
7148
  id