feat(chat): the chat is an editor tab, and only an editor tab - #81
Merged
Conversation
Removes the contributed chat view in the right-hand bar entirely. Sessions keeps that
container — an index of past conversations is a different thing from the conversation, and
does not need to split a narrow column with it.
WHY THE VIEW HAD TO GO RATHER THAN BE DEPRIORITISED. Two possible hosts for one
conversation is what produced every bug reported against it: closing the tab reopened the
chat on the right, ⇧⌘I opened it on the right, and the ResizeObserver console spam came
from `purpose=webviewView` — the sidebar copy. Each was fixable in isolation; the shape
that kept generating them was not.
Gone with it: the hand-over card (detachedHtml), the `reattach` message, the move command
and its button, the close-versus-move distinction, and two of the three transcript-replay
paths. `levelcode.ai.focus` (⇧⌘I) and every background reveal now open the tab.
CLOSING THE TAB IS AN ENDING, NOT A DISCARD. It seals the live session into History and
lets memory learn from it — the same `m.seal('done')` + `enrichMemoryAsync(sealedId)` that
New Chat has always done, now extracted into sealLiveSession() and shared. Two copies would
drift, and the half that drifted would be the close path, because that is the half nobody
watches. It cannot throw: it runs from a dispose handler, where an exception has nowhere
to go.
The three actions that lived on the sidebar view's title bar — New Chat, Add Files, Set API
Key — move to the chat TAB's title bar, gated on
`activeWebviewPanelId == 'levelcode.ai.chat'`. Deleting the view without moving them would
have deleted the only place they were reachable outside the palette, which is no place at
all for a capability nobody knows exists.
`chat.startLocation` drops `secondarySidebar`; `editor` and `none` are the only honest
values left. An existing `secondarySidebar` in settings.json falls back to the default
through the validation that was already there.
Guards, each bypass-verified by reverting the fix:
- closing no longer sealing, so the conversation is silently dropped
- memory never learning from the sealed session
- newChat growing its own copy of the sealing logic again
- ⇧⌘I pointing back at the removed view
- nothing constructing the chat provider (which still owns wire()/makeLive())
- the chat returning as a contributed view (caught in sessionsUi, where that guard lives)
Two test corrections worth naming:
- the fire-and-forget scan looked for executeCommand('levelcodeAi.chat.focus'), a string
that no longer appears anywhere — it would have kept passing while checking nothing.
Repointed at openChatInEditor(), which is what opens the chat now, and taught to skip
the function declaration.
- sessionsUi asserted "both Chat and Sessions views present". It now asserts Sessions is
there and Chat is NOT, which is the property that matters.
Supersedes #80: a close-versus-move distinction is meaningless when there is nothing to
move to.
19 tests in chatSurface, 34 suites green.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR makes the LevelCode AI chat editor-tab-only by removing the contributed sidebar Chat view, keeping the sidebar container exclusively for Sessions, and simplifying the chat lifecycle (open/focus/close) around a single WebviewPanel.
Changes:
- Removed the contributed
levelcodeAi.chatWebviewView surface and related “move/hand-off” machinery. - Ensured closing the chat tab seals the live session (shared sealing path with New Chat) and triggers memory enrichment.
- Updated extension contributions and tests to reflect the editor-only chat surface and moved title-bar actions onto the chat tab.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| extensions/levelcode-ai/extension.js | Removes sidebar chat surface, re-routes focus/open behavior to editor tab, and adds shared session-sealing on close. |
| extensions/levelcode-ai/package.json | Removes Chat view contribution and move command; relocates chat actions to the chat editor tab title bar. |
| extensions/levelcode-ai/test/chatSurface.test.js | Updates wiring/guard assertions for editor-only chat and adds close-path sealing and “no sidebar resurrection” checks. |
| extensions/levelcode-ai/test/sessionsUi.test.js | Updates view contribution assertions to ensure Sessions is present and Chat is not. |
Suppressed comments (1)
extensions/levelcode-ai/extension.js:2370
- The PR description says an existing
chat.startLocation: "secondarySidebar"should fall back to the default via validation, but the current flow still allows that value throughchatStartLocation()and then logs it. Even though it ultimately opens the editor tab, this means the setting does not actually “fall back” and diagnostics can misleadingly reportwhere: secondarySidebar. Consider mapping the legacy value toeditorbefore logging/branching.
const where = chatStartLocation();
dbg('chat.startLocation', { where });
if (where === 'none') { return; }
await openChatInEditor({ preserveFocus: true });
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
2328
to
2329
| // Hand the sidebar slot over. Its listener survives an html swap, so the card's button still | ||
| // reaches the same handler — see ChatViewProvider.wire. |
This was referenced Aug 16, 2026
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.
Removes the contributed chat view in the right-hand bar entirely. Sessions keeps that container — an index of past conversations is a different thing from the conversation, and doesn't need to split a narrow column with it.
Why the view had to go, rather than be deprioritised
Two possible hosts for one conversation is what produced every bug reported against it:
ResizeObserverconsole spam came frompurpose=webviewView— the sidebar copyEach was fixable in isolation. The shape that kept generating them was not.
Gone with the view: the hand-over card (
detachedHtml), thereattachmessage, the move command and its button, the close-versus-move distinction, and two of the three transcript-replay paths.levelcode.ai.focus(⇧⌘I) and every background reveal now open the tab.Closing the tab is an ending, not a discard
It seals the live session into History and lets memory learn from it — the same
m.seal('done')+enrichMemoryAsync(sealedId)New Chat has always done, now extracted intosealLiveSession()and shared.Two copies would drift, and the half that drifted would be the close path, because that's the half nobody watches. It cannot throw: it runs from a dispose handler, where an exception has nowhere to go.
The buttons moved with the chat
New Chat, Add Files and Set API Key lived on the sidebar view's title bar. They're now on the chat tab's title bar, gated on
activeWebviewPanelId == 'levelcode.ai.chat'.Deleting the view without moving them would have deleted the only place they were reachable outside the palette — which is no place at all for a capability nobody knows exists. A guard also fails on any menu entry still scoped to the removed view, since a stale
whenis a button that appears nowhere.Setting
chat.startLocationdropssecondarySidebar;editorandnoneare the only honest values left. An existingsecondarySidebarin settings.json falls back to the default through the validation that was already there.Guards
Each bypass-verified by reverting the fix:
newChatgrows its own copy of the sealing logic againwire()/makeLive())sessionsUi, where that guard lives)Two test corrections worth naming
The fire-and-forget scan was checking nothing. It looked for
executeCommand('levelcodeAi.chat.focus')— a string that no longer appears anywhere — so it would have kept passing forever. Repointed atopenChatInEditor(), and taught to skip the function declaration rather than flag it as an unhandled call.sessionsUiasserted "both Chat and Sessions views present". It now asserts Sessions is there and Chat is not, which is the property that matters.Supersedes #80
A close-versus-move distinction is meaningless when there's nothing to move to. #80 should be closed unmerged.
19 tests in
chatSurface, 34 suites green.