@burdenoff/microfe-vibecontrols 2026.623.1 → 2026.623.3

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 (295) hide show
  1. package/dist/components/actions/ActionCard.js +2 -2
  2. package/dist/components/actions/ActionCard.js.map +1 -1
  3. package/dist/components/actions/ActionTypeBadge.js +1 -1
  4. package/dist/components/actions/ActionTypeBadge.js.map +1 -1
  5. package/dist/components/actions/ExecutionHistory.js +1 -1
  6. package/dist/components/actions/ExecutionHistory.js.map +1 -1
  7. package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
  8. package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
  9. package/dist/components/agent-manager/AgentManagerChatPane.js +2 -2
  10. package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
  11. package/dist/components/agent-manager/AgentManagerInputArea.js +4 -4
  12. package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
  13. package/dist/components/agent-manager/AgentManagerLayout.js +1 -1
  14. package/dist/components/agent-manager/AgentManagerLayout.js.map +1 -1
  15. package/dist/components/agent-manager/AgentManagerMessageItem.js +1 -1
  16. package/dist/components/agent-manager/AgentManagerMessageItem.js.map +1 -1
  17. package/dist/components/agent-manager/AgentManagerSidebar.js +5 -5
  18. package/dist/components/agent-manager/AgentManagerSidebar.js.map +1 -1
  19. package/dist/components/agent-manager/AgentManagerTab.js +1 -1
  20. package/dist/components/agent-manager/AgentManagerTab.js.map +1 -1
  21. package/dist/components/agent-manager/AgentManagerTabBar.js +1 -1
  22. package/dist/components/agent-manager/AgentManagerTabBar.js.map +1 -1
  23. package/dist/components/agent-manager/BridleConfigPanel.js +24 -24
  24. package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
  25. package/dist/components/agent-manager/PluginInstallModal.js +1 -1
  26. package/dist/components/agent-manager/PluginInstallModal.js.map +1 -1
  27. package/dist/components/agent-manager/ProfilesPanel.js +5 -5
  28. package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
  29. package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
  30. package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
  31. package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
  32. package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
  33. package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
  34. package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
  35. package/dist/components/agent-manager/detail/SessionConfigPanel.js +6 -6
  36. package/dist/components/agent-manager/detail/SessionConfigPanel.js.map +1 -1
  37. package/dist/components/agent-manager/detail/SessionLogsPanel.js +5 -5
  38. package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
  39. package/dist/components/agent-manager/detail/SessionToolsPanel.js +1 -1
  40. package/dist/components/agent-manager/detail/SessionToolsPanel.js.map +1 -1
  41. package/dist/components/agent-manager/dialogs/NewSessionDialog.js +2 -2
  42. package/dist/components/agent-manager/dialogs/NewSessionDialog.js.map +1 -1
  43. package/dist/components/agent-manager/dialogs/RenameSessionDialog.js +1 -1
  44. package/dist/components/agent-manager/dialogs/RenameSessionDialog.js.map +1 -1
  45. package/dist/components/agents/AgentCard.js +14 -11
  46. package/dist/components/agents/AgentCard.js.map +1 -1
  47. package/dist/components/ai/AIContextsTab.js +1 -1
  48. package/dist/components/ai/AIContextsTab.js.map +1 -1
  49. package/dist/components/ai/AIOverviewTab.js +4 -4
  50. package/dist/components/ai/AIOverviewTab.js.map +1 -1
  51. package/dist/components/ai/AIPlanPanel.js +4 -4
  52. package/dist/components/ai/AIPlanPanel.js.map +1 -1
  53. package/dist/components/ai/AIPlaygroundTab.js +1 -1
  54. package/dist/components/ai/AIPlaygroundTab.js.map +1 -1
  55. package/dist/components/ai/AIPromptsTab.js +2 -2
  56. package/dist/components/ai/AIPromptsTab.js.map +1 -1
  57. package/dist/components/ai/AIProviderCredentialsCard.js +4 -4
  58. package/dist/components/ai/AIProviderCredentialsCard.js.map +1 -1
  59. package/dist/components/ai/AIQueueTab.js +4 -4
  60. package/dist/components/ai/AIQueueTab.js.map +1 -1
  61. package/dist/components/ai/AISessionsTab.js +4 -4
  62. package/dist/components/ai/AISessionsTab.js.map +1 -1
  63. package/dist/components/ai/AITasksTab.js +1 -1
  64. package/dist/components/ai/AITasksTab.js.map +1 -1
  65. package/dist/components/ai/ClaudeCredentialsCard.js +3 -3
  66. package/dist/components/ai/ClaudeCredentialsCard.js.map +1 -1
  67. package/dist/components/ai/PluginHarnessPicker.js +3 -3
  68. package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
  69. package/dist/components/analytics/AnalyticsBreakdownChart.js +8 -8
  70. package/dist/components/analytics/AnalyticsBreakdownChart.js.map +1 -1
  71. package/dist/components/analytics/AnalyticsTimeSeriesChart.js +5 -5
  72. package/dist/components/analytics/AnalyticsTimeSeriesChart.js.map +1 -1
  73. package/dist/components/audit/AuditDeleteDialog.js +2 -2
  74. package/dist/components/audit/AuditDeleteDialog.js.map +1 -1
  75. package/dist/components/audit/AuditExportButton.js +5 -5
  76. package/dist/components/audit/AuditExportButton.js.map +1 -1
  77. package/dist/components/audit/AuditLogDetail.js +9 -9
  78. package/dist/components/audit/AuditLogDetail.js.map +1 -1
  79. package/dist/components/audit/AuditLogFilters.js +1 -1
  80. package/dist/components/audit/AuditLogFilters.js.map +1 -1
  81. package/dist/components/audit/AuditLogStats.js +1 -1
  82. package/dist/components/audit/AuditLogStats.js.map +1 -1
  83. package/dist/components/audit/AuditLogTable.js +28 -28
  84. package/dist/components/audit/AuditLogTable.js.map +1 -1
  85. package/dist/components/catalog/CatalogCard.js +3 -3
  86. package/dist/components/catalog/CatalogCard.js.map +1 -1
  87. package/dist/components/catalog/CatalogForm.js +1 -1
  88. package/dist/components/catalog/CatalogForm.js.map +1 -1
  89. package/dist/components/common/ActionAgentTargetNotice.js +6 -6
  90. package/dist/components/common/ActionAgentTargetNotice.js.map +1 -1
  91. package/dist/components/common/CommandEditor.js +1 -1
  92. package/dist/components/common/CommandEditor.js.map +1 -1
  93. package/dist/components/docs/DocsPageTree.js +1 -1
  94. package/dist/components/docs/DocsPageTree.js.map +1 -1
  95. package/dist/components/docs/DocsPreviewPanel.js +1 -1
  96. package/dist/components/docs/DocsPreviewPanel.js.map +1 -1
  97. package/dist/components/docs/DocsRevisionHistory.js +4 -4
  98. package/dist/components/docs/DocsRevisionHistory.js.map +1 -1
  99. package/dist/components/docs/DocsSettingsPanel.js +7 -7
  100. package/dist/components/docs/DocsSettingsPanel.js.map +1 -1
  101. package/dist/components/docs/DocsTabPanel.js +1 -1
  102. package/dist/components/docs/DocsTabPanel.js.map +1 -1
  103. package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
  104. package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
  105. package/dist/components/graph/MeshDeployWizard.js +3 -3
  106. package/dist/components/graph/MeshDeployWizard.js.map +1 -1
  107. package/dist/components/graph/MeshRequestPanel.js +2 -2
  108. package/dist/components/graph/MeshRequestPanel.js.map +1 -1
  109. package/dist/components/idp-components/ComponentCard.js +5 -5
  110. package/dist/components/idp-components/ComponentCard.js.map +1 -1
  111. package/dist/components/idp-components/ComponentForm.js +1 -1
  112. package/dist/components/idp-components/ComponentForm.js.map +1 -1
  113. package/dist/components/idp-components/DependencyGraphTab.js +7 -7
  114. package/dist/components/idp-components/DependencyGraphTab.js.map +1 -1
  115. package/dist/components/logs/LogViewer.js +14 -14
  116. package/dist/components/logs/LogViewer.js.map +1 -1
  117. package/dist/components/notes/NoteCard.js +1 -1
  118. package/dist/components/notes/NoteCard.js.map +1 -1
  119. package/dist/components/notes/NoteEditor.js +2 -2
  120. package/dist/components/notes/NoteEditor.js.map +1 -1
  121. package/dist/components/notes/NoteList.js +1 -1
  122. package/dist/components/notes/NoteList.js.map +1 -1
  123. package/dist/components/notes/NotesTabPanel.js +1 -1
  124. package/dist/components/notes/NotesTabPanel.js.map +1 -1
  125. package/dist/components/scorecard/ScorecardEmptyState.js +1 -1
  126. package/dist/components/scorecard/ScorecardEmptyState.js.map +1 -1
  127. package/dist/components/scorecard/ScorecardPanel.js +3 -3
  128. package/dist/components/scorecard/ScorecardPanel.js.map +1 -1
  129. package/dist/components/security/SecurityScanList.js +11 -11
  130. package/dist/components/security/SecurityScanList.js.map +1 -1
  131. package/dist/components/sessions/SessionCard.js +26 -19
  132. package/dist/components/sessions/SessionCard.js.map +1 -1
  133. package/dist/components/sessions/SessionStatusBadge.js +7 -7
  134. package/dist/components/sessions/SessionStatusBadge.js.map +1 -1
  135. package/dist/components/shared/AgentSelector.js +4 -4
  136. package/dist/components/shared/AgentSelector.js.map +1 -1
  137. package/dist/components/shared/AgentSetupBanner.js +4 -4
  138. package/dist/components/shared/AgentSetupBanner.js.map +1 -1
  139. package/dist/components/shared/Card.js +4 -4
  140. package/dist/components/shared/Card.js.map +1 -1
  141. package/dist/components/shared/ErrorState.js +15 -9
  142. package/dist/components/shared/ErrorState.js.map +1 -1
  143. package/dist/components/shared/FilterDropdown.js +2 -2
  144. package/dist/components/shared/FilterDropdown.js.map +1 -1
  145. package/dist/components/shared/PageSkeleton.js +6 -6
  146. package/dist/components/shared/PageSkeleton.js.map +1 -1
  147. package/dist/components/shared/SearchInput.js +1 -1
  148. package/dist/components/shared/SearchInput.js.map +1 -1
  149. package/dist/components/shared/StatCard.js +7 -7
  150. package/dist/components/shared/StatCard.js.map +1 -1
  151. package/dist/components/templates/PublishTemplateForm.js +2 -2
  152. package/dist/components/templates/PublishTemplateForm.js.map +1 -1
  153. package/dist/components/templates/TemplateCard.js +5 -5
  154. package/dist/components/templates/TemplateCard.js.map +1 -1
  155. package/dist/components/templates/TemplateWizard.js +2 -2
  156. package/dist/components/templates/TemplateWizard.js.map +1 -1
  157. package/dist/components/tunnels/DomainVerificationBadge.js +4 -4
  158. package/dist/components/tunnels/DomainVerificationBadge.js.map +1 -1
  159. package/dist/components/tunnels/ProviderBadge.js +1 -1
  160. package/dist/components/tunnels/ProviderBadge.js.map +1 -1
  161. package/dist/components/tunnels/TunnelCard.js +16 -16
  162. package/dist/components/tunnels/TunnelCard.js.map +1 -1
  163. package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
  164. package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
  165. package/dist/components/tunnels/TunnelPublicAccessPanel.js +1 -1
  166. package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
  167. package/dist/components/tunnels/TunnelStatusBadge.js +4 -4
  168. package/dist/components/tunnels/TunnelStatusBadge.js.map +1 -1
  169. package/dist/components/vibecalendar/LogsDrawer.js +2 -2
  170. package/dist/components/vibecalendar/LogsDrawer.js.map +1 -1
  171. package/dist/components/vibecalendar/ScheduleTaskModal.js +5 -5
  172. package/dist/components/vibecalendar/ScheduleTaskModal.js.map +1 -1
  173. package/dist/components/vibecalendar/VibeCalAgendaView.js +2 -2
  174. package/dist/components/vibecalendar/VibeCalAgendaView.js.map +1 -1
  175. package/dist/components/vibecalendar/VibeCalDayView.js +3 -3
  176. package/dist/components/vibecalendar/VibeCalDayView.js.map +1 -1
  177. package/dist/components/vibecalendar/VibeCalMonthView.js +1 -1
  178. package/dist/components/vibecalendar/VibeCalMonthView.js.map +1 -1
  179. package/dist/components/vibecalendar/VibeCalWeekView.js +4 -4
  180. package/dist/components/vibecalendar/VibeCalWeekView.js.map +1 -1
  181. package/dist/components/vibecalendar/VibeCalendarHeader.js +4 -4
  182. package/dist/components/vibecalendar/VibeCalendarHeader.js.map +1 -1
  183. package/dist/components/vibecalendar/VibeTaskBlock.js +2 -2
  184. package/dist/components/vibecalendar/VibeTaskBlock.js.map +1 -1
  185. package/dist/components/vibecalendar/types.js +7 -7
  186. package/dist/components/vibecalendar/types.js.map +1 -1
  187. package/dist/components/vibedeck/VibeDeckButtonForm.js +9 -9
  188. package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
  189. package/dist/components/vibedeck/VibeDeckButtonTile.js +6 -6
  190. package/dist/components/vibedeck/VibeDeckButtonTile.js.map +1 -1
  191. package/dist/components/vibedeck/VibeDeckExecutionLog.js +10 -10
  192. package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
  193. package/dist/components/vibedeck/VibeDeckForm.js +8 -8
  194. package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
  195. package/dist/components/vibedeck/VibeDeckGrid.js +2 -2
  196. package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
  197. package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js +2 -2
  198. package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js.map +1 -1
  199. package/dist/components/vibedeck/VibeDeckSwitcher.js +8 -8
  200. package/dist/components/vibedeck/VibeDeckSwitcher.js.map +1 -1
  201. package/dist/components/vibes/VibeCard.js +22 -18
  202. package/dist/components/vibes/VibeCard.js.map +1 -1
  203. package/dist/components/vibes/VibeList.js +16 -12
  204. package/dist/components/vibes/VibeList.js.map +1 -1
  205. package/dist/pages/ActionsPage.js +4 -3
  206. package/dist/pages/ActionsPage.js.map +1 -1
  207. package/dist/pages/AgentDetailsPage.js +9 -9
  208. package/dist/pages/AgentDetailsPage.js.map +1 -1
  209. package/dist/pages/AgentsLandingPage.js +4 -4
  210. package/dist/pages/AgentsLandingPage.js.map +1 -1
  211. package/dist/pages/AgentsPage.js +14 -14
  212. package/dist/pages/AgentsPage.js.map +1 -1
  213. package/dist/pages/AnalyticsPage.js +8 -8
  214. package/dist/pages/AnalyticsPage.js.map +1 -1
  215. package/dist/pages/CatalogDetailPage.js +11 -11
  216. package/dist/pages/CatalogDetailPage.js.map +1 -1
  217. package/dist/pages/CatalogPage.js +12 -9
  218. package/dist/pages/CatalogPage.js.map +1 -1
  219. package/dist/pages/ComponentDetailPage.js +7 -7
  220. package/dist/pages/ComponentDetailPage.js.map +1 -1
  221. package/dist/pages/ComponentsPage.js +12 -9
  222. package/dist/pages/ComponentsPage.js.map +1 -1
  223. package/dist/pages/EnvTopologyPage.js +2 -2
  224. package/dist/pages/EnvTopologyPage.js.map +1 -1
  225. package/dist/pages/GettingStartedPage.js +12 -12
  226. package/dist/pages/GettingStartedPage.js.map +1 -1
  227. package/dist/pages/HealthPage.js +12 -12
  228. package/dist/pages/HealthPage.js.map +1 -1
  229. package/dist/pages/LogsPage.js +4 -4
  230. package/dist/pages/LogsPage.js.map +1 -1
  231. package/dist/pages/NoteDetailsPage.js +5 -5
  232. package/dist/pages/NoteDetailsPage.js.map +1 -1
  233. package/dist/pages/OverviewPage.js +76 -43
  234. package/dist/pages/OverviewPage.js.map +1 -1
  235. package/dist/pages/PluginsCatalogPage.js +7 -4
  236. package/dist/pages/PluginsCatalogPage.js.map +1 -1
  237. package/dist/pages/ScheduledJobDetailPage.js +7 -7
  238. package/dist/pages/ScheduledJobDetailPage.js.map +1 -1
  239. package/dist/pages/SessionDetailsPage.js +21 -21
  240. package/dist/pages/SessionDetailsPage.js.map +1 -1
  241. package/dist/pages/SessionsPage.js +20 -20
  242. package/dist/pages/SessionsPage.js.map +1 -1
  243. package/dist/pages/SettingsPage.js +64 -52
  244. package/dist/pages/SettingsPage.js.map +1 -1
  245. package/dist/pages/SharedSessionPage.js +21 -21
  246. package/dist/pages/SharedSessionPage.js.map +1 -1
  247. package/dist/pages/SharedVibeDeckPage.js +5 -5
  248. package/dist/pages/SharedVibeDeckPage.js.map +1 -1
  249. package/dist/pages/TargetDetailsPage.js +56 -65
  250. package/dist/pages/TargetDetailsPage.js.map +1 -1
  251. package/dist/pages/TargetsPage.js +4 -4
  252. package/dist/pages/TargetsPage.js.map +1 -1
  253. package/dist/pages/TemplateDetailPage.js +10 -10
  254. package/dist/pages/TemplateDetailPage.js.map +1 -1
  255. package/dist/pages/TemplatesPage.js +6 -5
  256. package/dist/pages/TemplatesPage.js.map +1 -1
  257. package/dist/pages/VibeAuditPage.js +3 -3
  258. package/dist/pages/VibeAuditPage.js.map +1 -1
  259. package/dist/pages/VibeCalendarPage.js +3 -3
  260. package/dist/pages/VibeCalendarPage.js.map +1 -1
  261. package/dist/pages/VibeDeckPage.js +3 -3
  262. package/dist/pages/VibeDeckPage.js.map +1 -1
  263. package/dist/pages/VibeDetailsPage.js +15 -15
  264. package/dist/pages/VibeDetailsPage.js.map +1 -1
  265. package/dist/pages/VibeTaskDetailPage.js +5 -5
  266. package/dist/pages/VibeTaskDetailPage.js.map +1 -1
  267. package/dist/pages/VibesPage.js +18 -18
  268. package/dist/pages/VibesPage.js.map +1 -1
  269. package/dist/pages/WebhooksPage.js +11 -11
  270. package/dist/pages/WebhooksPage.js.map +1 -1
  271. package/dist/pages/ai/AIBookmarksPage.js +1 -1
  272. package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
  273. package/dist/pages/ai/AISessionDetailPage.js +6 -6
  274. package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
  275. package/dist/pages/tunnels/AuditTab.js +1 -1
  276. package/dist/pages/tunnels/AuditTab.js.map +1 -1
  277. package/dist/pages/tunnels/DomainsTab.js +1 -1
  278. package/dist/pages/tunnels/DomainsTab.js.map +1 -1
  279. package/dist/pages/tunnels/ProvidersTab.js +1 -1
  280. package/dist/pages/tunnels/ProvidersTab.js.map +1 -1
  281. package/dist/pages/tunnels/SessionsTab.js +1 -1
  282. package/dist/pages/tunnels/SessionsTab.js.map +1 -1
  283. package/dist/pages/tunnels/TunnelDetailsPage.js +3 -3
  284. package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
  285. package/dist/pages/tunnels/UsageTab.js +1 -1
  286. package/dist/pages/tunnels/UsageTab.js.map +1 -1
  287. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +16 -16
  288. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
  289. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +1 -1
  290. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
  291. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +52 -49
  292. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
  293. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +9 -9
  294. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
  295. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"SharedSessionPage.js","names":[],"sources":["../../src/pages/SharedSessionPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useRef, useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Terminal,\n Loader2,\n AlertCircle,\n Lock,\n Eye,\n Gamepad2,\n Users,\n Clock,\n ArrowLeft,\n LogIn,\n} from 'lucide-react';\nimport {\n useValidateShareLinkQuery,\n useJoinSharedSessionMutation,\n useLeaveSharedSessionMutation,\n useRequestSessionControlMutation,\n useReleaseSessionControlMutation,\n useGetSessionCollaboratorsQuery,\n useCollaboratorJoinedSubscription,\n useCollaboratorLeftSubscription,\n useSessionControlChangedSubscription,\n type SessionCollaboratorFieldsFragment,\n} from '@/generated/wspace-operations';\nimport { SessionSharePermission } from '@/generated/wspace-types';\nimport { SharedTerminalPanel } from '@/components/sessions/SharedTerminalPanel';\nimport { CollaboratorsList } from '@/components/sessions/CollaboratorsList';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\n\ninterface PasswordFormProps {\n onSubmit: (password: string) => void;\n isLoading: boolean;\n error?: string | null;\n}\n\n/**\n * Password entry form for protected share links\n */\nfunction PasswordForm({ onSubmit, isLoading, error }: PasswordFormProps) {\n const [password, setPassword] = useState('');\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (password.trim()) {\n onSubmit(password);\n }\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-4 w-full max-w-sm\">\n <div>\n <label\n htmlFor=\"shared-session-password\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n This session is password protected\n </label>\n <div className=\"relative\">\n <Lock className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\" />\n <input\n aria-label=\"Enter password\"\n id=\"shared-session-password\"\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"Enter password\"\n className=\"w-full pl-10 pr-4 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n </div>\n </div>\n {error && (\n <div className=\"flex items-center gap-2 p-3 bg-status-error-bg/10 border border-status-error-bg/30 rounded-lg\">\n <AlertCircle className=\"size-4 text-status-error-text\" />\n <span className=\"text-sm text-status-error-text\">{error}</span>\n </div>\n )}\n <button\n type=\"submit\"\n disabled={isLoading || !password.trim()}\n className=\"w-full px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {isLoading ? (\n <span className=\"flex items-center justify-center gap-2\">\n <Loader2 className=\"size-4 animate-spin\" />\n Joining...\n </span>\n ) : (\n 'Join Session'\n )}\n </button>\n </form>\n );\n}\n\n/**\n * SharedSessionPage - Page for accessing shared terminal sessions via link\n *\n * URL: /share/:token\n *\n * Flow:\n * 1. Validate the share link token\n * 2. If password required, show password form\n * 3. Join the shared session\n * 4. Display terminal with collaboration UI\n */\nexport function SharedSessionPage() {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n const { token } = useParams<{ token: string }>();\n const { currentUser } = useVibeControls();\n // State\n const [joined, setJoined] = useState(false);\n const [sessionId, setSessionId] = useState<string | null>(null);\n const [password, setPassword] = useState('');\n const [passwordError, setPasswordError] = useState<string | null>(null);\n const [joinError, setJoinError] = useState<string | null>(null);\n const [hasLeft, setHasLeft] = useState(false);\n const joiningRef = useRef(false);\n const hasJoinedRef = useRef(false);\n\n // Get currentUserId from auth context\n const currentUserId = currentUser?.id ?? '';\n\n // Validate the share link\n const {\n data: validationData,\n loading: validating,\n error: validationError,\n refetch: refetchValidation,\n } = useValidateShareLinkQuery({\n variables: { token: token || '' },\n skip: !token,\n });\n\n const validation = validationData?.validateShareLink;\n\n // Mutations\n const [joinSession, { loading: joining }] = useJoinSharedSessionMutation();\n const [leaveSession] = useLeaveSharedSessionMutation();\n const [requestControl, { loading: requestingControl }] = useRequestSessionControlMutation();\n const [releaseControl, { loading: releasingControl }] = useReleaseSessionControlMutation();\n\n // Get collaborators once joined\n const { data: collaboratorsData, refetch: refetchCollaborators } =\n useGetSessionCollaboratorsQuery({\n variables: { sessionId: sessionId || '' },\n skip: !sessionId || !joined,\n // Subscription handles the primary signal (collaboratorJoined /\n // collaboratorLeft). Poll is a slow safety-net to recover if the\n // SSE stream drops silently — 60s is enough since presence flips\n // are pushed through the subscription.\n pollInterval: 60_000,\n });\n\n const collaborators = collaboratorsData?.sessionCollaborators || [];\n\n // Real-time subscriptions for collaboration events\n const { data: joinedData } = useCollaboratorJoinedSubscription({\n variables: { sessionId: sessionId || '' },\n skip: !sessionId || !joined,\n });\n\n const { data: leftData } = useCollaboratorLeftSubscription({\n variables: { sessionId: sessionId || '' },\n skip: !sessionId || !joined,\n });\n\n const { data: controlChangedData } = useSessionControlChangedSubscription({\n variables: { sessionId: sessionId || '' },\n skip: !sessionId || !joined,\n });\n\n // Refetch collaborators when subscription events arrive\n useEffect(() => {\n if (joinedData || leftData || controlChangedData) {\n refetchCollaborators();\n }\n }, [joinedData, leftData, controlChangedData, refetchCollaborators]);\n\n // Cleanup: Leave session only on unmount, and only if we actually joined\n useEffect(() => {\n return () => {\n if (hasJoinedRef.current && sessionId) {\n leaveSession({ variables: { sessionId } }).catch(console.error);\n }\n };\n }, [sessionId, leaveSession]);\n\n const handleJoinSession = useCallback(\n async (passwordInput?: string) => {\n if (!token || joiningRef.current) return;\n\n joiningRef.current = true;\n const pwd = passwordInput || password;\n setPasswordError(null);\n setJoinError(null);\n\n try {\n const result = await joinSession({\n variables: {\n input: {\n shareToken: token,\n password: pwd || undefined,\n },\n },\n });\n\n if (result.data?.joinSharedSession) {\n hasJoinedRef.current = true;\n setJoined(true);\n setSessionId(result.data.joinSharedSession.sessionId);\n void refetchValidation();\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to join session';\n if (message.toLowerCase().includes('password')) {\n setPasswordError('Incorrect password');\n } else {\n setPasswordError(message);\n setJoinError(message);\n }\n } finally {\n joiningRef.current = false;\n }\n },\n [joinSession, password, refetchValidation, token]\n );\n\n // Auto-join if no password required and link is valid\n useEffect(() => {\n if (\n validation?.isValid &&\n !validation.requiresPassword &&\n !joined &&\n !hasLeft &&\n !joiningRef.current &&\n token\n ) {\n // Set sessionId early from validation so collaborators query can start\n if (validation.sessionId && !sessionId) {\n setSessionId(validation.sessionId);\n }\n void handleJoinSession();\n }\n }, [\n validation?.isValid,\n validation?.requiresPassword,\n validation?.sessionId,\n joined,\n hasLeft,\n token,\n sessionId,\n handleJoinSession,\n ]);\n\n const handleRequestControl = useCallback(async () => {\n if (!sessionId) return;\n try {\n await requestControl({ variables: { sessionId } });\n refetchCollaborators();\n } catch (err) {\n console.error('Failed to request control:', err);\n }\n }, [sessionId, requestControl, refetchCollaborators]);\n\n const handleReleaseControl = useCallback(async () => {\n if (!sessionId) return;\n try {\n await releaseControl({ variables: { sessionId } });\n refetchCollaborators();\n } catch (err) {\n console.error('Failed to release control:', err);\n }\n }, [sessionId, releaseControl, refetchCollaborators]);\n\n const handleLeaveSession = useCallback(async () => {\n if (!sessionId) return;\n try {\n await leaveSession({ variables: { sessionId } });\n hasJoinedRef.current = false; // Prevent cleanup from firing a second leave\n setHasLeft(true);\n setJoined(false);\n setSessionId(null);\n window.history.back();\n } catch (err) {\n console.error('Failed to leave session:', err);\n }\n }, [sessionId, leaveSession]);\n\n // Determine current user's collaborator entry\n const currentCollaborator = collaborators.find(\n (c: SessionCollaboratorFieldsFragment) => c.userId === currentUserId\n );\n const permission = validation?.permission || SessionSharePermission.View;\n\n // Stabilize terminal URL to prevent iframe reloads on parent re-render\n const terminalUrl = useMemo(() => validation?.ttydUrl || null, [validation?.ttydUrl]);\n\n // Loading state\n if (validating) {\n return (\n <div className=\"min-h-screen bg-bg-surface flex items-center justify-center\">\n <div className=\"text-center\">\n <Loader2 className=\"size-8 text-action-primary-bg animate-spin mx-auto mb-4\" />\n <p className=\"text-text-secondary\">\n {tr('sharedSession.validating', 'Validating share link...')}\n </p>\n </div>\n </div>\n );\n }\n\n // Error state\n if (validationError || !validation) {\n return (\n <div className=\"min-h-screen bg-bg-surface flex items-center justify-center p-4\">\n <div className=\"text-center max-w-md\">\n <div className=\"size-16 bg-status-error-bg/10 rounded-full flex items-center justify-center mx-auto mb-4\">\n <AlertCircle className=\"size-8 text-status-error-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr('sharedSession.unableToLoad', 'Unable to Load Session')}\n </h2>\n <p className=\"text-text-secondary mb-6\">\n {validationError?.message ||\n tr('sharedSession.failedValidate', 'Failed to validate the share link')}\n </p>\n <button\n type=\"button\"\n onClick={() => window.history.back()}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-text-primary bg-bg-sunken border border-border-default rounded-md hover:bg-bg-elevated transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('sharedSession.goBack', 'Go Back')}\n </button>\n </div>\n </div>\n );\n }\n\n // Invalid link\n if (!validation.isValid) {\n return (\n <div className=\"min-h-screen bg-bg-surface flex items-center justify-center p-4\">\n <div className=\"text-center max-w-md\">\n <div className=\"size-16 bg-status-warning-bg/10 rounded-full flex items-center justify-center mx-auto mb-4\">\n <AlertCircle className=\"size-8 text-status-warning-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr('sharedSession.invalidExpiredLink', 'Invalid or Expired Link')}\n </h2>\n <p className=\"text-text-secondary mb-6\">\n {validation.error ||\n tr('sharedSession.linkNoLongerValid', 'This share link is no longer valid.')}\n </p>\n <button\n type=\"button\"\n onClick={() => window.history.back()}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-text-primary bg-bg-sunken border border-border-default rounded-md hover:bg-bg-elevated transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('sharedSession.goBack', 'Go Back')}\n </button>\n </div>\n </div>\n );\n }\n\n // Password required - show password form\n if (validation.requiresPassword && !joined) {\n return (\n <div className=\"min-h-screen bg-bg-surface flex items-center justify-center p-4\">\n <div className=\"w-full max-w-md\">\n <div className=\"bg-bg-surface border border-border-default rounded-lg p-6\">\n {/* Header */}\n <div className=\"text-center mb-6\">\n <div className=\"size-12 bg-action-primary-bg/10 rounded-full flex items-center justify-center mx-auto mb-4\">\n <Terminal className=\"size-6 text-action-primary-bg\" />\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {validation.sessionName || 'Shared Session'}\n </h2>\n {validation.ownerName && (\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('sharedSession.sharedBy', 'Shared by')} {validation.ownerName}\n </p>\n )}\n </div>\n\n {/* Permission badge */}\n <div className=\"flex justify-center mb-6\">\n <div\n className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm ${\n validation.permission === SessionSharePermission.View\n ? 'bg-bg-sunken text-text-secondary'\n : 'bg-action-primary-bg/10 text-action-primary-bg'\n }`}\n >\n {validation.permission === SessionSharePermission.View ? (\n <>\n <Eye className=\"size-4\" />\n {tr('sharedSession.viewOnly', 'View Only')}\n </>\n ) : (\n <>\n <Gamepad2 className=\"size-4\" />\n {tr('sharedSession.canControl', 'Can Control')}\n </>\n )}\n </div>\n </div>\n\n {/* Expiry info */}\n {validation.expiresAt && (\n <div className=\"flex items-center justify-center gap-2 mb-6 text-xs text-text-secondary\">\n <Clock className=\"size-3.5\" />\n <span>\n {tr('sharedSession.expires', 'Expires')}{' '}\n {new Date(validation.expiresAt).toLocaleString()}\n </span>\n </div>\n )}\n\n {/* Password form */}\n <PasswordForm\n onSubmit={(pwd) => {\n setPassword(pwd);\n handleJoinSession(pwd);\n }}\n isLoading={joining}\n error={passwordError}\n />\n </div>\n </div>\n </div>\n );\n }\n\n // Post-leave state — user explicitly left, don't show join spinner\n if (hasLeft) {\n return (\n <div className=\"min-h-screen bg-bg-surface flex items-center justify-center p-4\">\n <div className=\"text-center max-w-md\">\n <div className=\"size-16 bg-bg-sunken rounded-full flex items-center justify-center mx-auto mb-4\">\n <LogIn className=\"size-8 text-text-secondary\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr('sharedSession.youLeft', 'You left the session')}\n </h2>\n <p className=\"text-text-secondary mb-6\">\n {tr('sharedSession.rejoinHint', 'You can rejoin using the same share link.')}\n </p>\n <div className=\"flex items-center justify-center gap-3\">\n <button\n type=\"button\"\n onClick={() => window.history.back()}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-text-primary bg-bg-sunken border border-border-default rounded-md hover:bg-bg-elevated transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('sharedSession.goBack', 'Go Back')}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setHasLeft(false);\n setJoinError(null);\n handleJoinSession();\n }}\n disabled={joining}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n <LogIn className=\"size-4\" />\n {tr('sharedSession.rejoin', 'Rejoin')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n // Joining state - show error if join failed, otherwise spinner\n if (!joined && !validation.requiresPassword) {\n if (joinError) {\n return (\n <div className=\"min-h-screen bg-bg-surface flex items-center justify-center p-4\">\n <div className=\"text-center max-w-md\">\n <div className=\"size-16 bg-status-error-bg/10 rounded-full flex items-center justify-center mx-auto mb-4\">\n <AlertCircle className=\"size-8 text-status-error-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr('sharedSession.failedJoin', 'Failed to Join Session')}\n </h2>\n <p className=\"text-text-secondary mb-6\">{joinError}</p>\n <div className=\"flex items-center justify-center gap-3\">\n <button\n type=\"button\"\n onClick={() => window.history.back()}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-text-primary bg-bg-sunken border border-border-default rounded-md hover:bg-bg-elevated transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('sharedSession.goBack', 'Go Back')}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setJoinError(null);\n handleJoinSession();\n }}\n disabled={joining}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n <LogIn className=\"size-4\" />\n {tr('sharedSession.retry', 'Retry')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"min-h-screen bg-bg-surface flex items-center justify-center\">\n <div className=\"text-center\">\n <Loader2 className=\"size-8 text-action-primary-bg animate-spin mx-auto mb-4\" />\n <p className=\"text-text-secondary\">{tr('sharedSession.joining', 'Joining session...')}</p>\n </div>\n </div>\n );\n }\n\n // Main shared session view\n return (\n <MotionPage className=\"min-h-screen bg-bg-surface flex flex-col\">\n {/* Header */}\n <header className=\"bg-bg-surface border-b border-border-default px-4 py-3\">\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 max-w-screen-2xl mx-auto\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={handleLeaveSession}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors\"\n title=\"Leave session\"\n aria-label=\"Leave session\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 bg-action-primary-bg/10 rounded-lg\">\n <Terminal className=\"size-5 text-action-primary-bg\" />\n </div>\n <div>\n <h1 className=\"font-semibold text-text-primary\">\n {validation.sessionName || 'Shared Session'}\n </h1>\n {validation.ownerName && (\n <p className=\"text-xs text-text-secondary\">\n {tr('sharedSession.sharedBy', 'Shared by')} {validation.ownerName}\n </p>\n )}\n </div>\n </div>\n </div>\n\n {/* Permission and collaborator count */}\n <div className=\"flex items-center gap-3\">\n <div\n className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${\n permission === SessionSharePermission.View\n ? 'bg-bg-sunken text-text-secondary'\n : 'bg-action-primary-bg/10 text-action-primary-bg'\n }`}\n >\n {permission === SessionSharePermission.View ? (\n <>\n <Eye className=\"size-3.5\" />\n {tr('sharedSession.viewOnly', 'View Only')}\n </>\n ) : (\n <>\n <Gamepad2 className=\"size-3.5\" />\n {tr('sharedSession.interactive', 'Interactive')}\n </>\n )}\n </div>\n <div className=\"flex items-center gap-1.5 px-2.5 py-1 bg-bg-sunken rounded-full text-xs text-text-secondary\">\n <Users className=\"size-3.5\" />\n <span>{collaborators.length}</span>\n </div>\n </div>\n </div>\n </header>\n\n {/* Main content */}\n <div className=\"flex-1 flex\">\n {/* Terminal area */}\n <div className=\"flex-1 flex flex-col\">\n <SharedTerminalPanel\n isShared={true}\n collaborators={collaborators}\n currentUserId={currentUserId}\n permission={permission}\n onRequestControl={handleRequestControl}\n onReleaseControl={handleReleaseControl}\n isRequestingControl={requestingControl}\n isReleasingControl={releasingControl}\n className=\"flex-1\"\n >\n {/* Terminal iframe - in production this would be the ttyd URL */}\n <div className=\"w-full h-full bg-bg-inverse text-text-inverse flex items-center justify-center\">\n {terminalUrl ? (\n <iframe\n src={terminalUrl}\n title=\"Shared Terminal\"\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n sandbox=\"allow-scripts allow-same-origin allow-forms\"\n />\n ) : (\n <div className=\"text-center\">\n <Terminal className=\"size-12 text-text-secondary mx-auto mb-4\" />\n <p className=\"text-text-tertiary\">\n {tr('sharedSession.terminalLoading', 'Terminal loading...')}\n </p>\n <p className=\"text-text-secondary text-sm mt-1\">Session ID: {sessionId}</p>\n </div>\n )}\n </div>\n </SharedTerminalPanel>\n </div>\n\n {/* Sidebar - Collaborators */}\n <aside className=\"w-72 bg-bg-surface border-l border-border-default p-4 hidden lg:block\">\n <CollaboratorsList\n collaborators={collaborators}\n currentUserId={currentUserId}\n onRequestControl={\n permission === SessionSharePermission.Interact ? handleRequestControl : undefined\n }\n isRequestingControl={requestingControl}\n />\n\n {/* Session info */}\n <div className=\"mt-6 pt-6 border-t border-border-default\">\n <h4 className=\"text-xs font-medium text-text-secondary uppercase tracking-wide mb-3\">\n {tr('sharedSession.sessionInfo', 'Session Info')}\n </h4>\n <div className=\"space-y-2 text-sm\">\n {validation.expiresAt && (\n <div className=\"flex items-center justify-between\">\n <span className=\"text-text-secondary\">\n {tr('sharedSession.expires', 'Expires')}\n </span>\n <span className=\"text-text-primary\">\n {new Date(validation.expiresAt).toLocaleTimeString()}\n </span>\n </div>\n )}\n <div className=\"flex items-center justify-between\">\n <span className=\"text-text-secondary\">\n {tr('sharedSession.yourRole', 'Your role')}\n </span>\n <span className=\"text-text-primary capitalize\">\n {currentCollaborator?.role?.toLowerCase() || tr('sharedSession.viewer', 'Viewer')}\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-text-secondary\">{tr('sharedSession.status', 'Status')}</span>\n <span className=\"text-status-success-text\">\n {tr('sharedSession.connected', 'Connected')}\n </span>\n </div>\n </div>\n </div>\n </aside>\n </div>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SharedSessionPage.js","names":[],"sources":["../../src/pages/SharedSessionPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useRef, useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Terminal,\n Loader2,\n AlertCircle,\n Lock,\n Eye,\n Gamepad2,\n Users,\n Clock,\n ArrowLeft,\n LogIn,\n} from 'lucide-react';\nimport {\n useValidateShareLinkQuery,\n useJoinSharedSessionMutation,\n useLeaveSharedSessionMutation,\n useRequestSessionControlMutation,\n useReleaseSessionControlMutation,\n useGetSessionCollaboratorsQuery,\n useCollaboratorJoinedSubscription,\n useCollaboratorLeftSubscription,\n useSessionControlChangedSubscription,\n type SessionCollaboratorFieldsFragment,\n} from '@/generated/wspace-operations';\nimport { SessionSharePermission } from '@/generated/wspace-types';\nimport { SharedTerminalPanel } from '@/components/sessions/SharedTerminalPanel';\nimport { CollaboratorsList } from '@/components/sessions/CollaboratorsList';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\n\ninterface PasswordFormProps {\n onSubmit: (password: string) => void;\n isLoading: boolean;\n error?: string | null;\n}\n\n/**\n * Password entry form for protected share links\n */\nfunction PasswordForm({ onSubmit, isLoading, error }: PasswordFormProps) {\n const [password, setPassword] = useState('');\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (password.trim()) {\n onSubmit(password);\n }\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-4 w-full max-w-sm\">\n <div>\n <label\n htmlFor=\"shared-session-password\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n This session is password protected\n </label>\n <div className=\"relative\">\n <Lock className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\" />\n <input\n aria-label=\"Enter password\"\n id=\"shared-session-password\"\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"Enter password\"\n className=\"w-full pl-10 pr-4 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n </div>\n </div>\n {error && (\n <div className=\"flex items-center gap-2 p-3 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <AlertCircle className=\"size-4 text-status-error-text\" />\n <span className=\"text-sm text-status-error-text\">{error}</span>\n </div>\n )}\n <button\n type=\"submit\"\n disabled={isLoading || !password.trim()}\n className=\"w-full px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {isLoading ? (\n <span className=\"flex items-center justify-center gap-2\">\n <Loader2 className=\"size-4 animate-spin\" />\n Joining...\n </span>\n ) : (\n 'Join Session'\n )}\n </button>\n </form>\n );\n}\n\n/**\n * SharedSessionPage - Page for accessing shared terminal sessions via link\n *\n * URL: /share/:token\n *\n * Flow:\n * 1. Validate the share link token\n * 2. If password required, show password form\n * 3. Join the shared session\n * 4. Display terminal with collaboration UI\n */\nexport function SharedSessionPage() {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n const { token } = useParams<{ token: string }>();\n const { currentUser } = useVibeControls();\n // State\n const [joined, setJoined] = useState(false);\n const [sessionId, setSessionId] = useState<string | null>(null);\n const [password, setPassword] = useState('');\n const [passwordError, setPasswordError] = useState<string | null>(null);\n const [joinError, setJoinError] = useState<string | null>(null);\n const [hasLeft, setHasLeft] = useState(false);\n const joiningRef = useRef(false);\n const hasJoinedRef = useRef(false);\n\n // Get currentUserId from auth context\n const currentUserId = currentUser?.id ?? '';\n\n // Validate the share link\n const {\n data: validationData,\n loading: validating,\n error: validationError,\n refetch: refetchValidation,\n } = useValidateShareLinkQuery({\n variables: { token: token || '' },\n skip: !token,\n });\n\n const validation = validationData?.validateShareLink;\n\n // Mutations\n const [joinSession, { loading: joining }] = useJoinSharedSessionMutation();\n const [leaveSession] = useLeaveSharedSessionMutation();\n const [requestControl, { loading: requestingControl }] = useRequestSessionControlMutation();\n const [releaseControl, { loading: releasingControl }] = useReleaseSessionControlMutation();\n\n // Get collaborators once joined\n const { data: collaboratorsData, refetch: refetchCollaborators } =\n useGetSessionCollaboratorsQuery({\n variables: { sessionId: sessionId || '' },\n skip: !sessionId || !joined,\n // Subscription handles the primary signal (collaboratorJoined /\n // collaboratorLeft). Poll is a slow safety-net to recover if the\n // SSE stream drops silently — 60s is enough since presence flips\n // are pushed through the subscription.\n pollInterval: 60_000,\n });\n\n const collaborators = collaboratorsData?.sessionCollaborators || [];\n\n // Real-time subscriptions for collaboration events\n const { data: joinedData } = useCollaboratorJoinedSubscription({\n variables: { sessionId: sessionId || '' },\n skip: !sessionId || !joined,\n });\n\n const { data: leftData } = useCollaboratorLeftSubscription({\n variables: { sessionId: sessionId || '' },\n skip: !sessionId || !joined,\n });\n\n const { data: controlChangedData } = useSessionControlChangedSubscription({\n variables: { sessionId: sessionId || '' },\n skip: !sessionId || !joined,\n });\n\n // Refetch collaborators when subscription events arrive\n useEffect(() => {\n if (joinedData || leftData || controlChangedData) {\n refetchCollaborators();\n }\n }, [joinedData, leftData, controlChangedData, refetchCollaborators]);\n\n // Cleanup: Leave session only on unmount, and only if we actually joined\n useEffect(() => {\n return () => {\n if (hasJoinedRef.current && sessionId) {\n leaveSession({ variables: { sessionId } }).catch(console.error);\n }\n };\n }, [sessionId, leaveSession]);\n\n const handleJoinSession = useCallback(\n async (passwordInput?: string) => {\n if (!token || joiningRef.current) return;\n\n joiningRef.current = true;\n const pwd = passwordInput || password;\n setPasswordError(null);\n setJoinError(null);\n\n try {\n const result = await joinSession({\n variables: {\n input: {\n shareToken: token,\n password: pwd || undefined,\n },\n },\n });\n\n if (result.data?.joinSharedSession) {\n hasJoinedRef.current = true;\n setJoined(true);\n setSessionId(result.data.joinSharedSession.sessionId);\n void refetchValidation();\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to join session';\n if (message.toLowerCase().includes('password')) {\n setPasswordError('Incorrect password');\n } else {\n setPasswordError(message);\n setJoinError(message);\n }\n } finally {\n joiningRef.current = false;\n }\n },\n [joinSession, password, refetchValidation, token]\n );\n\n // Auto-join if no password required and link is valid\n useEffect(() => {\n if (\n validation?.isValid &&\n !validation.requiresPassword &&\n !joined &&\n !hasLeft &&\n !joiningRef.current &&\n token\n ) {\n // Set sessionId early from validation so collaborators query can start\n if (validation.sessionId && !sessionId) {\n setSessionId(validation.sessionId);\n }\n void handleJoinSession();\n }\n }, [\n validation?.isValid,\n validation?.requiresPassword,\n validation?.sessionId,\n joined,\n hasLeft,\n token,\n sessionId,\n handleJoinSession,\n ]);\n\n const handleRequestControl = useCallback(async () => {\n if (!sessionId) return;\n try {\n await requestControl({ variables: { sessionId } });\n refetchCollaborators();\n } catch (err) {\n console.error('Failed to request control:', err);\n }\n }, [sessionId, requestControl, refetchCollaborators]);\n\n const handleReleaseControl = useCallback(async () => {\n if (!sessionId) return;\n try {\n await releaseControl({ variables: { sessionId } });\n refetchCollaborators();\n } catch (err) {\n console.error('Failed to release control:', err);\n }\n }, [sessionId, releaseControl, refetchCollaborators]);\n\n const handleLeaveSession = useCallback(async () => {\n if (!sessionId) return;\n try {\n await leaveSession({ variables: { sessionId } });\n hasJoinedRef.current = false; // Prevent cleanup from firing a second leave\n setHasLeft(true);\n setJoined(false);\n setSessionId(null);\n window.history.back();\n } catch (err) {\n console.error('Failed to leave session:', err);\n }\n }, [sessionId, leaveSession]);\n\n // Determine current user's collaborator entry\n const currentCollaborator = collaborators.find(\n (c: SessionCollaboratorFieldsFragment) => c.userId === currentUserId\n );\n const permission = validation?.permission || SessionSharePermission.View;\n\n // Stabilize terminal URL to prevent iframe reloads on parent re-render\n const terminalUrl = useMemo(() => validation?.ttydUrl || null, [validation?.ttydUrl]);\n\n // Loading state\n if (validating) {\n return (\n <div className=\"min-h-screen bg-bg-canvas flex items-center justify-center\">\n <div className=\"text-center\">\n <Loader2 className=\"size-8 text-action-primary-bg animate-spin mx-auto mb-4\" />\n <p className=\"text-text-secondary\">\n {tr('sharedSession.validating', 'Validating share link...')}\n </p>\n </div>\n </div>\n );\n }\n\n // Error state\n if (validationError || !validation) {\n return (\n <div className=\"min-h-screen bg-bg-canvas flex items-center justify-center p-4\">\n <div className=\"text-center max-w-md\">\n <div className=\"size-16 bg-status-error-bg-subtle rounded-full flex items-center justify-center mx-auto mb-4\">\n <AlertCircle className=\"size-8 text-status-error-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr('sharedSession.unableToLoad', 'Unable to Load Session')}\n </h2>\n <p className=\"text-text-secondary mb-6\">\n {validationError?.message ||\n tr('sharedSession.failedValidate', 'Failed to validate the share link')}\n </p>\n <button\n type=\"button\"\n onClick={() => window.history.back()}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-text-primary bg-bg-sunken border border-border-default rounded-button hover:bg-bg-elevated hover:border-border-strong transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('sharedSession.goBack', 'Go Back')}\n </button>\n </div>\n </div>\n );\n }\n\n // Invalid link\n if (!validation.isValid) {\n return (\n <div className=\"min-h-screen bg-bg-canvas flex items-center justify-center p-4\">\n <div className=\"text-center max-w-md\">\n <div className=\"size-16 bg-status-warning-bg-subtle rounded-full flex items-center justify-center mx-auto mb-4\">\n <AlertCircle className=\"size-8 text-status-warning-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr('sharedSession.invalidExpiredLink', 'Invalid or Expired Link')}\n </h2>\n <p className=\"text-text-secondary mb-6\">\n {validation.error ||\n tr('sharedSession.linkNoLongerValid', 'This share link is no longer valid.')}\n </p>\n <button\n type=\"button\"\n onClick={() => window.history.back()}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-text-primary bg-bg-sunken border border-border-default rounded-button hover:bg-bg-elevated hover:border-border-strong transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('sharedSession.goBack', 'Go Back')}\n </button>\n </div>\n </div>\n );\n }\n\n // Password required - show password form\n if (validation.requiresPassword && !joined) {\n return (\n <div className=\"min-h-screen bg-bg-canvas flex items-center justify-center p-4\">\n <div className=\"w-full max-w-md\">\n <div className=\"bg-bg-surface border border-border-subtle rounded-card p-6 shadow-elevation-1\">\n {/* Header */}\n <div className=\"text-center mb-6\">\n <div className=\"size-12 bg-[var(--color-accent-soft)] rounded-full flex items-center justify-center mx-auto mb-4\">\n <Terminal className=\"size-6 text-action-primary-bg\" />\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {validation.sessionName || 'Shared Session'}\n </h2>\n {validation.ownerName && (\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('sharedSession.sharedBy', 'Shared by')} {validation.ownerName}\n </p>\n )}\n </div>\n\n {/* Permission badge */}\n <div className=\"flex justify-center mb-6\">\n <div\n className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm ${\n validation.permission === SessionSharePermission.View\n ? 'bg-bg-sunken text-text-secondary'\n : 'bg-[var(--color-accent-soft)] text-text-primary'\n }`}\n >\n {validation.permission === SessionSharePermission.View ? (\n <>\n <Eye className=\"size-4\" />\n {tr('sharedSession.viewOnly', 'View Only')}\n </>\n ) : (\n <>\n <Gamepad2 className=\"size-4\" />\n {tr('sharedSession.canControl', 'Can Control')}\n </>\n )}\n </div>\n </div>\n\n {/* Expiry info */}\n {validation.expiresAt && (\n <div className=\"flex items-center justify-center gap-2 mb-6 text-xs text-text-secondary\">\n <Clock className=\"size-3.5\" />\n <span>\n {tr('sharedSession.expires', 'Expires')}{' '}\n {new Date(validation.expiresAt).toLocaleString()}\n </span>\n </div>\n )}\n\n {/* Password form */}\n <PasswordForm\n onSubmit={(pwd) => {\n setPassword(pwd);\n handleJoinSession(pwd);\n }}\n isLoading={joining}\n error={passwordError}\n />\n </div>\n </div>\n </div>\n );\n }\n\n // Post-leave state — user explicitly left, don't show join spinner\n if (hasLeft) {\n return (\n <div className=\"min-h-screen bg-bg-canvas flex items-center justify-center p-4\">\n <div className=\"text-center max-w-md\">\n <div className=\"size-16 bg-bg-sunken rounded-full flex items-center justify-center mx-auto mb-4\">\n <LogIn className=\"size-8 text-text-secondary\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr('sharedSession.youLeft', 'You left the session')}\n </h2>\n <p className=\"text-text-secondary mb-6\">\n {tr('sharedSession.rejoinHint', 'You can rejoin using the same share link.')}\n </p>\n <div className=\"flex items-center justify-center gap-3\">\n <button\n type=\"button\"\n onClick={() => window.history.back()}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-text-primary bg-bg-sunken border border-border-default rounded-button hover:bg-bg-elevated hover:border-border-strong transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('sharedSession.goBack', 'Go Back')}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setHasLeft(false);\n setJoinError(null);\n handleJoinSession();\n }}\n disabled={joining}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n <LogIn className=\"size-4\" />\n {tr('sharedSession.rejoin', 'Rejoin')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n // Joining state - show error if join failed, otherwise spinner\n if (!joined && !validation.requiresPassword) {\n if (joinError) {\n return (\n <div className=\"min-h-screen bg-bg-canvas flex items-center justify-center p-4\">\n <div className=\"text-center max-w-md\">\n <div className=\"size-16 bg-status-error-bg-subtle rounded-full flex items-center justify-center mx-auto mb-4\">\n <AlertCircle className=\"size-8 text-status-error-text\" />\n </div>\n <h2 className=\"text-xl font-semibold text-text-primary mb-2\">\n {tr('sharedSession.failedJoin', 'Failed to Join Session')}\n </h2>\n <p className=\"text-text-secondary mb-6\">{joinError}</p>\n <div className=\"flex items-center justify-center gap-3\">\n <button\n type=\"button\"\n onClick={() => window.history.back()}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-text-primary bg-bg-sunken border border-border-default rounded-button hover:bg-bg-elevated hover:border-border-strong transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('sharedSession.goBack', 'Go Back')}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setJoinError(null);\n handleJoinSession();\n }}\n disabled={joining}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n <LogIn className=\"size-4\" />\n {tr('sharedSession.retry', 'Retry')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"min-h-screen bg-bg-canvas flex items-center justify-center\">\n <div className=\"text-center\">\n <Loader2 className=\"size-8 text-action-primary-bg animate-spin mx-auto mb-4\" />\n <p className=\"text-text-secondary\">{tr('sharedSession.joining', 'Joining session...')}</p>\n </div>\n </div>\n );\n }\n\n // Main shared session view\n return (\n <MotionPage className=\"min-h-screen bg-bg-canvas flex flex-col\">\n {/* Header */}\n <header className=\"bg-bg-surface border-b border-border-default px-4 py-3\">\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 max-w-screen-2xl mx-auto\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={handleLeaveSession}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors\"\n title=\"Leave session\"\n aria-label=\"Leave session\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 bg-[var(--color-accent-soft)] rounded-lg\">\n <Terminal className=\"size-5 text-action-primary-bg\" />\n </div>\n <div>\n <h1 className=\"font-semibold text-text-primary\">\n {validation.sessionName || 'Shared Session'}\n </h1>\n {validation.ownerName && (\n <p className=\"text-xs text-text-secondary\">\n {tr('sharedSession.sharedBy', 'Shared by')} {validation.ownerName}\n </p>\n )}\n </div>\n </div>\n </div>\n\n {/* Permission and collaborator count */}\n <div className=\"flex items-center gap-3\">\n <div\n className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${\n permission === SessionSharePermission.View\n ? 'bg-bg-sunken text-text-secondary'\n : 'bg-[var(--color-accent-soft)] text-text-primary'\n }`}\n >\n {permission === SessionSharePermission.View ? (\n <>\n <Eye className=\"size-3.5\" />\n {tr('sharedSession.viewOnly', 'View Only')}\n </>\n ) : (\n <>\n <Gamepad2 className=\"size-3.5\" />\n {tr('sharedSession.interactive', 'Interactive')}\n </>\n )}\n </div>\n <div className=\"flex items-center gap-1.5 px-2.5 py-1 bg-bg-sunken rounded-full text-xs text-text-secondary\">\n <Users className=\"size-3.5\" />\n <span>{collaborators.length}</span>\n </div>\n </div>\n </div>\n </header>\n\n {/* Main content */}\n <div className=\"flex-1 flex\">\n {/* Terminal area */}\n <div className=\"flex-1 flex flex-col\">\n <SharedTerminalPanel\n isShared={true}\n collaborators={collaborators}\n currentUserId={currentUserId}\n permission={permission}\n onRequestControl={handleRequestControl}\n onReleaseControl={handleReleaseControl}\n isRequestingControl={requestingControl}\n isReleasingControl={releasingControl}\n className=\"flex-1\"\n >\n {/* Terminal iframe - in production this would be the ttyd URL */}\n <div className=\"w-full h-full bg-bg-inverse text-text-inverse flex items-center justify-center\">\n {terminalUrl ? (\n <iframe\n src={terminalUrl}\n title=\"Shared Terminal\"\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n sandbox=\"allow-scripts allow-same-origin allow-forms\"\n />\n ) : (\n <div className=\"text-center\">\n <Terminal className=\"size-12 text-text-secondary mx-auto mb-4\" />\n <p className=\"text-text-tertiary\">\n {tr('sharedSession.terminalLoading', 'Terminal loading...')}\n </p>\n <p className=\"text-text-secondary text-sm mt-1\">Session ID: {sessionId}</p>\n </div>\n )}\n </div>\n </SharedTerminalPanel>\n </div>\n\n {/* Sidebar - Collaborators */}\n <aside className=\"w-72 bg-bg-surface border-l border-border-default p-4 hidden lg:block\">\n <CollaboratorsList\n collaborators={collaborators}\n currentUserId={currentUserId}\n onRequestControl={\n permission === SessionSharePermission.Interact ? handleRequestControl : undefined\n }\n isRequestingControl={requestingControl}\n />\n\n {/* Session info */}\n <div className=\"mt-6 pt-6 border-t border-border-default\">\n <h4 className=\"text-xs font-medium text-text-secondary uppercase tracking-wide mb-3\">\n {tr('sharedSession.sessionInfo', 'Session Info')}\n </h4>\n <div className=\"space-y-2 text-sm\">\n {validation.expiresAt && (\n <div className=\"flex items-center justify-between\">\n <span className=\"text-text-secondary\">\n {tr('sharedSession.expires', 'Expires')}\n </span>\n <span className=\"text-text-primary\">\n {new Date(validation.expiresAt).toLocaleTimeString()}\n </span>\n </div>\n )}\n <div className=\"flex items-center justify-between\">\n <span className=\"text-text-secondary\">\n {tr('sharedSession.yourRole', 'Your role')}\n </span>\n <span className=\"text-text-primary capitalize\">\n {currentCollaborator?.role?.toLowerCase() || tr('sharedSession.viewer', 'Viewer')}\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-text-secondary\">{tr('sharedSession.status', 'Status')}</span>\n <span className=\"text-status-success-text\">\n {tr('sharedSession.connected', 'Connected')}\n </span>\n </div>\n </div>\n </div>\n </aside>\n </div>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
@@ -32,7 +32,7 @@ function v({ onSubmit: e, isLoading: t, error: n }) {
32
32
  })]
33
33
  })] }),
34
34
  n && /* @__PURE__ */ g("div", {
35
- className: "flex items-center gap-2 p-3 bg-status-error-bg/10 border border-status-error-bg/30 rounded-lg",
35
+ className: "flex items-center gap-2 p-3 bg-status-error-bg-subtle border border-status-error-border rounded-card",
36
36
  children: [/* @__PURE__ */ h(s, { className: "size-4 text-status-error-text" }), /* @__PURE__ */ h("span", {
37
37
  className: "text-sm text-status-error-text",
38
38
  children: n
@@ -51,7 +51,7 @@ function y({ message: e }) {
51
51
  return /* @__PURE__ */ g("div", {
52
52
  className: "flex flex-col items-center justify-center min-h-[60vh] gap-4",
53
53
  children: [/* @__PURE__ */ h("div", {
54
- className: "size-16 rounded-full bg-status-error-bg/10 flex items-center justify-center",
54
+ className: "size-16 rounded-full bg-status-error-bg-subtle flex items-center justify-center",
55
55
  children: /* @__PURE__ */ h(s, {
56
56
  size: 32,
57
57
  className: "text-status-error-text"
@@ -131,7 +131,7 @@ function b() {
131
131
  className: "flex flex-col items-center justify-center min-h-[60vh] gap-6",
132
132
  children: [
133
133
  /* @__PURE__ */ h("div", {
134
- className: "size-16 rounded-full bg-action-primary-bg/10 flex items-center justify-center",
134
+ className: "size-16 rounded-full bg-[var(--color-accent-soft)] flex items-center justify-center",
135
135
  children: /* @__PURE__ */ h(u, {
136
136
  size: 28,
137
137
  className: "text-action-primary-bg"
@@ -206,7 +206,7 @@ function b() {
206
206
  children: [/* @__PURE__ */ h("div", { children: /* @__PURE__ */ g("div", {
207
207
  className: "flex items-center gap-3",
208
208
  children: [/* @__PURE__ */ h("div", {
209
- className: "size-10 rounded-xl bg-action-primary-bg/10 flex items-center justify-center",
209
+ className: "size-10 rounded-xl bg-[var(--color-accent-soft)] flex items-center justify-center",
210
210
  children: /* @__PURE__ */ h(f, {
211
211
  size: 20,
212
212
  className: "text-action-primary-bg"
@@ -221,7 +221,7 @@ function b() {
221
221
  }) }), /* @__PURE__ */ h("div", {
222
222
  className: "flex items-center gap-2",
223
223
  children: /* @__PURE__ */ g("span", {
224
- className: `inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium ${B ? "bg-status-success-bg/10 text-status-success-text border border-status-success-border/30" : "bg-bg-secondary text-text-secondary border border-border-default"}`,
224
+ className: `inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium ${B ? "bg-status-success-bg-subtle text-status-success-text border border-status-success-border" : "bg-bg-sunken text-text-secondary border border-border-default"}`,
225
225
  children: [h(B ? d : c, { size: 12 }), B ? b("sharedVibeDeck.canExecute", "Can execute") : b("sharedVibeDeck.viewOnly", "View only")]
226
226
  })
227
227
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"SharedVibeDeckPage.js","names":[],"sources":["../../src/pages/SharedVibeDeckPage.tsx"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { useParams, useSearchParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Loader2, AlertCircle, Lock, Play, Zap, Eye } from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { VibeDeckGrid } from '@/components/vibedeck/VibeDeckGrid';\nimport {\n useValidateVibeDeckShareLinkQuery,\n useGetSharedVibeDeckQuery,\n useExecuteSharedVibeDeckButtonMutation,\n} from '@/generated/wspace-operations';\n\n/**\n * Password entry form for protected share links\n */\nfunction PasswordForm({\n onSubmit,\n isLoading,\n error,\n}: {\n onSubmit: (password: string) => void;\n isLoading: boolean;\n error?: string | null;\n}) {\n const [password, setPassword] = useState('');\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (password.trim()) onSubmit(password);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-4 w-full max-w-sm\">\n <div>\n <label\n htmlFor=\"sharedvibedeckpage-57\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n This VibeDeck is password protected\n </label>\n <div className=\"relative\">\n <Lock className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\" />\n <input\n id=\"sharedvibedeckpage-57\"\n aria-label=\"Enter password\"\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"Enter password\"\n className=\"w-full pl-10 pr-4 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n </div>\n </div>\n {error && (\n <div className=\"flex items-center gap-2 p-3 bg-status-error-bg/10 border border-status-error-bg/30 rounded-lg\">\n <AlertCircle className=\"size-4 text-status-error-text\" />\n <span className=\"text-sm text-status-error-text\">{error}</span>\n </div>\n )}\n <button\n type=\"submit\"\n disabled={isLoading || !password.trim()}\n className=\"w-full px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {isLoading ? 'Verifying...' : 'Access VibeDeck'}\n </button>\n </form>\n );\n}\n\n/**\n * Error display component\n */\nfunction ShareError({ message }: { message: string }) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-[60vh] gap-4\">\n <div className=\"size-16 rounded-full bg-status-error-bg/10 flex items-center justify-center\">\n <AlertCircle size={32} className=\"text-status-error-text\" />\n </div>\n <div className=\"text-center\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary mt-1\">{message}</p>\n </div>\n </div>\n );\n}\n\n/**\n * SharedVibeDeckPage — standalone page for viewing/executing a shared VibeDeck via token.\n * Supports:\n * - /vibecontrols/deck/:token — full page with header\n * - ?embed=true — minimal iframe mode (no header/chrome)\n */\nexport function SharedVibeDeckPage() {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n const { token } = useParams<{ token: string }>();\n const [searchParams] = useSearchParams();\n const isEmbed = searchParams.get('embed') === 'true';\n const transparentBg = searchParams.get('bg') === 'transparent';\n\n const [password, setPassword] = useState<string | null>(null);\n const [passwordError, setPasswordError] = useState<string | null>(null);\n const [executingButtonId, setExecutingButtonId] = useState<string | null>(null);\n\n // Step 1: Validate the share link\n const { data: validationData, loading: validating } = useValidateVibeDeckShareLinkQuery({\n variables: { token: token || '' },\n skip: !token,\n });\n\n const validation = validationData?.validateVibeDeckShareLink;\n const needsPassword = validation?.isValid && validation.requiresPassword && !password;\n\n // Step 2: Fetch the deck data (once validated and password provided if needed)\n const shouldFetchDeck =\n validation?.isValid && (!validation.requiresPassword || password !== null);\n\n const {\n data: deckData,\n loading: deckLoading,\n error: deckError,\n } = useGetSharedVibeDeckQuery({\n variables: { token: token || '', password },\n skip: !shouldFetchDeck,\n });\n\n const sharedDeck = deckData?.sharedVibeDeck;\n const canExecute = sharedDeck?.permission === 'EXECUTE';\n\n // Execute mutation for shared buttons\n const [executeSharedButton] = useExecuteSharedVibeDeckButtonMutation();\n\n const handlePasswordSubmit = useCallback((pw: string) => {\n setPasswordError(null);\n setPassword(pw);\n }, []);\n\n const handleExecuteButton = useCallback(\n async (buttonId: string) => {\n if (!token || !canExecute) return;\n setExecutingButtonId(buttonId);\n try {\n await executeSharedButton({\n variables: { token, buttonId, password },\n });\n } catch {\n // Silent — execution status is visible from the button\n } finally {\n setExecutingButtonId(null);\n }\n },\n [token, password, canExecute, executeSharedButton]\n );\n\n // Handle password errors from the deck query\n const passwordRequired =\n deckError?.message?.includes('Password is required') ||\n deckError?.message?.includes('Incorrect password');\n\n if (passwordRequired && password) {\n // Reset and show password form with error\n if (!passwordError) {\n setPasswordError('Incorrect password. Please try again.');\n setPassword(null);\n }\n }\n\n // Loading state\n if (validating) {\n return (\n <div\n className={`flex items-center justify-center min-h-[60vh] ${transparentBg ? 'bg-transparent' : ''}`}\n >\n <Loader2 size={24} className=\"animate-spin text-text-tertiary\" />\n </div>\n );\n }\n\n // Invalid token\n if (!validation?.isValid) {\n if (isEmbed) {\n return (\n <div\n className={`flex items-center justify-center p-8 ${transparentBg ? 'bg-transparent' : ''}`}\n >\n <p className=\"text-sm text-text-secondary\">\n {validation?.error || tr('sharedVibeDeck.invalidLink', 'Invalid share link')}\n </p>\n </div>\n );\n }\n return (\n <MotionPage className=\"p-4 sm:p-6\">\n <ShareError\n message={\n validation?.error ||\n tr('sharedVibeDeck.linkInvalidOrExpired', 'This share link is invalid or has expired.')\n }\n />\n </MotionPage>\n );\n }\n\n // Password required\n if (needsPassword || (passwordRequired && !password)) {\n if (isEmbed) {\n return (\n <div\n className={`flex items-center justify-center p-8 ${transparentBg ? 'bg-transparent' : ''}`}\n >\n <p className=\"text-sm text-text-secondary\">\n {tr('sharedVibeDeck.requiresPassword', 'This VibeDeck requires a password')}\n </p>\n </div>\n );\n }\n return (\n <MotionPage className=\"p-4 sm:p-6\">\n <div className=\"flex flex-col items-center justify-center min-h-[60vh] gap-6\">\n <div className=\"size-16 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <Lock size={28} className=\"text-action-primary-bg\" />\n </div>\n <div className=\"text-center\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {validation.vibeDeckName || 'Shared VibeDeck'}\n </h2>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('sharedVibeDeck.enterPasswordHint', 'Enter the password to continue')}\n </p>\n </div>\n <PasswordForm\n onSubmit={handlePasswordSubmit}\n isLoading={deckLoading}\n error={passwordError}\n />\n </div>\n </MotionPage>\n );\n }\n\n // Loading deck\n if (deckLoading || !sharedDeck) {\n return (\n <div\n className={`flex items-center justify-center min-h-[60vh] ${transparentBg ? 'bg-transparent' : ''}`}\n >\n <Loader2 size={24} className=\"animate-spin text-text-tertiary\" />\n </div>\n );\n }\n\n // Map shared buttons to the format expected by VibeDeckGrid\n const gridButtons = sharedDeck.buttons.map((b: Record<string, unknown>) => ({\n id: b.id,\n label: b.label,\n icon: b.icon || null,\n color: b.color || null,\n command: null,\n actionType: b.actionType || 'COMMAND',\n category: b.category || null,\n confirmationRequired: b.confirmationRequired,\n keyboardShortcut: null,\n targetAgentId: null,\n scriptInterpreter: null,\n webhookUrl: null,\n webhookMethod: null,\n row: b.row,\n column: b.column,\n rowSpan: b.rowSpan,\n colSpan: b.colSpan,\n }));\n\n // Embed mode: just the grid, no chrome\n if (isEmbed) {\n return (\n <div className={`p-3 ${transparentBg ? 'bg-transparent' : ''}`}>\n <VibeDeckGrid\n gridColumns={sharedDeck.gridColumns}\n gridRows={sharedDeck.gridRows}\n buttons={gridButtons}\n onExecuteButton={handleExecuteButton}\n onEditButton={() => {}}\n onDeleteButton={() => {}}\n onDropButton={() => {}}\n onAddButton={() => {}}\n executingButtonId={executingButtonId}\n canCreate={false}\n canUpdate={false}\n canDelete={false}\n canExecute={canExecute}\n />\n </div>\n );\n }\n\n // Full page mode\n return (\n <MotionPage className=\"p-4 sm:p-6 space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-xl bg-action-primary-bg/10 flex items-center justify-center\">\n <Zap size={20} className=\"text-action-primary-bg\" />\n </div>\n <div>\n <h1 className=\"text-xl font-semibold text-text-primary\">{sharedDeck.name}</h1>\n {sharedDeck.description && (\n <p className=\"text-sm text-text-secondary mt-0.5\">{sharedDeck.description}</p>\n )}\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <span\n className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium ${\n canExecute\n ? 'bg-status-success-bg/10 text-status-success-text border border-status-success-border/30'\n : 'bg-bg-secondary text-text-secondary border border-border-default'\n }`}\n >\n {canExecute ? <Play size={12} /> : <Eye size={12} />}\n {canExecute\n ? tr('sharedVibeDeck.canExecute', 'Can execute')\n : tr('sharedVibeDeck.viewOnly', 'View only')}\n </span>\n </div>\n </div>\n\n {/* Grid */}\n <VibeDeckGrid\n gridColumns={sharedDeck.gridColumns}\n gridRows={sharedDeck.gridRows}\n buttons={gridButtons}\n onExecuteButton={handleExecuteButton}\n onEditButton={() => {}}\n onDeleteButton={() => {}}\n onDropButton={() => {}}\n onAddButton={() => {}}\n executingButtonId={executingButtonId}\n canCreate={false}\n canUpdate={false}\n canDelete={false}\n canExecute={canExecute}\n />\n\n {/* Footer branding */}\n <div className=\"text-center pt-4\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('sharedVibeDeck.poweredBy', 'Powered by')}{' '}\n <span className=\"font-medium text-text-secondary\">VibeControls</span>\n </p>\n </div>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SharedVibeDeckPage.js","names":[],"sources":["../../src/pages/SharedVibeDeckPage.tsx"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { useParams, useSearchParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Loader2, AlertCircle, Lock, Play, Zap, Eye } from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { VibeDeckGrid } from '@/components/vibedeck/VibeDeckGrid';\nimport {\n useValidateVibeDeckShareLinkQuery,\n useGetSharedVibeDeckQuery,\n useExecuteSharedVibeDeckButtonMutation,\n} from '@/generated/wspace-operations';\n\n/**\n * Password entry form for protected share links\n */\nfunction PasswordForm({\n onSubmit,\n isLoading,\n error,\n}: {\n onSubmit: (password: string) => void;\n isLoading: boolean;\n error?: string | null;\n}) {\n const [password, setPassword] = useState('');\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (password.trim()) onSubmit(password);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-4 w-full max-w-sm\">\n <div>\n <label\n htmlFor=\"sharedvibedeckpage-57\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n This VibeDeck is password protected\n </label>\n <div className=\"relative\">\n <Lock className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\" />\n <input\n id=\"sharedvibedeckpage-57\"\n aria-label=\"Enter password\"\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"Enter password\"\n className=\"w-full pl-10 pr-4 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n </div>\n </div>\n {error && (\n <div className=\"flex items-center gap-2 p-3 bg-status-error-bg-subtle border border-status-error-border rounded-card\">\n <AlertCircle className=\"size-4 text-status-error-text\" />\n <span className=\"text-sm text-status-error-text\">{error}</span>\n </div>\n )}\n <button\n type=\"submit\"\n disabled={isLoading || !password.trim()}\n className=\"w-full px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {isLoading ? 'Verifying...' : 'Access VibeDeck'}\n </button>\n </form>\n );\n}\n\n/**\n * Error display component\n */\nfunction ShareError({ message }: { message: string }) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-[60vh] gap-4\">\n <div className=\"size-16 rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <AlertCircle size={32} className=\"text-status-error-text\" />\n </div>\n <div className=\"text-center\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary mt-1\">{message}</p>\n </div>\n </div>\n );\n}\n\n/**\n * SharedVibeDeckPage — standalone page for viewing/executing a shared VibeDeck via token.\n * Supports:\n * - /vibecontrols/deck/:token — full page with header\n * - ?embed=true — minimal iframe mode (no header/chrome)\n */\nexport function SharedVibeDeckPage() {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n const { token } = useParams<{ token: string }>();\n const [searchParams] = useSearchParams();\n const isEmbed = searchParams.get('embed') === 'true';\n const transparentBg = searchParams.get('bg') === 'transparent';\n\n const [password, setPassword] = useState<string | null>(null);\n const [passwordError, setPasswordError] = useState<string | null>(null);\n const [executingButtonId, setExecutingButtonId] = useState<string | null>(null);\n\n // Step 1: Validate the share link\n const { data: validationData, loading: validating } = useValidateVibeDeckShareLinkQuery({\n variables: { token: token || '' },\n skip: !token,\n });\n\n const validation = validationData?.validateVibeDeckShareLink;\n const needsPassword = validation?.isValid && validation.requiresPassword && !password;\n\n // Step 2: Fetch the deck data (once validated and password provided if needed)\n const shouldFetchDeck =\n validation?.isValid && (!validation.requiresPassword || password !== null);\n\n const {\n data: deckData,\n loading: deckLoading,\n error: deckError,\n } = useGetSharedVibeDeckQuery({\n variables: { token: token || '', password },\n skip: !shouldFetchDeck,\n });\n\n const sharedDeck = deckData?.sharedVibeDeck;\n const canExecute = sharedDeck?.permission === 'EXECUTE';\n\n // Execute mutation for shared buttons\n const [executeSharedButton] = useExecuteSharedVibeDeckButtonMutation();\n\n const handlePasswordSubmit = useCallback((pw: string) => {\n setPasswordError(null);\n setPassword(pw);\n }, []);\n\n const handleExecuteButton = useCallback(\n async (buttonId: string) => {\n if (!token || !canExecute) return;\n setExecutingButtonId(buttonId);\n try {\n await executeSharedButton({\n variables: { token, buttonId, password },\n });\n } catch {\n // Silent — execution status is visible from the button\n } finally {\n setExecutingButtonId(null);\n }\n },\n [token, password, canExecute, executeSharedButton]\n );\n\n // Handle password errors from the deck query\n const passwordRequired =\n deckError?.message?.includes('Password is required') ||\n deckError?.message?.includes('Incorrect password');\n\n if (passwordRequired && password) {\n // Reset and show password form with error\n if (!passwordError) {\n setPasswordError('Incorrect password. Please try again.');\n setPassword(null);\n }\n }\n\n // Loading state\n if (validating) {\n return (\n <div\n className={`flex items-center justify-center min-h-[60vh] ${transparentBg ? 'bg-transparent' : ''}`}\n >\n <Loader2 size={24} className=\"animate-spin text-text-tertiary\" />\n </div>\n );\n }\n\n // Invalid token\n if (!validation?.isValid) {\n if (isEmbed) {\n return (\n <div\n className={`flex items-center justify-center p-8 ${transparentBg ? 'bg-transparent' : ''}`}\n >\n <p className=\"text-sm text-text-secondary\">\n {validation?.error || tr('sharedVibeDeck.invalidLink', 'Invalid share link')}\n </p>\n </div>\n );\n }\n return (\n <MotionPage className=\"p-4 sm:p-6\">\n <ShareError\n message={\n validation?.error ||\n tr('sharedVibeDeck.linkInvalidOrExpired', 'This share link is invalid or has expired.')\n }\n />\n </MotionPage>\n );\n }\n\n // Password required\n if (needsPassword || (passwordRequired && !password)) {\n if (isEmbed) {\n return (\n <div\n className={`flex items-center justify-center p-8 ${transparentBg ? 'bg-transparent' : ''}`}\n >\n <p className=\"text-sm text-text-secondary\">\n {tr('sharedVibeDeck.requiresPassword', 'This VibeDeck requires a password')}\n </p>\n </div>\n );\n }\n return (\n <MotionPage className=\"p-4 sm:p-6\">\n <div className=\"flex flex-col items-center justify-center min-h-[60vh] gap-6\">\n <div className=\"size-16 rounded-full bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Lock size={28} className=\"text-action-primary-bg\" />\n </div>\n <div className=\"text-center\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {validation.vibeDeckName || 'Shared VibeDeck'}\n </h2>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('sharedVibeDeck.enterPasswordHint', 'Enter the password to continue')}\n </p>\n </div>\n <PasswordForm\n onSubmit={handlePasswordSubmit}\n isLoading={deckLoading}\n error={passwordError}\n />\n </div>\n </MotionPage>\n );\n }\n\n // Loading deck\n if (deckLoading || !sharedDeck) {\n return (\n <div\n className={`flex items-center justify-center min-h-[60vh] ${transparentBg ? 'bg-transparent' : ''}`}\n >\n <Loader2 size={24} className=\"animate-spin text-text-tertiary\" />\n </div>\n );\n }\n\n // Map shared buttons to the format expected by VibeDeckGrid\n const gridButtons = sharedDeck.buttons.map((b: Record<string, unknown>) => ({\n id: b.id,\n label: b.label,\n icon: b.icon || null,\n color: b.color || null,\n command: null,\n actionType: b.actionType || 'COMMAND',\n category: b.category || null,\n confirmationRequired: b.confirmationRequired,\n keyboardShortcut: null,\n targetAgentId: null,\n scriptInterpreter: null,\n webhookUrl: null,\n webhookMethod: null,\n row: b.row,\n column: b.column,\n rowSpan: b.rowSpan,\n colSpan: b.colSpan,\n }));\n\n // Embed mode: just the grid, no chrome\n if (isEmbed) {\n return (\n <div className={`p-3 ${transparentBg ? 'bg-transparent' : ''}`}>\n <VibeDeckGrid\n gridColumns={sharedDeck.gridColumns}\n gridRows={sharedDeck.gridRows}\n buttons={gridButtons}\n onExecuteButton={handleExecuteButton}\n onEditButton={() => {}}\n onDeleteButton={() => {}}\n onDropButton={() => {}}\n onAddButton={() => {}}\n executingButtonId={executingButtonId}\n canCreate={false}\n canUpdate={false}\n canDelete={false}\n canExecute={canExecute}\n />\n </div>\n );\n }\n\n // Full page mode\n return (\n <MotionPage className=\"p-4 sm:p-6 space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-xl bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Zap size={20} className=\"text-action-primary-bg\" />\n </div>\n <div>\n <h1 className=\"text-xl font-semibold text-text-primary\">{sharedDeck.name}</h1>\n {sharedDeck.description && (\n <p className=\"text-sm text-text-secondary mt-0.5\">{sharedDeck.description}</p>\n )}\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <span\n className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium ${\n canExecute\n ? 'bg-status-success-bg-subtle text-status-success-text border border-status-success-border'\n : 'bg-bg-sunken text-text-secondary border border-border-default'\n }`}\n >\n {canExecute ? <Play size={12} /> : <Eye size={12} />}\n {canExecute\n ? tr('sharedVibeDeck.canExecute', 'Can execute')\n : tr('sharedVibeDeck.viewOnly', 'View only')}\n </span>\n </div>\n </div>\n\n {/* Grid */}\n <VibeDeckGrid\n gridColumns={sharedDeck.gridColumns}\n gridRows={sharedDeck.gridRows}\n buttons={gridButtons}\n onExecuteButton={handleExecuteButton}\n onEditButton={() => {}}\n onDeleteButton={() => {}}\n onDropButton={() => {}}\n onAddButton={() => {}}\n executingButtonId={executingButtonId}\n canCreate={false}\n canUpdate={false}\n canDelete={false}\n canExecute={canExecute}\n />\n\n {/* Footer branding */}\n <div className=\"text-center pt-4\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('sharedVibeDeck.poweredBy', 'Powered by')}{' '}\n <span className=\"font-medium text-text-secondary\">VibeControls</span>\n </p>\n </div>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
@@ -52,48 +52,48 @@ function A() {
52
52
  description: M("targetDetails.notFoundBody", "The target you requested may have been deleted or is in a different workspace.")
53
53
  });
54
54
  let U = B.updatedAt ? new Date(B.updatedAt).toLocaleString() : "—";
55
- return /* @__PURE__ */ E(k, { children: [
56
- /* @__PURE__ */ T(r, {
57
- title: B.name,
58
- description: `${B.type} · ${B.host ?? "no host"}`,
59
- breadcrumb: [
60
- {
61
- label: M("breadcrumbs.targetsAgents", "Targets & Agents"),
62
- path: "../agents"
63
- },
64
- {
65
- label: M("breadcrumbs.targets", "Targets"),
66
- path: "../agents?tab=targets"
67
- },
68
- { label: B.name }
69
- ],
70
- actions: /* @__PURE__ */ E("div", {
71
- className: "flex flex-wrap items-center gap-2",
72
- children: [/* @__PURE__ */ T(d, {
73
- entity: "target",
74
- resource: {
75
- id: B.id,
76
- name: B.name
55
+ return /* @__PURE__ */ E(k, {
56
+ className: "space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-full",
57
+ children: [
58
+ /* @__PURE__ */ T(r, {
59
+ title: B.name,
60
+ description: `${B.type} · ${B.host ?? "no host"}`,
61
+ breadcrumb: [
62
+ {
63
+ label: M("breadcrumbs.targetsAgents", "Targets & Agents"),
64
+ path: "../agents"
77
65
  },
78
- label: M("targetDetails.share", "Share"),
79
- variant: "button"
80
- }), /* @__PURE__ */ E(D, {
81
- type: "button",
82
- variant: "outline",
83
- size: "sm",
84
- onClick: () => j("/agents?tab=targets"),
85
- className: "max-md:min-h-11",
86
- children: [/* @__PURE__ */ T(h, {
87
- className: "size-3.5",
88
- "aria-hidden": "true"
89
- }), M("common.back", "Back")]
90
- })]
91
- })
92
- }),
93
- /* @__PURE__ */ E(c, {
94
- className: "mt-4",
95
- children: [/* @__PURE__ */ E("div", {
96
- className: "p-4 border-b border-border-default flex items-center gap-2",
66
+ {
67
+ label: M("breadcrumbs.targets", "Targets"),
68
+ path: "../agents?tab=targets"
69
+ },
70
+ { label: B.name }
71
+ ],
72
+ actions: /* @__PURE__ */ E("div", {
73
+ className: "flex flex-wrap items-center gap-2",
74
+ children: [/* @__PURE__ */ T(d, {
75
+ entity: "target",
76
+ resource: {
77
+ id: B.id,
78
+ name: B.name
79
+ },
80
+ label: M("targetDetails.share", "Share"),
81
+ variant: "button"
82
+ }), /* @__PURE__ */ E(D, {
83
+ type: "button",
84
+ variant: "outline",
85
+ size: "sm",
86
+ onClick: () => j("/agents?tab=targets"),
87
+ className: "max-md:min-h-11",
88
+ children: [/* @__PURE__ */ T(h, {
89
+ className: "size-3.5",
90
+ "aria-hidden": "true"
91
+ }), M("common.back", "Back")]
92
+ })]
93
+ })
94
+ }),
95
+ /* @__PURE__ */ E(c, { children: [/* @__PURE__ */ E("div", {
96
+ className: "p-4 border-b border-border-subtle flex items-center gap-2",
97
97
  children: [/* @__PURE__ */ T(b, {
98
98
  className: "size-5",
99
99
  "aria-hidden": "true"
@@ -158,18 +158,15 @@ function A() {
158
158
  }), /* @__PURE__ */ T("div", {
159
159
  className: "flex flex-wrap gap-1",
160
160
  children: B.tags.map((e) => /* @__PURE__ */ T("span", {
161
- className: "px-2 py-0.5 text-xs rounded bg-bg-sunken border border-border-default",
161
+ className: "px-2 py-0.5 text-xs rounded-full bg-bg-sunken border border-border-subtle",
162
162
  children: e
163
163
  }, e))
164
164
  })]
165
165
  })
166
166
  ]
167
- })]
168
- }),
169
- /* @__PURE__ */ E(c, {
170
- className: "mt-4",
171
- children: [/* @__PURE__ */ E("div", {
172
- className: "p-4 border-b border-border-default flex items-center gap-2",
167
+ })] }),
168
+ /* @__PURE__ */ E(c, { children: [/* @__PURE__ */ E("div", {
169
+ className: "p-4 border-b border-border-subtle flex items-center gap-2",
173
170
  children: [/* @__PURE__ */ T(x, {
174
171
  className: "size-5",
175
172
  "aria-hidden": "true"
@@ -183,12 +180,9 @@ function A() {
183
180
  entityType: "VIBECONTROLS_TARGET",
184
181
  entityId: B.id
185
182
  })
186
- })]
187
- }),
188
- /* @__PURE__ */ E(c, {
189
- className: "mt-4",
190
- children: [/* @__PURE__ */ E("div", {
191
- className: "p-4 border-b border-border-default flex flex-wrap items-center justify-between gap-2",
183
+ })] }),
184
+ /* @__PURE__ */ E(c, { children: [/* @__PURE__ */ E("div", {
185
+ className: "p-4 border-b border-border-subtle flex flex-wrap items-center justify-between gap-2",
192
186
  children: [/* @__PURE__ */ E("div", {
193
187
  className: "flex items-center gap-2",
194
188
  children: [/* @__PURE__ */ T(v, {
@@ -227,7 +221,7 @@ function A() {
227
221
  let t = l(e);
228
222
  return /* @__PURE__ */ E(S, {
229
223
  to: `../agents/${e.id}`,
230
- className: "flex items-center justify-between gap-3 p-3 rounded-card border border-border-default hover:bg-bg-sunken/40 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
224
+ className: "flex items-center justify-between gap-3 p-3 rounded-card border border-border-subtle hover:bg-bg-sunken/40 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
231
225
  children: [/* @__PURE__ */ E("div", {
232
226
  className: "flex items-center gap-3 min-w-0",
233
227
  children: [/* @__PURE__ */ T("div", {
@@ -257,12 +251,9 @@ function A() {
257
251
  })]
258
252
  }, e.id);
259
253
  })
260
- })]
261
- }),
262
- /* @__PURE__ */ E(c, {
263
- className: "mt-4",
264
- children: [/* @__PURE__ */ E("div", {
265
- className: "p-4 border-b border-border-default flex items-center gap-2",
254
+ })] }),
255
+ /* @__PURE__ */ E(c, { children: [/* @__PURE__ */ E("div", {
256
+ className: "p-4 border-b border-border-subtle flex items-center gap-2",
266
257
  children: [/* @__PURE__ */ T(m, {
267
258
  className: "size-5",
268
259
  "aria-hidden": "true"
@@ -288,7 +279,7 @@ function A() {
288
279
  }) : /* @__PURE__ */ T("div", {
289
280
  className: "p-4 space-y-2",
290
281
  children: H.map((e) => /* @__PURE__ */ E("div", {
291
- className: "flex flex-wrap items-center justify-between gap-3 p-3 rounded-card border border-border-default",
282
+ className: "flex flex-wrap items-center justify-between gap-3 p-3 rounded-card border border-border-subtle",
292
283
  children: [/* @__PURE__ */ E("div", {
293
284
  className: "min-w-0",
294
285
  children: [/* @__PURE__ */ T("p", {
@@ -313,9 +304,9 @@ function A() {
313
304
  })]
314
305
  }), /* @__PURE__ */ T(s, { status: e.status })]
315
306
  }, e.id))
316
- })]
317
- })
318
- ] });
307
+ })] })
308
+ ]
309
+ });
319
310
  }
320
311
  //#endregion
321
312
  export { A as TargetDetailsPage };
@@ -1 +1 @@
1
- {"version":3,"file":"TargetDetailsPage.js","names":[],"sources":["../../src/pages/TargetDetailsPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useParams, Link } from 'react-router-dom';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport {\n ArrowLeft,\n Server,\n CircleCheck,\n CircleX,\n Cpu,\n ExternalLink,\n Activity,\n Tag,\n} from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { PageHeader, Card, StatusBadge, EmptyState, ErrorState } from '@/components/shared';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { EntityShareButton } from '@/components/sharing';\nimport { getAgentStatus } from '@/components/agents/agentStatus';\nimport { useGetTargetQuery } from '@/generated/wspace-operations';\nimport type { GetTargetQuery } from '@/generated/wspace-operations';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { useTr } from '@/shared/hooks/useTr';\n\ntype TargetType = NonNullable<GetTargetQuery['target']>;\ntype AgentSummary = TargetType['agents'][number];\ntype ConnectionSummary = TargetType['inboundConnections'][number];\n\n/**\n * TargetDetailsPage — `/vibecontrols/targets/:id`\n *\n * Mirrors AgentDetailsPage shell. Three sections:\n * 1. Identity card (name, type, status, host, last updated)\n * 2. Agents on this target (table with link to AgentDetailsPage)\n * 3. Inbound agent connections\n *\n * Reuses the existing `GetTarget` query — no schema changes needed; the\n * `TargetFields` fragment already includes the agent list and metadata.\n */\nexport function TargetDetailsPage() {\n const { id } = useParams<{ id: string }>();\n const navigate = useVibeNavigate();\n const tr = useTr();\n // Page-level read gate (the gateway enforces `target:read` on GetTarget).\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(VIBE_PERMISSIONS.TARGET_READ);\n\n const { data, loading, error, refetch } = useGetTargetQuery({\n variables: { id: id ?? '' },\n skip: !id || permissionsLoading || !canRead,\n fetchPolicy: 'cache-and-network',\n });\n\n const target = data?.target ?? null;\n const agents = useMemo(() => target?.agents ?? [], [target]);\n const connections = useMemo(() => target?.inboundConnections ?? [], [target]);\n\n if (!id) {\n return (\n <ErrorState title={tr('targetDetails.missingId', 'Target id missing from URL')} message=\"\" />\n );\n }\n\n if (permissionsLoading) {\n return (\n <MotionPage>\n <PageSkeleton variant=\"detail\" />\n </MotionPage>\n );\n }\n\n if (!canRead) {\n return (\n <AccessDenied\n title={tr('targetDetails.denied.title', 'Access Denied')}\n description={tr(\n 'targetDetails.denied.description',\n 'You do not have permission to view this target.'\n )}\n />\n );\n }\n\n if (loading && !data) {\n return (\n <MotionPage>\n <PageSkeleton variant=\"detail\" />\n </MotionPage>\n );\n }\n\n if (error && !target) {\n return (\n <ErrorState\n title={tr('targetDetails.errorTitle', 'Failed to load target')}\n message={error.message}\n onRetry={() => void refetch()}\n />\n );\n }\n\n if (!target) {\n return (\n <EmptyState\n icon={<Server className=\"size-12 text-text-muted\" aria-hidden=\"true\" />}\n title={tr('targetDetails.notFoundTitle', 'Target not found')}\n description={tr(\n 'targetDetails.notFoundBody',\n 'The target you requested may have been deleted or is in a different workspace.'\n )}\n />\n );\n }\n\n const lastUpdatedAt = target.updatedAt\n ? new Date(target.updatedAt as string).toLocaleString()\n : '—';\n\n return (\n <MotionPage>\n <PageHeader\n title={target.name}\n description={`${target.type} · ${target.host ?? 'no host'}`}\n breadcrumb={[\n { label: tr('breadcrumbs.targetsAgents', 'Targets & Agents'), path: '../agents' },\n { label: tr('breadcrumbs.targets', 'Targets'), path: '../agents?tab=targets' },\n { label: target.name },\n ]}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <EntityShareButton\n entity=\"target\"\n resource={{ id: target.id, name: target.name }}\n label={tr('targetDetails.share', 'Share')}\n variant=\"button\"\n />\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => navigate('/agents?tab=targets')}\n className=\"max-md:min-h-11\"\n >\n <ArrowLeft className=\"size-3.5\" aria-hidden=\"true\" />\n {tr('common.back', 'Back')}\n </Button>\n </div>\n }\n />\n\n {/* Identity */}\n <Card className=\"mt-4\">\n <div className=\"p-4 border-b border-border-default flex items-center gap-2\">\n <Server className=\"size-5\" aria-hidden=\"true\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('targetDetails.identity', 'Identity')}\n </h3>\n </div>\n <div className=\"p-4 grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm\">\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.name', 'Name')}</p>\n <p className=\"text-text-primary font-medium break-words\">{target.name}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.type', 'Type')}</p>\n <p className=\"text-text-primary font-medium\">{target.type}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.status', 'Status')}</p>\n <StatusBadge status={target.status} />\n </div>\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.host', 'Host')}</p>\n <p className=\"text-text-primary font-mono text-xs break-words\">{target.host ?? '—'}</p>\n </div>\n {target.username && (\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.username', 'Username')}</p>\n <p className=\"text-text-primary font-mono text-xs break-words\">{target.username}</p>\n </div>\n )}\n {target.port != null && (\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.port', 'Port')}</p>\n <p className=\"text-text-primary font-mono text-xs\">{target.port}</p>\n </div>\n )}\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.updatedAt', 'Last updated')}</p>\n <p className=\"text-text-primary text-xs\">{lastUpdatedAt}</p>\n </div>\n {target.tags && target.tags.length > 0 && (\n <div className=\"sm:col-span-2\">\n {/* Legacy free-text labels stored on the target row — titled\n \"Labels\" to disambiguate from the workspace-level tag\n entities managed in the Tags card below. */}\n <p className=\"text-text-muted mb-1\">{tr('targetDetails.labels', 'Labels')}</p>\n <div className=\"flex flex-wrap gap-1\">\n {target.tags.map((tag: string) => (\n <span\n key={tag}\n className=\"px-2 py-0.5 text-xs rounded bg-bg-sunken border border-border-default\"\n >\n {tag}\n </span>\n ))}\n </div>\n </div>\n )}\n </div>\n </Card>\n\n {/* Tags */}\n <Card className=\"mt-4\">\n <div className=\"p-4 border-b border-border-default flex items-center gap-2\">\n <Tag className=\"size-5\" aria-hidden=\"true\" />\n <h3 className=\"font-semibold text-text-primary\">{tr('targetDetails.tags', 'Tags')}</h3>\n </div>\n <div className=\"p-4\">\n <EntityTagPicker entityType=\"VIBECONTROLS_TARGET\" entityId={target.id} />\n </div>\n </Card>\n\n {/* Agents on this target */}\n <Card className=\"mt-4\">\n <div className=\"p-4 border-b border-border-default flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <Cpu className=\"size-5\" aria-hidden=\"true\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('targetDetails.agents', 'Agents on this target')} ({agents.length})\n </h3>\n </div>\n <Link\n to=\"../agents?tab=agents\"\n className=\"text-sm text-text-link hover:text-text-linkHover hover:underline flex items-center gap-1 rounded-button focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n {tr('targetDetails.allAgents', 'All agents')}\n <ExternalLink className=\"size-3\" aria-hidden=\"true\" />\n </Link>\n </div>\n {agents.length === 0 ? (\n <div className=\"p-6\">\n <EmptyState\n icon={<Cpu className=\"size-10 text-text-muted\" aria-hidden=\"true\" />}\n title={tr('targetDetails.noAgents', 'No agents on this target yet')}\n description={tr(\n 'targetDetails.noAgentsBody',\n 'Use the Agents tab to onboard an agent and select this target during setup.'\n )}\n />\n </div>\n ) : (\n <div className=\"p-4 space-y-2\">\n {agents.map((agent: AgentSummary) => {\n const status = getAgentStatus(agent);\n return (\n <Link\n key={agent.id}\n to={`../agents/${agent.id}`}\n className=\"flex items-center justify-between gap-3 p-3 rounded-card border border-border-default hover:bg-bg-sunken/40 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div\n className=\"size-8 rounded-button bg-bg-sunken flex items-center justify-center flex-shrink-0\"\n aria-hidden=\"true\"\n >\n {status === 'ACTIVE' ? (\n <CircleCheck className=\"size-4 text-status-success-text\" />\n ) : (\n <CircleX className=\"size-4 text-text-muted\" />\n )}\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{agent.name}</p>\n <p className=\"text-xs text-text-muted truncate\">\n v{agent.version}\n {agent.tunnelUrl ? ` · ${agent.tunnelUrl}` : ''}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <StatusBadge status={status} />\n <ExternalLink className=\"size-3.5 text-text-muted\" aria-hidden=\"true\" />\n </div>\n </Link>\n );\n })}\n </div>\n )}\n </Card>\n\n {/* Inbound connections */}\n <Card className=\"mt-4\">\n <div className=\"p-4 border-b border-border-default flex items-center gap-2\">\n <Activity className=\"size-5\" aria-hidden=\"true\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('targetDetails.connections', 'Inbound connections')} ({connections.length})\n </h3>\n </div>\n {connections.length === 0 ? (\n <div className=\"p-6\">\n <EmptyState\n icon={<Activity className=\"size-10 text-text-muted\" aria-hidden=\"true\" />}\n title={tr('targetDetails.noConnections', 'No inbound connections')}\n description={tr(\n 'targetDetails.noConnectionsBody',\n 'No agents are currently connected to this target.'\n )}\n />\n </div>\n ) : (\n <div className=\"p-4 space-y-2\">\n {connections.map((conn: ConnectionSummary) => (\n <div\n key={conn.id}\n className=\"flex flex-wrap items-center justify-between gap-3 p-3 rounded-card border border-border-default\"\n >\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {conn.label || `${conn.protocol} connection`}\n </p>\n <p className=\"text-xs text-text-muted truncate\">\n {tr('targetDetails.from', 'From')}{' '}\n <Link\n to={`../agents/${conn.sourceAgent.id}`}\n className=\"text-text-link hover:text-text-linkHover hover:underline rounded-button focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n {conn.sourceAgent.name}\n </Link>{' '}\n · {conn.protocol} · {conn.status}\n </p>\n </div>\n <StatusBadge status={conn.status} />\n </div>\n ))}\n </div>\n )}\n </Card>\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TargetDetailsPage.js","names":[],"sources":["../../src/pages/TargetDetailsPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useParams, Link } from 'react-router-dom';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport {\n ArrowLeft,\n Server,\n CircleCheck,\n CircleX,\n Cpu,\n ExternalLink,\n Activity,\n Tag,\n} from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { PageHeader, Card, StatusBadge, EmptyState, ErrorState } from '@/components/shared';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { EntityShareButton } from '@/components/sharing';\nimport { getAgentStatus } from '@/components/agents/agentStatus';\nimport { useGetTargetQuery } from '@/generated/wspace-operations';\nimport type { GetTargetQuery } from '@/generated/wspace-operations';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { useTr } from '@/shared/hooks/useTr';\n\ntype TargetType = NonNullable<GetTargetQuery['target']>;\ntype AgentSummary = TargetType['agents'][number];\ntype ConnectionSummary = TargetType['inboundConnections'][number];\n\n/**\n * TargetDetailsPage — `/vibecontrols/targets/:id`\n *\n * Mirrors AgentDetailsPage shell. Three sections:\n * 1. Identity card (name, type, status, host, last updated)\n * 2. Agents on this target (table with link to AgentDetailsPage)\n * 3. Inbound agent connections\n *\n * Reuses the existing `GetTarget` query — no schema changes needed; the\n * `TargetFields` fragment already includes the agent list and metadata.\n */\nexport function TargetDetailsPage() {\n const { id } = useParams<{ id: string }>();\n const navigate = useVibeNavigate();\n const tr = useTr();\n // Page-level read gate (the gateway enforces `target:read` on GetTarget).\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(VIBE_PERMISSIONS.TARGET_READ);\n\n const { data, loading, error, refetch } = useGetTargetQuery({\n variables: { id: id ?? '' },\n skip: !id || permissionsLoading || !canRead,\n fetchPolicy: 'cache-and-network',\n });\n\n const target = data?.target ?? null;\n const agents = useMemo(() => target?.agents ?? [], [target]);\n const connections = useMemo(() => target?.inboundConnections ?? [], [target]);\n\n if (!id) {\n return (\n <ErrorState title={tr('targetDetails.missingId', 'Target id missing from URL')} message=\"\" />\n );\n }\n\n if (permissionsLoading) {\n return (\n <MotionPage>\n <PageSkeleton variant=\"detail\" />\n </MotionPage>\n );\n }\n\n if (!canRead) {\n return (\n <AccessDenied\n title={tr('targetDetails.denied.title', 'Access Denied')}\n description={tr(\n 'targetDetails.denied.description',\n 'You do not have permission to view this target.'\n )}\n />\n );\n }\n\n if (loading && !data) {\n return (\n <MotionPage>\n <PageSkeleton variant=\"detail\" />\n </MotionPage>\n );\n }\n\n if (error && !target) {\n return (\n <ErrorState\n title={tr('targetDetails.errorTitle', 'Failed to load target')}\n message={error.message}\n onRetry={() => void refetch()}\n />\n );\n }\n\n if (!target) {\n return (\n <EmptyState\n icon={<Server className=\"size-12 text-text-muted\" aria-hidden=\"true\" />}\n title={tr('targetDetails.notFoundTitle', 'Target not found')}\n description={tr(\n 'targetDetails.notFoundBody',\n 'The target you requested may have been deleted or is in a different workspace.'\n )}\n />\n );\n }\n\n const lastUpdatedAt = target.updatedAt\n ? new Date(target.updatedAt as string).toLocaleString()\n : '—';\n\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-full\">\n <PageHeader\n title={target.name}\n description={`${target.type} · ${target.host ?? 'no host'}`}\n breadcrumb={[\n { label: tr('breadcrumbs.targetsAgents', 'Targets & Agents'), path: '../agents' },\n { label: tr('breadcrumbs.targets', 'Targets'), path: '../agents?tab=targets' },\n { label: target.name },\n ]}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <EntityShareButton\n entity=\"target\"\n resource={{ id: target.id, name: target.name }}\n label={tr('targetDetails.share', 'Share')}\n variant=\"button\"\n />\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => navigate('/agents?tab=targets')}\n className=\"max-md:min-h-11\"\n >\n <ArrowLeft className=\"size-3.5\" aria-hidden=\"true\" />\n {tr('common.back', 'Back')}\n </Button>\n </div>\n }\n />\n\n {/* Identity */}\n <Card>\n <div className=\"p-4 border-b border-border-subtle flex items-center gap-2\">\n <Server className=\"size-5\" aria-hidden=\"true\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('targetDetails.identity', 'Identity')}\n </h3>\n </div>\n <div className=\"p-4 grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm\">\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.name', 'Name')}</p>\n <p className=\"text-text-primary font-medium break-words\">{target.name}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.type', 'Type')}</p>\n <p className=\"text-text-primary font-medium\">{target.type}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.status', 'Status')}</p>\n <StatusBadge status={target.status} />\n </div>\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.host', 'Host')}</p>\n <p className=\"text-text-primary font-mono text-xs break-words\">{target.host ?? '—'}</p>\n </div>\n {target.username && (\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.username', 'Username')}</p>\n <p className=\"text-text-primary font-mono text-xs break-words\">{target.username}</p>\n </div>\n )}\n {target.port != null && (\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.port', 'Port')}</p>\n <p className=\"text-text-primary font-mono text-xs\">{target.port}</p>\n </div>\n )}\n <div>\n <p className=\"text-text-muted\">{tr('targetDetails.updatedAt', 'Last updated')}</p>\n <p className=\"text-text-primary text-xs\">{lastUpdatedAt}</p>\n </div>\n {target.tags && target.tags.length > 0 && (\n <div className=\"sm:col-span-2\">\n {/* Legacy free-text labels stored on the target row — titled\n \"Labels\" to disambiguate from the workspace-level tag\n entities managed in the Tags card below. */}\n <p className=\"text-text-muted mb-1\">{tr('targetDetails.labels', 'Labels')}</p>\n <div className=\"flex flex-wrap gap-1\">\n {target.tags.map((tag: string) => (\n <span\n key={tag}\n className=\"px-2 py-0.5 text-xs rounded-full bg-bg-sunken border border-border-subtle\"\n >\n {tag}\n </span>\n ))}\n </div>\n </div>\n )}\n </div>\n </Card>\n\n {/* Tags */}\n <Card>\n <div className=\"p-4 border-b border-border-subtle flex items-center gap-2\">\n <Tag className=\"size-5\" aria-hidden=\"true\" />\n <h3 className=\"font-semibold text-text-primary\">{tr('targetDetails.tags', 'Tags')}</h3>\n </div>\n <div className=\"p-4\">\n <EntityTagPicker entityType=\"VIBECONTROLS_TARGET\" entityId={target.id} />\n </div>\n </Card>\n\n {/* Agents on this target */}\n <Card>\n <div className=\"p-4 border-b border-border-subtle flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <Cpu className=\"size-5\" aria-hidden=\"true\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('targetDetails.agents', 'Agents on this target')} ({agents.length})\n </h3>\n </div>\n <Link\n to=\"../agents?tab=agents\"\n className=\"text-sm text-text-link hover:text-text-linkHover hover:underline flex items-center gap-1 rounded-button focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n {tr('targetDetails.allAgents', 'All agents')}\n <ExternalLink className=\"size-3\" aria-hidden=\"true\" />\n </Link>\n </div>\n {agents.length === 0 ? (\n <div className=\"p-6\">\n <EmptyState\n icon={<Cpu className=\"size-10 text-text-muted\" aria-hidden=\"true\" />}\n title={tr('targetDetails.noAgents', 'No agents on this target yet')}\n description={tr(\n 'targetDetails.noAgentsBody',\n 'Use the Agents tab to onboard an agent and select this target during setup.'\n )}\n />\n </div>\n ) : (\n <div className=\"p-4 space-y-2\">\n {agents.map((agent: AgentSummary) => {\n const status = getAgentStatus(agent);\n return (\n <Link\n key={agent.id}\n to={`../agents/${agent.id}`}\n className=\"flex items-center justify-between gap-3 p-3 rounded-card border border-border-subtle hover:bg-bg-sunken/40 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div\n className=\"size-8 rounded-button bg-bg-sunken flex items-center justify-center flex-shrink-0\"\n aria-hidden=\"true\"\n >\n {status === 'ACTIVE' ? (\n <CircleCheck className=\"size-4 text-status-success-text\" />\n ) : (\n <CircleX className=\"size-4 text-text-muted\" />\n )}\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{agent.name}</p>\n <p className=\"text-xs text-text-muted truncate\">\n v{agent.version}\n {agent.tunnelUrl ? ` · ${agent.tunnelUrl}` : ''}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <StatusBadge status={status} />\n <ExternalLink className=\"size-3.5 text-text-muted\" aria-hidden=\"true\" />\n </div>\n </Link>\n );\n })}\n </div>\n )}\n </Card>\n\n {/* Inbound connections */}\n <Card>\n <div className=\"p-4 border-b border-border-subtle flex items-center gap-2\">\n <Activity className=\"size-5\" aria-hidden=\"true\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('targetDetails.connections', 'Inbound connections')} ({connections.length})\n </h3>\n </div>\n {connections.length === 0 ? (\n <div className=\"p-6\">\n <EmptyState\n icon={<Activity className=\"size-10 text-text-muted\" aria-hidden=\"true\" />}\n title={tr('targetDetails.noConnections', 'No inbound connections')}\n description={tr(\n 'targetDetails.noConnectionsBody',\n 'No agents are currently connected to this target.'\n )}\n />\n </div>\n ) : (\n <div className=\"p-4 space-y-2\">\n {connections.map((conn: ConnectionSummary) => (\n <div\n key={conn.id}\n className=\"flex flex-wrap items-center justify-between gap-3 p-3 rounded-card border border-border-subtle\"\n >\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {conn.label || `${conn.protocol} connection`}\n </p>\n <p className=\"text-xs text-text-muted truncate\">\n {tr('targetDetails.from', 'From')}{' '}\n <Link\n to={`../agents/${conn.sourceAgent.id}`}\n className=\"text-text-link hover:text-text-linkHover hover:underline rounded-button focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n {conn.sourceAgent.name}\n </Link>{' '}\n · {conn.protocol} · {conn.status}\n </p>\n </div>\n <StatusBadge status={conn.status} />\n </div>\n ))}\n </div>\n )}\n </Card>\n </MotionPage>\n );\n}\n"],"mappings":"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"}