@burdenoff/microfe-vibecontrols 2026.601.5 → 2026.601.6

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":"VibeCalendarPage.js","names":[],"sources":["../../src/pages/VibeCalendarPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback, useEffect } from 'react';\nimport { useParams, useSearchParams } from 'react-router-dom';\nimport { CalendarClock, AlertCircle, X } from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { useTenantContext } from '@/hooks/useTenantContext';\nimport {\n VibeCalendarHeader,\n VibeCalMonthView,\n VibeCalWeekView,\n VibeCalDayView,\n VibeCalAgendaView,\n ScheduleTaskModal,\n LogsDrawer,\n} from '../components/vibecalendar';\nimport { useVibeNavigate, useVibeControls } from '@/providers/VibeControlsProvider';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport type {\n CalendarViewMode,\n VibeCalendarTask,\n VibeCalendarExecution,\n ScheduleTaskInput,\n} from '../components/vibecalendar';\nimport {\n expandJobsToOccurrences,\n type SchedulerJobLike,\n} from '../components/vibecalendar/expandOccurrences';\nimport {\n useCreateVibeCalendarTaskMutation,\n useExecuteScheduledJobNowMutation,\n useTriggerVibeCalendarTaskNowMutation,\n} from '@/generated/wspace-operations';\n\n/**\n * The /scheduler page writes ScheduledJobs through the WSPACE gateway —\n * `scheduledJobs` is only published by `wspace-scheduler-svc` and is\n * workspace-scoped (the `$workspaceId: ID!` argument is required). The\n * Calendar renders those jobs alongside its own VibeCalendarTasks. We use\n * `graphqlFetch` rather than Apollo to keep the Calendar's local-state\n * timeline decoupled from the global Apollo cache that powers other\n * VibeControls pages.\n */\nconst WORKSPACE_SCHEDULED_JOBS_QUERY = /* GraphQL */ `\n query GetWorkspaceScheduledJobsForCalendar($workspaceId: ID!) {\n scheduledJobs(workspaceId: $workspaceId, pagination: { first: 500 }) {\n jobs {\n id\n name\n description\n status\n scheduleType\n cronExpression\n rrule\n interval\n scheduledAt\n nextExecutionAt\n lastExecutionAt\n actionId\n timezone\n startDate\n endDate\n }\n total\n }\n }\n`;\n\ninterface WorkspaceScheduledJobsResponse {\n scheduledJobs: {\n jobs: SchedulerJobLike[];\n total: number;\n };\n}\n\n/**\n * Calendar Page\n *\n * Main page for scheduling and managing tasks on VibeControls agents.\n * Supports month and agenda views with task creation, on-demand triggering,\n * and log viewing via the LogsDrawer.\n */\nexport function VibeCalendarPage() {\n const navigate = useVibeNavigate();\n const { workspaceId } = useVibeControls();\n const { t } = useI18n();\n const { viewType } = useParams<{ viewType?: string }>();\n const [searchParams, setSearchParams] = useSearchParams();\n // When navigated here from the Actions page via \"Add to calendar\",\n // the URL carries ?actionId=... — open the modal in Action-linked mode.\n const prefillActionId = searchParams.get('actionId') ?? undefined;\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n // The unified-source migration moved the calendar's only list\n // path onto `scheduledJobs(workspaceId)` (the scheduler subgraph) —\n // there's no parallel `vibeCalendarTasks` query firing here\n // anymore. The page-level access gate is therefore\n // `schedule:read` specifically; admitting calendar-task-only\n // readers would let them past the UI only to hit an empty\n // calendar / scheduler RBAC denial on the actual fetch.\n // calendar-agent-task permissions are still consumed downstream\n // (CREATE for the schedule button, EXECUTE for the per-tile ▶\n // branch) but they don't gate the page mount.\n const canList = hasPermission(VIBE_PERMISSIONS.SCHEDULE_READ);\n // Required by the @include gate on `output`/`errorMessage` in\n // both run-now mutations — without this Apollo rejects the\n // operation before it leaves the browser.\n const canReadExecutionLogs = hasPermission(VIBE_PERMISSIONS.LOG_READ);\n const canCreate = hasPermission(VIBE_PERMISSIONS.CALENDAR_AGENT_TASK_CREATE);\n // The agenda exclusively renders ScheduledJob tiles in the\n // current unified-source path, so the prop-level gate on\n // `onTriggerNow` is `schedule:execute` — admitting\n // `calendar-agent-task:execute`-only users would show ▶ on every\n // tile just to bounce off a friendly denial banner. Both\n // permissions stay tracked because handleTriggerNow's per-source\n // pre-check uses them defensively in case a future mixed-source\n // render lands here.\n const canExecuteCalendarTask = hasPermission(VIBE_PERMISSIONS.CALENDAR_AGENT_TASK_EXECUTE);\n const canExecuteScheduledJob = hasPermission(VIBE_PERMISSIONS.SCHEDULE_EXECUTE);\n const canExecute = canExecuteScheduledJob;\n\n // URL overrides reach Apollo via per-request headers so tile-▶\n // mutations stay in the same tenant the support deep-link list\n // loaded against.\n const tenantContext = useTenantContext();\n\n const [currentDate, setCurrentDate] = useState(new Date());\n const [viewMode, setViewMode] = useState<CalendarViewMode>(\n (viewType as CalendarViewMode) || 'month'\n );\n const [showCreateModal, setShowCreateModal] = useState(false);\n\n // Auto-open the modal when arriving with an actionId query param.\n useEffect(() => {\n if (prefillActionId) {\n setShowCreateModal(true);\n }\n }, [prefillActionId]);\n const [showLogsDrawer, setShowLogsDrawer] = useState(false);\n const [selectedTask, setSelectedTask] = useState<VibeCalendarTask | null>(null);\n const [selectedExecution, setSelectedExecution] = useState<VibeCalendarExecution | null>(null);\n const [triggerError, setTriggerError] = useState<string | null>(null);\n\n // Memoized so downstream useCallback deps stay referentially\n // stable across renders.\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n // Calculate date range for current view\n const dateRange = useMemo(() => {\n const start = new Date(currentDate);\n const end = new Date(currentDate);\n\n switch (viewMode) {\n case 'month':\n start.setDate(1);\n start.setHours(0, 0, 0, 0);\n end.setMonth(end.getMonth() + 1, 0);\n end.setHours(23, 59, 59, 999);\n break;\n case 'week':\n start.setDate(start.getDate() - start.getDay());\n start.setHours(0, 0, 0, 0);\n end.setDate(start.getDate() + 6);\n end.setHours(23, 59, 59, 999);\n break;\n case 'day':\n start.setHours(0, 0, 0, 0);\n end.setHours(23, 59, 59, 999);\n break;\n case 'agenda':\n start.setDate(1);\n start.setHours(0, 0, 0, 0);\n end.setMonth(end.getMonth() + 1, 0);\n end.setHours(23, 59, 59, 999);\n break;\n }\n\n return { startDate: start.toISOString(), endDate: end.toISOString() };\n }, [currentDate, viewMode]);\n\n // Unified calendar view: fetch every ScheduledJob the workspace owns\n // (where /scheduler writes). The calendar renders jobs regardless of how\n // they were created, so all schedule-to-Action tasks show up here as a\n // single source of truth.\n //\n // `scheduledJobs` is published by `wspace-scheduler-svc` and is workspace-\n // scoped — `workspaceId` is required as a query variable. `graphqlFetch`\n // auto-resolves the workspace/org/tenant context from the shell storage\n // and sets the matching headers; URL params (`?org=`, `?tenant=`)\n // override that resolution for the support-tooling deep-link flow.\n const [jobs, setJobs] = useState<SchedulerJobLike[]>([]);\n const [rangeError, setRangeError] = useState<Error | undefined>(undefined);\n const [jobsLoaded, setJobsLoaded] = useState(false);\n const [jobsLoading, setJobsLoading] = useState(false);\n const refetch = useCallback(async () => {\n // Guard: scheduledJobs requires workspaceId — if it isn't resolved yet,\n // skip rather than firing a request that's guaranteed to fail validation.\n // The effect below re-invokes refetch once workspaceId becomes available.\n // (The state-reset on workspace transitions lives in the effect itself,\n // not here, because the effect early-returns when workspaceId is falsy\n // and refetch never gets called in that path.)\n if (!workspaceId) return;\n setJobsLoading(true);\n try {\n // graphqlFetch reads workspace/org/tenant context from storage and sets\n // the proper x-workspace-id / x-org-id / x-tenant-id headers itself —\n // passing `true` opts in to that auto-resolution. URL params override\n // storage for the org/tenant context (used by the support tooling\n // when impersonating a different tenant via deep-link).\n const urlParams = new URLSearchParams(\n typeof window === 'undefined' ? '' : window.location.search\n );\n const orgFromUrl = urlParams.get('org') ?? undefined;\n const tenantFromUrl = urlParams.get('tenant') ?? undefined;\n\n const res = await graphqlFetch<WorkspaceScheduledJobsResponse>({\n gateway: 'workspace',\n query: WORKSPACE_SCHEDULED_JOBS_QUERY,\n operationName: 'GetWorkspaceScheduledJobsForCalendar',\n variables: { workspaceId },\n // Pass the resolved workspaceId STRING (not `true`) so the\n // `x-workspace-id` header matches the GraphQL variable even when\n // ActiveContextStorage is stale / not hydrated yet. Otherwise the\n // gateway can authorize/federate against the previous workspace's\n // context while the resolver receives the new one.\n workspaceId,\n organizationId: orgFromUrl ?? true,\n tenantId: tenantFromUrl ?? true,\n });\n\n if (res.errors && res.errors.length) {\n // Wrap GraphQL/network errors in a generic, UI-safe Error so we\n // never leak raw resolver / federation messages into the banner.\n throw new Error(res.errors[0]?.message ?? 'Unable to load scheduled tasks');\n }\n setJobs(res.data?.scheduledJobs?.jobs ?? []);\n setRangeError(undefined);\n setJobsLoaded(true);\n } catch (err) {\n console.error('[VibeCalendarPage] Failed to load scheduled jobs:', err);\n setRangeError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setJobsLoading(false);\n }\n }, [workspaceId]);\n\n useEffect(() => {\n if (!workspaceId) {\n // Reset on workspace transitions so the empty-state CTA doesn't\n // flash stale data from the previous workspace while the new one\n // is still resolving. Lives here, not in refetch, because refetch\n // is only called from this effect — when workspaceId is falsy,\n // refetch never runs.\n setJobs([]);\n setRangeError(undefined);\n setJobsLoaded(false);\n return;\n }\n void refetch();\n }, [workspaceId, refetch]);\n\n // `sourceById` built in the same pass so handleTriggerNow does\n // an O(1) Map lookup per click — month/agenda views can mint\n // hundreds of occurrences from a single recurring job.\n const { occurrences, sourceById } = useMemo(() => {\n if (!jobs.length) {\n return {\n occurrences: [],\n sourceById: new Map<string, VibeCalendarTask['source']>(),\n };\n }\n const occs = expandJobsToOccurrences(\n jobs,\n new Date(dateRange.startDate),\n new Date(dateRange.endDate)\n );\n const map = new Map<string, VibeCalendarTask['source']>();\n for (const o of occs) {\n // Always populate with an explicit discriminator. Future\n // shapes (e.g. an externalTrigger source) would otherwise\n // collapse into the default branch silently — `undefined`\n // here means \"id no longer in the visible window\", nothing\n // more.\n map.set(o.task.id, o.task.source ?? 'vibeCalendarTask');\n }\n return { occurrences: occs, sourceById: map };\n }, [jobs, dateRange.startDate, dateRange.endDate]);\n\n // Agents are no longer collected in the modal — the scheduler resolves\n // them from the referenced Action at execution time.\n\n // Mutations\n const [createTaskMutation, { loading: createLoading }] = useCreateVibeCalendarTaskMutation();\n const [triggerNowMutation] = useTriggerVibeCalendarTaskNowMutation();\n // Scheduled-job tiles route to the scheduler subgraph rather than\n // the calendar one (different id space, different RBAC resource).\n const [executeScheduledJobNowMutation] = useExecuteScheduledJobNowMutation();\n\n const handleViewModeChange = useCallback((mode: CalendarViewMode) => {\n setViewMode(mode);\n }, []);\n\n // `undefined` return signals \"no longer in the visible window\" —\n // callers should treat that as the safe default `'vibeCalendarTask'`.\n const sourceForTask = useCallback(\n (taskId: string): VibeCalendarTask['source'] => {\n return sourceById.get(taskId);\n },\n [sourceById]\n );\n\n const handleTriggerNow = useCallback(\n async (taskId: string) => {\n // Clear any prior failure banner before re-attempting — same\n // pattern as ScheduledJobDetailPage.handleExecuteNow.\n setTriggerError(null);\n // Branch on the discriminator set by `jobToVibeCalendarTask`.\n // The view passes `onTriggerNow` to every tile when EITHER\n // permission is held (combined `canExecute` gate above), so we\n // pre-check the per-source permission here and surface a\n // friendly inline banner if missing rather than firing the\n // mutation just to bounce off a server-side RBAC denial.\n const source = sourceForTask(taskId);\n if (source === 'scheduledJob' && !canExecuteScheduledJob) {\n setTriggerError(\n tr(\n 'calendar.scheduleExecuteDenied',\n 'You do not have permission to run scheduled jobs (schedule:execute).'\n )\n );\n return;\n }\n if (source !== 'scheduledJob' && !canExecuteCalendarTask) {\n setTriggerError(\n tr(\n 'calendar.calendarTaskExecuteDenied',\n 'You do not have permission to run calendar tasks (calendar-agent-task:execute).'\n )\n );\n return;\n }\n // VibeControlsProvider sets `errorPolicy: 'all'` so RBAC /\n // GraphQL errors resolve into `result.error` (ApolloError)\n // instead of throwing. Inspect both paths consistently —\n // denials would otherwise be silently dropped. Same pattern\n // ActionsPage already uses for quota-exhaust handling.\n const fallbackMsg = tr(\n 'calendar.triggerFailedFallback',\n 'Failed to trigger task. Try again or contact support.'\n );\n try {\n const result =\n source === 'scheduledJob'\n ? await executeScheduledJobNowMutation({\n variables: { id: taskId, canReadLogs: canReadExecutionLogs },\n context: tenantContext,\n })\n : await triggerNowMutation({\n variables: { id: taskId },\n context: tenantContext,\n });\n if (result.error) {\n console.error('Trigger mutation returned an Apollo error:', result.error);\n setTriggerError(result.error.message || fallbackMsg);\n }\n } catch (err) {\n console.error('Failed to trigger task:', err);\n const msg = err instanceof Error && err.message ? err.message : fallbackMsg;\n setTriggerError(msg);\n }\n },\n [\n triggerNowMutation,\n executeScheduledJobNowMutation,\n sourceForTask,\n canReadExecutionLogs,\n canExecuteCalendarTask,\n canExecuteScheduledJob,\n tenantContext,\n tr,\n ]\n );\n\n const handleViewLogs = useCallback((task: VibeCalendarTask) => {\n setSelectedTask(task);\n setSelectedExecution(null);\n setShowLogsDrawer(true);\n }, []);\n\n const handleViewDetail = useCallback(\n (taskId: string) => {\n // A tile that originated from a ScheduledJob has no row in\n // wspace-calendar-svc, so VibeTaskDetailPage would 404 on it.\n // Route to the matching in-product detail page instead.\n //\n // Carry the existing query string so the support deep-link\n // flow (`?org=…&tenant=…`) keeps its tenant context across\n // the navigation — without it the detail query runs against\n // the default/stored tenant and returns a wrong-context view.\n // Encode the id for defense-in-depth in case the scheduler\n // ever emits ids that contain URL-reserved characters.\n const encoded = encodeURIComponent(taskId);\n const search = searchParams.toString();\n const suffix = search ? `?${search}` : '';\n const path =\n sourceForTask(taskId) === 'scheduledJob'\n ? `/calendar/scheduled-job/${encoded}`\n : `/calendar/task/${encoded}`;\n navigate(`${path}${suffix}`);\n },\n [navigate, sourceForTask, searchParams]\n );\n\n const handleCreateTask = useCallback(\n async (input: ScheduleTaskInput) => {\n try {\n // Calendar entries always delegate execution to a VibeControls\n // Action. The scheduler resolves agent/command/webhook details\n // from the Action definition at run time — we only send\n // actionId + calendar/schedule config from this modal.\n await createTaskMutation({\n variables: {\n input: {\n calendarId: input.calendarId || 'default',\n title: input.title,\n description: input.description,\n actionId: input.actionId,\n scheduleType: input.scheduleType,\n scheduledAt: input.scheduledAt,\n cronExpression: input.cronExpression,\n interval: input.interval,\n intervalUnit: input.intervalUnit,\n timezone: input.timezone,\n },\n },\n });\n setShowCreateModal(false);\n // Drop the actionId query param after successful creation\n if (searchParams.has('actionId')) {\n const next = new URLSearchParams(searchParams);\n next.delete('actionId');\n setSearchParams(next, { replace: true });\n }\n } catch (err) {\n console.error('Failed to create task:', err);\n }\n },\n [createTaskMutation, searchParams, setSearchParams]\n );\n\n if (permissionsLoading)\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-4\">\n <LoadingState message={tr('calendar.loadingPermissions', 'Loading permissions...')} />\n </MotionPage>\n );\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canList) {\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-4\">\n <AccessDenied\n title={tr('common.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'calendar.accessDeniedDescription',\n \"You don't have permission to view the Calendar.\"\n )}\n />\n </MotionPage>\n );\n }\n\n // Show the empty-state CTA only after the first successful load returned\n // an empty list — never while loading, and never when an error blocks the\n // distinction between \"no data\" and \"couldn't fetch\".\n const showEmptyState = jobsLoaded && !rangeError && jobs.length === 0;\n\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-4\">\n {/* Error banner (non-blocking) — friendly message, never raw GraphQL */}\n {rangeError && jobs.length === 0 && (\n <div className=\"flex items-center gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-2\">\n <AlertCircle size={16} className=\"text-status-error-text flex-shrink-0\" />\n <p className=\"flex-1 text-sm text-status-error-text\">\n {tr(\n 'calendar.couldNotLoadEvents',\n \"Could not load events. We'll keep trying — you can retry below.\"\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={jobsLoading}\n className=\"rounded px-3 py-1 text-xs text-status-error-text transition-colors hover:bg-status-error-bg/80 disabled:opacity-50\"\n >\n {tr('calendar.retry', 'Retry')}\n </button>\n </div>\n )}\n\n {triggerError && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-lg border border-status-error-border/30 bg-status-error-bg/10 px-3 py-2 text-sm text-status-error-text\"\n >\n <AlertCircle size={16} className=\"mt-0.5 flex-shrink-0\" />\n <p className=\"flex-1 break-words\">{triggerError}</p>\n <button\n type=\"button\"\n onClick={() => setTriggerError(null)}\n className=\"p-0.5 rounded hover:bg-status-error-bg/20 transition-colors\"\n aria-label={tr('common.dismiss', 'Dismiss')}\n >\n <X size={14} />\n </button>\n </div>\n )}\n\n {/* Header */}\n <div className=\"flex items-center gap-3 mb-2\">\n <CalendarClock size={24} className=\"text-text-primary\" />\n <div>\n <h1 className=\"text-xl font-bold text-text-primary\">\n {tr('nav.vibeCalendar', 'Calendar')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {tr('calendar.description', 'Schedule and manage tasks across your agents')}\n </p>\n </div>\n </div>\n\n {/* Empty-state CTA shown when the user has no scheduled tasks yet. */}\n {showEmptyState && (\n <div className=\"flex flex-col sm:flex-row sm:items-center gap-3 rounded-lg border border-border-default bg-bg-surface px-4 py-3\">\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('calendar.empty.title', 'No scheduled tasks yet')}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {tr(\n 'calendar.empty.description',\n 'Schedule your first task to start automating actions across your agents.'\n )}\n </p>\n </div>\n {canCreate && (\n <button\n type=\"button\"\n onClick={() => setShowCreateModal(true)}\n className=\"px-3 py-1.5 text-xs font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('calendar.empty.cta', 'Schedule your first task')}\n </button>\n )}\n </div>\n )}\n\n {/* Calendar Header with nav + view switcher */}\n <div data-tour=\"calendar-header\">\n <VibeCalendarHeader\n currentDate={currentDate}\n viewMode={viewMode}\n onDateChange={setCurrentDate}\n onViewModeChange={handleViewModeChange}\n onCreateTask={canCreate ? () => setShowCreateModal(true) : undefined}\n />\n </div>\n\n {/* Calendar Body */}\n <div data-tour=\"calendar-view-container\" className=\"flex-1 overflow-hidden\">\n {viewMode === 'month' && (\n <VibeCalMonthView\n currentDate={currentDate}\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n {viewMode === 'week' && (\n <VibeCalWeekView\n currentDate={currentDate}\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n {viewMode === 'day' && (\n <VibeCalDayView\n currentDate={currentDate}\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n {viewMode === 'agenda' && (\n <VibeCalAgendaView\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n </div>\n\n {/* Schedule Task Modal */}\n <ScheduleTaskModal\n isOpen={showCreateModal}\n onClose={() => {\n setShowCreateModal(false);\n // Strip the actionId query param on dismiss so reopening the\n // modal without a fresh navigation doesn't silently resume\n // Action-linked mode.\n if (searchParams.has('actionId')) {\n const next = new URLSearchParams(searchParams);\n next.delete('actionId');\n setSearchParams(next, { replace: true });\n }\n }}\n onSubmit={handleCreateTask}\n loading={createLoading}\n prefillActionId={prefillActionId}\n />\n\n {/* Logs Drawer */}\n <LogsDrawer\n isOpen={showLogsDrawer}\n onClose={() => setShowLogsDrawer(false)}\n execution={selectedExecution}\n taskTitle={selectedTask?.title || ''}\n agentId={selectedTask?.agentId || ''}\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"VibeCalendarPage.js","names":[],"sources":["../../src/pages/VibeCalendarPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback, useEffect } from 'react';\nimport { useParams, useSearchParams } from 'react-router-dom';\nimport { CalendarClock, AlertCircle, X } from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { useTenantContext } from '@/hooks/useTenantContext';\nimport {\n VibeCalendarHeader,\n VibeCalMonthView,\n VibeCalWeekView,\n VibeCalDayView,\n VibeCalAgendaView,\n ScheduleTaskModal,\n LogsDrawer,\n} from '../components/vibecalendar';\nimport { useVibeNavigate, useVibeControls } from '@/providers/VibeControlsProvider';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport type {\n CalendarViewMode,\n VibeCalendarTask,\n VibeCalendarExecution,\n ScheduleTaskInput,\n} from '../components/vibecalendar';\nimport {\n expandJobsToOccurrences,\n type SchedulerJobLike,\n} from '../components/vibecalendar/expandOccurrences';\nimport {\n useCreateVibeCalendarTaskMutation,\n useExecuteScheduledJobNowMutation,\n useTriggerVibeCalendarTaskNowMutation,\n} from '@/generated/wspace-operations';\n\n/**\n * The /scheduler page writes ScheduledJobs through the WSPACE gateway —\n * `scheduledJobs` is only published by `wspace-scheduler-svc` and is\n * workspace-scoped (the `$workspaceId: ID!` argument is required). The\n * Calendar renders those jobs alongside its own VibeCalendarTasks. We use\n * `graphqlFetch` rather than Apollo to keep the Calendar's local-state\n * timeline decoupled from the global Apollo cache that powers other\n * VibeControls pages.\n */\nconst WORKSPACE_SCHEDULED_JOBS_QUERY = /* GraphQL */ `\n query GetWorkspaceScheduledJobsForCalendar($workspaceId: ID!) {\n scheduledJobs(workspaceId: $workspaceId, pagination: { first: 500 }) {\n jobs {\n id\n name\n description\n status\n scheduleType\n cronExpression\n rrule\n interval\n scheduledAt\n nextExecutionAt\n lastExecutionAt\n actionId\n timezone\n startDate\n endDate\n }\n total\n }\n }\n`;\n\ninterface WorkspaceScheduledJobsResponse {\n scheduledJobs: {\n jobs: SchedulerJobLike[];\n total: number;\n };\n}\n\n/**\n * Calendar Page\n *\n * Main page for scheduling and managing tasks on VibeControls agents.\n * Supports month and agenda views with task creation, on-demand triggering,\n * and log viewing via the LogsDrawer.\n */\nexport function VibeCalendarPage() {\n const navigate = useVibeNavigate();\n const { workspaceId } = useVibeControls();\n const { t } = useI18n();\n const { viewType } = useParams<{ viewType?: string }>();\n const [searchParams, setSearchParams] = useSearchParams();\n // When navigated here from the Actions page via \"Add to calendar\",\n // the URL carries ?actionId=... — open the modal in Action-linked mode.\n const prefillActionId = searchParams.get('actionId') ?? undefined;\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n // The unified-source migration moved the calendar's only list\n // path onto `scheduledJobs(workspaceId)` (the scheduler subgraph) —\n // there's no parallel `vibeCalendarTasks` query firing here\n // anymore. The page-level access gate is therefore\n // `schedule:read` specifically; admitting calendar-task-only\n // readers would let them past the UI only to hit an empty\n // calendar / scheduler RBAC denial on the actual fetch.\n // calendar-agent-task permissions are still consumed downstream\n // (CREATE for the schedule button, EXECUTE for the per-tile ▶\n // branch) but they don't gate the page mount.\n const canList = hasPermission(VIBE_PERMISSIONS.SCHEDULE_READ);\n // Required by the @include gate on `output`/`errorMessage` in\n // both run-now mutations — without this Apollo rejects the\n // operation before it leaves the browser.\n const canReadExecutionLogs = hasPermission(VIBE_PERMISSIONS.LOG_READ);\n const canCreate = hasPermission(VIBE_PERMISSIONS.CALENDAR_AGENT_TASK_CREATE);\n // The agenda exclusively renders ScheduledJob tiles in the\n // current unified-source path, so the prop-level gate on\n // `onTriggerNow` is `schedule:execute` — admitting\n // `calendar-agent-task:execute`-only users would show ▶ on every\n // tile just to bounce off a friendly denial banner. Both\n // permissions stay tracked because handleTriggerNow's per-source\n // pre-check uses them defensively in case a future mixed-source\n // render lands here.\n const canExecuteCalendarTask = hasPermission(VIBE_PERMISSIONS.CALENDAR_AGENT_TASK_EXECUTE);\n const canExecuteScheduledJob = hasPermission(VIBE_PERMISSIONS.SCHEDULE_EXECUTE);\n const canExecute = canExecuteScheduledJob;\n\n // URL overrides reach Apollo via per-request headers so tile-▶\n // mutations stay in the same tenant the support deep-link list\n // loaded against.\n const tenantContext = useTenantContext();\n\n const [currentDate, setCurrentDate] = useState(new Date());\n const [viewMode, setViewMode] = useState<CalendarViewMode>(\n (viewType as CalendarViewMode) || 'month'\n );\n const [showCreateModal, setShowCreateModal] = useState(false);\n\n // Auto-open the modal when arriving with an actionId query param.\n useEffect(() => {\n if (prefillActionId) {\n setShowCreateModal(true);\n }\n }, [prefillActionId]);\n const [showLogsDrawer, setShowLogsDrawer] = useState(false);\n const [selectedTask, setSelectedTask] = useState<VibeCalendarTask | null>(null);\n const [selectedExecution, setSelectedExecution] = useState<VibeCalendarExecution | null>(null);\n const [triggerError, setTriggerError] = useState<string | null>(null);\n\n // Memoized so downstream useCallback deps stay referentially\n // stable across renders.\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n // Calculate date range for current view\n const dateRange = useMemo(() => {\n const start = new Date(currentDate);\n const end = new Date(currentDate);\n\n switch (viewMode) {\n case 'month':\n start.setDate(1);\n start.setHours(0, 0, 0, 0);\n end.setMonth(end.getMonth() + 1, 0);\n end.setHours(23, 59, 59, 999);\n break;\n case 'week':\n start.setDate(start.getDate() - start.getDay());\n start.setHours(0, 0, 0, 0);\n end.setDate(start.getDate() + 6);\n end.setHours(23, 59, 59, 999);\n break;\n case 'day':\n start.setHours(0, 0, 0, 0);\n end.setHours(23, 59, 59, 999);\n break;\n case 'agenda':\n start.setDate(1);\n start.setHours(0, 0, 0, 0);\n end.setMonth(end.getMonth() + 1, 0);\n end.setHours(23, 59, 59, 999);\n break;\n }\n\n return { startDate: start.toISOString(), endDate: end.toISOString() };\n }, [currentDate, viewMode]);\n\n // Unified calendar view: fetch every ScheduledJob the workspace owns\n // (where /scheduler writes). The calendar renders jobs regardless of how\n // they were created, so all schedule-to-Action tasks show up here as a\n // single source of truth.\n //\n // `scheduledJobs` is published by `wspace-scheduler-svc` and is workspace-\n // scoped — `workspaceId` is required as a query variable. `graphqlFetch`\n // auto-resolves the workspace/org/tenant context from the shell storage\n // and sets the matching headers; URL params (`?org=`, `?tenant=`)\n // override that resolution for the support-tooling deep-link flow.\n const [jobs, setJobs] = useState<SchedulerJobLike[]>([]);\n const [rangeError, setRangeError] = useState<Error | undefined>(undefined);\n const [jobsLoaded, setJobsLoaded] = useState(false);\n const [jobsLoading, setJobsLoading] = useState(false);\n const refetch = useCallback(async () => {\n // Guard: scheduledJobs requires workspaceId — if it isn't resolved yet,\n // skip rather than firing a request that's guaranteed to fail validation.\n // The effect below re-invokes refetch once workspaceId becomes available.\n // (The state-reset on workspace transitions lives in the effect itself,\n // not here, because the effect early-returns when workspaceId is falsy\n // and refetch never gets called in that path.)\n if (!workspaceId) return;\n setJobsLoading(true);\n try {\n // graphqlFetch reads workspace/org/tenant context from storage and sets\n // the proper x-workspace-id / x-org-id / x-tenant-id headers itself —\n // passing `true` opts in to that auto-resolution. URL params override\n // storage for the org/tenant context (used by the support tooling\n // when impersonating a different tenant via deep-link).\n const urlParams = new URLSearchParams(\n typeof window === 'undefined' ? '' : window.location.search\n );\n const orgFromUrl = urlParams.get('org') ?? undefined;\n const tenantFromUrl = urlParams.get('tenant') ?? undefined;\n\n const res = await graphqlFetch<WorkspaceScheduledJobsResponse>({\n gateway: 'workspace',\n query: WORKSPACE_SCHEDULED_JOBS_QUERY,\n operationName: 'GetWorkspaceScheduledJobsForCalendar',\n variables: { workspaceId },\n // Pass the resolved workspaceId STRING (not `true`) so the\n // `x-workspace-id` header matches the GraphQL variable even when\n // ActiveContextStorage is stale / not hydrated yet. Otherwise the\n // gateway can authorize/federate against the previous workspace's\n // context while the resolver receives the new one.\n workspaceId,\n organizationId: orgFromUrl ?? true,\n tenantId: tenantFromUrl ?? true,\n });\n\n if (res.errors && res.errors.length) {\n // Wrap GraphQL/network errors in a generic, UI-safe Error so we\n // never leak raw resolver / federation messages into the banner.\n throw new Error(res.errors[0]?.message ?? 'Unable to load scheduled tasks');\n }\n setJobs(res.data?.scheduledJobs?.jobs ?? []);\n setRangeError(undefined);\n setJobsLoaded(true);\n } catch (err) {\n console.error('[VibeCalendarPage] Failed to load scheduled jobs:', err);\n setRangeError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setJobsLoading(false);\n }\n }, [workspaceId]);\n\n useEffect(() => {\n if (!workspaceId) {\n // Reset on workspace transitions so the empty-state CTA doesn't\n // flash stale data from the previous workspace while the new one\n // is still resolving. Lives here, not in refetch, because refetch\n // is only called from this effect — when workspaceId is falsy,\n // refetch never runs.\n setJobs([]);\n setRangeError(undefined);\n setJobsLoaded(false);\n return;\n }\n void refetch();\n }, [workspaceId, refetch]);\n\n // `sourceById` built in the same pass so handleTriggerNow does\n // an O(1) Map lookup per click — month/agenda views can mint\n // hundreds of occurrences from a single recurring job.\n const { occurrences, sourceById } = useMemo(() => {\n if (!jobs.length) {\n return {\n occurrences: [],\n sourceById: new Map<string, VibeCalendarTask['source']>(),\n };\n }\n const occs = expandJobsToOccurrences(\n jobs,\n new Date(dateRange.startDate),\n new Date(dateRange.endDate)\n );\n const map = new Map<string, VibeCalendarTask['source']>();\n for (const o of occs) {\n // Always populate with an explicit discriminator. Future\n // shapes (e.g. an externalTrigger source) would otherwise\n // collapse into the default branch silently — `undefined`\n // here means \"id no longer in the visible window\", nothing\n // more.\n map.set(o.task.id, o.task.source ?? 'vibeCalendarTask');\n }\n return { occurrences: occs, sourceById: map };\n }, [jobs, dateRange.startDate, dateRange.endDate]);\n\n // Agents are no longer collected in the modal — the scheduler resolves\n // them from the referenced Action at execution time.\n\n // Mutations\n const [createTaskMutation, { loading: createLoading }] = useCreateVibeCalendarTaskMutation();\n const [triggerNowMutation] = useTriggerVibeCalendarTaskNowMutation();\n // Scheduled-job tiles route to the scheduler subgraph rather than\n // the calendar one (different id space, different RBAC resource).\n const [executeScheduledJobNowMutation] = useExecuteScheduledJobNowMutation();\n\n const handleViewModeChange = useCallback((mode: CalendarViewMode) => {\n setViewMode(mode);\n }, []);\n\n // `undefined` return signals \"no longer in the visible window\" —\n // callers should treat that as the safe default `'vibeCalendarTask'`.\n const sourceForTask = useCallback(\n (taskId: string): VibeCalendarTask['source'] => {\n return sourceById.get(taskId);\n },\n [sourceById]\n );\n\n const handleTriggerNow = useCallback(\n async (taskId: string) => {\n // Clear any prior failure banner before re-attempting — same\n // pattern as ScheduledJobDetailPage.handleExecuteNow.\n setTriggerError(null);\n // Branch on the discriminator set by `jobToVibeCalendarTask`.\n // The view passes `onTriggerNow` to every tile when EITHER\n // permission is held (combined `canExecute` gate above), so we\n // pre-check the per-source permission here and surface a\n // friendly inline banner if missing rather than firing the\n // mutation just to bounce off a server-side RBAC denial.\n const source = sourceForTask(taskId);\n if (source === 'scheduledJob' && !canExecuteScheduledJob) {\n setTriggerError(\n tr(\n 'calendar.scheduleExecuteDenied',\n 'You do not have permission to run scheduled jobs (schedule:execute).'\n )\n );\n return;\n }\n if (source !== 'scheduledJob' && !canExecuteCalendarTask) {\n setTriggerError(\n tr(\n 'calendar.calendarTaskExecuteDenied',\n 'You do not have permission to run calendar tasks (calendar-agent-task:execute).'\n )\n );\n return;\n }\n // VibeControlsProvider sets `errorPolicy: 'all'` so RBAC /\n // GraphQL errors resolve into `result.error` (ApolloError)\n // instead of throwing. Inspect both paths consistently —\n // denials would otherwise be silently dropped. Same pattern\n // ActionsPage already uses for quota-exhaust handling.\n const fallbackMsg = tr(\n 'calendar.triggerFailedFallback',\n 'Failed to trigger task. Try again or contact support.'\n );\n try {\n const result =\n source === 'scheduledJob'\n ? await executeScheduledJobNowMutation({\n variables: { id: taskId, canReadLogs: canReadExecutionLogs },\n context: tenantContext,\n })\n : await triggerNowMutation({\n variables: { id: taskId },\n context: tenantContext,\n });\n if (result.error) {\n console.error('Trigger mutation returned an Apollo error:', result.error);\n setTriggerError(result.error.message || fallbackMsg);\n }\n } catch (err) {\n console.error('Failed to trigger task:', err);\n const msg = err instanceof Error && err.message ? err.message : fallbackMsg;\n setTriggerError(msg);\n }\n },\n [\n triggerNowMutation,\n executeScheduledJobNowMutation,\n sourceForTask,\n canReadExecutionLogs,\n canExecuteCalendarTask,\n canExecuteScheduledJob,\n tenantContext,\n tr,\n ]\n );\n\n const handleViewLogs = useCallback((task: VibeCalendarTask) => {\n setSelectedTask(task);\n setSelectedExecution(null);\n setShowLogsDrawer(true);\n }, []);\n\n const handleViewDetail = useCallback(\n (taskId: string) => {\n // A tile that originated from a ScheduledJob has no row in\n // wspace-calendar-svc, so VibeTaskDetailPage would 404 on it.\n // Route to the matching in-product detail page instead.\n //\n // Carry the existing query string so the support deep-link\n // flow (`?org=…&tenant=…`) keeps its tenant context across\n // the navigation — without it the detail query runs against\n // the default/stored tenant and returns a wrong-context view.\n // Encode the id for defense-in-depth in case the scheduler\n // ever emits ids that contain URL-reserved characters.\n const encoded = encodeURIComponent(taskId);\n const search = searchParams.toString();\n const suffix = search ? `?${search}` : '';\n const path =\n sourceForTask(taskId) === 'scheduledJob'\n ? `/calendar/scheduled-job/${encoded}`\n : `/calendar/task/${encoded}`;\n navigate(`${path}${suffix}`);\n },\n [navigate, sourceForTask, searchParams]\n );\n\n const handleCreateTask = useCallback(\n async (input: ScheduleTaskInput) => {\n try {\n // Calendar entries always delegate execution to a VibeControls\n // Action. The scheduler resolves agent/command/webhook details\n // from the Action definition at run time — we only send\n // actionId + calendar/schedule config from this modal.\n await createTaskMutation({\n variables: {\n input: {\n calendarId: input.calendarId || 'default',\n title: input.title,\n description: input.description,\n actionId: input.actionId,\n scheduleType: input.scheduleType,\n scheduledAt: input.scheduledAt,\n cronExpression: input.cronExpression,\n interval: input.interval,\n intervalUnit: input.intervalUnit,\n timezone: input.timezone,\n },\n },\n });\n await refetch();\n setShowCreateModal(false);\n // Drop the actionId query param after successful creation\n if (searchParams.has('actionId')) {\n const next = new URLSearchParams(searchParams);\n next.delete('actionId');\n setSearchParams(next, { replace: true });\n }\n } catch (err) {\n console.error('Failed to create task:', err);\n }\n },\n [createTaskMutation, refetch, searchParams, setSearchParams]\n );\n\n if (permissionsLoading)\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-4\">\n <LoadingState message={tr('calendar.loadingPermissions', 'Loading permissions...')} />\n </MotionPage>\n );\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canList) {\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-4\">\n <AccessDenied\n title={tr('common.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'calendar.accessDeniedDescription',\n \"You don't have permission to view the Calendar.\"\n )}\n />\n </MotionPage>\n );\n }\n\n // Show the empty-state CTA only after the first successful load returned\n // an empty list — never while loading, and never when an error blocks the\n // distinction between \"no data\" and \"couldn't fetch\".\n const showEmptyState = jobsLoaded && !rangeError && jobs.length === 0;\n\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-4\">\n {/* Error banner (non-blocking) — friendly message, never raw GraphQL */}\n {rangeError && jobs.length === 0 && (\n <div className=\"flex items-center gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-2\">\n <AlertCircle size={16} className=\"text-status-error-text flex-shrink-0\" />\n <p className=\"flex-1 text-sm text-status-error-text\">\n {tr(\n 'calendar.couldNotLoadEvents',\n \"Could not load events. We'll keep trying — you can retry below.\"\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={jobsLoading}\n className=\"rounded px-3 py-1 text-xs text-status-error-text transition-colors hover:bg-status-error-bg/80 disabled:opacity-50\"\n >\n {tr('calendar.retry', 'Retry')}\n </button>\n </div>\n )}\n\n {triggerError && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-lg border border-status-error-border/30 bg-status-error-bg/10 px-3 py-2 text-sm text-status-error-text\"\n >\n <AlertCircle size={16} className=\"mt-0.5 flex-shrink-0\" />\n <p className=\"flex-1 break-words\">{triggerError}</p>\n <button\n type=\"button\"\n onClick={() => setTriggerError(null)}\n className=\"p-0.5 rounded hover:bg-status-error-bg/20 transition-colors\"\n aria-label={tr('common.dismiss', 'Dismiss')}\n >\n <X size={14} />\n </button>\n </div>\n )}\n\n {/* Header */}\n <div className=\"flex items-center gap-3 mb-2\">\n <CalendarClock size={24} className=\"text-text-primary\" />\n <div>\n <h1 className=\"text-xl font-bold text-text-primary\">\n {tr('nav.vibeCalendar', 'Calendar')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {tr('calendar.description', 'Schedule and manage tasks across your agents')}\n </p>\n </div>\n </div>\n\n {/* Empty-state CTA shown when the user has no scheduled tasks yet. */}\n {showEmptyState && (\n <div className=\"flex flex-col sm:flex-row sm:items-center gap-3 rounded-lg border border-border-default bg-bg-surface px-4 py-3\">\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('calendar.empty.title', 'No scheduled tasks yet')}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {tr(\n 'calendar.empty.description',\n 'Schedule your first task to start automating actions across your agents.'\n )}\n </p>\n </div>\n {canCreate && (\n <button\n type=\"button\"\n onClick={() => setShowCreateModal(true)}\n className=\"px-3 py-1.5 text-xs font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('calendar.empty.cta', 'Schedule your first task')}\n </button>\n )}\n </div>\n )}\n\n {/* Calendar Header with nav + view switcher */}\n <div data-tour=\"calendar-header\">\n <VibeCalendarHeader\n currentDate={currentDate}\n viewMode={viewMode}\n onDateChange={setCurrentDate}\n onViewModeChange={handleViewModeChange}\n onCreateTask={canCreate ? () => setShowCreateModal(true) : undefined}\n />\n </div>\n\n {/* Calendar Body */}\n <div data-tour=\"calendar-view-container\" className=\"flex-1 overflow-hidden\">\n {viewMode === 'month' && (\n <VibeCalMonthView\n currentDate={currentDate}\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n {viewMode === 'week' && (\n <VibeCalWeekView\n currentDate={currentDate}\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n {viewMode === 'day' && (\n <VibeCalDayView\n currentDate={currentDate}\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n {viewMode === 'agenda' && (\n <VibeCalAgendaView\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n </div>\n\n {/* Schedule Task Modal */}\n <ScheduleTaskModal\n isOpen={showCreateModal}\n onClose={() => {\n setShowCreateModal(false);\n // Strip the actionId query param on dismiss so reopening the\n // modal without a fresh navigation doesn't silently resume\n // Action-linked mode.\n if (searchParams.has('actionId')) {\n const next = new URLSearchParams(searchParams);\n next.delete('actionId');\n setSearchParams(next, { replace: true });\n }\n }}\n onSubmit={handleCreateTask}\n loading={createLoading}\n prefillActionId={prefillActionId}\n />\n\n {/* Logs Drawer */}\n <LogsDrawer\n isOpen={showLogsDrawer}\n onClose={() => setShowLogsDrawer(false)}\n execution={selectedExecution}\n taskTitle={selectedTask?.title || ''}\n agentId={selectedTask?.agentId || ''}\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
@@ -1,24 +1,25 @@
1
1
  import { buildVibeControlsCache as e } from "../lib/apolloCache.js";
2
- import { CombinedGraphQLErrors as t } from "../node_modules/@apollo/client/errors/CombinedGraphQLErrors.js";
3
- import { BatchHttpLink as n } from "../node_modules/@apollo/client/link/batch-http/batchHttpLink.js";
4
- import r from "./AgentEventsProvider.js";
5
- import { QUOTA_EXHAUSTED_EVENT as i, inferQuotaKindFromOperation as a } from "../utils/quotaUtils.js";
6
- import { QuotaExhaustedProvider as o } from "./QuotaExhaustedProvider.js";
7
- import s from "../hooks/useInitialDataLoader.js";
8
- import { createContext as c, use as l, useCallback as u, useEffect as d, useMemo as f, useRef as p, useState as m } from "react";
2
+ import { getMainDefinition as t } from "../node_modules/@apollo/client/utilities/internal/getMainDefinition.js";
3
+ import { CombinedGraphQLErrors as n } from "../node_modules/@apollo/client/errors/CombinedGraphQLErrors.js";
4
+ import { BatchHttpLink as r } from "../node_modules/@apollo/client/link/batch-http/batchHttpLink.js";
5
+ import i from "./AgentEventsProvider.js";
6
+ import { QUOTA_EXHAUSTED_EVENT as a, inferQuotaKindFromOperation as o } from "../utils/quotaUtils.js";
7
+ import { QuotaExhaustedProvider as s } from "./QuotaExhaustedProvider.js";
8
+ import c from "../hooks/useInitialDataLoader.js";
9
+ import { createContext as l, use as u, useCallback as d, useEffect as f, useMemo as ee, useRef as p, useState as m } from "react";
9
10
  import { MemoryRouter as h, useNavigate as g } from "react-router-dom";
10
- import { ApolloClient as ee, from as _ } from "@apollo/client";
11
- import { onError as v } from "@apollo/client/link/error";
12
- import { setContext as y } from "@apollo/client/link/context";
13
- import { ApolloProvider as b } from "@apollo/client/react";
14
- import { MutationCache as x, QueryClient as te, QueryClientProvider as S } from "@tanstack/react-query";
15
- import { getStoredAuthToken as ne, getStoredWorkspaceToken as C, isWorkspaceTokenValidForContext as w } from "@burdenoff/fe-libs/shared/graphql";
16
- import { getStoredWorkspaceId as T } from "@burdenoff/fe-libs/shared/graphql/fetch";
17
- import { PermissionProvider as re } from "@burdenoff/fe-libs/shared/providers/shell";
18
- import { isAuthNetworkError as ie } from "@burdenoff/fe-libs/shared/utils";
19
- import { Fragment as E, jsx as D } from "react/jsx-runtime";
11
+ import { ApolloClient as te, ApolloLink as _, Observable as v, from as y } from "@apollo/client";
12
+ import { onError as b } from "@apollo/client/link/error";
13
+ import { setContext as x } from "@apollo/client/link/context";
14
+ import { ApolloProvider as S } from "@apollo/client/react";
15
+ import { MutationCache as C, QueryClient as w, QueryClientProvider as T } from "@tanstack/react-query";
16
+ import { getStoredAuthToken as E, getStoredWorkspaceToken as ne, isWorkspaceTokenValidForContext as re } from "@burdenoff/fe-libs/shared/graphql";
17
+ import { getStoredWorkspaceId as ie } from "@burdenoff/fe-libs/shared/graphql/fetch";
18
+ import { PermissionProvider as ae } from "@burdenoff/fe-libs/shared/providers/shell";
19
+ import { isAuthNetworkError as oe } from "@burdenoff/fe-libs/shared/utils";
20
+ import { Fragment as D, jsx as O } from "react/jsx-runtime";
20
21
  //#region src/providers/VibeControlsProvider.tsx
21
- function ae() {
22
+ function se() {
22
23
  try {
23
24
  let e = sessionStorage.getItem("burdenoff-active-context-project");
24
25
  if (e) return e;
@@ -30,25 +31,54 @@ function ae() {
30
31
  return null;
31
32
  }
32
33
  }
33
- function oe(e = []) {
34
+ function k(e = []) {
34
35
  return e.some((e) => {
35
36
  let t = typeof e.extensions?.code == "string" ? e.extensions.code : "", n = typeof e.extensions?.errorCode == "string" ? e.extensions.errorCode : "", r = (e.message ?? "").toLowerCase();
36
37
  return t === "UNAUTHENTICATED" || n === "TOKEN_EXPIRED" || r.includes("token_expired") || r.includes("token expired") || r.includes("session expired") || r.includes("unauthorized") || r.includes("unauthenticated") || r.includes("authentication required") || r.includes("workspace context missing");
37
38
  });
38
39
  }
39
- function O(e = []) {
40
+ function A(e) {
41
+ let n = t(e);
42
+ return n.kind === "OperationDefinition" && n.operation === "mutation";
43
+ }
44
+ function j(e) {
45
+ let t = e.errors;
46
+ return Array.isArray(t) && t.length > 0;
47
+ }
48
+ function M(e) {
49
+ return new _((t, n) => {
50
+ let r = A(t.query) && t.getContext().autoRefreshAfterMutation !== !1 && t.getContext().skipMutationAutoRefresh !== !0;
51
+ return new v((i) => {
52
+ let a = n(t).subscribe({
53
+ next: (t) => {
54
+ i.next(t), !(!r || j(t)) && e()?.refetchQueries({ include: "active" });
55
+ },
56
+ error: (e) => {
57
+ i.error(e);
58
+ },
59
+ complete: () => {
60
+ i.complete();
61
+ }
62
+ });
63
+ return () => {
64
+ a.unsubscribe();
65
+ };
66
+ });
67
+ });
68
+ }
69
+ function N(e = []) {
40
70
  return e.find((e) => {
41
71
  let t = typeof e.extensions?.code == "string" ? e.extensions.code : "", n = typeof e.extensions?.errorCode == "string" ? e.extensions.errorCode : "", r = (e.message ?? "").toLowerCase();
42
72
  return t === "QUOTA_EXHAUSTED" || n === "QUOTA_EXHAUSTED" || r.includes("quota_exhausted") || r.includes("quota exhausted");
43
73
  });
44
74
  }
45
- var k = ({ workspaceId: e, children: t }) => (s({ workspaceId: e }), /* @__PURE__ */ D(E, { children: t })), A = c(null), j = () => {
46
- let e = l(A);
75
+ var P = ({ workspaceId: e, children: t }) => (c({ workspaceId: e }), /* @__PURE__ */ O(D, { children: t })), F = l(null), I = () => {
76
+ let e = u(F);
47
77
  if (!e) throw Error("useVibeControls must be used within VibeControlsProvider");
48
78
  return e;
49
- }, M = () => {
50
- let { navigate: e, basePath: t } = j(), n = g();
51
- return u((r) => {
79
+ }, L = () => {
80
+ let { navigate: e, basePath: t } = I(), n = g();
81
+ return d((r) => {
52
82
  let i = t && t !== "/" ? `${t}${r}` : r;
53
83
  e ? e(i) : n(i);
54
84
  }, [
@@ -56,10 +86,10 @@ var k = ({ workspaceId: e, children: t }) => (s({ workspaceId: e }), /* @__PURE_
56
86
  t,
57
87
  n
58
88
  ]);
59
- }, N = ({ children: s, basePath: c, navigate: l, currentUser: u, workspaceId: g, organizationId: E, projectId: j, tenantId: M, apiGatewayUrl: N, authToken: P, workspaceToken: F, onAgentSelect: I, onSessionStart: L, onVibeActivate: R, defaultView: z, registerNavItems: B, registerFooterItems: V, onAuthError: H }) => {
89
+ }, R = ({ children: t, basePath: c, navigate: l, currentUser: u, workspaceId: d, organizationId: g, projectId: _, tenantId: v, apiGatewayUrl: D, authToken: A, workspaceToken: j, onAgentSelect: I, onSessionStart: L, onVibeActivate: R, defaultView: z, registerNavItems: B, registerFooterItems: V, onAuthError: H }) => {
60
90
  let [U] = m(() => {
61
- let e = new te({
62
- mutationCache: new x({ onSuccess: () => {
91
+ let e = new w({
92
+ mutationCache: new C({ onSuccess: () => {
63
93
  e.invalidateQueries();
64
94
  } }),
65
95
  defaultOptions: { queries: {
@@ -68,84 +98,88 @@ var k = ({ workspaceId: e, children: t }) => (s({ workspaceId: e }), /* @__PURE_
68
98
  } }
69
99
  });
70
100
  return e;
71
- }), W = p(P), G = p(F), K = p(g), q = p(E), J = p(M), Y = p(j), X = p(H);
72
- d(() => {
73
- W.current = P;
74
- }, [P]), d(() => {
75
- G.current = F;
76
- }, [F]), d(() => {
77
- K.current = g;
78
- }, [g]), d(() => {
79
- q.current = E;
80
- }, [E]), d(() => {
81
- J.current = M;
82
- }, [M]), d(() => {
83
- Y.current = j;
84
- }, [j]), d(() => {
101
+ }), W = p(A), G = p(j), K = p(d), q = p(g), J = p(v), Y = p(_), X = p(H);
102
+ f(() => {
103
+ W.current = A;
104
+ }, [A]), f(() => {
105
+ G.current = j;
106
+ }, [j]), f(() => {
107
+ K.current = d;
108
+ }, [d]), f(() => {
109
+ q.current = g;
110
+ }, [g]), f(() => {
111
+ J.current = v;
112
+ }, [v]), f(() => {
113
+ Y.current = _;
114
+ }, [_]), f(() => {
85
115
  X.current = H;
86
116
  }, [H]);
87
- let se = f(() => new ee({
88
- link: _([
89
- v(({ error: e, operation: n }) => {
90
- if (t.is(e)) {
91
- if (oe(e.errors)) {
92
- console.warn("[VibeControls] Auth error detected — notifying host shell"), X.current?.();
93
- return;
94
- }
95
- let t = O(e.errors);
96
- if (t) {
97
- let e = {
98
- kind: a(n?.operationName),
99
- message: t.message,
100
- operationName: n?.operationName ?? null
101
- };
102
- window.dispatchEvent(new CustomEvent(i, { detail: e }));
103
- return;
104
- }
117
+ let ce = ee(() => {
118
+ let t = b(({ error: e, operation: t }) => {
119
+ if (n.is(e)) {
120
+ if (k(e.errors)) {
121
+ console.warn("[VibeControls] Auth error detected — notifying host shell"), X.current?.();
122
+ return;
105
123
  }
106
- e instanceof Error && ie(e) && (console.warn("[VibeControls] Auth network error — notifying host shell"), X.current?.());
107
- }),
108
- y((e, { headers: t }) => {
109
- let n = W.current ?? ne(), r = K.current || T(), i = Y.current || ae(), a = G.current ?? C(), o = w(a, {
110
- workspaceId: r,
111
- organizationId: q.current,
112
- tenantId: J.current,
113
- projectId: i
114
- }) ? a : null;
115
- return { headers: {
116
- ...t,
117
- ...n ? { Authorization: `Bearer ${n}` } : {},
118
- ...o ? { "X-Workspace-Authorization": `Bearer ${o}` } : {},
119
- ...r ? { "x-workspace-id": r } : {},
120
- ...i ? { "x-project-id": i } : {}
121
- } };
122
- }),
123
- new n({
124
- uri: N ? `${N}/workspaces/graphql` : "/workspaces/graphql",
125
- batchMax: 10,
126
- batchInterval: 10
127
- })
128
- ]),
129
- cache: e(),
130
- defaultOptions: {
131
- watchQuery: {
132
- fetchPolicy: "cache-first",
133
- errorPolicy: "all"
134
- },
135
- query: { errorPolicy: "all" },
136
- mutate: { errorPolicy: "all" }
137
- }
138
- }), [N]), Z = {
124
+ let n = N(e.errors);
125
+ if (n) {
126
+ let e = {
127
+ kind: o(t?.operationName),
128
+ message: n.message,
129
+ operationName: t?.operationName ?? null
130
+ };
131
+ window.dispatchEvent(new CustomEvent(a, { detail: e }));
132
+ return;
133
+ }
134
+ }
135
+ e instanceof Error && oe(e) && (console.warn("[VibeControls] Auth network error — notifying host shell"), X.current?.());
136
+ }), i = x((e, { headers: t }) => {
137
+ let n = W.current ?? E(), r = K.current || ie(), i = Y.current || se(), a = G.current ?? ne(), o = re(a, {
138
+ workspaceId: r,
139
+ organizationId: q.current,
140
+ tenantId: J.current,
141
+ projectId: i
142
+ }) ? a : null;
143
+ return { headers: {
144
+ ...t,
145
+ ...n ? { Authorization: `Bearer ${n}` } : {},
146
+ ...o ? { "X-Workspace-Authorization": `Bearer ${o}` } : {},
147
+ ...r ? { "x-workspace-id": r } : {},
148
+ ...i ? { "x-project-id": i } : {}
149
+ } };
150
+ }), s = new r({
151
+ uri: D ? `${D}/workspaces/graphql` : "/workspaces/graphql",
152
+ batchMax: 10,
153
+ batchInterval: 10
154
+ }), c = null;
155
+ return c = new te({
156
+ link: y([
157
+ t,
158
+ M(() => c),
159
+ i,
160
+ s
161
+ ]),
162
+ cache: e(),
163
+ defaultOptions: {
164
+ watchQuery: {
165
+ fetchPolicy: "cache-first",
166
+ errorPolicy: "all"
167
+ },
168
+ query: { errorPolicy: "all" },
169
+ mutate: { errorPolicy: "all" }
170
+ }
171
+ }), c;
172
+ }, [D]), Z = {
139
173
  basePath: c,
140
174
  navigate: l,
141
175
  currentUser: u,
142
- workspaceId: g,
143
- organizationId: E,
144
- projectId: j,
145
- tenantId: M,
146
- apiGatewayUrl: N,
147
- authToken: P,
148
- workspaceToken: F,
176
+ workspaceId: d,
177
+ organizationId: g,
178
+ projectId: _,
179
+ tenantId: v,
180
+ apiGatewayUrl: D,
181
+ authToken: A,
182
+ workspaceToken: j,
149
183
  onAgentSelect: I,
150
184
  onSessionStart: L,
151
185
  onVibeActivate: R,
@@ -153,34 +187,34 @@ var k = ({ workspaceId: e, children: t }) => (s({ workspaceId: e }), /* @__PURE_
153
187
  registerNavItems: B,
154
188
  registerFooterItems: V,
155
189
  onAuthError: H
156
- }, Q = /* @__PURE__ */ D(o, { children: typeof window < "u" && /^\/vibecontrols\/(share|deck)\//.test(window.location.pathname) ? s : /* @__PURE__ */ D(k, {
157
- workspaceId: g,
158
- children: /* @__PURE__ */ D(re, {
159
- scopeId: g ?? null,
190
+ }, Q = /* @__PURE__ */ O(s, { children: typeof window < "u" && /^\/vibecontrols\/(share|deck)\//.test(window.location.pathname) ? t : /* @__PURE__ */ O(P, {
191
+ workspaceId: d,
192
+ children: /* @__PURE__ */ O(ae, {
193
+ scopeId: d ?? null,
160
194
  gateway: "workspace",
161
- children: /* @__PURE__ */ D(r, {
195
+ children: /* @__PURE__ */ O(i, {
162
196
  toastPosition: "bottom-right",
163
197
  autoDismissDelay: 5e3,
164
198
  maxVisibleToasts: 3,
165
- children: s
199
+ children: t
166
200
  })
167
201
  })
168
- }) }), $ = /* @__PURE__ */ D(S, {
202
+ }) }), $ = /* @__PURE__ */ O(T, {
169
203
  client: U,
170
- children: c ? /* @__PURE__ */ D(A.Provider, {
204
+ children: c ? /* @__PURE__ */ O(F.Provider, {
171
205
  value: Z,
172
206
  children: Q
173
- }) : /* @__PURE__ */ D(b, {
174
- client: se,
175
- children: /* @__PURE__ */ D(A.Provider, {
207
+ }) : /* @__PURE__ */ O(S, {
208
+ client: ce,
209
+ children: /* @__PURE__ */ O(F.Provider, {
176
210
  value: Z,
177
211
  children: Q
178
212
  })
179
213
  })
180
214
  });
181
- return c ? $ : /* @__PURE__ */ D(h, { children: $ });
215
+ return c ? $ : /* @__PURE__ */ O(h, { children: $ });
182
216
  };
183
217
  //#endregion
184
- export { N as VibeControlsProvider, j as useVibeControls, M as useVibeNavigate };
218
+ export { R as VibeControlsProvider, I as useVibeControls, L as useVibeNavigate };
185
219
 
186
220
  //# sourceMappingURL=VibeControlsProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"VibeControlsProvider.js","names":[],"sources":["../../src/providers/VibeControlsProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, use, useMemo, useCallback, useRef, useEffect, useState } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router-dom';\nimport { ApolloClient, from } from '@apollo/client';\nimport { buildVibeControlsCache } from '@/lib/apolloCache';\nimport { BatchHttpLink } from '@apollo/client/link/batch-http';\nimport { onError } from '@apollo/client/link/error';\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { setContext } from '@apollo/client/link/context';\nimport { ApolloProvider } from '@apollo/client/react';\nimport { MutationCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport {\n getStoredAuthToken,\n getStoredWorkspaceToken,\n isWorkspaceTokenValidForContext,\n} from '@burdenoff/fe-libs/shared/graphql';\nimport { getStoredWorkspaceId } from '@burdenoff/fe-libs/shared/graphql/fetch';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { isAuthNetworkError } from '@burdenoff/fe-libs/shared/utils';\nimport type { VibeControlsRootProps } from '../types';\nimport { AgentEventsProvider } from './AgentEventsProvider';\nimport { QuotaExhaustedProvider } from './QuotaExhaustedProvider';\nimport { useInitialDataLoader } from '../hooks/useInitialDataLoader';\nimport {\n QUOTA_EXHAUSTED_EVENT,\n inferQuotaKindFromOperation,\n type QuotaExhaustedEventDetail,\n} from '../utils/quotaUtils';\n\ntype GraphQLErrorLike = { message?: string; extensions?: Record<string, unknown> };\n\nfunction getStoredProjectId(): string | null {\n try {\n const fromSession = sessionStorage.getItem('burdenoff-active-context-project');\n if (fromSession) return fromSession;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n const profileContext = activeProfileId\n ? localStorage.getItem(`bf-p-${activeProfileId}-context`)\n : null;\n if (!profileContext) return null;\n\n const parsed = JSON.parse(profileContext) as { projectId?: unknown };\n return typeof parsed.projectId === 'string' && parsed.projectId.length > 0\n ? parsed.projectId\n : null;\n } catch {\n return null;\n }\n}\n\nfunction hasSessionAuthErrors(errors: ReadonlyArray<GraphQLErrorLike> = []): boolean {\n return errors.some((error) => {\n const code = typeof error.extensions?.code === 'string' ? error.extensions.code : '';\n const errorCode =\n typeof error.extensions?.errorCode === 'string' ? error.extensions.errorCode : '';\n const message = (error.message ?? '').toLowerCase();\n\n return (\n code === 'UNAUTHENTICATED' ||\n errorCode === 'TOKEN_EXPIRED' ||\n message.includes('token_expired') ||\n message.includes('token expired') ||\n message.includes('session expired') ||\n message.includes('unauthorized') ||\n message.includes('unauthenticated') ||\n message.includes('authentication required') ||\n message.includes('workspace context missing')\n );\n });\n}\n\nfunction findQuotaExhaustedError(\n errors: ReadonlyArray<GraphQLErrorLike> = []\n): GraphQLErrorLike | undefined {\n return errors.find((error) => {\n const code = typeof error.extensions?.code === 'string' ? error.extensions.code : '';\n const errorCode =\n typeof error.extensions?.errorCode === 'string' ? error.extensions.errorCode : '';\n // Match both the constant form and the human-readable phrase, case\n // insensitively — mirrors what `isQuotaExhaustedError` accepts at the\n // per-page level so the link doesn't miss any quota error the rest\n // of the code base already recognises.\n const message = (error.message ?? '').toLowerCase();\n return (\n code === 'QUOTA_EXHAUSTED' ||\n errorCode === 'QUOTA_EXHAUSTED' ||\n message.includes('quota_exhausted') ||\n message.includes('quota exhausted')\n );\n });\n}\n\n/**\n * Runs the initial data query inside the ApolloProvider tree so the result\n * lands in the MFE's own Apollo cache (not the shell's).\n */\nconst InitialDataPrefetcher: FC<PropsWithChildren<{ workspaceId?: string | null }>> = ({\n workspaceId,\n children,\n}) => {\n useInitialDataLoader({ workspaceId });\n return <>{children}</>;\n};\n\n/**\n * Context value for VibeControls microfrontend\n */\ntype VibeControlsContextValue = VibeControlsRootProps;\n\nconst VibeControlsContext = createContext<VibeControlsContextValue | null>(null);\n\n/**\n * Hook to access VibeControls context\n */\nexport const useVibeControls = () => {\n const context = use(VibeControlsContext);\n if (!context) {\n throw new Error('useVibeControls must be used within VibeControlsProvider');\n }\n return context;\n};\n\n/**\n * Hook for navigation within the VibeControls microfrontend.\n * Uses the shell's navigate function if available, falling back to\n * React Router's useNavigate. Automatically prepends basePath.\n *\n * Usage:\n * const navigate = useVibeNavigate();\n * navigate('/agents/123'); // navigates to /vibecontrols/agents/123\n */\nexport const useVibeNavigate = () => {\n const { navigate: shellNavigate, basePath } = useVibeControls();\n const routerNavigate = useRouterNavigate();\n\n return useCallback(\n (path: string) => {\n const fullPath = basePath && basePath !== '/' ? `${basePath}${path}` : path;\n if (shellNavigate) {\n shellNavigate(fullPath);\n } else {\n routerNavigate(fullPath);\n }\n },\n [shellNavigate, basePath, routerNavigate]\n );\n};\n\n/**\n * Provider component for VibeControls microfrontend\n * Sets up routing, Apollo Client with auth error detection, and context.\n *\n * The Apollo Client is configured with:\n * - An onError link that detects UNAUTHENTICATED / FORBIDDEN responses and\n * invokes the `onAuthError` prop so the host shell can trigger logout.\n * - A dynamic auth link that reads the token per-request (not baked in at\n * client creation time), so token refreshes take effect immediately.\n */\nexport const VibeControlsProvider: FC<PropsWithChildren<VibeControlsRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n projectId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n onAgentSelect,\n onSessionStart,\n onVibeActivate,\n defaultView,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n}) => {\n // Per-instance QueryClient so multiple MFE mounts don't share cache\n const [queryClient] = useState(\n () => {\n const client = new QueryClient({\n mutationCache: new MutationCache({\n onSuccess: () => {\n void client.invalidateQueries();\n },\n }),\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000, // 5 minutes\n retry: 2,\n },\n },\n });\n return client;\n }\n );\n\n // Store tokens and callbacks in refs so the Apollo Client (and its cache)\n // survives token refreshes. The setContext auth link reads from refs\n // per-request, so refreshed tokens take effect immediately without\n // destroying the InMemoryCache.\n const authTokenRef = useRef(authToken);\n const workspaceTokenRef = useRef(workspaceToken);\n const workspaceIdRef = useRef(workspaceId);\n const organizationIdRef = useRef(organizationId);\n const tenantIdRef = useRef(tenantId);\n const projectIdRef = useRef(projectId);\n const onAuthErrorRef = useRef(onAuthError);\n useEffect(() => {\n authTokenRef.current = authToken;\n }, [authToken]);\n useEffect(() => {\n workspaceTokenRef.current = workspaceToken;\n }, [workspaceToken]);\n useEffect(() => {\n workspaceIdRef.current = workspaceId;\n }, [workspaceId]);\n useEffect(() => {\n organizationIdRef.current = organizationId;\n }, [organizationId]);\n useEffect(() => {\n tenantIdRef.current = tenantId;\n }, [tenantId]);\n useEffect(() => {\n projectIdRef.current = projectId;\n }, [projectId]);\n useEffect(() => {\n onAuthErrorRef.current = onAuthError;\n }, [onAuthError]);\n\n // Build Apollo Client with error link + dynamic auth link.\n // Only recreated when apiGatewayUrl changes (different endpoint = different client).\n //\n // SCOPE NOTE (codex #128 review):\n // This client — and the `errorLink` below — is ONLY used when the microfe\n // mounts in standalone mode (no `basePath`, e.g. `bun run dev` against\n // this package directly). When integrated into an app shell (the\n // production path) `basePath` is set, the local ApolloProvider is\n // skipped further down, and mutations go through the shell's\n // `MultiGatewayProvider` Apollo client. In that integrated mode the\n // global error-dispatch path here does NOT fire — quota-handling\n // relies on the per-page `isQuotaExhaustedError(result.error)`\n // checks (e.g. TargetsPage.handleCreate) which still render the\n // local QuotaExhaustedDialog. Tracked as a follow-up: lift this\n // link into fe-libs `MultiGatewayProvider` so the global event\n // dispatches in shell mode too.\n const apolloClient = useMemo(() => {\n // Error link (Apollo v4 pattern): detect authentication failures + quota.\n //\n // Quota: we use `errorPolicy: 'all'` for mutations, so GraphQL errors land\n // on `result.error` instead of rejecting the mutation promise. Most\n // callsites only have a `catch` block, so quota errors silently disappear\n // (BOFF-2625). Dispatching a CustomEvent here lets a single provider\n // (QuotaExhaustedProvider) render the dialog for every mutation without\n // touching individual handlers. Per-page handlers that already react to\n // `isQuotaExhaustedError(result.error)` still work — both paths just open\n // the same modal state.\n const errorLink = onError(({ error, operation }) => {\n if (CombinedGraphQLErrors.is(error)) {\n // Auth first: a response can carry BOTH an UNAUTHENTICATED and a\n // QUOTA_EXHAUSTED error (token expired mid-request that also\n // triggered a quota check). If we returned after dispatching the\n // quota event, the host shell would never get the auth signal and\n // the session would stay broken behind the modal. The quota\n // dialog is moot for an expired session anyway.\n if (hasSessionAuthErrors(error.errors)) {\n console.warn('[VibeControls] Auth error detected — notifying host shell');\n onAuthErrorRef.current?.();\n return;\n }\n\n const quotaError = findQuotaExhaustedError(error.errors);\n if (quotaError) {\n const detail: QuotaExhaustedEventDetail = {\n kind: inferQuotaKindFromOperation(operation?.operationName),\n message: quotaError.message,\n operationName: operation?.operationName ?? null,\n };\n window.dispatchEvent(\n new CustomEvent<QuotaExhaustedEventDetail>(QUOTA_EXHAUSTED_EVENT, { detail })\n );\n return;\n }\n }\n\n if (error instanceof Error && isAuthNetworkError(error)) {\n console.warn('[VibeControls] Auth network error — notifying host shell');\n onAuthErrorRef.current?.();\n }\n });\n\n // Auth link: dynamically attach dual-token auth and workspace context per-request.\n // Reads from refs so token refreshes don't recreate the client.\n const authLink = setContext((_, { headers }) => {\n const resolvedAuthToken = authTokenRef.current ?? getStoredAuthToken();\n // Fall back to storage when workspaceId prop is empty (e.g. during initial render\n // before ActiveContextProvider has propagated the value through workspaceBaseProps)\n const resolvedWorkspaceId = workspaceIdRef.current || getStoredWorkspaceId();\n const resolvedProjectId = projectIdRef.current || getStoredProjectId();\n const rawWorkspaceToken = workspaceTokenRef.current ?? getStoredWorkspaceToken();\n const resolvedWorkspaceToken = isWorkspaceTokenValidForContext(rawWorkspaceToken, {\n workspaceId: resolvedWorkspaceId,\n organizationId: organizationIdRef.current,\n tenantId: tenantIdRef.current,\n projectId: resolvedProjectId,\n })\n ? rawWorkspaceToken\n : null;\n\n return {\n headers: {\n ...headers,\n ...(resolvedAuthToken ? { Authorization: `Bearer ${resolvedAuthToken}` } : {}),\n ...(resolvedWorkspaceToken\n ? { 'X-Workspace-Authorization': `Bearer ${resolvedWorkspaceToken}` }\n : {}),\n ...(resolvedWorkspaceId ? { 'x-workspace-id': resolvedWorkspaceId } : {}),\n ...(resolvedProjectId ? { 'x-project-id': resolvedProjectId } : {}),\n },\n };\n });\n\n const httpLink = new BatchHttpLink({\n uri: apiGatewayUrl ? `${apiGatewayUrl}/workspaces/graphql` : '/workspaces/graphql',\n batchMax: 10,\n batchInterval: 10, // ms — tight window so latency isn't hurt\n });\n\n return new ApolloClient({\n link: from([errorLink, authLink, httpLink]),\n cache: buildVibeControlsCache(),\n defaultOptions: {\n watchQuery: { fetchPolicy: 'cache-first', errorPolicy: 'all' },\n query: { errorPolicy: 'all' },\n mutate: { errorPolicy: 'all' },\n },\n });\n }, [apiGatewayUrl]);\n\n const contextValue: VibeControlsContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n projectId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n onAgentSelect,\n onSessionStart,\n onVibeActivate,\n defaultView,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n };\n\n const isPublicLinkRoute =\n typeof window !== 'undefined' &&\n /^\\/vibecontrols\\/(share|deck)\\//.test(window.location.pathname);\n\n // Core providers that wrap the children\n // PermissionProvider requires AuthProvider which is provided by the shell.\n // It fetches permissions from wspace-rbac-svc via the workspace gateway.\n // AgentEventsProvider manages WebSocket connections to agents for real-time notifications.\n //\n // QuotaExhaustedProvider wraps both public and authenticated trees because\n // public deck routes (`/vibecontrols/deck/:token`) still run mutations like\n // `ExecuteSharedVibeDeckButton` that can hit command-execution quota\n // (#128 review codex P2). The provider is cheap and route-agnostic — it\n // only listens for a window event.\n const authedTree = (\n <InitialDataPrefetcher workspaceId={workspaceId}>\n <PermissionProvider scopeId={workspaceId ?? null} gateway=\"workspace\">\n <AgentEventsProvider\n toastPosition=\"bottom-right\"\n autoDismissDelay={5000}\n maxVisibleToasts={3}\n >\n {children}\n </AgentEventsProvider>\n </PermissionProvider>\n </InitialDataPrefetcher>\n );\n\n const routedChildren = (\n <QuotaExhaustedProvider>{isPublicLinkRoute ? children : authedTree}</QuotaExhaustedProvider>\n );\n\n const coreProviders = (\n <QueryClientProvider client={queryClient}>\n {basePath ? (\n <VibeControlsContext.Provider value={contextValue}>\n {routedChildren}\n </VibeControlsContext.Provider>\n ) : (\n <ApolloProvider client={apolloClient}>\n <VibeControlsContext.Provider value={contextValue}>\n {routedChildren}\n </VibeControlsContext.Provider>\n </ApolloProvider>\n )}\n </QueryClientProvider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (basePath) {\n // Integrated mode: shell already has BrowserRouter, don't create nested router\n return coreProviders;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{coreProviders}</MemoryRouter>;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"VibeControlsProvider.js","names":[],"sources":["../../src/providers/VibeControlsProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, use, useMemo, useCallback, useRef, useEffect, useState } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router-dom';\nimport { ApolloClient, ApolloLink, type FetchResult, Observable, from } from '@apollo/client';\nimport { buildVibeControlsCache } from '@/lib/apolloCache';\nimport { BatchHttpLink } from '@apollo/client/link/batch-http';\nimport { onError } from '@apollo/client/link/error';\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { setContext } from '@apollo/client/link/context';\nimport { ApolloProvider } from '@apollo/client/react';\nimport { getMainDefinition } from '@apollo/client/utilities';\nimport { MutationCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport {\n getStoredAuthToken,\n getStoredWorkspaceToken,\n isWorkspaceTokenValidForContext,\n} from '@burdenoff/fe-libs/shared/graphql';\nimport { getStoredWorkspaceId } from '@burdenoff/fe-libs/shared/graphql/fetch';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { isAuthNetworkError } from '@burdenoff/fe-libs/shared/utils';\nimport type { VibeControlsRootProps } from '../types';\nimport { AgentEventsProvider } from './AgentEventsProvider';\nimport { QuotaExhaustedProvider } from './QuotaExhaustedProvider';\nimport { useInitialDataLoader } from '../hooks/useInitialDataLoader';\nimport {\n QUOTA_EXHAUSTED_EVENT,\n inferQuotaKindFromOperation,\n type QuotaExhaustedEventDetail,\n} from '../utils/quotaUtils';\n\ntype GraphQLErrorLike = { message?: string; extensions?: Record<string, unknown> };\n\nfunction getStoredProjectId(): string | null {\n try {\n const fromSession = sessionStorage.getItem('burdenoff-active-context-project');\n if (fromSession) return fromSession;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n const profileContext = activeProfileId\n ? localStorage.getItem(`bf-p-${activeProfileId}-context`)\n : null;\n if (!profileContext) return null;\n\n const parsed = JSON.parse(profileContext) as { projectId?: unknown };\n return typeof parsed.projectId === 'string' && parsed.projectId.length > 0\n ? parsed.projectId\n : null;\n } catch {\n return null;\n }\n}\n\nfunction hasSessionAuthErrors(errors: ReadonlyArray<GraphQLErrorLike> = []): boolean {\n return errors.some((error) => {\n const code = typeof error.extensions?.code === 'string' ? error.extensions.code : '';\n const errorCode =\n typeof error.extensions?.errorCode === 'string' ? error.extensions.errorCode : '';\n const message = (error.message ?? '').toLowerCase();\n\n return (\n code === 'UNAUTHENTICATED' ||\n errorCode === 'TOKEN_EXPIRED' ||\n message.includes('token_expired') ||\n message.includes('token expired') ||\n message.includes('session expired') ||\n message.includes('unauthorized') ||\n message.includes('unauthenticated') ||\n message.includes('authentication required') ||\n message.includes('workspace context missing')\n );\n });\n}\n\nfunction isMutationOperation(query: Parameters<typeof getMainDefinition>[0]): boolean {\n const definition = getMainDefinition(query);\n return definition.kind === 'OperationDefinition' && definition.operation === 'mutation';\n}\n\nfunction hasGraphQLResultErrors(result: FetchResult<Record<string, unknown>>): boolean {\n const errors = result.errors;\n return Array.isArray(errors) && errors.length > 0;\n}\n\nfunction createMutationAutoRefreshLink(\n getClient: () => InstanceType<typeof ApolloClient> | null\n): ApolloLink {\n return new ApolloLink((operation, forward) => {\n const shouldRefreshAfterMutation =\n isMutationOperation(operation.query) &&\n operation.getContext().autoRefreshAfterMutation !== false &&\n operation.getContext().skipMutationAutoRefresh !== true;\n\n return new Observable<FetchResult<Record<string, unknown>>>((observer) => {\n const subscription = forward(operation).subscribe({\n next: (result) => {\n observer.next(result as FetchResult<Record<string, unknown>>);\n if (!shouldRefreshAfterMutation || hasGraphQLResultErrors(result)) return;\n\n void getClient()?.refetchQueries({ include: 'active' });\n },\n error: (error: unknown) => {\n observer.error(error);\n },\n complete: () => {\n observer.complete();\n },\n });\n\n return () => {\n subscription.unsubscribe();\n };\n });\n });\n}\n\nfunction findQuotaExhaustedError(\n errors: ReadonlyArray<GraphQLErrorLike> = []\n): GraphQLErrorLike | undefined {\n return errors.find((error) => {\n const code = typeof error.extensions?.code === 'string' ? error.extensions.code : '';\n const errorCode =\n typeof error.extensions?.errorCode === 'string' ? error.extensions.errorCode : '';\n // Match both the constant form and the human-readable phrase, case\n // insensitively — mirrors what `isQuotaExhaustedError` accepts at the\n // per-page level so the link doesn't miss any quota error the rest\n // of the code base already recognises.\n const message = (error.message ?? '').toLowerCase();\n return (\n code === 'QUOTA_EXHAUSTED' ||\n errorCode === 'QUOTA_EXHAUSTED' ||\n message.includes('quota_exhausted') ||\n message.includes('quota exhausted')\n );\n });\n}\n\n/**\n * Runs the initial data query inside the ApolloProvider tree so the result\n * lands in the MFE's own Apollo cache (not the shell's).\n */\nconst InitialDataPrefetcher: FC<PropsWithChildren<{ workspaceId?: string | null }>> = ({\n workspaceId,\n children,\n}) => {\n useInitialDataLoader({ workspaceId });\n return <>{children}</>;\n};\n\n/**\n * Context value for VibeControls microfrontend\n */\ntype VibeControlsContextValue = VibeControlsRootProps;\n\nconst VibeControlsContext = createContext<VibeControlsContextValue | null>(null);\n\n/**\n * Hook to access VibeControls context\n */\nexport const useVibeControls = () => {\n const context = use(VibeControlsContext);\n if (!context) {\n throw new Error('useVibeControls must be used within VibeControlsProvider');\n }\n return context;\n};\n\n/**\n * Hook for navigation within the VibeControls microfrontend.\n * Uses the shell's navigate function if available, falling back to\n * React Router's useNavigate. Automatically prepends basePath.\n *\n * Usage:\n * const navigate = useVibeNavigate();\n * navigate('/agents/123'); // navigates to /vibecontrols/agents/123\n */\nexport const useVibeNavigate = () => {\n const { navigate: shellNavigate, basePath } = useVibeControls();\n const routerNavigate = useRouterNavigate();\n\n return useCallback(\n (path: string) => {\n const fullPath = basePath && basePath !== '/' ? `${basePath}${path}` : path;\n if (shellNavigate) {\n shellNavigate(fullPath);\n } else {\n routerNavigate(fullPath);\n }\n },\n [shellNavigate, basePath, routerNavigate]\n );\n};\n\n/**\n * Provider component for VibeControls microfrontend\n * Sets up routing, Apollo Client with auth error detection, and context.\n *\n * The Apollo Client is configured with:\n * - An onError link that detects UNAUTHENTICATED / FORBIDDEN responses and\n * invokes the `onAuthError` prop so the host shell can trigger logout.\n * - A dynamic auth link that reads the token per-request (not baked in at\n * client creation time), so token refreshes take effect immediately.\n */\nexport const VibeControlsProvider: FC<PropsWithChildren<VibeControlsRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n projectId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n onAgentSelect,\n onSessionStart,\n onVibeActivate,\n defaultView,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n}) => {\n // Per-instance QueryClient so multiple MFE mounts don't share cache\n const [queryClient] = useState(() => {\n const client = new QueryClient({\n mutationCache: new MutationCache({\n onSuccess: () => {\n void client.invalidateQueries();\n },\n }),\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000, // 5 minutes\n retry: 2,\n },\n },\n });\n return client;\n });\n\n // Store tokens and callbacks in refs so the Apollo Client (and its cache)\n // survives token refreshes. The setContext auth link reads from refs\n // per-request, so refreshed tokens take effect immediately without\n // destroying the InMemoryCache.\n const authTokenRef = useRef(authToken);\n const workspaceTokenRef = useRef(workspaceToken);\n const workspaceIdRef = useRef(workspaceId);\n const organizationIdRef = useRef(organizationId);\n const tenantIdRef = useRef(tenantId);\n const projectIdRef = useRef(projectId);\n const onAuthErrorRef = useRef(onAuthError);\n useEffect(() => {\n authTokenRef.current = authToken;\n }, [authToken]);\n useEffect(() => {\n workspaceTokenRef.current = workspaceToken;\n }, [workspaceToken]);\n useEffect(() => {\n workspaceIdRef.current = workspaceId;\n }, [workspaceId]);\n useEffect(() => {\n organizationIdRef.current = organizationId;\n }, [organizationId]);\n useEffect(() => {\n tenantIdRef.current = tenantId;\n }, [tenantId]);\n useEffect(() => {\n projectIdRef.current = projectId;\n }, [projectId]);\n useEffect(() => {\n onAuthErrorRef.current = onAuthError;\n }, [onAuthError]);\n\n // Build Apollo Client with error link + dynamic auth link.\n // Only recreated when apiGatewayUrl changes (different endpoint = different client).\n //\n // SCOPE NOTE (codex #128 review):\n // This client — and the `errorLink` below — is ONLY used when the microfe\n // mounts in standalone mode (no `basePath`, e.g. `bun run dev` against\n // this package directly). When integrated into an app shell (the\n // production path) `basePath` is set, the local ApolloProvider is\n // skipped further down, and mutations go through the shell's\n // `MultiGatewayProvider` Apollo client. In that integrated mode the\n // global error-dispatch path here does NOT fire — quota-handling\n // relies on the per-page `isQuotaExhaustedError(result.error)`\n // checks (e.g. TargetsPage.handleCreate) which still render the\n // local QuotaExhaustedDialog. Tracked as a follow-up: lift this\n // link into fe-libs `MultiGatewayProvider` so the global event\n // dispatches in shell mode too.\n const apolloClient = useMemo(() => {\n // Error link (Apollo v4 pattern): detect authentication failures + quota.\n //\n // Quota: we use `errorPolicy: 'all'` for mutations, so GraphQL errors land\n // on `result.error` instead of rejecting the mutation promise. Most\n // callsites only have a `catch` block, so quota errors silently disappear\n // (BOFF-2625). Dispatching a CustomEvent here lets a single provider\n // (QuotaExhaustedProvider) render the dialog for every mutation without\n // touching individual handlers. Per-page handlers that already react to\n // `isQuotaExhaustedError(result.error)` still work — both paths just open\n // the same modal state.\n const errorLink = onError(({ error, operation }) => {\n if (CombinedGraphQLErrors.is(error)) {\n // Auth first: a response can carry BOTH an UNAUTHENTICATED and a\n // QUOTA_EXHAUSTED error (token expired mid-request that also\n // triggered a quota check). If we returned after dispatching the\n // quota event, the host shell would never get the auth signal and\n // the session would stay broken behind the modal. The quota\n // dialog is moot for an expired session anyway.\n if (hasSessionAuthErrors(error.errors)) {\n console.warn('[VibeControls] Auth error detected — notifying host shell');\n onAuthErrorRef.current?.();\n return;\n }\n\n const quotaError = findQuotaExhaustedError(error.errors);\n if (quotaError) {\n const detail: QuotaExhaustedEventDetail = {\n kind: inferQuotaKindFromOperation(operation?.operationName),\n message: quotaError.message,\n operationName: operation?.operationName ?? null,\n };\n window.dispatchEvent(\n new CustomEvent<QuotaExhaustedEventDetail>(QUOTA_EXHAUSTED_EVENT, { detail })\n );\n return;\n }\n }\n\n if (error instanceof Error && isAuthNetworkError(error)) {\n console.warn('[VibeControls] Auth network error — notifying host shell');\n onAuthErrorRef.current?.();\n }\n });\n\n // Auth link: dynamically attach dual-token auth and workspace context per-request.\n // Reads from refs so token refreshes don't recreate the client.\n const authLink = setContext((_, { headers }) => {\n const resolvedAuthToken = authTokenRef.current ?? getStoredAuthToken();\n // Fall back to storage when workspaceId prop is empty (e.g. during initial render\n // before ActiveContextProvider has propagated the value through workspaceBaseProps)\n const resolvedWorkspaceId = workspaceIdRef.current || getStoredWorkspaceId();\n const resolvedProjectId = projectIdRef.current || getStoredProjectId();\n const rawWorkspaceToken = workspaceTokenRef.current ?? getStoredWorkspaceToken();\n const resolvedWorkspaceToken = isWorkspaceTokenValidForContext(rawWorkspaceToken, {\n workspaceId: resolvedWorkspaceId,\n organizationId: organizationIdRef.current,\n tenantId: tenantIdRef.current,\n projectId: resolvedProjectId,\n })\n ? rawWorkspaceToken\n : null;\n\n return {\n headers: {\n ...headers,\n ...(resolvedAuthToken ? { Authorization: `Bearer ${resolvedAuthToken}` } : {}),\n ...(resolvedWorkspaceToken\n ? { 'X-Workspace-Authorization': `Bearer ${resolvedWorkspaceToken}` }\n : {}),\n ...(resolvedWorkspaceId ? { 'x-workspace-id': resolvedWorkspaceId } : {}),\n ...(resolvedProjectId ? { 'x-project-id': resolvedProjectId } : {}),\n },\n };\n });\n\n const httpLink = new BatchHttpLink({\n uri: apiGatewayUrl ? `${apiGatewayUrl}/workspaces/graphql` : '/workspaces/graphql',\n batchMax: 10,\n batchInterval: 10, // ms — tight window so latency isn't hurt\n });\n\n let client: InstanceType<typeof ApolloClient> | null = null;\n const mutationAutoRefreshLink = createMutationAutoRefreshLink(() => client);\n\n client = new ApolloClient({\n link: from([errorLink, mutationAutoRefreshLink, authLink, httpLink]),\n cache: buildVibeControlsCache(),\n defaultOptions: {\n watchQuery: { fetchPolicy: 'cache-first', errorPolicy: 'all' },\n query: { errorPolicy: 'all' },\n mutate: { errorPolicy: 'all' },\n },\n });\n return client;\n }, [apiGatewayUrl]);\n\n const contextValue: VibeControlsContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n projectId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n onAgentSelect,\n onSessionStart,\n onVibeActivate,\n defaultView,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n };\n\n const isPublicLinkRoute =\n typeof window !== 'undefined' &&\n /^\\/vibecontrols\\/(share|deck)\\//.test(window.location.pathname);\n\n // Core providers that wrap the children\n // PermissionProvider requires AuthProvider which is provided by the shell.\n // It fetches permissions from wspace-rbac-svc via the workspace gateway.\n // AgentEventsProvider manages WebSocket connections to agents for real-time notifications.\n //\n // QuotaExhaustedProvider wraps both public and authenticated trees because\n // public deck routes (`/vibecontrols/deck/:token`) still run mutations like\n // `ExecuteSharedVibeDeckButton` that can hit command-execution quota\n // (#128 review codex P2). The provider is cheap and route-agnostic — it\n // only listens for a window event.\n const authedTree = (\n <InitialDataPrefetcher workspaceId={workspaceId}>\n <PermissionProvider scopeId={workspaceId ?? null} gateway=\"workspace\">\n <AgentEventsProvider\n toastPosition=\"bottom-right\"\n autoDismissDelay={5000}\n maxVisibleToasts={3}\n >\n {children}\n </AgentEventsProvider>\n </PermissionProvider>\n </InitialDataPrefetcher>\n );\n\n const routedChildren = (\n <QuotaExhaustedProvider>{isPublicLinkRoute ? children : authedTree}</QuotaExhaustedProvider>\n );\n\n const coreProviders = (\n <QueryClientProvider client={queryClient}>\n {basePath ? (\n <VibeControlsContext.Provider value={contextValue}>\n {routedChildren}\n </VibeControlsContext.Provider>\n ) : (\n <ApolloProvider client={apolloClient}>\n <VibeControlsContext.Provider value={contextValue}>\n {routedChildren}\n </VibeControlsContext.Provider>\n </ApolloProvider>\n )}\n </QueryClientProvider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (basePath) {\n // Integrated mode: shell already has BrowserRouter, don't create nested router\n return coreProviders;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{coreProviders}</MemoryRouter>;\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-vibecontrols",
3
- "version": "2026.601.5",
3
+ "version": "2026.601.6",
4
4
  "description": "VibeControls microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [