@trinityui/design-system 2.11.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2209 -0
- package/CONTRIBUTING.md +180 -69
- package/LICENSE +21 -0
- package/MIGRATION.md +523 -21
- package/README.md +281 -120
- package/dist/IllustratedMessage.js +546 -1341
- package/dist/accessibility.d.ts +596 -18
- package/dist/accessibility.d.ts.map +1 -1
- package/dist/accessibility.js +458 -0
- package/dist/app-shell.js +17230 -9097
- package/dist/assets/index.d.ts +1 -1
- package/dist/charts.js +8507 -6059
- package/dist/color-contrast.d.ts +35 -0
- package/dist/color-contrast.d.ts.map +1 -0
- package/dist/commonjs-helpers.js +8 -0
- package/dist/component-color-references.d.ts +175 -0
- package/dist/component-color-references.d.ts.map +1 -0
- package/dist/components/AI/AIActions.d.ts +72 -12
- package/dist/components/AI/AIActions.d.ts.map +1 -1
- package/dist/components/AI/AIAvatar.d.ts +31 -3
- package/dist/components/AI/AIAvatar.d.ts.map +1 -1
- package/dist/components/AI/AIChainOfThought.d.ts +192 -72
- package/dist/components/AI/AIChainOfThought.d.ts.map +1 -1
- package/dist/components/AI/AIChainOfThought.types.d.ts +296 -34
- package/dist/components/AI/AIChainOfThought.types.d.ts.map +1 -1
- package/dist/components/AI/AIChainOfThought.utils.d.ts +25 -7
- package/dist/components/AI/AIChainOfThought.utils.d.ts.map +1 -1
- package/dist/components/AI/AIChat.d.ts +12 -0
- package/dist/components/AI/AIChat.d.ts.map +1 -1
- package/dist/components/AI/AIChatWelcome.d.ts +254 -0
- package/dist/components/AI/AIChatWelcome.d.ts.map +1 -0
- package/dist/components/AI/AIClarificationPrompt.d.ts +134 -0
- package/dist/components/AI/AIClarificationPrompt.d.ts.map +1 -0
- package/dist/components/AI/AIConfidenceBadge.d.ts +117 -0
- package/dist/components/AI/AIConfidenceBadge.d.ts.map +1 -0
- package/dist/components/AI/AIConfigContext.d.ts +100 -0
- package/dist/components/AI/AIConfigContext.d.ts.map +1 -0
- package/dist/components/AI/AIConfigProvider.d.ts +66 -0
- package/dist/components/AI/AIConfigProvider.d.ts.map +1 -0
- package/dist/components/AI/AIContainer.d.ts +80 -11
- package/dist/components/AI/AIContainer.d.ts.map +1 -1
- package/dist/components/AI/AIExplainability.d.ts +39 -5
- package/dist/components/AI/AIExplainability.d.ts.map +1 -1
- package/dist/components/AI/AIFeedbackControl.d.ts +113 -0
- package/dist/components/AI/AIFeedbackControl.d.ts.map +1 -0
- package/dist/components/AI/AIFeedbackForm.d.ts +117 -0
- package/dist/components/AI/AIFeedbackForm.d.ts.map +1 -0
- package/dist/components/AI/AIFeedbackHostContext.d.ts +16 -0
- package/dist/components/AI/AIFeedbackHostContext.d.ts.map +1 -0
- package/dist/components/AI/AILabel.d.ts +39 -6
- package/dist/components/AI/AILabel.d.ts.map +1 -1
- package/dist/components/AI/AIMessage.d.ts +337 -42
- package/dist/components/AI/AIMessage.d.ts.map +1 -1
- package/dist/components/AI/AIPlanReview.d.ts +194 -0
- package/dist/components/AI/AIPlanReview.d.ts.map +1 -0
- package/dist/components/AI/AIQueryDisclosure.d.ts +119 -0
- package/dist/components/AI/AIQueryDisclosure.d.ts.map +1 -0
- package/dist/components/AI/AIRelatedQuestions.d.ts +50 -11
- package/dist/components/AI/AIRelatedQuestions.d.ts.map +1 -1
- package/dist/components/AI/AIResponseRenderer.d.ts +133 -21
- package/dist/components/AI/AIResponseRenderer.d.ts.map +1 -1
- package/dist/components/AI/AIResponseSourcesContext.d.ts +83 -0
- package/dist/components/AI/AIResponseSourcesContext.d.ts.map +1 -0
- package/dist/components/AI/AIReviewItem.d.ts +142 -0
- package/dist/components/AI/AIReviewItem.d.ts.map +1 -0
- package/dist/components/AI/AISourceChip.d.ts +135 -0
- package/dist/components/AI/AISourceChip.d.ts.map +1 -0
- package/dist/components/AI/AISources.d.ts +91 -13
- package/dist/components/AI/AISources.d.ts.map +1 -1
- package/dist/components/AI/AISourcesPanel.d.ts +43 -7
- package/dist/components/AI/AISourcesPanel.d.ts.map +1 -1
- package/dist/components/AI/AITextBlock.d.ts +107 -17
- package/dist/components/AI/AITextBlock.d.ts.map +1 -1
- package/dist/components/AI/AITextBlock.parser.d.ts +153 -0
- package/dist/components/AI/AITextBlock.parser.d.ts.map +1 -0
- package/dist/components/AI/AIVerificationBadge.d.ts +94 -0
- package/dist/components/AI/AIVerificationBadge.d.ts.map +1 -0
- package/dist/components/AI/AIVisuals.d.ts +92 -23
- package/dist/components/AI/AIVisuals.d.ts.map +1 -1
- package/dist/components/AI/AIVoiceInput.d.ts +42 -11
- package/dist/components/AI/AIVoiceInput.d.ts.map +1 -1
- package/dist/components/AI/AIVoiceMode.d.ts +16 -2
- package/dist/components/AI/AIVoiceMode.d.ts.map +1 -1
- package/dist/components/AI/BlockErrorBoundary.d.ts +77 -0
- package/dist/components/AI/BlockErrorBoundary.d.ts.map +1 -0
- package/dist/components/AI/ChatHeader.d.ts +41 -4
- package/dist/components/AI/ChatHeader.d.ts.map +1 -1
- package/dist/components/AI/ChatHistoryList.d.ts +25 -12
- package/dist/components/AI/ChatHistoryList.d.ts.map +1 -1
- package/dist/components/AI/ChatThread.d.ts +263 -13
- package/dist/components/AI/ChatThread.d.ts.map +1 -1
- package/dist/components/AI/InThreadContext.d.ts +14 -0
- package/dist/components/AI/InThreadContext.d.ts.map +1 -0
- package/dist/components/AI/InsightEngine.d.ts +270 -46
- package/dist/components/AI/InsightEngine.d.ts.map +1 -1
- package/dist/components/AI/InsightEngineInput.d.ts +52 -5
- package/dist/components/AI/InsightEngineInput.d.ts.map +1 -1
- package/dist/components/AI/MessageActionButton.d.ts +72 -0
- package/dist/components/AI/MessageActionButton.d.ts.map +1 -0
- package/dist/components/AI/QueryInput.d.ts.map +1 -1
- package/dist/components/AI/UserAvatar.d.ts +50 -4
- package/dist/components/AI/UserAvatar.d.ts.map +1 -1
- package/dist/components/AI/UserMessage.d.ts +32 -3
- package/dist/components/AI/UserMessage.d.ts.map +1 -1
- package/dist/components/AI/blockRendererContext.d.ts +64 -0
- package/dist/components/AI/blockRendererContext.d.ts.map +1 -0
- package/dist/components/AI/hitlBlockRenderers.d.ts +48 -0
- package/dist/components/AI/hitlBlockRenderers.d.ts.map +1 -0
- package/dist/components/AI/index.d.ts +23 -6
- package/dist/components/AI/index.d.ts.map +1 -1
- package/dist/components/AI/moleculeBlockRenderers.d.ts +15 -0
- package/dist/components/AI/moleculeBlockRenderers.d.ts.map +1 -0
- package/dist/components/AI/planReviewBlockRenderer.d.ts +8 -0
- package/dist/components/AI/planReviewBlockRenderer.d.ts.map +1 -0
- package/dist/components/AI/strings/defaults.d.ts +31 -0
- package/dist/components/AI/strings/defaults.d.ts.map +1 -0
- package/dist/components/AI/tokens.d.ts +185 -20
- package/dist/components/AI/tokens.d.ts.map +1 -1
- package/dist/components/AI/types/hitl.d.ts +2294 -0
- package/dist/components/AI/types/hitl.d.ts.map +1 -0
- package/dist/components/AI/types/index.d.ts +2 -1
- package/dist/components/AI/types/index.d.ts.map +1 -1
- package/dist/components/AI/types/response.d.ts +428 -40
- package/dist/components/AI/types/response.d.ts.map +1 -1
- package/dist/components/AI/useBlockSources.d.ts +67 -0
- package/dist/components/AI/useBlockSources.d.ts.map +1 -0
- package/dist/components/AI/useBrowserVoiceMode.d.ts +31 -2
- package/dist/components/AI/useBrowserVoiceMode.d.ts.map +1 -1
- package/dist/components/AI/useWelcomeComposerHandoff.d.ts +30 -0
- package/dist/components/AI/useWelcomeComposerHandoff.d.ts.map +1 -0
- package/dist/components/AI/utils/alphaIndex.d.ts +19 -0
- package/dist/components/AI/utils/alphaIndex.d.ts.map +1 -0
- package/dist/components/AI/utils/blockReady.d.ts +54 -0
- package/dist/components/AI/utils/blockReady.d.ts.map +1 -0
- package/dist/components/AI/utils/blocks.d.ts +28 -0
- package/dist/components/AI/utils/blocks.d.ts.map +1 -0
- package/dist/components/AI/utils/confidence.d.ts +182 -0
- package/dist/components/AI/utils/confidence.d.ts.map +1 -0
- package/dist/components/AI/utils/filters.d.ts +31 -0
- package/dist/components/AI/utils/filters.d.ts.map +1 -0
- package/dist/components/AI/utils/responseText.d.ts +8 -0
- package/dist/components/AI/utils/responseText.d.ts.map +1 -0
- package/dist/components/AI/utils/sources.d.ts +110 -0
- package/dist/components/AI/utils/sources.d.ts.map +1 -0
- package/dist/components/AIDigestCard/AIDigestCard.d.ts.map +1 -1
- package/dist/components/AIDigestCard/types.d.ts +42 -5
- package/dist/components/AIDigestCard/types.d.ts.map +1 -1
- package/dist/components/AppLayout/AIFab.d.ts +33 -6
- package/dist/components/AppLayout/AIFab.d.ts.map +1 -1
- package/dist/components/AppLayout/AIFabLayout.deprecated.d.ts.map +1 -1
- package/dist/components/AppLayout/AppLayout.d.ts +135 -7
- package/dist/components/AppLayout/AppLayout.d.ts.map +1 -1
- package/dist/components/AppLayout/InsightEnginePanel.d.ts +315 -21
- package/dist/components/AppLayout/InsightEnginePanel.d.ts.map +1 -1
- package/dist/components/AppLayout/ResizablePanel.d.ts +145 -8
- package/dist/components/AppLayout/ResizablePanel.d.ts.map +1 -1
- package/dist/components/AppLayout/ThreadHistorySidebar.d.ts +6 -0
- package/dist/components/AppLayout/ThreadHistorySidebar.d.ts.map +1 -1
- package/dist/components/AppLayout/index.d.ts +3 -3
- package/dist/components/AppLayout/index.d.ts.map +1 -1
- package/dist/components/AppLayout/insightEnginePanel.demo.d.ts +32 -0
- package/dist/components/AppLayout/insightEnginePanel.demo.d.ts.map +1 -0
- package/dist/components/AppLayout/panelPropsPath.d.ts +26 -0
- package/dist/components/AppLayout/panelPropsPath.d.ts.map +1 -0
- package/dist/components/AppLayout/panelResizeTransition.d.ts +49 -0
- package/dist/components/AppLayout/panelResizeTransition.d.ts.map +1 -0
- package/dist/components/AppLayout/toChatThreadMessage.d.ts +40 -0
- package/dist/components/AppLayout/toChatThreadMessage.d.ts.map +1 -0
- package/dist/components/AssessmentDrawer/AssessmentDrawer.d.ts +5 -1
- package/dist/components/AssessmentDrawer/AssessmentDrawer.d.ts.map +1 -1
- package/dist/components/AuditDrawer/AuditDrawer.d.ts +4 -3
- package/dist/components/AuditDrawer/AuditDrawer.d.ts.map +1 -1
- package/dist/components/AuditDrawer/index.d.ts +1 -1
- package/dist/components/AuditDrawer/index.d.ts.map +1 -1
- package/dist/components/AuditDrawer/types.d.ts +39 -5
- package/dist/components/AuditDrawer/types.d.ts.map +1 -1
- package/dist/components/Autocomplete/Autocomplete.d.ts +6 -1
- package/dist/components/Autocomplete/Autocomplete.d.ts.map +1 -1
- package/dist/components/BulkActionDock/BulkActionDock.d.ts.map +1 -1
- package/dist/components/Carousel/Carousel.d.ts +10 -2
- package/dist/components/Carousel/Carousel.d.ts.map +1 -1
- package/dist/components/Charts/AreaChart.d.ts.map +1 -1
- package/dist/components/Charts/BarChart.d.ts.map +1 -1
- package/dist/components/Charts/ChartWrapper.d.ts.map +1 -1
- package/dist/components/Charts/ComposedChart.d.ts.map +1 -1
- package/dist/components/Charts/CustomLegend.d.ts +11 -2
- package/dist/components/Charts/CustomLegend.d.ts.map +1 -1
- package/dist/components/Charts/LineChart.d.ts.map +1 -1
- package/dist/components/Charts/PieChart.d.ts.map +1 -1
- package/dist/components/Charts/RadialChart.d.ts.map +1 -1
- package/dist/components/Charts/ScatterChart.d.ts.map +1 -1
- package/dist/components/Charts/Sparkline.d.ts.map +1 -1
- package/dist/components/Charts/gaugeGeometry.d.ts +35 -0
- package/dist/components/Charts/gaugeGeometry.d.ts.map +1 -0
- package/dist/components/Charts/highcharts.d.ts +23 -0
- package/dist/components/Charts/highcharts.d.ts.map +1 -0
- package/dist/components/Charts/highchartsUtils.d.ts +101 -6
- package/dist/components/Charts/highchartsUtils.d.ts.map +1 -1
- package/dist/components/Charts/index.d.ts +1 -1
- package/dist/components/Charts/index.d.ts.map +1 -1
- package/dist/components/Charts/tokens.d.ts +21 -0
- package/dist/components/Charts/tokens.d.ts.map +1 -1
- package/dist/components/Charts/types.d.ts +7 -7
- package/dist/components/Charts/types.d.ts.map +1 -1
- package/dist/components/ChipsInput/ChipsInput.d.ts.map +1 -1
- package/dist/components/ChoiceChips/ChoiceChips.d.ts +106 -0
- package/dist/components/ChoiceChips/ChoiceChips.d.ts.map +1 -0
- package/dist/components/ClearableTextField/ClearableTextField.d.ts +23 -2
- package/dist/components/ClearableTextField/ClearableTextField.d.ts.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.d.ts +80 -0
- package/dist/components/CodeBlock/CodeBlock.d.ts.map +1 -0
- package/dist/components/Combobox/Combobox.d.ts +3 -2
- package/dist/components/Combobox/Combobox.d.ts.map +1 -1
- package/dist/components/CommandPalette/CommandPalette.d.ts +54 -3
- package/dist/components/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/components/CommandPalette/index.d.ts +1 -1
- package/dist/components/CommandPalette/index.d.ts.map +1 -1
- package/dist/components/CopyButton/CopyButton.d.ts +80 -0
- package/dist/components/CopyButton/CopyButton.d.ts.map +1 -0
- package/dist/components/DataCard/DataCard.d.ts +11 -1
- package/dist/components/DataCard/DataCard.d.ts.map +1 -1
- package/dist/components/DataTable/CellRenderers.d.ts +8 -11
- package/dist/components/DataTable/CellRenderers.d.ts.map +1 -1
- package/dist/components/DataTable/DataTable.d.ts +1 -1
- package/dist/components/DataTable/DataTable.d.ts.map +1 -1
- package/dist/components/DataTable/InlineAddRow.d.ts.map +1 -1
- package/dist/components/DataTable/columnContext.d.ts +8 -0
- package/dist/components/DataTable/columnContext.d.ts.map +1 -0
- package/dist/components/DataTable/index.d.ts +2 -2
- package/dist/components/DataTable/index.d.ts.map +1 -1
- package/dist/components/DataTable/labels.d.ts +13 -0
- package/dist/components/DataTable/labels.d.ts.map +1 -0
- package/dist/components/DataTable/types.d.ts +34 -0
- package/dist/components/DataTable/types.d.ts.map +1 -1
- package/dist/components/DateRangePicker/DateRangePicker.d.ts.map +1 -1
- package/dist/components/DetailsPopover/DetailsPopover.d.ts +185 -0
- package/dist/components/DetailsPopover/DetailsPopover.d.ts.map +1 -0
- package/dist/components/DiffViewer/DiffViewer.d.ts +3 -2
- package/dist/components/DiffViewer/DiffViewer.d.ts.map +1 -1
- package/dist/components/Disclosure/Disclosure.d.ts +83 -0
- package/dist/components/Disclosure/Disclosure.d.ts.map +1 -0
- package/dist/components/DockLayout/DockLayout.d.ts +58 -0
- package/dist/components/DockLayout/DockLayout.d.ts.map +1 -1
- package/dist/components/DockLayout/index.d.ts +1 -1
- package/dist/components/DockLayout/index.d.ts.map +1 -1
- package/dist/components/DocumentCard/DocumentCard.d.ts +5 -1
- package/dist/components/DocumentCard/DocumentCard.d.ts.map +1 -1
- package/dist/components/EntityDetailPanel/EntityDetailPanel.d.ts +6 -2
- package/dist/components/EntityDetailPanel/EntityDetailPanel.d.ts.map +1 -1
- package/dist/components/FileUpload/FileUpload.d.ts +141 -6
- package/dist/components/FileUpload/FileUpload.d.ts.map +1 -1
- package/dist/components/FileUpload/index.d.ts +1 -1
- package/dist/components/FileUpload/index.d.ts.map +1 -1
- package/dist/components/FilterBar/FilterBar.d.ts +27 -1
- package/dist/components/FilterBar/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterBar/index.d.ts +1 -1
- package/dist/components/FilterBar/index.d.ts.map +1 -1
- package/dist/components/FlowAutoLayout/useFlowAutoLayout.d.ts.map +1 -1
- package/dist/components/GanttView/GanttView.d.ts +24 -0
- package/dist/components/GanttView/GanttView.d.ts.map +1 -1
- package/dist/components/GanttView/types.d.ts +33 -7
- package/dist/components/GanttView/types.d.ts.map +1 -1
- package/dist/components/GaugeCard/GaugeCard.d.ts +4 -3
- package/dist/components/GaugeCard/GaugeCard.d.ts.map +1 -1
- package/dist/components/GaugeCard/geometry.d.ts +59 -0
- package/dist/components/GaugeCard/geometry.d.ts.map +1 -0
- package/dist/components/GeoStatusMap/GeoStatusMap.d.ts.map +1 -1
- package/dist/components/GeoStatusMap/types.d.ts +8 -5
- package/dist/components/GeoStatusMap/types.d.ts.map +1 -1
- package/dist/components/GoToTop/GoToTop.d.ts +1 -1
- package/dist/components/GoToTop/GoToTop.d.ts.map +1 -1
- package/dist/components/HierarchicalDrillDown/HierarchicalDrillDown.d.ts.map +1 -1
- package/dist/components/HistoricalDataIndicator/HistoricalDataIndicator.d.ts.map +1 -1
- package/dist/components/HtmlToMui/HtmlToMui.d.ts +32 -2
- package/dist/components/HtmlToMui/HtmlToMui.d.ts.map +1 -1
- package/dist/components/Icon.d.ts +567 -10
- package/dist/components/Icon.d.ts.map +1 -1
- package/dist/components/IllustratedMessage.d.ts +52 -3
- package/dist/components/IllustratedMessage.d.ts.map +1 -1
- package/dist/components/InlineEditableText/InlineEditableText.d.ts +117 -0
- package/dist/components/InlineEditableText/InlineEditableText.d.ts.map +1 -0
- package/dist/components/InsightCard/InsightCard.d.ts.map +1 -1
- package/dist/components/InsightCard/types.d.ts +10 -3
- package/dist/components/InsightCard/types.d.ts.map +1 -1
- package/dist/components/KeyValueList/KeyValueList.d.ts +94 -0
- package/dist/components/KeyValueList/KeyValueList.d.ts.map +1 -0
- package/dist/components/LandingPage/FeatureCard.d.ts +4 -0
- package/dist/components/LandingPage/FeatureCard.d.ts.map +1 -1
- package/dist/components/LandingPage/LandingPage.d.ts +43 -4
- package/dist/components/LandingPage/LandingPage.d.ts.map +1 -1
- package/dist/components/LandingPage/heroTone.d.ts +54 -0
- package/dist/components/LandingPage/heroTone.d.ts.map +1 -0
- package/dist/components/MentionsInput/MentionsInput.d.ts.map +1 -1
- package/dist/components/MetaChip/MetaChip.d.ts +113 -0
- package/dist/components/MetaChip/MetaChip.d.ts.map +1 -0
- package/dist/components/Modal/Modal.d.ts +222 -20
- package/dist/components/Modal/Modal.d.ts.map +1 -1
- package/dist/components/Modal/index.d.ts +1 -1
- package/dist/components/Modal/index.d.ts.map +1 -1
- package/dist/components/NotificationCenter/NotificationCenter.d.ts.map +1 -1
- package/dist/components/OrgChart/OrgChart.d.ts +5 -0
- package/dist/components/OrgChart/OrgChart.d.ts.map +1 -1
- package/dist/components/OrgChart/types.d.ts +31 -7
- package/dist/components/OrgChart/types.d.ts.map +1 -1
- package/dist/components/OverflowTooltip/OverflowTooltip.d.ts +1 -1
- package/dist/components/OverflowTooltip/OverflowTooltip.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeader.d.ts.map +1 -1
- package/dist/components/PageTOC/PageTOC.d.ts +2 -1
- package/dist/components/PageTOC/PageTOC.d.ts.map +1 -1
- package/dist/components/Popper/Popper.d.ts +80 -9
- package/dist/components/Popper/Popper.d.ts.map +1 -1
- package/dist/components/Popper/index.d.ts +1 -1
- package/dist/components/Popper/index.d.ts.map +1 -1
- package/dist/components/PredictiveTextInput/PredictiveTextInput.d.ts +5 -1
- package/dist/components/PredictiveTextInput/PredictiveTextInput.d.ts.map +1 -1
- package/dist/components/SandboxedIframeRenderer/SandboxedIframeRenderer.d.ts +48 -4
- package/dist/components/SandboxedIframeRenderer/SandboxedIframeRenderer.d.ts.map +1 -1
- package/dist/components/SearchInput/SearchInput.d.ts +49 -0
- package/dist/components/SearchInput/SearchInput.d.ts.map +1 -1
- package/dist/components/SearchInput/index.d.ts +1 -1
- package/dist/components/SearchInput/index.d.ts.map +1 -1
- package/dist/components/SimpleTable/SimpleTable.d.ts +11 -2
- package/dist/components/SimpleTable/SimpleTable.d.ts.map +1 -1
- package/dist/components/SplitPane/SplitPane.d.ts +2 -1
- package/dist/components/SplitPane/SplitPane.d.ts.map +1 -1
- package/dist/components/StatusHero/StatusHero.d.ts.map +1 -1
- package/dist/components/StatusIndicator/Badge.d.ts +6 -1
- package/dist/components/StatusIndicator/Badge.d.ts.map +1 -1
- package/dist/components/StatusIndicator/Chip.d.ts +3 -1
- package/dist/components/StatusIndicator/Chip.d.ts.map +1 -1
- package/dist/components/StatusIndicator/Indicators.d.ts +5 -1
- package/dist/components/StatusIndicator/Indicators.d.ts.map +1 -1
- package/dist/components/StatusIndicator/Legend.d.ts.map +1 -1
- package/dist/components/StatusIndicator/StatusIndicator.d.ts +98 -4
- package/dist/components/StatusIndicator/StatusIndicator.d.ts.map +1 -1
- package/dist/components/StatusIndicator/index.d.ts +2 -2
- package/dist/components/StatusIndicator/index.d.ts.map +1 -1
- package/dist/components/StatusIndicator/types.d.ts +48 -1
- package/dist/components/StatusIndicator/types.d.ts.map +1 -1
- package/dist/components/StepList/StepList.d.ts +80 -0
- package/dist/components/StepList/StepList.d.ts.map +1 -0
- package/dist/components/StepList/StepStatusMarker.d.ts +39 -0
- package/dist/components/StepList/StepStatusMarker.d.ts.map +1 -0
- package/dist/components/StepList/stepStatus.d.ts +30 -0
- package/dist/components/StepList/stepStatus.d.ts.map +1 -0
- package/dist/components/SystemBanner/SystemBanner.d.ts.map +1 -1
- package/dist/components/Timeline/Timeline.d.ts +57 -3
- package/dist/components/Timeline/Timeline.d.ts.map +1 -1
- package/dist/components/Timeline/index.d.ts +1 -1
- package/dist/components/Timeline/index.d.ts.map +1 -1
- package/dist/components/Timeline/timelineLabels.d.ts +70 -0
- package/dist/components/Timeline/timelineLabels.d.ts.map +1 -0
- package/dist/components/Toast/Toast.d.ts.map +1 -1
- package/dist/components/TopNavHeader.d.ts +6 -0
- package/dist/components/TopNavHeader.d.ts.map +1 -1
- package/dist/components/TopNavWithSidebar.d.ts +49 -1
- package/dist/components/TopNavWithSidebar.d.ts.map +1 -1
- package/dist/components/Tour/Tour.d.ts +18 -1
- package/dist/components/Tour/Tour.d.ts.map +1 -1
- package/dist/components/TransferList/TransferList.d.ts +123 -4
- package/dist/components/TransferList/TransferList.d.ts.map +1 -1
- package/dist/components/TransferList/index.d.ts +1 -1
- package/dist/components/TransferList/index.d.ts.map +1 -1
- package/dist/components/TreeView/TreeView.d.ts +97 -8
- package/dist/components/TreeView/TreeView.d.ts.map +1 -1
- package/dist/components/TreeView/index.d.ts +1 -1
- package/dist/components/TreeView/index.d.ts.map +1 -1
- package/dist/components/TrinityLoader.d.ts.map +1 -1
- package/dist/components/icons/featherExtensions.d.ts +55 -0
- package/dist/components/icons/featherExtensions.d.ts.map +1 -0
- package/dist/components/icons/materialExtensions.d.ts +20 -0
- package/dist/components/icons/materialExtensions.d.ts.map +1 -0
- package/dist/components/internal/index.d.ts +87 -0
- package/dist/components/internal/index.d.ts.map +1 -1
- package/dist/components/navigation/MenuListWithHeading.d.ts +33 -0
- package/dist/components/navigation/MenuListWithHeading.d.ts.map +1 -0
- package/dist/components/navigation/clientSelectorLabels.d.ts +13 -0
- package/dist/components/navigation/clientSelectorLabels.d.ts.map +1 -0
- package/dist/components/navigation/components.d.ts +6 -0
- package/dist/components/navigation/components.d.ts.map +1 -1
- package/dist/components/shared/AsyncOutcomeAlert.d.ts +91 -0
- package/dist/components/shared/AsyncOutcomeAlert.d.ts.map +1 -0
- package/dist/components/shared/TrinityIconSvg.d.ts.map +1 -1
- package/dist/components/shared/TrinityLogoSvg.d.ts.map +1 -1
- package/dist/components/shared/bidi.d.ts +44 -0
- package/dist/components/shared/bidi.d.ts.map +1 -0
- package/dist/components/shared/bidiText.d.ts +25 -0
- package/dist/components/shared/bidiText.d.ts.map +1 -0
- package/dist/components/shared/containerQueries.d.ts +73 -0
- package/dist/components/shared/containerQueries.d.ts.map +1 -0
- package/dist/components/shared/createStringsContext.d.ts +69 -0
- package/dist/components/shared/createStringsContext.d.ts.map +1 -0
- package/dist/components/shared/direction.d.ts +46 -0
- package/dist/components/shared/direction.d.ts.map +1 -0
- package/dist/components/shared/focusTrap.d.ts +36 -0
- package/dist/components/shared/focusTrap.d.ts.map +1 -0
- package/dist/components/shared/getInitials.d.ts +29 -0
- package/dist/components/shared/getInitials.d.ts.map +1 -0
- package/dist/components/shared/headingLevel.d.ts +69 -0
- package/dist/components/shared/headingLevel.d.ts.map +1 -0
- package/dist/components/shared/mergeSlotProps.d.ts +50 -0
- package/dist/components/shared/mergeSlotProps.d.ts.map +1 -0
- package/dist/components/shared/mergeSx.d.ts +26 -0
- package/dist/components/shared/mergeSx.d.ts.map +1 -0
- package/dist/components/shared/motion.d.ts +110 -0
- package/dist/components/shared/motion.d.ts.map +1 -0
- package/dist/components/shared/occurrenceKeys.d.ts +23 -0
- package/dist/components/shared/occurrenceKeys.d.ts.map +1 -0
- package/dist/components/shared/primaryAction.d.ts +89 -0
- package/dist/components/shared/primaryAction.d.ts.map +1 -0
- package/dist/components/shared/resizeKeyboard.d.ts +68 -0
- package/dist/components/shared/resizeKeyboard.d.ts.map +1 -0
- package/dist/components/shared/rootProps.d.ts +117 -0
- package/dist/components/shared/rootProps.d.ts.map +1 -0
- package/dist/components/shared/safeContent.d.ts +136 -0
- package/dist/components/shared/safeContent.d.ts.map +1 -0
- package/dist/components/shared/targetSize.d.ts +64 -0
- package/dist/components/shared/targetSize.d.ts.map +1 -0
- package/dist/components/shared/useContainerInlineSize.d.ts +43 -0
- package/dist/components/shared/useContainerInlineSize.d.ts.map +1 -0
- package/dist/components/shared/useCopyAction.d.ts +35 -0
- package/dist/components/shared/useCopyAction.d.ts.map +1 -0
- package/dist/components/shared/useOverflow.d.ts +44 -0
- package/dist/components/shared/useOverflow.d.ts.map +1 -0
- package/dist/components/shared/useResizable.d.ts +106 -7
- package/dist/components/shared/useResizable.d.ts.map +1 -1
- package/dist/components/shared/warnDeprecated.d.ts +89 -15
- package/dist/components/shared/warnDeprecated.d.ts.map +1 -1
- package/dist/components/shared/zIndexRoles.d.ts +62 -0
- package/dist/components/shared/zIndexRoles.d.ts.map +1 -0
- package/dist/components/templates/AIReviewQueue/AIReviewQueueTemplate.d.ts +213 -0
- package/dist/components/templates/AIReviewQueue/AIReviewQueueTemplate.d.ts.map +1 -0
- package/dist/components/templates/AIReviewQueue/index.d.ts +2 -0
- package/dist/components/templates/AIReviewQueue/index.d.ts.map +1 -0
- package/dist/components/templates/Auth/AuthTemplate.d.ts.map +1 -1
- package/dist/components/templates/Auth/types.d.ts +4 -1
- package/dist/components/templates/Auth/types.d.ts.map +1 -1
- package/dist/components/templates/Catalog/CatalogTemplate.d.ts.map +1 -1
- package/dist/components/templates/ConnectorWizard/ConnectorWizardTemplate.d.ts.map +1 -1
- package/dist/components/templates/InsightsCenter/InsightsCenterTemplate.d.ts.map +1 -1
- package/dist/components/templates/IntegrationDetail/IntegrationDetailTemplate.d.ts.map +1 -1
- package/dist/components/templates/ListDetail/ListDetailTemplate.d.ts +168 -10
- package/dist/components/templates/ListDetail/ListDetailTemplate.d.ts.map +1 -1
- package/dist/components/templates/ListDetail/index.d.ts +1 -1
- package/dist/components/templates/ListDetail/index.d.ts.map +1 -1
- package/dist/components/templates/ListDetail/listDetailLabels.d.ts +106 -0
- package/dist/components/templates/ListDetail/listDetailLabels.d.ts.map +1 -0
- package/dist/components/templates/Onboarding/OnboardingTemplate.d.ts.map +1 -1
- package/dist/components/templates/Settings/SettingsTemplate.d.ts +0 -3
- package/dist/components/templates/Settings/SettingsTemplate.d.ts.map +1 -1
- package/dist/components/templates/Wizard/WizardTemplate.d.ts +4 -1
- package/dist/components/templates/Wizard/WizardTemplate.d.ts.map +1 -1
- package/dist/components/templates/index.d.ts +2 -1
- package/dist/components/templates/index.d.ts.map +1 -1
- package/dist/data-table.d.ts +1 -1
- package/dist/data-table.d.ts.map +1 -1
- package/dist/data-table.js +1 -0
- package/dist/data-table2.js +13522 -13465
- package/dist/data.js +213 -194
- package/dist/elk.bundled.js +47371 -0
- package/dist/essentials.d.ts +3 -3
- package/dist/essentials.d.ts.map +1 -1
- package/dist/essentials.js +179 -147
- package/dist/focus-on-replace.js +49 -0
- package/dist/form.d.ts +26 -4
- package/dist/form.d.ts.map +1 -1
- package/dist/forms.js +575 -483
- package/dist/foundation.js +3704 -0
- package/dist/hooks/storageStore.d.ts +51 -0
- package/dist/hooks/storageStore.d.ts.map +1 -0
- package/dist/hooks/useAsyncAction.d.ts +65 -0
- package/dist/hooks/useAsyncAction.d.ts.map +1 -0
- package/dist/hooks/useDisclosure.d.ts +190 -0
- package/dist/hooks/useDisclosure.d.ts.map +1 -0
- package/dist/hooks/useFocusOnReplace.d.ts +95 -0
- package/dist/hooks/useFocusOnReplace.d.ts.map +1 -0
- package/dist/hooks/useTrinityPalette.d.ts +38 -40
- package/dist/hooks/useTrinityPalette.d.ts.map +1 -1
- package/dist/hooks.d.ts +44 -6
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +301 -0
- package/dist/index.d.ts +71 -26
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13923 -60619
- package/dist/patterns/AICopilotPattern/AICopilotPattern.d.ts +203 -0
- package/dist/patterns/AICopilotPattern/AICopilotPattern.d.ts.map +1 -0
- package/dist/patterns/AICopilotPattern/index.d.ts +3 -0
- package/dist/patterns/AICopilotPattern/index.d.ts.map +1 -0
- package/dist/patterns/index.d.ts +15 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/presets/analytics.d.ts +8 -1
- package/dist/presets/analytics.d.ts.map +1 -1
- package/dist/presets/crm.d.ts +8 -1
- package/dist/presets/crm.d.ts.map +1 -1
- package/dist/presets/index.d.ts +1 -1
- package/dist/presets/portal.d.ts +8 -1
- package/dist/presets/portal.d.ts.map +1 -1
- package/dist/safe-content.js +792 -0
- package/dist/theme-augmentation.d.ts +388 -0
- package/dist/theme-augmentation.d.ts.map +1 -0
- package/dist/theme.d.ts +116 -30
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +891 -436
- package/dist/token-color.d.ts +14 -0
- package/dist/token-color.d.ts.map +1 -0
- package/dist/token-merge.d.ts +56 -0
- package/dist/token-merge.d.ts.map +1 -0
- package/dist/token-serialization.d.ts +65 -0
- package/dist/token-serialization.d.ts.map +1 -0
- package/dist/tokens.d.ts +882 -13
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +21 -19
- package/dist/trinity.css +1077 -414
- package/package.json +111 -90
- package/dist/components/index.d.ts +0 -145
- package/dist/components/index.d.ts.map +0 -1
- package/dist/components/shared.d.ts +0 -92
- package/dist/components/shared.d.ts.map +0 -1
package/MIGRATION.md
CHANGED
|
@@ -4,13 +4,481 @@ This document outlines breaking changes and migration paths for the Trinity Desi
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## Behavior corrections (2.x)
|
|
8
|
+
|
|
9
|
+
A behavior correction changes a default, a token value or a behavior that is objectively broken today. Under ADR-23 it ships in a minor release instead of waiting for v3.0; a security-only correction may ship as a patch. Each row names the objective test it meets:
|
|
10
|
+
|
|
11
|
+
| Test | Today's behavior |
|
|
12
|
+
|---|---|
|
|
13
|
+
| a | contradicts the component's own documentation, JSDoc or stories |
|
|
14
|
+
| b | violates WCAG 2.2 AA, the behavior contract or a remediation canonical pattern |
|
|
15
|
+
| c | fails a documented contrast requirement |
|
|
16
|
+
| d | is a security or privacy defect |
|
|
17
|
+
| e | renders non-functional controls or fabricated content |
|
|
18
|
+
|
|
19
|
+
The rows below shipped together in 2.13.0, the first release after the remediation program (the React package moves from 2.11.1 to 2.13.0 to share one version line with the CSS package, which was at 2.12.1). The test letters follow the ADR-23 corrections inventory (`docs/decisions/ADR-23-rubric-RFC.md`). The records are `docs/remediation/STEP3-*.md` for step 3, the C-, H- and P- records of the reconciliation lane, and `docs/remediation/SEC-*.md` for 2.11.1.
|
|
20
|
+
|
|
21
|
+
This file is written in release PRs. A feature PR describes its correction in its changeset (category, test letter, opt-out and the row), and the release collates the rows here; `scripts/check-deprecations.mjs` rejects MIGRATION.md edits on other branches.
|
|
22
|
+
|
|
23
|
+
| Version | Component | Change | ADR-23 test | Opt-out | Consumer action |
|
|
24
|
+
|---|---|---|---|---|---|
|
|
25
|
+
| 2.13.0 | `FileUpload` `onFilesChange` and `files` | **Before:** With `onFilesChange` but without `files`, each call carried only the latest selection and the file list never rendered. With `files` plus `onUpload`, every upload transition was computed from a stale list and emitted `[]`, so the file disappeared after selection. A failed upload, or an unmount mid-upload, left the progress interval running every 200 ms (calling `onFilesChange` when set). **After:** `onFilesChange` receives the complete accumulated list on every change in both modes, and the uncontrolled list renders. Every transition keeps the file (`pending` → `uploading` → progress → `complete` or `error`). The progress timer stops when an upload settles or FileUpload unmounts, and `onFilesChange` is not called after unmount. **Verified by:** `FileUpload.test.tsx` ([STEP3-03](docs/remediation/STEP3-03-file-upload-lifecycle.md)) | a, e | None | If you appended each `onFilesChange` payload to your own list (treating it as a delta), use it as the whole list instead (`onFilesChange={setFiles}`). If FileUpload can unmount mid-upload while your state lives on, that file stays `uploading` in your state, so take the final outcome from your `onUpload` promise. |
|
|
26
|
+
| 2.13.0 | `FileUpload` image `preview` | **Before:** The object URL created for an image preview was revoked only when the user removed the file. Replaced files (single-file mode) and unmounted components leaked it. **After:** FileUpload revokes each object URL it created once the file has left the rendered list (a removal the parent declines keeps the URL) or when FileUpload unmounts. A `preview` you pass in through `files` is not revoked on replacement or unmount; as before, it is revoked when the user presses that file's remove control, even if you decline the removal. **Verified by:** `FileUpload.test.tsx` "image preview object URLs" and "preview of a declined removal (FILEUPLOAD-REG-03)" | a | None | To show a preview after that point, create your own URL from `file` (`URL.createObjectURL(file)`) and revoke it yourself. |
|
|
27
|
+
| 2.13.0 | `FileUpload` upload progress bar | **Before:** The progress bar had no accessible name (axe `aria-progressbar-name`). **After:** Each progress bar is named "Uploading {file name}". **Verified by:** `FileUpload.test.tsx` "Accessibility" | b | None | Nothing. Tests can query it with `getByRole('progressbar', { name: 'Uploading report.pdf' })`. |
|
|
28
|
+
| 2.13.0 | `FileUpload` `files` (controlled or uncontrolled) | **Before:** Whether `files` controlled the list was re-checked on every render. Passing `files` after mount (for example `files={data?.files}` once data loads) took over the list, and dropping it fell back to an empty list the component owned. **After:** The mode is decided on the first render and kept, as in MUI's own components (`useControlled`). Without a switch nothing changes: both modes report the same `onFilesChange` values and render the same rows. A later switch logs MUI's controlled/uncontrolled warning in development. `files` passed after an uncontrolled mount is ignored, and a controlled FileUpload whose `files` becomes `undefined` shows an empty list. **Verified by:** `FileUpload.test.tsx` "FileUpload controlled state (step 3, useControlled)" ([STEP3-04](docs/remediation/STEP3-04-file-upload-controlled.md)) | b | None | Pass an array from the first render (`files={data?.files ?? []}`) and keep passing one, or never pass `files`. To reset an uncontrolled FileUpload, remount it with a new `key`. |
|
|
29
|
+
| 2.13.0 | `GanttView` rows, bars, milestones and region | **Before:** Task-list rows, bars and milestone diamonds were `div`s with `onClick`: no button role, no Tab stop, no Enter or Space. With `onSelect` unset they still carried the no-op handler, and each milestone `div` got `aria-label` from its tooltip (axe `aria-prohibited-attr`). The scroll region could not take focus. The dependency-arrow marker id was the literal `gantt-arrow`. In dark mode the surfaces stayed light, so the dark focus ring measured 2.14:1 on a group row (chevron) and would have measured 1.83–2.23:1 on the task surfaces. **After:** With `onSelect`, every row, bar and milestone is a button (`role="button"`, `tabIndex=0`, Enter and Space select with the click payload). The row is named by its label and description and the selected row has `aria-current="true"`. The bar keeps its content name (assignee, else label) and the milestone is named by its label. Tooltips describe instead of name, and each bar and milestone has a visually hidden summary (label, dates, progress, assignee) referenced by `aria-describedby`. Without `onSelect` nothing is a button. The region has `tabIndex={0}` and a focus ring. The marker id comes from `useId`. Root, task list, rows, group rows and grid lines use palette roles (`background.paper`, `background.default`, `divider`, `text.primary`, MUI's selected tint), so in dark mode they turn dark and every ring measures 5.16:1 or more. Light mode keeps its values except the selected row (`#E8E8F0` → `#EBEBF0`); the bars and arrows are unchanged in both modes. The headers and captions were unchanged by this change, but a later step 3 fix moved them to palette roles (see the `GanttView` header strips and task descriptions row). **Verified by:** `GanttView.test.tsx` ([STEP3-06](docs/remediation/STEP3-06-gantt-view.md)) | b | None | Query rows and bars by role, for example `getByRole('button', { name: 'Priya' })`. Do not query `#gantt-arrow` or a milestone's `aria-label` in static mode. Expect one extra Tab stop (the region) and, with `onSelect`, two per task (row, then bar or milestone). |
|
|
30
|
+
| 2.13.0 | `OrgChart` expander, `onExpand` and `onSelect` | **Before:** A collapsed node never showed an operable expander: only a decorative ▸ rendered, and `onExpand` never fired. `onSelect` received a copy of any node that had children, in which collapsed descendants had `children: []`. In dark mode the node boxes, their text colours and the canvas stayed light (static colours; `canvasBackground` defaulted to a fixed light grey). **After:** With `onExpand`, each collapsed node that has at least one child gets an "Expand subtree" button right after the node (one extra Tab stop per such node), described by the node title. Enter, Space or click calls `onExpand` without selecting the node. Once you expand the node, the button disappears and focus moves to the node's own button. Without `onExpand` the decorative ▸ stays. `onSelect` and `onExpand` receive your own node object from `root`. Node, canvas, border and text colours follow the theme palette: in light mode only the subtitle and the ▸ marker get darker (`text.secondary`); in dark mode the surfaces are dark. **Verified by:** `OrgChart.test.tsx` "renders an expand control on collapsed nodes that fires onExpand", `OrgChart.expander.test.tsx` ([STEP3-08](docs/remediation/STEP3-08-org-chart.md)) | e | None | If you compared the `onSelect` argument with a pruned copy, compare by `id` or by reference instead. Expand by passing a new `root` with `collapsed: false` on that node from `onExpand`. A collapsed node without children gets no expander. To keep a light canvas in dark mode, pass `canvasBackground`. |
|
|
31
|
+
| 2.13.0 | `useAriaLive` `announce` and `clear` | **Before:** Each `announce` started its own 50 ms timer, and nothing ever cancelled it. Two calls less than 50 ms apart rendered both messages in turn, so a screen reader could start reading the outdated one. A `clear` within 50 ms of `announce` was undone when the pending message arrived. A timer still pending at unmount was left to fire. **After:** One announcement is pending at a time. A newer `announce` replaces the pending one (the last call wins), `clear` cancels it, and unmounting cancels it, so no state update runs after unmount. The signature, the 50 ms delay and the empty-then-set step that re-announces a repeated message are unchanged. **Verified by:** `accessibility.test.tsx` "useAriaLive pending announcement timer (step 3)" ([STEP3-09](docs/remediation/STEP3-09-use-aria-live.md)) | b | None | Nothing, unless you relied on every queued message being rendered. In that case, combine them into one message (for example "3 files uploaded"). |
|
|
32
|
+
| 2.13.0 | `CommandPalette` search input, keyboard and list markup | **Before:** The search input was a plain `textbox`. Its ref pointed at InputBase's root `<div>`, so focus stayed on the dialog surface after opening and the arrow keys did nothing until the user clicked the input. Every option was a tab stop. Escape called `onClose` twice (once with no arguments, then from the Dialog with `'escapeKeyDown'`). The list was `<ul role="listbox">` with `<li>` wrappers, header boxes and an `<hr>` (axe `aria-required-children`, `aria-required-parent`, `listitem`). **After:** Focus moves into the input when the palette opens. The input is an APG `combobox` (`aria-autocomplete="list"`, `aria-expanded`, `aria-controls`, `aria-activedescendant`); the active option has `aria-selected="true"`, and options are not tab stops (Tab stays in the dialog). Escape is handled by the Dialog only, so `onClose` runs once. The list is `div` markup: `role="listbox"` > `role="group"` (named by its heading) > `role="option"`; the `data-index` attribute is gone, and the listbox is now the scroll container. Row spacing is unchanged. **Verified by:** `CommandPalette.test.tsx` (4 origin tests and "CommandPalette Accessibility (step 3)"), `a11y-priority-components.test.tsx` ([STEP3-10](docs/remediation/STEP3-10-command-palette.md)) | a, b | None | Query the input with `getByRole('combobox', { name: 'Command search' })` instead of `'textbox'`. If you counted `onClose` calls on Escape, expect one. Replace selectors that relied on `ul`/`li`, `hr` or `data-index` with roles (`listbox`, `group`, `option`). |
|
|
33
|
+
| 2.13.0 | `CommandPalette` dialog name and result announcements (new optional `labels` prop) | **Before:** The dialog had no accessible name (MUI's `aria-labelledby` pointed at a title that is never rendered). The documented live-region announcement of the result count did not exist. **After:** The dialog is named "Command palette". While you type, the number of matching commands is announced politely ("3 commands available", "1 command available"), and `emptyMessage` is announced when nothing matches. Both strings can be overridden with `labels={{ dialog, resultsCount: ({ count }) => string }}` (new type `CommandPaletteLabels`). **Verified by:** `CommandPalette.test.tsx` "names and structure", "result announcements" ([STEP3-10](docs/remediation/STEP3-10-command-palette.md)) | a, b | None; the new optional `labels` prop localises the strings | Nothing. To localise, pass `labels`. |
|
|
34
|
+
| 2.13.0 | `ClearableTextField` clear button (also the `FilterBar` text filter) | **Before:** The clear button was named with the fixed lowercase string "clear input" and had no tooltip. Clearing with the keyboard (Tab to the button, then Enter or Space) removed the focused button, so focus dropped to `<body>`. **After:** The button is named by the new optional `clearLabel` prop, default "Clear input" (sentence case). The same string is shown as a tooltip on hover and keyboard focus. A keyboard clear moves focus back to the input, so the input's `onFocus` / `handleFocus` run again. A pointer clear leaves focus where it was, as before. `FilterBar`'s text filter renders `ClearableTextField` without `clearLabel`, so its clear button changes the same way: it is named "Clear input" (was "clear input"), shows that tooltip, and a keyboard clear moves focus to the filter input. **Verified by:** `ClearableTextField.test.tsx` "ClearableTextField step 3 (clear label, focus return, useControlled)" ([STEP3-11](docs/remediation/STEP3-11-clearable-text-field.md)) | b | None | Tests that match the name exactly with `'clear input'` (including the clear button of `FilterBar`'s text filter) should use `'Clear input'` or `/clear input/i`. Pass a specific label where the field has a purpose, for example `clearLabel="Clear search"`. |
|
|
35
|
+
| 2.13.0 | `ClearableTextField` `value` (controlled or uncontrolled) | **Before:** Whether `value` controlled the field was re-checked on every render. Passing `value` after an uncontrolled mount took over the field, and dropping it fell back to the component's own copy. A changed `defaultValue` was ignored silently. **After:** The mode is decided on the first render and kept (MUI `useControlled`). Without a switch nothing changes: both modes report the same `onHandleChange` values. A later switch logs MUI's controlled/uncontrolled warning in development; `value` passed after an uncontrolled mount is ignored, and a controlled field whose `value` becomes `undefined` renders empty. Changing `defaultValue` after mount is still ignored and now logs MUI's default-value warning in development. **Verified by:** `ClearableTextField.test.tsx` "controlled and uncontrolled modes (useControlled)" ([STEP3-11](docs/remediation/STEP3-11-clearable-text-field.md)) | b | None | Pass a string from the first render (`value={data?.name ?? ''}`) and keep passing one, or never pass `value`. To reset an uncontrolled field, remount it with a new `key`. |
|
|
36
|
+
| 2.13.0 | `ResizablePanel` resize handle (also the AI panel in `AppLayout` / `AIFabLayout`) | **Before:** The edge handle could only be dragged with a mouse. It had no role, name or keyboard support, and it was not in the Tab order. The width lived in private state with no callback and no controlled option. The width animated even for users who prefer reduced motion. **After:** The handle is a focusable window splitter: `role="separator"` named "Resize panel", with `aria-orientation="vertical"`, `aria-valuenow` / `aria-valuemin` / `aria-valuemax` in px and `aria-controls` pointing at the panel. Left / Right Arrow move it 10px (50px with Shift) and Home / End go to `minWidth` / `maxWidth`. It gains a Tab stop and a focus-visible ring. Touch drag now works. New optional props: `width` + `onWidthChange` (controlled width; `onWidthChange` also reports uncontrolled changes, clamped), `onResizeStart` / `onResizeEnd` (pointer drags), and `resizeHandleLabel`. `document.body`'s `cursor` and `user-select` are set only during a drag and then go back to their previous values; before, they were reset to `''` after a drag and also whenever the panel unmounted or its `minWidth` / `maxWidth` / `side` changed. No width animation under `prefers-reduced-motion: reduce`. **Verified by:** `ResizablePanel.test.tsx` ([STEP3-16](docs/remediation/STEP3-16-resizable-panel.md)) | b | None | Nothing required. Tests that count Tab stops in a page with an open panel gain one stop: the handle comes before the panel header. Tests can query the handle with `getByRole('separator', { name: 'Resize panel' })`. To own the width, pass `width` and `onWidthChange` from the first render. |
|
|
37
|
+
| 2.13.0 | `AppLayout` AI panel width (also `AIFabLayout`) | **Before:** The content margin and the AI FAB offset came from the `aiPanelWidth` prop only. After the user dragged the panel edge, page content sat under the widened panel (or a gap opened beside a narrowed one), so focused content could be hidden (WCAG 2.4.11). Changing `aiPanelWidth` moved the margin but not the panel. Closing and reopening the panel threw the dragged width away. An `aiPanelWidth` outside 320–800 gave a margin that did not match the panel the CSS clamped (for example a 900px margin next to an 800px panel). **After:** AppLayout owns the panel width. The content margin and the FAB offset (width + gutter) follow every drag and every key press on the panel's "Resize panel" splitter. The resized width is kept while AppLayout stays mounted, also across close and reopen. Changing `aiPanelWidth` resets the panel, the margin and the FAB to the new width. `aiPanelWidth` is kept within 320–800 for the panel, the margin and the FAB alike (`NaN` falls back to 420). The margin no longer animates during a drag or under `prefers-reduced-motion: reduce`; it still animates (225ms) when the panel opens or closes. **Verified by:** `AppLayout.test.tsx` ([STEP3-17](docs/remediation/STEP3-17-app-layout.md)) | b | None | Nothing required. Visual tests that drag or resize the AI panel now see the content and the FAB move with it. If you passed an `aiPanelWidth` below 320 or above 800, the margin now matches the clamped panel. AppLayout does not report the resized width yet (no `onAiPanelWidthChange`), so it cannot be saved between sessions. |
|
|
38
|
+
| 2.13.0 | `TransferList` rows, move buttons, select-all, lists and search | **Before:** The four move buttons were icon-only with no accessible name and no tooltip. Each item row was a `role="button"` holding an unlabeled MUI `Checkbox` input with `tabIndex={-1}`, so the row did not expose its checked state (axe `label`, `nested-interactive`). The header select-all checkbox had no name; with a disabled item in the list it could never clear the selection; while partial it carried MUI's `aria-checked="mixed"` without the native `indeterminate` state (axe `aria-conditional-attr`). The lists had no names, and the empty message was a `div` inside the `<ul>` (axe `list`). The search field had no name, and its clear button had none either. **After:** The move buttons are named "Move all right", "Move selected right", "Move selected left" and "Move all left" and show the same text as a tooltip on hover and keyboard focus; a disabled button still shows it on hover (each button sits in a `<span>`). Rows are `role="checkbox"` with `aria-checked` (and `aria-disabled="true"`, out of the Tab order, when disabled), named by their content; Space or Enter toggles. The check mark is a decorative icon with the same size and palette colours, so rows contain no `input`. Each list is named by its panel title. Select-all is named "Select all {title}", clears the selection when every enabled item is checked, is disabled when the panel has no enabled item, and reports mixed both natively and through ARIA. The empty message renders after the list, and the list's height and scrolling moved from the `<ul>` to a wrapping `div`. Search is a `ClearableTextField`: the field is named "Search {title}" (placeholder unchanged) and the clear button "Clear search {title}", with the same tooltip; a keyboard clear returns focus to the field. **Verified by:** `TransferList.test.tsx` (10 origin tests and "TransferList Accessibility (step 3)") ([STEP3-12](docs/remediation/STEP3-12-transfer-list.md)) | b | None | Query rows with `getByRole('checkbox', { name })` instead of `'button'` and assert `toBeChecked()` on the row. Selectors on `.MuiCheckbox-root` or `input[type="checkbox"]` inside rows no longer match (the header select-all is still a MUI `Checkbox`). Query move buttons by name. Styles that set the list's height or overflow on `.MuiList-root` now belong on its parent. |
|
|
39
|
+
| 2.13.0 | `TransferList` move enablement, focus and announcement after a move (new optional `labels` prop) | **Before:** "Move all" stayed enabled while only disabled items remained, and "Move selected" while its checked items had been replaced through `leftItems` / `rightItems`; activating either moved nothing but still called `onChange` with unchanged lists. After a move, the activated button disabled itself and keyboard focus dropped to `<body>`. Nothing announced the move. All strings were fixed English. **After:** A move button is enabled only when activating it moves at least one item, so `onChange` is no longer called for an empty move. After a move, focus goes to the first moved item in the destination list, or to that list's select-all checkbox when its search hides the item (only when focus was lost: focus the user put elsewhere stays). A polite live region announces the move, for example "Moved 2 items to Selected". The new optional `labels` prop (type `TransferListLabels`, exported) overrides every user-facing string: the four button names, `selectAll`, `search`, `clearSearch`, `selectedCount`, `empty`, `noMatches` and the `moved` announcement. Defaults are the English strings above. **Verified by:** `TransferList.test.tsx` "TransferList Accessibility (step 3)" ([STEP3-12](docs/remediation/STEP3-12-transfer-list.md)) | b, e | None; the new optional `labels` prop localises the strings | Nothing, unless you relied on `onChange` firing for a move that moved nothing. Pass `labels` to reword or translate the strings; functions receive `{ title }`, `{ count, total }` or `{ count, title }`. |
|
|
40
|
+
| 2.13.0 | `TreeView` keyboard focus, chevrons and checkboxes | **Before:** No node was a Tab stop until an arrow key had been pressed, so Tab skipped the nodes and landed on each branch's chevron button and on each checkbox instead. Those chevrons had no accessible name (axe `button-name`) and the checkboxes had no label (axe `label`). In a browser, naming the chevron would also have renamed its node ("Expand Documents Documents"). The arrow keys found the next node with a page-wide query, so with two trees on a page they could move focus into the other tree. Disabled nodes were reachable with the arrow keys. ArrowLeft on a child did nothing, and ArrowRight on a leaf reported the leaf through `onExpand`. Leaves rendered an invisible button. Child nodes had no `role="group"`. Expanding animated even for users who prefer reduced motion. **After:** Each tree is one Tab stop (WAI-ARIA APG Tree View, toolkit `useRovingTabIndex`): Tab lands on the first visible selected node, otherwise on the first enabled node, and the server markup already has that single Tab stop. After the user moves focus inside the tree, Tab returns to the node they last focused. ArrowUp, ArrowDown, Home and End stay inside the tree and skip disabled nodes. ArrowLeft on a child moves to its parent. ArrowRight on a leaf does nothing. The chevron is named "Expand {label}" / "Collapse {label}", shows the same text as a tooltip, and is not in the Tab order. Clicking it, or a checkbox, moves focus to that node. Checkboxes are not in the Tab order and are named by the node label. Each node is named by its label only (`aria-labelledby`). Leaves render a hidden, non-interactive placeholder instead of a button. Child nodes sit in a `role="group"`. Expanding does not animate under `prefers-reduced-motion: reduce`. The focused node shows a 2px `primary.main` ring, and the tree has 4px padding on all sides (vertical padding was 0) so the ring is not clipped. **Verified by:** `TreeView.test.tsx` (4 origin tests and "TreeView step 3 (B-35: roving tab stop, named chevron, composed search)") ([STEP3-14](docs/remediation/STEP3-14-tree-view.md)) | b | None | Tests that pressed Tab to reach a chevron or checkbox should Tab to the tree and use the arrow keys, Enter or Space on the node. Query the chevron by name (`getByRole('button', { name: 'Expand Documents' })`). Do not expect a button inside a leaf node or an `onExpand` call for ArrowRight on a leaf. Tests that call `element.focus()` on a node outside `act()` may log an act warning, because the tree now records the focused node. |
|
|
41
|
+
| 2.13.0 | `TreeView` tree name, `renderNode` and search (new optional `aria-label` / `aria-labelledby` props) | **Before:** The tree was always named "Tree view". `renderNode` output was placed directly in the row. The search field's clear button had no accessible name and no tooltip, and clearing it with the keyboard dropped focus to `<body>`. **After:** Additive: `aria-label` and `aria-labelledby` name the tree. "Tree view" stays the default when neither is passed, and `aria-labelledby` suppresses it. `renderNode` output is wrapped in a flex box (`display: flex`, `align-items: center`, `flex: 1`, `min-width: 0`) whose text names the node. The search field is the shared `ClearableTextField`: its clear button is named "Clear search" with the same tooltip, and a keyboard clear returns focus to the field. **Verified by:** `TreeView.test.tsx` "names and structure", "search" ([STEP3-14](docs/remediation/STEP3-14-tree-view.md)) | b | None | Name each tree: `aria-labelledby` pointing at a visible heading, or `aria-label`. If a `renderNode` layout relied on being a direct flex child of the row, check it; the wrapper keeps `flex: 1`. Query the clear button with `getByRole('button', { name: 'Clear search' })`. |
|
|
42
|
+
| 2.13.0 | `GanttView` focus under the sticky task column and header | **Before:** This step made bars and milestones Tab stops (see the `GanttView` row above). With `onSelect`, Tab or Shift+Tab could then move focus to a bar or milestone lying entirely under the sticky task column (after the timeline was scrolled sideways), or to a row or bar under the sticky header strip. Chromium did not scroll it into view, so the focused element was hidden (WCAG 2.4.11). **After:** The region reserves the task column (`labelColumnWidth`) and the 40px header strip as scroll padding (`scroll-padding-left`, `scroll-padding-top`), so focus scrolling never leaves a bar, milestone or row entirely under them. A bar that is already partly visible beside the column is not moved, so part of it and its focus ring can stay under the column. The task-list rows and group chevrons get `scroll-margin-left: -{labelColumnWidth}px` and `scroll-margin-right: {labelColumnWidth}px`, so focusing one scrolls the timeline only vertically, never sideways. Content you pass in a label or description keeps its own scroll margin, so a horizontal scroller nested there still reveals its focused item; focusing a control in that content can scroll the timeline back to its start, and the control stays visible. Pointer and wheel scrolling are unchanged. **Verified by:** `GanttView.test.tsx` "GanttView scroll padding and SSR-stable summaries (step 3, round 1)" ([STEP3-R1-gantt](docs/remediation/STEP3-R1-gantt.md)) | b | A `scrollPadding*` value passed through `sx` replaces the reservation | Nothing. A `scrollPadding*` value you pass through `sx` replaces this reservation. |
|
|
43
|
+
| 2.13.0 | `GanttView` task summaries in server-rendered markup | **Before:** The visually hidden summary that this step added to each bar and milestone (see the `GanttView` row above) formatted its dates with the runtime's default locale and time zone. When the server and the browser used a different locale or time zone, hydrating server markup failed ("Hydration failed because the server rendered text didn't match the client") and React regenerated the tree. **After:** On the server and during hydration the summary formats dates in a fixed form, en-US in UTC (for example "1/7/2026"), so the summary text matches between server and client. A date built in local time (`new Date(2026, 0, 7)`, or a date-time string without an offset such as `'2026-01-07T00:00:00'`) is a different instant in each time zone, so in server markup it can read another day (a local midnight reads one day early on a server east of UTC); that difference never fails hydration, and the browser's own text replaces it right after. Date-only strings such as `'2026-01-07'` are parsed as UTC, so they read the same day on the server and in the hydration render. Right after hydration, and on the first render of a client-only app, it uses the browser's locale and time zone, the same format as the hover tooltip. An invalid date reads "Invalid Date" as before. Tick labels in every granularity and bar positions still follow the runtime time zone (day and month labels also the locale). Week numbers and quarters come from local-time period starts, so date-only data on a period boundary (a Monday, or the first day of a quarter as in the `QuarterlyGranularity` story) can still fail hydration with a text mismatch when the server and the browser are in different time zones. This is pre-existing and is logged with the `toDate` UTC-parsing follow-up. **Verified by:** `GanttView.test.tsx` "GanttView scroll padding and SSR-stable summaries (step 3, round 1)" and "GanttView summaries across time zones (step 3, round 2)" ([STEP3-R1-gantt](docs/remediation/STEP3-R1-gantt.md)) | b | None | Nothing. Server-markup snapshots now show the fixed en-US/UTC dates in the summaries. |
|
|
44
|
+
| 2.13.0 | `GanttView` header strips and task descriptions (light and dark) | **Before:** This step moved the GanttView body to palette roles (see the first `GanttView` row above), but the two sticky header strips (the "Task" header over the task list and the time-scale header) kept static light colours: background `#FAFAFA`, borders `#E5E7EB`. In dark mode a light band therefore sat over a dark chart. In both modes the "Task" caption (`#9CA3AF`, 2.43:1 on its strip) failed text contrast, and in light mode the row descriptions (`#9CA3AF`) failed it too: 2.54:1 on a resting row, 2.43:1 hovered, 2.14:1 on the selected row (axe `color-contrast`). **After:** Both strips use `background.default` with `divider` borders, the same surface as the body's group rows, so the whole chart follows the theme mode. In dark mode the strips are `#18181B`. The "Task" caption, the tick labels and the row descriptions use `text.secondary`. In light mode that is `#374151`: 9.86:1 on the strip, 10.29:1 on a resting row, 9.86:1 hovered, 8.67:1 selected. In dark mode it is `#9CA3AF`: 7.02:1 on the strip, 5.91:1 resting, 7.02:1 hovered, 4.55:1 selected. Ratios are MUI `getContrastRatio`. In light mode the strip backgrounds and borders keep their values, and the caption, tick and description text gets darker. This supersedes the first `GanttView` row's statement that the headers and captions are unchanged. **Verified by:** `GanttView.test.tsx` "GanttView header strips and description text (step 3, round 1)" ([STEP3-R1-gantt](docs/remediation/STEP3-R1-gantt.md)) | c | None | Nothing, unless a visual snapshot or test pins the old colours. Update it to the light text `#374151` and the dark strips `#18181B`. |
|
|
45
|
+
| 2.13.0 | `FileUpload` declined updates (controlled `files`) and repeated `onFilesChange` values | **Before:** This step's lifecycle change (first `FileUpload` row) built each change on the last list FileUpload had reported, even when the parent had not applied it: a file the parent declined (by not calling `setFiles`, or by returning `prev`) came back with the next selection, removal or upload tick and still counted towards `maxFiles`, so a parent that declined one file could end up unable to add any. 2.11.1 built selections and removals on the rendered `files`, so a declined file stayed declined there. In 2.11.1, while simulated progress waited at 90%, and after a file was removed mid-upload, `onFilesChange` fired every 200 ms (with the stale list described in the first `FileUpload` row) until the upload settled. **After:** Each change builds on the list of the last render, with FileUpload's own upload transitions (`status`, `progress`, `error`) applied to the files in it. A declined file stays declined and is not counted by `maxFiles`, as in 2.11.1, and a parent edit made after it accepted a value is kept, including its own copy of a reported file (for example with a translated `error`). Values reported back-to-back before React renders (a selection and its first `uploading` transition) still build on each other. After a selection or removal, FileUpload holds its own upload reports until the parent's answer renders, for up to 200 ms (one progress step). An upload report the parent renders after the user's selection or removal (a transition that is still rendering) neither ends that hold nor drops a file selected after the report was made. So a parent that applies each value synchronously or in `startTransition` keeps each selection and removal its list shows before the user's next one, and every upload outcome of a listed file reaches it. A transition that changes nothing reports nothing, and an upload whose file is no longer listed stops its progress timer: unlike 2.11.1, no call is made while progress waits at 90% or after the file was removed. **Verified by:** `FileUpload.test.tsx` "declined and late updates (R1S3-BEHAV-01)", "no-op transitions report nothing (R1S3-BEHAV-06)", "FileUpload deferred parents, StrictMode re-show previews and declined removals (step 3, review round 2)" and "FileUpload late commits of earlier reports (step 3, review round 3)" ([STEP3-R1](docs/remediation/STEP3-R1-file-upload.md)) | a | None | Nothing, if you apply or decline each value synchronously (`onFilesChange={setFiles}`, or a functional updater such as `setFiles((prev) => …)`) or in `startTransition`. Values applied later (`setTimeout`, even `setTimeout(0)`, or after an `await`) are not guaranteed. An older report can be applied after the user's selection or removal and undo it: browsers delay a `setTimeout(0)` scheduled from FileUpload's progress timer by 4 ms, so it can run after the one for the user's change. A value held back past FileUpload's next report (the user's next selection or removal, or the next progress step or outcome of an upload in flight) is lost when you apply it, because that report is built on the list you rendered before (for example `await validate(next)` before `setFiles`): apply the value at once and validate afterwards, or keep pending files in your own state. If you counted `onFilesChange` calls as progress ticks, read `progress` instead: no call is made while it waits at 90%. |
|
|
46
|
+
| 2.13.0 | `FileUpload` inside React `<Activity mode="hidden">` | **Before:** This step's unmount cleanup (first `FileUpload` row) also ran when `<Activity>` hid FileUpload, and stopped all reporting for good: an upload that settled while hidden was lost (after showing it again the file stayed `uploading` at its last progress and no timer restarted), and the image preview URL in the list had already been revoked. 2.11.1 had no cleanup: uploads kept running and `onFilesChange` kept being called while hidden (with the stale list described in the first `FileUpload` row), an upload that settled while hidden completed, and no preview URL was revoked on hide. **After:** Uploads keep running while hidden and nothing is reported. When FileUpload is shown again, simulated progress resumes for uploads still in flight, each listed image file gets a new preview URL for the one revoked on hide, and whatever changed while hidden is reported in one `onFilesChange` call (two in development under React StrictMode when an image file gets a new preview URL, because StrictMode replays effects). After a true unmount nothing is reported. Against 2.11.1: uploads still complete; `onFilesChange` is not called while FileUpload is hidden, only when it is shown again; and preview URLs FileUpload created are revoked on hide and recreated on show. **Verified by:** `FileUpload.test.tsx` "React <Activity> (R1S3-BEHAV-03)" ([STEP3-R1](docs/remediation/STEP3-R1-file-upload.md)) | a | None | If you render previews from `files` outside the hidden subtree, expect the `preview` URL of FileUpload-created previews to change once when it is shown again (the old one is revoked on hide). If you relied on `onFilesChange` calls while FileUpload is hidden, expect what changed in one call when it is shown again. |
|
|
47
|
+
| 2.13.0 | `FileUpload` remove button and focus after removal (new optional `labels` prop) | **Before:** The remove button in each file row (dropzone and button variants) was icon-only with no accessible name and no tooltip (axe `button-name`). Removing a file with the keyboard (Enter or Space on that button, or Backspace or Delete on a compact chip) removed the focused control, so focus dropped to `<body>`. The progress bar name this step added ("Uploading {file name}", see the upload progress bar row) was fixed English. **After:** The remove button is named "Remove {file name}" and shows the same text as a tooltip on hover and keyboard focus. After a removal from the focused control, once the file has left the rendered list, focus moves to the next file's remove button (or chip), else the previous file's, else the dropzone, the upload button or Browse. Focus moves only when it was on the removed control: a click that did not focus the button, or focus elsewhere, is left alone, and a controlled parent that declines the removal keeps the row and the focus. The new optional `labels` prop (type `FileUploadLabels`, exported) overrides `remove` and `uploading`, each a function of `{ name }`; defaults are the English strings above. **Verified by:** `FileUpload.test.tsx` "FileUpload remove button, focus after removal and labels (step 3, review round 1)" ([STEP3-R1](docs/remediation/STEP3-R1-file-upload.md)) | b | None; the new optional `labels` prop localises the strings | Nothing required. Query remove buttons by name, for example `getByRole('button', { name: 'Remove report.pdf' })`; `within(row).getByRole('button')` still finds the one button in a row. If a test expected focus on `<body>` after a keyboard removal, expect it on the neighbouring control instead. Pass `labels` to reword or translate the names. |
|
|
48
|
+
| 2.13.0 | `useReducedMotion` during server rendering and hydration | **Before:** The hook read `matchMedia` in its `useState` initializer. In a server-rendered app, a user who prefers reduced motion got `false` from the server render and `true` from the hydration render, so a component that renders different markup from `useReducedMotion()` made React log a hydration mismatch. A component hidden and shown again by React `<Activity>` also kept a preference that had changed while it was hidden. **After:** The server render and the hydration render both return `false`, so the markup matches and React logs nothing. Straight after hydration the component re-renders with the user's preference. A render on the client without server markup still gets the preference on its first render, and a change of the preference still re-renders. After `<Activity>` shows the component again, it reads the current preference. The signature is unchanged (`useReducedMotion(): boolean`). New in this release: `ResizablePanel`, `AppLayout` and `TreeView` read the hook for their motion (see their rows above), so they also hydrate without a mismatch and follow the preference from the commit right after hydration. **Verified by:** `useReducedMotion.hydration.test.tsx`, `useReducedMotion.ssr.test.tsx` ([STEP3-R1-resize](docs/remediation/STEP3-R1-resize.md)) | b | None | Nothing. If your own server-rendered component renders different markup from `useReducedMotion()`, expect the server (`false`) markup until the commit right after hydration. To keep motion that starts on mount from playing in that window, guard it in CSS with `@media (prefers-reduced-motion: reduce)`. |
|
|
49
|
+
| 2.13.0 | `ResizablePanel` drag start, mode-switch warning and new optional `disableResize` prop (also `SplitPane`, and the AI panel in `AppLayout` / `AIFabLayout`) | **Before:** A drag started from the stored width (`defaultWidth` until the first drag), while CSS rendered it clamped to `minWidth`–`maxWidth`. With a `defaultWidth` outside that range, the first pointer moves did nothing until the pointer had crossed the overflow: with `defaultWidth={1000}` and `maxWidth={800}`, the panel stayed at 800 for the first 200px of a drag that narrows it. `SplitPane` shares the drag code: with `size` 400 and `maxSize` 300, a 10px drag reported 300. The resize handle could not be turned off. **After:** A drag starts from the width the panel renders (clamped): the same example gives 790 after 10px and 700 after 100px. `SplitPane`: a drag from a `size` or `defaultSize` above `maxSize` (or the container) starts from the clamped size, so the same `SplitPane` example now reports 290 (was 300). New in this release with the `width` prop (see the `ResizablePanel` resize handle row): a controlled `width` outside the range also moves from the rendered width, and switching `width` between a number and `undefined` logs MUI's development warning, which names the component and prop ("the controlled width state of ResizablePanel"). Additive: `disableResize` (default `false`) removes the resize handle, so there is no drag (and no Tab stop or `aria-valuenow` from the splitter this release adds); a drag in progress ends (`onResizeEnd` fires) and the width is kept for when resizing is turned back on. **Verified by:** `ResizablePanel.test.tsx` "drag start from the rendered width", "controlled/uncontrolled switch warning", "disableResize"; `SplitPane.test.tsx` "a drag from a size above maxSize starts from maxSize" ([STEP3-R1-resize](docs/remediation/STEP3-R1-resize.md)) | e | None | Nothing required. If you relied on the first pixels of a drag being ignored above `maxWidth` (or `SplitPane`'s `maxSize`), expect the panel or pane to move at once. Pass `disableResize` while the panel does not show its width (for example when it fills the screen). |
|
|
50
|
+
| 2.13.0 | `AppLayout` AI panel in fullscreen (also `AIFabLayout`) | **Before:** In fullscreen the panel kept its mouse resize handle on its left edge: a drag changed the hidden width, which the panel showed once it left fullscreen. In `navStyle="sidebar"`, leaving fullscreen docked the panel at once while the content margin animated from 0 over 225ms, so up to the panel's width of content sat under it for about 220ms. **After:** The fullscreen panel has no resize handle: a drag cannot change the hidden width, and a drag in progress ends when fullscreen starts. Leaving fullscreen brings the handle back at the width the user had chosen, with the content margin and the FAB at that width (see the `AppLayout` AI panel width row); the margin moves at once, with the panel (entering fullscreen keeps the 225ms margin animation). The keyboard splitter this release adds (see the `ResizablePanel` resize handle row) is not rendered in fullscreen either. **Verified by:** `AppLayout.test.tsx` "fullscreen AI panel (R1S3-BEHAV-04)", "panel and margin settle together (review round 2)" ([STEP3-R1-resize](docs/remediation/STEP3-R1-resize.md)) | b | None | Nothing required. Compared with 2.11.1, a fullscreen panel's Tab stops are unchanged: the splitter from the `ResizablePanel` resize handle row adds no stop in fullscreen. |
|
|
51
|
+
| 2.13.0 | `ResizablePanel` closed during a drag, and the end of a drag | **Before:** Closing a standalone panel (`open={false}`) during a mouse drag left the drag running: `document.body` kept the resize cursor and `user-select: none` until the button was released, and pointer moves kept changing the hidden width, which the panel showed when it reopened. `AppLayout` unmounts its panel when it closes, which ended the drag there. **After:** Closing the panel during a drag ends it the way `disableResize` does: the listeners, the body cursor and `user-select` are released, and the panel reopens at the width reached when it closed. New in this release with `onResizeStart` / `onResizeEnd` (see the `ResizablePanel` resize handle row): closing mid-drag calls `onResizeEnd` once with that width, and the panel's own width animation stays off from `onResizeStart` until `onResizeEnd`, so a parent that pauses its own transitions between the two callbacks settles in the same frame as the panel. `AppLayout`'s content margin, which follows drags from this release on, settles with the panel this way. **Verified by:** `ResizablePanel.test.tsx` "closing the panel (open={false}) ends a drag in progress", "keeps the width animation off until onResizeEnd is called"; `AppLayout.test.tsx` "panel and margin settle together (review round 2)" ([STEP3-R1-resize](docs/remediation/STEP3-R1-resize.md)) | a | None | Nothing required. Closing a standalone panel mid-drag now releases the cursor and text selection at once, and later pointer moves no longer change its width. |
|
|
52
|
+
| 2.13.0 | `OrgChart` node positions (top row and leftmost node) | **Before:** Each node box is drawn at its centre minus half the node size, but the layout shifted the tree by the 16px canvas padding only. The top row therefore started above the chart region (-32px with the default 96px `nodeHeight`) and the leftmost node to its left (-84px with the default 200px `nodeWidth`). The chart region cannot scroll to negative positions, so that strip could never be seen: the root node's title and the left part of the leftmost node were cut off in every orientation, with the default body and with `renderNode`. The bottom and right edges had half a node of extra space instead. **After:** Every node box starts 16px inside the chart region's top and left edges, and the canvas ends 16px after the lowest and right-most boxes. The canvas size and the scroll extent are unchanged: the whole tree, connectors included, moves right by half a node width and down by half a node height. **Verified by:** `OrgChart.layout.test.tsx` "OrgChart layout (step 3 review round 2)" ([STEP3-R1-misc](docs/remediation/STEP3-R1-misc.md)) | a | None | Nothing. Visual snapshots or tests that pin node `left` / `top` or connector coordinates shift by `nodeWidth / 2` horizontally and `nodeHeight / 2` vertically. |
|
|
53
|
+
| 2.13.0 | Status indicators, status chips and the coral `Button` states | **Before:** error, failed and rejected fills were coral `#FF6150` (white text 2.97:1) and success fills `#24A148` (3.35:1); coral button hover kept white labels; status chips used the fill colour as small text on a translucent tint. **After:** error fills are `#DA1E28` (ADR-03-R2) and success fills `#15803D` (white text meets 4.5:1), coral button hover labels are navy, status chips use the validated solid pair, and warning inline labels use the darker outline colour. **Verified by:** [C-03](docs/remediation/C-03-state-pair-contrast.md) | c | None | Nothing. If your own UI copied the old fills, update it, or pin the old hex values at the call site. |
|
|
54
|
+
| 2.13.0 | Dark theme palette and dark component colours (React and CSS) | **Before:** in dark mode the page was `#18181B`, paper `#27272A` and `text.secondary` `#9CA3AF`; status and progress fills, button and switch states, shadows, overlays and the legacy CSS aliases kept light-mode values. **After:** page `#121214`, paper `#1C1C1F`, `text.secondary` `#D1D5DB`; those roles take their dark values from one dark token matrix in React, runtime CSS and the CSS package, and legacy aliases follow `[data-theme="dark"]`. **Verified by:** [dark foundations](docs/remediation/C-02-dark-semantic-foundations.md), [status indicators](docs/remediation/C-02-dark-status-indicators.md), [interactive states](docs/remediation/C-02-dark-interactive-component-states.md), [shadows](docs/remediation/C-02-dark-shadow-matrix.md), [effects](docs/remediation/C-02-dark-effect-matrix.md), [alias indirection](docs/remediation/C-02-alias-indirection.md), [component tiering](docs/remediation/C-02-component-color-tiering.md) | c | Override the MUI palette or the `--trinity-*` variables in your app | Review custom dark surfaces that hard-coded the old dark values. |
|
|
55
|
+
| 2.13.0 | Charts: colours in dark mode and the default series palette | **Before:** axes, grid lines, legends, tooltips and gauge remainders used light-mode colours in dark mode, and default series colours cycled back to the first colour once the palette ran out. **After:** chart furniture follows the active palette mode; default series use eight governed colours (each at least 3:1 on the light and the dark page), and a chart with more than eight series and no `colors` throws a `RangeError` that names the series. **Verified by:** [chart colour contract](docs/remediation/C-02-chart-color-contract.md), [theme consumption](docs/remediation/C-02-chart-theme-consumption.md) | c | Pass `colors` (an explicit palette still cycles) or per-series colours | A chart with more than eight default-coloured series must pass `colors`, group the rest into an "Other" series, or encode the series another way. |
|
|
56
|
+
| 2.13.0 | `Autocomplete` single-select value | **Before:** a normal single selection threw (an array method ran on the selected object or string), clearing emitted `[]` in both modes, and a free-solo entry was ignored. **After:** single mode emits the selected option or free-solo string and clears to `null`; multiple mode emits arrays and clears to `[]`. **Verified by:** [C-01](docs/remediation/C-01-autocomplete-value-contract.md) | a | None | If you accepted `[]` from a single-select clear as a workaround, accept `null`. |
|
|
57
|
+
| 2.13.0 | Product theme presets (CRM, Portal and Analytics, light and dark) | **Before:** all six presets lost palette or shape state, and ordinary MUI components crashed while rendering with them. **After:** each preset is built from complete theme options in one `createTheme` call and renders. **Verified by:** [C-04](docs/remediation/C-04-theme-presets.md) | a | None | Remove any workaround that replaced a preset with the base theme. |
|
|
58
|
+
| 2.13.0 | `Tour` during server rendering | **Before:** an open `Tour` read `window` during render and crashed server rendering. **After:** render is environment-pure; the portal renders only after mount, and the viewport and target are measured on the client. **Verified by:** [H-01](docs/remediation/H-01-tour-ssr.md) | b | None | Nothing. |
|
|
59
|
+
| 2.13.0 | `FormProvider` validation and submit errors | **Before:** validators read the previous render's values, so cross-field rules saw stale input; `isValid` and the error map came from different renders; submit failures were caught and discarded. **After:** every rule in a validation pass receives the same proposed snapshot, async rules settle in order, and submit failures surface through the new `state.submitError` and `onSubmitError`. **Verified by:** [H-02](docs/remediation/H-02-form-snapshot-validation.md) | a | None | Nothing. To show submit failures, read `state.submitError` or pass `onSubmitError`. |
|
|
60
|
+
| 2.13.0 | CSS variables `--trinity-z-*` and `--trinity-leading-*` (both packages) | **Before:** z-index variables were emitted with a `px` unit (invalid for `z-index`) and line-height ratios with a unit. **After:** both are unitless; other values and every variable name are unchanged. **Verified by:** [H-05](docs/remediation/H-05-typed-numeric-units.md) | a | None | Remove any local override that stripped the unit. |
|
|
61
|
+
| 2.13.0 | `AITextBlock` markdown links | **Before:** Underlined only on hover; color was the only cue inside text. **After:** Always underlined, in light and dark mode. **Verified by:** `AITextBlock.test.tsx` | b | `sx={{ '& .MuiLink-root': { textDecoration: 'none' } }}` on `AITextBlock` (not recommended: it brings back the WCAG 1.4.1 failure) | Nothing. To restore the old look, pass the opt-out `sx` (fails WCAG 1.4.1). |
|
|
62
|
+
| 2.13.0 | `InsightEngineInput` attachment Remove and Retry upload buttons | **Before:** A 22 by 22px pointer target. **After:** A 24 by 24px pointer target; the button looks the same. **Verified by:** `InsightEngineInput.stories.tsx` Attachment Action Tooltips (`assertTargetSize`) | b | none (an invisible hit area around the same 22px button; there is nothing visible to restore, and removing it would bring back the WCAG 2.5.8 failure) | Nothing. |
|
|
63
|
+
| 2.13.0 | `UploadDropZone`, `StatusLegend` | **Before:** An array or function `sx` was ignored. **After:** It is applied, like an object `sx`. **Verified by:** `sx-composition.test.tsx` (the trinity/sx-spread ratchet at 0) | b | none (an array or function `sx` was silently ignored before; an object `sx` behaves as before) | Check any array or function `sx` you pass: it now takes effect. |
|
|
64
|
+
| 2.13.0 | `useFlowAutoLayout` / `layoutFlowElements` with `engine: 'elk'` | **Before:** Every direction (`TB`, the default, `BT` and `RL`) was laid out left to right; the handles still pointed the requested way. **After:** Nodes follow the requested direction: `TB` top to bottom, `BT` bottom to top, `LR` left to right, `RL` right to left. **Verified by:** `useFlowAutoLayout.test.ts` (ELK honours the direction) | b | none (the old output ignored the requested direction; pass `direction: 'LR'` to get the left-to-right layout every ELK call produced before) | If a graph relied on the left-to-right result, pass `direction: 'LR'`. |
|
|
65
|
+
| 2.13.0 | `LineChart`, `BarChart`, `AreaChart`, `PieChart`, `DonutChart`, `ScatterChart`, `BubbleChart`, `RadialBarChart`, `GaugeChart`, `ComposedChart` | **Before:** An unnamed `role="img"` SVG; `ariaLabel` reached no assistive technology; no keyboard access; a console warning asked for the accessibility module. **After:** A region named by `ariaLabel` (else `title`, else the chart type) with a screen-reader summary; Tab into the chart and the arrow keys reach every point; Tab reaches the legend entries; no warning. **Verified by:** `chartAccessibilityModule.test.tsx`; `chartHighchartsInstance.test.ts`; `Charts.stories.tsx` Line Chart - Basic (play: region, summary, keyboard reach of points) | b | none (the charts already asked for accessibility with `accessibility.enabled: true`; without the module they fail WCAG 1.1.1, 2.1.1 and 4.1.2, so there is no old behavior worth restoring) | Nothing. Pass `ariaLabel` (or `title`) so the region has a useful name. If a page test counted Tab stops or landmarks, count the chart's. |
|
|
66
|
+
| 2.13.0 | `Sparkline` | **Before:** An unnamed `role="img"` SVG. **After:** A screen-reader summary ("Line chart with N data points", value range); no landmark, no Tab stop. **Verified by:** `chartAccessibilityModule.test.tsx` "Sparkline keeps the summary but adds no landmark and no Tab stop" | b | none (the charts already asked for accessibility with `accessibility.enabled: true`; without the module they fail WCAG 1.1.1, 2.1.1 and 4.1.2, so there is no old behavior worth restoring) | Nothing. |
|
|
67
|
+
| 2.13.0 | `--trinity-input-border-default`, `-hover`, `-focus`, `-error` (static `trinity.css`) | **Before:** Fixed light colours (`#E5E7EB`, `#9CA3AF`, `#7841C9`, `#DA1E28`) in light and dark mode. **After:** `var(--trinity-input-border-color-*)`: in light mode `#D4D4D8`, `#6B7280`, `#7841C9`, `#DA1E28`; in dark mode `#3F3F46`, `#52525B`, `#A96ADA`, `#F87171`. **Verified by:** `css-light-dark-value-kinds.test.ts`, CSS Theme / Theme Aware Component Surfaces story | a | none (a consumer who wants the old fixed colours can set the variables on `:root`, for example `--trinity-input-border-default: #E5E7EB`) | Nothing. To keep the old fixed colours, set them on `:root`. |
|
|
68
|
+
| 2.13.0 | `--trinity-switch-track-border` (static `trinity.css`) | **Before:** `#D4D4D8` in light and dark mode. **After:** `#D4D4D8` in light mode, `#6B7280` in dark mode, the colours the React Switch draws. **Verified by:** `css-light-dark-value-kinds.test.ts`, CSS Theme / Theme Aware Component Surfaces story | a | none (a consumer who wants the old fixed colours can set the variables on `:root`, for example `--trinity-input-border-default: #E5E7EB`) | Nothing. |
|
|
69
|
+
| 2.13.0 | `--trinity-focus-ring` (static `trinity.css`, dark blocks) | **Before:** `#A96ADA`, a bare colour: `box-shadow: var(--trinity-focus-ring)` was invalid in dark mode and drew no focus ring. `--trinity-focus-ring-default` had the same value. **After:** `0 0 0 3px rgba(169, 106, 218, 0.45)`, a box-shadow like the light value, in `[data-theme="dark"]` and `prefers-color-scheme: dark`. **Verified by:** `css-light-dark-value-kinds.test.ts`, `dark-effect-matrix.test.tsx`, CSS Theme / Dark Effect Matrix story | b | none (the old dark value was not a valid box-shadow, so there is no behavior to restore) | Nothing if you write it into `box-shadow`. If you used it as a colour in dark mode (`outline-color`, `border-color`), use `--trinity-border-focus` instead. |
|
|
70
|
+
| 2.13.0 | `TopNavWithSidebar` / `AppLayout` `selectedNavItem` changed after the first render | **Before:** Ignored: the highlight and `aria-current` stayed on the first value until a sidebar click. **After:** The highlight and `aria-current` follow the prop on every render. **Verified by:** `TopNavWithSidebar.test.tsx` "controlled: a new selectedNavItem moves aria-current and the selected styling"; `AppLayout.test.tsx` "a controlled selectedNavItem moves the sidebar highlight when it changes"; `AppLayout.stories.tsx` Controlled Navigation (play) | a | `defaultSelectedNavItem` (the sidebar keeps the selection itself and a click moves it, as with a constant `selectedNavItem` before) | Nothing. |
|
|
71
|
+
| 2.13.0 | `TopNavWithSidebar` / `AppLayout` with a constant `selectedNavItem` and a sidebar click | **Before:** The click moved the highlight, whatever the prop said. **After:** The click fires `onNavItemClick` and `onSelectedNavItemChange`; the highlight moves when you pass the new id. **Verified by:** `TopNavWithSidebar.test.tsx` "controlled: a selectedNavItem passed once and never updated keeps its item selected" | a | `defaultSelectedNavItem` (the sidebar keeps the selection itself and a click moves it, as with a constant `selectedNavItem` before) | Set `selectedNavItem` from `onSelectedNavItemChange` (or from your router), or pass `defaultSelectedNavItem` instead to let the sidebar keep the selection. |
|
|
72
|
+
| 2.13.0 | `AppLayout` `onSelectedNavItemChange` | **Before:** Called with `(itemId)`. **After:** Called with `(itemId, previousId)`. **Verified by:** `AppLayout.test.tsx` "reports other nav items through onSelectedNavItemChange and onNavigate" | a | `defaultSelectedNavItem` (the sidebar keeps the selection itself and a click moves it, as with a constant `selectedNavItem` before) | Nothing, unless a test asserts the exact arguments. |
|
|
73
|
+
| 2.13.0 | `Button` (contained, outlined, text) and `OutlinedInput` / `TextField` in error under the Trinity theme | **Before:** Earlier 2.13 builds only (2.11.1 was not affected): a plain `sx` colour, an `sx` `'&:hover'` colour, an app theme's `MuiButton.styleOverrides.contained['&:hover']` and an `sx` error outline on hover or focus lost to the theme's rules. **After:** Your `sx` and your theme overrides win; the theme's default colours and the disabled, pending and pressed states are unchanged. **Verified by:** `theme-sx-overridable.test.tsx` | b | none (the old rules silently dropped your `sx` and your theme overrides; to keep a Trinity colour, leave the property out of `sx`) | Nothing. If a recoloured Button should keep its colour on hover, set `'&:hover'` in its `sx`. |
|
|
74
|
+
| 2.13.0 | `InsightEngine` | **Before:** The side panel's "HISTORY (n)" toggle and the expanded view's "HISTORY" header used the browser's black button text, 1.23:1 on the dark drawer. **After:** Both labels use the theme's text.secondary in light and dark. **Verified by:** `InsightEngine.test.tsx` | b, c | none (each change restores a WCAG 2.2 AA requirement: readable label colours, a name for every control and list markup that matches the content; bringing the old output back would bring the defect back) | Nothing. |
|
|
75
|
+
| 2.13.0 | `AIRelatedQuestions` | **Before:** The "Related Questions" header used the browser's black button text, unreadable in the dark theme. **After:** The header uses the theme's text.primary in light and dark. **Verified by:** `AIRelatedQuestions.test.tsx` | b, c | none (each change restores a WCAG 2.2 AA requirement: readable label colours, a name for every control and list markup that matches the content; bringing the old output back would bring the defect back) | Nothing. |
|
|
76
|
+
| 2.13.0 | `Autocomplete` | **Before:** A typed match split an option label into separate pieces, each with its own tooltip and accessible name; "Safe" typed showed "Safe ty". **After:** The label is one text run with the match in bold, one tooltip and one name ("Safety"). **Verified by:** `Autocomplete.test.tsx` | b, c | none (each change restores a WCAG 2.2 AA requirement: readable label colours, a name for every control and list markup that matches the content; bringing the old output back would bring the defect back) | Nothing. A test that found an option by a fragment of its text finds it by its whole name. |
|
|
77
|
+
| 2.13.0 | `DataTable` | **Before:** Row-action icon buttons had no accessible name (their tooltip named a wrapping element) and the overflow menu button had none at all. **After:** Each row-action button is named by its action's label; the overflow button is named "More actions". **Verified by:** `CellRenderers.test.tsx` | b, c | none (each change restores a WCAG 2.2 AA requirement: readable label colours, a name for every control and list markup that matches the content; bringing the old output back would bring the defect back) | Nothing. To translate the new name, pass `labels={{ rowActionsMenu: '...' }}`. |
|
|
78
|
+
| 2.13.0 | `TopNavWithSidebar` | **Before:** The sidebar's items were list items directly inside the navigation landmark, with no list. **After:** The landmark holds a list of the items; the sidebar looks the same. AppLayout's sidebar renders it. **Verified by:** `TopNavWithSidebar.test.tsx` | b, c | none (each change restores a WCAG 2.2 AA requirement: readable label colours, a name for every control and list markup that matches the content; bringing the old output back would bring the defect back) | Nothing. |
|
|
79
|
+
| 2.13.0 | `SimpleTable` (`selectable`) | **Before:** A selectable row had `role="checkbox"` and `aria-checked`; the row checkbox had no accessible name; clicking the checkbox also ran `onRowClick`. **After:** Rows keep the row role. The checkbox in each row is the selection control, named by the row's first cell, and clicking it does not run `onRowClick`. **Verified by:** `SimpleTable.test.tsx` "names each row checkbox by the row" | b | none (the old row role="checkbox" was not accessible, and no prop restores it) | In tests, find the checkbox within the row: `within(row).getByRole('checkbox')` (or `getByRole('checkbox', { name: <first cell text> })`) instead of `getByRole('checkbox')` matching the row. |
|
|
80
|
+
| 2.13.0 | `SimpleTable` (`onRowClick`) | **Before:** The row action ran only on a mouse click; rows were not focusable. **After:** Each row's first cell holds a button named by that cell: a Tab stop, with Enter and Space. A mouse click on the row still runs the action. **Verified by:** `SimpleTable.test.tsx` "reaches the row action with Tab and runs it with Enter and Space" | b | none (the old row role="checkbox" was not accessible, and no prop restores it) | Nothing. Tests that count buttons or Tab stops see one more per row; query the action with `within(row).getByRole('button', { name: <first cell text> })`. |
|
|
81
|
+
| 2.13.0 | `AssessmentDrawer`, `AuditDrawer`, `EntityDetailPanel` (close button) | **Before:** The icon-only close button was named "Close drawer" and showed no tooltip. **After:** It shows the tooltip "Close drawer" on hover and keyboard focus. The name is unchanged. **Verified by:** `AssessmentDrawer.test.tsx`, `AuditDrawer.test.tsx`, `EntityDetailPanel.test.tsx` | b | none (the tooltip shows the button's existing accessible name; the behavior contract requires it for every icon-only button) | Nothing. A test that counts `role="tooltip"` elements sees one while the button is hovered or focused. |
|
|
82
|
+
| 2.13.0 | `PageHeader` (more-actions button) | **Before:** The icon-only button was named "More actions" and showed no tooltip. **After:** It shows the tooltip "More actions" on hover and keyboard focus. The name is unchanged. **Verified by:** `PageHeader.test.tsx` | b | none (the tooltip shows the button's existing accessible name; the behavior contract requires it for every icon-only button) | Nothing. |
|
|
83
|
+
| 2.13.0 | `AreaChart`, `BarChart`, `LineChart`, `PieChart`, `DonutChart`, `ScatterChart`, `BubbleChart`, `ComposedChart`, `RadialBarChart`, `GaugeChart`, `Sparkline` | **Before:** Charts animated in even when the user prefers reduced motion. PieChart, DonutChart and GaugeChart series also animated with `animate={false}`. **After:** No animation while the user prefers reduced motion, also with `animate={true}`. `animate={false}` stops the animation in every chart. **Verified by:** `Charts/__tests__/reducedMotion.test.tsx` | b | Carousel: `ref.current.play()` starts autoplay; charts and scrolls: none (they follow the user's reduced-motion setting, and nothing changes without it) | Nothing. Visual tests that run with reduced motion now capture the finished chart at once. |
|
|
84
|
+
| 2.13.0 | `PageTOC`, `GoToTop` | **Before:** Choosing an entry or the button always scrolled smoothly. **After:** They jump to the target (`behavior: 'auto'`) while the user prefers reduced motion. **Verified by:** `PageTOC.test.tsx`, `GoToTop.test.tsx` | b | Carousel: `ref.current.play()` starts autoplay; charts and scrolls: none (they follow the user's reduced-motion setting, and nothing changes without it) | Nothing. A test that mocks the reduced-motion query and expects `behavior: 'smooth'` now gets `'auto'`. |
|
|
85
|
+
| 2.13.0 | `Carousel` | **Before:** Autoplay started, and slides moved with a transition, for users who prefer reduced motion. **After:** Autoplay does not start by itself and slides change without a transition while the user prefers reduced motion. **Verified by:** `Carousel.test.tsx` | b | Carousel: `ref.current.play()` starts autoplay; charts and scrolls: none (they follow the user's reduced-motion setting, and nothing changes without it) | To offer autoplay to these users, call `ref.current.play()` from a control they choose, such as a play button. |
|
|
86
|
+
| 2.13.0 | `Autocomplete` `scrollToIndex` | **Before:** Scrolled smoothly by default, also when the user prefers reduced motion. **After:** Smooth by default, and always a jump while the user prefers reduced motion, even when `'smooth'` is passed. **Verified by:** `Autocomplete.test.tsx` | b | Carousel: `ref.current.play()` starts autoplay; charts and scrolls: none (they follow the user's reduced-motion setting, and nothing changes without it) | Nothing. |
|
|
87
|
+
| 2.13.0 | `useAriaLive` without a rendered region (the documented usage) | **Before:** `announce` only changed `message`. With no element spreading `ariaProps`, nothing was read, and the documented call `announce(message, { politeness: 'assertive' })` was a type error. **After:** `announce` reads the message through `useAnnounce`: the nearest `LiveRegionProvider`, else document-level regions. It accepts `{ politeness }`. `message` still follows the announcement. **Verified by:** `liveRegion.test.tsx` "useAriaLive on useAnnounce (ADR-35-R3)"; `accessibility.test.tsx` B-01 records marked (B-04) | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Nothing. If you render `message` in a region of your own without spreading `ariaProps`, spread them (keep their `ref`, or merge yours with `useForkRef`), or the message is read twice. |
|
|
88
|
+
| 2.13.0 | `useAriaLive` `ariaProps` | **Before:** `{ role, 'aria-live', 'aria-atomic', 'aria-relevant'? }`; `role` was `'status'` even with `politeness: 'off'`. **After:** They also carry `ref`, which tells the hook that you render the region. `role` is omitted with `politeness: 'off'`, so its type is optional. **Verified by:** `liveRegion.test.tsx` "politeness "off" emits no role and announces nothing (10)", "keeps the ariaProps ref stable across renders" | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Keep the `ref` when you spread `ariaProps`. If you read `ariaProps.role`, handle `undefined`. |
|
|
89
|
+
| 2.13.0 | `SkipLink` z-index | **Before:** `9999`, above every theme layer. **After:** `theme.zIndex.tooltip` (1500 in the default theme); a theme override moves it. App bars (1100) and drawers (1200) stay below it. Toast and GoToTop (9999) can still cover a focused skip link. **Verified by:** `accessibility.test.tsx` "SkipLink (B-12)" | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Nothing. To keep 9999, pass `sx={{ zIndex: 9999 }}`. |
|
|
90
|
+
| 2.13.0 | `SkipLink` `sx` | **Before:** An array or function `sx` was spread into an object, so its styles never applied. **After:** Object, array and function `sx` merge after the link's own styles. **Verified by:** `accessibility.test.tsx` "SkipLink (B-12)", "merges object, array and function sx after its own styles" | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Nothing. |
|
|
91
|
+
| 2.13.0 | `SkipLink` in a right-to-left page | **Before:** The focused link showed at the left edge (`left: 16px`). **After:** It shows at the inline start: the right edge in RTL, the left in LTR (`inset-inline-start`). **Verified by:** `accessibility.test.tsx` "uses only logical insets…"; story `Foundation/Accessibility` › Skip Link (RTL) | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Nothing. |
|
|
92
|
+
| 2.13.0 | `SkipLink` focus on the target | **Before:** Activating it focused the target, then removed the target's temporary `tabindex` at once. Browsers then moved focus to `<body>`, so the skip did nothing, and a `tabindex` the app had set on the target was removed. **After:** Focus stays on the target. A target that is not focusable keeps `tabindex="-1"` until focus leaves it, and a `tabindex` the app set is kept. **Verified by:** `accessibility.test.tsx` "moves focus to the target on …, keeps it there…"; the Skip Link story play functions in Chromium | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | If a test expects no `tabindex` on the target right after activation, expect it until the target loses focus. |
|
|
93
|
+
| 2.13.0 | `useRovingTabIndex` legacy form, `currentIndex` | **Before:** It was read only on mount; changing it later did nothing. **After:** A changed `currentIndex` moves the tab stop. Focus does not move, and `onIndexChange` is not called for it. **Verified by:** `useRovingTabIndex.test.tsx` "legacy refs-array form (B-10 repairs)" 2; `accessibility.test.tsx` "legacy form re-syncs when currentIndex changes (B-10)" | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | If you changed `currentIndex` only to start from a new value on remount, nothing changes for you. |
|
|
94
|
+
| 2.13.0 | `useRovingTabIndex` legacy form, shrinking `items` | **Before:** The tab stop could point past the end of the list, leaving no item with `tabIndex` 0. **After:** The tab stop is clamped to the last item. **Verified by:** `useRovingTabIndex.test.tsx` "1. shrinking the list clamps…"; `accessibility.test.tsx` "legacy form clamps the tab stop when the list shrinks (B-10)" | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Nothing. |
|
|
95
|
+
| 2.13.0 | `useRovingTabIndex` legacy form, disabled items | **Before:** The arrow keys, Home and End moved onto `disabled` and `aria-disabled="true"` items. **After:** They are skipped, and the arrow keys start from the item that has focus. A tab stop on a disabled item passes to the nearest enabled one right after each commit, before paint, also when an item becomes disabled later; the server render and the first client render use `currentIndex` as given. **Verified by:** `useRovingTabIndex.test.tsx` 3 to 3g and "after hydration, a server tab stop on a disabled item…"; `accessibility.test.tsx` "legacy form skips disabled items… (B-10)" | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Nothing. |
|
|
96
|
+
| 2.13.0 | `useRovingTabIndex` legacy form in an RTL theme | **Before:** ArrowRight always moved to the next item. **After:** With `theme.direction === 'rtl'`, ArrowLeft moves to the next item and ArrowRight to the previous one (horizontal and both axes). **Verified by:** `useRovingTabIndex.test.tsx` 5 and 5b | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | If you swapped the keys yourself for RTL, stop doing it. |
|
|
97
|
+
| 2.13.0 | `useRovingTabIndex` registry form, `isRtl` (unreleased) | **Before:** `isRtl?: boolean`, default `false`. **After:** `direction?: 'ltr' \| 'rtl'`, default `theme.direction`. **Verified by:** `useRovingTabIndex.test.tsx` "direction defaults to theme.direction, and the option overrides it (B-10)"; `useRovingTabIndex.test-d.tsx` | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Pass `direction: 'rtl'`, or rely on an RTL theme. |
|
|
98
|
+
| 2.13.0 | `OverflowToolTip` | **Before:** The deprecation warning was logged during the first render. **After:** The same warning is logged once, from an effect after the first render. **Verified by:** `OverflowTooltip.test.tsx` "renders like OverflowTooltip and warns once, from an effect" | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Nothing, unless a test checks the warning synchronously inside render: check it after `render()` returns. |
|
|
99
|
+
| 2.13.0 | `useLocalStorage` (server rendering) | **Before:** The first client render read `localStorage`, so hydration disagreed with the server markup whenever a value was stored. **After:** Server and hydration renders use `initialValue`; the stored value follows in the next render. **Verified by:** `src/hooks/__tests__/useLocalStorage.test.tsx` | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Nothing for client-only apps. In a server-rendered app, anything drawn from the stored value starts from `initialValue` for one render. |
|
|
100
|
+
| 2.13.0 | `useLocalStorage` (same key, same tab) | **Before:** Each hook kept its own copy; a write through one hook did not reach the others until a reload. **After:** Every hook on the key shows the same value. **Verified by:** `src/hooks/__tests__/useLocalStorage.test.tsx` | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Give two parts of the page different keys if they must keep separate values. |
|
|
101
|
+
| 2.13.0 | `useLocalStorage` (other tabs) | **Before:** A key removed or cleared in another tab was ignored, and text that was not JSON became the value as a raw string. **After:** Both give `initialValue`. **Verified by:** `src/hooks/__tests__/useLocalStorage.test.tsx` | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Nothing, unless code relied on the old value surviving a removal in another tab. |
|
|
102
|
+
| 2.13.0 | `useLocalStorage` (setter) | **Before:** A functional update read the value of the last render, so two in one handler applied once; `setValue` changed identity whenever the value changed. **After:** Functional updates start from the latest stored value; `setValue` and `removeValue` are stable. **Verified by:** `src/hooks/__tests__/useLocalStorage.test.tsx` | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Remove workarounds that chained updates through effects or left `setValue` out of dependency lists. |
|
|
103
|
+
| 2.13.0 | All entries (root, `/essentials`, `/data-table`, `/theme`, `/tokens`) | **Before:** Importing from a React Server Component crashed: the entry called `createContext` on the server. **After:** Each entry starts with `'use client'`. A Server Component can render the components; they render on the client. **Verified by:** `use-client-banner.test.ts`; the consumer-smoke RSC leg | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Remove `next/dynamic` or wrapper files that only existed to avoid the crash, if you like. Keep event handlers, render props and the theme provider in your own `'use client'` files. Read tokens in client code or from the CSS variables. |
|
|
104
|
+
| 2.13.0 | `AIChainOfThought` phases added after mount | **Before:** A streamed phase with `defaultExpanded: false` opened. **After:** It starts collapsed, as `defaultExpanded` says. Initial phases and user toggles are unchanged. **Verified by:** `AIChainOfThought.test.tsx` | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Set `defaultExpanded: true`, or leave it unset, on a streamed phase that should open when it arrives. |
|
|
105
|
+
| 2.13.0 | `AIChainOfThought` artifacts with `showChart` | **Before:** The mini chart drew 8 random bars, different on every mount and between server and client. **After:** It draws one bar per `chartData` value; without data it shows a neutral glyph. **Verified by:** `AIChainOfThought.test.tsx` | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Pass `chartData` (an array of numbers) on artifacts that should show bars. |
|
|
106
|
+
| 2.13.0 | `PredictiveTextInput` options and combobox | **Before:** Option ids were `<listbox id>-option-<suggestion id>`, and `role="combobox"` sat on the field's wrapper `<div>`. **After:** Option ids are `<listbox id>-option-<index>`, and `role="combobox"` with `aria-activedescendant` is on the `<input>`. **Verified by:** `PredictiveTextInput.test.tsx` | a, b, d, e | `sx={{ zIndex: 9999 }}` restores SkipLink's old z-index, and `defaultExpanded: true` on a streamed AIChainOfThought phase keeps it opening on arrival; the other changes have none (each repairs a defect: a silent announcement, an out-of-range or disabled roving tab stop, a hydration mismatch, invented chart bars, model-derived DOM ids, a warning during render, a crash in Server Components) | Find options with `getByRole('option', { name })`, not by id. Expect `getByRole('combobox')` to return the input. |
|
|
107
|
+
| 2.13.0 | `TransferList` select-all | **Before:** With `@mui/material` 9.0.0 the input had no `aria-checked`, so a partly selected panel read as unchecked. With later 9.x it had none while unchecked or fully checked. **After:** The input always has `aria-checked`: `"mixed"`, `"true"` or `"false"`, equal to its native state. **Verified by:** `TransferList.test.tsx` ("select-all … reports mixed while some enabled items are checked") | b | none (a partly selected select-all that reads as unchecked, or whose ARIA and native states disagree, fails WCAG 4.1.2; there is no supported old state to keep) | A test that expects no `aria-checked` on the select-all input should expect `"false"`, or use `toBeChecked()` and `toBePartiallyChecked()`. |
|
|
108
|
+
| 2.13.0 | `SimpleTable` select-all (`selectable`, multiple) | **Before:** With `@mui/material` 9.0.0 the input had no `aria-checked`. With later 9.x it had `aria-checked="mixed"` while partly selected, but its native `indeterminate` stayed `false`. **After:** The input always has `aria-checked` (`"mixed"`, `"true"` or `"false"`), and `indeterminate` is `true` exactly while it is `"mixed"`. **Verified by:** `SimpleTable.selectAll.test.tsx` | b | none (a partly selected select-all that reads as unchecked, or whose ARIA and native states disagree, fails WCAG 4.1.2; there is no supported old state to keep) | A test that expects no `aria-checked` on the select-all input should expect `"false"`, or use `toBeChecked()` and `toBePartiallyChecked()`. |
|
|
109
|
+
| 2.13.0 | `ChatHeader` | **Before:** The root was a `<header>`: a banner landmark at body level, a second banner inside InsightEngine's drawer. **After:** The root is a `<div>` with the same classes, styles and ref. **Verified by:** `ChatHeader.test.tsx` | a, b, c | ChatHeader: none (a panel header is never the page banner; a page that needs one renders its own `<header>` around the panel); useBrowserVoiceMode, server rendering: none (only hydration changes, where the old value caused a mismatch); useBrowserVoiceMode, fatal errors: none (looping on an error that cannot clear is never wanted; call `start()` again to retry); AILabel: none (a disabled button misstates the role; pass `onClick` or popover content to get a button); StatCard: `valueComponent="h5"` (or the heading the value used: `h6`, `h5`, `h4` for small, medium, large); GradientIconBadge: none (the old output for non-hex colors was invalid CSS); AICircularAction: none (pass `badgeLabel` to word the badge differently); AI list renderers: none (keys are internal; nothing a consumer passes changes); AIDigestCard colors: none (the old colors were static light-mode values that fail contrast); AIDigestCard generatedAt: none (the visible text and styles are unchanged); Token overrides: none (a partial override threw or produced undefined colors before, so there is no working behavior to restore); Resolved tokens: none (the old behavior threw for these inputs; full token sets and the defaults give the same output as before); Contrast utilities: none (the old results for non-hex colors were wrong numbers, not a behavior to keep; hex results are unchanged) | Nothing. A test that found the panel header with `getByRole('banner')` queries its heading instead. |
|
|
110
|
+
| 2.13.0 | `useBrowserVoiceMode` | **Before:** `supported` was read from `window` during render: `false` on the server, `true` while hydrating, a hydration mismatch. The start timer survived `stop()` and unmount. **After:** `supported` is `false` on the server and during hydration and updates right after. `stop()`, unmount and a repeated `start()` clear the timer. **Verified by:** `useBrowserVoiceMode.test.tsx` | a, b, c | ChatHeader: none (a panel header is never the page banner; a page that needs one renders its own `<header>` around the panel); useBrowserVoiceMode, server rendering: none (only hydration changes, where the old value caused a mismatch); useBrowserVoiceMode, fatal errors: none (looping on an error that cannot clear is never wanted; call `start()` again to retry); AILabel: none (a disabled button misstates the role; pass `onClick` or popover content to get a button); StatCard: `valueComponent="h5"` (or the heading the value used: `h6`, `h5`, `h4` for small, medium, large); GradientIconBadge: none (the old output for non-hex colors was invalid CSS); AICircularAction: none (pass `badgeLabel` to word the badge differently); AI list renderers: none (keys are internal; nothing a consumer passes changes); AIDigestCard colors: none (the old colors were static light-mode values that fail contrast); AIDigestCard generatedAt: none (the visible text and styles are unchanged); Token overrides: none (a partial override threw or produced undefined colors before, so there is no working behavior to restore); Resolved tokens: none (the old behavior threw for these inputs; full token sets and the defaults give the same output as before); Contrast utilities: none (the old results for non-hex colors were wrong numbers, not a behavior to keep; hex results are unchanged) | Nothing. Code that rendered from `supported` keeps working, and server-rendered pages no longer mismatch. |
|
|
111
|
+
| 2.13.0 | `useBrowserVoiceMode` | **Before:** A denied or missing microphone restarted recognition in a loop; the UI showed "Listening…". **After:** Voice mode closes (`open` becomes `false`) and `error` says why. **Verified by:** `useBrowserVoiceMode.test.tsx` | a, b, c | ChatHeader: none (a panel header is never the page banner; a page that needs one renders its own `<header>` around the panel); useBrowserVoiceMode, server rendering: none (only hydration changes, where the old value caused a mismatch); useBrowserVoiceMode, fatal errors: none (looping on an error that cannot clear is never wanted; call `start()` again to retry); AILabel: none (a disabled button misstates the role; pass `onClick` or popover content to get a button); StatCard: `valueComponent="h5"` (or the heading the value used: `h6`, `h5`, `h4` for small, medium, large); GradientIconBadge: none (the old output for non-hex colors was invalid CSS); AICircularAction: none (pass `badgeLabel` to word the badge differently); AI list renderers: none (keys are internal; nothing a consumer passes changes); AIDigestCard colors: none (the old colors were static light-mode values that fail contrast); AIDigestCard generatedAt: none (the visible text and styles are unchanged); Token overrides: none (a partial override threw or produced undefined colors before, so there is no working behavior to restore); Resolved tokens: none (the old behavior threw for these inputs; full token sets and the defaults give the same output as before); Contrast utilities: none (the old results for non-hex colors were wrong numbers, not a behavior to keep; hex results are unchanged) | Show `error` to the user. Call `start()` to retry after they fix the cause. |
|
|
112
|
+
| 2.13.0 | `AILabel` | **Before:** `clickable={false}` rendered `<button disabled>`; a clickable label without `onClick` or popover content rendered an enabled button that did nothing. **After:** Both render a `<span>` with the same look and no tab stop. The ref is typed `HTMLElement`. **Verified by:** `AILabel.test.tsx` | a, b, c | ChatHeader: none (a panel header is never the page banner; a page that needs one renders its own `<header>` around the panel); useBrowserVoiceMode, server rendering: none (only hydration changes, where the old value caused a mismatch); useBrowserVoiceMode, fatal errors: none (looping on an error that cannot clear is never wanted; call `start()` again to retry); AILabel: none (a disabled button misstates the role; pass `onClick` or popover content to get a button); StatCard: `valueComponent="h5"` (or the heading the value used: `h6`, `h5`, `h4` for small, medium, large); GradientIconBadge: none (the old output for non-hex colors was invalid CSS); AICircularAction: none (pass `badgeLabel` to word the badge differently); AI list renderers: none (keys are internal; nothing a consumer passes changes); AIDigestCard colors: none (the old colors were static light-mode values that fail contrast); AIDigestCard generatedAt: none (the visible text and styles are unchanged); Token overrides: none (a partial override threw or produced undefined colors before, so there is no working behavior to restore); Resolved tokens: none (the old behavior threw for these inputs; full token sets and the defaults give the same output as before); Contrast utilities: none (the old results for non-hex colors were wrong numbers, not a behavior to keep; hex results are unchanged) | Query a static label by its text, not by the button role. Pass `onClick` or popover content when the label must be a control. |
|
|
113
|
+
| 2.13.0 | `StatCard` | **Before:** The value rendered as `h6`, `h5` or `h4` (small, medium, large). **After:** The value renders as `p` with the same look. **Verified by:** `AIVisuals.test.tsx` | a, b, c | ChatHeader: none (a panel header is never the page banner; a page that needs one renders its own `<header>` around the panel); useBrowserVoiceMode, server rendering: none (only hydration changes, where the old value caused a mismatch); useBrowserVoiceMode, fatal errors: none (looping on an error that cannot clear is never wanted; call `start()` again to retry); AILabel: none (a disabled button misstates the role; pass `onClick` or popover content to get a button); StatCard: `valueComponent="h5"` (or the heading the value used: `h6`, `h5`, `h4` for small, medium, large); GradientIconBadge: none (the old output for non-hex colors was invalid CSS); AICircularAction: none (pass `badgeLabel` to word the badge differently); AI list renderers: none (keys are internal; nothing a consumer passes changes); AIDigestCard colors: none (the old colors were static light-mode values that fail contrast); AIDigestCard generatedAt: none (the visible text and styles are unchanged); Token overrides: none (a partial override threw or produced undefined colors before, so there is no working behavior to restore); Resolved tokens: none (the old behavior threw for these inputs; full token sets and the defaults give the same output as before); Contrast utilities: none (the old results for non-hex colors were wrong numbers, not a behavior to keep; hex results are unchanged) | Nothing, unless the value must stay a heading: then pass `valueComponent` with that heading. Tests that found the value by the heading role query its text. |
|
|
114
|
+
| 2.13.0 | `GradientIconBadge` | **Before:** The glow was the start color with "33" appended: correct for 6-digit hex, invalid (no shadow) for `rgb()`, `hsl()` and short hex. **After:** The glow is the start color at 20% opacity for every format; unchanged for 6-digit hex. **Verified by:** `AIVisuals.test.tsx` | a, b, c | ChatHeader: none (a panel header is never the page banner; a page that needs one renders its own `<header>` around the panel); useBrowserVoiceMode, server rendering: none (only hydration changes, where the old value caused a mismatch); useBrowserVoiceMode, fatal errors: none (looping on an error that cannot clear is never wanted; call `start()` again to retry); AILabel: none (a disabled button misstates the role; pass `onClick` or popover content to get a button); StatCard: `valueComponent="h5"` (or the heading the value used: `h6`, `h5`, `h4` for small, medium, large); GradientIconBadge: none (the old output for non-hex colors was invalid CSS); AICircularAction: none (pass `badgeLabel` to word the badge differently); AI list renderers: none (keys are internal; nothing a consumer passes changes); AIDigestCard colors: none (the old colors were static light-mode values that fail contrast); AIDigestCard generatedAt: none (the visible text and styles are unchanged); Token overrides: none (a partial override threw or produced undefined colors before, so there is no working behavior to restore); Resolved tokens: none (the old behavior threw for these inputs; full token sets and the defaults give the same output as before); Contrast utilities: none (the old results for non-hex colors were wrong numbers, not a behavior to keep; hex results are unchanged) | Nothing. |
|
|
115
|
+
| 2.13.0 | `AICircularAction` | **Before:** The name was only `label`; the badge was a separate box outside the corner. The tooltip did not open on keyboard focus or while disabled. **After:** The name is `label, badge` ("Notifications, 3"); the badge overlaps the circle; the tooltip opens on focus and while disabled. **Verified by:** `AIActions.test.tsx` | a, b, c | ChatHeader: none (a panel header is never the page banner; a page that needs one renders its own `<header>` around the panel); useBrowserVoiceMode, server rendering: none (only hydration changes, where the old value caused a mismatch); useBrowserVoiceMode, fatal errors: none (looping on an error that cannot clear is never wanted; call `start()` again to retry); AILabel: none (a disabled button misstates the role; pass `onClick` or popover content to get a button); StatCard: `valueComponent="h5"` (or the heading the value used: `h6`, `h5`, `h4` for small, medium, large); GradientIconBadge: none (the old output for non-hex colors was invalid CSS); AICircularAction: none (pass `badgeLabel` to word the badge differently); AI list renderers: none (keys are internal; nothing a consumer passes changes); AIDigestCard colors: none (the old colors were static light-mode values that fail contrast); AIDigestCard generatedAt: none (the visible text and styles are unchanged); Token overrides: none (a partial override threw or produced undefined colors before, so there is no working behavior to restore); Resolved tokens: none (the old behavior threw for these inputs; full token sets and the defaults give the same output as before); Contrast utilities: none (the old results for non-hex colors were wrong numbers, not a behavior to keep; hex results are unchanged) | Query the button by its full name. Pass `badgeLabel` to word the badge. |
|
|
116
|
+
| 2.13.0 | `AISourcesSection`, `AIRelatedQuestions`, `AIActionsGroup`, `AIPersonaCard`, `AIQuickReply`, `AIExplainability` | **Before:** Items were keyed by index: after a removal or reorder, focus moved to a different item. **After:** Items are keyed by their URL, title, text or label; focus stays on the same item. **Verified by:** `stableListKeys.test.tsx`, `AIActions.test.tsx` | a, b, c | ChatHeader: none (a panel header is never the page banner; a page that needs one renders its own `<header>` around the panel); useBrowserVoiceMode, server rendering: none (only hydration changes, where the old value caused a mismatch); useBrowserVoiceMode, fatal errors: none (looping on an error that cannot clear is never wanted; call `start()` again to retry); AILabel: none (a disabled button misstates the role; pass `onClick` or popover content to get a button); StatCard: `valueComponent="h5"` (or the heading the value used: `h6`, `h5`, `h4` for small, medium, large); GradientIconBadge: none (the old output for non-hex colors was invalid CSS); AICircularAction: none (pass `badgeLabel` to word the badge differently); AI list renderers: none (keys are internal; nothing a consumer passes changes); AIDigestCard colors: none (the old colors were static light-mode values that fail contrast); AIDigestCard generatedAt: none (the visible text and styles are unchanged); Token overrides: none (a partial override threw or produced undefined colors before, so there is no working behavior to restore); Resolved tokens: none (the old behavior threw for these inputs; full token sets and the defaults give the same output as before); Contrast utilities: none (the old results for non-hex colors were wrong numbers, not a behavior to keep; hex results are unchanged) | Nothing. |
|
|
117
|
+
| 2.13.0 | `AIDigestCard` | **Before:** Labels, hover, focus ring and dividers used fixed light-mode colors (labels 2.27:1 in light mode; focus ring 2.93:1 in dark mode). **After:** They use `text.secondary`, `action.hover`, `primary.main` and `divider` from the theme. **Verified by:** `AIDigestCard.test.tsx` | a, b, c | ChatHeader: none (a panel header is never the page banner; a page that needs one renders its own `<header>` around the panel); useBrowserVoiceMode, server rendering: none (only hydration changes, where the old value caused a mismatch); useBrowserVoiceMode, fatal errors: none (looping on an error that cannot clear is never wanted; call `start()` again to retry); AILabel: none (a disabled button misstates the role; pass `onClick` or popover content to get a button); StatCard: `valueComponent="h5"` (or the heading the value used: `h6`, `h5`, `h4` for small, medium, large); GradientIconBadge: none (the old output for non-hex colors was invalid CSS); AICircularAction: none (pass `badgeLabel` to word the badge differently); AI list renderers: none (keys are internal; nothing a consumer passes changes); AIDigestCard colors: none (the old colors were static light-mode values that fail contrast); AIDigestCard generatedAt: none (the visible text and styles are unchanged); Token overrides: none (a partial override threw or produced undefined colors before, so there is no working behavior to restore); Resolved tokens: none (the old behavior threw for these inputs; full token sets and the defaults give the same output as before); Contrast utilities: none (the old results for non-hex colors were wrong numbers, not a behavior to keep; hex results are unchanged) | Nothing. Check custom themes that set those palette roles. |
|
|
118
|
+
| 2.13.0 | `AIDigestCard` | **Before:** `generatedAt` always rendered as a span; the JSDoc said it was formatted by the consumer. **After:** An ISO 8601 value renders as `<time datetime>` with the same text; display text stays a span. The JSDoc matches. **Verified by:** `AIDigestCard.test.tsx` | a, b, c | ChatHeader: none (a panel header is never the page banner; a page that needs one renders its own `<header>` around the panel); useBrowserVoiceMode, server rendering: none (only hydration changes, where the old value caused a mismatch); useBrowserVoiceMode, fatal errors: none (looping on an error that cannot clear is never wanted; call `start()` again to retry); AILabel: none (a disabled button misstates the role; pass `onClick` or popover content to get a button); StatCard: `valueComponent="h5"` (or the heading the value used: `h6`, `h5`, `h4` for small, medium, large); GradientIconBadge: none (the old output for non-hex colors was invalid CSS); AICircularAction: none (pass `badgeLabel` to word the badge differently); AI list renderers: none (keys are internal; nothing a consumer passes changes); AIDigestCard colors: none (the old colors were static light-mode values that fail contrast); AIDigestCard generatedAt: none (the visible text and styles are unchanged); Token overrides: none (a partial override threw or produced undefined colors before, so there is no working behavior to restore); Resolved tokens: none (the old behavior threw for these inputs; full token sets and the defaults give the same output as before); Contrast utilities: none (the old results for non-hex colors were wrong numbers, not a behavior to keep; hex results are unchanged) | Nothing. |
|
|
119
|
+
| 2.13.0 | `createTrinityTheme`, `createTrinityThemePreset` | **Before:** A partial token override replaced its whole group, so the theme threw or had undefined colors. **After:** Overrides are deep-merged; omitted keys keep their defaults. Unknown, wrong-type and undefined leaves warn once in development. **Verified by:** `token-merge.test.ts`, `theme-presets.test.tsx` | a, b, c | ChatHeader: none (a panel header is never the page banner; a page that needs one renders its own `<header>` around the panel); useBrowserVoiceMode, server rendering: none (only hydration changes, where the old value caused a mismatch); useBrowserVoiceMode, fatal errors: none (looping on an error that cannot clear is never wanted; call `start()` again to retry); AILabel: none (a disabled button misstates the role; pass `onClick` or popover content to get a button); StatCard: `valueComponent="h5"` (or the heading the value used: `h6`, `h5`, `h4` for small, medium, large); GradientIconBadge: none (the old output for non-hex colors was invalid CSS); AICircularAction: none (pass `badgeLabel` to word the badge differently); AI list renderers: none (keys are internal; nothing a consumer passes changes); AIDigestCard colors: none (the old colors were static light-mode values that fail contrast); AIDigestCard generatedAt: none (the visible text and styles are unchanged); Token overrides: none (a partial override threw or produced undefined colors before, so there is no working behavior to restore); Resolved tokens: none (the old behavior threw for these inputs; full token sets and the defaults give the same output as before); Contrast utilities: none (the old results for non-hex colors were wrong numbers, not a behavior to keep; hex results are unchanged) | Nothing for full-group overrides (same output). Remove workarounds that copied whole groups. |
|
|
120
|
+
| 2.13.0 | `generateCssVariables`, `injectTrinityCssVariables` | **Before:** A token set that overrode a referenced semantic role threw "Component color reference changes light output"; partial token sets were not accepted. **After:** Partial overrides and full token sets are resolved first; referencing members follow their role, and intentional overrides are emitted as literals. **Verified by:** `brand-override-parity.test.ts`, `component-color-tiering.test.ts` | a, b, c | ChatHeader: none (a panel header is never the page banner; a page that needs one renders its own `<header>` around the panel); useBrowserVoiceMode, server rendering: none (only hydration changes, where the old value caused a mismatch); useBrowserVoiceMode, fatal errors: none (looping on an error that cannot clear is never wanted; call `start()` again to retry); AILabel: none (a disabled button misstates the role; pass `onClick` or popover content to get a button); StatCard: `valueComponent="h5"` (or the heading the value used: `h6`, `h5`, `h4` for small, medium, large); GradientIconBadge: none (the old output for non-hex colors was invalid CSS); AICircularAction: none (pass `badgeLabel` to word the badge differently); AI list renderers: none (keys are internal; nothing a consumer passes changes); AIDigestCard colors: none (the old colors were static light-mode values that fail contrast); AIDigestCard generatedAt: none (the visible text and styles are unchanged); Token overrides: none (a partial override threw or produced undefined colors before, so there is no working behavior to restore); Resolved tokens: none (the old behavior threw for these inputs; full token sets and the defaults give the same output as before); Contrast utilities: none (the old results for non-hex colors were wrong numbers, not a behavior to keep; hex results are unchanged) | Pass the same overrides you give `createTrinityTheme`, or `theme.trinityTokens`. |
|
|
121
|
+
| 2.13.0 | `getContrastRatio`, `validateAccessibility` | **Before:** `rgba()`, `hsl()` and 8-digit hex colors gave wrong ratios (rgba was read as black). **After:** They give the WCAG ratio, compositing alpha over `options.over` (default white). Unreadable colors give `NaN` (and `false`). **Verified by:** `color-contrast.test.ts` | a, b, c | ChatHeader: none (a panel header is never the page banner; a page that needs one renders its own `<header>` around the panel); useBrowserVoiceMode, server rendering: none (only hydration changes, where the old value caused a mismatch); useBrowserVoiceMode, fatal errors: none (looping on an error that cannot clear is never wanted; call `start()` again to retry); AILabel: none (a disabled button misstates the role; pass `onClick` or popover content to get a button); StatCard: `valueComponent="h5"` (or the heading the value used: `h6`, `h5`, `h4` for small, medium, large); GradientIconBadge: none (the old output for non-hex colors was invalid CSS); AICircularAction: none (pass `badgeLabel` to word the badge differently); AI list renderers: none (keys are internal; nothing a consumer passes changes); AIDigestCard colors: none (the old colors were static light-mode values that fail contrast); AIDigestCard generatedAt: none (the visible text and styles are unchanged); Token overrides: none (a partial override threw or produced undefined colors before, so there is no working behavior to restore); Resolved tokens: none (the old behavior threw for these inputs; full token sets and the defaults give the same output as before); Contrast utilities: none (the old results for non-hex colors were wrong numbers, not a behavior to keep; hex results are unchanged) | Pass `{ over: <surface> }` when you check a translucent color on a dark surface. Hex input needs nothing. |
|
|
122
|
+
| 2.13.0 | Light error red (`status.error.text`, `text.error`, `border.error`, error/failed/rejected indicator fills, `--trinity-input-border-error`) | **Before:** `#DC2626`: 4.41:1 on `#FEF2F2`, below the 4.5:1 minimum. **After:** `#DA1E28`: 4.57:1 on `#FEF2F2`, 5.00:1 on white. **Verified by:** `contrast-matrix.test.ts` "status text on its own background (light mode, ADR-03-R2)" | a, b, c | Light error red: none (the old red failed WCAG 1.4.3 on its own background; pin `#DC2626` through a token override of `semantic.colors.status.error.text` if you must); Subtree theming (CSS): none (whole-page theming is unchanged; only subtrees, which rendered the wrong mode, change); Palette resolver: none (the old fallback paired main text with a light background at 1.4-1.8:1; Trinity themes return the same values as before); useTrinityTokens: none (themes without overrides return the same values; an overridden theme returned the wrong, default values); Base theme gaps: none (WCAG 2.4.7 requires a visible keyboard focus indicator; a theme `components` override can still restyle the ring, the divider and the body colours) | Nothing. A screenshot test of a light error state changes by a few RGB units; to keep the old value, override `semantic.colors.status.error.text` (and the other roles) with `#DC2626`. |
|
|
123
|
+
| 2.13.0 | CSS package `data-theme` on an element below `<html>` | **Before:** Aliases (for example `--trinity-status-success-bg`) kept the page's mode inside a themed section, and `data-theme="light"` inside a dark page did nothing. **After:** Every variable inside the element takes that element's mode, at any depth, including shadow hosts. **Verified by:** `css-alias-indirection.test.ts`, story Getting Started/CSS Theme/Subtree Theming | a, b, c | Light error red: none (the old red failed WCAG 1.4.3 on its own background; pin `#DC2626` through a token override of `semantic.colors.status.error.text` if you must); Subtree theming (CSS): none (whole-page theming is unchanged; only subtrees, which rendered the wrong mode, change); Palette resolver: none (the old fallback paired main text with a light background at 1.4-1.8:1; Trinity themes return the same values as before); useTrinityTokens: none (themes without overrides return the same values; an overridden theme returned the wrong, default values); Base theme gaps: none (WCAG 2.4.7 requires a visible keyboard focus indicator; a theme `components` override can still restyle the ring, the divider and the body colours) | Remove workarounds that re-declared Trinity variables inside themed sections. |
|
|
124
|
+
| 2.13.0 | `useTrinityPalette`, `getTrinityPalette` under a non-Trinity MUI theme | **Before:** The fallback paired `palette[x].main` text with `palette[x].light` backgrounds (1.4-1.8:1), covered only three groups, and logged a warning on every render. **After:** A complete palette with status text at 4.5:1 or more on the paper and on its tint; nothing is logged. **Verified by:** `useTrinityPalette.test.ts`, `non-trinity-theme.test.tsx` | a, b, c | Light error red: none (the old red failed WCAG 1.4.3 on its own background; pin `#DC2626` through a token override of `semantic.colors.status.error.text` if you must); Subtree theming (CSS): none (whole-page theming is unchanged; only subtrees, which rendered the wrong mode, change); Palette resolver: none (the old fallback paired main text with a light background at 1.4-1.8:1; Trinity themes return the same values as before); useTrinityTokens: none (themes without overrides return the same values; an overridden theme returned the wrong, default values); Base theme gaps: none (WCAG 2.4.7 requires a visible keyboard focus indicator; a theme `components` override can still restyle the ring, the divider and the body colours) | Nothing: read the same keys. Both are now root exports. |
|
|
125
|
+
| 2.13.0 | `useTrinityTokens` | **Before:** Returned the default tokens even under a theme built with overrides; `getSemanticColor('status', 'success.text')` returned `''`. **After:** Returns the theme's resolved tokens; nested names resolve, dark first in dark mode; a missing path warns once in development. **Verified by:** `useTrinityTokens.test.tsx` | a, b, c | Light error red: none (the old red failed WCAG 1.4.3 on its own background; pin `#DC2626` through a token override of `semantic.colors.status.error.text` if you must); Subtree theming (CSS): none (whole-page theming is unchanged; only subtrees, which rendered the wrong mode, change); Palette resolver: none (the old fallback paired main text with a light background at 1.4-1.8:1; Trinity themes return the same values as before); useTrinityTokens: none (themes without overrides return the same values; an overridden theme returned the wrong, default values); Base theme gaps: none (WCAG 2.4.7 requires a visible keyboard focus indicator; a theme `components` override can still restyle the ring, the divider and the body colours) | Nothing for default themes. If you worked around the defaults, read the hook's values directly. |
|
|
126
|
+
| 2.13.0 | `Checkbox`, `Radio` (theme) | **Before:** Keyboard focus showed only the focus ripple; the theme's ring never matched, because focus lands on the hidden input **After:** A 2px ring around the icon on keyboard focus (the button ring: navy in light, coral in dark), `Highlight` in forced-colors mode **Verified by:** `theme-base-gaps.test.tsx` | a, b, c | Light error red: none (the old red failed WCAG 1.4.3 on its own background; pin `#DC2626` through a token override of `semantic.colors.status.error.text` if you must); Subtree theming (CSS): none (whole-page theming is unchanged; only subtrees, which rendered the wrong mode, change); Palette resolver: none (the old fallback paired main text with a light background at 1.4-1.8:1; Trinity themes return the same values as before); useTrinityTokens: none (themes without overrides return the same values; an overridden theme returned the wrong, default values); Base theme gaps: none (WCAG 2.4.7 requires a visible keyboard focus indicator; a theme `components` override can still restyle the ring, the divider and the body colours) | Nothing. To restyle it, override `&.Mui-focusVisible svg:first-of-type` in `MuiCheckbox` or `MuiRadio` styleOverrides |
|
|
127
|
+
| 2.13.0 | `Switch` (theme) | **Before:** Keyboard focus changed only the thumb's colour and border **After:** The same ring around the track on keyboard focus, `Highlight` in forced-colors mode **Verified by:** `theme-base-gaps.test.tsx` | a, b, c | Light error red: none (the old red failed WCAG 1.4.3 on its own background; pin `#DC2626` through a token override of `semantic.colors.status.error.text` if you must); Subtree theming (CSS): none (whole-page theming is unchanged; only subtrees, which rendered the wrong mode, change); Palette resolver: none (the old fallback paired main text with a light background at 1.4-1.8:1; Trinity themes return the same values as before); useTrinityTokens: none (themes without overrides return the same values; an overridden theme returned the wrong, default values); Base theme gaps: none (WCAG 2.4.7 requires a visible keyboard focus indicator; a theme `components` override can still restyle the ring, the divider and the body colours) | Nothing. To restyle it, override `& .MuiSwitch-switchBase` › `&.Mui-focusVisible + .MuiSwitch-track` in `MuiSwitch` styleOverrides |
|
|
128
|
+
| 2.13.0 | `AccordionSummary` (dark theme) | **Before:** The divider was the light theme's gray[100] **After:** The dark subtle border (`palette.trinity.border.subtle`); light is unchanged **Verified by:** `theme-base-gaps.test.tsx`, `contrast-matrix.test.ts` | a, b, c | Light error red: none (the old red failed WCAG 1.4.3 on its own background; pin `#DC2626` through a token override of `semantic.colors.status.error.text` if you must); Subtree theming (CSS): none (whole-page theming is unchanged; only subtrees, which rendered the wrong mode, change); Palette resolver: none (the old fallback paired main text with a light background at 1.4-1.8:1; Trinity themes return the same values as before); useTrinityTokens: none (themes without overrides return the same values; an overridden theme returned the wrong, default values); Base theme gaps: none (WCAG 2.4.7 requires a visible keyboard focus indicator; a theme `components` override can still restyle the ring, the divider and the body colours) | Nothing |
|
|
129
|
+
| 2.13.0 | `CssBaseline` body (dark theme, presets) | **Before:** The theme painted the dark body gray[800] and ignored preset page colours **After:** The body follows `palette.background.default` and `palette.text.primary` in every mode and preset; light with the default theme is unchanged **Verified by:** `theme-base-gaps.test.tsx` | a, b, c | Light error red: none (the old red failed WCAG 1.4.3 on its own background; pin `#DC2626` through a token override of `semantic.colors.status.error.text` if you must); Subtree theming (CSS): none (whole-page theming is unchanged; only subtrees, which rendered the wrong mode, change); Palette resolver: none (the old fallback paired main text with a light background at 1.4-1.8:1; Trinity themes return the same values as before); useTrinityTokens: none (themes without overrides return the same values; an overridden theme returned the wrong, default values); Base theme gaps: none (WCAG 2.4.7 requires a visible keyboard focus indicator; a theme `components` override can still restyle the ring, the divider and the body colours) | To keep the old dark body colour, set `MuiCssBaseline` styleOverrides in your theme |
|
|
130
|
+
| 2.13.0 | MUI status palettes (`palette.error`, `warning`, `success`, `info`) in Trinity themes and presets | **Before:** MUI's defaults (`#d32f2f`, `#ed6c02`, `#2e7d32`, `#0288d1`; dark `#f44336`, `#ffa726`, `#66bb6a`, `#29b6f6`); white labels on light warning and info and on every dark status fill failed 4.5:1. **After:** `main` is the mode's status text token, `contrastText` the `onStatus` token (white light, `#09090B` dark); every pair is 5.00:1 or more. Alert, Chip, TextField errors, Checkbox, LinearProgress, contained status Buttons and Trinity components reading the palette show Trinity's status colors. **Verified by:** `status-palette.test.ts` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing. Update screenshot baselines of status-colored components. To pin MUI's colors, pass them as `palette` to `createTrinityThemePreset` (fails contrast). |
|
|
131
|
+
| 2.13.0 | Contained `Button` with `color` error, warning, success or info | **Before:** The label was always white, also on light dark-mode fills (1.94:1 on dark warning). **After:** The label is `palette.<color>.contrastText`: white in light mode, `#09090B` in dark mode. **Verified by:** `status-palette.test.ts` "Contained status Buttons take the palette pair (ADR-03-R7)" | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing. |
|
|
132
|
+
| 2.13.0 | `ToastProvider` | **Before:** Toast stacks at `z-index: 9999`, above dialogs, tooltips and the skip link. **After:** Toast stacks on the `toast` role (`zIndex.snackbar`, 1400): above dialogs, below tooltips and the skip link. **Verified by:** `zindex-roles.test.tsx` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing. To keep a toast above a tooltip, raise `zIndex.snackbar` in your theme. |
|
|
133
|
+
| 2.13.0 | `GoToTop` | **Before:** The button sits at `z-index: 9999`, above app bars, drawers and dialogs. **After:** The button sits on MUI's `fab` layer (1050), under app bars, drawers and dialogs. **Verified by:** `zindex-roles.test.tsx` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing; raise `zIndex.fab` in your theme to lift it. |
|
|
134
|
+
| 2.13.0 | `SearchInput` | **Before:** The suggestions popper sits at `z-index: 1000`, under a sticky app bar (1100). **After:** The popper sits on the `popover` role (`zIndex.modal`, 1300), like MUI's own popovers. **Verified by:** `zindex-roles.test.tsx` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing. |
|
|
135
|
+
| 2.13.0 | `AIFab` | **Before:** Without a `zIndex` prop the button sits at 1000, under app bars and drawers. **After:** Without a `zIndex` prop it sits on the `aiFab` role (`zIndex.drawer + 1`, 1201), as AppLayout already placed it. **Verified by:** `zindex-roles.test.tsx` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Pass `zIndex={1000}` to keep the old layer. |
|
|
136
|
+
| 2.13.0 | `SkipLink` | **Before:** The focused link sits on `zIndex.tooltip` (1500), level with tooltips. **After:** It sits on the `skipLink` role (`zIndex.tooltip + 1`), above every overlay but the diagnostic layer. **Verified by:** `zindex-roles.test.tsx` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing. |
|
|
137
|
+
| 2.13.0 | `ChartWrapper`, `Sparkline`, `DiffViewer`, `HistoricalDataIndicator`, `FeatureCard`, `LandingPage`, `PageHeader`, `StatusHero` | **Before:** An array `sx` lost its entries and a function `sx` (theme callback) was ignored; an object `sx` applied. **After:** Array and function `sx` apply, in order, after the component's own styles; an object `sx` renders the same declarations. **Verified by:** `sx-composition.test.tsx` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing, unless you passed an array or function `sx` that you relied on being ignored: remove it. |
|
|
138
|
+
| 2.13.0 | Theme `caption` and `overline` typography (light) | **Before:** `color` was `text.tertiary` `#9CA3AF` (2.54:1 on white). **After:** `color` is `text.secondary` `#6B7280` (4.83:1 on white); dark is unchanged. **Verified by:** `typography-color.test.tsx`, `contrast-matrix.test.ts` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing. To keep the old colour, set it through the preset's `typography` option (fails WCAG 1.4.3). |
|
|
139
|
+
| 2.13.0 | `Typography` with a `color` prop (any app) | **Before:** The text took its parent's colour; `color="textSecondary"`, `color="primary"` and `color="text.secondary"` had no effect. **After:** The text takes the requested palette colour, in light and dark. A Typography without `color` still inherits. **Verified by:** `typography-color.test.tsx` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Check text you coloured on purpose; to keep the parent's colour, drop the prop or pass `sx={{ color: 'inherit' }}`. |
|
|
140
|
+
| 2.13.0 | AI components: `AIChainOfThought`, `AIChat`, `AIExplainability`, `AIMessage`, `AITextBlock`, `ChatThread`, `InsightEnginePanel`, `ThreadHistorySidebar` | **Before:** Captions, timestamps, labels and meta text rendered in the inherited colour. **After:** They render `text.secondary` (or the palette colour the component requests). **Verified by:** `typography-color.test.tsx` (scan), the components' own specs | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing. |
|
|
141
|
+
| 2.13.0 | Data display and inputs: `DataCard`, `DataTable`, `SimpleTable`, `Timeline`, `TransferList`, `FileUpload`, `FilterBar`, `Combobox`, `CommandPalette`, `Cron`, `PredictiveTextInput`, `IllustratedMessage`, `Modal` | **Before:** Helper, empty-state, count and meta text rendered in the inherited colour. **After:** They render `text.secondary` (or `textPrimary`, `error`, `primary` where the component asks for it). **Verified by:** `typography-color.test.tsx` (scan), the components' own specs | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing. |
|
|
142
|
+
| 2.13.0 | Navigation and layout: `Breadcrumbs`, `TopNavHeader`, `TopNavWithSidebar`, the navigation components, the shared blocks | **Before:** Secondary labels rendered in the inherited colour. **After:** They render `text.secondary`. **Verified by:** `typography-color.test.tsx` (scan) | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing. |
|
|
143
|
+
| 2.13.0 | Templates: `AuthTemplate`, `CatalogTemplate`, `ConnectorWizardTemplate`, `DashboardTemplate`, `ErrorPageTemplate`, `InsightsCenterTemplate`, `IntegrationDetailTemplate`, `ListDetailTemplate`, `OnboardingTemplate`, `PlanHero`, `ReportTemplate`, `SettingsTemplate`, `WizardTemplate` | **Before:** Subtitles and helper text rendered in the inherited colour. **After:** They render `text.secondary`. **Verified by:** `typography-color.test.tsx` (scan) | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing. |
|
|
144
|
+
| 2.13.0 | CSS package, `@trinityui/design-system/css` | **Before:** `--trinity-space-0.5`, `--trinity-space-1.5`, `--trinity-space-2.5` were declared (invalid names that browsers dropped, and that made Vite 8 builds fail) **After:** Only `--trinity-space-0-5`, `--trinity-space-1-5`, `--trinity-space-2-5` are declared **Verified by:** `css-custom-property-names.test.ts`, `run-consumer-fixtures.mjs` vite-build-css leg | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Replace `var(--trinity-space-0.5)` with `var(--trinity-space-0-5)`, and the same for 1.5 and 2.5 |
|
|
145
|
+
| 2.13.0 | `generateCssVariables()` | **Before:** Emitted `--trinity-spacing-0.5`, `--trinity-spacing-1.5`, `--trinity-spacing-2.5` (dropped by browsers) **After:** Emits `--trinity-spacing-0-5`, `--trinity-spacing-1-5`, `--trinity-spacing-2-5` **Verified by:** `css-custom-property-names.test.ts` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Replace `var(--trinity-spacing-0.5)` with `var(--trinity-spacing-0-5)`, and the same for 1.5 and 2.5 |
|
|
146
|
+
| 2.13.0 | `AIVoiceMode` | **Before:** The full-screen surface had no role; `aria-label={title}` sat on Modal's presentation root. The title and the status were both `h6` headings, and the transcript was an `aria-live` region. **After:** A modal dialog named by its title (`h2`), with focus inside; inline, a named group. The status is a paragraph, announced through `useAnnounce`; the transcript is not live. **Verified by:** `AIVoiceMode.test.tsx` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Query the surface with `getByRole('dialog', { name: title })` (inline: `'group'`), and the status by its text. |
|
|
147
|
+
| 2.13.0 | `AIVoiceMode` mute control | **Before:** Named "Unmute microphone" while muted, with the tooltip "Unmute" or "Mute"; every status change was announced. **After:** Always named "Mute microphone", with the same tooltip; `aria-pressed` carries the state. Only listening and muted are announced. **Verified by:** `AIVoiceMode.test.tsx` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Query the control by "Mute microphone" and check `aria-pressed`. |
|
|
148
|
+
| 2.13.0 | `AIVoiceOverlay`, `AIListeningIndicator` | **Before:** The mic was a clickable `<div>`; the close and stop buttons had no name; listening changes were silent; the animations ignored reduced motion. **After:** The mic is a `<button>` named "Voice input" with `aria-pressed`; the buttons are "Close voice input" and "Stop listening", each with a matching tooltip; the overlay announces each listening change. **Verified by:** `AIVoiceInput.test.tsx` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Find the controls by these names. Nothing to change in props. |
|
|
149
|
+
| 2.13.0 | `AISource`, `AISourcesSection` | **Before:** The card was a `div` with `onClick`: not focusable, no role. `AISourcesSection` always passed `onClick`, so every card showed a pointer and a hover border. **After:** With a handler, the title is a button covering the card and the URL link is a separate tab stop. Without one, the card is plain content with no pointer or hover effect. **Verified by:** `AISources.test.tsx` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing. A test that clicked the card's text keeps working; query the card by `getByRole('button', { name: title })`. |
|
|
150
|
+
| 2.13.0 | `Icon` (Feather library) | **Before:** The svg had no `aria-hidden`: screen readers met an unnamed graphic for every Feather icon. **After:** Decorative Feather icons are hidden (`aria-hidden="true"`, `focusable="false"`), as Material icons were. **Verified by:** `Icon.test.tsx` "semantics and ref (C-16)" | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Pass `title` (or `aria-label`) to an icon that must be announced, such as a standalone status icon. A test that found a Feather svg by role finds it with `title` set, or queries the svg directly. |
|
|
151
|
+
| 2.13.0 | `Icon` | **Before:** `aria-*`, `id` and `data-*` props were dropped; the unknown-name warning fired on every render, also in production. **After:** They reach the svg; the warning fires once per name, in development only. **Verified by:** `Icon.test.tsx` | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing. Remove workarounds that wrapped Icon to set attributes. |
|
|
152
|
+
| 2.13.0 | `Icon` `color` with a literal hex or `rgb()` value (`TrinityIconProps.color`) | **Before:** Accepted, with no guidance **After:** Still accepted, and deprecated in the JSDoc (C-16): a literal colour follows neither the theme mode nor token overrides **Verified by:** `Icon.test.tsx` "color (C-31)" | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Use a palette key, or set the colour on a parent and pass `color="inherit"` (the resolver-colour pattern in NAMING.md, Icon) |
|
|
153
|
+
| 2.13.0 | `Icon` `color="success" \| "warning" \| "info"` | **Before:** An invalid CSS color: the icon showed the surrounding text color. **After:** The palette's `success.main`, `warning.main` or `info.main`, in both libraries. **Verified by:** `Icon.test.tsx` "color (C-31)" | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing, unless the inherited color was wanted: then pass `color="inherit"`. |
|
|
154
|
+
| 2.13.0 | `AISource` `type` | **Before:** A decorative icon only; `video` showed a trending-up arrow. **After:** An image named by the type ("Video"); `video` shows a play glyph. **Verified by:** `AISources.test.tsx` "AISource type (WI-20a)" | a, b, c | Status palettes: `createTrinityThemePreset({ mode, palette: { error, warning, success, info } })` with MUI's default palettes restores MUI's status colors (not recommended: white on MUI's light warning and info and on every dark default fails 4.5:1); a token override of `semantic.colors.onStatus` or `darkMode.colors.onStatus` changes the label on contained status buttons; Overlay layers: the `zIndex` prop of `AIFab` and `Tour`, none for Toast, GoToTop and SearchInput (a fixed literal above every overlay was the defect); sx composition: none (an object sx renders exactly as before; only array and function sx, which were dropped, now apply); Caption and overline: `createTrinityThemePreset({ mode: 'light', typography: { caption: { color: '#9CA3AF' }, overline: { color: '#9CA3AF' } } })` (not recommended: 2.54:1 on white fails WCAG 1.4.3); Typography colour props: `sx={{ color: 'inherit' }}` on a Typography, or drop its `color` prop, to keep the inherited colour; Fractional spacing names: none (a dot is not valid in a custom property name, so the old names never worked); AIVoiceMode, AIVoiceOverlay, AISource: none (unnamed dialogs, unreachable controls and unnamed buttons are never correct; leave out `onClick` or `onSourceClick` to keep a source card as plain content); Icon: none (pass `title` or `aria-label` to keep an icon announced; pass `color="inherit"` to keep the inherited colour) | Nothing. A test that counted images in a source card finds one more, named by the type. |
|
|
155
|
+
| 2.13.0 | `palette.trinity.indicator` and StatusIndicator (dark theme) | **Before:** The dark info, new and updated fills were the light `#0043CE` (1.93-2.40:1 on the dark surfaces); the theme had no indicator group. **After:** `#0F62FE` (3:1 or more on every dark surface, white text 5.00:1); `palette.trinity.indicator` gives every status its fill, text, outline and on-page colour per mode, from `semantic.colors.indicator`. The CSS dark blocks set `--trinity-indicator-info-bg`, `-new-bg` and `-updated-bg` to it. **Verified by:** `palette-indicator-group.test.ts`, `contrast-matrix.test.ts` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing. Read indicator colours from `getTrinityPalette(theme).indicator`. |
|
|
156
|
+
| 2.13.0 | `palette.trinity.border` (light) | **Before:** `default` `#E5E7EB`, `subtle` `#F4F4F5`, `strong` `#9CA3AF` (2.54:1 on white); semantic overrides did not reach them. **After:** `default` `#D4D4D8`, `subtle` `#E5E7EB`, `strong` `#6B7280` (4.83:1), read from `semantic.colors.border`, equal to `--trinity-border-*`. **Verified by:** `palette-indicator-group.test.ts`, `contrast-matrix.test.ts` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing. To keep the old values, override the semantic border tokens (the old strong border fails WCAG 1.4.11). |
|
|
157
|
+
| 2.13.0 | `Accordion` summary divider (light) | **Before:** gray 100 `#F4F4F5`. **After:** gray 200 `#E5E7EB` (`border.subtle`), still decorative. **Verified by:** `theme-base-gaps.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing. |
|
|
158
|
+
| 2.13.0 | `semanticTokens.colors.avatar` | **Before:** `backgrounds` documented as accessible with white text; white initials on coral 600, azure 600 and coral 500 fail WCAG 1.4.3. **After:** `backgrounds` unchanged; `foregrounds[i]` is the text colour for `backgrounds[i]` (4.72:1 or more). **Verified by:** `contrast-matrix.test.ts` ("Avatar background and text pairs") | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Render initials or icons on `backgrounds[i]` in `foregrounds[i]`, never white on every fill. |
|
|
159
|
+
| 2.13.0 | `CssBaseline` (every Trinity theme and preset) | **Before:** Under reduced motion, transitions and animations that a component did not guard ran at full length, including infinite loops **After:** They take 0.01ms and run once; elements with `trinity-essential-motion` are exempt **Verified by:** `theme-motion.test.ts`, the `Internal/Theme Motion` probe | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing for most apps. Add `trinity-essential-motion` to motion that carries information with no static equivalent, or override `MuiCssBaseline` styleOverrides to keep the old behavior |
|
|
160
|
+
| 2.13.0 | `theme.transitions` | **Before:** MUI's built-in values; motion token overrides did not reach them **After:** The same values, read from `semanticTokens.motion` where MUI's default equals a token, so token overrides now reach `duration.shortest`, `shorter`, `standard` and `easing.easeInOut`, `easeOut`, `easeIn` **Verified by:** `theme-motion.test.ts` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing, unless you override motion tokens and do not want `theme.transitions` to follow them: then set `transitions` in your theme options |
|
|
161
|
+
| 2.13.0 | `AIShimmer` | **Before:** The root was exposed to assistive technology as an empty element **After:** The root is `aria-hidden="true"` **Verified by:** `AIVisuals.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Announce loading on the loading region (`aria-busy="true"`) or with `useAnnounce`; pass `aria-hidden={false}` only for a shimmer you label yourself |
|
|
162
|
+
| 2.13.0 | `AIShimmer` (dark theme) | **Before:** Light-theme AI colours (a bright stripe on the dark page) **After:** The theme's AI surface roles (`ai.surface`, `ai.surfaceHover`) in every mode and brand **Verified by:** `AIVisuals.test.tsx`, the Shimmer Loading PNG compare | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing; restyle with `sx` if needed |
|
|
163
|
+
| 2.13.0 | `AIShimmer` (reduced motion) | **Before:** The sweep ran forever **After:** The sweep stops; the placeholder stays **Verified by:** `AIVisuals.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing |
|
|
164
|
+
| 2.13.0 | `InsightEngineInput` (forced colors) | **Before:** Keyboard focus in the glowing composer was invisible **After:** A 2px system-highlight outline around the composer on focus **Verified by:** `InsightEngineInput.test.tsx`, the Forced Colors Focus story | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing |
|
|
165
|
+
| 2.13.0 | `InsightEngineInput` popup buttons | **Before:** `aria-expanded` only; no `aria-controls` **After:** `aria-haspopup`, `aria-expanded` and, while open, `aria-controls` on the "+", source, model and voice-mode buttons **Verified by:** `InsightEngineInput.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing |
|
|
166
|
+
| 2.13.0 | `InsightEngineInput` listening mic | **Before:** Pulsed every 1.5s, also under reduced motion **After:** Pulses every 2s (`motion.loop.pulse`) and stops under reduced motion **Verified by:** `InsightEngineInput.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing |
|
|
167
|
+
| 2.13.0 | `InsightEngineInput` `value`, `isListening`, `voiceModeOpen`, `attachments` | **Before:** Switching a prop between defined and undefined silently switched modes **After:** The mode is fixed on the first render, and switching warns in development **Verified by:** `InsightEngineInput.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Keep each prop either always defined (controlled) or always undefined |
|
|
168
|
+
| 2.13.0 | `InsightEngineInput` (narrow containers) | **Before:** Below about 600px the right-hand controls were clipped (Send hidden at 320-400px) **After:** Below 360px of composer width the controls row wraps when the controls do not fit, and the model chip ellipsizes **Verified by:** The Width 320, 400 and 800 stories, `InsightEngineInput.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing. Hosts that set a fixed width on the composer's controls should remove it |
|
|
169
|
+
| 2.13.0 | `InsightEngineInput` attachments | **Before:** Two unnamed progress bars per upload **After:** One progress bar named "Uploading <file name>"; the spinner is decorative **Verified by:** `InsightEngineInput.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing |
|
|
170
|
+
| 2.13.0 | `AILabel` with `popoverContent` | **Before:** The label had no popup state, and the popover was an unnamed container. **After:** The label has `aria-haspopup="dialog"`, `aria-expanded` and `aria-controls`; the popover is a modal dialog named by `popoverLabel` (default "About this AI content") or `popoverLabelledBy`. **Verified by:** `AILabel.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Query the popover with `getByRole('dialog', { name })`. Pass `popoverLabel` or `popoverLabelledBy` to give it a more specific name. |
|
|
171
|
+
| 2.13.0 | `ChatHistoryList` | **Before:** The list was `role="listbox"`; each conversation was `role="option"` with `aria-selected`. The docs described arrow-key navigation. **After:** A list named "Conversation history" of buttons; the active conversation has `aria-current="true"`. Tab moves between conversations; there are no arrow-key bindings. **Verified by:** `ChatHistoryList.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Query `getByRole('button', { current: true })` for the active conversation and `getByRole('list', { name: 'Conversation history' })` for the list, instead of `listbox`, `option` or `aria-selected`. |
|
|
172
|
+
| 2.13.0 | `AILabel` (`size="mini"` or `"2xs"`, clickable) | **Before:** The pointer target was the visual pill, 21.5px or 23.25px tall. **After:** The pointer target is at least 24px tall; the pill looks the same. **Verified by:** `AILabel.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing, unless two such labels sit closer than 24px centre to centre: space them apart. |
|
|
173
|
+
| 2.13.0 | `AIPersonaCard` with `onClick` | **Before:** A clickable Paper: no button, no tab stop, no `aria-pressed`; the name was an `h6`. **After:** A toggle button named by `name`, with `aria-pressed={selected}`; Enter and Space toggle it; the name is a paragraph. **Verified by:** `AIPersonaCard.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Query `getByRole('button', { name, pressed })` instead of clicking the card's text; query the name as text, not as a heading. |
|
|
174
|
+
| 2.13.0 | `AIDigestCard` highlight with `onClick` | **Before:** The row was `role="button"` around the markdown; a link inside it was nested in the button. **After:** An empty native button covers the row, named by the text or `actionLabel`; the text and its links are outside the button; the focus ring is the theme's, inside the row. **Verified by:** `AIDigestCard.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing for most consumers. Pass `actionLabel` to give the row's button a short name, and query links in a row as links. |
|
|
175
|
+
| 2.13.0 | AIRelatedQuestions, AISourcesPanel, AIVoiceOverlay, AIListeningIndicator, AIVoiceMode, ChatHistoryList, InsightEngineInput, AIActions, InsightEnginePanel | **Before:** Informative text and enabled-control icons used `text.disabled` (2.54:1 in light mode). **After:** They use `text.secondary` (9.9:1 or more); `text.disabled` stays only on disabled controls. **Verified by:** `disabledTextMisuse.test.tsx`, `contrast-matrix.test.ts` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing. Update visual baselines of these components. |
|
|
176
|
+
| 2.13.0 | AIActions (list and card hover), AIPersonaCard (selected and hover), AISource (hover border and shadow, number badge), AILabel (hover glow and focus outline), InsightEngineInput (focus glow) | **Before:** Static light AI colours (`aiTokens.colors`), also in dark mode **After:** The theme's AI roles (`getTrinityPalette(theme).ai`: `primary`, `surfaceHover`, `surfaceEmphasis`, `focusRing`, `presence.*`): identical in light, the dark roles in dark mode. AILabel's gradient keeps its light stops until ADR-03-R13 is decided **Verified by:** `AIActions.test.tsx`, `AIPersonaCard.test.tsx`, `AISources.test.tsx`, `AILabel.test.tsx`, `InsightEngineInput.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing. Override the AI roles to restyle them |
|
|
177
|
+
| 2.13.0 | AIVoiceOverlay mic, AIListeningIndicator dot | **Before:** The pulse ran every 1.5s and the blink every 1s, under global animation names another component could override **After:** One `motion.loop.pulse` cycle (2s) and one `motion.loop.typing` cycle (1.2s), under scoped names; both stop under reduced motion as before **Verified by:** `AIVoiceInput.test.tsx` | a, b, c | Light borders: override the semantic border tokens (`createTrinityThemePreset({ mode: 'light', tokens: { semantic: { colors: { border: { default: '#E5E7EB', subtle: '#F4F4F5', strong: '#9CA3AF' } } } } })`; the old strong border fails WCAG 1.4.11); Dark indicator fills and avatar pairs: none (the old fills failed 3:1 and the backgrounds keep their values); Reduced-motion backstop: override `components.MuiCssBaseline.styleOverrides`, or put `trinity-essential-motion` on motion that must keep running; AIShimmer: `aria-hidden={false}` to expose it; AILabel, ChatHistoryList, AIPersonaCard, AIDigestCard, the AI target sizes and focus indicators: none (WCAG 4.1.2, 2.1.1, 2.4.7 and 2.5.8 failures have no valid old form); AI colours: none for `text.disabled` misuse (WCAG 1.4.3, 1.4.11); override the AI roles to restyle the hover and selection colours | Nothing |
|
|
178
|
+
| 2.13.0 | `GanttView` (group toggle) | **Before:** The icon-only toggle was named "Collapse group" / "Expand group" and showed no tooltip. **After:** It shows the same text as a tooltip on hover and keyboard focus. The name is unchanged. **Verified by:** `GanttView.test.tsx` "shows a tooltip with the same text as the toggle name on hover" | b | none (the tooltip shows the toggle's existing accessible name; the behavior contract requires it for every icon-only button) | Nothing. A test that counts `role="tooltip"` elements sees one while the toggle is hovered or focused. The chevron is 16px instead of 14px. |
|
|
179
|
+
| 2.13.0 | `CatalogTemplate` default card (`onItemClick`) | **Before:** A `role="button"` Paper with `tabIndex` and a key handler, named by all of its text (category, title, description, tags). **After:** A `ButtonBase` (`div` with `role="button"`) named by the title only; Enter, Space and the focus ring come from the theme. **Verified by:** `CatalogTemplate.test.tsx` "a card is a button named by its title" | b | none (the unnamed clear and close buttons and the hand-rolled card button failed WCAG 4.1.2; no prop restores them) | Nothing. Tests that found the card by its full text find it with `getByRole('button', { name: <title> })`. |
|
|
180
|
+
| 2.13.0 | `CatalogTemplate` search | **Before:** A `TextField` with an unnamed icon-only clear button (Feather X). **After:** A `ClearableTextField`: the clear button is named "Clear search" with the same tooltip (Material close glyph, 20px) and returns focus to the field after a keyboard clear. **Verified by:** `CatalogTemplate.test.tsx` "the search clear button is named" | b | none (the unnamed clear and close buttons and the hand-rolled card button failed WCAG 4.1.2; no prop restores them) | Nothing. |
|
|
181
|
+
| 2.13.0 | `CatalogTemplate` mobile filter drawer | **Before:** The close button had no name and no tooltip. **After:** It is named "Close filters" with the same tooltip. **Verified by:** `CatalogTemplate.test.tsx` "Close filters" | b | none (the unnamed clear and close buttons and the hand-rolled card button failed WCAG 4.1.2; no prop restores them) | Nothing. |
|
|
182
|
+
| 2.13.0 | `DataCard` (`onClick`) | **Before:** The card ran `onClick` only on a mouse click; it had no Tab stop and no name. **After:** The card holds a button named by `title`: a Tab stop, with Enter and Space, and the focus ring inside the card edge. A mouse click on the card still runs `onClick`. **Verified by:** `DataCard.test.tsx` "the clickable card is a button named by its title, reachable with Tab, run by Enter and Space" | b | none (the old card was not operable from the keyboard and its More button had no name; no prop restores them) | Nothing. Tests that count buttons or Tab stops see one more per clickable card; query it with `getByRole('button', { name: <title> })`. |
|
|
183
|
+
| 2.13.0 | `DataCard` (`onMoreClick`) | **Before:** The icon-only More button had no accessible name and no tooltip. **After:** It is named "More actions" (or `moreActionsLabel`) and shows the same text as its tooltip. **Verified by:** `DataCard.test.tsx` "the More button is named" | b | none (the old card was not operable from the keyboard and its More button had no name; no prop restores them) | Nothing. Pass `moreActionsLabel` to name it in your language. |
|
|
184
|
+
| 2.13.0 | `DataCard` (`sparklineData`) | **Before:** The sparkline gradient id was built from the trend colour, so two cards with the same colour shared it. **After:** Each sparkline's gradient id comes from `useId`. **Verified by:** `DataCard.test.tsx` "distinct sparkline gradient ids" | b | none (the old card was not operable from the keyboard and its More button had no name; no prop restores them) | Nothing. |
|
|
185
|
+
| 2.13.0 | `InsightCard` (`interactive`) | **Before:** The Card itself had `role="button"`, `tabIndex={0}` and `aria-label`, and held the dismiss button and the actions (nested interactive content). **After:** The Card holds a primary-action button named by `ariaLabel` or the title; the dismiss button and the actions sit beside it, not inside it. The focus ring is drawn inside the card edge. **Verified by:** `InsightCard.test.tsx` "an interactive card that holds a dismiss button has no nested interactive content" | b | none (a role="button" card around the dismiss button nested interactive content, which WCAG 4.1.2 and the behavior contract forbid) | Nothing. Tests that read `role="button"` on the card root find the button with `getByRole('button', { name: <title or ariaLabel> })`, which now is a child of the card. |
|
|
186
|
+
| 2.13.0 | `InsightCard` (`onDismiss`) | **Before:** The dismiss button was named "Dismiss insight" and showed no tooltip. **After:** It shows the same text as a tooltip on hover and keyboard focus. **Verified by:** `InsightCard.test.tsx` "the dismiss button shows" | b | none (a role="button" card around the dismiss button nested interactive content, which WCAG 4.1.2 and the behavior contract forbid) | Nothing. |
|
|
187
|
+
| 2.13.0 | `Timeline` expandable items | **Before:** Only a click on the content expanded an item in the default and compact variants; the detailed variant's expand button had no name, `aria-expanded` or `aria-controls`. **After:** Every variant shows a "Show details" / "Hide details" button with the same tooltip, `aria-expanded` and `aria-controls`. A click on the content still toggles. **Verified by:** `Timeline.test.tsx` "an expandable item has a "Show details" button" | b | none (expandable and clickable items had no keyboard path, which WCAG 2.1.1 requires) | Nothing. Pass `labels` to rename the button. Tests that count buttons see one per expandable item. |
|
|
188
|
+
| 2.13.0 | `Timeline` (`onItemClick`) | **Before:** Items ran `onItemClick` only on a mouse click. **After:** An item without expandable content holds a button named by its title: a Tab stop, with Enter and Space. **Verified by:** `Timeline.test.tsx` "a clickable item is a button named by its title" | b | none (expandable and clickable items had no keyboard path, which WCAG 2.1.1 requires) | Nothing. Query it with `getByRole('button', { name: <title> })`. |
|
|
189
|
+
| 2.13.0 | `CustomLegend` (`onClick`), `InteractiveLegend` | **Before:** Entries were boxes with a click handler: no Tab stop, no Enter or Space, no pressed state. **After:** Each entry is a toggle button named by its label with `aria-pressed`; the legend is a `group` named by `legendLabel` (default "Chart legend"). **Verified by:** `CustomLegend.test.tsx` "each entry is a toggle button" | b | none (legend entries that toggle a series had no keyboard path, which WCAG 2.1.1 requires) | Nothing. Tests that clicked the label text still work; query an entry with `getByRole('button', { name: <label> })`. |
|
|
190
|
+
| 2.13.0 | `Combobox` (`multiple`, `showSelectAll`) | **Before:** "Select All" was a row above the list, outside the listbox: mouse only. **After:** It is the first option while nothing is typed (it is hidden while you type, so the first match keeps the highlight); arrow keys and Enter select all, and again none. **Verified by:** `Combobox.test.tsx` "Select All is an option in the listbox" | b | none ("Select All" had no keyboard path and "Create" moved focus out of the input, which WCAG 2.1.1 and the combobox pattern do not allow) | Nothing. Tests that clicked the row find it with `getByRole('option', { name: /Select All/ })`. |
|
|
191
|
+
| 2.13.0 | `Combobox` (`creatable`) | **Before:** "Create" was a button below the list; Tab to it blurred the input and closed the list. **After:** It is the last option while the typed text can be created; Enter on it runs `onCreate`. **Verified by:** `Combobox.test.tsx` "Create is an option in the listbox" | b | none ("Select All" had no keyboard path and "Create" moved focus out of the input, which WCAG 2.1.1 and the combobox pattern do not allow) | Nothing. Query it with `getByRole('option', { name: 'Create "…"' })`. |
|
|
192
|
+
| 2.13.0 | `Combobox` option check boxes (`multiple`, `showCheckbox`) | **Before:** Each option held a checkbox input with no label (axe `label`, and focusable content in a listbox). **After:** A decorative mark of the same size; the option's `aria-selected` carries the state. **Verified by:** `Combobox.test.tsx` "has no axe violations with the listbox open" | b | none ("Select All" had no keyboard path and "Create" moved focus out of the input, which WCAG 2.1.1 and the combobox pattern do not allow) | Nothing. |
|
|
193
|
+
| 2.13.0 | `FileUpload` (`variant="dropzone"`) | **Before:** A Paper with a hand-written `role="button"`, `tabIndex` and Enter/Space handler, focused with the browser's default outline. **After:** A `ButtonBase` over the Paper: the same name, Enter and Space, the theme's focus ring; disabled, it is `aria-disabled` and ignores drags. **Verified by:** `FileUpload.test.tsx` "the dropzone is one button" | b | none (a hand-written role="button" is what the behavior contract replaces with ButtonBase; the keyboard behavior is unchanged) | Nothing. |
|
|
194
|
+
| 2.13.0 | `UploadDropZone` | **Before:** The Browse button had no handler; its click reached `onBrowse` through the zone. **After:** Browse runs `onBrowse` and stops the click there, so `onBrowse` still runs once. **Verified by:** `IllustratedMessage.test.tsx` "Browse runs onBrowse once" | b | none (a hand-written role="button" is what the behavior contract replaces with ButtonBase; the keyboard behavior is unchanged) | Nothing. |
|
|
195
|
+
| 2.13.0 | `DataTable` row actions menu button | **Before:** Named "More actions" with no popup state and no tooltip. **After:** `aria-haspopup="menu"`, `aria-expanded`, `aria-controls` while open, the same tooltip; Escape returns focus to it. **Verified by:** `CellRenderers.test.tsx` "the overflow menu trigger is a menu button" | b | none (the row menu, hover actions and add row had no keyboard or screen-reader path, and the copy was silent; WCAG 2.1.1, 4.1.2 and 4.1.3) | Nothing. |
|
|
196
|
+
| 2.13.0 | `DataTable` hover actions (`rowActionsMode="hover"`) | **Before:** `visibility: hidden` until the row was hovered: keyboard users never reached the actions. **After:** Hidden by opacity only; they stay in the Tab order and show while one has keyboard focus. **Verified by:** `CellRenderers.test.tsx` "hover actions stay in the tab order" | b | none (the row menu, hover actions and add row had no keyboard or screen-reader path, and the copy was silent; WCAG 2.1.1, 4.1.2 and 4.1.3) | Nothing. Tests that expected them hidden from the accessibility tree find them now. |
|
|
197
|
+
| 2.13.0 | `DataTable` inline add row | **Before:** The placeholder ran on a click only; the fields were named by placeholders only. **After:** The placeholder is a button (Tab, Enter, Space) that moves focus to the first field; each field is named by its column header. **Verified by:** `InlineAddRow.test.tsx` | b | none (the row menu, hover actions and add row had no keyboard or screen-reader path, and the copy was silent; WCAG 2.1.1, 4.1.2 and 4.1.3) | Nothing. |
|
|
198
|
+
| 2.13.0 | `DataTable` "Copy to clipboard" | **Before:** Called `navigator.clipboard` directly and announced nothing; it rejected outside a secure context. **After:** Copies through `useClipboard` and announces `labels.rowsCopied({ count })` ("Copied N rows"), or `labels.copyFailed` assertively. **Verified by:** `DataTable.test.tsx` "announces the copy" | b | none (the row menu, hover actions and add row had no keyboard or screen-reader path, and the copy was silent; WCAG 2.1.1, 4.1.2 and 4.1.3) | Pass `labels.rowsCopied` and `labels.copyFailed` to translate the announcements. |
|
|
199
|
+
| 2.13.0 | `DockLayout` panel header | **Before:** Icon-only Minimize, Maximize, More and Close buttons with no names or tooltips; More's menu had no popup state. **After:** Named buttons with the same tooltips (`labels`); More has `aria-haspopup="menu"` and `aria-expanded`. **Verified by:** `DockLayout.test.tsx` "the header actions are named" | b | none (unnamed buttons, a button nested in a tab and a hand-built live region failed WCAG 4.1.2 and the behavior contract) | Nothing. Pass `labels` to translate the names. |
|
|
200
|
+
| 2.13.0 | `DockLayout` tabs (`closable`) | **Before:** Each tab held a close button inside the tab (a button nested in a tab); keyboard users tabbed to it. **After:** Delete on a focused tab closes it (`aria-keyshortcuts="Delete"`); the close icon is a pointer-only shortcut and no longer a tab stop. **Verified by:** `DockLayout.test.tsx` "Delete on a focused closable tab fires onPanelClose" | b | none (unnamed buttons, a button nested in a tab and a hand-built live region failed WCAG 4.1.2 and the behavior contract) | Tests that clicked a button inside the tab press Delete on the tab, or click the icon. |
|
|
201
|
+
| 2.13.0 | `DockLayout` drag and drop | **Before:** Announced through its own visually hidden `role="status"` region. **After:** Announced through `useAnnounce` (the nearest `LiveRegionProvider`, else the document-level regions). **Verified by:** `DockLayout.test.tsx` "announced through the toolkit live region" | b | none (unnamed buttons, a button nested in a tab and a hand-built live region failed WCAG 4.1.2 and the behavior contract) | Nothing. |
|
|
202
|
+
| 2.13.0 | `SettingsTemplate` (phone width) | **Before:** The icon-only menu button had no name and no popup state; the drawer had no name. **After:** Named "Open settings navigation" with the same tooltip; `aria-haspopup="dialog"`, `aria-expanded`, `aria-controls` while open; the drawer is a dialog named by `title`. **Verified by:** `SettingsTemplate.test.tsx` | b | none (the unnamed trigger failed WCAG 4.1.2) | Nothing. |
|
|
203
|
+
| 2.13.0 | `TopNavHeader`, `TopNavWithSidebar`, `ClientMenu` client picker | **Before:** Named "Current client: {client}. Click to switch clients"; its list held the "Switch Clients" heading inside `role="listbox"` (TopNavWithSidebar: a `role="menu"` of menu items); fixed id `client-menu`. **After:** Named "Switch client, current: {client}" (`clientSelectorLabel`); a listbox of options named by the "Switch client" heading above it; ids from `useId`; `aria-controls` while open. **Verified by:** `TopNavHeader.test.tsx`, `TopNavWithSidebar.test.tsx`, `navigation.test.tsx` | a, b | Client picker name: pass `clientSelectorLabel` to word it differently; the rest: none (popup state, unique ids, list structure and tooltips are WCAG 1.3.1 and 4.1.2 fixes) | Tests that found the trigger by its old name use `getByRole('button', { name: /Switch client, current:/ })`; tests that found the list by `#client-menu` use `getByRole('listbox', { name: 'Switch client' })`. |
|
|
204
|
+
| 2.13.0 | Navigation apps and user menus | **Before:** `aria-haspopup` and `aria-expanded` by hand, fixed ids (`apps-menu`, `user-menu`), the user's name inside the menu. **After:** From `useDisclosure` with `aria-controls` while open; the name and email above the menu, naming it. **Verified by:** `navigation.test.tsx` | a, b | Client picker name: pass `clientSelectorLabel` to word it differently; the rest: none (popup state, unique ids, list structure and tooltips are WCAG 1.3.1 and 4.1.2 fixes) | Nothing. |
|
|
205
|
+
| 2.13.0 | `TopNavWithSidebar` sidebar toggle | **Before:** On phones it reported the desktop sidebar's state; `aria-controls="sidebar-navigation"`. **After:** On phones it reports the drawer ("Open/Close sidebar navigation"); `aria-controls` points at the drawer's generated id. **Verified by:** `TopNavWithSidebar.test.tsx` "the sidebar toggle controls the sidebar" | a, b | Client picker name: pass `clientSelectorLabel` to word it differently; the rest: none (popup state, unique ids, list structure and tooltips are WCAG 1.3.1 and 4.1.2 fixes) | Tests that read `#sidebar-navigation` follow the toggle's `aria-controls`. |
|
|
206
|
+
| 2.13.0 | Navigation icon-only buttons | **Before:** No tooltips. **After:** Each shows its name as a tooltip on hover and keyboard focus. **Verified by:** `TopNavHeader.test.tsx`, `navigation.test.tsx` | a, b | Client picker name: pass `clientSelectorLabel` to word it differently; the rest: none (popup state, unique ids, list structure and tooltips are WCAG 1.3.1 and 4.1.2 fixes) | Nothing. |
|
|
207
|
+
| 2.13.0 | `DateRangePicker` | **Before:** `aria-haspopup` and `aria-expanded` by hand; the calendar paper had no id, role or name. **After:** `aria-controls` while open; the paper is a dialog named by the trigger; month buttons have tooltips. **Verified by:** `DateRangePicker.test.tsx` "the trigger controls a dialog paper" | b | none (popups with no name or anchor, a list with a non-item child and mouse-only rows fail WCAG 1.3.1, 2.1.1 and 4.1.2) | Nothing. |
|
|
208
|
+
| 2.13.0 | `NotificationCenter` (`open`) | **Before:** A controlled `open={true}` had no anchor, so the popover opened at the page corner; the popover had no role or name. **After:** The popover anchors to the bell in every mode and is a dialog named by `title`; the bell has `aria-controls` while open and a tooltip. **Verified by:** `NotificationCenter.test.tsx` "a controlled open={true} anchors the popover" | b | none (popups with no name or anchor, a list with a non-item child and mouse-only rows fail WCAG 1.3.1, 2.1.1 and 4.1.2) | Nothing. |
|
|
209
|
+
| 2.13.0 | `NotificationCenter` notification rows | **Before:** A row with `onClick` ran it on a mouse click only; separators were `role="separator"` items inside the list. **After:** A row with `onClick` holds a button named by its title (Tab, Enter, Space); separators are borders of the rows. **Verified by:** `NotificationCenter.test.tsx` "a clickable row is a button" | b | none (popups with no name or anchor, a list with a non-item child and mouse-only rows fail WCAG 1.3.1, 2.1.1 and 4.1.2) | Nothing. Tests that clicked the row text click the button named by the title. |
|
|
210
|
+
| 2.13.0 | `BulkActionDock` | **Before:** The whole dock was `aria-live="polite"`, so a change reread the dock; the slide-in ran under reduced motion; Clear selection showed no tooltip. **After:** The dock region has no `aria-live`; the count is announced once per change through `useAnnounce`; no slide-in under reduced motion; Clear selection has a tooltip. **Verified by:** `BulkActionDock.test.tsx` | b | none (a dock that rereads all its buttons, motion that ignores the user's setting and an icon-only button whose name sighted users never see fail WCAG 4.1.3, 2.3.3 and the behavior contract) | Nothing. Tests that read the dock's text as a live region read the announcement (`readAnnouncements`). |
|
|
211
|
+
| 2.13.0 | Trinity logo and mark (`TopNavHeader`, `TopNavWithSidebar`, `TrinityLogo`) | **Before:** Literal ids `trinity-logo-gradient`, `trinity-logo-clip`, `trinity-icon-gradient`, shared by every instance. **After:** Each instance's ids come from `useId` (`trinity-logo-gradient-<id>` and so on). **Verified by:** `trinityBrandSvgIds.test.tsx` | b | none (shared ids made a second logo paint with the first one's gradient and clip; nothing restores that) | Nothing. Tests or styles that targeted the literal ids select the svg by its role and name (`img`, "Trinity") instead. |
|
|
212
|
+
| 2.13.0 | `HierarchicalDrillDown` `selectedPath`, `SystemBanner` `open`, `Tour` `current` | **Before:** Switching the prop between a value and `undefined` silently switched modes. **After:** The mode is fixed on the first render, and switching logs MUI's controlled/uncontrolled warning in development. **Verified by:** `HierarchicalDrillDown.test.tsx`, `SystemBanner.test.tsx`, `Tour.test.tsx` "switching ... warns once" | b | none (the warning is development-only and names a mode switch that was always unsupported; the hydration fix has no old form worth keeping) | Keep each prop either always defined (controlled) or always undefined. |
|
|
213
|
+
| 2.13.0 | `SystemBanner` (`storageKey`, server rendering) | **Before:** The dismissal was read in a `useState` initializer, so a server-rendered page hydrated with different markup when the user had dismissed the banner. **After:** The server and hydration renders show the banner, and it hides right after; a client-only render hides it at once. **Verified by:** `SystemBanner.test.tsx` "hydrates without a mismatch" | b | none (the warning is development-only and names a mode switch that was always unsupported; the hydration fix has no old form worth keeping) | Nothing. |
|
|
214
|
+
| 2.13.0 | `DockLayout` panel moves | **Before:** Only HTML5 drag and drop moved a panel. **After:** A "Move to" item per other zone in a single panel's More menu, and a "Move {title}" button at the end of each tab bar; both call the drop path. **Verified by:** `DockLayout.test.tsx` "a keyboard-only Move to from the More menu reaches onLayoutChange with the drag payload" | b | `draggable={false}` removes the move button and the "Move to" items together with dragging | Nothing. Pass `zoneLabels` to name the zones, and `labels` to translate. |
|
|
215
|
+
| 2.13.0 | `DockLayout` move announcement | **Before:** "Moved panel {title} to zone {zone id}." **After:** "Moved panel {title} to {zone}.", where zone is the `zoneLabels` entry or the id (`labels.moved`). **Verified by:** `DockLayout.test.tsx` "a keyboard-only Move to … announced" | b | `draggable={false}` removes the move button and the "Move to" items together with dragging | Tests that read the old string read the new one. |
|
|
216
|
+
| 2.13.0 | `TreeView` with `draggable` and `onDrop` | **Before:** Only HTML5 drag and drop moved a node. **After:** A move menu (Shift+F10, the ContextMenu key, or the row's actions button) calls `onDrop` with 'before', 'after' or 'inside'; each row has an actions button. **Verified by:** `TreeView.test.tsx` "drag and the menu make the same onDrop call" | b | the menu appears only with both `draggable` and `onDrop`; without `onDrop` nothing changes | Handle 'before' and 'after' in `onDrop` if it handled only 'inside' (the only position a drag produced). Pass `labels` to translate. |
|
|
217
|
+
| 2.13.0 | `DataTable` `onRowUpdate` | **Before:** `newRow` and `oldRow` were the same object, already holding the new value. **After:** `newRow` and `oldRow` are distinct copies; `oldRow` holds the edited field's previous value. **Verified by:** `DataTable.rowUpdate.test.tsx` | a | none (the old `oldRow` was the edited row itself, so no consumer could rely on it holding the old value) | Code that compared the two arguments now sees the edit; code that mutated `newRow` no longer mutates the grid's row. |
|
|
218
|
+
| 2.13.0 | `sx` on Combobox, DataTable, DateRangePicker, InsightCard, SplitPane, TransferList and TreeView | **Before:** An array `sx` was turned into ignored keys and a function `sx` was dropped; an object `sx` applied. **After:** Array and function `sx` apply, merged after the component's own styles; an object `sx` renders the same CSS. **Verified by:** `src/__tests__/sx-composition.test.tsx` | a | none (an object `sx` renders the same CSS; only array and function `sx`, which the documented `SxProps` type accepts, change from dropped to applied) | Remove workarounds that wrapped these components to apply an array or function `sx`. |
|
|
219
|
+
| 2.13.0 | `SplitPane` `size` | **Before:** Switching between a number and `undefined` silently switched modes; a mirror effect copied the prop into the drag state. **After:** The mode is fixed on the first render, and switching logs MUI's controlled/uncontrolled warning in development; the drag state reads the prop directly. **Verified by:** `SplitPane.test.tsx` (unchanged) | b | none (the warning is development-only and names a mode switch that was always unsupported) | Keep `size` either always defined (controlled) or always undefined. |
|
|
220
|
+
| 2.13.0 | `InsightEngineInput` `value` / `attachments` switching to `undefined` | **Before:** The render threw (`value.trim` of `undefined`), since `useControlled` keeps the controlled mode. **After:** Reads as an empty value or no attachments, and MUI's mode-switch warning names `InsightEngineInput` in development. **Verified by:** `InsightEngineInput.test.tsx` "warns once in development when value switches" | e | none (the old behavior was a render error) | Keep each prop either always defined or always undefined. |
|
|
221
|
+
| 2.13.0 | AI icon-only button tooltips | **Before:** ChatHeader close said "Close" against the name "Close panel"; InsightEngineInput Retry said "Retry upload" against "Retry upload of {file}"; ChatHistoryList options, AIDigestCard dismiss and InsightEngineInput Remove had no tooltip. **After:** Each tooltip shows the button's name. **Verified by:** `ChatHeader.test.tsx`, `ChatHistoryList.test.tsx`, `AIDigestCard.test.tsx`, `InsightEngineInput.test.tsx` (WI-57+ cases) | b | none (tooltips only; each shows the name the button already had) | Tests that read the old tooltip text read the name. |
|
|
222
|
+
| 2.13.0 | `StatusIndicator` (dark theme): inline text, icon-only icon, inline icon | **Before:** The light catalog colours, 3.00-3.65:1 on the dark surfaces **After:** The theme's dark on-surface role per status family, 4.5:1 or more on `#121214`, `#1C1C1F` and `#262629` **Verified by:** `dark-status-indicators.test.ts`, `StatusIndicator.test.tsx` | b, c | StatusIndicator differential colours: pass `positiveColor` and `negativeColor` (the old light positive `#24a148` fails WCAG 1.4.3); StatusIndicator dark on-surface colours: none (the light literals failed 1.4.3 on the dark surfaces); StatusIndicator live region: pass `live` or `live="assertive"` to keep a live region (the old static `role="status"` has no opt-out, since every indicator on a page was announced, WCAG 4.1.3); dot pulse: none (an infinite animation that ignores the reduced-motion setting fails WCAG 2.3.3); dot ring: pass `colors` to draw the dot without the ring (the plain warning-family fill fails WCAG 1.4.11 on white); StatusHero, InsightCard and HistoricalDataIndicator names: none (the old names were missing or duplicated, WCAG 1.1.1 and 4.1.2) | Nothing. Update dark visual baselines |
|
|
223
|
+
| 2.13.0 | `StatusIndicator variant="differential"` default colours | **Before:** Positive `#24a148` and negative `#da1e28` in both modes **After:** Light `#15803D` and `#DA1E28`; dark `#86EFAC` and `#FCA5A5` (the theme's success and critical roles) **Verified by:** `StatusIndicator.test.tsx` | b, c | StatusIndicator differential colours: pass `positiveColor` and `negativeColor` (the old light positive `#24a148` fails WCAG 1.4.3); StatusIndicator dark on-surface colours: none (the light literals failed 1.4.3 on the dark surfaces); StatusIndicator live region: pass `live` or `live="assertive"` to keep a live region (the old static `role="status"` has no opt-out, since every indicator on a page was announced, WCAG 4.1.3); dot pulse: none (an infinite animation that ignores the reduced-motion setting fails WCAG 2.3.3); dot ring: pass `colors` to draw the dot without the ring (the plain warning-family fill fails WCAG 1.4.11 on white); StatusHero, InsightCard and HistoricalDataIndicator names: none (the old names were missing or duplicated, WCAG 1.1.1 and 4.1.2) | Nothing. To keep the old colours, pass `positiveColor` and `negativeColor` |
|
|
224
|
+
| 2.13.0 | `StatusIndicator`, `StatusLegend` (custom tokens) | **Before:** Token overrides did not reach the indicator colours **After:** `semantic.colors.indicator` and the dark status roles you override reach every variant **Verified by:** `StatusIndicator.test.tsx` (brand flow) | b, c | StatusIndicator differential colours: pass `positiveColor` and `negativeColor` (the old light positive `#24a148` fails WCAG 1.4.3); StatusIndicator dark on-surface colours: none (the light literals failed 1.4.3 on the dark surfaces); StatusIndicator live region: pass `live` or `live="assertive"` to keep a live region (the old static `role="status"` has no opt-out, since every indicator on a page was announced, WCAG 4.1.3); dot pulse: none (an infinite animation that ignores the reduced-motion setting fails WCAG 2.3.3); dot ring: pass `colors` to draw the dot without the ring (the plain warning-family fill fails WCAG 1.4.11 on white); StatusHero, InsightCard and HistoricalDataIndicator names: none (the old names were missing or duplicated, WCAG 1.1.1 and 4.1.2) | Nothing |
|
|
225
|
+
| 2.13.0 | `StatusIndicator` (every variant but the badge) | **Before:** A polite `role="status"` region; `aria-label` was ignored; the dot was named after its status, not its `label` **After:** No live region unless `live` is set. Visible text is read; dot, `iconOnly` and `showLabel={false}` are `role="img"` named by `aria-label`, else `label`; the differential is `role="group"` named by its sentence **Verified by:** `StatusIndicator.test.tsx`, `a11y-audit.test.tsx` | b, c | StatusIndicator differential colours: pass `positiveColor` and `negativeColor` (the old light positive `#24a148` fails WCAG 1.4.3); StatusIndicator dark on-surface colours: none (the light literals failed 1.4.3 on the dark surfaces); StatusIndicator live region: pass `live` or `live="assertive"` to keep a live region (the old static `role="status"` has no opt-out, since every indicator on a page was announced, WCAG 4.1.3); dot pulse: none (an infinite animation that ignores the reduced-motion setting fails WCAG 2.3.3); dot ring: pass `colors` to draw the dot without the ring (the plain warning-family fill fails WCAG 1.4.11 on white); StatusHero, InsightCard and HistoricalDataIndicator names: none (the old names were missing or duplicated, WCAG 1.1.1 and 4.1.2) | Pass `live` where a status changes while the user is on the page. In tests, replace `getByRole('status')` with `getByText(label)` for variants with visible text, or `getByRole('img', { name })` for dot, icon-only and unlabelled icon or shape; use `getByRole('group', { name })` for the differential |
|
|
226
|
+
| 2.13.0 | `StatusIndicator variant="chip"` with `onClick` or `onDelete` | **Before:** `role="status"` replaced the chip's button role **After:** MUI Chip's button semantics; `aria-label` names the button **Verified by:** `StatusIndicator.test.tsx` | b, c | StatusIndicator differential colours: pass `positiveColor` and `negativeColor` (the old light positive `#24a148` fails WCAG 1.4.3); StatusIndicator dark on-surface colours: none (the light literals failed 1.4.3 on the dark surfaces); StatusIndicator live region: pass `live` or `live="assertive"` to keep a live region (the old static `role="status"` has no opt-out, since every indicator on a page was announced, WCAG 4.1.3); dot pulse: none (an infinite animation that ignores the reduced-motion setting fails WCAG 2.3.3); dot ring: pass `colors` to draw the dot without the ring (the plain warning-family fill fails WCAG 1.4.11 on white); StatusHero, InsightCard and HistoricalDataIndicator names: none (the old names were missing or duplicated, WCAG 1.1.1 and 4.1.2) | Query the chip with `getByRole('button', { name })` |
|
|
227
|
+
| 2.13.0 | `StatusIndicator variant="dot" pulse` (reduced motion) | **Before:** The pulse ran forever **After:** The pulse stops; the dot stays **Verified by:** `StatusIndicator.test.tsx`, the Dot Variant story under reduced motion | b, c | StatusIndicator differential colours: pass `positiveColor` and `negativeColor` (the old light positive `#24a148` fails WCAG 1.4.3); StatusIndicator dark on-surface colours: none (the light literals failed 1.4.3 on the dark surfaces); StatusIndicator live region: pass `live` or `live="assertive"` to keep a live region (the old static `role="status"` has no opt-out, since every indicator on a page was announced, WCAG 4.1.3); dot pulse: none (an infinite animation that ignores the reduced-motion setting fails WCAG 2.3.3); dot ring: pass `colors` to draw the dot without the ring (the plain warning-family fill fails WCAG 1.4.11 on white); StatusHero, InsightCard and HistoricalDataIndicator names: none (the old names were missing or duplicated, WCAG 1.1.1 and 4.1.2) | Nothing |
|
|
228
|
+
| 2.13.0 | `StatusIndicator variant="dot"`: `warning`, `caution`, `pending` (light themes) | **Before:** A plain fill, 1.68:1 (`#F1C21B`) or 2.46:1 (`#FF832B`) on white **After:** The fill with a 1px inset ring in the status outline colour (4.99 or 5.03:1) **Verified by:** `StatusIndicator.test.tsx` ("dot ring") | b, c | StatusIndicator differential colours: pass `positiveColor` and `negativeColor` (the old light positive `#24a148` fails WCAG 1.4.3); StatusIndicator dark on-surface colours: none (the light literals failed 1.4.3 on the dark surfaces); StatusIndicator live region: pass `live` or `live="assertive"` to keep a live region (the old static `role="status"` has no opt-out, since every indicator on a page was announced, WCAG 4.1.3); dot pulse: none (an infinite animation that ignores the reduced-motion setting fails WCAG 2.3.3); dot ring: pass `colors` to draw the dot without the ring (the plain warning-family fill fails WCAG 1.4.11 on white); StatusHero, InsightCard and HistoricalDataIndicator names: none (the old names were missing or duplicated, WCAG 1.1.1 and 4.1.2) | Nothing. Update light visual baselines; pass `colors` to restyle |
|
|
229
|
+
| 2.13.0 | `StatusHero` | **Before:** The status icon had no name; each breakdown icon was exposed without a name next to its label **After:** The status icon is `role="img"` named after the status; breakdown icons are `aria-hidden` **Verified by:** `StatusHero.test.tsx` | b, c | StatusIndicator differential colours: pass `positiveColor` and `negativeColor` (the old light positive `#24a148` fails WCAG 1.4.3); StatusIndicator dark on-surface colours: none (the light literals failed 1.4.3 on the dark surfaces); StatusIndicator live region: pass `live` or `live="assertive"` to keep a live region (the old static `role="status"` has no opt-out, since every indicator on a page was announced, WCAG 4.1.3); dot pulse: none (an infinite animation that ignores the reduced-motion setting fails WCAG 2.3.3); dot ring: pass `colors` to draw the dot without the ring (the plain warning-family fill fails WCAG 1.4.11 on white); StatusHero, InsightCard and HistoricalDataIndicator names: none (the old names were missing or duplicated, WCAG 1.1.1 and 4.1.2) | Query the icon with `getByRole('img', { name })` |
|
|
230
|
+
| 2.13.0 | `InsightCard` | **Before:** The icon circle was exposed without a name **After:** The icon circle is `aria-hidden`; the severity label is read **Verified by:** `InsightCard.test.tsx` | b, c | StatusIndicator differential colours: pass `positiveColor` and `negativeColor` (the old light positive `#24a148` fails WCAG 1.4.3); StatusIndicator dark on-surface colours: none (the light literals failed 1.4.3 on the dark surfaces); StatusIndicator live region: pass `live` or `live="assertive"` to keep a live region (the old static `role="status"` has no opt-out, since every indicator on a page was announced, WCAG 4.1.3); dot pulse: none (an infinite animation that ignores the reduced-motion setting fails WCAG 2.3.3); dot ring: pass `colors` to draw the dot without the ring (the plain warning-family fill fails WCAG 1.4.11 on white); StatusHero, InsightCard and HistoricalDataIndicator names: none (the old names were missing or duplicated, WCAG 1.1.1 and 4.1.2) | Nothing |
|
|
231
|
+
| 2.13.0 | `LinearProgress` (any palette colour) | **Before:** The track was a tint of the bar colour. **After:** The track is `palette.trinity.border.default` in every mode and preset. **Verified by:** `theme-overrides.test.tsx` | b, c | Progress track, reduced-motion loaders and pending buttons: none (theme defaults; an app can restore MUI's tinted track or its own motion with `createTrinityThemePreset({ components })` overrides of MuiLinearProgress, MuiCircularProgress and MuiSkeleton); pressed and selected controls: none (theme defaults; a preset can restyle `&[aria-pressed="true"]` and `&.Mui-selected` in its component overrides); dark selected background: override `darkMode.effects.overlay.selected` in the tokens to keep 0.16; Button, Badge and TextField error colours: none (theme defaults; a preset can restyle Button, Badge and OutlinedInput in its component overrides) | Nothing; to keep the old track, override `MuiLinearProgress.styleOverrides.root` in your preset. |
|
|
232
|
+
| 2.13.0 | `LinearProgress`, `CircularProgress`, `Skeleton` under reduced motion | **Before:** The loop ran once in 0.01ms and ended on an empty track or ring. **After:** A still bar at the inline start, a still arc, a still skeleton. **Verified by:** `theme-overrides.test.tsx`, `feedback-progress--track-and-motion` | b, c | Progress track, reduced-motion loaders and pending buttons: none (theme defaults; an app can restore MUI's tinted track or its own motion with `createTrinityThemePreset({ components })` overrides of MuiLinearProgress, MuiCircularProgress and MuiSkeleton); pressed and selected controls: none (theme defaults; a preset can restyle `&[aria-pressed="true"]` and `&.Mui-selected` in its component overrides); dark selected background: override `darkMode.effects.overlay.selected` in the tokens to keep 0.16; Button, Badge and TextField error colours: none (theme defaults; a preset can restyle Button, Badge and OutlinedInput in its component overrides) | Nothing. |
|
|
233
|
+
| 2.13.0 | `Button`, `IconButton` with `aria-disabled="true"` | **Before:** Drawn as enabled. **After:** Drawn as disabled, with `cursor: not-allowed`; pointer events stay. **Verified by:** `theme-overrides.test.tsx`, `actions-button--pending` | b, c | Progress track, reduced-motion loaders and pending buttons: none (theme defaults; an app can restore MUI's tinted track or its own motion with `createTrinityThemePreset({ components })` overrides of MuiLinearProgress, MuiCircularProgress and MuiSkeleton); pressed and selected controls: none (theme defaults; a preset can restyle `&[aria-pressed="true"]` and `&.Mui-selected` in its component overrides); dark selected background: override `darkMode.effects.overlay.selected` in the tokens to keep 0.16; Button, Badge and TextField error colours: none (theme defaults; a preset can restyle Button, Badge and OutlinedInput in its component overrides) | Nothing; keep ignoring clicks while pending. |
|
|
234
|
+
| 2.13.0 | `Button`, `IconButton` with `aria-pressed="true"`; selected `ToggleButton` | **Before:** The pressed state looked like the resting state (Button, IconButton) or was a faint tint (ToggleButton); an `sx` colour for the pressed state applied. **After:** The selected background, the brand text colour and an inset 2px ring; the theme's `&[aria-pressed="true"]` rule wins over a plain `sx` colour. **Verified by:** `theme-overrides.test.tsx`, `actions-button--pressed` | b, c | Progress track, reduced-motion loaders and pending buttons: none (theme defaults; an app can restore MUI's tinted track or its own motion with `createTrinityThemePreset({ components })` overrides of MuiLinearProgress, MuiCircularProgress and MuiSkeleton); pressed and selected controls: none (theme defaults; a preset can restyle `&[aria-pressed="true"]` and `&.Mui-selected` in its component overrides); dark selected background: override `darkMode.effects.overlay.selected` in the tokens to keep 0.16; Button, Badge and TextField error colours: none (theme defaults; a preset can restyle Button, Badge and OutlinedInput in its component overrides) | To restyle the pressed state, target `&[aria-pressed="true"]` (or `&.Mui-selected`) in `sx` or in the preset. |
|
|
235
|
+
| 2.13.0 | `palette.action.selected` (dark) | **Before:** `rgba(255, 255, 255, 0.16)`. **After:** `rgba(255, 255, 255, 0.10)`, the `overlay.selected` token. **Verified by:** `theme-overrides.test.tsx` | b, c | Progress track, reduced-motion loaders and pending buttons: none (theme defaults; an app can restore MUI's tinted track or its own motion with `createTrinityThemePreset({ components })` overrides of MuiLinearProgress, MuiCircularProgress and MuiSkeleton); pressed and selected controls: none (theme defaults; a preset can restyle `&[aria-pressed="true"]` and `&.Mui-selected` in its component overrides); dark selected background: override `darkMode.effects.overlay.selected` in the tokens to keep 0.16; Button, Badge and TextField error colours: none (theme defaults; a preset can restyle Button, Badge and OutlinedInput in its component overrides) | Nothing; to keep 0.16, override `darkMode.effects.overlay.selected` in the tokens. |
|
|
236
|
+
| 2.13.0 | `Button` contained `primary`/`secondary` | **Before:** Dark primary white on coral (2.69:1); light secondary white on coral (2.97:1). **After:** The button tokens in both modes: indigo fills with white labels, coral with navy on hover. **Verified by:** `status-contrast.test.tsx` | b, c | Progress track, reduced-motion loaders and pending buttons: none (theme defaults; an app can restore MUI's tinted track or its own motion with `createTrinityThemePreset({ components })` overrides of MuiLinearProgress, MuiCircularProgress and MuiSkeleton); pressed and selected controls: none (theme defaults; a preset can restyle `&[aria-pressed="true"]` and `&.Mui-selected` in its component overrides); dark selected background: override `darkMode.effects.overlay.selected` in the tokens to keep 0.16; Button, Badge and TextField error colours: none (theme defaults; a preset can restyle Button, Badge and OutlinedInput in its component overrides) | Nothing; to keep the old colours, override the contained rules in your preset. |
|
|
237
|
+
| 2.13.0 | `Button` contained `inherit` | **Before:** White label on the light grey fill (1.3:1). **After:** The theme's primary text colour. **Verified by:** `status-contrast.test.tsx` | b, c | Progress track, reduced-motion loaders and pending buttons: none (theme defaults; an app can restore MUI's tinted track or its own motion with `createTrinityThemePreset({ components })` overrides of MuiLinearProgress, MuiCircularProgress and MuiSkeleton); pressed and selected controls: none (theme defaults; a preset can restyle `&[aria-pressed="true"]` and `&.Mui-selected` in its component overrides); dark selected background: override `darkMode.effects.overlay.selected` in the tokens to keep 0.16; Button, Badge and TextField error colours: none (theme defaults; a preset can restyle Button, Badge and OutlinedInput in its component overrides) | Nothing. |
|
|
238
|
+
| 2.13.0 | `Button` outlined/text in a status colour | **Before:** Painted like primary (navy or purple). **After:** Painted in the status colour. **Verified by:** `status-contrast.test.tsx` | b, c | Progress track, reduced-motion loaders and pending buttons: none (theme defaults; an app can restore MUI's tinted track or its own motion with `createTrinityThemePreset({ components })` overrides of MuiLinearProgress, MuiCircularProgress and MuiSkeleton); pressed and selected controls: none (theme defaults; a preset can restyle `&[aria-pressed="true"]` and `&.Mui-selected` in its component overrides); dark selected background: override `darkMode.effects.overlay.selected` in the tokens to keep 0.16; Button, Badge and TextField error colours: none (theme defaults; a preset can restyle Button, Badge and OutlinedInput in its component overrides) | If you relied on the primary look, use `color="primary"`. |
|
|
239
|
+
| 2.13.0 | `Badge` `error`/`success` (and dark `secondary`) | **Before:** Fixed fills below 4.5:1 for the label in some modes. **After:** The palette pair of the colour. **Verified by:** `status-contrast.test.tsx` | b, c | Progress track, reduced-motion loaders and pending buttons: none (theme defaults; an app can restore MUI's tinted track or its own motion with `createTrinityThemePreset({ components })` overrides of MuiLinearProgress, MuiCircularProgress and MuiSkeleton); pressed and selected controls: none (theme defaults; a preset can restyle `&[aria-pressed="true"]` and `&.Mui-selected` in its component overrides); dark selected background: override `darkMode.effects.overlay.selected` in the tokens to keep 0.16; Button, Badge and TextField error colours: none (theme defaults; a preset can restyle Button, Badge and OutlinedInput in its component overrides) | Nothing. |
|
|
240
|
+
| 2.13.0 | `TextField` with `error` | **Before:** Dark outline `#FCA5A5`; hover and focus could replace it. **After:** `border.error` (`#F87171` dark) in every state. **Verified by:** `status-contrast.test.tsx` | b, c | Progress track, reduced-motion loaders and pending buttons: none (theme defaults; an app can restore MUI's tinted track or its own motion with `createTrinityThemePreset({ components })` overrides of MuiLinearProgress, MuiCircularProgress and MuiSkeleton); pressed and selected controls: none (theme defaults; a preset can restyle `&[aria-pressed="true"]` and `&.Mui-selected` in its component overrides); dark selected background: override `darkMode.effects.overlay.selected` in the tokens to keep 0.16; Button, Badge and TextField error colours: none (theme defaults; a preset can restyle Button, Badge and OutlinedInput in its component overrides) | Nothing. |
|
|
241
|
+
| 2.13.0 | `Popper` (click mode) | **Before:** Opening left focus on the trigger, so the dialog's controls were unreachable by keyboard; one Escape closed every open Popper; the surface sat inside an element with `role="tooltip"`. **After:** Focus moves into the surface on open (first control, or the surface) and returns to the trigger on close; Escape closes only the Popper that holds focus; the surface is the only element with an overlay role. **Verified by:** `Popper.test.tsx` | a, b | Popper click mode and naming: none (a dialog keyboard users cannot enter, and an Escape that closes every Popper at once, are never correct); Popper hover mode: `interactionKind="hover"` keeps focus on the trigger; Modal `fullScreenMobile`: `fullScreenMobile={false}` (the default; the prop is opt-in); Modal naming: `describedBy="content"` (the default) keeps the old description, and the naming changes have none (an unnamed dialog and an empty heading are never correct); Modal under non-Trinity themes: none (a component that throws under a plain MUI theme has no correct old behavior to restore); Modal icons: none (Modal's icons follow the app's icon library like every other Trinity icon; the default library, Material, looks the same); Modal close button: none (a dialog name with "Close dialog" appended is never correct; `showCloseButton={false}` still removes the button); Modal reduced motion: none (a fade that ignores the user's reduced-motion setting is never correct); Modal pending action: `pendingClosePolicy="allow"` keeps every close path working while the action is pending | Tests that expect focus on the trigger while open: expect it inside `getByRole('dialog', { name })`; tests that queried the outer `tooltip` role: query the surface. |
|
|
242
|
+
| 2.13.0 | `Popper` (hover mode) | **Before:** Escape closed every open Popper; interactive content was allowed silently. **After:** Escape closes only the topmost hover Popper; tabbable content logs a development warning. **Verified by:** `Popper.test.tsx` | a, b | Popper click mode and naming: none (a dialog keyboard users cannot enter, and an Escape that closes every Popper at once, are never correct); Popper hover mode: `interactionKind="hover"` keeps focus on the trigger; Modal `fullScreenMobile`: `fullScreenMobile={false}` (the default; the prop is opt-in); Modal naming: `describedBy="content"` (the default) keeps the old description, and the naming changes have none (an unnamed dialog and an empty heading are never correct); Modal under non-Trinity themes: none (a component that throws under a plain MUI theme has no correct old behavior to restore); Modal icons: none (Modal's icons follow the app's icon library like every other Trinity icon; the default library, Material, looks the same); Modal close button: none (a dialog name with "Close dialog" appended is never correct; `showCloseButton={false}` still removes the button); Modal reduced motion: none (a fade that ignores the user's reduced-motion setting is never correct); Modal pending action: `pendingClosePolicy="allow"` keeps every close path working while the action is pending | Move interactive content to `interactionKind="click"`. |
|
|
243
|
+
| 2.13.0 | `Modal` with `fullScreenMobile` | **Before:** The prop had no effect: the dialog kept its `maxWidth` and radius on every viewport. **After:** Below the theme's `sm` breakpoint the dialog is full screen with square corners; at `sm` and wider nothing changes. **Verified by:** `Modal.test.tsx` | a, b | Popper click mode and naming: none (a dialog keyboard users cannot enter, and an Escape that closes every Popper at once, are never correct); Popper hover mode: `interactionKind="hover"` keeps focus on the trigger; Modal `fullScreenMobile`: `fullScreenMobile={false}` (the default; the prop is opt-in); Modal naming: `describedBy="content"` (the default) keeps the old description, and the naming changes have none (an unnamed dialog and an empty heading are never correct); Modal under non-Trinity themes: none (a component that throws under a plain MUI theme has no correct old behavior to restore); Modal icons: none (Modal's icons follow the app's icon library like every other Trinity icon; the default library, Material, looks the same); Modal close button: none (a dialog name with "Close dialog" appended is never correct; `showCloseButton={false}` still removes the button); Modal reduced motion: none (a fade that ignores the user's reduced-motion setting is never correct); Modal pending action: `pendingClosePolicy="allow"` keeps every close path working while the action is pending | Remove `fullScreenMobile` to keep the old look on phones. |
|
|
244
|
+
| 2.13.0 | `Modal` without `title` | **Before:** An empty `h2` held only the close button, and the dialog had no accessible name (`aria-label` was not accepted). **After:** No heading; `aria-label` or `aria-labelledby` names the dialog surface; a development warning fires when the dialog has no name. **Verified by:** `Modal.test.tsx` | a, b | Popper click mode and naming: none (a dialog keyboard users cannot enter, and an Escape that closes every Popper at once, are never correct); Popper hover mode: `interactionKind="hover"` keeps focus on the trigger; Modal `fullScreenMobile`: `fullScreenMobile={false}` (the default; the prop is opt-in); Modal naming: `describedBy="content"` (the default) keeps the old description, and the naming changes have none (an unnamed dialog and an empty heading are never correct); Modal under non-Trinity themes: none (a component that throws under a plain MUI theme has no correct old behavior to restore); Modal icons: none (Modal's icons follow the app's icon library like every other Trinity icon; the default library, Material, looks the same); Modal close button: none (a dialog name with "Close dialog" appended is never correct; `showCloseButton={false}` still removes the button); Modal reduced motion: none (a fade that ignores the user's reduced-motion setting is never correct); Modal pending action: `pendingClosePolicy="allow"` keeps every close path working while the action is pending | Pass `aria-label` (or `aria-labelledby`) to title-less dialogs; stop querying the empty heading. |
|
|
245
|
+
| 2.13.0 | `ConfirmDialog` with a non-string `message` | **Before:** The message rendered inside a `p`, so block content was invalid HTML. **After:** A string message is a `p`, any other node a `div`; the color is unchanged. **Verified by:** `Modal.test.tsx` | a, b | Popper click mode and naming: none (a dialog keyboard users cannot enter, and an Escape that closes every Popper at once, are never correct); Popper hover mode: `interactionKind="hover"` keeps focus on the trigger; Modal `fullScreenMobile`: `fullScreenMobile={false}` (the default; the prop is opt-in); Modal naming: `describedBy="content"` (the default) keeps the old description, and the naming changes have none (an unnamed dialog and an empty heading are never correct); Modal under non-Trinity themes: none (a component that throws under a plain MUI theme has no correct old behavior to restore); Modal icons: none (Modal's icons follow the app's icon library like every other Trinity icon; the default library, Material, looks the same); Modal close button: none (a dialog name with "Close dialog" appended is never correct; `showCloseButton={false}` still removes the button); Modal reduced motion: none (a fade that ignores the user's reduced-motion setting is never correct); Modal pending action: `pendingClosePolicy="allow"` keeps every close path working while the action is pending | Nothing. |
|
|
246
|
+
| 2.13.0 | `Modal`, `ConfirmDialog` | **Before:** Rendering under a theme without `palette.trinity` (a plain `createTheme()`) threw a TypeError. **After:** They render; the variant icon colors come from `getTrinityPalette(theme)`, the same values under Trinity themes and the resolver's fallback elsewhere. **Verified by:** `Modal.test.tsx`, `non-trinity-theme.test.tsx` | a, b | Popper click mode and naming: none (a dialog keyboard users cannot enter, and an Escape that closes every Popper at once, are never correct); Popper hover mode: `interactionKind="hover"` keeps focus on the trigger; Modal `fullScreenMobile`: `fullScreenMobile={false}` (the default; the prop is opt-in); Modal naming: `describedBy="content"` (the default) keeps the old description, and the naming changes have none (an unnamed dialog and an empty heading are never correct); Modal under non-Trinity themes: none (a component that throws under a plain MUI theme has no correct old behavior to restore); Modal icons: none (Modal's icons follow the app's icon library like every other Trinity icon; the default library, Material, looks the same); Modal close button: none (a dialog name with "Close dialog" appended is never correct; `showCloseButton={false}` still removes the button); Modal reduced motion: none (a fade that ignores the user's reduced-motion setting is never correct); Modal pending action: `pendingClosePolicy="allow"` keeps every close path working while the action is pending | Nothing. |
|
|
247
|
+
| 2.13.0 | `Modal` (`variant="confirm"`), `ConfirmDialog` | **Before:** The confirm icon was MUI's `HelpOutlined` (`data-testid="HelpOutlinedIcon"`); under `IconProvider defaultLibrary="feather"` the icons stayed Material. **After:** The same glyph from MUI's `Help` (`data-testid="HelpIcon"`); under Feather, Feather glyphs. **Verified by:** `Modal.test.tsx` | a, b | Popper click mode and naming: none (a dialog keyboard users cannot enter, and an Escape that closes every Popper at once, are never correct); Popper hover mode: `interactionKind="hover"` keeps focus on the trigger; Modal `fullScreenMobile`: `fullScreenMobile={false}` (the default; the prop is opt-in); Modal naming: `describedBy="content"` (the default) keeps the old description, and the naming changes have none (an unnamed dialog and an empty heading are never correct); Modal under non-Trinity themes: none (a component that throws under a plain MUI theme has no correct old behavior to restore); Modal icons: none (Modal's icons follow the app's icon library like every other Trinity icon; the default library, Material, looks the same); Modal close button: none (a dialog name with "Close dialog" appended is never correct; `showCloseButton={false}` still removes the button); Modal reduced motion: none (a fade that ignores the user's reduced-motion setting is never correct); Modal pending action: `pendingClosePolicy="allow"` keeps every close path working while the action is pending | Tests that query `getByTestId('HelpOutlinedIcon')`: query `HelpIcon`. |
|
|
248
|
+
| 2.13.0 | `Modal`, `ConfirmDialog` close button | **Before:** The dialog's accessible name was the title followed by "Close dialog"; the close button sat inside the title's `h2`; in RTL it stayed on the right. **After:** The name is the title alone; the close button follows the title in the surface, has a "Close dialog" tooltip, and sits at the inline end. **Verified by:** `Modal.test.tsx` | a, b | Popper click mode and naming: none (a dialog keyboard users cannot enter, and an Escape that closes every Popper at once, are never correct); Popper hover mode: `interactionKind="hover"` keeps focus on the trigger; Modal `fullScreenMobile`: `fullScreenMobile={false}` (the default; the prop is opt-in); Modal naming: `describedBy="content"` (the default) keeps the old description, and the naming changes have none (an unnamed dialog and an empty heading are never correct); Modal under non-Trinity themes: none (a component that throws under a plain MUI theme has no correct old behavior to restore); Modal icons: none (Modal's icons follow the app's icon library like every other Trinity icon; the default library, Material, looks the same); Modal close button: none (a dialog name with "Close dialog" appended is never correct; `showCloseButton={false}` still removes the button); Modal reduced motion: none (a fade that ignores the user's reduced-motion setting is never correct); Modal pending action: `pendingClosePolicy="allow"` keeps every close path working while the action is pending | Tests that query `getByRole('dialog', { name: /Close dialog/ })` or the button inside the heading: query `getByRole('dialog', { name: title })` and `getByRole('button', { name: 'Close dialog' })`. |
|
|
249
|
+
| 2.13.0 | `Modal`, `ConfirmDialog` under reduced motion | **Before:** Under reduced motion the dialog still faded in and out (225ms/195ms) and stayed in the DOM for 195ms after closing. **After:** Under reduced motion it appears and disappears at once; without the preference nothing changes. **Verified by:** `Modal.test.tsx` | a, b | Popper click mode and naming: none (a dialog keyboard users cannot enter, and an Escape that closes every Popper at once, are never correct); Popper hover mode: `interactionKind="hover"` keeps focus on the trigger; Modal `fullScreenMobile`: `fullScreenMobile={false}` (the default; the prop is opt-in); Modal naming: `describedBy="content"` (the default) keeps the old description, and the naming changes have none (an unnamed dialog and an empty heading are never correct); Modal under non-Trinity themes: none (a component that throws under a plain MUI theme has no correct old behavior to restore); Modal icons: none (Modal's icons follow the app's icon library like every other Trinity icon; the default library, Material, looks the same); Modal close button: none (a dialog name with "Close dialog" appended is never correct; `showCloseButton={false}` still removes the button); Modal reduced motion: none (a fade that ignores the user's reduced-motion setting is never correct); Modal pending action: `pendingClosePolicy="allow"` keeps every close path working while the action is pending | Tests that emulate reduced motion and wait for the close animation can stop waiting. |
|
|
250
|
+
| 2.13.0 | `Modal` while `onPrimaryAction`'s promise (or `loading`) is pending | **Before:** The actions were `disabled` (focus dropped to `<body>`); backdrop clicks and Escape still closed the dialog. **After:** The actions are `aria-disabled` and ignore clicks; no close path closes the dialog. **Verified by:** `Modal.test.tsx` | a, b | Popper click mode and naming: none (a dialog keyboard users cannot enter, and an Escape that closes every Popper at once, are never correct); Popper hover mode: `interactionKind="hover"` keeps focus on the trigger; Modal `fullScreenMobile`: `fullScreenMobile={false}` (the default; the prop is opt-in); Modal naming: `describedBy="content"` (the default) keeps the old description, and the naming changes have none (an unnamed dialog and an empty heading are never correct); Modal under non-Trinity themes: none (a component that throws under a plain MUI theme has no correct old behavior to restore); Modal icons: none (Modal's icons follow the app's icon library like every other Trinity icon; the default library, Material, looks the same); Modal close button: none (a dialog name with "Close dialog" appended is never correct; `showCloseButton={false}` still removes the button); Modal reduced motion: none (a fade that ignores the user's reduced-motion setting is never correct); Modal pending action: `pendingClosePolicy="allow"` keeps every close path working while the action is pending | Pass `pendingClosePolicy="allow"` to keep closing while pending; tests that assert `toBeDisabled()` on pending actions assert `aria-disabled="true"`. |
|
|
251
|
+
| 2.13.0 | `Modal` whose `onPrimaryAction` throws or rejects | **Before:** The error escaped as an unhandled rejection and nothing was shown. **After:** An inline error with "Try again" is shown and announced; the error goes to `onActionError` or `reportError`. **Verified by:** `Modal.test.tsx` | a, b | Popper click mode and naming: none (a dialog keyboard users cannot enter, and an Escape that closes every Popper at once, are never correct); Popper hover mode: `interactionKind="hover"` keeps focus on the trigger; Modal `fullScreenMobile`: `fullScreenMobile={false}` (the default; the prop is opt-in); Modal naming: `describedBy="content"` (the default) keeps the old description, and the naming changes have none (an unnamed dialog and an empty heading are never correct); Modal under non-Trinity themes: none (a component that throws under a plain MUI theme has no correct old behavior to restore); Modal icons: none (Modal's icons follow the app's icon library like every other Trinity icon; the default library, Material, looks the same); Modal close button: none (a dialog name with "Close dialog" appended is never correct; `showCloseButton={false}` still removes the button); Modal reduced motion: none (a fade that ignores the user's reduced-motion setting is never correct); Modal pending action: `pendingClosePolicy="allow"` keeps every close path working while the action is pending | Handle the error in `onActionError`; set `errorMessage` for your own text. |
|
|
252
|
+
| 2.13.0 | `Modal` with a thenable that is not a `Promise` | **Before:** No pending state (`instanceof Promise` missed it). **After:** The pending state shows until it settles. **Verified by:** `Modal.test.tsx` | a, b | Popper click mode and naming: none (a dialog keyboard users cannot enter, and an Escape that closes every Popper at once, are never correct); Popper hover mode: `interactionKind="hover"` keeps focus on the trigger; Modal `fullScreenMobile`: `fullScreenMobile={false}` (the default; the prop is opt-in); Modal naming: `describedBy="content"` (the default) keeps the old description, and the naming changes have none (an unnamed dialog and an empty heading are never correct); Modal under non-Trinity themes: none (a component that throws under a plain MUI theme has no correct old behavior to restore); Modal icons: none (Modal's icons follow the app's icon library like every other Trinity icon; the default library, Material, looks the same); Modal close button: none (a dialog name with "Close dialog" appended is never correct; `showCloseButton={false}` still removes the button); Modal reduced motion: none (a fade that ignores the user's reduced-motion setting is never correct); Modal pending action: `pendingClosePolicy="allow"` keeps every close path working while the action is pending | Nothing. |
|
|
253
|
+
| 2.13.0 | `UserAvatar` (dark theme) | **Before:** A light grey disc (`#E0E0E0`) under a light icon, 1.1:1 **After:** The theme's neutral fill (`#374151` in the Trinity dark theme), 9.37:1 **Verified by:** `UserAvatar.test.tsx` | b, c | UserAvatar dark disc: none (the old light disc under a light icon measured 1.1:1, WCAG 1.4.11); UserAvatar exposure: pass `aria-hidden={false}` with your own name, or `aria-label`, to expose the avatar; IllustratedMessage colours: pass `primaryColor`, `secondaryColor` and `accentColor` to a standalone illustration, or a `customIllustration` to IllustratedMessage (the light knock-out fills in dark mode have no opt-out); SearchInput: none (a textbox that owns a listbox without the combobox role fails WCAG 4.1.2; no supported old state to keep); Tour: `mask={false}` gives a non-modal tour (the page stays usable), `keyboard={false}` turns off Escape and the arrow keys | Nothing. Update dark visual baselines |
|
|
254
|
+
| 2.13.0 | `UserAvatar` root | **Before:** A plain `div` exposed to assistive technology with no role or name; no `className` or rest props **After:** `aria-hidden="true"` by default; `aria-label` or `aria-labelledby` make it `role="img"` **Verified by:** `UserAvatar.test.tsx` | b, c | UserAvatar dark disc: none (the old light disc under a light icon measured 1.1:1, WCAG 1.4.11); UserAvatar exposure: pass `aria-hidden={false}` with your own name, or `aria-label`, to expose the avatar; IllustratedMessage colours: pass `primaryColor`, `secondaryColor` and `accentColor` to a standalone illustration, or a `customIllustration` to IllustratedMessage (the light knock-out fills in dark mode have no opt-out); SearchInput: none (a textbox that owns a listbox without the combobox role fails WCAG 4.1.2; no supported old state to keep); Tour: `mask={false}` gives a non-modal tour (the page stays usable), `keyboard={false}` turns off Escape and the arrow keys | Nothing for decorative use. Pass `aria-label` where the avatar is the only place the person is named |
|
|
255
|
+
| 2.13.0 | `IllustratedMessage`, `UploadDropZone`, the standalone illustrations (dark theme, presets, plain MUI themes) | **Before:** Trinity navy, gray 100, coral and white whatever the theme **After:** The theme's primary, subtle border, secondary and paper; status tints from the dark status backgrounds on a dark paper **Verified by:** `IllustratedMessage.test.tsx`, `non-trinity-theme.test.tsx` | b, c | UserAvatar dark disc: none (the old light disc under a light icon measured 1.1:1, WCAG 1.4.11); UserAvatar exposure: pass `aria-hidden={false}` with your own name, or `aria-label`, to expose the avatar; IllustratedMessage colours: pass `primaryColor`, `secondaryColor` and `accentColor` to a standalone illustration, or a `customIllustration` to IllustratedMessage (the light knock-out fills in dark mode have no opt-out); SearchInput: none (a textbox that owns a listbox without the combobox role fails WCAG 4.1.2; no supported old state to keep); Tour: `mask={false}` gives a non-modal tour (the page stays usable), `keyboard={false}` turns off Escape and the arrow keys | Nothing. Update dark and preset visual baselines; pass explicit colours to keep fixed ones |
|
|
256
|
+
| 2.13.0 | `SearchInput` queries | **Before:** The input was a textbox; "Clear all" was clickable text. **After:** The input is a combobox; "Clear recent searches" is an option. **Verified by:** `SearchInput.test.tsx` | b, c | UserAvatar dark disc: none (the old light disc under a light icon measured 1.1:1, WCAG 1.4.11); UserAvatar exposure: pass `aria-hidden={false}` with your own name, or `aria-label`, to expose the avatar; IllustratedMessage colours: pass `primaryColor`, `secondaryColor` and `accentColor` to a standalone illustration, or a `customIllustration` to IllustratedMessage (the light knock-out fills in dark mode have no opt-out); SearchInput: none (a textbox that owns a listbox without the combobox role fails WCAG 4.1.2; no supported old state to keep); Tour: `mask={false}` gives a non-modal tour (the page stays usable), `keyboard={false}` turns off Escape and the arrow keys | Query `getByRole('combobox')` and `getByRole('option', { name: 'Clear recent searches' })`. |
|
|
257
|
+
| 2.13.0 | `SearchInput` with a controlled `value` | **Before:** `onChange` was debounced, so keystrokes were lost. **After:** `onChange` runs on every change. **Verified by:** `SearchInput.test.tsx` | b, c | UserAvatar dark disc: none (the old light disc under a light icon measured 1.1:1, WCAG 1.4.11); UserAvatar exposure: pass `aria-hidden={false}` with your own name, or `aria-label`, to expose the avatar; IllustratedMessage colours: pass `primaryColor`, `secondaryColor` and `accentColor` to a standalone illustration, or a `customIllustration` to IllustratedMessage (the light knock-out fills in dark mode have no opt-out); SearchInput: none (a textbox that owns a listbox without the combobox role fails WCAG 4.1.2; no supported old state to keep); Tour: `mask={false}` gives a non-modal tour (the page stays usable), `keyboard={false}` turns off Escape and the arrow keys | Debounce your own search request if it is expensive. |
|
|
258
|
+
| 2.13.0 | `Tour` (masked, the default) | **Before:** The page behind stayed interactive and in the accessibility tree; focus stayed on the trigger. **After:** The page behind is inert while the tour is open; focus moves into the step and returns on close. **Verified by:** `Tour.test.tsx` | b, c | UserAvatar dark disc: none (the old light disc under a light icon measured 1.1:1, WCAG 1.4.11); UserAvatar exposure: pass `aria-hidden={false}` with your own name, or `aria-label`, to expose the avatar; IllustratedMessage colours: pass `primaryColor`, `secondaryColor` and `accentColor` to a standalone illustration, or a `customIllustration` to IllustratedMessage (the light knock-out fills in dark mode have no opt-out); SearchInput: none (a textbox that owns a listbox without the combobox role fails WCAG 4.1.2; no supported old state to keep); Tour: `mask={false}` gives a non-modal tour (the page stays usable), `keyboard={false}` turns off Escape and the arrow keys | Use `mask={false}` for a tour the user works alongside. |
|
|
259
|
+
| 2.13.0 | `Tour` queries | **Before:** `getByRole('dialog')` was the full-screen layer. **After:** The dialog is the step card; the layer carries the z-index. **Verified by:** `Tour.test.tsx`, `zindex-roles.test.tsx` | b, c | UserAvatar dark disc: none (the old light disc under a light icon measured 1.1:1, WCAG 1.4.11); UserAvatar exposure: pass `aria-hidden={false}` with your own name, or `aria-label`, to expose the avatar; IllustratedMessage colours: pass `primaryColor`, `secondaryColor` and `accentColor` to a standalone illustration, or a `customIllustration` to IllustratedMessage (the light knock-out fills in dark mode have no opt-out); SearchInput: none (a textbox that owns a listbox without the combobox role fails WCAG 4.1.2; no supported old state to keep); Tour: `mask={false}` gives a non-modal tour (the page stays usable), `keyboard={false}` turns off Escape and the arrow keys | Read the z-index from the dialog's parent. |
|
|
260
|
+
| 2.13.0 | `Tour` keys | **Before:** Escape and the arrow keys worked anywhere on the page (a window listener). **After:** They work while focus is in the step. **Verified by:** `Tour.test.tsx` | b, c | UserAvatar dark disc: none (the old light disc under a light icon measured 1.1:1, WCAG 1.4.11); UserAvatar exposure: pass `aria-hidden={false}` with your own name, or `aria-label`, to expose the avatar; IllustratedMessage colours: pass `primaryColor`, `secondaryColor` and `accentColor` to a standalone illustration, or a `customIllustration` to IllustratedMessage (the light knock-out fills in dark mode have no opt-out); SearchInput: none (a textbox that owns a listbox without the combobox role fails WCAG 4.1.2; no supported old state to keep); Tour: `mask={false}` gives a non-modal tour (the page stays usable), `keyboard={false}` turns off Escape and the arrow keys | Nothing for a masked tour (focus stays in it). |
|
|
261
|
+
| 2.13.0 | `AuditDrawer` (dark theme) | **Before:** A white paper with light-mode text **After:** The theme's dark surfaces and text roles **Verified by:** `AuditDrawer.test.tsx` | b, c | AuditDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); EntityDetailPanel colours: none (the old values were static light colours; four status pill pairs failed WCAG 1.4.3 in light); AssessmentDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); AuditDrawer filter chips and field changes: none (filter state and field changes were conveyed by colour and strike-through only, which WCAG 1.3.1 and 4.1.2 require as text); EntityDetailPanel field help: none (help text was reachable only by hover, which WCAG 2.1.1 requires from the keyboard); AssessmentDrawer sections and footer: none (section status was colour only, WCAG 1.3.1 and 1.4.1; footer actions dropped focus while pending); AssessmentDrawer progress bar: none (an unnamed progress bar fails WCAG 4.1.2) | Nothing. Update dark visual baselines |
|
|
262
|
+
| 2.13.0 | `AuditDrawer` (light theme) secondary texts, note and category chip | **Before:** #6B7280 and #9CA3AF text; #FAFAFA note and category background **After:** `text.secondary` #374151; `surface.sunken` #F4F4F5 **Verified by:** `AuditDrawer.test.tsx` | b, c | AuditDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); EntityDetailPanel colours: none (the old values were static light colours; four status pill pairs failed WCAG 1.4.3 in light); AssessmentDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); AuditDrawer filter chips and field changes: none (filter state and field changes were conveyed by colour and strike-through only, which WCAG 1.3.1 and 4.1.2 require as text); EntityDetailPanel field help: none (help text was reachable only by hover, which WCAG 2.1.1 requires from the keyboard); AssessmentDrawer sections and footer: none (section status was colour only, WCAG 1.3.1 and 1.4.1; footer actions dropped focus while pending); AssessmentDrawer progress bar: none (an unnamed progress bar fails WCAG 4.1.2) | Nothing. Update light visual baselines |
|
|
263
|
+
| 2.13.0 | `AuditDrawer` timeline rail | **Before:** A 100%-wide grey block over the event content **After:** A 1px line beside the dots **Verified by:** `AuditDrawer.test.tsx` | b, c | AuditDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); EntityDetailPanel colours: none (the old values were static light colours; four status pill pairs failed WCAG 1.4.3 in light); AssessmentDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); AuditDrawer filter chips and field changes: none (filter state and field changes were conveyed by colour and strike-through only, which WCAG 1.3.1 and 4.1.2 require as text); EntityDetailPanel field help: none (help text was reachable only by hover, which WCAG 2.1.1 requires from the keyboard); AssessmentDrawer sections and footer: none (section status was colour only, WCAG 1.3.1 and 1.4.1; footer actions dropped focus while pending); AssessmentDrawer progress bar: none (an unnamed progress bar fails WCAG 4.1.2) | Nothing |
|
|
264
|
+
| 2.13.0 | `AuditDrawer` filter chips | **Before:** Chips were buttons (role="button") with no checked state; an active chip was filled with its severity colour. **After:** A fieldset named "Filter events" of checkboxes; checked chips show a check icon and bold text. **Verified by:** `AuditDrawer.test.tsx` "D-15: an active filter chip is a checked checkbox named by its label" | b, c | AuditDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); EntityDetailPanel colours: none (the old values were static light colours; four status pill pairs failed WCAG 1.4.3 in light); AssessmentDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); AuditDrawer filter chips and field changes: none (filter state and field changes were conveyed by colour and strike-through only, which WCAG 1.3.1 and 4.1.2 require as text); EntityDetailPanel field help: none (help text was reachable only by hover, which WCAG 2.1.1 requires from the keyboard); AssessmentDrawer sections and footer: none (section status was colour only, WCAG 1.3.1 and 1.4.1; footer actions dropped focus while pending); AssessmentDrawer progress bar: none (an unnamed progress bar fails WCAG 4.1.2) | Nothing. Tests that queried `getByRole('button', { name: <chip> })` query `getByRole('checkbox', { name: <chip> })`. Pass `labels.filterChips` to rename the group. |
|
|
265
|
+
| 2.13.0 | `AuditDrawer` field changes | **Before:** "Field before → after" as three spans. **After:** A `<dl>`: `<dt>` field, `<dd>` with `<del>` and `<ins>` carrying hidden "Previous value" / "New value". **Verified by:** `AuditDrawer.test.tsx` "D-17: each change is a definition naming its previous and new value" | b, c | AuditDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); EntityDetailPanel colours: none (the old values were static light colours; four status pill pairs failed WCAG 1.4.3 in light); AssessmentDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); AuditDrawer filter chips and field changes: none (filter state and field changes were conveyed by colour and strike-through only, which WCAG 1.3.1 and 4.1.2 require as text); EntityDetailPanel field help: none (help text was reachable only by hover, which WCAG 2.1.1 requires from the keyboard); AssessmentDrawer sections and footer: none (section status was colour only, WCAG 1.3.1 and 1.4.1; footer actions dropped focus while pending); AssessmentDrawer progress bar: none (an unnamed progress bar fails WCAG 4.1.2) | Nothing. Pass `labels.previousValue` / `labels.newValue` to translate. |
|
|
266
|
+
| 2.13.0 | `EntityDetailPanel` (dark theme) | **Before:** A white panel with light-mode text and status washes **After:** The theme's dark surfaces, text roles and status pairs **Verified by:** `EntityDetailPanel.test.tsx` | b, c | AuditDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); EntityDetailPanel colours: none (the old values were static light colours; four status pill pairs failed WCAG 1.4.3 in light); AssessmentDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); AuditDrawer filter chips and field changes: none (filter state and field changes were conveyed by colour and strike-through only, which WCAG 1.3.1 and 4.1.2 require as text); EntityDetailPanel field help: none (help text was reachable only by hover, which WCAG 2.1.1 requires from the keyboard); AssessmentDrawer sections and footer: none (section status was colour only, WCAG 1.3.1 and 1.4.1; footer actions dropped focus while pending); AssessmentDrawer progress bar: none (an unnamed progress bar fails WCAG 4.1.2) | Nothing. Update dark visual baselines |
|
|
267
|
+
| 2.13.0 | `EntityDetailPanel` status pill (light) | **Before:** `rgba(…, 0.12)` washes behind the status text **After:** The theme's `status.<variant>.background` (#F0FDF4, #FFFBEB, #FEF2F2, #E5F5FC) **Verified by:** `EntityDetailPanel.test.tsx` | b, c | AuditDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); EntityDetailPanel colours: none (the old values were static light colours; four status pill pairs failed WCAG 1.4.3 in light); AssessmentDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); AuditDrawer filter chips and field changes: none (filter state and field changes were conveyed by colour and strike-through only, which WCAG 1.3.1 and 4.1.2 require as text); EntityDetailPanel field help: none (help text was reachable only by hover, which WCAG 2.1.1 requires from the keyboard); AssessmentDrawer sections and footer: none (section status was colour only, WCAG 1.3.1 and 1.4.1; footer actions dropped focus while pending); AssessmentDrawer progress bar: none (an unnamed progress bar fails WCAG 4.1.2) | Nothing. Update light visual baselines |
|
|
268
|
+
| 2.13.0 | `EntityDetailPanel` secondary texts, help icon, empty-tab message (light) | **Before:** #6B7280 and #9CA3AF **After:** `text.secondary` #374151 **Verified by:** `EntityDetailPanel.test.tsx` | b, c | AuditDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); EntityDetailPanel colours: none (the old values were static light colours; four status pill pairs failed WCAG 1.4.3 in light); AssessmentDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); AuditDrawer filter chips and field changes: none (filter state and field changes were conveyed by colour and strike-through only, which WCAG 1.3.1 and 4.1.2 require as text); EntityDetailPanel field help: none (help text was reachable only by hover, which WCAG 2.1.1 requires from the keyboard); AssessmentDrawer sections and footer: none (section status was colour only, WCAG 1.3.1 and 1.4.1; footer actions dropped focus while pending); AssessmentDrawer progress bar: none (an unnamed progress bar fails WCAG 4.1.2) | Nothing |
|
|
269
|
+
| 2.13.0 | `EntityDetailPanel` field help | **Before:** A span with an aria-label, shown on hover only. **After:** A button named "More information <label>", in the Tab order, described by the tooltip. **Verified by:** `EntityDetailPanel.test.tsx` "D-18: Tab reaches a help button named after its field and described by the tooltip" | b, c | AuditDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); EntityDetailPanel colours: none (the old values were static light colours; four status pill pairs failed WCAG 1.4.3 in light); AssessmentDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); AuditDrawer filter chips and field changes: none (filter state and field changes were conveyed by colour and strike-through only, which WCAG 1.3.1 and 4.1.2 require as text); EntityDetailPanel field help: none (help text was reachable only by hover, which WCAG 2.1.1 requires from the keyboard); AssessmentDrawer sections and footer: none (section status was colour only, WCAG 1.3.1 and 1.4.1; footer actions dropped focus while pending); AssessmentDrawer progress bar: none (an unnamed progress bar fails WCAG 4.1.2) | Nothing. Tests that used `getByLabelText(<helperText>)` query `getByRole('button', { name: 'More information <label>' })`. |
|
|
270
|
+
| 2.13.0 | `AssessmentDrawer` (dark theme) | **Before:** A white drawer and sidebar with light-mode text **After:** The theme's dark surfaces, text and status roles **Verified by:** `AssessmentDrawer.test.tsx` | b, c | AuditDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); EntityDetailPanel colours: none (the old values were static light colours; four status pill pairs failed WCAG 1.4.3 in light); AssessmentDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); AuditDrawer filter chips and field changes: none (filter state and field changes were conveyed by colour and strike-through only, which WCAG 1.3.1 and 4.1.2 require as text); EntityDetailPanel field help: none (help text was reachable only by hover, which WCAG 2.1.1 requires from the keyboard); AssessmentDrawer sections and footer: none (section status was colour only, WCAG 1.3.1 and 1.4.1; footer actions dropped focus while pending); AssessmentDrawer progress bar: none (an unnamed progress bar fails WCAG 4.1.2) | Nothing. Update dark visual baselines |
|
|
271
|
+
| 2.13.0 | `AssessmentDrawer` (light theme) labels, active section, review tints | **Before:** #9CA3AF and #6B7280 text; #E8E8F0 active fill; purple `rgba(120, 65, 201)` tints **After:** `text.secondary` #374151; `primary` at the selected opacity; `primary` tints **Verified by:** `AssessmentDrawer.test.tsx` | b, c | AuditDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); EntityDetailPanel colours: none (the old values were static light colours; four status pill pairs failed WCAG 1.4.3 in light); AssessmentDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); AuditDrawer filter chips and field changes: none (filter state and field changes were conveyed by colour and strike-through only, which WCAG 1.3.1 and 4.1.2 require as text); EntityDetailPanel field help: none (help text was reachable only by hover, which WCAG 2.1.1 requires from the keyboard); AssessmentDrawer sections and footer: none (section status was colour only, WCAG 1.3.1 and 1.4.1; footer actions dropped focus while pending); AssessmentDrawer progress bar: none (an unnamed progress bar fails WCAG 4.1.2) | Nothing. Update light visual baselines |
|
|
272
|
+
| 2.13.0 | `AssessmentDrawer` footer while pending | **Before:** Save draft and Submit were `disabled`. **After:** `aria-disabled="true"` (clicks ignored), the pending one `aria-busy="true"`. **Verified by:** `AssessmentDrawer.test.tsx` "B-42: while saving or submitting both actions keep focus" | b, c | AuditDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); EntityDetailPanel colours: none (the old values were static light colours; four status pill pairs failed WCAG 1.4.3 in light); AssessmentDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); AuditDrawer filter chips and field changes: none (filter state and field changes were conveyed by colour and strike-through only, which WCAG 1.3.1 and 4.1.2 require as text); EntityDetailPanel field help: none (help text was reachable only by hover, which WCAG 2.1.1 requires from the keyboard); AssessmentDrawer sections and footer: none (section status was colour only, WCAG 1.3.1 and 1.4.1; footer actions dropped focus while pending); AssessmentDrawer progress bar: none (an unnamed progress bar fails WCAG 4.1.2) | Nothing. Tests that asserted `toBeDisabled()` while saving assert `toHaveAttribute('aria-disabled', 'true')`. |
|
|
273
|
+
| 2.13.0 | `AssessmentDrawer` progress bar | **Before:** `role="progressbar"` with no accessible name **After:** Named by the visible "<n> of <total> sections complete" text (`aria-labelledby`) **Verified by:** `AssessmentDrawer.test.tsx` | b, c | AuditDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); EntityDetailPanel colours: none (the old values were static light colours; four status pill pairs failed WCAG 1.4.3 in light); AssessmentDrawer colours: none (the old values were static light colours that failed WCAG 1.4.3 in light and painted a light drawer in dark mode); AuditDrawer filter chips and field changes: none (filter state and field changes were conveyed by colour and strike-through only, which WCAG 1.3.1 and 4.1.2 require as text); EntityDetailPanel field help: none (help text was reachable only by hover, which WCAG 2.1.1 requires from the keyboard); AssessmentDrawer sections and footer: none (section status was colour only, WCAG 1.3.1 and 1.4.1; footer actions dropped focus while pending); AssessmentDrawer progress bar: none (an unnamed progress bar fails WCAG 4.1.2) | Nothing. Tests that query `getByRole('progressbar')` still match; one that asserts no name updates |
|
|
274
|
+
| 2.13.0 | `FilterBar` quick-filter buttons | **Before:** `aria-pressed="true"` while the filter had a value **After:** No `aria-pressed`; `aria-haspopup="dialog"` and `aria-expanded` **Verified by:** `FilterBar.test.tsx` | b | FilterBar triggers and chips: none (the old markup announced the quick filters as toggles and the editor as an unnamed panel, and a removable chip gave no way to know how to remove it, WCAG 4.1.2 and 2.4.3); DiffViewer view modes and hunks: none (the view-mode buttons had no accessible name, copy outcomes were silent and failures unhandled, and collapsible hunks had no keyboard path, WCAG 2.1.1 and 4.1.2); Timeline list semantics: none (items had no list semantics and the item type was conveyed by colour alone, WCAG 1.3.1 and 1.4.1); Timeline load more: none (a disabled button drops keyboard focus while more items load, WCAG 2.4.3); wizards: none (the focused Finish button dropped focus while loading; connector cards were hand-rolled buttons with no selected state); B-42 burn-down: none (each change gives keyboard and screen-reader users a path the component lacked, or makes controlled state follow MUI's canon) | Query a quick filter with `getByRole('button', { name })` and assert the applied value through its chip (`getByRole('button', { name: /status: active/i })`) instead of `aria-pressed` |
|
|
275
|
+
| 2.13.0 | `FilterBar` editor | **Before:** A Popover paper with no role; multiselect options were buttons; the title was an `h6` **After:** `getByRole('dialog', { name: filterLabel })`; options are `getByRole('checkbox', { name })`; the title is an `h2` **Verified by:** `FilterBar.test.tsx`, `FilterBar.stories.tsx` | b | FilterBar triggers and chips: none (the old markup announced the quick filters as toggles and the editor as an unnamed panel, and a removable chip gave no way to know how to remove it, WCAG 4.1.2 and 2.4.3); DiffViewer view modes and hunks: none (the view-mode buttons had no accessible name, copy outcomes were silent and failures unhandled, and collapsible hunks had no keyboard path, WCAG 2.1.1 and 4.1.2); Timeline list semantics: none (items had no list semantics and the item type was conveyed by colour alone, WCAG 1.3.1 and 1.4.1); Timeline load more: none (a disabled button drops keyboard focus while more items load, WCAG 2.4.3); wizards: none (the focused Finish button dropped focus while loading; connector cards were hand-rolled buttons with no selected state); B-42 burn-down: none (each change gives keyboard and screen-reader users a path the component lacked, or makes controlled state follow MUI's canon) | Replace `getByRole('button', { name: option })` inside the editor with `getByRole('checkbox', { name: option })` |
|
|
276
|
+
| 2.13.0 | `DiffViewer` view-mode toggle | **Before:** Two unnamed toggle buttons in an unnamed group **After:** `getByRole('button', { name: 'Unified view' })` and `{ name: 'Side-by-side view' }` with `aria-pressed`, in `getByRole('group', { name: 'View mode' })` **Verified by:** `DiffViewer.test.tsx` | b | FilterBar triggers and chips: none (the old markup announced the quick filters as toggles and the editor as an unnamed panel, and a removable chip gave no way to know how to remove it, WCAG 4.1.2 and 2.4.3); DiffViewer view modes and hunks: none (the view-mode buttons had no accessible name, copy outcomes were silent and failures unhandled, and collapsible hunks had no keyboard path, WCAG 2.1.1 and 4.1.2); Timeline list semantics: none (items had no list semantics and the item type was conveyed by colour alone, WCAG 1.3.1 and 1.4.1); Timeline load more: none (a disabled button drops keyboard focus while more items load, WCAG 2.4.3); wizards: none (the focused Finish button dropped focus while loading; connector cards were hand-rolled buttons with no selected state); B-42 burn-down: none (each change gives keyboard and screen-reader users a path the component lacked, or makes controlled state follow MUI's canon) | Query the buttons by name instead of by position or `value` |
|
|
277
|
+
| 2.13.0 | `Timeline` with `groupByDate` and `renderItem` | **Before:** `renderItem` was also called for the synthetic date-group rows (`item.isGroupHeader`) **After:** Timeline renders the date headings itself; `renderItem` gets only your items, each inside an `<li>` **Verified by:** `Timeline.test.tsx` | b | FilterBar triggers and chips: none (the old markup announced the quick filters as toggles and the editor as an unnamed panel, and a removable chip gave no way to know how to remove it, WCAG 4.1.2 and 2.4.3); DiffViewer view modes and hunks: none (the view-mode buttons had no accessible name, copy outcomes were silent and failures unhandled, and collapsible hunks had no keyboard path, WCAG 2.1.1 and 4.1.2); Timeline list semantics: none (items had no list semantics and the item type was conveyed by colour alone, WCAG 1.3.1 and 1.4.1); Timeline load more: none (a disabled button drops keyboard focus while more items load, WCAG 2.4.3); wizards: none (the focused Finish button dropped focus while loading; connector cards were hand-rolled buttons with no selected state); B-42 burn-down: none (each change gives keyboard and screen-reader users a path the component lacked, or makes controlled state follow MUI's canon) | Drop any `isGroupHeader` branch from `renderItem`; style the date headings through the Timeline instead |
|
|
278
|
+
| 2.13.0 | `Timeline` items | **Before:** Plain `div`s; titles were `h6` headings; the item text had no type **After:** `getAllByRole('listitem')` per item, `getByRole('heading', { level: 3 })` per date group; titles are text; a typed item's text starts with its type **Verified by:** `Timeline.test.tsx` | b | FilterBar triggers and chips: none (the old markup announced the quick filters as toggles and the editor as an unnamed panel, and a removable chip gave no way to know how to remove it, WCAG 4.1.2 and 2.4.3); DiffViewer view modes and hunks: none (the view-mode buttons had no accessible name, copy outcomes were silent and failures unhandled, and collapsible hunks had no keyboard path, WCAG 2.1.1 and 4.1.2); Timeline list semantics: none (items had no list semantics and the item type was conveyed by colour alone, WCAG 1.3.1 and 1.4.1); Timeline load more: none (a disabled button drops keyboard focus while more items load, WCAG 2.4.3); wizards: none (the focused Finish button dropped focus while loading; connector cards were hand-rolled buttons with no selected state); B-42 burn-down: none (each change gives keyboard and screen-reader users a path the component lacked, or makes controlled state follow MUI's canon) | Replace `getByRole('heading', { name: title })` with `getByText(title)`; match item text with a pattern if you assert its whole text |
|
|
279
|
+
| 2.13.0 | `Timeline` load-more button while `loadingMore` | **Before:** `disabled`: focus dropped to the page **After:** Enabled with `aria-disabled="true"` and `aria-busy="true"`; clicks are ignored until `loadingMore` is false **Verified by:** `Timeline.test.tsx` | b | FilterBar triggers and chips: none (the old markup announced the quick filters as toggles and the editor as an unnamed panel, and a removable chip gave no way to know how to remove it, WCAG 4.1.2 and 2.4.3); DiffViewer view modes and hunks: none (the view-mode buttons had no accessible name, copy outcomes were silent and failures unhandled, and collapsible hunks had no keyboard path, WCAG 2.1.1 and 4.1.2); Timeline list semantics: none (items had no list semantics and the item type was conveyed by colour alone, WCAG 1.3.1 and 1.4.1); Timeline load more: none (a disabled button drops keyboard focus while more items load, WCAG 2.4.3); wizards: none (the focused Finish button dropped focus while loading; connector cards were hand-rolled buttons with no selected state); B-42 burn-down: none (each change gives keyboard and screen-reader users a path the component lacked, or makes controlled state follow MUI's canon) | In tests, replace `toBeDisabled()` with `toHaveAttribute('aria-disabled', 'true')` |
|
|
280
|
+
| 2.13.0 | `WizardTemplate` while `loading` | **Before:** Footer buttons were `disabled`. **After:** `aria-disabled="true"`, Next/Finish `aria-busy="true"`; clicks ignored. **Verified by:** `WizardTemplate.test.tsx` "activeElement stays on Finish while pending" | b | FilterBar triggers and chips: none (the old markup announced the quick filters as toggles and the editor as an unnamed panel, and a removable chip gave no way to know how to remove it, WCAG 4.1.2 and 2.4.3); DiffViewer view modes and hunks: none (the view-mode buttons had no accessible name, copy outcomes were silent and failures unhandled, and collapsible hunks had no keyboard path, WCAG 2.1.1 and 4.1.2); Timeline list semantics: none (items had no list semantics and the item type was conveyed by colour alone, WCAG 1.3.1 and 1.4.1); Timeline load more: none (a disabled button drops keyboard focus while more items load, WCAG 2.4.3); wizards: none (the focused Finish button dropped focus while loading; connector cards were hand-rolled buttons with no selected state); B-42 burn-down: none (each change gives keyboard and screen-reader users a path the component lacked, or makes controlled state follow MUI's canon) | Nothing. Tests asserting `toBeDisabled()` while loading assert `aria-disabled`. |
|
|
281
|
+
| 2.13.0 | `ConnectorWizardTemplate` connector cards | **Before:** Papers with role="button". **After:** `<label>` cards around radios in a fieldset. **Verified by:** `ConnectorWizardTemplate.test.tsx` "the connectors are radios in a group named by its legend" | b | FilterBar triggers and chips: none (the old markup announced the quick filters as toggles and the editor as an unnamed panel, and a removable chip gave no way to know how to remove it, WCAG 4.1.2 and 2.4.3); DiffViewer view modes and hunks: none (the view-mode buttons had no accessible name, copy outcomes were silent and failures unhandled, and collapsible hunks had no keyboard path, WCAG 2.1.1 and 4.1.2); Timeline list semantics: none (items had no list semantics and the item type was conveyed by colour alone, WCAG 1.3.1 and 1.4.1); Timeline load more: none (a disabled button drops keyboard focus while more items load, WCAG 2.4.3); wizards: none (the focused Finish button dropped focus while loading; connector cards were hand-rolled buttons with no selected state); B-42 burn-down: none (each change gives keyboard and screen-reader users a path the component lacked, or makes controlled state follow MUI's canon) | Nothing. Query `getByRole('radio', { name: /<connector>/ })`. |
|
|
282
|
+
| 2.13.0 | `Autocomplete` id | **Before:** The input id was `<name>-autocomplete`. **After:** The consumer's `id`, else a generated id. **Verified by:** `Autocomplete.test.tsx` "two fields with the same name get different ids" | b | FilterBar triggers and chips: none (the old markup announced the quick filters as toggles and the editor as an unnamed panel, and a removable chip gave no way to know how to remove it, WCAG 4.1.2 and 2.4.3); DiffViewer view modes and hunks: none (the view-mode buttons had no accessible name, copy outcomes were silent and failures unhandled, and collapsible hunks had no keyboard path, WCAG 2.1.1 and 4.1.2); Timeline list semantics: none (items had no list semantics and the item type was conveyed by colour alone, WCAG 1.3.1 and 1.4.1); Timeline load more: none (a disabled button drops keyboard focus while more items load, WCAG 2.4.3); wizards: none (the focused Finish button dropped focus while loading; connector cards were hand-rolled buttons with no selected state); B-42 burn-down: none (each change gives keyboard and screen-reader users a path the component lacked, or makes controlled state follow MUI's canon) | Pass `id` if code relied on the old id. |
|
|
283
|
+
| 2.13.0 | `Carousel` / `OnboardingTemplate` dots | **Before:** role="tab" with aria-selected. **After:** Buttons with aria-current="step" in a named group. **Verified by:** `Carousel.test.tsx`, `OnboardingTemplate.test.tsx` | b | FilterBar triggers and chips: none (the old markup announced the quick filters as toggles and the editor as an unnamed panel, and a removable chip gave no way to know how to remove it, WCAG 4.1.2 and 2.4.3); DiffViewer view modes and hunks: none (the view-mode buttons had no accessible name, copy outcomes were silent and failures unhandled, and collapsible hunks had no keyboard path, WCAG 2.1.1 and 4.1.2); Timeline list semantics: none (items had no list semantics and the item type was conveyed by colour alone, WCAG 1.3.1 and 1.4.1); Timeline load more: none (a disabled button drops keyboard focus while more items load, WCAG 2.4.3); wizards: none (the focused Finish button dropped focus while loading; connector cards were hand-rolled buttons with no selected state); B-42 burn-down: none (each change gives keyboard and screen-reader users a path the component lacked, or makes controlled state follow MUI's canon) | Query `getByRole('button', { name: /Go to slide/ })` / `{ name: /Slide N of M/ }`. |
|
|
284
|
+
| 2.13.0 | `AuthTemplate` primary action without `onSubmit` | **Before:** A click ran `onClick` twice. **After:** Once. **Verified by:** `AuthTemplate.test.tsx` "a click on the primary action runs it once" | b | FilterBar triggers and chips: none (the old markup announced the quick filters as toggles and the editor as an unnamed panel, and a removable chip gave no way to know how to remove it, WCAG 4.1.2 and 2.4.3); DiffViewer view modes and hunks: none (the view-mode buttons had no accessible name, copy outcomes were silent and failures unhandled, and collapsible hunks had no keyboard path, WCAG 2.1.1 and 4.1.2); Timeline list semantics: none (items had no list semantics and the item type was conveyed by colour alone, WCAG 1.3.1 and 1.4.1); Timeline load more: none (a disabled button drops keyboard focus while more items load, WCAG 2.4.3); wizards: none (the focused Finish button dropped focus while loading; connector cards were hand-rolled buttons with no selected state); B-42 burn-down: none (each change gives keyboard and screen-reader users a path the component lacked, or makes controlled state follow MUI's canon) | Nothing. |
|
|
285
|
+
| 2.13.0 | `AILabel` | **Before:** White text on the light coral-to-purple gradient in every mode **After:** White (`ai.onPrimaryFill`) on solid `ai.primaryFill`, with a 1px gradient ring; the theme's values per mode **Verified by:** `AILabel.test.tsx`, `contrast-matrix.test.ts` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing; overrides of the AI roles now reach the label |
|
|
286
|
+
| 2.13.0 | `AILabel` popover (dark) | **Before:** A light gradient to white under the content **After:** `ai.surface` to `background.paper` **Verified by:** `AILabel.test.tsx` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing |
|
|
287
|
+
| 2.13.0 | `AILabel` glyph | **Before:** A hand-drawn five-point star that ignored the icon library **After:** `Icon name="sparkles"`, decorative, the same size and colour **Verified by:** `AILabel.test.tsx` ("AILabel glyph (WI-38)") | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing |
|
|
288
|
+
| 2.13.0 | `AIActions`, `AIRelatedQuestions`, `AIPersonaCard`, `AIVisuals`, `AIDigestCard` icons (dark theme) | **Before:** Hex or resolved colours; the StatCard header icon `#7841C9`; the dismiss button imported from `@mui/icons-material` **After:** Palette keys and AI roles; the StatCard header icon `#B985E1`, the trend icon and number in the dark status text roles; the dismiss button is `Icon name="close"` **Verified by:** the `ai-aivisuals--stat-cards` and `ai-aidigestcard--default` stories (owner Storybook review) | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing. Update dark visual baselines |
|
|
289
|
+
| 2.13.0 | `AIContainer` (dark, `filled`) | **Before:** The light AI tints in every mode; the header title at 1.15:1 **After:** The theme's AI roles per mode **Verified by:** `AIDigestCard.test.tsx` (WI-36 row), Storybook axe on AI/AIContainer and AI/AIDigestCard | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing; overrides of the AI roles now reach the container |
|
|
290
|
+
| 2.13.0 | `AIPersonaCard` capability chips | **Before:** Static light AI colours in every mode **After:** The theme's AI roles per mode (`ai.surface` tint, `ai.border` outline); 24px minimum height **Verified by:** `AIPersonaCard.test.tsx` ("capability chips (D-28)") | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing |
|
|
291
|
+
| 2.13.0 | `AIExplainability` details | **Before:** Pairs of spans in Stacks **After:** One `<dl>` with a `<div>` per pair holding `<dt>` and `<dd>` **Verified by:** `AIExplainability.test.tsx` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing; tests that walked the Stacks read `dl > div` instead |
|
|
292
|
+
| 2.13.0 | `AIExpandableSection` header | **Before:** A clickable div with an unnamed chevron IconButton as its only tab stop; no `aria-expanded` **After:** One button named by the title, `aria-expanded` and `aria-controls`; the title is a span inside it **Verified by:** `AIExpandableSection.test.tsx` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Query `getByRole('button', { name: title, expanded })`; keep `expanded` either always defined or always undefined |
|
|
293
|
+
| 2.13.0 | `AISourcesSection` header | **Before:** A clickable Stack with an unnamed chevron IconButton; the heading's name was the title **After:** The heading holds a button named by the title and the count ("Sources 5"), with `aria-expanded` and `aria-controls` **Verified by:** `AISourcesSection.test.tsx` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Query the heading by a pattern (`/Sources/`) or by "Sources 5"; query the toggle as `getByRole('button', { name: /Sources/, expanded })` |
|
|
294
|
+
| 2.13.0 | `AIRelatedQuestions` | **Before:** `role="region"` panel per instance, id `related-questions-content`, list named "Related questions list", a header button with no `type` **After:** No region; per-instance ids; the list named by the toggle (the title); `type="button"` **Verified by:** `AIRelatedQuestions.test.tsx` ("AIRelatedQuestions (D-30)") | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Query the list by the title, not "Related questions list"; drop `getByRole('region')` queries |
|
|
295
|
+
| 2.13.0 | `AISourcesPanel` header and categories | **Before:** Clickable divs; categories showed "[Show]" or "[Hide]" **After:** Disclosure buttons with `aria-expanded` and a chevron; the category name reads "1 Documents (2)" **Verified by:** `AISourcesPanel.test.tsx` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Query `getByRole('button', { name: /Documents/, expanded })` instead of the "[Show]" text |
|
|
296
|
+
| 2.13.0 | `AISourcesPanel` item chips | **Before:** Labels over 25 characters ended in '...' in the text and the name; the name ran the letter into the label; items after z were lettered '{', '\|' and so on; chips had no state and the selected chip filled with the primary colour **After:** The whole label is in the text and the name ("b Customer retention policy handbook"); CSS ellipsis at 200px with a tooltip; letters aa, ab ... after z; `aria-expanded` and `aria-controls` on each chip, the selected chip outlined (2px), bold and with a check icon **Verified by:** `AISourcesPanel.test.tsx` ("WI-21a", "D-32", "WI-21b") | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Tests that query a chip by its old name: use "<letter> <full label>"; query `getByRole('button', { name, expanded: true })` for the selected chip |
|
|
297
|
+
| 2.13.0 | `AISourcesPanel` item details | **Before:** A table of divs with a `border-right` key column **After:** A `<dl>` of `<dt>` and `<dd>` pairs with a `border-inline-end` term column **Verified by:** `AISourcesPanel.test.tsx` ("D-33") | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing; tests read `dl > div` |
|
|
298
|
+
| 2.13.0 | `AITextBlock` fenced code | **Before:** `<pre><code>` on `action.hover` with the browser monospace font; the fence language dropped; no copy **After:** A header with the language and a "Copy code" button, then `<pre dir="ltr"><code>` on `surface.sunken` in the data.code font **Verified by:** `AITextBlock.test.tsx` ("fenced code (D-34)") | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing. Tests that read `pre code` keep working; query the copy button by "Copy code" |
|
|
299
|
+
| 2.13.0 | `AIAvatar` | **Before:** An unnamed `div` exposed to assistive technology; `aria-*` and `className` were not accepted **After:** `aria-hidden="true"` by default; `aria-label` or `aria-labelledby` make it `role="img"` with that name **Verified by:** `AIAvatar.test.tsx` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing where the message names its author. Pass `aria-label` only where the avatar is the sole signal of AI authorship |
|
|
300
|
+
| 2.13.0 | `AIAvatar animated`, `AIVoiceMode` | **Before:** Global `@keyframes pulse` and `orb*` names that other components' keyframes could override; AIAvatar's pulse ran under reduced motion **After:** Scoped keyframes names; AIAvatar's pulse stops under reduced motion and its glow stays **Verified by:** `keyframeCollision.test.tsx` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing. Global CSS that targeted the old names (`animation: pulse`) no longer reaches these components |
|
|
301
|
+
| 2.13.0 | `AIChatInput` buttons | **Before:** The voice, stop and send buttons had no accessible name **After:** "Voice input", "Stop generating" and "Send message", each with a matching tooltip **Verified by:** `AIChat.test.tsx` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Query them with `getByRole('button', { name })` instead of by position |
|
|
302
|
+
| 2.13.0 | `AIChatMessage` actions, `QueryInput` clear and dictation | **Before:** Icons in `text.disabled` (2.54:1); hovers in the light `aiHover` tint in both modes **After:** Icons in `text.secondary`; hovers in `action.hover` **Verified by:** `AIChat.test.tsx`, `QueryInput.test.tsx` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing. Update visual baselines |
|
|
303
|
+
| 2.13.0 | `AIQuickReply`, `AIChatInput` focus and send (dark theme) | **Before:** The light AI colours (2.74:1 on the dark paper; a white icon on a light tint on hover) **After:** The theme's AI roles (`ai.primary`, `ai.surfaceHover`, `ai.focusRing`, `ai.primaryFill`, `ai.onPrimaryFill`) **Verified by:** `AIChat.test.tsx` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing. Update visual baselines |
|
|
304
|
+
| 2.13.0 | `AITypingIndicator`, `QueryInput` listening mic | **Before:** Global `bounce` and `pulse` keyframes at 1.4s and 1.5s, running under reduced motion **After:** Scoped keyframes at `aiTokens.motion.typing` (1.2s) and `motion.loop.pulse` (2s); stopped under reduced motion **Verified by:** `AIChat.test.tsx`, `QueryInput.test.tsx` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing |
|
|
305
|
+
| 2.13.0 | `AIChatInput`, `QueryInput` `value` | **Before:** Switching between controlled and uncontrolled silently switched modes **After:** The mode is fixed on the first render, and switching warns in development **Verified by:** `AIChat.test.tsx`, `QueryInput.test.tsx` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Keep `value` either always defined or always undefined |
|
|
306
|
+
| 2.13.0 | Deprecation warnings of the five deprecated AI components | **Before:** Logged during render, also when a Trinity component rendered them **After:** Logged once from an effect; silent inside `DeprecationSuppressionContext` **Verified by:** `AIChat.test.tsx`, `QueryInput.test.tsx` | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Nothing |
|
|
307
|
+
| 2.13.0 | `AIChatMessage` copy button | **Before:** The name flipped to "Copied" and the tooltip to "Copied!" for 2s; a rejected `writeText` still showed the check icon **After:** The name and tooltip stay "Copy message"; the check icon shows success and an error icon failure, each announced once **Verified by:** `AIChat.test.tsx` ("copy action: D-35 on CopyButton") | a, b, c | AILabel: override the AI roles (`palette.trinity.ai.primaryFill`, `.onPrimaryFill`, `.accent`) to restyle the label, and the glyph and icon colours have none (the old star ignored the icon library); AIContainer and AIPersonaCard: override the AI roles (`palette.trinity.ai.border`, `.surface`, `.surfaceEmphasis`, `.primaryFill`, `.primary`) to restyle them; AI icon colours: none (theme-resolved colours follow overrides and dark mode); AIExplainability details: none (WCAG 1.3.1: the name-value pairs had no structure); AIExpandableSection: none (the header was not operable by keyboard and exposed no state, WCAG 2.1.1 and 4.1.2); AISourcesSection: `collapsible={false}` for a static heading without a toggle; AIRelatedQuestions: none (a landmark per message, a duplicate id and a submit button inside forms are WCAG 1.3.1, 4.1.1 and 3.2.2 defects); AISourcesPanel: none (the header and category rows were not operable by keyboard, the chips exposed no state and showed the selection by fill colour only, the details had no structure, a label cut to 25 characters lost content from the accessible name, WCAG 1.3.1, 1.4.1, 2.1.1, 2.5.3 and 4.1.2); AITextBlock fenced code: none (the fence language was dropped and the code mirrored with the page direction; set `markdown={false}` and render code yourself to keep a custom look); deprecated AIChatMessage copy action: none (the old action showed a failed copy as copied and never announced the outcome, WCAG 4.1.3); AIAvatar: pass `aria-label` (or `aria-labelledby`) to expose the avatar as a named image (the unnamed exposed node has no opt-out, WCAG 1.1.1 and 4.1.2); AIAvatar and AIVoiceMode keyframes: none (a global animation name lets another component's keyframes replace this one's; the reduced-motion stop follows WCAG 2.2.2); deprecated AIChat components: none (each change fixes a WCAG 2.2 AA failure, a global animation name or a render-time warning; the deprecated components take no other changes, docs/remediation/workstream-I/deprecated-ai-fix-policy.md) | Query the button by "Copy message" only; assert the announcement or the icon instead of the "Copied" name |
|
|
308
|
+
| 2.13.0 | AIClarificationPrompt, AIFeedbackForm, AIQueryDisclosure, InsightEngineInput in a content-sized host | **Before:** The part inside the narrow-container wrapper gave the component no width: AIClarificationPrompt rendered 0px wide in a flex row without a width, a grid `auto` column or a popover **After:** 320px wide there (the narrow container width as the wrapper's intrinsic width); unchanged in hosts that set a width **Verified by:** `AIHitlConformance.stories.tsx` (ZeroWidthHosting), `containerQueries.test.ts` | b | a host that sets a width (or `sx` with a `width`) decides the width, as before | Nothing. Give the host or the component a width to choose another |
|
|
309
|
+
| 2.13.0 | `AITextBlock` with `markdown` and `variant` (also `AIResponseRenderer` text blocks) | **Before:** `variant` was ignored: markdown text always rendered as body text. **After:** `caption`, `heading` and `quote` style the markdown text as they style plain text (`heading` renders an `<h3>`, `quote` a `<blockquote>`). **Verified by:** `AITextBlock.test.tsx` (WI-59+) | a, b | WI-59+: `variant="body"` (or no `variant`) on a markdown block keeps the previous body styling; F-07: none (object `sx` renders exactly as before; the corrected array and function forms were already allowed by the published `sx` type, and attributes that were dropped apply only when a consumer passes them) | Nothing. To keep the old body look, drop `variant` or pass `variant="body"`. |
|
|
310
|
+
| 2.13.0 | `AITextBlock` `sx` with `markdown` | **Before:** An array `sx` broke (its entries became keys `'0'`, `'1'`) and a function `sx` was ignored. **After:** Array and function `sx` apply after the block's styles, as in plain mode. **Verified by:** `AITextBlock.test.tsx` (F-07) | a, b | WI-59+: `variant="body"` (or no `variant`) on a markdown block keeps the previous body styling; F-07: none (object `sx` renders exactly as before; the corrected array and function forms were already allowed by the published `sx` type, and attributes that were dropped apply only when a consumer passes them) | Nothing; check that the styles you passed are the ones you want now that they apply. |
|
|
311
|
+
| 2.13.0 | `AITextBlock` root attributes | **Before:** `className`, `data-*`, `aria-*` and event handlers were dropped; the root had no id unless one was passed. **After:** They reach the root element in both modes; the root gets a generated id when none is passed. **Verified by:** `AITextBlock.test.tsx` (F-07, rootPropsContract) | a, b | WI-59+: `variant="body"` (or no `variant`) on a markdown block keeps the previous body styling; F-07: none (object `sx` renders exactly as before; the corrected array and function forms were already allowed by the published `sx` type, and attributes that were dropped apply only when a consumer passes them) | Nothing. Remove attributes you passed that you do not want applied. |
|
|
312
|
+
| 2.13.0 | `AICircularAction` `variant="gradient"` | **Before:** The icon sat on the static coral-to-purple gradient (`aiTokens.gradient.diagonal`) in every mode and preset. **After:** The same gradient through the resolver, `ai.gradient.brandDiagonal` (coral 800 to purple 700 at 135deg, the same in light and dark; a brand or `ai.avatarFill` override reaches it), under the icon in `ai.onPrimaryFill`; no ring. Over the icon's box white is 3.4:1 or more at every size (owner decision 2026-10-09). **Verified by:** `AIActions.test.tsx` | a, b, c | WI-36: the component's `sx` (applied after its own styles, now also as an array or a function) restores any listed style; GradientText and GradientIconBadge take `startColor` and `endColor` as before; WI-39: the component's `sx` restores any listed style; motion follows the user's reduced-motion setting by design (none for the timeouts) | Nothing. |
|
|
313
|
+
| 2.13.0 | `StatCard` `variant="gradient"` | **Before:** Text on the purple-to-coral gradient, the label at 0.8 and the trend label at 0.7 white (3.88:1 to 3.99:1). **After:** A solid AI fill with the gradient as a ring; all text in the on-fill colour at full strength. **Verified by:** `AIVisuals.test.tsx` | a, b, c | WI-36: the component's `sx` (applied after its own styles, now also as an array or a function) restores any listed style; GradientText and GradientIconBadge take `startColor` and `endColor` as before; WI-39: the component's `sx` restores any listed style; motion follows the user's reduced-motion setting by design (none for the timeouts) | Nothing. |
|
|
314
|
+
| 2.13.0 | `AISuggestedAction` `variant="primary"` | **Before:** The description was white at 0.8 on `primary.main` (2.21:1 on the dark coral fill, 3.53:1 under a plain MUI theme). **After:** `primary.contrastText` at full strength, 4.5:1 or more in every theme. **Verified by:** `AIActions.test.tsx` | a, b, c | WI-36: the component's `sx` (applied after its own styles, now also as an array or a function) restores any listed style; GradientText and GradientIconBadge take `startColor` and `endColor` as before; WI-39: the component's `sx` restores any listed style; motion follows the user's reduced-motion setting by design (none for the timeouts) | Nothing. |
|
|
315
|
+
| 2.13.0 | AI molecules in dark mode (AIActions, AIVisuals, AIAvatar, ChatHistoryList, AIVoiceInput, AIVoiceMode, InsightEngineInput, AIRelatedQuestions) | **Before:** Static light AI colours in every mode and preset. **After:** The resolver's AI roles: the dark values in dark mode, the preset's and the overrides' values where set. **Verified by:** `aiResolverSweep.test.tsx`, each component's "AI roles (WI-36)" specs, `non-trinity-theme.test.tsx` | a, b, c | WI-36: the component's `sx` (applied after its own styles, now also as an array or a function) restores any listed style; GradientText and GradientIconBadge take `startColor` and `endColor` as before; WI-39: the component's `sx` restores any listed style; motion follows the user's reduced-motion setting by design (none for the timeouts) | Nothing; to pin a colour, pass it through the component's `sx`. |
|
|
316
|
+
| 2.13.0 | `InsightEngineInput` text sizes | **Before:** Literal sizes (0.68rem attachment meta, 0.875rem input, 0.75rem chips). **After:** `aiTypography.meta` (11px), `body2` and `caption` of the theme. **Verified by:** `InsightEngineInput.test.tsx` | a, b, c | WI-36: the component's `sx` (applied after its own styles, now also as an array or a function) restores any listed style; GradientText and GradientIconBadge take `startColor` and `endColor` as before; WI-39: the component's `sx` restores any listed style; motion follows the user's reduced-motion setting by design (none for the timeouts) | Nothing. |
|
|
317
|
+
| 2.13.0 | `AIRelatedQuestions`, `AISuggestedAction`, `ChatHistoryList`, `InsightEngineInput`, `AIVoiceMode` right to left | **Before:** Physical left and right margins, padding, insets, alignment and menu origins: wrong side in a right-to-left app without stylis-plugin-rtl, and menus anchored to the left or right edge in any right-to-left app. **After:** Logical properties and direction-aware menu origins. **Verified by:** `aiLogicalProperties.test.tsx` | a, b, c | WI-36: the component's `sx` (applied after its own styles, now also as an array or a function) restores any listed style; GradientText and GradientIconBadge take `startColor` and `endColor` as before; WI-39: the component's `sx` restores any listed style; motion follows the user's reduced-motion setting by design (none for the timeouts) | Nothing. |
|
|
318
|
+
| 2.13.0 | `AIVoiceOverlay`, `AIVoiceMode` (full screen) | **Before:** Collapse and Fade ran their 300ms and 225/195ms transitions under reduced motion. **After:** A 0 timeout under reduced motion; the same durations otherwise. **Verified by:** `aiMotion.test.tsx` | a, b, c | WI-36: the component's `sx` (applied after its own styles, now also as an array or a function) restores any listed style; GradientText and GradientIconBadge take `startColor` and `endColor` as before; WI-39: the component's `sx` restores any listed style; motion follows the user's reduced-motion setting by design (none for the timeouts) | Nothing. |
|
|
319
|
+
| 2.13.0 | `AILabel` explainability popover | **Before:** Its own popover, 280 to 360px wide. **After:** The shared details surface: at most 360px, never wider than the screen minus 32px, no minimum width. **Verified by:** `AILabel.test.tsx` | b | WI-60+: none (the popover moves onto the shared details surface, and the focus and motion rules are the remediation canon; no prop restores the old widths, the mouse-click ring or the reduced-motion grow) | Nothing. Content that relied on the 280px minimum sets its own width. |
|
|
320
|
+
| 2.13.0 | `AILabel` focus and hover | **Before:** The focus ring showed after a mouse click too; the label grew on hover under reduced motion. **After:** The ring shows for keyboard focus only; no hover grow under reduced motion. **Verified by:** `AILabel.test.tsx` | b | WI-60+: none (the popover moves onto the shared details surface, and the focus and motion rules are the remediation canon; no prop restores the old widths, the mouse-click ring or the reduced-motion grow) | Nothing. |
|
|
321
|
+
| 2.13.0 | `AITextBlock` `_text_` with `markdown` | **Before:** `_text_` rendered literally, underscores included. **After:** `_text_` at word boundaries renders as `<em>text</em>`; underscores inside words stay literal. **Verified by:** `AITextBlock.test.tsx`, `AITextBlock.parser.test.ts` (WI-31a) | a | WI-31a: none needed for identifiers: an underscore inside a word stays text; to keep `_x_` literal, escape it in the content (for example with a code span); WI-39 (AITextBlock): the component's `sx` restores any listed style | Nothing for identifiers. Put a literal `_text_` in a code span. |
|
|
322
|
+
| 2.13.0 | `AITextBlock` right to left | **Before:** List indents and quote rules on the left in a right-to-left app without stylis-plugin-rtl. **After:** At the reading start (logical properties). **Verified by:** `AITextBlock.test.tsx` (WI-39) | a | WI-31a: none needed for identifiers: an underscore inside a word stays text; to keep `_x_` literal, escape it in the content (for example with a code span); WI-39 (AITextBlock): the component's `sx` restores any listed style | Nothing. |
|
|
323
|
+
| 2.13.0 | `AITextBlock` inline code | **Before:** `monospace` at 0.9em of the surrounding text. **After:** `aiTypography.code`: the code family at 12px. **Verified by:** `AITextBlock.test.tsx` (WI-39) | a | WI-31a: none needed for identifiers: an underscore inside a word stays text; to keep `_x_` literal, escape it in the content (for example with a code span); WI-39 (AITextBlock): the component's `sx` restores any listed style | Nothing; pass `sx` on a wrapper only if you styled the old size. |
|
|
324
|
+
| 2.13.0 | AsyncOutcomeAlert (the inline error of Modal, ConfirmDialog, the connector wizard and AIFeedbackForm) | **Before:** In a narrow host the message shrank into a sideways-scrolling box next to 'Try again', a scrollable region with nothing focusable (WCAG 2.1.1) **After:** 'Try again' wraps below the message once the message would be narrower than 12rem, and long words wrap; wide hosts look the same **Verified by:** `AsyncOutcomeAlert.test.tsx`, ai-aifeedbackcontrol--send-error | b | F-16 (AsyncOutcomeAlert): none (consumer `sx` on the Alert still wins) | Nothing |
|
|
325
|
+
| 2.13.0 | AIClarificationPrompt, AIFeedbackForm, AIQueryDisclosure, InsightEngineInput in a content-sized host | **Before:** The part inside the narrow-container wrapper gave the component no width: AIClarificationPrompt rendered 0px wide in a flex row without a width, a grid `auto` column or a popover **After:** 320px wide there (the narrow container width as the wrapper's intrinsic width); unchanged in hosts that set a width **Verified by:** `AIHitlConformance.stories.tsx` (ZeroWidthHosting), `containerQueries.test.ts` | b | F-22 (container width): a host that sets a width (or `sx` with a `width`) decides the width, as before | Nothing. Give the host or the component a width to choose another |
|
|
326
|
+
| 2.13.0 | `ThreadHistorySidebar` (via `InsightEnginePanel` history) Star action | **Before:** Named 'Star' or 'Unstar' by state; the starred icon was yellow. **After:** One name, 'Star thread', with `aria-pressed`; the starred look is the theme's pressed recipe. **Verified by:** `ThreadHistorySidebar.test.tsx` | a, b, e | D-40: none (WCAG 2.1.1, 4.1.2, 2.4.3); H-39: none (the warnings were about Trinity's own usage, which the consumer cannot change); H-42: none (keyboard access, WCAG 2.1.1; dead controls); H-49+: none (the button was blank) | Tests or automation that query 'Star' or 'Unstar' query 'Star thread' and read `aria-pressed`. |
|
|
327
|
+
| 2.13.0 | `ThreadHistorySidebar` rename | **Before:** The field committed on blur and dropped an empty name. **After:** Enter or Save commits, Escape or Cancel cancels, Tab or a click elsewhere leaves without saving; an empty name shows an error. **Verified by:** `ThreadHistorySidebar.test.tsx` | a, b, e | D-40: none (WCAG 2.1.1, 4.1.2, 2.4.3); H-39: none (the warnings were about Trinity's own usage, which the consumer cannot change); H-42: none (keyboard access, WCAG 2.1.1; dead controls); H-49+: none (the button was blank) | None for `onThreadRename` handlers: they still receive the trimmed name. |
|
|
328
|
+
| 2.13.0 | `InsightEnginePanel` default `modelOptions` / `sourceOptions` (v3.0) | **Before:** Without `modelOptions` and `sourceOptions` the composer lists five sample models and five sample sources. **After:** v3.0: the defaults become empty, so the model and source menus do not render without your lists. **Verified by:** `InsightEnginePanel.test.tsx` | a, b, e | D-40: none (WCAG 2.1.1, 4.1.2, 2.4.3); H-39: none (the warnings were about Trinity's own usage, which the consumer cannot change); H-42: none (keyboard access, WCAG 2.1.1; dead controls); H-49+: none (the button was blank) | Pass `modelOptions` and `sourceOptions` (types `InsightModelOption`, `InsightSourceOption`). |
|
|
329
|
+
| 2.13.0 | `AppLayout` AI panel landmark, focus return and triggers (also `AIFabLayout`, `AIFab`, `TopNavWithSidebar` nav items) | **Before:** The docked panel was an unnamed `div`. Closing it from its Close button (or `renderAiPanel`'s `onClose`) unmounted the focused button, so focus fell to `<body>`. AIFab ignored `open` (no `aria-expanded`). The AI nav item became the selected item: it took `aria-current="page"` and the highlight from the page you were on, and kept them after the panel closed. **After:** The panel is a complementary landmark named by its title (or "AI assistant" with `renderAiPanel`). Closing it from inside, docked or fullscreen, returns focus to the FAB or the AI nav item (the main content if that control is gone). The FAB has `aria-expanded` and, while the panel is mounted, `aria-controls`. The AI nav item is a toggle: `aria-expanded`, no `aria-current`, and its highlight follows the open panel; the page item keeps both. TopNavWithSidebar nav items gain optional `kind`, `expanded` and `controls` for this. **Verified by:** `AppLayout.test.tsx` "[WH-11]" records and "AppLayout panel landmark and focus return (H-11)", `AIFab.test.tsx`, `TopNavWithSidebar.test.tsx` "toggle nav items (H-11)" | b | H-11: none; H-40: none; H-41: none | Nothing required. Tests that expected `aria-current="page"` on the AI nav item after a click should expect `aria-expanded="true"` instead, and the page item to keep `aria-current`. Query the panel with `getByRole('complementary', { name: /Insight Engine/ })`. |
|
|
330
|
+
| 2.13.0 | `AppLayout` AI panel below the md breakpoint (also `AIFabLayout`) | **Before:** On a phone the docked panel kept its 320–800px width (420 by default) on a 375px screen, the content kept a margin for it, the resize handle and the FAB stayed, and the page behind the panel stayed in the Tab order, so keyboard focus could land on content hidden under the panel (WCAG 2.4.11). **After:** Below `md` the panel opens as a full-width modal drawer (`role="dialog"`, `aria-modal`, named by the panel title or "AI assistant"): focus stays inside, Escape and the backdrop close it, the page behind it is inert, there is no content margin, resize handle, fullscreen toggle or FAB over it, and closing returns focus to the control that opened it. `keepAiPanelMounted` still keeps its content. **Verified by:** `AppLayout.test.tsx` "AppLayout H-40 characterization (mobile panel)" and "AppLayout mobile AI panel (H-40)" | b | H-11: none; H-40: none; H-41: none | Nothing required. Tests at a mobile viewport should query the panel with `getByRole('dialog', { name: /Insight Engine/ })` and expect no "Resize panel" separator there. Pass `mobilePanelPresentation="docked"` to keep the previous behavior. See [the AppLayout AI panel guide](docs/APPLAYOUT_AI_PANEL.md#the-phone-drawer-h-40). |
|
|
331
|
+
| 2.13.0 | `AIFab` motion and position (also the FAB of `AppLayout` / `AIFabLayout`) | **Before:** `transition: all 0.3s ease` with a hover and press scale that also ran under `prefers-reduced-motion: reduce`; the position used physical `right` / `left`, so a right-to-left page without stylis-plugin-rtl kept the FAB on the right. **After:** transform and box-shadow animate over the motion tokens (300ms, `easing.default`); under reduced motion nothing animates or scales. `right` / `left` in `position` are the inline end / start (`inset-inline-end` / `-start`). **Verified by:** `AIFab.test.tsx` "[WH-41]" records | b | H-11: none; H-40: none; H-41: none | Nothing required. Tests that read the FAB wrapper's `right` style in jsdom should read `inset-inline-end` (`getComputedStyle(el).insetInlineEnd`); browsers still resolve `right`. |
|
|
332
|
+
| 2.13.0 | InsightEngineInput Send tooltip | **Before:** 'Send (Enter)' **After:** 'Send message' (its name), with aria-keyshortcuts="Enter" **Verified by:** `aiStrings.wiring.test.tsx` | b | WI-37: override `ai.insightEngineInput.sendMessage` through AIConfigProvider | Nothing. Override `ai.insightEngineInput.sendMessage` through AIConfigProvider to change it. |
|
|
333
|
+
| 2.13.0 | InsightEngineInput source chip name | **Before:** The bare count ('2') **After:** '2 sources selected' (`ai.insightEngineInput.sourcesSelected`) **Verified by:** `InsightEngineInput.test.tsx` | b | WI-37: override `ai.insightEngineInput.sendMessage` through AIConfigProvider | Tests that found the chip by its number find it by the new name. |
|
|
334
|
+
| 2.13.0 | `InsightEngineInput` `attachmentCount` (`InsightEngineInputProps.attachmentCount`, declared on `InsightEngineInputOwnProps.attachmentCount`) | **Before:** Deprecated, not read; it reached the root as an unknown DOM attribute once rest props were spread. **After:** Still deprecated and not read; taken out of the rest props, so it never reaches the DOM (the props interface is now `InsightEngineInputOwnProps` plus the root props). **Verified by:** `aiRootProps.contract.test.tsx` (WI-40) | b | WI-37: override `ai.insightEngineInput.sendMessage` through AIConfigProvider | Pass `attachments`; the badge counts them. |
|
|
335
|
+
| 2.13.0 | Components with a pinned Icon library (AuditDrawer, EntityDetailPanel, AssessmentDrawer, GanttView, OrgChart, CatalogTemplate, NotificationCenter, BulkActionDock, HierarchicalDrillDown, WizardTemplate, ConnectorWizardTemplate, OnboardingTemplate, ResizablePanel; FilterBar, ThreadHistorySidebar, DocumentCard, DiffViewer, ClearableTextField, InsightCard, Timeline, Combobox, TreeView, SettingsTemplate, Tour, Carousel, AIFab) | **Before:** Each icon kept the library of its old direct import (`library="material"` or `"feather"`), ignoring the app's `IconProvider`. **After:** Every component icon follows the nearest `IconProvider` (Material without one); names, sizes, colours, accessible names and layout are unchanged. **Verified by:** `Icon.pins.test.ts`, `OrgChart.test.tsx` (C-38+) | b | C-38+: `<IconProvider defaultLibrary>` picks the library for every component | Nothing in code; update visual baselines that captured the old glyphs. |
|
|
336
|
+
| 2.13.0 | `AIResponseRenderer` block DOM ids | **Before:** A text block's DOM id was the model's `block.id`, so equal ids in two messages collided and an id such as `config` created a `window.config` global; equal ids in one response repeated a React key. **After:** The raw id is in `data-block-id`; the DOM id is `getBlockDomId(idPrefix ?? root id, block.id)`, sanitized and unique per renderer. **Verified by:** `AIResponseRenderer.test.tsx` "[G-08]" records and "DOM ids (G-OD12, WI-48)" | a, b, d | G-08 (block ids): none; pass `idPrefix` for stable DOM ids; G-10: `blockGap={1}` restores the 1px gap; G-34: none; omit `error` from the response to show only its blocks | Link to a block with `#${getBlockDomId(prefix, blockId)}` and pass that prefix as `idPrefix`; select blocks by `[data-block-id="…"]`. |
|
|
337
|
+
| 2.13.0 | `AIResponseRenderer` `blockGap` default | **Before:** The default gap between blocks rendered as 1px (`aiSpacing.sm` written as pixels). **After:** `theme.spacing(aiSpacing.sm)`, 8px in the default theme and following the theme's spacing; an explicit number is still pixels. **Verified by:** `AIResponseRenderer.test.tsx` "[G-10]" and "blockGap (G-10)" | a, b, d | G-08 (block ids): none; pass `idPrefix` for stable DOM ids; G-10: `blockGap={1}` restores the 1px gap; G-34: none; omit `error` from the response to show only its blocks | Nothing, unless you relied on the 1px gap: then pass `blockGap={1}`. |
|
|
338
|
+
| 2.13.0 | `AIResponseRenderer` `response.error` | **Before:** `AIResponse.error` was never rendered; a failed response showed only its blocks. **After:** An inline error (title 'This response failed' and the error's message as plain text) follows the blocks, and is announced once outside a thread. **Verified by:** `AIResponseRenderer.test.tsx` "[G-34]" and "error (G-34)" | a, b, d | G-08 (block ids): none; pass `idPrefix` for stable DOM ids; G-10: `blockGap={1}` restores the 1px gap; G-34: none; omit `error` from the response to show only its blocks | Make sure `error.message` is text meant for users; override the title with `AIConfigProvider` `strings.responseRenderer.errorTitle`. |
|
|
339
|
+
| 2.13.0 | `AIMessage` Copy | **Before:** Did nothing without `onCopy`; `onCopy()` took no argument. **After:** Writes the response to the clipboard and announces "Copied" unless `onCopy` is passed; `onCopy(text)` receives the copied text; `aria-disabled` while streaming. **Verified by:** `AIMessage.test.tsx` (G-04b) | b, c, e | G-04b: `copyBehavior="callback"`; G-04c: none (a toolbar without its arrow-key model fails the behavior contract); G-04d: none (WCAG 1.4.11 contrast); G-04e: none (the toggle state is the WCAG 4.1.2 fix); AIActionsGroup row wrap: none (WCAG 1.4.10 Reflow); AIMessage footer wrap: none (WCAG 1.4.10 Reflow) | To keep Copy a no-op without a handler, pass `copyBehavior="callback"`. Code that calls a stored `AIMessageProps['onCopy']` itself must pass the text: `props.onCopy?.(text)`. |
|
|
340
|
+
| 2.13.0 | `AIMessage` footer actions | **Before:** `role="toolbar"` named "AI response actions", without the toolbar arrow-key model. **After:** `role="group"` with the same name; every action keeps its own tab stop. **Verified by:** `AIMessage.test.tsx` (G-04c) | b, c, e | G-04b: `copyBehavior="callback"`; G-04c: none (a toolbar without its arrow-key model fails the behavior contract); G-04d: none (WCAG 1.4.11 contrast); G-04e: none (the toggle state is the WCAG 4.1.2 fix); AIActionsGroup row wrap: none (WCAG 1.4.10 Reflow); AIMessage footer wrap: none (WCAG 1.4.10 Reflow) | Query `getByRole('group', { name: 'AI response actions' })` in tests. |
|
|
341
|
+
| 2.13.0 | `AIMessage` idle action icons | **Before:** `text.disabled`, below 3:1 against the surface (WCAG 1.4.11). **After:** `text.secondary`, 3:1 or more in light and dark; focus outline; 24 by 24 px targets. **Verified by:** `AIMessage.test.tsx` (G-04d) | b, c, e | G-04b: `copyBehavior="callback"`; G-04c: none (a toolbar without its arrow-key model fails the behavior contract); G-04d: none (WCAG 1.4.11 contrast); G-04e: none (the toggle state is the WCAG 4.1.2 fix); AIActionsGroup row wrap: none (WCAG 1.4.10 Reflow); AIMessage footer wrap: none (WCAG 1.4.10 Reflow) | Nothing in code. Visual baselines of AIMessage and its hosts change. |
|
|
342
|
+
| 2.13.0 | `AIMessage` feedback thumbs | **Before:** No pressed state; pressing the same thumb twice called `onFeedback` twice. **After:** `AIFeedbackControl` toggles with `aria-pressed`; the second press clears the rating and calls only `onFeedbackChange(null)`. **Verified by:** `AIMessage.test.tsx` (G-04e) | b, c, e | G-04b: `copyBehavior="callback"`; G-04c: none (a toolbar without its arrow-key model fails the behavior contract); G-04d: none (WCAG 1.4.11 contrast); G-04e: none (the toggle state is the WCAG 4.1.2 fix); AIActionsGroup row wrap: none (WCAG 1.4.10 Reflow); AIMessage footer wrap: none (WCAG 1.4.10 Reflow) | If you counted repeated `onFeedback` calls, use `onFeedbackChange`, which reports every change including the clear. |
|
|
343
|
+
| 2.13.0 | `AIActionsGroup` `direction="row"` | **Before:** A row did not wrap and overflowed a narrow host (a 320 to 360px docked AI panel). **After:** Actions wrap onto the next line with the same `spacing` (a flex gap); each action sizes to its label, grows to share its line and shrinks with `minWidth: 0`, `maxWidth: 100%`. **Verified by:** `AIActions.test.tsx` (320px and 360px hosts), ai-aiactions--actions-group-row-narrow | b, c, e | G-04b: `copyBehavior="callback"`; G-04c: none (a toolbar without its arrow-key model fails the behavior contract); G-04d: none (WCAG 1.4.11 contrast); G-04e: none (the toggle state is the WCAG 4.1.2 fix); AIActionsGroup row wrap: none (WCAG 1.4.10 Reflow); AIMessage footer wrap: none (WCAG 1.4.10 Reflow) | Nothing. A consumer `sx` on an action still wins. |
|
|
344
|
+
| 2.13.0 | `AIMessage` footer (timestamp and actions) | **Before:** In a narrow host the footer stayed on one line: the timestamp broke over two lines and the thumbs stacked into a column. **After:** The footer wraps: the actions move under the timestamp, aligned to the end, and wrap as a group; the actions are spaced by a 4px flex gap. **Verified by:** ai-chatthread--docked-panel-widths play (320px host: thumbs side by side, timestamp on one line) | b, c, e | G-04b: `copyBehavior="callback"`; G-04c: none (a toolbar without its arrow-key model fails the behavior contract); G-04d: none (WCAG 1.4.11 contrast); G-04e: none (the toggle state is the WCAG 4.1.2 fix); AIActionsGroup row wrap: none (WCAG 1.4.10 Reflow); AIMessage footer wrap: none (WCAG 1.4.10 Reflow) | Nothing. A consumer `slotProps.footer` `sx` still wins. |
|
|
345
|
+
| 2.13.0 | `ChatHistoryList` options button | **Before:** Hidden until hover (invisible on focus and touch), named 'Chat options' on every row, no menu-trigger state; the Delete icon was missing. **After:** Visible on focus, while its menu is open and on touch; named 'Options for {title}' (`ai.chatHistoryList.optionsFor`); `aria-haspopup="menu"` and `aria-expanded`; Delete, Pin and Unpin icons. **Verified by:** `ChatHistoryList.test.tsx` (WI-23b) | b | WI-23b: none | Tests that query `getByRole('button', { name: 'Chat options' })` use the per-row name, for example `'Options for Q4 revenue'`. |
|
|
346
|
+
| 2.13.0 | `ResizablePanel` under `theme.direction: 'rtl'` | **Before:** Arrow keys and drag followed the physical layout (Left Arrow always widened a right-side panel). **After:** The arrow and the drag toward the content widen the panel. **Verified by:** `ResizablePanel.test.tsx` [WH-31] | b | H-31: none; H-32: none (use the panel header's Exit fullscreen and Close while fullscreen) | Nothing; an app that inverted the keys itself removes that. |
|
|
347
|
+
| 2.13.0 | `ResizablePanel` border and handle, RTL without stylis-plugin-rtl | **Before:** On the physical left/right edge, away from the content. **After:** On the inline-start/end edge that faces the content. **Verified by:** `ResizablePanel.test.tsx` [WH-31] | b | H-31: none; H-32: none (use the panel header's Exit fullscreen and Close while fullscreen) | Nothing. |
|
|
348
|
+
| 2.13.0 | `AppLayout` with `aiTrigger="fab"`, panel fullscreen | **Before:** The FAB stayed visible over the fullscreen panel. **After:** No FAB while fullscreen; it returns on exit or close. **Verified by:** `AppLayout.test.tsx` [WH-32] | b | H-31: none; H-32: none (use the panel header's Exit fullscreen and Close while fullscreen) | Use the panel header's Exit fullscreen or Close while fullscreen. |
|
|
349
|
+
| 2.13.0 | `AppLayout` content margin and panel position | **Before:** `marginRight` and `right: 0` (physical). **After:** `marginInlineEnd` and `insetInlineEnd: 0` (logical). **Verified by:** `AppLayout.test.tsx` [WH-32] | b | H-31: none; H-32: none (use the panel header's Exit fullscreen and Close while fullscreen) | Nothing left to right. Without stylis-plugin-rtl, RTL now docks on the left as intended. |
|
|
350
|
+
| 2.13.0 | `InsightEnginePanel` `onSendMessage` | **Before:** Called twice per send, the second time with `{}`. **After:** Called once per send with `{ proSearch }`. **Verified by:** `InsightEnginePanel.test.tsx` | a, b, e | H-02: none (a second call with empty options was never documented; the JSDoc describes one callback); H-04: `demoMode={true}`; H-05: none (the documented controlled mode lost data; there is nothing to opt back into); H-07: `onAttachmentClick`, `webSearchEnabled` with `onWebSearchToggle`, `onVoiceClick` with `listening`; H-34: `headingLevel={6}`; H-35: none (omit the props for the previous rendering); H-36: `userInitials="RD"` (or the signed-in user's initials) | Remove any de-duplication of the second call; a handler that ignored calls with empty options now ignores nothing. See [the AppLayout AI panel guide](docs/APPLAYOUT_AI_PANEL.md#one-send-per-message-h-02). |
|
|
351
|
+
| 2.13.0 | `InsightEnginePanel` `demoMode` (integrated) | **Before:** With `onSendMessage` or `messages` passed and `demoMode` unset, canned demo answers appeared after 2 s (and reached a controlled store), the history listed invented threads, and two history toggles always rendered. **After:** Demo answers and demo history are off unless `demoMode={true}`; demo mode shows a 'Demo responses' label in the title heading; the history toggles render only with `threadGroups`, `onThreadSelect` or demo mode. **Verified by:** `AppLayout.integration.test.tsx`, `InsightEnginePanel.test.tsx` | a, b, e | H-02: none (a second call with empty options was never documented; the JSDoc describes one callback); H-04: `demoMode={true}`; H-05: none (the documented controlled mode lost data; there is nothing to opt back into); H-07: `onAttachmentClick`, `webSearchEnabled` with `onWebSearchToggle`, `onVoiceClick` with `listening`; H-34: `headingLevel={6}`; H-35: none (omit the props for the previous rendering); H-36: `userInitials="RD"` (or the signed-in user's initials) | Pass `demoMode={true}` to keep the demo; pass `threadGroups` (and `onThreadSelect`) to show a history. See [the AppLayout AI panel guide](docs/APPLAYOUT_AI_PANEL.md#demo-mode-is-derived-h-04). |
|
|
352
|
+
| 2.13.0 | `InsightEnginePanel` `demoMode` default (v3.0) | **Before:** `demoMode` unset on an unwired panel runs the demo. **After:** v3.0: the default becomes `false`. **Verified by:** `InsightEnginePanel.test.tsx` | a, b, e | H-02: none (a second call with empty options was never documented; the JSDoc describes one callback); H-04: `demoMode={true}`; H-05: none (the documented controlled mode lost data; there is nothing to opt back into); H-07: `onAttachmentClick`, `webSearchEnabled` with `onWebSearchToggle`, `onVoiceClick` with `listening`; H-34: `headingLevel={6}`; H-35: none (omit the props for the previous rendering); H-36: `userInitials="RD"` (or the signed-in user's initials) | Pass `demoMode` explicitly. See [the AppLayout AI panel guide](docs/APPLAYOUT_AI_PANEL.md#demo-mode-is-derived-h-04). |
|
|
353
|
+
| 2.13.0 | `InsightEnginePanel` controlled `messages` | **Before:** A demo answer or a second quick send could replace the conversation with a stale copy (the user's message lost); `onMessagesChange` was silent in uncontrolled mode; new messages scrolled the host page. **After:** Updates build on the latest messages; `onMessagesChange` fires on every change in both modes; only the messages container scrolls, instantly under reduced motion. **Verified by:** `InsightEnginePanel.test.tsx` | a, b, e | H-02: none (a second call with empty options was never documented; the JSDoc describes one callback); H-04: `demoMode={true}`; H-05: none (the documented controlled mode lost data; there is nothing to opt back into); H-07: `onAttachmentClick`, `webSearchEnabled` with `onWebSearchToggle`, `onVoiceClick` with `listening`; H-34: `headingLevel={6}`; H-35: none (omit the props for the previous rendering); H-36: `userInitials="RD"` (or the signed-in user's initials) | Remove workarounds that re-added lost messages; expect `onMessagesChange` calls from an uncontrolled panel. |
|
|
354
|
+
| 2.13.0 | `InsightEnginePanel` `themeMode` (`InsightEnginePanelProps.themeMode`) | **Before:** Accepted and ignored. **After:** Deprecated: still ignored, with a one-time development warning; removed in v3.0. **Verified by:** `InsightEnginePanel.test.tsx` | a, b, e | H-02: none (a second call with empty options was never documented; the JSDoc describes one callback); H-04: `demoMode={true}`; H-05: none (the documented controlled mode lost data; there is nothing to opt back into); H-07: `onAttachmentClick`, `webSearchEnabled` with `onWebSearchToggle`, `onVoiceClick` with `listening`; H-34: `headingLevel={6}`; H-35: none (omit the props for the previous rendering); H-36: `userInitials="RD"` (or the signed-in user's initials) | Remove the prop; the panel takes its mode from the MUI theme (`ThemeProvider`). Runtime warning: yes. |
|
|
355
|
+
| 2.13.0 | `InsightEnginePanel` composer | **Before:** 'Add files or photos', 'Web search' and 'Dictate' always rendered and did nothing real (a console log, a stuck toggle, an invented listening state). **After:** They render only when wired: `onAttachmentClick`; `webSearchEnabled` with `onWebSearchToggle`; `onVoiceClick` with `listening`. **Verified by:** `InsightEnginePanel.test.tsx` | a, b, e | H-02: none (a second call with empty options was never documented; the JSDoc describes one callback); H-04: `demoMode={true}`; H-05: none (the documented controlled mode lost data; there is nothing to opt back into); H-07: `onAttachmentClick`, `webSearchEnabled` with `onWebSearchToggle`, `onVoiceClick` with `listening`; H-34: `headingLevel={6}`; H-35: none (omit the props for the previous rendering); H-36: `userInitials="RD"` (or the signed-in user's initials) | Pass the handlers (and state) for the controls your app supports. See [the AppLayout AI panel guide](docs/APPLAYOUT_AI_PANEL.md#no-dead-controls-h-07-h-08). |
|
|
356
|
+
| 2.13.0 | `InsightEnginePanel` title | **Before:** An `h6` without an id; the empty-state heading an `h6`. **After:** An `h2` (`headingLevel`, default 2) with an id (`titleId`, generated by default); the empty-state heading one level below. **Verified by:** `InsightEnginePanel.test.tsx` | a, b, e | H-02: none (a second call with empty options was never documented; the JSDoc describes one callback); H-04: `demoMode={true}`; H-05: none (the documented controlled mode lost data; there is nothing to opt back into); H-07: `onAttachmentClick`, `webSearchEnabled` with `onWebSearchToggle`, `onVoiceClick` with `listening`; H-34: `headingLevel={6}`; H-35: none (omit the props for the previous rendering); H-36: `userInitials="RD"` (or the signed-in user's initials) | Query the title with `getByRole('heading', { level: 2 })`; pass `headingLevel={6}` to keep the old outline. See [the AppLayout AI panel guide](docs/APPLAYOUT_AI_PANEL.md#the-title-is-a-real-heading-h-34). |
|
|
357
|
+
| 2.13.0 | `InsightEnginePanel` `placeholder`, `subtitle` | **Before:** Both ignored. **After:** `placeholder` sets the composer's placeholder; `subtitle` renders as a caption under the title, outside the heading. **Verified by:** `InsightEnginePanel.test.tsx` | a, b, e | H-02: none (a second call with empty options was never documented; the JSDoc describes one callback); H-04: `demoMode={true}`; H-05: none (the documented controlled mode lost data; there is nothing to opt back into); H-07: `onAttachmentClick`, `webSearchEnabled` with `onWebSearchToggle`, `onVoiceClick` with `listening`; H-34: `headingLevel={6}`; H-35: none (omit the props for the previous rendering); H-36: `userInitials="RD"` (or the signed-in user's initials) | Remove values you passed but did not want shown. |
|
|
358
|
+
| 2.13.0 | `InsightEnginePanel` `userInitials` | **Before:** Defaulted to 'RD': every user message showed invented initials. **After:** No default: the generic user avatar ('U'). **Verified by:** `InsightEnginePanel.test.tsx` | a, b, e | H-02: none (a second call with empty options was never documented; the JSDoc describes one callback); H-04: `demoMode={true}`; H-05: none (the documented controlled mode lost data; there is nothing to opt back into); H-07: `onAttachmentClick`, `webSearchEnabled` with `onWebSearchToggle`, `onVoiceClick` with `listening`; H-34: `headingLevel={6}`; H-35: none (omit the props for the previous rendering); H-36: `userInitials="RD"` (or the signed-in user's initials) | Pass `userInitials` with the signed-in user's initials. See [the AppLayout AI panel guide](docs/APPLAYOUT_AI_PANEL.md#no-invented-initials-h-36-h-08). |
|
|
359
|
+
| 2.13.0 | `AIChainOfThought` phase header | **Before:** `role="button"` div, `aria-label="<title> phase — <status>"`. **After:** `<button aria-expanded aria-controls>` named by its visible title, subtitle and duration, then the status. **Verified by:** `AIChainOfThought.test.tsx` (D-36) | b, c | D-36: none (the old triggers failed WCAG 2.5.3 and 4.1.2); D-37: none; D-38: none; D-50: none; WI-44: `announcements="stream"` restores the polite live container (outside a chat thread) | Replace `getByRole('button', { name: /^Plan phase/ })` with `getByRole('button', { name: /^Plan/ })`. |
|
|
360
|
+
| 2.13.0 | `AIChainOfThought` "Thought Process" bar | **Before:** `role="button"` div, `aria-label="Toggle thought process"`. **After:** `<button aria-expanded aria-controls>` named "Thought Process" and the elapsed time. **Verified by:** `AIChainOfThought.test.tsx` (D-36) | b, c | D-36: none (the old triggers failed WCAG 2.5.3 and 4.1.2); D-37: none; D-38: none; D-50: none; WI-44: `announcements="stream"` restores the polite live container (outside a chat thread) | Replace `getByRole('button', { name: 'Toggle thought process' })` with `getByRole('button', { name: /^Thought Process/ })`. |
|
|
361
|
+
| 2.13.0 | `AIChainOfThought` plan steps and tasks | **Before:** `div` stacks; a task's status was colour and an icon only. **After:** `<ol role="list">` with `<li>` rows; each task row starts with its status text. **Verified by:** `AIChainOfThought.test.tsx` (D-37) | b, c | D-36: none (the old triggers failed WCAG 2.5.3 and 4.1.2); D-37: none; D-38: none; D-50: none; WI-44: `announcements="stream"` restores the polite live container (outside a chat thread) | Query steps and tasks with `getAllByRole('listitem')`. Read a task's status from its text ("Failed", "In progress", ...), not its colour. |
|
|
362
|
+
| 2.13.0 | `AIChainOfThought` root | **Before:** `role="region"` "AI reasoning chain", `aria-live="polite"`; streamed text read token by token. **After:** `role="group"` "Reasoning for {query}", no `aria-live`; "Thinking" and "Response ready" announced once each. **Verified by:** `AIChainOfThought.test.tsx` (WI-44) | b, c | D-36: none (the old triggers failed WCAG 2.5.3 and 4.1.2); D-37: none; D-38: none; D-50: none; WI-44: `announcements="stream"` restores the polite live container (outside a chat thread) | Replace `getByRole('region', { name: 'AI reasoning chain' })` with `getByRole('group', { name: /Reasoning/ })`. For the old narration pass `announcements="stream"`. |
|
|
363
|
+
| 2.13.0 | `ChatThread`, `InsightEngine` assistant message actions | **Before:** Copy, thumbs, Share and Download on every message; only Copy did anything. **After:** Copy, plus each action whose handler is in `messageActions`. **Verified by:** `ChatThread.test.tsx` (G-05) | b, e | G-05: none; G-06: none for the thread (nested live regions and duplicate landmarks failed WCAG 1.3.1 and 4.1.3); `liveRegion` on a standalone AIMessage; G-16a: pass `inputValue` and `onInputChange` to keep controlling the text; G-16c: none (horizontal scrolling at 320px failed WCAG 1.4.10); G-16d: none (motion that ignores prefers-reduced-motion fails WCAG 2.3.3); G-37+: pass `headingOffset={0}` to keep a response's `#` as h1 inside a thread; a block without `citations: true` keeps its markers as text; D-39: none (the old toggle failed WCAG 4.1.2) | Pass `messageActions={{ onFeedback, onShare, onDownload }}` to keep the buttons (and make them work). |
|
|
364
|
+
| 2.13.0 | `AIMessage` content region | **Before:** `getByRole('region', { name: 'AI response' })`; content `aria-live="off"`/`"polite"`, `aria-busy`. **After:** Root `role="article"` named "Assistant response" (", <time>" with a timestamp); `aria-busy` on the article; content live only while streaming standalone (`liveRegion`). **Verified by:** `AIMessage.test.tsx` (G-06) | b, e | G-05: none; G-06: none for the thread (nested live regions and duplicate landmarks failed WCAG 1.3.1 and 4.1.3); `liveRegion` on a standalone AIMessage; G-16a: pass `inputValue` and `onInputChange` to keep controlling the text; G-16c: none (horizontal scrolling at 320px failed WCAG 1.4.10); G-16d: none (motion that ignores prefers-reduced-motion fails WCAG 2.3.3); G-37+: pass `headingOffset={0}` to keep a response's `#` as h1 inside a thread; a block without `citations: true` keeps its markers as text; D-39: none (the old toggle failed WCAG 4.1.2) | Replace with `getByRole('article', { name: /Assistant response/ })`; read `aria-busy` on the article. |
|
|
365
|
+
| 2.13.0 | `UserMessage` root | **Before:** No role. **After:** `role="article"` named "Your message". **Verified by:** `UserMessage.test.tsx` (G-06) | b, e | G-05: none; G-06: none for the thread (nested live regions and duplicate landmarks failed WCAG 1.3.1 and 4.1.3); `liveRegion` on a standalone AIMessage; G-16a: pass `inputValue` and `onInputChange` to keep controlling the text; G-16c: none (horizontal scrolling at 320px failed WCAG 1.4.10); G-16d: none (motion that ignores prefers-reduced-motion fails WCAG 2.3.3); G-37+: pass `headingOffset={0}` to keep a response's `#` as h1 inside a thread; a block without `citations: true` keeps its markers as text; D-39: none (the old toggle failed WCAG 4.1.2) | Query `getByRole('article', { name: 'Your message' })`. |
|
|
366
|
+
| 2.13.0 | `ChatThread` log | **Before:** `aria-relevant="additions text"`, `aria-atomic="false"`; each message's own live region. **After:** `aria-relevant="additions"`, no `aria-atomic`; no live region inside the log; "Response ready" announced after the log on completion. **Verified by:** `ChatThread.test.tsx` (G-06) | b, e | G-05: none; G-06: none for the thread (nested live regions and duplicate landmarks failed WCAG 1.3.1 and 4.1.3); `liveRegion` on a standalone AIMessage; G-16a: pass `inputValue` and `onInputChange` to keep controlling the text; G-16c: none (horizontal scrolling at 320px failed WCAG 1.4.10); G-16d: none (motion that ignores prefers-reduced-motion fails WCAG 2.3.3); G-37+: pass `headingOffset={0}` to keep a response's `#` as h1 inside a thread; a block without `citations: true` keeps its markers as text; D-39: none (the old toggle failed WCAG 4.1.2) | Assert `aria-relevant="additions"`; find the announcement in the regions after the log, not in the message. |
|
|
367
|
+
| 2.13.0 | `AIMessage` `onRenderBlock` (`AIMessageProps.onRenderBlock`, declared on `AIMessageOwnProps.onRenderBlock`) | **Before:** Custom block renderer passed to AIResponseRenderer. **After:** Deprecated alias of `renderBlock`, with a one-time development warning; removed in v3.0. **Verified by:** `AIMessage.test.tsx` (G-09) | b, e | G-05: none; G-06: none for the thread (nested live regions and duplicate landmarks failed WCAG 1.3.1 and 4.1.3); `liveRegion` on a standalone AIMessage; G-16a: pass `inputValue` and `onInputChange` to keep controlling the text; G-16c: none (horizontal scrolling at 320px failed WCAG 1.4.10); G-16d: none (motion that ignores prefers-reduced-motion fails WCAG 2.3.3); G-37+: pass `headingOffset={0}` to keep a response's `#` as h1 inside a thread; a block without `citations: true` keeps its markers as text; D-39: none (the old toggle failed WCAG 4.1.2) | Rename the prop to `renderBlock` (same function). Runtime warning: yes. |
|
|
368
|
+
| 2.13.0 | `AIResponseRenderer` `onRenderBlock` (`AIResponseRendererProps.onRenderBlock`) | **Before:** Custom block renderer. **After:** Deprecated alias of `renderBlock`; `renderBlock` wins when both are passed; one development warning; removed in v3.0. **Verified by:** `AIResponseRenderer.test.tsx` (G-09) | b, e | G-05: none; G-06: none for the thread (nested live regions and duplicate landmarks failed WCAG 1.3.1 and 4.1.3); `liveRegion` on a standalone AIMessage; G-16a: pass `inputValue` and `onInputChange` to keep controlling the text; G-16c: none (horizontal scrolling at 320px failed WCAG 1.4.10); G-16d: none (motion that ignores prefers-reduced-motion fails WCAG 2.3.3); G-37+: pass `headingOffset={0}` to keep a response's `#` as h1 inside a thread; a block without `citations: true` keeps its markers as text; D-39: none (the old toggle failed WCAG 4.1.2) | Rename the prop to `renderBlock` (same function). Runtime warning: yes. |
|
|
369
|
+
| 2.13.0 | `InsightEngine` composer without `inputValue` | **Before:** Always controlled (`inputValue` defaulted to `''`): typing did nothing. **After:** Uncontrolled when `inputValue` is omitted: types, sends the trimmed text through `onSendMessage`, clears. **Verified by:** `InsightEngine.characterization.test.tsx` (G-16a) | b, e | G-05: none; G-06: none for the thread (nested live regions and duplicate landmarks failed WCAG 1.3.1 and 4.1.3); `liveRegion` on a standalone AIMessage; G-16a: pass `inputValue` and `onInputChange` to keep controlling the text; G-16c: none (horizontal scrolling at 320px failed WCAG 1.4.10); G-16d: none (motion that ignores prefers-reduced-motion fails WCAG 2.3.3); G-37+: pass `headingOffset={0}` to keep a response's `#` as h1 inside a thread; a block without `citations: true` keeps its markers as text; D-39: none (the old toggle failed WCAG 4.1.2) | Nothing to do; to keep control, pass `inputValue` and `onInputChange`. A send handler that read `inputValue` should read its argument. |
|
|
370
|
+
| 2.13.0 | `InsightEngine` drawer and history column | **Before:** No `max-width` (overflowed narrow screens); a fixed 260px history column with a right border in every direction. **After:** `max-width: 100vw`; `historyWidth` (default 260) stacks above the thread below a 600px panel; divider on the inline end. **Verified by:** `InsightEngine.characterization.test.tsx` (G-16c) | b, e | G-05: none; G-06: none for the thread (nested live regions and duplicate landmarks failed WCAG 1.3.1 and 4.1.3); `liveRegion` on a standalone AIMessage; G-16a: pass `inputValue` and `onInputChange` to keep controlling the text; G-16c: none (horizontal scrolling at 320px failed WCAG 1.4.10); G-16d: none (motion that ignores prefers-reduced-motion fails WCAG 2.3.3); G-37+: pass `headingOffset={0}` to keep a response's `#` as h1 inside a thread; a block without `citations: true` keeps its markers as text; D-39: none (the old toggle failed WCAG 4.1.2) | Nothing to do. A root `sx` that set `& .MuiDrawer-paper { maxWidth }` still wins. |
|
|
371
|
+
| 2.13.0 | `InsightEngine` drawer width transition | **Before:** Animated under `prefers-reduced-motion: reduce` (only the theme backstop applied). **After:** `transition: none` under reduced motion. **Verified by:** `InsightEngine.characterization.test.tsx` (G-16d) | b, e | G-05: none; G-06: none for the thread (nested live regions and duplicate landmarks failed WCAG 1.3.1 and 4.1.3); `liveRegion` on a standalone AIMessage; G-16a: pass `inputValue` and `onInputChange` to keep controlling the text; G-16c: none (horizontal scrolling at 320px failed WCAG 1.4.10); G-16d: none (motion that ignores prefers-reduced-motion fails WCAG 2.3.3); G-37+: pass `headingOffset={0}` to keep a response's `#` as h1 inside a thread; a block without `citations: true` keeps its markers as text; D-39: none (the old toggle failed WCAG 4.1.2) | Nothing to do. |
|
|
372
|
+
| 2.13.0 | `AIResponseRenderer`, `AIMessage` text blocks with `citations: true` | **Before:** Markers rendered as text (`[^s1]`). **After:** Source chips numbered by `response.sources`; hidden while `streaming` if unresolved. **Verified by:** `AIResponseRenderer.test.tsx` (G-37+) | b, e | G-05: none; G-06: none for the thread (nested live regions and duplicate landmarks failed WCAG 1.3.1 and 4.1.3); `liveRegion` on a standalone AIMessage; G-16a: pass `inputValue` and `onInputChange` to keep controlling the text; G-16c: none (horizontal scrolling at 320px failed WCAG 1.4.10); G-16d: none (motion that ignores prefers-reduced-motion fails WCAG 2.3.3); G-37+: pass `headingOffset={0}` to keep a response's `#` as h1 inside a thread; a block without `citations: true` keeps its markers as text; D-39: none (the old toggle failed WCAG 4.1.2) | Query chips with `getByRole('button', { name: /^Source 1/ })`. |
|
|
373
|
+
| 2.13.0 | Markdown headings in a `ChatThread` message | **Before:** `#` rendered as h1. **After:** `#` rendered as h2 (`headingOffset` 1 in a thread). **Verified by:** `AIResponseRenderer.test.tsx` (G-37+) | b, e | G-05: none; G-06: none for the thread (nested live regions and duplicate landmarks failed WCAG 1.3.1 and 4.1.3); `liveRegion` on a standalone AIMessage; G-16a: pass `inputValue` and `onInputChange` to keep controlling the text; G-16c: none (horizontal scrolling at 320px failed WCAG 1.4.10); G-16d: none (motion that ignores prefers-reduced-motion fails WCAG 2.3.3); G-37+: pass `headingOffset={0}` to keep a response's `#` as h1 inside a thread; a block without `citations: true` keeps its markers as text; D-39: none (the old toggle failed WCAG 4.1.2) | Pass `headingOffset={0}` to AIMessage to keep h1, or query the heading by name, not level. |
|
|
374
|
+
| 2.13.0 | `InsightEngine` side-panel history toggle | **Before:** `<button>` without `type`, `aria-expanded` or `aria-controls`. **After:** `<button type="button" aria-expanded aria-controls>` (shared Disclosure). **Verified by:** `InsightEngine.characterization.test.tsx` (D-39) | b, e | G-05: none; G-06: none for the thread (nested live regions and duplicate landmarks failed WCAG 1.3.1 and 4.1.3); `liveRegion` on a standalone AIMessage; G-16a: pass `inputValue` and `onInputChange` to keep controlling the text; G-16c: none (horizontal scrolling at 320px failed WCAG 1.4.10); G-16d: none (motion that ignores prefers-reduced-motion fails WCAG 2.3.3); G-37+: pass `headingOffset={0}` to keep a response's `#` as h1 inside a thread; a block without `citations: true` keeps its markers as text; D-39: none (the old toggle failed WCAG 4.1.2) | Query it with `getByRole('button', { name: 'HISTORY (2)' })` and read `aria-expanded` instead of checking the chevron icon. |
|
|
375
|
+
| 2.13.0 | `AIChainOfThought` "Thought Process" bar | **Before:** Named "Thought Process" and the elapsed time. **After:** Named "Thought Process", the status word while a phase is active, the progress ("2 of 3 steps complete"), then the elapsed time. **Verified by:** `AIChainOfThought.test.tsx` (WI-10) | b | WI-10: none (the old indicators ignored prefers-reduced-motion, WCAG 2.3.3, and the toggle showed progress by colour alone, WCAG 1.4.1); WI-11a (alert): none (the old banner had no name and nothing announced a new failure, WCAG 4.1.3); WI-11b: none (a clickable Chip is not a sanctioned pressable, BEHAVIOR_CONTRACT §2); WI-11c: none (a clickable artifact card had no keyboard path or role, WCAG 2.1.1 and 4.1.2); WI-11d: none (a metric value rendered as an h6 heading polluted the page outline, WCAG 1.3.1); WI-11e: none (the references had no list semantics, WCAG 1.3.1) | Match the bar with `getByRole('button', { name: /^Thought Process/ })`, not an exact name. |
|
|
376
|
+
| 2.13.0 | `AIChainOfThought` busy indicators | **Before:** A `<span>` border spinner (`cot-spin`) and three dots (`cot-typing`). **After:** `.MuiCircularProgress-root` (`aria-hidden`) and two AIShimmer lines (`aria-hidden`) with a visually hidden "Thinking". **Verified by:** `AIChainOfThought.test.tsx` (WI-10) | b | WI-10: none (the old indicators ignored prefers-reduced-motion, WCAG 2.3.3, and the toggle showed progress by colour alone, WCAG 1.4.1); WI-11a (alert): none (the old banner had no name and nothing announced a new failure, WCAG 4.1.3); WI-11b: none (a clickable Chip is not a sanctioned pressable, BEHAVIOR_CONTRACT §2); WI-11c: none (a clickable artifact card had no keyboard path or role, WCAG 2.1.1 and 4.1.2); WI-11d: none (a metric value rendered as an h6 heading polluted the page outline, WCAG 1.3.1); WI-11e: none (the references had no list semantics, WCAG 1.3.1) | Query `.MuiCircularProgress-root` or the "Thinking" text instead of the old elements or keyframe names. |
|
|
377
|
+
| 2.13.0 | `AIChainOfThought` task error | **Before:** A `div` banner (`borderInlineStart` rule, `state.errorSubtle` tint) with the code, message and resolution. **After:** `.MuiAlert-root.MuiAlert-colorError` with `role="group"`, named by the code and message; the resolution inside it. **Verified by:** `AIChainOfThought.test.tsx` (WI-11a) | b | WI-10: none (the old indicators ignored prefers-reduced-motion, WCAG 2.3.3, and the toggle showed progress by colour alone, WCAG 1.4.1); WI-11a (alert): none (the old banner had no name and nothing announced a new failure, WCAG 4.1.3); WI-11b: none (a clickable Chip is not a sanctioned pressable, BEHAVIOR_CONTRACT §2); WI-11c: none (a clickable artifact card had no keyboard path or role, WCAG 2.1.1 and 4.1.2); WI-11d: none (a metric value rendered as an h6 heading polluted the page outline, WCAG 1.3.1); WI-11e: none (the references had no list semantics, WCAG 1.3.1) | Query the alert with `getByRole('group', { name: /Lock timeout/ })` or by its text; do not expect `role="alert"`. |
|
|
378
|
+
| 2.13.0 | `AIChainOfThought` plan Run action | **Before:** Clickable `MuiChip` (gradient pill, `role="button"` div). **After:** `<button class="MuiButton-contained">` named "Run". **Verified by:** `AIChainOfThought.test.tsx` (WI-11b) | b | WI-10: none (the old indicators ignored prefers-reduced-motion, WCAG 2.3.3, and the toggle showed progress by colour alone, WCAG 1.4.1); WI-11a (alert): none (the old banner had no name and nothing announced a new failure, WCAG 4.1.3); WI-11b: none (a clickable Chip is not a sanctioned pressable, BEHAVIOR_CONTRACT §2); WI-11c: none (a clickable artifact card had no keyboard path or role, WCAG 2.1.1 and 4.1.2); WI-11d: none (a metric value rendered as an h6 heading polluted the page outline, WCAG 1.3.1); WI-11e: none (the references had no list semantics, WCAG 1.3.1) | Query `getByRole('button', { name: 'Run' })`; replace `.MuiChip-*` selectors with `.MuiButton-*`. |
|
|
379
|
+
| 2.13.0 | `AIChainOfThought` clickable artifact | **Before:** A `div` card with `onClick` (no role, not focusable). **After:** A `<button>` overlay named by the label covers the card; `onClick` runs on click, Enter and Space. **Verified by:** `AIChainOfThought.test.tsx` (WI-11c) | b | WI-10: none (the old indicators ignored prefers-reduced-motion, WCAG 2.3.3, and the toggle showed progress by colour alone, WCAG 1.4.1); WI-11a (alert): none (the old banner had no name and nothing announced a new failure, WCAG 4.1.3); WI-11b: none (a clickable Chip is not a sanctioned pressable, BEHAVIOR_CONTRACT §2); WI-11c: none (a clickable artifact card had no keyboard path or role, WCAG 2.1.1 and 4.1.2); WI-11d: none (a metric value rendered as an h6 heading polluted the page outline, WCAG 1.3.1); WI-11e: none (the references had no list semantics, WCAG 1.3.1) | Query `getByRole('button', { name: '<label>' })` and click it instead of the card's text. |
|
|
380
|
+
| 2.13.0 | `AIChainOfThought` artifact count | **Before:** A circular `div` with the number. **After:** `.MuiChip-root` (outlined, small) with the number. **Verified by:** `AIChainOfThought.test.tsx` (WI-11c) | b | WI-10: none (the old indicators ignored prefers-reduced-motion, WCAG 2.3.3, and the toggle showed progress by colour alone, WCAG 1.4.1); WI-11a (alert): none (the old banner had no name and nothing announced a new failure, WCAG 4.1.3); WI-11b: none (a clickable Chip is not a sanctioned pressable, BEHAVIOR_CONTRACT §2); WI-11c: none (a clickable artifact card had no keyboard path or role, WCAG 2.1.1 and 4.1.2); WI-11d: none (a metric value rendered as an h6 heading polluted the page outline, WCAG 1.3.1); WI-11e: none (the references had no list semantics, WCAG 1.3.1) | Query the chip or the "Generated Artifacts" row text. |
|
|
381
|
+
| 2.13.0 | `AIChainOfThought` insight metrics | **Before:** `div`s with an `<h6>` value and a caption label. **After:** `<ul role="list" aria-label="Key metrics">`; each `<li>` holds a `<span>` value and a caption label. **Verified by:** `AIChainOfThought.test.tsx` (WI-11d) | b | WI-10: none (the old indicators ignored prefers-reduced-motion, WCAG 2.3.3, and the toggle showed progress by colour alone, WCAG 1.4.1); WI-11a (alert): none (the old banner had no name and nothing announced a new failure, WCAG 4.1.3); WI-11b: none (a clickable Chip is not a sanctioned pressable, BEHAVIOR_CONTRACT §2); WI-11c: none (a clickable artifact card had no keyboard path or role, WCAG 2.1.1 and 4.1.2); WI-11d: none (a metric value rendered as an h6 heading polluted the page outline, WCAG 1.3.1); WI-11e: none (the references had no list semantics, WCAG 1.3.1) | Query `getByRole('list', { name: 'Key metrics' })` and its items instead of `heading` roles. |
|
|
382
|
+
| 2.13.0 | `AIChainOfThought` insight references | **Before:** A stack of `div` cards with a coloured badge caption. **After:** `<ol role="list">`; each `<li>` holds an `AISource` card (badge as `.MuiChip-root`). **Verified by:** `AIChainOfThought.test.tsx` (WI-11e) | b | WI-10: none (the old indicators ignored prefers-reduced-motion, WCAG 2.3.3, and the toggle showed progress by colour alone, WCAG 1.4.1); WI-11a (alert): none (the old banner had no name and nothing announced a new failure, WCAG 4.1.3); WI-11b: none (a clickable Chip is not a sanctioned pressable, BEHAVIOR_CONTRACT §2); WI-11c: none (a clickable artifact card had no keyboard path or role, WCAG 2.1.1 and 4.1.2); WI-11d: none (a metric value rendered as an h6 heading polluted the page outline, WCAG 1.3.1); WI-11e: none (the references had no list semantics, WCAG 1.3.1) | Query `getByRole('list')` and its `listitem`s, or the reference text; replace badge style selectors with the chip. |
|
|
383
|
+
| 2.13.0 | `AIChainOfThought` response footer | **Before:** Copy, thumbs, Share and Download always shown. **After:** Copy always; the thumbs, Share, Download and Regenerate only with their handlers (`onFeedback` / `onFeedbackChange` / `feedback`, `onShare`, `onDownload`, `onRegenerate`). **Verified by:** `AIChainOfThought.test.tsx` (G-21) | b, e | G-21: none (the removed thumbs, Share and Download had no handler; pass the handler to show one); WI-12, WI-14a, WI-11f: none (the values come from the theme's roles; override the roles); WI-14b: none (props the root used to drop now take effect; stop passing the ones you do not want) | Pass the handler for every action you want shown. Copy without `onCopy` now writes to the clipboard. |
|
|
384
|
+
| 2.13.0 | `AIChainOfThought` sources, related questions, suggested actions | **Before:** Sibling boxes after the message with a 48px inset. **After:** Inside AIMessage's `attachments`, at the content column. **Verified by:** `AIChainOfThought.test.tsx` (G-21) | b, e | G-21: none (the removed thumbs, Share and Download had no handler; pass the handler to show one); WI-12, WI-14a, WI-11f: none (the values come from the theme's roles; override the roles); WI-14b: none (props the root used to drop now take effect; stop passing the ones you do not want) | Selectors that reached them as siblings of the article find them inside it. |
|
|
385
|
+
| 2.13.0 | `AIChainOfThought` colours | **Before:** Colour literals, `palette.mode` branches and static `aiTokens` colours; inline code and the insight highlight under 4.5:1 in some themes. **After:** Every colour from the palette resolver (E-26 rows); code and highlights on paper chips (4.5:1 or more in every theme); listed shade shifts. **Verified by:** `AIChainOfThought.test.tsx`, `contrast-matrix.test.ts` (WI-12) | b, e | G-21: none (the removed thumbs, Share and Download had no handler; pass the handler to show one); WI-12, WI-14a, WI-11f: none (the values come from the theme's roles; override the roles); WI-14b: none (props the root used to drop now take effect; stop passing the ones you do not want) | Override the theme's roles to restyle; update snapshot tests for the listed shades. |
|
|
386
|
+
| 2.13.0 | `AIChainOfThought` type, spacing and motion | **Before:** Inline rem sizes, physical spacing, a global `cot-fadeInUp` keyframes, entrances that ignored reduced motion; a 72px body indent at every width. **After:** AI type roles (listed snaps), logical spacing, scoped keyframes with motion tokens, every entrance stopped under reduced motion; 16px body indent below 360px. **Verified by:** `AIChainOfThought.test.tsx` (WI-14a) | b, e | G-21: none (the removed thumbs, Share and Download had no handler; pass the handler to show one); WI-12, WI-14a, WI-11f: none (the values come from the theme's roles; override the roles); WI-14b: none (props the root used to drop now take effect; stop passing the ones you do not want) | Nothing; remove CSS that targeted `cot-fadeInUp`. |
|
|
387
|
+
| 2.13.0 | `AIChainOfThought` root props | **Before:** `className`, `id`, `data-*`, `aria-*` and event handlers were dropped. **After:** Spread on the root; `role`, `aria-busy` and the live attributes stay the component's; NEW `slotProps`. **Verified by:** `AIChainOfThought.test.tsx` (WI-14b) | b, e | G-21: none (the removed thumbs, Share and Download had no handler; pass the handler to show one); WI-12, WI-14a, WI-11f: none (the values come from the theme's roles; override the roles); WI-14b: none (props the root used to drop now take effect; stop passing the ones you do not want) | Stop passing props you did not want applied. |
|
|
388
|
+
| 2.13.0 | `AIChainOfThought` insight confidence | **Before:** A hand-rolled 80px progress bar with the percent. **After:** AIConfidenceBadge `variant="bar"` ('Confidence: Medium, 82%'), levels from the provider's thresholds. **Verified by:** `AIChainOfThought.test.tsx` (WI-11f) | b, e | G-21: none (the removed thumbs, Share and Download had no handler; pass the handler to show one); WI-12, WI-14a, WI-11f: none (the values come from the theme's roles; override the roles); WI-14b: none (props the root used to drop now take effect; stop passing the ones you do not want) | Nothing; tests that queried a progressbar find the badge's text. |
|
|
389
|
+
| 2.13.0 | `InsightEnginePanel` message actions | **Before:** Every answer showed Copy, thumbs, Share and Regenerate; only Copy did anything. **After:** Copy always shows and writes to the clipboard; the thumbs show with `messageActions.onFeedback` (the detailed form with `onFeedbackSubmit`), Share with `onShare`, Download with `onDownload`, Regenerate with `onRegenerate` or `messageActions.onRegenerate`. **Verified by:** `InsightEnginePanel.test.tsx` (the AIChatMessage prop migration rows) | b, c, e | WH-08: none (the removed thumbs, Share and Regenerate had no handler behind them, so nothing works differently without them; pass `messageActions` or `onRegenerate` to show an action); WH-12: none (the colours follow the theme; override them through the theme's AI roles) | Pass the handlers for the actions your app supports. See [the AppLayout AI panel guide](docs/APPLAYOUT_AI_PANEL.md#no-dead-controls-h-07-h-08). |
|
|
390
|
+
| 2.13.0 | `InsightEnginePanel` action names | **Before:** Copy was named 'Copy message'; the thumbs 'Mark as good response' / 'Mark as bad response'; Share 'Share message'. **After:** 'Copy response', 'Mark as good response', 'Mark as poor response', 'Share response', 'Regenerate response' (AIMessage's names, `ai.message.*`). **Verified by:** `InsightEnginePanel.test.tsx` | b, c, e | WH-08: none (the removed thumbs, Share and Regenerate had no handler behind them, so nothing works differently without them; pass `messageActions` or `onRegenerate` to show an action); WH-12: none (the colours follow the theme; override them through the theme's AI roles) | Update tests or automation that find the actions by name. |
|
|
391
|
+
| 2.13.0 | `InsightEnginePanel` typing indicator | **Before:** `isTyping` showed AITypingIndicator 'Searching...' (not announced). **After:** A streaming entry: a decorative shimmer in an aria-busy assistant article, not announced, with no actions. **Verified by:** `InsightEnginePanel.test.tsx` | b, c, e | WH-08: none (the removed thumbs, Share and Regenerate had no handler behind them, so nothing works differently without them; pass `messageActions` or `onRegenerate` to show an action); WH-12: none (the colours follow the theme; override them through the theme's AI roles) | None. |
|
|
392
|
+
| 2.13.0 | `InsightEnginePanel` sources | **Before:** A message's `sources` showed an expanded AISourcesPanel. **After:** The sources start collapsed in the message's attachments, as in InsightEngine. **Verified by:** `InsightEnginePanel.test.tsx` | b, c, e | WH-08: none (the removed thumbs, Share and Regenerate had no handler behind them, so nothing works differently without them; pass `messageActions` or `onRegenerate` to show an action); WH-12: none (the colours follow the theme; override them through the theme's AI roles) | None. See [the AppLayout AI panel guide](docs/APPLAYOUT_AI_PANEL.md#sources-start-collapsed-h-08). |
|
|
393
|
+
| 2.13.0 | `InsightEnginePanel` avatars | **Before:** The answers used the 24px header avatar; without `userInitials` the user avatar showed a 'U'. **After:** `aiAvatar` is forwarded to the answers (default: AIMessage's 32px AI avatar); `userInitials` to the user avatar; without it, UserAvatar's user icon. The header keeps its avatar. **Verified by:** `InsightEnginePanel.test.tsx` | b, c, e | WH-08: none (the removed thumbs, Share and Regenerate had no handler behind them, so nothing works differently without them; pass `messageActions` or `onRegenerate` to show an action); WH-12: none (the colours follow the theme; override them through the theme's AI roles) | None. See [the AppLayout AI panel guide](docs/APPLAYOUT_AI_PANEL.md#no-invented-initials-h-36-h-08). |
|
|
394
|
+
| 2.13.0 | `InsightEnginePanel` empty state | **Before:** The empty state was a 400px-high block in the scroll area. **After:** The thread's empty state, the same content, filling the log; its heading one level under the title. **Verified by:** `InsightEnginePanel.test.tsx` | b, c, e | WH-08: none (the removed thumbs, Share and Regenerate had no handler behind them, so nothing works differently without them; pass `messageActions` or `onRegenerate` to show an action); WH-12: none (the colours follow the theme; override them through the theme's AI roles) | None. |
|
|
395
|
+
| 2.13.0 | `InsightEnginePanel` scrolling | **Before:** The panel scrolled its own container on every update. **After:** ChatThread scrolls the log only while the reader is at the end, instantly under reduced motion, and never moves focus. **Verified by:** `InsightEnginePanel.test.tsx` | b, c, e | WH-08: none (the removed thumbs, Share and Regenerate had no handler behind them, so nothing works differently without them; pass `messageActions` or `onRegenerate` to show an action); WH-12: none (the colours follow the theme; override them through the theme's AI roles) | None. |
|
|
396
|
+
| 2.13.0 | `UserMessage` text colour | **Before:** The text took the theme's body2 colour (text.secondary) on the primary bubble. **After:** The text takes the bubble's `primary.contrastText` (4.5:1 or more in light, dark and every preset). **Verified by:** `UserMessage.test.tsx` | b, c, e | WH-08: none (the removed thumbs, Share and Regenerate had no handler behind them, so nothing works differently without them; pass `messageActions` or `onRegenerate` to show an action); WH-12: none (the colours follow the theme; override them through the theme's AI roles) | None. |
|
|
397
|
+
| 2.13.0 | `ChatThread` empty streaming message | **Before:** An empty streaming assistant message showed an unavailable Copy and Regenerate and enabled thumbs. **After:** It shows no actions until its content arrives. **Verified by:** `ChatThread.test.tsx` | b, c, e | WH-08: none (the removed thumbs, Share and Regenerate had no handler behind them, so nothing works differently without them; pass `messageActions` or `onRegenerate` to show an action); WH-12: none (the colours follow the theme; override them through the theme's AI roles) | None. |
|
|
398
|
+
| 2.13.0 | `InsightEnginePanel` colours | **Before:** The surface, the empty-state tile and icon and the Pro chip used the static light AI tokens in every mode and preset. **After:** They follow the theme's AI roles (mode, preset, brand); the Pro chip text meets 4.5:1 on its fill. **Verified by:** `InsightEnginePanel.test.tsx` (colours, H-12) | b, c, e | WH-08: none (the removed thumbs, Share and Regenerate had no handler behind them, so nothing works differently without them; pass `messageActions` or `onRegenerate` to show an action); WH-12: none (the colours follow the theme; override them through the theme's AI roles) | None. |
|
|
399
|
+
| 2.13.0 | `InsightEnginePanel` history toggle | **Before:** The open toggle differed from the closed one by its icon colour only. **After:** It also gets the AI emphasis background (a second cue); its name still says Show history or Hide history. **Verified by:** `InsightEnginePanel.test.tsx` | b, c, e | WH-08: none (the removed thumbs, Share and Regenerate had no handler behind them, so nothing works differently without them; pass `messageActions` or `onRegenerate` to show an action); WH-12: none (the colours follow the theme; override them through the theme's AI roles) | None. |
|
|
400
|
+
| 2.13.0 | `InsightEnginePanelOwnProps.isTyping` (`InsightEnginePanel` `isTyping`) | **Before:** `isTyping` showed the streaming entry. **After:** `streaming` does; `isTyping` still works, with a one-time development warning, until v3.0. **Verified by:** `InsightEnginePanel.test.tsx` (H-50+) | b, c, e | WH-08: none (the removed thumbs, Share and Regenerate had no handler behind them, so nothing works differently without them; pass `messageActions` or `onRegenerate` to show an action); WH-12: none (the colours follow the theme; override them through the theme's AI roles) | Rename `isTyping` to `streaming`. See [the AppLayout AI panel guide](docs/APPLAYOUT_AI_PANEL.md#istyping-becomes-streaming-h-50). |
|
|
401
|
+
| 2.13.0 | `ChatMessage.isLoading` (`InsightEnginePanel` message `isLoading`) | **Before:** A message's `isLoading` marked it as loading. **After:** Its `streaming` field does (it wins when both are set); `isLoading` still works, with a one-time development warning, until v3.0. **Verified by:** `InsightEnginePanel.test.tsx`, `toChatThreadMessage.test.ts` | b, c, e | WH-08: none (the removed thumbs, Share and Regenerate had no handler behind them, so nothing works differently without them; pass `messageActions` or `onRegenerate` to show an action); WH-12: none (the colours follow the theme; override them through the theme's AI roles) | Rename the field to `streaming`. |
|
|
402
|
+
| 2.13.0 | `AIMessage` action tooltips | **Before:** Copy, Share, Download and Regenerate showed 'Copy', 'Share', 'Download' and 'Regenerate' while their names were '... response'. **After:** Each tooltip equals its name: 'Copy response', 'Share response', 'Download response', 'Regenerate response' (`ai.message.*Response`); an unavailable action shows its name, then the reason. **Verified by:** `AIMessage.test.tsx` (G-28) | b | G-28: `AIConfigProvider strings={{ message: { copyResponse: 'Copy', ... } }}` restores a short tooltip, which then also becomes the name | Tests that find a tooltip by the short text use the '... response' text, or find the button by its name (unchanged). |
|
|
403
|
+
| 2.13.0 | `AIClarificationPrompt` questions (R17) | **Before:** 'Is this what you mean by “term”?' / '“term” can mean N things. Which one do you mean?', 'Which term do you want?', 'What do you mean by “term”?' **After:** 'Did you mean term?', 'What about term?', 'What did you mean by term?' (also the group's accessible name) **Verified by:** `hitlRequirementCopy.test.tsx`, `AIClarificationPrompt.test.tsx` (G-42+) | b | `AIConfigProvider strings` with the previous text for any key (a component's own prop, where it has one, still wins) | Tests that find the group by the old question use the new text. |
|
|
404
|
+
| 2.13.0 | `AIVerificationBadge` golden label (R26) | **Before:** 'Answered from verified dataset' **After:** 'Answered from the verified dataset' **Verified by:** `hitlRequirementCopy.test.tsx`, `AIVerificationBadge.test.tsx` (G-42+) | b | `AIConfigProvider strings` with the previous text for any key (a component's own prop, where it has one, still wins) | Update tests that query the old label. |
|
|
405
|
+
| 2.13.0 | AI string defaults, sentence case | **Before:** 'Thought Process', 'Generated Artifacts', 'Sources & References', 'Add to Project', 'Related Questions', 'HISTORY', 'HISTORY (n)' **After:** 'Thought process', 'Generated artifacts', 'Sources & references', 'Add to project', 'Related questions', 'History', 'History (n)' (still upper case on screen, from CSS); accessible names follow **Verified by:** `aiStrings.defaults.test.ts`, `strings.defaults.test.tsx` (E-47+) | b | `AIConfigProvider strings` with the previous text for any key (a component's own prop, where it has one, still wins) | Tests that query the old text use the new one. |
|
|
406
|
+
| 2.13.0 | AI string defaults, ellipsis and plural | **Before:** 'Ask anything...', 'Searching...', 'Listening...', 'Listening... Speak now', 'Search threads...'; '1 threads' **After:** The one-character ellipsis ('Ask anything…' and the others); '1 thread' **Verified by:** `aiStrings.defaults.test.ts` (E-47+) | b | `AIConfigProvider strings` with the previous text for any key (a component's own prop, where it has one, still wins) | Tests that query the old text use the new one. |
|
|
407
|
+
| 2.13.0 | `ThreadHistorySidebar` colours | **Before:** A dark-only 95% paper and a `palette.mode` branch; the selected row 0.15 of the light purple in dark mode, no hover change; presets and brand overrides did not reach it. **After:** `background.paper`; the selected row, its border, glyph and the search focus outline from `ai.primary` (3:1 or more in both modes and every preset); selected + hover opacity under the pointer; logical borders. **Verified by:** `ThreadHistorySidebar.test.tsx`, `contrast-matrix.test.ts` (WH-12) | b, c | WH-12: none (the colours come from the theme's AI and `favorite` roles; override those roles in the theme); WH-48: none (the sizes come from the AI type roles; override them through `slotProps`/`sx`) | Nothing, unless you matched the old colours in tests or overrides: override the theme's AI roles instead. |
|
|
408
|
+
| 2.13.0 | `ThreadHistorySidebar` star | **Before:** The starred thread's filled star was the `#F59E0B` literal (below 3:1 on the light paper). **After:** The `favorite` role (light `#B45309`, dark `#FDE68A`); the filled glyph stays the non-colour cue. **Verified by:** `ThreadHistorySidebar.test.tsx`, `contrast-matrix.test.ts` (WH-12) | b, c | WH-12: none (the colours come from the theme's AI and `favorite` roles; override those roles in the theme); WH-48: none (the sizes come from the AI type roles; override them through `slotProps`/`sx`) | Override `palette.favorite` to change it. |
|
|
409
|
+
| 2.13.0 | `AIFab` | **Before:** White sparkle on the static coral-to-purple gradient; a static coral and purple glow. **After:** The same gradient through the resolver (`ai.gradient.brandDiagonal`, the same in light and dark; a brand or `ai.avatarFill` override reaches it) under the sparkle in `ai.onPrimaryFill`, no ring; the glow from `ai.focusRing`; the disabled FAB in the disabled colours. **Verified by:** `AIFab.test.tsx` (WH-12, owner decision 2026-10-09) | b, c | WH-12: none (the colours come from the theme's AI and `favorite` roles; override those roles in the theme); WH-48: none (the sizes come from the AI type roles; override them through `slotProps`/`sx`) | Nothing; override the theme's AI roles to restyle it. |
|
|
410
|
+
| 2.13.0 | `ThreadHistorySidebar` type sizes | **Before:** Group label and thread preview 0.7rem (11.2px), row glyph 18px, empty-state folder 40px. **After:** Group label `meta` 11px with `letterSpacing.wider`, preview `caption` 12px, row glyph 16px, folder 36px. **Verified by:** `ThreadHistorySidebar.test.tsx` (WH-48) | b, c | WH-12: none (the colours come from the theme's AI and `favorite` roles; override those roles in the theme); WH-48: none (the sizes come from the AI type roles; override them through `slotProps`/`sx`) | Nothing; style through `sx` if you depended on the old sizes. |
|
|
411
|
+
| 2.13.0 | `ChatThread` per-message sources and related questions | **Before:** Sibling boxes after the message at a 33.5px physical inset. **After:** Inside the message article (AIMessage `attachments`), above its footer, starting at the message text (44px, logical, mirrored in RTL); thread-level related questions and suggested actions at the same 44px. **Verified by:** `ChatThread.test.tsx` (G-13) | b | G-13 badges: `getMessageSlots={() => ({ badges: null })}`; G-13 alignment: none (the attachments start at the message text, the documented column); G-38+ auto-scroll: none (it follows only a reader at the end and never moves focus); G-40+: none (rendering only) | Selectors that reached them as siblings of the article find them inside it. |
|
|
412
|
+
| 2.13.0 | `ChatThread` metadata badges (G-OD11) | **Before:** `response.metadata.confidence` and `.verification` were not shown. **After:** They show as AIConfidenceBadge and AIVerificationBadge in the message footer. **Verified by:** `ChatThread.test.tsx` (G-13) | b | G-13 badges: `getMessageSlots={() => ({ badges: null })}`; G-13 alignment: none (the attachments start at the message text, the documented column); G-38+ auto-scroll: none (it follows only a reader at the end and never moves focus); G-40+: none (rendering only) | Suppress with `getMessageSlots={() => ({ badges: null })}`. |
|
|
413
|
+
| 2.13.0 | `ChatThread` response sources and related questions | **Before:** A response's own `sources` and `relatedQuestions` were not shown. **After:** Shown when the message has none of its own (the message's win, with a development warning). **Verified by:** `ChatThread.test.tsx` (G-13) | b | G-13 badges: `getMessageSlots={() => ({ badges: null })}`; G-13 alignment: none (the attachments start at the message text, the documented column); G-38+ auto-scroll: none (it follows only a reader at the end and never moves focus); G-40+: none (rendering only) | Remove the duplicates from one side. |
|
|
414
|
+
| 2.13.0 | `ChatThread` scrolling | **Before:** The log did not follow new messages; a long thread opened at its start. **After:** The log follows new messages while the reader is at the end (instant under reduced motion, focus never moves); a long thread opens at its latest message. **Verified by:** `ChatThread.test.tsx` (G-38+) | b | G-13 badges: `getMessageSlots={() => ({ badges: null })}`; G-13 alignment: none (the attachments start at the message text, the documented column); G-38+ auto-scroll: none (it follows only a reader at the end and never moves focus); G-40+: none (rendering only) | Remove app code that scrolled the log; scroll the log yourself only for a different behaviour. |
|
|
415
|
+
| 2.13.0 | `ChatThread` empty assistant content | **Before:** An empty `Typography` rendered for an assistant message with empty `content`. **After:** No children: a streaming message shows AIMessage's placeholder, a complete one renders nothing. **Verified by:** `ChatThread.test.tsx` (G-38+) | b | G-13 badges: `getMessageSlots={() => ({ badges: null })}`; G-13 alignment: none (the attachments start at the message text, the documented column); G-38+ auto-scroll: none (it follows only a reader at the end and never moves focus); G-40+: none (rendering only) | Nothing. |
|
|
416
|
+
| 2.13.0 | `ChatThread` rendering | **Before:** Every row re-rendered on every streamed token. **After:** Rows are memoized: only the changing message re-renders; handlers may be inline, render-time functions and avatars are compared by identity. **Verified by:** `ChatThread.perf.test.tsx` (G-40+) | b | G-13 badges: `getMessageSlots={() => ({ badges: null })}`; G-13 alignment: none (the attachments start at the message text, the documented column); G-38+ auto-scroll: none (it follows only a reader at the end and never moves focus); G-40+: none (rendering only) | Keep `getActions`, `getMessageSlots`, `renderBlock` and avatar elements stable in long threads. |
|
|
417
|
+
| 2.13.0 | `InsightEngine` on phones (below `sm`) | **Before:** A persistent drawer at `sidepanelWidth`/`expandedWidth` (clamped to the viewport), no focus trap, Escape did nothing, the expand toggle showed. **After:** A full-width modal dialog: focus trap, Escape and backdrop call `onClose('escapeKeyDown' / 'backdropClick')`, focus returns to the opener, no expand toggle; the ref is the Modal root. **Verified by:** `InsightEngine.test.tsx` (WH-15) | a, b | WH-15: `mobileVariant="persistent"` keeps the non-modal drawer on phones; G-15: none (`onError` was documented and never called); WH-16: none (a failed send was silently lost) | Nothing to do. Pass `mobileVariant="persistent"` to keep the non-modal drawer (full width). |
|
|
418
|
+
| 2.13.0 | `InsightEngine` drawer paper | **Before:** No role or name. **After:** `role="region"` (desktop) or `role="dialog"` + `aria-modal` (phones), named by the title heading. **Verified by:** `InsightEngine.characterization.test.tsx` ([WH-15], the [G-03] DOM snapshot) | a, b | WH-15: `mobileVariant="persistent"` keeps the non-modal drawer on phones; G-15: none (`onError` was documented and never called); WH-16: none (a failed send was silently lost) | Nothing to do. |
|
|
419
|
+
| 2.13.0 | `InsightEngine` `onError` | **Before:** Documented but never called. **After:** Called with an `Error` when a message action (Regenerate, Copy, an app action) or a block event fails; a non-Error value arrives wrapped with `cause`. **Verified by:** `InsightEngine.test.tsx` (G-15) | a, b | WH-15: `mobileVariant="persistent"` keeps the non-modal drawer on phones; G-15: none (`onError` was documented and never called); WH-16: none (a failed send was silently lost) | Check that your handler is ready to run; it no longer only sits unused. See [the AppLayout AI panel guide](docs/APPLAYOUT_AI_PANEL.md#onerror-now-fires-g-15-h-16-insightengine-and-the-pattern). |
|
|
420
|
+
| 2.13.0 | `InsightEngine` `onSendMessage` returning a promise | **Before:** The promise was ignored; a rejection was an unhandled rejection and the user saw nothing. **After:** Pending Send (aria-disabled, aria-busy, spinner); a rejection shows an inline alert with 'Try again' and reaches `onError` (or `reportError`). **Verified by:** `InsightEngine.test.tsx` (WH-16) | a, b | WH-15: `mobileVariant="persistent"` keeps the non-modal drawer on phones; G-15: none (`onError` was documented and never called); WH-16: none (a failed send was silently lost) | Nothing to do; return your request's promise to get the pending and error UI. See [the AppLayout AI panel guide](docs/APPLAYOUT_AI_PANEL.md#onerror-now-fires-g-15-h-16-insightengine-and-the-pattern). |
|
|
421
|
+
| 2.13.0 | `InsightEngine` `onSendMessage` timing | **Before:** Called synchronously in the key or click handler. **After:** Called from a microtask right after (useAsyncAction, ADR-07). **Verified by:** `InsightEngine.test.tsx` (WH-16) | a, b | WH-15: `mobileVariant="persistent"` keeps the non-modal drawer on phones; G-15: none (`onError` was documented and never called); WH-16: none (a failed send was silently lost) | Nothing to do, unless a test reads the call synchronously without awaiting the event. |
|
|
422
|
+
| 2.13.0 | `ListDetailTemplate` (list structure) | **Before:** Items were direct children of the `ul`, separated by `<li class="MuiDivider-root" role="separator">` elements. **After:** Each item is wrapped in an MUI `li` (`.MuiListItem-root`); there are no separator `li` elements. **Verified by:** `ListDetailTemplate.test.tsx` | a, b, e | none (each change repairs a WCAG 2.2 AA failure or renders what the JSDoc documents; the visible defaults are unchanged); none (both corrections render what the props' JSDoc documents; pass `selectedId` to keep a detail on screen); none (custom rows could not open the detail on a small screen, and closing it dropped focus to the page; nothing to restore) | Tests that count `li` elements: expect one per item (`getAllByRole('listitem')`). Tests that query `li.MuiDivider-root` or `role="separator"`: remove them; the separator is a CSS border. A `renderListItem` that returned its own `ListItem` now nests `li` elements: return the row content (a button) instead. |
|
|
423
|
+
| 2.13.0 | `ListDetailTemplate` (badge) | **Before:** `badge={0}` rendered a bare "0" next to the title, outside any chip. **After:** `badge={0}` renders a chip showing 0; `badge=""` renders nothing. **Verified by:** `ListDetailTemplate.test.tsx` | a, b, e | none (each change repairs a WCAG 2.2 AA failure or renders what the JSDoc documents; the visible defaults are unchanged); none (both corrections render what the props' JSDoc documents; pass `selectedId` to keep a detail on screen); none (custom rows could not open the detail on a small screen, and closing it dropped focus to the page; nothing to restore) | Pass `badge={undefined}` to show no chip. |
|
|
424
|
+
| 2.13.0 | `ListDetailTemplate` (names) | **Before:** Clear search, filter and Back were unnamed buttons with no tooltip; the search had no name; selection, unread and status were visual only. **After:** They are named from `labels` with same-text tooltips; the search is a named `searchbox`; the selected item has `aria-current="true"`; unread and status are hidden text in the item's name. **Verified by:** `ListDetailTemplate.test.tsx` | a, b, e | none (each change repairs a WCAG 2.2 AA failure or renders what the JSDoc documents; the visible defaults are unchanged); none (both corrections render what the props' JSDoc documents; pass `selectedId` to keep a detail on screen); none (custom rows could not open the detail on a small screen, and closing it dropped focus to the page; nothing to restore) | Tests that select items by exact accessible name: match the title with a regular expression (`{ name: /Project update/ }`), since the name now ends with ", Unread" or the status. |
|
|
425
|
+
| 2.13.0 | `ListDetailTemplate` (empty detail) | **Before:** A `detailContent` stayed on screen with nothing selected, so `emptyDetailContent` never showed. **After:** With no `selectedId`, the pane shows `emptyDetailContent` (or "Select an item to view details"). **Verified by:** `ListDetailTemplate.test.tsx` | a, b, e | none (each change repairs a WCAG 2.2 AA failure or renders what the JSDoc documents; the visible defaults are unchanged); none (both corrections render what the props' JSDoc documents; pass `selectedId` to keep a detail on screen); none (custom rows could not open the detail on a small screen, and closing it dropped focus to the page; nothing to restore) | If the detail should show without a selection, pass the shown record's id as `selectedId`. |
|
|
426
|
+
| 2.13.0 | `ListDetailTemplate` (listActions) | **Before:** With `showSearch={false}`, `listActions` were not rendered. **After:** They render in their own row of the list header. **Verified by:** `ListDetailTemplate.test.tsx` | a, b, e | none (each change repairs a WCAG 2.2 AA failure or renders what the JSDoc documents; the visible defaults are unchanged); none (both corrections render what the props' JSDoc documents; pass `selectedId` to keep a detail on screen); none (custom rows could not open the detail on a small screen, and closing it dropped focus to the page; nothing to restore) | Nothing; remove any workaround that put the actions in `listHeader`. |
|
|
427
|
+
| 2.13.0 | `ListDetailTemplate` (custom rows on a small screen) | **Before:** A `renderListItem` row could not open the detail drawer: only default rows did. **After:** A row that spreads `helpers.getItemProps()` (or calls `helpers.select()`) selects the item and opens the drawer. **Verified by:** `ListDetailTemplate.test.tsx` | a, b, e | none (each change repairs a WCAG 2.2 AA failure or renders what the JSDoc documents; the visible defaults are unchanged); none (both corrections render what the props' JSDoc documents; pass `selectedId` to keep a detail on screen); none (custom rows could not open the detail on a small screen, and closing it dropped focus to the page; nothing to restore) | Use the third `renderListItem` argument in custom rows. |
|
|
428
|
+
| 2.13.0 | `ListDetailTemplate` (mobile detail) | **Before:** The list unmounted while the drawer was open, and closing it left focus on the page body. **After:** The list stays mounted under the drawer, and closing returns focus to the selected row. **Verified by:** `ListDetailTemplate.test.tsx` | a, b, e | none (each change repairs a WCAG 2.2 AA failure or renders what the JSDoc documents; the visible defaults are unchanged); none (both corrections render what the props' JSDoc documents; pass `selectedId` to keep a detail on screen); none (custom rows could not open the detail on a small screen, and closing it dropped focus to the page; nothing to restore) | Tests that expected the list to be absent while the drawer is open: it is present (and hidden from assistive technology by the modal). |
|
|
429
|
+
| 2.13.0 | `AIChatMessage` action buttons | **Before:** Tooltips said "Good response", "Bad response", "Share" and "Regenerate" while the names said "Mark as good response", "Mark as bad response", "Share message" and "Regenerate response". **After:** Each tooltip is the button's name. **Verified by:** `AIChat.test.tsx` | b | none (a tooltip that says something other than the button's name breaks the behavior contract, WCAG 2.5.3 Label in Name; both components are deprecated and are removed in v3.0) | Nothing, unless a test queries the old tooltip text: query the button by its name. Better, migrate to `AIMessage`. |
|
|
430
|
+
| 2.13.0 | `QueryInput` Clear and Send | **Before:** Tooltips said "Clear" and "Send (Enter)" while the names said "Clear input" and "Send message"; Clear and dictation had no wrapper, so no tooltip while disabled. **After:** Tooltips "Clear input" and "Send message"; Send has `aria-keyshortcuts="Enter"`; Clear and dictation show their tooltips while disabled. **Verified by:** `QueryInput.test.tsx` | b | none (a tooltip that says something other than the button's name breaks the behavior contract, WCAG 2.5.3 Label in Name; both components are deprecated and are removed in v3.0) | Nothing, unless a test queries the old tooltip text: query the button by its name. Better, migrate to `InsightEngineInput`. |
|
|
431
|
+
| 2.13.0 | `AIFab` | **Before:** The sparkle on a solid `ai.primaryFill` backing with `ai.gradient.diagonal` as a 2px ring (dark: purple 600 inside a coral 400 ring). **After:** `ai.gradient.brandDiagonal` (coral 800 to purple 700, 135deg) under the white sparkle in light and dark, no ring. **Verified by:** `AIFab.test.tsx`, `contrast-matrix.test.ts` | b, c | the component's `sx` restores any listed style (AIFab has no `sx`: wrap it or override the theme's `ai.avatarFill` stops); `showFabWhenPanelOpen` (new, default `false`) keeps the FAB beside the docked panel as a toggle, as before; it is still hidden over a fullscreen panel and the mobile drawer | Nothing. Update visual baselines of the dark FAB. |
|
|
432
|
+
| 2.13.0 | `AICircularAction` `variant="gradient"` | **Before:** The icon on a solid `ai.primaryFill` backing with `ai.gradient.diagonal` as a 1px ring. **After:** `ai.gradient.brandDiagonal` under the white icon in light and dark, no ring. **Verified by:** `AIActions.test.tsx`, `contrast-matrix.test.ts` | b, c | the component's `sx` restores any listed style (AIFab has no `sx`: wrap it or override the theme's `ai.avatarFill` stops); `showFabWhenPanelOpen` (new, default `false`) keeps the FAB beside the docked panel as a toggle, as before; it is still hidden over a fullscreen panel and the mobile drawer | Nothing. Update visual baselines of the gradient action. |
|
|
433
|
+
| 2.13.0 | `ai.gradient` (resolver) and `--trinity-ai-gradient-*` (CSS) | **Before:** Eight gradients. **After:** Nine: `brandDiagonal` / `--trinity-ai-gradient-brand-diagonal`. **Verified by:** `brand-override-parity.test.ts`, `useTrinityPalette.test.ts`, Tokens/AI Semantic Roles `GradientCssParity` | b, c | the component's `sx` restores any listed style (AIFab has no `sx`: wrap it or override the theme's `ai.avatarFill` stops); `showFabWhenPanelOpen` (new, default `false`) keeps the FAB beside the docked panel as a toggle, as before; it is still hidden over a fullscreen panel and the mobile drawer | Nothing; use it for an icon on the brand gradient (never text). |
|
|
434
|
+
| 2.13.0 | `AppLayout` with `aiTrigger="fab"` (docked panel) | **Before:** The FAB stayed beside the open panel, offset by the panel width, and closed it on click. **After:** The FAB is hidden while the panel is open and comes back when it closes; opening from it moves focus into the panel, closing returns focus to it. **Verified by:** `AppLayout.test.tsx` (owner decision 2026-10-09 block, H-11, H-32, H-40) | b, c | the component's `sx` restores any listed style (AIFab has no `sx`: wrap it or override the theme's `ai.avatarFill` stops); `showFabWhenPanelOpen` (new, default `false`) keeps the FAB beside the docked panel as a toggle, as before; it is still hidden over a fullscreen panel and the mobile drawer | Nothing. To keep the FAB beside the docked panel, pass `showFabWhenPanelOpen`. Update tests that closed the panel by clicking the FAB: click the panel's Close button. |
|
|
435
|
+
| 2.13.0 | `AppLayout` new prop | **Before:** none **After:** `showFabWhenPanelOpen?: boolean` (default `false`). **Verified by:** `AppLayout.test.tsx` | b, c | the component's `sx` restores any listed style (AIFab has no `sx`: wrap it or override the theme's `ai.avatarFill` stops); `showFabWhenPanelOpen` (new, default `false`) keeps the FAB beside the docked panel as a toggle, as before; it is still hidden over a fullscreen panel and the mobile drawer | Optional. |
|
|
436
|
+
| 2.13.0 | `GaugeCard` | **Before:** Track and value arc drawn on two different centres; rings overflowed the card; value label off centre. **After:** One 260° arc inside the card, value arc on the track, label centred on the circle, at every size. **Verified by:** `GaugeCard.test.tsx` | a, b, c | GaugeCard and GaugeChart geometry: none (the old arcs were drawn on the wrong circle, so there is no correct drawing to restore); GeoStatusMap colors: pass `baseMapFill`, `baseMapStroke` and `canvasBackground` to pin any colors; GeoStatusMap name: none (pass `ariaLabel`; the old name came from the legend title "Status"); pulse markers: none (users who ask for reduced motion get a still halo, as WCAG 2.2.2 requires for endless motion) | Nothing. Remove any CSS that compensated for the offset. |
|
|
437
|
+
| 2.13.0 | `GaugeCard` colors | **Before:** Track, border, label, eyebrow and caption used light-mode colors in dark mode; `neutral` used `text.disabled`. **After:** Mode-aware colors; `neutral` uses `text.secondary`; value text keeps the severity color where it reads at 4.5:1, else the status text color. **Verified by:** `GaugeCard.test.tsx` | a, b, c | GaugeCard and GaugeChart geometry: none (the old arcs were drawn on the wrong circle, so there is no correct drawing to restore); GeoStatusMap colors: pass `baseMapFill`, `baseMapStroke` and `canvasBackground` to pin any colors; GeoStatusMap name: none (pass `ariaLabel`; the old name came from the legend title "Status"); pulse markers: none (users who ask for reduced motion get a still halo, as WCAG 2.2.2 requires for endless motion) | Nothing. |
|
|
438
|
+
| 2.13.0 | `GaugeChart` | **Before:** Semicircle at about half size, value label placed by a fixed percentage and font size. **After:** Largest semicircle that fits, value centred in the arc's hole and sized to it. **Verified by:** `gaugeGeometry.test.ts` | a, b, c | GaugeCard and GaugeChart geometry: none (the old arcs were drawn on the wrong circle, so there is no correct drawing to restore); GeoStatusMap colors: pass `baseMapFill`, `baseMapStroke` and `canvasBackground` to pin any colors; GeoStatusMap name: none (pass `ariaLabel`; the old name came from the legend title "Status"); pulse markers: none (users who ask for reduced motion get a still halo, as WCAG 2.2.2 requires for endless motion) | Nothing. |
|
|
439
|
+
| 2.13.0 | `GeoStatusMap` | **Before:** `<svg height="auto">` (an invalid attribute); region named "Status" by the legend title, or by a missing element without the legend. **After:** SVG sized by CSS from the `viewBox`; region named by `ariaLabel` (default "Geographic status map"). **Verified by:** `GeoStatusMap.test.tsx` | a, b, c | GaugeCard and GaugeChart geometry: none (the old arcs were drawn on the wrong circle, so there is no correct drawing to restore); GeoStatusMap colors: pass `baseMapFill`, `baseMapStroke` and `canvasBackground` to pin any colors; GeoStatusMap name: none (pass `ariaLabel`; the old name came from the legend title "Status"); pulse markers: none (users who ask for reduced motion get a still halo, as WCAG 2.2.2 requires for endless motion) | Pass a specific `ariaLabel`. |
|
|
440
|
+
| 2.13.0 | `GeoStatusMap` colors and motion | **Before:** Light-mode canvas, legend and outlines in dark mode; `pulse` markers animated under reduced motion. **After:** Mode-aware defaults; a still halo under reduced motion. **Verified by:** `GeoStatusMap.test.tsx` | a, b, c | GaugeCard and GaugeChart geometry: none (the old arcs were drawn on the wrong circle, so there is no correct drawing to restore); GeoStatusMap colors: pass `baseMapFill`, `baseMapStroke` and `canvasBackground` to pin any colors; GeoStatusMap name: none (pass `ariaLabel`; the old name came from the legend title "Status"); pulse markers: none (users who ask for reduced motion get a still halo, as WCAG 2.2.2 requires for endless motion) | Pass `baseMapFill`, `baseMapStroke`, `canvasBackground` to pin colors. |
|
|
441
|
+
| 2.13.0 | `AIAvatar` | **Before:** The literal `aiTokens.gradient.primary` (coral 800 to purple 700) in both modes, outside the theme; the dark icon was #F4F4F5. **After:** `ai.gradient.avatar`, the same coral 800 to purple 700 in both modes, from theme roles; the icon is `ai.onPrimaryFill` (white) in both modes, and light-mode brand overrides reach the gradient. **Verified by:** `AIAvatar.test.tsx` | c | `sx={(theme) => ({ background: getTrinityPalette(theme).ai.primaryFill })}` on `AIAvatar` for a solid fill, or any other `background` through `sx` (not recommended: the dark `ai.gradient.primary` leaves the white icon at 2.5-2.9:1, below WCAG 1.4.11's 3:1) | Nothing. To change the stops, override `ai.avatarFill` (keep white on the stops at 3:1 or more where the icon sits). |
|
|
442
|
+
| 2.11.1 | `AIChainOfThought` (`thoughts`, insight `content`) | **Before:** Raw HTML (`dangerouslySetInnerHTML`): scripts, event handlers and `javascript:` links could run. **After:** Rendered through an allowlist: `b strong i em code br p ul ol li span a mark`, plus `<span class="insight-highlight">`. Other tags show as text (block elements such as headings, divs and table cells become space-separated text); HTML 4 named references decode, HTML5-only ones (e.g. `✓`) stay literal; `style`, `id`, `data-*` and other classes are dropped; links keep only http(s), mailto, tel and relative URLs and use the theme's link colours; `<mark>` shows a theme-tinted highlight; the insight confidence bar is now named "Confidence" for assistive technology. **Verified by:** `AIChainOfThought.security.test.tsx`, `AIChainOfThought.test.tsx` | d | None (security correction; ADR-23 section 5) | Nothing: upgrade to 2.11.1 or later. |
|
|
443
|
+
| 2.11.1 | `AITextBlock` markdown links | **Before:** Any URL became a link. **After:** Only http(s), mailto, tel and relative URLs become links; others show as plain text. **Verified by:** `AIUrlPolicy.security.test.tsx`, `AIResponseRenderer.test.tsx` | d | None (security correction; ADR-23 section 5) | Nothing: upgrade to 2.11.1 or later. |
|
|
444
|
+
| 2.11.1 | `AISource` `url` / `favicon` | **Before:** Any URL / image source. **After:** Unsafe URLs show as text; favicons load only from safe sources, lazily and without a referrer. **Verified by:** `AIUrlPolicy.security.test.tsx` | d | None (security correction; ADR-23 section 5) | Nothing: upgrade to 2.11.1 or later. |
|
|
445
|
+
| 2.11.1 | `InsightEngineInput` attachment `previewUrl` | **Before:** Any source. **After:** Only blob:, http(s), relative or raster `data:image/*`; others show the file icon. **Verified by:** `AIUrlPolicy.security.test.tsx` | d | None (security correction; ADR-23 section 5) | Nothing: upgrade to 2.11.1 or later. |
|
|
446
|
+
| 2.11.1 | Development console | **After:** `AITextBlock`, `AISource` and `InsightEngineInput` log a one-time warning naming the component when a link or image URL is blocked (silent in production builds); `AIChainOfThought` drops blocked links silently. **Verified by:** `safeContent.test.tsx`, `AIUrlPolicy.security.test.tsx` | d | None (security correction; ADR-23 section 5) | Nothing: upgrade to 2.11.1 or later. |
|
|
447
|
+
|
|
448
|
+
### Additive changes in the same releases (no action needed)
|
|
449
|
+
|
|
450
|
+
- `VisuallyHidden` ref, `id` and other props (additive). **Before:** `VisuallyHidden` accepted only `children` and `as`. TypeScript rejected a ref, `id`, `aria-*`, `data-*`, `className`, `style` or event handler, and at runtime they were dropped. The hidden text therefore could not be the target of `aria-describedby` or `aria-labelledby`. **After:** The ref and every other prop reach the rendered element. The ref is typed `HTMLSpanElement`; with `as` it receives that element. `sx` merges after the hidden styles, and `children` is optional (for example `as="input"`). With no new prop, the DOM and the generated CSS class are unchanged. **What to do:** Nothing. To describe a control, give the hidden text an id from `useId()` and point `aria-describedby` at it. Plain JavaScript callers that already passed such props now get them on the element. An `sx` or `style` that changes `position`, size, `clip` or `overflow` can make the text visible. **Verified by:** `accessibility.test.tsx` "VisuallyHidden ref and props passthrough" ([STEP3-05](docs/remediation/STEP3-05-visually-hidden-id.md))
|
|
451
|
+
- `react` and `react-dom` peer ranges widen from `^19.3.0` back to `^19.2.4` (2.10.7 had narrowed them in a patch).
|
|
452
|
+
- `LandingPage` and `FeatureCard` and their public types are exported from the package root, as documented.
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## `@trinityui/design-system` v2.11.1 — Untrusted AI content hardening (security)
|
|
457
|
+
|
|
458
|
+
**No code changes required.** Upgrade to receive the fix for SEC-01 – SEC-04 (untrusted AI content rendered as HTML, unsafe URLs). Full record: [docs/remediation/SEC-01-ai-content-safety.md](docs/remediation/SEC-01-ai-content-safety.md).
|
|
459
|
+
|
|
460
|
+
### Behavior corrections
|
|
461
|
+
|
|
462
|
+
The five public corrections are the `2.11.1` rows of [Behavior corrections (2.x)](#behavior-corrections-2x) (ADR-23 test d).
|
|
463
|
+
|
|
464
|
+
### Internal components (not exported; listed for contributors)
|
|
465
|
+
|
|
466
|
+
- `HtmlToMui` / `StreamingHtmlToMui`: URL, style and class policies always apply; remote images blocked by default (`remoteImages`); MUI/Emotion classes filtered; `allowedClasses` to restrict further; blocked images show their alt text; content is clipped to the component's box; tables scroll horizontally inside a focusable, named container.
|
|
467
|
+
- `SandboxedIframeRenderer`: `strict` by default; a CSP meta blocks network resource loads (`contentSecurityPolicy`, or `false`); `allow-scripts` is never combined with `allow-same-origin` (any letter case); framed documents send no referrer while `allow-same-origin` is absent (opaque origin; `referrerPolicy="no-referrer"` covers only the iframe's own request).
|
|
468
|
+
|
|
469
|
+
### Images and schemes that no longer render
|
|
470
|
+
|
|
471
|
+
`data:image/svg+xml`, non-raster data URIs, and app-shell schemes (`file:`, `asset:`, `tauri:`, `capacitor:`) are refused as image sources; custom link schemes (e.g. `msteams:`, `sms:`) render as text. Serve such assets over http(s), as blob: URLs, or as raster data URIs.
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
7
475
|
## `@trinityui/design-system-css` v2.5.0 — CSS is now token-generated
|
|
8
476
|
|
|
9
477
|
**Non-breaking for the 602 variable names shipped in v2.4.x.** Every CSS variable that existed in the hand-maintained `trinity.css` is still present, at the same name, with the same or WCAG-improved value.
|
|
10
478
|
|
|
11
479
|
### What changed
|
|
12
480
|
|
|
13
|
-
The `dist/trinity.css` shipped in `@trinityui/design-system-css` is now **generated from `src/tokens.ts`** via `scripts/generate-css-from-tokens.ts`. The previous hand-maintained `src/styles/trinity.css` is retired
|
|
481
|
+
The `dist/trinity.css` shipped in `@trinityui/design-system-css` is now **generated from `src/tokens.ts`** via `scripts/generate-css-from-tokens.ts`. The previous hand-maintained `src/styles/trinity.css` is retired; its reference copy `trinity.css.legacy` was deleted in 2.13.0 (E-37), and git history keeps it.
|
|
14
482
|
|
|
15
483
|
### What you get as a consumer
|
|
16
484
|
|
|
@@ -51,24 +519,62 @@ pnpm build:css:sync # regenerates src/styles/trinity-generated.css
|
|
|
51
519
|
npx tsx scripts/check-token-drift.ts # CI gate
|
|
52
520
|
```
|
|
53
521
|
|
|
54
|
-
The hand-maintained `src/styles/trinity.css.legacy`
|
|
522
|
+
The hand-maintained `src/styles/trinity.css.legacy` reference copy was deleted in 2.13.0 (E-37). It was never built or shipped in either package, so consumers are unaffected.
|
|
55
523
|
|
|
56
524
|
---
|
|
57
525
|
|
|
58
|
-
## v3.0
|
|
526
|
+
## v3.0 — criteria-gated removals
|
|
527
|
+
|
|
528
|
+
v3.0 has no date. It ships when all of these exit criteria hold (ADR-14 (5)), and the maintainer signs the exit:
|
|
59
529
|
|
|
60
|
-
|
|
530
|
+
1. Every deprecation listed below has shipped its consumer-facing runtime warning in at least one stable minor. Warnings that only Trinity's own components trigger do not count. A name without a runtime warning needs a recorded maintainer waiver or a typed warning wrapper first; today that is `VirtualizedAutocomplete`.
|
|
531
|
+
2. `InsightEnginePanel` no longer imports `AIChatMessage` or `AITypingIndicator` (ADR-08).
|
|
532
|
+
3. Every removal has a codemod or a migration snippet in this section.
|
|
61
533
|
|
|
62
|
-
|
|
534
|
+
Until then everything below keeps working in 2.x. A workstream that deprecates something adds its row through its changeset (see [Behavior corrections (2.x)](#behavior-corrections-2x) for how rows reach this file); `scripts/check-deprecations.mjs` fails when a public `@deprecated` declaration has no row.
|
|
63
535
|
|
|
64
|
-
|
|
536
|
+
### Deprecated exports removed in v3.0
|
|
537
|
+
|
|
538
|
+
| Removed in v3.0 | Replace with | Runtime warning | Why |
|
|
539
|
+
|---|---|---|---|
|
|
540
|
+
| `OverflowToolTip` (capital T) | `OverflowTooltip` | Yes | Casing aligned with the component file name; the alias is a runtime wrapper. |
|
|
541
|
+
| `QueryInput`, `QueryInputProps` | `InsightEngineInput`, `InsightEngineInputProps` | Yes | `InsightEngineInput` is the canonical AI entry surface. |
|
|
542
|
+
| `AIChatMessage` | `AIMessage` | Yes | Richer block rendering, `aria-live` streaming support and better accessibility. |
|
|
543
|
+
| `AIChatInput` | `InsightEngineInput` for the input; `ChatThread` for a whole conversation | Yes | The runtime warning names `InsightEngineInput`. |
|
|
544
|
+
| `AITypingIndicator` | `AIMessage`'s streaming state plus `AIShimmer` | Yes | Consolidated AI chat primitives. |
|
|
545
|
+
| `AIQuickReply` | `AISuggestedAction` or `AIActionsGroup` | Yes | Consolidated AI chat primitives. |
|
|
546
|
+
| `AIChatMessageProps`, `AIChatInputProps`, `AITypingIndicatorProps`, `AIQuickReplyProps`, `MessageRole` | Prop types from `AIMessage`, `InsightEngineInput`, `ChatThread` and `AIResponseRenderer` | Not applicable (types) | — |
|
|
547
|
+
| `AIFabLayout` | `AppLayout` with `navStyle="topnav"` and `aiTrigger="fab"` | Yes | One layout API instead of a FAB wrapper component. |
|
|
548
|
+
| `VirtualizedAutocomplete` | `Autocomplete` | Waived for now: it is the same function object as `Autocomplete` (`export const VirtualizedAutocomplete = Autocomplete`), so it cannot warn without a wrapper; exit criterion 1 needs a maintainer waiver or a typed warning wrapper before v3.0 | MUI's built-in virtualization covers both paths. |
|
|
549
|
+
| `VirtualizedAutocompleteProps`, `VirtualizedAutocompleteHandle` | `AutocompleteProps`, `AutocompleteHandle` | Not applicable (types) | — |
|
|
550
|
+
| `useFocusTrap`, `UseFocusTrapOptions`, `UseFocusTrapReturn` | MUI's focus trap: `Dialog`, `Popover`, `Menu` and a temporary `Drawer` trap focus themselves; for a custom surface, `FocusTrap` from `@mui/material/Unstable_TrapFocus` | Yes, once per session in development (the hook); not applicable for the types | Deprecated in 2.13.0 (B-14): the behavior toolkit replaces it, and the hook skips `position: fixed` elements and finds nothing to focus in jsdom. |
|
|
551
|
+
|
|
552
|
+
### Deprecated props and token keys removed in v3.0
|
|
553
|
+
|
|
554
|
+
| Removed in v3.0 | Replace with | Notes |
|
|
65
555
|
|---|---|---|
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
556
|
+
| `InsightEngineInput` `attachmentCount` (`InsightEngineInputProps.attachmentCount`) | `attachments` | The badge counts `attachments`. |
|
|
557
|
+
| `AppLayout` `onNavigate` (`AppLayoutProps.onNavigate`) | `onSelectedNavItemChange` | — |
|
|
558
|
+
| `Autocomplete` `loadOptions` (`AutocompleteAsyncConfig.loadOptions`) | `dataSource` | — |
|
|
559
|
+
| Stack spacing keys `related`, `grouped`, `separated` and `distinct` in `TrinityStackSpacing`, `semanticTokens.stack` and `tokens.semantic.stack` | `tight`, `compact`, `comfortable` and `spacious` | `distinct` is 32px; its replacement `spacious` is 24px. |
|
|
560
|
+
| `brandColors.neutral.darkBg` (root, essentials, `theme` and `tokens` entries) | `theme.palette.background.default` (or `palette.trinity.surface`) | Not the dark theme's page colour. The value stays the same until removal; `@deprecated` JSDoc only, no runtime warning (a plain object read). E-36. |
|
|
561
|
+
| `brandColors.neutral.darkPaper` (root, essentials, `theme` and `tokens` entries) | `theme.palette.background.paper` (or `palette.trinity.surface.elevated`) | Not the dark theme's paper colour. The value stays the same until removal; `@deprecated` JSDoc only, no runtime warning. E-36. |
|
|
562
|
+
| `aiTokens.colors.aiBackgroundDark` | `useTrinityPalette().ai.surface` (the dark tint in dark mode), or `ai.badgeBackground` for the light emphasis fill | `aiTokens` is a static light facade; its colours never follow the mode or brand overrides. `@deprecated` JSDoc pointing to the theme resolver, no runtime warning. E-07. |
|
|
563
|
+
| `aiTokens.colors.aiBorderDark` | `useTrinityPalette().ai.border` | As above. E-07. |
|
|
564
|
+
| `aiTokens.colors.aiHoverDark` | `useTrinityPalette().ai.surfaceEmphasisHover` | As above. E-07. |
|
|
565
|
+
| `aiTokens.colors.sourceBadgeBgDark` | `useTrinityPalette().ai.badgeBackground` | As above. E-07. |
|
|
566
|
+
| `aiTokens.colors.typingDotDark` | `useTrinityPalette().ai.typingIndicator` | As above. E-07. |
|
|
567
|
+
|
|
568
|
+
### Other changes planned for v3.0
|
|
569
|
+
|
|
570
|
+
These are not deprecated yet. Each plan item adds its `@deprecated` tag, warning and row in the minor that deprecates it.
|
|
571
|
+
|
|
572
|
+
| Change in v3.0 | Today (2.x) | Plan item |
|
|
573
|
+
|---|---|---|
|
|
574
|
+
| `onRenderBlock` removed from `AIResponseRenderer` and `AIMessage` | `renderBlock` replaces it, with `onRenderBlock` kept as a deprecated alias | G-09 |
|
|
575
|
+
| `AIMessage` `show*` defaults flip | The defaults stay as they are in 2.x (ADR-13) | ADR-13 |
|
|
576
|
+
| `InsightEnginePanel` `demoMode` default, default model list and `themeMode` | The demo defaults, the hard-coded model list and the ignored `themeMode` prop are deprecated in turn | H-04 (`demoMode`), H-37 (model list), H-06 (`themeMode`); ADR-08 |
|
|
577
|
+
| Public paths of `@internal` symbols removed | Deprecated under ADR-02 | D-02 |
|
|
72
578
|
|
|
73
579
|
### Migration snippets
|
|
74
580
|
|
|
@@ -79,7 +585,8 @@ import {
|
|
|
79
585
|
AITypingIndicator, AIQuickReply, AIFabLayout, VirtualizedAutocomplete,
|
|
80
586
|
} from '@trinityui/design-system';
|
|
81
587
|
|
|
82
|
-
// After
|
|
588
|
+
// After (AIChatInput and QueryInput both become InsightEngineInput;
|
|
589
|
+
// use ChatThread when you render a whole conversation)
|
|
83
590
|
import {
|
|
84
591
|
OverflowTooltip, InsightEngineInput, AIMessage, ChatThread,
|
|
85
592
|
AIShimmer, AISuggestedAction, AppLayout, Autocomplete,
|
|
@@ -98,7 +605,7 @@ Three overlapping warning channels so accidental usage can't slip through:
|
|
|
98
605
|
|
|
99
606
|
1. **IDE hint** — every deprecated export has a `@deprecated` JSDoc tag. Your IDE strikes through the name on hover and in autocomplete.
|
|
100
607
|
2. **TypeScript warning** — the same JSDoc tag triggers `ts(6385)` "'X' is deprecated" in tsc output.
|
|
101
|
-
3. **Runtime console warning** — in development builds only, each deprecated component emits one `console.warn` per session
|
|
608
|
+
3. **Runtime console warning** — in development builds only, each deprecated component except `VirtualizedAutocomplete` emits one `console.warn` per session the first time its module loads. Production builds strip the warning. Deprecated types, props and token keys have no runtime warning.
|
|
102
609
|
|
|
103
610
|
If you're an end-user consuming Trinity via a framework that hides deprecation warnings, check your console in `NODE_ENV=development` mode before upgrading to v3.0.
|
|
104
611
|
|
|
@@ -135,12 +642,7 @@ The npm package has been optimized from **169MB to 261KB** (99.85% reduction) by
|
|
|
135
642
|
|
|
136
643
|
#### 1. Asset Exports Removed
|
|
137
644
|
|
|
138
|
-
The
|
|
139
|
-
|
|
140
|
-
```typescript
|
|
141
|
-
// ❌ No longer works
|
|
142
|
-
import { gradientIcons, backgroundImages, brandGradients } from '@trinityui/design-system';
|
|
143
|
-
```
|
|
645
|
+
The exports `gradientIcons`, `backgroundImages` and `brandGradients` are **no longer available** from the main package; importing them fails.
|
|
144
646
|
|
|
145
647
|
**Migration:** If you need Trinity brand assets:
|
|
146
648
|
1. Copy from `node_modules/@trinityui/design-system/src/assets/` to your public folder
|
|
@@ -269,7 +771,7 @@ import type {
|
|
|
269
771
|
TrinitySemanticTokens,
|
|
270
772
|
TrinityComponentTokens,
|
|
271
773
|
TrinityDarkModeTokens,
|
|
272
|
-
} from '@trinityui/design-system';
|
|
774
|
+
} from '@trinityui/design-system/tokens';
|
|
273
775
|
```
|
|
274
776
|
|
|
275
777
|
#### 2. Auto-generated CSS Variables
|