@burdenoff/microfe-vibecontrols 2026.623.2 → 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 (85) hide show
  1. package/dist/components/actions/ActionTypeBadge.js +1 -1
  2. package/dist/components/actions/ActionTypeBadge.js.map +1 -1
  3. package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
  4. package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
  5. package/dist/components/agent-manager/AgentManagerChatPane.js +1 -1
  6. package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
  7. package/dist/components/agent-manager/AgentManagerInputArea.js +1 -1
  8. package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
  9. package/dist/components/agent-manager/BridleConfigPanel.js +2 -2
  10. package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
  11. package/dist/components/agent-manager/ProfilesPanel.js +1 -1
  12. package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
  13. package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
  14. package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
  15. package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
  16. package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
  17. package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
  18. package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
  19. package/dist/components/agent-manager/detail/SessionLogsPanel.js +2 -2
  20. package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
  21. package/dist/components/ai/AIContextsTab.js +1 -1
  22. package/dist/components/ai/AIContextsTab.js.map +1 -1
  23. package/dist/components/ai/AIOverviewTab.js +1 -1
  24. package/dist/components/ai/AIOverviewTab.js.map +1 -1
  25. package/dist/components/ai/AIPromptsTab.js +1 -1
  26. package/dist/components/ai/AIPromptsTab.js.map +1 -1
  27. package/dist/components/ai/AIQueueTab.js +1 -1
  28. package/dist/components/ai/AIQueueTab.js.map +1 -1
  29. package/dist/components/ai/AISessionsTab.js +1 -1
  30. package/dist/components/ai/AISessionsTab.js.map +1 -1
  31. package/dist/components/audit/AuditLogTable.js +1 -1
  32. package/dist/components/audit/AuditLogTable.js.map +1 -1
  33. package/dist/components/docs/DocsPageTree.js +1 -1
  34. package/dist/components/docs/DocsPageTree.js.map +1 -1
  35. package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
  36. package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
  37. package/dist/components/graph/MeshDeployWizard.js +3 -3
  38. package/dist/components/graph/MeshDeployWizard.js.map +1 -1
  39. package/dist/components/graph/MeshRequestPanel.js +2 -2
  40. package/dist/components/graph/MeshRequestPanel.js.map +1 -1
  41. package/dist/components/notes/NoteCard.js +1 -1
  42. package/dist/components/notes/NoteCard.js.map +1 -1
  43. package/dist/components/notes/NoteEditor.js +1 -1
  44. package/dist/components/notes/NoteEditor.js.map +1 -1
  45. package/dist/components/notes/NoteList.js +1 -1
  46. package/dist/components/notes/NoteList.js.map +1 -1
  47. package/dist/components/shared/AgentSetupBanner.js +3 -3
  48. package/dist/components/shared/AgentSetupBanner.js.map +1 -1
  49. package/dist/components/templates/TemplateCard.js +1 -1
  50. package/dist/components/templates/TemplateCard.js.map +1 -1
  51. package/dist/components/vibecalendar/types.js +1 -1
  52. package/dist/components/vibecalendar/types.js.map +1 -1
  53. package/dist/components/vibedeck/VibeDeckButtonForm.js +1 -1
  54. package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
  55. package/dist/components/vibedeck/VibeDeckExecutionLog.js +2 -2
  56. package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
  57. package/dist/components/vibedeck/VibeDeckForm.js +1 -1
  58. package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
  59. package/dist/components/vibedeck/VibeDeckGrid.js +1 -1
  60. package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
  61. package/dist/pages/AgentsPage.js +2 -2
  62. package/dist/pages/AgentsPage.js.map +1 -1
  63. package/dist/pages/CatalogDetailPage.js +1 -1
  64. package/dist/pages/CatalogDetailPage.js.map +1 -1
  65. package/dist/pages/ComponentDetailPage.js +1 -1
  66. package/dist/pages/ComponentDetailPage.js.map +1 -1
  67. package/dist/pages/OverviewPage.js +10 -10
  68. package/dist/pages/OverviewPage.js.map +1 -1
  69. package/dist/pages/PluginsCatalogPage.js +1 -1
  70. package/dist/pages/PluginsCatalogPage.js.map +1 -1
  71. package/dist/pages/SessionsPage.js +3 -3
  72. package/dist/pages/SessionsPage.js.map +1 -1
  73. package/dist/pages/SharedSessionPage.js +2 -2
  74. package/dist/pages/SharedSessionPage.js.map +1 -1
  75. package/dist/pages/TargetsPage.js +2 -2
  76. package/dist/pages/TargetsPage.js.map +1 -1
  77. package/dist/pages/TemplateDetailPage.js +2 -2
  78. package/dist/pages/TemplateDetailPage.js.map +1 -1
  79. package/dist/pages/VibesPage.js +2 -2
  80. package/dist/pages/VibesPage.js.map +1 -1
  81. package/dist/pages/WebhooksPage.js +2 -2
  82. package/dist/pages/WebhooksPage.js.map +1 -1
  83. package/dist/pages/ai/AISessionDetailPage.js +2 -2
  84. package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
  85. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"NoteCard.js","names":[],"sources":["../../../src/components/notes/NoteCard.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n FileText,\n Pin,\n Tag,\n MoreVertical,\n Edit,\n Trash2,\n PinOff,\n Lock,\n Globe,\n Share2,\n} from 'lucide-react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport type { Note } from './types';\n\ninterface NoteCardProps {\n note: Note;\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n /**\n * Generic-sharing entry point: opens the full Share dialog (users / link /\n * vibe). Distinct from `onShare`, which only toggles PRIVATE/WORKSPACE\n * visibility. Provided only when the caller has share permission + the\n * `vibecontrols.sharing.note` flag is on.\n */\n onShareAccess?: (note: Note) => void;\n}\n\nexport function NoteCard({\n note,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n}: NoteCardProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const [showMenu, setShowMenu] = useState(false);\n\n const formatDate = (dateStr: string) => {\n return new Date(dateStr).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n };\n\n const getPreview = (content: string) => {\n // Remove markdown headers and get first 100 chars\n const text = content\n .replace(/^#+\\s+/gm, '')\n .replace(/\\n/g, ' ')\n .trim();\n return text.length > 100 ? text.slice(0, 100) + '...' : text;\n };\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className=\"bg-bg-surface border border-border-default rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer\"\n onClick={() => onOpen?.(note)}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3 min-w-0\">\n <div className=\"flex-shrink-0 size-10 rounded-lg bg-status-warning-bg flex items-center justify-center\">\n <FileText className=\"size-5 text-status-warning-text\" />\n </div>\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary truncate\">{note.title}</h3>\n {note.isPinned && <Pin className=\"size-4 text-status-warning-text flex-shrink-0\" />}\n {note.visibility === 'PRIVATE' ? (\n <span title=\"Private note\">\n <Lock className=\"size-3.5 text-text-muted flex-shrink-0\" />\n </span>\n ) : (\n <span title=\"Workspace note\">\n <Globe className=\"size-3.5 text-text-muted flex-shrink-0\" />\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-secondary\">{formatDate(note.updatedAt)}</p>\n </div>\n </div>\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-36 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {onEdit && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onEdit(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Edit className=\"size-4\" />\n Edit\n </motion.button>\n )}\n {onShare && note.visibility === 'PRIVATE' && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share with workspace\n </motion.button>\n )}\n {onShare && note.visibility === 'WORKSPACE' && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Lock className=\"size-4\" />\n Make private\n </motion.button>\n )}\n {onShareAccess && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n data-testid=\"note-share-access\"\n onClick={(e) => {\n e.stopPropagation();\n onShareAccess(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share access…\n </motion.button>\n )}\n {note.isPinned && onUnpin && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onUnpin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <PinOff className=\"size-4\" />\n Unpin\n </motion.button>\n )}\n {!note.isPinned && onPin && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onPin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Pin className=\"size-4\" />\n Pin\n </motion.button>\n )}\n {onDelete && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onDelete(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n Delete\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n </div>\n\n <p className=\"mt-3 text-sm text-text-secondary line-clamp-2\">{getPreview(note.content)}</p>\n\n {/* Render tagAssignments if available, otherwise fall back to legacy string tags */}\n {note.tagAssignments && note.tagAssignments.length > 0 ? (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {note.tagAssignments.slice(0, 3).map((assignment) => (\n <span\n key={assignment.id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-[var(--color-accent-soft)] text-action-primary-bg rounded\"\n style={\n assignment.tag?.color\n ? { borderLeft: `3px solid ${assignment.tag.color}` }\n : undefined\n }\n >\n <Tag className=\"size-3\" />\n {assignment.tag?.label ?? assignment.tagId.slice(0, 8)}\n </span>\n ))}\n {note.tagAssignments.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tagAssignments.length - 3}\n </span>\n )}\n </div>\n ) : note.tags && note.tags.length > 0 ? (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {note.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n <Tag className=\"size-3\" />\n {tag}\n </span>\n ))}\n {note.tags.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tags.length - 3}\n </span>\n )}\n </div>\n ) : null}\n </motion.div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"NoteCard.js","names":[],"sources":["../../../src/components/notes/NoteCard.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n FileText,\n Pin,\n Tag,\n MoreVertical,\n Edit,\n Trash2,\n PinOff,\n Lock,\n Globe,\n Share2,\n} from 'lucide-react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport type { Note } from './types';\n\ninterface NoteCardProps {\n note: Note;\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n /**\n * Generic-sharing entry point: opens the full Share dialog (users / link /\n * vibe). Distinct from `onShare`, which only toggles PRIVATE/WORKSPACE\n * visibility. Provided only when the caller has share permission + the\n * `vibecontrols.sharing.note` flag is on.\n */\n onShareAccess?: (note: Note) => void;\n}\n\nexport function NoteCard({\n note,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n}: NoteCardProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const [showMenu, setShowMenu] = useState(false);\n\n const formatDate = (dateStr: string) => {\n return new Date(dateStr).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n };\n\n const getPreview = (content: string) => {\n // Remove markdown headers and get first 100 chars\n const text = content\n .replace(/^#+\\s+/gm, '')\n .replace(/\\n/g, ' ')\n .trim();\n return text.length > 100 ? text.slice(0, 100) + '...' : text;\n };\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className=\"bg-bg-surface border border-border-default rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer\"\n onClick={() => onOpen?.(note)}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3 min-w-0\">\n <div className=\"flex-shrink-0 size-10 rounded-lg bg-status-warning-bg flex items-center justify-center\">\n <FileText className=\"size-5 text-status-warning-text\" />\n </div>\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary truncate\">{note.title}</h3>\n {note.isPinned && <Pin className=\"size-4 text-status-warning-text flex-shrink-0\" />}\n {note.visibility === 'PRIVATE' ? (\n <span title=\"Private note\">\n <Lock className=\"size-3.5 text-text-muted flex-shrink-0\" />\n </span>\n ) : (\n <span title=\"Workspace note\">\n <Globe className=\"size-3.5 text-text-muted flex-shrink-0\" />\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-secondary\">{formatDate(note.updatedAt)}</p>\n </div>\n </div>\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-36 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {onEdit && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onEdit(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Edit className=\"size-4\" />\n Edit\n </motion.button>\n )}\n {onShare && note.visibility === 'PRIVATE' && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share with workspace\n </motion.button>\n )}\n {onShare && note.visibility === 'WORKSPACE' && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Lock className=\"size-4\" />\n Make private\n </motion.button>\n )}\n {onShareAccess && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n data-testid=\"note-share-access\"\n onClick={(e) => {\n e.stopPropagation();\n onShareAccess(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share access…\n </motion.button>\n )}\n {note.isPinned && onUnpin && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onUnpin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <PinOff className=\"size-4\" />\n Unpin\n </motion.button>\n )}\n {!note.isPinned && onPin && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onPin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Pin className=\"size-4\" />\n Pin\n </motion.button>\n )}\n {onDelete && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onDelete(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n Delete\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n </div>\n\n <p className=\"mt-3 text-sm text-text-secondary line-clamp-2\">{getPreview(note.content)}</p>\n\n {/* Render tagAssignments if available, otherwise fall back to legacy string tags */}\n {note.tagAssignments && note.tagAssignments.length > 0 ? (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {note.tagAssignments.slice(0, 3).map((assignment) => (\n <span\n key={assignment.id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-[var(--color-accent-soft)] text-text-primary rounded\"\n style={\n assignment.tag?.color\n ? { borderLeft: `3px solid ${assignment.tag.color}` }\n : undefined\n }\n >\n <Tag className=\"size-3\" />\n {assignment.tag?.label ?? assignment.tagId.slice(0, 8)}\n </span>\n ))}\n {note.tagAssignments.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tagAssignments.length - 3}\n </span>\n )}\n </div>\n ) : note.tags && note.tags.length > 0 ? (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {note.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n <Tag className=\"size-3\" />\n {tag}\n </span>\n ))}\n {note.tags.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tags.length - 3}\n </span>\n )}\n </div>\n ) : null}\n </motion.div>\n );\n}\n"],"mappings":"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"}
@@ -186,7 +186,7 @@ function v({ note: v, vibeOptions: y = [], defaultVibeId: b, hideVibeSelect: x,
186
186
  /* @__PURE__ */ g("div", {
187
187
  className: "flex flex-wrap gap-1.5 min-h-[38px] px-3 py-2 bg-bg-sunken border border-border-default rounded-md",
188
188
  children: [z.filter((e) => j.has(e.id)).map((e) => /* @__PURE__ */ g("span", {
189
- className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-[var(--color-accent-soft)] text-action-primary-bg rounded",
189
+ className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-[var(--color-accent-soft)] text-text-primary rounded",
190
190
  children: [
191
191
  /* @__PURE__ */ h(p, { className: "size-3" }),
192
192
  e.label,
@@ -1 +1 @@
1
- {"version":3,"file":"NoteEditor.js","names":[],"sources":["../../../src/components/notes/NoteEditor.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { X, Save, Eye, Edit, Tag, Check, Plus, Lock, Globe } from 'lucide-react';\nimport type { CreateNoteInput, Note, NoteTagAssignment, NoteVisibility } from './types';\nimport { useAvailableNoteTagsQuery, useCreateNoteTagMutation } from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useVibeControls } from '../../providers/VibeControlsProvider';\n\n/** Tag selected in the editor */\nexport interface SelectedTag {\n id: string;\n key: string;\n label: string;\n color: string | null;\n}\n\n/** Note data submitted from the editor — includes `id` and selected tag IDs */\nexport type NoteEditorSubmitData = CreateNoteInput & {\n id?: string;\n selectedTagIds?: string[];\n visibility?: NoteVisibility;\n};\n\nconst TAG_COLORS = [\n 'var(--color-status-error-text)',\n 'var(--color-status-warning-text)',\n 'var(--color-status-success-text)',\n 'var(--color-accent-teal)',\n 'var(--color-text-link)',\n 'var(--color-accent-purple)',\n 'var(--color-text-tertiary)',\n];\n\ninterface NoteEditorProps {\n note?: Note;\n vibeOptions?: { id: string; name: string }[];\n /** Pre-set vibeId when creating a new note (used in vibe-scoped context) */\n defaultVibeId?: string;\n /** Hide the vibe dropdown (used when notes are scoped to a single vibe) */\n hideVibeSelect?: boolean;\n onSubmit: (data: NoteEditorSubmitData) => void;\n onCancel: () => void;\n isLoading?: boolean;\n}\n\nexport function NoteEditor({\n note,\n vibeOptions = [],\n defaultVibeId,\n hideVibeSelect,\n onSubmit,\n onCancel,\n isLoading,\n}: NoteEditorProps) {\n const tr = useTr();\n const { workspaceId } = useVibeControls();\n const [isPreview, setIsPreview] = useState(false);\n const [formData, setFormData] = useState({\n title: note?.title || '',\n content: note?.content || '',\n vibeId: note?.vibeId || defaultVibeId || '',\n isPinned: note?.isPinned ?? false,\n visibility: (note?.visibility ?? 'WORKSPACE') as NoteVisibility,\n });\n\n // Tag picker state\n const [selectedTagIds, setSelectedTagIds] = useState<Set<string>>(new Set());\n const [tagsInitialized, setTagsInitialized] = useState(false);\n const [showTagPicker, setShowTagPicker] = useState(false);\n\n // Fetch available tags from wspace-tags-svc\n const { data: tagsData, refetch: refetchTags } = useAvailableNoteTagsQuery();\n const availableTags: SelectedTag[] = (tagsData?.availableNoteTags ?? []).map(\n (t: { id: string; key: string; label: string; color?: string | null }) => ({\n id: t.id,\n key: t.key,\n label: t.label,\n color: t.color ?? null,\n })\n );\n\n // Initialize selected tags once available tags load\n useEffect(() => {\n if (tagsInitialized || availableTags.length === 0) return;\n const ids = new Set<string>();\n\n // First: pre-select from tagAssignments (tags service data)\n if (note?.tagAssignments && note.tagAssignments.length > 0) {\n note.tagAssignments.forEach((a: NoteTagAssignment) => ids.add(a.tagId));\n }\n // Fallback: match legacy string tags by label/key to available tags\n else if (note?.tags && note.tags.length > 0) {\n for (const legacyTag of note.tags) {\n const match = availableTags.find(\n (t) =>\n t.label.toLowerCase() === legacyTag.toLowerCase() || t.key === legacyTag.toLowerCase()\n );\n if (match) ids.add(match.id);\n }\n }\n\n if (ids.size > 0) setSelectedTagIds(ids);\n setTagsInitialized(true);\n }, [availableTags, note, tagsInitialized]);\n\n // Inline tag creation\n const [createNoteTag] = useCreateNoteTagMutation();\n const [newTagLabel, setNewTagLabel] = useState('');\n const [newTagColor, setNewTagColor] = useState('var(--color-accent-purple)');\n const [isCreatingTag, setIsCreatingTag] = useState(false);\n\n const handleCreateTag = async () => {\n const label = newTagLabel.trim();\n if (!label || isCreatingTag) return;\n setIsCreatingTag(true);\n try {\n const result = await createNoteTag({\n variables: { input: { label, color: newTagColor } },\n });\n const newTag = result.data?.createNoteTag;\n if (newTag) {\n setSelectedTagIds((prev) => new Set(prev).add(newTag.id));\n await refetchTags();\n }\n setNewTagLabel('');\n } catch (err) {\n console.error('Failed to create tag:', err);\n } finally {\n setIsCreatingTag(false);\n }\n };\n\n const toggleTag = (tagId: string) => {\n setSelectedTagIds((prev) => {\n const next = new Set(prev);\n if (next.has(tagId)) {\n next.delete(tagId);\n } else {\n next.add(tagId);\n }\n return next;\n });\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const data: NoteEditorSubmitData = {\n ...(note?.id ? { id: note.id } : {}),\n workspaceId: workspaceId ?? '',\n title: formData.title,\n content: formData.content,\n vibeId: formData.vibeId || undefined,\n isPinned: formData.isPinned,\n visibility: formData.visibility,\n selectedTagIds: Array.from(selectedTagIds),\n };\n onSubmit(data);\n };\n\n // Enhanced markdown preview with inline formatting support\n const parseInlineFormatting = (text: string): React.ReactNode[] => {\n const parts: React.ReactNode[] = [];\n let remaining = text;\n let key = 0;\n\n while (remaining.length > 0) {\n // Bold: **text** or __text__\n const boldMatch = remaining.match(/^(\\*\\*|__)(.+?)\\1/);\n if (boldMatch) {\n parts.push(<strong key={key++}>{parseInlineFormatting(boldMatch[2])}</strong>);\n remaining = remaining.slice(boldMatch[0].length);\n continue;\n }\n\n // Italic: *text* or _text_\n const italicMatch = remaining.match(/^(\\*|_)([^*_]+?)\\1/);\n if (italicMatch) {\n parts.push(<em key={key++}>{parseInlineFormatting(italicMatch[2])}</em>);\n remaining = remaining.slice(italicMatch[0].length);\n continue;\n }\n\n // Strikethrough: ~~text~~\n const strikeMatch = remaining.match(/^~~(.+?)~~/);\n if (strikeMatch) {\n parts.push(<del key={key++}>{parseInlineFormatting(strikeMatch[1])}</del>);\n remaining = remaining.slice(strikeMatch[0].length);\n continue;\n }\n\n // Inline code: `code`\n const codeMatch = remaining.match(/^`([^`]+)`/);\n if (codeMatch) {\n parts.push(\n <code\n key={key++}\n className=\"bg-bg-elevated px-1.5 py-0.5 rounded text-sm font-mono text-text-primary\"\n >\n {codeMatch[1]}\n </code>\n );\n remaining = remaining.slice(codeMatch[0].length);\n continue;\n }\n\n // Links: [text](url) - only allow safe URL schemes to prevent XSS\n const linkMatch = remaining.match(/^\\[([^\\]]+)\\]\\(([^)]+)\\)/);\n if (linkMatch) {\n const url = linkMatch[2];\n // Only allow http, https, mailto, relative paths, and hash links to prevent XSS via javascript: URLs\n const isSafeUrl = /^(https?:\\/\\/|mailto:|\\/|#)/i.test(url);\n if (isSafeUrl) {\n parts.push(\n <a\n key={key++}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-action-primary-bg hover:underline\"\n >\n {linkMatch[1]}\n </a>\n );\n } else {\n // Render unsafe links as plain text with muted styling\n parts.push(\n <span key={key++} className=\"text-text-secondary\">\n {linkMatch[1]}\n </span>\n );\n }\n remaining = remaining.slice(linkMatch[0].length);\n continue;\n }\n\n // No match - take the next character\n parts.push(remaining[0]);\n remaining = remaining.slice(1);\n }\n\n return parts;\n };\n\n // Parse markdown content into React elements\n const renderMarkdown = (text: string) => {\n const lines = text.split('\\n');\n const elements: React.ReactNode[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n // Code blocks: ```\n if (line.startsWith('```')) {\n const language = line.slice(3).trim();\n const codeLines: string[] = [];\n i++;\n while (i < lines.length && !lines[i].startsWith('```')) {\n codeLines.push(lines[i]);\n i++;\n }\n elements.push(\n <pre key={elements.length} className=\"bg-bg-elevated rounded-md p-3 my-2 overflow-x-auto\">\n <code className=\"text-sm font-mono text-text-primary\">\n {language && (\n <span className=\"text-xs text-text-secondary block mb-2\">{language}</span>\n )}\n {codeLines.join('\\n')}\n </code>\n </pre>\n );\n i++;\n continue;\n }\n\n // Headings\n if (line.startsWith('# ')) {\n elements.push(\n <h1 key={elements.length} className=\"text-2xl font-bold mb-2 mt-4\">\n {parseInlineFormatting(line.slice(2))}\n </h1>\n );\n i++;\n continue;\n }\n if (line.startsWith('## ')) {\n elements.push(\n <h2 key={elements.length} className=\"text-xl font-bold mb-2 mt-3\">\n {parseInlineFormatting(line.slice(3))}\n </h2>\n );\n i++;\n continue;\n }\n if (line.startsWith('### ')) {\n elements.push(\n <h3 key={elements.length} className=\"text-lg font-bold mb-2 mt-2\">\n {parseInlineFormatting(line.slice(4))}\n </h3>\n );\n i++;\n continue;\n }\n\n // Blockquotes: > text\n if (line.startsWith('> ')) {\n elements.push(\n <blockquote\n key={elements.length}\n className=\"border-l-4 border-border-default pl-4 my-2 italic text-text-secondary\"\n >\n {parseInlineFormatting(line.slice(2))}\n </blockquote>\n );\n i++;\n continue;\n }\n\n // Unordered list: - or * (collect consecutive items and wrap in <ul>)\n if (line.match(/^[-*] /)) {\n const listItems: React.ReactNode[] = [];\n while (i < lines.length && lines[i].match(/^[-*] /)) {\n listItems.push(\n <li key={listItems.length} className=\"list-disc\">\n {parseInlineFormatting(lines[i].slice(2))}\n </li>\n );\n i++;\n }\n elements.push(\n <ul key={elements.length} className=\"ml-6 my-2\">\n {listItems}\n </ul>\n );\n continue;\n }\n\n // Ordered list: 1. 2. etc. (collect consecutive items and wrap in <ol>)\n const orderedMatch = line.match(/^(\\d+)\\. /);\n if (orderedMatch) {\n const listItems: React.ReactNode[] = [];\n while (i < lines.length) {\n const olMatch = lines[i].match(/^(\\d+)\\. /);\n if (!olMatch) break;\n listItems.push(\n <li key={listItems.length} className=\"list-decimal\">\n {parseInlineFormatting(lines[i].slice(olMatch[0].length))}\n </li>\n );\n i++;\n }\n elements.push(\n <ol key={elements.length} className=\"ml-6 my-2\">\n {listItems}\n </ol>\n );\n continue;\n }\n\n // Horizontal rule: --- or ***\n if (line.match(/^(-{3,}|\\*{3,})$/)) {\n elements.push(<hr key={elements.length} className=\"my-4 border-border-default\" />);\n i++;\n continue;\n }\n\n // Table detection (simple): | col1 | col2 |\n if (line.startsWith('|') && line.endsWith('|')) {\n const tableLines: string[] = [line];\n i++;\n while (i < lines.length && lines[i].startsWith('|') && lines[i].endsWith('|')) {\n tableLines.push(lines[i]);\n i++;\n }\n\n // Parse table\n const rows = tableLines\n .filter((l) => !l.match(/^\\|[\\s\\-:|]+\\|$/)) // Skip separator rows (including alignment markers like :---:)\n .map((l) =>\n l\n .slice(1, -1)\n .split('|')\n .map((cell) => cell.trim())\n );\n\n if (rows.length > 0) {\n const [header, ...body] = rows;\n elements.push(\n <table key={elements.length} className=\"w-full my-2 border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-default\">\n {header.map((cell, ci) => (\n <th key={ci} className=\"text-left py-2 px-3 font-semibold text-text-primary\">\n {parseInlineFormatting(cell)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {body.map((row, ri) => (\n <tr key={ri} className=\"border-b border-border-subtle\">\n {row.map((cell, ci) => (\n <td key={ci} className=\"py-2 px-3 text-text-primary\">\n {parseInlineFormatting(cell)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n );\n }\n continue;\n }\n\n // Empty line\n if (line === '') {\n elements.push(<br key={elements.length} />);\n i++;\n continue;\n }\n\n // Regular paragraph with inline formatting\n elements.push(\n <p key={elements.length} className=\"mb-1\">\n {parseInlineFormatting(line)}\n </p>\n );\n i++;\n }\n\n return elements;\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay\">\n <div className=\"w-full max-w-3xl bg-bg-surface rounded-lg shadow-xl max-h-[90vh] flex flex-col\">\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {note\n ? tr('vibecontrols.notes.editNote', 'Edit Note')\n : tr('vibecontrols.notes.newNote', 'New Note')}\n </h2>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setIsPreview(!isPreview)}\n className={`inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded-md transition-colors ${\n isPreview\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-elevated'\n }`}\n >\n {isPreview ? <Edit className=\"size-4\" /> : <Eye className=\"size-4\" />}\n {isPreview\n ? tr('vibecontrols.notes.edit', 'Edit')\n : tr('vibecontrols.notes.preview', 'Preview')}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-5 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n <form onSubmit={handleSubmit} className=\"flex-1 flex flex-col overflow-hidden\">\n <div className=\"p-6 space-y-4 flex-1 overflow-y-auto\">\n <div>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={formData.title}\n onChange={(e) => setFormData({ ...formData, title: e.target.value })}\n placeholder={tr('vibecontrols.notes.titlePlaceholder', 'Note title...')}\n required\n className=\"w-full px-0 py-2 text-lg sm:text-2xl font-bold bg-transparent border-0 border-b border-border-default text-text-primary placeholder:text-text-placeholder focus:outline-none focus:border-action-primary-bg\"\n />\n </div>\n\n <div className={`grid ${hideVibeSelect ? 'grid-cols-1' : 'grid-cols-2'} gap-4`}>\n {!hideVibeSelect && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.notes.relatedVibe', 'Related Vibe (optional)')}\n </label>\n <select\n value={formData.vibeId}\n onChange={(e) => setFormData({ ...formData, vibeId: e.target.value })}\n className=\"w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">No vibe</option>\n {vibeOptions.map((vibe) => (\n <option key={vibe.id} value={vibe.id}>\n {vibe.name}\n </option>\n ))}\n </select>\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.notes.tags', 'Tags')}\n </label>\n {/* Selected tags */}\n <div className=\"flex flex-wrap gap-1.5 min-h-[38px] px-3 py-2 bg-bg-sunken border border-border-default rounded-md\">\n {availableTags\n .filter((t) => selectedTagIds.has(t.id))\n .map((tag) => (\n <span\n key={tag.id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-[var(--color-accent-soft)] text-action-primary-bg rounded\"\n >\n <Tag className=\"size-3\" />\n {tag.label}\n <button\n type=\"button\"\n onClick={() => toggleTag(tag.id)}\n className=\"ml-0.5 hover:text-status-error-text\"\n >\n <X className=\"size-3\" />\n </button>\n </span>\n ))}\n <button\n type=\"button\"\n onClick={() => setShowTagPicker(!showTagPicker)}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs text-text-secondary hover:text-text-primary hover:bg-bg-elevated rounded transition-colors\"\n >\n <Tag className=\"size-3\" />\n {selectedTagIds.size === 0 ? 'Add tags...' : '+'}\n </button>\n </div>\n {/* Tag picker dropdown */}\n {showTagPicker && (\n <div className=\"mt-1 bg-bg-surface border border-border-default rounded-md shadow-lg\">\n {/* Inline create: text input + color picker */}\n <div className=\"flex items-center gap-2 p-2 border-b border-border-subtle\">\n <span\n aria-hidden=\"true\"\n className=\"size-6 rounded-full border-2 border-border-default\"\n style={{ backgroundColor: newTagColor }}\n />\n <input\n aria-label=\"New tag name... (Enter to create)\"\n type=\"text\"\n value={newTagLabel}\n onChange={(e) => setNewTagLabel(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleCreateTag();\n }\n }}\n placeholder=\"New tag name... (Enter to create)\"\n disabled={isCreatingTag}\n className=\"flex-1 px-2 py-1 text-sm bg-bg-sunken border border-border-default rounded text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n />\n <button\n type=\"button\"\n onClick={handleCreateTag}\n disabled={!newTagLabel.trim() || isCreatingTag}\n className=\"p-1 text-action-primary-bg hover:bg-[var(--color-accent-soft)] rounded transition-colors disabled:opacity-30\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n {/* Quick color palette */}\n <div className=\"flex items-center gap-1 px-2 py-1.5 border-b border-border-subtle\">\n {TAG_COLORS.map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => setNewTagColor(c)}\n className={`size-4 rounded-full transition-transform ${newTagColor === c ? 'ring-2 ring-action-primary-bg scale-125' : 'hover:scale-110'}`}\n style={{ backgroundColor: c }}\n />\n ))}\n </div>\n {/* Existing tags list */}\n <div className=\"max-h-32 overflow-y-auto p-1\">\n {availableTags.length === 0 ? (\n <p className=\"text-xs text-text-muted px-2 py-1\">\n No tags yet — create one above\n </p>\n ) : (\n availableTags.map((tag) => (\n <button\n key={tag.id}\n type=\"button\"\n onClick={() => toggleTag(tag.id)}\n className=\"flex items-center justify-between w-full px-2 py-1.5 text-sm text-text-primary hover:bg-bg-sunken rounded transition-colors\"\n >\n <span className=\"flex items-center gap-2\">\n {tag.color && (\n <span\n className=\"size-2.5 rounded-full\"\n style={{ backgroundColor: tag.color }}\n />\n )}\n {tag.label}\n </span>\n {selectedTagIds.has(tag.id) && (\n <Check className=\"size-4 text-action-primary-bg\" />\n )}\n </button>\n ))\n )}\n </div>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"flex-1 min-h-[300px]\">\n {isPreview ? (\n <div className=\"h-full p-4 bg-bg-sunken rounded-md prose prose-sm dark:prose-invert max-w-none overflow-y-auto\">\n {renderMarkdown(formData.content)}\n </div>\n ) : (\n <textarea\n aria-label=\"control\"\n value={formData.content}\n onChange={(e) => setFormData({ ...formData, content: e.target.value })}\n placeholder={tr(\n 'vibecontrols.notes.contentPlaceholder',\n 'Write your note in Markdown...'\n )}\n className=\"w-full h-full min-h-[300px] px-3 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 resize-none font-mono\"\n />\n )}\n </div>\n\n <div className=\"flex items-center gap-4 flex-wrap\">\n <div className=\"flex items-center gap-2\">\n <input\n aria-label=\"isPinned\"\n type=\"checkbox\"\n id=\"isPinned\"\n checked={formData.isPinned}\n onChange={(e) => setFormData({ ...formData, isPinned: e.target.checked })}\n className=\"size-4 rounded border-border-default text-action-primary-bg focus:ring-action-primary-bg\"\n />\n <label htmlFor=\"isPinned\" className=\"text-sm text-text-primary\">\n {tr('vibecontrols.notes.pinThisNote', 'Pin this note')}\n </label>\n </div>\n\n {/* Visibility toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-lg p-0.5\">\n <button\n type=\"button\"\n onClick={() => setFormData({ ...formData, visibility: 'WORKSPACE' })}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${\n formData.visibility === 'WORKSPACE'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-secondary'\n }`}\n >\n <Globe className=\"size-3.5\" />\n Workspace\n </button>\n <button\n type=\"button\"\n onClick={() => setFormData({ ...formData, visibility: 'PRIVATE' })}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${\n formData.visibility === 'PRIVATE'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-secondary'\n }`}\n >\n <Lock className=\"size-3.5\" />\n Private\n </button>\n </div>\n {formData.visibility === 'PRIVATE' && (\n <p className=\"text-xs text-text-muted w-full -mt-2\">Only you can see this note.</p>\n )}\n </div>\n </div>\n\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border-default bg-bg-sunken\">\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-elevated transition-colors disabled:opacity-50\"\n >\n {tr('vibecontrols.notes.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading}\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 <Save className=\"size-4\" />\n {isLoading\n ? tr('vibecontrols.notes.saving', 'Saving...')\n : tr('vibecontrols.notes.saveNote', 'Save Note')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"NoteEditor.js","names":[],"sources":["../../../src/components/notes/NoteEditor.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { X, Save, Eye, Edit, Tag, Check, Plus, Lock, Globe } from 'lucide-react';\nimport type { CreateNoteInput, Note, NoteTagAssignment, NoteVisibility } from './types';\nimport { useAvailableNoteTagsQuery, useCreateNoteTagMutation } from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useVibeControls } from '../../providers/VibeControlsProvider';\n\n/** Tag selected in the editor */\nexport interface SelectedTag {\n id: string;\n key: string;\n label: string;\n color: string | null;\n}\n\n/** Note data submitted from the editor — includes `id` and selected tag IDs */\nexport type NoteEditorSubmitData = CreateNoteInput & {\n id?: string;\n selectedTagIds?: string[];\n visibility?: NoteVisibility;\n};\n\nconst TAG_COLORS = [\n 'var(--color-status-error-text)',\n 'var(--color-status-warning-text)',\n 'var(--color-status-success-text)',\n 'var(--color-accent-teal)',\n 'var(--color-text-link)',\n 'var(--color-accent-purple)',\n 'var(--color-text-tertiary)',\n];\n\ninterface NoteEditorProps {\n note?: Note;\n vibeOptions?: { id: string; name: string }[];\n /** Pre-set vibeId when creating a new note (used in vibe-scoped context) */\n defaultVibeId?: string;\n /** Hide the vibe dropdown (used when notes are scoped to a single vibe) */\n hideVibeSelect?: boolean;\n onSubmit: (data: NoteEditorSubmitData) => void;\n onCancel: () => void;\n isLoading?: boolean;\n}\n\nexport function NoteEditor({\n note,\n vibeOptions = [],\n defaultVibeId,\n hideVibeSelect,\n onSubmit,\n onCancel,\n isLoading,\n}: NoteEditorProps) {\n const tr = useTr();\n const { workspaceId } = useVibeControls();\n const [isPreview, setIsPreview] = useState(false);\n const [formData, setFormData] = useState({\n title: note?.title || '',\n content: note?.content || '',\n vibeId: note?.vibeId || defaultVibeId || '',\n isPinned: note?.isPinned ?? false,\n visibility: (note?.visibility ?? 'WORKSPACE') as NoteVisibility,\n });\n\n // Tag picker state\n const [selectedTagIds, setSelectedTagIds] = useState<Set<string>>(new Set());\n const [tagsInitialized, setTagsInitialized] = useState(false);\n const [showTagPicker, setShowTagPicker] = useState(false);\n\n // Fetch available tags from wspace-tags-svc\n const { data: tagsData, refetch: refetchTags } = useAvailableNoteTagsQuery();\n const availableTags: SelectedTag[] = (tagsData?.availableNoteTags ?? []).map(\n (t: { id: string; key: string; label: string; color?: string | null }) => ({\n id: t.id,\n key: t.key,\n label: t.label,\n color: t.color ?? null,\n })\n );\n\n // Initialize selected tags once available tags load\n useEffect(() => {\n if (tagsInitialized || availableTags.length === 0) return;\n const ids = new Set<string>();\n\n // First: pre-select from tagAssignments (tags service data)\n if (note?.tagAssignments && note.tagAssignments.length > 0) {\n note.tagAssignments.forEach((a: NoteTagAssignment) => ids.add(a.tagId));\n }\n // Fallback: match legacy string tags by label/key to available tags\n else if (note?.tags && note.tags.length > 0) {\n for (const legacyTag of note.tags) {\n const match = availableTags.find(\n (t) =>\n t.label.toLowerCase() === legacyTag.toLowerCase() || t.key === legacyTag.toLowerCase()\n );\n if (match) ids.add(match.id);\n }\n }\n\n if (ids.size > 0) setSelectedTagIds(ids);\n setTagsInitialized(true);\n }, [availableTags, note, tagsInitialized]);\n\n // Inline tag creation\n const [createNoteTag] = useCreateNoteTagMutation();\n const [newTagLabel, setNewTagLabel] = useState('');\n const [newTagColor, setNewTagColor] = useState('var(--color-accent-purple)');\n const [isCreatingTag, setIsCreatingTag] = useState(false);\n\n const handleCreateTag = async () => {\n const label = newTagLabel.trim();\n if (!label || isCreatingTag) return;\n setIsCreatingTag(true);\n try {\n const result = await createNoteTag({\n variables: { input: { label, color: newTagColor } },\n });\n const newTag = result.data?.createNoteTag;\n if (newTag) {\n setSelectedTagIds((prev) => new Set(prev).add(newTag.id));\n await refetchTags();\n }\n setNewTagLabel('');\n } catch (err) {\n console.error('Failed to create tag:', err);\n } finally {\n setIsCreatingTag(false);\n }\n };\n\n const toggleTag = (tagId: string) => {\n setSelectedTagIds((prev) => {\n const next = new Set(prev);\n if (next.has(tagId)) {\n next.delete(tagId);\n } else {\n next.add(tagId);\n }\n return next;\n });\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const data: NoteEditorSubmitData = {\n ...(note?.id ? { id: note.id } : {}),\n workspaceId: workspaceId ?? '',\n title: formData.title,\n content: formData.content,\n vibeId: formData.vibeId || undefined,\n isPinned: formData.isPinned,\n visibility: formData.visibility,\n selectedTagIds: Array.from(selectedTagIds),\n };\n onSubmit(data);\n };\n\n // Enhanced markdown preview with inline formatting support\n const parseInlineFormatting = (text: string): React.ReactNode[] => {\n const parts: React.ReactNode[] = [];\n let remaining = text;\n let key = 0;\n\n while (remaining.length > 0) {\n // Bold: **text** or __text__\n const boldMatch = remaining.match(/^(\\*\\*|__)(.+?)\\1/);\n if (boldMatch) {\n parts.push(<strong key={key++}>{parseInlineFormatting(boldMatch[2])}</strong>);\n remaining = remaining.slice(boldMatch[0].length);\n continue;\n }\n\n // Italic: *text* or _text_\n const italicMatch = remaining.match(/^(\\*|_)([^*_]+?)\\1/);\n if (italicMatch) {\n parts.push(<em key={key++}>{parseInlineFormatting(italicMatch[2])}</em>);\n remaining = remaining.slice(italicMatch[0].length);\n continue;\n }\n\n // Strikethrough: ~~text~~\n const strikeMatch = remaining.match(/^~~(.+?)~~/);\n if (strikeMatch) {\n parts.push(<del key={key++}>{parseInlineFormatting(strikeMatch[1])}</del>);\n remaining = remaining.slice(strikeMatch[0].length);\n continue;\n }\n\n // Inline code: `code`\n const codeMatch = remaining.match(/^`([^`]+)`/);\n if (codeMatch) {\n parts.push(\n <code\n key={key++}\n className=\"bg-bg-elevated px-1.5 py-0.5 rounded text-sm font-mono text-text-primary\"\n >\n {codeMatch[1]}\n </code>\n );\n remaining = remaining.slice(codeMatch[0].length);\n continue;\n }\n\n // Links: [text](url) - only allow safe URL schemes to prevent XSS\n const linkMatch = remaining.match(/^\\[([^\\]]+)\\]\\(([^)]+)\\)/);\n if (linkMatch) {\n const url = linkMatch[2];\n // Only allow http, https, mailto, relative paths, and hash links to prevent XSS via javascript: URLs\n const isSafeUrl = /^(https?:\\/\\/|mailto:|\\/|#)/i.test(url);\n if (isSafeUrl) {\n parts.push(\n <a\n key={key++}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-action-primary-bg hover:underline\"\n >\n {linkMatch[1]}\n </a>\n );\n } else {\n // Render unsafe links as plain text with muted styling\n parts.push(\n <span key={key++} className=\"text-text-secondary\">\n {linkMatch[1]}\n </span>\n );\n }\n remaining = remaining.slice(linkMatch[0].length);\n continue;\n }\n\n // No match - take the next character\n parts.push(remaining[0]);\n remaining = remaining.slice(1);\n }\n\n return parts;\n };\n\n // Parse markdown content into React elements\n const renderMarkdown = (text: string) => {\n const lines = text.split('\\n');\n const elements: React.ReactNode[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n // Code blocks: ```\n if (line.startsWith('```')) {\n const language = line.slice(3).trim();\n const codeLines: string[] = [];\n i++;\n while (i < lines.length && !lines[i].startsWith('```')) {\n codeLines.push(lines[i]);\n i++;\n }\n elements.push(\n <pre key={elements.length} className=\"bg-bg-elevated rounded-md p-3 my-2 overflow-x-auto\">\n <code className=\"text-sm font-mono text-text-primary\">\n {language && (\n <span className=\"text-xs text-text-secondary block mb-2\">{language}</span>\n )}\n {codeLines.join('\\n')}\n </code>\n </pre>\n );\n i++;\n continue;\n }\n\n // Headings\n if (line.startsWith('# ')) {\n elements.push(\n <h1 key={elements.length} className=\"text-2xl font-bold mb-2 mt-4\">\n {parseInlineFormatting(line.slice(2))}\n </h1>\n );\n i++;\n continue;\n }\n if (line.startsWith('## ')) {\n elements.push(\n <h2 key={elements.length} className=\"text-xl font-bold mb-2 mt-3\">\n {parseInlineFormatting(line.slice(3))}\n </h2>\n );\n i++;\n continue;\n }\n if (line.startsWith('### ')) {\n elements.push(\n <h3 key={elements.length} className=\"text-lg font-bold mb-2 mt-2\">\n {parseInlineFormatting(line.slice(4))}\n </h3>\n );\n i++;\n continue;\n }\n\n // Blockquotes: > text\n if (line.startsWith('> ')) {\n elements.push(\n <blockquote\n key={elements.length}\n className=\"border-l-4 border-border-default pl-4 my-2 italic text-text-secondary\"\n >\n {parseInlineFormatting(line.slice(2))}\n </blockquote>\n );\n i++;\n continue;\n }\n\n // Unordered list: - or * (collect consecutive items and wrap in <ul>)\n if (line.match(/^[-*] /)) {\n const listItems: React.ReactNode[] = [];\n while (i < lines.length && lines[i].match(/^[-*] /)) {\n listItems.push(\n <li key={listItems.length} className=\"list-disc\">\n {parseInlineFormatting(lines[i].slice(2))}\n </li>\n );\n i++;\n }\n elements.push(\n <ul key={elements.length} className=\"ml-6 my-2\">\n {listItems}\n </ul>\n );\n continue;\n }\n\n // Ordered list: 1. 2. etc. (collect consecutive items and wrap in <ol>)\n const orderedMatch = line.match(/^(\\d+)\\. /);\n if (orderedMatch) {\n const listItems: React.ReactNode[] = [];\n while (i < lines.length) {\n const olMatch = lines[i].match(/^(\\d+)\\. /);\n if (!olMatch) break;\n listItems.push(\n <li key={listItems.length} className=\"list-decimal\">\n {parseInlineFormatting(lines[i].slice(olMatch[0].length))}\n </li>\n );\n i++;\n }\n elements.push(\n <ol key={elements.length} className=\"ml-6 my-2\">\n {listItems}\n </ol>\n );\n continue;\n }\n\n // Horizontal rule: --- or ***\n if (line.match(/^(-{3,}|\\*{3,})$/)) {\n elements.push(<hr key={elements.length} className=\"my-4 border-border-default\" />);\n i++;\n continue;\n }\n\n // Table detection (simple): | col1 | col2 |\n if (line.startsWith('|') && line.endsWith('|')) {\n const tableLines: string[] = [line];\n i++;\n while (i < lines.length && lines[i].startsWith('|') && lines[i].endsWith('|')) {\n tableLines.push(lines[i]);\n i++;\n }\n\n // Parse table\n const rows = tableLines\n .filter((l) => !l.match(/^\\|[\\s\\-:|]+\\|$/)) // Skip separator rows (including alignment markers like :---:)\n .map((l) =>\n l\n .slice(1, -1)\n .split('|')\n .map((cell) => cell.trim())\n );\n\n if (rows.length > 0) {\n const [header, ...body] = rows;\n elements.push(\n <table key={elements.length} className=\"w-full my-2 border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-default\">\n {header.map((cell, ci) => (\n <th key={ci} className=\"text-left py-2 px-3 font-semibold text-text-primary\">\n {parseInlineFormatting(cell)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {body.map((row, ri) => (\n <tr key={ri} className=\"border-b border-border-subtle\">\n {row.map((cell, ci) => (\n <td key={ci} className=\"py-2 px-3 text-text-primary\">\n {parseInlineFormatting(cell)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n );\n }\n continue;\n }\n\n // Empty line\n if (line === '') {\n elements.push(<br key={elements.length} />);\n i++;\n continue;\n }\n\n // Regular paragraph with inline formatting\n elements.push(\n <p key={elements.length} className=\"mb-1\">\n {parseInlineFormatting(line)}\n </p>\n );\n i++;\n }\n\n return elements;\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay\">\n <div className=\"w-full max-w-3xl bg-bg-surface rounded-lg shadow-xl max-h-[90vh] flex flex-col\">\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {note\n ? tr('vibecontrols.notes.editNote', 'Edit Note')\n : tr('vibecontrols.notes.newNote', 'New Note')}\n </h2>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setIsPreview(!isPreview)}\n className={`inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded-md transition-colors ${\n isPreview\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-elevated'\n }`}\n >\n {isPreview ? <Edit className=\"size-4\" /> : <Eye className=\"size-4\" />}\n {isPreview\n ? tr('vibecontrols.notes.edit', 'Edit')\n : tr('vibecontrols.notes.preview', 'Preview')}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-5 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n <form onSubmit={handleSubmit} className=\"flex-1 flex flex-col overflow-hidden\">\n <div className=\"p-6 space-y-4 flex-1 overflow-y-auto\">\n <div>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={formData.title}\n onChange={(e) => setFormData({ ...formData, title: e.target.value })}\n placeholder={tr('vibecontrols.notes.titlePlaceholder', 'Note title...')}\n required\n className=\"w-full px-0 py-2 text-lg sm:text-2xl font-bold bg-transparent border-0 border-b border-border-default text-text-primary placeholder:text-text-placeholder focus:outline-none focus:border-action-primary-bg\"\n />\n </div>\n\n <div className={`grid ${hideVibeSelect ? 'grid-cols-1' : 'grid-cols-2'} gap-4`}>\n {!hideVibeSelect && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.notes.relatedVibe', 'Related Vibe (optional)')}\n </label>\n <select\n value={formData.vibeId}\n onChange={(e) => setFormData({ ...formData, vibeId: e.target.value })}\n className=\"w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">No vibe</option>\n {vibeOptions.map((vibe) => (\n <option key={vibe.id} value={vibe.id}>\n {vibe.name}\n </option>\n ))}\n </select>\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.notes.tags', 'Tags')}\n </label>\n {/* Selected tags */}\n <div className=\"flex flex-wrap gap-1.5 min-h-[38px] px-3 py-2 bg-bg-sunken border border-border-default rounded-md\">\n {availableTags\n .filter((t) => selectedTagIds.has(t.id))\n .map((tag) => (\n <span\n key={tag.id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-[var(--color-accent-soft)] text-text-primary rounded\"\n >\n <Tag className=\"size-3\" />\n {tag.label}\n <button\n type=\"button\"\n onClick={() => toggleTag(tag.id)}\n className=\"ml-0.5 hover:text-status-error-text\"\n >\n <X className=\"size-3\" />\n </button>\n </span>\n ))}\n <button\n type=\"button\"\n onClick={() => setShowTagPicker(!showTagPicker)}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs text-text-secondary hover:text-text-primary hover:bg-bg-elevated rounded transition-colors\"\n >\n <Tag className=\"size-3\" />\n {selectedTagIds.size === 0 ? 'Add tags...' : '+'}\n </button>\n </div>\n {/* Tag picker dropdown */}\n {showTagPicker && (\n <div className=\"mt-1 bg-bg-surface border border-border-default rounded-md shadow-lg\">\n {/* Inline create: text input + color picker */}\n <div className=\"flex items-center gap-2 p-2 border-b border-border-subtle\">\n <span\n aria-hidden=\"true\"\n className=\"size-6 rounded-full border-2 border-border-default\"\n style={{ backgroundColor: newTagColor }}\n />\n <input\n aria-label=\"New tag name... (Enter to create)\"\n type=\"text\"\n value={newTagLabel}\n onChange={(e) => setNewTagLabel(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleCreateTag();\n }\n }}\n placeholder=\"New tag name... (Enter to create)\"\n disabled={isCreatingTag}\n className=\"flex-1 px-2 py-1 text-sm bg-bg-sunken border border-border-default rounded text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n />\n <button\n type=\"button\"\n onClick={handleCreateTag}\n disabled={!newTagLabel.trim() || isCreatingTag}\n className=\"p-1 text-action-primary-bg hover:bg-[var(--color-accent-soft)] rounded transition-colors disabled:opacity-30\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n {/* Quick color palette */}\n <div className=\"flex items-center gap-1 px-2 py-1.5 border-b border-border-subtle\">\n {TAG_COLORS.map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => setNewTagColor(c)}\n className={`size-4 rounded-full transition-transform ${newTagColor === c ? 'ring-2 ring-action-primary-bg scale-125' : 'hover:scale-110'}`}\n style={{ backgroundColor: c }}\n />\n ))}\n </div>\n {/* Existing tags list */}\n <div className=\"max-h-32 overflow-y-auto p-1\">\n {availableTags.length === 0 ? (\n <p className=\"text-xs text-text-muted px-2 py-1\">\n No tags yet — create one above\n </p>\n ) : (\n availableTags.map((tag) => (\n <button\n key={tag.id}\n type=\"button\"\n onClick={() => toggleTag(tag.id)}\n className=\"flex items-center justify-between w-full px-2 py-1.5 text-sm text-text-primary hover:bg-bg-sunken rounded transition-colors\"\n >\n <span className=\"flex items-center gap-2\">\n {tag.color && (\n <span\n className=\"size-2.5 rounded-full\"\n style={{ backgroundColor: tag.color }}\n />\n )}\n {tag.label}\n </span>\n {selectedTagIds.has(tag.id) && (\n <Check className=\"size-4 text-action-primary-bg\" />\n )}\n </button>\n ))\n )}\n </div>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"flex-1 min-h-[300px]\">\n {isPreview ? (\n <div className=\"h-full p-4 bg-bg-sunken rounded-md prose prose-sm dark:prose-invert max-w-none overflow-y-auto\">\n {renderMarkdown(formData.content)}\n </div>\n ) : (\n <textarea\n aria-label=\"control\"\n value={formData.content}\n onChange={(e) => setFormData({ ...formData, content: e.target.value })}\n placeholder={tr(\n 'vibecontrols.notes.contentPlaceholder',\n 'Write your note in Markdown...'\n )}\n className=\"w-full h-full min-h-[300px] px-3 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 resize-none font-mono\"\n />\n )}\n </div>\n\n <div className=\"flex items-center gap-4 flex-wrap\">\n <div className=\"flex items-center gap-2\">\n <input\n aria-label=\"isPinned\"\n type=\"checkbox\"\n id=\"isPinned\"\n checked={formData.isPinned}\n onChange={(e) => setFormData({ ...formData, isPinned: e.target.checked })}\n className=\"size-4 rounded border-border-default text-action-primary-bg focus:ring-action-primary-bg\"\n />\n <label htmlFor=\"isPinned\" className=\"text-sm text-text-primary\">\n {tr('vibecontrols.notes.pinThisNote', 'Pin this note')}\n </label>\n </div>\n\n {/* Visibility toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-lg p-0.5\">\n <button\n type=\"button\"\n onClick={() => setFormData({ ...formData, visibility: 'WORKSPACE' })}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${\n formData.visibility === 'WORKSPACE'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-secondary'\n }`}\n >\n <Globe className=\"size-3.5\" />\n Workspace\n </button>\n <button\n type=\"button\"\n onClick={() => setFormData({ ...formData, visibility: 'PRIVATE' })}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${\n formData.visibility === 'PRIVATE'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-secondary'\n }`}\n >\n <Lock className=\"size-3.5\" />\n Private\n </button>\n </div>\n {formData.visibility === 'PRIVATE' && (\n <p className=\"text-xs text-text-muted w-full -mt-2\">Only you can see this note.</p>\n )}\n </div>\n </div>\n\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border-default bg-bg-sunken\">\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-elevated transition-colors disabled:opacity-50\"\n >\n {tr('vibecontrols.notes.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading}\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 <Save className=\"size-4\" />\n {isLoading\n ? tr('vibecontrols.notes.saving', 'Saving...')\n : tr('vibecontrols.notes.saveNote', 'Save Note')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -54,7 +54,7 @@ function g({ note: e, onOpen: t, onEdit: u, onDelete: g, onPin: _, onUnpin: v, o
54
54
  children: e.tagAssignments && e.tagAssignments.length > 0 ? /* @__PURE__ */ h("div", {
55
55
  className: "flex flex-wrap gap-1",
56
56
  children: [e.tagAssignments.slice(0, 2).map((e) => /* @__PURE__ */ h("span", {
57
- className: "inline-flex items-center gap-0.5 px-1.5 py-0.5 text-xs bg-[var(--color-accent-soft)] text-action-primary-bg rounded",
57
+ className: "inline-flex items-center gap-0.5 px-1.5 py-0.5 text-xs bg-[var(--color-accent-soft)] text-text-primary rounded",
58
58
  style: e.tag?.color ? { borderLeft: `2px solid ${e.tag.color}` } : void 0,
59
59
  children: [/* @__PURE__ */ m(f, { className: "size-2.5" }), e.tag?.label ?? e.tagId.slice(0, 8)]
60
60
  }, e.id)), e.tagAssignments.length > 2 && /* @__PURE__ */ h("span", {
@@ -1 +1 @@
1
- {"version":3,"file":"NoteList.js","names":[],"sources":["../../../src/components/notes/NoteList.tsx"],"sourcesContent":["import type { Note } from './types';\nimport { EmptyState } from '../shared/EmptyState';\nimport {\n FileText,\n Plus,\n Pin,\n Tag,\n MoreVertical,\n Edit,\n Trash2,\n PinOff,\n Lock,\n Globe,\n Share2,\n} from 'lucide-react';\nimport { useState } from 'react';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface NoteListProps {\n notes: Note[];\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n /** Generic-sharing entry point (full Share dialog); see NoteCard.onShareAccess. */\n onShareAccess?: (note: Note) => void;\n onCreateNew?: () => void;\n isLoading?: boolean;\n}\n\nfunction NoteRow({\n note,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n}: {\n note: Note;\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n onShareAccess?: (note: Note) => void;\n}) {\n const [showMenu, setShowMenu] = useState(false);\n\n const formatDate = (dateStr: string) => {\n return new Date(dateStr).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n };\n\n const getPreview = (content: string) => {\n const text = content\n .replace(/^#+\\s+/gm, '')\n .replace(/\\n/g, ' ')\n .trim();\n return text.length > 80 ? text.slice(0, 80) + '...' : text;\n };\n\n return (\n <tr\n className=\"border-b border-border-subtle hover:bg-bg-sunken/50 cursor-pointer transition-colors\"\n onClick={() => onOpen?.(note)}\n >\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <FileText className=\"size-4 text-status-warning-text shrink-0\" />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{note.title}</p>\n {note.isPinned && <Pin className=\"size-3 text-status-warning-text shrink-0\" />}\n {note.visibility === 'PRIVATE' ? (\n <span title=\"Private\">\n <Lock className=\"size-3 text-text-muted shrink-0\" />\n </span>\n ) : (\n <span title=\"Workspace\">\n <Globe className=\"size-3 text-text-muted shrink-0\" />\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-muted truncate max-w-[300px]\">\n {getPreview(note.content)}\n </p>\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-sm text-text-secondary truncate block max-w-[100px]\">\n {note.vibe?.name || '-'}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n {/* Render tagAssignments if available, otherwise fall back to legacy string tags */}\n {note.tagAssignments && note.tagAssignments.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {note.tagAssignments.slice(0, 2).map((assignment) => (\n <span\n key={assignment.id}\n className=\"inline-flex items-center gap-0.5 px-1.5 py-0.5 text-xs bg-[var(--color-accent-soft)] text-action-primary-bg rounded\"\n style={\n assignment.tag?.color\n ? { borderLeft: `2px solid ${assignment.tag.color}` }\n : undefined\n }\n >\n <Tag className=\"size-2.5\" />\n {assignment.tag?.label ?? assignment.tagId.slice(0, 8)}\n </span>\n ))}\n {note.tagAssignments.length > 2 && (\n <span className=\"px-1.5 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tagAssignments.length - 2}\n </span>\n )}\n </div>\n ) : note.tags && note.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {note.tags.slice(0, 2).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-0.5 px-1.5 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n <Tag className=\"size-2.5\" />\n {tag}\n </span>\n ))}\n {note.tags.length > 2 && (\n <span className=\"px-1.5 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tags.length - 2}\n </span>\n )}\n </div>\n ) : (\n <span className=\"text-xs text-text-muted\">-</span>\n )}\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-xs text-text-muted\">{formatDate(note.updatedAt)}</span>\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-1 justify-end\">\n {(onEdit || onDelete || onPin || onUnpin || onShare || onShareAccess) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-3.5 text-text-secondary\" />\n </button>\n {showMenu && (\n <div className=\"absolute right-0 mt-1 w-44 bg-bg-surface border border-border-default rounded-lg shadow-lg z-50\">\n {onEdit && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onEdit(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Edit className=\"size-4\" />\n Edit\n </button>\n )}\n {onShare && note.visibility === 'PRIVATE' && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share with workspace\n </button>\n )}\n {onShare && note.visibility === 'WORKSPACE' && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Lock className=\"size-4\" />\n Make private\n </button>\n )}\n {onShareAccess && (\n <button\n type=\"button\"\n data-testid=\"note-share-access\"\n onClick={(e) => {\n e.stopPropagation();\n onShareAccess(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share access…\n </button>\n )}\n {note.isPinned && onUnpin && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onUnpin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <PinOff className=\"size-4\" />\n Unpin\n </button>\n )}\n {!note.isPinned && onPin && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onPin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Pin className=\"size-4\" />\n Pin\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onDelete(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n Delete\n </button>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n </td>\n </tr>\n );\n}\n\nexport function NoteList({\n notes,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n onCreateNew,\n isLoading,\n}: NoteListProps) {\n const tr = useTr();\n if (!isLoading && notes.length === 0) {\n return (\n <EmptyState\n icon={<FileText className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.notes.emptyTitle', 'No notes yet')}\n description={tr(\n 'vibecontrols.notes.emptyDescription',\n 'Notes help you document your development process. Create your first note to get started.'\n )}\n action={\n onCreateNew && (\n <button\n type=\"button\"\n onClick={onCreateNew}\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\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.notes.createNote', 'Create Note')}\n </button>\n )\n }\n />\n );\n }\n\n // Sort: pinned first, then by updated date\n const sortedNotes = [...notes].sort((a, b) => {\n if (a.isPinned && !b.isPinned) return -1;\n if (!a.isPinned && b.isPinned) return 1;\n return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();\n });\n\n return (\n <div className=\"border border-border-default rounded-lg\">\n <div className=\"overflow-x-auto rounded-lg\">\n <table className=\"w-full\">\n <thead>\n <tr className=\"bg-bg-sunken border-b border-border-default\">\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider rounded-tl-lg\">\n {tr('vibecontrols.notes.columnTitle', 'Title')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.notes.columnVibe', 'Vibe')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.notes.columnTags', 'Tags')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.notes.columnUpdated', 'Updated')}\n </th>\n <th className=\"px-4 py-2.5 text-right text-xs font-medium text-text-secondary uppercase tracking-wider rounded-tr-lg\">\n {tr('vibecontrols.notes.columnActions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {sortedNotes.map((note) => (\n <NoteRow\n key={note.id}\n note={note}\n onOpen={onOpen}\n onEdit={onEdit}\n onDelete={onDelete}\n onPin={onPin}\n onUnpin={onUnpin}\n onShare={onShare}\n onShareAccess={onShareAccess}\n />\n ))}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"NoteList.js","names":[],"sources":["../../../src/components/notes/NoteList.tsx"],"sourcesContent":["import type { Note } from './types';\nimport { EmptyState } from '../shared/EmptyState';\nimport {\n FileText,\n Plus,\n Pin,\n Tag,\n MoreVertical,\n Edit,\n Trash2,\n PinOff,\n Lock,\n Globe,\n Share2,\n} from 'lucide-react';\nimport { useState } from 'react';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface NoteListProps {\n notes: Note[];\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n /** Generic-sharing entry point (full Share dialog); see NoteCard.onShareAccess. */\n onShareAccess?: (note: Note) => void;\n onCreateNew?: () => void;\n isLoading?: boolean;\n}\n\nfunction NoteRow({\n note,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n}: {\n note: Note;\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n onShareAccess?: (note: Note) => void;\n}) {\n const [showMenu, setShowMenu] = useState(false);\n\n const formatDate = (dateStr: string) => {\n return new Date(dateStr).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n };\n\n const getPreview = (content: string) => {\n const text = content\n .replace(/^#+\\s+/gm, '')\n .replace(/\\n/g, ' ')\n .trim();\n return text.length > 80 ? text.slice(0, 80) + '...' : text;\n };\n\n return (\n <tr\n className=\"border-b border-border-subtle hover:bg-bg-sunken/50 cursor-pointer transition-colors\"\n onClick={() => onOpen?.(note)}\n >\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <FileText className=\"size-4 text-status-warning-text shrink-0\" />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{note.title}</p>\n {note.isPinned && <Pin className=\"size-3 text-status-warning-text shrink-0\" />}\n {note.visibility === 'PRIVATE' ? (\n <span title=\"Private\">\n <Lock className=\"size-3 text-text-muted shrink-0\" />\n </span>\n ) : (\n <span title=\"Workspace\">\n <Globe className=\"size-3 text-text-muted shrink-0\" />\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-muted truncate max-w-[300px]\">\n {getPreview(note.content)}\n </p>\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-sm text-text-secondary truncate block max-w-[100px]\">\n {note.vibe?.name || '-'}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n {/* Render tagAssignments if available, otherwise fall back to legacy string tags */}\n {note.tagAssignments && note.tagAssignments.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {note.tagAssignments.slice(0, 2).map((assignment) => (\n <span\n key={assignment.id}\n className=\"inline-flex items-center gap-0.5 px-1.5 py-0.5 text-xs bg-[var(--color-accent-soft)] text-text-primary rounded\"\n style={\n assignment.tag?.color\n ? { borderLeft: `2px solid ${assignment.tag.color}` }\n : undefined\n }\n >\n <Tag className=\"size-2.5\" />\n {assignment.tag?.label ?? assignment.tagId.slice(0, 8)}\n </span>\n ))}\n {note.tagAssignments.length > 2 && (\n <span className=\"px-1.5 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tagAssignments.length - 2}\n </span>\n )}\n </div>\n ) : note.tags && note.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {note.tags.slice(0, 2).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-0.5 px-1.5 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n <Tag className=\"size-2.5\" />\n {tag}\n </span>\n ))}\n {note.tags.length > 2 && (\n <span className=\"px-1.5 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tags.length - 2}\n </span>\n )}\n </div>\n ) : (\n <span className=\"text-xs text-text-muted\">-</span>\n )}\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-xs text-text-muted\">{formatDate(note.updatedAt)}</span>\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-1 justify-end\">\n {(onEdit || onDelete || onPin || onUnpin || onShare || onShareAccess) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-3.5 text-text-secondary\" />\n </button>\n {showMenu && (\n <div className=\"absolute right-0 mt-1 w-44 bg-bg-surface border border-border-default rounded-lg shadow-lg z-50\">\n {onEdit && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onEdit(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Edit className=\"size-4\" />\n Edit\n </button>\n )}\n {onShare && note.visibility === 'PRIVATE' && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share with workspace\n </button>\n )}\n {onShare && note.visibility === 'WORKSPACE' && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Lock className=\"size-4\" />\n Make private\n </button>\n )}\n {onShareAccess && (\n <button\n type=\"button\"\n data-testid=\"note-share-access\"\n onClick={(e) => {\n e.stopPropagation();\n onShareAccess(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share access…\n </button>\n )}\n {note.isPinned && onUnpin && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onUnpin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <PinOff className=\"size-4\" />\n Unpin\n </button>\n )}\n {!note.isPinned && onPin && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onPin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Pin className=\"size-4\" />\n Pin\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onDelete(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n Delete\n </button>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n </td>\n </tr>\n );\n}\n\nexport function NoteList({\n notes,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n onCreateNew,\n isLoading,\n}: NoteListProps) {\n const tr = useTr();\n if (!isLoading && notes.length === 0) {\n return (\n <EmptyState\n icon={<FileText className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.notes.emptyTitle', 'No notes yet')}\n description={tr(\n 'vibecontrols.notes.emptyDescription',\n 'Notes help you document your development process. Create your first note to get started.'\n )}\n action={\n onCreateNew && (\n <button\n type=\"button\"\n onClick={onCreateNew}\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\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.notes.createNote', 'Create Note')}\n </button>\n )\n }\n />\n );\n }\n\n // Sort: pinned first, then by updated date\n const sortedNotes = [...notes].sort((a, b) => {\n if (a.isPinned && !b.isPinned) return -1;\n if (!a.isPinned && b.isPinned) return 1;\n return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();\n });\n\n return (\n <div className=\"border border-border-default rounded-lg\">\n <div className=\"overflow-x-auto rounded-lg\">\n <table className=\"w-full\">\n <thead>\n <tr className=\"bg-bg-sunken border-b border-border-default\">\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider rounded-tl-lg\">\n {tr('vibecontrols.notes.columnTitle', 'Title')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.notes.columnVibe', 'Vibe')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.notes.columnTags', 'Tags')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.notes.columnUpdated', 'Updated')}\n </th>\n <th className=\"px-4 py-2.5 text-right text-xs font-medium text-text-secondary uppercase tracking-wider rounded-tr-lg\">\n {tr('vibecontrols.notes.columnActions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {sortedNotes.map((note) => (\n <NoteRow\n key={note.id}\n note={note}\n onOpen={onOpen}\n onEdit={onEdit}\n onDelete={onDelete}\n onPin={onPin}\n onUnpin={onUnpin}\n onShare={onShare}\n onShareAccess={onShareAccess}\n />\n ))}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -57,7 +57,7 @@ function b({ variant: b = "compact", dismissible: x = !0, dismissKey: S = "vibec
57
57
  height: 0
58
58
  },
59
59
  transition: N,
60
- className: `relative flex flex-col items-start gap-3 px-4 py-3 bg-accent-purple-subtle border border-accent-purple/20 rounded-card sm:flex-row sm:items-center ${C}`,
60
+ className: `relative flex flex-col items-start gap-3 px-4 py-3 bg-bg-elevated border border-border-subtle rounded-card sm:flex-row sm:items-center ${C}`,
61
61
  children: [
62
62
  /* @__PURE__ */ h(c, { className: "size-5 text-accent-purple flex-shrink-0" }),
63
63
  /* @__PURE__ */ g("div", {
@@ -117,7 +117,7 @@ function b({ variant: b = "compact", dismissible: x = !0, dismissKey: S = "vibec
117
117
  height: 0
118
118
  },
119
119
  transition: N,
120
- className: `relative border border-accent-purple/20 bg-accent-purple-subtle rounded-card overflow-hidden ${C}`,
120
+ className: `relative border border-border-subtle bg-bg-elevated rounded-card overflow-hidden ${C}`,
121
121
  children: [x && /* @__PURE__ */ h("button", {
122
122
  type: "button",
123
123
  onClick: M,
@@ -130,7 +130,7 @@ function b({ variant: b = "compact", dismissible: x = !0, dismissKey: S = "vibec
130
130
  /* @__PURE__ */ g("div", {
131
131
  className: "flex items-start gap-3 mb-4",
132
132
  children: [/* @__PURE__ */ h("div", {
133
- className: "flex-shrink-0 size-10 rounded-lg bg-accent-purple/10 flex items-center justify-center",
133
+ className: "flex-shrink-0 size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center",
134
134
  children: /* @__PURE__ */ h(c, { className: "size-5 text-accent-purple" })
135
135
  }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("h3", {
136
136
  className: "text-base font-semibold text-text-primary",
@@ -1 +1 @@
1
- {"version":3,"file":"AgentSetupBanner.js","names":[],"sources":["../../../src/components/shared/AgentSetupBanner.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Download, Copy, Check, ExternalLink, X, Terminal } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { getProfileScopedKey } from '@burdenoff/fe-libs/shared/utils';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { InstallTabs } from './InstallTabs';\n\ninterface AgentSetupBannerProps {\n /** Compact mode shows a single-line banner; full mode shows a multi-step card */\n variant?: 'compact' | 'full';\n /** Allow the banner to be dismissed */\n dismissible?: boolean;\n /** Storage key for dismissed state */\n dismissKey?: string;\n /** Additional CSS class names */\n className?: string;\n}\n\nconst NPM_URL = 'https://www.npmjs.com/package/@vibecontrols/agent';\n\n/**\n * Build the quick-install one-liner using the live host so the command shown\n * to the user reflects the environment they are actually on (local dev,\n * alpha, prod). InstallTabs uses the same `resolveHost()` shape — keep them\n * aligned by reading from `window.__VIBECONTROLS_HOST__` first, then\n * falling back to `window.location.host`.\n */\nfunction getQuickInstallCommand(): string {\n // Trailing `&& hash -r` runs in the user's shell to flush a stale cached\n // `vibe` path so the new binary resolves immediately (the install script also\n // rebinds it on disk; this is belt-and-braces, harmless when nothing stale).\n if (typeof window === 'undefined') {\n return 'curl -fsSL https://app.vibecontrols.com/install | bash && hash -r 2>/dev/null';\n }\n const injected = (window as { __VIBECONTROLS_HOST__?: string }).__VIBECONTROLS_HOST__;\n const host = injected ?? window.location.host;\n return `curl -fsSL https://${host}/install | bash && hash -r 2>/dev/null`;\n}\n\n/**\n * Reusable banner/card prompting users to install the VibeControls Agent CLI.\n * Used across multiple pages (Overview, Agents, Sessions, Tunnels, SSH, etc.)\n *\n * The full variant mirrors the Getting Started page's tabbed install widget\n * (`InstallTabs`) so users see a single consistent install flow wherever\n * the banner shows up.\n */\nexport function AgentSetupBanner({\n variant = 'compact',\n dismissible = true,\n dismissKey = 'vibecontrols-setup-banner-dismissed',\n className = '',\n}: AgentSetupBannerProps) {\n const tr = useTr();\n const handleNavigate = useVibeNavigate();\n const quickInstall = useMemo(() => getQuickInstallCommand(), []);\n const [copied, setCopied] = useState(false);\n const [dismissed, setDismissed] = useState(() => {\n try {\n return localStorage.getItem(getProfileScopedKey(dismissKey)) === 'true';\n } catch {\n return false;\n }\n });\n\n const handleCopy = useCallback(async (text: string) => {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch {\n const el = document.createElement('textarea');\n el.value = text;\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n document.body.removeChild(el);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }\n }, []);\n\n const handleDismiss = useCallback(() => {\n setDismissed(true);\n try {\n localStorage.setItem(getProfileScopedKey(dismissKey), 'true');\n } catch {\n // ignore\n }\n }, [dismissKey]);\n\n const bannerTransition = { duration: 0.3, ease: [0.4, 0, 0.2, 1] as const };\n\n // ─── Compact variant: single-line info banner ─────────────────────────\n if (variant === 'compact') {\n return (\n <AnimatePresence>\n {!dismissed && (\n <motion.div\n initial={{ opacity: 0, y: -8, height: 0 }}\n animate={{ opacity: 1, y: 0, height: 'auto' }}\n exit={{ opacity: 0, y: -8, height: 0 }}\n transition={bannerTransition}\n className={`relative flex flex-col items-start gap-3 px-4 py-3 bg-accent-purple-subtle border border-accent-purple/20 rounded-card sm:flex-row sm:items-center ${className}`}\n >\n <Download className=\"size-5 text-accent-purple flex-shrink-0\" />\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-x-3 gap-y-1\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr(\n 'vibecontrols.shared.agentSetupBanner.installPrompt',\n 'Install the VibeControls Agent CLI to get started'\n )}\n </span>\n <code\n className=\"inline-flex min-w-0 max-w-full items-center gap-1.5 rounded border border-border-subtle bg-bg-sunken px-2.5 py-1 font-mono text-xs text-text-primary transition-colors hover:bg-bg-elevated cursor-pointer\"\n onClick={() => handleCopy(quickInstall)}\n title={tr('vibecontrols.shared.agentSetupBanner.clickToCopy', 'Click to copy')}\n >\n <Terminal className=\"size-3\" />\n <span className=\"min-w-0 break-all\">{quickInstall}</span>\n {copied ? (\n <Check className=\"size-3 text-status-success-text\" />\n ) : (\n <Copy className=\"size-3 text-text-muted\" />\n )}\n </code>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"text-sm text-accent-purple hover:underline flex items-center gap-1\"\n >\n {tr('vibecontrols.shared.agentSetupBanner.setupGuide', 'Setup guide')}{' '}\n <ExternalLink className=\"size-3\" />\n </button>\n </div>\n {dismissible && (\n <button\n type=\"button\"\n onClick={handleDismiss}\n className=\"flex-shrink-0 p-1 text-text-muted hover:text-text-secondary rounded transition-colors\"\n title={tr('vibecontrols.shared.agentSetupBanner.dismiss', 'Dismiss')}\n >\n <X className=\"size-4\" />\n </button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n );\n }\n\n // ─── Full variant: tabbed install card (mirrors Getting Started) ──────\n return (\n <AnimatePresence>\n {!dismissed && (\n <motion.div\n initial={{ opacity: 0, y: -8, height: 0 }}\n animate={{ opacity: 1, y: 0, height: 'auto' }}\n exit={{ opacity: 0, y: -8, height: 0 }}\n transition={bannerTransition}\n className={`relative border border-accent-purple/20 bg-accent-purple-subtle rounded-card overflow-hidden ${className}`}\n >\n {dismissible && (\n <button\n type=\"button\"\n onClick={handleDismiss}\n className=\"absolute top-3 right-3 p-1 text-text-muted hover:text-text-secondary rounded transition-colors z-10\"\n title={tr('vibecontrols.shared.agentSetupBanner.dismiss', 'Dismiss')}\n >\n <X className=\"size-4\" />\n </button>\n )}\n\n <div className=\"p-5 sm:p-6\">\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"flex-shrink-0 size-10 rounded-lg bg-accent-purple/10 flex items-center justify-center\">\n <Download className=\"size-5 text-accent-purple\" />\n </div>\n <div>\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr(\n 'vibecontrols.shared.agentSetupBanner.title',\n 'Get Started with VibeControls Agent'\n )}\n </h3>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {tr(\n 'vibecontrols.shared.agentSetupBanner.description',\n 'Install the CLI on any machine you want to manage. The only prerequisite is bun — the curl script installs it for you.'\n )}\n </p>\n </div>\n </div>\n\n {/* Tabbed install widget mirrors the Getting Started page */}\n <div className=\"ml-0 sm:ml-13\">\n <InstallTabs compact />\n </div>\n\n {/* Actions */}\n <div className=\"flex flex-wrap items-center gap-3 mt-5 ml-0 sm:ml-13\">\n <Button\n variant=\"default\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"gap-2\"\n >\n {tr(\n 'vibecontrols.shared.agentSetupBanner.viewFullSetupGuide',\n 'View full setup guide'\n )}\n <ExternalLink className=\"size-3.5\" />\n </Button>\n <Button variant=\"outline\" asChild className=\"gap-2\">\n <a href={NPM_URL} target=\"_blank\" rel=\"noopener noreferrer\">\n {tr('vibecontrols.shared.agentSetupBanner.npmPackage', 'npm package')}\n <ExternalLink className=\"size-3.5\" />\n </a>\n </Button>\n </div>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AgentSetupBanner.js","names":[],"sources":["../../../src/components/shared/AgentSetupBanner.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Download, Copy, Check, ExternalLink, X, Terminal } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { getProfileScopedKey } from '@burdenoff/fe-libs/shared/utils';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { InstallTabs } from './InstallTabs';\n\ninterface AgentSetupBannerProps {\n /** Compact mode shows a single-line banner; full mode shows a multi-step card */\n variant?: 'compact' | 'full';\n /** Allow the banner to be dismissed */\n dismissible?: boolean;\n /** Storage key for dismissed state */\n dismissKey?: string;\n /** Additional CSS class names */\n className?: string;\n}\n\nconst NPM_URL = 'https://www.npmjs.com/package/@vibecontrols/agent';\n\n/**\n * Build the quick-install one-liner using the live host so the command shown\n * to the user reflects the environment they are actually on (local dev,\n * alpha, prod). InstallTabs uses the same `resolveHost()` shape — keep them\n * aligned by reading from `window.__VIBECONTROLS_HOST__` first, then\n * falling back to `window.location.host`.\n */\nfunction getQuickInstallCommand(): string {\n // Trailing `&& hash -r` runs in the user's shell to flush a stale cached\n // `vibe` path so the new binary resolves immediately (the install script also\n // rebinds it on disk; this is belt-and-braces, harmless when nothing stale).\n if (typeof window === 'undefined') {\n return 'curl -fsSL https://app.vibecontrols.com/install | bash && hash -r 2>/dev/null';\n }\n const injected = (window as { __VIBECONTROLS_HOST__?: string }).__VIBECONTROLS_HOST__;\n const host = injected ?? window.location.host;\n return `curl -fsSL https://${host}/install | bash && hash -r 2>/dev/null`;\n}\n\n/**\n * Reusable banner/card prompting users to install the VibeControls Agent CLI.\n * Used across multiple pages (Overview, Agents, Sessions, Tunnels, SSH, etc.)\n *\n * The full variant mirrors the Getting Started page's tabbed install widget\n * (`InstallTabs`) so users see a single consistent install flow wherever\n * the banner shows up.\n */\nexport function AgentSetupBanner({\n variant = 'compact',\n dismissible = true,\n dismissKey = 'vibecontrols-setup-banner-dismissed',\n className = '',\n}: AgentSetupBannerProps) {\n const tr = useTr();\n const handleNavigate = useVibeNavigate();\n const quickInstall = useMemo(() => getQuickInstallCommand(), []);\n const [copied, setCopied] = useState(false);\n const [dismissed, setDismissed] = useState(() => {\n try {\n return localStorage.getItem(getProfileScopedKey(dismissKey)) === 'true';\n } catch {\n return false;\n }\n });\n\n const handleCopy = useCallback(async (text: string) => {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch {\n const el = document.createElement('textarea');\n el.value = text;\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n document.body.removeChild(el);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }\n }, []);\n\n const handleDismiss = useCallback(() => {\n setDismissed(true);\n try {\n localStorage.setItem(getProfileScopedKey(dismissKey), 'true');\n } catch {\n // ignore\n }\n }, [dismissKey]);\n\n const bannerTransition = { duration: 0.3, ease: [0.4, 0, 0.2, 1] as const };\n\n // ─── Compact variant: single-line info banner ─────────────────────────\n if (variant === 'compact') {\n return (\n <AnimatePresence>\n {!dismissed && (\n <motion.div\n initial={{ opacity: 0, y: -8, height: 0 }}\n animate={{ opacity: 1, y: 0, height: 'auto' }}\n exit={{ opacity: 0, y: -8, height: 0 }}\n transition={bannerTransition}\n className={`relative flex flex-col items-start gap-3 px-4 py-3 bg-bg-elevated border border-border-subtle rounded-card sm:flex-row sm:items-center ${className}`}\n >\n <Download className=\"size-5 text-accent-purple flex-shrink-0\" />\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-x-3 gap-y-1\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr(\n 'vibecontrols.shared.agentSetupBanner.installPrompt',\n 'Install the VibeControls Agent CLI to get started'\n )}\n </span>\n <code\n className=\"inline-flex min-w-0 max-w-full items-center gap-1.5 rounded border border-border-subtle bg-bg-sunken px-2.5 py-1 font-mono text-xs text-text-primary transition-colors hover:bg-bg-elevated cursor-pointer\"\n onClick={() => handleCopy(quickInstall)}\n title={tr('vibecontrols.shared.agentSetupBanner.clickToCopy', 'Click to copy')}\n >\n <Terminal className=\"size-3\" />\n <span className=\"min-w-0 break-all\">{quickInstall}</span>\n {copied ? (\n <Check className=\"size-3 text-status-success-text\" />\n ) : (\n <Copy className=\"size-3 text-text-muted\" />\n )}\n </code>\n <button\n type=\"button\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"text-sm text-accent-purple hover:underline flex items-center gap-1\"\n >\n {tr('vibecontrols.shared.agentSetupBanner.setupGuide', 'Setup guide')}{' '}\n <ExternalLink className=\"size-3\" />\n </button>\n </div>\n {dismissible && (\n <button\n type=\"button\"\n onClick={handleDismiss}\n className=\"flex-shrink-0 p-1 text-text-muted hover:text-text-secondary rounded transition-colors\"\n title={tr('vibecontrols.shared.agentSetupBanner.dismiss', 'Dismiss')}\n >\n <X className=\"size-4\" />\n </button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n );\n }\n\n // ─── Full variant: tabbed install card (mirrors Getting Started) ──────\n return (\n <AnimatePresence>\n {!dismissed && (\n <motion.div\n initial={{ opacity: 0, y: -8, height: 0 }}\n animate={{ opacity: 1, y: 0, height: 'auto' }}\n exit={{ opacity: 0, y: -8, height: 0 }}\n transition={bannerTransition}\n className={`relative border border-border-subtle bg-bg-elevated rounded-card overflow-hidden ${className}`}\n >\n {dismissible && (\n <button\n type=\"button\"\n onClick={handleDismiss}\n className=\"absolute top-3 right-3 p-1 text-text-muted hover:text-text-secondary rounded transition-colors z-10\"\n title={tr('vibecontrols.shared.agentSetupBanner.dismiss', 'Dismiss')}\n >\n <X className=\"size-4\" />\n </button>\n )}\n\n <div className=\"p-5 sm:p-6\">\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"flex-shrink-0 size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Download className=\"size-5 text-accent-purple\" />\n </div>\n <div>\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tr(\n 'vibecontrols.shared.agentSetupBanner.title',\n 'Get Started with VibeControls Agent'\n )}\n </h3>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {tr(\n 'vibecontrols.shared.agentSetupBanner.description',\n 'Install the CLI on any machine you want to manage. The only prerequisite is bun — the curl script installs it for you.'\n )}\n </p>\n </div>\n </div>\n\n {/* Tabbed install widget mirrors the Getting Started page */}\n <div className=\"ml-0 sm:ml-13\">\n <InstallTabs compact />\n </div>\n\n {/* Actions */}\n <div className=\"flex flex-wrap items-center gap-3 mt-5 ml-0 sm:ml-13\">\n <Button\n variant=\"default\"\n onClick={() => handleNavigate('/getting-started')}\n className=\"gap-2\"\n >\n {tr(\n 'vibecontrols.shared.agentSetupBanner.viewFullSetupGuide',\n 'View full setup guide'\n )}\n <ExternalLink className=\"size-3.5\" />\n </Button>\n <Button variant=\"outline\" asChild className=\"gap-2\">\n <a href={NPM_URL} target=\"_blank\" rel=\"noopener noreferrer\">\n {tr('vibecontrols.shared.agentSetupBanner.npmPackage', 'npm package')}\n <ExternalLink className=\"size-3.5\" />\n </a>\n </Button>\n </div>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n}\n"],"mappings":"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"}
@@ -5,7 +5,7 @@ import { motion as s } from "framer-motion";
5
5
  import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
6
  //#region src/components/templates/TemplateCard.tsx
7
7
  var u = {
8
- SERVICE: "bg-[var(--color-accent-soft)] text-action-primary-bg",
8
+ SERVICE: "bg-[var(--color-accent-soft)] text-text-primary",
9
9
  FRONTEND: "bg-status-info-bg-subtle text-status-info-text",
10
10
  LIBRARY: "bg-status-warning-bg-subtle text-status-warning-text",
11
11
  INFRASTRUCTURE: "bg-status-success-bg-subtle text-status-success-text",
@@ -1 +1 @@
1
- {"version":3,"file":"TemplateCard.js","names":[],"sources":["../../../src/components/templates/TemplateCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { motion } from 'framer-motion';\nimport { FileCode, Tag, Copy, Layers, Variable } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { cardHoverVariants } from '../shared/cardMotion';\n\nexport interface TemplateCardData {\n id: string;\n name: string;\n description: string | null;\n category: string | null;\n cloneCount: number;\n variableCount: number;\n tags: string[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface TemplateCardProps {\n template: TemplateCardData;\n onOpen?: (template: TemplateCardData) => void;\n className?: string;\n}\n\nconst categoryStyles: Record<string, string> = {\n SERVICE: 'bg-[var(--color-accent-soft)] text-action-primary-bg',\n FRONTEND: 'bg-status-info-bg-subtle text-status-info-text',\n LIBRARY: 'bg-status-warning-bg-subtle text-status-warning-text',\n INFRASTRUCTURE: 'bg-status-success-bg-subtle text-status-success-text',\n STARTER: 'bg-bg-sunken text-text-secondary',\n};\n\nexport const TemplateCard: FC<TemplateCardProps> = ({ template, onOpen, className = '' }) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n return (\n <motion.div\n initial=\"rest\"\n animate=\"rest\"\n whileHover=\"hover\"\n whileTap=\"tap\"\n variants={cardHoverVariants}\n className={`bg-bg-surface border border-border-default rounded-lg p-4 hover:border-border-strong transition-colors cursor-pointer ${className}`}\n onClick={() => onOpen?.(template)}\n >\n <div className=\"flex items-start gap-2 sm:gap-3\">\n <div className=\"flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <FileCode className=\"size-4 sm:w-5 sm:h-5 text-action-primary-bg\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"font-semibold text-text-primary truncate text-sm sm:text-base\">\n {template.name}\n </h3>\n <div className=\"flex items-center gap-2 mt-0.5 flex-wrap\">\n {template.category && (\n <span\n className={`inline-flex items-center gap-1 px-1.5 py-0.5 text-xs rounded ${categoryStyles[template.category] || 'bg-bg-sunken text-text-secondary'}`}\n >\n <Layers className=\"size-3\" />\n {tr(`template.category.${template.category.toLowerCase()}`, template.category)}\n </span>\n )}\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Copy className=\"size-3\" />\n {template.cloneCount} {tr('template.clones', 'clones')}\n </span>\n {template.variableCount > 0 && (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Variable className=\"size-3\" />\n {template.variableCount} var{template.variableCount !== 1 ? 's' : ''}\n </span>\n )}\n </div>\n </div>\n </div>\n\n {template.description && (\n <p className=\"mt-2 text-sm text-text-secondary line-clamp-2\">{template.description}</p>\n )}\n\n {template.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {template.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n <Tag className=\"size-3\" />\n {tag}\n </span>\n ))}\n {template.tags.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{template.tags.length - 3}\n </span>\n )}\n </div>\n )}\n </motion.div>\n );\n};\n"],"mappings":";;;;;;AAwBA,IAAM,IAAyC;CAC7C,SAAS;CACT,UAAU;CACV,SAAS;CACT,gBAAgB;CAChB,SAAS;CACV,EAEY,KAAuC,EAAE,aAAU,WAAQ,eAAY,SAAS;CAC3F,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAAqB;EAC5C,IAAM,IAAI,EAAE,EAAI;AAChB,SAAO,MAAM,IAAM,IAAW;;AAGhC,QACE,kBAAC,EAAO,KAAR;EACE,SAAQ;EACR,SAAQ;EACR,YAAW;EACX,UAAS;EACT,UAAU;EACV,WAAW,yHAAyH;EACpI,eAAe,IAAS,EAAS;YAPnC;GASE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAU,WAAU,+CAAgD,CAAA;KAChE,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAS;MACP,CAAA,EACL,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACG,EAAS,YACR,kBAAC,QAAD;QACE,WAAW,gEAAgE,EAAe,EAAS,aAAa;kBADlH,CAGE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,EAC5B,EAAG,qBAAqB,EAAS,SAAS,aAAa,IAAI,EAAS,SAAS,CACzE;;OAET,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;SAC1B,EAAS;SAAW;SAAE,EAAG,mBAAmB,SAAS;SACjD;;OACN,EAAS,gBAAgB,KACxB,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACE,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;SAC9B,EAAS;SAAc;SAAK,EAAS,kBAAkB,IAAU,KAAN;SACvD;;OAEL;QACF;OACF;;GAEL,EAAS,eACR,kBAAC,KAAD;IAAG,WAAU;cAAiD,EAAS;IAAgB,CAAA;GAGxF,EAAS,KAAK,SAAS,KACtB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAS,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,MAC9B,kBAAC,QAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA,EACzB,EACI;OALA,EAKA,CACP,EACD,EAAS,KAAK,SAAS,KACtB,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAA+E,KAC3E,EAAS,KAAK,SAAS,EACpB;OAEL;;GAEG"}
1
+ {"version":3,"file":"TemplateCard.js","names":[],"sources":["../../../src/components/templates/TemplateCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { motion } from 'framer-motion';\nimport { FileCode, Tag, Copy, Layers, Variable } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { cardHoverVariants } from '../shared/cardMotion';\n\nexport interface TemplateCardData {\n id: string;\n name: string;\n description: string | null;\n category: string | null;\n cloneCount: number;\n variableCount: number;\n tags: string[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface TemplateCardProps {\n template: TemplateCardData;\n onOpen?: (template: TemplateCardData) => void;\n className?: string;\n}\n\nconst categoryStyles: Record<string, string> = {\n SERVICE: 'bg-[var(--color-accent-soft)] text-text-primary',\n FRONTEND: 'bg-status-info-bg-subtle text-status-info-text',\n LIBRARY: 'bg-status-warning-bg-subtle text-status-warning-text',\n INFRASTRUCTURE: 'bg-status-success-bg-subtle text-status-success-text',\n STARTER: 'bg-bg-sunken text-text-secondary',\n};\n\nexport const TemplateCard: FC<TemplateCardProps> = ({ template, onOpen, className = '' }) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n return (\n <motion.div\n initial=\"rest\"\n animate=\"rest\"\n whileHover=\"hover\"\n whileTap=\"tap\"\n variants={cardHoverVariants}\n className={`bg-bg-surface border border-border-default rounded-lg p-4 hover:border-border-strong transition-colors cursor-pointer ${className}`}\n onClick={() => onOpen?.(template)}\n >\n <div className=\"flex items-start gap-2 sm:gap-3\">\n <div className=\"flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <FileCode className=\"size-4 sm:w-5 sm:h-5 text-action-primary-bg\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"font-semibold text-text-primary truncate text-sm sm:text-base\">\n {template.name}\n </h3>\n <div className=\"flex items-center gap-2 mt-0.5 flex-wrap\">\n {template.category && (\n <span\n className={`inline-flex items-center gap-1 px-1.5 py-0.5 text-xs rounded ${categoryStyles[template.category] || 'bg-bg-sunken text-text-secondary'}`}\n >\n <Layers className=\"size-3\" />\n {tr(`template.category.${template.category.toLowerCase()}`, template.category)}\n </span>\n )}\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Copy className=\"size-3\" />\n {template.cloneCount} {tr('template.clones', 'clones')}\n </span>\n {template.variableCount > 0 && (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Variable className=\"size-3\" />\n {template.variableCount} var{template.variableCount !== 1 ? 's' : ''}\n </span>\n )}\n </div>\n </div>\n </div>\n\n {template.description && (\n <p className=\"mt-2 text-sm text-text-secondary line-clamp-2\">{template.description}</p>\n )}\n\n {template.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {template.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n <Tag className=\"size-3\" />\n {tag}\n </span>\n ))}\n {template.tags.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{template.tags.length - 3}\n </span>\n )}\n </div>\n )}\n </motion.div>\n );\n};\n"],"mappings":";;;;;;AAwBA,IAAM,IAAyC;CAC7C,SAAS;CACT,UAAU;CACV,SAAS;CACT,gBAAgB;CAChB,SAAS;CACV,EAEY,KAAuC,EAAE,aAAU,WAAQ,eAAY,SAAS;CAC3F,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAAqB;EAC5C,IAAM,IAAI,EAAE,EAAI;AAChB,SAAO,MAAM,IAAM,IAAW;;AAGhC,QACE,kBAAC,EAAO,KAAR;EACE,SAAQ;EACR,SAAQ;EACR,YAAW;EACX,UAAS;EACT,UAAU;EACV,WAAW,yHAAyH;EACpI,eAAe,IAAS,EAAS;YAPnC;GASE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAU,WAAU,+CAAgD,CAAA;KAChE,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAS;MACP,CAAA,EACL,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACG,EAAS,YACR,kBAAC,QAAD;QACE,WAAW,gEAAgE,EAAe,EAAS,aAAa;kBADlH,CAGE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,EAC5B,EAAG,qBAAqB,EAAS,SAAS,aAAa,IAAI,EAAS,SAAS,CACzE;;OAET,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;SAC1B,EAAS;SAAW;SAAE,EAAG,mBAAmB,SAAS;SACjD;;OACN,EAAS,gBAAgB,KACxB,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACE,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;SAC9B,EAAS;SAAc;SAAK,EAAS,kBAAkB,IAAU,KAAN;SACvD;;OAEL;QACF;OACF;;GAEL,EAAS,eACR,kBAAC,KAAD;IAAG,WAAU;cAAiD,EAAS;IAAgB,CAAA;GAGxF,EAAS,KAAK,SAAS,KACtB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAS,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,MAC9B,kBAAC,QAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA,EACzB,EACI;OALA,EAKA,CACP,EACD,EAAS,KAAK,SAAS,KACtB,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAA+E,KAC3E,EAAS,KAAK,SAAS,EACpB;OAEL;;GAEG"}
@@ -8,7 +8,7 @@ var e = {
8
8
  CANCELLED: "bg-bg-muted/10 text-text-tertiary border-border-subtle/30",
9
9
  DRAFT: "bg-bg-muted/10 text-text-tertiary border-border-subtle/30",
10
10
  TIMEOUT: "bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border/30",
11
- MANUAL: "bg-accent-purple-subtle text-accent-purple border-accent-purple/30",
11
+ MANUAL: "bg-accent-purple-subtle text-text-primary border-accent-purple/30",
12
12
  SCHEDULE: "bg-status-info-bg-subtle text-status-info-text border-status-info-border/30",
13
13
  ARCHIVED: "bg-bg-muted/10 text-text-tertiary border-border-subtle/30",
14
14
  DISABLED: "bg-bg-muted/10 text-text-tertiary border-border-subtle/30"
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../src/components/vibecalendar/types.ts"],"sourcesContent":["export type CalendarViewMode = 'month' | 'week' | 'day' | 'agenda';\n\n// Where a calendar tile's data originally came from. Set by\n// client-side mappers ONLY — never present on the GraphQL response.\n// Page handlers branch on this to pick the matching subgraph for\n// mutations and the matching detail route. Omitted = default\n// `vibeCalendarTask` (rows from wspace-calendar-svc).\nexport type VibeCalendarTaskSource = 'scheduledJob' | 'vibeCalendarTask';\n\nexport interface VibeCalendarTask {\n id: string;\n /**\n * Set by client-side mappers (e.g. `jobToVibeCalendarTask`). Reading\n * code branches on this to pick the correct mutation/route. Omitted\n * for real VibeCalendarTask rows fetched from wspace-calendar-svc —\n * treat `undefined` as `'vibeCalendarTask'`.\n */\n source?: VibeCalendarTaskSource;\n title: string;\n description: string | null;\n calendarId: string;\n calendarEventId: string;\n agentId: string;\n taskConfig: {\n taskType: string;\n command?: string | null;\n script?: string | null;\n interpreter?: string | null;\n cwd?: string | null;\n env?: Record<string, string> | null;\n timeout?: number | null;\n webhookUrl?: string | null;\n webhookMethod?: string | null;\n webhookHeaders?: Record<string, string> | null;\n webhookAuthType?: string | null;\n webhookPayload?: Record<string, unknown> | null;\n };\n cronExpression: string | null;\n rrule: string | null;\n scheduledAt: string | null;\n timezone: string;\n nextExecutionAt: string | null;\n lastExecutedAt: string | null;\n status: string;\n totalExecutions: number;\n consecutiveFailures: number;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface VibeCalendarExecution {\n id: string;\n taskId: string;\n status: string;\n output: string | null;\n exitCode: number | null;\n webhookStatusCode: number | null;\n startedAt: string | null;\n completedAt: string | null;\n error: string | null;\n}\n\nexport interface CalendarOccurrence {\n task: VibeCalendarTask;\n occurrenceAt: string;\n isPast: boolean;\n execution: VibeCalendarExecution | null;\n}\n\nexport const STATUS_COLORS: Record<string, string> = {\n PENDING: 'bg-status-info-bg-subtle text-status-info-text border-status-info-border/30',\n RUNNING: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border/30',\n COMPLETED: 'bg-status-success-bg-subtle text-status-success-text border-status-success-border/30',\n FAILED: 'bg-status-error-bg-subtle text-status-error-text border-status-error-border/30',\n PAUSED: 'bg-bg-muted/10 text-text-tertiary border-border-subtle/30',\n CANCELLED: 'bg-bg-muted/10 text-text-tertiary border-border-subtle/30',\n DRAFT: 'bg-bg-muted/10 text-text-tertiary border-border-subtle/30',\n TIMEOUT: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border/30',\n MANUAL: 'bg-accent-purple-subtle text-accent-purple border-accent-purple/30',\n SCHEDULE: 'bg-status-info-bg-subtle text-status-info-text border-status-info-border/30',\n // Scheduler-svc JobStatus exclusives — without these the badge\n // silently falls back to PENDING styling on archived/disabled jobs.\n ARCHIVED: 'bg-bg-muted/10 text-text-tertiary border-border-subtle/30',\n DISABLED: 'bg-bg-muted/10 text-text-tertiary border-border-subtle/30',\n};\n"],"mappings":";AAqEA,IAAa,IAAwC;CACnD,SAAS;CACT,SAAS;CACT,WAAW;CACX,QAAQ;CACR,QAAQ;CACR,WAAW;CACX,OAAO;CACP,SAAS;CACT,QAAQ;CACR,UAAU;CAGV,UAAU;CACV,UAAU;CACX"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../src/components/vibecalendar/types.ts"],"sourcesContent":["export type CalendarViewMode = 'month' | 'week' | 'day' | 'agenda';\n\n// Where a calendar tile's data originally came from. Set by\n// client-side mappers ONLY — never present on the GraphQL response.\n// Page handlers branch on this to pick the matching subgraph for\n// mutations and the matching detail route. Omitted = default\n// `vibeCalendarTask` (rows from wspace-calendar-svc).\nexport type VibeCalendarTaskSource = 'scheduledJob' | 'vibeCalendarTask';\n\nexport interface VibeCalendarTask {\n id: string;\n /**\n * Set by client-side mappers (e.g. `jobToVibeCalendarTask`). Reading\n * code branches on this to pick the correct mutation/route. Omitted\n * for real VibeCalendarTask rows fetched from wspace-calendar-svc —\n * treat `undefined` as `'vibeCalendarTask'`.\n */\n source?: VibeCalendarTaskSource;\n title: string;\n description: string | null;\n calendarId: string;\n calendarEventId: string;\n agentId: string;\n taskConfig: {\n taskType: string;\n command?: string | null;\n script?: string | null;\n interpreter?: string | null;\n cwd?: string | null;\n env?: Record<string, string> | null;\n timeout?: number | null;\n webhookUrl?: string | null;\n webhookMethod?: string | null;\n webhookHeaders?: Record<string, string> | null;\n webhookAuthType?: string | null;\n webhookPayload?: Record<string, unknown> | null;\n };\n cronExpression: string | null;\n rrule: string | null;\n scheduledAt: string | null;\n timezone: string;\n nextExecutionAt: string | null;\n lastExecutedAt: string | null;\n status: string;\n totalExecutions: number;\n consecutiveFailures: number;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface VibeCalendarExecution {\n id: string;\n taskId: string;\n status: string;\n output: string | null;\n exitCode: number | null;\n webhookStatusCode: number | null;\n startedAt: string | null;\n completedAt: string | null;\n error: string | null;\n}\n\nexport interface CalendarOccurrence {\n task: VibeCalendarTask;\n occurrenceAt: string;\n isPast: boolean;\n execution: VibeCalendarExecution | null;\n}\n\nexport const STATUS_COLORS: Record<string, string> = {\n PENDING: 'bg-status-info-bg-subtle text-status-info-text border-status-info-border/30',\n RUNNING: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border/30',\n COMPLETED: 'bg-status-success-bg-subtle text-status-success-text border-status-success-border/30',\n FAILED: 'bg-status-error-bg-subtle text-status-error-text border-status-error-border/30',\n PAUSED: 'bg-bg-muted/10 text-text-tertiary border-border-subtle/30',\n CANCELLED: 'bg-bg-muted/10 text-text-tertiary border-border-subtle/30',\n DRAFT: 'bg-bg-muted/10 text-text-tertiary border-border-subtle/30',\n TIMEOUT: 'bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border/30',\n MANUAL: 'bg-accent-purple-subtle text-text-primary border-accent-purple/30',\n SCHEDULE: 'bg-status-info-bg-subtle text-status-info-text border-status-info-border/30',\n // Scheduler-svc JobStatus exclusives — without these the badge\n // silently falls back to PENDING styling on archived/disabled jobs.\n ARCHIVED: 'bg-bg-muted/10 text-text-tertiary border-border-subtle/30',\n DISABLED: 'bg-bg-muted/10 text-text-tertiary border-border-subtle/30',\n};\n"],"mappings":";AAqEA,IAAa,IAAwC;CACnD,SAAS;CACT,SAAS;CACT,WAAW;CACX,QAAQ;CACR,QAAQ;CACR,WAAW;CACX,OAAO;CACP,SAAS;CACT,QAAQ;CACR,UAAU;CAGV,UAAU;CACV,UAAU;CACX"}
@@ -208,7 +208,7 @@ var _ = ({ initialData: _, agents: v, onSubmit: y, onCancel: b, isLoading: x, ti
208
208
  }), /* @__PURE__ */ p("div", {
209
209
  className: "text-xs text-text-tertiary flex items-center gap-1",
210
210
  children: [/* @__PURE__ */ f("span", {
211
- className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-semibold bg-[var(--color-accent-soft)] text-action-primary-bg",
211
+ className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-semibold bg-[var(--color-accent-soft)] text-text-primary",
212
212
  children: V.actionType
213
213
  }), V.command && /* @__PURE__ */ f("span", {
214
214
  className: "font-mono truncate",