Repository navigation
fix(desktop): route forum message links to the post, not the post list - #7361
PresentJay wants to merge 4 commits into
Conversation
Signed-off-by: Hyeonjae.Jeong <[email protected]>
🔐 Codex Security Review
|
chessing234
left a comment
There was a problem hiding this comment.
forum vs stream branching matches search routing, and the failure path staying clickable is right. good regression coverage.
chessing234
left a comment
There was a problem hiding this comment.
deep link handler returns true before openMessageLink finishes, so we ack the pending link even if nav never lands — channel links still await goChannel. please make openMessageLink return the navigation promise and only return true after it resolves.
…link Signed-off-by: Hyeonjae.Jeong <[email protected]>
|
@chessing234 Good catch — that was a regression I introduced. Before this PR the handler Fixed as you asked:
I did not add a test for the ordering, and want to be explicit about why rather
If you would rather have it pinned by a test anyway, say the word and I will add Local results for the new head: |
chessing234
left a comment
There was a problem hiding this comment.
openMessageLink now returns the nav promise and the listener only acks after it resolves, same as goChannel above it. good.
|
@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. |
|
Could a maintainer approve the pending CI and Desktop Release Candidate workflow runs and take a code-owner look at this PR? The navigation/acknowledgement issue raised in review has been fixed and re-approved. The local results reported above for the current head ( Both workflow runs are waiting for approval: I've also requested a Codex review on this head. Thanks for taking a look! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d488231bc4
ℹ️ 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".
| messageId: payload.messageId, | ||
| threadRootId: payload.threadRootId, | ||
| }); | ||
| return true; |
There was a problem hiding this comment.
Return the navigation result before acknowledging the link
When an OS message link arrives while a thread reply is being edited, the registered navigation guard makes goChannel or goForumPost resolve to false, but useOpenMessageLink discards that result and this callback unconditionally returns true. listenForNavigationDeepLinks consequently removes the durable queue entry even though navigation was refused, so finishing or cancelling the edit cannot retry the link. Propagate the navigation helper's acceptance result instead of always acknowledging it.
AGENTS.md reference: AGENTS.md:L167-L174
Useful? React with 👍 / 👎.
| ).then(async (destination) => { | ||
| if (destination.kind === "forum-post") { | ||
| await goForumPost(destination.channelId, destination.postId, { |
There was a problem hiding this comment.
Cancel link resolution when its community unmounts
If getEventById remains pending while the user switches communities, the effect cleanup only sets cancelled before this promise continuation runs; the old callback can therefore call the global router after the new community is active and navigate it to the previous community's channel or post. The deep-link drain generation prevents acknowledgement but does not prevent this stale route write, so pass a lifecycle signal into this path and recheck it after destination resolution before navigating.
AGENTS.md reference: AGENTS.md:L176-L186
Useful? React with 👍 / 👎.
| import assert from "node:assert/strict"; | ||
| import test from "node:test"; | ||
|
|
||
| import { resolveMessageLinkDestination } from "./resolveMessageLinkDestination.ts"; |
There was a problem hiding this comment.
Exercise the production routing seam in the regression test
These tests import only the pure destination resolver and never exercise useOpenMessageLink or either production entry point. Replacing the new hook's branch with an unconditional goChannel call would therefore leave every test green while recreating #7315, so add a navigation-seam test that asserts forum links actually invoke goForumPost and stream links invoke goChannel.
AGENTS.md reference: AGENTS.md:L188-L192
Useful? React with 👍 / 👎.
Signed-off-by: Hyeonjae.Jeong <[email protected]>
|
Addressed all three Codex findings in
Validation:
The updated fork workflows will still need maintainer approval to run. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9258422fb4
ℹ️ 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".
| } catch (error) { | ||
| console.error( | ||
| "Failed to resolve message link destination", | ||
| messageId, | ||
| error, | ||
| ); | ||
| return channelDestination; |
There was a problem hiding this comment.
Keep failed destination lookups queued
When get_event fails transiently or the target was deleted, this catch converts the failure into a channel destination. For a queued OS link to a forum message, goChannel can then return success, causing drainPendingNavigationDeepLinks to acknowledge and remove the durable entry even though the forum post was never opened. Propagate a failed resolution to the queue consumer so it remains retryable; any best-effort in-app fallback can be handled separately.
AGENTS.md reference: AGENTS.md:L167-L174
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in e775087. Failed event lookups and forum comments without a resolvable root now return an unresolved destination. The OS deep-link caller disables channel fallback, so these cases neither navigate nor acknowledge the durable entry. In-app clicks retain their best-effort channel fallback.
Production-hook/drain regressions cover transient errors, missing events, successful retry, and missing forum roots. Re-enabling fallback for the queue consumer fails 3 tests. Focused tests pass 32/32; the final pre-push desktop suite, typecheck, lint and file-size gates passed.
| const destination = await resolveMessageLinkDestination( | ||
| link.channelId, | ||
| link.messageId, | ||
| link.threadRootId, | ||
| ); | ||
| if (cancelled()) return false; |
There was a problem hiding this comment.
Fence overlapping message-link resolutions
When a user activates two message links before the first get_event request completes—for example, two links within the currently mounted channel—the faster second lookup navigates first, but the slower older lookup can later navigate back over it. The current abort checks only cover component teardown or an external signal, not superseding activations, so destination resolution needs a latest-request generation fence before routing.
AGENTS.md reference: AGENTS.md:L176-L179
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in e775087. Each activation replaces a shared request token, checked after destination resolution and before routing or fallback. This also fences competing activations from separate mounted Markdown renderers. resetCommunityState invalidates the token before asynchronous teardown.
Production-hook tests cover older success/failure arriving after a newer activation, separate hook instances, and community reset before unmount. Removing the request fence fails 4 tests. Focused tests pass 32/32; the final pre-push desktop suite, typecheck, lint and file-size gates passed.
Signed-off-by: Hyeonjae.Jeong <[email protected]>
|
@codex review |
|
Validation update for e775087: the final pre-push desktop suite passed 6,471/6,471, alongside desktop lint, typecheck and file-size checks. Focused navigation tests passed 32/32. A fresh just ci run also passed workspace Rust fmt/Clippy, Tauri Rust fmt/Clippy and web checks. I stopped it during Flutter SDK provisioning for mobile-check when free disk space fell to about 3.6 GB; mobile and the later CI stages remain unverified locally. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e775087209
ℹ️ 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".
| const event = await fetchEvent(messageId); | ||
|
|
||
| if (event.kind === KIND_FORUM_POST) { | ||
| return { kind: "forum-post", channelId, postId: messageId }; |
There was a problem hiding this comment.
Verify the event belongs to the linked channel
For a malformed or manually authored link whose channel names A while id names a forum event in B, this unscoped lookup routes to /channels/A/posts/<B-event>. The forum thread backend fetches its root by exact ID without an #h filter and assigns the supplied channel ID, so the UI can display B's post under A and a reply can then be submitted into A against that foreign root. Compare the fetched event's h tag with channelId before returning any destination, as the existing message-link metadata resolver already does.
AGENTS.md reference: AGENTS.md:L265-L267
Useful? React with 👍 / 👎.
| } catch (error) { | ||
| console.error( | ||
| "Failed to resolve message link destination", | ||
| messageId, | ||
| error, | ||
| ); | ||
| return null; |
There was a problem hiding this comment.
Let terminal lookup failures advance the deep-link queue
When get_event returns the definitive event not found response for a deleted message, this catch treats it like a retryable outage and the OS caller returns false. Because take_pending_navigation_deep_link repeatedly returns the unchanged front of its VecDeque and the drain stops before acknowledging it, every later valid channel or message link is blocked until the queue is cleared by a community switch or process exit. Fresh evidence beyond the earlier retry-retention discussion is this FIFO head-of-line blocking for permanent failures; distinguish terminal not-found errors so the drain can resolve or discard that item and continue.
AGENTS.md reference: AGENTS.md:L198-L201
Useful? React with 👍 / 👎.
Why
Fixes #7315. A
buzz://messagelink into a forum channel opens the postlist and never selects the post, so a forum message is unreachable from its own
"Copy link".
Root cause
Both message-link entry points routed everything through
goChannel:desktop/src/shared/useMessageDeepLinks.ts— the deep-link listenerdesktop/src/shared/ui/markdown.tsx— the in-appbuzz://handler/channels/$channelIdcannot select a forum post:channels.$channelId.tsxhardcodes
selectedPostId={null}, anduseChannelRouteTargetreturns earlywhen
activeChannel.channelType === "forum". The route that works —/channels/$channelId/posts/$postId— was never used by message links.What
Add
resolveMessageLinkDestination(channelId, messageId, threadRootId),mirroring
resolveSearchHitDestination. Search already solves exactly this:openSearchHitWithNavigationresolves a destination first and branches togoForumPostfor forum targets. A message link carries no kind of its own, sothe event is fetched and branched on
KIND_FORUM_POST/KIND_FORUM_COMMENT;a comment resolves its post via
getThreadReferenceand passes itself asreplyId.Both entry points share
useOpenMessageLink. Queued OS links are acknowledgedonly after their destination is resolved and navigation is accepted. Lookup
failures, missing forum roots, and refused navigation leave the durable entry
available for retry. In-app clicks retain their best-effort channel fallback.
Pending lookups cannot navigate after community teardown or listener cancellation.
A shared request token makes the latest message-link activation win, including
clicks from separate mounted message renderers; community reset invalidates the token.
Extracting the hook also takes
markdown.tsxfrom 1862 to 1850 lines — thefile-size ratchet blocks growth on files already over the limit, and inlining
the branch there would have added 14.
Two comments asserting the old behaviour is fine are corrected in the same
change (
messageLink.ts,useMessageDeepLinks.ts); the issue calls both out.Before / after
The two destinations, in the mock
watercoolerforum. A forum message linkused to land on the left and now lands on the right.
Before —
/channels/<forum>?messageId=<post>: the post list, nothing selected:After —
/channels/<forum>/posts/<post>: the post, with its 25 replies:Tests
resolveMessageLinkDestination.test.mjscovers forum post, forum comment(asserting both
postIdandreplyId), stream message, a comment with noresolvable root, and a failed lookup.
useOpenMessageLink.test.mjsmounts theproduction hooks with the real resolver and durable deep-link drain. It covers
routing, acknowledgement ordering, failure/refusal and retry, teardown, and
out-of-order lookup completion across hook instances. Mutation checks verify
that restoring fallback acknowledgement or removing the request fence fails
the relevant regressions.
Fork PRs need maintainer approval before CI runs, so local results for the
pushed head, via the repo's pre-push lanes: