@burdenoff/microfe-vibecontrols 2026.623.2 → 2026.623.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/actions/ActionTypeBadge.js +1 -1
- package/dist/components/actions/ActionTypeBadge.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
- package/dist/components/agent-manager/BridleConfigPanel.js +2 -2
- package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionLogsPanel.js +2 -2
- package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
- package/dist/components/agents/AgentForm.js +2 -2
- package/dist/components/agents/AgentForm.js.map +1 -1
- package/dist/components/ai/AIContextsTab.js +1 -1
- package/dist/components/ai/AIContextsTab.js.map +1 -1
- package/dist/components/ai/AIOverviewTab.js +1 -1
- package/dist/components/ai/AIOverviewTab.js.map +1 -1
- package/dist/components/ai/AIPromptsTab.js +1 -1
- package/dist/components/ai/AIPromptsTab.js.map +1 -1
- package/dist/components/ai/AIQueueTab.js +1 -1
- package/dist/components/ai/AIQueueTab.js.map +1 -1
- package/dist/components/ai/AISessionsTab.js +1 -1
- package/dist/components/ai/AISessionsTab.js.map +1 -1
- package/dist/components/audit/AuditLogTable.js +1 -1
- package/dist/components/audit/AuditLogTable.js.map +1 -1
- package/dist/components/docs/DocsPageTree.js +1 -1
- package/dist/components/docs/DocsPageTree.js.map +1 -1
- package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
- package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
- package/dist/components/graph/MeshDeployWizard.js +3 -3
- package/dist/components/graph/MeshDeployWizard.js.map +1 -1
- package/dist/components/graph/MeshRequestPanel.js +2 -2
- package/dist/components/graph/MeshRequestPanel.js.map +1 -1
- package/dist/components/notes/NoteCard.js +1 -1
- package/dist/components/notes/NoteCard.js.map +1 -1
- package/dist/components/notes/NoteEditor.js +1 -1
- package/dist/components/notes/NoteEditor.js.map +1 -1
- package/dist/components/notes/NoteList.js +1 -1
- package/dist/components/notes/NoteList.js.map +1 -1
- package/dist/components/shared/AgentSetupBanner.js +5 -5
- package/dist/components/shared/AgentSetupBanner.js.map +1 -1
- package/dist/components/shared/InstallTabs.js +17 -56
- package/dist/components/shared/InstallTabs.js.map +1 -1
- package/dist/components/templates/TemplateCard.js +1 -1
- package/dist/components/templates/TemplateCard.js.map +1 -1
- package/dist/components/vibecalendar/types.js +1 -1
- package/dist/components/vibecalendar/types.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckExecutionLog.js +2 -2
- package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
- package/dist/pages/AgentsPage.js +2 -2
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +1 -1
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +1 -1
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/GettingStartedPage.js +130 -114
- package/dist/pages/GettingStartedPage.js.map +1 -1
- package/dist/pages/OverviewPage.js +10 -10
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +1 -1
- package/dist/pages/PluginsCatalogPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +3 -3
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/SharedSessionPage.js +2 -2
- package/dist/pages/SharedSessionPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +2 -2
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +2 -2
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/VibesPage.js +2 -2
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/WebhooksPage.js +2 -2
- package/dist/pages/WebhooksPage.js.map +1 -1
- package/dist/pages/ai/AISessionDetailPage.js +2 -2
- package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
- package/dist/translations/en.js +27 -52
- package/dist/translations/en.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MeshDeployWizard.js","names":[],"sources":["../../../src/components/graph/MeshDeployWizard.tsx"],"sourcesContent":["/**\n * MeshDeployWizard — deploy a new agent THROUGH the mesh from a chosen source\n * (root) agent. Supports two modes:\n * - \"Remote (SSH)\": deploy onto another machine over SSH (host/user/port/key).\n * - \"Same machine\": deploy a sibling agent on the source agent's own host.\n *\n * Submitting calls `deployAgentViaMesh({ rootAgentId, input })`, reads the\n * `{ jobId }` out of the AgentProxyResponse, then polls\n * `meshDeployJobStatus(rootAgentId, jobId)` on a 2s interval, rendering the\n * job's step list in a progress panel. On completion it calls `onCompleted`\n * so the caller can refetch the mesh graph.\n */\n\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { Loader2, CheckCircle, XCircle, Check, Server, Laptop } from 'lucide-react';\nimport {\n Button,\n Checkbox,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Stepper,\n} from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useField } from '../../shared/hooks/useField';\nimport {\n useDeployAgentViaMeshMutation,\n useMeshDeployJobStatusLazyQuery,\n} from '@/generated/wspace-operations';\n\n// English fallbacks for the mesh-deploy job step labels. Extends the\n// install-job labels with the mesh-specific bootstrap/onboard/peer-probe\n// steps. Translated via `mesh.deploy.steps.{step}` keys.\nconst stepLabels: Record<string, string> = {\n probe: 'Probing target',\n 'install-bun': 'Installing Bun runtime',\n 'install-agent': 'Installing VibeControls Agent',\n autostart: 'Configuring autostart',\n plugins: 'Installing plugins',\n start: 'Starting agent',\n bootstrap: 'Bootstrapping tunnel',\n onboard: 'Onboarding agent',\n 'store-creds': 'Storing credentials',\n 'report-link': 'Linking into mesh',\n 'probe-peer': 'Probing new peer',\n};\n\ninterface DeployJobStep {\n step: string;\n status: string;\n message?: string;\n}\n\ninterface DeployJob {\n status: 'running' | 'completed' | 'failed' | string;\n steps: DeployJobStep[];\n error?: string;\n}\n\ninterface SourceAgentOption {\n id: string;\n name: string;\n hostname: string;\n isActive: boolean;\n}\n\ninterface MeshDeployWizardProps {\n /** Agents that can act as the deploying root. */\n sourceAgents: SourceAgentOption[];\n /** Pre-seeded source (root) agent id (e.g. \"deploy from here\" on a node). */\n initialSourceAgentId?: string | null;\n availablePermissions?: string[];\n onClose: () => void;\n onCompleted: () => void;\n}\n\ntype DeployMode = 'ssh' | 'same';\ntype AuthMethod = 'agent' | 'key';\n\nfunction asRecord(v: unknown): Record<string, unknown> | null {\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : null;\n}\nfunction jstr(obj: Record<string, unknown> | null, key: string): string | null {\n const v = obj?.[key];\n return typeof v === 'string' ? v : null;\n}\n\nconst LABEL_CLASS = 'mb-1.5 block text-sm font-medium text-text-primary';\nconst CHIP_BASE_CLASS =\n 'rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors max-md:min-h-11 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2';\nconst MODE_BUTTON_BASE_CLASS =\n 'inline-flex flex-1 items-center justify-center gap-2 rounded-md border px-3 py-2 text-sm transition-colors max-md:min-h-11 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2';\n\nexport function MeshDeployWizard({\n sourceAgents,\n initialSourceAgentId,\n availablePermissions,\n onClose,\n onCompleted,\n}: MeshDeployWizardProps) {\n const tr = useTr();\n\n const [sourceAgentId, setSourceAgentId] = useState<string>(\n initialSourceAgentId ?? sourceAgents[0]?.id ?? ''\n );\n const [mode, setMode] = useState<DeployMode>('ssh');\n const [host, setHost] = useState('');\n const [port, setPort] = useState(22);\n const [user, setUser] = useState('');\n const [authMethod, setAuthMethod] = useState<AuthMethod>('agent');\n const [keyPath, setKeyPath] = useState('');\n const [label, setLabel] = useState('');\n const [connectExisting, setConnectExisting] = useState(false);\n const [permissions, setPermissions] = useState<Set<string>>(new Set());\n\n const [jobId, setJobId] = useState<string | null>(null);\n const [job, setJob] = useState<DeployJob | null>(null);\n const [submitError, setSubmitError] = useState<string | null>(null);\n\n const sourceField = useField();\n const hostField = useField();\n const portField = useField();\n const userField = useField();\n const keyField = useField();\n const labelField = useField();\n\n const [deploy, { loading: deploying }] = useDeployAgentViaMeshMutation();\n const [fetchJobStatus] = useMeshDeployJobStatusLazyQuery({ fetchPolicy: 'network-only' });\n\n const canSubmit = useMemo(() => {\n if (!sourceAgentId) return false;\n if (mode === 'ssh') {\n const targetSpec = host.trim();\n return targetSpec.length > 0 && user.trim().length > 0;\n }\n return true;\n }, [sourceAgentId, mode, host, user]);\n\n const togglePermission = useCallback((p: string) => {\n setPermissions((prev) => {\n const next = new Set(prev);\n if (next.has(p)) next.delete(p);\n else next.add(p);\n return next;\n });\n }, []);\n\n // Build the SSH `target` string the backend expects (user@host:port).\n const buildTargetString = useCallback((): string => {\n if (mode === 'same') return 'localhost';\n const trimmedHost = host.trim();\n const trimmedUser = user.trim();\n const base = trimmedUser ? `${trimmedUser}@${trimmedHost}` : trimmedHost;\n return port && port !== 22 ? `${base}:${port}` : base;\n }, [mode, host, user, port]);\n\n const handleSubmit = useCallback(async () => {\n if (!canSubmit) return;\n setSubmitError(null);\n setJob(null);\n setJobId(null);\n try {\n const res = await deploy({\n variables: {\n rootAgentId: sourceAgentId,\n input: {\n target: buildTargetString(),\n permissions: permissions.size > 0 ? Array.from(permissions) : undefined,\n identityFile: mode === 'ssh' && authMethod === 'key' ? keyPath || undefined : undefined,\n label: label || undefined,\n connectExisting,\n },\n },\n });\n const proxy = res.data?.deployAgentViaMesh;\n if (res.error || proxy?.error || proxy?.ok === false) {\n setSubmitError(\n proxy?.error ?? res.error?.message ?? tr('mesh.deploy.failed', 'Deploy failed')\n );\n return;\n }\n const data = asRecord(proxy?.data);\n const newJobId = jstr(data, 'jobId');\n if (!newJobId) {\n setSubmitError(\n tr('mesh.deploy.noJob', 'Deploy started but no job id was returned by the agent.')\n );\n return;\n }\n setJobId(newJobId);\n setJob({ status: jstr(data, 'status') ?? 'running', steps: [] });\n } catch (err) {\n setSubmitError(err instanceof Error ? err.message : String(err));\n }\n }, [\n canSubmit,\n deploy,\n sourceAgentId,\n buildTargetString,\n permissions,\n mode,\n authMethod,\n keyPath,\n label,\n connectExisting,\n tr,\n ]);\n\n // Poll the mesh-deploy job once a jobId is established.\n useEffect(() => {\n if (!jobId || !sourceAgentId) return;\n let cancelled = false;\n\n const poll = () => {\n void fetchJobStatus({ variables: { rootAgentId: sourceAgentId, jobId } })\n .then((result) => {\n if (cancelled) return;\n const proxy = result.data?.meshDeployJobStatus;\n const data = asRecord(proxy?.data);\n if (!data) return;\n const rawSteps = Array.isArray(data.steps) ? (data.steps as DeployJobStep[]) : [];\n setJob({\n status: jstr(data, 'status') ?? 'running',\n steps: rawSteps,\n error: jstr(data, 'error') ?? proxy?.error ?? undefined,\n });\n const status = jstr(data, 'status');\n if (status === 'completed') {\n onCompleted();\n }\n })\n .catch(() => {\n // transient — retry on next interval\n });\n };\n\n poll();\n const interval = setInterval(poll, 2000);\n return () => {\n cancelled = true;\n clearInterval(interval);\n };\n }, [jobId, sourceAgentId, fetchJobStatus, onCompleted]);\n\n const jobRunning =\n job?.status === 'running' ||\n (jobId !== null && job?.status !== 'completed' && job?.status !== 'failed');\n\n const wizardSteps = [\n { key: 'configure', label: tr('mesh.deploy.steps.configure', 'Configure') },\n { key: 'deploy', label: tr('mesh.deploy.steps.deploy', 'Deploy') },\n ];\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(open) => {\n if (!open && !jobRunning) onClose();\n }}\n >\n <ResponsiveDialogContent\n className=\"max-h-[min(85vh,44rem)] overflow-y-auto sm:max-w-lg\"\n showCloseButton={!jobRunning}\n >\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('mesh.deploy.title', 'Deploy Agent via Mesh')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {tr(\n 'mesh.deploy.description',\n 'Deploy a new agent through the mesh from a selected source agent.'\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <Stepper steps={wizardSteps} active={jobId ? 1 : 0} />\n\n {/* Job progress takes over once a job is running */}\n {jobId ? (\n <div className=\"space-y-3\">\n <p role=\"status\" className=\"text-sm text-text-secondary\">\n {tr('mesh.deploy.deploying', 'Deploying through {{agent}}…', {\n agent: sourceAgents.find((a) => a.id === sourceAgentId)?.name ?? 'agent',\n })}\n </p>\n <div className=\"space-y-2\">\n {job && job.steps.length > 0 ? (\n job.steps.map((step, idx) => (\n <div key={`${step.step}-${idx}`} className=\"flex items-center gap-3\">\n {step.status === 'running' && (\n <Loader2 className=\"size-4 animate-spin text-text-link\" aria-hidden=\"true\" />\n )}\n {step.status === 'completed' && (\n <CheckCircle className=\"size-4 text-status-success-text\" aria-hidden=\"true\" />\n )}\n {step.status === 'failed' && (\n <XCircle className=\"size-4 text-status-error-text\" aria-hidden=\"true\" />\n )}\n {step.status === 'skipped' && (\n <Check className=\"size-4 text-text-muted\" aria-hidden=\"true\" />\n )}\n {step.status === 'pending' && (\n <div\n className=\"size-4 rounded-full border-2 border-border-default\"\n aria-hidden=\"true\"\n />\n )}\n <div className=\"min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">\n {tr(`mesh.deploy.steps.${step.step}`, stepLabels[step.step] ?? step.step)}\n </div>\n {step.message ? (\n <div className=\"text-xs break-words text-text-muted\">{step.message}</div>\n ) : null}\n </div>\n </div>\n ))\n ) : (\n <div className=\"flex items-center gap-3\">\n <Loader2 className=\"size-4 animate-spin text-text-link\" aria-hidden=\"true\" />\n <span className=\"text-sm text-text-secondary\">\n {tr('mesh.deploy.starting', 'Starting deploy job…')}\n </span>\n </div>\n )}\n </div>\n\n {job?.status === 'completed' && (\n <div\n role=\"status\"\n className=\"rounded-lg border border-status-success-border bg-status-success-bg-subtle p-3 text-sm text-status-success-text\"\n >\n {tr('mesh.deploy.success', 'Agent deployed successfully!')}\n </div>\n )}\n {job?.status === 'failed' && (\n <div\n role=\"alert\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\"\n >\n {job.error ?? tr('mesh.deploy.jobFailed', 'Deploy failed.')}\n </div>\n )}\n\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={onClose}\n disabled={jobRunning}\n aria-busy={jobRunning}\n className=\"w-full max-md:min-h-11\"\n >\n {jobRunning\n ? tr('mesh.deploy.deployingShort', 'Deploying…')\n : tr('common.close', 'Close')}\n </Button>\n </ResponsiveDialogFooter>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {/* Source agent */}\n <div>\n <label className={LABEL_CLASS} htmlFor={sourceField.id}>\n {tr('mesh.deploy.sourceAgent', 'Source agent (deploys from)')}\n </label>\n <Select\n value={sourceAgentId || undefined}\n onValueChange={setSourceAgentId}\n disabled={sourceAgents.length === 0}\n >\n <SelectTrigger id={sourceField.id} className=\"w-full text-base md:text-sm\">\n <SelectValue placeholder={tr('mesh.deploy.noAgents', 'No agents available')} />\n </SelectTrigger>\n <SelectContent>\n {sourceAgents.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name} ({a.hostname}){a.isActive ? '' : ' · offline'}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {/* Mode toggle */}\n <div>\n <span className={LABEL_CLASS}>{tr('mesh.deploy.mode', 'Deploy mode')}</span>\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n aria-pressed={mode === 'ssh'}\n onClick={() => setMode('ssh')}\n className={`${MODE_BUTTON_BASE_CLASS} ${\n mode === 'ssh'\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n <Server className=\"size-3.5\" aria-hidden=\"true\" />\n {tr('mesh.deploy.modeSsh', 'Remote (SSH)')}\n </button>\n <button\n type=\"button\"\n aria-pressed={mode === 'same'}\n onClick={() => setMode('same')}\n className={`${MODE_BUTTON_BASE_CLASS} ${\n mode === 'same'\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n <Laptop className=\"size-3.5\" aria-hidden=\"true\" />\n {tr('mesh.deploy.modeSame', 'Same machine')}\n </button>\n </div>\n </div>\n\n {/* SSH fields */}\n {mode === 'ssh' && (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-1 gap-2 sm:grid-cols-3\">\n <div className=\"sm:col-span-2\">\n <label className={LABEL_CLASS} htmlFor={hostField.id}>\n {tr('mesh.deploy.host', 'Host / alias')}\n </label>\n <Input\n {...hostField.inputProps}\n type=\"text\"\n value={host}\n onChange={(e) => setHost(e.target.value)}\n placeholder=\"prod-web-01 or 10.0.0.5\"\n className=\"font-mono text-base md:text-sm\"\n />\n </div>\n <div>\n <label className={LABEL_CLASS} htmlFor={portField.id}>\n {tr('mesh.deploy.port', 'Port')}\n </label>\n <Input\n {...portField.inputProps}\n type=\"number\"\n value={port}\n onChange={(e) => setPort(Number(e.target.value) || 22)}\n className=\"text-base md:text-sm\"\n />\n </div>\n </div>\n <div>\n <label className={LABEL_CLASS} htmlFor={userField.id}>\n {tr('mesh.deploy.user', 'User')}\n </label>\n <Input\n {...userField.inputProps}\n type=\"text\"\n value={user}\n onChange={(e) => setUser(e.target.value)}\n placeholder=\"root\"\n className=\"font-mono text-base md:text-sm\"\n />\n </div>\n <div>\n <span className={LABEL_CLASS}>{tr('mesh.deploy.auth', 'Authentication')}</span>\n <div className=\"flex flex-wrap gap-x-4 gap-y-2\">\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-primary max-md:min-h-11\">\n <input\n type=\"radio\"\n name=\"mesh-auth\"\n checked={authMethod === 'agent'}\n onChange={() => setAuthMethod('agent')}\n />\n {tr('mesh.deploy.authAgent', 'SSH agent / default key')}\n </label>\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-primary max-md:min-h-11\">\n <input\n type=\"radio\"\n name=\"mesh-auth\"\n checked={authMethod === 'key'}\n onChange={() => setAuthMethod('key')}\n />\n {tr('mesh.deploy.authKey', 'Key path')}\n </label>\n </div>\n </div>\n {authMethod === 'key' && (\n <div>\n <label className={LABEL_CLASS} htmlFor={keyField.id}>\n {tr('mesh.deploy.keyPath', 'Identity file (key path)')}\n </label>\n <Input\n {...keyField.inputProps}\n type=\"text\"\n value={keyPath}\n onChange={(e) => setKeyPath(e.target.value)}\n placeholder=\"~/.ssh/id_ed25519\"\n className=\"font-mono text-base md:text-sm\"\n />\n </div>\n )}\n </div>\n )}\n\n {/* Label */}\n <div>\n <label className={LABEL_CLASS} htmlFor={labelField.id}>\n {tr('mesh.deploy.label', 'Label (optional)')}\n </label>\n <Input\n {...labelField.inputProps}\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n placeholder={tr('mesh.deploy.labelPlaceholder', 'e.g. Production web tier')}\n className=\"text-base md:text-sm\"\n />\n </div>\n\n {/* Permission chips */}\n {availablePermissions && availablePermissions.length > 0 && (\n <div>\n <span className={LABEL_CLASS}>\n {tr('mesh.deploy.permissions', 'Grant permissions on the new link')}\n </span>\n <div className=\"flex flex-wrap gap-2\">\n {availablePermissions.map((p) => (\n <button\n type=\"button\"\n key={p}\n aria-pressed={permissions.has(p)}\n onClick={() => togglePermission(p)}\n className={`${CHIP_BASE_CLASS} ${\n permissions.has(p)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {p}\n </button>\n ))}\n </div>\n </div>\n )}\n\n <label className=\"flex cursor-pointer items-start gap-2 text-sm text-text-primary\">\n <Checkbox\n checked={connectExisting}\n onCheckedChange={(checked) => setConnectExisting(checked === true)}\n className=\"mt-0.5\"\n />\n <span>\n {tr(\n 'mesh.deploy.connectExisting',\n 'Connect to an existing agent on the target instead of installing'\n )}\n </span>\n </label>\n\n {submitError && (\n <div\n role=\"alert\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\"\n >\n {submitError}\n </div>\n )}\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={onClose} className=\"max-md:min-h-11\">\n {tr('common.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => void handleSubmit()}\n disabled={!canSubmit || deploying}\n aria-busy={deploying}\n className=\"max-md:min-h-11\"\n >\n {deploying ? <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" /> : null}\n {tr('mesh.deploy.submit', 'Deploy')}\n </Button>\n </ResponsiveDialogFooter>\n </div>\n )}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MeshDeployWizard.js","names":[],"sources":["../../../src/components/graph/MeshDeployWizard.tsx"],"sourcesContent":["/**\n * MeshDeployWizard — deploy a new agent THROUGH the mesh from a chosen source\n * (root) agent. Supports two modes:\n * - \"Remote (SSH)\": deploy onto another machine over SSH (host/user/port/key).\n * - \"Same machine\": deploy a sibling agent on the source agent's own host.\n *\n * Submitting calls `deployAgentViaMesh({ rootAgentId, input })`, reads the\n * `{ jobId }` out of the AgentProxyResponse, then polls\n * `meshDeployJobStatus(rootAgentId, jobId)` on a 2s interval, rendering the\n * job's step list in a progress panel. On completion it calls `onCompleted`\n * so the caller can refetch the mesh graph.\n */\n\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { Loader2, CheckCircle, XCircle, Check, Server, Laptop } from 'lucide-react';\nimport {\n Button,\n Checkbox,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Stepper,\n} from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useField } from '../../shared/hooks/useField';\nimport {\n useDeployAgentViaMeshMutation,\n useMeshDeployJobStatusLazyQuery,\n} from '@/generated/wspace-operations';\n\n// English fallbacks for the mesh-deploy job step labels. Extends the\n// install-job labels with the mesh-specific bootstrap/onboard/peer-probe\n// steps. Translated via `mesh.deploy.steps.{step}` keys.\nconst stepLabels: Record<string, string> = {\n probe: 'Probing target',\n 'install-bun': 'Installing Bun runtime',\n 'install-agent': 'Installing VibeControls Agent',\n autostart: 'Configuring autostart',\n plugins: 'Installing plugins',\n start: 'Starting agent',\n bootstrap: 'Bootstrapping tunnel',\n onboard: 'Onboarding agent',\n 'store-creds': 'Storing credentials',\n 'report-link': 'Linking into mesh',\n 'probe-peer': 'Probing new peer',\n};\n\ninterface DeployJobStep {\n step: string;\n status: string;\n message?: string;\n}\n\ninterface DeployJob {\n status: 'running' | 'completed' | 'failed' | string;\n steps: DeployJobStep[];\n error?: string;\n}\n\ninterface SourceAgentOption {\n id: string;\n name: string;\n hostname: string;\n isActive: boolean;\n}\n\ninterface MeshDeployWizardProps {\n /** Agents that can act as the deploying root. */\n sourceAgents: SourceAgentOption[];\n /** Pre-seeded source (root) agent id (e.g. \"deploy from here\" on a node). */\n initialSourceAgentId?: string | null;\n availablePermissions?: string[];\n onClose: () => void;\n onCompleted: () => void;\n}\n\ntype DeployMode = 'ssh' | 'same';\ntype AuthMethod = 'agent' | 'key';\n\nfunction asRecord(v: unknown): Record<string, unknown> | null {\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : null;\n}\nfunction jstr(obj: Record<string, unknown> | null, key: string): string | null {\n const v = obj?.[key];\n return typeof v === 'string' ? v : null;\n}\n\nconst LABEL_CLASS = 'mb-1.5 block text-sm font-medium text-text-primary';\nconst CHIP_BASE_CLASS =\n 'rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors max-md:min-h-11 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2';\nconst MODE_BUTTON_BASE_CLASS =\n 'inline-flex flex-1 items-center justify-center gap-2 rounded-md border px-3 py-2 text-sm transition-colors max-md:min-h-11 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2';\n\nexport function MeshDeployWizard({\n sourceAgents,\n initialSourceAgentId,\n availablePermissions,\n onClose,\n onCompleted,\n}: MeshDeployWizardProps) {\n const tr = useTr();\n\n const [sourceAgentId, setSourceAgentId] = useState<string>(\n initialSourceAgentId ?? sourceAgents[0]?.id ?? ''\n );\n const [mode, setMode] = useState<DeployMode>('ssh');\n const [host, setHost] = useState('');\n const [port, setPort] = useState(22);\n const [user, setUser] = useState('');\n const [authMethod, setAuthMethod] = useState<AuthMethod>('agent');\n const [keyPath, setKeyPath] = useState('');\n const [label, setLabel] = useState('');\n const [connectExisting, setConnectExisting] = useState(false);\n const [permissions, setPermissions] = useState<Set<string>>(new Set());\n\n const [jobId, setJobId] = useState<string | null>(null);\n const [job, setJob] = useState<DeployJob | null>(null);\n const [submitError, setSubmitError] = useState<string | null>(null);\n\n const sourceField = useField();\n const hostField = useField();\n const portField = useField();\n const userField = useField();\n const keyField = useField();\n const labelField = useField();\n\n const [deploy, { loading: deploying }] = useDeployAgentViaMeshMutation();\n const [fetchJobStatus] = useMeshDeployJobStatusLazyQuery({ fetchPolicy: 'network-only' });\n\n const canSubmit = useMemo(() => {\n if (!sourceAgentId) return false;\n if (mode === 'ssh') {\n const targetSpec = host.trim();\n return targetSpec.length > 0 && user.trim().length > 0;\n }\n return true;\n }, [sourceAgentId, mode, host, user]);\n\n const togglePermission = useCallback((p: string) => {\n setPermissions((prev) => {\n const next = new Set(prev);\n if (next.has(p)) next.delete(p);\n else next.add(p);\n return next;\n });\n }, []);\n\n // Build the SSH `target` string the backend expects (user@host:port).\n const buildTargetString = useCallback((): string => {\n if (mode === 'same') return 'localhost';\n const trimmedHost = host.trim();\n const trimmedUser = user.trim();\n const base = trimmedUser ? `${trimmedUser}@${trimmedHost}` : trimmedHost;\n return port && port !== 22 ? `${base}:${port}` : base;\n }, [mode, host, user, port]);\n\n const handleSubmit = useCallback(async () => {\n if (!canSubmit) return;\n setSubmitError(null);\n setJob(null);\n setJobId(null);\n try {\n const res = await deploy({\n variables: {\n rootAgentId: sourceAgentId,\n input: {\n target: buildTargetString(),\n permissions: permissions.size > 0 ? Array.from(permissions) : undefined,\n identityFile: mode === 'ssh' && authMethod === 'key' ? keyPath || undefined : undefined,\n label: label || undefined,\n connectExisting,\n },\n },\n });\n const proxy = res.data?.deployAgentViaMesh;\n if (res.error || proxy?.error || proxy?.ok === false) {\n setSubmitError(\n proxy?.error ?? res.error?.message ?? tr('mesh.deploy.failed', 'Deploy failed')\n );\n return;\n }\n const data = asRecord(proxy?.data);\n const newJobId = jstr(data, 'jobId');\n if (!newJobId) {\n setSubmitError(\n tr('mesh.deploy.noJob', 'Deploy started but no job id was returned by the agent.')\n );\n return;\n }\n setJobId(newJobId);\n setJob({ status: jstr(data, 'status') ?? 'running', steps: [] });\n } catch (err) {\n setSubmitError(err instanceof Error ? err.message : String(err));\n }\n }, [\n canSubmit,\n deploy,\n sourceAgentId,\n buildTargetString,\n permissions,\n mode,\n authMethod,\n keyPath,\n label,\n connectExisting,\n tr,\n ]);\n\n // Poll the mesh-deploy job once a jobId is established.\n useEffect(() => {\n if (!jobId || !sourceAgentId) return;\n let cancelled = false;\n\n const poll = () => {\n void fetchJobStatus({ variables: { rootAgentId: sourceAgentId, jobId } })\n .then((result) => {\n if (cancelled) return;\n const proxy = result.data?.meshDeployJobStatus;\n const data = asRecord(proxy?.data);\n if (!data) return;\n const rawSteps = Array.isArray(data.steps) ? (data.steps as DeployJobStep[]) : [];\n setJob({\n status: jstr(data, 'status') ?? 'running',\n steps: rawSteps,\n error: jstr(data, 'error') ?? proxy?.error ?? undefined,\n });\n const status = jstr(data, 'status');\n if (status === 'completed') {\n onCompleted();\n }\n })\n .catch(() => {\n // transient — retry on next interval\n });\n };\n\n poll();\n const interval = setInterval(poll, 2000);\n return () => {\n cancelled = true;\n clearInterval(interval);\n };\n }, [jobId, sourceAgentId, fetchJobStatus, onCompleted]);\n\n const jobRunning =\n job?.status === 'running' ||\n (jobId !== null && job?.status !== 'completed' && job?.status !== 'failed');\n\n const wizardSteps = [\n { key: 'configure', label: tr('mesh.deploy.steps.configure', 'Configure') },\n { key: 'deploy', label: tr('mesh.deploy.steps.deploy', 'Deploy') },\n ];\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(open) => {\n if (!open && !jobRunning) onClose();\n }}\n >\n <ResponsiveDialogContent\n className=\"max-h-[min(85vh,44rem)] overflow-y-auto sm:max-w-lg\"\n showCloseButton={!jobRunning}\n >\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('mesh.deploy.title', 'Deploy Agent via Mesh')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {tr(\n 'mesh.deploy.description',\n 'Deploy a new agent through the mesh from a selected source agent.'\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <Stepper steps={wizardSteps} active={jobId ? 1 : 0} />\n\n {/* Job progress takes over once a job is running */}\n {jobId ? (\n <div className=\"space-y-3\">\n <p role=\"status\" className=\"text-sm text-text-secondary\">\n {tr('mesh.deploy.deploying', 'Deploying through {{agent}}…', {\n agent: sourceAgents.find((a) => a.id === sourceAgentId)?.name ?? 'agent',\n })}\n </p>\n <div className=\"space-y-2\">\n {job && job.steps.length > 0 ? (\n job.steps.map((step, idx) => (\n <div key={`${step.step}-${idx}`} className=\"flex items-center gap-3\">\n {step.status === 'running' && (\n <Loader2 className=\"size-4 animate-spin text-text-link\" aria-hidden=\"true\" />\n )}\n {step.status === 'completed' && (\n <CheckCircle className=\"size-4 text-status-success-text\" aria-hidden=\"true\" />\n )}\n {step.status === 'failed' && (\n <XCircle className=\"size-4 text-status-error-text\" aria-hidden=\"true\" />\n )}\n {step.status === 'skipped' && (\n <Check className=\"size-4 text-text-muted\" aria-hidden=\"true\" />\n )}\n {step.status === 'pending' && (\n <div\n className=\"size-4 rounded-full border-2 border-border-default\"\n aria-hidden=\"true\"\n />\n )}\n <div className=\"min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">\n {tr(`mesh.deploy.steps.${step.step}`, stepLabels[step.step] ?? step.step)}\n </div>\n {step.message ? (\n <div className=\"text-xs break-words text-text-muted\">{step.message}</div>\n ) : null}\n </div>\n </div>\n ))\n ) : (\n <div className=\"flex items-center gap-3\">\n <Loader2 className=\"size-4 animate-spin text-text-link\" aria-hidden=\"true\" />\n <span className=\"text-sm text-text-secondary\">\n {tr('mesh.deploy.starting', 'Starting deploy job…')}\n </span>\n </div>\n )}\n </div>\n\n {job?.status === 'completed' && (\n <div\n role=\"status\"\n className=\"rounded-lg border border-status-success-border bg-status-success-bg-subtle p-3 text-sm text-status-success-text\"\n >\n {tr('mesh.deploy.success', 'Agent deployed successfully!')}\n </div>\n )}\n {job?.status === 'failed' && (\n <div\n role=\"alert\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\"\n >\n {job.error ?? tr('mesh.deploy.jobFailed', 'Deploy failed.')}\n </div>\n )}\n\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={onClose}\n disabled={jobRunning}\n aria-busy={jobRunning}\n className=\"w-full max-md:min-h-11\"\n >\n {jobRunning\n ? tr('mesh.deploy.deployingShort', 'Deploying…')\n : tr('common.close', 'Close')}\n </Button>\n </ResponsiveDialogFooter>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {/* Source agent */}\n <div>\n <label className={LABEL_CLASS} htmlFor={sourceField.id}>\n {tr('mesh.deploy.sourceAgent', 'Source agent (deploys from)')}\n </label>\n <Select\n value={sourceAgentId || undefined}\n onValueChange={setSourceAgentId}\n disabled={sourceAgents.length === 0}\n >\n <SelectTrigger id={sourceField.id} className=\"w-full text-base md:text-sm\">\n <SelectValue placeholder={tr('mesh.deploy.noAgents', 'No agents available')} />\n </SelectTrigger>\n <SelectContent>\n {sourceAgents.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name} ({a.hostname}){a.isActive ? '' : ' · offline'}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {/* Mode toggle */}\n <div>\n <span className={LABEL_CLASS}>{tr('mesh.deploy.mode', 'Deploy mode')}</span>\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n aria-pressed={mode === 'ssh'}\n onClick={() => setMode('ssh')}\n className={`${MODE_BUTTON_BASE_CLASS} ${\n mode === 'ssh'\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n <Server className=\"size-3.5\" aria-hidden=\"true\" />\n {tr('mesh.deploy.modeSsh', 'Remote (SSH)')}\n </button>\n <button\n type=\"button\"\n aria-pressed={mode === 'same'}\n onClick={() => setMode('same')}\n className={`${MODE_BUTTON_BASE_CLASS} ${\n mode === 'same'\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n <Laptop className=\"size-3.5\" aria-hidden=\"true\" />\n {tr('mesh.deploy.modeSame', 'Same machine')}\n </button>\n </div>\n </div>\n\n {/* SSH fields */}\n {mode === 'ssh' && (\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-1 gap-2 sm:grid-cols-3\">\n <div className=\"sm:col-span-2\">\n <label className={LABEL_CLASS} htmlFor={hostField.id}>\n {tr('mesh.deploy.host', 'Host / alias')}\n </label>\n <Input\n {...hostField.inputProps}\n type=\"text\"\n value={host}\n onChange={(e) => setHost(e.target.value)}\n placeholder=\"prod-web-01 or 10.0.0.5\"\n className=\"font-mono text-base md:text-sm\"\n />\n </div>\n <div>\n <label className={LABEL_CLASS} htmlFor={portField.id}>\n {tr('mesh.deploy.port', 'Port')}\n </label>\n <Input\n {...portField.inputProps}\n type=\"number\"\n value={port}\n onChange={(e) => setPort(Number(e.target.value) || 22)}\n className=\"text-base md:text-sm\"\n />\n </div>\n </div>\n <div>\n <label className={LABEL_CLASS} htmlFor={userField.id}>\n {tr('mesh.deploy.user', 'User')}\n </label>\n <Input\n {...userField.inputProps}\n type=\"text\"\n value={user}\n onChange={(e) => setUser(e.target.value)}\n placeholder=\"root\"\n className=\"font-mono text-base md:text-sm\"\n />\n </div>\n <div>\n <span className={LABEL_CLASS}>{tr('mesh.deploy.auth', 'Authentication')}</span>\n <div className=\"flex flex-wrap gap-x-4 gap-y-2\">\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-primary max-md:min-h-11\">\n <input\n type=\"radio\"\n name=\"mesh-auth\"\n checked={authMethod === 'agent'}\n onChange={() => setAuthMethod('agent')}\n />\n {tr('mesh.deploy.authAgent', 'SSH agent / default key')}\n </label>\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-primary max-md:min-h-11\">\n <input\n type=\"radio\"\n name=\"mesh-auth\"\n checked={authMethod === 'key'}\n onChange={() => setAuthMethod('key')}\n />\n {tr('mesh.deploy.authKey', 'Key path')}\n </label>\n </div>\n </div>\n {authMethod === 'key' && (\n <div>\n <label className={LABEL_CLASS} htmlFor={keyField.id}>\n {tr('mesh.deploy.keyPath', 'Identity file (key path)')}\n </label>\n <Input\n {...keyField.inputProps}\n type=\"text\"\n value={keyPath}\n onChange={(e) => setKeyPath(e.target.value)}\n placeholder=\"~/.ssh/id_ed25519\"\n className=\"font-mono text-base md:text-sm\"\n />\n </div>\n )}\n </div>\n )}\n\n {/* Label */}\n <div>\n <label className={LABEL_CLASS} htmlFor={labelField.id}>\n {tr('mesh.deploy.label', 'Label (optional)')}\n </label>\n <Input\n {...labelField.inputProps}\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n placeholder={tr('mesh.deploy.labelPlaceholder', 'e.g. Production web tier')}\n className=\"text-base md:text-sm\"\n />\n </div>\n\n {/* Permission chips */}\n {availablePermissions && availablePermissions.length > 0 && (\n <div>\n <span className={LABEL_CLASS}>\n {tr('mesh.deploy.permissions', 'Grant permissions on the new link')}\n </span>\n <div className=\"flex flex-wrap gap-2\">\n {availablePermissions.map((p) => (\n <button\n type=\"button\"\n key={p}\n aria-pressed={permissions.has(p)}\n onClick={() => togglePermission(p)}\n className={`${CHIP_BASE_CLASS} ${\n permissions.has(p)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {p}\n </button>\n ))}\n </div>\n </div>\n )}\n\n <label className=\"flex cursor-pointer items-start gap-2 text-sm text-text-primary\">\n <Checkbox\n checked={connectExisting}\n onCheckedChange={(checked) => setConnectExisting(checked === true)}\n className=\"mt-0.5\"\n />\n <span>\n {tr(\n 'mesh.deploy.connectExisting',\n 'Connect to an existing agent on the target instead of installing'\n )}\n </span>\n </label>\n\n {submitError && (\n <div\n role=\"alert\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\"\n >\n {submitError}\n </div>\n )}\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={onClose} className=\"max-md:min-h-11\">\n {tr('common.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => void handleSubmit()}\n disabled={!canSubmit || deploying}\n aria-busy={deploying}\n className=\"max-md:min-h-11\"\n >\n {deploying ? <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" /> : null}\n {tr('mesh.deploy.submit', 'Deploy')}\n </Button>\n </ResponsiveDialogFooter>\n </div>\n )}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -146,12 +146,12 @@ function h({ rootAgentId: h, destinations: g, initialDestinationId: _, onClose:
|
|
|
146
146
|
children: [/* @__PURE__ */ l("button", {
|
|
147
147
|
type: "button",
|
|
148
148
|
onClick: () => C("command"),
|
|
149
|
-
className: `px-3 py-2 text-sm rounded-md border transition-colors ${S === "command" ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-
|
|
149
|
+
className: `px-3 py-2 text-sm rounded-md border transition-colors ${S === "command" ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
|
|
150
150
|
children: y("mesh.request.command", "Command")
|
|
151
151
|
}), /* @__PURE__ */ l("button", {
|
|
152
152
|
type: "button",
|
|
153
153
|
onClick: () => C("api"),
|
|
154
|
-
className: `px-3 py-2 text-sm rounded-md border transition-colors ${S === "api" ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-
|
|
154
|
+
className: `px-3 py-2 text-sm rounded-md border transition-colors ${S === "api" ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary" : "border-border-default text-text-secondary hover:bg-bg-sunken"}`,
|
|
155
155
|
children: y("mesh.request.api", "API")
|
|
156
156
|
})]
|
|
157
157
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MeshRequestPanel.js","names":[],"sources":["../../../src/components/graph/MeshRequestPanel.tsx"],"sourcesContent":["/**\n * MeshRequestPanel — run a request against a destination agent THROUGH the mesh.\n *\n * The backend computes the hop path from the chosen root (source) agent to the\n * destination, so the panel only supplies `rootAgentId` + `destinationAgentId`\n * + the request. Two request modes:\n * - COMMAND: type a `vibe` argv; we POST to\n * `/api/profiles/default/agent/commands/run` with body `{ argv }`.\n * - API: raw method + path + optional JSON body.\n *\n * The AgentProxyResponse is rendered like AgentMaintenanceTab's output pane,\n * and a hops breadcrumb is shown when the response surfaces hop info.\n */\n\nimport { useCallback, useMemo, useState } from 'react';\nimport { Loader2, Play, ChevronRight, Network } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useExecuteAgentMeshRequestMutation } from '@/generated/wspace-operations';\nimport type { MeshAgentUi } from './meshTypes';\n\ntype RequestMode = 'command' | 'api';\ntype HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n\nfunction asRecord(v: unknown): Record<string, unknown> | null {\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : null;\n}\nfunction jstr(obj: Record<string, unknown> | null, key: string): string | null {\n const v = obj?.[key];\n return typeof v === 'string' ? v : null;\n}\n\nfunction extractHops(data: Record<string, unknown> | null): string[] {\n if (!data) return [];\n const raw = data.hops ?? asRecord(data.meta)?.hops;\n if (Array.isArray(raw)) return raw.filter((h): h is string => typeof h === 'string');\n return [];\n}\n\nconst INPUT_CLASS =\n 'px-3 py-2 text-sm rounded-md bg-bg-sunken border border-border-default text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40';\n\ninterface MeshRequestPanelProps {\n /** The root/source agent the request is dispatched from. */\n rootAgentId: string | null;\n /** Candidate destination agents (typically the workspace mesh agents). */\n destinations: MeshAgentUi[];\n /** Pre-selected destination (e.g. clicked node). */\n initialDestinationId?: string | null;\n onClose?: () => void;\n}\n\nexport function MeshRequestPanel({\n rootAgentId,\n destinations,\n initialDestinationId,\n onClose,\n}: MeshRequestPanelProps) {\n const tr = useTr();\n\n const [destinationId, setDestinationId] = useState<string>(\n initialDestinationId ?? destinations[0]?.id ?? ''\n );\n const [mode, setMode] = useState<RequestMode>('command');\n const [argv, setArgv] = useState('status');\n const [method, setMethod] = useState<HttpMethod>('GET');\n const [path, setPath] = useState('/api/health');\n const [bodyText, setBodyText] = useState('');\n const [output, setOutput] = useState<{ body: string; error?: boolean } | null>(null);\n const [hops, setHops] = useState<string[]>([]);\n\n const [execute, { loading }] = useExecuteAgentMeshRequestMutation();\n\n const destName = useCallback(\n (id: string) => destinations.find((d) => d.id === id)?.name ?? id,\n [destinations]\n );\n\n const canRun = useMemo(() => {\n if (!rootAgentId || !destinationId) return false;\n if (mode === 'command') return argv.trim().length > 0;\n return path.trim().length > 0;\n }, [rootAgentId, destinationId, mode, argv, path]);\n\n const handleRun = useCallback(async () => {\n if (!rootAgentId || !destinationId) return;\n setOutput(null);\n setHops([]);\n\n let requestInput: {\n method: string;\n path: string;\n body?: Record<string, unknown>;\n };\n\n if (mode === 'command') {\n const parts = argv.trim().split(/\\s+/).filter(Boolean);\n requestInput = {\n method: 'POST',\n path: '/api/profiles/default/agent/commands/run',\n body: { argv: parts },\n };\n } else {\n let parsedBody: Record<string, unknown> | undefined;\n if (bodyText.trim().length > 0) {\n try {\n const parsed: unknown = JSON.parse(bodyText);\n parsedBody = asRecord(parsed) ?? undefined;\n if (!parsedBody) {\n setOutput({\n body: tr('mesh.request.bodyNotObject', 'Body must be a JSON object.'),\n error: true,\n });\n return;\n }\n } catch {\n setOutput({\n body: tr('mesh.request.invalidJson', 'Request body is not valid JSON.'),\n error: true,\n });\n return;\n }\n }\n requestInput = { method, path: path.trim(), body: parsedBody };\n }\n\n try {\n const res = await execute({\n variables: {\n rootAgentId,\n destinationAgentId: destinationId,\n request: requestInput,\n },\n });\n const proxy = res.data?.executeAgentMeshRequest;\n const data = asRecord(proxy?.data);\n setHops(extractHops(data));\n\n if (res.error || proxy?.error || proxy?.ok === false) {\n setOutput({\n body:\n proxy?.error ??\n res.error?.message ??\n jstr(data, 'error') ??\n tr('mesh.request.failed', 'Request failed'),\n error: true,\n });\n return;\n }\n\n const stdout = jstr(data, 'stdout');\n const text = proxy?.text;\n const pretty = data ? JSON.stringify(data, null, 2) : (text ?? '(no output)');\n setOutput({ body: stdout || text || pretty });\n } catch (err) {\n setOutput({ body: err instanceof Error ? err.message : String(err), error: true });\n }\n }, [rootAgentId, destinationId, mode, argv, method, path, bodyText, execute, tr]);\n\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-4 space-y-3\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Network className=\"size-4 text-text-secondary\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('mesh.request.title', 'Run mesh request')}\n </h3>\n </div>\n {onClose && (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"text-xs text-text-muted hover:text-text-primary\"\n >\n {tr('common.close', 'Close')}\n </button>\n )}\n </div>\n\n {!rootAgentId && (\n <p className=\"text-xs text-status-warning-text\">\n {tr('mesh.request.noRoot', 'Select a reachable root agent to dispatch from.')}\n </p>\n )}\n\n <div className=\"flex flex-wrap items-end gap-2\">\n <div className=\"flex flex-col gap-1\">\n <label className=\"text-xs text-text-muted\" htmlFor=\"mesh-dest\">\n {tr('mesh.request.destination', 'Destination agent')}\n </label>\n <select\n id=\"mesh-dest\"\n value={destinationId}\n onChange={(e) => setDestinationId(e.target.value)}\n className={`${INPUT_CLASS} min-w-44`}\n >\n {destinations.length === 0 && (\n <option value=\"\">{tr('mesh.request.noDest', 'No agents')}</option>\n )}\n {destinations.map((d) => (\n <option key={d.id} value={d.id}>\n {d.name} ({d.hostname})\n </option>\n ))}\n </select>\n </div>\n\n <div className=\"flex flex-col gap-1\">\n <span className=\"text-xs text-text-muted\">{tr('mesh.request.mode', 'Mode')}</span>\n <div className=\"flex gap-1\">\n <button\n type=\"button\"\n onClick={() => setMode('command')}\n className={`px-3 py-2 text-sm rounded-md border transition-colors ${\n mode === 'command'\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {tr('mesh.request.command', 'Command')}\n </button>\n <button\n type=\"button\"\n onClick={() => setMode('api')}\n className={`px-3 py-2 text-sm rounded-md border transition-colors ${\n mode === 'api'\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {tr('mesh.request.api', 'API')}\n </button>\n </div>\n </div>\n </div>\n\n {mode === 'command' ? (\n <div className=\"flex flex-col gap-1\">\n <label className=\"text-xs text-text-muted\" htmlFor=\"mesh-argv\">\n {tr('mesh.request.argv', 'Command (vibe …)')}\n </label>\n <input\n id=\"mesh-argv\"\n type=\"text\"\n value={argv}\n onChange={(e) => setArgv(e.target.value)}\n placeholder=\"status --json\"\n className={`${INPUT_CLASS} font-mono w-full`}\n />\n </div>\n ) : (\n <div className=\"space-y-2\">\n <div className=\"flex gap-2\">\n <select\n value={method}\n onChange={(e) => setMethod(e.target.value as HttpMethod)}\n className={`${INPUT_CLASS} w-28`}\n aria-label={tr('mesh.request.method', 'Method')}\n >\n {(['GET', 'POST', 'PUT', 'DELETE', 'PATCH'] as HttpMethod[]).map((m) => (\n <option key={m} value={m}>\n {m}\n </option>\n ))}\n </select>\n <input\n type=\"text\"\n value={path}\n onChange={(e) => setPath(e.target.value)}\n placeholder=\"/api/health\"\n className={`${INPUT_CLASS} font-mono flex-1`}\n aria-label={tr('mesh.request.path', 'Path')}\n />\n </div>\n <textarea\n value={bodyText}\n onChange={(e) => setBodyText(e.target.value)}\n placeholder={tr('mesh.request.bodyPlaceholder', 'JSON body (optional)')}\n rows={3}\n className={`${INPUT_CLASS} font-mono w-full resize-y`}\n aria-label={tr('mesh.request.body', 'Request body')}\n />\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={() => void handleRun()}\n disabled={!canRun || loading}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {loading ? <Loader2 className=\"size-4 animate-spin\" /> : <Play className=\"size-4\" />}\n {tr('mesh.request.run', 'Run')}\n </button>\n\n {hops.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1 text-xs text-text-tertiary\">\n <span className=\"font-medium text-text-secondary\">\n {tr('mesh.request.hops', 'Hops:')}\n </span>\n {hops.map((hop, idx) => (\n <span key={`${hop}-${idx}`} className=\"inline-flex items-center gap-1\">\n <span className=\"rounded bg-bg-sunken px-1.5 py-0.5 text-text-primary\">\n {destName(hop)}\n </span>\n {idx < hops.length - 1 && <ChevronRight className=\"size-3\" />}\n </span>\n ))}\n </div>\n )}\n\n {output && (\n <pre\n className={`text-xs whitespace-pre-wrap break-words max-h-80 overflow-auto rounded-md p-3 ${\n output.error\n ? 'bg-status-error-bg-subtle text-status-error-text'\n : 'bg-bg-sunken text-text-primary'\n }`}\n >\n {output.body}\n </pre>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MeshRequestPanel.js","names":[],"sources":["../../../src/components/graph/MeshRequestPanel.tsx"],"sourcesContent":["/**\n * MeshRequestPanel — run a request against a destination agent THROUGH the mesh.\n *\n * The backend computes the hop path from the chosen root (source) agent to the\n * destination, so the panel only supplies `rootAgentId` + `destinationAgentId`\n * + the request. Two request modes:\n * - COMMAND: type a `vibe` argv; we POST to\n * `/api/profiles/default/agent/commands/run` with body `{ argv }`.\n * - API: raw method + path + optional JSON body.\n *\n * The AgentProxyResponse is rendered like AgentMaintenanceTab's output pane,\n * and a hops breadcrumb is shown when the response surfaces hop info.\n */\n\nimport { useCallback, useMemo, useState } from 'react';\nimport { Loader2, Play, ChevronRight, Network } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useExecuteAgentMeshRequestMutation } from '@/generated/wspace-operations';\nimport type { MeshAgentUi } from './meshTypes';\n\ntype RequestMode = 'command' | 'api';\ntype HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n\nfunction asRecord(v: unknown): Record<string, unknown> | null {\n return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : null;\n}\nfunction jstr(obj: Record<string, unknown> | null, key: string): string | null {\n const v = obj?.[key];\n return typeof v === 'string' ? v : null;\n}\n\nfunction extractHops(data: Record<string, unknown> | null): string[] {\n if (!data) return [];\n const raw = data.hops ?? asRecord(data.meta)?.hops;\n if (Array.isArray(raw)) return raw.filter((h): h is string => typeof h === 'string');\n return [];\n}\n\nconst INPUT_CLASS =\n 'px-3 py-2 text-sm rounded-md bg-bg-sunken border border-border-default text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40';\n\ninterface MeshRequestPanelProps {\n /** The root/source agent the request is dispatched from. */\n rootAgentId: string | null;\n /** Candidate destination agents (typically the workspace mesh agents). */\n destinations: MeshAgentUi[];\n /** Pre-selected destination (e.g. clicked node). */\n initialDestinationId?: string | null;\n onClose?: () => void;\n}\n\nexport function MeshRequestPanel({\n rootAgentId,\n destinations,\n initialDestinationId,\n onClose,\n}: MeshRequestPanelProps) {\n const tr = useTr();\n\n const [destinationId, setDestinationId] = useState<string>(\n initialDestinationId ?? destinations[0]?.id ?? ''\n );\n const [mode, setMode] = useState<RequestMode>('command');\n const [argv, setArgv] = useState('status');\n const [method, setMethod] = useState<HttpMethod>('GET');\n const [path, setPath] = useState('/api/health');\n const [bodyText, setBodyText] = useState('');\n const [output, setOutput] = useState<{ body: string; error?: boolean } | null>(null);\n const [hops, setHops] = useState<string[]>([]);\n\n const [execute, { loading }] = useExecuteAgentMeshRequestMutation();\n\n const destName = useCallback(\n (id: string) => destinations.find((d) => d.id === id)?.name ?? id,\n [destinations]\n );\n\n const canRun = useMemo(() => {\n if (!rootAgentId || !destinationId) return false;\n if (mode === 'command') return argv.trim().length > 0;\n return path.trim().length > 0;\n }, [rootAgentId, destinationId, mode, argv, path]);\n\n const handleRun = useCallback(async () => {\n if (!rootAgentId || !destinationId) return;\n setOutput(null);\n setHops([]);\n\n let requestInput: {\n method: string;\n path: string;\n body?: Record<string, unknown>;\n };\n\n if (mode === 'command') {\n const parts = argv.trim().split(/\\s+/).filter(Boolean);\n requestInput = {\n method: 'POST',\n path: '/api/profiles/default/agent/commands/run',\n body: { argv: parts },\n };\n } else {\n let parsedBody: Record<string, unknown> | undefined;\n if (bodyText.trim().length > 0) {\n try {\n const parsed: unknown = JSON.parse(bodyText);\n parsedBody = asRecord(parsed) ?? undefined;\n if (!parsedBody) {\n setOutput({\n body: tr('mesh.request.bodyNotObject', 'Body must be a JSON object.'),\n error: true,\n });\n return;\n }\n } catch {\n setOutput({\n body: tr('mesh.request.invalidJson', 'Request body is not valid JSON.'),\n error: true,\n });\n return;\n }\n }\n requestInput = { method, path: path.trim(), body: parsedBody };\n }\n\n try {\n const res = await execute({\n variables: {\n rootAgentId,\n destinationAgentId: destinationId,\n request: requestInput,\n },\n });\n const proxy = res.data?.executeAgentMeshRequest;\n const data = asRecord(proxy?.data);\n setHops(extractHops(data));\n\n if (res.error || proxy?.error || proxy?.ok === false) {\n setOutput({\n body:\n proxy?.error ??\n res.error?.message ??\n jstr(data, 'error') ??\n tr('mesh.request.failed', 'Request failed'),\n error: true,\n });\n return;\n }\n\n const stdout = jstr(data, 'stdout');\n const text = proxy?.text;\n const pretty = data ? JSON.stringify(data, null, 2) : (text ?? '(no output)');\n setOutput({ body: stdout || text || pretty });\n } catch (err) {\n setOutput({ body: err instanceof Error ? err.message : String(err), error: true });\n }\n }, [rootAgentId, destinationId, mode, argv, method, path, bodyText, execute, tr]);\n\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-4 space-y-3\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Network className=\"size-4 text-text-secondary\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('mesh.request.title', 'Run mesh request')}\n </h3>\n </div>\n {onClose && (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"text-xs text-text-muted hover:text-text-primary\"\n >\n {tr('common.close', 'Close')}\n </button>\n )}\n </div>\n\n {!rootAgentId && (\n <p className=\"text-xs text-status-warning-text\">\n {tr('mesh.request.noRoot', 'Select a reachable root agent to dispatch from.')}\n </p>\n )}\n\n <div className=\"flex flex-wrap items-end gap-2\">\n <div className=\"flex flex-col gap-1\">\n <label className=\"text-xs text-text-muted\" htmlFor=\"mesh-dest\">\n {tr('mesh.request.destination', 'Destination agent')}\n </label>\n <select\n id=\"mesh-dest\"\n value={destinationId}\n onChange={(e) => setDestinationId(e.target.value)}\n className={`${INPUT_CLASS} min-w-44`}\n >\n {destinations.length === 0 && (\n <option value=\"\">{tr('mesh.request.noDest', 'No agents')}</option>\n )}\n {destinations.map((d) => (\n <option key={d.id} value={d.id}>\n {d.name} ({d.hostname})\n </option>\n ))}\n </select>\n </div>\n\n <div className=\"flex flex-col gap-1\">\n <span className=\"text-xs text-text-muted\">{tr('mesh.request.mode', 'Mode')}</span>\n <div className=\"flex gap-1\">\n <button\n type=\"button\"\n onClick={() => setMode('command')}\n className={`px-3 py-2 text-sm rounded-md border transition-colors ${\n mode === 'command'\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {tr('mesh.request.command', 'Command')}\n </button>\n <button\n type=\"button\"\n onClick={() => setMode('api')}\n className={`px-3 py-2 text-sm rounded-md border transition-colors ${\n mode === 'api'\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-default text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {tr('mesh.request.api', 'API')}\n </button>\n </div>\n </div>\n </div>\n\n {mode === 'command' ? (\n <div className=\"flex flex-col gap-1\">\n <label className=\"text-xs text-text-muted\" htmlFor=\"mesh-argv\">\n {tr('mesh.request.argv', 'Command (vibe …)')}\n </label>\n <input\n id=\"mesh-argv\"\n type=\"text\"\n value={argv}\n onChange={(e) => setArgv(e.target.value)}\n placeholder=\"status --json\"\n className={`${INPUT_CLASS} font-mono w-full`}\n />\n </div>\n ) : (\n <div className=\"space-y-2\">\n <div className=\"flex gap-2\">\n <select\n value={method}\n onChange={(e) => setMethod(e.target.value as HttpMethod)}\n className={`${INPUT_CLASS} w-28`}\n aria-label={tr('mesh.request.method', 'Method')}\n >\n {(['GET', 'POST', 'PUT', 'DELETE', 'PATCH'] as HttpMethod[]).map((m) => (\n <option key={m} value={m}>\n {m}\n </option>\n ))}\n </select>\n <input\n type=\"text\"\n value={path}\n onChange={(e) => setPath(e.target.value)}\n placeholder=\"/api/health\"\n className={`${INPUT_CLASS} font-mono flex-1`}\n aria-label={tr('mesh.request.path', 'Path')}\n />\n </div>\n <textarea\n value={bodyText}\n onChange={(e) => setBodyText(e.target.value)}\n placeholder={tr('mesh.request.bodyPlaceholder', 'JSON body (optional)')}\n rows={3}\n className={`${INPUT_CLASS} font-mono w-full resize-y`}\n aria-label={tr('mesh.request.body', 'Request body')}\n />\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={() => void handleRun()}\n disabled={!canRun || loading}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {loading ? <Loader2 className=\"size-4 animate-spin\" /> : <Play className=\"size-4\" />}\n {tr('mesh.request.run', 'Run')}\n </button>\n\n {hops.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1 text-xs text-text-tertiary\">\n <span className=\"font-medium text-text-secondary\">\n {tr('mesh.request.hops', 'Hops:')}\n </span>\n {hops.map((hop, idx) => (\n <span key={`${hop}-${idx}`} className=\"inline-flex items-center gap-1\">\n <span className=\"rounded bg-bg-sunken px-1.5 py-0.5 text-text-primary\">\n {destName(hop)}\n </span>\n {idx < hops.length - 1 && <ChevronRight className=\"size-3\" />}\n </span>\n ))}\n </div>\n )}\n\n {output && (\n <pre\n className={`text-xs whitespace-pre-wrap break-words max-h-80 overflow-auto rounded-md p-3 ${\n output.error\n ? 'bg-status-error-bg-subtle text-status-error-text'\n : 'bg-bg-sunken text-text-primary'\n }`}\n >\n {output.body}\n </pre>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -156,7 +156,7 @@ function g({ note: g, onOpen: _, onEdit: v, onDelete: y, onPin: b, onUnpin: x, o
|
|
|
156
156
|
g.tagAssignments && g.tagAssignments.length > 0 ? /* @__PURE__ */ h("div", {
|
|
157
157
|
className: "mt-3 flex flex-wrap gap-1",
|
|
158
158
|
children: [g.tagAssignments.slice(0, 3).map((e) => /* @__PURE__ */ h("span", {
|
|
159
|
-
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-[var(--color-accent-soft)] text-
|
|
159
|
+
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-[var(--color-accent-soft)] text-text-primary rounded",
|
|
160
160
|
style: e.tag?.color ? { borderLeft: `3px solid ${e.tag.color}` } : void 0,
|
|
161
161
|
children: [/* @__PURE__ */ m(u, { className: "size-3" }), e.tag?.label ?? e.tagId.slice(0, 8)]
|
|
162
162
|
}, e.id)), g.tagAssignments.length > 3 && /* @__PURE__ */ h("span", {
|
|
@@ -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-
|
|
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-
|
|
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", {
|