@burdenoff/microfe-vibecontrols 2026.623.3 → 2026.623.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/VibeControlsRoutes.js +161 -149
- package/dist/VibeControlsRoutes.js.map +1 -1
- package/dist/components/agents/AgentCard.js +1 -1
- package/dist/components/agents/AgentCard.js.map +1 -1
- package/dist/components/agents/AgentForm.js +2 -2
- package/dist/components/agents/AgentForm.js.map +1 -1
- package/dist/components/catalog/CatalogCard.js +9 -12
- package/dist/components/catalog/CatalogCard.js.map +1 -1
- package/dist/components/idp-components/ComponentCard.js +9 -12
- package/dist/components/idp-components/ComponentCard.js.map +1 -1
- package/dist/components/notes/NoteCard.js +9 -12
- package/dist/components/notes/NoteCard.js.map +1 -1
- package/dist/components/sessions/SessionCard.js +9 -12
- package/dist/components/sessions/SessionCard.js.map +1 -1
- package/dist/components/shared/AgentSetupBanner.js +2 -2
- package/dist/components/shared/AgentSetupBanner.js.map +1 -1
- package/dist/components/shared/Card.js +1 -1
- package/dist/components/shared/Card.js.map +1 -1
- package/dist/components/shared/InstallTabs.js +17 -56
- package/dist/components/shared/InstallTabs.js.map +1 -1
- package/dist/components/shared/StatCard.js +1 -1
- package/dist/components/shared/StatCard.js.map +1 -1
- package/dist/components/tunnels/TunnelCard.js +1 -1
- package/dist/components/tunnels/TunnelCard.js.map +1 -1
- package/dist/pages/GettingStartedPage.js +130 -114
- package/dist/pages/GettingStartedPage.js.map +1 -1
- package/dist/translations/en.js +27 -52
- package/dist/translations/en.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CatalogCard.js","names":[],"sources":["../../../src/components/catalog/CatalogCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n BookOpen,\n Eye,\n EyeOff,\n Globe,\n Tag,\n MoreVertical,\n Settings,\n Archive,\n Layers,\n Zap,\n} from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport interface CatalogCardData {\n id: string;\n name: string;\n description: string | null;\n visibility: 'PRIVATE' | 'INTERNAL' | 'PUBLIC';\n componentCount: number;\n vibeCount: number;\n tags: string[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface CatalogCardProps {\n catalog: CatalogCardData;\n onOpen?: (catalog: CatalogCardData) => void;\n onEdit?: (catalog: CatalogCardData) => void;\n onArchive?: (catalog: CatalogCardData) => void;\n className?: string;\n}\n\nconst visibilityConfig = {\n PRIVATE: { icon: EyeOff, label: 'Private', className: 'bg-bg-sunken text-text-secondary' },\n INTERNAL: {\n icon: Eye,\n label: 'Internal',\n className: 'bg-status-warning-bg-subtle text-status-warning-text',\n },\n PUBLIC: {\n icon: Globe,\n label: 'Public',\n className: 'bg-status-success-bg-subtle text-status-success-text',\n },\n};\n\nexport const CatalogCard: FC<CatalogCardProps> = ({\n catalog,\n onOpen,\n onEdit,\n onArchive,\n className = '',\n}) => {\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 visConfig = visibilityConfig[catalog.visibility] ?? visibilityConfig.PRIVATE;\n const VisIcon = visConfig.icon;\n\n return (\n <motion.div\n whileHover={{
|
|
1
|
+
{"version":3,"file":"CatalogCard.js","names":[],"sources":["../../../src/components/catalog/CatalogCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n BookOpen,\n Eye,\n EyeOff,\n Globe,\n Tag,\n MoreVertical,\n Settings,\n Archive,\n Layers,\n Zap,\n} from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport interface CatalogCardData {\n id: string;\n name: string;\n description: string | null;\n visibility: 'PRIVATE' | 'INTERNAL' | 'PUBLIC';\n componentCount: number;\n vibeCount: number;\n tags: string[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface CatalogCardProps {\n catalog: CatalogCardData;\n onOpen?: (catalog: CatalogCardData) => void;\n onEdit?: (catalog: CatalogCardData) => void;\n onArchive?: (catalog: CatalogCardData) => void;\n className?: string;\n}\n\nconst visibilityConfig = {\n PRIVATE: { icon: EyeOff, label: 'Private', className: 'bg-bg-sunken text-text-secondary' },\n INTERNAL: {\n icon: Eye,\n label: 'Internal',\n className: 'bg-status-warning-bg-subtle text-status-warning-text',\n },\n PUBLIC: {\n icon: Globe,\n label: 'Public',\n className: 'bg-status-success-bg-subtle text-status-success-text',\n },\n};\n\nexport const CatalogCard: FC<CatalogCardProps> = ({\n catalog,\n onOpen,\n onEdit,\n onArchive,\n className = '',\n}) => {\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 visConfig = visibilityConfig[catalog.visibility] ?? visibilityConfig.PRIVATE;\n const VisIcon = visConfig.icon;\n\n return (\n <motion.div\n whileHover={{ 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 ${className}`}\n onClick={() => onOpen?.(catalog)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n <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 <BookOpen 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 {catalog.name}\n </h3>\n <div className=\"flex items-center gap-2 mt-0.5\">\n <span\n className={`inline-flex items-center gap-1 px-1.5 py-0.5 text-xs rounded ${visConfig.className}`}\n >\n <VisIcon className=\"size-3\" />\n {tr(`catalog.visibility.${catalog.visibility.toLowerCase()}`, visConfig.label)}\n </span>\n </div>\n </div>\n </div>\n {(onEdit || onArchive) && (\n <div className=\"relative flex-shrink-0\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {onEdit && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onEdit(catalog);\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 <Settings className=\"size-4\" />\n {tr('common.edit', 'Edit')}\n </motion.button>\n )}\n {onArchive && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onArchive(catalog);\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-b-lg\"\n >\n <Archive className=\"size-4\" />\n {tr('common.archive', 'Archive')}\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n\n {catalog.description && (\n <p className=\"mt-2 text-sm text-text-secondary line-clamp-2\">{catalog.description}</p>\n )}\n\n {/* Stats row */}\n <div className=\"mt-3 flex items-center gap-4 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1\">\n <Layers className=\"size-3.5\" />\n {catalog.componentCount} {tr('catalog.components', 'components')}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Zap className=\"size-3.5\" />\n {catalog.vibeCount} {tr('catalog.vibes', 'vibes')}\n </span>\n </div>\n\n {catalog.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {catalog.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 {catalog.tags.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{catalog.tags.length - 3}\n </span>\n )}\n </div>\n )}\n </motion.div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -24,18 +24,15 @@ var v = {
|
|
|
24
24
|
return n === e ? t : n;
|
|
25
25
|
}, [b, x] = e(!1), S = v[i.type] || r;
|
|
26
26
|
return /* @__PURE__ */ _(h.div, {
|
|
27
|
-
whileHover: {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
]
|
|
37
|
-
}
|
|
38
|
-
},
|
|
27
|
+
whileHover: { transition: {
|
|
28
|
+
duration: .15,
|
|
29
|
+
ease: [
|
|
30
|
+
.4,
|
|
31
|
+
0,
|
|
32
|
+
.2,
|
|
33
|
+
1
|
|
34
|
+
]
|
|
35
|
+
} },
|
|
39
36
|
whileTap: { scale: .98 },
|
|
40
37
|
className: `bg-bg-surface border border-border-default rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer ${c}`,
|
|
41
38
|
onClick: () => a?.(i),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComponentCard.js","names":[],"sources":["../../../src/components/idp-components/ComponentCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Box,\n Globe,\n Library,\n Server,\n Smartphone,\n Database,\n Cog,\n Tag,\n MoreVertical,\n Settings,\n Archive,\n Code,\n} from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport interface ComponentCardData {\n id: string;\n name: string;\n description: string | null;\n type:\n | 'SERVICE'\n | 'FRONTEND'\n | 'LIBRARY'\n | 'API'\n | 'MOBILE'\n | 'DATABASE'\n | 'INFRASTRUCTURE'\n | 'CUSTOM';\n lifecycle: 'EXPERIMENTAL' | 'ACTIVE' | 'DEPRECATED' | 'END_OF_LIFE';\n version: string | null;\n language: string | null;\n framework: string | null;\n tags: string[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface ComponentCardProps {\n component: ComponentCardData;\n onOpen?: (component: ComponentCardData) => void;\n onEdit?: (component: ComponentCardData) => void;\n onArchive?: (component: ComponentCardData) => void;\n className?: string;\n}\n\nconst typeIcons: Record<string, typeof Box> = {\n SERVICE: Server,\n FRONTEND: Globe,\n LIBRARY: Library,\n API: Code,\n MOBILE: Smartphone,\n DATABASE: Database,\n INFRASTRUCTURE: Cog,\n CUSTOM: Box,\n};\n\nconst lifecycleStyles: Record<string, string> = {\n EXPERIMENTAL: 'bg-status-warning-bg-subtle text-status-warning-text',\n ACTIVE: 'bg-status-success-bg-subtle text-status-success-text',\n DEPRECATED: 'bg-status-error-bg-subtle text-status-error-text',\n END_OF_LIFE: 'bg-status-error-bg-subtle text-status-error-text',\n};\n\nexport const ComponentCard: FC<ComponentCardProps> = ({\n component,\n onOpen,\n onEdit,\n onArchive,\n className = '',\n}) => {\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 Icon = typeIcons[component.type] || Box;\n\n return (\n <motion.div\n whileHover={{
|
|
1
|
+
{"version":3,"file":"ComponentCard.js","names":[],"sources":["../../../src/components/idp-components/ComponentCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Box,\n Globe,\n Library,\n Server,\n Smartphone,\n Database,\n Cog,\n Tag,\n MoreVertical,\n Settings,\n Archive,\n Code,\n} from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport interface ComponentCardData {\n id: string;\n name: string;\n description: string | null;\n type:\n | 'SERVICE'\n | 'FRONTEND'\n | 'LIBRARY'\n | 'API'\n | 'MOBILE'\n | 'DATABASE'\n | 'INFRASTRUCTURE'\n | 'CUSTOM';\n lifecycle: 'EXPERIMENTAL' | 'ACTIVE' | 'DEPRECATED' | 'END_OF_LIFE';\n version: string | null;\n language: string | null;\n framework: string | null;\n tags: string[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface ComponentCardProps {\n component: ComponentCardData;\n onOpen?: (component: ComponentCardData) => void;\n onEdit?: (component: ComponentCardData) => void;\n onArchive?: (component: ComponentCardData) => void;\n className?: string;\n}\n\nconst typeIcons: Record<string, typeof Box> = {\n SERVICE: Server,\n FRONTEND: Globe,\n LIBRARY: Library,\n API: Code,\n MOBILE: Smartphone,\n DATABASE: Database,\n INFRASTRUCTURE: Cog,\n CUSTOM: Box,\n};\n\nconst lifecycleStyles: Record<string, string> = {\n EXPERIMENTAL: 'bg-status-warning-bg-subtle text-status-warning-text',\n ACTIVE: 'bg-status-success-bg-subtle text-status-success-text',\n DEPRECATED: 'bg-status-error-bg-subtle text-status-error-text',\n END_OF_LIFE: 'bg-status-error-bg-subtle text-status-error-text',\n};\n\nexport const ComponentCard: FC<ComponentCardProps> = ({\n component,\n onOpen,\n onEdit,\n onArchive,\n className = '',\n}) => {\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 Icon = typeIcons[component.type] || Box;\n\n return (\n <motion.div\n whileHover={{ 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 ${className}`}\n onClick={() => onOpen?.(component)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n <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 <Icon 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 {component.name}\n </h3>\n <div className=\"flex items-center gap-2 mt-0.5 flex-wrap\">\n <span className=\"text-xs text-text-secondary\">{component.type}</span>\n <span\n className={`inline-flex items-center px-1.5 py-0.5 text-xs rounded ${lifecycleStyles[component.lifecycle] || 'bg-bg-sunken text-text-secondary'}`}\n >\n {tr(\n `component.lifecycle.${component.lifecycle.toLowerCase()}`,\n component.lifecycle\n )}\n </span>\n </div>\n </div>\n </div>\n {(onEdit || onArchive) && (\n <div className=\"relative flex-shrink-0\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {onEdit && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onEdit(component);\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 <Settings className=\"size-4\" />\n {tr('common.edit', 'Edit')}\n </motion.button>\n )}\n {onArchive && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onArchive(component);\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-b-lg\"\n >\n <Archive className=\"size-4\" />\n {tr('common.archive', 'Archive')}\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n\n {component.description && (\n <p className=\"mt-2 text-sm text-text-secondary line-clamp-2\">{component.description}</p>\n )}\n\n {/* Metadata row */}\n <div className=\"mt-3 flex items-center gap-3 text-xs text-text-secondary flex-wrap\">\n {component.version && (\n <span className=\"font-mono bg-bg-sunken px-1.5 py-0.5 rounded\">v{component.version}</span>\n )}\n {component.language && <span>{component.language}</span>}\n {component.framework && <span className=\"text-text-muted\">{component.framework}</span>}\n </div>\n\n {component.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {component.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 {component.tags.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{component.tags.length - 3}\n </span>\n )}\n </div>\n )}\n </motion.div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -7,18 +7,15 @@ import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
|
7
7
|
function g({ note: g, onOpen: _, onEdit: v, onDelete: y, onPin: b, onUnpin: x, onShare: S, onShareAccess: C }) {
|
|
8
8
|
let { t: w } = t(), [T, E] = e(!1);
|
|
9
9
|
return /* @__PURE__ */ h(p.div, {
|
|
10
|
-
whileHover: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
]
|
|
20
|
-
}
|
|
21
|
-
},
|
|
10
|
+
whileHover: { transition: {
|
|
11
|
+
duration: .15,
|
|
12
|
+
ease: [
|
|
13
|
+
.4,
|
|
14
|
+
0,
|
|
15
|
+
.2,
|
|
16
|
+
1
|
|
17
|
+
]
|
|
18
|
+
} },
|
|
22
19
|
whileTap: { scale: .98 },
|
|
23
20
|
className: "bg-bg-surface border border-border-default rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer",
|
|
24
21
|
onClick: () => _?.(g),
|
|
@@ -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-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":";;;;;;AAkCA,SAAgB,EAAS,EACvB,SACA,WACA,WACA,aACA,UACA,YACA,YACA,oBACgB;CAChB,IAAM,EAAE,SAAM,GAAS,EAKjB,CAAC,GAAU,KAAe,EAAS,GAAM;AAmB/C,QACE,kBAAC,EAAO,KAAR;EACE,YAAY;GAAE,GAAG;GAAI,YAAY;IAAE,UAAU;IAAM,MAAM;KAAC;KAAK;KAAG;KAAK;KAAE;IAAE;GAAE;EAC7E,UAAU,EAAE,OAAO,KAAM;EACzB,WAAU;EACV,eAAe,IAAS,EAAK;YAJ/B;GAME,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAU,WAAU,mCAAoC,CAAA;MACpD,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,MAAD;SAAI,WAAU;mBAA4C,EAAK;SAAW,CAAA;QACzE,EAAK,YAAY,kBAAC,GAAD,EAAK,WAAU,iDAAkD,CAAA;QAClF,EAAK,eAAe,YACnB,kBAAC,QAAD;SAAM,OAAM;mBACV,kBAAC,GAAD,EAAM,WAAU,0CAA2C,CAAA;SACtD,CAAA,GAEP,kBAAC,QAAD;SAAM,OAAM;mBACV,kBAAC,GAAD,EAAO,WAAU,0CAA2C,CAAA;SACvD,CAAA;QAEL;UACN,kBAAC,KAAD;OAAG,WAAU;mBA3CH,MACX,IAAI,KAAK,EAAQ,CAAC,mBAAmB,SAAS;QACnD,OAAO;QACP,KAAK;QACL,MAAM;QACP,CAAC,EAsC6D,EAAK,UAAU;OAAK,CAAA,CACvE;QACF;QACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,UAAU,MAAM;AAEd,OADA,EAAE,iBAAiB,EACnB,EAAY,CAAC,EAAS;;MAExB,WAAU;gBAEV,kBAAC,GAAD,EAAc,WAAU,8BAA+B,CAAA;MAChD,CAAA,EACT,kBAAC,GAAD,EAAA,UACG,KACC,kBAAC,EAAO,KAAR;MACE,SAAS;OAAE,SAAS;OAAG,OAAO;OAAM,GAAG;OAAI;MAC3C,SAAS;OAAE,SAAS;OAAG,OAAO;OAAG,GAAG;OAAG;MACvC,MAAM;OAAE,SAAS;OAAG,OAAO;OAAM,GAAG;OAAI;MACxC,YAAY,EAAE,UAAU,KAAM;MAC9B,WAAU;gBALZ;OAOG,KACC,kBAAC,EAAO,QAAR;QACE,UAAU,EAAE,OAAO,KAAM;QACzB,UAAU,MAAM;AAGd,SAFA,EAAE,iBAAiB,EACnB,EAAO,EAAK,EACZ,EAAY,GAAM;;QAEpB,WAAU;kBAPZ,CASE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAAA,OAEb;;OAEjB,KAAW,EAAK,eAAe,aAC9B,kBAAC,EAAO,QAAR;QACE,UAAU,EAAE,OAAO,KAAM;QACzB,UAAU,MAAM;AAGd,SAFA,EAAE,iBAAiB,EACnB,EAAQ,EAAK,EACb,EAAY,GAAM;;QAEpB,WAAU;kBAPZ,CASE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,EAAA,uBAEf;;OAEjB,KAAW,EAAK,eAAe,eAC9B,kBAAC,EAAO,QAAR;QACE,UAAU,EAAE,OAAO,KAAM;QACzB,UAAU,MAAM;AAGd,SAFA,EAAE,iBAAiB,EACnB,EAAQ,EAAK,EACb,EAAY,GAAM;;QAEpB,WAAU;kBAPZ,CASE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAAA,eAEb;;OAEjB,KACC,kBAAC,EAAO,QAAR;QACE,UAAU,EAAE,OAAO,KAAM;QACzB,eAAY;QACZ,UAAU,MAAM;AAGd,SAFA,EAAE,iBAAiB,EACnB,EAAc,EAAK,EACnB,EAAY,GAAM;;QAEpB,WAAU;kBARZ,CAUE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,EAAA,gBAEf;;OAEjB,EAAK,YAAY,KAChB,kBAAC,EAAO,QAAR;QACE,UAAU,EAAE,OAAO,KAAM;QACzB,UAAU,MAAM;AAGd,SAFA,EAAE,iBAAiB,EACnB,EAAQ,EAAK,EACb,EAAY,GAAM;;QAEpB,WAAU;kBAPZ,CASE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,EAAA,QAEf;;OAEjB,CAAC,EAAK,YAAY,KACjB,kBAAC,EAAO,QAAR;QACE,UAAU,EAAE,OAAO,KAAM;QACzB,UAAU,MAAM;AAGd,SAFA,EAAE,iBAAiB,EACnB,EAAM,EAAK,EACX,EAAY,GAAM;;QAEpB,WAAU;kBAPZ,CASE,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA,EAAA,MAEZ;;OAEjB,KACC,kBAAC,EAAO,QAAR;QACE,UAAU,EAAE,OAAO,KAAM;QACzB,UAAU,MAAM;AAGd,SAFA,EAAE,iBAAiB,EACnB,EAAS,EAAK,EACd,EAAY,GAAM;;QAEpB,WAAU;kBAPZ,CASE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,EAAA,SAEf;;OAEP;SAEC,CAAA,CACd;OACF;;GAEN,kBAAC,KAAD;IAAG,WAAU;gBAnKG,MAAoB;KAEtC,IAAM,IAAO,EACV,QAAQ,YAAY,GAAG,CACvB,QAAQ,OAAO,IAAI,CACnB,MAAM;AACT,YAAO,EAAK,SAAS,MAAM,EAAK,MAAM,GAAG,IAAI,GAAG,QAAQ;OA6JmB,EAAK,QAAQ;IAAK,CAAA;GAG1F,EAAK,kBAAkB,EAAK,eAAe,SAAS,IACnD,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAK,eAAe,MAAM,GAAG,EAAE,CAAC,KAAK,MACpC,kBAAC,QAAD;KAEE,WAAU;KACV,OACE,EAAW,KAAK,QACZ,EAAE,YAAY,aAAa,EAAW,IAAI,SAAS,GACnD,KAAA;eANR,CASE,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA,EACzB,EAAW,KAAK,SAAS,EAAW,MAAM,MAAM,GAAG,EAAE,CACjD;OAVA,EAAW,GAUX,CACP,EACD,EAAK,eAAe,SAAS,KAC5B,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAA+E,KAC3E,EAAK,eAAe,SAAS,EAC1B;OAEL;QACJ,EAAK,QAAQ,EAAK,KAAK,SAAS,IAClC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAK,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,MAC1B,kBAAC,QAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA,EACzB,EACI;OALA,EAKA,CACP,EACD,EAAK,KAAK,SAAS,KAClB,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAA+E,KAC3E,EAAK,KAAK,SAAS,EAChB;OAEL;QACJ;GACO"}
|
|
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={{ 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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"}
|
|
@@ -19,18 +19,15 @@ var _ = {
|
|
|
19
19
|
return t < 6e4 ? "Just now" : t < 36e5 ? `${Math.floor(t / 6e4)}m ago` : t < 864e5 ? `${Math.floor(t / 36e5)}h ago` : new Date(e).toLocaleDateString();
|
|
20
20
|
}, N = v.status === "RUNNING", P = v.status === "STOPPED" || v.status === "TERMINATED";
|
|
21
21
|
return /* @__PURE__ */ g(m.div, {
|
|
22
|
-
whileHover: {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
},
|
|
22
|
+
whileHover: { transition: {
|
|
23
|
+
duration: .15,
|
|
24
|
+
ease: [
|
|
25
|
+
.4,
|
|
26
|
+
0,
|
|
27
|
+
.2,
|
|
28
|
+
1
|
|
29
|
+
]
|
|
30
|
+
} },
|
|
34
31
|
whileTap: { scale: .98 },
|
|
35
32
|
className: `bg-bg-surface border rounded-card shadow-elevation-1 p-5 hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 ease-[var(--motion-easing-out)] cursor-pointer ${E ? "border-transparent ring-1 ring-[var(--color-focus-ring)] bg-[var(--color-accent-soft)]" : "border-border-subtle"} ${O}`,
|
|
36
33
|
onClick: () => y?.(v),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/components/sessions/SessionCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Terminal,\n Clock,\n MoreVertical,\n Play,\n Square,\n RotateCcw,\n Trash2,\n Share2,\n} from 'lucide-react';\nimport { SessionStatusBadge } from './SessionStatusBadge';\nimport { AgentInfoPopover, TargetInfoPopover } from './EntityInfoPopover';\nimport type { Session } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface SessionCardProps {\n session: Session;\n onOpen?: (session: Session) => void;\n onStart?: (session: Session) => void;\n onStop?: (session: Session) => void;\n onRestart?: (session: Session) => void;\n onTerminate?: (session: Session) => void;\n onOpenTerminal?: (session: Session) => void;\n /** Callback to open the share dialog */\n onShare?: (session: Session) => void;\n /** Whether this card is selected for bulk operations */\n selected?: boolean;\n /** Toggle selection for bulk operations */\n onToggleSelect?: () => void;\n className?: string;\n}\n\nconst sessionTypeIcons: Record<string, string> = {\n TMUX: '🖥️',\n SSH: '🔐',\n TERMINAL: '💻',\n SCRIPT: '📜',\n CUSTOM: '⚙️',\n};\n\n/**\n * SessionCard - Display terminal session information\n */\nexport const SessionCard: FC<SessionCardProps> = ({\n session,\n onOpen,\n onStart,\n onStop,\n onRestart,\n onTerminate,\n onOpenTerminal,\n onShare,\n selected = false,\n onToggleSelect,\n className = '',\n}) => {\n const tr = useTr();\n const [showMenu, setShowMenu] = useState(false);\n\n const formatTime = (timestamp: string | undefined | null) => {\n if (!timestamp) return 'Never';\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return 'Just now';\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return new Date(timestamp).toLocaleDateString();\n };\n\n const isRunning = session.status === 'RUNNING';\n const isStopped = session.status === 'STOPPED' || session.status === 'TERMINATED';\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className={`bg-bg-surface border rounded-card shadow-elevation-1 p-5 hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 ease-[var(--motion-easing-out)] cursor-pointer ${selected ? 'border-transparent ring-1 ring-[var(--color-focus-ring)] bg-[var(--color-accent-soft)]' : 'border-border-subtle'} ${className}`}\n onClick={() => onOpen?.(session)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {onToggleSelect && (\n <div className=\"flex-shrink-0 pt-0.5\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selected}\n onChange={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n onClick={(e) => e.stopPropagation()}\n className=\"size-4 rounded border-border-default cursor-pointer\"\n />\n </div>\n )}\n <div className=\"flex-shrink-0 size-9 sm:size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Terminal className=\"size-4 sm:size-5 text-action-primary-bg\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base tracking-tight\">\n {session.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">\n {sessionTypeIcons[session.type] || '💻'}\n </span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary\">{session.type}</p>\n {session.agent && (\n <div className=\"mt-1.5 flex items-center gap-1 flex-wrap\">\n <AgentInfoPopover agent={session.agent} />\n {session.agent.target && <TargetInfoPopover target={session.agent.target} />}\n </div>\n )}\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <SessionStatusBadge status={session.status} />\n {(onStart || onStop || onRestart || onTerminate) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"inline-flex size-8 items-center justify-center rounded-button text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n aria-label={tr('vibecontrols.sessions.card.actions', 'Session actions')}\n >\n <MoreVertical className=\"size-4\" aria-hidden=\"true\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1.5 w-44 bg-bg-elevated border border-border-subtle rounded-card shadow-elevation-3 z-10 overflow-hidden py-1\"\n >\n {isStopped && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Play className=\"size-4\" />\n {tr('vibecontrols.sessions.card.start', 'Start')}\n </motion.button>\n )}\n {isRunning && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Square className=\"size-4\" />\n {tr('vibecontrols.sessions.card.stop', 'Stop')}\n </motion.button>\n )}\n {isRunning && onRestart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onRestart(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <RotateCcw className=\"size-4\" />\n {tr('vibecontrols.sessions.card.restart', 'Restart')}\n </motion.button>\n )}\n {isRunning && onOpenTerminal && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onOpenTerminal(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Terminal className=\"size-4\" />\n {tr('vibecontrols.sessions.card.openTerminal', 'Open Terminal')}\n </motion.button>\n )}\n {isRunning && onShare && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Share2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.shareSession', 'Share Session')}\n </motion.button>\n )}\n {onTerminate && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onTerminate(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-status-error-bg-subtle transition-colors\"\n >\n <Trash2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.terminate', 'Terminate')}\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n {/* Provider-native session name (e.g. tmux session name like\n `vibe-051c9c62`). Surfaced so users can recognise their session\n if they ever need to attach from a CLI (`tmux attach -t …`)\n and so reconnect-after-restart flows are obvious. */}\n {(() => {\n const meta = (session.metadata as Record<string, unknown> | null) || {};\n const externalName =\n (typeof meta.tmuxSessionName === 'string' && meta.tmuxSessionName) ||\n (typeof meta.weztermWorkspace === 'string' && meta.weztermWorkspace) ||\n (typeof meta.zellijSessionName === 'string' && meta.zellijSessionName) ||\n (typeof meta.externalSessionName === 'string' && meta.externalSessionName) ||\n session.providerSessionName ||\n null;\n return externalName ? (\n <div\n className=\"mt-3 text-[10px] font-mono text-text-muted\"\n title={`Provider session: ${externalName}`}\n data-testid=\"session-card-external-name\"\n >\n <span className=\"opacity-70\">{session.type.toLowerCase()}: </span>\n <span className=\"break-all\">{externalName}</span>\n </div>\n ) : null;\n })()}\n\n {session.command && (\n <div className=\"mt-4 text-xs font-mono bg-bg-sunken/60 px-2 py-1.5 rounded truncate text-text-secondary\">\n $ {session.command}\n </div>\n )}\n\n {session.lastOutput && (\n <div className=\"mt-2 text-xs font-mono bg-bg-sunken/60 text-status-success-text px-2 py-1.5 rounded truncate\">\n {session.lastOutput}\n </div>\n )}\n\n <div className=\"mt-4 flex items-center justify-between text-sm text-text-secondary\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-4\" />\n <span className=\"tabular-nums\">{formatTime(session.updatedAt)}</span>\n </div>\n {session.pid && <span className=\"text-xs font-mono tabular-nums\">PID: {session.pid}</span>}\n </div>\n </motion.div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/components/sessions/SessionCard.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport {\n Terminal,\n Clock,\n MoreVertical,\n Play,\n Square,\n RotateCcw,\n Trash2,\n Share2,\n} from 'lucide-react';\nimport { SessionStatusBadge } from './SessionStatusBadge';\nimport { AgentInfoPopover, TargetInfoPopover } from './EntityInfoPopover';\nimport type { Session } from './types';\nimport { useTr } from '../../shared/hooks/useTr';\n\nexport interface SessionCardProps {\n session: Session;\n onOpen?: (session: Session) => void;\n onStart?: (session: Session) => void;\n onStop?: (session: Session) => void;\n onRestart?: (session: Session) => void;\n onTerminate?: (session: Session) => void;\n onOpenTerminal?: (session: Session) => void;\n /** Callback to open the share dialog */\n onShare?: (session: Session) => void;\n /** Whether this card is selected for bulk operations */\n selected?: boolean;\n /** Toggle selection for bulk operations */\n onToggleSelect?: () => void;\n className?: string;\n}\n\nconst sessionTypeIcons: Record<string, string> = {\n TMUX: '🖥️',\n SSH: '🔐',\n TERMINAL: '💻',\n SCRIPT: '📜',\n CUSTOM: '⚙️',\n};\n\n/**\n * SessionCard - Display terminal session information\n */\nexport const SessionCard: FC<SessionCardProps> = ({\n session,\n onOpen,\n onStart,\n onStop,\n onRestart,\n onTerminate,\n onOpenTerminal,\n onShare,\n selected = false,\n onToggleSelect,\n className = '',\n}) => {\n const tr = useTr();\n const [showMenu, setShowMenu] = useState(false);\n\n const formatTime = (timestamp: string | undefined | null) => {\n if (!timestamp) return 'Never';\n const diff = Date.now() - new Date(timestamp).getTime();\n if (diff < 60000) return 'Just now';\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n return new Date(timestamp).toLocaleDateString();\n };\n\n const isRunning = session.status === 'RUNNING';\n const isStopped = session.status === 'STOPPED' || session.status === 'TERMINATED';\n\n return (\n <motion.div\n whileHover={{ transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className={`bg-bg-surface border rounded-card shadow-elevation-1 p-5 hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 ease-[var(--motion-easing-out)] cursor-pointer ${selected ? 'border-transparent ring-1 ring-[var(--color-focus-ring)] bg-[var(--color-accent-soft)]' : 'border-border-subtle'} ${className}`}\n onClick={() => onOpen?.(session)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-start gap-2 sm:gap-3 min-w-0 flex-1\">\n {onToggleSelect && (\n <div className=\"flex-shrink-0 pt-0.5\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={selected}\n onChange={(e) => {\n e.stopPropagation();\n onToggleSelect();\n }}\n onClick={(e) => e.stopPropagation()}\n className=\"size-4 rounded border-border-default cursor-pointer\"\n />\n </div>\n )}\n <div className=\"flex-shrink-0 size-9 sm:size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Terminal className=\"size-4 sm:size-5 text-action-primary-bg\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 sm:gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary truncate max-w-[120px] sm:max-w-none text-sm sm:text-base tracking-tight\">\n {session.name}\n </h3>\n <span className=\"text-base sm:text-lg flex-shrink-0\">\n {sessionTypeIcons[session.type] || '💻'}\n </span>\n </div>\n <p className=\"text-xs sm:text-sm text-text-secondary\">{session.type}</p>\n {session.agent && (\n <div className=\"mt-1.5 flex items-center gap-1 flex-wrap\">\n <AgentInfoPopover agent={session.agent} />\n {session.agent.target && <TargetInfoPopover target={session.agent.target} />}\n </div>\n )}\n </div>\n </div>\n {/* Status and Actions - always visible */}\n <div className=\"flex items-center gap-1 sm:gap-2 flex-shrink-0\">\n <SessionStatusBadge status={session.status} />\n {(onStart || onStop || onRestart || onTerminate) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"inline-flex size-8 items-center justify-center rounded-button text-text-secondary hover:bg-bg-sunken hover:text-text-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n aria-label={tr('vibecontrols.sessions.card.actions', 'Session actions')}\n >\n <MoreVertical className=\"size-4\" aria-hidden=\"true\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1.5 w-44 bg-bg-elevated border border-border-subtle rounded-card shadow-elevation-3 z-10 overflow-hidden py-1\"\n >\n {isStopped && onStart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStart(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Play className=\"size-4\" />\n {tr('vibecontrols.sessions.card.start', 'Start')}\n </motion.button>\n )}\n {isRunning && onStop && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onStop(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Square className=\"size-4\" />\n {tr('vibecontrols.sessions.card.stop', 'Stop')}\n </motion.button>\n )}\n {isRunning && onRestart && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onRestart(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <RotateCcw className=\"size-4\" />\n {tr('vibecontrols.sessions.card.restart', 'Restart')}\n </motion.button>\n )}\n {isRunning && onOpenTerminal && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onOpenTerminal(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Terminal className=\"size-4\" />\n {tr('vibecontrols.sessions.card.openTerminal', 'Open Terminal')}\n </motion.button>\n )}\n {isRunning && onShare && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Share2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.shareSession', 'Share Session')}\n </motion.button>\n )}\n {onTerminate && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onTerminate(session);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-status-error-bg-subtle transition-colors\"\n >\n <Trash2 className=\"size-4\" />\n {tr('vibecontrols.sessions.card.terminate', 'Terminate')}\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </div>\n </div>\n\n {/* Provider-native session name (e.g. tmux session name like\n `vibe-051c9c62`). Surfaced so users can recognise their session\n if they ever need to attach from a CLI (`tmux attach -t …`)\n and so reconnect-after-restart flows are obvious. */}\n {(() => {\n const meta = (session.metadata as Record<string, unknown> | null) || {};\n const externalName =\n (typeof meta.tmuxSessionName === 'string' && meta.tmuxSessionName) ||\n (typeof meta.weztermWorkspace === 'string' && meta.weztermWorkspace) ||\n (typeof meta.zellijSessionName === 'string' && meta.zellijSessionName) ||\n (typeof meta.externalSessionName === 'string' && meta.externalSessionName) ||\n session.providerSessionName ||\n null;\n return externalName ? (\n <div\n className=\"mt-3 text-[10px] font-mono text-text-muted\"\n title={`Provider session: ${externalName}`}\n data-testid=\"session-card-external-name\"\n >\n <span className=\"opacity-70\">{session.type.toLowerCase()}: </span>\n <span className=\"break-all\">{externalName}</span>\n </div>\n ) : null;\n })()}\n\n {session.command && (\n <div className=\"mt-4 text-xs font-mono bg-bg-sunken/60 px-2 py-1.5 rounded truncate text-text-secondary\">\n $ {session.command}\n </div>\n )}\n\n {session.lastOutput && (\n <div className=\"mt-2 text-xs font-mono bg-bg-sunken/60 text-status-success-text px-2 py-1.5 rounded truncate\">\n {session.lastOutput}\n </div>\n )}\n\n <div className=\"mt-4 flex items-center justify-between text-sm text-text-secondary\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-4\" />\n <span className=\"tabular-nums\">{formatTime(session.updatedAt)}</span>\n </div>\n {session.pid && <span className=\"text-xs font-mono tabular-nums\">PID: {session.pid}</span>}\n </div>\n </motion.div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ import { Button as _ } from "@burdenoff/fe-libs/ui";
|
|
|
10
10
|
//#region src/components/shared/AgentSetupBanner.tsx
|
|
11
11
|
var v = "https://www.npmjs.com/package/@vibecontrols/agent";
|
|
12
12
|
function y() {
|
|
13
|
-
return
|
|
13
|
+
return "bun install -g @vibecontrols/agent";
|
|
14
14
|
}
|
|
15
15
|
function b({ variant: b = "compact", dismissible: x = !0, dismissKey: S = "vibecontrols-setup-banner-dismissed", className: C = "" }) {
|
|
16
16
|
let w = e(), T = t(), E = i(() => y(), []), [D, O] = a(!1), [k, A] = a(() => {
|
|
@@ -137,7 +137,7 @@ function b({ variant: b = "compact", dismissible: x = !0, dismissKey: S = "vibec
|
|
|
137
137
|
children: w("vibecontrols.shared.agentSetupBanner.title", "Get Started with VibeControls Agent")
|
|
138
138
|
}), /* @__PURE__ */ h("p", {
|
|
139
139
|
className: "text-sm text-text-secondary mt-0.5",
|
|
140
|
-
children: w("vibecontrols.shared.agentSetupBanner.description", "Install the
|
|
140
|
+
children: w("vibecontrols.shared.agentSetupBanner.description", "Install the agent globally on any machine you want to manage. Bun (or Node >= 18 for npm) is the only prerequisite.")
|
|
141
141
|
})] })]
|
|
142
142
|
}),
|
|
143
143
|
/* @__PURE__ */ h("div", {
|
|
@@ -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-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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"}
|
|
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 * The documented quick-install command — install the agent globally with bun\n * (the platform default). Mirrors `InstallTabs`' recommended method so the\n * banner and the Getting Started guide stay in sync.\n *\n * Official docs: https://docs.vibecontrols.com/users/installing-the-agent\n */\nfunction getQuickInstallCommand(): string {\n return 'bun install -g @vibecontrols/agent';\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 agent globally on any machine you want to manage. Bun (or Node >= 18 for npm) is the only prerequisite.'\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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"}
|
|
@@ -8,7 +8,7 @@ function t({ children: t, className: n = "", onClick: r, hoverable: i = !1, padd
|
|
|
8
8
|
lg: "p-6"
|
|
9
9
|
}[a]}`;
|
|
10
10
|
return /* @__PURE__ */ e(r ? "button" : "div", {
|
|
11
|
-
className: `${o} ${i || r ? "hover:border-border-strong hover:shadow-elevation-2
|
|
11
|
+
className: `${o} ${i || r ? "hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 ease-[var(--motion-easing-out)] cursor-pointer" : ""} ${n}`,
|
|
12
12
|
onClick: r,
|
|
13
13
|
children: t
|
|
14
14
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Card.js","names":[],"sources":["../../../src/components/shared/Card.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\n\ninterface CardProps {\n children: ReactNode;\n className?: string;\n onClick?: () => void;\n hoverable?: boolean;\n padding?: 'none' | 'sm' | 'md' | 'lg';\n}\n\nexport function Card({\n children,\n className = '',\n onClick,\n hoverable = false,\n padding = 'md',\n}: CardProps) {\n const paddingStyles = {\n none: '',\n sm: 'p-4',\n md: 'p-5',\n lg: 'p-6',\n };\n\n const baseStyles = `bg-bg-surface border border-border-subtle rounded-card shadow-elevation-1 ${paddingStyles[padding]}`;\n const isInteractive = hoverable || !!onClick;\n\n // Boff UI interactive card: calm border + elevation lift + ≤2px translate on\n // hover. `transition-all duration-200` with the easing token; reduced-motion\n // is handled globally so the lift is safe.\n const interactiveStyles = isInteractive\n ? 'hover:border-border-strong hover:shadow-elevation-2
|
|
1
|
+
{"version":3,"file":"Card.js","names":[],"sources":["../../../src/components/shared/Card.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\n\ninterface CardProps {\n children: ReactNode;\n className?: string;\n onClick?: () => void;\n hoverable?: boolean;\n padding?: 'none' | 'sm' | 'md' | 'lg';\n}\n\nexport function Card({\n children,\n className = '',\n onClick,\n hoverable = false,\n padding = 'md',\n}: CardProps) {\n const paddingStyles = {\n none: '',\n sm: 'p-4',\n md: 'p-5',\n lg: 'p-6',\n };\n\n const baseStyles = `bg-bg-surface border border-border-subtle rounded-card shadow-elevation-1 ${paddingStyles[padding]}`;\n const isInteractive = hoverable || !!onClick;\n\n // Boff UI interactive card: calm border + elevation lift + ≤2px translate on\n // hover. `transition-all duration-200` with the easing token; reduced-motion\n // is handled globally so the lift is safe.\n const interactiveStyles = isInteractive\n ? 'hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 ease-[var(--motion-easing-out)] cursor-pointer'\n : '';\n\n const Component = onClick ? 'button' : 'div';\n\n return (\n <Component className={`${baseStyles} ${interactiveStyles} ${className}`} onClick={onClick}>\n {children}\n </Component>\n );\n}\n\ninterface CardHeaderProps {\n title: string;\n subtitle?: string;\n action?: ReactNode;\n icon?: ReactNode;\n}\n\nexport function CardHeader({ title, subtitle, action, icon }: CardHeaderProps) {\n return (\n <div className=\"flex items-start justify-between gap-3 mb-4\">\n <div className=\"flex items-start gap-3 min-w-0\">\n {icon && (\n <div className=\"flex-shrink-0 size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n {icon}\n </div>\n )}\n <div className=\"min-w-0\">\n <h3 className=\"font-semibold text-text-primary truncate tracking-tight\">{title}</h3>\n {subtitle && <p className=\"text-sm text-text-secondary truncate\">{subtitle}</p>}\n </div>\n </div>\n {action && <div className=\"flex-shrink-0\">{action}</div>}\n </div>\n );\n}\n\ninterface CardContentProps {\n children: ReactNode;\n className?: string;\n}\n\nexport function CardContent({ children, className = '' }: CardContentProps) {\n return <div className={className}>{children}</div>;\n}\n\ninterface CardFooterProps {\n children: ReactNode;\n className?: string;\n}\n\nexport function CardFooter({ children, className = '' }: CardFooterProps) {\n return <div className={`mt-5 pt-4 border-t border-border-subtle ${className}`}>{children}</div>;\n}\n"],"mappings":";;AAUA,SAAgB,EAAK,EACnB,aACA,eAAY,IACZ,YACA,eAAY,IACZ,aAAU,QACE;CAQZ,IAAM,IAAa,6EAPG;EACpB,MAAM;EACN,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,CAE6G;AAY9G,QACE,kBAHgB,IAAU,WAAW,OAGrC;EAAW,WAAW,GAAG,EAAW,GAZhB,KAAe,IAMjC,mIACA,GAKuD,GAAG;EAAsB;EAC/E;EACS,CAAA"}
|
|
@@ -4,10 +4,7 @@ import { Check as i, Copy as a, Terminal as o } from "lucide-react";
|
|
|
4
4
|
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
5
5
|
import { Tabs as l } from "@burdenoff/fe-libs/chrome";
|
|
6
6
|
//#region src/components/shared/InstallTabs.tsx
|
|
7
|
-
function u() {
|
|
8
|
-
return typeof window > "u" ? "vibecontrols.local.burdenoff.com" : window.__VIBECONTROLS_HOST__ ?? window.location.host;
|
|
9
|
-
}
|
|
10
|
-
function d({ command: n }) {
|
|
7
|
+
function u({ command: n }) {
|
|
11
8
|
let l = e(), [u, d] = r(!1), f = t(async () => {
|
|
12
9
|
try {
|
|
13
10
|
await navigator.clipboard.writeText(n);
|
|
@@ -36,75 +33,39 @@ function d({ command: n }) {
|
|
|
36
33
|
]
|
|
37
34
|
});
|
|
38
35
|
}
|
|
39
|
-
function
|
|
40
|
-
let a = e(), o
|
|
41
|
-
id: "
|
|
42
|
-
label: a("vibecontrols.shared.installTabs.
|
|
36
|
+
function d({ pinnedAgentVersion: t, compact: i = !1 }) {
|
|
37
|
+
let a = e(), [o, d] = r("bun"), f = n(() => [{
|
|
38
|
+
id: "bun",
|
|
39
|
+
label: a("vibecontrols.shared.installTabs.bunRecommended", "Bun (Recommended)")
|
|
43
40
|
}, {
|
|
44
41
|
id: "npm",
|
|
45
42
|
label: a("vibecontrols.shared.installTabs.npm", "NPM")
|
|
46
|
-
}], [a]),
|
|
47
|
-
{
|
|
48
|
-
id: "linux",
|
|
49
|
-
label: a("vibecontrols.shared.installTabs.linux", "Linux")
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
id: "macos",
|
|
53
|
-
label: a("vibecontrols.shared.installTabs.macos", "macOS")
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
id: "windows",
|
|
57
|
-
label: a("vibecontrols.shared.installTabs.windows", "Windows")
|
|
58
|
-
}
|
|
59
|
-
], [a]), v = n(() => {
|
|
43
|
+
}], [a]), p = n(() => {
|
|
60
44
|
let e = t ? `@${t}` : "@latest";
|
|
61
|
-
return
|
|
62
|
-
}, [
|
|
63
|
-
f,
|
|
64
|
-
m,
|
|
65
|
-
o,
|
|
66
|
-
t
|
|
67
|
-
]), y = f === "curl" ? m === "windows" ? `https://${o}/install.ps1` : `https://${o}/install` : null;
|
|
45
|
+
return o === "bun" ? `bun install -g @vibecontrols/agent${e}` : `npm install -g @vibecontrols/agent${e}`;
|
|
46
|
+
}, [o, t]);
|
|
68
47
|
return /* @__PURE__ */ c("div", {
|
|
69
48
|
className: i ? "space-y-3" : "space-y-4",
|
|
70
49
|
children: [
|
|
71
50
|
/* @__PURE__ */ s(l, {
|
|
72
|
-
items:
|
|
73
|
-
activeTabId:
|
|
74
|
-
onTabChange: (e) =>
|
|
51
|
+
items: f,
|
|
52
|
+
activeTabId: o,
|
|
53
|
+
onTabChange: (e) => d(e),
|
|
75
54
|
variant: "underline"
|
|
76
55
|
}),
|
|
77
|
-
/* @__PURE__ */ s(
|
|
78
|
-
|
|
79
|
-
activeTabId: m,
|
|
80
|
-
onTabChange: (e) => h(e),
|
|
81
|
-
variant: "pills",
|
|
82
|
-
size: "sm"
|
|
83
|
-
}),
|
|
84
|
-
/* @__PURE__ */ s(d, { command: v }),
|
|
85
|
-
f === "curl" && y && /* @__PURE__ */ c("p", {
|
|
56
|
+
/* @__PURE__ */ s(u, { command: p }),
|
|
57
|
+
o === "bun" && /* @__PURE__ */ s("p", {
|
|
86
58
|
className: "text-xs text-text-muted",
|
|
87
|
-
children:
|
|
88
|
-
a("vibecontrols.shared.installTabs.curlDescription", "The script installs bun (pinned, v1.3.10) then installs @vibecontrols/agent and core plugins. Only bun is required on your machine — no other prerequisites. The trailing “&& hash -r” refreshes your shell so “vibe” works immediately (using fish? drop it — fish auto-rehashes)."),
|
|
89
|
-
" ",
|
|
90
|
-
/* @__PURE__ */ s("a", {
|
|
91
|
-
href: y,
|
|
92
|
-
target: "_blank",
|
|
93
|
-
rel: "noreferrer",
|
|
94
|
-
className: "text-text-link hover:text-text-linkHover hover:underline",
|
|
95
|
-
children: a("vibecontrols.shared.installTabs.viewInstallScript", "View the install script")
|
|
96
|
-
}),
|
|
97
|
-
"."
|
|
98
|
-
]
|
|
59
|
+
children: a("vibecontrols.shared.installTabs.bunDescription", "Bun is the platform default and the only prerequisite. You can pin a specific release with @<version> (defaults to the latest).")
|
|
99
60
|
}),
|
|
100
|
-
|
|
61
|
+
o === "npm" && /* @__PURE__ */ s("p", {
|
|
101
62
|
className: "text-xs text-text-muted",
|
|
102
|
-
children: a("vibecontrols.shared.installTabs.npmDescription", "Requires Node.js >= 18. You can
|
|
63
|
+
children: a("vibecontrols.shared.installTabs.npmDescription", "Requires Node.js >= 18. You can pin a specific release with @<version> (defaults to the latest).")
|
|
103
64
|
})
|
|
104
65
|
]
|
|
105
66
|
});
|
|
106
67
|
}
|
|
107
68
|
//#endregion
|
|
108
|
-
export {
|
|
69
|
+
export { d as InstallTabs };
|
|
109
70
|
|
|
110
71
|
//# sourceMappingURL=InstallTabs.js.map
|