fix(desktop): open the thread panel for bare ?thread= deep links - #158
Merged
Conversation
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This was referenced Aug 11, 2026
Closed
devin-ai-integration
Bot
force-pushed
the
devin/fix-153-bare-thread-deeplink
branch
2 times, most recently
from
August 11, 2026 10:45
0ae1a11 to
13e3b49
Compare
oscarlehuu
force-pushed
the
devin/fix-153-bare-thread-deeplink
branch
from
August 11, 2026 11:04
13e3b49 to
93c9271
Compare
devin-ai-integration
Bot
force-pushed
the
devin/fix-153-bare-thread-deeplink
branch
from
August 11, 2026 13:01
93c9271 to
54859fb
Compare
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
A bare `?thread=<id>` deep link commits the head to panel state on the same render, but the head and its ancestors are only fetched afterwards by the channel route. `useThreadTargetSync` saw an open head with no message and a settled timeline, so it tore the panel down before the fetched events landed. Hold that teardown while the channel route is resolving that exact head: `ChannelRouteScreen` derives `isRouteTargetResolving` during render (a consumer's effects run before the route's own fetch effect, so an effect-set flag is still false on the commit that first sees the new target) and commits the resolved events together with the end of the resolve. The hold is keyed to the head's identity and released when resolution settles, so unrelated missing heads and deleted or bogus ids still close the panel. Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-Authored-By: Oscar Le <oscar.lehuu@gmail.com>
oscarlehuu
force-pushed
the
devin/fix-153-bare-thread-deeplink
branch
from
August 11, 2026 14:23
70ae39f to
5fa1a25
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A bare
?thread=<id>deep link never opened the thread panel. The reportedmechanism (
useChannelRouteTargetreturning early whentargetMessageIdisnull) is real but not what breaks the link — instrumenting the failing scenario
showed the panel does open and is then torn down:
?thread=is mapped to panel state, soopenThreadHeadIdis the linked headfrom the first render (<ref_snippet file="desktop/src/features/channels/ui/useChannelPanelHistoryState.ts" />).
ChannelRouteScreenfetches that head plus its ancestors asynchronously andsplices them into the timeline.
useThreadTargetSyncsees an open head with nomessage and a settled timeline (
isTimelineLoading === false) and treats itas deleted: it clears
threadfrom the URL, so the route target disappearstoo and the spliced events arrive with nothing left to open.
So the missing piece is not a second target input — the panel state already
names the right head, which for a bare thread link is the root itself — but the
fact that "head not in the timeline" was read as "head does not exist" while the
route was still resolving it. No new URL parameter or route shape is introduced.
shouldHoldMissingThreadHead()inuseThreadTargetSyncholds teardown onlywhen the missing open head is the head the route is resolving, and only
until resolution settles. An unrelated missing head still closes immediately,
and an invalid id closes once the fetch settles instead of leaving a permanent
skeleton.
ChannelRouteScreenderivesisRouteTargetResolvingduring render ratherthan setting it from the fetch effect:
ChannelScreenis a child, so itseffects run before the route's fetch effect, and an effect-set flag would
still be
falseon the commit that first sees a new target in the URL (thatordering hole is exactly the teardown window). The resolved events and the end
of the resolve are committed in one continuation, so no consumer ever sees
"settled, but events not yet spliced".
useChannelRouteTarget's?messageId=semantics are untouched: its earlyreturn only skips main-timeline centering/ancestor expansion, which a bare
thread link does not ask for (the panel derives ancestry and the top-level
anchor from the head via
threadOrientation).Related issue
Fixes #153. Filed by PR #156; no other duplicates found.
Testing
RED before the fix (
tests/e2e/thread-orientation.spec.ts, on this branch'sbase):
GREEN after the fix:
Neighbouring deep-link forms (
?messageId=+?thread=inpersistent-agent-audience.spec.ts:37, panel-state URL round-trips innavigation.spec.ts,buzz://message?...→goChannel(channelId, {messageId, threadRootId})inuseMessageDeepLinks):navigation+persistent-agent-audience+thread-orientationrun together — base 22passed / 2 failed (the two above) / 1 skipped, this branch 25 passed / 0 failed
/ 1 skipped.
Unit: three focused cases for the hold predicate (holds the matching head while
resolving; releases once settled; never holds an unrelated head) in
desktop/src/features/channels/useThreadTargetSync.test.mjs. Gates:pnpm --filter buzz check,pnpm --filter buzz typecheck,pnpm --filter buzz test(5049 passed, 1 environment-gated skip, 0 failed).Full
--project=smokesuite, all 4 shards, before vs after — before is thisbranch's base, PR #156 head
(run 31458263391),
after is this branch
(run 31460257139),
same workflow and sharding:
channel-composer-overflow:217+community-rail:1088before,channel-browser:399after)thread-orientation03 and 04No failure appears after the fix that was not already failing before it, and the
only removed failures are the two this PR targets. The red lanes themselves are
preexisting on
main: the latestmainrun(31362178966) has
Desktop Smoke E2E (1)and(3)failing and(4)cancelled at the timeout(#109), and
channels.spec.ts:500(#110) fails in shard 1 there too. Localxvfb runs of shards 1–3 on this branch agree on the failing families
(10 / 0 / 7 failures in 629s / 567s / 640s; the local extras over CI are the
usual xvfb-only flakes such as
messaging.spec.ts:1300andonboarding-agent-defaults.spec.ts:878, none in the thread deep-link path).No visual change beyond the panel now opening, which the specs assert.
docs/crew/STATE.mdrecords the fixed deep-link behavior. No new decision wasneeded, so no
D-0xxnumber was taken.Link to Devin session: https://app.devin.ai/sessions/9ac4ed023b79405a824070632a15449e
Requested by: @oscarlehuu