Skip to content

fix(links): say why a message preview is unavailable, and preview unjoined public channels - #2136

Merged
matt2e merged 10 commits into
mainfrom
unavailable-link-preview
Nov 4, 2026
Merged

matt2e merged 10 commits into
mainfrom
unavailable-link-preview

Conversation

@matt2e

@matt2e matt2e commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Hovering a buzz://message link showed "Message preview unavailable." for five different failures. One of them was simply wrong: a link into a public channel the viewer hasn't joined, which opens fine when clicked. The preview read the message before looking up its channel, so discovery refused the read. Now the preview looks up the channel first, as clicking does, and each failure says why it failed.

Link target Card shows
Public channel the viewer hasn't joined The message (author, #channel, text), after a brief "Loading message…"
Private or missing channel, or a link from another community "Private channel. You aren't a member, it no longer exists, or the link is from another community."
Deleted or missing message in a readable channel Channel byline, then "This message isn't available. It may have been deleted."
Read error or bad timestamp "Couldn't load this preview." Reopening the card retries

Every failure card ends with a muted "Open link" line. The card is still the same link, so click, Enter and Tab work as before. ThreadPanel and the preview now share one threadFailure classifier in threads.ts. MessageLink's tooltip uses the same withheld wording, and docs/buzz-help.md describes the new behavior.

Channel lookup state: looking

ChannelReference gains a looking answer, read from the store's existing queue and pending sets. The card waits only while a lookup is queued or in flight, or while discovery is still loading the channel list. A failed lookup that is backing off, a list that errored, or a cached-only store (which no longer offers refer) falls back to the read's own answer, so the card can't load forever. A queued batch tells list subscribers once, from its microtask. A failed lookup arms its retry before telling them. useChannelReference asks for the channel again only when whether it was found changes.

Row toolbar stays under the card

The card opens as "Loading message…" and grows to its final size, covering its link for one frame. During that frame the row's toolbar hid, then showed again above the card, where a click meant for the card could land on a reaction button. main has the same problem when the pointer flicks off the bubble and back within the card's close delay.

  • 9d95f5564: useFloatingActionBar keeps a toolbar that is already showing while the row has an open preview card (PreviewCard marks its link with data-preview-card-trigger). An open card never brings back a hidden toolbar. This replaces the pointerout exemption from ef8bedfae; that commit's cleanup of data-actions-revealed stays.
  • f7db65b26: a card that reopens during its 150 ms exit animation re-enters the top layer after the reopen renders, so it ends up above any toolbar that render showed.

Video

Before (merge base ac8495a8b) on the left, after (9d95f5564) on the right, in headless Chromium. Watch the card in each step:

  • Step 1: hover the link into #open, which the viewer hasn't joined. Only the right side previews the message.
  • Step 2: at about 12.5 s the pointer flicks off the bubble's edge and back. On the left, the toolbar's reactions then cover the card. On the right, the card stays on top.
  • Steps 3–4: only the right side shows the deleted-message and private-channel reasons.
  • Step 5: clicking opens #open with a Join bar on both sides.
unavailable-link-preview-before-after.mp4

The video was recorded at 9d95f5564, so it doesn't include f7db65b26. That commit only changes what happens when the card reopens during its exit animation (leave the link for 75–225 ms, then return). The video never takes that path; it was checked with a probe in Chromium and WebKit.

Tradeoffs / known residuals

  • Leaving the link for the row's empty space keeps the toolbar for the card's 75 ms close delay instead of hiding it at once, so two rows can briefly both show a toolbar. Tabbing into the card keeps the toolbar beneath it until the card closes.
  • When the card opens it still grows over its link for one frame. The toolbar now ignores this, but the card's positioning is unchanged.
  • Out of scope: App.tsx's failure page and the "Message" link label.

Testing

At f7db65b26, on local macOS:

  • Vitest, 14 files covering the changed code (BuzzLinkPreview, MessageLink ×2, channel-reference, MessageActionBar, ThreadPanel ×4, and relay channel-references, threads, store, store-discovery and store-system-discovery): 314 of 315 passed. store-system-discovery.test.ts timed out once while the machine's load average was about 50, then passed 5/5 when rerun on its own.
  • playwright test --project=chromium --project=webkit --no-deps tests/browser/buzz-links.spec.mjs: 18 passed.
  • tsc --noEmit, biome check --error-on-warnings on the changed files, and pnpm design:check: clean.

Earlier on the branch:

  • The regression tests added in 218098689, ef8bedfae, 9d95f5564 and f7db65b26 each failed on the previous commit.
  • Headless-Chromium probes (1024×720, light theme) of the flick-off-and-back routes at 9d95f5564, and Chromium and WebKit probes of the reopen-during-exit route at f7db65b26.

Not yet checked: a human trying it in the app, dark theme, narrow widths, enlarged text, keyboard-only use, and the native desktop app. Push hooks were skipped (--no-verify), so hosted CI is the first full run.

🤖 Generated with Claude Code

@matt2e
matt2e requested a review from a team as a code owner November 2, 2026 08:37

@kalvinnchau kalvinnchau left a comment

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.

🤖 One remaining public-channel preview case is not covered by the fix.

@kalvinnchau kalvinnchau left a comment

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.

🤖 The prior leave-to-preview failure is addressed. One non-blocking regression-test gap remains.

Comment thread src/features/relay/store.ts Resolved
@matt2e
matt2e force-pushed the unavailable-link-preview branch from beba65e to f3a29f3 Compare November 2, 2026 11:41

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, with independent Rocket, Mantis and Groot coverage. One minor P3: a missing exact-message response is presented as confirmed deletion; details inline. The earlier left-channel re-admission and five-minute recheck findings are addressed in source.

Reviewed head f3a29f3 against base/merge-base 3d8699e. Source-only: no tests or app execution. Hosted checks were successful except skipped Windows validation at the snapshot. Keyboard/screen-reader behavior, native acceptance and performance remain unverified. The public description was inspected; its attached video was not viewed.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source follow-up via Wes’s account, with independent Rocket, Mantis and Groot coverage. No new actionable findings. The previous P3 is addressed: an empty exact-message read now says the message is unavailable and may have been deleted; the regression assertion, help text and description agree.

Reviewed head f3906b9 against base 3d8699e, focusing on the three-file change since f3a29f3. Source-only: no tests or app execution. The hosted snapshot showed successful DCO, Semgrep and zizmor checks; this does not establish full-suite or runtime validation. Keyboard/screen-reader behavior, native acceptance and performance remain unverified. The public description was inspected; its historical video attachment was not viewed.

@matt2e
matt2e force-pushed the unavailable-link-preview branch from f3906b9 to 9f80fcf Compare November 3, 2026 12:33

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source follow-up via Wes’s account, with independent Rocket, Mantis and Groot coverage. No new actionable findings. The rebase preserves the prior deletion-copy repair and lookup/toolbar behavior; the integration with linkedMessage and ThreadPanel retains the reviewed terminal-state handling.

Reviewed head 9f80fcf against base 90ce5e3, comparing the feature patches with previously reviewed f3906b9. Source-only: no tests or app execution. Hosted checks were successful except skipped Windows validation at the snapshot. Live keyboard/screen-reader behavior, native acceptance and performance remain unverified. The public description was inspected; its historical video attachment was not viewed.

matt2e and others added 10 commits November 4, 2026 14:44
…rd opens

Hovering a buzz://message link into a public channel the viewer hadn't
joined said "Message preview unavailable." even though clicking opened the
message. The preview read the message before looking the channel up, so
discovery refused the read. The same text also covered private channels,
deleted messages, read errors and links from another community.

The preview now looks the channel up first, as the click path does. While
the lookup is pending it shows "Loading message…". Once the channel is
found, a fresh read runs, and a read from before the lookup is never
shown. A withheld channel is never read. A cached-only store can't look
anything up, so it no longer offers `refer`, and its preview keeps the
read's own answer.

Failures come from one table: withheld ("Private channel. You aren't a
member, or it no longer exists."), not-found ("This message was deleted,
or the link is from another community.") and unavailable ("Couldn't load
this preview."). When the channel is found, the card starts with the
channel byline. It ends with a muted "Open link" line; PreviewCard is
unchanged, so the card is still the link. The target-to-reason mapping
moves from ThreadPanel into `threadFailure` in threads.ts, so ThreadPanel
completes navigation with the same reasons as before. The success card
now names an unjoined channel instead of "Channel unavailable".

App.tsx's failure page and the "Message" link label are out of scope.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
… on the way

The preview showed "Loading message…" whenever the channel reference was
`unknown` and the store offered `refer`. Unknown also covers a lookup that
failed and is backing off, and a list that isn't ready or has errored, in
which case the hook never asks for one. With a refused read, the card then
loaded until a later lookup succeeded; offline, or with a list that keeps
erroring, that never came, and a read error never showed "Couldn't load
this preview."

The store now answers `looking` only while a lookup for that channel is
queued or in flight, from the queue and pending sets it already keeps, and
notifies list subscribers when a lookup starts and when it fails, as it
already did on success. The preview waits only on `looking`; a backoff, an
unready or errored list, and a cached-only store all fall back to the
read's own answer. A still-demanded reference is looked up again after a
cache reset, so it reads `looking` there too.

A link from another community almost always reaches `withheld`, since its
channel ID resolves to nothing here, so that clause moves from the
not-found text to the withheld one, which MessageLink's tooltip shares:
"Private channel. You aren't a member, it no longer exists, or the link is
from another community." A channel that resolves here is in this
community, so not-found is "This message was deleted." buzz-help follows.

Tests cover a lookup that fails while the read is refused and a list that
isn't ready, the store's `looking` answers and their notifications, and the
new withheld wording in the browser journey.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
…t while the list loads

A queued channel lookup notified every list subscriber synchronously,
once per id. A cache reset or the wake timer queues every demanded
channel in a loop, so D mounted links cost 1 + D rounds of session-wide
list work (interest updates, live snapshot, activity sort and the rest),
and rechecking an expired withheld answer notified although nothing on
screen changed. A listener that threw there also left the id queued with
no microtask, so `describe` answered `looking` indefinitely.

The batch's microtask now notifies once, after its reads start, and
skips it when every id in the batch is already withheld. A component
that renders in between reads `looking` from the queue anyway. A cache
reset with three demanded links now notifies twice: once for the reset
and once as their lookup starts.

useChannelReference re-ran its demand effect on every answer change, so
each lookup released and re-demanded the reference and walked every
demanded id in wakeReferences. The effect now depends on whether the
answer is `found`, which is the only thing that changes the demand.

While discovery is still loading the channel list, the preview treated a
refused read as final and showed "Couldn't load this preview.", then
flipped back to "Loading message..." once the list was ready and the
lookup started. Both are status text, so screen readers announced each
switch. The preview now waits while the list is `loading`; discovery
either finishes, and the channel is looked up, or errors. An errored
list, a lookup backing off and a cached-only store, which never loads a
list, still fall back to the read's own answer.

Tests cover the per-batch notification, a withheld recheck that notifies
no one, the bounded count on a cache reset, a listener that throws while
a lookup is queued, and a preview that waits on a loading list and stops
once it errors. Each failed on the previous commit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
…er a preview waiting through a ready list

When a channel lookup failed, the store told list subscribers before
arming the backoff retry. A listener that threw there, inside the read's
rejection handler, skipped wakeReferences, so no retry timer was set and
a mounted link stayed `unknown` until something else woke it, such as a
fresh `refer` or a cache reset. The retry is now armed first.
wakeReferences only sets a timer and doesn't change what `describe`
answers, so links see the same thing either way.

BuzzLinkPreview had no test for a preview whose channel list goes from
loading to ready. The harness's `refer` never moved the answer to
`looking`, so it couldn't express that path. Now it does, and it tells
list subscribers from a microtask, as the store's batch does. The new
test checks the settled states: once the list is ready the card is still
"Loading message…" and the lookup was demanded once, and when the
channel is found it reads again and shows the message. It doesn't check
that the refusal never renders. That refusal is in the DOM for the one
render before the demand effect runs, by design.

The test fails if the preview stops waiting on `looking`, and it is the
only test that fails if useChannelReference re-demands on every answer
change. No store test covers the new ordering, because the throwing
listener surfaces as an unhandled rejection, which fails a Vitest run.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
On this branch the hovered row's floating toolbar drew over a message
link's preview card, which PreviewCard means to stay on top. The card
opens as "Loading message…", then grows to its final content. Base UI
places it from its top edge, so for one frame it grows down over its
trigger before moving back up. The pointer, still on the link, is on the
card for that frame. The row's pointerout read that as leaving, so
useFloatingActionBar hid the toolbar, and the pointerover back on the
link re-showed it at the top of the top layer, above the card. Moving the
pointer from the link into the card and back did the same.

PreviewCard now records which trigger owns each card it mounts, and
previewCardTrigger looks it up. A pointerout into the row's own open card
no longer counts as leaving the row. The row never hears the pointer
leave a portaled card, so the retained hover ends when the card closes
and its trigger loses data-popup-open.

The layout effect's cleanup now also removes data-actions-revealed. A
hover update removes it before React commits the hidden bar, and an
overlay or positioning update in between could restore it, leaving the
bubble in its hover colour after the toolbar hid. A card closing while
it holds the hover always hit that.

A MessageActionBar test covers a pointerout into the row's own open card,
the return to the row's empty space, the card closing, and a closed card
no longer holding the hover. It fails on the previous commit.

The card still grows over its trigger for one frame. This change keeps
the toolbar from reacting to it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
…is open

ef8bedf stopped a pointerout into the row's own open card from hiding
the toolbar. Other routes off the content still hid it: a jiggle off the
bubble's edge into row space and back inside the card's 75 ms close
grace, or a diagonal through row space into a card wider than a short
bubble. The next pointerover on content then showed the toolbar again,
above a card that never closed, where a click meant for the card could
land on a reaction button.

useFloatingActionBar now keeps a toolbar that is already shown while the
row holds an open preview trigger. PreviewCard marks its trigger with
data-preview-card-trigger, so it can be told apart from other popups'
triggers, which Base UI also marks data-popup-open. data-popup-open stays
in the observer's filter, so the card's close releases the hold. An open
card never reveals a hidden toolbar. This replaces the pointerout early
return, its observer clause and PreviewCard's previewCardTrigger
registry. The cleanup that removes data-actions-revealed stays.

Tradeoff: leaving the link for the row's empty space, or for its
reactions, keeps the toolbar for the card's close grace instead of
hiding it at once. Tabbing into the card keeps the toolbar beneath it
until the card closes.

The MessageActionBar test now expects the toolbar to stay through a
return to the row's empty space while the card is open, to hide there
once the card has closed, and to stay hidden when the card reopens. The
return step fails on ef8bedf.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
…bove the row toolbar

9d95f55's hold ends when the card's trigger loses data-popup-open,
which Base UI drops as soon as the card starts closing. The card stays
mounted through its 150 ms exit. If the pointer comes back to the same
link during that exit, Base UI 1.9.0 reopens the card in place, so
`promote` never runs again. Leave the link for row space, wait 75–225
ms and come back: the toolbar hid at about 75 ms, showed again on
return, and stayed above the reopened card. A click meant for the card
could then land on a reaction button. The reviewer's probe reproduced
this at 120 and 160 ms on the own and peer message, in Chromium and
WebKit.

The review suggested hidePopover() then showPopover() in onOpenChange.
That runs too early. Base UI flushes a hover change with flushSync, and
React commits the row's pending pointerover reveal in the same flush,
after onOpenChange returns. The toolbar's showPopover came after the
card's, and the probe still had the toolbar on top in every check.

onOpenChange now marks a card that reopens while its popup is still
data-closed. The overlay observer then runs after the reopening has
committed, which flips data-open, and joins the top layer again, after
any toolbar that commit showed. A card that is hidden behind an overlay
waits for it to close, as before. Hiding and showing a manual popover in
one task keeps focus inside it in Chromium and WebKit, with no focusout,
so the dialog (actionRef) variant keeps its focus.

The hold's comment now says that its selector matches a preview trigger
with any popup open. MessageLink's context menu shares the link, so
that menu also holds the toolbar, hidden behind it.

A MessageActionBar test models the top layer with popover stubs, holds
the card's exit at Base UI's getAnimations boundary, and sends the
return's pointerover and mouseenter before React commits either. It
fails on 9d95f55 and with the hide-and-show in onOpenChange.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
…g its message

After the viewer joined and then left a public channel, hovering a link
to one of its messages failed. Discovery denies the old admission until
a fresh lookup confirms the channel is still open, but describeReference
answered `found` from its cached open metadata. lookUpReference skipped
found answers, and useChannelReference only demanded a lookup while an
answer wasn't found, so no lookup or read ever ran. The preview showed
"Couldn't load this preview." on every hover, while a click, which
resolves the channel, opened the message.

Admission now decides. A channel named only by its open metadata answers
`found` with `readable: false`, and `looking: true` while its lookup is
queued or in flight, so a link keeps the name. lookUpReference skips a
channel only when discovery admits it. useChannelReference demands a
lookup until the answer is readable. A lookup that still admits nothing
is kept in `withheld` for the usual five minutes, as a withheld answer
is.

BuzzLinkPreview keys its read on readability instead of `found`, so it
reads again when the lookup admits the channel, even though the metadata
is unchanged. It waits while that lookup is on the way.

A channel-only link to a left public channel now runs one background
lookup too. Once admitted, its hover card can show the member count, as
it already does for an unjoined channel that has been resolved.

The store test that expected no lookup for a left channel now covers
leave, refer, the found-and-looking answer, and readable recovery. A
preview test covers a left channel that the lookup admits again. Both
fail on the previous commit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
…hat stays unreadable

e607882 keeps a lookup that still admits a left public channel in
`withheld`, so a mounted link rechecks it after five minutes. No test
covered that branch. Putting back the old predicate, which keeps the
channel out of `withheld` because its open metadata still answers
`found`, passed the relay and conversation suites. With that predicate no
recheck is ever scheduled, and the link stays unreadable until something
else asks for a lookup.

A fake-timer store test now leaves a public channel, mounts a link to it,
and answers the lookup with no admission. It checks that the link still
names the channel as `found` with `readable: false` and no `looking`, that
nothing is looked up before five minutes, and that the store's own
wake-up looks it up again at five minutes. When that lookup admits the
channel, the link is readable again. The test fails with the old
predicate and passes on the current code. No product change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
A well-formed message link whose ID wasn't in a readable channel said
"This message was deleted." readTarget in threads.ts returns
`unavailable` as soon as the exact read has no matching event, before it
reads any tombstone, and threadFailure maps that to `not-found`. A
nonexistent ID, or a message ID paired with the wrong channel, got the
same definite deletion claim as a deleted message.

The not-found text is now "This message isn't available. It may have
been deleted." No reader path tells an observed deletion apart from an
empty read: a tombstone that hides the target also ends as
`targetStatus: unavailable`. So no case keeps the definite wording, and
the reader is unchanged. MessageLink's tooltip uses only the withheld
text, and ThreadPanel passes the reason code to navigation, whose page
already says "This message isn't available.", so neither changes.
buzz-help follows.

The preview test for a readable channel without the message now checks
the wording itself. It fails with the old text.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
@matt2e
matt2e force-pushed the unavailable-link-preview branch from 9f80fcf to fde2585 Compare November 4, 2026 03:45

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source follow-up via Wes’s account, with independent Rocket, Mantis and Groot coverage. No new actionable findings. The feature patches are unchanged from the previously reviewed revision; the current toolbar geometry, preview placement and ThreadPanel integration preserve the reviewed behavior. The earlier deletion-copy fix remains intact.

Reviewed head fde2585 against base 8668dbd, comparing with prior head 9f80fcf. Source-only: no tests, builds or app execution; CI was not assessed. Live keyboard/screen-reader behavior, native acceptance and performance remain unverified. The public description and six sampled frames of its 37.64-second historical fixture video were inspected; remaining footage was not reviewed.

@matt2e
matt2e merged commit e5d7183 into main Nov 4, 2026
42 checks passed
@matt2e
matt2e deleted the unavailable-link-preview branch November 4, 2026 07:09
gilgtm added a commit that referenced this pull request Nov 4, 2026
* origin/main: (52 commits)
  feat(agents): give plugin agent types the Buzz side of a turn (#2364)
  feat(plugins): pin buzz://plugin-install links to a path and commit (#2315)
  feat(plugins): tint currentColor SVG page icons like host icons (#2314)
  feat(notifications): add a soft notification sound (#1536)
  feat(agents): let agents propose a profile picture (#2267)
  fix(plugins): default reminders and workflows off for fresh installs (#2291)
  Show all reactors in compact, separate emoji capsules (#2179)
  fix(panels): remember page split widths per workspace (#2264)
  feat(search): browse open channels from a Channels tab in search (#2164)
  perf(relay): page combined replay filters after a reconnect (#2308)
  fix(composer): make native Mac text shortcuts work past link chips (#2390)
  perf(relay): read creator proofs for many channels per filter (#2312)
  fix(channels): let published leave and delete confirm through access loss (#2357)
  fix(media): save native context menu media downloads and hide loopback links on macOS (#2131)
  fix(links): say why a message preview is unavailable, and preview unjoined public channels (#2136)
  test(browser): serve emoji artwork in the reaction hit-area journey (#2138)
  test(browser): click a reply link only after its row settles (#2358)
  fix(agents): restore delegated workflow mentions (#2399)
  feat(identity): always back up keys in managed builds (#2398)
  test(threads): render before checking the reader across activity resizes (#2389)
  ...

Signed-off-by: Gil Birman <[email protected]>
Co-authored-by: Claude <[email protected]>

# Conflicts:
#	src/plugins/author.test.mjs
wpfleger96 added a commit that referenced this pull request Nov 4, 2026
…w-tab

* origin/main: (41 commits)
  [3/5] Add protection to the Claude harness plugin (#1878)
  fix(settings): keep the bottom padding on long sections (#2361)
  fix(mermaid): cap diagram output size (#2231)
  feat(agents): give plugin agent types the Buzz side of a turn (#2364)
  feat(plugins): pin buzz://plugin-install links to a path and commit (#2315)
  feat(plugins): tint currentColor SVG page icons like host icons (#2314)
  feat(notifications): add a soft notification sound (#1536)
  feat(agents): let agents propose a profile picture (#2267)
  fix(plugins): default reminders and workflows off for fresh installs (#2291)
  Show all reactors in compact, separate emoji capsules (#2179)
  fix(panels): remember page split widths per workspace (#2264)
  feat(search): browse open channels from a Channels tab in search (#2164)
  perf(relay): page combined replay filters after a reconnect (#2308)
  fix(composer): make native Mac text shortcuts work past link chips (#2390)
  perf(relay): read creator proofs for many channels per filter (#2312)
  fix(channels): let published leave and delete confirm through access loss (#2357)
  fix(media): save native context menu media downloads and hide loopback links on macOS (#2131)
  fix(links): say why a message preview is unavailable, and preview unjoined public channels (#2136)
  test(browser): serve emoji artwork in the reaction hit-area journey (#2138)
  test(browser): click a reply link only after its row settles (#2358)
  ...

# Conflicts:
#	src/buzz-mcp/tools.ts
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.

3 participants