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
main.ts2 KB
// The detail pane: tabs open through the convergence/tabs service, and, for
// third-party plugins, through the legacy shared openTab/closeTab requests.
// Views and subscriptions register synchronously at the start; the service
// is provided after them. The plugin scope owns request listeners and file
// reloads.
import * as Effect from "effect/Effect";
import * as Stream from "effect/Stream";
import { App, Events, Services, Views, runPlugin } from "convergence/effect";
import { Tabs } from "./controller.ts";
import { Tabs as TabsService } from "./contract.ts";
import { runFinished } from "./tabs.ts";
import { renderDetail } from "./views.ts";
const program = Effect.gen(function* () {
const tabs = yield* Tabs;
const views = yield* Views;
tabs.views.push(yield* views.slot("detail", (ctx) => renderDetail(ctx, tabs), { on: ["app_state"] }));
const app = yield* App;
const events = yield* Events;
yield* app.changes.pipe(
Stream.runForEach((next) => Effect.forkScoped(tabs.followApp(next), { startImmediately: true })),
Effect.forkScoped({ startImmediately: true }),
);
yield* events.stream("agent").pipe(
Stream.runForEach((event) => {
const tab = tabs.state.tabs.find((tab) => tab.id === tabs.state.active);
return Effect.forkScoped(runFinished(event.kind) && tab?.kind === "file" ? tabs.open(tab) : Effect.void, {
startImmediately: true,
});
}),
Effect.forkScoped({ startImmediately: true }),
);
yield* tabs.followApp(yield* app.state);
// The service goes last: everything above registers synchronously, and a
// provider that arrives later only adds calls, never views.
const services = yield* Services;
const handle = yield* services.provide(TabsService, {
open: (params, _ctx) => tabs.open(params).pipe(Effect.map((tab) => ({ id: tab.id }))),
close: (params, _ctx) => tabs.closeByRef(params),
active: (_params, _ctx) => tabs.activeRef(),
});
tabs.emitActive((tab) => handle.emit("activeChanged", tab));
});
export const activate = runPlugin(Tabs.layer, program);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.