@burdenoff/microfe-vibecontrols 2026.623.1 → 2026.623.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/actions/ActionCard.js +2 -2
- package/dist/components/actions/ActionCard.js.map +1 -1
- package/dist/components/actions/ExecutionHistory.js +1 -1
- package/dist/components/actions/ExecutionHistory.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js +2 -2
- package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js +4 -4
- package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerLayout.js +1 -1
- package/dist/components/agent-manager/AgentManagerLayout.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerMessageItem.js +1 -1
- package/dist/components/agent-manager/AgentManagerMessageItem.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerSidebar.js +5 -5
- package/dist/components/agent-manager/AgentManagerSidebar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerTab.js +1 -1
- package/dist/components/agent-manager/AgentManagerTab.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerTabBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerTabBar.js.map +1 -1
- package/dist/components/agent-manager/BridleConfigPanel.js +23 -23
- package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/PluginInstallModal.js +1 -1
- package/dist/components/agent-manager/PluginInstallModal.js.map +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js +5 -5
- package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionConfigPanel.js +6 -6
- package/dist/components/agent-manager/detail/SessionConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionLogsPanel.js +5 -5
- package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionToolsPanel.js +1 -1
- package/dist/components/agent-manager/detail/SessionToolsPanel.js.map +1 -1
- package/dist/components/agent-manager/dialogs/NewSessionDialog.js +2 -2
- package/dist/components/agent-manager/dialogs/NewSessionDialog.js.map +1 -1
- package/dist/components/agent-manager/dialogs/RenameSessionDialog.js +1 -1
- package/dist/components/agent-manager/dialogs/RenameSessionDialog.js.map +1 -1
- package/dist/components/agents/AgentCard.js +14 -11
- package/dist/components/agents/AgentCard.js.map +1 -1
- package/dist/components/ai/AIContextsTab.js +1 -1
- package/dist/components/ai/AIContextsTab.js.map +1 -1
- package/dist/components/ai/AIOverviewTab.js +4 -4
- package/dist/components/ai/AIOverviewTab.js.map +1 -1
- package/dist/components/ai/AIPlanPanel.js +4 -4
- package/dist/components/ai/AIPlanPanel.js.map +1 -1
- package/dist/components/ai/AIPlaygroundTab.js +1 -1
- package/dist/components/ai/AIPlaygroundTab.js.map +1 -1
- package/dist/components/ai/AIPromptsTab.js +2 -2
- package/dist/components/ai/AIPromptsTab.js.map +1 -1
- package/dist/components/ai/AIProviderCredentialsCard.js +4 -4
- package/dist/components/ai/AIProviderCredentialsCard.js.map +1 -1
- package/dist/components/ai/AIQueueTab.js +4 -4
- package/dist/components/ai/AIQueueTab.js.map +1 -1
- package/dist/components/ai/AISessionsTab.js +4 -4
- package/dist/components/ai/AISessionsTab.js.map +1 -1
- package/dist/components/ai/AITasksTab.js +1 -1
- package/dist/components/ai/AITasksTab.js.map +1 -1
- package/dist/components/ai/ClaudeCredentialsCard.js +3 -3
- package/dist/components/ai/ClaudeCredentialsCard.js.map +1 -1
- package/dist/components/ai/PluginHarnessPicker.js +3 -3
- package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
- package/dist/components/analytics/AnalyticsBreakdownChart.js +8 -8
- package/dist/components/analytics/AnalyticsBreakdownChart.js.map +1 -1
- package/dist/components/analytics/AnalyticsTimeSeriesChart.js +5 -5
- package/dist/components/analytics/AnalyticsTimeSeriesChart.js.map +1 -1
- package/dist/components/audit/AuditDeleteDialog.js +2 -2
- package/dist/components/audit/AuditDeleteDialog.js.map +1 -1
- package/dist/components/audit/AuditExportButton.js +5 -5
- package/dist/components/audit/AuditExportButton.js.map +1 -1
- package/dist/components/audit/AuditLogDetail.js +9 -9
- package/dist/components/audit/AuditLogDetail.js.map +1 -1
- package/dist/components/audit/AuditLogFilters.js +1 -1
- package/dist/components/audit/AuditLogFilters.js.map +1 -1
- package/dist/components/audit/AuditLogStats.js +1 -1
- package/dist/components/audit/AuditLogStats.js.map +1 -1
- package/dist/components/audit/AuditLogTable.js +28 -28
- package/dist/components/audit/AuditLogTable.js.map +1 -1
- package/dist/components/catalog/CatalogCard.js +3 -3
- package/dist/components/catalog/CatalogCard.js.map +1 -1
- package/dist/components/catalog/CatalogForm.js +1 -1
- package/dist/components/catalog/CatalogForm.js.map +1 -1
- package/dist/components/common/ActionAgentTargetNotice.js +6 -6
- package/dist/components/common/ActionAgentTargetNotice.js.map +1 -1
- package/dist/components/common/CommandEditor.js +1 -1
- package/dist/components/common/CommandEditor.js.map +1 -1
- package/dist/components/docs/DocsPageTree.js +1 -1
- package/dist/components/docs/DocsPageTree.js.map +1 -1
- package/dist/components/docs/DocsPreviewPanel.js +1 -1
- package/dist/components/docs/DocsPreviewPanel.js.map +1 -1
- package/dist/components/docs/DocsRevisionHistory.js +4 -4
- package/dist/components/docs/DocsRevisionHistory.js.map +1 -1
- package/dist/components/docs/DocsSettingsPanel.js +7 -7
- package/dist/components/docs/DocsSettingsPanel.js.map +1 -1
- package/dist/components/docs/DocsTabPanel.js +1 -1
- package/dist/components/docs/DocsTabPanel.js.map +1 -1
- package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
- package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
- package/dist/components/graph/MeshDeployWizard.js +3 -3
- package/dist/components/graph/MeshDeployWizard.js.map +1 -1
- package/dist/components/graph/MeshRequestPanel.js +2 -2
- package/dist/components/graph/MeshRequestPanel.js.map +1 -1
- package/dist/components/idp-components/ComponentCard.js +5 -5
- package/dist/components/idp-components/ComponentCard.js.map +1 -1
- package/dist/components/idp-components/ComponentForm.js +1 -1
- package/dist/components/idp-components/ComponentForm.js.map +1 -1
- package/dist/components/idp-components/DependencyGraphTab.js +7 -7
- package/dist/components/idp-components/DependencyGraphTab.js.map +1 -1
- package/dist/components/logs/LogViewer.js +14 -14
- package/dist/components/logs/LogViewer.js.map +1 -1
- package/dist/components/notes/NoteCard.js +1 -1
- package/dist/components/notes/NoteCard.js.map +1 -1
- package/dist/components/notes/NoteEditor.js +2 -2
- package/dist/components/notes/NoteEditor.js.map +1 -1
- package/dist/components/notes/NoteList.js +1 -1
- package/dist/components/notes/NoteList.js.map +1 -1
- package/dist/components/notes/NotesTabPanel.js +1 -1
- package/dist/components/notes/NotesTabPanel.js.map +1 -1
- package/dist/components/scorecard/ScorecardEmptyState.js +1 -1
- package/dist/components/scorecard/ScorecardEmptyState.js.map +1 -1
- package/dist/components/scorecard/ScorecardPanel.js +3 -3
- package/dist/components/scorecard/ScorecardPanel.js.map +1 -1
- package/dist/components/security/SecurityScanList.js +11 -11
- package/dist/components/security/SecurityScanList.js.map +1 -1
- package/dist/components/sessions/SessionCard.js +26 -19
- package/dist/components/sessions/SessionCard.js.map +1 -1
- package/dist/components/sessions/SessionStatusBadge.js +7 -7
- package/dist/components/sessions/SessionStatusBadge.js.map +1 -1
- package/dist/components/shared/AgentSelector.js +4 -4
- package/dist/components/shared/AgentSelector.js.map +1 -1
- package/dist/components/shared/AgentSetupBanner.js +3 -3
- package/dist/components/shared/AgentSetupBanner.js.map +1 -1
- package/dist/components/shared/Card.js +4 -4
- package/dist/components/shared/Card.js.map +1 -1
- package/dist/components/shared/ErrorState.js +15 -9
- package/dist/components/shared/ErrorState.js.map +1 -1
- package/dist/components/shared/FilterDropdown.js +2 -2
- package/dist/components/shared/FilterDropdown.js.map +1 -1
- package/dist/components/shared/PageSkeleton.js +6 -6
- package/dist/components/shared/PageSkeleton.js.map +1 -1
- package/dist/components/shared/SearchInput.js +1 -1
- package/dist/components/shared/SearchInput.js.map +1 -1
- package/dist/components/shared/StatCard.js +7 -7
- package/dist/components/shared/StatCard.js.map +1 -1
- package/dist/components/templates/PublishTemplateForm.js +2 -2
- package/dist/components/templates/PublishTemplateForm.js.map +1 -1
- package/dist/components/templates/TemplateCard.js +5 -5
- package/dist/components/templates/TemplateCard.js.map +1 -1
- package/dist/components/templates/TemplateWizard.js +2 -2
- package/dist/components/templates/TemplateWizard.js.map +1 -1
- package/dist/components/tunnels/DomainVerificationBadge.js +4 -4
- package/dist/components/tunnels/DomainVerificationBadge.js.map +1 -1
- package/dist/components/tunnels/ProviderBadge.js +1 -1
- package/dist/components/tunnels/ProviderBadge.js.map +1 -1
- package/dist/components/tunnels/TunnelCard.js +16 -16
- package/dist/components/tunnels/TunnelCard.js.map +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
- package/dist/components/tunnels/TunnelStatusBadge.js +4 -4
- package/dist/components/tunnels/TunnelStatusBadge.js.map +1 -1
- package/dist/components/vibecalendar/LogsDrawer.js +2 -2
- package/dist/components/vibecalendar/LogsDrawer.js.map +1 -1
- package/dist/components/vibecalendar/ScheduleTaskModal.js +5 -5
- package/dist/components/vibecalendar/ScheduleTaskModal.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalAgendaView.js +2 -2
- package/dist/components/vibecalendar/VibeCalAgendaView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalDayView.js +3 -3
- package/dist/components/vibecalendar/VibeCalDayView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalMonthView.js +1 -1
- package/dist/components/vibecalendar/VibeCalMonthView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalWeekView.js +4 -4
- package/dist/components/vibecalendar/VibeCalWeekView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalendarHeader.js +4 -4
- package/dist/components/vibecalendar/VibeCalendarHeader.js.map +1 -1
- package/dist/components/vibecalendar/VibeTaskBlock.js +2 -2
- package/dist/components/vibecalendar/VibeTaskBlock.js.map +1 -1
- package/dist/components/vibecalendar/types.js +6 -6
- package/dist/components/vibecalendar/types.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js +9 -9
- package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonTile.js +6 -6
- package/dist/components/vibedeck/VibeDeckButtonTile.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckExecutionLog.js +10 -10
- package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js +8 -8
- package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js +2 -2
- package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js +2 -2
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckSwitcher.js +8 -8
- package/dist/components/vibedeck/VibeDeckSwitcher.js.map +1 -1
- package/dist/components/vibes/VibeCard.js +22 -18
- package/dist/components/vibes/VibeCard.js.map +1 -1
- package/dist/components/vibes/VibeList.js +16 -12
- package/dist/components/vibes/VibeList.js.map +1 -1
- package/dist/pages/ActionsPage.js +4 -3
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +9 -9
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/AgentsLandingPage.js +4 -4
- package/dist/pages/AgentsLandingPage.js.map +1 -1
- package/dist/pages/AgentsPage.js +14 -14
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/AnalyticsPage.js +8 -8
- package/dist/pages/AnalyticsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +11 -11
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/CatalogPage.js +12 -9
- package/dist/pages/CatalogPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +7 -7
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ComponentsPage.js +12 -9
- package/dist/pages/ComponentsPage.js.map +1 -1
- package/dist/pages/EnvTopologyPage.js +2 -2
- package/dist/pages/EnvTopologyPage.js.map +1 -1
- package/dist/pages/GettingStartedPage.js +12 -12
- package/dist/pages/GettingStartedPage.js.map +1 -1
- package/dist/pages/HealthPage.js +12 -12
- package/dist/pages/HealthPage.js.map +1 -1
- package/dist/pages/LogsPage.js +4 -4
- package/dist/pages/LogsPage.js.map +1 -1
- package/dist/pages/NoteDetailsPage.js +5 -5
- package/dist/pages/NoteDetailsPage.js.map +1 -1
- package/dist/pages/OverviewPage.js +74 -41
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +6 -3
- package/dist/pages/PluginsCatalogPage.js.map +1 -1
- package/dist/pages/ScheduledJobDetailPage.js +7 -7
- package/dist/pages/ScheduledJobDetailPage.js.map +1 -1
- package/dist/pages/SessionDetailsPage.js +21 -21
- package/dist/pages/SessionDetailsPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +20 -20
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/SettingsPage.js +64 -52
- package/dist/pages/SettingsPage.js.map +1 -1
- package/dist/pages/SharedSessionPage.js +21 -21
- package/dist/pages/SharedSessionPage.js.map +1 -1
- package/dist/pages/SharedVibeDeckPage.js +5 -5
- package/dist/pages/SharedVibeDeckPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js +56 -65
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +4 -4
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +10 -10
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/TemplatesPage.js +6 -5
- package/dist/pages/TemplatesPage.js.map +1 -1
- package/dist/pages/VibeAuditPage.js +3 -3
- package/dist/pages/VibeAuditPage.js.map +1 -1
- package/dist/pages/VibeCalendarPage.js +3 -3
- package/dist/pages/VibeCalendarPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +3 -3
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +15 -15
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibeTaskDetailPage.js +5 -5
- package/dist/pages/VibeTaskDetailPage.js.map +1 -1
- package/dist/pages/VibesPage.js +18 -18
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/WebhooksPage.js +11 -11
- package/dist/pages/WebhooksPage.js.map +1 -1
- package/dist/pages/ai/AIBookmarksPage.js +1 -1
- package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
- package/dist/pages/ai/AISessionDetailPage.js +6 -6
- package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
- package/dist/pages/tunnels/AuditTab.js +1 -1
- package/dist/pages/tunnels/AuditTab.js.map +1 -1
- package/dist/pages/tunnels/DomainsTab.js +1 -1
- package/dist/pages/tunnels/DomainsTab.js.map +1 -1
- package/dist/pages/tunnels/ProvidersTab.js +1 -1
- package/dist/pages/tunnels/ProvidersTab.js.map +1 -1
- package/dist/pages/tunnels/SessionsTab.js +1 -1
- package/dist/pages/tunnels/SessionsTab.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +3 -3
- package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
- package/dist/pages/tunnels/UsageTab.js +1 -1
- package/dist/pages/tunnels/UsageTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +16 -16
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +52 -49
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +9 -9
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,15 +5,15 @@ import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
|
5
5
|
//#region src/components/security/SecurityScanList.tsx
|
|
6
6
|
var f = {
|
|
7
7
|
queued: "bg-bg-sunken text-text-secondary",
|
|
8
|
-
running: "bg-status-info-bg
|
|
9
|
-
succeeded: "bg-status-success-bg
|
|
10
|
-
failed: "bg-status-error-bg
|
|
11
|
-
errored: "bg-status-error-bg
|
|
8
|
+
running: "bg-status-info-bg-subtle text-status-info-text",
|
|
9
|
+
succeeded: "bg-status-success-bg-subtle text-status-success-text",
|
|
10
|
+
failed: "bg-status-error-bg-subtle text-status-error-text",
|
|
11
|
+
errored: "bg-status-error-bg-subtle text-status-error-text",
|
|
12
12
|
cancelled: "bg-bg-sunken text-text-tertiary"
|
|
13
13
|
}, p = {
|
|
14
|
-
pass: "bg-status-success-bg
|
|
15
|
-
warn: "bg-status-warning-bg
|
|
16
|
-
fail: "bg-status-error-bg
|
|
14
|
+
pass: "bg-status-success-bg-subtle text-status-success-text",
|
|
15
|
+
warn: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
16
|
+
fail: "bg-status-error-bg-subtle text-status-error-text",
|
|
17
17
|
skipped: "bg-bg-sunken text-text-tertiary"
|
|
18
18
|
};
|
|
19
19
|
function m(e) {
|
|
@@ -70,17 +70,17 @@ function v({ run: e }) {
|
|
|
70
70
|
className: "flex items-center gap-1 text-xs",
|
|
71
71
|
children: [
|
|
72
72
|
t.critical > 0 && /* @__PURE__ */ d("span", {
|
|
73
|
-
className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-error-bg
|
|
73
|
+
className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-error-bg-subtle text-status-error-text font-mono",
|
|
74
74
|
title: `${t.critical} critical`,
|
|
75
75
|
children: ["C ", t.critical]
|
|
76
76
|
}),
|
|
77
77
|
t.high > 0 && /* @__PURE__ */ d("span", {
|
|
78
|
-
className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg
|
|
78
|
+
className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg-subtle text-status-warning-text font-mono",
|
|
79
79
|
title: `${t.high} high`,
|
|
80
80
|
children: ["H ", t.high]
|
|
81
81
|
}),
|
|
82
82
|
t.medium > 0 && /* @__PURE__ */ d("span", {
|
|
83
|
-
className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg
|
|
83
|
+
className: "inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg-subtle text-status-warning-text/80 font-mono",
|
|
84
84
|
title: `${t.medium} medium`,
|
|
85
85
|
children: ["M ", t.medium]
|
|
86
86
|
}),
|
|
@@ -165,7 +165,7 @@ function b({ vibeId: r, agentId: i, profile: a, limit: o = 20 }) {
|
|
|
165
165
|
})]
|
|
166
166
|
}),
|
|
167
167
|
x && /* @__PURE__ */ d("div", {
|
|
168
|
-
className: "m-4 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg/10 p-3 text-sm",
|
|
168
|
+
className: "m-4 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg-subtle/10 p-3 text-sm",
|
|
169
169
|
children: [/* @__PURE__ */ u(n, { className: "size-4 text-status-error-text mt-0.5 shrink-0" }), /* @__PURE__ */ d("div", {
|
|
170
170
|
className: "text-text-primary",
|
|
171
171
|
children: [/* @__PURE__ */ u("p", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityScanList.js","names":[],"sources":["../../../src/components/security/SecurityScanList.tsx"],"sourcesContent":["/**\n * Recent scan runs list for a vibe, read directly from the agent over\n * the tunnel. No backend GraphQL — see `./agentClient.ts` for the\n * REST surface and `./hooks.ts` for the React Query wrappers.\n *\n * The agent doesn't expose a list-runs endpoint; we derive distinct\n * run ids from the findings cache (see `listRecentScanRuns`). When a\n * vibe has zero findings the list will be empty even if scans have\n * been queued — that's correct: an in-flight queued/running scan\n * doesn't yet have any findings, and the empty state already nudges\n * the user toward kicking off a scan.\n */\nimport { useMemo, type ReactElement } from 'react';\nimport {\n AlertCircle,\n CheckCircle2,\n Clock,\n Loader2,\n MinusCircle,\n ShieldAlert,\n XCircle,\n} from 'lucide-react';\n\nimport { useSecurityRecentScansQuery, type UseSecurityAgentContext } from './hooks';\nimport type { AgentScanConclusion, AgentScanStatus, SecurityScanRun } from './agentClient';\n\ninterface Props extends UseSecurityAgentContext {\n vibeId: string;\n /** Max rows to surface. Defaults to 20. */\n limit?: number;\n}\n\nconst STATUS_BADGE: Record<AgentScanStatus, string> = {\n queued: 'bg-bg-sunken text-text-secondary',\n running: 'bg-status-info-bg/20 text-status-info-text',\n succeeded: 'bg-status-success-bg/20 text-status-success-text',\n failed: 'bg-status-error-bg/20 text-status-error-text',\n errored: 'bg-status-error-bg/20 text-status-error-text',\n cancelled: 'bg-bg-sunken text-text-tertiary',\n};\n\nconst CONCLUSION_BADGE: Record<AgentScanConclusion, string> = {\n pass: 'bg-status-success-bg/20 text-status-success-text',\n warn: 'bg-status-warning-bg/20 text-status-warning-text',\n fail: 'bg-status-error-bg/20 text-status-error-text',\n skipped: 'bg-bg-sunken text-text-tertiary',\n};\n\nfunction statusIcon(status: AgentScanStatus): ReactElement {\n switch (status) {\n case 'queued':\n return <Clock className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'running':\n return <Loader2 className=\"size-3.5 animate-spin\" aria-hidden=\"true\" />;\n case 'succeeded':\n return <CheckCircle2 className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'failed':\n case 'errored':\n return <XCircle className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'cancelled':\n return <MinusCircle className=\"size-3.5\" aria-hidden=\"true\" />;\n }\n}\n\nfunction relativeTime(epochMs?: number): string {\n if (!epochMs) return '—';\n const diff = Date.now() - epochMs;\n const sec = Math.round(diff / 1000);\n if (sec < 60) return `${sec}s ago`;\n const min = Math.round(sec / 60);\n if (min < 60) return `${min}m ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr}h ago`;\n const day = Math.round(hr / 24);\n return `${day}d ago`;\n}\n\nfunction formatDuration(ms?: number): string {\n if (ms === undefined || ms === null) return '—';\n if (ms < 1000) return `${ms}ms`;\n const sec = ms / 1000;\n if (sec < 60) return `${sec.toFixed(1)}s`;\n const min = sec / 60;\n if (min < 60) return `${min.toFixed(1)}m`;\n const hr = min / 60;\n return `${hr.toFixed(1)}h`;\n}\n\n/**\n * Prettify the agent's dotted stage id (e.g. `pull_request.fast` →\n * `pull_request fast`). We don't gate on the canonical 14-stage taxonomy\n * here because providers can — in principle — emit arbitrary stages and\n * we want to surface them rather than hide them.\n */\nfunction formatStage(stage: string): string {\n return stage.replace(/\\./g, ' ');\n}\n\nfunction SummaryCell({ run }: { run: SecurityScanRun }): ReactElement {\n const s = run.summary;\n const total = s.critical + s.high + s.medium + s.low + s.info;\n if (total === 0) {\n return <span className=\"text-xs text-text-tertiary\">clean</span>;\n }\n return (\n <div className=\"flex items-center gap-1 text-xs\">\n {s.critical > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-error-bg/20 text-status-error-text font-mono\"\n title={`${s.critical} critical`}\n >\n C {s.critical}\n </span>\n )}\n {s.high > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg/20 text-status-warning-text font-mono\"\n title={`${s.high} high`}\n >\n H {s.high}\n </span>\n )}\n {s.medium > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg/10 text-status-warning-text/80 font-mono\"\n title={`${s.medium} medium`}\n >\n M {s.medium}\n </span>\n )}\n {s.low > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-bg-sunken text-text-secondary font-mono\"\n title={`${s.low} low`}\n >\n L {s.low}\n </span>\n )}\n {s.info > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-bg-sunken text-text-tertiary font-mono\"\n title={`${s.info} info`}\n >\n I {s.info}\n </span>\n )}\n </div>\n );\n}\n\nfunction SkeletonRow({ rowKey }: { rowKey: number }): ReactElement {\n return (\n <tr key={rowKey} className=\"border-t border-border-subtle\">\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-20 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-24 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-16 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-16 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-20 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-14 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-28 rounded bg-bg-sunken animate-pulse\" />\n </td>\n </tr>\n );\n}\n\nexport function SecurityScanList({ vibeId, agentId, profile, limit = 20 }: Props): ReactElement {\n const ctx = useMemo(() => ({ agentId, profile }), [agentId, profile]);\n const { data, isLoading, error, isFetching, refetch } = useSecurityRecentScansQuery(\n ctx,\n vibeId,\n limit\n );\n\n // F4 Wave C: agentApiKey + tunnelUrl no longer needed in the browser —\n // the svc proxy owns them. A non-null `agentId` is the only readiness\n // gate (svc rejects unreachable agents with a structured envelope).\n const ready = !!agentId;\n\n if (!ready) {\n return (\n <section\n data-testid=\"security-scan-list\"\n className=\"rounded-lg border border-border-default bg-bg-surface p-4 text-sm text-text-secondary\"\n >\n Connect to a reachable agent to load scan history. Scan runs are read from the agent via the\n svc GraphQL proxy — there's no backend cache.\n </section>\n );\n }\n\n return (\n <section\n data-testid=\"security-scan-list\"\n className=\"rounded-lg border border-border-default bg-bg-surface\"\n >\n <header className=\"flex items-center justify-between gap-3 border-b border-border-default px-4 py-3\">\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Recent scan runs</h3>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Read live from the agent. Showing up to {limit} most recently active runs for this vibe.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => void refetch()}\n disabled={isFetching}\n className=\"px-2.5 py-1 text-xs rounded-md border border-border-default bg-bg-sunken text-text-primary hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {isFetching ? 'Refreshing…' : 'Refresh'}\n </button>\n </header>\n\n {error && (\n <div className=\"m-4 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg/10 p-3 text-sm\">\n <AlertCircle className=\"size-4 text-status-error-text mt-0.5 shrink-0\" />\n <div className=\"text-text-primary\">\n <p className=\"font-medium\">Couldn't load scan runs</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{error.message}</p>\n </div>\n </div>\n )}\n\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-bg-sunken/40 text-text-secondary\">\n <tr>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Stage</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Provider</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Status</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Conclusion</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Started</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Duration</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Findings</th>\n </tr>\n </thead>\n <tbody>\n {isLoading && data === undefined && (\n <>\n <SkeletonRow rowKey={0} />\n <SkeletonRow rowKey={1} />\n <SkeletonRow rowKey={2} />\n </>\n )}\n {!isLoading && (data?.length ?? 0) === 0 && !error && (\n <tr>\n <td colSpan={7} className=\"px-3 py-8\">\n <div className=\"flex flex-col items-center gap-2 text-center\">\n <ShieldAlert className=\"size-6 text-text-tertiary\" />\n <p className=\"text-sm text-text-primary\">No scans yet</p>\n <p className=\"text-xs text-text-secondary max-w-md\">\n Install a provider above and run a scan to see results here. Findings live in\n the agent's local SQLite cache.\n </p>\n </div>\n </td>\n </tr>\n )}\n {(data ?? []).map((run) => (\n <tr key={run.runId} className=\"border-t border-border-subtle align-middle\">\n <td className=\"px-3 py-2\">\n <span className=\"inline-flex items-center rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-primary\">\n {formatStage(run.stage)}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-text-primary\">{run.providerName}</td>\n <td className=\"px-3 py-2\">\n <span\n className={`inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs font-medium ${STATUS_BADGE[run.status]}`}\n >\n {statusIcon(run.status)}\n {run.status}\n </span>\n </td>\n <td className=\"px-3 py-2\">\n {run.conclusion ? (\n <span\n className={`inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium ${CONCLUSION_BADGE[run.conclusion]}`}\n >\n {run.conclusion}\n </span>\n ) : (\n <span className=\"text-xs text-text-tertiary\">—</span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary\">\n <span title={run.startedAt ? new Date(run.startedAt).toLocaleString() : ''}>\n {relativeTime(run.startedAt)}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary font-mono\">\n {formatDuration(run.durationMs)}\n </td>\n <td className=\"px-3 py-2\">\n <SummaryCell run={run} />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </section>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SecurityScanList.js","names":[],"sources":["../../../src/components/security/SecurityScanList.tsx"],"sourcesContent":["/**\n * Recent scan runs list for a vibe, read directly from the agent over\n * the tunnel. No backend GraphQL — see `./agentClient.ts` for the\n * REST surface and `./hooks.ts` for the React Query wrappers.\n *\n * The agent doesn't expose a list-runs endpoint; we derive distinct\n * run ids from the findings cache (see `listRecentScanRuns`). When a\n * vibe has zero findings the list will be empty even if scans have\n * been queued — that's correct: an in-flight queued/running scan\n * doesn't yet have any findings, and the empty state already nudges\n * the user toward kicking off a scan.\n */\nimport { useMemo, type ReactElement } from 'react';\nimport {\n AlertCircle,\n CheckCircle2,\n Clock,\n Loader2,\n MinusCircle,\n ShieldAlert,\n XCircle,\n} from 'lucide-react';\n\nimport { useSecurityRecentScansQuery, type UseSecurityAgentContext } from './hooks';\nimport type { AgentScanConclusion, AgentScanStatus, SecurityScanRun } from './agentClient';\n\ninterface Props extends UseSecurityAgentContext {\n vibeId: string;\n /** Max rows to surface. Defaults to 20. */\n limit?: number;\n}\n\nconst STATUS_BADGE: Record<AgentScanStatus, string> = {\n queued: 'bg-bg-sunken text-text-secondary',\n running: 'bg-status-info-bg-subtle text-status-info-text',\n succeeded: 'bg-status-success-bg-subtle text-status-success-text',\n failed: 'bg-status-error-bg-subtle text-status-error-text',\n errored: 'bg-status-error-bg-subtle text-status-error-text',\n cancelled: 'bg-bg-sunken text-text-tertiary',\n};\n\nconst CONCLUSION_BADGE: Record<AgentScanConclusion, string> = {\n pass: 'bg-status-success-bg-subtle text-status-success-text',\n warn: 'bg-status-warning-bg-subtle text-status-warning-text',\n fail: 'bg-status-error-bg-subtle text-status-error-text',\n skipped: 'bg-bg-sunken text-text-tertiary',\n};\n\nfunction statusIcon(status: AgentScanStatus): ReactElement {\n switch (status) {\n case 'queued':\n return <Clock className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'running':\n return <Loader2 className=\"size-3.5 animate-spin\" aria-hidden=\"true\" />;\n case 'succeeded':\n return <CheckCircle2 className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'failed':\n case 'errored':\n return <XCircle className=\"size-3.5\" aria-hidden=\"true\" />;\n case 'cancelled':\n return <MinusCircle className=\"size-3.5\" aria-hidden=\"true\" />;\n }\n}\n\nfunction relativeTime(epochMs?: number): string {\n if (!epochMs) return '—';\n const diff = Date.now() - epochMs;\n const sec = Math.round(diff / 1000);\n if (sec < 60) return `${sec}s ago`;\n const min = Math.round(sec / 60);\n if (min < 60) return `${min}m ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr}h ago`;\n const day = Math.round(hr / 24);\n return `${day}d ago`;\n}\n\nfunction formatDuration(ms?: number): string {\n if (ms === undefined || ms === null) return '—';\n if (ms < 1000) return `${ms}ms`;\n const sec = ms / 1000;\n if (sec < 60) return `${sec.toFixed(1)}s`;\n const min = sec / 60;\n if (min < 60) return `${min.toFixed(1)}m`;\n const hr = min / 60;\n return `${hr.toFixed(1)}h`;\n}\n\n/**\n * Prettify the agent's dotted stage id (e.g. `pull_request.fast` →\n * `pull_request fast`). We don't gate on the canonical 14-stage taxonomy\n * here because providers can — in principle — emit arbitrary stages and\n * we want to surface them rather than hide them.\n */\nfunction formatStage(stage: string): string {\n return stage.replace(/\\./g, ' ');\n}\n\nfunction SummaryCell({ run }: { run: SecurityScanRun }): ReactElement {\n const s = run.summary;\n const total = s.critical + s.high + s.medium + s.low + s.info;\n if (total === 0) {\n return <span className=\"text-xs text-text-tertiary\">clean</span>;\n }\n return (\n <div className=\"flex items-center gap-1 text-xs\">\n {s.critical > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-error-bg-subtle text-status-error-text font-mono\"\n title={`${s.critical} critical`}\n >\n C {s.critical}\n </span>\n )}\n {s.high > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg-subtle text-status-warning-text font-mono\"\n title={`${s.high} high`}\n >\n H {s.high}\n </span>\n )}\n {s.medium > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-status-warning-bg-subtle text-status-warning-text/80 font-mono\"\n title={`${s.medium} medium`}\n >\n M {s.medium}\n </span>\n )}\n {s.low > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-bg-sunken text-text-secondary font-mono\"\n title={`${s.low} low`}\n >\n L {s.low}\n </span>\n )}\n {s.info > 0 && (\n <span\n className=\"inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 bg-bg-sunken text-text-tertiary font-mono\"\n title={`${s.info} info`}\n >\n I {s.info}\n </span>\n )}\n </div>\n );\n}\n\nfunction SkeletonRow({ rowKey }: { rowKey: number }): ReactElement {\n return (\n <tr key={rowKey} className=\"border-t border-border-subtle\">\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-20 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-24 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-16 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-16 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-20 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-14 rounded bg-bg-sunken animate-pulse\" />\n </td>\n <td className=\"px-3 py-2\">\n <div className=\"h-3 w-28 rounded bg-bg-sunken animate-pulse\" />\n </td>\n </tr>\n );\n}\n\nexport function SecurityScanList({ vibeId, agentId, profile, limit = 20 }: Props): ReactElement {\n const ctx = useMemo(() => ({ agentId, profile }), [agentId, profile]);\n const { data, isLoading, error, isFetching, refetch } = useSecurityRecentScansQuery(\n ctx,\n vibeId,\n limit\n );\n\n // F4 Wave C: agentApiKey + tunnelUrl no longer needed in the browser —\n // the svc proxy owns them. A non-null `agentId` is the only readiness\n // gate (svc rejects unreachable agents with a structured envelope).\n const ready = !!agentId;\n\n if (!ready) {\n return (\n <section\n data-testid=\"security-scan-list\"\n className=\"rounded-lg border border-border-default bg-bg-surface p-4 text-sm text-text-secondary\"\n >\n Connect to a reachable agent to load scan history. Scan runs are read from the agent via the\n svc GraphQL proxy — there's no backend cache.\n </section>\n );\n }\n\n return (\n <section\n data-testid=\"security-scan-list\"\n className=\"rounded-lg border border-border-default bg-bg-surface\"\n >\n <header className=\"flex items-center justify-between gap-3 border-b border-border-default px-4 py-3\">\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Recent scan runs</h3>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Read live from the agent. Showing up to {limit} most recently active runs for this vibe.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => void refetch()}\n disabled={isFetching}\n className=\"px-2.5 py-1 text-xs rounded-md border border-border-default bg-bg-sunken text-text-primary hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {isFetching ? 'Refreshing…' : 'Refresh'}\n </button>\n </header>\n\n {error && (\n <div className=\"m-4 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg-subtle/10 p-3 text-sm\">\n <AlertCircle className=\"size-4 text-status-error-text mt-0.5 shrink-0\" />\n <div className=\"text-text-primary\">\n <p className=\"font-medium\">Couldn't load scan runs</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{error.message}</p>\n </div>\n </div>\n )}\n\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-bg-sunken/40 text-text-secondary\">\n <tr>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Stage</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Provider</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Status</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Conclusion</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Started</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Duration</th>\n <th className=\"px-3 py-2 text-left text-xs font-medium\">Findings</th>\n </tr>\n </thead>\n <tbody>\n {isLoading && data === undefined && (\n <>\n <SkeletonRow rowKey={0} />\n <SkeletonRow rowKey={1} />\n <SkeletonRow rowKey={2} />\n </>\n )}\n {!isLoading && (data?.length ?? 0) === 0 && !error && (\n <tr>\n <td colSpan={7} className=\"px-3 py-8\">\n <div className=\"flex flex-col items-center gap-2 text-center\">\n <ShieldAlert className=\"size-6 text-text-tertiary\" />\n <p className=\"text-sm text-text-primary\">No scans yet</p>\n <p className=\"text-xs text-text-secondary max-w-md\">\n Install a provider above and run a scan to see results here. Findings live in\n the agent's local SQLite cache.\n </p>\n </div>\n </td>\n </tr>\n )}\n {(data ?? []).map((run) => (\n <tr key={run.runId} className=\"border-t border-border-subtle align-middle\">\n <td className=\"px-3 py-2\">\n <span className=\"inline-flex items-center rounded-md bg-bg-sunken px-2 py-0.5 text-xs text-text-primary\">\n {formatStage(run.stage)}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-text-primary\">{run.providerName}</td>\n <td className=\"px-3 py-2\">\n <span\n className={`inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs font-medium ${STATUS_BADGE[run.status]}`}\n >\n {statusIcon(run.status)}\n {run.status}\n </span>\n </td>\n <td className=\"px-3 py-2\">\n {run.conclusion ? (\n <span\n className={`inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium ${CONCLUSION_BADGE[run.conclusion]}`}\n >\n {run.conclusion}\n </span>\n ) : (\n <span className=\"text-xs text-text-tertiary\">—</span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary\">\n <span title={run.startedAt ? new Date(run.startedAt).toLocaleString() : ''}>\n {relativeTime(run.startedAt)}\n </span>\n </td>\n <td className=\"px-3 py-2 text-xs text-text-secondary font-mono\">\n {formatDuration(run.durationMs)}\n </td>\n <td className=\"px-3 py-2\">\n <SummaryCell run={run} />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </section>\n );\n}\n"],"mappings":"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"}
|
|
@@ -32,7 +32,7 @@ var _ = {
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
whileTap: { scale: .98 },
|
|
35
|
-
className: `bg-bg-surface border rounded-
|
|
35
|
+
className: `bg-bg-surface border rounded-card shadow-elevation-1 p-5 hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 ease-[var(--motion-easing-out)] cursor-pointer ${E ? "border-transparent ring-1 ring-[var(--color-focus-ring)] bg-[var(--color-accent-soft)]" : "border-border-subtle"} ${O}`,
|
|
36
36
|
onClick: () => y?.(v),
|
|
37
37
|
children: [
|
|
38
38
|
/* @__PURE__ */ g("div", {
|
|
@@ -54,8 +54,8 @@ var _ = {
|
|
|
54
54
|
})
|
|
55
55
|
}),
|
|
56
56
|
/* @__PURE__ */ h("div", {
|
|
57
|
-
className: "flex-shrink-0 size-
|
|
58
|
-
children: /* @__PURE__ */ h(d, { className: "size-4 sm:
|
|
57
|
+
className: "flex-shrink-0 size-9 sm:size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center",
|
|
58
|
+
children: /* @__PURE__ */ h(d, { className: "size-4 sm:size-5 text-action-primary-bg" })
|
|
59
59
|
}),
|
|
60
60
|
/* @__PURE__ */ g("div", {
|
|
61
61
|
className: "min-w-0 flex-1",
|
|
@@ -63,7 +63,7 @@ var _ = {
|
|
|
63
63
|
/* @__PURE__ */ g("div", {
|
|
64
64
|
className: "flex items-center gap-1.5 sm:gap-2 flex-wrap",
|
|
65
65
|
children: [/* @__PURE__ */ h("h3", {
|
|
66
|
-
className: "font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base",
|
|
66
|
+
className: "font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base tracking-tight",
|
|
67
67
|
children: v.name
|
|
68
68
|
}), /* @__PURE__ */ h("span", {
|
|
69
69
|
className: "text-base sm:text-lg flex-shrink-0",
|
|
@@ -90,8 +90,12 @@ var _ = {
|
|
|
90
90
|
onClick: (e) => {
|
|
91
91
|
e.stopPropagation(), j(!A);
|
|
92
92
|
},
|
|
93
|
-
className: "
|
|
94
|
-
|
|
93
|
+
className: "inline-flex size-8 items-center justify-center rounded-button text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
94
|
+
"aria-label": k("vibecontrols.sessions.card.actions", "Session actions"),
|
|
95
|
+
children: /* @__PURE__ */ h(o, {
|
|
96
|
+
className: "size-4",
|
|
97
|
+
"aria-hidden": "true"
|
|
98
|
+
})
|
|
95
99
|
}), /* @__PURE__ */ h(p, { children: A && /* @__PURE__ */ g(m.div, {
|
|
96
100
|
initial: {
|
|
97
101
|
opacity: 0,
|
|
@@ -109,14 +113,14 @@ var _ = {
|
|
|
109
113
|
y: -4
|
|
110
114
|
},
|
|
111
115
|
transition: { duration: .15 },
|
|
112
|
-
className: "absolute right-0 mt-1 w-
|
|
116
|
+
className: "absolute right-0 mt-1.5 w-44 bg-bg-elevated border border-border-subtle rounded-card shadow-elevation-3 z-10 overflow-hidden py-1",
|
|
113
117
|
children: [
|
|
114
118
|
P && b && /* @__PURE__ */ g(m.button, {
|
|
115
119
|
whileTap: { scale: .97 },
|
|
116
120
|
onClick: (e) => {
|
|
117
121
|
e.stopPropagation(), b(v), j(!1);
|
|
118
122
|
},
|
|
119
|
-
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken
|
|
123
|
+
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
|
|
120
124
|
children: [/* @__PURE__ */ h(s, { className: "size-4" }), k("vibecontrols.sessions.card.start", "Start")]
|
|
121
125
|
}),
|
|
122
126
|
N && x && /* @__PURE__ */ g(m.button, {
|
|
@@ -124,7 +128,7 @@ var _ = {
|
|
|
124
128
|
onClick: (e) => {
|
|
125
129
|
e.stopPropagation(), x(v), j(!1);
|
|
126
130
|
},
|
|
127
|
-
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
|
|
131
|
+
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
|
|
128
132
|
children: [/* @__PURE__ */ h(u, { className: "size-4" }), k("vibecontrols.sessions.card.stop", "Stop")]
|
|
129
133
|
}),
|
|
130
134
|
N && S && /* @__PURE__ */ g(m.button, {
|
|
@@ -132,7 +136,7 @@ var _ = {
|
|
|
132
136
|
onClick: (e) => {
|
|
133
137
|
e.stopPropagation(), S(v), j(!1);
|
|
134
138
|
},
|
|
135
|
-
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
|
|
139
|
+
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
|
|
136
140
|
children: [/* @__PURE__ */ h(c, { className: "size-4" }), k("vibecontrols.sessions.card.restart", "Restart")]
|
|
137
141
|
}),
|
|
138
142
|
N && w && /* @__PURE__ */ g(m.button, {
|
|
@@ -140,7 +144,7 @@ var _ = {
|
|
|
140
144
|
onClick: (e) => {
|
|
141
145
|
e.stopPropagation(), w(v), j(!1);
|
|
142
146
|
},
|
|
143
|
-
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
|
|
147
|
+
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
|
|
144
148
|
children: [/* @__PURE__ */ h(d, { className: "size-4" }), k("vibecontrols.sessions.card.openTerminal", "Open Terminal")]
|
|
145
149
|
}),
|
|
146
150
|
N && T && /* @__PURE__ */ g(m.button, {
|
|
@@ -148,7 +152,7 @@ var _ = {
|
|
|
148
152
|
onClick: (e) => {
|
|
149
153
|
e.stopPropagation(), T(v), j(!1);
|
|
150
154
|
},
|
|
151
|
-
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
|
|
155
|
+
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors",
|
|
152
156
|
children: [/* @__PURE__ */ h(l, { className: "size-4" }), k("vibecontrols.sessions.card.shareSession", "Share Session")]
|
|
153
157
|
}),
|
|
154
158
|
C && /* @__PURE__ */ g(m.button, {
|
|
@@ -156,7 +160,7 @@ var _ = {
|
|
|
156
160
|
onClick: (e) => {
|
|
157
161
|
e.stopPropagation(), C(v), j(!1);
|
|
158
162
|
},
|
|
159
|
-
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-
|
|
163
|
+
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-status-error-bg-subtle transition-colors",
|
|
160
164
|
children: [/* @__PURE__ */ h(f, { className: "size-4" }), k("vibecontrols.sessions.card.terminate", "Terminate")]
|
|
161
165
|
})
|
|
162
166
|
]
|
|
@@ -167,7 +171,7 @@ var _ = {
|
|
|
167
171
|
(() => {
|
|
168
172
|
let e = v.metadata || {}, t = typeof e.tmuxSessionName == "string" && e.tmuxSessionName || typeof e.weztermWorkspace == "string" && e.weztermWorkspace || typeof e.zellijSessionName == "string" && e.zellijSessionName || typeof e.externalSessionName == "string" && e.externalSessionName || v.providerSessionName || null;
|
|
169
173
|
return t ? /* @__PURE__ */ g("div", {
|
|
170
|
-
className: "mt-
|
|
174
|
+
className: "mt-3 text-[10px] font-mono text-text-muted",
|
|
171
175
|
title: `Provider session: ${t}`,
|
|
172
176
|
"data-testid": "session-card-external-name",
|
|
173
177
|
children: [/* @__PURE__ */ g("span", {
|
|
@@ -180,20 +184,23 @@ var _ = {
|
|
|
180
184
|
}) : null;
|
|
181
185
|
})(),
|
|
182
186
|
v.command && /* @__PURE__ */ g("div", {
|
|
183
|
-
className: "mt-
|
|
187
|
+
className: "mt-4 text-xs font-mono bg-bg-sunken/60 px-2 py-1.5 rounded truncate text-text-secondary",
|
|
184
188
|
children: ["$ ", v.command]
|
|
185
189
|
}),
|
|
186
190
|
v.lastOutput && /* @__PURE__ */ h("div", {
|
|
187
|
-
className: "mt-2 text-xs font-mono bg-bg-sunken text-status-success-text
|
|
191
|
+
className: "mt-2 text-xs font-mono bg-bg-sunken/60 text-status-success-text px-2 py-1.5 rounded truncate",
|
|
188
192
|
children: v.lastOutput
|
|
189
193
|
}),
|
|
190
194
|
/* @__PURE__ */ g("div", {
|
|
191
|
-
className: "mt-
|
|
195
|
+
className: "mt-4 flex items-center justify-between text-sm text-text-secondary",
|
|
192
196
|
children: [/* @__PURE__ */ g("div", {
|
|
193
197
|
className: "flex items-center gap-2",
|
|
194
|
-
children: [/* @__PURE__ */ h(a, { className: "size-4" }), /* @__PURE__ */ h("span", {
|
|
198
|
+
children: [/* @__PURE__ */ h(a, { className: "size-4" }), /* @__PURE__ */ h("span", {
|
|
199
|
+
className: "tabular-nums",
|
|
200
|
+
children: M(v.updatedAt)
|
|
201
|
+
})]
|
|
195
202
|
}), v.pid && /* @__PURE__ */ g("span", {
|
|
196
|
-
className: "text-xs font-mono",
|
|
203
|
+
className: "text-xs font-mono tabular-nums",
|
|
197
204
|
children: ["PID: ", v.pid]
|
|
198
205
|
})]
|
|
199
206
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/components/sessions/SessionCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Terminal,\n Clock,\n MoreVertical,\n Play,\n Square,\n RotateCcw,\n Trash2,\n Share2,\n} from 'lucide-react';\nimport { SessionStatusBadge } from './SessionStatusBadge';\nimport { AgentInfoPopover, TargetInfoPopover } from './EntityInfoPopover';\nimport type { Session } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface SessionCardProps {\n session: Session;\n onOpen?: (session: Session) => void;\n onStart?: (session: Session) => void;\n onStop?: (session: Session) => void;\n onRestart?: (session: Session) => void;\n onTerminate?: (session: Session) => void;\n onOpenTerminal?: (session: Session) => void;\n /** Callback to open the share dialog */\n onShare?: (session: Session) => void;\n /** Whether this card is selected for bulk operations */\n selected?: boolean;\n /** Toggle selection for bulk operations */\n onToggleSelect?: () => void;\n className?: string;\n}\n\nconst sessionTypeIcons: Record<string, string> = {\n TMUX: '🖥️',\n SSH: '🔐',\n TERMINAL: '💻',\n SCRIPT: '📜',\n CUSTOM: '⚙️',\n};\n\n/**\n * SessionCard - Display terminal session information\n */\nexport const SessionCard: FC<SessionCardProps> = ({\n session,\n onOpen,\n onStart,\n onStop,\n onRestart,\n onTerminate,\n onOpenTerminal,\n onShare,\n selected = false,\n onToggleSelect,\n className = '',\n}) => {\n const tr = useTr();\n const [showMenu, setShowMenu] = useState(false);\n\n const formatTime = (timestamp: string | undefined | null) => {\n if (!timestamp) return 'Never';\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return 'Just now';\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return new Date(timestamp).toLocaleDateString();\n };\n\n const isRunning = session.status === 'RUNNING';\n const isStopped = session.status === 'STOPPED' || session.status === 'TERMINATED';\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className={`bg-bg-surface border rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${selected ? 'border-action-primary-bg ring-1 ring-action-primary-bg/30' : 'border-border-default'} ${className}`}\n onClick={() => onOpen?.(session)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {onToggleSelect && (\n <div className=\"flex-shrink-0 pt-0.5\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selected}\n onChange={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n onClick={(e) => e.stopPropagation()}\n className=\"size-4 rounded border-border-default cursor-pointer\"\n />\n </div>\n )}\n <div className=\"flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-bg-sunken flex items-center justify-center\">\n <Terminal className=\"size-4 sm:w-5 sm:h-5 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base\">\n {session.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">\n {sessionTypeIcons[session.type] || '💻'}\n </span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary\">{session.type}</p>\n {session.agent && (\n <div className=\"mt-1.5 flex items-center gap-1 flex-wrap\">\n <AgentInfoPopover agent={session.agent} />\n {session.agent.target && <TargetInfoPopover target={session.agent.target} />}\n </div>\n )}\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <SessionStatusBadge status={session.status} />\n {(onStart || onStop || onRestart || onTerminate) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {isStopped && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Play className=\"size-4\" />\n {tr('vibecontrols.sessions.card.start', 'Start')}\n </motion.button>\n )}\n {isRunning && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Square className=\"size-4\" />\n {tr('vibecontrols.sessions.card.stop', 'Stop')}\n </motion.button>\n )}\n {isRunning && onRestart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onRestart(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <RotateCcw className=\"size-4\" />\n {tr('vibecontrols.sessions.card.restart', 'Restart')}\n </motion.button>\n )}\n {isRunning && onOpenTerminal && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onOpenTerminal(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Terminal className=\"size-4\" />\n {tr('vibecontrols.sessions.card.openTerminal', 'Open Terminal')}\n </motion.button>\n )}\n {isRunning && onShare && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.shareSession', 'Share Session')}\n </motion.button>\n )}\n {onTerminate && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onTerminate(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.terminate', 'Terminate')}\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n {/* Provider-native session name (e.g. tmux session name like\n `vibe-051c9c62`). Surfaced so users can recognise their session\n if they ever need to attach from a CLI (`tmux attach -t …`)\n and so reconnect-after-restart flows are obvious. */}\n {(() => {\n const meta = (session.metadata as Record<string, unknown> | null) || {};\n const externalName =\n (typeof meta.tmuxSessionName === 'string' && meta.tmuxSessionName) ||\n (typeof meta.weztermWorkspace === 'string' && meta.weztermWorkspace) ||\n (typeof meta.zellijSessionName === 'string' && meta.zellijSessionName) ||\n (typeof meta.externalSessionName === 'string' && meta.externalSessionName) ||\n session.providerSessionName ||\n null;\n return externalName ? (\n <div\n className=\"mt-2 text-[10px] font-mono text-text-tertiary\"\n title={`Provider session: ${externalName}`}\n data-testid=\"session-card-external-name\"\n >\n <span className=\"opacity-70\">{session.type.toLowerCase()}: </span>\n <span className=\"break-all\">{externalName}</span>\n </div>\n ) : null;\n })()}\n\n {session.command && (\n <div className=\"mt-3 text-xs font-mono bg-bg-sunken p-2 rounded truncate text-text-secondary\">\n $ {session.command}\n </div>\n )}\n\n {session.lastOutput && (\n <div className=\"mt-2 text-xs font-mono bg-bg-sunken text-status-success-text p-2 rounded truncate\">\n {session.lastOutput}\n </div>\n )}\n\n <div className=\"mt-3 flex items-center justify-between text-sm text-text-secondary\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-4\" />\n <span>{formatTime(session.updatedAt)}</span>\n </div>\n {session.pid && <span className=\"text-xs font-mono\">PID: {session.pid}</span>}\n </div>\n </motion.div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/components/sessions/SessionCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Terminal,\n Clock,\n MoreVertical,\n Play,\n Square,\n RotateCcw,\n Trash2,\n Share2,\n} from 'lucide-react';\nimport { SessionStatusBadge } from './SessionStatusBadge';\nimport { AgentInfoPopover, TargetInfoPopover } from './EntityInfoPopover';\nimport type { Session } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface SessionCardProps {\n session: Session;\n onOpen?: (session: Session) => void;\n onStart?: (session: Session) => void;\n onStop?: (session: Session) => void;\n onRestart?: (session: Session) => void;\n onTerminate?: (session: Session) => void;\n onOpenTerminal?: (session: Session) => void;\n /** Callback to open the share dialog */\n onShare?: (session: Session) => void;\n /** Whether this card is selected for bulk operations */\n selected?: boolean;\n /** Toggle selection for bulk operations */\n onToggleSelect?: () => void;\n className?: string;\n}\n\nconst sessionTypeIcons: Record<string, string> = {\n TMUX: '🖥️',\n SSH: '🔐',\n TERMINAL: '💻',\n SCRIPT: '📜',\n CUSTOM: '⚙️',\n};\n\n/**\n * SessionCard - Display terminal session information\n */\nexport const SessionCard: FC<SessionCardProps> = ({\n session,\n onOpen,\n onStart,\n onStop,\n onRestart,\n onTerminate,\n onOpenTerminal,\n onShare,\n selected = false,\n onToggleSelect,\n className = '',\n}) => {\n const tr = useTr();\n const [showMenu, setShowMenu] = useState(false);\n\n const formatTime = (timestamp: string | undefined | null) => {\n if (!timestamp) return 'Never';\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return 'Just now';\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return new Date(timestamp).toLocaleDateString();\n };\n\n const isRunning = session.status === 'RUNNING';\n const isStopped = session.status === 'STOPPED' || session.status === 'TERMINATED';\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className={`bg-bg-surface border rounded-card shadow-elevation-1 p-5 hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 ease-[var(--motion-easing-out)] cursor-pointer ${selected ? 'border-transparent ring-1 ring-[var(--color-focus-ring)] bg-[var(--color-accent-soft)]' : 'border-border-subtle'} ${className}`}\n onClick={() => onOpen?.(session)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {onToggleSelect && (\n <div className=\"flex-shrink-0 pt-0.5\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selected}\n onChange={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n onClick={(e) => e.stopPropagation()}\n className=\"size-4 rounded border-border-default cursor-pointer\"\n />\n </div>\n )}\n <div className=\"flex-shrink-0 size-9 sm:size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Terminal className=\"size-4 sm:size-5 text-action-primary-bg\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base tracking-tight\">\n {session.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">\n {sessionTypeIcons[session.type] || '💻'}\n </span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary\">{session.type}</p>\n {session.agent && (\n <div className=\"mt-1.5 flex items-center gap-1 flex-wrap\">\n <AgentInfoPopover agent={session.agent} />\n {session.agent.target && <TargetInfoPopover target={session.agent.target} />}\n </div>\n )}\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <SessionStatusBadge status={session.status} />\n {(onStart || onStop || onRestart || onTerminate) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"inline-flex size-8 items-center justify-center rounded-button text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n aria-label={tr('vibecontrols.sessions.card.actions', 'Session actions')}\n >\n <MoreVertical className=\"size-4\" aria-hidden=\"true\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1.5 w-44 bg-bg-elevated border border-border-subtle rounded-card shadow-elevation-3 z-10 overflow-hidden py-1\"\n >\n {isStopped && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Play className=\"size-4\" />\n {tr('vibecontrols.sessions.card.start', 'Start')}\n </motion.button>\n )}\n {isRunning && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Square className=\"size-4\" />\n {tr('vibecontrols.sessions.card.stop', 'Stop')}\n </motion.button>\n )}\n {isRunning && onRestart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onRestart(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <RotateCcw className=\"size-4\" />\n {tr('vibecontrols.sessions.card.restart', 'Restart')}\n </motion.button>\n )}\n {isRunning && onOpenTerminal && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onOpenTerminal(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Terminal className=\"size-4\" />\n {tr('vibecontrols.sessions.card.openTerminal', 'Open Terminal')}\n </motion.button>\n )}\n {isRunning && onShare && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Share2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.shareSession', 'Share Session')}\n </motion.button>\n )}\n {onTerminate && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onTerminate(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-status-error-bg-subtle transition-colors\"\n >\n <Trash2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.terminate', 'Terminate')}\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n {/* Provider-native session name (e.g. tmux session name like\n `vibe-051c9c62`). Surfaced so users can recognise their session\n if they ever need to attach from a CLI (`tmux attach -t …`)\n and so reconnect-after-restart flows are obvious. */}\n {(() => {\n const meta = (session.metadata as Record<string, unknown> | null) || {};\n const externalName =\n (typeof meta.tmuxSessionName === 'string' && meta.tmuxSessionName) ||\n (typeof meta.weztermWorkspace === 'string' && meta.weztermWorkspace) ||\n (typeof meta.zellijSessionName === 'string' && meta.zellijSessionName) ||\n (typeof meta.externalSessionName === 'string' && meta.externalSessionName) ||\n session.providerSessionName ||\n null;\n return externalName ? (\n <div\n className=\"mt-3 text-[10px] font-mono text-text-muted\"\n title={`Provider session: ${externalName}`}\n data-testid=\"session-card-external-name\"\n >\n <span className=\"opacity-70\">{session.type.toLowerCase()}: </span>\n <span className=\"break-all\">{externalName}</span>\n </div>\n ) : null;\n })()}\n\n {session.command && (\n <div className=\"mt-4 text-xs font-mono bg-bg-sunken/60 px-2 py-1.5 rounded truncate text-text-secondary\">\n $ {session.command}\n </div>\n )}\n\n {session.lastOutput && (\n <div className=\"mt-2 text-xs font-mono bg-bg-sunken/60 text-status-success-text px-2 py-1.5 rounded truncate\">\n {session.lastOutput}\n </div>\n )}\n\n <div className=\"mt-4 flex items-center justify-between text-sm text-text-secondary\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-4\" />\n <span className=\"tabular-nums\">{formatTime(session.updatedAt)}</span>\n </div>\n {session.pid && <span className=\"text-xs font-mono tabular-nums\">PID: {session.pid}</span>}\n </div>\n </motion.div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -4,31 +4,31 @@ import { jsx as t } from "react/jsx-runtime";
|
|
|
4
4
|
var n = {
|
|
5
5
|
PENDING: {
|
|
6
6
|
label: "Pending",
|
|
7
|
-
color: "bg-
|
|
7
|
+
color: "bg-bg-sunken text-text-secondary border-border-subtle"
|
|
8
8
|
},
|
|
9
9
|
STARTING: {
|
|
10
10
|
label: "Starting",
|
|
11
|
-
color: "bg-
|
|
11
|
+
color: "bg-bg-sunken text-text-secondary border-border-subtle"
|
|
12
12
|
},
|
|
13
13
|
RUNNING: {
|
|
14
14
|
label: "Running",
|
|
15
|
-
color: "bg-status-success-bg text-status-success-text border-status-success-border"
|
|
15
|
+
color: "bg-status-success-bg-subtle text-status-success-text border-status-success-border"
|
|
16
16
|
},
|
|
17
17
|
STOPPING: {
|
|
18
18
|
label: "Stopping",
|
|
19
|
-
color: "bg-status-warning-bg text-status-warning-text border-status-warning-border"
|
|
19
|
+
color: "bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border"
|
|
20
20
|
},
|
|
21
21
|
STOPPED: {
|
|
22
22
|
label: "Stopped",
|
|
23
|
-
color: "bg-bg-sunken text-text-secondary border-border-
|
|
23
|
+
color: "bg-bg-sunken text-text-secondary border-border-subtle"
|
|
24
24
|
},
|
|
25
25
|
ERROR: {
|
|
26
26
|
label: "Error",
|
|
27
|
-
color: "bg-status-error-bg text-status-error-text border-status-error-border"
|
|
27
|
+
color: "bg-status-error-bg-subtle text-status-error-text border-status-error-border"
|
|
28
28
|
},
|
|
29
29
|
TERMINATED: {
|
|
30
30
|
label: "Terminated",
|
|
31
|
-
color: "bg-bg-sunken text-text-secondary border-border-
|
|
31
|
+
color: "bg-bg-sunken text-text-secondary border-border-subtle"
|
|
32
32
|
}
|
|
33
33
|
}, r = ({ status: r, className: i = "" }) => {
|
|
34
34
|
let { t: a } = e(), o = n[r];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionStatusBadge.js","names":[],"sources":["../../../src/components/sessions/SessionStatusBadge.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { FC } from 'react';\nimport type { SessionStatus } from './types';\n\nexport interface SessionStatusBadgeProps {\n status: SessionStatus;\n className?: string;\n}\n\nconst statusConfig: Record<SessionStatus, { label: string; color: string }> = {\n PENDING: {\n label: 'Pending',\n color: 'bg-
|
|
1
|
+
{"version":3,"file":"SessionStatusBadge.js","names":[],"sources":["../../../src/components/sessions/SessionStatusBadge.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { FC } from 'react';\nimport type { SessionStatus } from './types';\n\nexport interface SessionStatusBadgeProps {\n status: SessionStatus;\n className?: string;\n}\n\n// Subtle-recipe pills (tinted background + same-hue saturated text + matching\n// border) so they pass WCAG AA in every theme and stay visually consistent with\n// the shared StatusBadge. Transient states (pending/starting) read NEUTRAL —\n// loud blue clashes with the purple brand accent.\nconst statusConfig: Record<SessionStatus, { label: string; color: string }> = {\n PENDING: {\n label: 'Pending',\n color: 'bg-bg-sunken text-text-secondary border-border-subtle',\n },\n STARTING: {\n label: 'Starting',\n color: 'bg-bg-sunken text-text-secondary border-border-subtle',\n },\n RUNNING: {\n label: 'Running',\n color: 'bg-status-success-bg-subtle text-status-success-text border-status-success-border',\n },\n STOPPING: {\n label: 'Stopping',\n color: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border',\n },\n STOPPED: {\n label: 'Stopped',\n color: 'bg-bg-sunken text-text-secondary border-border-subtle',\n },\n ERROR: {\n label: 'Error',\n color: 'bg-status-error-bg-subtle text-status-error-text border-status-error-border',\n },\n TERMINATED: {\n label: 'Terminated',\n color: 'bg-bg-sunken text-text-secondary border-border-subtle',\n },\n};\n\nexport const SessionStatusBadge: FC<SessionStatusBadgeProps> = ({ status, className = '' }) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const config = statusConfig[status];\n\n return (\n <span\n className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border transition-colors duration-150 ${config.color} ${className}`}\n >\n {config.label}\n </span>\n );\n};\n"],"mappings":";;;AAaA,IAAM,IAAwE;CAC5E,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,UAAU;EACR,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,UAAU;EACR,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,OAAO;EACP,OAAO;EACR;CACD,OAAO;EACL,OAAO;EACP,OAAO;EACR;CACD,YAAY;EACV,OAAO;EACP,OAAO;EACR;CACF,EAEY,KAAmD,EAAE,WAAQ,eAAY,SAAS;CAC7F,IAAM,EAAE,SAAM,GAAS,EAKjB,IAAS,EAAa;AAE5B,QACE,kBAAC,QAAD;EACE,WAAW,iHAAiH,EAAO,MAAM,GAAG;YAE3I,EAAO;EACH,CAAA"}
|
|
@@ -53,14 +53,14 @@ function p({ label: p, className: m, style: h, onChange: g, compact: _ = !1 }) {
|
|
|
53
53
|
className: m,
|
|
54
54
|
style: h,
|
|
55
55
|
children: /* @__PURE__ */ d("div", {
|
|
56
|
-
className: "inline-flex items-center gap-2 rounded-
|
|
56
|
+
className: "inline-flex items-center gap-2 rounded-input border border-border-subtle bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary",
|
|
57
57
|
children: [/* @__PURE__ */ u(l, { className: "size-3.5" }), "Loading agents…"]
|
|
58
58
|
})
|
|
59
59
|
}) : S.length === 0 ? /* @__PURE__ */ u("div", {
|
|
60
60
|
className: m,
|
|
61
61
|
style: h,
|
|
62
62
|
children: /* @__PURE__ */ d("div", {
|
|
63
|
-
className: "inline-flex items-center gap-2 rounded-
|
|
63
|
+
className: "inline-flex items-center gap-2 rounded-input border border-border-subtle bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary",
|
|
64
64
|
children: [/* @__PURE__ */ u(l, { className: "size-3.5" }), "No agents yet — add one from the Agents page"]
|
|
65
65
|
})
|
|
66
66
|
}) : /* @__PURE__ */ d("div", {
|
|
@@ -81,7 +81,7 @@ function p({ label: p, className: m, style: h, onChange: g, compact: _ = !1 }) {
|
|
|
81
81
|
/* @__PURE__ */ u("select", {
|
|
82
82
|
value: C?.id ?? "",
|
|
83
83
|
onChange: w,
|
|
84
|
-
className: `appearance-none rounded-
|
|
84
|
+
className: `appearance-none rounded-input border border-border-subtle bg-bg-surface pl-7 pr-8 text-text-primary transition-colors hover:border-border-strong focus:outline-none focus:ring-1 focus:ring-[var(--color-focus-ring)] focus:border-border-strong ${_ ? "py-1 text-xs" : "py-2 text-sm"}`,
|
|
85
85
|
"data-testid": "agent-selector",
|
|
86
86
|
children: S.map((e) => /* @__PURE__ */ d("option", {
|
|
87
87
|
value: e.id,
|
|
@@ -109,7 +109,7 @@ function p({ label: p, className: m, style: h, onChange: g, compact: _ = !1 }) {
|
|
|
109
109
|
children: [/* @__PURE__ */ u("select", {
|
|
110
110
|
value: E,
|
|
111
111
|
onChange: O,
|
|
112
|
-
className: `appearance-none rounded-
|
|
112
|
+
className: `appearance-none rounded-input border border-border-subtle bg-bg-surface pl-3 pr-7 text-text-primary transition-colors hover:border-border-strong focus:outline-none focus:ring-1 focus:ring-[var(--color-focus-ring)] focus:border-border-strong font-mono ${_ ? "py-1 text-xs" : "py-2 text-sm"}`,
|
|
113
113
|
"data-testid": "agent-profile-selector",
|
|
114
114
|
"aria-label": "Active profile",
|
|
115
115
|
children: T.map((e) => /* @__PURE__ */ d("option", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentSelector.js","names":[],"sources":["../../../src/components/shared/AgentSelector.tsx"],"sourcesContent":["/**\n * AgentSelector — dropdown of workspace agents with selection persisted to the\n * VibeControls Zustand store. Used on the AI page and anywhere else a user\n * needs to pick \"the active agent\" explicitly rather than rely on auto-pick.\n *\n * The selector shows active agents first, then inactive/disconnected ones,\n * and surfaces the agent's tunnel/heartbeat state via a colored dot so the\n * user knows at a glance which one is actually running.\n */\n\nimport { useCallback, useEffect, useMemo, type CSSProperties } from 'react';\nimport { Check, ChevronDown, Server } from 'lucide-react';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { getAgentStatus } from '@/components/agents/agentStatus';\n\nexport interface AgentOption {\n id: string;\n name: string;\n hostname: string;\n isActive: boolean;\n lastHeartbeat?: string | null;\n tunnelUrl?: string | null;\n tunnelStatus?: string | null;\n}\n\ninterface AgentSelectorProps {\n /** Optional label above the selector. */\n label?: string;\n /** Optional className applied to the outer wrapper. */\n className?: string;\n /** Optional inline style applied to the outer wrapper. */\n style?: CSSProperties;\n /** Fired whenever the user changes the selected agent. */\n onChange?: (agent: AgentOption | null) => void;\n /** Render compact (smaller, no label). */\n compact?: boolean;\n}\n\nfunction statusDotColor(agent: AgentOption): string {\n const status = getAgentStatus(agent);\n if (status === 'OFFLINE') return 'bg-text-placeholder';\n if (status === 'DEGRADED' || !agent.tunnelUrl) return 'bg-status-warning-text';\n return 'bg-status-success-text';\n}\n\nexport function AgentSelector({\n label,\n className,\n style,\n onChange,\n compact = false,\n}: AgentSelectorProps) {\n const { agents, loading } = useCachedInitialData();\n const activeAgentId = useVibeControlsStore((s) => s.activeAgentId);\n const setActiveAgentId = useVibeControlsStore((s) => s.setActiveAgentId);\n\n const options: AgentOption[] = useMemo(() => {\n const sorted = [...agents].sort((a, b) => {\n // ACTIVE + online first; then ACTIVE but no tunnel; then DEGRADED;\n // then OFFLINE. Falls back through getAgentStatus precedence.\n const statusRank = (x: (typeof agents)[number]) => {\n const s = getAgentStatus(x);\n if (s === 'ACTIVE') return 0;\n if (s === 'DEGRADED') return 1;\n return 2;\n };\n const score = (x: (typeof agents)[number]) => statusRank(x) * 2 + (x.tunnelUrl ? 0 : 1);\n if (score(a) !== score(b)) return score(a) - score(b);\n return a.name.localeCompare(b.name);\n });\n return sorted.map((a) => ({\n id: a.id,\n name: a.name,\n hostname: a.hostname,\n isActive: a.isActive,\n lastHeartbeat: a.lastHeartbeat ?? null,\n tunnelUrl: a.tunnelUrl ?? null,\n tunnelStatus: a.tunnelStatus ?? null,\n }));\n }, [agents]);\n\n const selected = useMemo<AgentOption | null>(() => {\n if (activeAgentId) {\n const match = options.find((a) => a.id === activeAgentId);\n if (match) return match;\n }\n // Auto-pick the first healthy agent the first time.\n return options.find((a) => getAgentStatus(a) === 'ACTIVE' && a.tunnelUrl) ?? options[0] ?? null;\n }, [activeAgentId, options]);\n\n // Commit the auto-picked agent into the store so downstream consumers can\n // read it directly without running the same \"first healthy\" logic.\n useEffect(() => {\n if (selected && activeAgentId !== selected.id) {\n setActiveAgentId(selected.id);\n }\n }, [selected, activeAgentId, setActiveAgentId]);\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLSelectElement>) => {\n const id = e.target.value || null;\n setActiveAgentId(id);\n onChange?.(id ? (options.find((o) => o.id === id) ?? null) : null);\n },\n [options, setActiveAgentId, onChange]\n );\n\n // Profile sub-dropdown — Phase 5B (multi-profile-per-daemon).\n // Resolves the AgentProfile rows for the currently-selected agent and\n // exposes a switcher next to the agent dropdown. Hidden until profiles\n // beyond the implicit \"default\" exist so single-profile setups stay clean.\n const {\n profiles: agentProfiles,\n active: activeProfile,\n setActive: setActiveProfile,\n } = useActiveAgentProfile(selected?.id ?? null);\n const handleProfileChange = useCallback(\n (e: React.ChangeEvent<HTMLSelectElement>) => {\n setActiveProfile(e.target.value);\n },\n [setActiveProfile]\n );\n\n if (loading && options.length === 0) {\n return (\n <div className={className} style={style}>\n <div className=\"inline-flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary\">\n <Server className=\"size-3.5\" />\n Loading agents…\n </div>\n </div>\n );\n }\n\n if (options.length === 0) {\n return (\n <div className={className} style={style}>\n <div className=\"inline-flex items-center gap-2 rounded-md border border-border-default bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary\">\n <Server className=\"size-3.5\" />\n No agents yet — add one from the Agents page\n </div>\n </div>\n );\n }\n\n return (\n <div className={className} style={style}>\n {label && !compact && (\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">{label}</label>\n )}\n <div className=\"relative inline-flex items-center\">\n {selected && (\n <span\n className={`absolute left-2.5 z-10 size-2 rounded-full ${statusDotColor(selected)}`}\n aria-hidden\n />\n )}\n <select\n value={selected?.id ?? ''}\n onChange={handleChange}\n className={`appearance-none rounded-md border border-border-default bg-bg-surface pl-7 pr-8 text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40 ${compact ? 'py-1 text-xs' : 'py-2 text-sm'}`}\n data-testid=\"agent-selector\"\n >\n {options.map((agent) => (\n <option key={agent.id} value={agent.id}>\n {agent.name} ({agent.hostname})\n {(() => {\n const s = getAgentStatus(agent);\n if (s === 'ACTIVE') return '';\n if (s === 'DEGRADED') return ' · degraded';\n return ' · offline';\n })()}\n </option>\n ))}\n </select>\n <ChevronDown className=\"pointer-events-none absolute right-2 size-3.5 text-text-secondary\" />\n {selected?.tunnelUrl && (\n <Check\n className=\"pointer-events-none absolute -right-5 size-3.5 text-status-success-text\"\n aria-label=\"Tunnel reachable\"\n />\n )}\n </div>\n {selected && agentProfiles.length > 1 && (\n <div className=\"relative inline-flex items-center ml-2\">\n <select\n value={activeProfile}\n onChange={handleProfileChange}\n className={`appearance-none rounded-md border border-border-default bg-bg-surface pl-3 pr-7 text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40 font-mono ${compact ? 'py-1 text-xs' : 'py-2 text-sm'}`}\n data-testid=\"agent-profile-selector\"\n aria-label=\"Active profile\"\n >\n {agentProfiles.map((p) => (\n <option key={p.id} value={p.name}>\n {p.name}\n {p.isDefault ? ' (default)' : ''}\n </option>\n ))}\n </select>\n <ChevronDown className=\"pointer-events-none absolute right-2 size-3.5 text-text-secondary\" />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AgentSelector.js","names":[],"sources":["../../../src/components/shared/AgentSelector.tsx"],"sourcesContent":["/**\n * AgentSelector — dropdown of workspace agents with selection persisted to the\n * VibeControls Zustand store. Used on the AI page and anywhere else a user\n * needs to pick \"the active agent\" explicitly rather than rely on auto-pick.\n *\n * The selector shows active agents first, then inactive/disconnected ones,\n * and surfaces the agent's tunnel/heartbeat state via a colored dot so the\n * user knows at a glance which one is actually running.\n */\n\nimport { useCallback, useEffect, useMemo, type CSSProperties } from 'react';\nimport { Check, ChevronDown, Server } from 'lucide-react';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { getAgentStatus } from '@/components/agents/agentStatus';\n\nexport interface AgentOption {\n id: string;\n name: string;\n hostname: string;\n isActive: boolean;\n lastHeartbeat?: string | null;\n tunnelUrl?: string | null;\n tunnelStatus?: string | null;\n}\n\ninterface AgentSelectorProps {\n /** Optional label above the selector. */\n label?: string;\n /** Optional className applied to the outer wrapper. */\n className?: string;\n /** Optional inline style applied to the outer wrapper. */\n style?: CSSProperties;\n /** Fired whenever the user changes the selected agent. */\n onChange?: (agent: AgentOption | null) => void;\n /** Render compact (smaller, no label). */\n compact?: boolean;\n}\n\nfunction statusDotColor(agent: AgentOption): string {\n const status = getAgentStatus(agent);\n if (status === 'OFFLINE') return 'bg-text-placeholder';\n if (status === 'DEGRADED' || !agent.tunnelUrl) return 'bg-status-warning-text';\n return 'bg-status-success-text';\n}\n\nexport function AgentSelector({\n label,\n className,\n style,\n onChange,\n compact = false,\n}: AgentSelectorProps) {\n const { agents, loading } = useCachedInitialData();\n const activeAgentId = useVibeControlsStore((s) => s.activeAgentId);\n const setActiveAgentId = useVibeControlsStore((s) => s.setActiveAgentId);\n\n const options: AgentOption[] = useMemo(() => {\n const sorted = [...agents].sort((a, b) => {\n // ACTIVE + online first; then ACTIVE but no tunnel; then DEGRADED;\n // then OFFLINE. Falls back through getAgentStatus precedence.\n const statusRank = (x: (typeof agents)[number]) => {\n const s = getAgentStatus(x);\n if (s === 'ACTIVE') return 0;\n if (s === 'DEGRADED') return 1;\n return 2;\n };\n const score = (x: (typeof agents)[number]) => statusRank(x) * 2 + (x.tunnelUrl ? 0 : 1);\n if (score(a) !== score(b)) return score(a) - score(b);\n return a.name.localeCompare(b.name);\n });\n return sorted.map((a) => ({\n id: a.id,\n name: a.name,\n hostname: a.hostname,\n isActive: a.isActive,\n lastHeartbeat: a.lastHeartbeat ?? null,\n tunnelUrl: a.tunnelUrl ?? null,\n tunnelStatus: a.tunnelStatus ?? null,\n }));\n }, [agents]);\n\n const selected = useMemo<AgentOption | null>(() => {\n if (activeAgentId) {\n const match = options.find((a) => a.id === activeAgentId);\n if (match) return match;\n }\n // Auto-pick the first healthy agent the first time.\n return options.find((a) => getAgentStatus(a) === 'ACTIVE' && a.tunnelUrl) ?? options[0] ?? null;\n }, [activeAgentId, options]);\n\n // Commit the auto-picked agent into the store so downstream consumers can\n // read it directly without running the same \"first healthy\" logic.\n useEffect(() => {\n if (selected && activeAgentId !== selected.id) {\n setActiveAgentId(selected.id);\n }\n }, [selected, activeAgentId, setActiveAgentId]);\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLSelectElement>) => {\n const id = e.target.value || null;\n setActiveAgentId(id);\n onChange?.(id ? (options.find((o) => o.id === id) ?? null) : null);\n },\n [options, setActiveAgentId, onChange]\n );\n\n // Profile sub-dropdown — Phase 5B (multi-profile-per-daemon).\n // Resolves the AgentProfile rows for the currently-selected agent and\n // exposes a switcher next to the agent dropdown. Hidden until profiles\n // beyond the implicit \"default\" exist so single-profile setups stay clean.\n const {\n profiles: agentProfiles,\n active: activeProfile,\n setActive: setActiveProfile,\n } = useActiveAgentProfile(selected?.id ?? null);\n const handleProfileChange = useCallback(\n (e: React.ChangeEvent<HTMLSelectElement>) => {\n setActiveProfile(e.target.value);\n },\n [setActiveProfile]\n );\n\n if (loading && options.length === 0) {\n return (\n <div className={className} style={style}>\n <div className=\"inline-flex items-center gap-2 rounded-input border border-border-subtle bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary\">\n <Server className=\"size-3.5\" />\n Loading agents…\n </div>\n </div>\n );\n }\n\n if (options.length === 0) {\n return (\n <div className={className} style={style}>\n <div className=\"inline-flex items-center gap-2 rounded-input border border-border-subtle bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary\">\n <Server className=\"size-3.5\" />\n No agents yet — add one from the Agents page\n </div>\n </div>\n );\n }\n\n return (\n <div className={className} style={style}>\n {label && !compact && (\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">{label}</label>\n )}\n <div className=\"relative inline-flex items-center\">\n {selected && (\n <span\n className={`absolute left-2.5 z-10 size-2 rounded-full ${statusDotColor(selected)}`}\n aria-hidden\n />\n )}\n <select\n value={selected?.id ?? ''}\n onChange={handleChange}\n className={`appearance-none rounded-input border border-border-subtle bg-bg-surface pl-7 pr-8 text-text-primary transition-colors hover:border-border-strong focus:outline-none focus:ring-1 focus:ring-[var(--color-focus-ring)] focus:border-border-strong ${compact ? 'py-1 text-xs' : 'py-2 text-sm'}`}\n data-testid=\"agent-selector\"\n >\n {options.map((agent) => (\n <option key={agent.id} value={agent.id}>\n {agent.name} ({agent.hostname})\n {(() => {\n const s = getAgentStatus(agent);\n if (s === 'ACTIVE') return '';\n if (s === 'DEGRADED') return ' · degraded';\n return ' · offline';\n })()}\n </option>\n ))}\n </select>\n <ChevronDown className=\"pointer-events-none absolute right-2 size-3.5 text-text-secondary\" />\n {selected?.tunnelUrl && (\n <Check\n className=\"pointer-events-none absolute -right-5 size-3.5 text-status-success-text\"\n aria-label=\"Tunnel reachable\"\n />\n )}\n </div>\n {selected && agentProfiles.length > 1 && (\n <div className=\"relative inline-flex items-center ml-2\">\n <select\n value={activeProfile}\n onChange={handleProfileChange}\n className={`appearance-none rounded-input border border-border-subtle bg-bg-surface pl-3 pr-7 text-text-primary transition-colors hover:border-border-strong focus:outline-none focus:ring-1 focus:ring-[var(--color-focus-ring)] focus:border-border-strong font-mono ${compact ? 'py-1 text-xs' : 'py-2 text-sm'}`}\n data-testid=\"agent-profile-selector\"\n aria-label=\"Active profile\"\n >\n {agentProfiles.map((p) => (\n <option key={p.id} value={p.name}>\n {p.name}\n {p.isDefault ? ' (default)' : ''}\n </option>\n ))}\n </select>\n <ChevronDown className=\"pointer-events-none absolute right-2 size-3.5 text-text-secondary\" />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -57,7 +57,7 @@ function b({ variant: b = "compact", dismissible: x = !0, dismissKey: S = "vibec
|
|
|
57
57
|
height: 0
|
|
58
58
|
},
|
|
59
59
|
transition: N,
|
|
60
|
-
className: `relative flex flex-col items-start gap-3 px-4 py-3 bg-accent-purple-subtle border border-accent-purple/20 rounded-
|
|
60
|
+
className: `relative flex flex-col items-start gap-3 px-4 py-3 bg-accent-purple-subtle border border-accent-purple/20 rounded-card sm:flex-row sm:items-center ${C}`,
|
|
61
61
|
children: [
|
|
62
62
|
/* @__PURE__ */ h(c, { className: "size-5 text-accent-purple flex-shrink-0" }),
|
|
63
63
|
/* @__PURE__ */ g("div", {
|
|
@@ -68,7 +68,7 @@ function b({ variant: b = "compact", dismissible: x = !0, dismissKey: S = "vibec
|
|
|
68
68
|
children: w("vibecontrols.shared.agentSetupBanner.installPrompt", "Install the VibeControls Agent CLI to get started")
|
|
69
69
|
}),
|
|
70
70
|
/* @__PURE__ */ g("code", {
|
|
71
|
-
className: "inline-flex min-w-0 max-w-full items-center gap-1.5 rounded border border-border-
|
|
71
|
+
className: "inline-flex min-w-0 max-w-full items-center gap-1.5 rounded border border-border-subtle bg-bg-sunken px-2.5 py-1 font-mono text-xs text-text-primary transition-colors hover:bg-bg-elevated cursor-pointer",
|
|
72
72
|
onClick: () => j(E),
|
|
73
73
|
title: w("vibecontrols.shared.agentSetupBanner.clickToCopy", "Click to copy"),
|
|
74
74
|
children: [
|
|
@@ -117,7 +117,7 @@ function b({ variant: b = "compact", dismissible: x = !0, dismissKey: S = "vibec
|
|
|
117
117
|
height: 0
|
|
118
118
|
},
|
|
119
119
|
transition: N,
|
|
120
|
-
className: `relative border border-accent-purple/20 bg-accent-purple-subtle rounded-
|
|
120
|
+
className: `relative border border-accent-purple/20 bg-accent-purple-subtle rounded-card overflow-hidden ${C}`,
|
|
121
121
|
children: [x && /* @__PURE__ */ h("button", {
|
|
122
122
|
type: "button",
|
|
123
123
|
onClick: M,
|