feat(chat): the chat opens centred by default, with a button back to the sidebar - #76
Conversation
…the sidebar
New setting `levelcode.ai.chat.startLocation` — `editor` (default), `secondarySidebar`,
or `none` — decides where the chat opens when a window opens. The centre is the default:
it is where the reference puts the conversation, and after T1/T7 it is the only surface
where the transcript gets its full reading column.
This is only the STARTING position. Both directions stay one click away and neither
touches the setting: "AI: Open Chat in Editor" from the sidebar, "AI: Move Chat to
Sidebar" from the tab.
THE WAY BACK NEEDED A BUTTON. Until now the only affordance for moving the chat right was
the card in the sidebar — which you cannot see while the chat is a tab, and which nobody
would open to find out they could. Now that the chat opens centred for everyone, that path
had to become visible from the centre: an editor/title action gated on
`activeWebviewPanelId == 'levelcode.ai.chat'`, so it appears on the chat tab and nowhere
else. It disposes the panel rather than reimplementing the move, so the button and ⌘W
behave identically and cannot drift.
REPLACES THE OLD AUTO-REVEAL, rather than sitting beside it. That block revealed the
SIDEBAR for at most five launches. Left in place it would have opened both surfaces on
every fresh install — and because it was capped, the double-open would have stopped on its
own after five launches, which is the worst kind of bug to be handed. Its cap was standing
in for a setting that did not exist; `none` is a better answer, and a chat that silently
stops appearing is harder to diagnose than one that keeps showing you it is broken.
TWO THINGS THAT WOULD HAVE BITTEN:
- The startup open passes `preserveFocus: true`. Opening the chat centred is the ask;
taking the caret off a file VS Code just restored is not, and at startup those happen
in the same instant. The panel is still the visible tab.
- `openChatInEditor` now reads an options object from its first argument, and VS Code
hands a command its menu context in exactly that position. The registration is wrapped
(`() => openChatInEditor()`), or a title-bar click could set preserveFocus by accident.
`preserveFocus === true` is also read strictly for the same reason.
Guards, each bypass-verified by reverting the fix:
- default flipped back to the sidebar; the `none` opt-out renamed away
- startup reverting to a sidebar reveal; `none` no longer returning early
- the startup open stealing focus; the command bound by reference; loose preserveFocus
- the move-back duplicating the hand-over instead of reusing dispose
Plus a control edit (comment-only) confirmed NOT to fail, so the suite is not just
matching on whitespace.
19 tests in chatSurface, 32 suites green.
There was a problem hiding this comment.
Pull request overview
This PR changes the default chat surface behavior in extensions/levelcode-ai by introducing a new levelcode.ai.chat.startLocation setting (defaulting to opening chat as a centered editor tab), and adds a discoverable “move back to sidebar” title-bar action when the chat is in an editor tab.
Changes:
- Add
levelcode.ai.chat.startLocation(editordefault,secondarySidebar,none) to control startup placement of the chat. - Add
AI: Move Chat to Sidebarcommand and aneditor/titleaction scoped to the chat webview panel. - Replace the prior capped sidebar auto-reveal onboarding logic with a single startup open path driven by the new setting.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| extensions/levelcode-ai/extension.js | Implements startup location selection, preserves focus on startup editor open, and adds the move-back command using panel disposal handover. |
| extensions/levelcode-ai/package.json | Declares the new command + editor/title menu entry, and adds the levelcode.ai.chat.startLocation configuration property. |
| extensions/levelcode-ai/test/chatSurface.test.js | Updates/extends tests to pin the new startup behavior, command wiring, and move-back affordance. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // broken webview forcing the panel open forever; `chat.startLocation: none` is a better answer | ||
| // to that, and a chat that silently stops appearing after five launches is worse to diagnose | ||
| // than one that keeps showing you it is broken. | ||
| setTimeout(() => { revealChatAtStartup(); }, 600); |
Review on #76. `revealChatAtStartup()` is async and nothing awaits the timer, so a rejection from createWebviewPanel or from the focus command would surface as an unhandled rejection in the extension host — noisy, and attributed to nothing in particular. Caught, but LOGGED rather than swallowed. A chat that never appears with no trace of why is the exact failure this setting exists to make explicable; `.catch(() => {})` would have answered the review and made the product worse. The window still starts and both surfaces stay openable by hand. The same defect was in moveChatToSidebar, which I wrote in this PR — but it wants the OPPOSITE treatment. That one is an explicit click, and `registerCommand` awaits whatever the handler returns, so the reveal is now RETURNED: a failure reaches the user as a failed command instead of leaving them pressing a button that does nothing. Silence is right for an implicit startup action and wrong for a deliberate one. Guards, each bypass-verified by reverting the fix: - the .catch removed (the reviewer's bug, restored) — caught - caught but swallowed with no dbg — caught - move-back firing and forgetting again — caught - the registration dropping the returned promise, which would make returning it pointless NOT swept into this commit: seven other bare `executeCommand('levelcodeAi.chat.focus')` calls predate this PR and have the same shape. Rewriting unrelated call sites inside a review fix would bury the change under a diff nobody asked for; filed separately. 21 tests in chatSurface, 32 suites green.
|
Valid. Fixed in b0a00d7.
Caught, but logged rather than swallowedrevealChatAtStartup().catch((e) => dbg('chat.startLocation.failed', { msg: String((e && e.message) || e) }));
The same defect was in
|
| bypass | caught |
|---|---|
the .catch removed — your bug, restored |
✓ |
caught but swallowed with no dbg |
✓ |
| move-back firing and forgetting again | ✓ |
| the registration dropping the returned promise (which would make returning it pointless) | ✓ |
Scope note
Seven other bare executeCommand('levelcodeAi.chat.focus') calls in extension.js have the same shape and predate this PR — around lines 423, 468, 1055, 2298, 2430, 2526 and 2696. They are not all the same category: 2696 is a command registration that should return, while the rest are background paths that should catch and log.
I've left them out deliberately rather than sweeping seven unrelated call sites into a review fix, and filed them as follow-up work with the pattern to apply.
21 tests in chatSurface, 32 suites green.
New setting
levelcode.ai.chat.startLocation—editor(default),secondarySidebar, ornone— decides where the chat opens when a window opens.The centre is the default: it's where the reference puts the conversation, and after T1/T7 it's the only surface where the transcript gets its full reading column.
This is only the starting position. Both directions stay one click away, and neither touches the setting:
AI: Open Chat in EditorAI: Move Chat to SidebarThe way back needed a button
Until now the only affordance for moving the chat right was the card in the sidebar — which you cannot see while the chat is a tab, and which nobody would think to open in order to discover the capability. Now that the chat opens centred for everyone, that path had to become visible from the centre.
It's an
editor/titleaction gated onactiveWebviewPanelId == 'levelcode.ai.chat', so it appears on the chat tab and nowhere else. It disposes the panel rather than reimplementing the move —onDidDisposealready hands the slot back and replays the transcript — so the button and ⌘W behave identically and can't drift apart.It replaces the old auto-reveal rather than sitting beside it
There was already a startup block that revealed the sidebar, for at most five launches. Left in place it would have opened both surfaces on every fresh install — and because it was capped, the double-open would have stopped on its own after five launches, which is the worst kind of bug to be handed.
Its cap was standing in for a setting that didn't exist.
noneis a better answer, and a chat that silently stops appearing is harder to diagnose than one that keeps showing you it's broken.Two things that would have bitten
Focus. The startup open passes
preserveFocus: true. Opening the chat centred is the ask; taking the caret off a file VS Code just restored is not, and at startup those happen in the same instant. The panel is still the visible tab either way.Menu arguments.
openChatInEditornow reads an options object from its first argument — and VS Code hands a command its menu context in exactly that position. Bound by reference, a title-bar click could have setpreserveFocusby accident. The registration is wrapped (() => openChatInEditor()), andpreserveFocus === trueis read strictly for the same reason.Guards
Each bypass-verified by reverting the fix:
noneopt-out renamed awaynoneno longer returning earlypreserveFocusread looselyPlus a control: a comment-only edit to the same function, confirmed not to fail — so the suite is matching on behaviour rather than on whitespace.
19 tests in
chatSurface, 32 suites green.