forge
Clone forge-starter
It replays, it never executesrecorded runs, stepped through a state machine
Built from forge's own registriescapabilities · resources · events · routes · errors
Your progress is in your browserdone marks + deep links · no server

How the playground works

The playground is a twin, not a forge behind the site. It has two inputs, one engine and one rule — nothing on screen is typed by hand.

the forge reposrc/capabilities/index.ts · 24src/resources/types.ts · 23src/events/catalog.ts · closedsrc/api/*-routes.ts · 142src/shared/errors.ts · envelope+ the consumer's dispatch tablemodel generatorbuild step · CI · per forge tagscenario recorderruns the ask on a throwaway control planereadsdrivesplatform model · JSONcapabilities+schemas · resources · events · routes · errorsscenario fixtures · JSONask · skill · hops[] · state0 · err · fixedemitsemitstwin engine · in the browserapply(k): hops ≤ k · events ≤ k · state ≤ kRun = k → k+1 on a timer · scrub = apply(k)loads one fixture · resolves sources via the modelyour progress · localStoragedone marks per scenarioshare = a deep link to a hopsavesthe panestrace · state · events · this hopsummary · scrubber · error cardrenderstwo inputs (generated + recorded) · one engine · one rule: nothing on screen is typed by handdashed = a build/CI step · solid = shipped with the site · the engine has no server and runs no visitor code
The generator turns forge's registries into a model; the recorder turns a real run into a fixture; the browser engine replays the fixture. Re-run both in CI on every forge tag, and the diff is the docs changelog.

What is real, what is simulated

on screenwhere it comes fromreal or simulated
a hop's source (file:line)the recording, resolved against the model's capability → source mapreal
a hop's request and responsecaptured by the recorder (payloads bounded, secrets redacted)real · recorded
events and their dataGET /events + GET /app-events?subject=trace:… during the recordingreal · recorded
timingsthe recording's durationsrecorded, replayed at 520 ms per hop
state rows (resources, tables)GET /resources before/after + row counts; modelled as named rows with a statusmodelled · not a live database
the error cardthe real ForgeError envelope from the recording + fixes authored by a reviewerreal envelope · authored fixes
the skill mapping (ask → capability)the forge skill set, once it existsassumed until the skill ships
free-text askv1: a picker; v2: matched to a scenario by the skill's action vocabularyv2
your codenever runs · no containers per visitor

A scenario fixture

{
  "key": "provision",  "forge": "v1.56.0",
  "title": "A non-technical builder adds a database",
  "ask": "Add a Postgres database to hello and restart it.",
  "skill": "forge → provision-environment · run-dev-server",
  "plane": "control · :3717",
  "state0": [ ["Environment", "i-db", "ok", "web only"], … ],
  "hops": [
    { "where": "control", "src": "provision-environment/converge.ts:83-104",
      "what": "converge: flags + manifest + existing compose → desired infra",
      "tag": "control-plane", "ms": 2,
      "io": { "req": "…", "res": "…" },
      "ev": ["fe", "EnvironmentProvisioned", "postgres=true"],
      "st": [ ["Environment", "upd", "web + postgres"] ] }
  ],
  "err":   { "title": "…", "reason": "…", "trace": "…", "fixes": [ … ] },
  "fixed": { "plane": "…", "hops": [ … ] }
}

What Run does, step by step

1 · load

Load the fixture; render every hop grey; render state0; hide events; k = 0.

2 · step

Every 520 ms (0 under reduced motion): apply(k+1) — hops < k ✓, hop k ▶ selected with its payload; events of hops < k shown with timestamps from cumulative ms; state = state0 + mutations of hops < k, last mutation highlighted.

3 · done

At k = N: the summary (hops · state rows touched · events · simulated time) and the scrubber, whose value is k. Scrubbing calls apply(k) and stops the timer.

3′ · fail

A hop with fail turns red; the run stops at N; the error card renders from err. A fix with an id loads the fixed variant and runs again. Nothing is ever a dead end.

How a scenario is recorded

stepwhat the recorder doesreads
1provision a throwaway control plane from forge-starter in CI, pinned to the forge tag./new-app · compose.yaml
2run the ask (through the skill) or the commands (CLI), with a fresh traceparentPOST /capabilities/:slug
3collect everything the run recordedGET /events · GET /app-events?subject=trace:… · GET /logs/:id
4turn each event / log line into a hop; resolve its source through the model's capability → file map; bound and redact payloadsthe platform model
5diff state before and afterGET /resources · table row counts
6write the fixture; a reviewer adds the prose (what, ask, fixes) — the only hand-typed fieldsscenarios/<key>.json

Non-goals, and what step 4 must decide

Non-goals

Running a visitor's code. A container per visitor. A live database. Anything that needs a server behind the site: the twin is static files plus the browser.

Open in step 4

Which repo owns the generator and the recorder (forge, or the site). Whether fixtures re-record on every forge tag (recommended: yes; the diff is the changelog). How free-text asks match scenarios once the skill exists.

The scenario table (lib/scenarios.ts) and the engine (PlaygroundClient.tsx) are the reference implementation of what is described above. The generator and the recorder are new.

← Back to the playground