Public
Foldkit counter ported to Val Town
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

Foldkit Counter — on Val Town

A port of the Foldkit counter playground (foldkit/examples/counter) to Val Town — no build step, no bundler.

Foldkit is a TypeScript frontend framework built on Effect, using The Elm Architecture: one immutable Model, typed Messages, a pure update, and a view. The original example is compiled with Vite. Here it runs straight in the browser via an import map that pulls Foldkit + Effect from esm.sh.

Architecture

Rendering mermaid diagram...

Files

FileRole
main.tsHTTP handler. Serves the shell, the client module, and a /source redirect.
index.htmlThe shell: Twind (runtime Tailwind), the import map, and <div id="root">.
client.jsThe Foldkit app — a faithful port of the example's main.ts + entry.ts.

How the no-build port works

client.js uses the exact bare specifiers Foldkit's own source uses (effect, foldkit/runtime, foldkit/html, foldkit/message). A browser import map in index.html resolves them to esm.sh:

  • The Foldkit subpaths are requested with ?external=effect,@effect/platform-browser so they don't bundle their own copy of Effect.
  • "effect" and "effect/" both point at the same pinned Effect build. Foldkit imports the effect namespace; @effect/platform-browser imports granular subpaths (effect/Effect, effect/Schema, …). Both forms resolve to the same chunks, so Effect stays a single shared instance — without that, Effect's Context tags wouldn't match and the runtime would fail.

The only port-level change from the upstream TypeScript is dropping the type annotations (and the type-only withReturnType) so the file is plain JS the browser runs as-is. The Model, Message, update, init, and view are otherwise identical to the original.

Notes

  • Styling uses Twind so the original Tailwind utility classes work with no build. Twind rewrites them to hashed class names at runtime — the visual result is the same.
  • DevTools are enabled via devTools: { Message }, matching the upstream entry.ts.
  • Click view source (bottom-right) to open this val.