@burdenoff/microfe-buildmyiq 2026.1027.2 → 2026.1027.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -179,7 +179,7 @@ var E = () => {
179
179
  }),
180
180
  n && (e.discardedActionCount ?? 0) > 0 && /* @__PURE__ */ y("p", {
181
181
  className: "mt-2 text-xs text-text-tertiary",
182
- children: [e.discardedActionCount, " suggested action(s) referred to data that does not exist in this workspace and were left out."]
182
+ children: [e.discardedActionCount, " suggested action(s) were left out because they referred to data that is not in this workspace or had nobody to act on."]
183
183
  })
184
184
  ]
185
185
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"AdminCopilotPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/copilot/AdminCopilotPage.tsx"],"sourcesContent":["/**\n * AdminCopilotPage — L&D admin copilot chat (BOFF-4434, actions BOFF-9687).\n * @module buildmyiq/pages/copilot\n *\n * A workspace-operator surface: every answer comes from `buiAdminCopilotAsk`,\n * grounded in real, live-computed course/enrollment/assessment aggregate\n * stats. The copilot never executes anything itself. When the admin asks for\n * a bulk enrollment, a reminder or a report, the answer carries proposed\n * actions that render as cards; each runs only after the admin clicks\n * Confirm, and can be dismissed instead. The page holds the transcript itself\n * and resends prior turns as `history` on every ask for multi-turn context.\n */\n\nimport type { FC } from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { Bot, History, Send, ShieldQuestion, Sparkles } from 'lucide-react';\nimport { Button, GlassCard, Textarea } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, StatusBadge } from '../../components';\nimport {\n useBuiAdminCopilotActions,\n useBuiAdminCopilotAsk,\n useBuiAdminCopilotDismissAction,\n useBuiAdminCopilotExecuteAction,\n} from '../../hooks';\nimport { useBuildmyiq } from '../../context';\nimport type { BuiAdminCopilotProposedAction, BuiAdminCopilotTurnInput } from '../../types';\nimport { formatDateTime } from '../../utils';\nimport { AdminCopilotActionCard, describeResult } from './AdminCopilotActionCard';\n\ninterface ChatTurn {\n role: 'USER' | 'ASSISTANT';\n content: string;\n actions?: BuiAdminCopilotProposedAction[];\n discardedActionCount?: number;\n}\n\ninterface ReportFile {\n csv: string;\n fileName: string;\n}\n\nconst SUGGESTED_QUESTIONS = [\n 'How many published courses do we have?',\n 'What is our overall course completion rate?',\n 'Remind learners who have not finished their courses',\n 'Export an enrollment and completion report for all courses',\n];\n\nfunction downloadCsv(file: ReportFile): void {\n const url = URL.createObjectURL(new Blob([file.csv], { type: 'text/csv;charset=utf-8' }));\n const link = document.createElement('a');\n link.href = url;\n link.download = file.fileName;\n document.body.appendChild(link);\n link.click();\n link.remove();\n URL.revokeObjectURL(url);\n}\n\nfunction displayName(user: ReturnType<typeof useBuildmyiq>['currentUser']): string {\n if (!user) return 'You';\n const full = [user.firstName, user.lastName].filter(Boolean).join(' ').trim();\n return user.name || full || user.username || user.email || 'You';\n}\n\nexport const AdminCopilotPage: FC = () => {\n const { currentUser } = useBuildmyiq();\n const ask = useBuiAdminCopilotAsk();\n const execute = useBuiAdminCopilotExecuteAction();\n const dismiss = useBuiAdminCopilotDismissAction();\n const recent = useBuiAdminCopilotActions(undefined, 10);\n const [turns, setTurns] = useState<ChatTurn[]>([]);\n const [draft, setDraft] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n const [busyActionId, setBusyActionId] = useState<string | null>(null);\n const [reports, setReports] = useState<Record<string, ReportFile>>({});\n const scrollRef = useRef<HTMLDivElement>(null);\n const selfName = displayName(currentUser);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' });\n }, [turns.length]);\n\n const send = async (question: string) => {\n const trimmed = question.trim();\n if (!trimmed || ask.isPending) return;\n setError(null);\n const history: BuiAdminCopilotTurnInput[] = turns.map((t) => ({\n role: t.role,\n content: t.content,\n }));\n setTurns((prev) => [...prev, { role: 'USER', content: trimmed }]);\n setDraft('');\n try {\n const result = await ask.mutateAsync({ question: trimmed, history });\n setTurns((prev) => [\n ...prev,\n {\n role: 'ASSISTANT',\n content: result.answer,\n actions: result.proposedActions,\n discardedActionCount: result.discardedActionCount,\n },\n ]);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The admin copilot is temporarily unavailable.');\n // Roll back the optimistic USER turn so a retry doesn't duplicate it.\n setTurns((prev) => prev.slice(0, -1));\n setDraft(trimmed);\n }\n };\n\n const replaceAction = (updated: BuiAdminCopilotProposedAction) => {\n setTurns((prev) =>\n prev.map((turn) =>\n turn.actions?.some((a) => a.id === updated.id)\n ? { ...turn, actions: turn.actions.map((a) => (a.id === updated.id ? updated : a)) }\n : turn\n )\n );\n };\n\n const confirmAction = async (action: BuiAdminCopilotProposedAction) => {\n setActionError(null);\n setBusyActionId(action.id);\n try {\n const execution = await execute.mutateAsync({ actionId: action.id, kind: action.kind });\n replaceAction(execution.action);\n if (execution.reportCsv && execution.reportFileName) {\n const file = { csv: execution.reportCsv, fileName: execution.reportFileName };\n setReports((prev) => ({ ...prev, [action.id]: file }));\n downloadCsv(file);\n }\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The action could not be completed.');\n } finally {\n setBusyActionId(null);\n }\n };\n\n const dismissAction = async (action: BuiAdminCopilotProposedAction) => {\n setActionError(null);\n setBusyActionId(action.id);\n try {\n replaceAction(await dismiss.mutateAsync(action.id));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The action could not be dismissed.');\n } finally {\n setBusyActionId(null);\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n void send(draft);\n }\n };\n\n return (\n <PageLayout\n title=\"Admin copilot\"\n description=\"Ask about your workspace's courses, enrollments and grading, or ask for an enrollment, reminder or report. Nothing runs until you confirm it.\"\n titleGradient\n >\n <GlassCard treatment=\"plain\" className=\"flex flex-col overflow-hidden p-0\">\n <div className=\"flex items-center gap-2 border-b border-border-seam px-4 py-3\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Sparkles className=\"h-4 w-4 text-[var(--color-accent-teal)]\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-semibold text-text-primary\">BuildMyIQ Admin Copilot</p>\n <p className=\"text-xs text-text-secondary\">\n Answers from live workspace data. Proposes actions; you confirm each one.\n </p>\n </div>\n </div>\n\n <div ref={scrollRef} className=\"max-h-[28rem] min-h-64 flex-1 overflow-y-auto px-4 py-4\">\n {turns.length === 0 ? (\n <div className=\"flex flex-col items-center gap-3 py-8 text-center\">\n <ShieldQuestion className=\"h-9 w-9 text-text-tertiary\" />\n <p className=\"max-w-sm text-sm text-text-secondary\">\n Ask about course counts, enrollment/completion rates or grading volume, or ask the\n copilot to enroll learners, send reminders or export a report.\n </p>\n <div className=\"flex flex-wrap justify-center gap-2\">\n {SUGGESTED_QUESTIONS.map((q) => (\n <Button\n key={q}\n variant=\"outline\"\n size=\"sm\"\n onClick={() => send(q)}\n disabled={ask.isPending}\n >\n {q}\n </Button>\n ))}\n </div>\n </div>\n ) : (\n <ul className=\"flex flex-col gap-4\">\n {turns.map((turn, i) => {\n const isAssistant = turn.role === 'ASSISTANT';\n return (\n <li key={i} className=\"flex gap-3\">\n <div\n className={\n isAssistant\n ? 'flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]'\n : 'flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold text-text-secondary'\n }\n >\n {isAssistant ? (\n <Bot className=\"h-4 w-4 text-[var(--color-accent-teal)]\" />\n ) : (\n (selfName.trim()[0]?.toUpperCase() ?? '?')\n )}\n </div>\n <div className=\"min-w-0 flex-1\">\n <span className=\"text-sm font-semibold text-text-primary\">\n {isAssistant ? 'Admin Copilot' : selfName}\n </span>\n <p className=\"mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {turn.content}\n </p>\n {turn.actions && turn.actions.length > 0 && (\n <div className=\"mt-3 flex flex-col gap-3\">\n {turn.actions.map((action) => {\n const report = reports[action.id];\n return (\n <AdminCopilotActionCard\n key={action.id}\n action={action}\n busy={busyActionId === action.id}\n onConfirm={(a) => void confirmAction(a)}\n onDismiss={(a) => void dismissAction(a)}\n onDownload={report ? () => downloadCsv(report) : undefined}\n />\n );\n })}\n </div>\n )}\n {isAssistant && (turn.discardedActionCount ?? 0) > 0 && (\n <p className=\"mt-2 text-xs text-text-tertiary\">\n {turn.discardedActionCount} suggested action(s) referred to data that does\n not exist in this workspace and were left out.\n </p>\n )}\n </div>\n </li>\n );\n })}\n {ask.isPending && (\n <li className=\"flex items-center gap-3\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Bot className=\"h-4 w-4 animate-pulse text-[var(--color-accent-teal)]\" />\n </span>\n <span className=\"text-sm text-text-secondary\">\n Checking workspace statistics…\n </span>\n </li>\n )}\n </ul>\n )}\n </div>\n\n <div className=\"border-t border-border-seam p-3\">\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder=\"Ask about courses, enrollments, grading…\"\n rows={2}\n disabled={ask.isPending}\n />\n <div className=\"mt-2 flex items-center justify-end gap-2\">\n {(error ?? actionError) && (\n <span className=\"mr-auto text-xs text-status-error-text\">{error ?? actionError}</span>\n )}\n <Button size=\"sm\" onClick={() => send(draft)} disabled={!draft.trim() || ask.isPending}>\n <Send className=\"mr-1.5 h-4 w-4\" />\n {ask.isPending ? 'Asking…' : 'Ask'}\n </Button>\n </div>\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"mt-4 p-0\" data-testid=\"copilot-recent-actions\">\n <div className=\"flex items-center gap-2 border-b border-border-seam px-4 py-3\">\n <History className=\"h-4 w-4 text-text-secondary\" />\n <p className=\"text-sm font-semibold text-text-primary\">Recent copilot actions</p>\n </div>\n {recent.isLoading ? (\n <p className=\"px-4 py-3 text-sm text-text-secondary\">Loading…</p>\n ) : recent.isError ? (\n <p className=\"px-4 py-3 text-sm text-status-error-text\">\n Recent actions could not be loaded.\n </p>\n ) : (recent.data ?? []).length === 0 ? (\n <p className=\"px-4 py-3 text-sm text-text-secondary\">\n No actions yet. Proposals the copilot makes, and what happened to them, appear here.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {(recent.data ?? []).map((action) => (\n <li key={action.id} className=\"flex flex-wrap items-center gap-2 px-4 py-2.5\">\n <span className=\"min-w-0 flex-1 truncate text-sm text-text-primary\">\n {action.title}\n </span>\n <span className=\"text-xs text-text-tertiary\">\n {action.status === 'EXECUTED'\n ? describeResult(action)\n : formatDateTime(action.createdAt)}\n </span>\n <StatusBadge status={action.status} />\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AdminCopilotPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/copilot/AdminCopilotPage.tsx"],"sourcesContent":["/**\n * AdminCopilotPage — L&D admin copilot chat (BOFF-4434, actions BOFF-9687).\n * @module buildmyiq/pages/copilot\n *\n * A workspace-operator surface: every answer comes from `buiAdminCopilotAsk`,\n * grounded in real, live-computed course/enrollment/assessment aggregate\n * stats. The copilot never executes anything itself. When the admin asks for\n * a bulk enrollment, a reminder or a report, the answer carries proposed\n * actions that render as cards; each runs only after the admin clicks\n * Confirm, and can be dismissed instead. The page holds the transcript itself\n * and resends prior turns as `history` on every ask for multi-turn context.\n */\n\nimport type { FC } from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { Bot, History, Send, ShieldQuestion, Sparkles } from 'lucide-react';\nimport { Button, GlassCard, Textarea } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, StatusBadge } from '../../components';\nimport {\n useBuiAdminCopilotActions,\n useBuiAdminCopilotAsk,\n useBuiAdminCopilotDismissAction,\n useBuiAdminCopilotExecuteAction,\n} from '../../hooks';\nimport { useBuildmyiq } from '../../context';\nimport type { BuiAdminCopilotProposedAction, BuiAdminCopilotTurnInput } from '../../types';\nimport { formatDateTime } from '../../utils';\nimport { AdminCopilotActionCard, describeResult } from './AdminCopilotActionCard';\n\ninterface ChatTurn {\n role: 'USER' | 'ASSISTANT';\n content: string;\n actions?: BuiAdminCopilotProposedAction[];\n discardedActionCount?: number;\n}\n\ninterface ReportFile {\n csv: string;\n fileName: string;\n}\n\nconst SUGGESTED_QUESTIONS = [\n 'How many published courses do we have?',\n 'What is our overall course completion rate?',\n 'Remind learners who have not finished their courses',\n 'Export an enrollment and completion report for all courses',\n];\n\nfunction downloadCsv(file: ReportFile): void {\n const url = URL.createObjectURL(new Blob([file.csv], { type: 'text/csv;charset=utf-8' }));\n const link = document.createElement('a');\n link.href = url;\n link.download = file.fileName;\n document.body.appendChild(link);\n link.click();\n link.remove();\n URL.revokeObjectURL(url);\n}\n\nfunction displayName(user: ReturnType<typeof useBuildmyiq>['currentUser']): string {\n if (!user) return 'You';\n const full = [user.firstName, user.lastName].filter(Boolean).join(' ').trim();\n return user.name || full || user.username || user.email || 'You';\n}\n\nexport const AdminCopilotPage: FC = () => {\n const { currentUser } = useBuildmyiq();\n const ask = useBuiAdminCopilotAsk();\n const execute = useBuiAdminCopilotExecuteAction();\n const dismiss = useBuiAdminCopilotDismissAction();\n const recent = useBuiAdminCopilotActions(undefined, 10);\n const [turns, setTurns] = useState<ChatTurn[]>([]);\n const [draft, setDraft] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n const [busyActionId, setBusyActionId] = useState<string | null>(null);\n const [reports, setReports] = useState<Record<string, ReportFile>>({});\n const scrollRef = useRef<HTMLDivElement>(null);\n const selfName = displayName(currentUser);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' });\n }, [turns.length]);\n\n const send = async (question: string) => {\n const trimmed = question.trim();\n if (!trimmed || ask.isPending) return;\n setError(null);\n const history: BuiAdminCopilotTurnInput[] = turns.map((t) => ({\n role: t.role,\n content: t.content,\n }));\n setTurns((prev) => [...prev, { role: 'USER', content: trimmed }]);\n setDraft('');\n try {\n const result = await ask.mutateAsync({ question: trimmed, history });\n setTurns((prev) => [\n ...prev,\n {\n role: 'ASSISTANT',\n content: result.answer,\n actions: result.proposedActions,\n discardedActionCount: result.discardedActionCount,\n },\n ]);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The admin copilot is temporarily unavailable.');\n // Roll back the optimistic USER turn so a retry doesn't duplicate it.\n setTurns((prev) => prev.slice(0, -1));\n setDraft(trimmed);\n }\n };\n\n const replaceAction = (updated: BuiAdminCopilotProposedAction) => {\n setTurns((prev) =>\n prev.map((turn) =>\n turn.actions?.some((a) => a.id === updated.id)\n ? { ...turn, actions: turn.actions.map((a) => (a.id === updated.id ? updated : a)) }\n : turn\n )\n );\n };\n\n const confirmAction = async (action: BuiAdminCopilotProposedAction) => {\n setActionError(null);\n setBusyActionId(action.id);\n try {\n const execution = await execute.mutateAsync({ actionId: action.id, kind: action.kind });\n replaceAction(execution.action);\n if (execution.reportCsv && execution.reportFileName) {\n const file = { csv: execution.reportCsv, fileName: execution.reportFileName };\n setReports((prev) => ({ ...prev, [action.id]: file }));\n downloadCsv(file);\n }\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The action could not be completed.');\n } finally {\n setBusyActionId(null);\n }\n };\n\n const dismissAction = async (action: BuiAdminCopilotProposedAction) => {\n setActionError(null);\n setBusyActionId(action.id);\n try {\n replaceAction(await dismiss.mutateAsync(action.id));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The action could not be dismissed.');\n } finally {\n setBusyActionId(null);\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n void send(draft);\n }\n };\n\n return (\n <PageLayout\n title=\"Admin copilot\"\n description=\"Ask about your workspace's courses, enrollments and grading, or ask for an enrollment, reminder or report. Nothing runs until you confirm it.\"\n titleGradient\n >\n <GlassCard treatment=\"plain\" className=\"flex flex-col overflow-hidden p-0\">\n <div className=\"flex items-center gap-2 border-b border-border-seam px-4 py-3\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Sparkles className=\"h-4 w-4 text-[var(--color-accent-teal)]\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-semibold text-text-primary\">BuildMyIQ Admin Copilot</p>\n <p className=\"text-xs text-text-secondary\">\n Answers from live workspace data. Proposes actions; you confirm each one.\n </p>\n </div>\n </div>\n\n <div ref={scrollRef} className=\"max-h-[28rem] min-h-64 flex-1 overflow-y-auto px-4 py-4\">\n {turns.length === 0 ? (\n <div className=\"flex flex-col items-center gap-3 py-8 text-center\">\n <ShieldQuestion className=\"h-9 w-9 text-text-tertiary\" />\n <p className=\"max-w-sm text-sm text-text-secondary\">\n Ask about course counts, enrollment/completion rates or grading volume, or ask the\n copilot to enroll learners, send reminders or export a report.\n </p>\n <div className=\"flex flex-wrap justify-center gap-2\">\n {SUGGESTED_QUESTIONS.map((q) => (\n <Button\n key={q}\n variant=\"outline\"\n size=\"sm\"\n onClick={() => send(q)}\n disabled={ask.isPending}\n >\n {q}\n </Button>\n ))}\n </div>\n </div>\n ) : (\n <ul className=\"flex flex-col gap-4\">\n {turns.map((turn, i) => {\n const isAssistant = turn.role === 'ASSISTANT';\n return (\n <li key={i} className=\"flex gap-3\">\n <div\n className={\n isAssistant\n ? 'flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]'\n : 'flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold text-text-secondary'\n }\n >\n {isAssistant ? (\n <Bot className=\"h-4 w-4 text-[var(--color-accent-teal)]\" />\n ) : (\n (selfName.trim()[0]?.toUpperCase() ?? '?')\n )}\n </div>\n <div className=\"min-w-0 flex-1\">\n <span className=\"text-sm font-semibold text-text-primary\">\n {isAssistant ? 'Admin Copilot' : selfName}\n </span>\n <p className=\"mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {turn.content}\n </p>\n {turn.actions && turn.actions.length > 0 && (\n <div className=\"mt-3 flex flex-col gap-3\">\n {turn.actions.map((action) => {\n const report = reports[action.id];\n return (\n <AdminCopilotActionCard\n key={action.id}\n action={action}\n busy={busyActionId === action.id}\n onConfirm={(a) => void confirmAction(a)}\n onDismiss={(a) => void dismissAction(a)}\n onDownload={report ? () => downloadCsv(report) : undefined}\n />\n );\n })}\n </div>\n )}\n {isAssistant && (turn.discardedActionCount ?? 0) > 0 && (\n <p className=\"mt-2 text-xs text-text-tertiary\">\n {turn.discardedActionCount} suggested action(s) were left out because they\n referred to data that is not in this workspace or had nobody to act on.\n </p>\n )}\n </div>\n </li>\n );\n })}\n {ask.isPending && (\n <li className=\"flex items-center gap-3\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Bot className=\"h-4 w-4 animate-pulse text-[var(--color-accent-teal)]\" />\n </span>\n <span className=\"text-sm text-text-secondary\">\n Checking workspace statistics…\n </span>\n </li>\n )}\n </ul>\n )}\n </div>\n\n <div className=\"border-t border-border-seam p-3\">\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder=\"Ask about courses, enrollments, grading…\"\n rows={2}\n disabled={ask.isPending}\n />\n <div className=\"mt-2 flex items-center justify-end gap-2\">\n {(error ?? actionError) && (\n <span className=\"mr-auto text-xs text-status-error-text\">{error ?? actionError}</span>\n )}\n <Button size=\"sm\" onClick={() => send(draft)} disabled={!draft.trim() || ask.isPending}>\n <Send className=\"mr-1.5 h-4 w-4\" />\n {ask.isPending ? 'Asking…' : 'Ask'}\n </Button>\n </div>\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"mt-4 p-0\" data-testid=\"copilot-recent-actions\">\n <div className=\"flex items-center gap-2 border-b border-border-seam px-4 py-3\">\n <History className=\"h-4 w-4 text-text-secondary\" />\n <p className=\"text-sm font-semibold text-text-primary\">Recent copilot actions</p>\n </div>\n {recent.isLoading ? (\n <p className=\"px-4 py-3 text-sm text-text-secondary\">Loading…</p>\n ) : recent.isError ? (\n <p className=\"px-4 py-3 text-sm text-status-error-text\">\n Recent actions could not be loaded.\n </p>\n ) : (recent.data ?? []).length === 0 ? (\n <p className=\"px-4 py-3 text-sm text-text-secondary\">\n No actions yet. Proposals the copilot makes, and what happened to them, appear here.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {(recent.data ?? []).map((action) => (\n <li key={action.id} className=\"flex flex-wrap items-center gap-2 px-4 py-2.5\">\n <span className=\"min-w-0 flex-1 truncate text-sm text-text-primary\">\n {action.title}\n </span>\n <span className=\"text-xs text-text-tertiary\">\n {action.status === 'EXECUTED'\n ? describeResult(action)\n : formatDateTime(action.createdAt)}\n </span>\n <StatusBadge status={action.status} />\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-buildmyiq",
3
- "version": "2026.1027.2",
3
+ "version": "2026.1027.3",
4
4
  "description": "Burdenoff BuildMyIQ Microfrontend — education, learning & development",
5
5
  "type": "module",
6
6
  "files": [