Examples — tcm-ts

Examples

Four complete apps, a family of packages, and a large imported library. For each you can:

  • run it on the docs site, under its own path (tcm start --base, or a static site). The notes and polls apps reset to empty every six hours.
  • browse it as a codebase, in a forge of its own: definitions, history and releases (code).
  • build it yourself: download it and run its setup.sh, which builds it into a fresh codebase with the tcm on your PATH (or the command line in TCM).

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 nothing 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 (setup.sh mounts it with tcm 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 using withRequest
ui.ts (examples/polls/ui.ts)
browser code calling the server like local functions, including for await over 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 with links and evaluated examples (tcm docs polls.chart); polls.README is the namespace's front page.

setup.sh adds the three files to a new codebase, then deletes them: the code lives in the codebase from then on. Browse it with tcm ls polls and tcm view polls.vote, or in VS Code's Codebase sidebar. tcm view shows where each runs: polls.chart anywhere, polls.vote on the server (callable from the browser by hash), polls.main in the browser.

notes: React server components

Run it

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, with no "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 Markdown component: plain elements, so one hash renders on the server in a note's page and in the browser in the editor's live preview
server.ts (examples/notes/server.ts)
the notes table and the server functions that change it (the server library, mounted by setup.sh with tcm lib add tcm server), with a test
styles.css (examples/notes/styles.css)
the page's look: a stylesheet kept in the codebase as written, which ui.tsx imports like a definition (its text) and links to with <Stylesheet>
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 (the react library's location, mounted by setup.sh with tcm lib add tcm react), and a browser test of the editor
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, links to notes included, is rendered on the server and hydrated in the browser. Saving a note refreshes the sidebar by itself: after a server function call, server components fetch their trees again.

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. It has no server of its own. Every record lives in its author's repository; the app reads them through userinput.app's index of boards, or through Constellation (an index of which records point at which), 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 discussion from userinput.app's 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 with useData, 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.

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 the plain files tcm build --static writes. It's a PWA: it installs, opens without a network, and updates 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 telling 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; one 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, which is why tcm build --static can write it as files.

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 builds up their 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, each with a summary (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 geo makes 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 shared codebase (.hub, the origin remote); a new case of Shape is 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 finish and release
playground.ts (examples/geometry/playground.ts)
the scratch file: watch expressions over both packages
README.md (examples/geometry/README.md)
the codebase's README, added first: what's in it, and its front page in its forge

Then try:

  • 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 behind it.
  • tcm pack atlas@1.1.0: an npm package of atlas 1.1, depending on geo ^1.1.0 (the first geo with what it uses) instead of carrying a copy.
  • In VS Code, the Releases view: both packages, each one's next release with its findings, and their releases. Click a package for its page and changelog. release-geo.md has a Release lens at the top. setup.sh points the extension at the tcm that built the codebase (.vscode/settings.json).

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 and 15,671 definitions, in 6–9 minutes. It's a library written without tcm in mind, and it exercises most of what importing accepts: 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 imports it from #codebase/effect/…. Browse it as a codebase (code).

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
README.md (examples/effect/README.md)
the codebase's README: what's in it, and its front page in its forge

Left out of the import: SchemaJITCompiler/enable.ts, a module there only for its side effect, and the Scalar and Swagger pages, which load their bundled UIs with import().

Built from c482a14e