@burdenoff/microfe-buildmyiq 2026.1030.1 → 2026.1030.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.
Files changed (136) hide show
  1. package/dist/buildmyiq/BuildmyiqRoutes.js +3 -3
  2. package/dist/buildmyiq/BuildmyiqRoutes.js.map +1 -1
  3. package/dist/buildmyiq/analytics/buildmyiqAnalytics.d.ts +129 -0
  4. package/dist/buildmyiq/analytics/buildmyiqAnalytics.d.ts.map +1 -0
  5. package/dist/buildmyiq/analytics/buildmyiqAnalytics.js +97 -0
  6. package/dist/buildmyiq/analytics/buildmyiqAnalytics.js.map +1 -0
  7. package/dist/buildmyiq/analytics/index.d.ts +7 -0
  8. package/dist/buildmyiq/analytics/index.d.ts.map +1 -0
  9. package/dist/buildmyiq/analytics/index.js +2 -0
  10. package/dist/buildmyiq/analytics/useBuildmyiqAnalytics.d.ts +12 -0
  11. package/dist/buildmyiq/analytics/useBuildmyiqAnalytics.d.ts.map +1 -0
  12. package/dist/buildmyiq/analytics/useBuildmyiqAnalytics.js +26 -0
  13. package/dist/buildmyiq/analytics/useBuildmyiqAnalytics.js.map +1 -0
  14. package/dist/buildmyiq/components/AccessibilityChecker.js +1 -1
  15. package/dist/buildmyiq/components/AccessibilityChecker.js.map +1 -1
  16. package/dist/buildmyiq/components/BuiDiscussion.d.ts.map +1 -1
  17. package/dist/buildmyiq/components/BuiDiscussion.js +4 -2
  18. package/dist/buildmyiq/components/BuiDiscussion.js.map +1 -1
  19. package/dist/buildmyiq/components/BuiTags.d.ts.map +1 -1
  20. package/dist/buildmyiq/components/BuiTags.js +8 -3
  21. package/dist/buildmyiq/components/BuiTags.js.map +1 -1
  22. package/dist/buildmyiq/components/DataTable.js +2 -2
  23. package/dist/buildmyiq/components/DataTable.js.map +1 -1
  24. package/dist/buildmyiq/components/EmptyState.d.ts.map +1 -1
  25. package/dist/buildmyiq/components/EmptyState.js +33 -8
  26. package/dist/buildmyiq/components/EmptyState.js.map +1 -1
  27. package/dist/buildmyiq/components/EvidenceUpload.d.ts.map +1 -1
  28. package/dist/buildmyiq/components/EvidenceUpload.js +20 -9
  29. package/dist/buildmyiq/components/EvidenceUpload.js.map +1 -1
  30. package/dist/buildmyiq/components/FilterTabs.js +1 -1
  31. package/dist/buildmyiq/components/FilterTabs.js.map +1 -1
  32. package/dist/buildmyiq/components/GroundedTutorPanel.d.ts.map +1 -1
  33. package/dist/buildmyiq/components/GroundedTutorPanel.js +5 -3
  34. package/dist/buildmyiq/components/GroundedTutorPanel.js.map +1 -1
  35. package/dist/buildmyiq/components/OfflineLessonControl.js +3 -3
  36. package/dist/buildmyiq/components/OfflineLessonControl.js.map +1 -1
  37. package/dist/buildmyiq/components/Pagination.js +2 -2
  38. package/dist/buildmyiq/components/Pagination.js.map +1 -1
  39. package/dist/buildmyiq/components/StateViews.d.ts +5 -0
  40. package/dist/buildmyiq/components/StateViews.d.ts.map +1 -1
  41. package/dist/buildmyiq/components/StateViews.js +11 -2
  42. package/dist/buildmyiq/components/StateViews.js.map +1 -1
  43. package/dist/buildmyiq/components/Switch.d.ts +6 -0
  44. package/dist/buildmyiq/components/Switch.d.ts.map +1 -0
  45. package/dist/buildmyiq/components/Switch.js +12 -0
  46. package/dist/buildmyiq/components/Switch.js.map +1 -0
  47. package/dist/buildmyiq/pages/DashboardPage.d.ts.map +1 -1
  48. package/dist/buildmyiq/pages/DashboardPage.js +5 -5
  49. package/dist/buildmyiq/pages/DashboardPage.js.map +1 -1
  50. package/dist/buildmyiq/pages/_shared/CourseCard.js +1 -1
  51. package/dist/buildmyiq/pages/_shared/CourseCard.js.map +1 -1
  52. package/dist/buildmyiq/pages/aiSettings/AiSettingsPage.d.ts.map +1 -1
  53. package/dist/buildmyiq/pages/aiSettings/AiSettingsPage.js +53 -51
  54. package/dist/buildmyiq/pages/aiSettings/AiSettingsPage.js.map +1 -1
  55. package/dist/buildmyiq/pages/assessments/AiAssessmentDraftDialog.d.ts.map +1 -1
  56. package/dist/buildmyiq/pages/assessments/AiAssessmentDraftDialog.js +5 -3
  57. package/dist/buildmyiq/pages/assessments/AiAssessmentDraftDialog.js.map +1 -1
  58. package/dist/buildmyiq/pages/assessments/AiMonitoringConsent.js +2 -2
  59. package/dist/buildmyiq/pages/assessments/AiMonitoringConsent.js.map +1 -1
  60. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.d.ts.map +1 -1
  61. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js +12 -10
  62. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js.map +1 -1
  63. package/dist/buildmyiq/pages/assessments/AssessmentsPage.d.ts.map +1 -1
  64. package/dist/buildmyiq/pages/assessments/AssessmentsPage.js +97 -95
  65. package/dist/buildmyiq/pages/assessments/AssessmentsPage.js.map +1 -1
  66. package/dist/buildmyiq/pages/assessments/ProctoringGate.js +5 -5
  67. package/dist/buildmyiq/pages/assessments/ProctoringGate.js.map +1 -1
  68. package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.d.ts.map +1 -1
  69. package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js +103 -84
  70. package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js.map +1 -1
  71. package/dist/buildmyiq/pages/authoring/AuthoringPage.d.ts.map +1 -1
  72. package/dist/buildmyiq/pages/authoring/AuthoringPage.js +132 -128
  73. package/dist/buildmyiq/pages/authoring/AuthoringPage.js.map +1 -1
  74. package/dist/buildmyiq/pages/authoring/CourseAccessibilityTab.js +1 -1
  75. package/dist/buildmyiq/pages/authoring/CourseAccessibilityTab.js.map +1 -1
  76. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.d.ts.map +1 -1
  77. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js +206 -202
  78. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js.map +1 -1
  79. package/dist/buildmyiq/pages/authoring/_forms.d.ts +19 -2
  80. package/dist/buildmyiq/pages/authoring/_forms.d.ts.map +1 -1
  81. package/dist/buildmyiq/pages/authoring/_forms.js +51 -35
  82. package/dist/buildmyiq/pages/authoring/_forms.js.map +1 -1
  83. package/dist/buildmyiq/pages/automations/AutomationsPage.d.ts.map +1 -1
  84. package/dist/buildmyiq/pages/automations/AutomationsPage.js +125 -122
  85. package/dist/buildmyiq/pages/automations/AutomationsPage.js.map +1 -1
  86. package/dist/buildmyiq/pages/catalog/CatalogPage.d.ts.map +1 -1
  87. package/dist/buildmyiq/pages/catalog/CatalogPage.js +66 -60
  88. package/dist/buildmyiq/pages/catalog/CatalogPage.js.map +1 -1
  89. package/dist/buildmyiq/pages/catalog/CourseDetailPage.d.ts.map +1 -1
  90. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js +253 -246
  91. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js.map +1 -1
  92. package/dist/buildmyiq/pages/certificates/CertificatesPage.d.ts.map +1 -1
  93. package/dist/buildmyiq/pages/certificates/CertificatesPage.js +49 -41
  94. package/dist/buildmyiq/pages/certificates/CertificatesPage.js.map +1 -1
  95. package/dist/buildmyiq/pages/certificates/CredentialShareDialog.js +4 -4
  96. package/dist/buildmyiq/pages/certificates/CredentialShareDialog.js.map +1 -1
  97. package/dist/buildmyiq/pages/cohorts/CohortsPage.d.ts.map +1 -1
  98. package/dist/buildmyiq/pages/cohorts/CohortsPage.js +5 -4
  99. package/dist/buildmyiq/pages/cohorts/CohortsPage.js.map +1 -1
  100. package/dist/buildmyiq/pages/copilot/AdminCopilotActionCard.js +6 -6
  101. package/dist/buildmyiq/pages/copilot/AdminCopilotActionCard.js.map +1 -1
  102. package/dist/buildmyiq/pages/copilot/AdminCopilotPage.d.ts.map +1 -1
  103. package/dist/buildmyiq/pages/copilot/AdminCopilotPage.js +5 -4
  104. package/dist/buildmyiq/pages/copilot/AdminCopilotPage.js.map +1 -1
  105. package/dist/buildmyiq/pages/insights/InsightsPage.js +1 -1
  106. package/dist/buildmyiq/pages/insights/InsightsPage.js.map +1 -1
  107. package/dist/buildmyiq/pages/integrations/LtiPlacementsPanel.d.ts.map +1 -1
  108. package/dist/buildmyiq/pages/integrations/LtiPlacementsPanel.js +6 -4
  109. package/dist/buildmyiq/pages/integrations/LtiPlacementsPanel.js.map +1 -1
  110. package/dist/buildmyiq/pages/integrations/LtiPlatformsPage.d.ts.map +1 -1
  111. package/dist/buildmyiq/pages/integrations/LtiPlatformsPage.js +7 -5
  112. package/dist/buildmyiq/pages/integrations/LtiPlatformsPage.js.map +1 -1
  113. package/dist/buildmyiq/pages/learning/LessonPlayerPage.d.ts.map +1 -1
  114. package/dist/buildmyiq/pages/learning/LessonPlayerPage.js +145 -122
  115. package/dist/buildmyiq/pages/learning/LessonPlayerPage.js.map +1 -1
  116. package/dist/buildmyiq/pages/learning/MyLearningPage.js +5 -5
  117. package/dist/buildmyiq/pages/learning/MyLearningPage.js.map +1 -1
  118. package/dist/buildmyiq/pages/lti/LtiLaunchPage.d.ts.map +1 -1
  119. package/dist/buildmyiq/pages/lti/LtiLaunchPage.js +2 -1
  120. package/dist/buildmyiq/pages/lti/LtiLaunchPage.js.map +1 -1
  121. package/dist/buildmyiq/pages/paths/PathsPage.js +3 -3
  122. package/dist/buildmyiq/pages/paths/PathsPage.js.map +1 -1
  123. package/dist/buildmyiq/pages/proctoring/ProctoringReviewPage.js +4 -4
  124. package/dist/buildmyiq/pages/proctoring/ProctoringReviewPage.js.map +1 -1
  125. package/dist/buildmyiq/pages/skills/SkillsPage.d.ts.map +1 -1
  126. package/dist/buildmyiq/pages/skills/SkillsPage.js +4 -3
  127. package/dist/buildmyiq/pages/skills/SkillsPage.js.map +1 -1
  128. package/dist/buildmyiq/pages/submissions/BulkGradingPage.d.ts.map +1 -1
  129. package/dist/buildmyiq/pages/submissions/BulkGradingPage.js +105 -99
  130. package/dist/buildmyiq/pages/submissions/BulkGradingPage.js.map +1 -1
  131. package/dist/buildmyiq/pages/submissions/SubmissionsPage.d.ts.map +1 -1
  132. package/dist/buildmyiq/pages/submissions/SubmissionsPage.js +179 -177
  133. package/dist/buildmyiq/pages/submissions/SubmissionsPage.js.map +1 -1
  134. package/dist/buildmyiq/pages/tutorReview/TutorReviewPage.js +6 -6
  135. package/dist/buildmyiq/pages/tutorReview/TutorReviewPage.js.map +1 -1
  136. package/package.json +1 -1
@@ -99,7 +99,7 @@ var z = () => {
99
99
  }),
100
100
  /* @__PURE__ */ u(s, {
101
101
  path: "tutor-review",
102
- element: R("buildmyiq.tutorReview", /* @__PURE__ */ u(D, {}))
102
+ element: R("buildmyiq.tutor-review", /* @__PURE__ */ u(D, {}))
103
103
  }),
104
104
  /* @__PURE__ */ u(s, {
105
105
  path: "cohorts",
@@ -115,7 +115,7 @@ var z = () => {
115
115
  }),
116
116
  /* @__PURE__ */ u(s, {
117
117
  path: "admin-copilot",
118
- element: R("buildmyiq.adminCopilot", /* @__PURE__ */ u(j, {}))
118
+ element: R("buildmyiq.admin-copilot", /* @__PURE__ */ u(j, {}))
119
119
  }),
120
120
  /* @__PURE__ */ u(s, {
121
121
  path: "integrations",
@@ -127,7 +127,7 @@ var z = () => {
127
127
  }),
128
128
  /* @__PURE__ */ u(s, {
129
129
  path: "ai-settings",
130
- element: R("buildmyiq.aiSettings", /* @__PURE__ */ u(F, {}))
130
+ element: R("buildmyiq.ai-settings", /* @__PURE__ */ u(F, {}))
131
131
  }),
132
132
  /* @__PURE__ */ u(s, {
133
133
  path: "lti/launch/:ticketId",
@@ -1 +1 @@
1
- {"version":3,"file":"BuildmyiqRoutes.js","names":[],"sources":["../../src/buildmyiq/BuildmyiqRoutes.tsx"],"sourcesContent":["/**\n * Buildmyiq Routes\n * @module buildmyiq\n *\n * Domain routes. The dashboard is eager-loaded (primary landing); all other\n * pages are lazy-loaded. Every route maps to a real `bui*` backend capability.\n * All product routes are gated by feature flags that default to enabled.\n */\n\nimport React, { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { useBuildmyiq } from './context';\nimport { useTr } from './shared/hooks/useTr';\n\n// Eager — primary landing\nimport { DashboardPage } from './pages/DashboardPage';\n\n// Catalog (learner-facing course discovery + detail + enrol)\nconst CatalogPage = lazy(() =>\n import('./pages/catalog/CatalogPage').then((m) => ({ default: m.CatalogPage }))\n);\nconst CourseDetailPage = lazy(() =>\n import('./pages/catalog/CourseDetailPage').then((m) => ({ default: m.CourseDetailPage }))\n);\n\n// My Learning (enrollments + progress + lesson player)\nconst MyLearningPage = lazy(() =>\n import('./pages/learning/MyLearningPage').then((m) => ({ default: m.MyLearningPage }))\n);\nconst LessonPlayerPage = lazy(() =>\n import('./pages/learning/LessonPlayerPage').then((m) => ({ default: m.LessonPlayerPage }))\n);\n\n// Authoring (instructor course management + builder)\nconst AuthoringPage = lazy(() =>\n import('./pages/authoring/AuthoringPage').then((m) => ({ default: m.AuthoringPage }))\n);\nconst CourseBuilderPage = lazy(() =>\n import('./pages/authoring/CourseBuilderPage').then((m) => ({ default: m.CourseBuilderPage }))\n);\n\n// Assessments (list + builder/questions/detail)\nconst AssessmentsPage = lazy(() =>\n import('./pages/assessments/AssessmentsPage').then((m) => ({ default: m.AssessmentsPage }))\n);\nconst AssessmentDetailPage = lazy(() =>\n import('./pages/assessments/AssessmentDetailPage').then((m) => ({\n default: m.AssessmentDetailPage,\n }))\n);\n// Learner-facing: take a published assessment (answer + submit)\nconst TakeAssessmentPage = lazy(() =>\n import('./pages/assessments/TakeAssessmentPage').then((m) => ({ default: m.TakeAssessmentPage }))\n);\n\n// Insights (educator analytics, derived from real data)\nconst InsightsPage = lazy(() =>\n import('./pages/insights/InsightsPage').then((m) => ({ default: m.InsightsPage }))\n);\n\n// Skill Journeys (derived guided paths)\nconst PathsPage = lazy(() =>\n import('./pages/paths/PathsPage').then((m) => ({ default: m.PathsPage }))\n);\n\n// Grading (submissions queue)\nconst SubmissionsPage = lazy(() =>\n import('./pages/submissions/SubmissionsPage').then((m) => ({ default: m.SubmissionsPage }))\n);\nconst BulkGradingPage = lazy(() =>\n import('./pages/submissions/BulkGradingPage').then((m) => ({ default: m.BulkGradingPage }))\n);\n\n// Proctoring review queue — high-stakes exam identity checks (BOFF-4431)\nconst ProctoringReviewPage = lazy(() =>\n import('./pages/proctoring/ProctoringReviewPage').then((m) => ({\n default: m.ProctoringReviewPage,\n }))\n);\n\n// Educator review of AI tutor answers (BOFF-9663)\nconst TutorReviewPage = lazy(() =>\n import('./pages/tutorReview/TutorReviewPage').then((m) => ({ default: m.TutorReviewPage }))\n);\n\n// Cohorts\nconst CohortsPage = lazy(() =>\n import('./pages/cohorts/CohortsPage').then((m) => ({ default: m.CohortsPage }))\n);\n\n// Skills taxonomy\nconst SkillsPage = lazy(() =>\n import('./pages/skills/SkillsPage').then((m) => ({ default: m.SkillsPage }))\n);\n\n// Certificates\nconst CertificatesPage = lazy(() =>\n import('./pages/certificates/CertificatesPage').then((m) => ({ default: m.CertificatesPage }))\n);\n\n// AI Admin Copilot (BOFF-4434) — read-only workspace Q&A\nconst AdminCopilotPage = lazy(() =>\n import('./pages/copilot/AdminCopilotPage').then((m) => ({ default: m.AdminCopilotPage }))\n);\n\n// LMS launch handoff — where a validated LTI 1.3 launch lands (BOFF-9680)\nconst LtiLaunchPage = lazy(() =>\n import('./pages/lti/LtiLaunchPage').then((m) => ({ default: m.LtiLaunchPage }))\n);\n// LMS integrations — LTI 1.3 Advantage platform registry (BOFF-4419)\nconst LtiPlatformsPage = lazy(() =>\n import('./pages/integrations/LtiPlatformsPage').then((m) => ({ default: m.LtiPlatformsPage }))\n);\n\n// L&D workflow automation via FluidGrids (BOFF-9688)\nconst AutomationsPage = lazy(() =>\n import('./pages/automations/AutomationsPage').then((m) => ({ default: m.AutomationsPage }))\n);\n\n// Workspace AI content controls (BOFF-9689)\nconst AiSettingsPage = lazy(() =>\n import('./pages/aiSettings/AiSettingsPage').then((m) => ({ default: m.AiSettingsPage }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n);\n\nfunction FeatureDisabledFallback() {\n const tr = useTr();\n return (\n <div className=\"flex h-full min-h-[200px] items-center justify-center text-sm text-text-secondary\">\n {tr(\n 'buildmyiq.pages.featureDisabled',\n 'This feature is currently disabled. You can enable it from Feature Previews.'\n )}\n </div>\n );\n}\n\nfunction gated(flag: string, element: React.ReactNode, defaultValue = true): React.ReactElement {\n return (\n <FeatureGate flag={flag} defaultValue={defaultValue} fallback={<FeatureDisabledFallback />}>\n {element}\n </FeatureGate>\n );\n}\n\nexport const BuildmyiqRoutes: FC = () => {\n const { basePath, isStandalone } = useBuildmyiq();\n const location = useLocation();\n\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render if the current path matches basePath (integrated mode).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route index element={gated('buildmyiq.overview', <DashboardPage />)} />\n\n <Route path=\"catalog\" element={gated('buildmyiq.catalog', <CatalogPage />)} />\n <Route\n path=\"catalog/:courseId\"\n element={gated('buildmyiq.catalog', <CourseDetailPage />)}\n />\n\n <Route path=\"learning\" element={gated('buildmyiq.learning', <MyLearningPage />)} />\n <Route\n path=\"learning/:enrollmentId\"\n element={gated('buildmyiq.learning', <LessonPlayerPage />)}\n />\n\n <Route path=\"paths\" element={gated('buildmyiq.paths', <PathsPage />)} />\n\n <Route path=\"authoring\" element={gated('buildmyiq.authoring', <AuthoringPage />)} />\n <Route\n path=\"authoring/:courseId\"\n element={gated('buildmyiq.authoring', <CourseBuilderPage />)}\n />\n\n <Route path=\"assessments\" element={gated('buildmyiq.assessments', <AssessmentsPage />)} />\n <Route\n path=\"assessments/:assessmentId\"\n element={gated('buildmyiq.assessments', <AssessmentDetailPage />)}\n />\n <Route\n path=\"assessments/:assessmentId/take\"\n element={gated('buildmyiq.assessments', <TakeAssessmentPage />)}\n />\n\n <Route path=\"insights\" element={gated('buildmyiq.insights', <InsightsPage />)} />\n\n <Route path=\"submissions\" element={gated('buildmyiq.submissions', <SubmissionsPage />)} />\n <Route\n path=\"submissions/bulk\"\n element={gated('buildmyiq.submissions', <BulkGradingPage />)}\n />\n <Route\n path=\"submissions/:submissionId\"\n element={gated('buildmyiq.submissions', <SubmissionsPage />)}\n />\n <Route\n path=\"proctoring\"\n element={gated('buildmyiq.proctoring', <ProctoringReviewPage />)}\n />\n <Route\n path=\"tutor-review\"\n element={gated('buildmyiq.tutorReview', <TutorReviewPage />)}\n />\n <Route path=\"cohorts\" element={gated('buildmyiq.cohorts', <CohortsPage />)} />\n <Route path=\"skills\" element={gated('buildmyiq.skills', <SkillsPage />)} />\n <Route\n path=\"certificates\"\n element={gated('buildmyiq.certificates', <CertificatesPage />)}\n />\n\n <Route\n path=\"admin-copilot\"\n element={gated('buildmyiq.adminCopilot', <AdminCopilotPage />)}\n />\n <Route\n path=\"integrations\"\n element={gated('buildmyiq.integrations', <LtiPlatformsPage />)}\n />\n <Route path=\"automations\" element={gated('buildmyiq.automations', <AutomationsPage />)} />\n <Route path=\"ai-settings\" element={gated('buildmyiq.aiSettings', <AiSettingsPage />)} />\n {/* Not feature-gated: learners arrive here from their LMS, and the\n launch only exists because a workspace admin registered it. */}\n <Route path=\"lti/launch/:ticketId\" element={<LtiLaunchPage />} />\n\n <Route path=\"*\" element={<Navigate to={normalizedBasePath || '/'} replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"BuildmyiqRoutes.js","names":[],"sources":["../../src/buildmyiq/BuildmyiqRoutes.tsx"],"sourcesContent":["/**\n * Buildmyiq Routes\n * @module buildmyiq\n *\n * Domain routes. The dashboard is eager-loaded (primary landing); all other\n * pages are lazy-loaded. Every route maps to a real `bui*` backend capability.\n * All product routes are gated by feature flags that default to enabled.\n */\n\nimport React, { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { useBuildmyiq } from './context';\nimport { useTr } from './shared/hooks/useTr';\n\n// Eager — primary landing\nimport { DashboardPage } from './pages/DashboardPage';\n\n// Catalog (learner-facing course discovery + detail + enrol)\nconst CatalogPage = lazy(() =>\n import('./pages/catalog/CatalogPage').then((m) => ({ default: m.CatalogPage }))\n);\nconst CourseDetailPage = lazy(() =>\n import('./pages/catalog/CourseDetailPage').then((m) => ({ default: m.CourseDetailPage }))\n);\n\n// My Learning (enrollments + progress + lesson player)\nconst MyLearningPage = lazy(() =>\n import('./pages/learning/MyLearningPage').then((m) => ({ default: m.MyLearningPage }))\n);\nconst LessonPlayerPage = lazy(() =>\n import('./pages/learning/LessonPlayerPage').then((m) => ({ default: m.LessonPlayerPage }))\n);\n\n// Authoring (instructor course management + builder)\nconst AuthoringPage = lazy(() =>\n import('./pages/authoring/AuthoringPage').then((m) => ({ default: m.AuthoringPage }))\n);\nconst CourseBuilderPage = lazy(() =>\n import('./pages/authoring/CourseBuilderPage').then((m) => ({ default: m.CourseBuilderPage }))\n);\n\n// Assessments (list + builder/questions/detail)\nconst AssessmentsPage = lazy(() =>\n import('./pages/assessments/AssessmentsPage').then((m) => ({ default: m.AssessmentsPage }))\n);\nconst AssessmentDetailPage = lazy(() =>\n import('./pages/assessments/AssessmentDetailPage').then((m) => ({\n default: m.AssessmentDetailPage,\n }))\n);\n// Learner-facing: take a published assessment (answer + submit)\nconst TakeAssessmentPage = lazy(() =>\n import('./pages/assessments/TakeAssessmentPage').then((m) => ({ default: m.TakeAssessmentPage }))\n);\n\n// Insights (educator analytics, derived from real data)\nconst InsightsPage = lazy(() =>\n import('./pages/insights/InsightsPage').then((m) => ({ default: m.InsightsPage }))\n);\n\n// Skill Journeys (derived guided paths)\nconst PathsPage = lazy(() =>\n import('./pages/paths/PathsPage').then((m) => ({ default: m.PathsPage }))\n);\n\n// Grading (submissions queue)\nconst SubmissionsPage = lazy(() =>\n import('./pages/submissions/SubmissionsPage').then((m) => ({ default: m.SubmissionsPage }))\n);\nconst BulkGradingPage = lazy(() =>\n import('./pages/submissions/BulkGradingPage').then((m) => ({ default: m.BulkGradingPage }))\n);\n\n// Proctoring review queue — high-stakes exam identity checks (BOFF-4431)\nconst ProctoringReviewPage = lazy(() =>\n import('./pages/proctoring/ProctoringReviewPage').then((m) => ({\n default: m.ProctoringReviewPage,\n }))\n);\n\n// Educator review of AI tutor answers (BOFF-9663)\nconst TutorReviewPage = lazy(() =>\n import('./pages/tutorReview/TutorReviewPage').then((m) => ({ default: m.TutorReviewPage }))\n);\n\n// Cohorts\nconst CohortsPage = lazy(() =>\n import('./pages/cohorts/CohortsPage').then((m) => ({ default: m.CohortsPage }))\n);\n\n// Skills taxonomy\nconst SkillsPage = lazy(() =>\n import('./pages/skills/SkillsPage').then((m) => ({ default: m.SkillsPage }))\n);\n\n// Certificates\nconst CertificatesPage = lazy(() =>\n import('./pages/certificates/CertificatesPage').then((m) => ({ default: m.CertificatesPage }))\n);\n\n// AI Admin Copilot (BOFF-4434) — read-only workspace Q&A\nconst AdminCopilotPage = lazy(() =>\n import('./pages/copilot/AdminCopilotPage').then((m) => ({ default: m.AdminCopilotPage }))\n);\n\n// LMS launch handoff — where a validated LTI 1.3 launch lands (BOFF-9680)\nconst LtiLaunchPage = lazy(() =>\n import('./pages/lti/LtiLaunchPage').then((m) => ({ default: m.LtiLaunchPage }))\n);\n// LMS integrations — LTI 1.3 Advantage platform registry (BOFF-4419)\nconst LtiPlatformsPage = lazy(() =>\n import('./pages/integrations/LtiPlatformsPage').then((m) => ({ default: m.LtiPlatformsPage }))\n);\n\n// L&D workflow automation via FluidGrids (BOFF-9688)\nconst AutomationsPage = lazy(() =>\n import('./pages/automations/AutomationsPage').then((m) => ({ default: m.AutomationsPage }))\n);\n\n// Workspace AI content controls (BOFF-9689)\nconst AiSettingsPage = lazy(() =>\n import('./pages/aiSettings/AiSettingsPage').then((m) => ({ default: m.AiSettingsPage }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n);\n\nfunction FeatureDisabledFallback() {\n const tr = useTr();\n return (\n <div className=\"flex h-full min-h-[200px] items-center justify-center text-sm text-text-secondary\">\n {tr(\n 'buildmyiq.pages.featureDisabled',\n 'This feature is currently disabled. You can enable it from Feature Previews.'\n )}\n </div>\n );\n}\n\nfunction gated(flag: string, element: React.ReactNode, defaultValue = true): React.ReactElement {\n return (\n <FeatureGate flag={flag} defaultValue={defaultValue} fallback={<FeatureDisabledFallback />}>\n {element}\n </FeatureGate>\n );\n}\n\nexport const BuildmyiqRoutes: FC = () => {\n const { basePath, isStandalone } = useBuildmyiq();\n const location = useLocation();\n\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render if the current path matches basePath (integrated mode).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route index element={gated('buildmyiq.overview', <DashboardPage />)} />\n\n <Route path=\"catalog\" element={gated('buildmyiq.catalog', <CatalogPage />)} />\n <Route\n path=\"catalog/:courseId\"\n element={gated('buildmyiq.catalog', <CourseDetailPage />)}\n />\n\n <Route path=\"learning\" element={gated('buildmyiq.learning', <MyLearningPage />)} />\n <Route\n path=\"learning/:enrollmentId\"\n element={gated('buildmyiq.learning', <LessonPlayerPage />)}\n />\n\n <Route path=\"paths\" element={gated('buildmyiq.paths', <PathsPage />)} />\n\n <Route path=\"authoring\" element={gated('buildmyiq.authoring', <AuthoringPage />)} />\n <Route\n path=\"authoring/:courseId\"\n element={gated('buildmyiq.authoring', <CourseBuilderPage />)}\n />\n\n <Route path=\"assessments\" element={gated('buildmyiq.assessments', <AssessmentsPage />)} />\n <Route\n path=\"assessments/:assessmentId\"\n element={gated('buildmyiq.assessments', <AssessmentDetailPage />)}\n />\n <Route\n path=\"assessments/:assessmentId/take\"\n element={gated('buildmyiq.assessments', <TakeAssessmentPage />)}\n />\n\n <Route path=\"insights\" element={gated('buildmyiq.insights', <InsightsPage />)} />\n\n <Route path=\"submissions\" element={gated('buildmyiq.submissions', <SubmissionsPage />)} />\n <Route\n path=\"submissions/bulk\"\n element={gated('buildmyiq.submissions', <BulkGradingPage />)}\n />\n <Route\n path=\"submissions/:submissionId\"\n element={gated('buildmyiq.submissions', <SubmissionsPage />)}\n />\n <Route\n path=\"proctoring\"\n element={gated('buildmyiq.proctoring', <ProctoringReviewPage />)}\n />\n <Route\n path=\"tutor-review\"\n element={gated('buildmyiq.tutor-review', <TutorReviewPage />)}\n />\n <Route path=\"cohorts\" element={gated('buildmyiq.cohorts', <CohortsPage />)} />\n <Route path=\"skills\" element={gated('buildmyiq.skills', <SkillsPage />)} />\n <Route\n path=\"certificates\"\n element={gated('buildmyiq.certificates', <CertificatesPage />)}\n />\n\n <Route\n path=\"admin-copilot\"\n element={gated('buildmyiq.admin-copilot', <AdminCopilotPage />)}\n />\n <Route\n path=\"integrations\"\n element={gated('buildmyiq.integrations', <LtiPlatformsPage />)}\n />\n <Route path=\"automations\" element={gated('buildmyiq.automations', <AutomationsPage />)} />\n <Route path=\"ai-settings\" element={gated('buildmyiq.ai-settings', <AiSettingsPage />)} />\n {/* Not feature-gated: learners arrive here from their LMS, and the\n launch only exists because a workspace admin registered it. */}\n <Route path=\"lti/launch/:ticketId\" element={<LtiLaunchPage />} />\n\n <Route path=\"*\" element={<Navigate to={normalizedBasePath || '/'} replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * BuildMyIQ product analytics (BOFF-3082).
3
+ *
4
+ * The learner funnel events this MFE emits on the shared fe-libs event bus.
5
+ * The MFE never talks to Rybbit: the app shell's RybbitSink forwards the names
6
+ * in `BUILDMYIQ_RYBBIT_EVENTS` (fe-libs `shared/events/buildmyiq.ts`) and the
7
+ * other sinks (BroadcastChannel, OTel, devtools) see every event.
8
+ *
9
+ * The event dictionary — firing rules, dimensions, funnels — is
10
+ * `buildmyiq-specs/business/analytics/ANALYTICS_PLAN.md`. Keep the names here,
11
+ * the fe-libs taxonomy and that plan identical.
12
+ *
13
+ * Payload rule: ids are carried for the bus sinks but RybbitSink strips every
14
+ * identifier key before Rybbit. Never put titles, answers, names or other
15
+ * free text in a payload.
16
+ */
17
+ /** Every BuildMyIQ analytics event this MFE emits. */
18
+ export declare const BUILDMYIQ_ANALYTICS_EVENTS: {
19
+ readonly catalogViewed: "buildmyiq.catalog.viewed";
20
+ readonly courseEnrolled: "buildmyiq.course.enrolled";
21
+ readonly lessonStarted: "buildmyiq.lesson.started";
22
+ readonly lessonCompleted: "buildmyiq.lesson.completed";
23
+ readonly assessmentSubmitted: "buildmyiq.assessment.submitted";
24
+ readonly courseCompleted: "buildmyiq.course.completed";
25
+ readonly certificateIssued: "buildmyiq.certificate.issued";
26
+ };
27
+ export type BuildmyiqAnalyticsEventName = (typeof BUILDMYIQ_ANALYTICS_EVENTS)[keyof typeof BUILDMYIQ_ANALYTICS_EVENTS];
28
+ export interface CatalogViewedInput {
29
+ courseCount: number;
30
+ }
31
+ export interface CourseEventInput {
32
+ courseId?: string;
33
+ enrollmentId?: string;
34
+ }
35
+ export interface LessonEventInput {
36
+ courseId?: string;
37
+ enrollmentId: string;
38
+ lessonId: string;
39
+ lessonType?: string;
40
+ }
41
+ export interface AssessmentSubmittedInput {
42
+ assessmentId: string;
43
+ submissionId?: string;
44
+ assessmentType?: string;
45
+ proctored: boolean;
46
+ answeredCount: number;
47
+ questionCount: number;
48
+ }
49
+ export interface CertificateIssuedInput {
50
+ certificateId?: string;
51
+ courseId?: string;
52
+ expires: boolean;
53
+ }
54
+ /** A single bus emission: the event name and its payload. */
55
+ export interface BuildmyiqAnalyticsEmission {
56
+ name: BuildmyiqAnalyticsEventName;
57
+ payload: Record<string, string | number | boolean | undefined>;
58
+ }
59
+ /**
60
+ * Pure payload builders. Kept separate from the hook so the exact shape that
61
+ * reaches the bus is unit-testable without a React tree.
62
+ */
63
+ export declare const buildmyiqAnalytics: {
64
+ catalogViewed(workspaceId: string | undefined, input: CatalogViewedInput): {
65
+ name: "buildmyiq.catalog.viewed";
66
+ payload: {
67
+ workspaceId: string | undefined;
68
+ courseCount: number | undefined;
69
+ };
70
+ };
71
+ courseEnrolled(workspaceId: string | undefined, input: CourseEventInput): {
72
+ name: "buildmyiq.course.enrolled";
73
+ payload: {
74
+ workspaceId: string | undefined;
75
+ courseId: string | undefined;
76
+ enrollmentId: string | undefined;
77
+ };
78
+ };
79
+ lessonStarted(workspaceId: string | undefined, input: LessonEventInput): {
80
+ name: "buildmyiq.lesson.started";
81
+ payload: {
82
+ workspaceId: string | undefined;
83
+ courseId: string | undefined;
84
+ enrollmentId: string;
85
+ lessonId: string;
86
+ lessonType: string | undefined;
87
+ };
88
+ };
89
+ lessonCompleted(workspaceId: string | undefined, input: LessonEventInput): {
90
+ name: "buildmyiq.lesson.completed";
91
+ payload: {
92
+ workspaceId: string | undefined;
93
+ courseId: string | undefined;
94
+ enrollmentId: string;
95
+ lessonId: string;
96
+ lessonType: string | undefined;
97
+ };
98
+ };
99
+ assessmentSubmitted(workspaceId: string | undefined, input: AssessmentSubmittedInput): {
100
+ name: "buildmyiq.assessment.submitted";
101
+ payload: {
102
+ workspaceId: string | undefined;
103
+ assessmentId: string;
104
+ submissionId: string | undefined;
105
+ assessmentType: string | undefined;
106
+ proctored: boolean;
107
+ answeredCount: number | undefined;
108
+ questionCount: number | undefined;
109
+ };
110
+ };
111
+ courseCompleted(workspaceId: string | undefined, input: CourseEventInput): {
112
+ name: "buildmyiq.course.completed";
113
+ payload: {
114
+ workspaceId: string | undefined;
115
+ courseId: string | undefined;
116
+ enrollmentId: string | undefined;
117
+ };
118
+ };
119
+ certificateIssued(workspaceId: string | undefined, input: CertificateIssuedInput): {
120
+ name: "buildmyiq.certificate.issued";
121
+ payload: {
122
+ workspaceId: string | undefined;
123
+ certificateId: string | undefined;
124
+ courseId: string | undefined;
125
+ expires: boolean;
126
+ };
127
+ };
128
+ };
129
+ //# sourceMappingURL=buildmyiqAnalytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildmyiqAnalytics.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/analytics/buildmyiqAnalytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,sDAAsD;AACtD,eAAO,MAAM,0BAA0B;;;;;;;;CAQ7B,CAAC;AAEX,MAAM,MAAM,2BAA2B,GACrC,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,OAAO,0BAA0B,CAAC,CAAC;AAE/E,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,6DAA6D;AAC7D,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,2BAA2B,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;CAChE;AAOD;;;GAGG;AACH,eAAO,MAAM,kBAAkB;+BACF,MAAM,GAAG,SAAS,SAAS,kBAAkB;;;;;;;gCAM5C,MAAM,GAAG,SAAS,SAAS,gBAAgB;;;;;;;;+BAM5C,MAAM,GAAG,SAAS,SAAS,gBAAgB;;;;;;;;;;iCAYzC,MAAM,GAAG,SAAS,SAAS,gBAAgB;;;;;;;;;;qCAYvC,MAAM,GAAG,SAAS,SAAS,wBAAwB;;;;;;;;;;;;iCAcvD,MAAM,GAAG,SAAS,SAAS,gBAAgB;;;;;;;;mCAMzC,MAAM,GAAG,SAAS,SAAS,sBAAsB;;;;;;;;;CAWjF,CAAC"}
@@ -0,0 +1,97 @@
1
+ //#region src/buildmyiq/analytics/buildmyiqAnalytics.ts
2
+ var e = {
3
+ catalogViewed: "buildmyiq.catalog.viewed",
4
+ courseEnrolled: "buildmyiq.course.enrolled",
5
+ lessonStarted: "buildmyiq.lesson.started",
6
+ lessonCompleted: "buildmyiq.lesson.completed",
7
+ assessmentSubmitted: "buildmyiq.assessment.submitted",
8
+ courseCompleted: "buildmyiq.course.completed",
9
+ certificateIssued: "buildmyiq.certificate.issued"
10
+ };
11
+ function t(e) {
12
+ return Number.isFinite(e) && e >= 0 ? Math.floor(e) : void 0;
13
+ }
14
+ var n = {
15
+ catalogViewed(n, r) {
16
+ return {
17
+ name: e.catalogViewed,
18
+ payload: {
19
+ workspaceId: n,
20
+ courseCount: t(r.courseCount)
21
+ }
22
+ };
23
+ },
24
+ courseEnrolled(t, n) {
25
+ return {
26
+ name: e.courseEnrolled,
27
+ payload: {
28
+ workspaceId: t,
29
+ courseId: n.courseId,
30
+ enrollmentId: n.enrollmentId
31
+ }
32
+ };
33
+ },
34
+ lessonStarted(t, n) {
35
+ return {
36
+ name: e.lessonStarted,
37
+ payload: {
38
+ workspaceId: t,
39
+ courseId: n.courseId,
40
+ enrollmentId: n.enrollmentId,
41
+ lessonId: n.lessonId,
42
+ lessonType: n.lessonType
43
+ }
44
+ };
45
+ },
46
+ lessonCompleted(t, n) {
47
+ return {
48
+ name: e.lessonCompleted,
49
+ payload: {
50
+ workspaceId: t,
51
+ courseId: n.courseId,
52
+ enrollmentId: n.enrollmentId,
53
+ lessonId: n.lessonId,
54
+ lessonType: n.lessonType
55
+ }
56
+ };
57
+ },
58
+ assessmentSubmitted(n, r) {
59
+ return {
60
+ name: e.assessmentSubmitted,
61
+ payload: {
62
+ workspaceId: n,
63
+ assessmentId: r.assessmentId,
64
+ submissionId: r.submissionId,
65
+ assessmentType: r.assessmentType,
66
+ proctored: r.proctored,
67
+ answeredCount: t(r.answeredCount),
68
+ questionCount: t(r.questionCount)
69
+ }
70
+ };
71
+ },
72
+ courseCompleted(t, n) {
73
+ return {
74
+ name: e.courseCompleted,
75
+ payload: {
76
+ workspaceId: t,
77
+ courseId: n.courseId,
78
+ enrollmentId: n.enrollmentId
79
+ }
80
+ };
81
+ },
82
+ certificateIssued(t, n) {
83
+ return {
84
+ name: e.certificateIssued,
85
+ payload: {
86
+ workspaceId: t,
87
+ certificateId: n.certificateId,
88
+ courseId: n.courseId,
89
+ expires: n.expires
90
+ }
91
+ };
92
+ }
93
+ };
94
+ //#endregion
95
+ export { e as BUILDMYIQ_ANALYTICS_EVENTS, n as buildmyiqAnalytics };
96
+
97
+ //# sourceMappingURL=buildmyiqAnalytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildmyiqAnalytics.js","names":[],"sources":["../../../src/buildmyiq/analytics/buildmyiqAnalytics.ts"],"sourcesContent":["/**\n * BuildMyIQ product analytics (BOFF-3082).\n *\n * The learner funnel events this MFE emits on the shared fe-libs event bus.\n * The MFE never talks to Rybbit: the app shell's RybbitSink forwards the names\n * in `BUILDMYIQ_RYBBIT_EVENTS` (fe-libs `shared/events/buildmyiq.ts`) and the\n * other sinks (BroadcastChannel, OTel, devtools) see every event.\n *\n * The event dictionary — firing rules, dimensions, funnels — is\n * `buildmyiq-specs/business/analytics/ANALYTICS_PLAN.md`. Keep the names here,\n * the fe-libs taxonomy and that plan identical.\n *\n * Payload rule: ids are carried for the bus sinks but RybbitSink strips every\n * identifier key before Rybbit. Never put titles, answers, names or other\n * free text in a payload.\n */\n\n/** Every BuildMyIQ analytics event this MFE emits. */\nexport const BUILDMYIQ_ANALYTICS_EVENTS = {\n catalogViewed: 'buildmyiq.catalog.viewed',\n courseEnrolled: 'buildmyiq.course.enrolled',\n lessonStarted: 'buildmyiq.lesson.started',\n lessonCompleted: 'buildmyiq.lesson.completed',\n assessmentSubmitted: 'buildmyiq.assessment.submitted',\n courseCompleted: 'buildmyiq.course.completed',\n certificateIssued: 'buildmyiq.certificate.issued',\n} as const;\n\nexport type BuildmyiqAnalyticsEventName =\n (typeof BUILDMYIQ_ANALYTICS_EVENTS)[keyof typeof BUILDMYIQ_ANALYTICS_EVENTS];\n\nexport interface CatalogViewedInput {\n courseCount: number;\n}\n\nexport interface CourseEventInput {\n courseId?: string;\n enrollmentId?: string;\n}\n\nexport interface LessonEventInput {\n courseId?: string;\n enrollmentId: string;\n lessonId: string;\n lessonType?: string;\n}\n\nexport interface AssessmentSubmittedInput {\n assessmentId: string;\n submissionId?: string;\n assessmentType?: string;\n proctored: boolean;\n answeredCount: number;\n questionCount: number;\n}\n\nexport interface CertificateIssuedInput {\n certificateId?: string;\n courseId?: string;\n expires: boolean;\n}\n\n/** A single bus emission: the event name and its payload. */\nexport interface BuildmyiqAnalyticsEmission {\n name: BuildmyiqAnalyticsEventName;\n payload: Record<string, string | number | boolean | undefined>;\n}\n\n/** Non-negative integer, or undefined for anything that is not a finite count. */\nfunction count(value: number): number | undefined {\n return Number.isFinite(value) && value >= 0 ? Math.floor(value) : undefined;\n}\n\n/**\n * Pure payload builders. Kept separate from the hook so the exact shape that\n * reaches the bus is unit-testable without a React tree.\n */\nexport const buildmyiqAnalytics = {\n catalogViewed(workspaceId: string | undefined, input: CatalogViewedInput) {\n return {\n name: BUILDMYIQ_ANALYTICS_EVENTS.catalogViewed,\n payload: { workspaceId, courseCount: count(input.courseCount) },\n } satisfies BuildmyiqAnalyticsEmission;\n },\n courseEnrolled(workspaceId: string | undefined, input: CourseEventInput) {\n return {\n name: BUILDMYIQ_ANALYTICS_EVENTS.courseEnrolled,\n payload: { workspaceId, courseId: input.courseId, enrollmentId: input.enrollmentId },\n } satisfies BuildmyiqAnalyticsEmission;\n },\n lessonStarted(workspaceId: string | undefined, input: LessonEventInput) {\n return {\n name: BUILDMYIQ_ANALYTICS_EVENTS.lessonStarted,\n payload: {\n workspaceId,\n courseId: input.courseId,\n enrollmentId: input.enrollmentId,\n lessonId: input.lessonId,\n lessonType: input.lessonType,\n },\n } satisfies BuildmyiqAnalyticsEmission;\n },\n lessonCompleted(workspaceId: string | undefined, input: LessonEventInput) {\n return {\n name: BUILDMYIQ_ANALYTICS_EVENTS.lessonCompleted,\n payload: {\n workspaceId,\n courseId: input.courseId,\n enrollmentId: input.enrollmentId,\n lessonId: input.lessonId,\n lessonType: input.lessonType,\n },\n } satisfies BuildmyiqAnalyticsEmission;\n },\n assessmentSubmitted(workspaceId: string | undefined, input: AssessmentSubmittedInput) {\n return {\n name: BUILDMYIQ_ANALYTICS_EVENTS.assessmentSubmitted,\n payload: {\n workspaceId,\n assessmentId: input.assessmentId,\n submissionId: input.submissionId,\n assessmentType: input.assessmentType,\n proctored: input.proctored,\n answeredCount: count(input.answeredCount),\n questionCount: count(input.questionCount),\n },\n } satisfies BuildmyiqAnalyticsEmission;\n },\n courseCompleted(workspaceId: string | undefined, input: CourseEventInput) {\n return {\n name: BUILDMYIQ_ANALYTICS_EVENTS.courseCompleted,\n payload: { workspaceId, courseId: input.courseId, enrollmentId: input.enrollmentId },\n } satisfies BuildmyiqAnalyticsEmission;\n },\n certificateIssued(workspaceId: string | undefined, input: CertificateIssuedInput) {\n return {\n name: BUILDMYIQ_ANALYTICS_EVENTS.certificateIssued,\n payload: {\n workspaceId,\n certificateId: input.certificateId,\n courseId: input.courseId,\n expires: input.expires,\n },\n } satisfies BuildmyiqAnalyticsEmission;\n },\n};\n"],"mappings":";AAkBA,IAAa,IAA6B;CACxC,eAAe;CACf,gBAAgB;CAChB,eAAe;CACf,iBAAiB;CACjB,qBAAqB;CACrB,iBAAiB;CACjB,mBAAmB;AACrB;AA2CA,SAAS,EAAM,GAAmC;CAChD,OAAO,OAAO,SAAS,CAAK,KAAK,KAAS,IAAI,KAAK,MAAM,CAAK,IAAI,KAAA;AACpE;AAMA,IAAa,IAAqB;CAChC,cAAc,GAAiC,GAA2B;EACxE,OAAO;GACL,MAAM,EAA2B;GACjC,SAAS;IAAE;IAAa,aAAa,EAAM,EAAM,WAAW;GAAE;EAChE;CACF;CACA,eAAe,GAAiC,GAAyB;EACvE,OAAO;GACL,MAAM,EAA2B;GACjC,SAAS;IAAE;IAAa,UAAU,EAAM;IAAU,cAAc,EAAM;GAAa;EACrF;CACF;CACA,cAAc,GAAiC,GAAyB;EACtE,OAAO;GACL,MAAM,EAA2B;GACjC,SAAS;IACP;IACA,UAAU,EAAM;IAChB,cAAc,EAAM;IACpB,UAAU,EAAM;IAChB,YAAY,EAAM;GACpB;EACF;CACF;CACA,gBAAgB,GAAiC,GAAyB;EACxE,OAAO;GACL,MAAM,EAA2B;GACjC,SAAS;IACP;IACA,UAAU,EAAM;IAChB,cAAc,EAAM;IACpB,UAAU,EAAM;IAChB,YAAY,EAAM;GACpB;EACF;CACF;CACA,oBAAoB,GAAiC,GAAiC;EACpF,OAAO;GACL,MAAM,EAA2B;GACjC,SAAS;IACP;IACA,cAAc,EAAM;IACpB,cAAc,EAAM;IACpB,gBAAgB,EAAM;IACtB,WAAW,EAAM;IACjB,eAAe,EAAM,EAAM,aAAa;IACxC,eAAe,EAAM,EAAM,aAAa;GAC1C;EACF;CACF;CACA,gBAAgB,GAAiC,GAAyB;EACxE,OAAO;GACL,MAAM,EAA2B;GACjC,SAAS;IAAE;IAAa,UAAU,EAAM;IAAU,cAAc,EAAM;GAAa;EACrF;CACF;CACA,kBAAkB,GAAiC,GAA+B;EAChF,OAAO;GACL,MAAM,EAA2B;GACjC,SAAS;IACP;IACA,eAAe,EAAM;IACrB,UAAU,EAAM;IAChB,SAAS,EAAM;GACjB;EACF;CACF;AACF"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * BuildMyIQ product analytics (BOFF-3082).
3
+ * @module buildmyiq/analytics
4
+ */
5
+ export { BUILDMYIQ_ANALYTICS_EVENTS, buildmyiqAnalytics, type BuildmyiqAnalyticsEventName, type BuildmyiqAnalyticsEmission, } from './buildmyiqAnalytics';
6
+ export { useBuildmyiqAnalytics, type BuildmyiqAnalyticsTrackers } from './useBuildmyiqAnalytics';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/analytics/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,KAAK,0BAA0B,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import "./buildmyiqAnalytics.js";
2
+ import "./useBuildmyiqAnalytics.js";
@@ -0,0 +1,12 @@
1
+ import { AssessmentSubmittedInput, CatalogViewedInput, CertificateIssuedInput, CourseEventInput, LessonEventInput } from './buildmyiqAnalytics';
2
+ export interface BuildmyiqAnalyticsTrackers {
3
+ trackCatalogViewed: (input: CatalogViewedInput) => void;
4
+ trackCourseEnrolled: (input: CourseEventInput) => void;
5
+ trackLessonStarted: (input: LessonEventInput) => void;
6
+ trackLessonCompleted: (input: LessonEventInput) => void;
7
+ trackAssessmentSubmitted: (input: AssessmentSubmittedInput) => void;
8
+ trackCourseCompleted: (input: CourseEventInput) => void;
9
+ trackCertificateIssued: (input: CertificateIssuedInput) => void;
10
+ }
11
+ export declare function useBuildmyiqAnalytics(): BuildmyiqAnalyticsTrackers;
12
+ //# sourceMappingURL=useBuildmyiqAnalytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBuildmyiqAnalytics.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/analytics/useBuildmyiqAnalytics.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,wBAAwB,EAE7B,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACxD,mBAAmB,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACvD,kBAAkB,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACtD,oBAAoB,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACxD,wBAAwB,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,CAAC;IACpE,oBAAoB,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACxD,sBAAsB,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACjE;AAED,wBAAgB,qBAAqB,IAAI,0BAA0B,CAgClE"}
@@ -0,0 +1,26 @@
1
+ import { useBuildmyiq as e } from "../context/BuildmyiqContext.js";
2
+ import "../context/index.js";
3
+ import { buildmyiqAnalytics as t } from "./buildmyiqAnalytics.js";
4
+ import { useCallback as n, useMemo as r } from "react";
5
+ import { useEventBus as i } from "@burdenoff/fe-libs/shared/events";
6
+ //#region src/buildmyiq/analytics/useBuildmyiqAnalytics.ts
7
+ function a() {
8
+ let a = i(), { workspaceId: o } = e(), s = n((e) => {
9
+ try {
10
+ a.emitCustom(e.name, e.payload);
11
+ } catch {}
12
+ }, [a]);
13
+ return r(() => ({
14
+ trackCatalogViewed: (e) => s(t.catalogViewed(o, e)),
15
+ trackCourseEnrolled: (e) => s(t.courseEnrolled(o, e)),
16
+ trackLessonStarted: (e) => s(t.lessonStarted(o, e)),
17
+ trackLessonCompleted: (e) => s(t.lessonCompleted(o, e)),
18
+ trackAssessmentSubmitted: (e) => s(t.assessmentSubmitted(o, e)),
19
+ trackCourseCompleted: (e) => s(t.courseCompleted(o, e)),
20
+ trackCertificateIssued: (e) => s(t.certificateIssued(o, e))
21
+ }), [s, o]);
22
+ }
23
+ //#endregion
24
+ export { a as useBuildmyiqAnalytics };
25
+
26
+ //# sourceMappingURL=useBuildmyiqAnalytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBuildmyiqAnalytics.js","names":[],"sources":["../../../src/buildmyiq/analytics/useBuildmyiqAnalytics.ts"],"sourcesContent":["/**\n * React hook that emits the BuildMyIQ funnel events on the shared event bus.\n *\n * Every tracker is fire-and-forget and never throws: analytics must not be able\n * to break a learner's enrolment, lesson or submission. Outside an app shell\n * (Storybook, standalone) the bus context is a no-op, so the calls are inert.\n */\nimport { useCallback, useMemo } from 'react';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\n\nimport { useBuildmyiq } from '../context';\nimport {\n buildmyiqAnalytics,\n type AssessmentSubmittedInput,\n type BuildmyiqAnalyticsEmission,\n type CatalogViewedInput,\n type CertificateIssuedInput,\n type CourseEventInput,\n type LessonEventInput,\n} from './buildmyiqAnalytics';\n\nexport interface BuildmyiqAnalyticsTrackers {\n trackCatalogViewed: (input: CatalogViewedInput) => void;\n trackCourseEnrolled: (input: CourseEventInput) => void;\n trackLessonStarted: (input: LessonEventInput) => void;\n trackLessonCompleted: (input: LessonEventInput) => void;\n trackAssessmentSubmitted: (input: AssessmentSubmittedInput) => void;\n trackCourseCompleted: (input: CourseEventInput) => void;\n trackCertificateIssued: (input: CertificateIssuedInput) => void;\n}\n\nexport function useBuildmyiqAnalytics(): BuildmyiqAnalyticsTrackers {\n const bus = useEventBus();\n const { workspaceId } = useBuildmyiq();\n\n const send = useCallback(\n (emission: BuildmyiqAnalyticsEmission) => {\n try {\n // emitCustom: the names are registered in fe-libs' BurdenoffEventName\n // union from the fe-libs release that ships the BuildMyIQ taxonomy; the\n // runtime path (bus.onAny -> sinks) is identical to emit().\n bus.emitCustom(emission.name, emission.payload);\n } catch {\n /* analytics is best-effort and must never break the learner flow */\n }\n },\n [bus]\n );\n\n return useMemo(\n () => ({\n trackCatalogViewed: (input) => send(buildmyiqAnalytics.catalogViewed(workspaceId, input)),\n trackCourseEnrolled: (input) => send(buildmyiqAnalytics.courseEnrolled(workspaceId, input)),\n trackLessonStarted: (input) => send(buildmyiqAnalytics.lessonStarted(workspaceId, input)),\n trackLessonCompleted: (input) => send(buildmyiqAnalytics.lessonCompleted(workspaceId, input)),\n trackAssessmentSubmitted: (input) =>\n send(buildmyiqAnalytics.assessmentSubmitted(workspaceId, input)),\n trackCourseCompleted: (input) => send(buildmyiqAnalytics.courseCompleted(workspaceId, input)),\n trackCertificateIssued: (input) =>\n send(buildmyiqAnalytics.certificateIssued(workspaceId, input)),\n }),\n [send, workspaceId]\n );\n}\n"],"mappings":";;;;;;AA+BA,SAAgB,IAAoD;CAClE,IAAM,IAAM,EAAY,GAClB,EAAE,mBAAgB,EAAa,GAE/B,IAAO,GACV,MAAyC;EACxC,IAAI;GAIF,EAAI,WAAW,EAAS,MAAM,EAAS,OAAO;EAChD,QAAQ,CAER;CACF,GACA,CAAC,CAAG,CACN;CAEA,OAAO,SACE;EACL,qBAAqB,MAAU,EAAK,EAAmB,cAAc,GAAa,CAAK,CAAC;EACxF,sBAAsB,MAAU,EAAK,EAAmB,eAAe,GAAa,CAAK,CAAC;EAC1F,qBAAqB,MAAU,EAAK,EAAmB,cAAc,GAAa,CAAK,CAAC;EACxF,uBAAuB,MAAU,EAAK,EAAmB,gBAAgB,GAAa,CAAK,CAAC;EAC5F,2BAA2B,MACzB,EAAK,EAAmB,oBAAoB,GAAa,CAAK,CAAC;EACjE,uBAAuB,MAAU,EAAK,EAAmB,gBAAgB,GAAa,CAAK,CAAC;EAC5F,yBAAyB,MACvB,EAAK,EAAmB,kBAAkB,GAAa,CAAK,CAAC;CACjE,IACA,CAAC,GAAM,CAAW,CACpB;AACF"}
@@ -47,7 +47,7 @@ var p = 600, m = {
47
47
  }),
48
48
  r(`buildmyiq.accessibility.rules.${n.rule}`, m[n.rule] ?? n.rule),
49
49
  n.line != null && /* @__PURE__ */ d("span", {
50
- className: "ml-1.5 text-xs font-normal text-text-secondary",
50
+ className: "ms-1.5 text-xs font-normal text-text-secondary",
51
51
  children: r("buildmyiq.accessibility.line", "line {{line}}", { line: n.line })
52
52
  })
53
53
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"AccessibilityChecker.js","names":[],"sources":["../../../src/buildmyiq/components/AccessibilityChecker.tsx"],"sourcesContent":["/**\n * Accessibility checker UI (BOFF-4423).\n * @module buildmyiq/components/AccessibilityChecker\n *\n * - `AccessibilityChecker` — live, debounced check of a draft inside an\n * authoring form (lesson body, question prompt/options/explanation).\n * - `AccessibilityIssueList` — the issue list, shared with the course-level\n * report on the Course builder's Accessibility tab.\n *\n * Rules run server-side (`buiCheckContentAccessibility`); this only renders.\n * Status changes are announced through a polite live region so screen-reader\n * authors hear the result without losing their place in the form.\n */\n\nimport type { FC } from 'react';\nimport { AlertTriangle, CheckCircle2, ExternalLink, Loader2, XCircle } from 'lucide-react';\n\nimport {\n hasCheckableContent,\n useBuiCheckContentAccessibility,\n useDebouncedValue,\n} from '../hooks/useBuiAccessibility';\nimport { useTr } from '../shared/hooks/useTr';\nimport type {\n BuiAccessibilityIssue,\n BuiAccessibilityReport,\n BuiAccessibilityRule,\n BuiCheckContentAccessibilityInput,\n} from '../types';\nimport { cn } from '../utils/cn';\nimport { wcagUnderstandingUrl } from '../utils/wcag';\n\nconst DEBOUNCE_MS = 600;\n\n/** Short English title per rule (translatable via `buildmyiq.accessibility.rules.<RULE>`). */\nconst RULE_TITLES: Record<BuiAccessibilityRule, string> = {\n IMAGE_ALT_MISSING: 'Image without alt text',\n IMAGE_ALT_EMPTY: 'Image marked decorative',\n IMAGE_ALT_NOT_DESCRIPTIVE: 'Alt text is not descriptive',\n LINK_TEXT_EMPTY: 'Link without text',\n LINK_TEXT_NOT_DESCRIPTIVE: 'Vague link text',\n LINK_TEXT_RAW_URL: 'Raw URL as link text',\n HEADING_EMPTY: 'Empty heading',\n HEADING_LEVEL_SKIPPED: 'Skipped heading level',\n TABLE_HEADER_MISSING: 'Table without headers',\n MEDIA_CAPTIONS_MISSING: 'Video without captions',\n MEDIA_AUTOPLAY: 'Autoplaying media',\n MEDIA_TRANSCRIPT_MISSING: 'No transcript',\n IFRAME_TITLE_MISSING: 'Embed without a title',\n MOVING_CONTENT: 'Moving or blinking text',\n COLOR_STYLING: 'Inline text colour',\n SENSORY_INSTRUCTION: 'Colour or position-only instruction',\n OPTION_LABEL_EMPTY: 'Answer option without a label',\n OPTION_LABEL_DUPLICATE: 'Duplicate answer options',\n};\n\nconst IssueRow: FC<{ issue: BuiAccessibilityIssue }> = ({ issue }) => {\n const tr = useTr();\n const isError = issue.severity === 'ERROR';\n const Icon = isError ? XCircle : AlertTriangle;\n const url = wcagUnderstandingUrl(issue.wcag);\n const wcagLabel = tr('buildmyiq.accessibility.wcagLink', 'WCAG {{criterion}} (Level {{level}})', {\n criterion: issue.wcag,\n level: issue.wcagLevel,\n });\n return (\n <li className=\"flex gap-2.5 py-2.5 first:pt-0 last:pb-0\">\n <Icon\n aria-hidden=\"true\"\n className={cn(\n 'mt-0.5 h-4 w-4 shrink-0',\n isError ? 'text-status-error-text' : 'text-status-warning-text'\n )}\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n <span className=\"sr-only\">\n {isError\n ? tr('buildmyiq.accessibility.severity.error', 'Error:')\n : tr('buildmyiq.accessibility.severity.warning', 'Warning:')}{' '}\n </span>\n {tr(`buildmyiq.accessibility.rules.${issue.rule}`, RULE_TITLES[issue.rule] ?? issue.rule)}\n {issue.line != null && (\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n {tr('buildmyiq.accessibility.line', 'line {{line}}', { line: issue.line })}\n </span>\n )}\n </p>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {tr(`buildmyiq.accessibility.messages.${issue.rule}`, issue.message)}\n </p>\n {issue.excerpt && (\n <code className=\"mt-1 block truncate rounded bg-bg-sunken px-1.5 py-0.5 font-mono text-xs text-text-secondary\">\n {issue.excerpt}\n </code>\n )}\n {url ? (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className=\"mt-1 inline-flex items-center gap-1 text-xs text-text-secondary underline underline-offset-2 hover:text-text-primary\"\n >\n {wcagLabel}\n <ExternalLink aria-hidden=\"true\" className=\"h-3 w-3\" />\n <span className=\"sr-only\">\n {tr('buildmyiq.accessibility.opensNewTab', '(opens in a new tab)')}\n </span>\n </a>\n ) : (\n <span className=\"mt-1 block text-xs text-text-secondary\">{wcagLabel}</span>\n )}\n </div>\n </li>\n );\n};\n\nexport const AccessibilityIssueList: FC<{\n issues: BuiAccessibilityIssue[];\n className?: string;\n}> = ({ issues, className }) => (\n <ul className={cn('divide-y divide-border-seam', className)}>\n {issues.map((issue, index) => (\n <IssueRow key={`${issue.rule}-${issue.line ?? 'x'}-${index}`} issue={issue} />\n ))}\n </ul>\n);\n\n/** One-line result summary, e.g. \"2 errors · 1 warning\". */\nexport const useAccessibilitySummary = (): ((\n report: Pick<BuiAccessibilityReport, 'errorCount' | 'warningCount'>\n) => string) => {\n const tr = useTr();\n return (report) => {\n if (report.errorCount === 0 && report.warningCount === 0) {\n return tr('buildmyiq.accessibility.noIssues', 'No accessibility issues found');\n }\n const parts: string[] = [];\n if (report.errorCount > 0) {\n parts.push(\n report.errorCount === 1\n ? tr('buildmyiq.accessibility.oneError', '1 error')\n : tr('buildmyiq.accessibility.errors', '{{count}} errors', { count: report.errorCount })\n );\n }\n if (report.warningCount > 0) {\n parts.push(\n report.warningCount === 1\n ? tr('buildmyiq.accessibility.oneWarning', '1 warning')\n : tr('buildmyiq.accessibility.warnings', '{{count}} warnings', {\n count: report.warningCount,\n })\n );\n }\n return parts.join(' · ');\n };\n};\n\ntype StatusTone = 'checking' | 'error' | 'warning' | 'clean';\n\nconst STATUS_ICONS = {\n checking: Loader2,\n error: XCircle,\n warning: AlertTriangle,\n clean: CheckCircle2,\n} as const;\n\nconst STATUS_TONE_CLASS: Record<StatusTone, string> = {\n checking: 'motion-safe:animate-spin',\n error: 'text-status-error-text',\n warning: 'text-status-warning-text',\n clean: 'text-status-success-text',\n};\n\nconst statusTone = (stale: boolean, report: BuiAccessibilityReport | undefined): StatusTone => {\n if (stale || !report) return 'checking';\n if (report.errorCount > 0) return 'error';\n if (report.warningCount > 0) return 'warning';\n return 'clean';\n};\n\ninterface AccessibilityCheckerProps {\n /** Draft to check; re-checked 600 ms after it stops changing. */\n input: BuiCheckContentAccessibilityInput;\n id?: string;\n className?: string;\n}\n\n/**\n * Live accessibility check for an authoring form. Advisory: it never blocks\n * saving, it tells the author what to fix and why (with the WCAG criterion).\n */\nexport const AccessibilityChecker: FC<AccessibilityCheckerProps> = ({\n input,\n id = 'accessibility-check',\n className,\n}) => {\n const tr = useTr();\n const summarise = useAccessibilitySummary();\n const debounced = useDebouncedValue(input, DEBOUNCE_MS);\n const query = useBuiCheckContentAccessibility(debounced);\n // A cleared draft is idle again: never show the previous draft's result.\n const checkable = hasCheckableContent(input);\n const report = checkable ? query.data : undefined;\n // The shown report belongs to an older draft while the author is still\n // typing (debounce pending), while a request is in flight, or while React\n // Query is showing the previous key's data as a placeholder.\n const stale =\n JSON.stringify(input) !== JSON.stringify(debounced) ||\n query.isFetching ||\n query.isPlaceholderData;\n const failed = checkable && !stale && query.isError;\n const idle = !checkable;\n const headingId = `${id}-heading`;\n\n let status: string;\n if (idle) {\n status = tr(\n 'buildmyiq.accessibility.idle',\n 'Start writing and the content will be checked for accessibility.'\n );\n } else if (failed) {\n status = tr(\n 'buildmyiq.accessibility.unavailable',\n 'Accessibility check is unavailable right now.'\n );\n } else if (stale || !report) {\n status = tr('buildmyiq.accessibility.checking', 'Checking accessibility…');\n } else {\n status = summarise(report);\n }\n // Screen readers only hear settled results, never the transient \"checking\"\n // state, so typing does not trigger two announcements per pause.\n const announcement = idle || stale || (!report && !failed) ? '' : status;\n\n const tone = statusTone(stale, report);\n const StatusIcon = STATUS_ICONS[tone];\n\n return (\n <section\n aria-labelledby={headingId}\n data-testid=\"accessibility-checker\"\n className={cn('rounded-xl border border-border-seam bg-bg-sunken/40 p-3', className)}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <h3 id={headingId} className=\"text-sm font-semibold text-text-primary\">\n {tr('buildmyiq.accessibility.title', 'Accessibility check')}\n </h3>\n <p\n aria-hidden=\"true\"\n data-testid=\"accessibility-checker-status\"\n className=\"inline-flex items-center gap-1.5 text-xs text-text-secondary\"\n >\n {!idle && !failed && (\n <StatusIcon className={cn('h-3.5 w-3.5', STATUS_TONE_CLASS[tone])} />\n )}\n {status}\n </p>\n <p role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </p>\n </div>\n {report && !failed && report.issues.length > 0 && (\n <AccessibilityIssueList issues={report.issues} className=\"mt-3\" />\n )}\n {report?.truncated && (\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr(\n 'buildmyiq.accessibility.truncated',\n 'Only the first part of this content was checked because it is very long.'\n )}\n </p>\n )}\n </section>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AccessibilityChecker.js","names":[],"sources":["../../../src/buildmyiq/components/AccessibilityChecker.tsx"],"sourcesContent":["/**\n * Accessibility checker UI (BOFF-4423).\n * @module buildmyiq/components/AccessibilityChecker\n *\n * - `AccessibilityChecker` — live, debounced check of a draft inside an\n * authoring form (lesson body, question prompt/options/explanation).\n * - `AccessibilityIssueList` — the issue list, shared with the course-level\n * report on the Course builder's Accessibility tab.\n *\n * Rules run server-side (`buiCheckContentAccessibility`); this only renders.\n * Status changes are announced through a polite live region so screen-reader\n * authors hear the result without losing their place in the form.\n */\n\nimport type { FC } from 'react';\nimport { AlertTriangle, CheckCircle2, ExternalLink, Loader2, XCircle } from 'lucide-react';\n\nimport {\n hasCheckableContent,\n useBuiCheckContentAccessibility,\n useDebouncedValue,\n} from '../hooks/useBuiAccessibility';\nimport { useTr } from '../shared/hooks/useTr';\nimport type {\n BuiAccessibilityIssue,\n BuiAccessibilityReport,\n BuiAccessibilityRule,\n BuiCheckContentAccessibilityInput,\n} from '../types';\nimport { cn } from '../utils/cn';\nimport { wcagUnderstandingUrl } from '../utils/wcag';\n\nconst DEBOUNCE_MS = 600;\n\n/** Short English title per rule (translatable via `buildmyiq.accessibility.rules.<RULE>`). */\nconst RULE_TITLES: Record<BuiAccessibilityRule, string> = {\n IMAGE_ALT_MISSING: 'Image without alt text',\n IMAGE_ALT_EMPTY: 'Image marked decorative',\n IMAGE_ALT_NOT_DESCRIPTIVE: 'Alt text is not descriptive',\n LINK_TEXT_EMPTY: 'Link without text',\n LINK_TEXT_NOT_DESCRIPTIVE: 'Vague link text',\n LINK_TEXT_RAW_URL: 'Raw URL as link text',\n HEADING_EMPTY: 'Empty heading',\n HEADING_LEVEL_SKIPPED: 'Skipped heading level',\n TABLE_HEADER_MISSING: 'Table without headers',\n MEDIA_CAPTIONS_MISSING: 'Video without captions',\n MEDIA_AUTOPLAY: 'Autoplaying media',\n MEDIA_TRANSCRIPT_MISSING: 'No transcript',\n IFRAME_TITLE_MISSING: 'Embed without a title',\n MOVING_CONTENT: 'Moving or blinking text',\n COLOR_STYLING: 'Inline text colour',\n SENSORY_INSTRUCTION: 'Colour or position-only instruction',\n OPTION_LABEL_EMPTY: 'Answer option without a label',\n OPTION_LABEL_DUPLICATE: 'Duplicate answer options',\n};\n\nconst IssueRow: FC<{ issue: BuiAccessibilityIssue }> = ({ issue }) => {\n const tr = useTr();\n const isError = issue.severity === 'ERROR';\n const Icon = isError ? XCircle : AlertTriangle;\n const url = wcagUnderstandingUrl(issue.wcag);\n const wcagLabel = tr('buildmyiq.accessibility.wcagLink', 'WCAG {{criterion}} (Level {{level}})', {\n criterion: issue.wcag,\n level: issue.wcagLevel,\n });\n return (\n <li className=\"flex gap-2.5 py-2.5 first:pt-0 last:pb-0\">\n <Icon\n aria-hidden=\"true\"\n className={cn(\n 'mt-0.5 h-4 w-4 shrink-0',\n isError ? 'text-status-error-text' : 'text-status-warning-text'\n )}\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n <span className=\"sr-only\">\n {isError\n ? tr('buildmyiq.accessibility.severity.error', 'Error:')\n : tr('buildmyiq.accessibility.severity.warning', 'Warning:')}{' '}\n </span>\n {tr(`buildmyiq.accessibility.rules.${issue.rule}`, RULE_TITLES[issue.rule] ?? issue.rule)}\n {issue.line != null && (\n <span className=\"ms-1.5 text-xs font-normal text-text-secondary\">\n {tr('buildmyiq.accessibility.line', 'line {{line}}', { line: issue.line })}\n </span>\n )}\n </p>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {tr(`buildmyiq.accessibility.messages.${issue.rule}`, issue.message)}\n </p>\n {issue.excerpt && (\n <code className=\"mt-1 block truncate rounded bg-bg-sunken px-1.5 py-0.5 font-mono text-xs text-text-secondary\">\n {issue.excerpt}\n </code>\n )}\n {url ? (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className=\"mt-1 inline-flex items-center gap-1 text-xs text-text-secondary underline underline-offset-2 hover:text-text-primary\"\n >\n {wcagLabel}\n <ExternalLink aria-hidden=\"true\" className=\"h-3 w-3\" />\n <span className=\"sr-only\">\n {tr('buildmyiq.accessibility.opensNewTab', '(opens in a new tab)')}\n </span>\n </a>\n ) : (\n <span className=\"mt-1 block text-xs text-text-secondary\">{wcagLabel}</span>\n )}\n </div>\n </li>\n );\n};\n\nexport const AccessibilityIssueList: FC<{\n issues: BuiAccessibilityIssue[];\n className?: string;\n}> = ({ issues, className }) => (\n <ul className={cn('divide-y divide-border-seam', className)}>\n {issues.map((issue, index) => (\n <IssueRow key={`${issue.rule}-${issue.line ?? 'x'}-${index}`} issue={issue} />\n ))}\n </ul>\n);\n\n/** One-line result summary, e.g. \"2 errors · 1 warning\". */\nexport const useAccessibilitySummary = (): ((\n report: Pick<BuiAccessibilityReport, 'errorCount' | 'warningCount'>\n) => string) => {\n const tr = useTr();\n return (report) => {\n if (report.errorCount === 0 && report.warningCount === 0) {\n return tr('buildmyiq.accessibility.noIssues', 'No accessibility issues found');\n }\n const parts: string[] = [];\n if (report.errorCount > 0) {\n parts.push(\n report.errorCount === 1\n ? tr('buildmyiq.accessibility.oneError', '1 error')\n : tr('buildmyiq.accessibility.errors', '{{count}} errors', { count: report.errorCount })\n );\n }\n if (report.warningCount > 0) {\n parts.push(\n report.warningCount === 1\n ? tr('buildmyiq.accessibility.oneWarning', '1 warning')\n : tr('buildmyiq.accessibility.warnings', '{{count}} warnings', {\n count: report.warningCount,\n })\n );\n }\n return parts.join(' · ');\n };\n};\n\ntype StatusTone = 'checking' | 'error' | 'warning' | 'clean';\n\nconst STATUS_ICONS = {\n checking: Loader2,\n error: XCircle,\n warning: AlertTriangle,\n clean: CheckCircle2,\n} as const;\n\nconst STATUS_TONE_CLASS: Record<StatusTone, string> = {\n checking: 'motion-safe:animate-spin',\n error: 'text-status-error-text',\n warning: 'text-status-warning-text',\n clean: 'text-status-success-text',\n};\n\nconst statusTone = (stale: boolean, report: BuiAccessibilityReport | undefined): StatusTone => {\n if (stale || !report) return 'checking';\n if (report.errorCount > 0) return 'error';\n if (report.warningCount > 0) return 'warning';\n return 'clean';\n};\n\ninterface AccessibilityCheckerProps {\n /** Draft to check; re-checked 600 ms after it stops changing. */\n input: BuiCheckContentAccessibilityInput;\n id?: string;\n className?: string;\n}\n\n/**\n * Live accessibility check for an authoring form. Advisory: it never blocks\n * saving, it tells the author what to fix and why (with the WCAG criterion).\n */\nexport const AccessibilityChecker: FC<AccessibilityCheckerProps> = ({\n input,\n id = 'accessibility-check',\n className,\n}) => {\n const tr = useTr();\n const summarise = useAccessibilitySummary();\n const debounced = useDebouncedValue(input, DEBOUNCE_MS);\n const query = useBuiCheckContentAccessibility(debounced);\n // A cleared draft is idle again: never show the previous draft's result.\n const checkable = hasCheckableContent(input);\n const report = checkable ? query.data : undefined;\n // The shown report belongs to an older draft while the author is still\n // typing (debounce pending), while a request is in flight, or while React\n // Query is showing the previous key's data as a placeholder.\n const stale =\n JSON.stringify(input) !== JSON.stringify(debounced) ||\n query.isFetching ||\n query.isPlaceholderData;\n const failed = checkable && !stale && query.isError;\n const idle = !checkable;\n const headingId = `${id}-heading`;\n\n let status: string;\n if (idle) {\n status = tr(\n 'buildmyiq.accessibility.idle',\n 'Start writing and the content will be checked for accessibility.'\n );\n } else if (failed) {\n status = tr(\n 'buildmyiq.accessibility.unavailable',\n 'Accessibility check is unavailable right now.'\n );\n } else if (stale || !report) {\n status = tr('buildmyiq.accessibility.checking', 'Checking accessibility…');\n } else {\n status = summarise(report);\n }\n // Screen readers only hear settled results, never the transient \"checking\"\n // state, so typing does not trigger two announcements per pause.\n const announcement = idle || stale || (!report && !failed) ? '' : status;\n\n const tone = statusTone(stale, report);\n const StatusIcon = STATUS_ICONS[tone];\n\n return (\n <section\n aria-labelledby={headingId}\n data-testid=\"accessibility-checker\"\n className={cn('rounded-xl border border-border-seam bg-bg-sunken/40 p-3', className)}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <h3 id={headingId} className=\"text-sm font-semibold text-text-primary\">\n {tr('buildmyiq.accessibility.title', 'Accessibility check')}\n </h3>\n <p\n aria-hidden=\"true\"\n data-testid=\"accessibility-checker-status\"\n className=\"inline-flex items-center gap-1.5 text-xs text-text-secondary\"\n >\n {!idle && !failed && (\n <StatusIcon className={cn('h-3.5 w-3.5', STATUS_TONE_CLASS[tone])} />\n )}\n {status}\n </p>\n <p role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </p>\n </div>\n {report && !failed && report.issues.length > 0 && (\n <AccessibilityIssueList issues={report.issues} className=\"mt-3\" />\n )}\n {report?.truncated && (\n <p className=\"mt-2 text-xs text-text-secondary\">\n {tr(\n 'buildmyiq.accessibility.truncated',\n 'Only the first part of this content was checked because it is very long.'\n )}\n </p>\n )}\n </section>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BuiDiscussion.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/BuiDiscussion.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAaD,wBAAgB,aAAa,CAAC,EAC5B,UAAU,EACV,QAAQ,EACR,KAAoB,EACpB,WAAkE,GACnE,EAAE,kBAAkB,+BAoGpB"}
1
+ {"version":3,"file":"BuiDiscussion.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/BuiDiscussion.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAaD,wBAAgB,aAAa,CAAC,EAC5B,UAAU,EACV,QAAQ,EACR,KAAoB,EACpB,WAAkE,GACnE,EAAE,kBAAkB,+BAyGpB"}
@@ -47,17 +47,19 @@ function _({ entityType: _, entityId: v, title: y = "Discussion", description: b
47
47
  value: D,
48
48
  onChange: (e) => O(e.target.value),
49
49
  placeholder: "Add a comment for your team…",
50
+ "aria-label": "Comment",
50
51
  rows: 2
51
52
  }), /* @__PURE__ */ f("div", {
52
53
  className: "mt-2 flex items-center justify-end gap-2",
53
54
  children: [k && /* @__PURE__ */ d("span", {
54
- className: "mr-auto text-xs text-status-error-text",
55
+ role: "alert",
56
+ className: "me-auto text-xs text-status-error-text",
55
57
  children: k
56
58
  }), /* @__PURE__ */ f(p, {
57
59
  size: "sm",
58
60
  onClick: N,
59
61
  disabled: !D.trim() || E.isPending,
60
- children: [/* @__PURE__ */ d(u, { className: "mr-1.5 h-4 w-4" }), E.isPending ? "Posting…" : "Comment"]
62
+ children: [/* @__PURE__ */ d(u, { className: "me-1.5 h-4 w-4" }), E.isPending ? "Posting…" : "Comment"]
61
63
  })]
62
64
  })]
63
65
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"BuiDiscussion.js","names":[],"sources":["../../../src/buildmyiq/components/BuiDiscussion.tsx"],"sourcesContent":["/**\n * BuiDiscussion — a comment thread on any Buildmyiq entity (Integration B).\n * @module buildmyiq/components/BuiDiscussion\n *\n * Reuses the platform EntityComment API (`getEntityComments` /\n * `createEntityComment`) keyed by `(entityType, entityId)` — no buildmyiq-svc change.\n * Renders root comments (author, body, time) + a composer. Robust to empty\n * state and errors.\n */\n\nimport { useState } from 'react';\nimport { MessageSquare, Send } from 'lucide-react';\nimport { Button, Textarea } from '@burdenoff/fe-libs/ui';\nimport { Section, LoadingView, ErrorView } from './StateViews';\nimport { EmptyState } from './EmptyState';\nimport { useBuildmyiq } from '../context';\nimport { useBuiComments, useEcoCreateComment, type BuiComment } from '../hooks';\nimport { formatDateTime } from '../utils/formatters';\n\ninterface BuiDiscussionProps {\n entityType: string;\n entityId: string;\n title?: string;\n description?: string;\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\nfunction initialFor(label: string): string {\n const trimmed = label.trim();\n return trimmed ? trimmed[0]!.toUpperCase() : '?';\n}\n\nexport function BuiDiscussion({\n entityType,\n entityId,\n title = 'Discussion',\n description = 'Coordinate review and verification with your team.',\n}: BuiDiscussionProps) {\n const { currentUser } = useBuildmyiq();\n const { data, isLoading, isError, refetch } = useBuiComments(entityType, entityId);\n const createComment = useEcoCreateComment(entityType, entityId);\n\n const [draft, setDraft] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const selfName = displayName(currentUser);\n\n const authorLabel = (comment: BuiComment): string =>\n currentUser?.id && comment.authorId === currentUser.id ? selfName : 'Workspace member';\n\n const handleSubmit = async () => {\n const text = draft.trim();\n if (!text) return;\n setError(null);\n try {\n await createComment.mutateAsync(text);\n setDraft('');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to post comment.');\n }\n };\n\n const comments = data?.items ?? [];\n\n return (\n <Section title={title} description={description}>\n <div className=\"rounded-xl border border-border-seam bg-bg-surface/40 p-4\">\n {/* Composer */}\n <div className=\"flex gap-3\">\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/15 text-sm font-semibold text-action-primary-bg\">\n {initialFor(selfName)}\n </div>\n <div className=\"flex-1\">\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n placeholder=\"Add a comment for your team…\"\n rows={2}\n />\n <div className=\"mt-2 flex items-center justify-end gap-2\">\n {error && <span className=\"mr-auto text-xs text-status-error-text\">{error}</span>}\n <Button\n size=\"sm\"\n onClick={handleSubmit}\n disabled={!draft.trim() || createComment.isPending}\n >\n <Send className=\"mr-1.5 h-4 w-4\" />\n {createComment.isPending ? 'Posting…' : 'Comment'}\n </Button>\n </div>\n </div>\n </div>\n\n {/* Thread */}\n <div className=\"mt-4 border-t border-border-seam pt-4\">\n {isLoading ? (\n <LoadingView label=\"Loading discussion…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load the discussion.\" onRetry={() => refetch()} />\n ) : comments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<MessageSquare className=\"h-9 w-9\" />}\n title=\"No comments yet\"\n description=\"Start the discussion by adding the first comment.\"\n />\n ) : (\n <ul className=\"flex flex-col gap-4\">\n {comments.map((comment) => {\n const label = authorLabel(comment);\n const body = comment.content.text ?? comment.content.markdown ?? '';\n return (\n <li key={comment.id} className=\"flex gap-3\">\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold text-text-secondary\">\n {initialFor(label)}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-baseline gap-x-2\">\n <span className=\"text-sm font-semibold text-text-primary\">{label}</span>\n <span className=\"text-xs text-text-tertiary\">\n {formatDateTime(comment.createdAt)}\n {comment.edited ? ' · edited' : ''}\n </span>\n </div>\n <p className=\"mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {body}\n </p>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </div>\n </Section>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"BuiDiscussion.js","names":[],"sources":["../../../src/buildmyiq/components/BuiDiscussion.tsx"],"sourcesContent":["/**\n * BuiDiscussion — a comment thread on any Buildmyiq entity (Integration B).\n * @module buildmyiq/components/BuiDiscussion\n *\n * Reuses the platform EntityComment API (`getEntityComments` /\n * `createEntityComment`) keyed by `(entityType, entityId)` — no buildmyiq-svc change.\n * Renders root comments (author, body, time) + a composer. Robust to empty\n * state and errors.\n */\n\nimport { useState } from 'react';\nimport { MessageSquare, Send } from 'lucide-react';\nimport { Button, Textarea } from '@burdenoff/fe-libs/ui';\nimport { Section, LoadingView, ErrorView } from './StateViews';\nimport { EmptyState } from './EmptyState';\nimport { useBuildmyiq } from '../context';\nimport { useBuiComments, useEcoCreateComment, type BuiComment } from '../hooks';\nimport { formatDateTime } from '../utils/formatters';\n\ninterface BuiDiscussionProps {\n entityType: string;\n entityId: string;\n title?: string;\n description?: string;\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\nfunction initialFor(label: string): string {\n const trimmed = label.trim();\n return trimmed ? trimmed[0]!.toUpperCase() : '?';\n}\n\nexport function BuiDiscussion({\n entityType,\n entityId,\n title = 'Discussion',\n description = 'Coordinate review and verification with your team.',\n}: BuiDiscussionProps) {\n const { currentUser } = useBuildmyiq();\n const { data, isLoading, isError, refetch } = useBuiComments(entityType, entityId);\n const createComment = useEcoCreateComment(entityType, entityId);\n\n const [draft, setDraft] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const selfName = displayName(currentUser);\n\n const authorLabel = (comment: BuiComment): string =>\n currentUser?.id && comment.authorId === currentUser.id ? selfName : 'Workspace member';\n\n const handleSubmit = async () => {\n const text = draft.trim();\n if (!text) return;\n setError(null);\n try {\n await createComment.mutateAsync(text);\n setDraft('');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to post comment.');\n }\n };\n\n const comments = data?.items ?? [];\n\n return (\n <Section title={title} description={description}>\n <div className=\"rounded-xl border border-border-seam bg-bg-surface/40 p-4\">\n {/* Composer */}\n <div className=\"flex gap-3\">\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/15 text-sm font-semibold text-action-primary-bg\">\n {initialFor(selfName)}\n </div>\n <div className=\"flex-1\">\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n placeholder=\"Add a comment for your team…\"\n aria-label=\"Comment\"\n rows={2}\n />\n <div className=\"mt-2 flex items-center justify-end gap-2\">\n {error && (\n <span role=\"alert\" className=\"me-auto text-xs text-status-error-text\">\n {error}\n </span>\n )}\n <Button\n size=\"sm\"\n onClick={handleSubmit}\n disabled={!draft.trim() || createComment.isPending}\n >\n <Send className=\"me-1.5 h-4 w-4\" />\n {createComment.isPending ? 'Posting…' : 'Comment'}\n </Button>\n </div>\n </div>\n </div>\n\n {/* Thread */}\n <div className=\"mt-4 border-t border-border-seam pt-4\">\n {isLoading ? (\n <LoadingView label=\"Loading discussion…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load the discussion.\" onRetry={() => refetch()} />\n ) : comments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<MessageSquare className=\"h-9 w-9\" />}\n title=\"No comments yet\"\n description=\"Start the discussion by adding the first comment.\"\n />\n ) : (\n <ul className=\"flex flex-col gap-4\">\n {comments.map((comment) => {\n const label = authorLabel(comment);\n const body = comment.content.text ?? comment.content.markdown ?? '';\n return (\n <li key={comment.id} className=\"flex gap-3\">\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold text-text-secondary\">\n {initialFor(label)}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-baseline gap-x-2\">\n <span className=\"text-sm font-semibold text-text-primary\">{label}</span>\n <span className=\"text-xs text-text-tertiary\">\n {formatDateTime(comment.createdAt)}\n {comment.edited ? ' · edited' : ''}\n </span>\n </div>\n <p className=\"mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {body}\n </p>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </div>\n </Section>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BuiTags.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/BuiTags.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoBH,UAAU,YAAY;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,OAAO,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAc,EAAE,EAAE,YAAY,+BAuL7E"}
1
+ {"version":3,"file":"BuiTags.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/BuiTags.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoBH,UAAU,YAAY;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,OAAO,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAc,EAAE,EAAE,YAAY,+BA2L7E"}
@@ -89,18 +89,23 @@ function b({ entityType: b, entityId: x, label: S = "Tags" }) {
89
89
  value: k,
90
90
  onChange: (e) => A(e.target.value),
91
91
  placeholder: "Search or create a tag…",
92
+ "aria-label": "Search or create a tag",
92
93
  className: "h-8"
93
94
  }), /* @__PURE__ */ f("div", {
94
95
  className: "mt-2 max-h-56 overflow-y-auto",
95
96
  children: F.isFetching && R.length > 0 ? /* @__PURE__ */ p("div", {
97
+ role: "status",
96
98
  className: "flex items-center gap-2 px-1 py-2 text-sm text-text-tertiary",
97
- children: [/* @__PURE__ */ f(y, { size: "sm" }), " Searching…"]
99
+ children: [/* @__PURE__ */ f(y, {
100
+ size: "sm",
101
+ "aria-hidden": "true"
102
+ }), " Searching…"]
98
103
  }) : /* @__PURE__ */ p(d, { children: [
99
104
  z.map((e) => /* @__PURE__ */ p("button", {
100
105
  type: "button",
101
106
  onClick: () => W(e.id),
102
107
  disabled: H,
103
- className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm text-text-secondary hover:bg-bg-sunken disabled:opacity-60",
108
+ className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-start text-sm text-text-secondary hover:bg-bg-sunken disabled:opacity-60",
104
109
  children: [e.color ? /* @__PURE__ */ f("span", {
105
110
  className: "h-2 w-2 shrink-0 rounded-full",
106
111
  style: { backgroundColor: e.color },
@@ -114,7 +119,7 @@ function b({ entityType: b, entityId: x, label: S = "Tags" }) {
114
119
  type: "button",
115
120
  onClick: G,
116
121
  disabled: H,
117
- className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm text-action-primary-bg hover:bg-bg-sunken disabled:opacity-60",
122
+ className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-start text-sm text-action-primary-bg hover:bg-bg-sunken disabled:opacity-60",
118
123
  children: [/* @__PURE__ */ f(c, { className: "h-3.5 w-3.5 shrink-0" }), /* @__PURE__ */ p("span", {
119
124
  className: "truncate",
120
125
  children: [