UI behaviour is a scene

A Foldkit16 program has a Model, Messages, an update function, and a view. A Command is a named side effect. Its result comes back as a Message. A Scene runs update, the view, and subscriptions together, without a browser.ΒΆ

Input and time are data

A Scene starts from a Model with given. Input is a step such as pointerDown, click, or Subscription.emit. A timer is a Command, so the test finishes it with Command.resolve.ΒΆ

The test does not use fake timers, a clock, or a DOM. Each step runs in order, and the result is the same on every run.ΒΆ

The Toast example

Devin Jameson shared a Scene test for the Foldkit UI Toast. The test name is β€œreturns a short swipe to zero while settling”.ΒΆ

  1. A pointer goes down on the toast.ΒΆ
  2. The pointer moves 30 pixels and releases.ΒΆ
  3. The swipe is too short, so the toast settles back. It does not dismiss.ΒΆ
  4. The test resolves the settle wait.ΒΆ
  5. The test resolves an old dismissal timer with a stale version.ΒΆ
  6. The toast still exists.ΒΆ

This paraphrase keeps the steps and drops the setup:ΒΆ

toast short swipe (paraphrase)
scene(
  { update: Toast.update, view },
  given(withEntry()),
  pointerDown(toast, { clientX: 100 }),
  Subscription.emit(MovedSwipePointer({ pointerId, clientX: 130 })),
  Subscription.emit(ReleasedSwipePointer({ pointerId, clientX: 130 })),
  expect(toast).toHaveAttr("data-swipe", "settling"),
  expect(toast).not.toHaveAttr("data-leave"),
  Command.resolve(
    WaitForSwipeSettled,
    CompletedWaitForSwipeSettled({ entryId, version: settlingVersion })
  ),
  expect(toast).not.toHaveAttr("data-swipe"),
  Command.resolve(
    WaitBeforeDismissal,
    CompletedWaitBeforeDismissal({ entryId, version: staleVersion })
  ),
  expect(toast).toExist()
);

Read the full test in the Foldkit source.ΒΆ

A race is a plain assertion

A race happens when an old result arrives after the state has moved on. In a Scene, the test resolves the old Command late. Then it asserts that nothing broke. The stale dismissal step above is that race.ΒΆ

Assert on what the view shows

Find elements by role, text, or data attribute. Do not assert on Model fields. A Model field can change name while the behaviour stays the same. A person and an agent can read the steps without the implementation.ΒΆ

The rat-stack reader uses the same shape for its copy button. The test clicks copy, resolves the copy, and checks the status text. Then it resolves the reset wait and checks that the label returns:ΒΆ

apps/web/test/reader.scene.test.ts Β· 1d4d3252
it("a copy confirms with text the reader can see and hear, then resets after the wait", () => {
  scene(
    { update, view },
    given(clipboardReady),
    click(copyButton),
    Command.expectExact(CopyReaderText({ id, text: promptText })),
    Command.resolve(CopyReaderText, Message.SucceededCopyReaderText({ id })),
    expect(role("status")).toHaveText("Copied βœ“"),
    expect(copyButton).toContainText("Copied βœ“"),
    Command.expectExact(WaitBeforeCopyReset({ id })),
    Command.resolve(
      WaitBeforeCopyReset,
      Message.CompletedWaitBeforeCopyReset({ id })
    ),
    expect(role("status")).toHaveText(""),
    expect(copyButton).toContainText(label),
    Command.expectNone()
  );
});

Plant a bug first

Break the rule the Scene protects. For the Toast, let a stale timer dismiss the entry. The Scene must fail. Restore the code and watch it pass.ΒΆ

See also: tests that earn their place22, model-based testing14, and the test-behaviour skill.ΒΆ

Sources

  1. Jameson, Devin. 2026. A Scene test for the Foldkit UI Toast.. Devin Jameson Β· X. Post; used for the short-swipe Toast scene and the case for synchronous behaviour tests. Accessed 2026-10-10.
  2. Foldkit contributors. 2026. Toast scene tests. Foldkit UI.. GitHub foldkit/foldkit. Scene test; used for the short swipe that settles and the stale dismissal timer. Accessed 2026-10-10.
  3. rat-stack contributors. 2026. Reader scene tests.. GitHub joelhooks/rat-stack. Scene test; used for a copy confirmation whose reset wait the test resolves. Accessed 2026-10-10.

Last changed 2026-10-09 in 3107109. Source on GitHub. Change log.

Linked from

  1. Agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.

    UI behaviour is a scene β€” A Foldkit Scene drives update, view, and subscriptions with input and time as plain data, then asserts on what the view shows.

  2. Change log. What changed in the files served here, newest first.

    UI behaviour is a scene β€” new page: docs(skills): add test-behaviour skill and the UI behaviour is a scene lore page

  3. Full agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.
  4. Rat Stack lore | rat-stack. Short, source-grounded notes on the ideas and decisions behind rat-stack.

    UI behaviour is a scene β€” A Foldkit Scene drives update, view, and subscriptions with input and time as plain data, then asserts on what the view shows.

  5. Source change history (log.md). The generated change log as Markdown.

    UI behaviour is a scene β€” new page: docs(skills): add test-behaviour skill and the UI behaviour is a scene lore page

  6. test-behaviour. Pick the test shape that proves a behaviour. Use when you ask "how do I test this?", add a test, test a domain rule, a lifecycle, a retry, a session, a UI interaction, a timer, or a race, or review a test that might be tautological.

    Read UI behaviour is a scene for the Foldkit UI Toast race.ΒΆ

  7. Tests prove behavior through supplied services. Supply services through Layers, choose properties or models for domain rules, and reject tests that cannot detect broken behavior.

    See also: properties, models, scenes, exploration, and the fence.ΒΆ