fix(vscode): file an editor comment under the session it was delivered to

A comment written with no session tab open opens one, and that panel knows
its directory long before it has loaded the session list and selected its
session. The webview filed the draft on the first snapshot that had a
directory, with the session key falling back to "draft" because no session
was current yet. The panel's composer reads the session's key, so the chip
never appeared, while the editor thread saw the draft in the store snapshot
and reported it attached.

A session panel now stamps its session on every comment it delivers, and
the webview waits until it actually shows that session before filing. The
sidebar files on its current session or an open new-session draft, and no
longer falls back to "draft" merely because nothing is selected yet. The
resolver is a pure module with tests.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
This commit is contained in:
Bohdan Triapitsyn
2026-09-05 14:18:51 +03:00
parent 4f1f9e6650
commit 60a4dccb32
5 changed files with 141 additions and 17 deletions
+1 -1
View File
@@ -87,7 +87,7 @@ The webview build emits each worker as one self-contained file. VS Code webviews
- A thread never owns a draft. It mints the draft id, hands the payload to a chat webview (the active or newly opened session panel, else the sidebar), and follows the webview's whole-draft-list `inlineComments:sync` snapshots: present means show, absent after having been seen means dispose. A snapshot is tagged with the surface that produced it (a panel id or `sidebar`) and only decides that surface's own threads, because every webview runs its own draft store.
- A comment the composer never confirms holding within 30 s is retracted from every surface's pending hold, its thread disposed, and the user told, so a thread cannot promise a send that will never happen.
- `inlineCommentSelection.ts` holds the pure pieces (line ranges, the diff-side and real-path resolution for `git:` documents, the pending hold, removal broadcast, thread fate) without the `vscode` import so they are unit-tested directly.
- Webview side: `webview/inlineCommentRemovals.ts` remembers removals that arrive before a delayed delivery lands, so a comment dropped while its panel was still booting does not appear as a chip later. The extension is not activated on startup for this; the right-click command activates it, and the gutter `+` appears from then on.
- Webview side: `webview/inlineCommentTarget.ts` decides where a delivered comment is filed. A session panel stamps its session on every comment it delivers and the webview waits until it shows that session; the sidebar files on its current session or open draft. Filing on the first snapshot with a directory put the draft under `draft` while a fresh panel was still loading its session list, a key that composer never reads. `webview/inlineCommentRemovals.ts` remembers removals that arrive before a delayed delivery lands, so a comment dropped while its panel was still booting does not appear as a chip later. The extension is not activated on startup for this; the right-click command activates it, and the gutter `+` appears from then on.
## Shared webview message ordering
@@ -30,6 +30,12 @@ type LineCommentPayload = {
type SessionPanelState = {
/** This panel's id, which is also its surface identity for comment threads. */
id: string;
/**
* The session this panel was opened for; null for a new-session panel. A
* comment delivered here names it, so the webview files the draft under that
* session's key rather than whatever it shows while still booting.
*/
sessionId: string | null;
panel: vscode.WebviewPanel;
sseStreams: Map<string, AbortController>;
/**
@@ -148,6 +154,7 @@ export class SessionEditorPanelProvider {
const state: SessionPanelState = {
id: panelId,
sessionId: initialSessionId,
panel,
sseStreams: new Map(),
pendingLineComments: [],
@@ -183,7 +190,7 @@ export class SessionEditorPanelProvider {
void panel.webview.postMessage({
type: 'command',
command: 'addLineComment',
payload: pending,
payload: { ...pending, targetSessionId: state.sessionId ?? undefined },
});
}
@@ -363,7 +370,7 @@ export class SessionEditorPanelProvider {
void entry.panel.webview.postMessage({
type: 'command',
command: 'addLineComment',
payload,
payload: { ...payload, targetSessionId: entry.sessionId ?? undefined },
});
return entry.id;
}