@burdenoff/microfe-vibecontrols 2026.601.2 → 2026.601.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"VibesPage.js","names":[],"sources":["../../src/pages/VibesPage.tsx"],"sourcesContent":["import { useState, useMemo, useRef, useEffect, useCallback } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Plus, Zap, Grid, List, RefreshCw, Archive, Play } from 'lucide-react';\nimport { VibeCard, VibeList } from '@/components/vibes';\nimport type { Vibe } from '@/components/vibes';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { useDraftEntityTags } from '@/hooks/useDraftEntityTags';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n EmptyState,\n LoadingState,\n ErrorState,\n ConfirmDialog,\n QuotaExhaustedDialog,\n SelectionCheckbox,\n GetHelpButton,\n} from '@/components/shared';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { AnimatePresence } from 'framer-motion';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport {\n useListVibesQuery,\n useCreateVibeMutation,\n useAttachAgentToVibeMutation,\n useUpdateVibeStatusMutation,\n useArchiveVibeMutation,\n useListAgentsLazyQuery,\n type ListAgentsQuery,\n} from '@/generated/wspace-operations';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\n\ntype Agent = NonNullable<ListAgentsQuery['vibecontrolsAgents']>[number];\nimport {\n PermissionGate,\n PermissionButton,\n AccessDenied,\n} from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useUpgradePrompt } from '@burdenoff/fe-libs/shared/hooks';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { isQuotaExhaustedError, markQuotaHandledLocally } from '@/utils/quotaUtils';\nimport {\n buildProtocolUrl,\n getIdePreset,\n resolveEffectiveIdeCommand,\n} from '@/components/agents/idePresets';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Vibes (projects/repositories) management page\n */\nexport function VibesPage() {\n const { onVibeActivate } = useVibeControls();\n const navigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const { t } = useI18n();\n const { isFree, approachingLimits } = useUpgradePrompt({ resource: 'vibes' });\n const vibeUsage = approachingLimits[0];\n\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n\n if (!params) return template;\n\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n },\n [t]\n );\n\n const statusOptions = [\n { value: 'all', label: tr('vibesPage.statusAll', 'All Statuses') },\n { value: 'ACTIVE', label: tr('vibesPage.statusActive', 'Active') },\n { value: 'INACTIVE', label: tr('vibesPage.statusInactive', 'Inactive') },\n { value: 'ARCHIVED', label: tr('vibesPage.statusArchived', 'Archived') },\n { value: 'ERROR', label: tr('vibesPage.statusError', 'Error') },\n ];\n\n const typeOptions = [\n { value: 'all', label: tr('vibesPage.typeAll', 'All Types') },\n { value: 'PROJECT', label: tr('vibesPage.typeProject', 'Project') },\n { value: 'REPOSITORY', label: tr('vibesPage.typeRepository', 'Repository') },\n { value: 'WORKSPACE', label: tr('vibesPage.typeWorkspace', 'Workspace') },\n { value: 'MONOREPO', label: tr('vibesPage.typeMonorepo', 'Monorepo') },\n { value: 'PACKAGE', label: tr('vibesPage.typePackage', 'Package') },\n { value: 'CUSTOM', label: tr('vibesPage.typeCustom', 'Custom') },\n ];\n\n // Check permissions\n const canListVibes = hasPermission(VIBE_PERMISSIONS.VIBE_LIST);\n const canUpdateVibe = hasPermission(VIBE_PERMISSIONS.VIBE_UPDATE);\n\n // Skip queries until permissions are loaded and user has permission\n // This prevents FORBIDDEN errors from triggering auth error handlers\n const shouldSkipQueries = permissionsLoading || !canListVibes;\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState('all');\n const [typeFilter, setTypeFilter] = useState('all');\n const [agentFilter, setAgentFilter] = useState('all');\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [createError, setCreateError] = useState<string | null>(null);\n const [createLoading, setCreateLoading] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [fieldErrors, setFieldErrors] = useState<{ name?: string; path?: string }>({});\n const [pageError, setPageError] = useState<string | null>(null);\n const [selectedVibeIds, setSelectedVibeIds] = useState<Set<string>>(new Set());\n const [bulkAction, setBulkAction] = useState<'activate' | 'archive' | null>(null);\n const pageErrorTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n return () => {\n if (pageErrorTimer.current) clearTimeout(pageErrorTimer.current);\n };\n }, []);\n\n const clearPageError = useCallback(() => {\n setPageError(null);\n if (pageErrorTimer.current) {\n clearTimeout(pageErrorTimer.current);\n pageErrorTimer.current = null;\n }\n }, []);\n\n // Form refs for create modal\n const nameRef = useRef<HTMLInputElement>(null);\n const pathRef = useRef<HTMLInputElement>(null);\n const agentRef = useRef<HTMLSelectElement>(null);\n // Hidden input today — the type selector dropdown is suppressed and\n // every newly created vibe defaults to PROJECT (see the Add New Vibe\n // modal below for the hidden input).\n const typeRef = useRef<HTMLInputElement>(null);\n\n const [createVibe] = useCreateVibeMutation();\n const [attachAgentToVibe] = useAttachAgentToVibeMutation();\n const { setDraftTagIds, flushTags } = useDraftEntityTags('VIBECONTROLS_VIBE');\n const [updateVibeStatus] = useUpdateVibeStatusMutation();\n const [archiveVibe] = useArchiveVibeMutation();\n\n // Memoise variables so a stable identity keeps Apollo from treating each\n // render as a new query (which caused multiple loading flashes).\n // NOTE: we intentionally do NOT include `searchQuery` in the query\n // variables — the list is filtered client-side by name below, and keying\n // the query on `searchQuery` caused a fresh refetch on every keystroke\n // (previously mitigated by an unusable `limit: 1000` fire-hose). Rely on\n // the default backend pagination and filter client-side within the page.\n const listVibesVariables = useMemo(\n () => ({\n filter: {\n status:\n statusFilter !== 'all'\n ? (statusFilter as 'ACTIVE' | 'INACTIVE' | 'ARCHIVED' | 'ERROR')\n : undefined,\n type:\n typeFilter !== 'all'\n ? (typeFilter as\n | 'PROJECT'\n | 'REPOSITORY'\n | 'WORKSPACE'\n | 'MONOREPO'\n | 'PACKAGE'\n | 'CUSTOM')\n : undefined,\n // Multi-agent: server-side filter takes a list. Single-select UI maps\n // to a one-element array; \"all\" clears the filter.\n attachedAgentIds: agentFilter !== 'all' ? [agentFilter] : undefined,\n },\n }),\n [statusFilter, typeFilter, agentFilter]\n );\n\n // When no filters are applied, the variables match the InitialData\n // prefetch (no filters) — use cache-first to avoid a redundant network\n // hit on every page visit. When user actually narrows by filter, fall\n // back to cache-and-network for fresh server-side filtering.\n const noVibeFilters = statusFilter === 'all' && typeFilter === 'all' && agentFilter === 'all';\n const vibesFetchPolicy = noVibeFilters ? 'cache-first' : 'cache-and-network';\n\n // Fetch vibes from GraphQL API. cache-and-network returns the cached list\n // immediately while revalidating once in the background — avoids the\n // \"loading → cache → network → cache\" double-flash seen with cache-first\n // plus mutation refetchQueries. `notifyOnNetworkStatusChange` keeps the\n // loading flag true during refetches so the UI can show a spinner rather\n // than showing stale data silently.\n const { data, loading, error, refetch } = useListVibesQuery({\n variables: listVibesVariables,\n fetchPolicy: vibesFetchPolicy,\n nextFetchPolicy: 'cache-first',\n skip: shouldSkipQueries,\n notifyOnNetworkStatusChange: true,\n });\n\n // Cached agents (already prefetched by InitialDataPrefetcher) cover the\n // initial agent-filter dropdown without firing any network query. The\n // lazy ListAgentsQuery below is only triggered on first interaction with\n // the agent filter (in case the prefetch was stale).\n const { agents: cachedAgentsForFilter } = useCachedInitialData();\n const [fetchAgents, { data: agentsData }] = useListAgentsLazyQuery({\n fetchPolicy: 'cache-first',\n });\n const triggeredAgentsFetchRef = useRef(false);\n const handleAgentFilterInteract = useCallback(() => {\n if (triggeredAgentsFetchRef.current || shouldSkipQueries) return;\n triggeredAgentsFetchRef.current = true;\n void fetchAgents({ variables: { pagination: { limit: 100 } } });\n }, [fetchAgents, shouldSkipQueries]);\n\n // Build agent options for filter — prefer freshly-fetched data once the\n // dropdown has been opened, otherwise use the cached set from\n // InitialDataPrefetcher.\n const agentOptions = useMemo(() => {\n const live = agentsData?.vibecontrolsAgents ?? null;\n const source: Array<{ id: string; name: string }> = live\n ? live.map((a: Agent) => ({ id: a.id, name: a.name }))\n : cachedAgentsForFilter.map((a) => ({ id: a.id, name: a.name }));\n return [\n { value: 'all', label: tr('vibesPage.allAgents', 'All Agents') },\n ...source.map((agent) => ({\n value: agent.id,\n label: agent.name,\n })),\n ];\n }, [agentsData?.vibecontrolsAgents, cachedAgentsForFilter, tr]);\n\n // Map GraphQL response to Vibe type\n const vibes: Vibe[] = useMemo(() => {\n const rawVibes = data?.vibes?.items ?? [];\n return rawVibes.map((vibe: NonNullable<typeof rawVibes>[number]) => ({\n id: vibe.id,\n workspaceId: vibe.workspaceId,\n name: vibe.name,\n description: vibe.description || null,\n type: vibe.type as Vibe['type'],\n status: vibe.status as Vibe['status'],\n path: vibe.path || null,\n parentId: vibe.parentId || null,\n gitBranch: vibe.gitBranch || null,\n gitRemote: vibe.gitRemote || null,\n gitStatus: vibe.gitStatus\n ? typeof vibe.gitStatus === 'string'\n ? vibe.gitStatus\n : typeof vibe.gitStatus === 'object' &&\n vibe.gitStatus !== null &&\n 'status' in vibe.gitStatus\n ? String((vibe.gitStatus as Record<string, unknown>).status)\n : JSON.stringify(vibe.gitStatus)\n : null,\n environmentVariables: (vibe.environmentVariables as Record<string, string>) || {},\n tags: vibe.tags || [],\n metadata: (vibe.metadata as Record<string, unknown>) || {},\n createdAt: vibe.createdAt,\n updatedAt: vibe.updatedAt,\n agents: (vibe.agents ?? []).map((a: NonNullable<typeof vibe.agents>[number]) => ({\n id: a.id,\n name: a.name,\n isActive: a.isActive,\n ownerUserId: a.ownerUserId ?? null,\n })),\n }));\n }, [data?.vibes?.items]);\n\n // Filter vibes based on search (client-side filtering for search)\n const filteredVibes = useMemo(() => {\n return vibes.filter((vibe) => {\n // Search filter\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n vibe.name.toLowerCase().includes(query) ||\n vibe.description?.toLowerCase().includes(query) ||\n vibe.path?.toLowerCase().includes(query) ||\n vibe.tags?.some((tag) => tag.toLowerCase().includes(query));\n if (!matchesSearch) return false;\n }\n\n return true;\n });\n }, [vibes, searchQuery]);\n\n const selectedVibes = useMemo(\n () => vibes.filter((vibe) => selectedVibeIds.has(vibe.id)),\n [vibes, selectedVibeIds]\n );\n const activatableVibes = selectedVibes.filter((vibe) => vibe.status !== 'ACTIVE');\n const archivableVibes = selectedVibes.filter((vibe) => vibe.status !== 'ARCHIVED');\n const allVisibleSelected =\n filteredVibes.length > 0 && filteredVibes.every((vibe) => selectedVibeIds.has(vibe.id));\n const partiallySelected =\n filteredVibes.some((vibe) => selectedVibeIds.has(vibe.id)) && !allVisibleSelected;\n\n const handleVibeSelect = (vibe: Vibe) => {\n navigate(`/vibes/${vibe.id}`);\n };\n\n const handleVibeActivate = async (vibe: Vibe) => {\n try {\n const result = await updateVibeStatus({\n variables: { id: vibe.id, status: 'ACTIVE' as const },\n });\n if (result.data?.updateVibeStatus) {\n onVibeActivate?.({ id: vibe.id, name: vibe.name, type: vibe.type, status: 'ACTIVE' });\n }\n } catch (err) {\n console.error('Failed to activate vibe:', err);\n }\n };\n\n const handleVibeArchive = async (vibe: Vibe) => {\n try {\n await archiveVibe({ variables: { id: vibe.id } });\n } catch (err) {\n console.error('Failed to archive vibe:', err);\n }\n };\n\n // Honour the user's preferred IDE + per-Vibe override when opening from\n // the card. Falls back to `vscode://` for IDEs without a registered URL\n // protocol; for richer launching (agent-exec, browser editor) use the\n // VibeOpenInEditorMenu on the Vibe detail page.\n const preferredIdeCommand = useVibeControlsStore((s) => s.preferredIdeCommand);\n const vibeIdeOverrides = useVibeControlsStore((s) => s.vibeIdeOverrides);\n\n const handleOpenInEditor = (vibe: Vibe) => {\n if (!vibe.path) {\n setPageError(tr('vibesPage.openEditorPathMissing', 'No path available to open in editor'));\n pageErrorTimer.current = setTimeout(() => {\n setPageError(null);\n pageErrorTimer.current = null;\n }, 3000);\n return;\n }\n const cmd = resolveEffectiveIdeCommand(vibe.id, preferredIdeCommand, vibeIdeOverrides);\n const preset = getIdePreset(cmd);\n const url = preset ? buildProtocolUrl(preset, vibe.path) : `vscode://file/${vibe.path}`;\n if (url) {\n window.location.href = url;\n } else {\n setPageError(\n tr(\n 'vibesPage.openEditorNoProtocol',\n `${preset?.label ?? cmd} has no URL handler — open this Vibe and use the editor menu.`\n )\n );\n pageErrorTimer.current = setTimeout(() => {\n setPageError(null);\n pageErrorTimer.current = null;\n }, 4000);\n }\n };\n\n const handleCreateVibe = useCallback(() => {\n setCreateError(null);\n setShowCreateForm(true);\n // Ensure the create-form agent dropdown has options even if the user\n // never opened the page-level agent filter.\n handleAgentFilterInteract();\n }, [handleAgentFilterInteract]);\n\n // Auto-open the create modal when arriving via /vibes/new or /vibes?create=true\n // (used by dashboard widgets and deep links). Strip the param after opening\n // so the back button returns to the previous page rather than re-opening.\n const [searchParams, setSearchParams] = useSearchParams();\n useEffect(() => {\n if (searchParams.get('create') === 'true') {\n handleCreateVibe();\n const next = new URLSearchParams(searchParams);\n next.delete('create');\n setSearchParams(next, { replace: true });\n }\n }, [searchParams, setSearchParams, handleCreateVibe]);\n\n const toggleVibeSelection = (vibe: Vibe, checked: boolean) => {\n setSelectedVibeIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(vibe.id);\n } else {\n next.delete(vibe.id);\n }\n return next;\n });\n };\n\n const toggleAllVisibleVibes = (checked: boolean) => {\n setSelectedVibeIds((prev) => {\n const next = new Set(prev);\n filteredVibes.forEach((vibe) => {\n if (checked) {\n next.add(vibe.id);\n } else {\n next.delete(vibe.id);\n }\n });\n return next;\n });\n };\n\n const clearSelection = () => setSelectedVibeIds(new Set());\n\n const handleBulkActivate = async () => {\n if (activatableVibes.length === 0) return;\n\n const results = await Promise.allSettled(\n activatableVibes.map((vibe) =>\n updateVibeStatus({ variables: { id: vibe.id, status: 'ACTIVE' as const } })\n )\n );\n\n const succeeded = results.filter((result) => result.status === 'fulfilled').length;\n const failed = results.length - succeeded;\n\n setSuccessMessage(\n `Activated ${succeeded} vibe${succeeded === 1 ? '' : 's'}${failed > 0 ? ` • ${failed} failed` : ''}`\n );\n setTimeout(() => setSuccessMessage(null), 4000);\n clearSelection();\n setBulkAction(null);\n await refetch();\n };\n\n const handleBulkArchive = async () => {\n if (archivableVibes.length === 0) return;\n\n const results = await Promise.allSettled(\n archivableVibes.map((vibe) => archiveVibe({ variables: { id: vibe.id } }))\n );\n\n const succeeded = results.filter((result) => result.status === 'fulfilled').length;\n const failed = results.length - succeeded;\n\n setSuccessMessage(\n `Archived ${succeeded} vibe${succeeded === 1 ? '' : 's'}${failed > 0 ? ` • ${failed} failed` : ''}`\n );\n setTimeout(() => setSuccessMessage(null), 4000);\n clearSelection();\n setBulkAction(null);\n await refetch();\n };\n\n const handleSubmitCreateVibe = async () => {\n const name = nameRef.current?.value?.trim();\n const path = pathRef.current?.value?.trim();\n const agentId = agentRef.current?.value;\n const type = typeRef.current?.value;\n\n // Path is optional now — vibes can be created without a specific\n // working dir up front and an agent can be attached later. Only the\n // name is required.\n const errors: { name?: string; path?: string } = {};\n if (!name) errors.name = tr('vibesPage.nameRequired', 'Name is required');\n if (errors.name) {\n setFieldErrors(errors);\n setCreateError(tr('vibesPage.fillRequired', 'Please fill in all required fields'));\n return;\n }\n setFieldErrors({});\n\n // Validate type against allowed enum values\n const validTypes = [\n 'PROJECT',\n 'REPOSITORY',\n 'WORKSPACE',\n 'MONOREPO',\n 'PACKAGE',\n 'CUSTOM',\n ] as const;\n const resolvedType = validTypes.includes(type as (typeof validTypes)[number])\n ? (type as (typeof validTypes)[number])\n : 'PROJECT';\n\n setCreateLoading(true);\n setCreateError(null);\n try {\n // Path is optional — when the user leaves it blank we send an empty\n // string so the (still required at the DB level) column has a value\n // the backend accepts without forcing a schema migration.\n const result = await createVibe({\n variables: {\n input: {\n name,\n path: path ?? '',\n type: resolvedType,\n },\n },\n });\n if (!result.data?.createVibe?.id) {\n throw new Error(tr('vibesPage.failedCreate', 'Failed to create vibe'));\n }\n const newVibeId = result.data.createVibe.id;\n // Multi-agent: vibes are created agent-less; attach the optionally\n // selected agent afterward via the attachment mutation (the old\n // `agentId` field on CreateVibeInput was removed server-side).\n if (agentId) {\n try {\n await attachAgentToVibe({ variables: { input: { vibeId: newVibeId, agentId } } });\n } catch {\n // Non-fatal: vibe created, agent attachment failed\n }\n }\n setShowCreateForm(false);\n setSuccessMessage(tr('vibesPage.createdSuccess', 'Vibe created successfully'));\n await flushTags(newVibeId);\n await refetch();\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowCreateForm(false);\n setCreateError(null);\n setFieldErrors({});\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n setCreateError(\n err instanceof Error ? err.message : tr('vibesPage.failedCreate', 'Failed to create vibe')\n );\n } finally {\n setCreateLoading(false);\n }\n };\n\n // Stats\n const stats = {\n total: vibes.length,\n active: vibes.filter((v) => v.status === 'ACTIVE').length,\n inactive: vibes.filter((v) => v.status === 'INACTIVE').length,\n };\n\n // Collapse guard states into a single MotionPage shell so transitioning\n // between loading / error / data does not unmount+remount and replay the\n // entrance animation. Contents are swapped in-place.\n const showPermissionsLoading = permissionsLoading;\n const showAccessDenied =\n !permissionsLoading &&\n !!permissions &&\n (permissions.permissionStrings?.length ?? 0) > 0 &&\n !canListVibes;\n const showInitialLoading =\n !showPermissionsLoading && !showAccessDenied && loading && vibes.length === 0;\n const showInitialError =\n !showPermissionsLoading && !showAccessDenied && !!error && vibes.length === 0;\n const showDataShell = !showPermissionsLoading && !showAccessDenied && !showInitialError;\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n {showPermissionsLoading && (\n <LoadingState message={tr('vibesPage.loadingPermissions', 'Loading permissions...')} />\n )}\n {showAccessDenied && (\n <AccessDenied\n title={tr('vibesPage.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'vibesPage.accessDeniedDescription',\n \"You don't have permission to view vibes. Contact your workspace administrator for access.\"\n )}\n />\n )}\n {showInitialError && (\n <ErrorState\n title={tr('vibesPage.failedLoad', 'Failed to load vibes')}\n message={error?.message ?? ''}\n onRetry={() => refetch()}\n />\n )}\n {showDataShell && (\n <>\n <PageHeader\n title={tr('vibesPage.title', 'Vibes')}\n description={tr('vibesPage.descriptionActive', '{{active}} of {{total}} vibes active', {\n active: stats.active,\n total: stats.total,\n })}\n actions={\n <div className=\"flex items-center gap-2\">\n {isFree && vibeUsage && typeof vibeUsage.limit === 'number' && (\n <a\n href=\"/billing/plans\"\n className={`inline-flex items-center gap-1 px-2 py-1 text-xs font-medium rounded-md border ${\n vibeUsage.current >= vibeUsage.limit\n ? 'border-status-error-border bg-status-error-bg-subtle text-status-error-text'\n : 'border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text'\n }`}\n title={tr(\n 'vibesPage.upgradeChipTooltip',\n 'Free plan limit — upgrade for more vibes'\n )}\n >\n {tr('vibesPage.usageChip', '{{current}} of {{limit}} vibes used', {\n current: vibeUsage.current,\n limit: vibeUsage.limit,\n })}\n </a>\n )}\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title={tr('vibesPage.refreshTitle', 'Refresh vibes')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <PermissionButton\n permission={VIBE_PERMISSIONS.VIBE_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tr(\n 'vibesPage.deniedCreateTooltip',\n \"You don't have permission to create vibes\"\n )}\n >\n <button\n type=\"button\"\n onClick={handleCreateVibe}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <Plus className=\"size-4\" />\n {tr('vibesPage.addVibe', 'Add Vibe')}\n </button>\n </PermissionButton>\n </div>\n }\n />\n\n {/* Filters Bar */}\n <div className=\"space-y-3\">\n {/* Row 1: Search + View Toggle */}\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('vibesPage.searchPlaceholder', 'Search vibes...')}\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('vibesPage.gridViewTitle', 'Grid view')}\n >\n <Grid className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('vibesPage.listViewTitle', 'List view')}\n >\n <List className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Filters - wrap on all screens */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <FilterDropdown\n label={tr('vibesPage.filterStatus', 'Status')}\n value={statusFilter}\n options={statusOptions}\n onChange={setStatusFilter}\n />\n <div onMouseDown={handleAgentFilterInteract} onFocus={handleAgentFilterInteract}>\n <FilterDropdown\n label={tr('vibesPage.filterAgent', 'Agent')}\n value={agentFilter}\n options={agentOptions}\n onChange={setAgentFilter}\n />\n </div>\n </div>\n </div>\n\n {/* Results Count */}\n {searchQuery ||\n statusFilter !== 'all' ||\n typeFilter !== 'all' ||\n agentFilter !== 'all' ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('vibesPage.showingResults', 'Showing {{filtered}} of {{total}} vibes', {\n filtered: filteredVibes.length,\n total: vibes.length,\n })}\n {loading && (\n <span className=\"ml-2 text-text-muted\">\n {tr('vibesPage.refreshing', '(refreshing...)')}\n </span>\n )}\n </p>\n ) : null}\n\n {filteredVibes.length > 0 && (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-2\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisibleVibes}\n ariaLabel=\"Select all visible vibes\"\n />\n <span className=\"text-sm font-medium text-text-primary\">\n Select all visible\n </span>\n </div>\n {selectedVibeIds.size > 0 && (\n <button\n type=\"button\"\n onClick={clearSelection}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n Clear selection\n </button>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-bg-sunken px-3 py-1 text-sm font-medium text-text-secondary\">\n {selectedVibeIds.size} selected\n </span>\n <button\n type=\"button\"\n onClick={() => setBulkAction('activate')}\n disabled={activatableVibes.length === 0}\n title=\"Mark selected vibes as ACTIVE so they appear in the default vibe list and can run sessions/tunnels via their attached agent(s).\"\n className=\"rounded-md border border-status-success-border px-3 py-2 text-sm font-medium text-status-success-text hover:bg-status-success-bg disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <span className=\"inline-flex items-center gap-2\">\n <Play className=\"size-4\" />\n Activate Selected\n {activatableVibes.length > 0 ? ` (${activatableVibes.length})` : ''}\n </span>\n </button>\n <button\n type=\"button\"\n onClick={() => setBulkAction('archive')}\n disabled={archivableVibes.length === 0}\n className=\"rounded-md border border-status-warning-border px-3 py-2 text-sm font-medium text-status-warning-text hover:bg-status-warning-bg disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <span className=\"inline-flex items-center gap-2\">\n <Archive className=\"size-4\" />\n Archive Selected\n {archivableVibes.length > 0 ? ` (${archivableVibes.length})` : ''}\n </span>\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Vibes Display */}\n {showInitialLoading ? (\n <PageSkeleton variant=\"cards\" count={6} />\n ) : filteredVibes.length > 0 ? (\n <AnimatePresence mode=\"wait\">\n {viewMode === 'grid' ? (\n <MotionList\n key=\"grid\"\n data-tour=\"vibes-page-grid-view\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {filteredVibes.map((vibe) => (\n <MotionItem key={vibe.id}>\n <VibeCard\n vibe={vibe}\n onOpen={handleVibeSelect}\n onActivate={canUpdateVibe ? handleVibeActivate : undefined}\n onArchive={canUpdateVibe ? handleVibeArchive : undefined}\n onOpenInEditor={handleOpenInEditor}\n selected={selectedVibeIds.has(vibe.id)}\n onToggleSelect={(checked) => toggleVibeSelection(vibe, checked)}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <MotionList key=\"list\">\n <VibeList\n vibes={filteredVibes}\n onOpen={handleVibeSelect}\n onActivate={handleVibeActivate}\n onArchive={handleVibeArchive}\n onOpenInEditor={handleOpenInEditor}\n selectedVibeIds={selectedVibeIds}\n onToggleSelect={toggleVibeSelection}\n onToggleAllVisible={toggleAllVisibleVibes}\n allVisibleSelected={allVisibleSelected}\n partiallySelected={partiallySelected}\n />\n </MotionList>\n )}\n </AnimatePresence>\n ) : (\n <EmptyState\n icon={<Zap className=\"size-8 text-text-secondary\" />}\n title={\n searchQuery ||\n statusFilter !== 'all' ||\n typeFilter !== 'all' ||\n agentFilter !== 'all'\n ? tr('vibesPage.emptyNoVibesFound', 'No vibes found')\n : tr('vibesPage.emptyNoVibesConfigured', 'No vibes configured')\n }\n description={\n searchQuery ||\n statusFilter !== 'all' ||\n typeFilter !== 'all' ||\n agentFilter !== 'all'\n ? tr('vibesPage.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'vibesPage.emptyNoVibesDescription',\n 'Add your first vibe to start managing your development projects'\n )\n }\n action={\n !searchQuery &&\n statusFilter === 'all' &&\n typeFilter === 'all' &&\n agentFilter === 'all' ? (\n <div className=\"flex items-center gap-2\">\n <PermissionGate permission={VIBE_PERMISSIONS.VIBE_CREATE}>\n <button\n type=\"button\"\n onClick={handleCreateVibe}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('vibesPage.addVibe', 'Add Vibe')}\n </button>\n </PermissionGate>\n <GetHelpButton prompt=\"What is a Vibe in VibeControls and how do I create one?\" />\n </div>\n ) : undefined\n }\n />\n )}\n\n {/* Success Toast */}\n {successMessage && (\n <div className=\"fixed bottom-4 right-4 z-50 px-4 py-3 bg-status-success-bg text-status-success-text rounded-md shadow-lg text-sm\">\n {successMessage}\n </div>\n )}\n\n <ConfirmDialog\n isOpen={bulkAction === 'activate'}\n onClose={() => setBulkAction(null)}\n onConfirm={handleBulkActivate}\n title=\"Activate selected vibes\"\n message={`Activate ${activatableVibes.length} selected vibe${activatableVibes.length === 1 ? '' : 's'}?`}\n confirmLabel=\"Activate\"\n variant=\"default\"\n />\n\n <ConfirmDialog\n isOpen={bulkAction === 'archive'}\n onClose={() => setBulkAction(null)}\n onConfirm={handleBulkArchive}\n title=\"Archive selected vibes\"\n message={`Archive ${archivableVibes.length} selected vibe${archivableVibes.length === 1 ? '' : 's'}? Archived vibes will be hidden from active workflows.`}\n confirmLabel=\"Archive\"\n variant=\"warning\"\n />\n\n {/* Page-level Error Toast */}\n {pageError && (\n <div\n role=\"alert\"\n className=\"fixed bottom-4 right-4 z-50 px-4 py-3 bg-status-error-bg text-status-error-text rounded-md shadow-lg text-sm\"\n >\n {pageError}\n <button\n type=\"button\"\n onClick={clearPageError}\n className=\"ml-3 text-xs underline opacity-80 hover:opacity-100\"\n >\n {tr('vibesPage.dismiss', 'Dismiss')}\n </button>\n </div>\n )}\n\n {/* Create Vibe Modal */}\n {showCreateForm && (\n <div className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\">\n <div className=\"bg-bg-surface rounded-lg p-6 w-full max-w-lg\">\n <h2 className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr('vibesPage.addNewVibe', 'Add New Vibe')}\n </h2>\n <div className=\"space-y-4\">\n {createError && (\n <div className=\"p-3 bg-status-error-bg text-status-error-text text-sm rounded-md\">\n {createError}\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibesPage.nameLabel', 'Name *')}\n </label>\n <input\n aria-label=\"control\"\n ref={nameRef}\n type=\"text\"\n placeholder={tr('vibesPage.namePlaceholder', 'My Project')}\n onChange={() =>\n fieldErrors.name && setFieldErrors((e) => ({ ...e, name: undefined }))\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-placeholder ${fieldErrors.name ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.name && (\n <p className=\"mt-1 text-xs text-status-error-text\">{fieldErrors.name}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibesPage.pathLabel', 'Path')}\n <span className=\"ml-1 text-xs text-text-tertiary font-normal\">\n {tr('vibesPage.pathOptional', '(optional)')}\n </span>\n </label>\n <input\n aria-label=\"control\"\n ref={pathRef}\n type=\"text\"\n placeholder={tr('vibesPage.pathPlaceholder', '/path/to/project')}\n onChange={() =>\n fieldErrors.path && setFieldErrors((e) => ({ ...e, path: undefined }))\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-placeholder ${fieldErrors.path ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.path && (\n <p className=\"mt-1 text-xs text-status-error-text\">{fieldErrors.path}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibesPage.agentLabel', 'Agent')}\n </label>\n <select\n ref={agentRef}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary\"\n >\n <option value=\"\">{tr('vibesPage.selectAgent', 'Select an agent')}</option>\n {agentOptions\n .filter((opt) => opt.value !== 'all')\n .map((agent) => (\n <option key={agent.value} value={agent.value}>\n {agent.label}\n </option>\n ))}\n </select>\n </div>\n {/* Type selector is hidden for now — every newly created\n vibe defaults to PROJECT. The other types\n (REPOSITORY, WORKSPACE, MONOREPO, PACKAGE, CUSTOM)\n are still valid in the schema but aren't exposed\n here until the corresponding UX is shipped per type.\n A hidden input keeps `typeRef.current?.value` valid\n so the submit-time validation logic doesn't crash. */}\n <input ref={typeRef} type=\"hidden\" value=\"PROJECT\" />\n </div>\n <div className=\"mt-4 border-t border-border-default pt-4\">\n <h3 className=\"text-sm font-medium text-text-secondary mb-2\">\n {tr('vibesPage.tags', 'Tags')}\n </h3>\n <EntityTagPicker entityType=\"VIBECONTROLS_VIBE\" onDraftChange={setDraftTagIds} />\n </div>\n <div className=\"flex flex-col sm:flex-row justify-end gap-3 mt-6\">\n <button\n type=\"button\"\n onClick={() => setShowCreateForm(false)}\n disabled={createLoading}\n className=\"px-4 py-2 text-text-primary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n {tr('vibesPage.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleSubmitCreateVibe}\n disabled={createLoading}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {createLoading\n ? tr('vibesPage.creating', 'Creating...')\n : tr('vibesPage.createVibe', 'Create Vibe')}\n </button>\n </div>\n </div>\n </div>\n )}\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Vibe Quota Reached\"\n message=\"You've used all available vibe creation quota on your current plan. Upgrade your plan to create more vibes.\"\n />\n </>\n )}\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"VibesPage.js","names":[],"sources":["../../src/pages/VibesPage.tsx"],"sourcesContent":["import { useState, useMemo, useRef, useEffect, useCallback } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Plus, Zap, Grid, List, RefreshCw, Archive, Play } from 'lucide-react';\nimport { VibeCard, VibeList } from '@/components/vibes';\nimport type { Vibe } from '@/components/vibes';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { useDraftEntityTags } from '@/hooks/useDraftEntityTags';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n EmptyState,\n LoadingState,\n ErrorState,\n ConfirmDialog,\n QuotaExhaustedDialog,\n SelectionCheckbox,\n GetHelpButton,\n} from '@/components/shared';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { AnimatePresence } from 'framer-motion';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport {\n useListVibesQuery,\n useCreateVibeMutation,\n useAttachAgentToVibeMutation,\n useUpdateVibeStatusMutation,\n useArchiveVibeMutation,\n useListAgentsLazyQuery,\n type ListAgentsQuery,\n} from '@/generated/wspace-operations';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\n\ntype Agent = NonNullable<ListAgentsQuery['vibecontrolsAgents']>[number];\nimport {\n PermissionGate,\n PermissionButton,\n AccessDenied,\n} from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useUpgradePrompt } from '@burdenoff/fe-libs/shared/hooks';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { isQuotaExhaustedError, markQuotaHandledLocally } from '@/utils/quotaUtils';\nimport {\n buildProtocolUrl,\n getIdePreset,\n resolveEffectiveIdeCommand,\n} from '@/components/agents/idePresets';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Vibes (projects/repositories) management page\n */\nexport function VibesPage() {\n const { onVibeActivate } = useVibeControls();\n const navigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const { t } = useI18n();\n const { isFree, approachingLimits } = useUpgradePrompt({ resource: 'vibes' });\n const vibeUsage = approachingLimits[0];\n\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n\n if (!params) return template;\n\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n },\n [t]\n );\n\n const statusOptions = [\n { value: 'all', label: tr('vibesPage.statusAll', 'All Statuses') },\n { value: 'ACTIVE', label: tr('vibesPage.statusActive', 'Active') },\n { value: 'INACTIVE', label: tr('vibesPage.statusInactive', 'Inactive') },\n { value: 'ARCHIVED', label: tr('vibesPage.statusArchived', 'Archived') },\n { value: 'ERROR', label: tr('vibesPage.statusError', 'Error') },\n ];\n\n const typeOptions = [\n { value: 'all', label: tr('vibesPage.typeAll', 'All Types') },\n { value: 'PROJECT', label: tr('vibesPage.typeProject', 'Project') },\n { value: 'REPOSITORY', label: tr('vibesPage.typeRepository', 'Repository') },\n { value: 'WORKSPACE', label: tr('vibesPage.typeWorkspace', 'Workspace') },\n { value: 'MONOREPO', label: tr('vibesPage.typeMonorepo', 'Monorepo') },\n { value: 'PACKAGE', label: tr('vibesPage.typePackage', 'Package') },\n { value: 'CUSTOM', label: tr('vibesPage.typeCustom', 'Custom') },\n ];\n\n // Check permissions\n const canListVibes = hasPermission(VIBE_PERMISSIONS.VIBE_LIST);\n const canUpdateVibe = hasPermission(VIBE_PERMISSIONS.VIBE_UPDATE);\n\n // Skip queries until permissions are loaded and user has permission\n // This prevents FORBIDDEN errors from triggering auth error handlers\n const shouldSkipQueries = permissionsLoading || !canListVibes;\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState('all');\n const [typeFilter, setTypeFilter] = useState('all');\n const [agentFilter, setAgentFilter] = useState('all');\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [createError, setCreateError] = useState<string | null>(null);\n const [createLoading, setCreateLoading] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [fieldErrors, setFieldErrors] = useState<{ name?: string; path?: string }>({});\n const [pageError, setPageError] = useState<string | null>(null);\n const [selectedVibeIds, setSelectedVibeIds] = useState<Set<string>>(new Set());\n const [bulkAction, setBulkAction] = useState<'activate' | 'archive' | null>(null);\n const pageErrorTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n return () => {\n if (pageErrorTimer.current) clearTimeout(pageErrorTimer.current);\n };\n }, []);\n\n const clearPageError = useCallback(() => {\n setPageError(null);\n if (pageErrorTimer.current) {\n clearTimeout(pageErrorTimer.current);\n pageErrorTimer.current = null;\n }\n }, []);\n\n // Form refs for create modal\n const nameRef = useRef<HTMLInputElement>(null);\n const pathRef = useRef<HTMLInputElement>(null);\n const agentRef = useRef<HTMLSelectElement>(null);\n // Hidden input today — the type selector dropdown is suppressed and\n // every newly created vibe defaults to PROJECT (see the Add New Vibe\n // modal below for the hidden input).\n const typeRef = useRef<HTMLInputElement>(null);\n\n const [createVibe] = useCreateVibeMutation();\n const [attachAgentToVibe] = useAttachAgentToVibeMutation();\n const { setDraftTagIds, flushTags } = useDraftEntityTags('VIBECONTROLS_VIBE');\n const [updateVibeStatus] = useUpdateVibeStatusMutation();\n const [archiveVibe] = useArchiveVibeMutation();\n\n // Memoise variables so a stable identity keeps Apollo from treating each\n // render as a new query (which caused multiple loading flashes).\n // NOTE: we intentionally do NOT include `searchQuery` in the query\n // variables — the list is filtered client-side by name below, and keying\n // the query on `searchQuery` caused a fresh refetch on every keystroke\n // (previously mitigated by an unusable `limit: 1000` fire-hose). Rely on\n // the default backend pagination and filter client-side within the page.\n const listVibesVariables = useMemo(\n () => ({\n filter: {\n status:\n statusFilter !== 'all'\n ? (statusFilter as 'ACTIVE' | 'INACTIVE' | 'ARCHIVED' | 'ERROR')\n : undefined,\n type:\n typeFilter !== 'all'\n ? (typeFilter as\n | 'PROJECT'\n | 'REPOSITORY'\n | 'WORKSPACE'\n | 'MONOREPO'\n | 'PACKAGE'\n | 'CUSTOM')\n : undefined,\n // Multi-agent: server-side filter takes a list. Single-select UI maps\n // to a one-element array; \"all\" clears the filter.\n attachedAgentIds: agentFilter !== 'all' ? [agentFilter] : undefined,\n },\n }),\n [statusFilter, typeFilter, agentFilter]\n );\n\n // When no filters are applied, the variables match the InitialData\n // prefetch (no filters) — use cache-first to avoid a redundant network\n // hit on every page visit. When user actually narrows by filter, fall\n // back to cache-and-network for fresh server-side filtering.\n const noVibeFilters = statusFilter === 'all' && typeFilter === 'all' && agentFilter === 'all';\n const vibesFetchPolicy = noVibeFilters ? 'cache-first' : 'cache-and-network';\n\n // Fetch vibes from GraphQL API. cache-and-network returns the cached list\n // immediately while revalidating once in the background — avoids the\n // \"loading → cache → network → cache\" double-flash seen with cache-first\n // plus mutation refetchQueries. `notifyOnNetworkStatusChange` keeps the\n // loading flag true during refetches so the UI can show a spinner rather\n // than showing stale data silently.\n const { data, loading, error, refetch } = useListVibesQuery({\n variables: listVibesVariables,\n fetchPolicy: vibesFetchPolicy,\n nextFetchPolicy: 'cache-first',\n skip: shouldSkipQueries,\n notifyOnNetworkStatusChange: true,\n });\n\n // Cached agents (already prefetched by InitialDataPrefetcher) cover the\n // initial agent-filter dropdown without firing any network query. The\n // lazy ListAgentsQuery below is only triggered on first interaction with\n // the agent filter (in case the prefetch was stale).\n const { agents: cachedAgentsForFilter } = useCachedInitialData();\n const [fetchAgents, { data: agentsData }] = useListAgentsLazyQuery({\n fetchPolicy: 'cache-first',\n });\n const triggeredAgentsFetchRef = useRef(false);\n const handleAgentFilterInteract = useCallback(() => {\n if (triggeredAgentsFetchRef.current || shouldSkipQueries) return;\n triggeredAgentsFetchRef.current = true;\n void fetchAgents({ variables: { pagination: { limit: 100 } } });\n }, [fetchAgents, shouldSkipQueries]);\n\n // Build agent options for filter — prefer freshly-fetched data once the\n // dropdown has been opened, otherwise use the cached set from\n // InitialDataPrefetcher.\n const agentOptions = useMemo(() => {\n const live = agentsData?.vibecontrolsAgents ?? null;\n const source: Array<{ id: string; name: string }> = live\n ? live.map((a: Agent) => ({ id: a.id, name: a.name }))\n : cachedAgentsForFilter.map((a) => ({ id: a.id, name: a.name }));\n return [\n { value: 'all', label: tr('vibesPage.allAgents', 'All Agents') },\n ...source.map((agent) => ({\n value: agent.id,\n label: agent.name,\n })),\n ];\n }, [agentsData?.vibecontrolsAgents, cachedAgentsForFilter, tr]);\n\n // Map GraphQL response to Vibe type\n const vibes: Vibe[] = useMemo(() => {\n const rawVibes = data?.vibes?.items ?? [];\n return rawVibes.map((vibe: NonNullable<typeof rawVibes>[number]) => ({\n id: vibe.id,\n workspaceId: vibe.workspaceId,\n name: vibe.name,\n description: vibe.description || null,\n type: vibe.type as Vibe['type'],\n status: vibe.status as Vibe['status'],\n path: vibe.path || null,\n parentId: vibe.parentId || null,\n gitBranch: vibe.gitBranch || null,\n gitRemote: vibe.gitRemote || null,\n gitStatus: vibe.gitStatus\n ? typeof vibe.gitStatus === 'string'\n ? vibe.gitStatus\n : typeof vibe.gitStatus === 'object' &&\n vibe.gitStatus !== null &&\n 'status' in vibe.gitStatus\n ? String((vibe.gitStatus as Record<string, unknown>).status)\n : JSON.stringify(vibe.gitStatus)\n : null,\n environmentVariables: (vibe.environmentVariables as Record<string, string>) || {},\n tags: vibe.tags || [],\n metadata: (vibe.metadata as Record<string, unknown>) || {},\n createdAt: vibe.createdAt,\n updatedAt: vibe.updatedAt,\n agents: (vibe.agents ?? []).map((a: NonNullable<typeof vibe.agents>[number]) => ({\n id: a.id,\n name: a.name,\n isActive: a.isActive,\n ownerUserId: a.ownerUserId ?? null,\n })),\n }));\n }, [data?.vibes?.items]);\n\n // Filter vibes based on search (client-side filtering for search)\n const filteredVibes = useMemo(() => {\n return vibes.filter((vibe) => {\n // Search filter\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n vibe.name.toLowerCase().includes(query) ||\n vibe.description?.toLowerCase().includes(query) ||\n vibe.path?.toLowerCase().includes(query) ||\n vibe.tags?.some((tag) => tag.toLowerCase().includes(query));\n if (!matchesSearch) return false;\n }\n\n return true;\n });\n }, [vibes, searchQuery]);\n\n const selectedVibes = useMemo(\n () => vibes.filter((vibe) => selectedVibeIds.has(vibe.id)),\n [vibes, selectedVibeIds]\n );\n const activatableVibes = selectedVibes.filter((vibe) => vibe.status !== 'ACTIVE');\n const archivableVibes = selectedVibes.filter((vibe) => vibe.status !== 'ARCHIVED');\n const allVisibleSelected =\n filteredVibes.length > 0 && filteredVibes.every((vibe) => selectedVibeIds.has(vibe.id));\n const partiallySelected =\n filteredVibes.some((vibe) => selectedVibeIds.has(vibe.id)) && !allVisibleSelected;\n\n const handleVibeSelect = (vibe: Vibe) => {\n navigate(`/vibes/${vibe.id}`);\n };\n\n const handleVibeActivate = async (vibe: Vibe) => {\n try {\n const result = await updateVibeStatus({\n variables: { id: vibe.id, status: 'ACTIVE' as const },\n });\n if (result.data?.updateVibeStatus) {\n onVibeActivate?.({ id: vibe.id, name: vibe.name, type: vibe.type, status: 'ACTIVE' });\n await refetch();\n }\n } catch (err) {\n console.error('Failed to activate vibe:', err);\n }\n };\n\n const handleVibeArchive = async (vibe: Vibe) => {\n try {\n await archiveVibe({ variables: { id: vibe.id } });\n await refetch();\n } catch (err) {\n console.error('Failed to archive vibe:', err);\n }\n };\n\n // Honour the user's preferred IDE + per-Vibe override when opening from\n // the card. Falls back to `vscode://` for IDEs without a registered URL\n // protocol; for richer launching (agent-exec, browser editor) use the\n // VibeOpenInEditorMenu on the Vibe detail page.\n const preferredIdeCommand = useVibeControlsStore((s) => s.preferredIdeCommand);\n const vibeIdeOverrides = useVibeControlsStore((s) => s.vibeIdeOverrides);\n\n const handleOpenInEditor = (vibe: Vibe) => {\n if (!vibe.path) {\n setPageError(tr('vibesPage.openEditorPathMissing', 'No path available to open in editor'));\n pageErrorTimer.current = setTimeout(() => {\n setPageError(null);\n pageErrorTimer.current = null;\n }, 3000);\n return;\n }\n const cmd = resolveEffectiveIdeCommand(vibe.id, preferredIdeCommand, vibeIdeOverrides);\n const preset = getIdePreset(cmd);\n const url = preset ? buildProtocolUrl(preset, vibe.path) : `vscode://file/${vibe.path}`;\n if (url) {\n window.location.href = url;\n } else {\n setPageError(\n tr(\n 'vibesPage.openEditorNoProtocol',\n `${preset?.label ?? cmd} has no URL handler — open this Vibe and use the editor menu.`\n )\n );\n pageErrorTimer.current = setTimeout(() => {\n setPageError(null);\n pageErrorTimer.current = null;\n }, 4000);\n }\n };\n\n const handleCreateVibe = useCallback(() => {\n setCreateError(null);\n setShowCreateForm(true);\n // Ensure the create-form agent dropdown has options even if the user\n // never opened the page-level agent filter.\n handleAgentFilterInteract();\n }, [handleAgentFilterInteract]);\n\n // Auto-open the create modal when arriving via /vibes/new or /vibes?create=true\n // (used by dashboard widgets and deep links). Strip the param after opening\n // so the back button returns to the previous page rather than re-opening.\n const [searchParams, setSearchParams] = useSearchParams();\n useEffect(() => {\n if (searchParams.get('create') === 'true') {\n handleCreateVibe();\n const next = new URLSearchParams(searchParams);\n next.delete('create');\n setSearchParams(next, { replace: true });\n }\n }, [searchParams, setSearchParams, handleCreateVibe]);\n\n const toggleVibeSelection = (vibe: Vibe, checked: boolean) => {\n setSelectedVibeIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(vibe.id);\n } else {\n next.delete(vibe.id);\n }\n return next;\n });\n };\n\n const toggleAllVisibleVibes = (checked: boolean) => {\n setSelectedVibeIds((prev) => {\n const next = new Set(prev);\n filteredVibes.forEach((vibe) => {\n if (checked) {\n next.add(vibe.id);\n } else {\n next.delete(vibe.id);\n }\n });\n return next;\n });\n };\n\n const clearSelection = () => setSelectedVibeIds(new Set());\n\n const handleBulkActivate = async () => {\n if (activatableVibes.length === 0) return;\n\n const results = await Promise.allSettled(\n activatableVibes.map((vibe) =>\n updateVibeStatus({ variables: { id: vibe.id, status: 'ACTIVE' as const } })\n )\n );\n\n const succeeded = results.filter((result) => result.status === 'fulfilled').length;\n const failed = results.length - succeeded;\n\n setSuccessMessage(\n `Activated ${succeeded} vibe${succeeded === 1 ? '' : 's'}${failed > 0 ? ` • ${failed} failed` : ''}`\n );\n setTimeout(() => setSuccessMessage(null), 4000);\n clearSelection();\n setBulkAction(null);\n await refetch();\n };\n\n const handleBulkArchive = async () => {\n if (archivableVibes.length === 0) return;\n\n const results = await Promise.allSettled(\n archivableVibes.map((vibe) => archiveVibe({ variables: { id: vibe.id } }))\n );\n\n const succeeded = results.filter((result) => result.status === 'fulfilled').length;\n const failed = results.length - succeeded;\n\n setSuccessMessage(\n `Archived ${succeeded} vibe${succeeded === 1 ? '' : 's'}${failed > 0 ? ` • ${failed} failed` : ''}`\n );\n setTimeout(() => setSuccessMessage(null), 4000);\n clearSelection();\n setBulkAction(null);\n await refetch();\n };\n\n const handleSubmitCreateVibe = async () => {\n const name = nameRef.current?.value?.trim();\n const path = pathRef.current?.value?.trim();\n const agentId = agentRef.current?.value;\n const type = typeRef.current?.value;\n\n // Path is optional now — vibes can be created without a specific\n // working dir up front and an agent can be attached later. Only the\n // name is required.\n const errors: { name?: string; path?: string } = {};\n if (!name) errors.name = tr('vibesPage.nameRequired', 'Name is required');\n if (errors.name) {\n setFieldErrors(errors);\n setCreateError(tr('vibesPage.fillRequired', 'Please fill in all required fields'));\n return;\n }\n setFieldErrors({});\n\n // Validate type against allowed enum values\n const validTypes = [\n 'PROJECT',\n 'REPOSITORY',\n 'WORKSPACE',\n 'MONOREPO',\n 'PACKAGE',\n 'CUSTOM',\n ] as const;\n const resolvedType = validTypes.includes(type as (typeof validTypes)[number])\n ? (type as (typeof validTypes)[number])\n : 'PROJECT';\n\n setCreateLoading(true);\n setCreateError(null);\n try {\n // Path is optional — when the user leaves it blank we send an empty\n // string so the (still required at the DB level) column has a value\n // the backend accepts without forcing a schema migration.\n const result = await createVibe({\n variables: {\n input: {\n name,\n path: path ?? '',\n type: resolvedType,\n },\n },\n });\n if (!result.data?.createVibe?.id) {\n throw new Error(tr('vibesPage.failedCreate', 'Failed to create vibe'));\n }\n const newVibeId = result.data.createVibe.id;\n // Multi-agent: vibes are created agent-less; attach the optionally\n // selected agent afterward via the attachment mutation (the old\n // `agentId` field on CreateVibeInput was removed server-side).\n if (agentId) {\n try {\n await attachAgentToVibe({ variables: { input: { vibeId: newVibeId, agentId } } });\n } catch {\n // Non-fatal: vibe created, agent attachment failed\n }\n }\n setShowCreateForm(false);\n setSuccessMessage(tr('vibesPage.createdSuccess', 'Vibe created successfully'));\n await flushTags(newVibeId);\n await refetch();\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowCreateForm(false);\n setCreateError(null);\n setFieldErrors({});\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n\n setCreateError(\n err instanceof Error ? err.message : tr('vibesPage.failedCreate', 'Failed to create vibe')\n );\n } finally {\n setCreateLoading(false);\n }\n };\n\n // Stats\n const stats = {\n total: vibes.length,\n active: vibes.filter((v) => v.status === 'ACTIVE').length,\n inactive: vibes.filter((v) => v.status === 'INACTIVE').length,\n };\n\n // Collapse guard states into a single MotionPage shell so transitioning\n // between loading / error / data does not unmount+remount and replay the\n // entrance animation. Contents are swapped in-place.\n const showPermissionsLoading = permissionsLoading;\n const showAccessDenied =\n !permissionsLoading &&\n !!permissions &&\n (permissions.permissionStrings?.length ?? 0) > 0 &&\n !canListVibes;\n const showInitialLoading =\n !showPermissionsLoading && !showAccessDenied && loading && vibes.length === 0;\n const showInitialError =\n !showPermissionsLoading && !showAccessDenied && !!error && vibes.length === 0;\n const showDataShell = !showPermissionsLoading && !showAccessDenied && !showInitialError;\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n {showPermissionsLoading && (\n <LoadingState message={tr('vibesPage.loadingPermissions', 'Loading permissions...')} />\n )}\n {showAccessDenied && (\n <AccessDenied\n title={tr('vibesPage.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'vibesPage.accessDeniedDescription',\n \"You don't have permission to view vibes. Contact your workspace administrator for access.\"\n )}\n />\n )}\n {showInitialError && (\n <ErrorState\n title={tr('vibesPage.failedLoad', 'Failed to load vibes')}\n message={error?.message ?? ''}\n onRetry={() => refetch()}\n />\n )}\n {showDataShell && (\n <>\n <PageHeader\n title={tr('vibesPage.title', 'Vibes')}\n description={tr('vibesPage.descriptionActive', '{{active}} of {{total}} vibes active', {\n active: stats.active,\n total: stats.total,\n })}\n actions={\n <div className=\"flex items-center gap-2\">\n {isFree && vibeUsage && typeof vibeUsage.limit === 'number' && (\n <a\n href=\"/billing/plans\"\n className={`inline-flex items-center gap-1 px-2 py-1 text-xs font-medium rounded-md border ${\n vibeUsage.current >= vibeUsage.limit\n ? 'border-status-error-border bg-status-error-bg-subtle text-status-error-text'\n : 'border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text'\n }`}\n title={tr(\n 'vibesPage.upgradeChipTooltip',\n 'Free plan limit — upgrade for more vibes'\n )}\n >\n {tr('vibesPage.usageChip', '{{current}} of {{limit}} vibes used', {\n current: vibeUsage.current,\n limit: vibeUsage.limit,\n })}\n </a>\n )}\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title={tr('vibesPage.refreshTitle', 'Refresh vibes')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <PermissionButton\n permission={VIBE_PERMISSIONS.VIBE_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tr(\n 'vibesPage.deniedCreateTooltip',\n \"You don't have permission to create vibes\"\n )}\n >\n <button\n type=\"button\"\n onClick={handleCreateVibe}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <Plus className=\"size-4\" />\n {tr('vibesPage.addVibe', 'Add Vibe')}\n </button>\n </PermissionButton>\n </div>\n }\n />\n\n {/* Filters Bar */}\n <div className=\"space-y-3\">\n {/* Row 1: Search + View Toggle */}\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('vibesPage.searchPlaceholder', 'Search vibes...')}\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('vibesPage.gridViewTitle', 'Grid view')}\n >\n <Grid className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('vibesPage.listViewTitle', 'List view')}\n >\n <List className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Filters - wrap on all screens */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <FilterDropdown\n label={tr('vibesPage.filterStatus', 'Status')}\n value={statusFilter}\n options={statusOptions}\n onChange={setStatusFilter}\n />\n <div onMouseDown={handleAgentFilterInteract} onFocus={handleAgentFilterInteract}>\n <FilterDropdown\n label={tr('vibesPage.filterAgent', 'Agent')}\n value={agentFilter}\n options={agentOptions}\n onChange={setAgentFilter}\n />\n </div>\n </div>\n </div>\n\n {/* Results Count */}\n {searchQuery ||\n statusFilter !== 'all' ||\n typeFilter !== 'all' ||\n agentFilter !== 'all' ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('vibesPage.showingResults', 'Showing {{filtered}} of {{total}} vibes', {\n filtered: filteredVibes.length,\n total: vibes.length,\n })}\n {loading && (\n <span className=\"ml-2 text-text-muted\">\n {tr('vibesPage.refreshing', '(refreshing...)')}\n </span>\n )}\n </p>\n ) : null}\n\n {filteredVibes.length > 0 && (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-2\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisibleVibes}\n ariaLabel=\"Select all visible vibes\"\n />\n <span className=\"text-sm font-medium text-text-primary\">\n Select all visible\n </span>\n </div>\n {selectedVibeIds.size > 0 && (\n <button\n type=\"button\"\n onClick={clearSelection}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n Clear selection\n </button>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-bg-sunken px-3 py-1 text-sm font-medium text-text-secondary\">\n {selectedVibeIds.size} selected\n </span>\n <button\n type=\"button\"\n onClick={() => setBulkAction('activate')}\n disabled={activatableVibes.length === 0}\n title=\"Mark selected vibes as ACTIVE so they appear in the default vibe list and can run sessions/tunnels via their attached agent(s).\"\n className=\"rounded-md border border-status-success-border px-3 py-2 text-sm font-medium text-status-success-text hover:bg-status-success-bg disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <span className=\"inline-flex items-center gap-2\">\n <Play className=\"size-4\" />\n Activate Selected\n {activatableVibes.length > 0 ? ` (${activatableVibes.length})` : ''}\n </span>\n </button>\n <button\n type=\"button\"\n onClick={() => setBulkAction('archive')}\n disabled={archivableVibes.length === 0}\n className=\"rounded-md border border-status-warning-border px-3 py-2 text-sm font-medium text-status-warning-text hover:bg-status-warning-bg disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <span className=\"inline-flex items-center gap-2\">\n <Archive className=\"size-4\" />\n Archive Selected\n {archivableVibes.length > 0 ? ` (${archivableVibes.length})` : ''}\n </span>\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Vibes Display */}\n {showInitialLoading ? (\n <PageSkeleton variant=\"cards\" count={6} />\n ) : filteredVibes.length > 0 ? (\n <AnimatePresence mode=\"wait\">\n {viewMode === 'grid' ? (\n <MotionList\n key=\"grid\"\n data-tour=\"vibes-page-grid-view\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {filteredVibes.map((vibe) => (\n <MotionItem key={vibe.id}>\n <VibeCard\n vibe={vibe}\n onOpen={handleVibeSelect}\n onActivate={canUpdateVibe ? handleVibeActivate : undefined}\n onArchive={canUpdateVibe ? handleVibeArchive : undefined}\n onOpenInEditor={handleOpenInEditor}\n selected={selectedVibeIds.has(vibe.id)}\n onToggleSelect={(checked) => toggleVibeSelection(vibe, checked)}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <MotionList key=\"list\">\n <VibeList\n vibes={filteredVibes}\n onOpen={handleVibeSelect}\n onActivate={handleVibeActivate}\n onArchive={handleVibeArchive}\n onOpenInEditor={handleOpenInEditor}\n selectedVibeIds={selectedVibeIds}\n onToggleSelect={toggleVibeSelection}\n onToggleAllVisible={toggleAllVisibleVibes}\n allVisibleSelected={allVisibleSelected}\n partiallySelected={partiallySelected}\n />\n </MotionList>\n )}\n </AnimatePresence>\n ) : (\n <EmptyState\n icon={<Zap className=\"size-8 text-text-secondary\" />}\n title={\n searchQuery ||\n statusFilter !== 'all' ||\n typeFilter !== 'all' ||\n agentFilter !== 'all'\n ? tr('vibesPage.emptyNoVibesFound', 'No vibes found')\n : tr('vibesPage.emptyNoVibesConfigured', 'No vibes configured')\n }\n description={\n searchQuery ||\n statusFilter !== 'all' ||\n typeFilter !== 'all' ||\n agentFilter !== 'all'\n ? tr('vibesPage.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'vibesPage.emptyNoVibesDescription',\n 'Add your first vibe to start managing your development projects'\n )\n }\n action={\n !searchQuery &&\n statusFilter === 'all' &&\n typeFilter === 'all' &&\n agentFilter === 'all' ? (\n <div className=\"flex items-center gap-2\">\n <PermissionGate permission={VIBE_PERMISSIONS.VIBE_CREATE}>\n <button\n type=\"button\"\n onClick={handleCreateVibe}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('vibesPage.addVibe', 'Add Vibe')}\n </button>\n </PermissionGate>\n <GetHelpButton prompt=\"What is a Vibe in VibeControls and how do I create one?\" />\n </div>\n ) : undefined\n }\n />\n )}\n\n {/* Success Toast */}\n {successMessage && (\n <div className=\"fixed bottom-4 right-4 z-50 px-4 py-3 bg-status-success-bg text-status-success-text rounded-md shadow-lg text-sm\">\n {successMessage}\n </div>\n )}\n\n <ConfirmDialog\n isOpen={bulkAction === 'activate'}\n onClose={() => setBulkAction(null)}\n onConfirm={handleBulkActivate}\n title=\"Activate selected vibes\"\n message={`Activate ${activatableVibes.length} selected vibe${activatableVibes.length === 1 ? '' : 's'}?`}\n confirmLabel=\"Activate\"\n variant=\"default\"\n />\n\n <ConfirmDialog\n isOpen={bulkAction === 'archive'}\n onClose={() => setBulkAction(null)}\n onConfirm={handleBulkArchive}\n title=\"Archive selected vibes\"\n message={`Archive ${archivableVibes.length} selected vibe${archivableVibes.length === 1 ? '' : 's'}? Archived vibes will be hidden from active workflows.`}\n confirmLabel=\"Archive\"\n variant=\"warning\"\n />\n\n {/* Page-level Error Toast */}\n {pageError && (\n <div\n role=\"alert\"\n className=\"fixed bottom-4 right-4 z-50 px-4 py-3 bg-status-error-bg text-status-error-text rounded-md shadow-lg text-sm\"\n >\n {pageError}\n <button\n type=\"button\"\n onClick={clearPageError}\n className=\"ml-3 text-xs underline opacity-80 hover:opacity-100\"\n >\n {tr('vibesPage.dismiss', 'Dismiss')}\n </button>\n </div>\n )}\n\n {/* Create Vibe Modal */}\n {showCreateForm && (\n <div className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4\">\n <div className=\"bg-bg-surface rounded-lg p-6 w-full max-w-lg\">\n <h2 className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr('vibesPage.addNewVibe', 'Add New Vibe')}\n </h2>\n <div className=\"space-y-4\">\n {createError && (\n <div className=\"p-3 bg-status-error-bg text-status-error-text text-sm rounded-md\">\n {createError}\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibesPage.nameLabel', 'Name *')}\n </label>\n <input\n aria-label=\"control\"\n ref={nameRef}\n type=\"text\"\n placeholder={tr('vibesPage.namePlaceholder', 'My Project')}\n onChange={() =>\n fieldErrors.name && setFieldErrors((e) => ({ ...e, name: undefined }))\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-placeholder ${fieldErrors.name ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.name && (\n <p className=\"mt-1 text-xs text-status-error-text\">{fieldErrors.name}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibesPage.pathLabel', 'Path')}\n <span className=\"ml-1 text-xs text-text-tertiary font-normal\">\n {tr('vibesPage.pathOptional', '(optional)')}\n </span>\n </label>\n <input\n aria-label=\"control\"\n ref={pathRef}\n type=\"text\"\n placeholder={tr('vibesPage.pathPlaceholder', '/path/to/project')}\n onChange={() =>\n fieldErrors.path && setFieldErrors((e) => ({ ...e, path: undefined }))\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-placeholder ${fieldErrors.path ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.path && (\n <p className=\"mt-1 text-xs text-status-error-text\">{fieldErrors.path}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibesPage.agentLabel', 'Agent')}\n </label>\n <select\n ref={agentRef}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary\"\n >\n <option value=\"\">{tr('vibesPage.selectAgent', 'Select an agent')}</option>\n {agentOptions\n .filter((opt) => opt.value !== 'all')\n .map((agent) => (\n <option key={agent.value} value={agent.value}>\n {agent.label}\n </option>\n ))}\n </select>\n </div>\n {/* Type selector is hidden for now — every newly created\n vibe defaults to PROJECT. The other types\n (REPOSITORY, WORKSPACE, MONOREPO, PACKAGE, CUSTOM)\n are still valid in the schema but aren't exposed\n here until the corresponding UX is shipped per type.\n A hidden input keeps `typeRef.current?.value` valid\n so the submit-time validation logic doesn't crash. */}\n <input ref={typeRef} type=\"hidden\" value=\"PROJECT\" />\n </div>\n <div className=\"mt-4 border-t border-border-default pt-4\">\n <h3 className=\"text-sm font-medium text-text-secondary mb-2\">\n {tr('vibesPage.tags', 'Tags')}\n </h3>\n <EntityTagPicker entityType=\"VIBECONTROLS_VIBE\" onDraftChange={setDraftTagIds} />\n </div>\n <div className=\"flex flex-col sm:flex-row justify-end gap-3 mt-6\">\n <button\n type=\"button\"\n onClick={() => setShowCreateForm(false)}\n disabled={createLoading}\n className=\"px-4 py-2 text-text-primary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n {tr('vibesPage.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleSubmitCreateVibe}\n disabled={createLoading}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {createLoading\n ? tr('vibesPage.creating', 'Creating...')\n : tr('vibesPage.createVibe', 'Create Vibe')}\n </button>\n </div>\n </div>\n </div>\n )}\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Vibe Quota Reached\"\n message=\"You've used all available vibe creation quota on your current plan. Upgrade your plan to create more vibes.\"\n />\n </>\n )}\n </MotionPage>\n );\n}\n"],"mappings":"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"}