Repository navigation
fix(mobile): open unjoined channels from links and search results - #8923
Conversation
A channel link or search hit only opened a channel already in the local list, so a link to an open channel you hadn't joined showed "Channel could not be opened" and a search hit outside the list did nothing. Both taps now share one resolver that falls back to the channel's own metadata, as buzz:// deep links do. A private or withheld channel stays closed with a toast, a repeated tap during a slow lookup opens once, and a lookup that finishes after a community switch is dropped. Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Signed-off-by: Tom Brow <[email protected]>
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
🔐 Codex Security Review
Review SummaryOverall Risk: NONE
FindingsNo concrete security, correctness, or reliability findings were identified. Notes
Generated by Codex Security Review | |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c7e3dcc3f1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A lookup pending in one community blocked a tap on the same channel id after a community switch, and the old lookup was then dropped, so neither tap opened anything. Signed-off-by: Tom Brow <[email protected]>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eb43229041
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A failed list load rethrew before the metadata lookup, so every tap on a search hit showed an error instead of retrying. Signed-off-by: Tom Brow <[email protected]>
|
@codex review |
💡 Codex ReviewWhen a user switches A → B → A before A's original lookup settles, this equality passes again because the scope contains only the relay URL and pubkey, so the retired operation can show its cancellation error or navigate during the new visit, while AGENTS.md reference: AGENTS.md:L228-L238 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
A switch away and back to the same community kept the same scope string, so a lookup from before the switch still blocked a new tap and could land in the later visit. The relay config is replaced on every switch, so its identity is the generation. Signed-off-by: Tom Brow <[email protected]>
|
🤖 Agreed. Fixed in 37d61ab. The relay config is replaced on every community or identity switch, including a switch back to the same community, so both the result fence and the tap lock now key on that object's identity. The switch test now goes away and back, and it fails on the previous commit. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 37d61ab80f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A lookup from a page that closed still blocked the same channel's tap on another page until it settled, though its result was dropped. Signed-off-by: Tom Brow <[email protected]>
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.
The resolver design holds up: lock cleanup in finally, the connection fence, and private/DM rejection all checked out, and the copy matches buzz:// links. One recommended fix before merge: a slow lookup can push its channel over a page the user opened while it was waiting (inline on :84). The other two inline comments don't block merge.
Validation gaps (optional, extend existing journeys rather than adding tests):
- The success rows stop at
page.channel.isMember == false. Asserting the visible preview banner and absent composer would prove the user-facing outcome. - No row retries after a failure toast, so nothing proves the lock is released.
- The pending initial-list path is untested, as the PR says.
Reviewed at 83090be1 with source review across product, tests and concurrency. CI is green. We didn't re-run the app.
| failure = error; | ||
| } | ||
| // The tap belongs to the connection it was made on. | ||
| if (!context.mounted || |
There was a problem hiding this comment.
🤖 Recommended fix: a late lookup pushes over whatever the user opened next.
This fence (and the context.mounted check at :19, and message_tile.dart:202) only checks that the tapping page still exists. A page with another route on top of it is still mounted, so a slow lookup passes the check and pushes anyway. The lookup can take up to 8s in fetchHistory, after any rate-limit wait. With no cached list it can wait even longer on the initial load at :75.
Repro: tap a link or search hit for unlisted channel A. Nothing visible happens. Tap a listed channel B, or open a thread, and it opens at once. Then A resolves and is pushed on top of B. A repeat tap on A is swallowed by _resolving, which makes "try something else" the likely next move.
Fix: also require ModalRoute.of(context)?.isCurrent ?? true here, so both callers drop a tap once the user has moved on. There's precedent in home_page.dart:54. To cover it, add a step to the gated table: tap A, push another route, release the gate, and assert A wasn't pushed.
There was a problem hiding this comment.
🤖 Fixed in 27bf22a. The resolver now opens nothing unless the tapping page is still the current route (ModalRoute.isCurrent), which covers chat bubbles, thread messages and search. New test: "a channel link lookup that settles after the user moved on opens nothing".
| content: Text( | ||
| failure != null | ||
| ? 'Could not open the channel' | ||
| : 'Channel not found here', |
There was a problem hiding this comment.
🤖 Non-blocking: a failed list load turns a temporary failure into "Channel not found here".
When the list load fails, :75 swallows the error and falls back to fetchOpenChannel. That function returns null for every private channel and DM by design (channels_provider.dart:980). So a private channel the user is a member of gets the final "not found" answer, even though membership just couldn't be checked.
Suggested: when the list has no value because it errored and the fallback returns null, show "Could not open the channel". Leaking whether a channel exists isn't a concern here, because that copy is already the generic failure message.
There was a problem hiding this comment.
🤖 Done in 27bf22a. A failed list load is kept as the failure, so a channel that can't be found then shows "Could not open the channel". Covered by a new search-page row ("reports a failure when the channel list failed"). Toast rows also re-tap now to show the tap lock is released.
| // A tap can arrive before the first list load finishes. A failed load | ||
| // still leaves the channel's own metadata, so the tap is a real retry. | ||
| if (!ref.read(channelsProvider).hasValue) { | ||
| await ref.read(channelsProvider.future).then((_) {}, onError: (_) {}); |
There was a problem hiding this comment.
🤖 Optional: this wait has no deadline and no loading state.
With no cached snapshot, the initial list load waits for the connection with no time limit (channels_provider.dart:269-270). The tap gives no feedback while it waits, and its _resolving key stays held until the wait ends, even if the page is closed. We couldn't find a realistic journey where this hangs forever, so it doesn't block merge. With the isCurrent fence above, the worst outcome is a silent no-op. A short in-progress indicator would still make the wait visible to the user.
There was a problem hiding this comment.
🤖 Leaving this one for now. With the current-page fence, the worst case is a tap that does nothing while the connection is still coming up, and a later tap after the list loads works. A deadline or spinner would be a separate UX change shared with the other open-channel paths.
A slow lookup pushed its channel over whatever page the user had opened while it waited. A failed channel list now reports a missing channel as a failure, since it may be one the user belongs to. Signed-off-by: Tom Brow <[email protected]>
…channels Signed-off-by: Tom Brow <[email protected]>
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.
Follow-up on 4becf4a1 (base 8f83c632): no blockers. The two fixes from the last review hold up.
- Late push: the
ModalRoute.isCurrentfence drops a lookup once anything is on top of the tapping page. That covers links in chat bubbles and threads and search hits. The new "moved on" test fails without it. - Failed list load: that error now stays the reported failure. A member's private channel shows "Could not open the channel" instead of "not found", and a public channel still opens from its metadata.
- Retry after a failure toast: the toast rows now tap again, which closes the lock-release gap.
Leaving the wait without a deadline as a follow-up is fine. With the fence, the worst case is a tap that silently does nothing. The merge from main doesn't touch the changed files, and CI is green. We reviewed the source only and didn't run the app.
Tapping a
#channellink to an open channel you haven't joined showed "Channel could not be opened", because a tap could only open a channel already in the local list. A search hit had the same gap: it did nothing when its channel wasn't in the loaded list, for example when the open-channel directory failed to load or hadn't loaded yet.Both taps now go through one resolver. It looks in the loaded list first, then reads the channel's own metadata, as
buzz://deep links already do.The resolver also guards two cases:
Fixes AIDA-2324 and AIDA-2286. #8764 already made search load the open directory, which fixed the common search case, so the search part here covers the remaining fallback.
Before: a link to an unjoined open channel fails
before-link.mp4
After: the same link opens the channel in preview
after-link.mp4
Validation
mainonce and confirmed every new test fails there.mobile-checkpass.#speed-tests.mainand opens a preview with this change (videos above).mainit also opens, through #8764's directory load.buzz-review-completed
🤖 Generated with Claude Code