@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.
- package/dist/components/sessions/EntityInfoPopover.js +183 -0
- package/dist/components/sessions/EntityInfoPopover.js.map +1 -0
- package/dist/components/sessions/SessionCard.js +80 -72
- package/dist/components/sessions/SessionCard.js.map +1 -1
- package/dist/components/sessions/SessionGroup.js +49 -0
- package/dist/components/sessions/SessionGroup.js.map +1 -0
- package/dist/components/sessions/SessionInfoPanel.js +128 -0
- package/dist/components/sessions/SessionInfoPanel.js.map +1 -0
- package/dist/components/sessions/SessionList.js +109 -96
- package/dist/components/sessions/SessionList.js.map +1 -1
- package/dist/components/sessions/SessionsCommandPalette.js +36 -0
- package/dist/components/sessions/SessionsCommandPalette.js.map +1 -0
- package/dist/components/sessions/ShortcutCheatsheet.js +73 -0
- package/dist/components/sessions/ShortcutCheatsheet.js.map +1 -0
- package/dist/components/sessions/TerminalPanel.js +640 -537
- package/dist/components/sessions/TerminalPanel.js.map +1 -1
- package/dist/components/sessions/groupSessions.js +46 -0
- package/dist/components/sessions/groupSessions.js.map +1 -0
- package/dist/components/sessions/index.js +7 -0
- package/dist/components/sessions/sessionShortcuts.js +149 -0
- package/dist/components/sessions/sessionShortcuts.js.map +1 -0
- package/dist/components/shared/FilterDropdown.js +14 -13
- package/dist/components/shared/FilterDropdown.js.map +1 -1
- package/dist/components/shared/LinkToVibePanel.js.map +1 -1
- package/dist/components/vibe/VibeLinkedEntitiesPanel.js.map +1 -1
- package/dist/generated/wspace-operations.js +21 -0
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/pages/SessionsPage.js +1189 -920
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/ai/AgentManagerPage.js.map +1 -1
- package/dist/store/vibeControlsStore.js +16 -1
- package/dist/store/vibeControlsStore.js.map +1 -1
- package/dist/translations/en.js +97 -2
- package/dist/translations/en.js.map +1 -1
- 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
|
|
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
|
-
|
|
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
|
|
15
|
+
let C = d.find((e) => e.value === f);
|
|
16
16
|
return /* @__PURE__ */ l("div", {
|
|
17
17
|
className: `relative ${m}`,
|
|
18
|
-
ref:
|
|
18
|
+
ref: x,
|
|
19
19
|
children: [/* @__PURE__ */ l("button", {
|
|
20
20
|
type: "button",
|
|
21
|
-
onClick: () =>
|
|
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:
|
|
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 ${
|
|
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:
|
|
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(
|
|
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: [
|
|
69
|
-
}), d.filter((e) => e.value !==
|
|
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),
|
|
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
|
|
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'
|
|
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":"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"}
|
|
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
|