ada-cflf-f719/ada-cflf

Public: anyone can read this; 1 person can change it.

Something wrong? Report a problem, no account needed.

Running c29f658 https://ada-cflf--forge-trevato-apps.open-platform.sh
History

README.md

ada-cflf

A new app on the forge.

This is an app: the forge runs it. Production follows the default branch; every open pull gets a preview at its own address with its own copy of the data. Everything the app keeps lives under FORGE_APP_DATA, which is what makes "production data" and "preview data" two different things.

PORT=8080 FORGE_APP_DATA=./data bun run src/server.ts   # run it here
bun test                                                # what `forge.json`'s app.test runs on every push
FORGE_APP_DATA=./data bun run src/tty.ts                # the terminal
FORGE_APP_DATA=./data bun run src/db.ts --check         # schema version, integrity
bun run lint:design                                     # no colour but the tokens'
bun run contract:emit                                   # rewrite contract/openapi.json from the routes

app.test in forge.json runs beside app.run in every deploy, not before it: the preview comes up the moment the app answers, and says live, or serving · tests failed the moment the suite disagrees.

Every response carries Content-Security-Policy, X-Content-Type-Options, Referrer-Policy and Cross-Origin-Opener-Policy: same-origin (the last cuts a page off from any window that opened it); they are set in src/http.ts.

The shape

One implementation, many doors. src/actions.ts is every operation as a plain function — (db, who, input) in, data or a named failure out — and the page, the JSON API, the MCP server and the terminal all call it and decide nothing themselves. AGENTS.md says how to add to it.

filewhat it is
src/actions.tslist, add, delete; the audit trail, read
contract.jsonwho may do what: roles, and the least role each verb needs
src/contract.ts, src/can.tsthe table, read at boot; the forge's evaluator, verbatim
src/security.tsthe door: rationing, same origin, the table before the body
src/audit.tsevery change and every refused one, as a row
src/who.tswho is asking, from the forge's signed headers
src/signin.tssign-in through the forge: OpenID Connect, code flow with PKCE
src/once.tsa job the forge fires: verified, and run once per key
src/jobs.ts, src/reminder.tsdurable jobs (steps, sleeps, waits) and the worked example
src/db.tsSQLite under FORGE_APP_DATA, WAL, forward-only migrations
src/live.tsthe in-process bus and GET /events
src/mcp.tsthe actions as MCP tools at POST /mcp
src/routes.tsevery path, in one table, each API route described beside it
src/openapi.tsthe routes as OpenAPI; committed at contract/openapi.json
src/page.tsthe HTML, rendered on the server, with its share metadata
src/site.tsicon, manifest, card, sitemap, llms.txt, structured data
src/compress.tsgzip for text responses
design/tokens.json, src/tokens.tsthe look as data, light and dark; compiled to /tokens.css
src/http.tsthe floor: headers, body cap, files
src/server.tsboot: open the database, serve
src/tty.tsthe terminal, over the same actions
src/env.tswhat the forge told this process
static/app.css, static/live.jsthe look in the tokens' words, and the list that stays current
static/beacon.jsreports each page's load and INP to the forge, which counts visitors: no secret, no address kept

What every app stands on

  • The door. Before a route sees a request, src/security.ts rations

writes (and a stranger's API calls), refuses a write from another site, and turns away — before the body is read — anyone the table lets through a resource's door for no row at all: 401 for a stranger, 403 for anyone else. The action still decides about the real row.

  • The audit trail. Every change, and every change refused, is a row:

who, which resource and verb, which row, when, and whether they were let. Admins read it at GET /api/audit. A row that cannot be written is a log line, never a failed request.

  • The API, described. GET /openapi.json is contract/openapi.json,

written from the routes by bun run contract:emit; each operation names its cell of the table (x-contract). bun test fails while it is stale.

  • Up, or not. GET /healthz answers { ok, version, uptime }; the

forge waits on it before it sends anyone to a deploy.

  • The look. design/tokens.json holds every colour, space and size,

light and dark; nothing else spells out a colour (bun run lint:design), and every pair a person reads meets 4.5:1 in both themes.

  • The open web. A shared link unfurls (description, Open Graph, a

large generated card at /og.png); every page names its canonical address and carries WebSite structured data; there is an icon (/icon.svg), a 180px home-screen icon and a manifest; /sitemap.xml and /llms.txt describe the site; /robots.txt keeps crawlers to the page (and names the sitemap), and out of previews entirely; /.well-known/security.txt names the forge that runs the app as the place to report; /.well-known/change-password sends people to it too. A page carries an ETag and answers If-None-Match with 304. A missing page and a failure are the app's own page, with the right status. HSTS covers subdomains and is never a preload promise.

  • Reachable. A skip link and <main id="main">, forced-colours

styles, and one <h1> with no skipped level (src/site.test.ts holds all of it).

  • Small on the wire. src/compress.ts gzips text of a kilobyte or

more for browsers that read gzip (never the event stream); a proxy in front that compresses sees content-encoding and leaves it alone.

  • Left to the owner. Telling AI crawlers what they may do, content

signals, an agent card and .md versions of pages are decisions about who may read the site and for what; the template makes none of them. Decide, then add them (and a test) deliberately. The card and icons are flat shapes in the accent colour, not a drawn logo: swap in your own.

Who you are

The forge says who is asking on every request it proxies, and the app verifies the signature (src/who.ts); nothing else is trusted. Three tiers:

  • anon — anyone. Reads.
  • user — signed in to the forge and able to see this app. Adds notes, as

themselves, and removes their own.

  • admin — able to write this repository. Removes any note.

"Sign in" is /signin: the OpenID Connect code flow with PKCE against FORGE_URL (src/signin.ts). The app registers its own redirect address on first boot and keeps the client id in FORGE_APP_DATA, checks the id_token itself, and keeps a session in an HttpOnly cookie. A person signed in this way is a user; admin still comes from the forge's signed headers (which win over a session), until those are retired. "Sign out" is a POST to /signout. The forge answers /__forge/report: it sends anyone, with no account, to report a problem on this app's repository, if that repository is public.

Any interface

The same actions, the same rules, at every door. A script is the person whose forge grant it carries; a bare script is anonymous.

curl https://<app>/api/notes
curl -X POST https://<app>/api/notes -H 'content-type: application/json' -d '{"body":"hi"}'
curl -X DELETE https://<app>/api/notes/1

As an MCP server, for Claude Code or Claude Desktop — list_notes, add_note, delete_note:

claude mcp add --transport http __TITLE__ https://<app>/mcp
{
  "mcpServers": { "__TITLE__": { "type": "http", "url": "https://<app>/mcp" } }
}

Jobs

A job is code that reads top to bottom and can wait an hour or a week with no process running. Each step result is saved in this app's database; sleep and waitFor save where the run is, ask the forge for a timer and return. When the timer fires, or signal is called, the function runs again from the top and every finished step answers from its saved result.

export const remind = job("remind", async (ctx, input) => {
  const order = await ctx.step("load", () => loadOrder(input.id));
  await ctx.sleep("wait", { days: 3 });
  if (!(await ctx.step("paid?", () => isPaid(order)))) await ctx.step("email", () => sendReminder(order));
  const reply = await ctx.waitFor("reply", { event: `reply:${order.id}`, timeout: { days: 7 } });
  return reply ?? "no reply";
});
await remind.start({ id }, { key: `remind:${id}` });   // returns at once; the same key is the same run
await remind.signal(`reply:${id}`, "thanks");          // wakes a run that is waiting for it
  • A step must be safe to retry until it returns: it may run twice if the

process dies inside it. Give info.idempotencyKey (ctx.step("pay", (info) => …)) to anything that accepts one and it acts once.

  • A step that throws is retried with backoff ({ retries }, default 3), then the

run is failed and the forge asks the owner once. A renamed, removed or reordered step fails the run plainly; ctx.version keeps an old path for old runs.

  • A result is JSON, at most 256 KB. JOBS_ACTIVE_MAX (default 1000) caps unfinished

runs; the forge caps pending timers (FORGE_TIMERS_PER_APP).

  • The app's page on the forge lists the runs — where each is now — with Cancel

and Retry from the failed step; src/reminder.ts is the worked example (POST /notes/:id/remind, a reminder that waits a day), src/jobs.test.ts its tests.

The page stays current over /events: a note someone else adds or removes appears without a reload. Without JavaScript it is the same page, one reload behind.

The terminal opens with the exact command in FORGE_APP_SSH when the app runs (ssh -p 2222 <name>@<forge> — the app is the login name, the forge the host; <name>--<pull>@<forge> for a preview's). It is src/tty.ts on a real pty over the same data, as the account at the keyboard.

The forge sets PORT, FORGE_APP_DATA, FORGE_APP_ENV (production or preview), FORGE_APP_URL, FORGE_APP_COMMIT, FORGE_APP_SSH and FORGE_APP_IDENTITY_KEY when it starts the app. forge.json says how.

↑↓ navigate ↵ open esc close

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs