Writing a Showcase
A Showcase file is named .showcase.ts and exports one array. There is no metadata format to learn and no parse step — the array is the catalog.
A Showcase file is named *.showcase.ts and exports one array. There is no metadata
format to learn and no parse step — the array is the catalog.
import assert from "node:assert/strict"
import type { Showcase } from "foldcase"
import { Story } from "foldkit/test"
import { ClickedIncrement, initialModel, Message, Model, update } from "./counter"
export const showcases: ReadonlyArray<Showcase> = [
{
id: "counter/click-twice",
play: () =>
Story.story(
update,
Story.given(initialModel),
Story.message(ClickedIncrement()),
Story.message(ClickedIncrement()),
Story.model((model) => assert.equal(model.count, 2)),
),
message: Message, // optional — the Message-union Schema
model: Model, // optional — the Model Schema
dispatches: ["ClickedIncrement"], // optional — the tags this play sends
},
]Assertions come from node:assert rather than bun:test, because a catalog is loaded by
whichever bin you run — foldcase under Node, foldcase-bun under Bun — and an import
only one runtime has would tie the catalog to that runtime.
The seam is small on purpose:
export interface Showcase {
readonly id: string
readonly play: () => void | Promise<void> // throws on assertion failure
readonly message?: Schema.Top // Message-union Schema — read by `mcp` and `docs`
readonly model?: Schema.Top // Model Schema — read by `docs` and `mcp`
readonly dispatches?: ReadonlyArray<string> // Message tags the play sends — validated against `message`
}play is any thunk that throws when an assertion fails, so a Showcase is not tied to one
assertion library or one framework. message and model are optional; a Showcase that
declares neither still runs, and foldcase docs simply writes no page for it — there is
nothing to table. dispatches is optional too, and declared rather than observed — a
closure cannot be watched — so it is validated against the Message union: a tag the union
does not carry fails foldcase docs, and an absent declaration means unknown, never
"sends nothing" (an empty array says that).
Everything Foldcase does is derived from this one record. See ADR-0001.
A working app is in examples/counter — two Foldkit components, ten
Showcases (two of them holding a Scene), every dispatches declared, and the
Markdown foldcase docs writes from them. mise run
dogfood drives it under both bins in CI, so the example is a check as well as a demo.
A type-only import must say import type
The foldcase bin loads your catalog through Node's type stripping, and Node cannot tell
a type-only import from a value import — it emits a real ESM import for both. So a
showcase, or any module it reaches, that writes
import { Document, Html } from "foldkit/html" // these are typeswill not load: SyntaxError: The requested module 'foldkit/html' does not provide an export
named 'Document'. Write import type { Document, Html } from "foldkit/html" instead.
Foldcase names the cause and the fix in the failed-file line, so you need not recognise the
error yourself.
foldcase-bun erases the import itself and has no such rule.
Last updated Aug 4, 2026