22. The web UI
tcm serve has a web UI for the codebase it serves, at /forge/ (and at /, on a server with no
--site): a project's front page, its code by namespace and definition, search, its
contributions and their review, its history, branches, releases and people. Anyone can browse a
--public server; on one that isn't, it takes a token.
22.1 Pages
- The front page says what the project is and how to get it (
tcm init && tcm remote add … && tcm pull), what you can use (its packages and their latest releases, its apps, its pages), a map of its namespaces with a bar of where their code runs (server, anywhere, browser), and its README. - A namespace lists the namespaces in it and its definitions by what they're for (apps, types, data, jobs, server, browser and shared code, tests, files), each with its type and the first line of its doc, and its README if it has one.
- A definition shows its doc and its code, every reference a link to what it names (its type when you point at
it), where it runs and why, and what uses it (what a change to it re-hashes). Beside them is what only a codebase
knows: what it uses, the tests that reach it (passing or failing, as their results say), the history entries that
changed it, its other names, and the releases it's in. Its address is its name,
/forge/code/lib/math/square. - Code and files have line numbers. A line's number links to it (
/forge/code/lib/math/square#L3), and shift-clicking another number makes a range (#L3-L8); an address naming lines marks them and scrolls to them. A Markdown file (its images too, each a link to its file's page) or an image shows rendered first, with a switch to its text; an address naming lines shows the text. - A package's manifest shows the package as well as its source: its namespace and releases, the commands it runs,
what it exports, the apps, files and end-to-end tests it ships and what else it installs, each a link to the name it
means (a path like
../README.mdto the file kept under the namespace it's written from). - Search (the box in every page's header; ⌘K or /) finds names by the letters in them, words in docs, and types
(
:(xs: T[]) => T), astcm finddoes, with results as you type. Enter opens the result chosen, or the search page if the results for what's typed aren't in yet. - History is a branch's entries by day, each called by its message (the command that
made it under it) or, without one, by the command, with what it edited, added, removed and renamed, by name, and
how many definitions it only re-hashed, so an entry that re-hashed hundreds but edited four reads as small. It can
be narrowed to the entries changing a namespace. An entry's page has its whole message and shows what it changed as
a contribution's changes are shown, each definition with its lines (added, removed, or a diff of an edit; past the
first twenty, loaded as they're scrolled to), what only re-hashed folded away; its address takes the start of its
hash, as
tcm historyshows it (/forge/history/6iewhvaq). - Branches says when each last changed, how many names it differs from the default by, and the contribution proposing it, if one does. Releases has each package's versions and their notes. People has the maintainer and the contributors, with what they've proposed and merged, and how to become one.
- A page that doesn't load (the server failed, or is unreachable) says why, with a button to reload, rather than leaving the page blank. A tab opened before the UI was updated reloads by itself onto the new version (production).
22.2 Reviewing a contribution
A contribution's page has its conversation (the description, comments, closing and reopening, and whether it merges) and its changes:
- The changes are read along a review path: what's new before what uses it (each definition after what it depends on), then tests, then what was removed and renamed. What changed only because something it uses did (re-hashed, its code the same) comes last, folded: there's nothing in it to read.
- What it touches shows the change as a row: what it added and edited, what re-hashed because of it, and the tests that reach it.
- Each definition's change is its code before and after (two lines of context around each change), coloured as code and numbered before and after, with its comments. "Unified" or "Split" shows every diff on the page one line after another or side by side (the browser remembers which; a phone's narrow screen gets them one after another, as history entries' diffs do). "Viewed" folds a change away, and the browser remembers it for that version of it.
- Checks say whether it merges (as a fast-forward, cleanly, or with conflicts in which names), that its definitions typechecked when they were pushed, how many tests reach it, and whether docs changed with it.
- Commenting, closing, reopening and merging are what the viewer may do (below). A merge is the same as
tcm contribution merge: by history, refused on conflicts.
22.3 Signing in
Who you are is the token the browser signed in with, kept as a cookie (HttpOnly, SameSite=Strict):
- The link
tcm contributeprints (/forge/contributions/<id>#token=…) signs you in by itself. The token is in the link's fragment, which browsers never send to a server; the page signs in with it and takes it out of the address. - Sign in in the header takes a token: a contributor's (
tcm user add) or the server's. Signed in, your avatar there opens a menu that says who you are, links to your work, and signs you out. - What you may do is what your token may do, as the server's own routes allow it: a contributor comments (signed as them, whatever they say) and closes or reopens their own contributions; the maintainer (the server's token) merges, and signs with the name they give. With no token, a public server's pages only read.
22.4 A page runs no code
The UI reads the codebase; it never runs what's in it. A doc's examples are shown as written, not evaluated, and packages are read from their manifests' source. So a public server can show anyone its code, contributions included, without running any of it.
22.5 How it runs
The UI is an app of tcm's own (tcmts.share.App, React server components, its controls React Aria's, so the finder, tabs, menus, selects and folds work by keyboard and with a screen reader), shipped in tcm's package as a
codebase of its own that tcm start runs. tcm serve starts it the first time /forge/ is
asked for, or, on a --public server, as soon as it's listening (so a new tcm's first build is done before anyone asks): its codebase is copied into the served one's .tcm/share/ (again whenever tcm's changes: a tab still on the old UI reloads onto the
new one at its next call), tcm start
runs it there under /forge, told which codebase to show and the server's token, and tcm serve passes /forge/'s
requests to it. It stops when tcm serve does, however that ends (stopped, or killed). The first start, and the first after tcm changes, builds it (and installs React and what else it uses, as any app's first build does),
which takes a few seconds; a page asked for meanwhile says it's starting and reloads by itself. Its output is in
tcm serve's, each line marked ui: what it says when it starts, stops or fails (with the stack saying where), and a page followed to in the
browser that took a second or more to render. How long each page took is in its response's Server-Timing
(React). Browsers asking the server for /favicon.ico get the UI's icon,
unless the --site has one.
22.6 Pages are quick, the first time too
Pages are small. They come gzipped (as everything
tcm servepasses on to an app does), and a diff sends only what it shows: each change with two lines of context, each line once. A history entry with many changes comes with its first twenty, and the rest load ten at a time as the page is scrolled to them; what only re-hashed lists the first hundred.What a page reads of the branch's head is read once for each head, and kept until the head moves: its names, the project's name and packages, where each definition runs and what it's for, the front page's map, a definition's tests and history. What never changes is kept for good: what each history entry changed, what's in each release.
A history entry's changes are read from what differs. A history entry's names are stored as a tree whose nodes are named by their content, so comparing an entry with its parent reads only the nodes that differ, and one name's history is a walk down its path in each entry. Only the definitions whose hashes changed are compared, to tell an edit from a re-hash: an entry that re-hashed hundreds reads hundreds, not every name.
When the head moves (a push, a merge) it's made ahead, before anyone asks: the UI looks every two seconds, and when it starts. It works out where each definition runs and what it's for, the front page's map, and the history's first ten pages.
One page doesn't hold up the others. The UI is one process. What's made in bulk (a page's history entries, where a namespace's definitions run, the work ahead) is made a few milliseconds at a time, other pages' work in between, and the work ahead waits while a page is being made.
A busy UI doesn't hold
tcm serve's requests. A page or file the UI hasn't begun to answer in 30 seconds gets a 503 saying it's busy (a page reloads by itself, five seconds on), so one long page doesn't leave every other hanging. A UI that has answered nothing for a minute while asked is stuck:tcm servestops it (ui answered nothing for 60sin its output) and starts it again at the next request, unless an action it's making (a merge, a comment) is still going, which is never cut short.
22.7 Other codebases
tcm serve --forge /code/notes=../notes shows another codebase under a path of the server, in a web UI of its own
(/code/notes/, its pages as /forge/'s are: /code/notes/code/notes/model): for codebases a server shows beside
the one it serves, as tcm's server shows its examples. Several are comma-separated.
- It only reads. Anyone reads it, whether or not the server is
--public, and no one signs in: there's no token for it. There's nothing to pull from it either (it isn't served), so its pages leave out how to get it, its contributions and its people. - It's started when it's first asked for, as
/forge/is, its app kept in the served codebase's.tcm/share-<path>/. Until there's a codebase in the directory, its pages say there's none yet (a 503). - A directory that's a link is followed. When it comes to link to another codebase (a new build of it), the next page asked for starts the UI again on that one.
A tcm run from a codebase rather than its package (TCM_LIVE, or the suites tcm test runs) has no package beside
it: tcm test packs the apps its package ships, as tcm pack would, and says where (TCM_APPS).
To work on the UI, tcm dev tcmts.share.App in tcm's codebase serves it on that codebase, everyone its maintainer.