Typecaast

← All docs

Authoring configs by hand

A Typecaast simulation is one JSON config. The visual playground (https://typecaast.com/playground) is the easy way to build one, but the config is plain JSON — you can write or edit it by hand in any editor (or have an LLM do it). This is the reference for doing that without the playground.

Timing lives in its own guide. "Why are my messages too fast / too close together?" and "how do I get ~1–2s between messages?" are answered in pacing.md. Read that one for anything about gaps, delays, and typing speed.

The shape

{
  "$schema": "https://typecaast.com/schema/v1/typecaast.schema.json",
  "version": 1,
  "meta": {
    "canvas": { "width": 880, "height": 720 },
    "skin": { "id": "slack" }
  },
  "participants": [
    { "id": "me", "name": "You", "isSelf": true },
    { "id": "sam", "name": "Sam" }
  ],
  "pacing": { "readingWpm": 240 },
  "timeline": [
    { "type": "message", "from": "sam", "text": "ship it?" },
    { "type": "message", "from": "me", "text": "shipping 🚀" }
  ]
}

That's a complete, valid config. Five top-level keys:

KeyRequiredWhat it is
versionyesConfig schema version. Currently 1 (a literal).
metayesCanvas, skin, theme, fps — how it renders.
participantsyesWho is in the conversation.
pacingnoGlobal timing model. Omit for defaults. See pacing.md.
timelineyesThe ordered list of steps that play out.

Add the $schema line at the top and your editor gives you autocomplete and inline validation against the live schema. It's optional but recommended.

meta

"meta": {
  "canvas": { "width": 880, "height": 720 },
  "skin": { "id": "slack", "options": { "channel": "#alerts" } },
  "fps": 30,
  "fit": "reflow",
  "theme": "auto",
  "seed": 42,
  "background": "transparent",
  "assets": "inline",
  "composer": "auto",
  "loop": false
}
FieldDefaultNotes
canvas{ width, height } in px. Required. The authoring reference size / the video frame.
skin{ id, options? }. Required. id picks the skin (see Skins); options are skin-specific.
fps30Frames per second for video export.
fit"reflow"reflow (re-wrap to container), scale (CSS-scale the canvas), or fixed (pin to px).
theme"auto"light, dark, or auto (follows the host page; video resolves autolight).
seed42Seeds all deterministic jitter — same seed ⇒ identical timings.
background"transparent""transparent" or any CSS color.
assets"inline"inline (embed images as data URLs) or url (reference hosted images).
composer"auto"Reply box: auto (shown only while typing/sending), always, or never.
loopfalseAuto-replay at the end (unless the <Typecaast> consumer passes an explicit loop).

participants

An array of speakers. Reference them everywhere by id.

"participants": [
  { "id": "me",  "name": "You", "isSelf": true },
  { "id": "sam", "name": "Sam", "avatar": "https://…/sam.png", "color": "#5b3a8e" },
  { "id": "bot", "name": "PostHog", "kind": "app" }
]
FieldRequiredNotes
idyesStable id used by from / target / by in the timeline.
nameyesDisplay name.
avatarnoData URL, or a hosted URL (per meta.assets).
colornoAccent color (CSS) some skins use for the author.
isSelfnoMarks the viewer — rendered on the "self" side and as the composer's author.
kindno"person" (default) or "app" (a bot/integration; skins render it distinctly).

Set isSelf: true on exactly one participant if you use the composer (composerType / send).

timeline — the steps

The timeline is an ordered array. Each step has a type and its own fields. Two fields are shared by every step:

Steps that target an earlier message take a target: either a message's id or the literal "$prev" (the most-recent message). target defaults to "$prev".

The step types

typePurpose
messageAn incoming message (optionally preceded by a typing indicator).
typingA standalone typing indicator (no message need follow).
composerTypeThe isSelf participant typing into the composer, char by char.
sendCommit the composer's current text to the thread.
reactionAn emoji reaction landing on a target message.
editReplace a previously sent message's body.
deleteRemove a previously sent message.
readReceiptA read-receipt indicator (skins that support it).
systemA system / notice line (e.g. "Sam joined #alerts") — not a chat message.
delayAn explicit pause on the timeline. See pacing.md.

message

{ "type": "message", "from": "sam", "text": "on it" }
FieldNotes
fromParticipant id. Required.
textMessage body. Supports Slack-style mrkdwn — see message-content.md.
images[{ src, alt?, width?, height? }] — in-message images.
contentExplicit Block Kit content nodes (wins over text) — see message-content.md.
typingShow a typing indicator first: true, or { "showTypingFor": 1800 } (ms).

Model an app "card" as a message from an app participant carrying content (header/section/context/actions/…), not a system step.

typing

{ "type": "typing", "from": "sam", "showTypingFor": 1500 }

showTypingFor is in milliseconds (defaults to ~1500ms if omitted).

composerType and send

{ "type": "composerType", "from": "me", "text": "let me check…" },
{ "type": "send" }

composerType types into the composer (the from should be your isSelf participant); send then posts it. send.from is optional — it inherits the composer's author. composerType.typingDuration (ms) overrides the computed typing time.

reaction

{ "type": "reaction", "target": "$prev", "emoji": "🚀", "delay": 800 }
FieldNotes
emojiThe emoji. Required.
targetMessage id or "$prev" (default).
fromOptional reactor.
shortcodeOptional shortcode without colons (e.g. "rocket") — shown in some skins' tooltips.
delayGap (ms) from when the target appears before the reaction lands. This delay is a field on the reaction, not the delay step type — don't confuse them (see pacing.md).

edit and delete

{ "type": "edit", "target": "m2", "text": "fixed typo" },
{ "type": "delete", "target": "m3" }

Both default target to "$prev". edit takes the same body fields as message (text / images / content).

readReceipt

{ "type": "readReceipt", "by": "sam", "target": "m2" }

by (who read it) and target (up to which message) are both optional.

system

{ "type": "system", "text": "Sam joined #alerts" }

A centered notice / tool-output line, rendered distinctly per skin. Takes the same body fields as message.

delay

{ "type": "delay", "duration": 1500 }

An explicit pause. duration is in milliseconds and is required. This is the most predictable way to space messages out — see pacing.md.

Skins

meta.skin.id selects the skin. The conversation content is the same regardless of skin; the skin decides how it looks. Built-in ids:

slack · telegram · claude-code · imessage · messages-macos · whatsapp · cursor · discord

Some steps aren't rendered by every skin (e.g. a terminal skin has no reactions). The skin's capabilities decide; unsupported steps are dropped. Pick a skin that supports the steps you use.

Validate it

Always validate after editing by hand:

See also

Edit this guide on GitHub.