feat: add a dedicated Runa sidebar

Move the image source tree and actor view into a Runa Activity Bar container with sources first. Verify both views and image editing in an empty VS Code window without workspace folders.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Steve Biedermann 2026-11-03 17:49:04 +01:00
parent f1a2aab789
commit 7e3af6e501
8 changed files with 53 additions and 26 deletions

View File

@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file.
### Changed ### Changed
- Move image sources and actors into a dedicated Runa Activity Bar sidebar,
with the source file tree first and both views visible by default.
- Remove the VS Code Add Source Folder to Workspace command; image files are - Remove the VS Code Add Source Folder to Workspace command; image files are
accessed exclusively through the automatic image-source Explorer view. accessed exclusively through the automatic image-source Explorer view.
- Show the image's editable virtual `source` filesystem automatically in the - Show the image's editable virtual `source` filesystem automatically in the

View File

@ -168,12 +168,13 @@ the runtime contract.
With Node.js 22+ and npm installed, run `npm --prefix vscode ci`. In VS Code, With Node.js 22+ and npm installed, run `npm --prefix vscode ci`. In VS Code,
open this repository and launch **Run Runa Extension** from Run and Debug open this repository and launch **Run Runa Extension** from Run and Debug
(F5). Start `mise run start`, then use **Runa: Connect to Local Image** (F5). Start `mise run start`, then use **Runa: Connect to Local Image**
in the Extension Development Host. The **Runa Actors** view in Explorer lists in the Extension Development Host. Open the **Runa** Activity Bar sidebar.
Its **Actors** view lists
actors created through the REPL or another client. Click an actor to inspect actors created through the REPL or another client. Click an actor to inspect
it. Server notifications automatically refresh the tree and open snapshots; it. Server notifications automatically refresh the tree and open snapshots;
**Runa: Refresh Image** is also available on demand. **Runa: Refresh Image** is also available on demand.
The **Runa Image Sources** Explorer view automatically shows the image's virtual The **Image Sources** view in the Runa sidebar automatically shows the image's virtual
filesystem beneath **source**, including `actors/counter.rn`, without adding a filesystem beneath **source**, including `actors/counter.rn`, without adding a
workspace folder. Open a file and save edits to update the image without workspace folder. Open a file and save edits to update the image without
committing. Right-click a source file to **Spawn** (null state) or **Spawn with committing. Right-click a source file to **Spawn** (null state) or **Spawn with

View File

@ -141,7 +141,7 @@ The stdio transport and child-process client have been removed.
The TypeScript VS Code client issues `list_actors`, `inspect_actor`, The TypeScript VS Code client issues `list_actors`, `inspect_actor`,
source file/version-control operations, `spawn`, `send`, `tick`, and `system`. source file/version-control operations, `spawn`, `send`, `tick`, and `system`.
VS Code automatically displays writable image files under a `source` directory VS Code automatically displays writable image files under a `source` directory
in its image-source Explorer tree, without modifying workspace folders, in its dedicated Runa sidebar's source tree, without modifying workspace folders,
and caches read-only commit documents by connection, path, and revision. and caches read-only commit documents by connection, path, and revision.
It preserves i64 inputs and bounds the complete outgoing envelope as well as It preserves i64 inputs and bounds the complete outgoing envelope as well as
incoming messages. It enforces loopback endpoints, bounded UTF-8 text messages, incoming messages. It enforces loopback endpoints, bounded UTF-8 text messages,

View File

@ -24,11 +24,11 @@ In that host:
1. Run **Runa: Connect to Local Image**. The default is `127.0.0.1:3737`; 1. Run **Runa: Connect to Local Image**. The default is `127.0.0.1:3737`;
`runa.address` changes the suggested endpoint. `runa.address` changes the suggested endpoint.
2. The **Runa Image Sources** Explorer view automatically shows the image's 2. Open **Runa** in the Activity Bar. Its **Image Sources** view automatically shows the image's
`source` directory (see [Image source files](#image-source-files)). `source` directory (see [Image source files](#image-source-files)).
Right-click a `.rn` file and choose **Spawn** (initial state `null`) or Right-click a `.rn` file and choose **Spawn** (initial state `null`) or
**Spawn with State...** (enter JSON in the input field, for example `0` for **Spawn with State...** (enter JSON in the input field, for example `0` for
`actors/counter`). **Runa: Spawn Actor** in **Runa Actors** prompts for a source `actors/counter`). **Runa: Spawn Actor** in **Actors** prompts for a source
path and state. The command reports and returns the new actor reference. path and state. The command reports and returns the new actor reference.
Mailbox capacity is 64. Mailbox capacity is 64.
3. Click an actor or run **Runa: Inspect Actor** to open its JSON snapshot. 3. Click an actor or run **Runa: Inspect Actor** to open its JSON snapshot.
@ -98,13 +98,18 @@ in the Runa output channel and an error notification; commands are never retried
## Image source files ## Image source files
The image owns actor source text. On connect, **Runa Image Sources** automatically The dedicated **Runa** Activity Bar sidebar contains **Image Sources** first,
followed by **Actors**. Both views are visible by default and do not require
an open workspace. The source tree uses native VS Code tree items, file icons,
directory expansion, editor opening, and file context menus.
The image owns actor source text. On connect, **Image Sources** automatically
shows its virtual filesystem beneath `source` (`runa-image://<endpoint>/source`). shows its virtual filesystem beneath `source` (`runa-image://<endpoint>/source`).
No workspace changes, folder prompt, or extension restart is required. No workspace changes, folder prompt, or extension restart is required.
Opening a file reads it from the image; saving writes back to the image. Opening a file reads it from the image; saving writes back to the image.
The extension never adds image folders to the workspace. VS Code's public API The extension never adds image folders to the workspace. VS Code's public API
does not support overlaying virtual files onto the built-in workspace file tree, does not support overlaying virtual files onto the built-in workspace file tree,
so image files appear in their own Explorer view backed by the filesystem provider. so image files appear in the Runa sidebar's source tree backed by the filesystem provider.
Directories are derived from Directories are derived from
source paths: `actors/counter` appears as `source/actors/counter.rn`. Source source paths: `actors/counter` appears as `source/actors/counter.rn`. Source
paths are canonical and exclude the `source/` prefix and `.rn` suffix. paths are canonical and exclude the `source/` prefix and `.rn` suffix.

View File

@ -7,6 +7,22 @@ export async function run(): Promise<void> {
const image = await startImage(); const image = await startImage();
const folders = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.toString()); const folders = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.toString());
try { try {
const extension = vscode.extensions.all.find(
(candidate) => candidate.packageJSON.name === "runa",
);
assert.ok(extension);
const contributions = extension.packageJSON.contributes;
assert.ok(
contributions.viewsContainers.activitybar.some(
(container: { id: string }) => container.id === "runa",
),
);
assert.deepEqual(
contributions.views.runa.map((view: { id: string }) => view.id),
["runa.sources", "runa.actors"],
);
assert.equal(contributions.views.explorer, undefined);
await vscode.commands.executeCommand("workbench.view.extension.runa");
await vscode.commands.executeCommand("runa.connect", image.address); await vscode.commands.executeCommand("runa.connect", image.address);
assert.ok(!(await vscode.commands.getCommands(true)).includes("runa.addSourceFolder")); assert.ok(!(await vscode.commands.getCommands(true)).includes("runa.addSourceFolder"));
assert.deepEqual( assert.deepEqual(
@ -308,7 +324,7 @@ async function editSources(address: string): Promise<void> {
await vscode.commands.executeCommand("runa.connect", address); await vscode.commands.executeCommand("runa.connect", address);
assert.equal( assert.equal(
vscode.workspace.workspaceFolders?.filter((candidate) => candidate.uri.scheme === "runa-image") vscode.workspace.workspaceFolders?.filter((candidate) => candidate.uri.scheme === "runa-image")
.length, .length ?? 0,
0, 0,
); );
const reopened = await vscode.commands.executeCommand<string>( const reopened = await vscode.commands.executeCommand<string>(

View File

@ -153,15 +153,26 @@
"icon": "$(inspect)" "icon": "$(inspect)"
} }
], ],
"views": { "viewsContainers": {
"explorer": [ "activitybar": [
{ {
"id": "runa.actors", "id": "runa",
"name": "Runa Actors" "title": "Runa",
"icon": "resources/runa.svg"
}
]
}, },
"views": {
"runa": [
{ {
"id": "runa.sources", "id": "runa.sources",
"name": "Runa Image Sources" "name": "Image Sources",
"visibility": "visible"
},
{
"id": "runa.actors",
"name": "Actors",
"visibility": "visible"
} }
] ]
}, },

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#C5C5C5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M7 21V3l10 6-10 5 10 7"/>
</svg>

After

Width:  |  Height:  |  Size: 223 B

View File

@ -1,23 +1,12 @@
const path = require("node:path"); const path = require("node:path");
const fs = require("node:fs");
const { runTests } = require("@vscode/test-electron"); const { runTests } = require("@vscode/test-electron");
// Adding the image source folder to an empty or single-folder window restarts the
// extension host, so tests start in a multi-root workspace with one local folder.
const root = path.resolve(__dirname, "..");
const workspaceFile = path.join(root, ".vscode-test", "runa-host.code-workspace");
fs.mkdirSync(path.dirname(workspaceFile), { recursive: true });
fs.writeFileSync(
workspaceFile,
JSON.stringify({ folders: [{ path: path.join(root, "integration") }] }, null, 2),
);
runTests({ runTests({
version: "1.96.4", version: "1.96.4",
extensionDevelopmentPath: path.resolve(__dirname, ".."), extensionDevelopmentPath: path.resolve(__dirname, ".."),
extensionTestsPath: path.resolve(__dirname, "../out/integration/host.js"), extensionTestsPath: path.resolve(__dirname, "../out/integration/host.js"),
launchArgs: [ launchArgs: [
workspaceFile, "--new-window",
"--disable-gpu", "--disable-gpu",
"--no-sandbox", "--no-sandbox",
"--skip-welcome", "--skip-welcome",