Skip to content

sdk: plugins register demos, and the demos service lists and plays them - #608

Merged
foxnne merged 3 commits into
mainfrom
sdk/demo-registry
Oct 19, 2026
Merged

foxnne merged 3 commits into
mainfrom
sdk/demo-registry

Conversation

@foxnne

@foxnne foxnne commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Part of fizzyedit/plans#30 (step 1: a demo registry).

What changes

A plugin can ship demos, and anything can list and play the demos the app has. This is the seam the guides plugin (step 2) is built on, and that pixi's own guides come in through later.

  • host.registerDemo(.{ .name, .title, .tape }), called in register. The tape is ZON or binary bytes built with the SDK's tape module. The host copies the name, title and tape, so nothing points into the plugin's image, and drops them when the plugin unregisters. A name already registered is refused (error.DemoExists). Host.demos_generation moves whenever the list changes.
  • The demos service (sdk.services.demos), provided by fizzy in src/editor/Demos.zig:
    • list returns every demo fizzy can play, its own first, each with its length, chapters and origin (bundled, plugin, or hosted for step 3).
    • play(name, at_ms) plays one, from a chapter's start if asked. It returns unknown, busy (a live tape is driving) or unsaved (the same refusals a demo already had, now with a reason).
    • current names the demo playing. A tape loaded some other way since isn't mistaken for it.
    • generation is cheap to ask every frame.
  • The list is built lazily, and again only when the host's demos change. A plugin's tape is loaded the way a demo is played. One that wouldn't play is left out with a warning instead of being listed and then refused: a live tape, a step no loaded plugin applies, or a name fizzy already has.
  • fizzy.demo.play ("Demo: Play by Name…") plays any listed demo by name, from at. The bundled demos keep their own palette rows. A plugin's demo gets none, because a Command carries only its owner's state, so one row per runtime demo would need a context it doesn't have. The guides view (step 2) is where those are browsed.
  • Demo.startable says why a demo can't start, and Demo.loadDemoTape is the demo check playTape already used.

Changed from the plan: the plan had the automation service growing demos() and playDemo(). automation is about live tapes on whatever is open. A demo is fizzy's player setting the session aside, so it got a service of its own, and automation's version is untouched.

SDK impact

  • None
  • Core-only or additive: reaches plugins at the next SDK release
  • Fingerprint moved: recorded in sdk/src/version.zig, sdk_version untouched, PR labelled sdk
  • SDK release: bumps sdk_version, lists the sdk PRs since the last sdk-v* tag

For plugin authors: sdk.Demo and Host.registerDemo are new; sdk.services.demos is a new service at version 1. Nothing existing changed shape.

Verified

  • macOS:
    • zig build test: a new Host test checks that registerDemo keeps its own copy (the caller's buffers are overwritten after it returns), refuses a name twice, and drops a plugin's demos at unregisterPlugin, with the generation moving each time.
    • zig build test-integration (81/81 steps, after merging main with app: a demo gives back a folder opened from a zip's bytes #596 in): the demo test lists the bundled demos through the service, then registers three demos. A plugin demo is listed with two chapters; one reusing the name tour and a live tape are left out, with exactly those two warnings. The plugin demo plays by name to its end, named by current. Started at its second chapter, it's at that chapter. A tape loaded another way isn't current, and nothing is once unloaded.
    • zig build, zig build check-web, zig build test-sdk-version, zig build check-tests-reached, scripts/check-fmt.sh.
  • Windows:
  • Linux:
  • Web: compiles (check-web); not run in a browser.

Follow-ups

  • Step 2, the guides plugin, lists the service and plays what's clicked.
  • Step 3: hosted returns unknown until the web's demos/index.json is read into the list.

🤖 Generated with Claude Code

A plugin registers a demo with `host.registerDemo(.{ .name, .title, .tape })`: the tape as ZON
or binary bytes, copied by the host and dropped when the plugin goes. The new `demos` service
(`sdk.services.demos`, fizzy's in src/editor/Demos.zig) lists every demo fizzy can play, its own
first, each with its length, chapters and origin, and plays one by name, from a chapter if
asked; `current` names the one playing. A plugin's demo that would not play as one (a live
tape, a step nothing applies, a name fizzy has) is left out of the list with a warning.
`fizzy.demo.play` plays any of them by name.

The boundary's fingerprint moves; `sdk_version` does not.

Part of fizzyedit/plans#30 (step 1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne foxnne added the sdk Moves the plugin boundary's fingerprint; ships at the next SDK release label Oct 19, 2026
@foxnne
foxnne marked this pull request as ready for review October 19, 2026 01:26
@foxnne
foxnne enabled auto-merge (squash) October 19, 2026 03:24
@foxnne
foxnne merged commit bfd4445 into main Oct 19, 2026
11 checks passed
@foxnne
foxnne deleted the sdk/demo-registry branch October 19, 2026 03:24
foxnne added a commit that referenced this pull request Oct 19, 2026
Part of fizzyedit/plans#30 (step 2: the guides plugin).

## What changes

A new bundled plugin, **`guides`**, adds a view on the rail (a
graduation cap, under Files).

- The view lists what the `demos` service (#608) lists: fizzy's tour
first, then each plugin's own demos. Each guide is a row with its
length, and each chapter is an indented row with where it starts.
- Clicking a guide plays it from the start. Clicking a chapter plays the
guide from that chapter.
- The guide playing is drawn as selected.
- The service already refuses a demo over unsaved work or while a live
tape drives, and says why in a toast, so the view adds nothing for that.
- With no `demos` service, the view says the app plays no demos. With an
empty list, it says there are no guides yet.
- `guides.show` reveals the view wherever it lives.

It reaches nothing but `sdk.services.demos`, so an app on fizzy links it
or leaves it out like any other plugin. It's static-only, like
`devtools` and `archive`: no bundled dylib.

Fixed while looking at it: the time is laid out before the title. A dvui
box gives its children room in order, so a long title ("Markdown,
Previewed as You Type") had squeezed the length to "0…". Now the title
is cut short instead.

## SDK impact

- [x] None

## Verified

- [x] Web, in the browser pane, on `zig build web`:
- The view lists both guides: the tour at 1:09 with its five chapters
past the first, and the markdown guide at 0:26 with three.
- Clicking "Editing" (0:23) cut the tour straight into its Editing
chapter, which then typed on.
  - Reopening the view during the demo shows the tour drawn as selected.
  - No console errors.
- [x] macOS: `zig build`, `zig build test`, `zig build test-integration`
(81/81 steps), `zig build check-web`, `zig build check-tests-reached`,
`zig build test-sdk-version`, `scripts/check-fmt.sh`. The standalone
plugin build (`cd plugins/guides && zig build`, into a sandbox
`FIZZY_PROFILE`) builds the dylib, and its `zig-pkg` holds no Velopack.
- [ ] Windows:
- [ ] Linux:

No automated test drives the view. What it shows and plays comes from
the `demos` service, which #608's tests cover.

## Follow-ups

- Step 3: the web's hosted guides (`demos/index.json`) join the list.
- Step 4: `?view=guides` opens with the view showing, for the site.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne foxnne mentioned this pull request Oct 19, 2026
3 of 7 tasks
foxnne added a commit that referenced this pull request Oct 19, 2026
## What changes

`sdk_version` goes from 0.2.31 to 0.2.32. Merging this tags
`sdk-v0.2.32` and publishes its tarball. `sdk-repin` then asks each
store plugin to repin.

**Why now:** the SDK's shape has moved three times since 0.2.31, so
store plugins built for 0.2.31 don't load in a fizzy built from `main`.
Pixi's history trim (reading fizzy's history limit) needs #620's call.

## SDK impact

- [ ] None
- [ ] Core-only or additive: reaches plugins at the next SDK release
- [ ] Fingerprint moved: recorded in `sdk/src/version.zig`,
`sdk_version` untouched, PR labelled `sdk`
- [x] SDK release: bumps `sdk_version`, lists the `sdk` PRs since the
last `sdk-v*` tag

**`sdk` PRs since `sdk-v0.2.31`:**
- **#608: plugins ship demos.**
- `host.registerDemo(.{ .name, .title, .tape })` in `register`; the host
copies them and drops them on unregister.
- The `demos` service (`sdk.services.demos`, version 1) lists every demo
fizzy can play and plays one by name.
  - `sdk.Demo` is new. Nothing existing changed shape.
- **#606: a document's bytes without a save.**
- New optional `Plugin.VTable.documentBytes(state, doc, path,
allocator)`: the bytes a save would write, with none of a save's
bookkeeping (no format on save, no undo stop). Fizzy uses them for crash
checkpoints, a restart's carried work, a tape's file and
`fizzy.document.changed` listeners.
- An owner without it falls back to `documentSnapshot` +
`encodeSnapshot`, as before.
- **#620: the history limit is fizzy's.** `host.historyLimit()` (a new
`EditorAPI` member) is how many steps a document's undo history keeps:
fizzy's "Undo steps per document" setting, 0 for no limit. Each plugin
that owns a history trims its own.

**Also reaching plugins at this release** (no boundary change):
- **#616:** `Host.Activity` gains an optional `agent`, the author index
of an agent's edit.
- **#589:** the dvui pin moves to the zig-0.17 branch's floating-window
background hook (`docs/DEPENDENCIES.md`, patch 14).
- **#614:** glass panels in `core/dialogs.zig` get a drop's rim.

(`app/` changes since 0.2.31 are not compiled into plugins.)

## Verified

- [x] macOS: `scripts/check-fmt.sh` and `zig build test-sdk-version`
pass. The fingerprint is main's recorded value (`0x3d7fca8d3d211e73`);
only `sdk_version` changed.
- [x] Store plugins built against this `sdk/` (`--fork`, Zig 0.17,
macOS, sandbox profile): pixi, zig, atlas, drive and ghostty, each from
its `main`, all build.
- [ ] Linux: CI

## Follow-ups

- Repin and release the store plugins (pixi, zig, ghostty, drive, atlas;
then agent and chat, by hand) when `sdk-repin` opens their PRs.
- Make 0.2.32 live on fizzyed.it/app once the repinned plugins are in
the store: `gh workflow run web.yml --ref main -f go_live=0.2.32`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sdk Moves the plugin boundary's fingerprint; ships at the next SDK release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant