6. Examples
Four complete apps, a family of packages and a large library imported, each built into a fresh codebase by its setup.sh. Run the apps on
the docs site, each under its own path there (tcm start --base, or as a static site; the notes and polls start
over, empty, every six hours), browse each as its codebase, in a forge of its own: its definitions, history and
releases (code), or download one and build it yourself: its
setup.sh uses the tcm on your PATH (or TCM, a command line for another).
6.1 polls: a live polling app
Run it (open it in two windows to see the votes arrive)
curl -fsSL https://tcm.codes/docs/examples/polls.tar.gz | tar xz
polls/setup.sh /tmp/polls # builds the codebase there and runs its tests
cd /tmp/polls && tcm dev polls.main # or open /tmp/polls in VS Code with the tcm-ts extension
A small app that uses most of what tcm does, with no annotations saying what runs where:
model.ts(examples/polls/model.ts)- plain functions (
tally,leader,chart) that run anywhere; tests; sample data server.ts(examples/polls/server.ts)- the server library tcm ships (which
setup.shmounts withtcm lib add tcm server): typed tables, sessions, a rate limit, a background job that announces the winner when a poll closes, a nightly schedule, a streaming function for live results, and a server test usingwithRequest ui.ts(examples/polls/ui.ts)- browser code calling the server like local functions, including
for awaitover the live stream playground.ts(examples/polls/playground.ts)- the scratch file you start with: watch expressions over the model, and a definition to change
Its definitions are documented the tcm way: links, and examples that are evaluated
(tcm docs polls.chart), and polls.README is the namespace's front page.
setup.sh adds the three files to a new codebase and then deletes them: from then on the code lives in the
codebase, not in files. Browse it with tcm ls polls and tcm view polls.vote, or in VS Code's Codebase
sidebar. tcm view shows where each definition runs: polls.chart anywhere, polls.vote on the server (and
callable from the browser by hash), polls.main in the browser.
tcmts.cli.test.e2e.examples builds this example, starts it, votes, and checks the page in Chrome.
6.2 notes: React server components
curl -fsSL https://tcm.codes/docs/examples/notes.tar.gz | tar xz
notes/setup.sh /tmp/notes # builds the codebase there and runs its tests
cd /tmp/notes && tcm dev notes.App # or ▶ Start in VS Code's Apps view
A notes app in the style of React's Server Components demo, without a single "use client" or "use server".
tcm infers where each component renders from what it uses (React):
model.ts(examples/notes/model.ts)- plain functions (
excerpt,matches,ago) that run anywhere; tests; sample notes markdown.tsx(examples/notes/markdown.tsx)- a markdown renderer and its
Markdowncomponent: plain elements, so it renders on the server inside a note's page and in the browser inside the editor's live preview, from one hash server.ts(examples/notes/server.ts)- the
notestable and the server functions that change it (the server library tcm ships, whichsetup.shmounts withtcm lib add tcm server), with a test styles.css(examples/notes/styles.css)- the page's look: a stylesheet kept in the codebase as it's written, which
ui.tsximports like a definition (its text, as a string) and links to with<Stylesheet>, as a file the build serves ui.tsx(examples/notes/ui.tsx)- server components (
NoteList,NotePreview: async, they read the table), browser components (App,NoteEditor,SidebarNote: state and handlers), the URL as state (through the location of the react library tcm ships, whichsetup.shmounts withtcm lib add tcm react), and a test of the editor that runs in a browser (it renders a component with state) playground.tsx(examples/notes/playground.tsx)- the scratch file you start with: watch expressions, including a component rendered to HTML
tcm dev notes.App prints the split: three server components fetched by hash, the browser components they send
by hash, and the server functions the browser calls. The first paint is rendered on the server, links to a note
included, and the browser hydrates it. Saving a note refreshes the sidebar by itself: after a server function
call, server components fetch their trees again.
The test suite builds this example too, then adds, opens, edits and searches notes in Chrome.
6.3 userinput: an AT Protocol client
Run it (signing in writes to your own repository, as userinput.app does)
curl -fsSL https://tcm.codes/docs/examples/userinput.tar.gz | tar xz
userinput/setup.sh /tmp/userinput # builds the codebase there and runs its tests
cd /tmp/userinput && tcm dev userinput.app.App # then sign in with your Bluesky handle
A client for userinput.app, feedback boards on the AT Protocol: post ideas into a space, vote, reply in threads, and, as a space's owner or moderator, set statuses, pin, hide, lock and ban. There's no server of its own: every record lives in its author's repository, and the app reads them through userinput.app's index of boards, or through an index of which records point at which (Constellation), and signs in with AT Protocol OAuth from the browser. It's the largest example (about two hundred definitions, React and React Aria), styled like Apple's apps:
model.ts(examples/userinput/model.ts)- the records (userinput.app's lexicons) as types,
at://URIs, and the services it talks to network.ts(examples/userinput/network.ts)- reading the network politely: a couple of dozen requests at a time per service, backing off when asked, each read once
rules.ts(examples/userinput/rules.ts)- what the records add up to: a status counts only from the space's moderators, an edit only from its author; scores, threads, sorting; tested
data.ts(examples/userinput/data.ts)- each page's data: a board or a discussion from userinput.app's own index (its app view) in one request, with your votes and new posts from your repository, which has them first; or, when the index can't say, assembled from records in many repositories
session.ts(examples/userinput/session.ts),writes.ts(examples/userinput/writes.ts)- signing in (OAuth, or an app password), and every change as a record written to your own repository
design.tsx(examples/userinput/design.tsx),markdown.tsx(examples/userinput/markdown.tsx),styles.css(examples/userinput/styles.css)- the design system on React Aria, accessible by keyboard and screen reader; Markdown rendered safely; large type and squircles
home.tsx(examples/userinput/home.tsx),board.tsx(examples/userinput/board.tsx),discussion.tsx(examples/userinput/discussion.tsx),people.tsx(examples/userinput/people.tsx),app.tsx(examples/userinput/app.tsx)- the pages, with the path as the state: routes
matched from the react library's location, and each page's data read once per key withuseData, so going back shows it at once and a refresh after a write keeps it on screen
tcm dev says it all runs in the browser. The test suite builds it too, and uses it in Chrome against a fake
network (tcmts.cli.test.e2e.examples.startFakeAtproto): reading a board, signing in, replying, voting and posting.
6.4 reminders: a local-first PWA
Run it (then go offline and reload, or install it)
curl -fsSL https://tcm.codes/docs/examples/reminders.tar.gz | tar xz
reminders/setup.sh /tmp/reminders # builds the codebase there and runs its tests
cd /tmp/reminders && tcm build reminders.App --static site # a static site in site/, for any static host
Reminders in the style of Apple's, kept on the device: there's no server, and nothing leaves it. The docs site
serves it as plain files, tcm build --static's, and it's a PWA: it installs, opens
without a network, and takes a new version by itself.
model.ts(examples/reminders/model.ts)- plain functions (which list a reminder is in, due days, a reminder typed in one line) that run anywhere; tests
store.ts(examples/reminders/store.ts)- the device's IndexedDB database, and a channel that tells the app's other tabs and windows of a change: browser code, inferred from what it uses
ui.tsx(examples/reminders/ui.tsx)- the app: smart lists and yours, each list a path (
/today,/lists/<id>), so a static build has a page for each smart list, a page at a time on a phone; it says when it's offline, and when a new version is ready (useAppUpdate) pwa.ts(examples/reminders/pwa.ts)- the web app manifest, beside the app: its icons are files of the codebase (
icon.svg(examples/reminders/icon.svg),appicon.png(examples/reminders/appicon.png)) styles.css(examples/reminders/styles.css)- Apple's look, light and dark
playground.ts(examples/reminders/playground.ts)- the scratch file you start with
tcm view reminders.App says it all runs in the browser, so tcm build --static can write it as files: a static site
has no server to run anything. The test suite builds it so, serves the files, and uses it in Chrome: it adds a
reminder, reloads with the server gone, and then takes a new version, built into the same directory, by itself.
6.5 geometry: packages, releases and changelogs
curl -fsSL https://tcm.codes/docs/examples/geometry.tar.gz | tar xz
geometry/setup.sh /tmp/geometry # builds the codebase, and its history, there
code /tmp/geometry # the Releases view, release-geo.md, the playground
One codebase holding two npm packages: geo, a geometry library, and atlas, a command built on it. Each is
a namespace with a manifest (publishing). setup.sh gives them a history, step by
step, printing each:
geo.ts(examples/geometry/geo.ts),atlas.ts(examples/geometry/atlas.ts)- two packages in one codebase, one using the other; documented, with tests, and a command (
atlas 0,0 3,4) tcm release geo@1.0.0 --notes …- first releases, declared stable, with a summary each (
notes/(examples/geometry/notes)); tcm writes the rest of the notes steps/geo-1-1.ts(examples/geometry/steps/geo-1-1.ts)- a new definition and an optional parameter: additions, so
tcm release geomakes it 1.1.0 by itself steps/hexagons.ts(examples/geometry/steps/hexagons.ts)- a contribution, proposed on a branch, commented on and merged through a hub (another codebase,
.hub); a new case ofShapeis breaking, so it's geo 2.0.0, and its notes name the contribution steps/next.ts(examples/geometry/steps/next.ts)- an unreleased change, and a draft of its notes (
release-geo.md) for you to write and release playground.ts(examples/geometry/playground.ts)- the scratch file: watch expressions over both packages
Then:
tcm releases,tcm changelog geo: the releases, and a changelog nobody wrote by hand (releases).tcm release check geo: what geo's next release would be (2.1.0, additions), and the semver findings why.tcm pack atlas@1.1.0: an npm package of atlas 1.1, which depends ongeo ^1.1.0(the first geo with what it uses) rather than carrying a copy.- In VS Code, the Releases view shows both packages, the next release of each with its findings, and their
releases; click a package for its page and changelog.
release-geo.mdhas a Release lens at the top.setup.shpoints the extension at the tcm that built the codebase (.vscode/settings.json).
The test suite builds this example too, and checks its releases, changelog and packages.
6.6 effect: a large library
curl -fsSL https://tcm.codes/docs/examples/effect.tar.gz | tar xz
effect/setup.sh /tmp/effect # fetches effect@4.1.2 from npm, imports it (minutes), runs the playground
cd /tmp/effect && tcm view effect.Effect.Effect && tcm run demo.main
Effect, imported whole from its npm package's source: 580 files, 15,671 definitions, in
6–9 minutes. It's tcm's stress test, a library written without tcm in mind, and it exercises most of what
importing takes: interfaces with namespaces of types beside them (Effect.Success<…>),
statements setting up constants (Impl.prototype = Proto), declare global and declare module augmentations,
namespace imports everywhere, and large cycles. Once imported it's code like any other: tcm verify re-renders and
re-hashes every definition in a couple of minutes, and the playground uses it from #codebase/effect/…. Browse it
as its codebase (code). It's too large to
build for every change to tcm: the test suite doesn't, and pnpm examples:check effect --tests checks it when
importing changes.
setup.sh(examples/effect/setup.sh)- the import:
npm pack effect@4.1.2, the changes below, Effect's compiler options,tcm import --into effect changes.patch(examples/effect/changes.patch)- what tcm needs changed, in seven files: four where TypeScript 7.1, which tcm checks with, reports errors 7.0 doesn't; a module object used as a value (
? Aead : Cipher), which tcm can't import yet; and two imports of the package by its own name (effect/Cause), which its npm package maps to its build playground.ts(examples/effect/playground.ts)- programs using Effect: generators, a typed error caught by its tag, an asynchronous sleep; tests
Left out of the import: SchemaJITCompiler/enable.ts, a module that's there only for its side effect, and the Scalar
and Swagger pages, which load their bundled UIs with import().