Official
files
Built-in files UI (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/files@0.2.0
Permissions in 0.2.0
Files
views.ts2.8 KB
// Files rail and panel trees. Rendering only reads state; handlers run effects.
// HTM whitespace is significant; keep these templates out of Prettier's HTML formatter.
import { html as htm } from "convergence";
import type { EffectViewContext } from "convergence/effect";
import type { Files } from "./controller.ts";
import { isOpen, selectedPath, visiblePaths } from "./panel.ts";
// Layout for the templates below: a flex column, a row that centres its
// children across (the old `ui.row`), and a child that takes the free
// space and may shrink below its content.
const column = { display: "flex", flexDirection: "column" };
const row = { display: "flex", flexDirection: "row", alignItems: "center" };
const fill = { flexGrow: 1, flexShrink: 1, flexBasis: "0%", minWidth: 0, minHeight: 0 };
const full = { width: "100%", height: "100%" };
export function renderRail(ctx: EffectViewContext<object>, actions: Files["Service"]) {
return htm`
<button id="rail-files" icon="files" variant="ghost" active=${ctx.app.shared?.rightPanel === "files"} tooltip="Files"
onClick=${() => ctx.run(actions.toggle(ctx.app))} />`;
}
export function renderPanel(ctx: EffectViewContext<object>, actions: Files["Service"]) {
const { state } = actions;
if (!isOpen(ctx.app)) return null;
const workspaceId = ctx.app.workspaceId;
if (!workspaceId) {
return htm`<div ...${column} gap="small" padding="medium"><text style="muted">Select a workspace to browse files.</text></div>`;
}
const paths = visiblePaths(state.paths, state.query);
return htm`
<div ...${column} ...${fill} ...${full} padding="small" paddingX="small">
<div ...${row} gap="small" padding="small" paddingY="small">
<text style="body" size=${13.125} weight=${600}>Files</text>
<spacer />
<button id="files-refresh" icon="rotate-cw" variant="ghost" small tooltip="Refresh" onClick=${() => ctx.run(actions.refresh(workspaceId))} />
<button id="files-close" icon="x" variant="ghost" small tooltip="Close" onClick=${() => ctx.run(actions.close)} />
</div>
<input id="files-search" value=${state.query} placeholder="Search files..."
onChange=${(e: { value: string }) => {
state.query = e.value;
ctx.update();
}} />
${!state.loaded ? htm`<div ...${column} gap="small" padding="medium"><text style="muted">Loading files…</text></div>` : null}
<div ...${column} ...${fill} gap="small" padding="small" paddingY="small">
<tree id=${`files-${workspaceId}`} files=${paths.map((path) => ({ path }))} selected=${selectedPath(state.activePath)}
onSelect=${(e: { id: string; folder: boolean }) => {
if (!e.folder) ctx.run(actions.openFile(workspaceId, e.id));
}} />
</div>
</div>`;
}Versions
| Version | Published | Plugin API | Size | Permissions | Status |
|---|---|---|---|---|---|
| 0.2.0latest | Oct 5, 2026 | >=2 <3 | 6.4 KB | 3 permissions | Listed |
No comments yet.