Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3396c9e
fix(activate): target title-bar commands to their click-origin instance
easonliang28 Sep 4, 2026
a143267
ci: re-trigger PR review-state labeler reconciliation (no-op commit)
easonliang28 Sep 7, 2026
723d871
fix(activate): serialize overlapping openClineInNewTab calls and hard…
easonliang28 Sep 7, 2026
c3027c8
test(activate): pin openClineInNewTab creation branches and strengthe…
easonliang28 Sep 7, 2026
00eb15f
fix(activate): harden tab creation serialization and centralize title…
easonliang28 Sep 7, 2026
ae038ab
test(activate): pin tracked tab identity against the created panel
easonliang28 Sep 7, 2026
99662bd
test(activate): pin the tracked tab panel passed to getInstanceForView
easonliang28 Sep 7, 2026
6c5d54e
chore(vps2-f0): re-stack F0 onto upstream/main 7c291bb08
easonliang28 Sep 28, 2026
f94a8a6
Merge branch 'main' into vps2/f0-button-targeting
easonLiangWorldedtech Oct 5, 2026
56542c5
Merge upstream/main into vps2/f0-button-targeting
Oct 7, 2026
9b0d897
test(activate): pass the focus tracker to the tab-creation tests
Oct 7, 2026
faa5fd7
Merge upstream/main into vps2/f0-button-targeting
Oct 7, 2026
bc39e81
fix(activate): focus the provider focusPanel selects, and never hand …
Oct 8, 2026
08225f5
fix(activate): scope palette commands to their surface and pin the fo…
easonliang28 Oct 13, 2026
6a7ae11
style(activate): bring the touched files under the repo format gate
easonliang28 Oct 13, 2026
f8dde3e
Merge upstream/main (4f1dee18e) into vps2/f0-button-targeting
easonLiangWorldedtech Oct 16, 2026
a088195
fix(activate): keep the tracked surface live across resolutions and d…
easonLiangWorldedtech Oct 22, 2026
219572f
chore(knip): mark the test-only getPanel seam @internal
easonLiangWorldedtech Oct 22, 2026
16522b6
fix(webview): scope sidebar listener disposal to the disposed view
easonLiangWorldedtech Oct 22, 2026
e931e35
test(webview): type the replacement-view double without an explicit any
easonLiangWorldedtech Oct 22, 2026
2597a1b
fix(webview): ignore undefined disposables from view doubles
easonLiangWorldedtech Oct 22, 2026
fc1614d
test(activate): cover the focusPanel command negative path
easonLiangWorldedtech Oct 22, 2026
cb25147
Merge upstream/main into vps2/f0-button-targeting
Nov 1, 2026
8e03755
fix(merge): restore the upstream hunks dropped by the main merge
easonLiangWorldedtech Nov 1, 2026
f61e069
chore(lint): normalize eslint-suppressions.json formatting
easonLiangWorldedtech Nov 1, 2026
3eb0556
merge upstream/main (a4f774b17) into vps2/f0-button-targeting
easonLiangWorldedtech Nov 2, 2026
2860e2b
fix(commands): type the command callback args without any
easonLiangWorldedtech Nov 2, 2026
f1105fd
test(activate): keep the diagnostics command wired to the arguments i…
easonLiangWorldedtech Nov 4, 2026
5e44ab0
refactor(webview): drop the per-surface disposable map that nothing r…
easonLiangWorldedtech Nov 4, 2026
e4b3151
test(webview): pin the tab panel's listener tracking as well
easonLiangWorldedtech Nov 4, 2026
9ab5356
test(activate): give the focusInput silence assertions teeth, drop th…
easonLiangWorldedtech Nov 7, 2026
d0e3c3e
fix(commands): keep the palette path off a hidden sidebar provider
easonLiangWorldedtech Nov 7, 2026
b0946f1
test: cover the no-visible-provider path for the sidebar commands
easonLiangWorldedtech Nov 7, 2026
050b5f0
fix(commands): answer the ten human review rows on the panel seam
easonLiangWorldedtech Nov 9, 2026
e3d2460
fix(activate): resolve manifest rows before reading them
easonLiangWorldedtech Nov 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/types/src/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ export const commandIds = [
"popoutButtonClicked",
"settingsButtonClicked",

// Editor-tab (popped-out) surface variants of the title-bar buttons. The
// shared ids above target the sidebar click origin, so the tab surface
// needs its own ids (see registerCommands.ts getTabProvider).
"plusButtonClickedInTab",
"settingsButtonClickedInTab",
"marketplaceButtonClickedInTab",
"historyButtonClickedInTab",

"openInNewTab",

"newTask",
Expand Down
90 changes: 90 additions & 0 deletions src/activate/__tests__/commandManifest.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import fs from "fs"
import { fileURLToPath } from "url"

import { describe, expect, it } from "vitest"

// This suite reads the contributes manifest itself rather than a constant copied from it: the
// command ids, the menu rows and the `when` clauses below are what decide whether a title-bar
// button reaches the handler that owns it, and nothing else in the repo checks them. Renaming an
// id, flipping a clause or dropping a row is invisible to every other test in the suite.
const manifest = JSON.parse(fs.readFileSync(fileURLToPath(new URL("../../package.json", import.meta.url)), "utf8"))
const commands: { command: string; title: string }[] = manifest.contributes.commands
const menus = manifest.contributes.menus as Record<string, { command: string; when?: string }[]>

// `find` answers `T | undefined`, and TS does not narrow a row back through a `toBeDefined` assertion,
// so the `row.when` read that follows such an assertion stays a type error. Resolving every lookup
// through this helper keeps each row's own failure message and makes the dropped row the thing that
// fails, rather than the property read after it.
function resolved<T>(rows: T[], matches: (row: T) => boolean, message: string): T {
const row = rows.find(matches)
if (!row) {
throw new Error(message)
}
return row
}

const TAB_PANEL_ID = "zoo-code.TabPanelProvider"
const SIDEBAR_VIEW_CLAUSE = `view == zoo-code.SidebarProvider`
const TAB_ACTIVE_CLAUSE = `activeWebviewPanelId == ${TAB_PANEL_ID}`
const TAB_INACTIVE_CLAUSE = `activeWebviewPanelId != ${TAB_PANEL_ID}`

const declared = new Set(commands.map((c) => c.command))
const titleBarCommands = [...new Set([...menus["editor/title"], ...menus["view/title"]].map((r) => r.command))]
const tabTitleCommands = titleBarCommands.filter((id) => id.endsWith("InTab"))
const sidebarTitleCommands = titleBarCommands.filter((id) => !id.endsWith("InTab"))

// popoutButtonClicked is a view/title-only affordance: it has no commandPalette row.
const sidebarTaskCommands = sidebarTitleCommands.filter((id) => !id.endsWith("popoutButtonClicked"))
describe("contributes manifest for the title-bar commands", () => {
it("declares every command the menus reference", () => {
for (const id of titleBarCommands) {
expect(declared, `${id} is used in a menu but not declared in contributes.commands`).toContain(id)
}
})

it.each(tabTitleCommands)("%s has an editor/title row gated on the tab panel", (id) => {
const row = resolved(menus["editor/title"], (r) => r.command === id, `${id} must appear in editor/title`)
expect(row.when, `${id} must be gated on the active tab panel`).toBe(TAB_ACTIVE_CLAUSE)
})

it.each(tabTitleCommands)("%s has a commandPalette row gated on the tab panel", (id) => {
const row = resolved(menus.commandPalette, (r) => r.command === id, `${id} must appear in commandPalette`)
expect(row.when, `${id} must be gated on the active tab panel`).toBe(TAB_ACTIVE_CLAUSE)
})

it.each(sidebarTitleCommands)("%s has a view/title row gated on the sidebar view", (id) => {
const row = resolved(menus["view/title"], (r) => r.command === id, `${id} must appear in view/title`)
expect(row.when, `${id} must be gated on the sidebar view`).toBe(SIDEBAR_VIEW_CLAUSE)
})

// popoutButtonClicked is a view/title-only row: it has no commandPalette entry, and the four
// sidebar task commands each have one hidden while the tab panel is the active webview.
it.each(sidebarTaskCommands)("%s has a commandPalette row hidden while the tab panel is active", (id) => {
const row = resolved(menus.commandPalette, (r) => r.command === id, `${id} must appear in commandPalette`)
expect(row.when, `${id} must be hidden while the tab panel is the active webview`).toBe(TAB_INACTIVE_CLAUSE)
})
it("keeps popoutButtonClicked out of the command palette", () => {
// It is a title-bar affordance only: giving it a palette row would expose a command that
// needs an active webview to mean anything.
expect(
menus.commandPalette.map((r) => r.command),
"popout belongs in view/title only",
).not.toContain("zoo-code.popoutButtonClicked")
})

it("surfaces the failure message when a menu references an undeclared id", () => {
// The per-id rows above carry a failure message built from the id. This row proves the message
// reaches the reporter: without it a renamed command id would fail with an empty diff.
const id = "zoo-code.notDeclaredInCommands"
const message = id + " is used in a menu but not declared in contributes.commands"
expect(() => {
expect(id, message).toBe("zoo-code.declaredElsewhere")
}, message).toThrowError(message)
})
it("names the title-bar commands with i18n placeholders, not literals", () => {
for (const id of titleBarCommands) {
const entry = resolved(commands, (c) => c.command === id, `${id} must be declared`)
expect(entry.title, `${id} must carry a %placeholder title`).toMatch(/^%/)
}
})
})
Loading
Loading