@burdenoff/microfe-vibecontrols 2026.826.1 → 2026.827.1

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 (53) hide show
  1. package/dist/components/actions/ActionCard.js +99 -84
  2. package/dist/components/actions/ActionCard.js.map +1 -1
  3. package/dist/components/actions/ActionWebhookEndpoint.js +57 -0
  4. package/dist/components/actions/ActionWebhookEndpoint.js.map +1 -0
  5. package/dist/components/actions/index.js +1 -0
  6. package/dist/components/ai/AIQueueTab.js +5 -5
  7. package/dist/components/components/ComponentHealthCell.js +62 -0
  8. package/dist/components/components/ComponentHealthCell.js.map +1 -0
  9. package/dist/components/components/ComponentTierBadge.js +42 -0
  10. package/dist/components/components/ComponentTierBadge.js.map +1 -0
  11. package/dist/components/components/ComponentsTable.js +192 -0
  12. package/dist/components/components/ComponentsTable.js.map +1 -0
  13. package/dist/components/components/index.js +3 -0
  14. package/dist/components/sessions/SessionPublicLinkPanel.js +10 -10
  15. package/dist/components/shared/DiffView.js +7 -0
  16. package/dist/components/shared/index.js +1 -0
  17. package/dist/components/tunnels/TunnelConnectionCard.js +38 -0
  18. package/dist/components/tunnels/TunnelConnectionCard.js.map +1 -0
  19. package/dist/components/tunnels/TunnelCopyButton.js +34 -0
  20. package/dist/components/tunnels/TunnelCopyButton.js.map +1 -0
  21. package/dist/components/tunnels/TunnelMetricTiles.js +132 -0
  22. package/dist/components/tunnels/TunnelMetricTiles.js.map +1 -0
  23. package/dist/components/tunnels/TunnelSparkline.js +28 -0
  24. package/dist/components/tunnels/TunnelSparkline.js.map +1 -0
  25. package/dist/components/tunnels/tunnelEndpoints.js +34 -0
  26. package/dist/components/tunnels/tunnelEndpoints.js.map +1 -0
  27. package/dist/components/tunnels/tunnelMetrics.js +43 -0
  28. package/dist/components/tunnels/tunnelMetrics.js.map +1 -0
  29. package/dist/components/vibe/VibeEnvVarsPanel.js +8 -8
  30. package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js +10 -10
  31. package/dist/generated/wspace-operations.js +5 -0
  32. package/dist/generated/wspace-operations.js.map +1 -1
  33. package/dist/pages/ActionsPage.js.map +1 -1
  34. package/dist/pages/AgentDetailsPage.js +11 -11
  35. package/dist/pages/AgentGraphPage.js +1 -1
  36. package/dist/pages/AgentsPage.js +15 -15
  37. package/dist/pages/ComponentsPage.js +205 -167
  38. package/dist/pages/ComponentsPage.js.map +1 -1
  39. package/dist/pages/EnvTopologyPage.js +7 -7
  40. package/dist/pages/HealthPage.js +1 -1
  41. package/dist/pages/TargetsPage.js +478 -478
  42. package/dist/pages/TargetsPage.js.map +1 -1
  43. package/dist/pages/tunnels/tabs/TunnelEndpointsTab.js +26 -38
  44. package/dist/pages/tunnels/tabs/TunnelEndpointsTab.js.map +1 -1
  45. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +136 -122
  46. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
  47. package/dist/pages/tunnels/tabs/TunnelUsageTab.js +37 -39
  48. package/dist/pages/tunnels/tabs/TunnelUsageTab.js.map +1 -1
  49. package/dist/translations/en.js +17 -0
  50. package/dist/translations/en.js.map +1 -1
  51. package/dist/utils/shareUrl.js +18 -0
  52. package/dist/utils/shareUrl.js.map +1 -0
  53. package/package.json +1 -1
@@ -3,168 +3,183 @@ import { VibeEntityType as t } from "../../generated/wspace-types.js";
3
3
  import { EntityVibeLinkControl as n } from "../shared/EntityVibeLinkControl.js";
4
4
  import { SelectionCheckbox as r } from "../shared/SelectionCheckbox.js";
5
5
  import { ActionTypeBadge as i, SourceTypeBadge as a, TriggerTypeBadge as o } from "./ActionTypeBadge.js";
6
- import { EntityShareButton as s } from "../sharing/EntityShareButton.js";
6
+ import { ActionWebhookEndpoint as s } from "./ActionWebhookEndpoint.js";
7
+ import { EntityShareButton as c } from "../sharing/EntityShareButton.js";
7
8
  import "../sharing/index.js";
8
- import { AlarmClock as c, CalendarClock as l, Clock as u, Copy as d, Hash as f, Pencil as p, Play as m, Trash2 as h } from "lucide-react";
9
- import { motion as g, useReducedMotion as _ } from "framer-motion";
10
- import { jsx as v, jsxs as y } from "react/jsx-runtime";
9
+ import { AlarmClock as l, AlertTriangle as u, CalendarClock as d, Clock as f, Copy as p, Hash as m, Pencil as h, Play as g, Trash2 as _ } from "lucide-react";
10
+ import { motion as v, useReducedMotion as y } from "framer-motion";
11
+ import { jsx as b, jsxs as x } from "react/jsx-runtime";
11
12
  //#region src/components/actions/ActionCard.tsx
12
- function b({ action: b, onExecute: x, onEdit: S, onDuplicate: C, onDelete: w, onSchedule: T, onAddToCalendar: E, canUpdate: D, canDelete: O, canExecute: k, isExecuting: A = !1, selected: j = !1, onToggleSelect: M }) {
13
- let N = _(), P = e();
14
- return /* @__PURE__ */ y(g.div, {
15
- className: `bg-bg-surface border rounded-lg p-4 hover:border-border-strong transition-colors group ${j ? "border-action-primary-bg ring-1 ring-action-primary-bg/25" : "border-border-default"}`,
16
- whileHover: N ? void 0 : {
13
+ function S({ action: S, onExecute: C, onEdit: w, onDuplicate: T, onDelete: E, onSchedule: D, onAddToCalendar: O, canUpdate: k, canDelete: A, canExecute: j, isExecuting: M = !1, selected: N = !1, onToggleSelect: P }) {
14
+ let F = y(), I = e();
15
+ return /* @__PURE__ */ x(v.div, {
16
+ className: `bg-bg-surface border rounded-lg p-4 hover:border-border-strong transition-colors group ${N ? "border-action-primary-bg ring-1 ring-action-primary-bg/25" : "border-border-default"}`,
17
+ whileHover: F ? void 0 : {
17
18
  y: -2,
18
19
  boxShadow: "0 4px 12px var(--color-border-default)"
19
20
  },
20
21
  transition: { duration: .15 },
21
22
  layout: !0,
22
23
  role: "group",
23
- "aria-label": `Action: ${b.displayName || b.name}`,
24
+ "aria-label": `Action: ${S.displayName || S.name}`,
24
25
  children: [
25
- /* @__PURE__ */ y("div", {
26
+ /* @__PURE__ */ x("div", {
26
27
  className: "flex items-start justify-between gap-3 mb-3",
27
- children: [/* @__PURE__ */ y("div", {
28
+ children: [/* @__PURE__ */ x("div", {
28
29
  className: "flex items-start gap-3 min-w-0 flex-1",
29
- children: [M && /* @__PURE__ */ v("div", {
30
+ children: [P && /* @__PURE__ */ b("div", {
30
31
  onClick: (e) => e.stopPropagation(),
31
- children: /* @__PURE__ */ v(r, {
32
- checked: j,
33
- onChange: M,
34
- ariaLabel: `Select action ${b.displayName || b.name}`
32
+ children: /* @__PURE__ */ b(r, {
33
+ checked: N,
34
+ onChange: P,
35
+ ariaLabel: `Select action ${S.displayName || S.name}`
35
36
  })
36
- }), /* @__PURE__ */ y("div", {
37
+ }), /* @__PURE__ */ x("div", {
37
38
  className: "min-w-0 flex-1",
38
- children: [/* @__PURE__ */ v("h3", {
39
+ children: [/* @__PURE__ */ b("h3", {
39
40
  className: "font-semibold text-text-primary truncate",
40
- children: b.displayName || b.name
41
- }), b.description && /* @__PURE__ */ v("p", {
41
+ children: S.displayName || S.name
42
+ }), S.description && /* @__PURE__ */ b("p", {
42
43
  className: "text-sm text-text-secondary mt-0.5 line-clamp-2",
43
- children: b.description
44
+ children: S.description
44
45
  })]
45
46
  })]
46
- }), /* @__PURE__ */ y("div", {
47
+ }), /* @__PURE__ */ x("div", {
47
48
  className: "flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0",
48
49
  children: [
49
- k && /* @__PURE__ */ v("button", {
50
+ j && /* @__PURE__ */ b("button", {
50
51
  type: "button",
51
52
  onClick: (e) => {
52
- e.stopPropagation(), x(b.id);
53
+ e.stopPropagation(), C(S.id);
53
54
  },
54
- disabled: A,
55
+ disabled: M,
55
56
  className: "p-1.5 rounded-md hover:bg-status-success-bg-subtle text-status-success-text transition-colors disabled:opacity-50",
56
- title: P("vibecontrols.actions.card.execute", "Execute"),
57
- children: /* @__PURE__ */ v(m, { size: 14 })
57
+ title: I("vibecontrols.actions.card.execute", "Execute"),
58
+ children: /* @__PURE__ */ b(g, { size: 14 })
58
59
  }),
59
- D && /* @__PURE__ */ v("button", {
60
+ k && /* @__PURE__ */ b("button", {
60
61
  type: "button",
61
62
  onClick: (e) => {
62
- e.stopPropagation(), S(b.id);
63
+ e.stopPropagation(), w(S.id);
63
64
  },
64
65
  className: "p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors",
65
- title: P("vibecontrols.actions.card.edit", "Edit"),
66
- children: /* @__PURE__ */ v(p, { size: 14 })
66
+ title: I("vibecontrols.actions.card.edit", "Edit"),
67
+ children: /* @__PURE__ */ b(h, { size: 14 })
67
68
  }),
68
- T && /* @__PURE__ */ v("button", {
69
+ D && /* @__PURE__ */ b("button", {
69
70
  type: "button",
70
71
  onClick: (e) => {
71
- e.stopPropagation(), T(b.id);
72
+ e.stopPropagation(), D(S.id);
72
73
  },
73
74
  className: "p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors",
74
- title: P("vibecontrols.actions.card.schedule", "Schedule this action"),
75
- children: /* @__PURE__ */ v(c, { size: 14 })
75
+ title: I("vibecontrols.actions.card.schedule", "Schedule this action"),
76
+ children: /* @__PURE__ */ b(l, { size: 14 })
76
77
  }),
77
- E && /* @__PURE__ */ v("button", {
78
+ O && /* @__PURE__ */ b("button", {
78
79
  type: "button",
79
80
  onClick: (e) => {
80
- e.stopPropagation(), E(b.id);
81
+ e.stopPropagation(), O(S.id);
81
82
  },
82
83
  className: "p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors",
83
- title: P("vibecontrols.actions.card.addToCalendar", "Add to calendar"),
84
- children: /* @__PURE__ */ v(l, { size: 14 })
84
+ title: I("vibecontrols.actions.card.addToCalendar", "Add to calendar"),
85
+ children: /* @__PURE__ */ b(d, { size: 14 })
85
86
  }),
86
- /* @__PURE__ */ v("button", {
87
+ /* @__PURE__ */ b("button", {
87
88
  type: "button",
88
89
  onClick: (e) => {
89
- e.stopPropagation(), C(b.id);
90
+ e.stopPropagation(), T(S.id);
90
91
  },
91
92
  className: "p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors",
92
- title: P("vibecontrols.actions.card.duplicate", "Duplicate"),
93
- children: /* @__PURE__ */ v(d, { size: 14 })
93
+ title: I("vibecontrols.actions.card.duplicate", "Duplicate"),
94
+ children: /* @__PURE__ */ b(p, { size: 14 })
94
95
  }),
95
- /* @__PURE__ */ v(s, {
96
+ /* @__PURE__ */ b(c, {
96
97
  entity: "action",
97
98
  resource: {
98
- id: b.id,
99
- name: b.displayName || b.name,
100
- vibeId: b.vibeId ?? null
99
+ id: S.id,
100
+ name: S.displayName || S.name,
101
+ vibeId: S.vibeId ?? null
101
102
  },
102
- label: P("vibecontrols.actions.card.share", "Share")
103
+ label: I("vibecontrols.actions.card.share", "Share")
103
104
  }),
104
- O && /* @__PURE__ */ v("button", {
105
+ A && /* @__PURE__ */ b("button", {
105
106
  type: "button",
106
107
  onClick: (e) => {
107
- e.stopPropagation(), w(b.id);
108
+ e.stopPropagation(), E(S.id);
108
109
  },
109
110
  className: "p-1.5 rounded-md hover:bg-status-error-bg-subtle text-status-error-text transition-colors",
110
- title: P("vibecontrols.actions.card.delete", "Delete"),
111
- children: /* @__PURE__ */ v(h, { size: 14 })
111
+ title: I("vibecontrols.actions.card.delete", "Delete"),
112
+ children: /* @__PURE__ */ b(_, { size: 14 })
112
113
  })
113
114
  ]
114
115
  })]
115
116
  }),
116
- /* @__PURE__ */ y("div", {
117
+ /* @__PURE__ */ x("div", {
117
118
  className: "flex flex-wrap items-center gap-1.5 mb-3",
118
119
  children: [
119
- /* @__PURE__ */ v(i, { type: b.actionType }),
120
- /* @__PURE__ */ v(o, { type: b.triggerType }),
121
- b.sourceType && /* @__PURE__ */ v(a, { type: b.sourceType })
120
+ /* @__PURE__ */ b(i, { type: S.actionType }),
121
+ /* @__PURE__ */ b(o, { type: S.triggerType }),
122
+ S.sourceType && /* @__PURE__ */ b(a, { type: S.sourceType })
122
123
  ]
123
124
  }),
124
- /* @__PURE__ */ v("div", {
125
+ S.actionType === "WEBHOOK" && S.webhookUrl && /* @__PURE__ */ b("div", {
126
+ className: "mb-3",
127
+ children: /* @__PURE__ */ b(s, {
128
+ url: S.webhookUrl,
129
+ method: S.webhookMethod
130
+ })
131
+ }),
132
+ /* @__PURE__ */ b("div", {
125
133
  className: "mb-3",
126
134
  onClick: (e) => e.stopPropagation(),
127
- children: /* @__PURE__ */ v(n, {
135
+ children: /* @__PURE__ */ b(n, {
128
136
  entityType: t.Action,
129
- entityId: b.id,
130
- currentVibeId: b.vibeId ?? null,
131
- currentVisibility: b.visibility ?? null,
137
+ entityId: S.id,
138
+ currentVibeId: S.vibeId ?? null,
139
+ currentVisibility: S.visibility ?? null,
132
140
  refetchQueries: ["ListActions"]
133
141
  })
134
142
  }),
135
- (b.category || b.tags && b.tags.length > 0) && /* @__PURE__ */ y("div", {
143
+ (S.category || S.tags && S.tags.length > 0) && /* @__PURE__ */ x("div", {
136
144
  className: "flex flex-wrap items-center gap-1.5 mb-3",
137
- children: [b.category && /* @__PURE__ */ v("span", {
145
+ children: [S.category && /* @__PURE__ */ b("span", {
138
146
  className: "text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-secondary",
139
- children: b.category
140
- }), b.tags?.map((e) => /* @__PURE__ */ y("span", {
147
+ children: S.category
148
+ }), S.tags?.map((e) => /* @__PURE__ */ x("span", {
141
149
  className: "text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-placeholder",
142
150
  children: ["#", e]
143
151
  }, e))]
144
152
  }),
145
- /* @__PURE__ */ y("div", {
153
+ /* @__PURE__ */ x("div", {
146
154
  className: "flex items-center gap-4 text-xs text-text-placeholder",
147
- children: [/* @__PURE__ */ y("span", {
148
- className: "inline-flex items-center gap-1",
149
- children: [/* @__PURE__ */ v(f, { size: 12 }), P("vibecontrols.actions.card.executionRuns", "{{count}} runs", { count: b.executionCount ?? 0 })]
150
- }), /* @__PURE__ */ y("span", {
151
- className: "inline-flex items-center gap-1",
152
- children: [/* @__PURE__ */ v(u, { size: 12 }), ((e) => {
153
- if (!e) return P("vibecontrols.actions.card.never", "Never");
154
- let t = new Date(e), n = (/* @__PURE__ */ new Date()).getTime() - t.getTime(), r = Math.floor(n / 6e4);
155
- if (r < 1) return P("vibecontrols.actions.card.justNow", "Just now");
156
- if (r < 60) return P("vibecontrols.actions.card.minutesAgo", "{{count}}m ago", { count: r });
157
- let i = Math.floor(r / 60);
158
- if (i < 24) return P("vibecontrols.actions.card.hoursAgo", "{{count}}h ago", { count: i });
159
- let a = Math.floor(i / 24);
160
- return a < 7 ? `${a}d ago` : t.toLocaleDateString();
161
- })(b.lastExecutedAt)]
162
- })]
155
+ children: [
156
+ S.confirmationRequired && /* @__PURE__ */ x("span", {
157
+ className: "inline-flex items-center gap-1 text-status-warning-text",
158
+ children: [/* @__PURE__ */ b(u, { size: 12 }), I("vibecontrols.actions.card.confirmationRequired", "Confirmation required")]
159
+ }),
160
+ /* @__PURE__ */ x("span", {
161
+ className: "inline-flex items-center gap-1",
162
+ children: [/* @__PURE__ */ b(m, { size: 12 }), I("vibecontrols.actions.card.executionRuns", "{{count}} runs", { count: S.executionCount ?? 0 })]
163
+ }),
164
+ /* @__PURE__ */ x("span", {
165
+ className: "inline-flex items-center gap-1",
166
+ children: [/* @__PURE__ */ b(f, { size: 12 }), ((e) => {
167
+ if (!e) return I("vibecontrols.actions.card.never", "Never");
168
+ let t = new Date(e), n = (/* @__PURE__ */ new Date()).getTime() - t.getTime(), r = Math.floor(n / 6e4);
169
+ if (r < 1) return I("vibecontrols.actions.card.justNow", "Just now");
170
+ if (r < 60) return I("vibecontrols.actions.card.minutesAgo", "{{count}}m ago", { count: r });
171
+ let i = Math.floor(r / 60);
172
+ if (i < 24) return I("vibecontrols.actions.card.hoursAgo", "{{count}}h ago", { count: i });
173
+ let a = Math.floor(i / 24);
174
+ return a < 7 ? `${a}d ago` : t.toLocaleDateString();
175
+ })(S.lastExecutedAt)]
176
+ })
177
+ ]
163
178
  })
164
179
  ]
165
180
  });
166
181
  }
167
182
  //#endregion
168
- export { b as ActionCard };
183
+ export { S as ActionCard };
169
184
 
170
185
  //# sourceMappingURL=ActionCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ActionCard.js","names":[],"sources":["../../../src/components/actions/ActionCard.tsx"],"sourcesContent":["import { Play, Pencil, Copy, Trash2, Clock, Hash, CalendarClock, AlarmClock } from 'lucide-react';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport { ActionTypeBadge, TriggerTypeBadge, SourceTypeBadge } from './ActionTypeBadge';\nimport { SelectionCheckbox } from '../shared/SelectionCheckbox';\nimport { EntityVibeLinkControl } from '../shared/EntityVibeLinkControl';\nimport { EntityShareButton } from '../sharing';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { VibeEntityType, VibeVisibility } from '@/generated/wspace-types';\n\ninterface ActionCardAction {\n id: string;\n name: string;\n displayName?: string | null;\n description?: string | null;\n actionType: string;\n sourceType?: string | null;\n triggerType: string;\n category?: string | null;\n tags?: string[] | null;\n executionCount?: number | null;\n lastExecutedAt?: string | null;\n confirmationRequired?: boolean;\n vibeId?: string | null;\n visibility?: VibeVisibility | null;\n}\n\ninterface ActionCardProps {\n action: ActionCardAction;\n onExecute: (id: string) => void;\n onEdit: (id: string) => void;\n onDuplicate: (id: string) => void;\n onDelete: (id: string) => void;\n onSchedule?: (id: string) => void;\n onAddToCalendar?: (id: string) => void;\n canUpdate: boolean;\n canDelete: boolean;\n canExecute: boolean;\n isExecuting?: boolean;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n}\n\nexport function ActionCard({\n action,\n onExecute,\n onEdit,\n onDuplicate,\n onDelete,\n onSchedule,\n onAddToCalendar,\n canUpdate,\n canDelete,\n canExecute,\n isExecuting = false,\n selected = false,\n onToggleSelect,\n}: ActionCardProps) {\n const shouldReduceMotion = useReducedMotion();\n const tr = useTr();\n\n const formatDate = (dateStr: string | null | undefined) => {\n if (!dateStr) return tr('vibecontrols.actions.card.never', 'Never');\n const date = new Date(dateStr);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMin = Math.floor(diffMs / 60000);\n if (diffMin < 1) return tr('vibecontrols.actions.card.justNow', 'Just now');\n if (diffMin < 60)\n return tr('vibecontrols.actions.card.minutesAgo', '{{count}}m ago', { count: diffMin });\n const diffHr = Math.floor(diffMin / 60);\n if (diffHr < 24)\n return tr('vibecontrols.actions.card.hoursAgo', '{{count}}h ago', { count: diffHr });\n const diffDays = Math.floor(diffHr / 24);\n if (diffDays < 7) return `${diffDays}d ago`;\n return date.toLocaleDateString();\n };\n\n return (\n <motion.div\n className={`bg-bg-surface border rounded-lg p-4 hover:border-border-strong transition-colors group ${\n selected\n ? 'border-action-primary-bg ring-1 ring-action-primary-bg/25'\n : 'border-border-default'\n }`}\n whileHover={\n shouldReduceMotion\n ? undefined\n : { y: -2, boxShadow: '0 4px 12px var(--color-border-default)' }\n }\n transition={{ duration: 0.15 }}\n layout\n role=\"group\"\n aria-label={`Action: ${action.displayName || action.name}`}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between gap-3 mb-3\">\n <div className=\"flex items-start gap-3 min-w-0 flex-1\">\n {onToggleSelect && (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={`Select action ${action.displayName || action.name}`}\n />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"font-semibold text-text-primary truncate\">\n {action.displayName || action.name}\n </h3>\n {action.description && (\n <p className=\"text-sm text-text-secondary mt-0.5 line-clamp-2\">\n {action.description}\n </p>\n )}\n </div>\n </div>\n {/* Action buttons */}\n <div className=\"flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0\">\n {canExecute && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onExecute(action.id);\n }}\n disabled={isExecuting}\n className=\"p-1.5 rounded-md hover:bg-status-success-bg-subtle text-status-success-text transition-colors disabled:opacity-50\"\n title={tr('vibecontrols.actions.card.execute', 'Execute')}\n >\n <Play size={14} />\n </button>\n )}\n {canUpdate && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onEdit(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.edit', 'Edit')}\n >\n <Pencil size={14} />\n </button>\n )}\n {onSchedule && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onSchedule(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.schedule', 'Schedule this action')}\n >\n <AlarmClock size={14} />\n </button>\n )}\n {onAddToCalendar && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onAddToCalendar(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.addToCalendar', 'Add to calendar')}\n >\n <CalendarClock size={14} />\n </button>\n )}\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onDuplicate(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.duplicate', 'Duplicate')}\n >\n <Copy size={14} />\n </button>\n <EntityShareButton\n entity=\"action\"\n resource={{\n id: action.id,\n name: action.displayName || action.name,\n vibeId: action.vibeId ?? null,\n }}\n label={tr('vibecontrols.actions.card.share', 'Share')}\n />\n {canDelete && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onDelete(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-status-error-bg-subtle text-status-error-text transition-colors\"\n title={tr('vibecontrols.actions.card.delete', 'Delete')}\n >\n <Trash2 size={14} />\n </button>\n )}\n </div>\n </div>\n\n {/* Badges */}\n <div className=\"flex flex-wrap items-center gap-1.5 mb-3\">\n <ActionTypeBadge type={action.actionType} />\n <TriggerTypeBadge type={action.triggerType} />\n {action.sourceType && <SourceTypeBadge type={action.sourceType} />}\n </div>\n\n {/* Linked vibe — map / unmap this action to a vibe */}\n <div className=\"mb-3\" onClick={(event) => event.stopPropagation()}>\n <EntityVibeLinkControl\n entityType={VibeEntityType.Action}\n entityId={action.id}\n currentVibeId={action.vibeId ?? null}\n currentVisibility={action.visibility ?? null}\n refetchQueries={['ListActions']}\n />\n </div>\n\n {/* Category and Tags */}\n {(action.category || (action.tags && action.tags.length > 0)) && (\n <div className=\"flex flex-wrap items-center gap-1.5 mb-3\">\n {action.category && (\n <span className=\"text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-secondary\">\n {action.category}\n </span>\n )}\n {action.tags?.map((tag) => (\n <span\n key={tag}\n className=\"text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-placeholder\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Stats */}\n <div className=\"flex items-center gap-4 text-xs text-text-placeholder\">\n <span className=\"inline-flex items-center gap-1\">\n <Hash size={12} />\n {tr('vibecontrols.actions.card.executionRuns', '{{count}} runs', {\n count: action.executionCount ?? 0,\n })}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Clock size={12} />\n {formatDate(action.lastExecutedAt)}\n </span>\n </div>\n </motion.div>\n );\n}\n"],"mappings":";;;;;;;;;;;AA0CA,SAAgB,EAAW,EACzB,WACA,cACA,WACA,gBACA,aACA,eACA,oBACA,cACA,cACA,eACA,iBAAc,IACd,cAAW,IACX,qBACkB;CAClB,IAAM,IAAqB,GAAkB,EACvC,IAAK,GAAO;AAmBlB,QACE,kBAAC,EAAO,KAAR;EACE,WAAW,0FACT,IACI,8DACA;EAEN,YACE,IACI,KAAA,IACA;GAAE,GAAG;GAAI,WAAW;GAA0C;EAEpE,YAAY,EAAE,UAAU,KAAM;EAC9B,QAAA;EACA,MAAK;EACL,cAAY,WAAW,EAAO,eAAe,EAAO;YAdtD;GAiBE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACG,KACC,kBAAC,OAAD;MAAK,UAAU,MAAU,EAAM,iBAAiB;gBAC9C,kBAAC,GAAD;OACE,SAAS;OACT,UAAU;OACV,WAAW,iBAAiB,EAAO,eAAe,EAAO;OACzD,CAAA;MACE,CAAA,EAER,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAO,eAAe,EAAO;OAC3B,CAAA,EACJ,EAAO,eACN,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAO;OACN,CAAA,CAEF;QACF;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG,KACC,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAU;AAElB,QADA,EAAM,iBAAiB,EACvB,EAAU,EAAO,GAAG;;OAEtB,UAAU;OACV,WAAU;OACV,OAAO,EAAG,qCAAqC,UAAU;iBAEzD,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;OACX,CAAA;MAEV,KACC,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAU;AAElB,QADA,EAAM,iBAAiB,EACvB,EAAO,EAAO,GAAG;;OAEnB,WAAU;OACV,OAAO,EAAG,kCAAkC,OAAO;iBAEnD,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA;OACb,CAAA;MAEV,KACC,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAU;AAElB,QADA,EAAM,iBAAiB,EACvB,EAAW,EAAO,GAAG;;OAEvB,WAAU;OACV,OAAO,EAAG,sCAAsC,uBAAuB;iBAEvE,kBAAC,GAAD,EAAY,MAAM,IAAM,CAAA;OACjB,CAAA;MAEV,KACC,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAU;AAElB,QADA,EAAM,iBAAiB,EACvB,EAAgB,EAAO,GAAG;;OAE5B,WAAU;OACV,OAAO,EAAG,2CAA2C,kBAAkB;iBAEvE,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA;OACpB,CAAA;MAEX,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAU;AAElB,QADA,EAAM,iBAAiB,EACvB,EAAY,EAAO,GAAG;;OAExB,WAAU;OACV,OAAO,EAAG,uCAAuC,YAAY;iBAE7D,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;OACX,CAAA;MACT,kBAAC,GAAD;OACE,QAAO;OACP,UAAU;QACR,IAAI,EAAO;QACX,MAAM,EAAO,eAAe,EAAO;QACnC,QAAQ,EAAO,UAAU;QAC1B;OACD,OAAO,EAAG,mCAAmC,QAAQ;OACrD,CAAA;MACD,KACC,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAU;AAElB,QADA,EAAM,iBAAiB,EACvB,EAAS,EAAO,GAAG;;OAErB,WAAU;OACV,OAAO,EAAG,oCAAoC,SAAS;iBAEvD,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA;OACb,CAAA;MAEP;OACF;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAiB,MAAM,EAAO,YAAc,CAAA;KAC5C,kBAAC,GAAD,EAAkB,MAAM,EAAO,aAAe,CAAA;KAC7C,EAAO,cAAc,kBAAC,GAAD,EAAiB,MAAM,EAAO,YAAc,CAAA;KAC9D;;GAGN,kBAAC,OAAD;IAAK,WAAU;IAAO,UAAU,MAAU,EAAM,iBAAiB;cAC/D,kBAAC,GAAD;KACE,YAAY,EAAe;KAC3B,UAAU,EAAO;KACjB,eAAe,EAAO,UAAU;KAChC,mBAAmB,EAAO,cAAc;KACxC,gBAAgB,CAAC,cAAc;KAC/B,CAAA;IACE,CAAA;IAGJ,EAAO,YAAa,EAAO,QAAQ,EAAO,KAAK,SAAS,MACxD,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAO,YACN,kBAAC,QAAD;KAAM,WAAU;eACb,EAAO;KACH,CAAA,EAER,EAAO,MAAM,KAAK,MACjB,kBAAC,QAAD;KAEE,WAAU;eAFZ,CAGC,KACG,EACG;OAJA,EAIA,CACP,CACE;;GAIR,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EAAG,2CAA2C,kBAAkB,EAC/D,OAAO,EAAO,kBAAkB,GACjC,CAAC,CACG;QACP,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA,IAlMP,MAAuC;AACzD,UAAI,CAAC,EAAS,QAAO,EAAG,mCAAmC,QAAQ;MACnE,IAAM,IAAO,IAAI,KAAK,EAAQ,EAExB,qBADM,IAAI,MAAM,EACH,SAAS,GAAG,EAAK,SAAS,EACvC,IAAU,KAAK,MAAM,IAAS,IAAM;AAC1C,UAAI,IAAU,EAAG,QAAO,EAAG,qCAAqC,WAAW;AAC3E,UAAI,IAAU,GACZ,QAAO,EAAG,wCAAwC,kBAAkB,EAAE,OAAO,GAAS,CAAC;MACzF,IAAM,IAAS,KAAK,MAAM,IAAU,GAAG;AACvC,UAAI,IAAS,GACX,QAAO,EAAG,sCAAsC,kBAAkB,EAAE,OAAO,GAAQ,CAAC;MACtF,IAAM,IAAW,KAAK,MAAM,IAAS,GAAG;AAExC,aADI,IAAW,IAAU,GAAG,EAAS,SAC9B,EAAK,oBAAoB;QAqLd,EAAO,eAAe,CAC7B;OACH;;GACK"}
1
+ {"version":3,"file":"ActionCard.js","names":[],"sources":["../../../src/components/actions/ActionCard.tsx"],"sourcesContent":["import {\n Play,\n Pencil,\n Copy,\n Trash2,\n Clock,\n Hash,\n CalendarClock,\n AlarmClock,\n AlertTriangle,\n} from 'lucide-react';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport { ActionTypeBadge, TriggerTypeBadge, SourceTypeBadge } from './ActionTypeBadge';\nimport { ActionWebhookEndpoint } from './ActionWebhookEndpoint';\nimport { SelectionCheckbox } from '../shared/SelectionCheckbox';\nimport { EntityVibeLinkControl } from '../shared/EntityVibeLinkControl';\nimport { EntityShareButton } from '../sharing';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { VibeEntityType, VibeVisibility } from '@/generated/wspace-types';\n\ninterface ActionCardAction {\n id: string;\n name: string;\n displayName?: string | null;\n description?: string | null;\n actionType: string;\n sourceType?: string | null;\n triggerType: string;\n category?: string | null;\n tags?: string[] | null;\n executionCount?: number | null;\n lastExecutedAt?: string | null;\n confirmationRequired?: boolean;\n webhookUrl?: string | null;\n webhookMethod?: string | null;\n vibeId?: string | null;\n visibility?: VibeVisibility | null;\n}\n\ninterface ActionCardProps {\n action: ActionCardAction;\n onExecute: (id: string) => void;\n onEdit: (id: string) => void;\n onDuplicate: (id: string) => void;\n onDelete: (id: string) => void;\n onSchedule?: (id: string) => void;\n onAddToCalendar?: (id: string) => void;\n canUpdate: boolean;\n canDelete: boolean;\n canExecute: boolean;\n isExecuting?: boolean;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n}\n\nexport function ActionCard({\n action,\n onExecute,\n onEdit,\n onDuplicate,\n onDelete,\n onSchedule,\n onAddToCalendar,\n canUpdate,\n canDelete,\n canExecute,\n isExecuting = false,\n selected = false,\n onToggleSelect,\n}: ActionCardProps) {\n const shouldReduceMotion = useReducedMotion();\n const tr = useTr();\n\n const formatDate = (dateStr: string | null | undefined) => {\n if (!dateStr) return tr('vibecontrols.actions.card.never', 'Never');\n const date = new Date(dateStr);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMin = Math.floor(diffMs / 60000);\n if (diffMin < 1) return tr('vibecontrols.actions.card.justNow', 'Just now');\n if (diffMin < 60)\n return tr('vibecontrols.actions.card.minutesAgo', '{{count}}m ago', { count: diffMin });\n const diffHr = Math.floor(diffMin / 60);\n if (diffHr < 24)\n return tr('vibecontrols.actions.card.hoursAgo', '{{count}}h ago', { count: diffHr });\n const diffDays = Math.floor(diffHr / 24);\n if (diffDays < 7) return `${diffDays}d ago`;\n return date.toLocaleDateString();\n };\n\n return (\n <motion.div\n className={`bg-bg-surface border rounded-lg p-4 hover:border-border-strong transition-colors group ${\n selected\n ? 'border-action-primary-bg ring-1 ring-action-primary-bg/25'\n : 'border-border-default'\n }`}\n whileHover={\n shouldReduceMotion\n ? undefined\n : { y: -2, boxShadow: '0 4px 12px var(--color-border-default)' }\n }\n transition={{ duration: 0.15 }}\n layout\n role=\"group\"\n aria-label={`Action: ${action.displayName || action.name}`}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between gap-3 mb-3\">\n <div className=\"flex items-start gap-3 min-w-0 flex-1\">\n {onToggleSelect && (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={`Select action ${action.displayName || action.name}`}\n />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"font-semibold text-text-primary truncate\">\n {action.displayName || action.name}\n </h3>\n {action.description && (\n <p className=\"text-sm text-text-secondary mt-0.5 line-clamp-2\">\n {action.description}\n </p>\n )}\n </div>\n </div>\n {/* Action buttons */}\n <div className=\"flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0\">\n {canExecute && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onExecute(action.id);\n }}\n disabled={isExecuting}\n className=\"p-1.5 rounded-md hover:bg-status-success-bg-subtle text-status-success-text transition-colors disabled:opacity-50\"\n title={tr('vibecontrols.actions.card.execute', 'Execute')}\n >\n <Play size={14} />\n </button>\n )}\n {canUpdate && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onEdit(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.edit', 'Edit')}\n >\n <Pencil size={14} />\n </button>\n )}\n {onSchedule && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onSchedule(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.schedule', 'Schedule this action')}\n >\n <AlarmClock size={14} />\n </button>\n )}\n {onAddToCalendar && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onAddToCalendar(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.addToCalendar', 'Add to calendar')}\n >\n <CalendarClock size={14} />\n </button>\n )}\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onDuplicate(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n title={tr('vibecontrols.actions.card.duplicate', 'Duplicate')}\n >\n <Copy size={14} />\n </button>\n <EntityShareButton\n entity=\"action\"\n resource={{\n id: action.id,\n name: action.displayName || action.name,\n vibeId: action.vibeId ?? null,\n }}\n label={tr('vibecontrols.actions.card.share', 'Share')}\n />\n {canDelete && (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation();\n onDelete(action.id);\n }}\n className=\"p-1.5 rounded-md hover:bg-status-error-bg-subtle text-status-error-text transition-colors\"\n title={tr('vibecontrols.actions.card.delete', 'Delete')}\n >\n <Trash2 size={14} />\n </button>\n )}\n </div>\n </div>\n\n {/* Badges */}\n <div className=\"flex flex-wrap items-center gap-1.5 mb-3\">\n <ActionTypeBadge type={action.actionType} />\n <TriggerTypeBadge type={action.triggerType} />\n {action.sourceType && <SourceTypeBadge type={action.sourceType} />}\n </div>\n\n {/* Webhook target — only webhook actions have one to show. */}\n {action.actionType === 'WEBHOOK' && action.webhookUrl && (\n <div className=\"mb-3\">\n <ActionWebhookEndpoint url={action.webhookUrl} method={action.webhookMethod} />\n </div>\n )}\n\n {/* Linked vibe — map / unmap this action to a vibe */}\n <div className=\"mb-3\" onClick={(event) => event.stopPropagation()}>\n <EntityVibeLinkControl\n entityType={VibeEntityType.Action}\n entityId={action.id}\n currentVibeId={action.vibeId ?? null}\n currentVisibility={action.visibility ?? null}\n refetchQueries={['ListActions']}\n />\n </div>\n\n {/* Category and Tags */}\n {(action.category || (action.tags && action.tags.length > 0)) && (\n <div className=\"flex flex-wrap items-center gap-1.5 mb-3\">\n {action.category && (\n <span className=\"text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-secondary\">\n {action.category}\n </span>\n )}\n {action.tags?.map((tag) => (\n <span\n key={tag}\n className=\"text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-placeholder\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Stats */}\n <div className=\"flex items-center gap-4 text-xs text-text-placeholder\">\n {/* This action prompts before it runs — worth knowing before you hit\n Execute, and the only place the flag is visible outside the form. */}\n {action.confirmationRequired && (\n <span className=\"inline-flex items-center gap-1 text-status-warning-text\">\n <AlertTriangle size={12} />\n {tr('vibecontrols.actions.card.confirmationRequired', 'Confirmation required')}\n </span>\n )}\n <span className=\"inline-flex items-center gap-1\">\n <Hash size={12} />\n {tr('vibecontrols.actions.card.executionRuns', '{{count}} runs', {\n count: action.executionCount ?? 0,\n })}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Clock size={12} />\n {formatDate(action.lastExecutedAt)}\n </span>\n </div>\n </motion.div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { notify as t } from "../../lib/toast.js";
3
+ import { copyTextToClipboard as n } from "../../utils/shareUrl.js";
4
+ import { useState as r } from "react";
5
+ import { Check as i, Copy as a } from "lucide-react";
6
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
7
+ //#region src/components/actions/ActionWebhookEndpoint.tsx
8
+ var c = 34, l = "POST";
9
+ function u(e) {
10
+ let t;
11
+ try {
12
+ let n = new URL(e);
13
+ t = n.pathname === "/" ? n.host : `${n.host}${n.pathname}`;
14
+ } catch {
15
+ t = e.replace(/^[a-zA-Z][a-zA-Z\d+.-]*:\/\//, "");
16
+ }
17
+ return t.length <= c ? t : `${t.slice(0, c - 1)}…`;
18
+ }
19
+ function d({ url: c, method: d }) {
20
+ let f = e(), [p, m] = r(!1), h = (d ?? l).toUpperCase(), g = u(c), _ = async () => {
21
+ try {
22
+ await n(c), m(!0), t.success(f("vibecontrols.actions.card.endpointCopied", "Webhook URL copied")), window.setTimeout(() => m(!1), 1500);
23
+ } catch {
24
+ t.error(f("vibecontrols.actions.card.endpointCopyFailed", "Could not copy — copy it manually"));
25
+ }
26
+ };
27
+ return /* @__PURE__ */ s("div", {
28
+ className: "flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-2 py-1.5",
29
+ onClick: (e) => e.stopPropagation(),
30
+ children: [
31
+ /* @__PURE__ */ o("span", {
32
+ className: "font-mono text-xs font-semibold text-text-secondary flex-shrink-0",
33
+ children: h
34
+ }),
35
+ /* @__PURE__ */ o("span", {
36
+ className: "flex-1 truncate font-mono text-xs text-text-placeholder",
37
+ title: g,
38
+ children: g
39
+ }),
40
+ /* @__PURE__ */ o("button", {
41
+ type: "button",
42
+ onClick: () => void _(),
43
+ className: "p-0.5 rounded text-text-secondary hover:text-text-primary transition-colors flex-shrink-0",
44
+ "aria-label": f("vibecontrols.actions.card.copyEndpoint", "Copy webhook URL"),
45
+ title: f("vibecontrols.actions.card.copyEndpoint", "Copy webhook URL"),
46
+ children: p ? /* @__PURE__ */ o(i, {
47
+ size: 12,
48
+ className: "text-status-success-text"
49
+ }) : /* @__PURE__ */ o(a, { size: 12 })
50
+ })
51
+ ]
52
+ });
53
+ }
54
+ //#endregion
55
+ export { d as ActionWebhookEndpoint };
56
+
57
+ //# sourceMappingURL=ActionWebhookEndpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActionWebhookEndpoint.js","names":[],"sources":["../../../src/components/actions/ActionWebhookEndpoint.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Copy, Check } from 'lucide-react';\nimport { notify } from '@/lib/toast';\nimport { copyTextToClipboard } from '@/utils/shareUrl';\nimport { useTr } from '../../shared/hooks/useTr';\n\n/**\n * Longest endpoint string we render before eliding. Sized so a preview still\n * fits on one line inside a three-up action card at the narrowest grid step.\n */\nconst MAX_ENDPOINT_LENGTH = 34;\n\n/**\n * Webhook actions default to POST in ActionForm, so a row that predates an\n * explicit method (or a null from the API) reads as the same default here\n * instead of rendering a blank verb.\n */\nconst DEFAULT_METHOD = 'POST';\n\n/**\n * Build the at-a-glance form of a webhook target: host + path, no scheme.\n *\n * The query string is deliberately dropped from the *preview* — webhook URLs\n * routinely carry a token there (Slack, Discord, PagerDuty all do) and a card\n * in a shared library is exactly where that should not be shoulder-surfable.\n * The copy button still copies the untouched URL, so nothing is lost.\n */\nfunction formatEndpoint(rawUrl: string): string {\n let display: string;\n try {\n const parsed = new URL(rawUrl);\n display = parsed.pathname === '/' ? parsed.host : `${parsed.host}${parsed.pathname}`;\n } catch {\n // Not a parseable absolute URL (a template or a relative target) — strip\n // any scheme we can recognise and show the rest verbatim.\n display = rawUrl.replace(/^[a-zA-Z][a-zA-Z\\d+.-]*:\\/\\//, '');\n }\n if (display.length <= MAX_ENDPOINT_LENGTH) return display;\n return `${display.slice(0, MAX_ENDPOINT_LENGTH - 1)}…`;\n}\n\ninterface ActionWebhookEndpointProps {\n /** The full webhook target as stored on the action. */\n url: string;\n /** HTTP verb the action fires; falls back to POST when unset. */\n method?: string | null;\n}\n\n/**\n * ActionWebhookEndpoint — the `POST hooks.slack.com/…` line on a webhook action\n * card, with one-click copy of the full URL.\n *\n * Without it a webhook action is indistinguishable from every other webhook\n * action on the library screen: the card shows the name and a \"Webhook\" badge\n * but nothing about where the request actually goes. Surfacing method + host\n * makes \"which of these three alert hooks points at the on-call channel?\"\n * answerable without opening the edit form.\n */\nexport function ActionWebhookEndpoint({ url, method }: ActionWebhookEndpointProps) {\n const tr = useTr();\n const [copied, setCopied] = useState(false);\n\n const verb = (method ?? DEFAULT_METHOD).toUpperCase();\n const endpoint = formatEndpoint(url);\n\n const handleCopy = async (): Promise<void> => {\n try {\n await copyTextToClipboard(url);\n setCopied(true);\n notify.success(tr('vibecontrols.actions.card.endpointCopied', 'Webhook URL copied'));\n window.setTimeout(() => setCopied(false), 1500);\n } catch {\n notify.error(\n tr('vibecontrols.actions.card.endpointCopyFailed', 'Could not copy — copy it manually')\n );\n }\n };\n\n return (\n <div\n className=\"flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-2 py-1.5\"\n onClick={(event) => event.stopPropagation()}\n >\n <span className=\"font-mono text-xs font-semibold text-text-secondary flex-shrink-0\">\n {verb}\n </span>\n <span className=\"flex-1 truncate font-mono text-xs text-text-placeholder\" title={endpoint}>\n {endpoint}\n </span>\n <button\n type=\"button\"\n onClick={() => void handleCopy()}\n className=\"p-0.5 rounded text-text-secondary hover:text-text-primary transition-colors flex-shrink-0\"\n aria-label={tr('vibecontrols.actions.card.copyEndpoint', 'Copy webhook URL')}\n title={tr('vibecontrols.actions.card.copyEndpoint', 'Copy webhook URL')}\n >\n {copied ? <Check size={12} className=\"text-status-success-text\" /> : <Copy size={12} />}\n </button>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAUA,IAAM,IAAsB,IAOtB,IAAiB;AAUvB,SAAS,EAAe,GAAwB;CAC9C,IAAI;AACJ,KAAI;EACF,IAAM,IAAS,IAAI,IAAI,EAAO;AAC9B,MAAU,EAAO,aAAa,MAAM,EAAO,OAAO,GAAG,EAAO,OAAO,EAAO;SACpE;AAGN,MAAU,EAAO,QAAQ,gCAAgC,GAAG;;AAG9D,QADI,EAAQ,UAAU,IAA4B,IAC3C,GAAG,EAAQ,MAAM,GAAG,IAAsB,EAAE,CAAC;;AAoBtD,SAAgB,EAAsB,EAAE,QAAK,aAAsC;CACjF,IAAM,IAAK,GAAO,EACZ,CAAC,GAAQ,KAAa,EAAS,GAAM,EAErC,KAAQ,KAAU,GAAgB,aAAa,EAC/C,IAAW,EAAe,EAAI,EAE9B,IAAa,YAA2B;AAC5C,MAAI;AAIF,GAHA,MAAM,EAAoB,EAAI,EAC9B,EAAU,GAAK,EACf,EAAO,QAAQ,EAAG,4CAA4C,qBAAqB,CAAC,EACpF,OAAO,iBAAiB,EAAU,GAAM,EAAE,KAAK;UACzC;AACN,KAAO,MACL,EAAG,gDAAgD,oCAAoC,CACxF;;;AAIL,QACE,kBAAC,OAAD;EACE,WAAU;EACV,UAAU,MAAU,EAAM,iBAAiB;YAF7C;GAIE,kBAAC,QAAD;IAAM,WAAU;cACb;IACI,CAAA;GACP,kBAAC,QAAD;IAAM,WAAU;IAA0D,OAAO;cAC9E;IACI,CAAA;GACP,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,KAAK,GAAY;IAChC,WAAU;IACV,cAAY,EAAG,0CAA0C,mBAAmB;IAC5E,OAAO,EAAG,0CAA0C,mBAAmB;cAEtE,IAAS,kBAAC,GAAD;KAAO,MAAM;KAAI,WAAU;KAA6B,CAAA,GAAG,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;IAChF,CAAA;GACL"}
@@ -1,4 +1,5 @@
1
1
  import "./ActionTypeBadge.js";
2
+ import "./ActionWebhookEndpoint.js";
2
3
  import "./ActionCard.js";
3
4
  import "./ActionForm.js";
4
5
  import "./ExecutionHistory.js";
@@ -4,8 +4,8 @@ import { ErrorState as n } from "../shared/ErrorState.js";
4
4
  import { EmptyState as r } from "../shared/EmptyState.js";
5
5
  import { Card as i } from "../shared/Card.js";
6
6
  import "../shared/index.js";
7
- import { cancelQueueItem as a, fetchQueueItems as o } from "../../services/aiApi.js";
8
- import { notify as s } from "../../lib/toast.js";
7
+ import { notify as a } from "../../lib/toast.js";
8
+ import { cancelQueueItem as o, fetchQueueItems as s } from "../../services/aiApi.js";
9
9
  import { useCallback as c, useEffect as l, useState as u } from "react";
10
10
  import { AlertCircle as d, Ban as f, CheckCircle2 as p, Clock as m, ListChecks as h, Loader2 as g, RefreshCw as _, X as v } from "lucide-react";
11
11
  import { Fragment as y, jsx as b, jsxs as x } from "react/jsx-runtime";
@@ -42,7 +42,7 @@ function C({ agent: d, onNavigateToSession: f }) {
42
42
  if (d.id) {
43
43
  e && w(!0);
44
44
  try {
45
- g(await o(d)), E(null);
45
+ g(await s(d)), E(null);
46
46
  } catch (e) {
47
47
  E(e.message);
48
48
  } finally {
@@ -58,9 +58,9 @@ function C({ agent: d, onNavigateToSession: f }) {
58
58
  let A = async (e) => {
59
59
  O(e);
60
60
  try {
61
- await a(d, e), s.success(p("aiQueue.cancelled", "Queued item cancelled")), await k(!1);
61
+ await o(d, e), a.success(p("aiQueue.cancelled", "Queued item cancelled")), await k(!1);
62
62
  } catch (e) {
63
- s.error(p("aiQueue.cancelFailed", "Could not cancel"), e.message);
63
+ a.error(p("aiQueue.cancelFailed", "Could not cancel"), e.message);
64
64
  } finally {
65
65
  O(null);
66
66
  }
@@ -0,0 +1,62 @@
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { AlertTriangle as t, CheckCircle2 as n, XCircle as r } from "lucide-react";
3
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
4
+ //#region src/components/components/ComponentHealthCell.tsx
5
+ function o(e) {
6
+ return e >= 80 ? "good" : e >= 50 ? "warn" : "bad";
7
+ }
8
+ var s = {
9
+ good: {
10
+ text: "text-status-success-text",
11
+ bar: "bg-status-success-text",
12
+ icon: n
13
+ },
14
+ warn: {
15
+ text: "text-status-warning-text",
16
+ bar: "bg-status-warning-text",
17
+ icon: t
18
+ },
19
+ bad: {
20
+ text: "text-status-error-text",
21
+ bar: "bg-status-error-text",
22
+ icon: r
23
+ }
24
+ }, c = ({ score: t, className: n = "" }) => {
25
+ let r = e();
26
+ if (t === null) return /* @__PURE__ */ i("span", {
27
+ className: `text-xs text-text-muted ${n}`,
28
+ title: r("component.healthNone", "No scorecard yet — add scorecard metrics to track this component"),
29
+ children: "—"
30
+ });
31
+ let c = Math.round(t), l = Math.max(0, Math.min(100, t)), u = s[o(t)], d = u.icon;
32
+ return /* @__PURE__ */ a("span", {
33
+ className: `flex items-center justify-end gap-2 ${n}`,
34
+ title: r("component.healthTitle", "Scorecard health: {{score}} out of 100", { score: c }),
35
+ children: [
36
+ /* @__PURE__ */ i("span", {
37
+ role: "progressbar",
38
+ "aria-valuenow": c,
39
+ "aria-valuemin": 0,
40
+ "aria-valuemax": 100,
41
+ "aria-label": r("component.columnHealth", "Health"),
42
+ className: "hidden h-1.5 w-16 overflow-hidden rounded-full bg-bg-sunken sm:block",
43
+ children: /* @__PURE__ */ i("span", {
44
+ className: `block h-full rounded-full ${u.bar}`,
45
+ style: { width: `${l}%` }
46
+ })
47
+ }),
48
+ /* @__PURE__ */ i("span", {
49
+ className: `tabular-nums text-xs font-semibold ${u.text}`,
50
+ children: c
51
+ }),
52
+ /* @__PURE__ */ i(d, {
53
+ className: `size-3.5 flex-shrink-0 ${u.text}`,
54
+ "aria-hidden": "true"
55
+ })
56
+ ]
57
+ });
58
+ };
59
+ //#endregion
60
+ export { c as ComponentHealthCell };
61
+
62
+ //# sourceMappingURL=ComponentHealthCell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComponentHealthCell.js","names":[],"sources":["../../../src/components/components/ComponentHealthCell.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { AlertTriangle, CheckCircle2, XCircle } from 'lucide-react';\nimport { useTr } from '@/shared/hooks/useTr';\n\ntype HealthBand = 'good' | 'warn' | 'bad';\n\n/**\n * Bands mirror `scoreColor` in ScorecardsOverviewPanel so the same\n * `scorecard.overallScore` reads the same everywhere in the product.\n */\nfunction bandFor(score: number): HealthBand {\n if (score >= 80) return 'good';\n if (score >= 50) return 'warn';\n return 'bad';\n}\n\nconst BAND_STYLE: Record<HealthBand, { text: string; bar: string; icon: typeof CheckCircle2 }> = {\n good: { text: 'text-status-success-text', bar: 'bg-status-success-text', icon: CheckCircle2 },\n warn: { text: 'text-status-warning-text', bar: 'bg-status-warning-text', icon: AlertTriangle },\n bad: { text: 'text-status-error-text', bar: 'bg-status-error-text', icon: XCircle },\n};\n\nexport interface ComponentHealthCellProps {\n /** Aggregate scorecard score (0-100). Null when the component has no scorecard. */\n score: number | null;\n className?: string;\n}\n\n/**\n * ComponentHealthCell — the score bar + value shown in the catalogue table's\n * Health column. Renders `scorecard.overallScore` only; nothing is estimated\n * client-side when the score is absent.\n */\nexport const ComponentHealthCell: FC<ComponentHealthCellProps> = ({ score, className = '' }) => {\n const tr = useTr();\n\n if (score === null) {\n return (\n <span\n className={`text-xs text-text-muted ${className}`}\n title={tr(\n 'component.healthNone',\n 'No scorecard yet — add scorecard metrics to track this component'\n )}\n >\n &mdash;\n </span>\n );\n }\n\n const rounded = Math.round(score);\n const pct = Math.max(0, Math.min(100, score));\n const style = BAND_STYLE[bandFor(score)];\n const Icon = style.icon;\n\n return (\n <span\n className={`flex items-center justify-end gap-2 ${className}`}\n title={tr('component.healthTitle', 'Scorecard health: {{score}} out of 100', {\n score: rounded,\n })}\n >\n <span\n role=\"progressbar\"\n aria-valuenow={rounded}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={tr('component.columnHealth', 'Health')}\n className=\"hidden h-1.5 w-16 overflow-hidden rounded-full bg-bg-sunken sm:block\"\n >\n <span className={`block h-full rounded-full ${style.bar}`} style={{ width: `${pct}%` }} />\n </span>\n <span className={`tabular-nums text-xs font-semibold ${style.text}`}>{rounded}</span>\n <Icon className={`size-3.5 flex-shrink-0 ${style.text}`} aria-hidden=\"true\" />\n </span>\n );\n};\n"],"mappings":";;;;AAUA,SAAS,EAAQ,GAA2B;AAG1C,QAFI,KAAS,KAAW,SACpB,KAAS,KAAW,SACjB;;AAGT,IAAM,IAA2F;CAC/F,MAAM;EAAE,MAAM;EAA4B,KAAK;EAA0B,MAAM;EAAc;CAC7F,MAAM;EAAE,MAAM;EAA4B,KAAK;EAA0B,MAAM;EAAe;CAC9F,KAAK;EAAE,MAAM;EAA0B,KAAK;EAAwB,MAAM;EAAS;CACpF,EAaY,KAAqD,EAAE,UAAO,eAAY,SAAS;CAC9F,IAAM,IAAK,GAAO;AAElB,KAAI,MAAU,KACZ,QACE,kBAAC,QAAD;EACE,WAAW,2BAA2B;EACtC,OAAO,EACL,wBACA,mEACD;YACF;EAEM,CAAA;CAIX,IAAM,IAAU,KAAK,MAAM,EAAM,EAC3B,IAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,EAAM,CAAC,EACvC,IAAQ,EAAW,EAAQ,EAAM,GACjC,IAAO,EAAM;AAEnB,QACE,kBAAC,QAAD;EACE,WAAW,uCAAuC;EAClD,OAAO,EAAG,yBAAyB,0CAA0C,EAC3E,OAAO,GACR,CAAC;YAJJ;GAME,kBAAC,QAAD;IACE,MAAK;IACL,iBAAe;IACf,iBAAe;IACf,iBAAe;IACf,cAAY,EAAG,0BAA0B,SAAS;IAClD,WAAU;cAEV,kBAAC,QAAD;KAAM,WAAW,6BAA6B,EAAM;KAAO,OAAO,EAAE,OAAO,GAAG,EAAI,IAAI;KAAI,CAAA;IACrF,CAAA;GACP,kBAAC,QAAD;IAAM,WAAW,sCAAsC,EAAM;cAAS;IAAe,CAAA;GACrF,kBAAC,GAAD;IAAM,WAAW,0BAA0B,EAAM;IAAQ,eAAY;IAAS,CAAA;GACzE"}
@@ -0,0 +1,42 @@
1
+ import { useTr as e } from "../../shared/hooks/useTr.js";
2
+ import { ScorecardTier as t } from "../../generated/wspace-types.js";
3
+ import { ShieldCheck as n } from "lucide-react";
4
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
5
+ //#region src/components/components/ComponentTierBadge.tsx
6
+ var a = {
7
+ [t.Bronze]: {
8
+ key: "component.tierBronze",
9
+ fallback: "Bronze",
10
+ cls: "bg-status-warning-bg-subtle text-status-warning-text border border-status-warning-border"
11
+ },
12
+ [t.Silver]: {
13
+ key: "component.tierSilver",
14
+ fallback: "Silver",
15
+ cls: "bg-bg-sunken text-text-secondary border border-border-strong"
16
+ },
17
+ [t.Gold]: {
18
+ key: "component.tierGold",
19
+ fallback: "Gold",
20
+ cls: "bg-status-success-bg-subtle text-status-success-text border border-status-success-border"
21
+ }
22
+ }, o = ({ tier: t, className: o = "" }) => {
23
+ let s = e();
24
+ if (!t) return /* @__PURE__ */ r("span", {
25
+ className: `text-xs text-text-muted ${o}`,
26
+ title: s("component.tierNone", "No tier yet — this component has no tier-tagged scorecard metrics"),
27
+ children: "—"
28
+ });
29
+ let c = a[t], l = s(c.key, c.fallback);
30
+ return /* @__PURE__ */ i("span", {
31
+ className: `inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-semibold ${c.cls} ${o}`,
32
+ title: s("component.tierTitle", "Security readiness tier: {{tier}}", { tier: l }),
33
+ children: [/* @__PURE__ */ r(n, {
34
+ className: "size-3",
35
+ "aria-hidden": "true"
36
+ }), l]
37
+ });
38
+ };
39
+ //#endregion
40
+ export { o as ComponentTierBadge };
41
+
42
+ //# sourceMappingURL=ComponentTierBadge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComponentTierBadge.js","names":[],"sources":["../../../src/components/components/ComponentTierBadge.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { ShieldCheck } from 'lucide-react';\nimport { ScorecardTier } from '@/generated/wspace-types';\nimport { useTr } from '@/shared/hooks/useTr';\n\n/**\n * Token styling mirrors the TierBadge already used on the component detail\n * page's scorecard panel, so a component grades identically in the catalogue\n * table and on its own page.\n */\nconst TIER_STYLE: Record<ScorecardTier, { key: string; fallback: string; cls: string }> = {\n [ScorecardTier.Bronze]: {\n key: 'component.tierBronze',\n fallback: 'Bronze',\n cls: 'bg-status-warning-bg-subtle text-status-warning-text border border-status-warning-border',\n },\n [ScorecardTier.Silver]: {\n key: 'component.tierSilver',\n fallback: 'Silver',\n cls: 'bg-bg-sunken text-text-secondary border border-border-strong',\n },\n [ScorecardTier.Gold]: {\n key: 'component.tierGold',\n fallback: 'Gold',\n cls: 'bg-status-success-bg-subtle text-status-success-text border border-status-success-border',\n },\n};\n\nexport interface ComponentTierBadgeProps {\n /** Server-derived production-readiness tier. Null when the component has no\n * scorecard, or when not even BRONZE is fully met. */\n tier: ScorecardTier | null;\n className?: string;\n}\n\n/**\n * ComponentTierBadge — read-only view of `scorecard.securityTier`.\n * The tier is computed server-side from tier-tagged scorecard metrics; there is\n * no client-side derivation here.\n */\nexport const ComponentTierBadge: FC<ComponentTierBadgeProps> = ({ tier, className = '' }) => {\n const tr = useTr();\n\n if (!tier) {\n return (\n <span\n className={`text-xs text-text-muted ${className}`}\n title={tr(\n 'component.tierNone',\n 'No tier yet — this component has no tier-tagged scorecard metrics'\n )}\n >\n &mdash;\n </span>\n );\n }\n\n const style = TIER_STYLE[tier];\n const label = tr(style.key, style.fallback);\n\n return (\n <span\n className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-semibold ${style.cls} ${className}`}\n title={tr('component.tierTitle', 'Security readiness tier: {{tier}}', { tier: label })}\n >\n <ShieldCheck className=\"size-3\" aria-hidden=\"true\" />\n {label}\n </span>\n );\n};\n"],"mappings":";;;;;AAUA,IAAM,IAAoF;EACvF,EAAc,SAAS;EACtB,KAAK;EACL,UAAU;EACV,KAAK;EACN;EACA,EAAc,SAAS;EACtB,KAAK;EACL,UAAU;EACV,KAAK;EACN;EACA,EAAc,OAAO;EACpB,KAAK;EACL,UAAU;EACV,KAAK;EACN;CACF,EAcY,KAAmD,EAAE,SAAM,eAAY,SAAS;CAC3F,IAAM,IAAK,GAAO;AAElB,KAAI,CAAC,EACH,QACE,kBAAC,QAAD;EACE,WAAW,2BAA2B;EACtC,OAAO,EACL,sBACA,oEACD;YACF;EAEM,CAAA;CAIX,IAAM,IAAQ,EAAW,IACnB,IAAQ,EAAG,EAAM,KAAK,EAAM,SAAS;AAE3C,QACE,kBAAC,QAAD;EACE,WAAW,iFAAiF,EAAM,IAAI,GAAG;EACzG,OAAO,EAAG,uBAAuB,qCAAqC,EAAE,MAAM,GAAO,CAAC;YAFxF,CAIE,kBAAC,GAAD;GAAa,WAAU;GAAS,eAAY;GAAS,CAAA,EACpD,EACI"}