VS Code Web extension that lets users open Veric workspaces inside their
existing VS Code, Cursor, or VS Code Web (vscode.dev / github.dev)
install. Dual-published to the Microsoft Marketplace
(veric.veric-extension) and Open VSX (veric/veric-extension).
Design doc: docs/multi-surface-uplift/surface-3-extension.md.
Targeting
This extension is web-targeted — the manifest sets browser (not
main), the esbuild config sets platform: "browser", and the source
must not import any Node-only API (fs, child_process,
path.resolve, etc.). A web-targeted extension is the only form that
runs in all three of vscode.dev, desktop VS Code, and Cursor (see
design doc §2).
Scope (0.3.0)
Phases 3.1 – 3.8 are now live:
- Commands
veric.hello,veric.run,veric.showResult,veric.showTransportLog,veric.importFile,veric.exportFile,veric.lsp.showCFG,veric.lsp.explainStep,veric.properties.openEditor. - Property editor sidebar (Phase C, 2026-05-20). New
veric.propertiesactivity-bar container hosting theveric.properties.editorwebview view. Authored properties persist as*.veric.jsonsidecars next to the source file (src/foo.ts↔src/foo.veric.json). Each row gets a "Run" button that firesveric.runwith{ sourceUri, propertyId }; verdicts surface on the Result panel. VericFsProvideragainst the in-memory bridge shim (src/fs/bridge-shim.ts). A real remote-VFS swap-in is queued for Phase 4.8.vscode-languageclient/browserlanguage client for.whilefiles, configured via theveric.lspUrlsetting.vscode.AuthenticationProvider(idveric) + OAuth URI handler.- Result webview panel mounting
<ResultCard/>from@veric/views-reactover a WebView HostBridge transport. - Direct kernel transport (0.3.0).
veric.runresolves a kernel bridge from the cross-realmSymbol.for("veric.ide.kernel-bridge")first (so the embedded/ideroute stays a thin client); when no bridge is published, the extension self-constructs one against theveric.kernelUrlendpoint (defaultwss://kernel.veric.dev/rpc). Bearer auth is plumbed via theSec-WebSocket-Protocolsubprotocol carrier. - Desktop local-kernel spawn (P8 / Track C). In the desktop (Node)
extension host — VS Code, Cursor; not vscode.dev / github.dev — the
mainentry (dist/extension.node.js) spawns the realveric-server-rpckernel on loopback before activation, then points the self-constructed bridge at it. See the launch contract below. The web entry (dist/extension.js,browser) can't spawn a process and keeps usingveric.kernelUrl(hosted default, or the opt-in"mock"echo mode), unchanged.
Desktop local-kernel launch contract (P7)
The desktop entry consumes the fixed veric-server-rpc spawn contract
(owned by ~/dev/veric-server; not built in this repo). Exact
invocation the extension uses:
- Binary discovery.
veric.kernelBinaryPath(absolute path) if set; otherwise the bare nameveric-server-rpcresolved fromPATH. Build it withcargo build -p veric-server-rpcin~/dev/veric-server. - Spawn.
veric-server-rpc --port <free-loopback-port> --host 127.0.0.1 --log-level info --provider mock. The port is an OS-allocated free loopback port (bind:0, read it back);--host 127.0.0.1keeps the kernel loopback-only (never public);--provider mockneeds no Claude credential or network for WHILE / Solidity analysis. - Endpoints. JSON-RPC over WS + HTTP at
/rpc; health at/health. The launcher GETshttp://127.0.0.1:<port>/healthuntil it answers 2xx (15s budget), then connects theKernelRpcTransporttows://127.0.0.1:<port>/rpc(Nodewsconstructor injected). - Shutdown. Graceful
SIGTERMon extension deactivation,SIGKILLafter a 2s grace window. - Loud failure (integrity rule, mirrors Track A's embed). Missing
binary, spawn error, premature exit, or health-probe timeout each
raise a visible error notification ("local kernel failed to
start — no verification is available") with a Show Log action, and
install no bridge override — so the run path shows no verdict.
The extension never fabricates a passing result to paper over a dead
kernel. (Implementation:
src/run/local-kernel-launcher.ts,src/extension.node.ts.)
Step-through debugger
The extension registers a VS Code debug adapter (veric debug type)
backed by an in-process inline adapter — there is no separate
veric-dap-server process. The session (VericDebugSession) drives the
kernel's solver trace over the same RPC transport the run path uses, so
stepping is a replay of an already-computed trace, not a live
execution. That replay model is the reason several DAP capabilities are
degraded or unsupported, and the limits are surfaced here so the UI
behaviour isn't surprising:
stepOutdegrades to a single forward step. The solver trace is flat — there is no call stack to step out of — so a "step out" request advances one trace step forward, the same asstepOver/stepInto. (veric-debug-session.tsstepOutRequest.)stepBackis supported. Because the whole trace is materialised up front, stepping backwards is just moving the cursor — the adapter advertisessupportsStepBack: trueand reverse-continue works.- Restart / Terminate / EvaluateForHovers are supported.
supportsRestartRequest,supportsTerminateRequest, andsupportsEvaluateForHoversare all advertisedtrue. setVariable, conditional breakpoints, and function breakpoints are not supported. The trace is a read-only replay: abstract values aren't assignable (supportsSetVariable: false), and the adapter has no evaluator to gate a breakpoint on a condition or bind one to a function symbol (supportsConditionalBreakpoints: false,supportsFunctionBreakpoints: false). Plain line breakpoints work.
Settings
Every contributed veric.* setting is listed here in one place; this
table is the human-readable mirror of apps/extension/package.json
contributes.configuration. A CI gate (pnpm settings-doc-check →
scripts/check-settings-doc.mjs) fails any PR that adds a setting to
the manifest without documenting it here, and vice-versa.
| Setting | Type | Default | Description | Read by |
|---|---|---|---|---|
veric.lspUrl |
string |
wss://kernel.veric.dev/rpc |
WebSocket URL for the WHILE language server. Default targets the hosted kernel; set to ws://localhost:7037/rpc against a local veric-server-rpc. Speaks the channel-envelope contract (channel/send + channel/subscribe over channel while.lsp). An empty string disables the language client. |
src/lsp/client.ts → resolveLspUrl(); src/lsp/status-bar.ts renders the resulting state in the bar. |
veric.kernelUrl |
string |
wss://kernel.veric.dev/rpc |
WebSocket URL for the Veric kernel RPC endpoint. Default targets the hosted production kernel. In the desktop (Node) host this default triggers a local spawn of veric-server-rpc on loopback (see the launch contract above); set any other URL (e.g. ws://localhost:7037/rpc against a manually-run kernel, or mock for the web echo mode) to opt out of the local spawn. |
src/run/kernel-bridge-self.ts, src/extension.node.ts (desktop spawn), and src/run/run-command.ts (transport-log diagnostic). |
veric.kernelBinaryPath |
string |
"" |
Desktop only: absolute path to the veric-server-rpc kernel binary the extension spawns on loopback. Empty resolves veric-server-rpc from PATH. Built with cargo build -p veric-server-rpc in ~/dev/veric-server. |
src/extension.node.ts → src/run/local-kernel-launcher.ts. |
Scripts
| Script | What it does |
|---|---|
pnpm typecheck |
tsc --noEmit against the strict tsconfig (ES2022, Bundler, noUncheckedIndexedAccess, exactOptionalPropertyTypes). |
pnpm build |
esbuild bundles src/extension.ts → dist/extension.js (browser CJS, vscode external). Also emits dist/lsp-worker.js and dist/result-webview.js. |
pnpm test |
vitest — unit tests with a stubbed vscode module (tests/setup.ts). 57 specs at last count. |
pnpm test:electron |
Boots a real desktop VS Code via @vscode/test-electron (web extension host, --extensionDevelopmentKind=web), installs this extension as a dev extension, and runs the Mocha smoke suite at test/host/suite/smoke.test.ts against the live vscode API. Downloads VS Code stable into .vscode-test/ on first run (~200 MB). |
pnpm test:web |
Same suite, but against VS Code Web via @vscode/test-web + headless Chromium — proves activation on the vscode.dev / Cursor target. Downloads VS Code Insider Web into .vscode-test-web/ on first run (~45 MB). |
Two test layers
The extension has two complementary test layers:
- vitest unit tests under
tests/mock thevscodemodule viatests/setup.ts. They run on plain Node, cover every provider / command handler in detail, and are the workhorse for TDD iteration. - Mocha real-host smoke under
test/host/runs inside a real VS Code spawned by@vscode/test-electronor@vscode/test-web. It asserts that the manifest + the bundleddist/extension.jsactually register the contributed commands / FS provider / auth provider against the genuine host. The CI publish workflow gates onpnpm test(the unit layer); the host suite is run locally and in nightly CI to catch manifest / bundler regressions that the mock layer can't see.
Release process (Phase 3.6)
Dual-publish is automated by .github/workflows/extension-publish.yml.
The workflow fires on tags matching extension-v* (e.g.
extension-v0.2.1) and on manual workflow_dispatch runs (dispatch
defaults to dry_run=true — it builds + packages but skips the actual
Marketplace / Open VSX uploads).
To cut a release:
- Bump
versioninapps/extension/package.jsonto the new semver. - Open a PR with just the bump, get it merged to
main. - From a clean checkout of
main:git pull VERSION=$(node -p "require('./apps/extension/package.json').version") git tag "extension-v${VERSION}" git push origin "extension-v${VERSION}" - Watch the workflow under Actions →
extension-publish. On green, the new version is live on the Microsoft Marketplace (veric.veric-extension) and Open VSX (veric/veric-extension) within a few minutes.
Required repo secrets (one-time setup)
Before the first release, configure the following GitHub repo secrets in Settings → Secrets and variables → Actions:
| Secret | Purpose | Where to get it |
|---|---|---|
VSCE_PAT |
Microsoft Marketplace publish token | Azure DevOps PAT under the veric Marketplace publisher, scope Marketplace · Manage. See VS Code docs. |
OVSX_PAT |
Open VSX publish token | Generated at https://open-vsx.org/user-settings/tokens for an account that owns the veric namespace. See Open VSX docs. |
The workflow has no fallback — if either secret is unset when a tag lands, the corresponding publish step fails loudly. That's intentional: missing secrets must not silently skip publishing.