VS Code and editors
tcm's VS Code extension shows, as you type in a scratch file, what each definition is to the codebase, with Add and Update buttons, watch values and test results inline. It also shows the codebase itself: definitions as read-only documents, pages, search, and sidebars for apps, history, data, packages and releases.
Underneath is tcm lsp, a language server that runs alongside TypeScript's own: TypeScript keeps doing
completions, type errors, go-to-definition and refactors, and tcm adds what only it knows. Any editor that speaks LSP
can run it.
VS Code
Needs VS Code 1.143 or later. The extension (tcm-ts-vscode) is packed from tcm's repository. Build a .vsix from
what that writes (the first npx downloads vsce, which takes a while), and install it with Extensions → … →
Install from VSIX… or VS Code's CLI (on macOS it's inside the app if code isn't on your PATH):
pnpm tcm pack tcm-ts-vscode --out dist/vscode
cd dist/vscode && npm install && npx @vscode/vsce package
"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" --install-extension tcm-ts-*.vsix
Reload the window after installing. The .vsix takes its version from the extension's latest release
(tcm release tcm-ts-vscode); unreleased, it's 0.0.0.
- Don't symlink the folder into
~/.vscode/extensions: VS Code only keeps extensions in its manifest, and marks unlisted folders as removed. - In an untrusted workspace (Restricted Mode), the extension stays off until you trust it.
- Set
tcm.pathiftcmisn't on your PATH: the path to tcm'scli/bin/tcm.js(run with Node; spaces are fine), or a command line with quotes around words containing spaces. - Watch values need inlay hints on (
editor.inlayHints.enabled, the default).
It starts tcm lsp in any workspace with a codebase. Try it on the polls example.
Other editors
Run tcm lsp (LSP over stdio) for TypeScript files, next to your TypeScript server. The workspace root must be the
codebase directory, or inside it.
Packages tcm provides (ones the project hasn't installed; see packages) are resolved by tcm's
TypeScript plugin, vscode/files/plugin. Load it in your TypeScript server as a tsserver plugin; without it,
TypeScript reports those imports as missing (tcm's own checks are unaffected).
Neovim (0.10+):
vim.api.nvim_create_autocmd("FileType", {
pattern = "typescript",
callback = function(args)
local root = vim.fs.root(args.buf, { ".tcm" })
if root then vim.lsp.start({ name = "tcm", cmd = { "tcm", "lsp" }, root_dir = root }) end
end,
})
vim.lsp.inlay_hint.enable(true)
vim.lsp.codelens.refresh() -- or map vim.lsp.codelens.run to run the Add/Update actions
Helix (languages.toml):
[language-server.tcm]
command = "tcm"
args = ["lsp"]
[[language]]
name = "typescript"
language-servers = ["typescript-language-server", "tcm"]
Zed needs an extension to register a language server; there isn't one yet.
In scratch files
Scratch files are the .ts and .tsx files at the top of the codebase directory, the ones the watcher checks.
Unsaved buffers are analyzed as you type.
- What each definition is to the codebase, as a code lens above it:
+ new lib.squarewith Add to codebase;~ update lib.square (was #a1b2c3d4)with Update codebase;lib.square #a1b2c3d4 · runs on the serveronce it's saved (placement shown for app code);- aliases and conflicts;
tcm: 2 type errors; nothing hashed until they're fixedwhile TypeScript reports errors.
- Watch values inline, at the end of the line:
square(12) ⧩ 144. They're cached by hash, so known values show at once as you type; new ones are evaluated when the file is opened or saved, so a half-typed expression with side effects doesn't run on every keystroke. - Test results on tests:
✓ passed (cached)or✗ failed: …, run on open and save. - Hovers on codebase names: every path a definition has, its hash, signature, where it runs and why, how many named definitions use it, and its documentation.
- Quick fixes (Cmd/Ctrl+.): a codebase name the file doesn't import gets Import x from "#codebase/…" (one per namespace that has it); a name it imports from the codebase gets Edit x here too, adding that definition to the file.
- Debug lenses on tests and runnable definitions (debugging).
- Formatting on save with oxfmt, and Format Document (formatting).
- Diagnostics from oxlint (source
oxlint, linked to the rule's page; linting) and from tcm (sourcetcm: a top-levelvarordeclare, say).
Files elsewhere (an imported project's sources) are ignored.
The codebase as documents
- Definitions are documents.
.tcm/src/lib/square.tsislib.squarewith current names (whattcm viewprints), its first line giving its names, hash and where it runs (.tsxfor JSX). They're real files, so debuggers stop in them and TypeScript reads them. They're the codebase's own branch's (the onetcm switchchecks out); a session's are in.tcm/sessions/<branch>/src, opened by a language server run with itsTCM_BRANCH. - They're read-only. Edit writes the definition into the scratch file (
scratch.ts, orscratch.tsxfor JSX), added to what's there if it holds work, where the lens offers Update codebase. Imports are folded away (the extension setseditor.foldingImportsByDefault). - Go to Definition (Cmd/Ctrl+click) on a codebase name opens its document; in a document, every reference is a
link. In VS Code, the extension's TypeScript plugin hides TypeScript's own answer (the generated module in
.tcm/cache); other editors show both. - Find All References lists every definition that uses one, at the exact spot (alongside TypeScript's results in scratch files).
- Lenses on a document: how many named definitions use it (click to list them), Edit, Rename, Debug.
- Symbols: workspace symbol search (Cmd/Ctrl+T) covers every name; the outline shows a document's definitions.
- Hovers work as in scratch files.
Clients can also open a definition as a tcm:/lib/square.ts document, with the same text.
Pages
In VS Code, definitions and namespaces have pages, as in Unison's codebase UI. Each opens in its own tab (opening one already open focuses it), links open more, and open pages refresh as the codebase changes.
- A definition's page: its names, hash, kind and badges (where it runs, endpoint, test result), why it runs
where it does, its doc rendered (links, embeds, examples with values), and its source with
every reference a link (imports too: a name to its definition,
#codebase/…to the namespace, a package to its page). Then what it uses, what it's used by, and the packages it imports. Buttons: Source (thetcm:document), Edit, Usages, Run (Start for an app, or open it if it's running), Debug, Rename, Copy (the hash). - A file's page: its format and content: Markdown rendered (its
{@link}s and links open their pages), an image shown, other text as it is (highlighted for code or JSON), with what it links to and what links to it. Buttons: Edit, Rename, Copy. - A namespace's page: its
READMEdefinition's doc, if any, then its contents grouped by role (apps, types, data, jobs, server, browser, shared, tests, files), each with the first paragraph of its doc, and its sub-namespaces.
Click a definition in the Codebase view to open its page (Open source in its menu opens the document); a namespace's Open page button (the book) opens its page.
Finding things
- tcm: Find (Cmd/Ctrl+Alt+U, or the search icon on the Codebase view): one box for names (letters in order, best
first), words in docs, and types. Anything that looks like a type,
(xs: T[]) => T, or starts with:, is a type search. Results show signatures and doc summaries, and open as pages. - tcm: Find by type (the sidebar's search icon, or the palette): type a TypeScript type, pick a match.
- Search (the first view in the
#sidebar, or tcm: Search the codebase's code, Cmd/Ctrl+Alt+Shift+F, which searches for the selection): find in files for the codebase. Every line of definitions and pages with the text, grouped by definition, with Match Case, Match Whole Word and Use Regular Expression. Definitions are searched as they read now, with current names; their header line isn't searched, nor are the mirror's files and.tcm. Click a definition to open its page, or a line to open the page there (Alt/Option-click for the document).
The sidebar
The # icon in VS Code's activity bar opens these views. They refresh when the codebase changes (from a terminal
too), and the Data view also when the data does.
Apps. Every app: a browser entry point, a function that runs in the browser, takes no arguments, returns a page (an element, or nothing, as a
maindoes), isn't called by other code and isn't a hook (use…), likepolls.main.- ▶ Start runs
tcm devon a free port, its log in an output channel, and opens it in VS Code's browser; the status bar shows running apps. Open (or in an external browser), Log and Stop act on a running one. - Debug runs it under the debugger, server and browser at once (debugging).
- Click an app for its page; right-click to open or edit its source. An app expands only to show problems that would stop it building.
- ▶ Start runs
Codebase. Namespaces, and the definitions and files (pages, configuration, images) in each. Large namespaces are grouped: Apps, Types, Data (tables), Jobs (schedules), Server, Browser, Shared (runs anywhere), Tests, Files. Apps show whether they're running, with start, open and stop. Icons show kinds, endpoints (a cloud) and test results; hover for details, click to open. Right-click (or the inline icons) for Edit in a scratch file, Find usages, Run (
tcm runin a terminal), Debug, Rename (tcm mv, a whole namespace too) and Copy hash.Changes. History and branches.
- Branches: every branch, this one first, each with how many names differ and when it last changed. Another branch opens to those names; right-click merges it here.
- History: this branch's entries, newest first, each the command that made it, opening to the names it changed (edited, re-hashed, added, removed, renamed). An edited definition opens in the diff editor, before and after, each side with its own names; anything else opens its page.
- Open changes (on a branch or entry) shows all its changed code in the multi-file diff editor (Show changes
as text is
tcm diff). Reset names to here brings the names back to an entry, as a new entry (tcm reset).
Data. Every table, declared or with rows, with its row count and rows stored as an older row type. Migrate rows brings those up to date (
tcm data migrate); Go to definition opens thetable<…>()declaring it. Click a table for the table viewer: a column per field (each header gives its type), the row's id and when it was written.- Click a header to sort; type to search anywhere in a row. Sorting, search and paging (100 rows at a time) run in SQLite, so big tables stay fast.
- Click a row to see its full value beside the grid.
- Older-row-type rows are badged, and the header offers Migrate rows.
- It refreshes as a running app writes rows. It's read-only: editing rows by hand would bypass the table's type checks and migrations.
Packages. The npm packages the codebase runs (packages): each with its locked version and how many definitions use it, by range when it runs more than one (React 18 and 19, say), and the definitions using it. Click one for its package page: description, license, homepage and npm links, other ranges, Upgrade (newest versions within the range, nothing re-hashed:
tcm upgrade <package>) and Move to another range (tcm upgrade <package>@<range> [namespace]), then tabs:- README (the
@typespackage's, when it has none); - API, as its types declare it: every export's signature and JSDoc, in a section per kind;
- Used by, linking to their pages.
A definition page's Packages list links here too.
- README (the
Releases. The codebase's own packages (publishing), each with its latest version. Under each:
- Next: its next release (the version, and why: breaking changes, additions, or the same API), with every semver finding behind it, coloured by level (hover for the lint and what it means; click to open the definition);
- its releases, newest first, with summaries (hover for the notes).
Release (the rocket) shows the version and notes, and asks; then offers Push, Pack and the package page. Draft release notes opens
release-<package>.md: a lens above says what the release would be and releases it (saving the file first), another regenerates the list of changes. Pack packs the package or a release of it; Save changelog… writes its changelog. Click a package for its page: exports and commands, the next release with findings and notes, the whole changelog, and the same buttons.
From the palette, tcm: Add / Update save the file first; Rename and Edit ask for names.
Language server reference
Commands
Editors expose these as code-lens actions or palette commands (workspace/executeCommand). Each reports its output
as a message.
| Command | Arguments | |
|---|---|---|
tcm.add | file URI | tcm add the file; it must be saved (tcm reads what's on disk) |
tcm.update | file URI | tcm update from the file; it must be saved |
tcm.mv | from, to | tcm mv |
tcm.edit | names | tcm edit them into the scratch file (.tsx for JSX); returns its URI |
tcm.editInto | file URI, name | add a definition to that scratch file |
tcm.dataMigrate | table | tcm data migrate <table> |
tcm.upgrade | package, [range, namespace] | tcm upgrade within its ranges, or to another range |
tcm.release | package, [version] | tcm release |
tcm.releaseDraft | package | write its release notes draft; returns its URI |
tcm.pack | package, [version] | tcm pack |
After tcm.add, tcm.update, tcm.mv, tcm.upgrade and tcm.release, every open file's status is refreshed.
Requests
For clients that show more:
tcm/tree- one level of the namespace tree: each definition's kind, role (app, type, table, schedule, server, browser, shared, test), signature, placement, whether it's an endpoint, test status
tcm/apps- each app with its endpoints, browser code, tables (with row counts), schedules and tests
tcm/data- tables, with row counts, older-row-type rows, the declaring definition and the row type's fields
tcm/rows- a page of a table's rows: searched, sorted by a field and paged in SQLite; the fields, and each row's cells, full value, and whether it's of an older row type
tcm/source- a
tcm:document's text: a definition,tcm:/.data/<table>.txt, ortcm:/.data/<table>/<id>.txt tcm/findByType- type search, as
tcm find ':…' tcm/find- the finder: names, doc words and types
tcm/page- a definition's page: rendered doc (links as
tcm-page:<hash>), highlighted source with references and imports as links, placement, uses and used-by tcm/namespace- a namespace's page: its README rendered, and its definitions by role with doc summaries
tcm/branches,tcm/history,tcm/historyEntry,tcm/branchChanges- the Changes view: branches (how many names differ), this branch's entries, and the names an entry changed or another branch has, an edit with its documents before and after
tcm/searchCode- find in files for the codebase, by definition: text as written, or
/…/(finding code);caseSensitive,wholeWord,regex(all the text a regular expression),under tcm/debug- a launch configuration for VS Code's JavaScript debugger:
kindrun,testorapp, with anameor fileuri tcm/packages,tcm/packageUsers,tcm/packagePage- the npm packages the codebase runs, what uses a package at a range, and its page
tcm/publishing,tcm/releaseCheck,tcm/releasePage,tcm/releaseList,tcm/changelog- the codebase's own packages, a package's next release and its findings, its page, its releases, and its changelog as Markdown
Notifications
The server sends tcm/changed when the codebase changes and tcm/dataChanged when the data store does, whether from
the editor, a terminal or a running app.
Notes
- The server uses the same TypeScript 7 compiler process as the watcher, with a snapshot per open file, so updates take tens of milliseconds.
- Its stdout is the protocol channel. Output from watch expressions and tests goes to stderr, which editors show in the server's log.