Official
tabs
The tabbed detail pane beside the chat: opened files and diffs (JavaScript UI plugin).
The app opens the listing; nothing installs until an agent in your Plugins workspace has read the files and you enable the plugin. In a terminal: cvg install convergence/tabs@0.2.0
Permissions in 0.2.0
Files
tabs.test.ts4.8 KB
// Tab identity and content regressions, using the public pure functions.
import assert from "node:assert/strict";
import { test } from "node:test";
import {
activeTab,
closeTab,
extension,
fileContent,
fileResponse,
initialState,
LANGUAGES,
openRequest,
openTab,
runFinished,
readContent,
tabId,
tabName,
} from "./tabs.ts";
import type { TabInput } from "./tabs.ts";
const file: TabInput = { kind: "file", workspaceId: "w1", path: "src/README.md" };
test("tab ids distinguish kind, workspace and path", () => {
assert.equal(tabId(file), "file:w1:src/README.md");
assert.equal(
new Set(
[file, { ...file, kind: "diff" }, { ...file, workspaceId: "w2" }, { ...file, path: "README.md" }].map((input) =>
tabId(openRequest.omit({ nonce: true }).parse(input)),
),
).size,
4,
);
});
test("opening an existing tab keeps its position and preview, while view metadata updates", () => {
const state = initialState();
const first = openTab(state, file);
first.preview = true;
openTab(state, { kind: "terminal", workspaceId: "w1", path: "t1" });
assert.equal(openTab(state, file), first);
assert.equal(first.preview, true);
assert.equal(state.tabs.length, 2);
const view = openTab(state, { kind: "view", workspaceId: "w1", path: "v1", title: "Old", icon: "file", view: 1 });
assert.equal(openTab(state, { kind: "view", workspaceId: "w1", path: "v1", title: "New", view: 2 }), view);
assert.equal(view.title, "New");
assert.equal(view.view, 2);
assert.equal(view.icon, undefined);
});
test("diff contents replace in place and default to the empty diff", () => {
const state = initialState();
const request: TabInput = { ...file, kind: "diff", diff: "first" };
const tab = openTab(state, request);
assert.deepEqual(state.contents[tab.id], { diff: "first" });
openTab(state, { ...request, diff: "second" });
assert.deepEqual(state.contents[tab.id], { diff: "second" });
openTab(state, { ...request, diff: undefined });
assert.deepEqual(state.contents[tab.id], { diff: "" });
});
test("closing selects the next tab, then the previous last tab, and clears content", () => {
const state = initialState();
const tabs = ["a", "b", "c"].map((path) => openTab(state, { ...file, path }));
state.active = tabs[1].id;
state.contents[tabs[1].id] = { text: "old" };
assert.equal(closeTab(state, tabs[1].id), tabs[2]);
assert.equal(state.contents[tabs[1].id], undefined);
assert.equal(closeTab(state, tabs[2].id), tabs[0]);
assert.equal(closeTab(state, tabs[0].id), undefined);
assert.equal(state.active, null);
});
test("closing an inactive or missing tab preserves selection; fallback uses the first tab", () => {
const state = initialState();
const first = openTab(state, file);
const second = openTab(state, { ...file, path: "next" });
closeTab(state, first.id);
closeTab(state, "missing");
assert.equal(state.active, second.id);
state.active = "missing";
assert.equal(activeTab(state), second);
});
test("file truncation starts only above two million UTF-16 code units", () => {
assert.deepEqual(fileContent(""), { text: "" });
const boundary = "a".repeat(2_000_000);
assert.equal(fileContent(boundary).text, boundary);
assert.equal(fileContent(boundary + "extra").text, boundary + "\n… file truncated");
});
test("missing files preserve the runtime error while file responses stay typed", () => {
assert.deepEqual(readContent(fileResponse.parse(null)), { error: "cannot read property 'text' of null" });
assert.deepEqual(readContent(fileResponse.parse({ text: "" })), { text: "" });
assert.deepEqual(readContent(fileResponse.parse({ text: "contents" })), { text: "contents" });
assert.equal(fileResponse.safeParse({ text: 1 }).success, false);
});
test("tab labels, markdown extension and language aliases preserve defaults", () => {
const state = initialState();
assert.equal(tabName(openTab(state, file)), "README.md");
assert.equal(tabName(openTab(state, { ...file, kind: "terminal" })), "Terminal");
assert.equal(tabName(openTab(state, { ...file, kind: "view" })), "View");
assert.equal(tabName(openTab(state, { ...file, kind: "terminal", path: "empty-title", title: "" })), "");
assert.equal(extension("README.MD"), "md");
assert.equal(extension("file"), "file");
assert.equal(LANGUAGES.mts, "typescript");
assert.equal(LANGUAGES.tsx, "tsx");
assert.equal(LANGUAGES.markdown, "markdown");
});
test("legacy requests validate and only finished runs match the event selector", () => {
assert.equal(openRequest.safeParse({ ...file, nonce: 1 }).success, true);
assert.equal(openRequest.safeParse({ ...file, nonce: "1" }).success, false);
assert.equal(runFinished({ event: "run_finished", outcome: {} }), true);
for (const kind of [undefined, null, {}, { event: "text_delta" }]) assert.equal(runFinished(kind), false);
});Versions
| Version | Published | Plugin API | Size | Permissions | Status |
|---|---|---|---|---|---|
| 0.2.0latest | Oct 5, 2026 | >=2 <3 | 18.8 KB | 3 permissions | Listed |
No comments yet.