feat(ui): sync default message actions with design system#2701
Conversation
Reorders the default message-action context menu to match the Chat SDK Design System, adds a Block / Unblock User action, and fixes two English labels that didn't match Figma. Brings parity with Android Compose and iOS SwiftUI, which both already ship Block/Unblock by default. - New order: Reply, Thread Reply, Pin/Unpin, Copy, Mark Unread, Edit, Flag, Mute/Unmute, Delete (destructive), Block/Unblock (destructive). - Adds `BlockUser` / `UnblockUser` `MessageAction` classes wired to `client.blockUser` / `client.unblockUser`. Toggles based on `currentUser.blockedUserIds`. Rendered as destructive (red) with the separator above it, matching the design. - Adds `Translations.toggleBlockUnblockUserText` for all 11 supported locales. - Fixes English labels: `threadReplyLabel` `'Thread'` -> `'Thread Reply'` and `markAsUnreadLabel` `'Mark as Unread'` -> `'Mark Unread'`. Non-EN locales already used correct local translations and are unchanged.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ 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 |
Match the early-return style used by `toggleMuteUnmuteUserText` in `stream_chat_localizations_no.dart`. Also tighten the CHANGELOG entries to match the brevity of surrounding bullets.
Other Korean menu entries use bare noun phrases (`사용자 음소거`, `사용자 차단`); `스레드 응답입니다` ended with the declarative copula `입니다`, reading as a sentence rather than a menu item. Switched to `스레드 답변`, which is the more idiomatic term for a message reply in Korean.
Renaming `threadReplyLabel` to `'Thread Reply'` to match the design system unintentionally renamed `StreamThreadHeader`'s default title, which was sourcing the same key. Introduce a dedicated `threadLabel` translation key (`'Thread'`) and switch the header to use it. The message action keeps the longer `'Thread Reply'` label.
Group destructive actions so the most-destructive one (Delete) ends up furthest from the natural tap target, matching Android Compose / React Native conventions.
Extends `translations_test.dart`'s per-locale loop to assert both branches of `toggleBlockUnblockUserText` and the new `threadLabel` getter for every supported language. Also implements the new methods in the example `NnStreamChatLocalizations` so the example tree analyzes clean.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## v10.0.0 #2701 +/- ##
==========================================
Coverage ? 67.61%
==========================================
Files ? 407
Lines ? 24469
Branches ? 0
==========================================
Hits ? 16545
Misses ? 7924
Partials ? 0 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
The previous docs `message_widget_actions` golden was missing the Mute User entry because the shared `setupMockChannel` helper never stubbed `channel.config`, so the message-actions builder's `channel.config?.mutes == true` guard short-circuited to false. Stub `channel.config` with `ChannelConfig(mutes: true)` to mirror the default Stream channel-type config (and what the SDK's `message_actions_builder_test.dart` already does). After this lands, run `update_goldens` with target=docs to regenerate the snapshot.
Two issues with the prior workflow: 1. With target=both, the two parallel regen jobs both tried to commit directly; whichever pushed second saw a non-fast-forward and dropped its golden updates with no recovery. 2. Common case (regenerate everything) required picking from the dropdown. Refactor each regen job to upload its goldens as a job artifact instead of committing, and introduce a third job that downloads whatever ran and creates a single `chore: Update Goldens` commit. This eliminates the race, produces one commit regardless of target, preserves the ubuntu/macos OS split (SDK on cheap Ubuntu, docs on macOS), and keeps parallelism on target=both. Also reorder the input choices so `both` sits first and is the default. The commit job uses `always()` with an explicit result check so: - target=sdk: docs job skipped, commit runs with only the sdk artifact. - target=docs: sdk job skipped, commit runs with only the docs artifact. - target=both: both regens run in parallel, commit job waits for both. - A hard failure on one side (e.g. bootstrap crash) skips the commit rather than landing partial state.
`packages/` and `docs/` are the only top-level directories hosting golden PNGs today; both patterns differ only in that prefix. Switch the commit step to `**/test/**/goldens/**/*.png` so any future package added under a different top-level directory is picked up automatically. The per-job upload paths are intentionally left scoped so each artifact contains only its own slice.
Match the SDK job's `goldens/**/*.png` pattern. If docs ever moves from alchemist's macOS platform goldens to its platform-independent CI mode, the upload would otherwise silently miss the regenerated PNGs at `goldens/ci/`. The docs job runs on macOS so today's output still lands at `goldens/macos/`, which the broader pattern still matches.
Each regen job now runs a 'Detect changed goldens' step that uses `git status --porcelain` (catches modified AND untracked PNGs) to set a `changed` output. The artifact upload is gated on `changed == 'true'`, and the tail commit job's `if:` reads both job outputs so it only runs when at least one regen actually produced changes. Net effect: - No-op runs (no goldens drifted, no UI change to capture) finish with two green regen jobs, skipped upload steps, and a skipped commit job — no bytes pushed to artifact storage, no empty commit attempt. - Runs that produce changes behave exactly as before.
`actions/upload-artifact@v4` strips the path prefix before the first wildcard, so an upload of `packages/**/test/**/goldens/**/*.png` lands in the archive as `stream_chat_flutter/test/...`, dropping `packages/`. On download the file is extracted to `./stream_chat_flutter/...` and the real `packages/stream_chat_flutter/...` in the checkout is never touched — `git-auto-commit-action` then reports a clean tree even though the regen produced changes. Include `melos.yaml` (a workspace-root file) alongside the glob so the artifact's least-common-ancestor is the workspace root, preserving the full path. The anchor file overwrites an identical copy on download and is a no-op for the commit (its path doesn't match the goldens glob in `file_pattern`).
`actions/upload-artifact@v4` strips the path prefix up to the first wildcard, so `packages/**/.../foo.png` lands in the archive as `.../foo.png` and on download extracts to the wrong location. The previous commit worked around this by including `melos.yaml` as an anchor to force the LCA to the workspace root — clever but load-bearing on undocumented behavior. Replace the trick with an explicit tar/untar: each regen job pipes `find` into `tar` to bundle the goldens with their full paths, the commit job extracts the tarballs in place, and `git-auto-commit-action` sees the real diff. No anchor, no LCA dance, behavior is independent of what upload-artifact decides to do with globs.
Summary
Reorders the default message-action context menu to match the Chat SDK Design System Figma, adds the missing Block / Unblock User action, and fixes two English labels that didn't match the design. This brings v10 to parity with Android Compose and iOS SwiftUI, which both already ship Block/Unblock by default.
New default order
What's in here
BlockUser/UnblockUserMessageActionclasses, dispatched toclient.blockUser/client.unblockUser. Toggle is driven bycurrentUser.blockedUserIds. Rendered red below the separator, matching Figma.Translations.toggleBlockUnblockUserText({required bool isBlocked})added to the abstract API + default impl, plus implementations for all 11 supported locales (ca, de, en, es, fr, hi, it, ja, ko, no, pt).threadReplyLabel:'Thread'→'Thread Reply'markAsUnreadLabel:'Mark as Unread'→'Mark Unread'StreamMessageActionsBuilder.buildActions.What's NOT in here
ContactDetailSheet, channel-swipe action, andChannelDetailSheet— all target different surfaces, so no removal was needed.Test plan
flutter test packages/stream_chat_flutter/test/src/message_action/message_actions_builder_test.dart— 15 pass (1 new test for Block/Unblock visibility logic, 3 cases: non-blocked → BlockUser; already-blocked → UnblockUser; own message → neither)flutter test packages/stream_chat_flutter/test/src/localization/default_translations_test.dart— passes (new assertion fortoggleBlockUnblockUserTextboth states)melos run testonstream_chat_localizations— all 22 passdart analyzeon changed dirs — no new errorsnoSign) and Unblock icon (userCheck), and that tapping Block callsclient.blockUser.🤖 Generated with Claude Code