16. Pages and other files
A codebase keeps more than code: docs pages, stylesheets, images. Each is a definition of its own, a file,
named like any definition (docs.getting-started) and kept as written. It's hashed by its content, and it's in
history, branches, merges and sync like code.
.md,.txt,.css,.svg,.html,.json,.yaml,.yml,.toml,.csv- text
.png,.jpg,.jpeg,.gif,.webp,.ico,.woff,.woff2,.pdf- bytes
- anything else: another extension (
.sh,.js), or none (Dockerfile,Makefile,LICENSE,.gitignore) - text, or bytes if it isn't UTF-8
A file's name is its namespace and its file name without the extension, dashes kept: getting-started.md in
docs is docs.getting-started. A hidden file's name drops its dot (.gitignore is gitignore, .mcp.json is
mcp), and the file keeps it: tcm edit, tcm export and a mirror write .gitignore again. (Its name can't have
other dots.) A hidden file saved by a tcm from before it kept the dot is written without it, until it's saved again
from its dotted file (tcm update .gitignore --into app), which gives it a new hash.
16.1 Saving and editing
A file is saved like a scratch file, by its path:
tcm add guide.md --into docs # docs.guide
tcm check guide.md --into docs # what saving it would do
tcm edit docs.guide # writes guide.md, to change and `tcm update guide.md`
tcm patch docs.guide --old "…" --new "…" # a small change, saved back as the page it is
tcm view docs.guide
A Markdown file can say where it goes in its first line, as tcm edit writes it:
<!-- tcm: path docs.guide -->, or <!-- tcm: namespace docs --> and its own name. That line isn't kept.
A file of code kept as written (an example's ui.tsx, a script; see writing them out) is
saved the same way, with --into: tcm edit examples.notes.ui writes ui.tsx, and
tcm update ui.tsx --into examples.notes saves it back as written, because that name holds a file. A new one is
added from a directory marked as files (.tcm-files): tcm add examples/userinput/model.ts --into examples.userinput. Otherwise (without --into, or where the name holds no file and the directory isn't marked) a
.ts file is a scratch file, read as definitions.
Only a .ts or .tsx file is ever read as code. One without an extension (Dockerfile, Makefile, LICENSE) or
a hidden one (.gitignore, .env.example) is a file kept as written, in every command: tcm add Dockerfile --into deploy adds deploy.Dockerfile, tcm edit deploy.Dockerfile writes Dockerfile, and tcm check and
tcm update Dockerfile --into deploy check and save it back; tcm patch changes it as text. In a
session's directory, tcm status lists one tcm edit wrote there as unsaved work
(so the session's directory isn't cleared away with it), and tcm watch --into <namespace> checks one the namespace
holds as it changes (a page that says where it goes, without --into).
16.2 Links in Markdown
- To code, with
{@link name}as in doc comments: the link is to the definition by hash, so it follows renames and updates. The page names it as it's called now, as short as it reads from the page's namespace. A{@link}to nothing in the codebase is an error when the page is saved, andtcm rmasks before removing a name a page links to. - To other pages and files, with ordinary relative links (
[the FAQ](faq.md#why)): kept as written, and checked when the page is saved: a link to a file of the codebase that isn't there is a warning.tcm mvof a file rewrites the links to it in every page (and a moved page's own links, from where it is now);tcm rmasks before removing a file a page links to.
Links in code (fences and inline code) aren't links.
16.3 Files are values
Code imports a file like any definition, by its name: its content, as it was saved (text as a string, bytes as
a Uint8Array). The code depends on it by hash, so updating the file re-derives the code that uses it.
// tcm: namespace ui
import { app } from "#codebase/styles"; // styles.app, a .css file
import { logo } from "#codebase/media"; // media.logo, a .png
export const page = () => `<style>${app}</style>${logo.length} bytes of logo`;
In a React app, <Stylesheet css={app} /> (from the react library) links to a stylesheet so imported, as a file the app's
build serves (React).
A file whose own name isn't an identifier is imported by its name in quotes:
import { "getting-started" as gettingStarted } from "#codebase/docs". A file is data: what it
links to doesn't make the code using it run on the server or in the browser.
16.4 Writing them out
tcm export <namespace> <dir> writes a namespace's files to a directory, laid out by their names (docs.a.b is
a/b.md, app.gitignore is .gitignore), with links and names as they are now; tcm export docs.guide <dir> writes the one file, guide.md. A package can ship them too: an entry of its manifest's files
can be a namespace of files instead of a path (publishing).
In a mirror, every file under the mirrored directory that isn't code is a file of the
codebase, written back as it changes (in a git repository, every one git keeps). So is everything in a directory
with a .tcm-files file in it, code included: example projects, an editor extension, kept as written. The mark's
lines are patterns of what to leave out, as in a .gitignore (*.vsix).
tcm's own docs are files of its codebase (tcmts.docs), and its docs site is code that
imports them (tcmts.site): a React app, tcmts.site.Docs, that renders each page on the server (pnpm docs:dev
serves it with tcm dev), built as a static site, every page rendered as it's built
(pnpm docs:site, which is pnpm tcm run tcmts.dev.site). It depends on each page by hash, so updating one re-renders the site. pnpm verify builds it
too, and fails on any link or anchor that doesn't resolve.