30. Node and TypeScript, scoped to the VS Code client only
- Status: Accepted
- Date: 2026-09-05
Context
editors/vscode/ has existed since before this milestone as a grammar-only
extension: a package.json manifest, a TextMate grammar, and a language
configuration file — no build step, no scripts, no devDependencies, no
CI coverage. Every other part of this repository, including the website
(korrin-web/, hand-written HTML/CSS plus mdbook, a Rust tool) and the docs
site build, is Rust or plain static files. CI is, and through three milestones
has been, entirely cargo commands.
A real VS Code extension client that starts korrin-lsp and shows what it
sends — live diagnostics, hover, go-to-definition — is written against the
vscode extension API, which is JavaScript/TypeScript only; there is no
alternative. This is not a preference to weigh against a Rust option, the way
ADR 0026 weighed lsp-server against tower-lsp — VS Code extensions cannot
be written in Rust today, so Node and TypeScript enter this repository's
tooling for the first time out of genuine necessity, not convenience.
Decision
Node and TypeScript are scoped to editors/vscode/ only, with its own
package.json, package-lock.json, and build step, kept as separate and
self-contained from the Rust workspace as korrin-web/'s mdbook build
already is from it.
- TypeScript, strict mode (
strict: trueplusnoUnusedLocals,noUnusedParameters,noImplicitReturns,noFallthroughCasesInSwitch), compiled withesbuildrather thantscfor the actual output —esbuildbundlesvscode-languageclientand its own dependencies into a singleout/extension.js, so the packaged extension has nonode_modulesto ship and no runtime dependency resolution to fail.tsc --noEmitstill runs, in CI and by hand, purely for type-checking. vscode-languageclientis the standard client library for exactly this: starting a language server as a subprocess and speaking the LSP over its stdio, translating betweenvscode's own API types and the protocol's wire types. Writing that translation layer by hand would be reimplementing a solved, boring problem for no benefit.- A dedicated CI job (
vscode-extension, usingactions/setup-node+npm ci+tsc --noEmit+ theesbuildcompile), separate from every Rust job. None of the existingtest/lint/msrv/docsjobs touch this directory or would ever catch a TypeScript error; a broken extension client should fail its own check, not slip through unnoticed because nothing looked. - No dependency here is added to the Rust workspace's dependency policy
(ADR 0013) or
docs/internals/dependencies.md— that policy governs Cargo dependencies of Rust crates, andnpm's dependency tree is a genuinely separate concern with its own separate ecosystem norms. It is not silently exempt from scrutiny; it is scoped to one small, isolated corner of the repository and reviewable there on its own terms.
Consequences
- Building or contributing to
crates/korrin,crates/korrin-cli, orcrates/korrin-lspneeds nothing butcargo— Node is only required for someone touching the VS Code client specifically, the same waymdbookis only required for someone touching the docs site. - The repository now has two package managers with two different sets of
norms (Cargo's committed
Cargo.lockfor a workspace that ships binaries, npm's committedpackage-lock.jsonfor reproducible CI installs) rather than one. A real, ongoing cost, accepted because a VS Code extension has no Rust-only path around it. - The packaged
.vsixdoes not bundle a prebuiltkorrin-lspbinary for any platform (per ADR 0026's Milestone 4 scope) — a user installs the language server themselves viacargo install. Multi-platform binary packaging is real, separate work for a later milestone if this project ever wants a from-the-marketplace "it just works" install experience.
Alternatives considered
- A webview-based or LSP-free extension avoiding TypeScript entirely.
Not viable:
textDocument/publishDiagnostics, hover, and go-to-definition are integrated into VS Code's own editor chrome (the Problems panel, the hover popup,F12) through the extension API, which has no non-JS/TS surface. There is no version of "show live diagnostics in the editor" that avoids this. - Hand-rolled JSON-RPC in the client, skipping
vscode-languageclient. Symmetric tokorrin-lspnot hand-rolling its own transport (ADR 0026) — the framing, cancellation, and API-translation work is exactly the kind of solved problem worth depending on a library for. - Fold the Node/TypeScript check into an existing CI job rather than a
new one. Rejected: none of the existing jobs' names, caching strategy, or
failure semantics have anything to do with this, and a Rust contributor
reading a red
testjob should not have to wonder whether their Rust change broke something or a TypeScript file did.