@burdenoff/microfe-vibecontrols 2026.623.2 → 2026.623.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/components/actions/ActionTypeBadge.js +1 -1
  2. package/dist/components/actions/ActionTypeBadge.js.map +1 -1
  3. package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
  4. package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
  5. package/dist/components/agent-manager/AgentManagerChatPane.js +1 -1
  6. package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
  7. package/dist/components/agent-manager/AgentManagerInputArea.js +1 -1
  8. package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
  9. package/dist/components/agent-manager/BridleConfigPanel.js +2 -2
  10. package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
  11. package/dist/components/agent-manager/ProfilesPanel.js +1 -1
  12. package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
  13. package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
  14. package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
  15. package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
  16. package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
  17. package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
  18. package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
  19. package/dist/components/agent-manager/detail/SessionLogsPanel.js +2 -2
  20. package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
  21. package/dist/components/agents/AgentForm.js +2 -2
  22. package/dist/components/agents/AgentForm.js.map +1 -1
  23. package/dist/components/ai/AIContextsTab.js +1 -1
  24. package/dist/components/ai/AIContextsTab.js.map +1 -1
  25. package/dist/components/ai/AIOverviewTab.js +1 -1
  26. package/dist/components/ai/AIOverviewTab.js.map +1 -1
  27. package/dist/components/ai/AIPromptsTab.js +1 -1
  28. package/dist/components/ai/AIPromptsTab.js.map +1 -1
  29. package/dist/components/ai/AIQueueTab.js +1 -1
  30. package/dist/components/ai/AIQueueTab.js.map +1 -1
  31. package/dist/components/ai/AISessionsTab.js +1 -1
  32. package/dist/components/ai/AISessionsTab.js.map +1 -1
  33. package/dist/components/audit/AuditLogTable.js +1 -1
  34. package/dist/components/audit/AuditLogTable.js.map +1 -1
  35. package/dist/components/docs/DocsPageTree.js +1 -1
  36. package/dist/components/docs/DocsPageTree.js.map +1 -1
  37. package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
  38. package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
  39. package/dist/components/graph/MeshDeployWizard.js +3 -3
  40. package/dist/components/graph/MeshDeployWizard.js.map +1 -1
  41. package/dist/components/graph/MeshRequestPanel.js +2 -2
  42. package/dist/components/graph/MeshRequestPanel.js.map +1 -1
  43. package/dist/components/notes/NoteCard.js +1 -1
  44. package/dist/components/notes/NoteCard.js.map +1 -1
  45. package/dist/components/notes/NoteEditor.js +1 -1
  46. package/dist/components/notes/NoteEditor.js.map +1 -1
  47. package/dist/components/notes/NoteList.js +1 -1
  48. package/dist/components/notes/NoteList.js.map +1 -1
  49. package/dist/components/shared/AgentSetupBanner.js +5 -5
  50. package/dist/components/shared/AgentSetupBanner.js.map +1 -1
  51. package/dist/components/shared/InstallTabs.js +17 -56
  52. package/dist/components/shared/InstallTabs.js.map +1 -1
  53. package/dist/components/templates/TemplateCard.js +1 -1
  54. package/dist/components/templates/TemplateCard.js.map +1 -1
  55. package/dist/components/vibecalendar/types.js +1 -1
  56. package/dist/components/vibecalendar/types.js.map +1 -1
  57. package/dist/components/vibedeck/VibeDeckButtonForm.js +1 -1
  58. package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
  59. package/dist/components/vibedeck/VibeDeckExecutionLog.js +2 -2
  60. package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
  61. package/dist/components/vibedeck/VibeDeckForm.js +1 -1
  62. package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
  63. package/dist/components/vibedeck/VibeDeckGrid.js +1 -1
  64. package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
  65. package/dist/pages/AgentsPage.js +2 -2
  66. package/dist/pages/AgentsPage.js.map +1 -1
  67. package/dist/pages/CatalogDetailPage.js +1 -1
  68. package/dist/pages/CatalogDetailPage.js.map +1 -1
  69. package/dist/pages/ComponentDetailPage.js +1 -1
  70. package/dist/pages/ComponentDetailPage.js.map +1 -1
  71. package/dist/pages/GettingStartedPage.js +130 -114
  72. package/dist/pages/GettingStartedPage.js.map +1 -1
  73. package/dist/pages/OverviewPage.js +10 -10
  74. package/dist/pages/OverviewPage.js.map +1 -1
  75. package/dist/pages/PluginsCatalogPage.js +1 -1
  76. package/dist/pages/PluginsCatalogPage.js.map +1 -1
  77. package/dist/pages/SessionsPage.js +3 -3
  78. package/dist/pages/SessionsPage.js.map +1 -1
  79. package/dist/pages/SharedSessionPage.js +2 -2
  80. package/dist/pages/SharedSessionPage.js.map +1 -1
  81. package/dist/pages/TargetsPage.js +2 -2
  82. package/dist/pages/TargetsPage.js.map +1 -1
  83. package/dist/pages/TemplateDetailPage.js +2 -2
  84. package/dist/pages/TemplateDetailPage.js.map +1 -1
  85. package/dist/pages/VibesPage.js +2 -2
  86. package/dist/pages/VibesPage.js.map +1 -1
  87. package/dist/pages/WebhooksPage.js +2 -2
  88. package/dist/pages/WebhooksPage.js.map +1 -1
  89. package/dist/pages/ai/AISessionDetailPage.js +2 -2
  90. package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
  91. package/dist/translations/en.js +27 -52
  92. package/dist/translations/en.js.map +1 -1
  93. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"AIPromptsTab.js","names":[],"sources":["../../../src/components/ai/AIPromptsTab.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport { Send, FileText, Wand2, Clock } from 'lucide-react';\nimport { Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport type { AITemplate, AIContext, AISession, AIDispatch, AgentRef } from '@/services/aiApi';\nimport {\n fetchTemplates,\n fetchContexts,\n fetchSessions,\n fetchDispatchHistory,\n renderTemplate,\n recordTemplateUsage,\n sendDispatch,\n} from '@/services/aiApi';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AIPromptsTabProps {\n agent: AgentRef;\n}\n\nexport function AIPromptsTab({ agent }: AIPromptsTabProps) {\n const tr = useTr();\n const [promptContent, setPromptContent] = useState('');\n const [selectedContextIds, setSelectedContextIds] = useState<string[]>([]);\n const [targetSessionId, setTargetSessionId] = useState('');\n const [sending, setSending] = useState(false);\n const [successMsg, setSuccessMsg] = useState<string | null>(null);\n\n // Generate from template state\n const [showTemplateGen, setShowTemplateGen] = useState(false);\n const [selectedTemplateId, setSelectedTemplateId] = useState('');\n const [sourceTemplateId, setSourceTemplateId] = useState<string | null>(null);\n const [templateVars, setTemplateVars] = useState<Record<string, string>>({});\n const [rendering, setRendering] = useState(false);\n\n // Data\n const [templates, setTemplates] = useState<AITemplate[]>([]);\n const [contexts, setContexts] = useState<AIContext[]>([]);\n const [sessions, setSessions] = useState<AISession[]>([]);\n const [history, setHistory] = useState<AIDispatch[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const loadData = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const [t, c, s, h] = await Promise.all([\n fetchTemplates(agent.id),\n fetchContexts(agent),\n fetchSessions(agent),\n fetchDispatchHistory(agent, { limit: 20 }),\n ]);\n setTemplates(t);\n setContexts(c);\n setSessions(s);\n setHistory(h);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load data');\n } finally {\n setLoading(false);\n }\n }, [agent]);\n\n useEffect(() => {\n loadData();\n }, [loadData]);\n\n const handleTemplateSelect = (templateId: string) => {\n setSelectedTemplateId(templateId);\n const template = templates.find((t) => t.id === templateId);\n if (template?.variables) {\n const vars: Record<string, string> = {};\n for (const key of Object.keys(template.variables)) {\n vars[key] = String(template.variables[key] ?? '');\n }\n setTemplateVars(vars);\n } else {\n setTemplateVars({});\n }\n };\n\n const handleGenerateFromTemplate = async () => {\n if (!selectedTemplateId) return;\n setRendering(true);\n try {\n const result = await renderTemplate(agent.id, selectedTemplateId, templateVars);\n setPromptContent(result.rendered);\n setSourceTemplateId(selectedTemplateId);\n setShowTemplateGen(false);\n } catch (err) {\n console.error('Failed to render template:', err);\n } finally {\n setRendering(false);\n }\n };\n\n const toggleContext = (contextId: string) => {\n setSelectedContextIds((prev) =>\n prev.includes(contextId) ? prev.filter((id) => id !== contextId) : [...prev, contextId]\n );\n };\n\n const handleSendToSession = async () => {\n if (!promptContent.trim() || !targetSessionId) return;\n setSending(true);\n setSuccessMsg(null);\n try {\n await sendDispatch(agent, {\n prompt: promptContent,\n sessionId: targetSessionId,\n contextIds: selectedContextIds.length > 0 ? selectedContextIds : undefined,\n });\n if (sourceTemplateId) {\n await recordTemplateUsage(agent.id, sourceTemplateId);\n }\n setSuccessMsg('Prompt dispatched successfully');\n setPromptContent('');\n setSourceTemplateId(null);\n setSelectedContextIds([]);\n setTimeout(() => setSuccessMsg(null), 3000);\n // Refresh history\n const h = await fetchDispatchHistory(agent, { limit: 20 });\n setHistory(h);\n } catch (err) {\n console.error('Failed to dispatch prompt:', err);\n } finally {\n setSending(false);\n }\n };\n\n if (loading)\n return <LoadingState message={tr('vibecontrols.ai.prompts.loading', 'Loading prompts...')} />;\n if (error)\n return (\n <ErrorState\n title={tr('vibecontrols.ai.prompts.loadFailed', 'Failed to load data')}\n message={error}\n onRetry={loadData}\n />\n );\n\n return (\n <div className=\"space-y-6\">\n {successMsg && (\n <div className=\"bg-status-success-bg-subtle text-status-success-text px-4 py-2 rounded-md text-sm\">\n {successMsg}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Prompt Editor */}\n <div className=\"lg:col-span-2 space-y-4\">\n <Card className=\"p-4\">\n <div className=\"flex items-center justify-between mb-3\">\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.ai.prompts.composePrompt', 'Compose Prompt')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowTemplateGen(!showTemplateGen)}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-action-primary-bg bg-[var(--color-accent-soft)] rounded-md hover:bg-[var(--color-accent-soft)] transition-colors\"\n >\n <Wand2 className=\"size-3.5\" />\n {tr('vibecontrols.ai.prompts.generateFromTemplate', 'Generate from Template')}\n </button>\n </div>\n\n {/* Template Generator */}\n {showTemplateGen && (\n <div className=\"mb-4 p-3 bg-bg-sunken rounded-md space-y-3\">\n <select\n value={selectedTemplateId}\n onChange={(e) => handleTemplateSelect(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-surface border border-border-default rounded-md text-text-primary text-sm\"\n >\n <option value=\"\">\n {tr('vibecontrols.ai.prompts.selectTemplate', 'Select a template...')}\n </option>\n {templates.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n {Object.keys(templateVars).length > 0 && (\n <div className=\"space-y-2\">\n {Object.entries(templateVars).map(([key, val]) => (\n <div key={key}>\n <label className=\"block text-xs font-medium text-text-secondary mb-0.5\">\n {key}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={val}\n onChange={(e) =>\n setTemplateVars((prev) => ({ ...prev, [key]: e.target.value }))\n }\n className=\"w-full px-2 py-1.5 bg-bg-surface border border-border-default rounded text-sm text-text-primary focus:outline-none focus:border-border-strong\"\n />\n </div>\n ))}\n </div>\n )}\n <button\n type=\"button\"\n onClick={handleGenerateFromTemplate}\n disabled={rendering || !selectedTemplateId}\n className=\"w-full px-3 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {rendering\n ? tr('vibecontrols.ai.prompts.generating', 'Generating...')\n : tr('vibecontrols.ai.prompts.generate', 'Generate')}\n </button>\n </div>\n )}\n\n <textarea\n aria-label=\"control\"\n value={promptContent}\n onChange={(e) => {\n setPromptContent(e.target.value);\n setSourceTemplateId(null);\n }}\n rows={8}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm font-mono focus:outline-none focus:border-border-strong\"\n placeholder={tr(\n 'vibecontrols.ai.prompts.promptPlaceholder',\n 'Write your prompt here... (Markdown supported)'\n )}\n />\n\n {/* Session Target & Send */}\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-end gap-3 mt-3\">\n <div className=\"flex-1\">\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">\n {tr('vibecontrols.ai.prompts.targetSession', 'Target Session')}\n </label>\n <select\n value={targetSessionId}\n onChange={(e) => setTargetSessionId(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n <option value=\"\">\n {tr('vibecontrols.ai.prompts.selectSession', 'Select session...')}\n </option>\n {sessions\n .filter((s) => s.status !== 'terminated')\n .map((s) => (\n <option key={s.id} value={s.id}>\n {s.name} ({s.status})\n </option>\n ))}\n </select>\n </div>\n <button\n type=\"button\"\n onClick={handleSendToSession}\n disabled={sending || !promptContent.trim() || !targetSessionId}\n className=\"flex items-center justify-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 text-sm\"\n >\n <Send className=\"size-4\" />\n {sending\n ? tr('vibecontrols.ai.prompts.sending', 'Sending...')\n : tr('vibecontrols.ai.prompts.sendToSession', 'Send to Session')}\n </button>\n </div>\n </Card>\n </div>\n\n {/* Context Picker */}\n <div className=\"space-y-4\">\n <Card className=\"p-4\">\n <h3 className=\"font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.ai.prompts.attachContexts', 'Attach Contexts')}\n </h3>\n {contexts.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('vibecontrols.ai.prompts.noContexts', 'No contexts available')}\n </p>\n ) : (\n <div className=\"space-y-2 max-h-64 overflow-y-auto\">\n {contexts.map((ctx) => (\n <label\n key={ctx.id}\n className=\"flex items-start gap-2 p-2 rounded hover:bg-bg-sunken cursor-pointer\"\n >\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selectedContextIds.includes(ctx.id)}\n onChange={() => toggleContext(ctx.id)}\n className=\"mt-0.5 rounded border-border-default\"\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-sm font-medium text-text-primary truncate\">\n {ctx.name}\n </div>\n <div className=\"text-xs text-text-secondary\">{ctx.type}</div>\n </div>\n </label>\n ))}\n </div>\n )}\n </Card>\n </div>\n </div>\n\n {/* Dispatch History */}\n <Card className=\"p-0\">\n <div className=\"px-4 py-3 border-b border-border-default flex items-center gap-2\">\n <Clock className=\"size-4 text-text-secondary\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.ai.prompts.dispatchHistory', 'Dispatch History')}\n </h3>\n </div>\n <div className=\"divide-y divide-border-default\">\n {history.length === 0 ? (\n <EmptyState\n icon={<FileText className=\"size-6 text-text-secondary\" />}\n title={tr('vibecontrols.ai.prompts.noDispatched', 'No dispatched prompts')}\n description={tr(\n 'vibecontrols.ai.prompts.noDispatchedDesc',\n 'Send a prompt to a session to see history here'\n )}\n />\n ) : (\n history.map((d) => (\n <div key={d.id} className=\"px-4 py-3\">\n <div className=\"text-sm text-text-primary line-clamp-2\">{d.prompt}</div>\n <div className=\"mt-1 flex items-center gap-3 text-xs text-text-secondary\">\n <span className=\"font-medium\">{d.status}</span>\n <span>Session: {d.sessionId ? `${d.sessionId.substring(0, 8)}...` : '—'}</span>\n {d.createdAt && <span>{new Date(d.createdAt).toLocaleString()}</span>}\n </div>\n </div>\n ))\n )}\n </div>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AIPromptsTab.js","names":[],"sources":["../../../src/components/ai/AIPromptsTab.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport { Send, FileText, Wand2, Clock } from 'lucide-react';\nimport { Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport type { AITemplate, AIContext, AISession, AIDispatch, AgentRef } from '@/services/aiApi';\nimport {\n fetchTemplates,\n fetchContexts,\n fetchSessions,\n fetchDispatchHistory,\n renderTemplate,\n recordTemplateUsage,\n sendDispatch,\n} from '@/services/aiApi';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AIPromptsTabProps {\n agent: AgentRef;\n}\n\nexport function AIPromptsTab({ agent }: AIPromptsTabProps) {\n const tr = useTr();\n const [promptContent, setPromptContent] = useState('');\n const [selectedContextIds, setSelectedContextIds] = useState<string[]>([]);\n const [targetSessionId, setTargetSessionId] = useState('');\n const [sending, setSending] = useState(false);\n const [successMsg, setSuccessMsg] = useState<string | null>(null);\n\n // Generate from template state\n const [showTemplateGen, setShowTemplateGen] = useState(false);\n const [selectedTemplateId, setSelectedTemplateId] = useState('');\n const [sourceTemplateId, setSourceTemplateId] = useState<string | null>(null);\n const [templateVars, setTemplateVars] = useState<Record<string, string>>({});\n const [rendering, setRendering] = useState(false);\n\n // Data\n const [templates, setTemplates] = useState<AITemplate[]>([]);\n const [contexts, setContexts] = useState<AIContext[]>([]);\n const [sessions, setSessions] = useState<AISession[]>([]);\n const [history, setHistory] = useState<AIDispatch[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const loadData = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const [t, c, s, h] = await Promise.all([\n fetchTemplates(agent.id),\n fetchContexts(agent),\n fetchSessions(agent),\n fetchDispatchHistory(agent, { limit: 20 }),\n ]);\n setTemplates(t);\n setContexts(c);\n setSessions(s);\n setHistory(h);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load data');\n } finally {\n setLoading(false);\n }\n }, [agent]);\n\n useEffect(() => {\n loadData();\n }, [loadData]);\n\n const handleTemplateSelect = (templateId: string) => {\n setSelectedTemplateId(templateId);\n const template = templates.find((t) => t.id === templateId);\n if (template?.variables) {\n const vars: Record<string, string> = {};\n for (const key of Object.keys(template.variables)) {\n vars[key] = String(template.variables[key] ?? '');\n }\n setTemplateVars(vars);\n } else {\n setTemplateVars({});\n }\n };\n\n const handleGenerateFromTemplate = async () => {\n if (!selectedTemplateId) return;\n setRendering(true);\n try {\n const result = await renderTemplate(agent.id, selectedTemplateId, templateVars);\n setPromptContent(result.rendered);\n setSourceTemplateId(selectedTemplateId);\n setShowTemplateGen(false);\n } catch (err) {\n console.error('Failed to render template:', err);\n } finally {\n setRendering(false);\n }\n };\n\n const toggleContext = (contextId: string) => {\n setSelectedContextIds((prev) =>\n prev.includes(contextId) ? prev.filter((id) => id !== contextId) : [...prev, contextId]\n );\n };\n\n const handleSendToSession = async () => {\n if (!promptContent.trim() || !targetSessionId) return;\n setSending(true);\n setSuccessMsg(null);\n try {\n await sendDispatch(agent, {\n prompt: promptContent,\n sessionId: targetSessionId,\n contextIds: selectedContextIds.length > 0 ? selectedContextIds : undefined,\n });\n if (sourceTemplateId) {\n await recordTemplateUsage(agent.id, sourceTemplateId);\n }\n setSuccessMsg('Prompt dispatched successfully');\n setPromptContent('');\n setSourceTemplateId(null);\n setSelectedContextIds([]);\n setTimeout(() => setSuccessMsg(null), 3000);\n // Refresh history\n const h = await fetchDispatchHistory(agent, { limit: 20 });\n setHistory(h);\n } catch (err) {\n console.error('Failed to dispatch prompt:', err);\n } finally {\n setSending(false);\n }\n };\n\n if (loading)\n return <LoadingState message={tr('vibecontrols.ai.prompts.loading', 'Loading prompts...')} />;\n if (error)\n return (\n <ErrorState\n title={tr('vibecontrols.ai.prompts.loadFailed', 'Failed to load data')}\n message={error}\n onRetry={loadData}\n />\n );\n\n return (\n <div className=\"space-y-6\">\n {successMsg && (\n <div className=\"bg-status-success-bg-subtle text-status-success-text px-4 py-2 rounded-md text-sm\">\n {successMsg}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Prompt Editor */}\n <div className=\"lg:col-span-2 space-y-4\">\n <Card className=\"p-4\">\n <div className=\"flex items-center justify-between mb-3\">\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.ai.prompts.composePrompt', 'Compose Prompt')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowTemplateGen(!showTemplateGen)}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-text-primary bg-[var(--color-accent-soft)] rounded-md hover:bg-[var(--color-accent-soft)] transition-colors\"\n >\n <Wand2 className=\"size-3.5\" />\n {tr('vibecontrols.ai.prompts.generateFromTemplate', 'Generate from Template')}\n </button>\n </div>\n\n {/* Template Generator */}\n {showTemplateGen && (\n <div className=\"mb-4 p-3 bg-bg-sunken rounded-md space-y-3\">\n <select\n value={selectedTemplateId}\n onChange={(e) => handleTemplateSelect(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-surface border border-border-default rounded-md text-text-primary text-sm\"\n >\n <option value=\"\">\n {tr('vibecontrols.ai.prompts.selectTemplate', 'Select a template...')}\n </option>\n {templates.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n {Object.keys(templateVars).length > 0 && (\n <div className=\"space-y-2\">\n {Object.entries(templateVars).map(([key, val]) => (\n <div key={key}>\n <label className=\"block text-xs font-medium text-text-secondary mb-0.5\">\n {key}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={val}\n onChange={(e) =>\n setTemplateVars((prev) => ({ ...prev, [key]: e.target.value }))\n }\n className=\"w-full px-2 py-1.5 bg-bg-surface border border-border-default rounded text-sm text-text-primary focus:outline-none focus:border-border-strong\"\n />\n </div>\n ))}\n </div>\n )}\n <button\n type=\"button\"\n onClick={handleGenerateFromTemplate}\n disabled={rendering || !selectedTemplateId}\n className=\"w-full px-3 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {rendering\n ? tr('vibecontrols.ai.prompts.generating', 'Generating...')\n : tr('vibecontrols.ai.prompts.generate', 'Generate')}\n </button>\n </div>\n )}\n\n <textarea\n aria-label=\"control\"\n value={promptContent}\n onChange={(e) => {\n setPromptContent(e.target.value);\n setSourceTemplateId(null);\n }}\n rows={8}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm font-mono focus:outline-none focus:border-border-strong\"\n placeholder={tr(\n 'vibecontrols.ai.prompts.promptPlaceholder',\n 'Write your prompt here... (Markdown supported)'\n )}\n />\n\n {/* Session Target & Send */}\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-end gap-3 mt-3\">\n <div className=\"flex-1\">\n <label className=\"block text-xs font-medium text-text-secondary mb-1\">\n {tr('vibecontrols.ai.prompts.targetSession', 'Target Session')}\n </label>\n <select\n value={targetSessionId}\n onChange={(e) => setTargetSessionId(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n <option value=\"\">\n {tr('vibecontrols.ai.prompts.selectSession', 'Select session...')}\n </option>\n {sessions\n .filter((s) => s.status !== 'terminated')\n .map((s) => (\n <option key={s.id} value={s.id}>\n {s.name} ({s.status})\n </option>\n ))}\n </select>\n </div>\n <button\n type=\"button\"\n onClick={handleSendToSession}\n disabled={sending || !promptContent.trim() || !targetSessionId}\n className=\"flex items-center justify-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 text-sm\"\n >\n <Send className=\"size-4\" />\n {sending\n ? tr('vibecontrols.ai.prompts.sending', 'Sending...')\n : tr('vibecontrols.ai.prompts.sendToSession', 'Send to Session')}\n </button>\n </div>\n </Card>\n </div>\n\n {/* Context Picker */}\n <div className=\"space-y-4\">\n <Card className=\"p-4\">\n <h3 className=\"font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.ai.prompts.attachContexts', 'Attach Contexts')}\n </h3>\n {contexts.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('vibecontrols.ai.prompts.noContexts', 'No contexts available')}\n </p>\n ) : (\n <div className=\"space-y-2 max-h-64 overflow-y-auto\">\n {contexts.map((ctx) => (\n <label\n key={ctx.id}\n className=\"flex items-start gap-2 p-2 rounded hover:bg-bg-sunken cursor-pointer\"\n >\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selectedContextIds.includes(ctx.id)}\n onChange={() => toggleContext(ctx.id)}\n className=\"mt-0.5 rounded border-border-default\"\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-sm font-medium text-text-primary truncate\">\n {ctx.name}\n </div>\n <div className=\"text-xs text-text-secondary\">{ctx.type}</div>\n </div>\n </label>\n ))}\n </div>\n )}\n </Card>\n </div>\n </div>\n\n {/* Dispatch History */}\n <Card className=\"p-0\">\n <div className=\"px-4 py-3 border-b border-border-default flex items-center gap-2\">\n <Clock className=\"size-4 text-text-secondary\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.ai.prompts.dispatchHistory', 'Dispatch History')}\n </h3>\n </div>\n <div className=\"divide-y divide-border-default\">\n {history.length === 0 ? (\n <EmptyState\n icon={<FileText className=\"size-6 text-text-secondary\" />}\n title={tr('vibecontrols.ai.prompts.noDispatched', 'No dispatched prompts')}\n description={tr(\n 'vibecontrols.ai.prompts.noDispatchedDesc',\n 'Send a prompt to a session to see history here'\n )}\n />\n ) : (\n history.map((d) => (\n <div key={d.id} className=\"px-4 py-3\">\n <div className=\"text-sm text-text-primary line-clamp-2\">{d.prompt}</div>\n <div className=\"mt-1 flex items-center gap-3 text-xs text-text-secondary\">\n <span className=\"font-medium\">{d.status}</span>\n <span>Session: {d.sessionId ? `${d.sessionId.substring(0, 8)}...` : '—'}</span>\n {d.createdAt && <span>{new Date(d.createdAt).toLocaleString()}</span>}\n </div>\n </div>\n ))\n )}\n </div>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -18,7 +18,7 @@ var S = {
18
18
  },
19
19
  processing: {
20
20
  label: "Processing",
21
- cls: "bg-[var(--color-accent-soft)] text-action-primary-bg",
21
+ cls: "bg-[var(--color-accent-soft)] text-text-primary",
22
22
  icon: g
23
23
  },
24
24
  completed: {
@@ -1 +1 @@
1
- {"version":3,"file":"AIQueueTab.js","names":[],"sources":["../../../src/components/ai/AIQueueTab.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport {\n ListChecks,\n RefreshCw,\n X,\n AlertCircle,\n Clock,\n Loader2,\n CheckCircle2,\n Ban,\n} from 'lucide-react';\nimport { Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport type { AIQueueItem, AgentRef } from '@/services/aiApi';\nimport { fetchQueueItems, cancelQueueItem } from '@/services/aiApi';\nimport { notify } from '@/lib/toast';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AIQueueTabProps {\n agent: AgentRef;\n onNavigateToSession?: (sessionId: string) => void;\n}\n\nconst STATUS_META: Record<\n AIQueueItem['status'],\n { label: string; cls: string; icon: typeof Clock }\n> = {\n pending: {\n label: 'Pending',\n cls: 'bg-status-warning-bg-subtle text-status-warning-text',\n icon: Clock,\n },\n processing: {\n label: 'Processing',\n cls: 'bg-[var(--color-accent-soft)] text-action-primary-bg',\n icon: Loader2,\n },\n completed: {\n label: 'Completed',\n cls: 'bg-status-success-bg-subtle text-status-success-text',\n icon: CheckCircle2,\n },\n failed: {\n label: 'Failed',\n cls: 'bg-status-error-bg-subtle text-status-error-text',\n icon: AlertCircle,\n },\n cancelled: { label: 'Cancelled', cls: 'bg-bg-sunken text-text-secondary', icon: Ban },\n};\n\n/**\n * AIQueueTab — the \"queue overnight, review in the morning\" surface. Lists the queued AI prompts on\n * the active agent (the agent runs them in the background), with per-item status, attempts, and\n * errors, plus cancel for anything still pending. Auto-refreshes so a morning check shows the latest\n * results. Backed by the agent's `/ai/queue` API (fetchQueueItems / cancelQueueItem) — the same\n * governed agent surface the rest of AI mode uses.\n */\nexport function AIQueueTab({ agent, onNavigateToSession }: AIQueueTabProps): React.ReactElement {\n const tr = useTr();\n const [items, setItems] = useState<AIQueueItem[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [cancelling, setCancelling] = useState<string | null>(null);\n\n const load = useCallback(\n async (showSpinner: boolean): Promise<void> => {\n if (!agent.id) return;\n if (showSpinner) setLoading(true);\n try {\n const data = await fetchQueueItems(agent);\n setItems(data);\n setError(null);\n } catch (err) {\n setError((err as Error).message);\n } finally {\n if (showSpinner) setLoading(false);\n }\n },\n [agent]\n );\n\n useEffect(() => {\n void load(true);\n // Poll while items are still in flight so a left-open tab updates itself overnight.\n const timer = window.setInterval(() => void load(false), 15000);\n return () => window.clearInterval(timer);\n }, [load]);\n\n const handleCancel = async (id: string): Promise<void> => {\n setCancelling(id);\n try {\n await cancelQueueItem(agent, id);\n notify.success(tr('aiQueue.cancelled', 'Queued item cancelled'));\n await load(false);\n } catch (err) {\n notify.error(tr('aiQueue.cancelFailed', 'Could not cancel'), (err as Error).message);\n } finally {\n setCancelling(null);\n }\n };\n\n if (!agent.id) {\n return (\n <EmptyState\n icon={<ListChecks className=\"size-10\" />}\n title={tr('aiQueue.noAgentTitle', 'No active agent')}\n description={tr(\n 'aiQueue.noAgentDesc',\n 'Start or attach an agent to queue AI prompts for background processing.'\n )}\n />\n );\n }\n\n if (loading && items.length === 0) return <LoadingState />;\n if (error && items.length === 0) {\n return <ErrorState message={error} onRetry={() => void load(true)} />;\n }\n\n const pendingCount = items.filter(\n (i) => i.status === 'pending' || i.status === 'processing'\n ).length;\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <ListChecks className=\"size-5\" />\n {tr('aiQueue.title', 'AI Queue')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'aiQueue.subtitle',\n 'Prompts queued for background processing on this agent — queue at night, review in the morning.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => void load(true)}\n className=\"flex items-center gap-1.5 rounded-md border border-border-default px-3 py-1.5 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n {tr('common.refresh', 'Refresh')}\n </button>\n </div>\n\n {items.length === 0 ? (\n <EmptyState\n icon={<ListChecks className=\"size-10\" />}\n title={tr('aiQueue.emptyTitle', 'Queue is empty')}\n description={tr(\n 'aiQueue.emptyDesc',\n 'Dispatch a prompt with \"Queue\" instead of \"Run\" to have the agent process it in the background.'\n )}\n />\n ) : (\n <>\n {pendingCount > 0 && (\n <p className=\"text-xs text-text-tertiary\">\n {pendingCount} {tr('aiQueue.inFlight', 'in flight')} · {items.length}{' '}\n {tr('aiQueue.total', 'total')}\n </p>\n )}\n <Card>\n <ul className=\"divide-y divide-border-subtle\">\n {items.map((item) => {\n const meta = STATUS_META[item.status];\n const StatusIcon = meta.icon;\n return (\n <li key={item.id} className=\"flex items-center gap-3 px-4 py-3\">\n <span\n className={`inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium ${meta.cls}`}\n >\n <StatusIcon\n className={`size-3.5 ${item.status === 'processing' ? 'animate-spin' : ''}`}\n />\n {meta.label}\n </span>\n <div className=\"min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={() => onNavigateToSession?.(item.sessionId)}\n className=\"truncate text-left font-mono text-sm text-text-primary hover:underline\"\n title={tr('aiQueue.openSession', 'Open the session this prompt runs in')}\n >\n {item.sessionId}\n </button>\n <div className=\"text-xs text-text-tertiary\">\n {tr('aiQueue.attempt', 'attempt')} {item.attempts}/{item.maxAttempts}\n {item.createdAt ? ` · ${new Date(item.createdAt).toLocaleString()}` : ''}\n </div>\n {item.lastError && (\n <div className=\"mt-0.5 truncate text-xs text-status-error-text\">\n {item.lastError}\n </div>\n )}\n </div>\n {item.status === 'pending' && (\n <button\n type=\"button\"\n onClick={() => void handleCancel(item.id)}\n disabled={cancelling === item.id}\n className=\"shrink-0 rounded p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-status-error-text disabled:opacity-50\"\n aria-label={tr('common.cancel', 'Cancel')}\n >\n <X className=\"size-4\" />\n </button>\n )}\n </li>\n );\n })}\n </ul>\n </Card>\n </>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAsBA,IAAM,IAGF;CACF,SAAS;EACP,OAAO;EACP,KAAK;EACL,MAAM;EACP;CACD,YAAY;EACV,OAAO;EACP,KAAK;EACL,MAAM;EACP;CACD,WAAW;EACT,OAAO;EACP,KAAK;EACL,MAAM;EACP;CACD,QAAQ;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACP;CACD,WAAW;EAAE,OAAO;EAAa,KAAK;EAAoC,MAAM;EAAK;CACtF;AASD,SAAgB,EAAW,EAAE,UAAO,0BAA4D;CAC9F,IAAM,IAAK,GAAO,EACZ,CAAC,GAAO,KAAY,EAAwB,EAAE,CAAC,EAC/C,CAAC,GAAS,KAAc,EAAS,GAAK,EACtC,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAY,KAAiB,EAAwB,KAAK,EAE3D,IAAO,EACX,OAAO,MAAwC;AACxC,QAAM,IACX;GAAI,KAAa,EAAW,GAAK;AACjC,OAAI;AAGF,IADA,EADa,MAAM,EAAgB,EAAM,CAC3B,EACd,EAAS,KAAK;YACP,GAAK;AACZ,MAAU,EAAc,QAAQ;aACxB;AACR,IAAI,KAAa,EAAW,GAAM;;;IAGtC,CAAC,EAAM,CACR;AAED,SAAgB;AACT,IAAK,GAAK;EAEf,IAAM,IAAQ,OAAO,kBAAkB,KAAK,EAAK,GAAM,EAAE,KAAM;AAC/D,eAAa,OAAO,cAAc,EAAM;IACvC,CAAC,EAAK,CAAC;CAEV,IAAM,IAAe,OAAO,MAA8B;AACxD,IAAc,EAAG;AACjB,MAAI;AAGF,GAFA,MAAM,EAAgB,GAAO,EAAG,EAChC,EAAO,QAAQ,EAAG,qBAAqB,wBAAwB,CAAC,EAChE,MAAM,EAAK,GAAM;WACV,GAAK;AACZ,KAAO,MAAM,EAAG,wBAAwB,mBAAmB,EAAG,EAAc,QAAQ;YAC5E;AACR,KAAc,KAAK;;;AAIvB,KAAI,CAAC,EAAM,GACT,QACE,kBAAC,GAAD;EACE,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;EACxC,OAAO,EAAG,wBAAwB,kBAAkB;EACpD,aAAa,EACX,uBACA,0EACD;EACD,CAAA;AAIN,KAAI,KAAW,EAAM,WAAW,EAAG,QAAO,kBAAC,GAAD,EAAgB,CAAA;AAC1D,KAAI,KAAS,EAAM,WAAW,EAC5B,QAAO,kBAAC,GAAD;EAAY,SAAS;EAAO,eAAe,KAAK,EAAK,GAAK;EAAI,CAAA;CAGvE,IAAM,IAAe,EAAM,QACxB,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,aAC/C,CAAC;AAEF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cAAd,CACE,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA,EAChC,EAAG,iBAAiB,WAAW,CAC7B;OACL,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,oBACA,kGACD;IACC,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,KAAK,EAAK,GAAK;IAC9B,WAAU;cAHZ,CAKE,kBAAC,GAAD,EAAW,WAAW,UAAU,IAAU,iBAAiB,MAAQ,CAAA,EAClE,EAAG,kBAAkB,UAAU,CACzB;MACL;MAEL,EAAM,WAAW,IAChB,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;GACxC,OAAO,EAAG,sBAAsB,iBAAiB;GACjD,aAAa,EACX,qBACA,sGACD;GACD,CAAA,GAEF,kBAAA,GAAA,EAAA,UAAA,CACG,IAAe,KACd,kBAAC,KAAD;GAAG,WAAU;aAAb;IACG;IAAa;IAAE,EAAG,oBAAoB,YAAY;IAAC;IAAI,EAAM;IAAQ;IACrE,EAAG,iBAAiB,QAAQ;IAC3B;MAEN,kBAAC,GAAD,EAAA,UACE,kBAAC,MAAD;GAAI,WAAU;aACX,EAAM,KAAK,MAAS;IACnB,IAAM,IAAO,EAAY,EAAK,SACxB,IAAa,EAAK;AACxB,WACE,kBAAC,MAAD;KAAkB,WAAU;eAA5B;MACE,kBAAC,QAAD;OACE,WAAW,wFAAwF,EAAK;iBAD1G,CAGE,kBAAC,GAAD,EACE,WAAW,YAAY,EAAK,WAAW,eAAe,iBAAiB,MACvE,CAAA,EACD,EAAK,MACD;;MACP,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,IAAsB,EAAK,UAAU;SACpD,WAAU;SACV,OAAO,EAAG,uBAAuB,uCAAuC;mBAEvE,EAAK;SACC,CAAA;QACT,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACG,EAAG,mBAAmB,UAAU;UAAC;UAAE,EAAK;UAAS;UAAE,EAAK;UACxD,EAAK,YAAY,MAAM,IAAI,KAAK,EAAK,UAAU,CAAC,gBAAgB,KAAK;UAClE;;QACL,EAAK,aACJ,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAK;SACF,CAAA;QAEJ;;MACL,EAAK,WAAW,aACf,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,KAAK,EAAa,EAAK,GAAG;OACzC,UAAU,MAAe,EAAK;OAC9B,WAAU;OACV,cAAY,EAAG,iBAAiB,SAAS;iBAEzC,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;OACjB,CAAA;MAER;OAvCI,EAAK,GAuCT;KAEP;GACC,CAAA,EACA,CAAA,CACN,EAAA,CAAA,CAED"}
1
+ {"version":3,"file":"AIQueueTab.js","names":[],"sources":["../../../src/components/ai/AIQueueTab.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport {\n ListChecks,\n RefreshCw,\n X,\n AlertCircle,\n Clock,\n Loader2,\n CheckCircle2,\n Ban,\n} from 'lucide-react';\nimport { Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport type { AIQueueItem, AgentRef } from '@/services/aiApi';\nimport { fetchQueueItems, cancelQueueItem } from '@/services/aiApi';\nimport { notify } from '@/lib/toast';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AIQueueTabProps {\n agent: AgentRef;\n onNavigateToSession?: (sessionId: string) => void;\n}\n\nconst STATUS_META: Record<\n AIQueueItem['status'],\n { label: string; cls: string; icon: typeof Clock }\n> = {\n pending: {\n label: 'Pending',\n cls: 'bg-status-warning-bg-subtle text-status-warning-text',\n icon: Clock,\n },\n processing: {\n label: 'Processing',\n cls: 'bg-[var(--color-accent-soft)] text-text-primary',\n icon: Loader2,\n },\n completed: {\n label: 'Completed',\n cls: 'bg-status-success-bg-subtle text-status-success-text',\n icon: CheckCircle2,\n },\n failed: {\n label: 'Failed',\n cls: 'bg-status-error-bg-subtle text-status-error-text',\n icon: AlertCircle,\n },\n cancelled: { label: 'Cancelled', cls: 'bg-bg-sunken text-text-secondary', icon: Ban },\n};\n\n/**\n * AIQueueTab — the \"queue overnight, review in the morning\" surface. Lists the queued AI prompts on\n * the active agent (the agent runs them in the background), with per-item status, attempts, and\n * errors, plus cancel for anything still pending. Auto-refreshes so a morning check shows the latest\n * results. Backed by the agent's `/ai/queue` API (fetchQueueItems / cancelQueueItem) — the same\n * governed agent surface the rest of AI mode uses.\n */\nexport function AIQueueTab({ agent, onNavigateToSession }: AIQueueTabProps): React.ReactElement {\n const tr = useTr();\n const [items, setItems] = useState<AIQueueItem[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [cancelling, setCancelling] = useState<string | null>(null);\n\n const load = useCallback(\n async (showSpinner: boolean): Promise<void> => {\n if (!agent.id) return;\n if (showSpinner) setLoading(true);\n try {\n const data = await fetchQueueItems(agent);\n setItems(data);\n setError(null);\n } catch (err) {\n setError((err as Error).message);\n } finally {\n if (showSpinner) setLoading(false);\n }\n },\n [agent]\n );\n\n useEffect(() => {\n void load(true);\n // Poll while items are still in flight so a left-open tab updates itself overnight.\n const timer = window.setInterval(() => void load(false), 15000);\n return () => window.clearInterval(timer);\n }, [load]);\n\n const handleCancel = async (id: string): Promise<void> => {\n setCancelling(id);\n try {\n await cancelQueueItem(agent, id);\n notify.success(tr('aiQueue.cancelled', 'Queued item cancelled'));\n await load(false);\n } catch (err) {\n notify.error(tr('aiQueue.cancelFailed', 'Could not cancel'), (err as Error).message);\n } finally {\n setCancelling(null);\n }\n };\n\n if (!agent.id) {\n return (\n <EmptyState\n icon={<ListChecks className=\"size-10\" />}\n title={tr('aiQueue.noAgentTitle', 'No active agent')}\n description={tr(\n 'aiQueue.noAgentDesc',\n 'Start or attach an agent to queue AI prompts for background processing.'\n )}\n />\n );\n }\n\n if (loading && items.length === 0) return <LoadingState />;\n if (error && items.length === 0) {\n return <ErrorState message={error} onRetry={() => void load(true)} />;\n }\n\n const pendingCount = items.filter(\n (i) => i.status === 'pending' || i.status === 'processing'\n ).length;\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <ListChecks className=\"size-5\" />\n {tr('aiQueue.title', 'AI Queue')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'aiQueue.subtitle',\n 'Prompts queued for background processing on this agent — queue at night, review in the morning.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => void load(true)}\n className=\"flex items-center gap-1.5 rounded-md border border-border-default px-3 py-1.5 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n {tr('common.refresh', 'Refresh')}\n </button>\n </div>\n\n {items.length === 0 ? (\n <EmptyState\n icon={<ListChecks className=\"size-10\" />}\n title={tr('aiQueue.emptyTitle', 'Queue is empty')}\n description={tr(\n 'aiQueue.emptyDesc',\n 'Dispatch a prompt with \"Queue\" instead of \"Run\" to have the agent process it in the background.'\n )}\n />\n ) : (\n <>\n {pendingCount > 0 && (\n <p className=\"text-xs text-text-tertiary\">\n {pendingCount} {tr('aiQueue.inFlight', 'in flight')} · {items.length}{' '}\n {tr('aiQueue.total', 'total')}\n </p>\n )}\n <Card>\n <ul className=\"divide-y divide-border-subtle\">\n {items.map((item) => {\n const meta = STATUS_META[item.status];\n const StatusIcon = meta.icon;\n return (\n <li key={item.id} className=\"flex items-center gap-3 px-4 py-3\">\n <span\n className={`inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium ${meta.cls}`}\n >\n <StatusIcon\n className={`size-3.5 ${item.status === 'processing' ? 'animate-spin' : ''}`}\n />\n {meta.label}\n </span>\n <div className=\"min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={() => onNavigateToSession?.(item.sessionId)}\n className=\"truncate text-left font-mono text-sm text-text-primary hover:underline\"\n title={tr('aiQueue.openSession', 'Open the session this prompt runs in')}\n >\n {item.sessionId}\n </button>\n <div className=\"text-xs text-text-tertiary\">\n {tr('aiQueue.attempt', 'attempt')} {item.attempts}/{item.maxAttempts}\n {item.createdAt ? ` · ${new Date(item.createdAt).toLocaleString()}` : ''}\n </div>\n {item.lastError && (\n <div className=\"mt-0.5 truncate text-xs text-status-error-text\">\n {item.lastError}\n </div>\n )}\n </div>\n {item.status === 'pending' && (\n <button\n type=\"button\"\n onClick={() => void handleCancel(item.id)}\n disabled={cancelling === item.id}\n className=\"shrink-0 rounded p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-status-error-text disabled:opacity-50\"\n aria-label={tr('common.cancel', 'Cancel')}\n >\n <X className=\"size-4\" />\n </button>\n )}\n </li>\n );\n })}\n </ul>\n </Card>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -12,7 +12,7 @@ import { jsx as p, jsxs as m } from "react/jsx-runtime";
12
12
  var h = {
13
13
  active: "bg-status-success-bg-subtle text-status-success-text",
14
14
  idle: "bg-status-warning-bg-subtle text-status-warning-text",
15
- processing: "bg-[var(--color-accent-soft)] text-action-primary-bg",
15
+ processing: "bg-[var(--color-accent-soft)] text-text-primary",
16
16
  error: "bg-status-error-bg-subtle text-status-error-text",
17
17
  terminated: "bg-bg-sunken text-text-secondary"
18
18
  }, g = [
@@ -1 +1 @@
1
- {"version":3,"file":"AISessionsTab.js","names":[],"sources":["../../../src/components/ai/AISessionsTab.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport { Plus, MessageSquare, X } from 'lucide-react';\nimport { Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport type { AISession, AgentRef } from '@/services/aiApi';\nimport { fetchSessions, createSession } from '@/services/aiApi';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AISessionsTabProps {\n agent: AgentRef;\n onNavigateToSession?: (sessionId: string) => void;\n}\n\nconst statusColors: Record<string, string> = {\n active: 'bg-status-success-bg-subtle text-status-success-text',\n idle: 'bg-status-warning-bg-subtle text-status-warning-text',\n processing: 'bg-[var(--color-accent-soft)] text-action-primary-bg',\n error: 'bg-status-error-bg-subtle text-status-error-text',\n terminated: 'bg-bg-sunken text-text-secondary',\n};\n\nconst agentTypes = [\n { value: 'claude', label: 'Claude' },\n { value: 'codex', label: 'Codex' },\n { value: 'opencode', label: 'OpenCode' },\n { value: 'gemini', label: 'Gemini' },\n { value: 'cursor', label: 'Cursor' },\n { value: 'openrouter', label: 'OpenRouter' },\n { value: 'minimax', label: 'Minimax' },\n { value: 'ollama', label: 'Ollama' },\n { value: 'openai-compat', label: 'OpenAI-compatible' },\n { value: 'copilot', label: 'Copilot' },\n { value: 'aider', label: 'Aider' },\n { value: 'amp', label: 'Amp' },\n { value: 'crush', label: 'Crush' },\n { value: 'goose', label: 'Goose' },\n { value: 'pi', label: 'Pi' },\n { value: 'plandex', label: 'Plandex' },\n];\n\nexport function AISessionsTab({ agent, onNavigateToSession }: AISessionsTabProps) {\n const tr = useTr();\n const [sessions, setSessions] = useState<AISession[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [showCreate, setShowCreate] = useState(false);\n const [creating, setCreating] = useState(false);\n const [newName, setNewName] = useState('');\n const [newAgentType, setNewAgentType] = useState('claude');\n\n const loadSessions = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const result = await fetchSessions(agent);\n setSessions(result);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load sessions');\n } finally {\n setLoading(false);\n }\n }, [agent]);\n\n useEffect(() => {\n loadSessions();\n }, [loadSessions]);\n\n const handleCreate = async () => {\n if (!newName.trim()) return;\n setCreating(true);\n try {\n const session = await createSession(agent, {\n name: newName,\n agentType: newAgentType,\n });\n setShowCreate(false);\n setNewName('');\n setNewAgentType('claude');\n await loadSessions();\n onNavigateToSession?.(session.id);\n } catch (err) {\n console.error('Failed to create session:', err);\n } finally {\n setCreating(false);\n }\n };\n\n if (loading)\n return <LoadingState message={tr('vibecontrols.ai.sessions.loading', 'Loading sessions...')} />;\n if (error)\n return (\n <ErrorState\n title={tr('vibecontrols.ai.sessions.loadFailed', 'Failed to load sessions')}\n message={error}\n onRetry={loadSessions}\n />\n );\n\n return (\n <div className=\"space-y-4\">\n {/* Toolbar */}\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <p className=\"text-sm text-text-secondary\">{sessions.length} session(s)</p>\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"flex w-full items-center justify-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm text-action-primary-text transition-colors hover:bg-action-primary-bg-hover sm:w-auto\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.ai.sessions.newSession', 'New Session')}\n </button>\n </div>\n\n {/* Session Grid */}\n {sessions.length === 0 ? (\n <EmptyState\n icon={<MessageSquare className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.ai.sessions.emptyTitle', 'No AI sessions')}\n description={tr(\n 'vibecontrols.ai.sessions.emptyDescription',\n 'Create a new session to start interacting with an AI agent'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.ai.sessions.newSession', 'New Session')}\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {sessions.map((session) => (\n <Card\n key={session.id}\n hoverable\n onClick={() => onNavigateToSession?.(session.id)}\n className=\"p-4 cursor-pointer\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <h4 className=\"font-semibold text-text-primary truncate\">{session.name}</h4>\n <p className=\"text-xs text-text-secondary mt-0.5\">{session.agentType}</p>\n </div>\n <span\n className={`px-2 py-0.5 text-xs rounded-full font-medium flex-shrink-0 ${statusColors[session.status] ?? 'bg-bg-sunken text-text-secondary'}`}\n >\n {session.status}\n </span>\n </div>\n\n {session.stats && (\n <div className=\"mt-3 grid grid-cols-2 gap-2 text-xs\">\n <div className=\"bg-bg-sunken rounded p-2\">\n <div className=\"text-text-secondary\">Total Tokens</div>\n <div className=\"font-semibold text-text-primary\">\n {session.stats.totalTokens?.toLocaleString() ?? 0}\n </div>\n </div>\n <div className=\"bg-bg-sunken rounded p-2\">\n <div className=\"text-text-secondary\">Input / Output</div>\n <div className=\"font-semibold text-text-primary\">\n {session.stats.inputTokens?.toLocaleString() ?? 0} /{' '}\n {session.stats.outputTokens?.toLocaleString() ?? 0}\n </div>\n </div>\n </div>\n )}\n\n {session.createdAt && (\n <p className=\"mt-2 text-xs text-text-muted\">\n Created {new Date(session.createdAt).toLocaleDateString()}\n </p>\n )}\n </Card>\n ))}\n </div>\n )}\n\n {/* Create Session Dialog */}\n {showCreate && (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => setShowCreate(false))();\n }\n }}\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\"\n onClick={() => setShowCreate(false)}\n >\n <div\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-sm p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {tr('vibecontrols.ai.sessions.newAISession', 'New AI Session')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"p-1 hover:bg-bg-sunken rounded-md\"\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.ai.sessions.name', 'Name')}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:border-border-strong\"\n placeholder={tr(\n 'vibecontrols.ai.sessions.sessionNamePlaceholder',\n 'Session name'\n )}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.ai.sessions.agentType', 'Agent Type')}\n </label>\n <select\n value={newAgentType}\n onChange={(e) => setNewAgentType(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n {agentTypes.map((t) => (\n <option key={t.value} value={t.value}>\n {t.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"flex justify-end gap-2 mt-6\">\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n {tr('vibecontrols.ai.sessions.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleCreate}\n disabled={creating || !newName.trim()}\n className=\"px-4 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {creating\n ? tr('vibecontrols.ai.sessions.creating', 'Creating...')\n : tr('vibecontrols.ai.sessions.create', 'Create')}\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AISessionsTab.js","names":[],"sources":["../../../src/components/ai/AISessionsTab.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport { Plus, MessageSquare, X } from 'lucide-react';\nimport { Card, LoadingState, ErrorState, EmptyState } from '@/components/shared';\nimport type { AISession, AgentRef } from '@/services/aiApi';\nimport { fetchSessions, createSession } from '@/services/aiApi';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AISessionsTabProps {\n agent: AgentRef;\n onNavigateToSession?: (sessionId: string) => void;\n}\n\nconst statusColors: Record<string, string> = {\n active: 'bg-status-success-bg-subtle text-status-success-text',\n idle: 'bg-status-warning-bg-subtle text-status-warning-text',\n processing: 'bg-[var(--color-accent-soft)] text-text-primary',\n error: 'bg-status-error-bg-subtle text-status-error-text',\n terminated: 'bg-bg-sunken text-text-secondary',\n};\n\nconst agentTypes = [\n { value: 'claude', label: 'Claude' },\n { value: 'codex', label: 'Codex' },\n { value: 'opencode', label: 'OpenCode' },\n { value: 'gemini', label: 'Gemini' },\n { value: 'cursor', label: 'Cursor' },\n { value: 'openrouter', label: 'OpenRouter' },\n { value: 'minimax', label: 'Minimax' },\n { value: 'ollama', label: 'Ollama' },\n { value: 'openai-compat', label: 'OpenAI-compatible' },\n { value: 'copilot', label: 'Copilot' },\n { value: 'aider', label: 'Aider' },\n { value: 'amp', label: 'Amp' },\n { value: 'crush', label: 'Crush' },\n { value: 'goose', label: 'Goose' },\n { value: 'pi', label: 'Pi' },\n { value: 'plandex', label: 'Plandex' },\n];\n\nexport function AISessionsTab({ agent, onNavigateToSession }: AISessionsTabProps) {\n const tr = useTr();\n const [sessions, setSessions] = useState<AISession[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [showCreate, setShowCreate] = useState(false);\n const [creating, setCreating] = useState(false);\n const [newName, setNewName] = useState('');\n const [newAgentType, setNewAgentType] = useState('claude');\n\n const loadSessions = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const result = await fetchSessions(agent);\n setSessions(result);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load sessions');\n } finally {\n setLoading(false);\n }\n }, [agent]);\n\n useEffect(() => {\n loadSessions();\n }, [loadSessions]);\n\n const handleCreate = async () => {\n if (!newName.trim()) return;\n setCreating(true);\n try {\n const session = await createSession(agent, {\n name: newName,\n agentType: newAgentType,\n });\n setShowCreate(false);\n setNewName('');\n setNewAgentType('claude');\n await loadSessions();\n onNavigateToSession?.(session.id);\n } catch (err) {\n console.error('Failed to create session:', err);\n } finally {\n setCreating(false);\n }\n };\n\n if (loading)\n return <LoadingState message={tr('vibecontrols.ai.sessions.loading', 'Loading sessions...')} />;\n if (error)\n return (\n <ErrorState\n title={tr('vibecontrols.ai.sessions.loadFailed', 'Failed to load sessions')}\n message={error}\n onRetry={loadSessions}\n />\n );\n\n return (\n <div className=\"space-y-4\">\n {/* Toolbar */}\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <p className=\"text-sm text-text-secondary\">{sessions.length} session(s)</p>\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"flex w-full items-center justify-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm text-action-primary-text transition-colors hover:bg-action-primary-bg-hover sm:w-auto\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.ai.sessions.newSession', 'New Session')}\n </button>\n </div>\n\n {/* Session Grid */}\n {sessions.length === 0 ? (\n <EmptyState\n icon={<MessageSquare className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.ai.sessions.emptyTitle', 'No AI sessions')}\n description={tr(\n 'vibecontrols.ai.sessions.emptyDescription',\n 'Create a new session to start interacting with an AI agent'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.ai.sessions.newSession', 'New Session')}\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {sessions.map((session) => (\n <Card\n key={session.id}\n hoverable\n onClick={() => onNavigateToSession?.(session.id)}\n className=\"p-4 cursor-pointer\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <h4 className=\"font-semibold text-text-primary truncate\">{session.name}</h4>\n <p className=\"text-xs text-text-secondary mt-0.5\">{session.agentType}</p>\n </div>\n <span\n className={`px-2 py-0.5 text-xs rounded-full font-medium flex-shrink-0 ${statusColors[session.status] ?? 'bg-bg-sunken text-text-secondary'}`}\n >\n {session.status}\n </span>\n </div>\n\n {session.stats && (\n <div className=\"mt-3 grid grid-cols-2 gap-2 text-xs\">\n <div className=\"bg-bg-sunken rounded p-2\">\n <div className=\"text-text-secondary\">Total Tokens</div>\n <div className=\"font-semibold text-text-primary\">\n {session.stats.totalTokens?.toLocaleString() ?? 0}\n </div>\n </div>\n <div className=\"bg-bg-sunken rounded p-2\">\n <div className=\"text-text-secondary\">Input / Output</div>\n <div className=\"font-semibold text-text-primary\">\n {session.stats.inputTokens?.toLocaleString() ?? 0} /{' '}\n {session.stats.outputTokens?.toLocaleString() ?? 0}\n </div>\n </div>\n </div>\n )}\n\n {session.createdAt && (\n <p className=\"mt-2 text-xs text-text-muted\">\n Created {new Date(session.createdAt).toLocaleDateString()}\n </p>\n )}\n </Card>\n ))}\n </div>\n )}\n\n {/* Create Session Dialog */}\n {showCreate && (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => setShowCreate(false))();\n }\n }}\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\"\n onClick={() => setShowCreate(false)}\n >\n <div\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-sm p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {tr('vibecontrols.ai.sessions.newAISession', 'New AI Session')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"p-1 hover:bg-bg-sunken rounded-md\"\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.ai.sessions.name', 'Name')}\n </label>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm focus:outline-none focus:border-border-strong\"\n placeholder={tr(\n 'vibecontrols.ai.sessions.sessionNamePlaceholder',\n 'Session name'\n )}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.ai.sessions.agentType', 'Agent Type')}\n </label>\n <select\n value={newAgentType}\n onChange={(e) => setNewAgentType(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary text-sm\"\n >\n {agentTypes.map((t) => (\n <option key={t.value} value={t.value}>\n {t.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"flex justify-end gap-2 mt-6\">\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n {tr('vibecontrols.ai.sessions.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleCreate}\n disabled={creating || !newName.trim()}\n className=\"px-4 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {creating\n ? tr('vibecontrols.ai.sessions.creating', 'Creating...')\n : tr('vibecontrols.ai.sessions.create', 'Create')}\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -14,7 +14,7 @@ var c = {
14
14
  CREATE: "bg-status-success-bg-subtle text-status-success-text",
15
15
  UPDATE: "bg-status-info-bg-subtle text-status-info-text",
16
16
  DELETE: "bg-status-error-bg-subtle text-status-error-text",
17
- EXECUTE: "bg-[var(--color-accent-soft)] text-action-primary-bg",
17
+ EXECUTE: "bg-[var(--color-accent-soft)] text-text-primary",
18
18
  EXPORT: "bg-status-warning-bg-subtle text-status-warning-text",
19
19
  SHARE: "bg-status-success-bg-subtle text-status-success-text",
20
20
  REVOKE: "bg-status-warning-bg-subtle text-status-warning-text",
@@ -1 +1 @@
1
- {"version":3,"file":"AuditLogTable.js","names":[],"sources":["../../../src/components/audit/AuditLogTable.tsx"],"sourcesContent":["import { ChevronRight, Terminal, Trash2 } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { StatusBadge } from '@/components/shared';\nimport { SelectionCheckbox } from '@/components/shared/SelectionCheckbox';\nimport type { VibeAuditLogFieldsFragment } from '@/generated/wspace-operations';\n\ninterface AuditLogTableProps {\n items: VibeAuditLogFieldsFragment[];\n total: number;\n hasMore: boolean;\n page: number;\n pageSize: number;\n onPageChange: (page: number) => void;\n onSelectLog: (id: string) => void;\n onDeleteLog: (id: string) => void;\n selectedLogId: string | null;\n selectedLogIds: Set<string>;\n allVisibleSelected: boolean;\n partiallySelected: boolean;\n onToggleLogSelection: (id: string, checked: boolean) => void;\n onToggleAllVisible: (checked: boolean) => void;\n}\n\nconst statusVariantMap: Record<string, 'success' | 'error' | 'warning' | 'info'> = {\n SUCCESS: 'success',\n FAILURE: 'error',\n DENIED: 'warning',\n ERROR: 'error',\n};\n\nconst actionColors: Record<string, string> = {\n CREATE: 'bg-status-success-bg-subtle text-status-success-text',\n UPDATE: 'bg-status-info-bg-subtle text-status-info-text',\n DELETE: 'bg-status-error-bg-subtle text-status-error-text',\n EXECUTE: 'bg-[var(--color-accent-soft)] text-action-primary-bg',\n EXPORT: 'bg-status-warning-bg-subtle text-status-warning-text',\n SHARE: 'bg-status-success-bg-subtle text-status-success-text',\n REVOKE: 'bg-status-warning-bg-subtle text-status-warning-text',\n ARCHIVE: 'bg-bg-muted text-text-secondary',\n RESTORE: 'bg-status-info-bg-subtle text-status-info-text',\n};\n\nfunction formatDate(dateString: string): string {\n const date = new Date(dateString);\n return date.toLocaleString('en-US', {\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n });\n}\n\nfunction formatResourceType(type: string): string {\n return type\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/\\b\\w/g, (character) => character.toUpperCase());\n}\n\nexport function AuditLogTable({\n items,\n total,\n hasMore,\n page,\n pageSize,\n onPageChange,\n onSelectLog,\n onDeleteLog,\n selectedLogId,\n selectedLogIds,\n allVisibleSelected,\n partiallySelected,\n onToggleLogSelection,\n onToggleAllVisible,\n}: AuditLogTableProps) {\n const { t } = useI18n();\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n\n const tr = (key: string, fallback: string) => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n return (\n <div>\n <div className=\"space-y-3 md:hidden\">\n {items.map((item) => {\n const isSelected = selectedLogIds.has(item.id);\n return (\n <article\n key={item.id}\n className={`rounded-lg border border-border-default p-4 transition-colors ${\n selectedLogId === item.id ? 'bg-bg-sunken' : 'bg-bg-surface'\n }`}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3\">\n <SelectionCheckbox\n checked={isSelected}\n onChange={(checked) => onToggleLogSelection(item.id, checked)}\n ariaLabel={`Select audit log ${item.id}`}\n className=\"mt-1\"\n />\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">{formatDate(item.createdAt)}</p>\n <p className=\"mt-1 truncate font-medium text-text-primary\">\n {item.resourceName || item.description || '-'}\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {formatResourceType(item.resourceType)}\n </p>\n </div>\n </div>\n <span\n className={`inline-flex shrink-0 items-center gap-1 rounded px-2 py-0.5 text-xs font-medium ${\n actionColors[item.action] || 'bg-bg-surface text-text-secondary'\n }`}\n >\n {item.command ? <Terminal size={12} /> : null}\n {item.action}\n </span>\n </div>\n <div className=\"mt-3 flex items-center justify-between gap-3\">\n <p className=\"min-w-0 truncate text-xs capitalize text-text-secondary\">\n {(item.actorType || 'unknown').toLowerCase().replace(/_/g, ' ')}\n {item.resourceName ? ` · ${item.resourceName}` : ''}\n </p>\n <StatusBadge\n status={item.status}\n variant={statusVariantMap[item.status]}\n size=\"sm\"\n />\n </div>\n <div className=\"mt-4 flex items-center justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => onSelectLog(item.id)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-3 py-1.5 text-sm text-text-primary transition-colors hover:bg-bg-surface\"\n >\n View details\n <ChevronRight size={14} />\n </button>\n <button\n type=\"button\"\n onClick={() => onDeleteLog(item.id)}\n className=\"rounded p-1 text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text\"\n title={tr('audit.deleteAuditLog', 'Delete audit log')}\n >\n <Trash2 size={14} />\n </button>\n </div>\n </article>\n );\n })}\n </div>\n\n <div className=\"hidden overflow-x-auto rounded-card border border-border-subtle bg-bg-surface shadow-elevation-1 md:block\">\n <table className=\"w-full min-w-[860px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle bg-bg-sunken\">\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={onToggleAllVisible}\n ariaLabel=\"Select all visible audit logs\"\n />\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.time', 'Time')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.action', 'Action')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.resource', 'Resource')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.name', 'Name')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.actor', 'Actor')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.status', 'Status')}\n </th>\n <th className=\"px-4 py-3 text-right text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {items.map((item) => {\n const isSelected = selectedLogIds.has(item.id);\n return (\n <tr\n key={item.id}\n className={`border-b border-border-subtle transition-colors hover:bg-bg-sunken ${\n selectedLogId === item.id ? 'bg-bg-sunken' : ''\n }`}\n >\n <td className=\"px-4 py-3 align-top\">\n <SelectionCheckbox\n checked={isSelected}\n onChange={(checked) => onToggleLogSelection(item.id, checked)}\n ariaLabel={`Select audit log ${item.id}`}\n />\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-xs text-text-secondary\">\n {formatDate(item.createdAt)}\n </td>\n <td className=\"px-4 py-3\">\n <span\n className={`inline-flex items-center gap-1 rounded px-2 py-0.5 text-xs font-medium ${\n actionColors[item.action] || 'bg-bg-surface text-text-secondary'\n }`}\n >\n {item.command ? <Terminal size={12} /> : null}\n {item.action}\n </span>\n </td>\n <td className=\"px-4 py-3 text-xs text-text-secondary\">\n {formatResourceType(item.resourceType)}\n </td>\n <td\n className=\"max-w-[220px] truncate px-4 py-3\"\n title={item.resourceName || item.description || ''}\n >\n {item.resourceName || item.description || '-'}\n </td>\n <td\n className=\"max-w-[140px] truncate px-4 py-3 text-xs capitalize text-text-secondary\"\n title={item.actorId}\n >\n {(item.actorType || 'unknown').toLowerCase().replace(/_/g, ' ')}\n </td>\n <td className=\"px-4 py-3\">\n <StatusBadge\n status={item.status}\n variant={statusVariantMap[item.status]}\n size=\"sm\"\n />\n </td>\n <td className=\"px-4 py-3 text-right\">\n <div className=\"flex items-center justify-end gap-1\">\n <button\n type=\"button\"\n onClick={() => onSelectLog(item.id)}\n className=\"rounded p-1 text-text-secondary transition-colors hover:bg-bg-surface hover:text-text-primary\"\n title={tr('audit.viewAuditLog', 'View details')}\n >\n <ChevronRight size={14} />\n </button>\n <button\n type=\"button\"\n onClick={() => onDeleteLog(item.id)}\n className=\"rounded p-1 text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text\"\n title={tr('audit.deleteAuditLog', 'Delete audit log')}\n >\n <Trash2 size={14} />\n </button>\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n\n <div className=\"mt-4 flex flex-col gap-3 text-sm text-text-secondary sm:flex-row sm:items-center sm:justify-between\">\n <span>\n {tr('audit.showing', 'Showing')} {page * pageSize + 1}-\n {Math.min((page + 1) * pageSize, total)} {tr('audit.of', 'of')} {total}\n </span>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n disabled={page === 0}\n onClick={() => onPageChange(page - 1)}\n className=\"rounded border border-border-default px-3 py-1 transition-colors hover:bg-bg-surface disabled:opacity-50\"\n >\n {tr('audit.previous', 'Previous')}\n </button>\n <span>\n {tr('audit.page', 'Page')} {page + 1} {tr('audit.of', 'of')} {totalPages}\n </span>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => onPageChange(page + 1)}\n className=\"rounded border border-border-default px-3 py-1 transition-colors hover:bg-bg-surface disabled:opacity-50\"\n >\n {tr('audit.next', 'Next')}\n </button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AuditLogTable.js","names":[],"sources":["../../../src/components/audit/AuditLogTable.tsx"],"sourcesContent":["import { ChevronRight, Terminal, Trash2 } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { StatusBadge } from '@/components/shared';\nimport { SelectionCheckbox } from '@/components/shared/SelectionCheckbox';\nimport type { VibeAuditLogFieldsFragment } from '@/generated/wspace-operations';\n\ninterface AuditLogTableProps {\n items: VibeAuditLogFieldsFragment[];\n total: number;\n hasMore: boolean;\n page: number;\n pageSize: number;\n onPageChange: (page: number) => void;\n onSelectLog: (id: string) => void;\n onDeleteLog: (id: string) => void;\n selectedLogId: string | null;\n selectedLogIds: Set<string>;\n allVisibleSelected: boolean;\n partiallySelected: boolean;\n onToggleLogSelection: (id: string, checked: boolean) => void;\n onToggleAllVisible: (checked: boolean) => void;\n}\n\nconst statusVariantMap: Record<string, 'success' | 'error' | 'warning' | 'info'> = {\n SUCCESS: 'success',\n FAILURE: 'error',\n DENIED: 'warning',\n ERROR: 'error',\n};\n\nconst actionColors: Record<string, string> = {\n CREATE: 'bg-status-success-bg-subtle text-status-success-text',\n UPDATE: 'bg-status-info-bg-subtle text-status-info-text',\n DELETE: 'bg-status-error-bg-subtle text-status-error-text',\n EXECUTE: 'bg-[var(--color-accent-soft)] text-text-primary',\n EXPORT: 'bg-status-warning-bg-subtle text-status-warning-text',\n SHARE: 'bg-status-success-bg-subtle text-status-success-text',\n REVOKE: 'bg-status-warning-bg-subtle text-status-warning-text',\n ARCHIVE: 'bg-bg-muted text-text-secondary',\n RESTORE: 'bg-status-info-bg-subtle text-status-info-text',\n};\n\nfunction formatDate(dateString: string): string {\n const date = new Date(dateString);\n return date.toLocaleString('en-US', {\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n });\n}\n\nfunction formatResourceType(type: string): string {\n return type\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/\\b\\w/g, (character) => character.toUpperCase());\n}\n\nexport function AuditLogTable({\n items,\n total,\n hasMore,\n page,\n pageSize,\n onPageChange,\n onSelectLog,\n onDeleteLog,\n selectedLogId,\n selectedLogIds,\n allVisibleSelected,\n partiallySelected,\n onToggleLogSelection,\n onToggleAllVisible,\n}: AuditLogTableProps) {\n const { t } = useI18n();\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n\n const tr = (key: string, fallback: string) => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n return (\n <div>\n <div className=\"space-y-3 md:hidden\">\n {items.map((item) => {\n const isSelected = selectedLogIds.has(item.id);\n return (\n <article\n key={item.id}\n className={`rounded-lg border border-border-default p-4 transition-colors ${\n selectedLogId === item.id ? 'bg-bg-sunken' : 'bg-bg-surface'\n }`}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3\">\n <SelectionCheckbox\n checked={isSelected}\n onChange={(checked) => onToggleLogSelection(item.id, checked)}\n ariaLabel={`Select audit log ${item.id}`}\n className=\"mt-1\"\n />\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">{formatDate(item.createdAt)}</p>\n <p className=\"mt-1 truncate font-medium text-text-primary\">\n {item.resourceName || item.description || '-'}\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {formatResourceType(item.resourceType)}\n </p>\n </div>\n </div>\n <span\n className={`inline-flex shrink-0 items-center gap-1 rounded px-2 py-0.5 text-xs font-medium ${\n actionColors[item.action] || 'bg-bg-surface text-text-secondary'\n }`}\n >\n {item.command ? <Terminal size={12} /> : null}\n {item.action}\n </span>\n </div>\n <div className=\"mt-3 flex items-center justify-between gap-3\">\n <p className=\"min-w-0 truncate text-xs capitalize text-text-secondary\">\n {(item.actorType || 'unknown').toLowerCase().replace(/_/g, ' ')}\n {item.resourceName ? ` · ${item.resourceName}` : ''}\n </p>\n <StatusBadge\n status={item.status}\n variant={statusVariantMap[item.status]}\n size=\"sm\"\n />\n </div>\n <div className=\"mt-4 flex items-center justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => onSelectLog(item.id)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-3 py-1.5 text-sm text-text-primary transition-colors hover:bg-bg-surface\"\n >\n View details\n <ChevronRight size={14} />\n </button>\n <button\n type=\"button\"\n onClick={() => onDeleteLog(item.id)}\n className=\"rounded p-1 text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text\"\n title={tr('audit.deleteAuditLog', 'Delete audit log')}\n >\n <Trash2 size={14} />\n </button>\n </div>\n </article>\n );\n })}\n </div>\n\n <div className=\"hidden overflow-x-auto rounded-card border border-border-subtle bg-bg-surface shadow-elevation-1 md:block\">\n <table className=\"w-full min-w-[860px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle bg-bg-sunken\">\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={onToggleAllVisible}\n ariaLabel=\"Select all visible audit logs\"\n />\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.time', 'Time')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.action', 'Action')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.resource', 'Resource')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.name', 'Name')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.actor', 'Actor')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.status', 'Status')}\n </th>\n <th className=\"px-4 py-3 text-right text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('audit.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {items.map((item) => {\n const isSelected = selectedLogIds.has(item.id);\n return (\n <tr\n key={item.id}\n className={`border-b border-border-subtle transition-colors hover:bg-bg-sunken ${\n selectedLogId === item.id ? 'bg-bg-sunken' : ''\n }`}\n >\n <td className=\"px-4 py-3 align-top\">\n <SelectionCheckbox\n checked={isSelected}\n onChange={(checked) => onToggleLogSelection(item.id, checked)}\n ariaLabel={`Select audit log ${item.id}`}\n />\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-xs text-text-secondary\">\n {formatDate(item.createdAt)}\n </td>\n <td className=\"px-4 py-3\">\n <span\n className={`inline-flex items-center gap-1 rounded px-2 py-0.5 text-xs font-medium ${\n actionColors[item.action] || 'bg-bg-surface text-text-secondary'\n }`}\n >\n {item.command ? <Terminal size={12} /> : null}\n {item.action}\n </span>\n </td>\n <td className=\"px-4 py-3 text-xs text-text-secondary\">\n {formatResourceType(item.resourceType)}\n </td>\n <td\n className=\"max-w-[220px] truncate px-4 py-3\"\n title={item.resourceName || item.description || ''}\n >\n {item.resourceName || item.description || '-'}\n </td>\n <td\n className=\"max-w-[140px] truncate px-4 py-3 text-xs capitalize text-text-secondary\"\n title={item.actorId}\n >\n {(item.actorType || 'unknown').toLowerCase().replace(/_/g, ' ')}\n </td>\n <td className=\"px-4 py-3\">\n <StatusBadge\n status={item.status}\n variant={statusVariantMap[item.status]}\n size=\"sm\"\n />\n </td>\n <td className=\"px-4 py-3 text-right\">\n <div className=\"flex items-center justify-end gap-1\">\n <button\n type=\"button\"\n onClick={() => onSelectLog(item.id)}\n className=\"rounded p-1 text-text-secondary transition-colors hover:bg-bg-surface hover:text-text-primary\"\n title={tr('audit.viewAuditLog', 'View details')}\n >\n <ChevronRight size={14} />\n </button>\n <button\n type=\"button\"\n onClick={() => onDeleteLog(item.id)}\n className=\"rounded p-1 text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text\"\n title={tr('audit.deleteAuditLog', 'Delete audit log')}\n >\n <Trash2 size={14} />\n </button>\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n\n <div className=\"mt-4 flex flex-col gap-3 text-sm text-text-secondary sm:flex-row sm:items-center sm:justify-between\">\n <span>\n {tr('audit.showing', 'Showing')} {page * pageSize + 1}-\n {Math.min((page + 1) * pageSize, total)} {tr('audit.of', 'of')} {total}\n </span>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n disabled={page === 0}\n onClick={() => onPageChange(page - 1)}\n className=\"rounded border border-border-default px-3 py-1 transition-colors hover:bg-bg-surface disabled:opacity-50\"\n >\n {tr('audit.previous', 'Previous')}\n </button>\n <span>\n {tr('audit.page', 'Page')} {page + 1} {tr('audit.of', 'of')} {totalPages}\n </span>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => onPageChange(page + 1)}\n className=\"rounded border border-border-default px-3 py-1 transition-colors hover:bg-bg-surface disabled:opacity-50\"\n >\n {tr('audit.next', 'Next')}\n </button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -23,7 +23,7 @@ function f({ node: e, depth: t, selectedPageId: o, onSelectPage: s, onDeletePage
23
23
  onKeyDown: (t) => {
24
24
  (t.key === "Enter" || t.key === " ") && (t.preventDefault(), s(e.page.id));
25
25
  },
26
- className: `flex items-center gap-1 px-2 py-1.5 cursor-pointer rounded text-sm transition-colors group ${h ? "bg-[var(--color-accent-soft)] text-action-primary-bg" : "text-text-secondary hover:bg-bg-sunken hover:text-text-primary"}`,
26
+ className: `flex items-center gap-1 px-2 py-1.5 cursor-pointer rounded text-sm transition-colors group ${h ? "bg-[var(--color-accent-soft)] text-text-primary" : "text-text-secondary hover:bg-bg-sunken hover:text-text-primary"}`,
27
27
  style: { paddingLeft: `${8 + t * 16}px` },
28
28
  onClick: () => s(e.page.id),
29
29
  children: [
@@ -1 +1 @@
1
- {"version":3,"file":"DocsPageTree.js","names":[],"sources":["../../../src/components/docs/DocsPageTree.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n FileText,\n FolderOpen,\n Plus,\n Trash2,\n ChevronRight,\n ChevronDown,\n Loader2,\n} from 'lucide-react';\nimport type { DocsPage } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface DocsPageTreeProps {\n pages: DocsPage[];\n selectedPageId: string | null;\n onSelectPage: (pageId: string) => void;\n onAddPage: (parentId?: string, category?: string) => void;\n pageAdding?: boolean;\n onDeletePage: (pageId: string) => void;\n}\n\ninterface TreeNode {\n page: DocsPage;\n children: TreeNode[];\n}\n\nfunction buildTree(pages: DocsPage[]): TreeNode[] {\n const map = new Map<string, TreeNode>();\n const roots: TreeNode[] = [];\n\n for (const page of pages) {\n map.set(page.id, { page, children: [] });\n }\n\n for (const page of pages) {\n const node = map.get(page.id)!;\n if (page.parentId && map.has(page.parentId)) {\n map.get(page.parentId)!.children.push(node);\n } else {\n roots.push(node);\n }\n }\n\n return roots;\n}\n\nfunction TreeItem({\n node,\n depth,\n selectedPageId,\n onSelectPage,\n onDeletePage,\n expandedIds,\n toggleExpand,\n}: {\n node: TreeNode;\n depth: number;\n selectedPageId: string | null;\n onSelectPage: (id: string) => void;\n onDeletePage: (id: string) => void;\n expandedIds: Set<string>;\n toggleExpand: (id: string) => void;\n}) {\n const isSelected = selectedPageId === node.page.id;\n const hasChildren = node.children.length > 0;\n const isExpanded = expandedIds.has(node.page.id);\n\n return (\n <div>\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => onSelectPage(node.page.id))();\n }\n }}\n className={`flex items-center gap-1 px-2 py-1.5 cursor-pointer rounded text-sm transition-colors group ${\n isSelected\n ? 'bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n }`}\n style={{ paddingLeft: `${8 + depth * 16}px` }}\n onClick={() => onSelectPage(node.page.id)}\n >\n {hasChildren ? (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n toggleExpand(node.page.id);\n }}\n className=\"p-0.5 -ml-1\"\n >\n {isExpanded ? <ChevronDown className=\"size-3\" /> : <ChevronRight className=\"size-3\" />}\n </button>\n ) : (\n <span className=\"w-4\" />\n )}\n {node.page.category ? (\n <FolderOpen className=\"size-3.5 flex-shrink-0\" />\n ) : (\n <FileText className=\"size-3.5 flex-shrink-0\" />\n )}\n <span className=\"truncate flex-1\">{node.page.title}</span>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onDeletePage(node.page.id);\n }}\n className=\"opacity-0 group-hover:opacity-100 p-0.5 text-text-muted hover:text-status-error-text transition-all\"\n title=\"Delete\"\n >\n <Trash2 className=\"size-3\" />\n </button>\n </div>\n {hasChildren && isExpanded && (\n <div>\n {node.children.map((child) => (\n <TreeItem\n key={child.page.id}\n node={child}\n depth={depth + 1}\n selectedPageId={selectedPageId}\n onSelectPage={onSelectPage}\n onDeletePage={onDeletePage}\n expandedIds={expandedIds}\n toggleExpand={toggleExpand}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport function DocsPageTree({\n pages,\n selectedPageId,\n onSelectPage,\n onAddPage,\n pageAdding,\n onDeletePage,\n}: DocsPageTreeProps) {\n const tr = useTr();\n const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set(pages.map((p) => p.id)));\n\n const toggleExpand = (id: string) => {\n setExpandedIds((prev) => {\n const next = new Set(prev);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n };\n\n const tree = buildTree(pages);\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex items-center justify-between px-3 py-2 border-b border-border-default\">\n <span className=\"text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.docs.pageTree.pages', 'Pages')}\n </span>\n <button\n type=\"button\"\n onClick={() => onAddPage()}\n disabled={pageAdding}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors disabled:opacity-50\"\n title=\"Add page\"\n >\n {pageAdding ? <Loader2 className=\"size-4 animate-spin\" /> : <Plus className=\"size-4\" />}\n </button>\n </div>\n <div className=\"flex-1 overflow-y-auto py-1\">\n {tree.length > 0 ? (\n tree.map((node) => (\n <TreeItem\n key={node.page.id}\n node={node}\n depth={0}\n selectedPageId={selectedPageId}\n onSelectPage={onSelectPage}\n onDeletePage={onDeletePage}\n expandedIds={expandedIds}\n toggleExpand={toggleExpand}\n />\n ))\n ) : (\n <div className=\"p-4 text-center\">\n <p className=\"text-sm text-text-muted mb-2\">\n {tr('vibecontrols.docs.pageTree.noPages', 'No pages yet')}\n </p>\n <button\n type=\"button\"\n onClick={() => onAddPage()}\n className=\"text-sm text-action-primary-bg hover:underline\"\n >\n {tr('vibecontrols.docs.pageTree.addFirstPage', 'Add your first page')}\n </button>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DocsPageTree.js","names":[],"sources":["../../../src/components/docs/DocsPageTree.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n FileText,\n FolderOpen,\n Plus,\n Trash2,\n ChevronRight,\n ChevronDown,\n Loader2,\n} from 'lucide-react';\nimport type { DocsPage } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface DocsPageTreeProps {\n pages: DocsPage[];\n selectedPageId: string | null;\n onSelectPage: (pageId: string) => void;\n onAddPage: (parentId?: string, category?: string) => void;\n pageAdding?: boolean;\n onDeletePage: (pageId: string) => void;\n}\n\ninterface TreeNode {\n page: DocsPage;\n children: TreeNode[];\n}\n\nfunction buildTree(pages: DocsPage[]): TreeNode[] {\n const map = new Map<string, TreeNode>();\n const roots: TreeNode[] = [];\n\n for (const page of pages) {\n map.set(page.id, { page, children: [] });\n }\n\n for (const page of pages) {\n const node = map.get(page.id)!;\n if (page.parentId && map.has(page.parentId)) {\n map.get(page.parentId)!.children.push(node);\n } else {\n roots.push(node);\n }\n }\n\n return roots;\n}\n\nfunction TreeItem({\n node,\n depth,\n selectedPageId,\n onSelectPage,\n onDeletePage,\n expandedIds,\n toggleExpand,\n}: {\n node: TreeNode;\n depth: number;\n selectedPageId: string | null;\n onSelectPage: (id: string) => void;\n onDeletePage: (id: string) => void;\n expandedIds: Set<string>;\n toggleExpand: (id: string) => void;\n}) {\n const isSelected = selectedPageId === node.page.id;\n const hasChildren = node.children.length > 0;\n const isExpanded = expandedIds.has(node.page.id);\n\n return (\n <div>\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => onSelectPage(node.page.id))();\n }\n }}\n className={`flex items-center gap-1 px-2 py-1.5 cursor-pointer rounded text-sm transition-colors group ${\n isSelected\n ? 'bg-[var(--color-accent-soft)] text-text-primary'\n : 'text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n }`}\n style={{ paddingLeft: `${8 + depth * 16}px` }}\n onClick={() => onSelectPage(node.page.id)}\n >\n {hasChildren ? (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n toggleExpand(node.page.id);\n }}\n className=\"p-0.5 -ml-1\"\n >\n {isExpanded ? <ChevronDown className=\"size-3\" /> : <ChevronRight className=\"size-3\" />}\n </button>\n ) : (\n <span className=\"w-4\" />\n )}\n {node.page.category ? (\n <FolderOpen className=\"size-3.5 flex-shrink-0\" />\n ) : (\n <FileText className=\"size-3.5 flex-shrink-0\" />\n )}\n <span className=\"truncate flex-1\">{node.page.title}</span>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onDeletePage(node.page.id);\n }}\n className=\"opacity-0 group-hover:opacity-100 p-0.5 text-text-muted hover:text-status-error-text transition-all\"\n title=\"Delete\"\n >\n <Trash2 className=\"size-3\" />\n </button>\n </div>\n {hasChildren && isExpanded && (\n <div>\n {node.children.map((child) => (\n <TreeItem\n key={child.page.id}\n node={child}\n depth={depth + 1}\n selectedPageId={selectedPageId}\n onSelectPage={onSelectPage}\n onDeletePage={onDeletePage}\n expandedIds={expandedIds}\n toggleExpand={toggleExpand}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport function DocsPageTree({\n pages,\n selectedPageId,\n onSelectPage,\n onAddPage,\n pageAdding,\n onDeletePage,\n}: DocsPageTreeProps) {\n const tr = useTr();\n const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set(pages.map((p) => p.id)));\n\n const toggleExpand = (id: string) => {\n setExpandedIds((prev) => {\n const next = new Set(prev);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n };\n\n const tree = buildTree(pages);\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex items-center justify-between px-3 py-2 border-b border-border-default\">\n <span className=\"text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.docs.pageTree.pages', 'Pages')}\n </span>\n <button\n type=\"button\"\n onClick={() => onAddPage()}\n disabled={pageAdding}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors disabled:opacity-50\"\n title=\"Add page\"\n >\n {pageAdding ? <Loader2 className=\"size-4 animate-spin\" /> : <Plus className=\"size-4\" />}\n </button>\n </div>\n <div className=\"flex-1 overflow-y-auto py-1\">\n {tree.length > 0 ? (\n tree.map((node) => (\n <TreeItem\n key={node.page.id}\n node={node}\n depth={0}\n selectedPageId={selectedPageId}\n onSelectPage={onSelectPage}\n onDeletePage={onDeletePage}\n expandedIds={expandedIds}\n toggleExpand={toggleExpand}\n />\n ))\n ) : (\n <div className=\"p-4 text-center\">\n <p className=\"text-sm text-text-muted mb-2\">\n {tr('vibecontrols.docs.pageTree.noPages', 'No pages yet')}\n </p>\n <button\n type=\"button\"\n onClick={() => onAddPage()}\n className=\"text-sm text-action-primary-bg hover:underline\"\n >\n {tr('vibecontrols.docs.pageTree.addFirstPage', 'Add your first page')}\n </button>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -54,7 +54,7 @@ function p({ connection: p, availablePermissions: m, availableScopes: h, onSave:
54
54
  type: "button",
55
55
  "aria-pressed": x.has(e),
56
56
  onClick: () => T(e),
57
- className: `${f} ${x.has(e) ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
57
+ className: `${f} ${x.has(e) ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
58
58
  children: e
59
59
  }, e)), (m ?? []).length === 0 && /* @__PURE__ */ r("span", {
60
60
  className: "text-xs text-text-muted",
@@ -74,7 +74,7 @@ function p({ connection: p, availablePermissions: m, availableScopes: h, onSave:
74
74
  type: "button",
75
75
  "aria-pressed": C.has(e),
76
76
  onClick: () => E(e),
77
- className: `${f} ${C.has(e) ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
77
+ className: `${f} ${C.has(e) ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
78
78
  children: e
79
79
  }, e)), (h ?? []).length === 0 && /* @__PURE__ */ r("span", {
80
80
  className: "text-xs text-text-muted",
@@ -1 +1 @@
1
- {"version":3,"file":"ConnectionPermissionsDialog.js","names":[],"sources":["../../../src/components/graph/ConnectionPermissionsDialog.tsx"],"sourcesContent":["/**\n * ConnectionPermissionsDialog — Edit RBAC permissions and scopes for an agent connection edge.\n */\n\nimport { useId, useState } from 'react';\nimport {\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface Connection {\n id: string;\n protocol: string;\n permissions?: string[];\n scopes?: string[];\n label?: string | null;\n description?: string | null;\n sourceAgent: { id: string; name: string };\n target: { id: string; name: string };\n}\n\ninterface Props {\n connection: Connection;\n availablePermissions?: string[];\n availableScopes?: string[];\n onSave: (permissions: string[], scopes: string[]) => void;\n onClose: () => void;\n}\n\nconst CHIP_BASE_CLASS =\n 'rounded-full border px-3 py-1.5 text-xs font-medium transition-colors max-md:min-h-11 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2';\n\nexport function ConnectionPermissionsDialog({\n connection,\n availablePermissions,\n availableScopes,\n onSave,\n onClose,\n}: Props) {\n const tr = useTr();\n const permissionsLabelId = useId();\n const scopesLabelId = useId();\n const [permissions, setPermissions] = useState<Set<string>>(\n new Set(connection.permissions || [])\n );\n const [scopes, setScopes] = useState<Set<string>>(new Set(connection.scopes || []));\n\n const togglePermission = (p: string) => {\n setPermissions((prev) => {\n const next = new Set(prev);\n if (next.has(p)) next.delete(p);\n else next.add(p);\n return next;\n });\n };\n\n const toggleScope = (s: string) => {\n setScopes((prev) => {\n const next = new Set(prev);\n if (next.has(s)) next.delete(s);\n else next.add(s);\n return next;\n });\n };\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(open) => {\n if (!open) onClose();\n }}\n >\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('connectionPermissionsDialog.title', 'Connection Permissions')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n <span className=\"font-medium text-text-primary\">{connection.sourceAgent.name}</span>\n {' → '}\n <span className=\"font-medium text-text-primary\">{connection.target.name}</span>\n <span className=\"ml-2 rounded bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {connection.protocol}\n </span>\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4\">\n {/* Permissions */}\n <div role=\"group\" aria-labelledby={permissionsLabelId}>\n <span\n id={permissionsLabelId}\n className=\"mb-2 block text-sm font-medium text-text-primary\"\n >\n {tr('connectionPermissionsDialog.permissions', 'Permissions')}\n </span>\n <div className=\"flex flex-wrap gap-2\">\n {(availablePermissions ?? []).map((p) => (\n <button\n type=\"button\"\n key={p}\n aria-pressed={permissions.has(p)}\n onClick={() => togglePermission(p)}\n className={`${CHIP_BASE_CLASS} ${\n permissions.has(p)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {p}\n </button>\n ))}\n {(availablePermissions ?? []).length === 0 && (\n <span className=\"text-xs text-text-muted\">\n {tr('connectionPermissionsDialog.noPermissions', 'No permissions configured')}\n </span>\n )}\n </div>\n </div>\n\n {/* Scopes */}\n <div role=\"group\" aria-labelledby={scopesLabelId}>\n <span id={scopesLabelId} className=\"mb-2 block text-sm font-medium text-text-primary\">\n {tr('connectionPermissionsDialog.scopes', 'Scopes')}\n </span>\n <div className=\"flex flex-wrap gap-2\">\n {(availableScopes ?? []).map((s) => (\n <button\n type=\"button\"\n key={s}\n aria-pressed={scopes.has(s)}\n onClick={() => toggleScope(s)}\n className={`${CHIP_BASE_CLASS} ${\n scopes.has(s)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {s}\n </button>\n ))}\n {(availableScopes ?? []).length === 0 && (\n <span className=\"text-xs text-text-muted\">\n {tr('connectionPermissionsDialog.noScopes', 'No scopes configured')}\n </span>\n )}\n </div>\n </div>\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={onClose} className=\"max-md:min-h-11\">\n {tr('connectionPermissionsDialog.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => onSave(Array.from(permissions), Array.from(scopes))}\n className=\"max-md:min-h-11\"\n >\n {tr('connectionPermissionsDialog.save', 'Save Permissions')}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ConnectionPermissionsDialog.js","names":[],"sources":["../../../src/components/graph/ConnectionPermissionsDialog.tsx"],"sourcesContent":["/**\n * ConnectionPermissionsDialog — Edit RBAC permissions and scopes for an agent connection edge.\n */\n\nimport { useId, useState } from 'react';\nimport {\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface Connection {\n id: string;\n protocol: string;\n permissions?: string[];\n scopes?: string[];\n label?: string | null;\n description?: string | null;\n sourceAgent: { id: string; name: string };\n target: { id: string; name: string };\n}\n\ninterface Props {\n connection: Connection;\n availablePermissions?: string[];\n availableScopes?: string[];\n onSave: (permissions: string[], scopes: string[]) => void;\n onClose: () => void;\n}\n\nconst CHIP_BASE_CLASS =\n 'rounded-full border px-3 py-1.5 text-xs font-medium transition-colors max-md:min-h-11 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2';\n\nexport function ConnectionPermissionsDialog({\n connection,\n availablePermissions,\n availableScopes,\n onSave,\n onClose,\n}: Props) {\n const tr = useTr();\n const permissionsLabelId = useId();\n const scopesLabelId = useId();\n const [permissions, setPermissions] = useState<Set<string>>(\n new Set(connection.permissions || [])\n );\n const [scopes, setScopes] = useState<Set<string>>(new Set(connection.scopes || []));\n\n const togglePermission = (p: string) => {\n setPermissions((prev) => {\n const next = new Set(prev);\n if (next.has(p)) next.delete(p);\n else next.add(p);\n return next;\n });\n };\n\n const toggleScope = (s: string) => {\n setScopes((prev) => {\n const next = new Set(prev);\n if (next.has(s)) next.delete(s);\n else next.add(s);\n return next;\n });\n };\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(open) => {\n if (!open) onClose();\n }}\n >\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('connectionPermissionsDialog.title', 'Connection Permissions')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n <span className=\"font-medium text-text-primary\">{connection.sourceAgent.name}</span>\n {' → '}\n <span className=\"font-medium text-text-primary\">{connection.target.name}</span>\n <span className=\"ml-2 rounded bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {connection.protocol}\n </span>\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4\">\n {/* Permissions */}\n <div role=\"group\" aria-labelledby={permissionsLabelId}>\n <span\n id={permissionsLabelId}\n className=\"mb-2 block text-sm font-medium text-text-primary\"\n >\n {tr('connectionPermissionsDialog.permissions', 'Permissions')}\n </span>\n <div className=\"flex flex-wrap gap-2\">\n {(availablePermissions ?? []).map((p) => (\n <button\n type=\"button\"\n key={p}\n aria-pressed={permissions.has(p)}\n onClick={() => togglePermission(p)}\n className={`${CHIP_BASE_CLASS} ${\n permissions.has(p)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {p}\n </button>\n ))}\n {(availablePermissions ?? []).length === 0 && (\n <span className=\"text-xs text-text-muted\">\n {tr('connectionPermissionsDialog.noPermissions', 'No permissions configured')}\n </span>\n )}\n </div>\n </div>\n\n {/* Scopes */}\n <div role=\"group\" aria-labelledby={scopesLabelId}>\n <span id={scopesLabelId} className=\"mb-2 block text-sm font-medium text-text-primary\">\n {tr('connectionPermissionsDialog.scopes', 'Scopes')}\n </span>\n <div className=\"flex flex-wrap gap-2\">\n {(availableScopes ?? []).map((s) => (\n <button\n type=\"button\"\n key={s}\n aria-pressed={scopes.has(s)}\n onClick={() => toggleScope(s)}\n className={`${CHIP_BASE_CLASS} ${\n scopes.has(s)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {s}\n </button>\n ))}\n {(availableScopes ?? []).length === 0 && (\n <span className=\"text-xs text-text-muted\">\n {tr('connectionPermissionsDialog.noScopes', 'No scopes configured')}\n </span>\n )}\n </div>\n </div>\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={onClose} className=\"max-md:min-h-11\">\n {tr('connectionPermissionsDialog.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => onSave(Array.from(permissions), Array.from(scopes))}\n className=\"max-md:min-h-11\"\n >\n {tr('connectionPermissionsDialog.save', 'Save Permissions')}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
@@ -251,7 +251,7 @@ function y({ sourceAgents: y, initialSourceAgentId: ve, availablePermissions: b,
251
251
  type: "button",
252
252
  "aria-pressed": T === "ssh",
253
253
  onClick: () => E("ssh"),
254
- className: `${v} ${T === "ssh" ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
254
+ className: `${v} ${T === "ssh" ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
255
255
  children: [/* @__PURE__ */ s(ae, {
256
256
  className: "size-3.5",
257
257
  "aria-hidden": "true"
@@ -260,7 +260,7 @@ function y({ sourceAgents: y, initialSourceAgentId: ve, availablePermissions: b,
260
260
  type: "button",
261
261
  "aria-pressed": T === "same",
262
262
  onClick: () => E("same"),
263
- className: `${v} ${T === "same" ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
263
+ className: `${v} ${T === "same" ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
264
264
  children: [/* @__PURE__ */ s(ie, {
265
265
  className: "size-3.5",
266
266
  "aria-hidden": "true"
@@ -368,7 +368,7 @@ function y({ sourceAgents: y, initialSourceAgentId: ve, availablePermissions: b,
368
368
  type: "button",
369
369
  "aria-pressed": F.has(e),
370
370
  onClick: () => De(e),
371
- className: `${_e} ${F.has(e) ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
371
+ className: `${_e} ${F.has(e) ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
372
372
  children: e
373
373
  }, e))
374
374
  })] }),