Skip to content

feat(ui): sync default message actions with design system#2701

Merged
xsahil03x merged 16 commits into
v10.0.0from
feat/sync-message-actions-with-figma
Jun 1, 2026
Merged

feat(ui): sync default message actions with design system#2701
xsahil03x merged 16 commits into
v10.0.0from
feat/sync-message-actions-with-figma

Conversation

@xsahil03x
Copy link
Copy Markdown
Member

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

  1. Reply
  2. Thread Reply
  3. Pin / Unpin
  4. Copy Message
  5. Mark Unread
  6. Edit Message
  7. Flag Message
  8. Mute / Unmute User
  9. — separator —
  10. Delete Message (destructive)
  11. Block / Unblock User (destructive, new)

What's in here

  • New BlockUser / UnblockUser MessageAction classes, dispatched to client.blockUser / client.unblockUser. Toggle is driven by currentUser.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).
  • English label fixes (mismatch with Figma):
    • threadReplyLabel: 'Thread''Thread Reply'
    • markAsUnreadLabel: 'Mark as Unread''Mark Unread'
    • Non-EN locales already used correct local translations and are unchanged.
  • Reordered actions in StreamMessageActionsBuilder.buildActions.

What's NOT in here

  • Remind Me and Save For Later menu entries — these are visible in the Figma but require backend reminder + save-for-later infrastructure on the UI side. Separate feature; deliberately deferred.
  • Sample app already exposes Block/Unblock through ContactDetailSheet, channel-swipe action, and ChannelDetailSheet — 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 for toggleBlockUnblockUserText both states)
  • melos run test on stream_chat_localizations — all 22 pass
  • dart analyze on changed dirs — no new errors
  • Manual: long-press a message in the sample app to confirm the new order, the destructive separator, Block icon (noSign) and Unblock icon (userCheck), and that tapping Block calls client.blockUser.

🤖 Generated with Claude Code

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.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f4e48171-bb3e-4f63-b0d4-91d4f4f0de86

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sync-message-actions-with-figma

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

xsahil03x and others added 7 commits June 1, 2026 16:25
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
Copy link
Copy Markdown

codecov Bot commented Jun 1, 2026

Codecov Report

❌ Patch coverage is 96.55172% with 2 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (v10.0.0@8fc3f86). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...er/lib/src/message_widget/stream_message_item.dart 0.00% 2 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

xsahil03x and others added 8 commits June 1, 2026 17:05
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.
@xsahil03x xsahil03x merged commit 98097fa into v10.0.0 Jun 1, 2026
11 checks passed
@xsahil03x xsahil03x deleted the feat/sync-message-actions-with-figma branch June 1, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants