Repository navigation
fix(workspace): a chat or agent URL you cannot open shows no composer (#3140) - #3156
Merged
Merged
Conversation
…#3140) Two Workspace URLs offered a live composer for something the URL did not name: - /workspace/c/<an id that is not yours>: the shell fell back to the first roster agent, so the page showed an empty chat of one of your own agents under someone else's chat id. - /workspace/a/<an agent not shared with you>: landOnAgent opened a fresh chat with no roster check. The band then said "You don't have access" above "Start a conversation below." and a live composer, with a "Try again" that cannot help. The chat case is decided in two places. The shell's thread list is the viewer's whole set (no server-side LIMIT), so when it loaded cleanly an id it lacks, and that the shell is not holding as a just-adopted new chat, renders "This chat isn't available" with a way back. The conversation is never mounted and its history is never read. When the list failed, the conversation still tries, and a 404 on its history read emits `thread-missing`. The shell takes that only while the URL still names the id, so a late answer cannot blank the chat the person moved to. An off-roster /workspace/a/<name> now uses the existing "You don't have access" stage (the ?agent= rule) and gains a way back. The roster must have loaded cleanly first, so a roster error keeps its own copy. usePortalAgentPage exposes `denied` for a 403/404, and the band and the details panel withhold "Try again" for it, since a retry gets the same answer. A 5xx keeps its retry. Tests (mounted, #2918), 14 in all: - portalUnavailableTargets.mount.spec.js: the shell, both URLs, own chat and agent still open, in-app navigation, the failed-list fallback, a late 404, and the composable's denied flag. - portalAgentBandDenied.mount.spec.js: no retry when refused. - portalThreadMissing.mount.spec.js: a 404 emits, a 5xx does not. Reverting each source file turns its tests red: Portal.vue 4, the composable 3, the band 2, the conversation 1. Full suite 4,512 passed; the design-token check and the build pass. Looked at on a branch build against the local stack, in light and dark: both states render with no composer and no page errors, the way back works, and an own agent and chat still open with their composer. Fixes #3140 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 tasks done
Contributor
Author
/review report:
|
| changed symbol | executed by | live consumer | verdict |
|---|---|---|---|
Portal.vue chatUnavailable / bootstrap + watcher rule |
portalUnavailableTargets.mount.spec.js (shell mounted) |
the stage branch | ✅ |
Portal.vue off-roster /workspace/a/:name |
same | the unreachableAgent stage |
✅ |
PortalConversation → thread-missing on a history 404 |
portalThreadMissing.mount.spec.js |
Portal.vue onThreadMissing |
✅ |
usePortalAgentPage.denied |
portalUnavailableTargets (composable), portalAgentBandDenied.mount.spec.js |
PortalAgentBand, PortalAgentDetails |
✅ for the band; see I1 |
Fix mutations: reverting each file turns its tests red. Portal.vue: 4; the composable: 3; the band: 2; the conversation: 1.
Live check (local dev stack, 2026-10-01, dev + this PR, light and dark)
/workspace/c/<foreign id>: "This chat isn't available", no composer./workspace/a/no-such-agent: "You don't have access to …", no composer, no retry.- 0 page errors.
- An own agent and chat still open with the composer.
Critical findings
None.
Informational
- [I1] Test gap:
PortalAgentDetailsretry bindings aren't mounted (confidence 7/10).PortalAgentDetails.vue::retryable="!pageDenied"and:show-retry="!pageDenied". They mirror the band's, which is mutation-proven. A mount of the details panel with a denied composable would close it. - [I2] Depends on
dev'sthreadsLoaded. Cherry-picked onto a branch older thandev, the shell throws a ReferenceError and the fix doesn't apply. Ondevit's correct. Note this for anyone backporting.
Clean categories
- Auth: UI only; the server already refuses these reads.
- Race: a just-adopted new chat is excluded via
pendingSession, and a late 404 for a chat already left is ignored (both tested). - Frontend: existing stage classes and tokens; no
v-html.
Summary: Critical 0 · Informational 2 · Scope clean.
…vailable' (#3140) The sessionId watcher set unavailableChatId when the trusted list lacked the id, but its 'known' branch never cleared it, so a chat created in another tab after this one loaded its list stayed behind the unavailable screen until the user navigated away. Clear it when the id turns up. Mechanical, per the merge-train note on the PR; new mount test fails without the line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
merge-train: on this train. I pushed one mechanical commit to your branch ( Why: the What changed:
Not fixed, follow-ups:
Note: #3168 conflicts with this PR in |
Merged
7 tasks done
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
/workspace/c/<a chat that isn't yours>used to show an empty chat of one of your own agents, with a live composer, under that id. It now shows "This chat isn't available" with Back to your chats. No conversation is mounted and no history is read./workspace/a/<an agent not shared with you>used to show "You don't have access" next to "Start a conversation below.", a live composer, and a "Try again" that couldn't help. It now shows the existing "You don't have access to …" stage alone, with Back to your chats.How
activeAgentstays null, so the conversation never mounts. This applies on deep links (bootstrap) and on in-app navigation (the route watcher).thread-missing. The shell accepts it only while the URL still names that id, so a late 404 can't blank the chat the person moved to./workspace/a/:namewatcher applies the same roster rule as?agent=: a name missing from a cleanly loaded roster gets the "unreachable" stage instead oflandOnAgent. A roster error keeps its own copy.usePortalAgentPageexposesdenied(403/404). The band and the details panel bindretryable/show-retryto it.Changes
src/frontend/src/views/Portal.vue:unavailableChatId/chatUnavailable, the new stage, the roster check on the agent route, and a way back from both stagessrc/frontend/src/components/portal/PortalConversation.vue: emitsthread-missingon a history 404src/frontend/src/composables/usePortalAgentPage.js:deniedsrc/frontend/src/components/portal/PortalAgentBand.vue,PortalAgentDetails.vue: no retry when deniedportalUnavailableTargets.mount.spec.jsportalAgentBandDenied.mount.spec.jsportalThreadMissing.mount.spec.jsTest Plan
denied, while 503 is not denied;check-design-tokens: OK.npm run build: OK./workspace/a/<own>) still lands in its chat, and reloading that chat URL still shows the composer.Fixes #3140
🤖 Generated with Claude Code