Skip to content

feat(chat): the chat opens centred by default, with a button back to the sidebar - #76

Merged
ndemianc merged 2 commits into
developfrom
feat/chat-center-by-default
Aug 16, 2026
Merged

feat(chat): the chat opens centred by default, with a button back to the sidebar#76
ndemianc merged 2 commits into
developfrom
feat/chat-center-by-default

Conversation

@ndemianc

Copy link
Copy Markdown
Contributor

New setting levelcode.ai.chat.startLocationeditor (default), secondarySidebar, or none — 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:

from button command
sidebar → centre chat view title bar AI: Open Chat in Editor
centre → sidebar chat tab title bar (new) AI: Move Chat to Sidebar

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 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/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 — onDidDispose already 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. none is 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. openChatInEditor now 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 set preserveFocus by accident. The registration is wrapped (() => openChatInEditor()), and preserveFocus === true is read strictly for the same reason.

Guards

Each bypass-verified by reverting the fix:

bypass caught
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
preserveFocus read loosely
move-back duplicating the hand-over instead of reusing dispose

Plus 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.

…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.
Copilot AI lite review requested due to automatic review settings August 15, 2026 22:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (editor default, secondarySidebar, none) to control startup placement of the chat.
  • Add AI: Move Chat to Sidebar command and an editor/title action 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.

Comment thread extensions/levelcode-ai/extension.js Outdated
// 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.
@ndemianc

Copy link
Copy Markdown
Contributor Author

Valid. Fixed in b0a00d7.

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, attributed to nothing.

Caught, but logged rather than swallowed

revealChatAtStartup().catch((e) => dbg('chat.startLocation.failed', { msg: String((e && e.message) || e) }));

.catch(() => {}) would have answered the review and made the product worse: a chat that never appears, with no trace of why, is the exact failure this setting exists to make explicable. The window still starts and both surfaces stay openable by hand.

The same defect was in moveChatToSidebar — and it wants the opposite fix

I'd written the same shape there. But that one is an explicit click, and registerCommand awaits whatever the handler returns, so the reveal is now returned instead of caught:

return vscode.commands.executeCommand('levelcodeAi.chat.focus');

A failure now reaches the user as a failed command, rather than leaving them pressing a button that does nothing. Silence is right for an implicit startup action and wrong for a deliberate one — so the two paths deliberately differ, and both are pinned.

Guards

Each bypass-verified by reverting the fix:

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.

@ndemianc
ndemianc merged commit e65286f into develop Aug 16, 2026
1 check passed
@ndemianc
ndemianc deleted the feat/chat-center-by-default branch August 16, 2026 00:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants