Skip to content

docs(ui): make docs snapshots authentic to the production SDK#2676

Merged
xsahil03x merged 29 commits into
v10.0.0from
docs/snapshots-platform-pin
May 21, 2026
Merged

docs(ui): make docs snapshots authentic to the production SDK#2676
xsahil03x merged 29 commits into
v10.0.0from
docs/snapshots-platform-pin

Conversation

@xsahil03x
Copy link
Copy Markdown
Member

@xsahil03x xsahil03x commented May 20, 2026

Summary

Reworks the docs_screenshots golden suite so every snapshot looks like the real app instead of a Material approximation. The pivots:

  • Pin the platform. Override CurrentPlatform, defaultTargetPlatform, and ThemeData.platform to iOS in a docsGoldenTest wrapper. Set inside alchemist's pumpWidget hook, reset via whilePerforming's cleanup callback — the only timing that satisfies _verifyInvariants. Snapshots render identically on macOS dev and Linux CI.
  • Real San Francisco on macOS. _loadAppleSystemFont registers /System/Library/Fonts/SFNS.ttf under the CupertinoSystemDisplay / CupertinoSystemText family aliases Material's iOS typography expects. Apple Color Emoji loaded the same way. No more Roboto stand-in.
  • Real user avatars. 18 portrait fixtures sourced directly from Figma node 2867:55922 ("User Photo") live under test/fixtures/avatars/. A networkImage StreamComponentBuilder (installed via GoldenComponentFactory) resolves any URL by its slug (https://docs.fixture/avatar/amelia-moore.png → that fixture). Adding a new avatar is two lines: drop a PNG + add the slug to _avatarSlugs.
  • Named sample users. test/src/sample_users.dart exports 18 named User constants matching the Figma roster (ameliaMoore, noahSmith, …) plus a single ownUser (OwnUser) for the signed-in identity used across every snapshot. User(id: 'user-2', name: 'Bob', …) literals are gone.
  • Chrome matches sample_app. StreamChannelListHeader everywhere a generic AppBar used to live (with trailing: StreamButton.icon(icons.plus)), bottom nav copies sample's Chats / Threads two-tab theming verbatim, StreamSheetHeader for the poll creator, message-actions modal uses StreamMessageActionsBuilder.buildActions + StreamContextMenuAction.partitioned so Delete lands at the end under a separator. Empty states use the SDK's default StreamScrollViewEmptyWidget.
  • Real Slidable + real drag. The slidable snapshots now wrap each row in Slidable (sample app's exact ActionPane with BehindMotion, more + mute) and trigger the reveal via whilePerforming: (tester) async { await tester.drag(...); await tester.pumpAndSettle(); }. ~70 lines of Stack + Transform.translate fakery deleted.
  • Mock fidelity. setupMockChannel defaults to a deterministic 2–6 member group seeded off channel.id (so the Engineering channel happens to surface the +N overflow chip), stubs channel.memberCount so group headers read "3 Members" instead of misleading "Last online a few seconds ago".
  • macOS workflow for docs goldens. update_goldens.yml gained a target: sdk|docs dispatch input — SDK target stays on ubuntu-latest, docs target runs on macos-latest for authoritative platform-variant regen. Auto-commit patterns are scoped so the two jobs never fight.
  • Gitignore actually enforces macOS-only. Previous !**/goldens/macos/* was a no-op without a preceding ignore. Now **/test/**/goldens/** ignores by default with !.../macos/ (re-include the directory) + !.../macos/** (re-include files) — both lines required because git can't re-include a file beneath an ignored directory.

What this fixes / adds

Before After
Snapshots flake between hosts (mic button visibility, font metrics) Pinned platform — same render anywhere
AppBar/TextField text rendered as Ahem tofu Real SF on macOS, Roboto/CI obscured on Linux runners
Gradient-letter avatar placeholders 18 real portraits matching the design system
Generic AppBar('Stream Chat') everywhere StreamChannelListHeader with avatar leading + plus trailing
Custom 6-action message menu with Material icons SDK's canonical 9-action menu, destructive partitioned to end
Fake Transform.translate slidable Real Slidable + real swipe gesture
channel.image: null → "Last online a few seconds ago" on group channels Stubbed memberCount → "3 Members"

Test plan

  • fvm flutter analyze — no issues across the suite
  • fvm flutter test (no --update-goldens) — 41/41 pass
  • fvm flutter test --update-goldens — clean regeneration, all goldens stable across local runs
  • update_goldens workflow with target=docs on macOS — verified end-to-end, bot auto-commits PNGs scoped to docs/**/goldens/macos/*.png

🤖 Generated with Claude Code

📦 Migration guide — bringing #2649's snapshots onto this branch

#2649 ("docs(ui): more docs screenshots") adds four new snapshot scenarios (flutter_showcase, localization_support, theming, stream_message_composer extensions) plus a GoldenComponentFactory and a docsScreenshotsDarkTheme(). This branch reshapes most of that surface area. Here's how to land #2649's content on top of these patterns.

TL;DR

#2649 ships This branch wants you to use
Inline User(id: 'user-1', name: 'Mace Windu', …) Sample users from test/src/sample_users.dartameliaMoore, noahSmith, etc.
final _currentUser = OwnUser(id: 'user-1', name: 'Alice') ownUser (already an OwnUser, single source of truth)
goldenTest('…', …) docsGoldenTest('…', …) — pins platform, wraps in GoldenComponentFactory, precaches images
GoldenComponentFactory wrapped inside every scaffold Auto-applied by docsGoldenTest — drop the manual wrap
import 'package:alchemist/alchemist.dart'; Not needed anymore (the helper re-exports what tests use)
final _redTheme = ThemeData(…) hand-built Extend docsScreenshotsTheme() (preserves platform: TargetPlatform.iOS, fontFamily: CupertinoSystemText, etc.)
stream_chat_localizations added to pubspec.yaml Same — but goes under dev_dependencies, not dependencies

File-by-file

test/src/golden_component_factory.dart

Already on this branch — delete #2649's copy entirely and accept ours. It also overrides networkImage to render avatar fixtures, which #2649's version doesn't.

test/src/golden_theme.dart

#2649 adds docsScreenshotsDarkTheme(). Keep that addition, but rebuild it from our light theme so it inherits the platform pin and SF font setup. Sketch:

ThemeData docsScreenshotsDarkTheme() {
  final streamTextTheme = core.StreamTextTheme().apply(
    color: core.StreamColorScheme.dark().systemText,
    fontFamily: 'CupertinoSystemText',
  );
  return ThemeData(
    useMaterial3: true,
    brightness: Brightness.dark,
    platform: docsScreenshotsTargetPlatform,
    scaffoldBackgroundColor: const Color(0xFF000000),
    appBarTheme: const AppBarTheme(backgroundColor: Color(0xFF000000)),
    extensions: [
      StreamTheme(brightness: Brightness.dark, textTheme: streamTextTheme),
    ],
  );
}

test/theming/theming_test.dart

  • Replace final _currentUser = OwnUser(id: 'user-1', name: 'Mace Windu')ownUser.
  • Replace final _otherUser = User(id: 'user-2', name: 'Lando Calrissian') → pick a sample user (e.g. noahSmith). If the original "Mace / Lando" identities matter for the brand demo, add them to sample_users.dart and ship matching PNGs to test/fixtures/avatars/.
  • _redTheme() should compose from docsScreenshotsTheme() — copy its body and override extensions with the red StreamColorScheme. Keep the platform: and fontFamily: lines so iOS typography + SF font still resolve.
  • Use docsGoldenTest(...) instead of goldenTest(...).
  • Remove the inline GoldenComponentFactory(child: ...) wrap — docsGoldenTest already handles it.

test/localization/localization_support_test.dart

  • Replace inline User(id: 'user-1', name: 'Alice')ownUser.
  • Each per-locale phone wraps StreamChannel in MaterialApp. Use theme: docsScreenshotsTheme() so SF + Roboto fallback work in every locale.
  • Add stream_chat_localizations: ^10.0.0-beta.13 to dev_dependencies (not dependencies — the docs package is publish_to: none).
  • Switch to docsGoldenTest. Remove manual GoldenComponentFactory.

test/flutter_showcase/flutter_showcase_test.dart

  • The four phone crops use light + dark themes — pass docsScreenshotsTheme() and the new docsScreenshotsDarkTheme() (see above).
  • Inline User(id: 'user-2', name: 'Bassett') etc. → pick from sampleUsers. If you need the actual bassett identity for the marketing image, add it to sample_users.dart + drop a PNG into test/fixtures/avatars/.
  • Use docsGoldenTest. Remove any explicit GoldenComponentFactory wraps.
  • setupMockChannel now defaults to a deterministic 2–6 member group and stubs channel.memberCount. If the showcase phones need a specific member count, pass members: [...] explicitly.

test/message_input/stream_message_composer_test.dart & test/message_list/message_widget_test.dart

  • The "message input custom buttons" and "message reaction theming" additions from docs(ui): showcase, localization, theming, and composer snapshots #2649 mostly survive — but swap User(id: 'other-user', …)ameliaMoore / noahSmith / similar.
  • Replace any goldenTest(...) with docsGoldenTest(...).
  • Drop import 'package:alchemist/alchemist.dart'; if it's only used for goldenTest.

Channel + member defaults

#2649's tests construct channels via setupMockChannel or fakeChannel without specifying members or capabilities. This branch now:

  • Defaults to a deterministic 2–6 sample-user group (seeded off channel.id) so each channel gets a unique stacked-avatar group.
  • Stubs channel.image: null so StreamChannelInfo derives the avatar from members.
  • Stubs channel.memberCount so group headers render "N Members" instead of "Last online a few seconds ago".

If a #2649 test relies on a single specific member or a channel.image, pass them explicitly. Otherwise the defaults give you authentic group avatars for free.

Workflow

#2649 doesn't touch CI. Once these tests land, run them via the new workflow:

gh workflow run update_goldens.yml --ref <branch> -f target=docs

The macOS job will render fresh goldens/macos/*.png and auto-commit them. Don't commit local goldens — let the workflow produce the authoritative bytes (local macOS rendering drifts by a few bytes from CI macOS).

Quick checklist

  • Delete docs(ui): showcase, localization, theming, and composer snapshots #2649's golden_component_factory.dart — use ours.
  • Move all User(id: …, name: …) literals into sample_users.dart (or reference existing constants).
  • Replace goldenTestdocsGoldenTest. Drop the alchemist import if unused.
  • Drop manual GoldenComponentFactory(child: …) wraps from each scaffold.
  • Rebuild any custom ThemeData() to compose from docsScreenshotsTheme() so the platform pin and SF setup carry through.
  • Add stream_chat_localizations under dev_dependencies.
  • Drop any custom emptyBuilder / hand-rolled "no items" widgets — let the SDK render its default.
  • Don't commit golden PNGs locally; dispatch update_goldens.yml with target=docs.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

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: b7e9dae3-5c2a-4800-ac3b-eb3563862db3

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 docs/snapshots-platform-pin

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 xsahil03x force-pushed the docs/snapshots-platform-pin branch from 3e10e67 to 498f38c Compare May 20, 2026 13:46
Doc snapshots previously rendered differently depending on the host running
the tests (macOS dev machines vs Linux CI) because three platform reads were
unpinned: `CurrentPlatform.type`, `defaultTargetPlatform`, and
`Theme.of(context).platform`. Code paths conditional on any of these would
flip — most visibly the mic button visibility in `StreamMessageComposer`.

Introduce `docsGoldenTest`, a wrapper around alchemist's `goldenTest` that
pins all three platform reads to iOS:

- `CurrentPlatform.debugCurrentPlatformOverride` and
  `debugDefaultTargetPlatformOverride` are set inside the alchemist
  `pumpWidget` hook (i.e. inside the test body) and reset via
  `whilePerforming`'s cleanup callback — the latest hook before
  `_verifyInvariants` enforces that foundation debug vars are null.
- `Theme.of(context).platform` is pinned via `ThemeData.platform` on
  `docsScreenshotsTheme()`.

Also adopt the `GoldenComponentFactory` from #2649, wired automatically
into every snapshot through the same `pumpWidget` hook so no per-test
plumbing is needed. It overrides the `emoji` component builder to add an
8 px top padding, nudging emoji glyphs to align with surrounding text.

Goldens regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@xsahil03x xsahil03x force-pushed the docs/snapshots-platform-pin branch from 498f38c to db011e0 Compare May 20, 2026 13:48
xsahil03x and others added 25 commits May 20, 2026 16:01
Roboto carries no emoji glyphs, so any inline emoji in plain Text widgets
(message bodies, etc.) would render as tofu. Add the emoji font families
loaded by `_loadEmojiFont` to the textTheme's fallback chain so Flutter
finds the glyph automatically.

Defensive change — no current snapshot has inline emoji in plain text;
goldens unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pinning the platform to iOS makes Material's iOS typography request the
`CupertinoSystemDisplay` and `CupertinoSystemText` family aliases. On a
real device the platform resolves those to SF Pro automatically; the
Flutter test renderer has no such mapping, so until now we worked around
it by overriding everything to Roboto.

Register `/System/Library/Fonts/SFNS.ttf` under both alias family names
via `FontLoader` in `_loadAppleSystemFont`. With that in place we can
drop the `fontFamily: 'Roboto'` override on `ThemeData` and switch Stream's
`StreamTextTheme` to `CupertinoSystemText` — Material widgets, message
text, and headings all now render in authentic SF on macOS.

The loader is a no-op outside macOS. Linux CI doesn't have SFNS.ttf, but
its goldens use `obscureText: true` so every glyph is replaced with an
Ahem block regardless — no behavioral change there.

Goldens regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`_loadEmojiFont` and `_loadAppleSystemFont` both register absolute-path
host system fonts that aren't in any asset manifest. Wrap them in a
single `_loadHostSystemFonts()` so `testExecutable` reads as two awaits
(asset-manifest fonts, then host fonts) instead of three.

Pure refactor — no behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-aware runners

The single Linux job previously regenerated `goldens/ci/*.png` for every
alchemist-dependent package — which is what the SDK packages commit, but
not what `docs_screenshots` commits. Docs snapshots commit
`goldens/macos/*.png` (real-text variant rendered with SF on macOS); the
Linux runner can never produce them.

Split the workflow by adding a `workflow_dispatch.inputs.target` choice
(`sdk` | `docs`) that routes to one of two jobs:

- `update_sdk_goldens` on ubuntu-latest — runs the new
  `update:goldens:sdk` melos script, which skips `docs_*` packages.
  Auto-commits `packages/**/goldens/**/*.png`.
- `update_docs_goldens` on macos-latest — runs `update:goldens:docs`,
  scoped to `docs_*` and exporting `UPDATE_PLATFORM_GOLDENS=1`.
  Auto-commits `docs/**/goldens/macos/*.png`.

`UPDATE_PLATFORM_GOLDENS` is a carve-out in
`docs/docs_screenshots/test/flutter_test_config.dart`: by default the CI
variant gate (`isRunningInCi → CiGoldens only`) skips the platform
snapshots, which is correct for any other runner; setting the env var
flips PlatformGoldens back on so the macOS job actually regenerates the
files it's there for.

The macOS job stays manual-dispatch only and is opt-in via the choice
input — macOS runner minutes cost ~10x Linux on GitHub Actions, so
defaulting to `sdk` keeps routine regenerations cheap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`.gitignore` for docs_screenshots commits only `goldens/macos/*.png` —
the CI/obscured variant exists transiently on disk but is never inspected
and never compared against anything in the repo. Generating it wastes
test time and obscures intent.

Simplify `docs/docs_screenshots/test/flutter_test_config.dart` to enable
only `PlatformGoldensConfig` regardless of the host. The
`UPDATE_PLATFORM_GOLDENS` env-var carve-out is no longer needed (the gate
it flipped is gone), so drop it from `update:goldens:docs` in melos.yaml.

Side effect: with PlatformGoldens always on, running docs_screenshots
tests on a Linux runner would compare against a nonexistent `goldens/linux/`
set and fail. Add `--ignore="docs_*"` to the `test:flutter` melos script
so the regular Linux CI pipeline skips them entirely — docs goldens are
regenerated and verified through the dedicated macOS `update_docs_goldens`
job instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`DefaultStreamEmoji` carries its own `fontFamilyFallback` chain pointing
at the platform emoji fonts, and that's the path every emoji in the
current snapshot set takes. No test renders an inline emoji glyph
through a plain Text widget, so the theme-level fallback was defending a
case that doesn't exist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the candidate-list pattern in `_loadEmojiFont`: walk a list of
absolute font paths and bail at the first one that exists. Catches
pre-Catalina macOS where the system font ships as `SFNSDisplay.ttf`
instead of the modern `SFNS.ttf`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stream's `StreamAvatar` / `StreamNetworkImage` / `CachedNetworkImage`
chain doesn't fetch real network bytes in tests — and even if it tried,
`flutter_test`'s HttpClient short-circuits every request. Up until now
every `User(image: 'https://…')` rendered as the gradient-letter
placeholder, which makes the docs look like nothing's loading.

Intercept the chain at the `StreamComponentFactory.networkImage` hook:
`GoldenComponentFactory` now installs a builder that returns an
`Image.memory` backed by one of 18 portrait fixtures sourced directly
from Stream's Chat SDK Design System Figma (node 2867:55922, "User
Photo"). Files live in `test/fixtures/avatars/` and are named to match
the Figma component names (e.g. `amelia-moore.png`); add or replace one
by dropping a PNG there and listing its slug in `_avatarSlugs`.

URLs hash deterministically into the list, so the same
`User(image: 'https://docs.fixture/avatar/bob.png')` always resolves to
the same person across snapshots.

To make this actually fire across the suite:

- `setupMockChannel` stubs `channel.image` and the default member User's
  image to synthetic `https://docs.fixture/avatar/{id}.png` URLs. Every
  channel previously had `channel.image: null`, so the channel-avatar
  branch never even reached `StreamNetworkImage`.
- Sweep across `User(id: '…')` constructions in test files — same
  `image:` URL pattern derived from the id — so user avatars in poll,
  thread, message, and search snapshots route through the same fixture.
- `docsGoldenTest` default `pumpBeforeTest` is now alchemist's
  `precacheImages` (was `onlyPumpAndSettle`). Without that pump,
  `MemoryImage` decode races the frame and the avatar circle shows the
  default background color instead of the actual bytes.

Goldens regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…vatars

Two related cleanups, both about making the snapshots reflect a real
chat in a real product.

**Sample users in one file.** Inline `User(id: 'user-2', name: 'Bob', …)`
constructions were scattered across nine test files with arbitrary IDs
that didn't match anything in the design system. Move all of them into
`test/src/sample_users.dart` as named constants (`ameliaMoore`,
`noahSmith`, …) corresponding to the 18 named users in Figma node
2867:55922. Tests now reference users by name; adding or replacing a
sample user is one constant + one fixture PNG.

**Channel avatars derive from members.** `setupMockChannel` previously
stubbed `channel.image` to a synthetic URL, so every channel snapshot
took the `channel.image` branch in `StreamChannelAvatar` and rendered a
single circle. Real chats almost never set `channel.image` on group
channels — the avatar is derived from the member set. Stub
`channel.image` back to `null`, default the member list to three sample
users so the channel-list snapshot shows the stacked group avatar from
`StreamUserAvatarGroup`. Distinct DM channels with two members render
the other member's avatar, matching production behavior.

Also along the way:

- `golden_network_image.dart` now resolves URLs by slug (extracted from
  the last path segment), with a hash-based fallback for dynamic URLs
  like `user-$messageId` that don't match a known slug. `User(image:
  'https://docs.fixture/avatar/amelia-moore.png')` now deterministically
  renders Amelia regardless of how the URL hashes.
- `asOwnUser(user)` helper on `sample_users.dart` for `currentUser`
  stubs.
- `user_list_view` snapshot iterates the full 18-user roster with a
  seeded `Random(42)` toggling online indicators — varied without being
  flaky.

Goldens regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Default channels all picked the same 3 members (Amelia, Charlotte,
Elena), so every channel-list row rendered the same stacked group
avatar. Switch the `setupMockChannel` fallback to a per-channel
deterministic pick: seed a `Random` off the channel id, choose between
2 and 6 sample users, shuffle them in. Each channel now gets a unique
group avatar (Engineering happens to land on 6 → triggers the "+4"
overflow chip from `StreamUserAvatarGroup`, exactly like a real busy
channel would).

Determinism comes from a stable djb2-ish content hash, not Dart's
process-randomized `String.hashCode` — so the same channel id always
picks the same member set across runs.

Goldens regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The full 18-user roster overflows naturally — keeping the snapshot at
the prior 500 px height keeps it consistent with the surrounding
fixtures (channel previews, message lists) and avoids a one-off tall
golden in the docs grid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`_makeSearchResult` previously built inline `User(id: 'user-$messageId',
…)` so the URL slug couldn't match any fixture and the avatar resolved
through the hash fallback — names ("Alice", "Bob", "Carol", "Dave")
ended up paired with arbitrary portraits. Pass a `User` directly so the
slug aligns with the photo, and use design-system constants (Amelia,
Noah, Charlotte, Liam) so the names match the rest of the docs.

Side benefit: the first row now reads "You in General" because
`ameliaMoore` is the current user — accurate to how
`StreamMessageSearchListView` renders the current user's own messages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A single [OwnUser] constant in `sample_users.dart` replaces the
per-file `final _currentUser = ameliaMoore;` / `final _user1 = …;`
aliases and the ad-hoc `asOwnUser(ameliaMoore)` calls scattered across
tests. Because `OwnUser` is-a `User`, the same constant drops into both
`currentUser` stubs and any place a `User` is expected (message authors,
member lists, etc.) — so "who is signed in" is decided in exactly one
place across every doc snapshot.

`asOwnUser` is kept for the rare case where a test needs to promote a
different sample user (e.g. variant tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match the original snapshot's row count so the diff against the prior
golden stays compact — the design-system roster is still demonstrated
elsewhere via avatars rendering with the new fixture set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mple app

The chat-list, thread-list, and swipe-action snapshots were dressed in
generic Material chrome — `AppBar('Stream Chat')`, a fictional
`Chats / Mentions / Threads` bottom nav, a red "Delete" swipe-action.
Real Stream apps look nothing like that, so the docs misrepresented the
product.

Rework the chrome to match `sample_app`:

- **Channel list & slidable channel list** (`channel_preview_test.dart`)
  swap the generic `AppBar` for `StreamChannelListHeader(title:
  Text('Chats'))`. The leading slot resolves to the signed-in user's
  avatar (Amelia) via the existing `currentUser` stub.
- **Swipe channel reveal** (same file) shows
  `streamIcons.more` (surface bg) + `streamIcons.mute` (accent bg) — the
  exact actions `sample_app/lib/widgets/channel_list.dart` ships.
- **Thread list** (`thread_list_view_test.dart`) uses
  `StreamChannelListHeader(title: Text('Threads'))` and a four-tab
  bottom nav: `Chats` (`streamIcons.messageBubble` / `messageBubbleFill`),
  `Threads` (`streamIcons.thread` / `threadFill`, active),
  `Drafts` (`Icons.drafts_outlined` / `drafts_rounded`), and
  `Reminders` (`Icons.bookmark_outline_rounded` / `bookmark_rounded`).
  The Material icons mirror the sample's actual choices — Stream
  doesn't ship a Drafts or Reminders icon.
- **Poll creator AppBar** (`poll_test.dart`) replaces `Icons.close` and
  `Icons.send` with `streamIcons.xmark` and `streamIcons.send` —
  thinner, design-system shapes.

Goldens regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ist headers

Two follow-ups now that we're using the SDK's header components:

- **Poll creator** swaps Material `AppBar` + `IconButton` for `StreamAppBar`
  + `StreamButton.icon`, the toolbar + button primitives the SDK ships
  for modal/dialog chrome. Circular icon buttons, hairline bottom
  divider, design-system padding — same shape any production app gets
  for free when using these widgets.
- **Channel list, slidable channel list, and thread list** add a trailing
  `StreamButton.icon(icon: Icon(icons.plus))` to `StreamChannelListHeader`.
  The docstring for `StreamChannelListHeader` calls this out as the
  canonical "new chat" affordance, and rendering it makes the header's
  three-slot shape (avatar leading / title / action trailing)
  immediately visible to readers.

Also revert `user_list_view_test.dart` to the explicit five-User array
style — keeps the original visual layout and drops the dart:math import.

Goldens regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r demos

Two follow-on tweaks to make the header demos cover the full SDK API:

- Poll creator now uses `StreamSheetHeader` (the SDK's modal/dialog
  toolbar) instead of `StreamAppBar`. Visually similar but semantically
  matches what a sheet-launched poll creator actually renders in
  production. Skipped `automaticallyImplyLeading: true` because our
  test scaffold has no popable route — kept the explicit leading
  button.
- `channel_list_header_test.dart` now wires `trailing: StreamButton.icon(
  icon: Icon(context.streamIcons.plus))` on both snapshots. Combined
  with the existing leading (avatar) and `subtitle: Text('12 channels')`,
  the "custom subtitle" snapshot now demos every slot of
  `StreamChannelListHeader` in a single golden.

Goldens regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t/threads

Two corrections that bring the docs closer to the production SDK:

- **Message widget actions** stopped reinventing the action list. The
  snapshot used to render Material `Icons.*` against hand-rolled
  placeholder action types — visually plausible but missing the SDK's
  canonical "Mark as Unread" and "Flag Message" entries, and unaware
  of channel capabilities. Switch to
  `StreamMessageActionsBuilder.buildActions(context: ..., message: ...,
  channel: ..., currentUser: ...)` so the modal renders exactly what a
  user sees on long-press: Stream icons, translated labels, capability-
  aware visibility. Channel mock gets the full capability set
  (`sendReply`, `pinMessage`, `quoteMessage`, `readEvents`,
  `update*Message`, `delete*Message`) so every default action surfaces.
- **Thread list bottom nav** trims `Chats / Threads / Drafts / Reminders`
  to just `Chats / Threads` (matches what `sample_app` actually shows
  by default; Drafts and Reminders are config-gated additions there).
  Theming wraps the `BottomNavigationBar` in a `DecoratedBox` with
  `backgroundElevation1` + `borderSubtle` top divider, sets `elevation:
  0`, `iconSize: 20`, `textPrimary`/`textTertiary` item colors, and
  `metadataEmphasis` label styles — copied verbatim from
  `sample_app/lib/pages/channel_list_page.dart`.

Goldens regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ry thread authorship

Four corrections to the SDK alignment:

- Message actions modal wraps the builder output in
  `StreamContextMenuAction.partitioned(...)` so the destructive **Delete
  Message** lands at the end with a separator above it — the canonical
  grouping the SDK exposes for that purpose.
- Poll creator's `StreamSheetHeader` buttons now use
  `StreamButton.icon(type: .outline, style: .secondary)` for the
  leading dismiss button (matching the SDK's auto-implied leading on
  sheet routes) and `type: .solid` for the trailing confirm — the
  pattern `sample_app/lib/widgets/add_members_sheet.dart` uses.
- `_makeThread` now takes a `parentAuthor` so the thread list renders
  mixed authorship — one thread started by the current user (rendered as
  "You: …"), the others by Charlotte and Noah. Previously every thread
  was authored by `ownUser`, which made the list look like a single-user
  digest.
- Thread list empty state drops the hand-rolled `Center(Column(...))`
  and lets `StreamThreadListView` render its default
  `StreamScrollViewEmptyWidget` (messageBubblesLarge icon + translated
  "Reply to a message to start a thread") — same widget production
  users see.

Goldens regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…on list

`partitioned` adds Flag and Mute on top of Reply/Thread/Mark/Copy/Edit/
Pin/Delete, so the previous 500 px constraint clipped the modal. 700 px
fits the full list with breathing room.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…odal height

Two cleanups:

- `slidable_channel_list` was stubbing `currentUser` *before* the
  `fakeChannel` calls, so each `setupMockChannel` invocation
  overwrote `client.state` with its own `MockClientState()` (whose
  default `currentUser` is `OwnUser(id: 'user-id')`, the "U"
  placeholder). Move the stub after the channels are created so it
  wins, matching the order `channel_list_view` already uses. The
  header now renders Amelia's avatar properly.
- `message_widget_actions` was at 700 px after adding the full action
  list — visibly too tall. Trim to 620 px so the modal fits with
  comfortable margin and no dead space below.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous `.gitignore` pattern `!**/goldens/macos/*` only un-ignored
macos files relative to a (missing) preceding ignore rule, so the `ci/`
subdirectory was being tracked alongside `macos/`. With
`flutter_test_config` already disabling `CiGoldensConfig.enabled`, the
`ci/` files were stale leftovers from before that change — committing
them was pure noise.

Switch the ignore to `**/test/**/goldens/**` (recursively ignore
everything under any goldens dir) with `!**/test/**/goldens/macos/**`
re-allowing the macos subtree. Delete the orphan `ci/` PNGs so the
docs only ship the platform variant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`StreamChannelInfo` (the default subtitle for `StreamChannelHeader`)
branches on `channel.memberCount`: > 2 → "N members, M online", else
the DM-style "Last online …". Our mock left `memberCount` un-stubbed,
so it resolved to null and every snapshot fell into the DM branch —
yielding a misleading "Last online a few seconds ago" for channels
that visibly have 3+ avatars stacked.

Stub `memberCount` (and `memberCountStream`) from the configured member
list in `setupMockChannel`. Group-channel snapshots now read "3 Members"
(or whatever the per-channel deterministic count is), matching what
users actually see.

`channel_list_header` snapshots were already correct — they use the
connection-state default title with no subtitle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gesture

The slidable snapshots were hand-rolled — Row+Stack+Transform.translate
faking a swiped state with hardcoded action icons. Replace with the
actual `flutter_slidable` setup the sample app uses
(`sample_app/lib/widgets/channel_list.dart`):
`Slidable` with `endActionPane: ActionPane(extentRatio: 0.4, motion:
BehindMotion(), children: [more, mute])`. For the multi-channel test
the wrap goes through `StreamChannelListView.itemBuilder` so every row
gets the same slidable.

`whilePerforming: (tester) async { await tester.drag(...); await
tester.pumpAndSettle(); }` actually performs the gesture before the
snapshot. The result is a real animated reveal instead of a fake
translation, which catches any drift in Slidable's API or our usage.

Adds `flutter_slidable: ^4.0.0` to docs_screenshots dev_dependencies.
Drops the now-unused mocktail import in this file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@xsahil03x xsahil03x changed the title docs(ui): pin platform and align emoji baseline in docs snapshots docs(ui): make docs snapshots authentic to the production SDK May 21, 2026
xsahil03x and others added 3 commits May 21, 2026 00:34
`StreamPollCreatorSheet` (the production widget) wires its trailing
`StreamButton.icon` to `context.streamIcons.checkmark`, not `send`. Our
snapshot was showing a paper-plane icon which doesn't appear anywhere
in the real poll creation flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now from Noah

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we now use users from the list we created

@xsahil03x xsahil03x merged commit ea29e6d into v10.0.0 May 21, 2026
1 check passed
@xsahil03x xsahil03x deleted the docs/snapshots-platform-pin branch May 21, 2026 10:14
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