mirror of
https://github.com/block/buzz.git
synced 2026-11-04 08:21:48 +00:00
Follows [#8860](https://github.com/block/buzz/pull/8860) (merged). On mobile, any `#token` rendered as a channel chip, so GitHub-style references like `#8700` turned into inert chips for channels that don't exist. Desktop already avoids this by only linking names in the known channel list. This makes mobile's message body and one-line preview do the same. With no known names (channels still loading, or none visible), nothing becomes a chip. An open channel the reader hasn't joined stays plain text until the directory has loaded; before this change it was an inert, untappable chip. messages rebuild with chips once channel names arrive. Unlike desktop, there's no "any `#token` while empty" fallback, because an empty map can't distinguish loading from an account with no visible channels. The known names come from the existing `channelIdsByNameProvider` (the reader's channels, plus open directory channels once Browse channels or invite recovery has loaded the directory), so there is no new fetch, subscription or sync change. When the list is non-empty, the regex has one alternative fewer than before. ### Validation Added tests: - Preview: `#8700`, `#8774's`, a known-name prefix (`#gen` vs `general`) and `#eng-ios` with only `eng` known stay text, while `#general` and a longer known `#eng-ios` are chips. With no channel names, nothing chips. - Message body: `Took over #8700 from #general` renders one chip (`general`) and keeps `#8700` as text. - Message body: with no channel names, `#unknown` stays text. The new assertions fail with the previous always-generic pattern. The full mobile test suite passed in pre-push. Simulator render on an iPhone 18 Pro (iOS 27): real message-body and preview widgets with channels `general` and `eng` loaded, rendered with both stacked PRs applied. | Before (`main`) | After ([#8860](https://github.com/block/buzz/pull/8860) + [#8862](https://github.com/block/buzz/pull/8862)) | | --- | --- | | <img src="https://raw.githubusercontent.com/block/buzz/5afead6a162d5c8554ad17175fc2dccff93bf2af/pr-8862--01-before-main.png" width="300"> | <img src="https://raw.githubusercontent.com/block/buzz/5afead6a162d5c8554ad17175fc2dccff93bf2af/pr-8862--02-after-both-prs.png" width="300"> | Human app test: not applicable under the mobile-client testing exception; agent review and simulator validation above. buzz-review-completed 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com>
35 lines
1.3 KiB
Dart
35 lines
1.3 KiB
Dart
/// Matches inline `#channel` tokens. The message body and its one-line
|
|
/// preview share it so they always agree on what is a channel token.
|
|
library;
|
|
|
|
/// Only [knownNames] match, longest first so `#eng-ios` wins over `#eng`.
|
|
/// That keeps `#8700` or `#123` in prose as text instead of an inert chip.
|
|
/// With no known names (channels still loading, or none visible) nothing
|
|
/// matches; the message rebuilds with chips once channel names arrive.
|
|
///
|
|
/// A token can't start inside a word, path or URL (`a#b`, `x.y/#z`), and it
|
|
/// ends at whitespace, the end of a line, or punctuation that commonly follows
|
|
/// a word in prose: `#general's`, `"#general"`, `#general…`, `#general—`.
|
|
RegExp channelTokenPattern(Iterable<String> knownNames) {
|
|
final names =
|
|
knownNames
|
|
.map((name) => name.trim())
|
|
.where((name) => name.isNotEmpty)
|
|
.toSet()
|
|
.toList()
|
|
..sort((a, b) => b.length.compareTo(a.length));
|
|
if (names.isEmpty) return _noMatch;
|
|
return RegExp(
|
|
'$_leading#(?:${names.map(RegExp.escape).join('|')})$_trailing',
|
|
caseSensitive: false,
|
|
multiLine: true,
|
|
);
|
|
}
|
|
|
|
final _noMatch = RegExp(r'(?!)');
|
|
const _leading = r'(?<![\w./:-])';
|
|
const _trailing =
|
|
r'(?=$|[\s,;.!?:)\]}'
|
|
"'\"\u2018\u2019\u201C\u201D\u00BB\u2026\u2013\u2014"
|
|
r'])';
|