feat(ui): composer & design-system a11y pass#126
Conversation
|
Warning Review limit reached
Next review available in: 40 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis PR adds Flutter accessibility guidance, new shared a11y helpers, a gallery semantics debugger, and semantics/focus/announcement updates across core components and their tests. ChangesAccessibility feature rollout
Estimated code review effort: 4 (Complex) | ~75 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
A developer tool that overlays role-colored semantic node boundaries on the previewed widget, scoped to the preview area only. Toggle via the toolbar's debug section: - Green for text fields, blue for buttons, purple for selected - Cyan for other tappables, neutral gray for decorative - Dashed outline highlights MergeSemantics roots so over-collapse bugs are visible at a glance For actual SR announcements, real TalkBack / VoiceOver on a device is still the ground truth. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Skip enter/exit animations when MediaQuery.accessibleNavigation is on, matching ScaffoldMessenger's pattern. Prevents screen readers from reading content mid-fade. Fix onDismiss to call hideCurrent() so semantic dismissals resolve with .dismiss instead of .remove — aligning with Flutter's SnackBar where a semantic dismiss is a user-initiated close. Migrate matchesSemantics → isSemantics in tests (deprecation) and add coverage for the animation-skip behavior under FakeAccessibilityFeatures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match Flutter's TextField semantics exactly by wrapping the inner EditableText with ExcludeSemantics and re-emitting the field semantics on the outer chassis. The chassis Semantics now carries textField, readOnly, enabled, focused, label (hint while empty), value, hint (error helper), maxValueLength / currentValueLength, and gated onTap / onFocus actions — keeping the screen reader on a single focus stop that mirrors the visible bounds. Leading / trailing slots get OrdinalSortKey so logical reading order (leading → field → trailing) survives RTL. Decorative icons are wrapped in ExcludeSemantics so they don't pollute the tree. StreamHelperText in the error state becomes a live region so screen readers announce validation failures the moment they appear. Add focused-on-a11y test coverage: guideline checks, label/hint/value contract, disabled vs read-only action gating, focus tracking, helper state, maxLength, RTL traversal, and verification that interactive trailing widgets stay as their own semantic node rather than merging into the chassis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire up tooltip on StreamButton.icon so the message renders as a Material Tooltip on hover / long-press *and* surfaces to assistive technologies as the button's accessibility label. Expose autofocus on both constructors so consumers can drop focus on the primary action of a freshly-opened dialog, sheet, or picker without plumbing FocusNodes. Move the selection flag onto the button's own semantic node by wrapping the inner child in Semantics(selected:) — props.isSelected already drove the visual state via WidgetState.selected but didn't reach screen readers, so filter chips / tab-strip buttons silently failed to announce "selected". Add coverage for the icon-button label / enabled / selected / tap-target contract and verify that a regular StreamButton derives its label from its child Text. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document the workflow used while landing the composer a11y pass: how to manage Semantics in design-system components, how to audit with Flutter's semantics testing APIs, and how to debug the semantics tree when something's off. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add five recipes / pitfalls surfaced while landing the composer a11y work: - Pitfall: Semantics(container: false) merges *upward* into ancestor containers, not downward into descendant containers. Common mistake is to wrap ElevatedButton from the outside hoping a selected: flag lands on the button's node; the dartdoc spells out the upward direction, with the explicitChildNodes caveat. - Recipe: "Re-emitting semantics on a custom chassis" — for chrome around an inner editable (TextField, Switch, Image), suppress the inner's auto-emitted node and re-emit a full semantic node on the outer chassis. Includes when to prefer ExcludeSemantics + outer Semantics over MergeSemantics. - Recipe: OrdinalSortKey for logical traversal order in RTL when visual and logical orders diverge (leading/trailing in a Row). - Subsection: "Gating SR-specific behavior" — replaces the one-liner with concrete adaptations (skip transient animations, extend timers, promote swipe-only affordances), mirroring ScaffoldMessenger's pattern. - Testing: prefer tester.semantics.performAction(find.semantics.byLabel, SemanticsAction.tap) over the lower-level tester.platformDispatcher.onSemanticsActionEvent! for new tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expose a persistent labelText that renders above the bordered input area and becomes the field's accessibility label — announced by screen readers regardless of whether the field is empty or filled. When labelText is null, the field falls back to the hint while empty so SR users still hear an identifier. Add a StreamHelperAffinity enum (inside | outside) on StreamTextInputStyle so the design team can choose whether helper text sits inside the bordered chassis (default, backwards compatible) or below it as an external caption. Set globally via StreamTextInputTheme or per-instance via the style: prop. Theme owns the new labelStyle and helperAffinity fields; the inner label renders single-line with ellipsis overflow to keep layout predictable. Tests cover the semantic-label contract, visible label rendering, helper affinity positioning, and theme inheritance. Gallery playground gains Label Text and Helper Affinity knobs; the real-world login form switches to labelText for Email / Password. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap SemanticsService.sendAnnouncement with platform-aware delivery so features dispatching screen-reader announcements don't each have to re-implement the same workarounds: - On VoiceOver (iOS and macOS) polite announcements queue behind the screen reader's current speech and can be silently dropped. The helper schedules them after a 1-second delay on those platforms, matching what ExpansionTile does internally. - Assertive announcements interrupt the current speech and dispatch immediately on every platform, so the delay is skipped. - A single shared timer means rapid calls cancel the previous pending delivery and only the latest message is announced. - Platform-channel errors are surfaced via FlutterError.reportError instead of disappearing silently. Also expose tooltip(...) as a thin pass-through to SemanticsService.tooltip for API parity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Wrap sheet body in Semantics(scopesRoute, explicitChildNodes) so StreamSheetHeader's namesRoute descendant can name the route. - Add outer Semantics(hitTestBehavior: opaque) so SR taps on the sheet don't leak to the barrier — matches Material's showModalBottomSheet. - Override buildModalBarrier to wire barrierOnTapHint through as semanticsOnTapHint on the barrier; SR reads "double tap to <hint>" (e.g. "close attachment picker") instead of "activate". - Add barrierOnTapHint parameter on showStreamSheet with a scrimOnTapHint fallback so undecorated callers get the platform-standard hint. - Override semanticsDismissible so SR dismiss action tracks barrierDismissible. Add semantics tests covering the route scope, header title as the route name on Android, and the drag handle exposing a working Dismiss action. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nnotation guidance Adds three sections capturing lessons from the composer a11y pass: - Role-annotation vs. container wrapping — role-only tabPanel matches Flutter's own TabBarView and avoids introducing an extra SR stop. - FocusTraversalGroup is for keyboards, not screen readers — clarifies that Focus and Semantics are separate trees. - Programmatic focus dispatch (FocusSemanticEvent) — narrow use case guidance, notes that onDidGainAccessibilityFocus doesn't round-trip from these dispatches. Also swaps a project-specific factory reference for a generic factory/builder-pattern description so the skill stays product-neutral. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A behavior-only wrapper that requests screen-reader focus on its child shortly after mount. Targets the case where the platform's automatic focus decision on route entry (typically the first interactive element in the toolbar) is not the element the user needs to interact with. Highlights: - Only moves the SR reading cursor; input focus is untouched so the soft keyboard stays closed. - Passthrough when MediaQuery.accessibleNavigation is false; responds to the screen reader being toggled at runtime. - Debug-only assertion catches multiple instances competing for focus on the same route (checked per-route via ModalRoute.of, deferred to postFrame to avoid framework focus-tracking cascades). - Retry cadence bounded (defaults: 300ms interval, 1500ms window) to avoid choppy re-announcement from repeated platform focus events. Ships with tests covering the SR-off passthrough, SR-toggle-mid-session, retry cadence, window close, dispose cleanup, and the per-route uniqueness assertion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A behavior-only wrapper that observes a Listenable, snapshots its value on each notification, derives a transition message via a resolver, and dispatches it through StreamSemanticsAnnouncer. Baseline is captured at init so the first transition compares against a real value rather than a synthetic default. Useful for announcing add/remove or state-change transitions on collections and value streams (attachment lists, upload progress, mention state) without wiring imperative SR announcement calls into every mutation site. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rapper Moves the Semantics(selected:) wrapper outside the ElevatedButton and adds an outer MergeSemantics + optional Tooltip. The merge collapses the two nodes (isSelected annotation + button's own label/role/state) into a single reported node so SR announces "label, selected, button" correctly. Also brings the button's semantic node shape closer to Material's IconButton — the wrapping Semantics/Tooltip sits at the tap-target bounds rather than inside the ElevatedButton child. Adds two tests: - Color resolution across enabled/disabled/selected states. - Semantic parity vs. Flutter's IconButton (same label + role + focusability + hasEnabledState flags). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps the stepper in a Semantics(slider: true, excludeSemantics: true)
node that exposes value, increasedValue, decreasedValue, onIncrease,
and onDecrease. Descendant Semantics from the internal +/- buttons and
read-only text field are excluded so SR sees a single slider node
instead of three separate focus stops.
Adds an optional semanticLabel prop so callers can describe what the
value represents ("Quantity", "Number of items"); announced alongside
the current value with the platform's adjustable hint appended.
Sighted users still tap the +/- buttons; SR users swipe up/down on the
slider node.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds pass-through focusNode and autofocus parameters mirroring Material's ListTile. Enables callers to programmatically focus a tile (e.g., landing focus on the first-enabled option when a picker opens) without wrapping in an external Focus widget. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…TypeIcon
Both components accept an optional semanticLabel so callers can
override the SR announcement — useful when the visible label is a
bare count ("5") that would be ambiguous on its own ("5 unread"), or
when the file-type icon needs to describe the file kind.
StreamFileTypeIcon defaults to ExcludeSemantics when no label is
given, matching the convention that decorative icons should not
introduce SR stops.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fires after the snackbar's entrance animation completes. When MediaQuery.accessibleNavigation is true (SR active), the entrance animation is skipped and this fires synchronously on display. Provides a hook to push an explicit SemanticsService.sendAnnouncement for screen readers that don't reliably observe newly-mounted live regions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps the title/subtitle column in Semantics(header: true) so SR announces it as a heading. On Android/Fuchsia/Linux/Windows, sets namesRoute: true so the platform can use the title as the route's accessible name (iOS/macOS handle route names differently, so it's left null there). Also plumbs MaterialLocalizations tooltips (closeButtonTooltip, backButtonTooltip) onto the auto-implied leading icon so it announces "Back" / "Close" instead of an unlabeled icon. New excludeHeaderSemantics prop lets callers opt out — useful when the title widget carries its own heading semantics or when the bar is shown in a sub-section that shouldn't identify the route. Adds tests covering: - Localized back/close tooltips on the auto-implied leading. - Heading flag on the title. - namesRoute on Android. - Focus isolation between multiple StreamAppBars. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the StreamAppBar treatment for sheet headers: title/subtitle merged as one announcement, wrapped in Semantics(header: true, namesRoute: true) by default so SR treats it as the sheet's heading and accessible name. Also plumbs the localized close/back tooltip onto the auto-implied leading icon, and adds the excludeHeaderSemantics opt-out for cases where the title widget provides its own heading semantics. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps the bottom bar in Semantics(container: true) so its leading, title, subtitle, and trailing slots are grouped as one region and don't intermix with surrounding page content during SR traversal. Uses MergeSemantics on the title/subtitle column so they read as a single announcement — but unlike StreamAppBar / StreamSheetHeader, the bottom bar deliberately does not claim the route's accessible name or expose the title as a heading (it's a secondary toolbar). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The onDidGainAccessibilityFocus / onDidLoseAccessibilityFocus handlers that request input focus (and consequently open the IME) are now attached only on desktop platforms (macOS / Linux / Windows). On mobile (iOS / Android), where focusing an editable text field opens the soft keyboard, the handlers are omitted so SR users can swipe onto the field without triggering the keyboard. Also moves the error text into its own live-region Semantics container (liveRegion: hasError) so SR auto-announces validation errors on appearance instead of piggybacking on the field's hint. Test updated to assert the error string lives in its own live-region node and is not duplicated onto the field hint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
StreamMessageComposerAttachment gains two new props: - semanticLabel — SR announcement (typically the type prefix "File", "Photo", "Video"). - mergeRemoveAction — when true (default), the whole tile (content + remove button) collapses into a single SR stop whose tap action maps to onRemovePressed. Appropriate for tiles whose child has no interactive elements (file row, image thumbnail, unsupported chip). Subclasses updated to forward the semantic label: - StreamMessageComposerFileAttachment - StreamMessageComposerMediaAttachment (ExcludeSemantics on the visual child so its label is the sole announcement). - StreamMessageComposerUnsupportedAttachment Adds new tests covering the semantic tree shape for file and media attachments (label, merge behavior, remove action). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace deprecated hasFlag(SemanticsFlag.x) with flagsCollection.x (or flagsCollection.toStrings() when iterating). - Replace deprecated tester.binding.pipelineOwner with tester.binding.rootPipelineOwner (or RendererBinding.instance.rootPipelineOwner where tester isn't in scope). - Remove unused _fmt helper in file_attachment_semantics_test.dart. - Remove now-unnecessary imports and add const constructor usages flagged by dart fix --apply. Fixes the analyze CI job (dart analyze --fatal-infos). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9ef7700 to
6f3d14e
Compare
Adds a 0.5.0 section listing the new a11y primitives (StreamAccessibilityAutofocus, StreamSemanticsAnnouncer, StreamSemanticsTransitionAnnouncer) and the component-level a11y improvements shipped in this branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (4)
packages/stream_core_flutter/test/components/message_composer/attachment/file_attachment_semantics_test.dart (1)
36-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest performs no assertions.
This test only
debugPrints the traversal/semantics tree and never callsexpect(), so it will pass regardless of whether the actual semantics regress. Consider asserting expected labels/flags (e.g. viaisSemantics(...)matchers as used instream_button_test.dart) fortitle,subtitle, and the remove action node.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/stream_core_flutter/test/components/message_composer/attachment/file_attachment_semantics_test.dart` around lines 36 - 69, The semantic test for StreamMessageComposerFileAttachment currently only dumps traversal output and never verifies behavior, so it can’t catch regressions. Update the test in the file_attachment_semantics_test widget test to add explicit expectations using semantics matchers or similar assertions, referencing StreamMessageComposerFileAttachment, the title/subtitle Text nodes, and the remove action triggered by onRemovePressed, so the test checks labels/flags/actions instead of only calling debugPrint.packages/stream_core_flutter/test/components/message_composer/attachment/media_attachment_semantics_test.dart (1)
15-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest performs no assertions.
Same gap as
file_attachment_semantics_test.dart: onlydebugPrints the traversal/tree, with noexpect()verifying the twosemanticLabelvalues ("Photo 1"/"Photo 2") or remove-action merging actually appear as expected.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/stream_core_flutter/test/components/message_composer/attachment/media_attachment_semantics_test.dart` around lines 15 - 56, The semantics test only prints the traversal/tree and never verifies behavior, so add explicit assertions in media_attachment_semantics_test.dart. In StreamMessageComposerMediaAttachment’s test, replace the debug-only inspection with expect checks against tester.semantics.simulatedAccessibilityTraversal() and/or the root SemanticsNode to confirm both semanticLabel values (“Photo 1” and “Photo 2”) are present and that the remove action is merged as expected.packages/stream_core_flutter/test/components/buttons/stream_button_vs_icon_button_semantics_test.dart (1)
42-425: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftDiagnostic-only tests: no assertions anywhere in this file.
Every
testWidgetshere onlydebugPrints semantics data for manual comparison; none assert on the result, so this file always "passes" and adds no regression coverage despite looking like a semantics test suite. Consider converting the interesting comparisons into realexpect(..., matchesSemantics(...))assertions, or moving this exploratory script out of the automatedtest/tree (e.g., into a dev script or the skill docs) so it isn't mistaken for coverage.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/stream_core_flutter/test/components/buttons/stream_button_vs_icon_button_semantics_test.dart` around lines 42 - 425, These tests are diagnostic-only and never assert anything, so they provide no real coverage. Update the relevant testWidgets cases in stream_button_vs_icon_button_semantics_test to use actual expectations with semantics matchers instead of only debugPrint, focusing on the comparison helpers like capturedEvents, _fmt, and the IconButton/StreamButton/ToggleButtons checks. If they are meant to stay exploratory, move them out of the automated test suite so the file is not treated as regression coverage.packages/stream_core_flutter/test/components/snackbar/stream_snackbar_test.dart (1)
850-916: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAdd coverage for
onVisibleand timed auto-dismiss under accessible navigation.The two new tests here only assert
FadeTransitionopacity/text presence, not thatStreamSnackbarProps.onVisibleactually fires or that a timed (non-persistent) snackbar still auto-dismisses whenaccessibleNavigationis true. See the related concern raised instream_snackbar_messenger.dart(Lines 704-710, 758-764) — the current implementation may silently skip both of these under accessible navigation, and this test suite wouldn't currently catch it.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/stream_core_flutter/test/components/snackbar/stream_snackbar_test.dart` around lines 850 - 916, The accessibleNavigation tests in StreamSnackbarHost currently verify only opacity and text visibility, but they do not cover StreamSnackbarProps.onVisible or timed auto-dismiss behavior. Update the accessibleNavigation group in stream_snackbar_test.dart to assert that onVisible is invoked when a snackbar is shown and that a non-persistent snackbar still closes automatically after its duration even with accessible navigation enabled. Use the existing StreamSnackbarMessenger, StreamSnackbarHost, and controller.closed expectations to keep the coverage tied to the same behavior paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/skills/flutter-a11y/SKILL.md:
- Around line 265-271: The matcher/version guidance is incorrect:
`isSemantics(...)` is not a Flutter 3.43 replacement for `matchesSemantics`, and
the current SDK pin means the docs should not point readers to an unavailable
API. Update the semantics matcher table and note in `SKILL.md` to reflect the
correct relationship between `containsSemantics(...)`, `matchesSemantics(...)`,
and `isSemantics(...)`, and make sure the version guidance only mentions APIs
supported by the pinned Flutter range. Use the existing matcher names in that
section to locate and revise the affected text.
In `@apps/design_system_gallery/lib/widgets/scoped_semantics_debugger.dart`:
- Around line 229-236: The selection-color check in _colorForNode is treating
both explicitly selected and explicitly not-selected nodes as selected, which
makes the role visualization misleading. Update the logic to only use the purple
color when SemanticsNode.getSemanticsData().flagsCollection.isSelected is
Tristate.isTrue, and let Tristate.isFalse fall through to the later role checks
in _colorForNode.
- Around line 42-83: The `ScopedSemanticsDebugger` currently calls
`View.pipelineOwnerOf(context)` from `_attach()`, which can be reached from
`initState()` and trigger an inherited-widget lifecycle assert. Keep
`ensureSemantics()` and the `WidgetsBinding` observer setup in `_attach()`, but
move the pipeline-owner lookup/rebind logic into `didChangeDependencies()` so it
runs after dependencies are available. Update the existing
`didChangeDependencies()`, `_attach()`, and `_detach()` paths to ensure listener
registration is still balanced when `widget.enabled` changes.
In
`@packages/stream_core_flutter/lib/src/a11y/stream_accessibility_autofocus.dart`:
- Around line 96-118: Handle runtime changes to widget.enabled in
StreamAccessibilityAutofocus: add a didUpdateWidget override to detect enabled
toggles and start/stop the attempt window accordingly, since
didChangeDependencies only tracks MediaQuery.accessibleNavigationOf changes.
Also guard _requestFocus so it no-ops when widget.enabled is false, ensuring the
class behaves as a passthrough when disabled and can begin autofocus when
re-enabled without waiting for a MediaQuery change.
In
`@packages/stream_core_flutter/lib/src/components/common/stream_text_input.dart`:
- Around line 477-493: The desktop accessibility focus handlers in
stream_text_input should not move real input focus into a disabled
StreamTextInput. Update handleDidGainAccessibilityFocus to also check
props.enabled before calling _effectiveFocusNode.requestFocus, using the same
enabled gate already used by onFocus, and keep the existing
canRequestFocus/hasFocus checks in the StreamTextInput build logic.
- Around line 437-441: The label style default is not resolving per widget
state, so disabled inputs never dim like textStyle and hintStyle do. Update the
labelStyle fallback in StreamTextInputTheme/StreamTextInput to use a state-aware
style resolver (for example the same WidgetStateTextStyle pattern used by
textStyle and hintStyle) so WidgetStateProperty.resolveAs can apply
disabled-state changes. Make sure the default label style matches the documented
theme behavior and the actual referenced typography token used in the
implementation.
- Around line 467-475: The helper text semantics in StreamTextInput are applying
liveRegion twice, once on the outer Semantics wrapper and again inside
StreamHelperText, which can cause duplicate announcements. Update the
helperWidget branch so the outer Semantics around props.helperText keeps only
the sortKey (_helperSortKey) and remove the liveRegion flag there, leaving
StreamHelperText to manage the live-region behavior.
In
`@packages/stream_core_flutter/lib/src/components/controls/stream_stepper.dart`:
- Around line 225-261: The StreamStepper semantics wrapper currently hides
descendant semantics but does not prevent focus traversal or pointer
interaction. Update the widget around the Row in StreamStepper so it also
includes a FocusScope configured to block focus for descendants and a
pointer-blocking wrapper such as IgnorePointer or AbsorbPointer to keep taps
from reaching the inner StreamTextInput and buttons. Keep the existing Semantics
configuration intact, and make sure the new wrappers are applied in the same
build path as the slider semantics so the descendant focus and hit-testing
behavior match the stream_stepper_semantics_test expectations.
In
`@packages/stream_core_flutter/lib/src/components/message_composer/attachment/stream_message_composer_attachment.dart`:
- Around line 180-190: The attachment semantics in
stream_message_composer_attachment.dart currently drops props.semanticLabel when
mergeRemoveAction is false because the ternary returns bare innerContent in that
branch. Update the container-building logic around the semantics wrapper so the
label is always applied, and only conditionally add the
MergeSemantics/onTap/onTapHint remove action behavior when mergeRemoveAction is
true. Use the existing props.semanticLabel, props.mergeRemoveAction, and
innerContent logic to keep the announced label consistent in both branches.
In
`@packages/stream_core_flutter/lib/src/theme/components/stream_text_input_theme.dart`:
- Around line 232-241: The default label styling for Stream text input is
inconsistent with the documented disabled-state behavior and font description.
Update the _StreamTextInputDefaults.labelStyle getter in stream_text_input.dart
so it uses a state-aware WidgetStateTextStyle.resolveWith pattern like textStyle
and hintStyle, returning textDisabled when disabled and the normal label color
otherwise. Also align the docs/comments for labelStyle with the actual default
typography, or change the default to match the documented captionEmphasis if
that is the intended design.
In
`@packages/stream_core_flutter/test/a11y/stream_accessibility_autofocus_test.dart`:
- Around line 204-218: The test widget tree in the accessibility autofocus test
is missing const constructor usage, triggering prefer_const_constructors under
dart analyze --fatal-infos. Update the pumpWidget setup to use const where
possible for MaterialApp, MediaQuery, Scaffold, and Column, keeping the existing
StreamAccessibilityAutofocus widgets and SizedBox children intact so the test
remains functionally unchanged.
In
`@packages/stream_core_flutter/test/components/buttons/stream_button_color_resolution_test.dart`:
- Around line 19-22: The test setup in StreamTheme is passing a redundant
default value for brightness, which triggers avoid_redundant_argument_values
under analysis. Update the StreamTheme constructions in
stream_button_color_resolution_test to omit brightness: Brightness.light where
the default already applies, and make the same adjustment in the repeated setup
near the other StreamTheme instance so both use the default consistently.
In
`@packages/stream_core_flutter/test/components/message_composer/attachment/file_attachment_semantics_test.dart`:
- Around line 7-26: Remove the unused _fmt helper from the
file_attachment_semantics_test test file, since it is not referenced anywhere
and causes the analyze step to fail with unused_element. Delete the standalone
_fmt(SemanticsData d) function and keep the existing inline semantics formatting
logic used in the test assertions so the test still covers the same behavior.
In
`@packages/stream_core_flutter/test/components/snackbar/stream_snackbar_test.dart`:
- Around line 433-440: The snackbar semantics test uses isSemantics in
stream_snackbar_test, but that matcher is not available on the oldest Flutter
SDK still allowed by the package constraint. Update the compatibility choice in
StreamSnackbar tests by either bumping the Flutter constraint in pubspec.yaml to
a version that includes isSemantics, or replace this assertion with
matchesSemantics in the StreamSnackbar test to keep the test compiling on
supported SDKs.
---
Nitpick comments:
In
`@packages/stream_core_flutter/test/components/buttons/stream_button_vs_icon_button_semantics_test.dart`:
- Around line 42-425: These tests are diagnostic-only and never assert anything,
so they provide no real coverage. Update the relevant testWidgets cases in
stream_button_vs_icon_button_semantics_test to use actual expectations with
semantics matchers instead of only debugPrint, focusing on the comparison
helpers like capturedEvents, _fmt, and the IconButton/StreamButton/ToggleButtons
checks. If they are meant to stay exploratory, move them out of the automated
test suite so the file is not treated as regression coverage.
In
`@packages/stream_core_flutter/test/components/message_composer/attachment/file_attachment_semantics_test.dart`:
- Around line 36-69: The semantic test for StreamMessageComposerFileAttachment
currently only dumps traversal output and never verifies behavior, so it can’t
catch regressions. Update the test in the file_attachment_semantics_test widget
test to add explicit expectations using semantics matchers or similar
assertions, referencing StreamMessageComposerFileAttachment, the title/subtitle
Text nodes, and the remove action triggered by onRemovePressed, so the test
checks labels/flags/actions instead of only calling debugPrint.
In
`@packages/stream_core_flutter/test/components/message_composer/attachment/media_attachment_semantics_test.dart`:
- Around line 15-56: The semantics test only prints the traversal/tree and never
verifies behavior, so add explicit assertions in
media_attachment_semantics_test.dart. In StreamMessageComposerMediaAttachment’s
test, replace the debug-only inspection with expect checks against
tester.semantics.simulatedAccessibilityTraversal() and/or the root SemanticsNode
to confirm both semanticLabel values (“Photo 1” and “Photo 2”) are present and
that the remove action is merged as expected.
In
`@packages/stream_core_flutter/test/components/snackbar/stream_snackbar_test.dart`:
- Around line 850-916: The accessibleNavigation tests in StreamSnackbarHost
currently verify only opacity and text visibility, but they do not cover
StreamSnackbarProps.onVisible or timed auto-dismiss behavior. Update the
accessibleNavigation group in stream_snackbar_test.dart to assert that onVisible
is invoked when a snackbar is shown and that a non-persistent snackbar still
closes automatically after its duration even with accessible navigation enabled.
Use the existing StreamSnackbarMessenger, StreamSnackbarHost, and
controller.closed expectations to keep the coverage tied to the same behavior
paths.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b6031289-a129-4399-bf5d-81de156f9081
📒 Files selected for processing (42)
.claude/skills/flutter-a11y/SKILL.mdapps/design_system_gallery/lib/components/common/stream_text_input.dartapps/design_system_gallery/lib/config/preview_configuration.dartapps/design_system_gallery/lib/core/preview_wrapper.dartapps/design_system_gallery/lib/widgets/scoped_semantics_debugger.dartapps/design_system_gallery/lib/widgets/toolbar/semantics_debugger_toggle.dartapps/design_system_gallery/lib/widgets/toolbar/toolbar.dartpackages/stream_core_flutter/lib/core.dartpackages/stream_core_flutter/lib/src/a11y/stream_accessibility_autofocus.dartpackages/stream_core_flutter/lib/src/a11y/stream_semantics_announcer.dartpackages/stream_core_flutter/lib/src/a11y/stream_semantics_transition_announcer.dartpackages/stream_core_flutter/lib/src/components/accessories/stream_file_type_icon.dartpackages/stream_core_flutter/lib/src/components/badge/stream_badge_notification.dartpackages/stream_core_flutter/lib/src/components/buttons/stream_button.dartpackages/stream_core_flutter/lib/src/components/common/stream_text_input.dartpackages/stream_core_flutter/lib/src/components/controls/stream_stepper.dartpackages/stream_core_flutter/lib/src/components/list/stream_list_tile.dartpackages/stream_core_flutter/lib/src/components/message_composer/attachment/stream_message_composer_attachment.dartpackages/stream_core_flutter/lib/src/components/message_composer/attachment/stream_message_composer_file_attachment.dartpackages/stream_core_flutter/lib/src/components/message_composer/attachment/stream_message_composer_media_attachment.dartpackages/stream_core_flutter/lib/src/components/message_composer/attachment/stream_message_composer_unsupported_attachment.dartpackages/stream_core_flutter/lib/src/components/sheet/stream_sheet.dartpackages/stream_core_flutter/lib/src/components/snackbar/stream_snackbar.dartpackages/stream_core_flutter/lib/src/components/snackbar/stream_snackbar_messenger.dartpackages/stream_core_flutter/lib/src/components/toolbar/stream_app_bar.dartpackages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_app_bar.dartpackages/stream_core_flutter/lib/src/components/toolbar/stream_sheet_header.dartpackages/stream_core_flutter/lib/src/theme/components/stream_text_input_theme.dartpackages/stream_core_flutter/lib/src/theme/components/stream_text_input_theme.g.theme.dartpackages/stream_core_flutter/test/a11y/stream_accessibility_autofocus_test.dartpackages/stream_core_flutter/test/a11y/stream_semantics_announcer_test.dartpackages/stream_core_flutter/test/components/buttons/stream_button_color_resolution_test.dartpackages/stream_core_flutter/test/components/buttons/stream_button_test.dartpackages/stream_core_flutter/test/components/buttons/stream_button_vs_icon_button_semantics_test.dartpackages/stream_core_flutter/test/components/common/stream_text_input_test.dartpackages/stream_core_flutter/test/components/controls/stream_stepper_semantics_test.dartpackages/stream_core_flutter/test/components/message_composer/attachment/file_attachment_semantics_test.dartpackages/stream_core_flutter/test/components/message_composer/attachment/media_attachment_semantics_test.dartpackages/stream_core_flutter/test/components/sheet/stream_sheet_test.dartpackages/stream_core_flutter/test/components/snackbar/stream_snackbar_test.dartpackages/stream_core_flutter/test/components/toolbar/stream_app_bar_focus_isolation_test.dartpackages/stream_core_flutter/test/components/toolbar/stream_app_bar_test.dart
- Format two files flagged by dart format --set-exit-if-changed. - Rewrite the 0.5.0 changelog block to only list public-API surface changes; drop the internal semantic-tree improvements that don't change the widget contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
30d3d48 to
bcb5383
Compare
- StreamTextInput: drop the outer helper Semantics' liveRegion — the inner StreamHelperText already sets liveRegion on its own container, and nesting two liveRegion nodes can cause duplicate SR announcements. - StreamMessageComposerAttachment: apply semanticLabel in both branches of mergeRemoveAction so the label is announced regardless of whether the tile collapses into a single tap-to-remove focus stop. - StreamStepper tests: rewrite the boundary-action assertions to check the semantic data directly (hasAction) instead of invoking through the deprecated pipelineOwner.semanticsOwner path; drop the focus-isolation tests that asserted FocusScope / IgnorePointer wrappers not present in the impl (readOnly text input + Semantics excludeSemantics is sufficient for the intended SR-only slider abstraction). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rAttachment The prop was added defensively for a hypothetical future case (voice recording tile with play/pause controls needing the remove button as a separate SR stop), but every caller uses the default (true), no tests exercise false, and the branch for false introduced the bug where semanticLabel was silently dropped. The remaining behavior — whole tile is one SR focus stop, tap activates remove, the overlaid remove control is ExcludeSemantics'd — is what every caller actually wants. If the voice-recording case emerges, the prop can be re-added then with a real usage justifying it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n test env RendererBinding.rootPipelineOwner.semanticsOwner is null in the test binding (the semantic root lives on a descendant PipelineOwner in tests), so switching from tester.binding.pipelineOwner to rootPipelineOwner broke the sheet + app-bar-focus-isolation tests that assert on the semantic tree. Revert to the deprecated tester.binding.pipelineOwner path with an inline // ignore: deprecated_member_use comment. Flutter's own tests still use the deprecated path for the same reason (see e.g. packages/flutter/test/widgets/text_semantics_test.dart). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #126 +/- ##
==========================================
+ Coverage 37.26% 45.80% +8.54%
==========================================
Files 175 178 +3
Lines 6986 7243 +257
==========================================
+ Hits 2603 3318 +715
+ Misses 4383 3925 -458 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- ScopedSemanticsDebugger: move `View.pipelineOwnerOf(context)` lookup out of `_attach()` (called from `initState`, where `dependOnInheritedWidgetOfExactType` isn't safe). The listener is wired up in `didChangeDependencies` instead, which fires right after `initState` on first mount and on every owner change. - ScopedSemanticsDebugger: `_colorForNode` no longer paints explicitly-not-selected nodes with the "selected" color. Check `isSelected == Tristate.isTrue` instead of `!= Tristate.none`. - flutter-a11y skill: correct matcher table — `isSemantics` was added in Flutter 3.41 to replace `containsSemantics`, not `matchesSemantics`. `matchesSemantics` is still a distinct, non-deprecated matcher. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7ace3d3 to
4ed5aa1
Compare
…nabled If enabled is toggled to false while a retry timer is running, callbacks still fire but no longer produce FocusSemanticEvent dispatches. Cheaper than adding didUpdateWidget + full timer teardown, and matches the widget's stated use case (opt-in on first mount, not dynamic toggling). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2de5b4c to
60d02ba
Compare
- StreamMessageComposerAttachment: use MaterialLocalizations.of(context).deleteButtonTooltip for the remove onTapHint instead of the hardcoded 'remove' — localized alongside every other Material tooltip in the app, no extra translation infrastructure needed. - StreamMessageComposerFileAttachment: expose semanticLabel as a prop and drop the hardcoded 'File' default. The chat layer is responsible for passing the localized string (matches the pattern already used by StreamMessageComposerMediaAttachment). Core stays localization-free — all locale-sensitive strings either come from MaterialLocalizations or are passed in by the consumer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match the docstring shape used by StreamMessageComposerMediaAttachmentProps.semanticLabel — summary line, accessibility-modes note, example type prefixes, and the SemanticsProperties.label reference — on StreamMessageComposerFileAttachmentProps.semanticLabel too. Also refresh the base StreamMessageComposerAttachmentProps.semanticLabel doc: the tap hint is now MaterialLocalizations.deleteButtonTooltip (not the previous hardcoded "remove") — the doc referenced the old wording. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VelikovPetar
left a comment
There was a problem hiding this comment.
Looks very good to me!
Left some minor remarks, but probably would be good to also get a review from someone else who is more familiar with Flutter accessibility before merging the PR!
| ), | ||
| ); | ||
|
|
||
| debugPrint('=== ACCESSIBILITY TRAVERSAL ORDER (what SR walks) ==='); |
There was a problem hiding this comment.
Should we maybe add some assertions in this tests? As far as I can see, we only have print logs
|
|
||
| debugPrint('\n=== FULL TREE ==='); | ||
| final root = RendererBinding.instance.rootPipelineOwner.semanticsOwner?.rootSemanticsNode; | ||
| debugPrint(root?.toStringDeep() ?? '(none)'); |
There was a problem hiding this comment.
Same as above, I don't think we are asserting anything here
Brazol
left a comment
There was a problem hiding this comment.
LGTM, added small comment about platform check
Provides an alternative screen-reader label that replaces the badge's rendered icon and duration when set. Consumers can pass a spelled-out duration (e.g. '1 minute 23 seconds') so TalkBack/VoiceOver read the value unambiguously instead of the M:SS visual form. The badge itself wraps its content in Semantics + ExcludeSemantics so the caller's label fully replaces the visual reading. Dartdoc mirrors Flutter's own `Text.semanticsLabel` style: state what the property does and what it replaces, nothing more. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ions The file/media attachment semantic tests previously dumped the semantic tree via `debugPrint` — useful during authoring, but they didn't gate anything, as flagged in review. Replace the diagnostics with concrete expectations against the merged-node contract: - File attachment: one SR focus stop whose label includes both the title and subtitle Text, with a tap action and 'Delete' onTapHint. - Media attachment (two tiles): one SR node per tile with the caller-supplied semanticLabel, tap action, and 'Delete' onTapHint; no leaked semantics from the ExcludeSemantics'd remove control; correct left-to-right traversal order. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two focused expectations for the property added in 7042a57: - When null, the badge's visual duration text is announced verbatim by screen readers (e.g. TalkBack reads '1:23'). - When set, the caller-supplied label replaces the visual content in the semantic tree — the ExcludeSemantics wrapping prevents the SR from also announcing the M:SS form, so the badge speaks only the spelled-out duration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/stream_core_flutter/lib/src/components/badge/stream_media_badge.dart (1)
84-92: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winConsider
container: truefor the label override wrapper.Without
container: true, thisSemantics(label: label, ...)node can merge upward into an ancestor's semantics node instead of remaining its own node, depending on whereStreamMediaBadgeis composited (e.g., overlaid inside a media attachment tile that itself sets asemanticLabel). The provided test wraps the badge directly underScaffold, so it doesn't exercise this nested scenario.♿ Proposed fix
if (semanticsLabel case final label?) { badge = Semantics( + container: true, label: label, child: ExcludeSemantics(child: badge), ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/stream_core_flutter/lib/src/components/badge/stream_media_badge.dart` around lines 84 - 92, The semantics override in StreamMediaBadge should stay as its own node instead of merging into an ancestor. Update the Semantics wrapper in the badge-building logic to use container semantics when applying the optional semanticsLabel override, keeping the ExcludeSemantics child behavior intact. Make this change in the badge composition path around the label override so nested uses of StreamMediaBadge preserve the intended accessibility label.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/stream_core_flutter/test/components/message_composer/attachment/file_attachment_semantics_test.dart`:
- Line 29: The semantics lookup in file_attachment_semantics_test uses a regular
string with escaped dots, which violates the use_raw_strings lint. Update the
tester.getSemantics call to use a raw string for the RegExp pattern in the
message composer attachment semantics test, keeping the same matching behavior
while satisfying analyzer rules.
---
Nitpick comments:
In
`@packages/stream_core_flutter/lib/src/components/badge/stream_media_badge.dart`:
- Around line 84-92: The semantics override in StreamMediaBadge should stay as
its own node instead of merging into an ancestor. Update the Semantics wrapper
in the badge-building logic to use container semantics when applying the
optional semanticsLabel override, keeping the ExcludeSemantics child behavior
intact. Make this change in the badge composition path around the label override
so nested uses of StreamMediaBadge preserve the intended accessibility label.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0754a911-b991-4ab3-9580-f5a28d20e4de
📒 Files selected for processing (45)
.claude/skills/flutter-a11y/SKILL.mdapps/design_system_gallery/lib/components/common/stream_text_input.dartapps/design_system_gallery/lib/config/preview_configuration.dartapps/design_system_gallery/lib/core/preview_wrapper.dartapps/design_system_gallery/lib/widgets/scoped_semantics_debugger.dartapps/design_system_gallery/lib/widgets/toolbar/semantics_debugger_toggle.dartapps/design_system_gallery/lib/widgets/toolbar/toolbar.dartpackages/stream_core_flutter/CHANGELOG.mdpackages/stream_core_flutter/lib/core.dartpackages/stream_core_flutter/lib/src/a11y/stream_accessibility_autofocus.dartpackages/stream_core_flutter/lib/src/a11y/stream_semantics_announcer.dartpackages/stream_core_flutter/lib/src/a11y/stream_semantics_transition_announcer.dartpackages/stream_core_flutter/lib/src/components/accessories/stream_file_type_icon.dartpackages/stream_core_flutter/lib/src/components/badge/stream_badge_notification.dartpackages/stream_core_flutter/lib/src/components/badge/stream_media_badge.dartpackages/stream_core_flutter/lib/src/components/buttons/stream_button.dartpackages/stream_core_flutter/lib/src/components/common/stream_text_input.dartpackages/stream_core_flutter/lib/src/components/controls/stream_stepper.dartpackages/stream_core_flutter/lib/src/components/list/stream_list_tile.dartpackages/stream_core_flutter/lib/src/components/message_composer/attachment/stream_message_composer_attachment.dartpackages/stream_core_flutter/lib/src/components/message_composer/attachment/stream_message_composer_file_attachment.dartpackages/stream_core_flutter/lib/src/components/message_composer/attachment/stream_message_composer_media_attachment.dartpackages/stream_core_flutter/lib/src/components/message_composer/attachment/stream_message_composer_unsupported_attachment.dartpackages/stream_core_flutter/lib/src/components/sheet/stream_sheet.dartpackages/stream_core_flutter/lib/src/components/snackbar/stream_snackbar.dartpackages/stream_core_flutter/lib/src/components/snackbar/stream_snackbar_messenger.dartpackages/stream_core_flutter/lib/src/components/toolbar/stream_app_bar.dartpackages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_app_bar.dartpackages/stream_core_flutter/lib/src/components/toolbar/stream_sheet_header.dartpackages/stream_core_flutter/lib/src/theme/components/stream_text_input_theme.dartpackages/stream_core_flutter/lib/src/theme/components/stream_text_input_theme.g.theme.dartpackages/stream_core_flutter/test/a11y/stream_accessibility_autofocus_test.dartpackages/stream_core_flutter/test/a11y/stream_semantics_announcer_test.dartpackages/stream_core_flutter/test/components/badge/stream_media_badge_semantics_test.dartpackages/stream_core_flutter/test/components/buttons/stream_button_color_resolution_test.dartpackages/stream_core_flutter/test/components/buttons/stream_button_test.dartpackages/stream_core_flutter/test/components/buttons/stream_button_vs_icon_button_semantics_test.dartpackages/stream_core_flutter/test/components/common/stream_text_input_test.dartpackages/stream_core_flutter/test/components/controls/stream_stepper_semantics_test.dartpackages/stream_core_flutter/test/components/message_composer/attachment/file_attachment_semantics_test.dartpackages/stream_core_flutter/test/components/message_composer/attachment/media_attachment_semantics_test.dartpackages/stream_core_flutter/test/components/sheet/stream_sheet_test.dartpackages/stream_core_flutter/test/components/snackbar/stream_snackbar_test.dartpackages/stream_core_flutter/test/components/toolbar/stream_app_bar_focus_isolation_test.dartpackages/stream_core_flutter/test/components/toolbar/stream_app_bar_test.dart
✅ Files skipped from review due to trivial changes (2)
- packages/stream_core_flutter/lib/src/components/message_composer/attachment/stream_message_composer_unsupported_attachment.dart
- packages/stream_core_flutter/lib/src/theme/components/stream_text_input_theme.g.theme.dart
🚧 Files skipped from review as they are similar to previous changes (34)
- packages/stream_core_flutter/lib/core.dart
- packages/stream_core_flutter/test/components/buttons/stream_button_test.dart
- apps/design_system_gallery/lib/widgets/toolbar/toolbar.dart
- apps/design_system_gallery/lib/widgets/toolbar/semantics_debugger_toggle.dart
- apps/design_system_gallery/lib/config/preview_configuration.dart
- packages/stream_core_flutter/test/components/buttons/stream_button_color_resolution_test.dart
- apps/design_system_gallery/lib/core/preview_wrapper.dart
- packages/stream_core_flutter/test/a11y/stream_semantics_announcer_test.dart
- packages/stream_core_flutter/test/components/toolbar/stream_app_bar_test.dart
- packages/stream_core_flutter/lib/src/components/snackbar/stream_snackbar_messenger.dart
- packages/stream_core_flutter/test/components/common/stream_text_input_test.dart
- packages/stream_core_flutter/test/a11y/stream_accessibility_autofocus_test.dart
- packages/stream_core_flutter/test/components/toolbar/stream_app_bar_focus_isolation_test.dart
- packages/stream_core_flutter/lib/src/components/badge/stream_badge_notification.dart
- packages/stream_core_flutter/lib/src/a11y/stream_semantics_transition_announcer.dart
- packages/stream_core_flutter/test/components/sheet/stream_sheet_test.dart
- packages/stream_core_flutter/lib/src/components/accessories/stream_file_type_icon.dart
- packages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_app_bar.dart
- packages/stream_core_flutter/lib/src/theme/components/stream_text_input_theme.dart
- packages/stream_core_flutter/lib/src/components/buttons/stream_button.dart
- packages/stream_core_flutter/lib/src/components/toolbar/stream_sheet_header.dart
- packages/stream_core_flutter/lib/src/components/list/stream_list_tile.dart
- packages/stream_core_flutter/test/components/buttons/stream_button_vs_icon_button_semantics_test.dart
- apps/design_system_gallery/lib/components/common/stream_text_input.dart
- packages/stream_core_flutter/lib/src/components/message_composer/attachment/stream_message_composer_media_attachment.dart
- packages/stream_core_flutter/lib/src/components/controls/stream_stepper.dart
- packages/stream_core_flutter/lib/src/a11y/stream_accessibility_autofocus.dart
- packages/stream_core_flutter/lib/src/a11y/stream_semantics_announcer.dart
- packages/stream_core_flutter/test/components/snackbar/stream_snackbar_test.dart
- packages/stream_core_flutter/lib/src/components/snackbar/stream_snackbar.dart
- packages/stream_core_flutter/lib/src/components/sheet/stream_sheet.dart
- packages/stream_core_flutter/lib/src/components/toolbar/stream_app_bar.dart
- apps/design_system_gallery/lib/widgets/scoped_semantics_debugger.dart
- packages/stream_core_flutter/lib/src/components/common/stream_text_input.dart
Summary
Design-system-wide accessibility pass touching most reusable primitives in
stream_core_flutter. Focus areas: screen-reader announcement, focus management, and semantic tree structure. No product/chat-domain code — all changes are generic.New generic a11y primitives:
StreamAccessibilityAutofocus— behavior-only wrapper that requests SR entry focus on its child, with bounded retry, SR-toggle reactivity, and a per-route uniqueness debug assertion.StreamSemanticsTransitionAnnouncer— observes a Listenable, computes a transition message, dispatches viaStreamSemanticsAnnouncer.Component updates (each in its own commit):
StreamButton— Semantics + Tooltip moved outsideElevatedButton, wrapped inMergeSemanticssoselected/tooltipmerge cleanly with the button's own node.StreamStepper— announces as a singleslidernode withvalue/increasedValue/decreasedValue/onIncrease/onDecrease; internal +/- buttons and text field are excluded from SR.StreamListTile— exposesfocusNode/autofocus(mirrors MaterialListTile).StreamBadgeNotification,StreamFileTypeIcon— accept optionalsemanticLabel.StreamSnackbar— newonVisiblecallback (fires after entrance, or synchronously when SR is active) for SR announcement hooks.StreamAppBar,StreamSheetHeader— title/subtitle merged; wrapped inSemantics(header: true, namesRoute: true)by default withexcludeHeaderSemanticsopt-out; auto-implied leading icon getsMaterialLocalizationsback/close tooltip.StreamBottomAppBar— grouped in an outerSemantics(container: true); title/subtitle merge without heading/namesRoute (secondary toolbar).StreamTextInput— auto-focus-on-a11y-focus behavior gated to desktop platforms; error text moved to its own live-region container.semanticLabel+mergeRemoveActionprops; content sub-trees excluded so the tile is one focus stop with aremovetap action.Skill doc:
chore(claude): expand flutter-a11y skill with focus, dispatch, role-annotation guidance— adds guidance on role-annotation vs. container wrapping,FocusTraversalGroupbeing keyboard-only, andFocusSemanticEventplatform mapping /onDidGainAccessibilityFocusnon-round-trip.Test plan
melos run test:flutterfrom the core repo passesmelos run analyzepassesmelos run formatpasses🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes