stillmade-sdk 0.1.48 → 0.1.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/AGENTS.md +6 -2
  2. package/AGENT_CONTROL.md +15 -0
  3. package/FRAME_VIEWS.md +24 -0
  4. package/INTERFACE_DESIGN.md +40 -5
  5. package/MODULES.md +2 -2
  6. package/PASTE_TO_LLM.md +26 -19
  7. package/SDK.md +126 -39
  8. package/UI_AESTHETIC.md +15 -0
  9. package/blocks/generate/package.js +1 -1
  10. package/blocks/generate/versions/1.1.0/package.js +471 -0
  11. package/blocks/generate/versions/1.2.0/package.js +555 -0
  12. package/blocks/native/sm.board-editor/versions/3.0.0/package.js +50 -17
  13. package/blocks/native/sm.board-editor/versions/3.1.0/package.js +1037 -0
  14. package/blocks/native/sm.editor/versions/3.0.0/package.js +128 -83
  15. package/blocks/native/sm.editor/versions/3.1.0/package.js +1299 -0
  16. package/blocks/native/sm.panel-strip/versions/3.0.0/package.js +45 -12
  17. package/blocks/native/sm.panel-strip/versions/3.1.0/package.js +479 -0
  18. package/blocks/native/sm.voiceover/versions/3.0.0/package.js +9 -3
  19. package/docs/build/block-builder-contract.md +6 -2
  20. package/docs/build/frame-views.md +24 -0
  21. package/docs/build/interface-design.md +40 -5
  22. package/docs/build/modules.md +2 -2
  23. package/docs/reference/authoring-guide.md +4 -4
  24. package/docs/reference/execution.md +15 -1
  25. package/docs/reference/first-party-blocks.md +4 -4
  26. package/docs/reference/parity.md +1 -1
  27. package/docs/reference/ports.md +1 -1
  28. package/docs/reference/view-bridge.md +3 -0
  29. package/docs/tools/llm.md +26 -19
  30. package/package.json +1 -1
  31. package/packages/block-cli/dev-host/assets/host.js +161 -33
  32. package/packages/block-cli/dev-host/assets/index.css +1 -1
  33. package/packages/block-platform/block-readiness.js +16 -3
  34. package/packages/block-platform/canvas.js +13 -1
  35. package/packages/block-platform/catalog.js +32 -5
  36. package/packages/block-platform/flow-inspection.js +16 -2
  37. package/packages/block-platform/listings.js +17 -3
  38. package/packages/block-platform/steps.js +4 -1
  39. package/packages/block-sdk/admission.js +1 -1
  40. package/packages/block-sdk/agent-inputs.js +4 -2
  41. package/packages/block-sdk/builder-contract.js +7 -4
  42. package/packages/block-sdk/first-party-identities.js +9 -1
  43. package/packages/block-sdk/first-party-packages.js +11 -1
  44. package/packages/block-sdk/generation-catalog.js +55 -5
  45. package/packages/block-sdk/generation-models.js +60 -0
  46. package/packages/block-sdk/index.d.ts +15 -1
  47. package/packages/block-sdk/index.js +2 -1
  48. package/packages/block-sdk/input-source-policy.js +2 -1
  49. package/packages/block-sdk/module-hosted.js +13 -5
  50. package/packages/block-sdk/module-worker.js +1 -1
  51. package/packages/block-sdk/port-types.js +6 -2
  52. package/packages/block-sdk/semantic-contracts.d.ts +1 -1
  53. package/packages/block-sdk/semantic-relationships.js +3 -0
  54. package/packages/block-sdk/semantic-schema.js +4 -1
  55. package/packages/block-sdk/shared-controls.js +4 -1
  56. package/packages/block-sdk/staged-releases.js +2 -1
  57. package/packages/block-sdk/view-bridge.js +3 -0
  58. package/packages/block-sdk/view-design.js +54 -10
  59. package/packages/block-sdk/view-renderer.js +19 -6
  60. package/packages/design-system/block-kit.css +12 -3
@@ -1,6 +1,6 @@
1
1
  # Block builder contract
2
2
 
3
- Contract `stillmade.block-builder@1.18.0`. Humans, coding agents, imported source, GitHub repositories and generated code all adapt to the Block platform; anything that becomes a StillMade Block obeys this contract.
3
+ Contract `stillmade.block-builder@1.20.0`. Humans, coding agents, imported source, GitHub repositories and generated code all adapt to the Block platform; anything that becomes a StillMade Block obeys this contract.
4
4
 
5
5
  ## Source of truth
6
6
 
@@ -69,7 +69,7 @@ These are StillMade's defaults: unless the person or the creator asked for somet
69
69
 
70
70
  ## Interface
71
71
 
72
- If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, drawn before the first run in the shape and size of what is coming (`data-ratio="9:16"` for a vertical clip, lines for text, a player bar for sound) so every part of the screen is in place at once and the result fills a place the person already sees instead of popping in somewhere unexpected; StillMade draws it the same way on the Steps it draws; on a wide screen what goes in sits on the left and the result on the right, side by side, and they stack only on narrow screens and phones, the result's place after the inputs with the job's action row on it (`sm-actions` and `sm-status` inside `sm-stage`: the kit puts the status top-left and the actions top-right in the player's cream blocks, over a band above the media; the run button is always the rightmost, so a button that spends credits never moves and making again is pressed where it made the first time, with Download and other result actions to its left), picks as pictures in `sm-choices`, the picked one in the kit's ink fill (never restyled to an outline, a tint or the page's background) so it reads at a glance, one solid `sm-primary` naming the action in the job's words ("Plan the tour", "Generate voiceover"; never "Run" or "Make it") and no price (StillMade's run button adds it), extras in a closed `sm-more`; a look the person names wins. Fit one laptop screen without scrolling: label beside control, status in a few words, files as tiles, rare tools as icons, (i) on unclear controls. These compact rules are for a job or an ingredient. Size the view to the Block's tier (block-tier.js): an app, a workspace a person works in for a while (video editor, 3D configurator, CAD or drawing tool, photo editor), declares `manifest.kind: "workspace"` and looks and works like that kind of app instead of a compact page, and StillMade's checks (admission, readiness, its own builder) then apply the app rules instead of the compact ones: no limit on controls in view, no Block kit page, its own layout, the craft's own words, and only its run button (`data-stillmade-action="run"`) held to naming what it makes: its canvas or stage fills the view, with the panels, toolbars, inspector or timeline such an app has, direct manipulation (select, drag, zoom), keyboard shortcuts and undo; it keeps the host appearance and Block kit controls, its panels may scroll, and on a phone its tools fold into sheets while the canvas stays usable. Never shrink an app into a form of fields and one button. StillMade centers a standard view narrower than its Step and zooms a view to fill its space; a stage shows what it holds in its middle as one group (a caption stays with its picture) and several results fill its width as one row, on a Block's screen and on the pages StillMade draws; an app that lays itself out (full-size canvas or WebGL, its own scroll, pan or zoom, its own Ctrl/Cmd +/- keys) puts `data-stillmade-layout="own"` on its outermost element, and StillMade then never centers, zooms or takes those keys. StillMade draws one player for every `<video controls>` (play, timeline, time, sound and full screen, shown for 3 s the first time it comes into view, then while the pointer is on the video, with keys and touch) and a full-screen button on result pictures in `sm-stage` or `sm-gallery` and on any element marked `data-stillmade-expand` (the same value makes one set of pictures, `off` removes it); `data-stillmade-player="native"` keeps the browser's controls, and a `<video>` without `controls` is the Block's own and is never touched. A picture or clip that cannot show here (kept on another device, or it did not open) keeps its place as a quiet `sm-media-off` tile in its own shape with the `image-off` glyph and the reason in its label, never words in its place. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Mark each button `data-stillmade-action` (`run` on the one that runs the Block, which the device checks press once; a page that checks first keeps `run` on that button for both presses; what it does on the others, `"play sample"`) and label each field: Chat presses and fills them. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Show no raw JSON, internal IDs, filesystem details, provider terms or developer settings unless the Block is a developer tool. When a person chooses among options they need to hear or see (voices, music, fonts, looks, templates, images), let them preview each option before choosing: a sample or thumbnail from the Block's own files or the host's free samples, never a paid generation. Skip previews only when there is nothing to preview. Choices StillMade owns (voices, models in the run review) already show samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (the inputs, the button in normal flow, never pinned, then the result's place under it), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.
72
+ If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, drawn before the first run in the shape and size of what is coming (`data-ratio="9:16"` for a vertical clip, lines for text, a player bar for sound) so every part of the screen is in place at once and the result fills a place the person already sees instead of popping in somewhere unexpected; StillMade draws it the same way on the Steps it draws; on a wide screen what goes in sits on the left and the result on the right, side by side, and they stack only on narrow screens and phones, the result's place after the inputs with the job's action row on it (`sm-actions` and `sm-status` inside `sm-stage`: the kit puts the status top-left and the actions top-right in the player's cream blocks, over a band above the media; the run button is always the rightmost, so a button that spends credits never moves and making again is pressed where it made the first time, with Download (`data-stillmade-action="download"`, which needs no code: StillMade saves the stage's picture or clip under the export policy) and other result actions to its left), picks as pictures in `sm-choices`, the picked one in the kit's ink fill (never restyled to an outline, a tint or the page's background) so it reads at a glance, one solid `sm-primary` naming the action in the job's words ("Plan the tour", "Generate voiceover"; never "Run" or "Make it") and no price (StillMade adds it) once the view says what its next run makes (`StillMade.nextRun`: its `media.generate` items or hosted operations), extras in a closed `sm-more`; a look the person names wins. Fit one laptop screen without scrolling: label beside control, status in a few words, files as tiles, rare tools as icons, (i) on unclear controls. These compact rules are for a job or an ingredient. Size the view to the Block's tier (block-tier.js): an app, a workspace a person works in for a while (video editor, 3D configurator, CAD or drawing tool, photo editor), declares `manifest.kind: "workspace"` and looks and works like that kind of app instead of a compact page, and StillMade's checks (admission, readiness, its own builder) then apply the app rules instead of the compact ones: no limit on controls in view, no Block kit page, its own layout, the craft's own words, and only its run button (`data-stillmade-action="run"`) held to naming what it makes: its canvas or stage fills the view, with the panels, toolbars, inspector or timeline such an app has, direct manipulation (select, drag, zoom), keyboard shortcuts and undo; it keeps the host appearance and Block kit controls, its panels may scroll, and on a phone its tools fold into sheets while the canvas stays usable. Never shrink an app into a form of fields and one button. StillMade centers a standard view narrower than its Step and zooms a view to fill its space; a stage shows what it holds in its middle as one group (a caption stays with its picture) and several results fill its width as one row, on a Block's screen and on the pages StillMade draws; an app that lays itself out (full-size canvas or WebGL, its own scroll, pan or zoom, its own Ctrl/Cmd +/- keys) puts `data-stillmade-layout="own"` on its outermost element, and StillMade then never centers, zooms or takes those keys. StillMade draws one player for every `<video controls>` (play, timeline, time, sound and full screen, shown for 3 s the first time it comes into view, then while the pointer is on the video, with keys and touch) and a full-screen button on result pictures in `sm-stage` or `sm-gallery` and on any element marked `data-stillmade-expand` (the same value makes one set of pictures, `off` removes it); `data-stillmade-player="native"` keeps the browser's controls, and a `<video>` without `controls` is the Block's own and is never touched. A picture or clip that cannot show here (kept on another device, or it did not open) keeps its place as a quiet `sm-media-off` tile in its own shape with the `image-off` glyph and the reason in its label, never words in its place. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Mark each button `data-stillmade-action` (`run` on the one that runs the Block, which the device checks press once; a page that checks first keeps `run` on that button for both presses; what it does on the others, `"play sample"`) and label each field: Chat presses and fills them. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Show no raw JSON, internal IDs, filesystem details, provider terms or developer settings unless the Block is a developer tool. When a person chooses among options they need to hear or see (voices, music, fonts, looks, templates, images), let them preview each option before choosing: a sample or thumbnail from the Block's own files or the host's free samples, never a paid generation. Skip previews only when there is nothing to preview. Choices StillMade owns (voices, models in the run review) already show samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (the inputs, the button in normal flow, never pinned, then the result's place under it), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.
73
73
 
74
74
  ## Shared editing
75
75
 
@@ -79,6 +79,10 @@ Projects are multiplayer, and StillMade builds it: every ordinary control on a B
79
79
 
80
80
  Reach external services only through StillMade's hosted operations, the connections service (`connection.execute`), or, in a module Block, `stillmade.net.fetch` to origins declared in `permissions.network` (with the key each needs), `stillmade.net.connect` for a live WebSocket to one of them, and `stillmade.connections.call` to adapters or catalog apps in `permissions.connections`; never put keys in code, inputs or headers: StillMade adds each person's own key on its servers. The host owns the model and provider choice, BYOK keys, StillMade credits, spending consent, authorization, token refresh, rate limits, retries and provider errors; a Block never receives a secret and never names a provider unless it must. Treat provider unavailability, timeout, cancellation and partial failure as outcomes the Block reports, not crashes: a call the result needs throws and fails the run (see Useful failures); an optional part's failure is caught, given its fallback and named in an optional `notes` text output. Generated images, video, voices, music and sound effects come from hosted operations or the `media.generate` action, paid with the user's StillMade credits at StillMade's prices; for structured model output declare a `json` output with `capability.schema` instead of parsing prose. When a Block needs npm libraries, WebAssembly (threaded: `"threads": true` in module.json) or heavy compute, use `runtime: "module"` and reach hosted operations through `stillmade.hosted.run` with the operations listed in `permissions.capabilities`. Decode, cut, join, re-encode, extract or normalise audio and video with `stillmade.media.transform` on the device; never ask for a server render. For a rich interactive interface (React or another framework, SVG, canvas, drag), use a frame view: `src/view.config.json` with `{"runtime":"frame"}` and `src/view.js` bundled as one IIFE, colored with StillMade's theme variables. For files and structured data use the `file`, `document`, `table`, `url`, `date` and `color` types and their generated controls and presentations, not JSON text.
81
81
 
82
+ ## Models come from StillMade
83
+
84
+ A Block never names a model, in its capability, its module code or its screen, and never keeps a list of models: the person picks the model on the Step from the models StillMade runs that day, so models StillMade adds or retires later reach the Block with no new version (owner, November 8, 2026). Ask for what the result needs instead, in `settings` (`aspectRatio`, `resolution`, `quality`, `duration`, `generateAudio`): with no model picked StillMade runs its default model, or the first one that makes what was asked, and a value the chosen model does not make falls back to its own. A screen that offers its own model picker draws it from `StillMade.models(operation)` when it opens (each model's settings arrive as data, so a setting StillMade adds later draws too) and passes the picked `id` as `settings.model`. A model StillMade retires runs on the one that replaced it.
85
+
82
86
  ## Dependencies
83
87
 
84
88
  A Block must declare everything it requires and must not assume anything is installed. Portable packages carry no package dependencies (`portable.dependencies` is empty): inline small, license-compatible code and record its license and notice in `manifest.license` and `manifest.provenance`. The copyright holder in the Block's own `provenance.notice` is the person it is made for: replace the placeholder `create` writes with their name, never StillMade, the SDK or yourself. Desktop-only tools are reached solely through declared desktop permissions and the host desktop bridge. If a dependency cannot be delivered through the Block runtime, report the incompatibility instead of assuming the user's machine has it.
@@ -198,6 +198,30 @@ view, and also when the file at that place changed after the view was given it
198
198
  tries again. StillMade hides these buttons wherever it hides the + tile. The SDK
199
199
  preview changes its example input the same way.
200
200
 
201
+ ## What Chat can use
202
+
203
+ The project's Chat reads and uses a frame view's controls the way a person does:
204
+ every labelled field (`<label>`, `aria-label` or `title`), and every button
205
+ with words of its own, by those words. A frame view's screen is built by its
206
+ code as it runs, so no check reads it beforehand; a button marked
207
+ `data-stillmade-action` is offered by that name instead (`run` for the button
208
+ that runs the Block). A button with no words, an icon alone, needs an
209
+ `aria-label` to be offered. Fields inside `data-stillmade-local` are the
210
+ person's own and never offered. Whatever a press starts keeps its own
211
+ approval: spending still asks the person (`StillMade.allowSpending` refuses
212
+ Chat).
213
+
214
+ The one exception is a run of every Step that the person approved on Chat's
215
+ card for the whole project. There StillMade presses the screen's run control
216
+ for them: the button marked `data-stillmade-action="run"`, else the button
217
+ worded as the Block's `manifest.action.label`. Move that mark to whichever
218
+ button moves the job forward now (plan, then build, then save): StillMade
219
+ presses it, waits for the runs it starts, and presses the next one until the
220
+ Step's result is saved. Only while StillMade presses it, the runs it starts,
221
+ and one `StillMade.allowSpending` request for each press, draw on that approval,
222
+ and only while they fit what is left; past it, or at any other time, the person
223
+ is asked as usual.
224
+
201
225
  ## Theming
202
226
 
203
227
  The theme variables from StillMade's design system are always defined:
@@ -101,7 +101,7 @@ place (rule 24).
101
101
  | `sm-media-off` | A picture or clip that cannot show here (on another device, or it did not open): a quiet tile in its own shape (`--sm-photo-ratio`, the tile's shape in a gallery) holding the `image-off` glyph, the reason in `aria-label` and `title`, no words inside. |
102
102
  | `sm-add` | The + tile that adds files to an input: `<button type="button" class="sm-add" data-stillmade-add="listingPhotos" aria-label="Add photos">`. StillMade opens its own picker and saves the files; they arrive through `onInput`. In an `sm-gallery` it takes the tile's shape. |
103
103
  | `sm-scrim` | Words over a photo or video, inside a positioned box such as an `sm-stage`: a see-through shade of the page under them, in every theme. `var(--scrim)` is the same shade for your own rules. |
104
- | `sm-glyph` | A small icon in `currentColor`: `play`, `pause`, `plus`, `close`, `check`, `chevron`, `chevron-left`, `chevron-right`, `upload`, `image`, `image-off`, `video`, `audio`, `text`, `spark`, `info`, `warning`, `spinner`. Works in standard views, which cannot use SVG. |
104
+ | `sm-glyph` | A small icon in `currentColor`: `play`, `pause`, `plus`, `close`, `check`, `chevron`, `chevron-left`, `chevron-right`, `upload`, `download`, `image`, `image-off`, `video`, `audio`, `text`, `spark`, `info`, `warning`, `spinner`. Works in standard views, which cannot use SVG. |
105
105
 
106
106
  **Function first.** When the person building a Block does not ask for a look,
107
107
  use the kit as is: they want it to work and be easy to use. When they name a
@@ -133,6 +133,17 @@ the same single priced button. Pressing a priced button runs at that price; a
133
133
  confirmation appears only when the server's price is higher than the one shown.
134
134
  Do not write credit numbers in the Block.
135
135
 
136
+ A custom view's own run button gets its price the same way (November 8, 2026):
137
+ the view tells StillMade what its next run makes, and StillMade writes the price
138
+ after the button's words, "Generate image · 4 credits". Call
139
+ `StillMade.nextRun({items})` with the `media.generate` items the run will ask for
140
+ (the prompt left out), or `StillMade.nextRun({hosted: ["text.generate"]})` for
141
+ the hosted operations it calls, whenever a choice that changes the price changes.
142
+ StillMade waits until the choices settle, prices them as the run will be priced,
143
+ leaves the button where it is, shows no price while the run works or to a
144
+ viewer, and the Block never sees the number. `StillMade.nextRun(null)` takes it
145
+ off.
146
+
136
147
  ## 3. Speak the job
137
148
 
138
149
  These are StillMade's defaults for every Block, and they apply wherever the
@@ -289,7 +300,9 @@ exception, never unrelated technical UI.
289
300
  segmented control in StillMade); never restyle it to an outline, a tint or
290
301
  the page's background. A locked row (`disabled`, or a person who can only
291
302
  view) keeps its pick filled, fades the other options and does not react to
292
- the pointer.
303
+ the pointer. A picture, or a pick shown by its picture (a thumbnail, a
304
+ shot, a swatch), is a rounded rectangle in the kit's radii, never a circle,
305
+ unless the person asks for one; readiness notes a circle.
293
306
  22. Media that cannot show here (kept on another device, or it did not open)
294
307
  keeps its place as a quiet `sm-media-off` tile in its own shape with the
295
308
  `image-off` glyph, the reason in its `aria-label` and `title`. Never words
@@ -320,8 +333,12 @@ exception, never unrelated technical UI.
320
333
  then "Make video again"); what the result adds (Download, Share) goes to
321
334
  its left. Controls on media (StillMade's player, the full-screen square)
322
335
  show for 3 seconds the first time the media comes into view, then only
323
- while it is hovered or tapped. StillMade's own Step pages do the same. An
324
- app that lays itself out keeps its own toolbar.
336
+ while it is hovered or tapped. Under a rendered video the stage shows its
337
+ shots with their times and the line being said: a video from
338
+ `timeline.render` carries them as `parts` (`shots` with `start`, `end` and
339
+ the `source` each shows, `lines` with `start`, `end` and `text`), and the
340
+ status carries its length. StillMade's own Step pages do the same. An app
341
+ that lays itself out keeps its own toolbar.
325
342
 
326
343
  ```html
327
344
  <section class="sm-stage" data-ratio="9:16">
@@ -334,6 +351,17 @@ exception, never unrelated technical UI.
334
351
  </section>
335
352
  ```
336
353
 
354
+ The Download button needs no code (November 8, 2026): StillMade saves the
355
+ largest picture or clip the stage shows (one the view showed with
356
+ `previewImage` or `previewMedia`) through its own download, under the
357
+ export policy, as the Library does. It works in frame views. An icon fits
358
+ as well: `<button type="button" class="sm-icon" data-stillmade-action="download"
359
+ aria-label="Download" title="Download"><span class="sm-glyph"
360
+ data-glyph="download" aria-hidden="true"></span></button>`. From the view's
361
+ own code, `StillMade.download(media)` saves one picture, clip or sound
362
+ StillMade gave the screen. The run button's price is StillMade's too
363
+ (`StillMade.nextRun`, under "Decide every sentence").
364
+
337
365
  ## 4a. App rules
338
366
 
339
367
  An app (`"kind": "workspace"`) follows these instead of the job-only rules
@@ -397,7 +425,14 @@ check list.
397
425
  - Admission also warns, and `stillmade-block ready` fails the "UI works" check
398
426
  (DOD-15), when a button says "Run", "Run step" or "Submit", when the
399
427
  interface shows a credit price, or when it has a page heading
400
- (`blockKitProblems`; the failing rules are `BLOCK_KIT_READINESS_RULES`). StillMade's own Block builder also sends back a custom
428
+ (`blockKitProblems`; the failing rules are `BLOCK_KIT_READINESS_RULES`).
429
+ The same goes, standard and frame views alike, for a job page built from the
430
+ kit (`sm-block`) whose result is a picture, clip, sound, asset or document
431
+ and that has no `sm-stage`, or whose own run button
432
+ (`data-stillmade-action="run"`) is not in the stage's own `sm-actions` with
433
+ an `sm-status` beside it on the stage (rule 24; November 8, 2026). A page not
434
+ built from the kit is held to the kit rule instead, and a frame view that
435
+ builds its stage in code is not checked by the source. StillMade's own Block builder also sends back a custom
401
436
  view that is not built from `sm-block`, unless the person asked for their
402
437
  own look (`appearance: "original"`).
403
438
  - Admission warns, and readiness lists a "Wording" limitation, when a button
@@ -56,7 +56,7 @@ against the manifest's port types.
56
56
  | --- | --- |
57
57
  | `input` | The validated input values (the same object as the first argument). |
58
58
  | `signal` | An `AbortSignal` that fires when the person cancels. |
59
- | `progress(value, label)` | Reports progress from 0 to 1 with a short label. Each report extends the run limit. |
59
+ | `progress(value, label, items)` | Reports progress from 0 to 1 with a short label. Each report extends the run limit. `items` (`{done, total}`, optional) says how many of the run's items are finished, such as pictures made; StillMade shows it ("9 of 14") while Chat runs the project's Steps. |
60
60
  | `log(...values)` | Adds a line to the run log (up to 500 lines). |
61
61
  | `asset(path)` | A copy of one of the module's own files, such as a `.wasm` build, as an `ArrayBuffer`. |
62
62
  | `files.read(ref)` | The bytes, as an `ArrayBuffer`, of a saved image, video, audio, asset or file (a PDF, text, CSV, Markdown or JSON file on a `file` input) the Block received as input, or that StillMade made in this run: files it wrote, transform results and generated media. Media the person kept on their device (a `sm-media://device/...` address in a project Step) reads the same way, without leaving the device. Other files cannot be read, and transforms, hosted calls and actions accept only these references too; a hosted call or action gets a cloud copy of device media, made once per version. A reference is the file its `kind`, `assetId`, `versionId` and `url` name, so `{kind, assetId, versionId, url}` rebuilt from a `media.generate` result item (without its `key`) is the same file; any other file is refused with `MODULE_FILE_READ`, by `stillmade-block test --mock`, `ready` and `gate` as by StillMade. |
@@ -119,7 +119,7 @@ const { value, failures } = await stillmade.actions.run({
119
119
  resultPort: null,
120
120
  request: { items: [
121
121
  { key: 'front', operation: 'image', prompt: `The front of ${input.address} at dusk`,
122
- settings: { model: 'nano-banana-pro', aspectRatio: '16:9' } },
122
+ settings: { aspectRatio: '16:9' } },
123
123
  { key: 'tour', operation: 'speech', prompt: input.script },
124
124
  ] },
125
125
  });
@@ -9,7 +9,7 @@ Every finished Block must support automatic remixing through the standard SDK co
9
9
 
10
10
  ### Package format
11
11
 
12
- A Block package is StillMade SDK 0.1.0 JSON. Prefer {manifest,recipe,tests} for supported operations. Otherwise use {manifest,code,tests} with runtime:"javascript": code is a synchronous function body receiving input and returning declared output bindings, e.g. return {text:input.text.toUpperCase()}. JavaScript runs in isolated QuickJS: no fetch, DOM, Node APIs, modules, dependencies, timers, or host services; no promises, imports, require, eval or Function. Limits: 500 ms execution, 16 MiB heap, 256 KiB code, 4 MiB JSON I/O. Include concrete passing fixtures that reflect the requested behavior, including an edge case; never approximate a hosted model call with fabricated deterministic output. Manifest fields: schemaVersion:1,sdkVersion:"0.1.0",id:"creator.name",version:"1.0.0",name,description,kind:"task" (or "workspace" for an app a person works in, such as a CAD tool or photo editor),runtime:"recipe", "javascript", "comfyui", or "capability",inputs/outputs as named {type,default?,min?,max?,role?,primary?}. Usually mark one primary input and output with primary:true. Semantic roles such as source_image and character_reference are distinct and must never be guessed,permissions:{project:[],network:[],filesystem:[],secrets:[]},ui:[{control,port}]. Recipe: {schemaVersion:1,steps:[{id,op,args}],outputs}. References: {"$input":"port"}, {"$step":"step-id"}. Supported operations: {"value.copy":{"description":"Copy a JSON value without changing it.","args":["value"]},"text.trim":{"description":"Remove leading and trailing whitespace.","args":["text"]},"text.replace":{"description":"Replace literal text; no regular expressions.","args":["text","find","replacement"]},"text.join":{"description":"Join an array of text values.","args":["items","separator"]},"image.thicken-lines":{"description":"Expand dark outlines while preserving other colors.","args":["image","thickness","threshold"]}}. Port types: text, number, integer, boolean, json, object, image, video, audio, asset, script, transcript, brief, bible, shot-plan, panel-document, board-document, scene-document, timeline, timeline-edit, file-bundle, workspace-edit, timeline-range, research, character, location, style, pipeline, approval, scene, shot, mask, depth, pose, metadata, project-context, file, document, table, url, date, color. A value is exactly: text: a string; number: a number; integer: a whole number; boolean: true or false; json: any JSON value; object: a plain JSON object; image: {kind: 'image'; assetId: string; versionId: string; url: string; mimeType?: string; bytes?: number; name?: string; role?: string; width?: number; height?: number}; video: {kind: 'video'; assetId: string; versionId: string; url: string; mimeType?: string; bytes?: number; name?: string; role?: string; width?: number; height?: number; duration?: number}; audio: {kind: 'audio'; assetId: string; versionId: string; url: string; mimeType?: string; bytes?: number; name?: string; role?: string; duration?: number; sampleRate?: number; channels?: number}; asset: {kind: 'image' | 'video' | 'audio' | 'asset'; assetId: string; versionId: string; url: string; mimeType?: string; bytes?: number; name?: string; role?: string}; script: {schemaVersion: 1; text: string; [key: string]: unknown}; transcript: {schemaVersion: 1; text: string; words: Array<{text: string; start: number; end: number}>}; file: {kind: 'file'; assetId: string; versionId: string; mimeType: 'application/pdf' | 'text/plain' | 'text/csv' | 'text/markdown' | 'application/json' | 'application/zip'; url?: string; name?: string; bytes?: number}; document: {schemaVersion: 1; format: 'markdown' | 'plain'; text: string; title?: string}; table: {schemaVersion?: 1; columns: string[]; rows: Array<Record<string, string | number | boolean | null>>}; url: an absolute http or https address string; date: an ISO 8601 date string ('2026-10-09' or '2026-10-09T14:30'); color: a hex color string ('#ff8800' or '#ff8800cc'). A file value is a saved file: one the person gives the Block (a module Block reads its bytes with stillmade.files.read) or what stillmade.files.write returns in a module Block; an image may also be decoded RGBA pixels {width, height, data} in recipe and JavaScript Blocks, which hold only 4 MiB of JSON (a picture of about 300 by 300 pixels), so a Block that joins photos into one picture (side by side, a grid, a contact sheet, a poster) is a module Block calling media.transform image.compose. Every other type is {schemaVersion: 1; [key: string]: unknown} with the fields its own section gives. A type ending in [] is a plain array of those values (audio[] is a list of audio references, never objects that wrap them). Include test {name,input,expected} where expected is output bindings.
12
+ A Block package is StillMade SDK 0.1.0 JSON. Prefer {manifest,recipe,tests} for supported operations. Otherwise use {manifest,code,tests} with runtime:"javascript": code is a synchronous function body receiving input and returning declared output bindings, e.g. return {text:input.text.toUpperCase()}. JavaScript runs in isolated QuickJS: no fetch, DOM, Node APIs, modules, dependencies, timers, or host services; no promises, imports, require, eval or Function. Limits: 500 ms execution, 16 MiB heap, 256 KiB code, 4 MiB JSON I/O. Include concrete passing fixtures that reflect the requested behavior, including an edge case; never approximate a hosted model call with fabricated deterministic output. Manifest fields: schemaVersion:1,sdkVersion:"0.1.0",id:"creator.name",version:"1.0.0",name,description,kind:"task" (or "workspace" for an app a person works in, such as a CAD tool or photo editor),runtime:"recipe", "javascript", "comfyui", or "capability",inputs/outputs as named {type,default?,min?,max?,role?,primary?}. Usually mark one primary input and output with primary:true. Semantic roles such as source_image and character_reference are distinct and must never be guessed,permissions:{project:[],network:[],filesystem:[],secrets:[]},ui:[{control,port}]. Recipe: {schemaVersion:1,steps:[{id,op,args}],outputs}. References: {"$input":"port"}, {"$step":"step-id"}. Supported operations: {"value.copy":{"description":"Copy a JSON value without changing it.","args":["value"]},"text.trim":{"description":"Remove leading and trailing whitespace.","args":["text"]},"text.replace":{"description":"Replace literal text; no regular expressions.","args":["text","find","replacement"]},"text.join":{"description":"Join an array of text values.","args":["items","separator"]},"image.thicken-lines":{"description":"Expand dark outlines while preserving other colors.","args":["image","thickness","threshold"]}}. Port types: text, number, integer, boolean, json, object, image, video, audio, asset, script, transcript, brief, bible, shot-plan, panel-document, board-document, scene-document, timeline, timeline-edit, file-bundle, workspace-edit, timeline-range, research, character, location, style, pipeline, approval, scene, shot, mask, depth, pose, metadata, project-context, file, document, table, url, date, color. A value is exactly: text: a string; number: a number; integer: a whole number; boolean: true or false; json: any JSON value; object: a plain JSON object; image: {kind: 'image'; assetId: string; versionId: string; url: string; mimeType?: string; bytes?: number; name?: string; role?: string; width?: number; height?: number}; video: {kind: 'video'; assetId: string; versionId: string; url: string; mimeType?: string; bytes?: number; name?: string; role?: string; width?: number; height?: number; duration?: number; parts?: {shots: {start: number; end: number; from?: number; source: {kind: string; assetId: string; versionId: string; url?: string}}[]; lines: {start: number; end: number; text: string}[]}}; audio: {kind: 'audio'; assetId: string; versionId: string; url: string; mimeType?: string; bytes?: number; name?: string; role?: string; duration?: number; sampleRate?: number; channels?: number}; asset: {kind: 'image' | 'video' | 'audio' | 'asset'; assetId: string; versionId: string; url: string; mimeType?: string; bytes?: number; name?: string; role?: string}; script: {schemaVersion: 1; text: string; [key: string]: unknown}; transcript: {schemaVersion: 1; text: string; words: Array<{text: string; start: number; end: number}>}; file: {kind: 'file'; assetId: string; versionId: string; mimeType: 'application/pdf' | 'text/plain' | 'text/csv' | 'text/markdown' | 'application/json' | 'application/zip'; url?: string; name?: string; bytes?: number}; document: {schemaVersion: 1; format: 'markdown' | 'plain'; text: string; title?: string}; table: {schemaVersion?: 1; columns: string[]; rows: Array<Record<string, string | number | boolean | null>>}; url: an absolute http or https address string; date: an ISO 8601 date string ('2026-10-09' or '2026-10-09T14:30'); color: a hex color string ('#ff8800' or '#ff8800cc'). A file value is a saved file: one the person gives the Block (a module Block reads its bytes with stillmade.files.read) or what stillmade.files.write returns in a module Block; an image may also be decoded RGBA pixels {width, height, data} in recipe and JavaScript Blocks, which hold only 4 MiB of JSON (a picture of about 300 by 300 pixels), so a Block that joins photos into one picture (side by side, a grid, a contact sheet, a poster) is a module Block calling media.transform image.compose. Every other type is {schemaVersion: 1; [key: string]: unknown} with the fields its own section gives. A type ending in [] is a plain array of those values (audio[] is a list of audio references, never objects that wrap them). Include test {name,input,expected} where expected is output bindings.
13
13
 
14
14
  ### Feature map
15
15
 
@@ -17,7 +17,7 @@ Every Block lists its separate tools in manifest.features, one entry per tool or
17
17
 
18
18
  ### Port meanings
19
19
 
20
- For each newly created Block, declare the meaning of every input and output in its manifest contract, not just a physical type. Each input needs required:boolean, a short plain-language description and a semantic (import refuses an input without one, and gate checks it). An input accepts values declared with its meaning or a narrower one: an input for any photo a Step hands on uses source_image, as StillMade's Image editor does (generated_image and processed_image are kinds of source_image), and a photo editor's result is processed_image. Videos and sounds work the same way: an input for any video uses source_video (generated_video, edited_video and rendered_video are kinds of it) and one for any sound source_audio (narration_audio and rendered_audio are kinds of it); a video the Block renders is rendered_video, a sound it renders rendered_audio. Each output needs semantic and a short description. Use lowercase snake_case or dotted semantic names, such as company_profile or email_subject; different meanings are not interchangeable merely because both are text. If an existing role is present, semantic must equal that role. Reuse an exact existing meaning only when the information actually has that meaning. Describe structured fields with schema when their shape matters, and always on a json or object output (import refuses one without it): for example {name:"string",website:"string",industry:"string?"}. Supported schema fields are string/text, number, integer, boolean, object, json, arrays of those types, and optional fields with ?. A value of a structured type (transcript, script, brief, shot-plan and the like) is an object with schemaVersion:1, in code and fixtures; a transcript as the Transcription Block hands it on is {schemaVersion:1,text:"...",words:[{text:"Today",start:0,end:0.4}]}, times in seconds. Declare sources only when a requirement restricts where its answer may come from: upstream, project, adapter, chat, default, user or external. Do not mark a credential as ordinary text or put secrets in defaults. Contracts describe information; they never authorize network access, model calls, project edits, credit spending or new runtime APIs. Keep existing imported, installed and base Block contracts unchanged; do not silently relabel old published contracts to make a connection appear compatible. New contracts must match actual code and fixtures.
20
+ For each newly created Block, declare the meaning of every input and output in its manifest contract, not just a physical type. Each input needs required:boolean, a short plain-language description and a semantic (import refuses an input without one, and gate checks it). An input accepts values declared with its meaning or a narrower one: an input for any photo a Step hands on uses source_image, as StillMade's Image editor does (generated_image and processed_image are kinds of source_image), and a photo editor's result is processed_image. Videos and sounds work the same way: an input for any video uses source_video (generated_video, edited_video and rendered_video are kinds of it) and one for any sound source_audio (narration_audio and rendered_audio are kinds of it); a video the Block renders is rendered_video, a sound it renders rendered_audio. Each output needs semantic and a short description. Use lowercase snake_case or dotted semantic names, such as company_profile or email_subject; different meanings are not interchangeable merely because both are text. If an existing role is present, semantic must equal that role. Reuse an exact existing meaning only when the information actually has that meaning. Describe structured fields with schema when their shape matters, and always on a json or object output (import refuses one without it): for example {name:"string",website:"string",industry:"string?"}. Supported schema fields are string/text, number, integer, boolean, object, json, arrays of those types, and optional fields with ?. A value of a structured type (transcript, script, brief, shot-plan and the like) is an object with schemaVersion:1, in code and fixtures; a transcript as the Transcription Block hands it on is {schemaVersion:1,text:"...",words:[{text:"Today",start:0,end:0.4}]}, times in seconds. Set sources deliberately on each input a person or Chat fills: user when only the person can give it (their own photos, voice, names, dates), chat when Chat may write it from their request, upstream when only an earlier Step can supply it (also project, adapter, default or external). Chat's run of every Step reads sources to choose what to ask the person for and what to write; an input without sources is judged by its type (a required photo, video or sound is asked for; text and settings are written). Do not mark a credential as ordinary text or put secrets in defaults. Contracts describe information; they never authorize network access, model calls, project edits, credit spending or new runtime APIs. Keep existing imported, installed and base Block contracts unchanged; do not silently relabel old published contracts to make a connection appear compatible. New contracts must match actual code and fixtures.
21
21
  Custom inheritance is persisted in manifest.semantics, an array of {name,description?,parents?}. Use namespaced custom names, at most 64 definitions per Block, eight parents per definition, and 500 description characters. Never redefine unnamespaced standard meanings, create cycles, or combine conflicting parent sets. Preserve effective inherited meanings of all existing base inputs, outputs and accepts declarations; adding or changing a parent is a contract change even when port labels stay identical. New independent ports may declare new meanings.
22
22
  In isolated computation code, StillMade.emit(outputName,value) collects declared final outputs without a return object; emit each name once and never combine it with a returned output object or job.pending result. StillMade.resolveRequirement(inputName) reads the fixed host-validated declared input snapshot as {resolved:true,requirement,value}, or {resolved:false,requirement} for an absent optional input. StillMade.getProjectContext() returns only context fields explicitly bound in manifest.inputs with their matching context.FIELD.read permissions; getProjectContext(field) reads one declared field and rejects undeclared fields. These snapshot readers do not run the resolver again, request Chat/user answers, read extra project data, expose credentials, write projects or grant permissions. StillMade.validate(value,schema) checks JSON values against SDK semantic schemas and returns a boolean; it does not replace host output validation, prove semantic meaning or authorize execution. These methods are not available in view.javascript.
23
23
 
@@ -37,7 +37,7 @@ A capability package performs exactly one supported operation. A job that needs
37
37
 
38
38
  ### Module Blocks
39
39
 
40
- A job that needs several hosted operations, or code around one (reading a transcript's words and times, shaping a list), or media work on the person's device even with no hosted operation (rendering clips and photos into one video, joining pictures into one), is a module Block: {manifest,moduleSource,tests}, runtime:"module", moduleSource:{"main.js":"<the code>"}. StillMade names main.js by its digest and adds module. StillMade's in-app builder takes one file: give no other files, and main.js is plain JavaScript that imports nothing and uses no browser APIs (no fetch, TextEncoder, timers or WebCodecs). That single-file rule is the in-app builder's; a module Block built with the SDK may use timers, WebCodecs, bundled npm packages and .wasm files as MODULES.md describes. main.js: export default async function run(input, stillmade) returns the declared outputs. It calls each hosted operation with await stillmade.hosted.run(operation, request), listed in manifest.permissions.capabilities: text.generate, image.generate, video.generate, audio.speech, audio.music, audio.sfx, audio.transcribe, image.describe, media.analyze, web.fetch, web.research. Requests, and what each call returns (a test's mocks record exactly that value): text.generate {prompt, system, maxTokens} returns the text as a string, and with schema (a JSON schema object) returns that object, so ask for JSON with schema; one text.generate prompt holds at most 32,000 characters, so a longer file or transcript is read in parts (one call per part of about 25,000 characters, then one call that joins what they found), never cut off, and when the result is the whole text again (a cleaned transcript, a translation) each part's answer is kept and joined in order in main.js, since one answer holds at most 4,096 tokens; audio.transcribe {audio: the input's saved audio or video reference (a video's own sound is transcribed, so never extract it first), language:"auto"} returns {schemaVersion:1,text,words:[{text,start,end}]}; image.generate {prompt, references?: one to eight saved images the new picture keeps, such as the product, person or place in the person's photos} and audio.speech {text} return a saved image or audio reference (an audio reference always carries its measured duration); web.fetch {url, instruction} returns a string; media.analyze {media: one to four saved images or one saved video, question, range?: {start, end} in seconds of that video, at most 12 apart, to look closely at one part of it} returns {schemaVersion:1,items:[{kind,summary,observations,detectedText}]}, one item per image, and is how a Block looks at pictures (picking the photos that show different angles, reading a menu off a photo): StillMade credits pay for it; image.describe {image, instruction} returns a string but runs only on the person's own OpenRouter key, so never use it; web.research {topic} returns {summary,sources}. A file the person gives the Block (a text, CSV, Markdown or JSON file on a file input) is read with await stillmade.files.read(input.<port>), which returns its bytes; turn them into text in main.js as the worked file input below does, and give the test's input file an assetId whose text mocks.files records as {files:{"<assetId>":{text:"..."}}}. A Block that reads a file the person gives it is a module Block, never a text.generate capability package. A PDF (lesson slides, a lease, a contract) is read with await stillmade.media.transform({operation:"document.text",source:input.<port>}), which returns {text, pages}, all the words and pages, a list holding each page's words as one string, on the person's device and without credits (a text file reads the same way); a scanned PDF has no words to read. A call that fails throws. When the Block cannot make its result without that call, let it fail the run (StillMade shows its message), never catching it to return an empty result; catch it only around an optional part that has a fallback (a missing room photo, a skipped sample), and name what was left out in the result, as actions.run names its failures. Media work on the person's device (cut, join or resize video, decode or extract audio, thumbnails, joining images) is await stillmade.media.transform(request). Joining saved images into one picture (two photos side by side with Before and After labels, a grid or collage, a contact sheet, a storyboard sheet, a poster with a title) is {operation:"image.compose",width,height,background:"#ffffff",layers:[{source,x,y,width,height,fit:"cover" or "contain"}],text:[{text,x,y,size,color,background,align:"left" or "center" or "right",width}]}: a canvas up to 4096 pixels a side, positions in pixels worked out in main.js, layers drawn in order and text on top (y is the top of the line, background puts a box behind it, colours are #rrggbb or, see-through, #rrggbbaa such as #00000099 for a caption box, width wraps the words onto lines no wider than it: give a caption under a frame the frame's width so it never runs into the next one); it returns the saved image, and main.js never decodes pixels itself. Exporting is a transform too: {operation:"timeline.render",kind:"video" or "audio",aspect:"9:16",clips:[{source,start,duration,from,transition:{type:"Fade"},look:"Filmic",motion:"zoom-in"}],audio:[{source,start,duration}],captions:[{text,start,end,words:[{text,start,end}],highlight:"#ffd400",size:80}]} (motion, a slow camera move over a photo, is zoom-in, zoom-out, pan-left, pan-right, pan-up or pan-down; size is the caption height in pixels, 80 for a vertical video) renders the Block's own timeline with the Editor's transitions, looks and word-by-word captions under the person's export plan (start is a clip's place on that timeline, so each clip starts where the one before it ends, less the transition on the clip before it, which blends that clip into the next, and from is where it begins in its own file; overlapping clips are refused; a clip of another shape than the frame is cropped to fill it around its middle, or around focus:{x,y} on the clip, the point to keep in view as fractions 0 to 1 across the picture, such as where the speaker stands (focus.scale, 1 to 2.5 and instead of a motion, holds the clip that much closer on that point), or is fitted whole with fit:"contain"), so never ask for a separate export, captions or color Block; stillmade.log and stillmade.progress(fraction, label) report. One press of the Step's button approves each hosted operation once at the price shown beside it, and media.analyze for every picture or video the Step is given, across as many calls as that takes; any further call asks the person first. A module Block runs in the person's browser while StillMade is open, never on a schedule or event. Tests are {name,input,expected,mocks} and compare outputs, so every test is a case that runs to its outputs; an input the Block refuses (it throws a plain Error with a message for the person, such as a clip with no duration) is never a test. mocks records what each call answers, as {hosted:{"audio.transcribe":{schemaVersion:1,text:"...",words:[...]},"text.generate":{...}},transforms:{...}} (a list answers successive calls in order), and expected is the exact output main.js returns for those answers: exact values of each output's type, never capability bounds such as minDuration or maxBytes. In a module test, mock each media call with its complete saved reference (kind, assetId, versionId, url, mimeType and its measured fields: duration, bytes, sampleRate, channels for audio; width, height, bytes for images), its url an example file such as /api/media/example/clip.mp4 so the preview can play it, and repeat those references unchanged in expected; names or labels go in their own output. Worked outputs, each a passing test. A document output is the document object on a document port (not text): {"outputs":{"sheet":{"type":"document"}},"main.js":"export default async function run(input, stillmade) { const text = await stillmade.hosted.run('text.generate', {prompt: 'A Markdown care sheet for ' + input.plant, maxTokens: 800}); return {sheet: {schemaVersion: 1, format: 'markdown', text, title: input.plant + ' care'}}; }","test":{"name":"Fern","input":{"plant":"Fern"},"mocks":{"hosted":{"text.generate":"## Light\nBright, indirect light."}},"expected":{"sheet":{"schemaVersion":1,"format":"markdown","text":"## Light\nBright, indirect light.","title":"Fern care"}}}}. A file output is the reference await stillmade.files.write(data, {mediaType, name}) returns, handed on unchanged; mediaType is one of application/pdf, text/plain, text/csv, text/markdown, application/json, application/zip: {"outputs":{"report":{"type":"file"}},"main.js":"export default async function run(input, stillmade) { const csv = 'Room\\n' + input.rooms.join('\\n'); return {report: await stillmade.files.write(csv, {mediaType: 'text/csv', name: 'rooms.csv'})}; }","test":{"name":"Two rooms","input":{"rooms":["Kitchen","Hall"]},"expected":{"report":{"kind":"file","mimeType":"text/csv","name":"rooms.csv"}}}} (in a test, files.write answers with fields the host makes: assetId local-written-file-N, versionId, url /api/media/stillmade-mock/written-file-N.csv with the extension of its mediaType, and bytes; expected gives that reference as {kind:"file", mimeType, name} and it matches when those three match; passed on in an asset output as kind "asset", it is given as {kind:"asset", mimeType, name} and matches the same way). An audio[] output is the list of references audio.speech returned, unchanged; its mock is a list answering successive calls, and the labels go in a separate text[] output: {"outputs":{"tracks":{"type":"audio[]"},"names":{"type":"text[]"}},"main.js":"export default async function run(input, stillmade) { const tracks = []; for (const room of input.rooms) tracks.push(await stillmade.hosted.run('audio.speech', {text: 'Welcome to the ' + room + '.'})); return {tracks, names: input.rooms}; }","test":{"name":"Two rooms","input":{"rooms":["Kitchen","Hall"]},"mocks":{"hosted":{"audio.speech":[{"kind":"audio","assetId":"speech-1","versionId":"v1","url":"/api/media/example/kitchen.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1},{"kind":"audio","assetId":"speech-2","versionId":"v1","url":"/api/media/example/hall.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1}]}},"expected":{"tracks":[{"kind":"audio","assetId":"speech-1","versionId":"v1","url":"/api/media/example/kitchen.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1},{"kind":"audio","assetId":"speech-2","versionId":"v1","url":"/api/media/example/hall.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1}],"names":["Kitchen","Hall"]}}}. A file input read as text: {"outputs":{"guide":{"type":"text"}},"main.js":"function textOf(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.length; i += 8192) binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 8192)); try { return decodeURIComponent(escape(binary)); } catch (error) { return binary; } }\nexport default async function run(input, stillmade) { const notes = textOf(await stillmade.files.read(input.notes)); const guide = await stillmade.hosted.run('text.generate', {prompt: 'Rewrite these notes as numbered steps, keeping every number as written:\\n' + notes, maxTokens: 1200}); return {guide}; }","test":{"name":"Two notes","input":{"notes":{"kind":"file","assetId":"notes-1","versionId":"v1","url":"/api/media/example/notes.txt","mimeType":"text/plain","name":"notes.txt","bytes":31}},"mocks":{"files":{"notes-1":{"text":"pull the probe\nwipe the tines"}},"hosted":{"text.generate":"1. Pull the probe.\n2. Wipe the tines."}},"expected":{"guide":"1. Pull the probe.\n2. Wipe the tines."}}}. Photos joined into one image, its transform mocked with the saved image it returns: {"outputs":{"sheet":{"type":"image"}},"main.js":"export default async function run(input, stillmade) { const photos = input.photos; if (!photos.length) throw new Error('Add at least one photo.'); const columns = Math.ceil(Math.sqrt(photos.length)), rows = Math.ceil(photos.length / columns), cell = 400, gap = 20, top = 160; const width = columns * cell + (columns + 1) * gap, height = top + rows * cell + (rows + 1) * gap; const layers = photos.map((source, i) => ({source, x: gap + (i % columns) * (cell + gap), y: top + gap + Math.floor(i / columns) * (cell + gap), width: cell, height: cell, fit: 'cover'})); const sheet = await stillmade.media.transform({operation: 'image.compose', width, height, background: '#ffffff', layers, text: [{text: input.title, x: width / 2, y: 50, size: 64, align: 'center', color: '#111111'}]}); return {sheet}; }","test":{"name":"Four photos","input":{"photos":[{"kind":"image","assetId":"photo-1","versionId":"v1","url":"/api/media/example/photo-1.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000},{"kind":"image","assetId":"photo-2","versionId":"v1","url":"/api/media/example/photo-2.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000},{"kind":"image","assetId":"photo-3","versionId":"v1","url":"/api/media/example/photo-3.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000},{"kind":"image","assetId":"photo-4","versionId":"v1","url":"/api/media/example/photo-4.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000}],"title":"Saturday games"},"mocks":{"transforms":{"image.compose":{"kind":"image","assetId":"sheet-1","versionId":"v1","url":"/api/media/example/sheet.png","mimeType":"image/png","width":1300,"height":1440,"bytes":1820000}}},"expected":{"sheet":{"kind":"image","assetId":"sheet-1","versionId":"v1","url":"/api/media/example/sheet.png","mimeType":"image/png","width":1300,"height":1440,"bytes":1820000}}}}. StillMade runs every test before anyone sees the Block, without spending credits. With the SDK instead, the same code is module/main.js in a Block folder and the same tests are tests/fixtures.json; stillmade-block pack writes the file to import (MODULES.md).
40
+ A job that needs several hosted operations, or code around one (reading a transcript's words and times, shaping a list), or media work on the person's device even with no hosted operation (rendering clips and photos into one video, joining pictures into one), is a module Block: {manifest,moduleSource,tests}, runtime:"module", moduleSource:{"main.js":"<the code>"}. StillMade names main.js by its digest and adds module. StillMade's in-app builder takes one file: give no other files, and main.js is plain JavaScript that imports nothing and uses no browser APIs (no fetch, TextEncoder, timers or WebCodecs). That single-file rule is the in-app builder's; a module Block built with the SDK may use timers, WebCodecs, bundled npm packages and .wasm files as MODULES.md describes. main.js: export default async function run(input, stillmade) returns the declared outputs. It calls each hosted operation with await stillmade.hosted.run(operation, request), listed in manifest.permissions.capabilities: text.generate, image.generate, video.generate, audio.speech, audio.music, audio.sfx, audio.transcribe, image.describe, media.analyze, web.fetch, web.research. Requests, and what each call returns (a test's mocks record exactly that value): text.generate {prompt, system, maxTokens} returns the text as a string, and with schema (a JSON schema object) returns that object, so ask for JSON with schema; one text.generate prompt holds at most 32,000 characters, so a longer file or transcript is read in parts (one call per part of about 25,000 characters, then one call that joins what they found), never cut off, and when the result is the whole text again (a cleaned transcript, a translation) each part's answer is kept and joined in order in main.js, since one answer holds at most 4,096 tokens; audio.transcribe {audio: the input's saved audio or video reference (a video's own sound is transcribed, so never extract it first), language:"auto"} returns {schemaVersion:1,text,words:[{text,start,end}]}; image.generate {prompt, references?: one to eight saved images the new picture keeps, such as the product, person or place in the person's photos} and audio.speech {text} return a saved image or audio reference (an audio reference always carries its measured duration); web.fetch {url, instruction} returns a string; media.analyze {media: one to four saved images or one saved video, question, range?: {start, end} in seconds of that video, at most 12 apart, to look closely at one part of it} returns {schemaVersion:1,items:[{kind,summary,observations,detectedText}]}, one item per image, and is how a Block looks at pictures (picking the photos that show different angles, reading a menu off a photo): StillMade credits pay for it; image.describe {image, instruction} returns a string but runs only on the person's own OpenRouter key, so never use it; web.research {topic} returns {summary,sources}. A file the person gives the Block (a text, CSV, Markdown or JSON file on a file input) is read with await stillmade.files.read(input.<port>), which returns its bytes; turn them into text in main.js as the worked file input below does, and give the test's input file an assetId whose text mocks.files records as {files:{"<assetId>":{text:"..."}}}. A Block that reads a file the person gives it is a module Block, never a text.generate capability package. A PDF (lesson slides, a lease, a contract) is read with await stillmade.media.transform({operation:"document.text",source:input.<port>}), which returns {text, pages}, all the words and pages, a list holding each page's words as one string, on the person's device and without credits (a text file reads the same way); a scanned PDF has no words to read. A call that fails throws. When the Block cannot make its result without that call, let it fail the run (StillMade shows its message), never catching it to return an empty result; catch it only around an optional part that has a fallback (a missing room photo, a skipped sample), and name what was left out in the result, as actions.run names its failures. Media work on the person's device (cut, join or resize video, decode or extract audio, thumbnails, joining images) is await stillmade.media.transform(request). Joining saved images into one picture (two photos side by side with Before and After labels, a grid or collage, a contact sheet, a storyboard sheet, a poster with a title) is {operation:"image.compose",width,height,background:"#ffffff",layers:[{source,x,y,width,height,fit:"cover" or "contain"}],text:[{text,x,y,size,color,background,align:"left" or "center" or "right",width}]}: a canvas up to 4096 pixels a side, positions in pixels worked out in main.js, layers drawn in order and text on top (y is the top of the line, background puts a box behind it, colours are #rrggbb or, see-through, #rrggbbaa such as #00000099 for a caption box, width wraps the words onto lines no wider than it: give a caption under a frame the frame's width so it never runs into the next one); it returns the saved image, and main.js never decodes pixels itself. Exporting is a transform too: {operation:"timeline.render",kind:"video" or "audio",aspect:"9:16",clips:[{source,start,duration,from,transition:{type:"Fade"},look:"Filmic",motion:"zoom-in"}],audio:[{source,start,duration}],captions:[{text,start,end,words:[{text,start,end}],highlight:"#ffd400",size:80}]} (motion, a slow camera move over a photo, is zoom-in, zoom-out, pan-left, pan-right, pan-up or pan-down; size is the caption height in pixels, 80 for a vertical video) renders the Block's own timeline with the Editor's transitions, looks and word-by-word captions under the person's export plan (start is a clip's place on that timeline, so each clip starts where the one before it ends, less the transition on the clip before it, which blends that clip into the next, and from is where it begins in its own file; overlapping clips are refused; a clip of another shape than the frame is cropped to fill it around its middle, or around focus:{x,y} on the clip, the point to keep in view as fractions 0 to 1 across the picture, such as where the speaker stands (focus.scale, 1 to 2.5 and instead of a motion, holds the clip that much closer on that point), or is fitted whole with fit:"contain"), so never ask for a separate export, captions or color Block; the video it returns carries parts:{shots:[{start,end,from,source}],lines:[{start,end,text}]}, the clips and captions it was made from, and StillMade shows those shots with their times and the line being said under it; stillmade.log and stillmade.progress(fraction, label) report. One press of the Step's button approves each hosted operation once at the price shown beside it, and media.analyze for every picture or video the Step is given, across as many calls as that takes; any further call asks the person first. A module Block runs in the person's browser while StillMade is open, never on a schedule or event. Tests are {name,input,expected,mocks} and compare outputs, so every test is a case that runs to its outputs; an input the Block refuses (it throws a plain Error with a message for the person, such as a clip with no duration) is never a test. mocks records what each call answers, as {hosted:{"audio.transcribe":{schemaVersion:1,text:"...",words:[...]},"text.generate":{...}},transforms:{...}} (a list answers successive calls in order), and expected is the exact output main.js returns for those answers: exact values of each output's type, never capability bounds such as minDuration or maxBytes. In a module test, mock each media call with its complete saved reference (kind, assetId, versionId, url, mimeType and its measured fields: duration, bytes, sampleRate, channels for audio; width, height, bytes for images), its url an example file such as /api/media/example/clip.mp4 so the preview can play it, and repeat those references unchanged in expected; names or labels go in their own output. Worked outputs, each a passing test. A document output is the document object on a document port (not text): {"outputs":{"sheet":{"type":"document"}},"main.js":"export default async function run(input, stillmade) { const text = await stillmade.hosted.run('text.generate', {prompt: 'A Markdown care sheet for ' + input.plant, maxTokens: 800}); return {sheet: {schemaVersion: 1, format: 'markdown', text, title: input.plant + ' care'}}; }","test":{"name":"Fern","input":{"plant":"Fern"},"mocks":{"hosted":{"text.generate":"## Light\nBright, indirect light."}},"expected":{"sheet":{"schemaVersion":1,"format":"markdown","text":"## Light\nBright, indirect light.","title":"Fern care"}}}}. A file output is the reference await stillmade.files.write(data, {mediaType, name}) returns, handed on unchanged; mediaType is one of application/pdf, text/plain, text/csv, text/markdown, application/json, application/zip: {"outputs":{"report":{"type":"file"}},"main.js":"export default async function run(input, stillmade) { const csv = 'Room\\n' + input.rooms.join('\\n'); return {report: await stillmade.files.write(csv, {mediaType: 'text/csv', name: 'rooms.csv'})}; }","test":{"name":"Two rooms","input":{"rooms":["Kitchen","Hall"]},"expected":{"report":{"kind":"file","mimeType":"text/csv","name":"rooms.csv"}}}} (in a test, files.write answers with fields the host makes: assetId local-written-file-N, versionId, url /api/media/stillmade-mock/written-file-N.csv with the extension of its mediaType, and bytes; expected gives that reference as {kind:"file", mimeType, name} and it matches when those three match; passed on in an asset output as kind "asset", it is given as {kind:"asset", mimeType, name} and matches the same way). An audio[] output is the list of references audio.speech returned, unchanged; its mock is a list answering successive calls, and the labels go in a separate text[] output: {"outputs":{"tracks":{"type":"audio[]"},"names":{"type":"text[]"}},"main.js":"export default async function run(input, stillmade) { const tracks = []; for (const room of input.rooms) tracks.push(await stillmade.hosted.run('audio.speech', {text: 'Welcome to the ' + room + '.'})); return {tracks, names: input.rooms}; }","test":{"name":"Two rooms","input":{"rooms":["Kitchen","Hall"]},"mocks":{"hosted":{"audio.speech":[{"kind":"audio","assetId":"speech-1","versionId":"v1","url":"/api/media/example/kitchen.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1},{"kind":"audio","assetId":"speech-2","versionId":"v1","url":"/api/media/example/hall.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1}]}},"expected":{"tracks":[{"kind":"audio","assetId":"speech-1","versionId":"v1","url":"/api/media/example/kitchen.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1},{"kind":"audio","assetId":"speech-2","versionId":"v1","url":"/api/media/example/hall.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1}],"names":["Kitchen","Hall"]}}}. A file input read as text: {"outputs":{"guide":{"type":"text"}},"main.js":"function textOf(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.length; i += 8192) binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 8192)); try { return decodeURIComponent(escape(binary)); } catch (error) { return binary; } }\nexport default async function run(input, stillmade) { const notes = textOf(await stillmade.files.read(input.notes)); const guide = await stillmade.hosted.run('text.generate', {prompt: 'Rewrite these notes as numbered steps, keeping every number as written:\\n' + notes, maxTokens: 1200}); return {guide}; }","test":{"name":"Two notes","input":{"notes":{"kind":"file","assetId":"notes-1","versionId":"v1","url":"/api/media/example/notes.txt","mimeType":"text/plain","name":"notes.txt","bytes":31}},"mocks":{"files":{"notes-1":{"text":"pull the probe\nwipe the tines"}},"hosted":{"text.generate":"1. Pull the probe.\n2. Wipe the tines."}},"expected":{"guide":"1. Pull the probe.\n2. Wipe the tines."}}}. Photos joined into one image, its transform mocked with the saved image it returns: {"outputs":{"sheet":{"type":"image"}},"main.js":"export default async function run(input, stillmade) { const photos = input.photos; if (!photos.length) throw new Error('Add at least one photo.'); const columns = Math.ceil(Math.sqrt(photos.length)), rows = Math.ceil(photos.length / columns), cell = 400, gap = 20, top = 160; const width = columns * cell + (columns + 1) * gap, height = top + rows * cell + (rows + 1) * gap; const layers = photos.map((source, i) => ({source, x: gap + (i % columns) * (cell + gap), y: top + gap + Math.floor(i / columns) * (cell + gap), width: cell, height: cell, fit: 'cover'})); const sheet = await stillmade.media.transform({operation: 'image.compose', width, height, background: '#ffffff', layers, text: [{text: input.title, x: width / 2, y: 50, size: 64, align: 'center', color: '#111111'}]}); return {sheet}; }","test":{"name":"Four photos","input":{"photos":[{"kind":"image","assetId":"photo-1","versionId":"v1","url":"/api/media/example/photo-1.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000},{"kind":"image","assetId":"photo-2","versionId":"v1","url":"/api/media/example/photo-2.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000},{"kind":"image","assetId":"photo-3","versionId":"v1","url":"/api/media/example/photo-3.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000},{"kind":"image","assetId":"photo-4","versionId":"v1","url":"/api/media/example/photo-4.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000}],"title":"Saturday games"},"mocks":{"transforms":{"image.compose":{"kind":"image","assetId":"sheet-1","versionId":"v1","url":"/api/media/example/sheet.png","mimeType":"image/png","width":1300,"height":1440,"bytes":1820000}}},"expected":{"sheet":{"kind":"image","assetId":"sheet-1","versionId":"v1","url":"/api/media/example/sheet.png","mimeType":"image/png","width":1300,"height":1440,"bytes":1820000}}}}. StillMade runs every test before anyone sees the Block, without spending credits. With the SDK instead, the same code is module/main.js in a Block folder and the same tests are tests/fixtures.json; stillmade-block pack writes the file to import (MODULES.md).
41
41
 
42
42
  ### ComfyUI workflows
43
43
 
@@ -85,7 +85,7 @@ Optional custom view interfaces are supported, but generated manifest.ui control
85
85
 
86
86
  *When:* StillMade's builder adds this section when the person asks for an interface, preview or controls, or refines a Block that has one.
87
87
 
88
- A custom interface is the optional package-level view:{html,css?,themedCss?,javascript?}; when refining, preserve and refine base.view. Design it by StillMade's interface principles. Every Block: Speak the job unless the person or creator asks otherwise. The action button names what it does in the job's words (`manifest.action`: "Plan the tour"; AI pictures, video and sound start with Generate and say what, the person's own media cut or rendered says Make: "Generate voiceover", "Make 6 clips"), never "Make it", "Run", "Run step", "Submit", "Process" or a bare "Generate"; progress counts the job's units ("4 of 9 rooms"), never "Processing"; results and problems use its words in one line. Show no credit numbers: StillMade puts the price beside the verb on its own run button ("Write · 1 credit"), with a chip for the model, quality or count when they can change. Pressing it runs at that price; a confirmation appears only when the server price is higher than the one shown. Pictures over words: choices people see or hear are thumbnails, swatches, samples or glyphs (`sm-choices`), not word lists. Icons carry an accessible name and tooltip; never emoji. A picked option reads at a glance: keep the kit's ink fill on an `sm-choices` pick, never restyled to an outline, a tint or the page's background. Media that cannot show here (on another device, or it did not open) keeps its place as a quiet `sm-media-off` tile in its own shape, never words in its place. Words only when needed. Drop what nobody needs to finish the task (intros, "how it works", restating the Block name). Words that might help start collapsed (`sm-hint`, `sm-more`). Errors, spending approvals, stale inputs and destructive steps stay visible, in few words. Status is a dot or a few words ("No narration yet"), never a paragraph; the reason sits behind an `sm-hint`. Files are tiles: a saved file is a thumbnail or chip and adding one is the + tile (`<button class="sm-add" data-stillmade-add="<input>">`), which opens StillMade's picker. Never an `<input type="file">` or the bare browser file picker, and no size limits in view until a file is too big. An (i) beside anything a newcomer could misread: a setting, status or number whose effect is not obvious from its label gets a closed `sm-hint` with one or two sentences. Give every input that needs one a `description`; StillMade shows it as that (i). Obvious controls get none. Nothing is lost when a Block gets smaller: every control stays on the screen, behind an (i), in `sm-more`, or inside the item it belongs to. Compacting moves controls; it never deletes them. Text scene descriptions are planning notes, never a storyboard or an approved frame. Say nothing about how the Block works: no notes on where it runs (this device, StillMade Desktop, the cloud), saving, syncing, recovery, accounts, credits or where a file is kept. StillMade tells the person what they need as one short notification, or nothing; never a sentence on the page, and never the same line under each item. References fill your inputs: every drop area in StillMade offers "Use a reference", and the person's saved photos, videos and notes fill the Step with no typing. Media inputs fill by kind, so take `video` or `asset` wherever a clip works as well as a photo; text inputs fill by plain names and labels (`productName`, `website`, `brandColors`, `fonts`, `voice`, and `details` or `notes` for the reference's note). Never build your own reference picker or library upload. Days and times are picked, never typed: a `date` input is a calendar, with a list of times when it is about a time (`postAt`, `startsAt`, a label with Time, When or Starts). In a custom view use `<input type="date">` or `type="datetime-local"`, never a text box for a date. Posting is its own Step: sending a result to Instagram, TikTok, email or any app is a connector Step after the Block, worded by StillMade ("Post to Instagram"). A Block never posts from inside itself. A job (the default; manifest.kind "task" or none) also: Function first: unless the person asks for a design direction, build from the Block kit (`sm-block`, `sm-stage`, `sm-choices`, `sm-actions`, `sm-more`) and ship little or no CSS of your own. A direction the person names wins over the kit. Show the thing being made: the preview, media or result gets the largest area (`sm-stage`), and every part shows at once: before the first run the result's place is drawn in the shape and size of what is coming (a 9:16 frame for a vertical clip, lines for text, a player bar for sound), so the result fills it instead of popping in. On a wide screen what goes in sits on the left and the result on the right; they stack only on narrow screens and phones. A picture or clip the Block makes fills it in its own shape, as large as fits; several fill it as a grid, each big enough to watch. Small tiles are for the files a person gives the Block, never for what it makes. A clip is a `<video controls>`, which StillMade plays in its own player with full screen, and a picture in `sm-stage` or `sm-gallery` (or marked `data-stillmade-expand`) gets StillMade's full-screen button; leave out `controls` only when the Block drives playback itself. Never a JSON dump, source editor, manifest or diagnostic panel unless the task is about source. One obvious next action: one solid `sm-primary` in the kit's ink, never restyled to the background or a ghost outline; peers are `sm-quiet` buttons or named icons, and close is an X icon. Seven or fewer choices in view. Actions on the stage (owner, November 5, 2026): a job's `sm-actions` and `sm-status` go inside its `sm-stage`, and the kit lays them over it, the status top-left and the actions top-right, in the cream blocks of StillMade's player, with a band kept above the media so they never cover it; the media fills the rest of the stage. A button that spends credits never moves: the run button (`data-stillmade-action="run"`) is always the rightmost and stays in that spot when it makes again ("Make video", then "Make video again"), and what the result adds (Download, Share) goes to its left. Controls on media (StillMade's player, the full-screen square) show for 3 seconds the first time it comes into view, then only while it is hovered or tapped. StillMade's own Step pages do the same. An app that lays itself out keeps its own toolbar. Strong defaults; secondary settings go in `sm-more`, closed. Compact: the whole Block fits one laptop screen without scrolling. A short setting is one row, its label beside the control; no card around a single control. Rare tools (update, schedule, history, reload) are named icon buttons beside the action, never sections under the work. One workspace: no cards inside cards, no second title, step counter or Back/Next bar; the host owns navigation. One page per job (November 3, 2026): a guided job's screen holds the inputs and settings, then a cheap check before the expensive run (the script and a voice sample before the video, a second `StillMade.run` that StillMade prices on its own), then the result; never a second Block or Step for the check. A workspace a person works in (a CAD tool, a canvas, an editor) is its own Block, and turning what it made into something else is the next one. Status goes in `sm-status`, in a few words: top-left on the stage when the actions are there, otherwise beside the action it describes. Keep the page in normal scroll flow; no sticky media or control strips unless the editing task needs them. Use the width: from 860px wide the kit puts the controls on the left at a reading width (300–380px) and the result (`sm-stage`) fills the right, in its real shape; under that everything stacks in the page's order: the inputs, then the result's place with its actions on it (never a pinned bar), so `sm-stage` comes after the inputs. Extra width goes to the result, never to wider text fields. Before there is a result, the stage previews what will be made from the inputs. `data-layout="stack"` opts out. Size with the kit, never fixed sizes: no fixed pixel widths or heights for the workspace or stage, no 100vh, no position:fixed panels. StillMade zooms a Block's screen to fill the space it has (up to 150%, never down to a phone layout on a computer), so a Block laid out with the kit's spacing fills its Step without empty space, and it centers a standard view narrower than its Step. The kit's `sm-stage` shows what it holds in its middle as one group (a caption stays with its picture), and several results in a stage fill its width as one row; the pages StillMade draws follow the same rule. An app that lays itself out (full-size canvas or WebGL, its own scroll, pan or zoom, its own Ctrl/Cmd +/- keys) puts `data-stillmade-layout="own"` on its outermost element, and StillMade then never centers, zooms or takes those keys. Group what the person sets in one `sm-fields` (or `sm-settings` for short ones): the kit gives the group its own surface, so the inputs, the settings and the result read as separate parts. Settings sit side by side (`sm-settings`), next to what they change. Three or four short options are one segmented row (`data-segmented`), not a row of big tiles. Lists fill the width at a small size: input files are small tiles (`sm-gallery data-size="small"`), many items are one line each (`sm-list`): a thumbnail, one line of text, a short fact. Tapping an item opens all of its fields in place. Extra width means more items per row, never bigger cards. On a phone chip and segment rows scroll sideways instead of wrapping; long lists show their first three with "N more" (`data-peek`) and long text five lines until tapped (`sm-clamp`). An app (manifest.kind "workspace"; software a person works in, like a CAD tool or a photo editor) follows these instead of the job-only ones, and StillMade's checks hold it to them: An app (manifest.kind "workspace") is software a person works in for a while: a CAD or 3D tool, a photo editor, a timeline, a canvas, a studio. Its work surface (viewport, canvas, image, timeline) owns the page, laid out like the best apps of its kind, with the tool rail, panels, inspector, layers, timeline and menus the craft needs and as many controls as it needs. Never shrink it into a form of fields and one button. It lays itself out: `data-stillmade-layout="own"` on its outermost element, so StillMade never centers or zooms it and leaves Ctrl/Cmd +/- to it. Full-size panels, fixed toolbars and its own scroll, pan and zoom are fine. Work happens on the surface: select, drag, zoom and pan, keyboard shortcuts for the common tools, undo and redo. Speak the craft: tools and panels use the words people of that craft use (Exposure, Keyframe, Extrude, Layers, Mask), never a plain-language rewrite. A button that spends credits still names what it makes ("Generate fill"), carries `data-stillmade-action="run"` and shows no price; StillMade adds it. Order, not sparseness: tools grouped in panels that fold, the most used in view, rare ones in menus. No intros or paragraphs on the surface; a tool that needs explaining gets a tooltip or an (i). StillMade's appearance (Light, Dark and White) and the Block kit's controls (buttons, fields, sliders, `sm-choices` with thumbnails for presets) wherever they fit; the layout is the app's own. A look the person names wins. On a phone the work surface stays usable and the panels fold into sheets or one tool strip. What the person makes leaves the app as the Block's outputs (and, while they work, its shared state), so the next Step and the other people in the project get it. Its DOM controller is separate from manifest runtime code. The view may also declare appearance:"original" or "host". For an imported design, preserve its colors with appearance:"original" and view.css; no themed replacement is required. Preserve base.view.appearance when refining unless the user requests a change. Omitted/host appearance retains the existing Light, Dark and White requirement: supply semantic colors in view.css or a complete replacement view.themedCss. An original-style view may optionally supply valid themedCss for a StillMade appearance preview. Use StillMade variables (--bg,--fg,--fg-muted,--border-tok,--r-3) only when opting into host styling; do not redefine host tokens in the themed variant. Appearance never relaxes sandbox constraints: no CSS escapes/filters/blending, imports/url(), or element.style mutations in JS. Use classes for UI state; media pixels retain their colors. Both CSS fields have the same 32 KiB limit; all existing shared-state, security and permission checks still apply. Declare elements in html, bind with document.getElementById/querySelector and addEventListener; use literal property access, array methods, textContent, input.value, button.disabled and canvas. Initial values: StillMade.onInput(values=>{...}); await StillMade.run(inputBindings) returns validated output bindings from the isolated package runtime; catch errors and show results. No computation in the DOM controller; put it in code/recipe. Disable the action button while pending.
88
+ A custom interface is the optional package-level view:{html,css?,themedCss?,javascript?}; when refining, preserve and refine base.view. Design it by StillMade's interface principles. Every Block: Speak the job unless the person or creator asks otherwise. The action button names what it does in the job's words (`manifest.action`: "Plan the tour"; AI pictures, video and sound start with Generate and say what, the person's own media cut or rendered says Make: "Generate voiceover", "Make 6 clips"), never "Make it", "Run", "Run step", "Submit", "Process" or a bare "Generate"; progress counts the job's units ("4 of 9 rooms"), never "Processing"; results and problems use its words in one line. Show no credit numbers: StillMade puts the price beside the verb on its own run button ("Write · 1 credit"), with a chip for the model, quality or count when they can change. Pressing it runs at that price; a confirmation appears only when the server price is higher than the one shown. Pictures over words: choices people see or hear are thumbnails, swatches, samples or glyphs (`sm-choices`), not word lists. Icons carry an accessible name and tooltip; never emoji. A picked option reads at a glance: keep the kit's ink fill on an `sm-choices` pick, never restyled to an outline, a tint or the page's background. A picture, or a pick shown by its picture (a thumbnail, a shot, a swatch), is a rounded rectangle in the kit's radii, never a circle, unless the person asks for one. Media that cannot show here (on another device, or it did not open) keeps its place as a quiet `sm-media-off` tile in its own shape, never words in its place. Words only when needed. Drop what nobody needs to finish the task (intros, "how it works", restating the Block name). Words that might help start collapsed (`sm-hint`, `sm-more`). Errors, spending approvals, stale inputs and destructive steps stay visible, in few words. Status is a dot or a few words ("No narration yet"), never a paragraph; the reason sits behind an `sm-hint`. Files are tiles: a saved file is a thumbnail or chip and adding one is the + tile (`<button class="sm-add" data-stillmade-add="<input>">`), which opens StillMade's picker. Never an `<input type="file">` or the bare browser file picker, and no size limits in view until a file is too big. An (i) beside anything a newcomer could misread: a setting, status or number whose effect is not obvious from its label gets a closed `sm-hint` with one or two sentences. Give every input that needs one a `description`; StillMade shows it as that (i). Obvious controls get none. Nothing is lost when a Block gets smaller: every control stays on the screen, behind an (i), in `sm-more`, or inside the item it belongs to. Compacting moves controls; it never deletes them. Text scene descriptions are planning notes, never a storyboard or an approved frame. Say nothing about how the Block works: no notes on where it runs (this device, StillMade Desktop, the cloud), saving, syncing, recovery, accounts, credits or where a file is kept. StillMade tells the person what they need as one short notification, or nothing; never a sentence on the page, and never the same line under each item. References fill your inputs: every drop area in StillMade offers "Use a reference", and the person's saved photos, videos and notes fill the Step with no typing. Media inputs fill by kind, so take `video` or `asset` wherever a clip works as well as a photo; text inputs fill by plain names and labels (`productName`, `website`, `brandColors`, `fonts`, `voice`, and `details` or `notes` for the reference's note). Never build your own reference picker or library upload. Days and times are picked, never typed: a `date` input is a calendar, with a list of times when it is about a time (`postAt`, `startsAt`, a label with Time, When or Starts). In a custom view use `<input type="date">` or `type="datetime-local"`, never a text box for a date. Posting is its own Step: sending a result to Instagram, TikTok, email or any app is a connector Step after the Block, worded by StillMade ("Post to Instagram"). A Block never posts from inside itself. A job (the default; manifest.kind "task" or none) also: Function first: unless the person asks for a design direction, build from the Block kit (`sm-block`, `sm-stage`, `sm-choices`, `sm-actions`, `sm-more`) and ship little or no CSS of your own. A direction the person names wins over the kit. Show the thing being made: the preview, media or result gets the largest area (`sm-stage`), and every part shows at once: before the first run the result's place is drawn in the shape and size of what is coming (a 9:16 frame for a vertical clip, lines for text, a player bar for sound), so the result fills it instead of popping in. On a wide screen what goes in sits on the left and the result on the right; they stack only on narrow screens and phones. A picture or clip the Block makes fills it in its own shape, as large as fits; several fill it as a grid, each big enough to watch. Small tiles are for the files a person gives the Block, never for what it makes. A clip is a `<video controls>`, which StillMade plays in its own player with full screen, and a picture in `sm-stage` or `sm-gallery` (or marked `data-stillmade-expand`) gets StillMade's full-screen button; leave out `controls` only when the Block drives playback itself. Never a JSON dump, source editor, manifest or diagnostic panel unless the task is about source. One obvious next action: one solid `sm-primary` in the kit's ink, never restyled to the background or a ghost outline; peers are `sm-quiet` buttons or named icons, and close is an X icon. Seven or fewer choices in view. Actions on the stage (owner, November 5, 2026): a job's `sm-actions` and `sm-status` go inside its `sm-stage`, and the kit lays them over it, the status top-left and the actions top-right, in the cream blocks of StillMade's player, with a band kept above the media so they never cover it; the media fills the rest of the stage. A button that spends credits never moves: the run button (`data-stillmade-action="run"`) is always the rightmost and stays in that spot when it makes again ("Make video", then "Make video again"), and what the result adds (Download, Share) goes to its left. Controls on media (StillMade's player, the full-screen square) show for 3 seconds the first time it comes into view, then only while it is hovered or tapped. Under a rendered video the stage shows its shots with their times and the line being said (the video's `parts`, which `timeline.render` fills), and the status carries its length. StillMade's own Step pages do the same. An app that lays itself out keeps its own toolbar. Strong defaults; secondary settings go in `sm-more`, closed. Compact: the whole Block fits one laptop screen without scrolling. A short setting is one row, its label beside the control; no card around a single control. Rare tools (update, schedule, history, reload) are named icon buttons beside the action, never sections under the work. One workspace: no cards inside cards, no second title, step counter or Back/Next bar; the host owns navigation. One page per job (November 3, 2026): a guided job's screen holds the inputs and settings, then a cheap check before the expensive run (the script and a voice sample before the video, a second `StillMade.run` that StillMade prices on its own), then the result; never a second Block or Step for the check. A workspace a person works in (a CAD tool, a canvas, an editor) is its own Block, and turning what it made into something else is the next one. Status goes in `sm-status`, in a few words: top-left on the stage when the actions are there, otherwise beside the action it describes. Keep the page in normal scroll flow; no sticky media or control strips unless the editing task needs them. Use the width: from 860px wide the kit puts the controls on the left at a reading width (300–380px) and the result (`sm-stage`) fills the right, in its real shape; under that everything stacks in the page's order: the inputs, then the result's place with its actions on it (never a pinned bar), so `sm-stage` comes after the inputs. Extra width goes to the result, never to wider text fields. Before there is a result, the stage previews what will be made from the inputs. `data-layout="stack"` opts out. Size with the kit, never fixed sizes: no fixed pixel widths or heights for the workspace or stage, no 100vh, no position:fixed panels. StillMade zooms a Block's screen to fill the space it has (up to 150%, never down to a phone layout on a computer), so a Block laid out with the kit's spacing fills its Step without empty space, and it centers a standard view narrower than its Step. The kit's `sm-stage` shows what it holds in its middle as one group (a caption stays with its picture), and several results in a stage fill its width as one row; the pages StillMade draws follow the same rule. An app that lays itself out (full-size canvas or WebGL, its own scroll, pan or zoom, its own Ctrl/Cmd +/- keys) puts `data-stillmade-layout="own"` on its outermost element, and StillMade then never centers, zooms or takes those keys. Group what the person sets in one `sm-fields` (or `sm-settings` for short ones): the kit gives the group its own surface, so the inputs, the settings and the result read as separate parts. Settings sit side by side (`sm-settings`), next to what they change. Three or four short options are one segmented row (`data-segmented`), not a row of big tiles. Lists fill the width at a small size: input files are small tiles (`sm-gallery data-size="small"`), many items are one line each (`sm-list`): a thumbnail, one line of text, a short fact. Tapping an item opens all of its fields in place. Extra width means more items per row, never bigger cards. On a phone chip and segment rows scroll sideways instead of wrapping; long lists show their first three with "N more" (`data-peek`) and long text five lines until tapped (`sm-clamp`). An app (manifest.kind "workspace"; software a person works in, like a CAD tool or a photo editor) follows these instead of the job-only ones, and StillMade's checks hold it to them: An app (manifest.kind "workspace") is software a person works in for a while: a CAD or 3D tool, a photo editor, a timeline, a canvas, a studio. Its work surface (viewport, canvas, image, timeline) owns the page, laid out like the best apps of its kind, with the tool rail, panels, inspector, layers, timeline and menus the craft needs and as many controls as it needs. Never shrink it into a form of fields and one button. It lays itself out: `data-stillmade-layout="own"` on its outermost element, so StillMade never centers or zooms it and leaves Ctrl/Cmd +/- to it. Full-size panels, fixed toolbars and its own scroll, pan and zoom are fine. Work happens on the surface: select, drag, zoom and pan, keyboard shortcuts for the common tools, undo and redo. Speak the craft: tools and panels use the words people of that craft use (Exposure, Keyframe, Extrude, Layers, Mask), never a plain-language rewrite. A button that spends credits still names what it makes ("Generate fill"), carries `data-stillmade-action="run"` and shows no price; StillMade adds it. Order, not sparseness: tools grouped in panels that fold, the most used in view, rare ones in menus. No intros or paragraphs on the surface; a tool that needs explaining gets a tooltip or an (i). StillMade's appearance (Light, Dark and White) and the Block kit's controls (buttons, fields, sliders, `sm-choices` with thumbnails for presets) wherever they fit; the layout is the app's own. A look the person names wins. On a phone the work surface stays usable and the panels fold into sheets or one tool strip. What the person makes leaves the app as the Block's outputs (and, while they work, its shared state), so the next Step and the other people in the project get it. Its DOM controller is separate from manifest runtime code. The view may also declare appearance:"original" or "host". For an imported design, preserve its colors with appearance:"original" and view.css; no themed replacement is required. Preserve base.view.appearance when refining unless the user requests a change. Omitted/host appearance retains the existing Light, Dark and White requirement: supply semantic colors in view.css or a complete replacement view.themedCss. An original-style view may optionally supply valid themedCss for a StillMade appearance preview. Use StillMade variables (--bg,--fg,--fg-muted,--border-tok,--r-3) only when opting into host styling; do not redefine host tokens in the themed variant. Appearance never relaxes sandbox constraints: no CSS escapes/filters/blending, imports/url(), or element.style mutations in JS. Use classes for UI state; media pixels retain their colors. Both CSS fields have the same 32 KiB limit; all existing shared-state, security and permission checks still apply. Declare elements in html, bind with document.getElementById/querySelector and addEventListener; use literal property access, array methods, textContent, input.value, button.disabled and canvas. Initial values: StillMade.onInput(values=>{...}); await StillMade.run(inputBindings) returns validated output bindings from the isolated package runtime; catch errors and show results. No computation in the DOM controller; put it in code/recipe. Disable the action button while pending.
89
89
  Standard custom view code (no `view.config.json` frame runtime) is checked before it runs. These rules come straight from the validator; frame views run in their own sandbox and may build the page with ordinary DOM calls such as `createElement`:
90
90
 
91
91
  - **HTML elements:** only `div`, `section`, `article`, `header`, `footer`, `main`, `aside`, `nav`, `h1`, `h2`, `h3`, `h4`, `p`, `span`, `strong`, `em`, `i`, `small`, `br`, `hr`, `label`, `input`, `textarea`, `button`, `select`, `option`, `optgroup`, `fieldset`, `legend`, `output`, `progress`, `meter`, `ul`, `ol`, `li`, `dl`, `dt`, `dd`, `figure`, `figcaption`, `img`, `video`, `audio`, `source`, `canvas`, `table`, `thead`, `tbody`, `tfoot`, `tr`, `th`, `td`, `pre`, `code`, `details`, `summary`. No scripts, iframes, SVG, forms, comments or inline event attributes; media URLs must be `data:` or `blob:`.
@@ -329,6 +329,7 @@ export interface VideoGenerationModel {label:string;aspectRatios:readonly string
329
329
  export interface ImageSettings {model:string;aspectRatio:string;resolution:string;quality:string}
330
330
  export interface VideoSettings {model:string;duration:number;resolution:string;aspectRatio:string;generateAudio:boolean}
331
331
  /** Every image model a Block can use; the same catalog as the app's own Blocks. */
332
+ /** The image and video models this SDK version knew when it was published. A Block's screen lists the models StillMade runs now with `StillMade.models(operation)`; never copy this list into a Block. */
332
333
  export const IMAGE_GENERATION_MODELS: Readonly<Record<string,ImageGenerationModel>>;
333
334
  /** Every video model a Block can use; the same catalog as the app's own Blocks. */
334
335
  export const VIDEO_GENERATION_MODELS: Readonly<Record<string,VideoGenerationModel>>;
@@ -342,6 +343,19 @@ export function imageSettings(value?:Partial<ImageSettings>,options?:{strict?:bo
342
343
  /** Fill and check video settings, including Veo's 1080p-is-8-seconds rule. */
343
344
  /** With no model named, the default is the cheapest model that makes the asked aspect ratio and takes the frames given. */
344
345
  export function videoSettings(value?:Partial<VideoSettings>,options?:{strict?:boolean;firstFrame?:boolean;lastFrame?:boolean}):VideoSettings;
346
+ /** Model ids StillMade replaced, each pointing at its replacement: a Block or Step that names one runs on the replacement. */
347
+ export const RETIRED_IMAGE_MODELS: Readonly<Record<string,string>>;
348
+ export const RETIRED_VIDEO_MODELS: Readonly<Record<string,string>>;
349
+ /** The image model that runs for this id: itself, or the model that replaced it. */
350
+ export function currentImageModel(id:string):string;
351
+ /** The video model that runs for this id: itself, or the model that replaced it. */
352
+ export function currentVideoModel(id:string):string;
353
+ /** Whether a catalog model takes every setting asked for (a setting left out or 'default' always fits). */
354
+ export function modelTakes(kind:'image'|'video',model:string,settings?:Record<string,unknown>):boolean;
355
+ /** The hosted operations a Block may list models for: those it declares, and what its media actions make. */
356
+ export function modelOperationsFor(manifest:unknown):string[];
357
+ /** StillMade.models' answer from this SDK's own catalog, without prices (the SDK preview and tests). */
358
+ export function catalogModelList(operation:string):{operation:string;default:string|null;models:Array<{id:string;label:string;credits:number|null;available:boolean;settings:Array<{key:string;label:string;values:Array<string|number|boolean>;default:string|number|boolean}>;[key:string]:unknown}>};
345
359
  export function validateSeed(seed:number):number;
346
360
  export const MEDIA_GENERATE_LIMITS: Readonly<{items:50;prompt:4000}>;
347
361
  export const MEDIA_GENERATE_OPERATIONS: readonly ['image','video','speech','music','sfx'];
@@ -706,7 +720,7 @@ export function moduleRunClock(now?: () => number): Readonly<{progress(): void;
706
720
  export interface ModuleHost {
707
721
  input: Record<string, unknown>;
708
722
  signal: AbortSignal;
709
- progress(value: number, label?: string): void;
723
+ progress(value: number, label?: string, items?: {done: number; total: number}): void;
710
724
  log(...values: unknown[]): void;
711
725
  /** A copy of one of the module's own files, such as a .wasm build. */
712
726
  asset(path: string): ArrayBuffer;
@@ -7,14 +7,14 @@ The latest version of each Block bundled with the SDK, generated from the packag
7
7
  | --- | --- | --- | --- | --- |
8
8
  | `sm.ai-decide` 1.0.0 | AI decide | task | `data` text (external_data) · primary; `rules` text (decision_rules) | `decision` object (ai_decision) · primary |
9
9
  | `sm.ai-write` 1.0.0 | AI write | task | `data` text (external_data) · primary; `instructions` text (writing_instructions) | `text` text (written_message) · primary |
10
- | `sm.board-editor` 3.0.0 | White Board Motion | workspace | `narrationTiming` transcript (timed_transcript) · primary; `narrationAudio` audio (narration_audio); `board` board-document (whiteboard_document); `files` asset[] (export_media); `make` text (board_action); `request` json (board_request); `references` image[] (reference_image); `image` image (board_image); `firstFrame` image (first_frame); `lastFrame` image (last_frame); `dataUrl` text (file_data); `key` text (storage_key); `value` json (storage_value) | `output` board-document (whiteboard_document) · primary; `editorTimeline` timeline (editor_handoff); `export` asset (exported_video); `made` asset[] (made_media); `result` json (board_answer) |
10
+ | `sm.board-editor` 3.1.0 | White Board Motion | workspace | `narrationTiming` transcript (timed_transcript) · primary; `narrationAudio` audio (narration_audio); `board` board-document (whiteboard_document); `files` asset[] (export_media); `make` text (board_action); `request` json (board_request); `references` image[] (reference_image); `image` image (board_image); `firstFrame` image (first_frame); `lastFrame` image (last_frame); `dataUrl` text (file_data); `key` text (storage_key); `value` json (storage_value) | `output` board-document (whiteboard_document) · primary; `editorTimeline` timeline (editor_handoff); `export` video (finished_video); `made` asset[] (made_media); `result` json (board_answer) |
11
11
  | `sm.captions` 2.0.0 | Captions | editor-extension | `transcript` transcript · primary; `projectTranscript` transcript · project context only; `timeline` timeline · project context only; `text` text; `start` number; `duration` number; `highlightColor` text; `animation` text | `edit` timeline-edit · primary |
12
12
  | `sm.character` 2.0.1 | Character reference | workspace | `input` text (character_description) · primary; `name` text (character_name); `role` text (character_role); `appearance` text (character_appearance); `personality` text (character_personality) | `output` character (character_reference) · primary |
13
13
  | `sm.compare` 2.0.2 | Compare assets | workspace | `imageA` image (comparison_image_a) · primary; `imageB` image (comparison_image_b); `selection` text (comparison_selected_image); `position` integer (comparison_split_position) | `output` image (selected_comparison_image) · primary |
14
14
  | `sm.connect-app` 1.0.0 | Connect an app | task | `app` text (connected_app) · primary | `result` object (external_data) · primary |
15
15
  | `sm.contact-fields` 1.0.0 | Contact fields | task | `record` object (external_data) · primary | `email` text (contact_email) · primary; `first_name` text (contact_first_name); `last_name` text (contact_last_name); `full_name` text (contact_full_name); `phone` text (contact_phone); `company` text (contact_company); `city` text (contact_city); `other_answers` text (contact_other_answers) |
16
16
  | `sm.edit-assistant` 2.0.2 | Edit Assistant | task | `timeline` timeline (timeline) · project context only · primary; `selection` timeline-range (timeline_range); `request` text (edit_request) | `edit` timeline-edit (timeline_edit_proposal) · primary |
17
- | `sm.editor` 3.0.0 | Editor | workspace | `editor` timeline (editor_timeline) · primary; `cut` timeline (editor_handoff); `media` asset[] (source_media); `narrationAudio` audio (narration_audio); `narrationTiming` transcript (timed_transcript); `narrationScript` script (narration_script); `panelImages` asset[] (panel_images); `panels` panel-document (image_panel_document); `make` text (editor_action); `request` json (editor_request); `references` image[] (reference_image); `firstFrame` image (first_frame); `lastFrame` image (last_frame); `audio` asset (source_audio); `files` asset[] (export_media); `dataUrl` text (file_data); `key` text (storage_key); `value` json (storage_value) | `editor` timeline (editor_timeline) · primary; `export` asset (exported_video); `made` asset[] (made_media); `result` json (editor_answer) |
17
+ | `sm.editor` 3.1.0 | Editor | workspace | `editor` timeline (editor_timeline) · primary; `cut` timeline (editor_handoff); `media` asset[] (editable_media); `canvas` pipeline (canvas_document); `narrationAudio` audio (narration_audio); `narrationTiming` transcript (timed_transcript); `narrationScript` script (narration_script); `panelImages` asset[] (panel_images); `panels` panel-document (image_panel_document); `make` text (editor_action); `request` json (editor_request); `references` image[] (reference_image); `firstFrame` image (first_frame); `lastFrame` image (last_frame); `audio` asset (source_audio); `files` asset[] (export_media); `dataUrl` text (file_data); `key` text (storage_key); `value` json (storage_value) | `editor` timeline (editor_timeline) · primary; `export` video (finished_video); `exportAudio` audio (finished_audio); `made` asset[] (made_media); `result` json (editor_answer) |
18
18
  | `sm.export-audio` 2.0.0 | Export audio | task | `input` timeline (timeline) · primary; `renderFps` text (render_fps); `renderResolution` text (render_resolution); `renderQuality` text (render_quality); `projectTimeline` timeline (timeline) · project context only | `mediaAction` approval (portable_media_action); `output` audio (rendered_audio) |
19
19
  | `sm.export-timeline` 2.0.0 | Export timeline | editor-extension | `timeline` timeline · project context only · primary; `assets` asset[] · project context only; `blueprint` bible · project context only; `shotPlan` shot-plan · project context only; `metadata` metadata · project context only; `title` text; `format` text | `download` file-bundle · primary |
20
20
  | `sm.export-video` 2.1.0 | Export video | task | `input` timeline (timeline) · primary; `media` asset[] (project_media); `narration` audio (narration_audio); `renderAspect` text (render_aspect); `renderFps` text (render_fps); `renderResolution` text (render_resolution); `renderQuality` text (render_quality); `projectTimeline` timeline (timeline) · project context only; `projectShotPlan` shot-plan (production_shot_plan) · project context only | `mediaAction` approval (portable_media_action); `output` video (rendered_video) |
@@ -26,7 +26,7 @@ The latest version of each Block bundled with the SDK, generated from the packag
26
26
  | `sm.location` 2.0.1 | Location reference | workspace | `input` text (location_description) · primary; `name` text (location_name); `visualDetails` text (location_visual_details); `timeOfDay` text (location_time); `atmosphere` text (location_atmosphere) | `output` location (location_reference) · primary |
27
27
  | `sm.meta-creative` 1.0.0 | Meta ad creative | task | `upload` object (external_data) · primary; `message` text (ad_primary_text); `website_url` text (ad_destination_url); `page_id` text (facebook_page_id); `call_to_action` text (ad_call_to_action) | `creative` object (meta_ad_creative) · primary |
28
28
  | `sm.motion-graphics` 2.0.0 | Motion graphics | editor-extension | `timeline` timeline (timeline) · primary; `projectTimeline` timeline (timeline) · project context only; `plan` json (motion_graphics_plan) | `timeline` timeline (timeline) · primary; `edit` workspace-edit (editor_workspace_edit) |
29
- | `sm.panel-strip` 3.0.0 | Image Panels | workspace | `narrationTiming` transcript (timed_transcript) · primary; `narrationAudio` audio (narration_audio); `narrationScript` script (narration_script); `panels` panel-document (image_panel_document); `make` text (panel_action); `request` json (panel_request); `pictures` asset[] (panel_media); `references` image[] (reference_image); `image` image (panel_image); `firstFrame` image (first_frame); `lastFrame` image (last_frame); `dataUrl` text (picture_data); `key` text (storage_key); `value` json (storage_value) | `panels` panel-document (image_panel_document) · primary; `images` asset[] (panel_images); `made` asset[] (made_media); `result` json (panel_answer) |
29
+ | `sm.panel-strip` 3.1.0 | Image Panels | workspace | `narrationTiming` transcript (timed_transcript) · primary; `narrationAudio` audio (narration_audio); `narrationScript` script (narration_script); `panels` panel-document (image_panel_document); `make` text (panel_action); `request` json (panel_request); `pictures` asset[] (panel_media); `references` image[] (reference_image); `image` image (panel_image); `firstFrame` image (first_frame); `lastFrame` image (last_frame); `dataUrl` text (picture_data); `key` text (storage_key); `value` json (storage_value) | `panels` panel-document (image_panel_document) · primary; `images` asset[] (panel_images); `made` asset[] (made_media); `result` json (panel_answer) |
30
30
  | `sm.photo-editor` 1.0.0 | Photo editor | workspace | `photo` image (source_image) · primary; `edits` json (photo_edit_settings) | `image` image (processed_image) · primary |
31
31
  | `sm.photo-notes` 1.1.0 | Photo notes | task | `photos` image[] (photo_set) · primary | `notes` text (photo_notes) · primary |
32
32
  | `sm.pipeline` 2.2.6 | Canvas | workspace | `canvas` pipeline (canvas_document) · project context only · primary; `assets` asset[] (project_assets) · project context only; `versions` metadata[] (asset_versions) · project context only; `updatedCanvas` pipeline (canvas_document); `commit` boolean (canvas_commit_intent); `generatedImage` image (generated_image_version); `addGeneratedImage` boolean (attach_generated_image); `generatedImageTitle` text (generated_image_title); `generatedVideo` video (generated_video_version); `addGeneratedVideo` boolean (attach_generated_video); `generatedVideoTitle` text (generated_video_title); `generatedAudio` audio (generated_audio_version); `addGeneratedAudio` boolean (attach_generated_audio); `generatedAudioTitle` text (generated_audio_title); `connectionProposal` json (context_connection_proposal); `applyContextConnections` boolean (context_connection_intent) | `canvas` pipeline (canvas_document) · primary; `edit` workspace-edit (canvas_edit_proposal) |
@@ -38,4 +38,4 @@ The latest version of each Block bundled with the SDK, generated from the packag
38
38
  | `sm.voiceover` 3.0.0 | Voiceover | workspace | `input` script (narration_script) · primary; `script` text (voiceover_text); `voice` text (voice_choice); `speed` number (speech_speed); `take` audio (narration_audio); `takes` audio[] (voiceover_takes); `words` transcript (timed_transcript); `transcript` text (pasted_transcript); `make` text (voiceover_action) | `output` audio (narration_audio) · primary; `timing` transcript (timed_transcript); `spokenScript` script (narration_script); `takes` audio[] (voiceover_takes) |
39
39
  | `sm.when` 1.1.0 | Automation | task | `event` object (workflow_event); `start` object (automation_start); `only_if` text (automation_condition) | `item` object (external_data) · primary; `trigger` object (automation_trigger); `check` object (ai_decision) |
40
40
  | `sm.write-summary` 1.0.0 | Write summary | task | `data` text (external_data) · primary | `summary` text (summary_report) · primary |
41
- | `stillmade.generate` 1.0.0 | Generate | task | `prompt` text (generation_prompt) · primary; `mode` text (generation_mode); `model` text (generation_model); `aspectRatio` text (aspect_ratio); `size` text (render_resolution); `quality` text (render_quality); `duration` integer (duration_seconds); `sound` boolean (generate_audio); `count` integer (generation_count); `avoid` text (negative_prompt); `seed` integer (generation_seed); `references` image[] (reference_image); `startFrame` image (first_frame); `endFrame` image (last_frame) | `media` asset[] (generated_media) · primary; `notMade` text (generation_failures) |
41
+ | `stillmade.generate` 1.2.0 | Generate | task | `prompt` text (generation_prompt) · primary; `mode` text (generation_mode); `model` text (generation_model); `aspectRatio` text (aspect_ratio); `size` text (render_resolution); `quality` text (render_quality); `duration` integer (duration_seconds); `sound` boolean (generate_audio); `camera` text (camera_motion); `settings` json (generation_settings); `count` integer (generation_count); `avoid` text (negative_prompt); `seed` integer (generation_seed); `references` image[] (reference_image); `startFrame` image (first_frame); `endFrame` image (last_frame) | `media` asset[] (generated_media) · primary; `notMade` text (generation_failures) |
@@ -12,7 +12,7 @@ StillMade's own Blocks run on host services. Each one is listed here with the pu
12
12
  | Creates a visual style with generated samples. | hosted `image.generate`, permission `context.styles.read` | Same capability |
13
13
  | Credits or the person's own key for image, video, chat, voice, music and effects. | hosted `text.generate`, hosted `image.generate`, hosted `video.generate`, hosted `audio.speech`, hosted `audio.music`, hosted `audio.sfx` | Same capability |
14
14
  | Crops panel images. | `stillmade.files.read`, `stillmade.files.write`, `stillmade.media.transform` | Same capability |
15
- | Downloads the media a controller selected, under the export policy. | control `download`, port type `file-bundle`, action `final.render` | Same capability |
15
+ | Downloads the media a controller selected, under the export policy. | control `download`, `StillMade.download`, port type `file-bundle`, action `final.render` | Same capability |
16
16
  | Editor asset generation with a model, prompt, aspect, quality and reference images. | hosted `image.generate`, hosted `video.generate`, action `media.generate` | Same capability |
17
17
  | Edits panel settings with local runs and confirmations. | `StillMade.run`, `StillMade.updateShared` | Same capability |
18
18
  | Embeds the score, vibe and rough-cut panels in the Editor. | port type `timeline-edit`, permission `timeline.propose`, permission `context.timeline.read` | Same capability |
@@ -19,7 +19,7 @@ Each input and output is a named port (a lowercase letter, then letters, numbers
19
19
  | `semantic` | Required on every input and output of a new Block. The exact meaning in lowercase snake_case or dotted form (`company_profile`, `production_shot_plan`). Downstream matching uses it; different meanings never connect. |
20
20
  | `accepts` | Additional meanings this input also accepts, besides its own `semantic`. |
21
21
  | `schema` | Required on every `json` or `object` port (admission rejects one without it). Not JSON Schema: a field-type word or a map of field names to field-type words, nested for objects. Words are `string`, `text`, `number`, `integer`, `boolean`, `object`, `json`; add `[]` for a list and `?` for an optional field, for example `{title:"string",tags:"string[]?",size:{width:"integer",height:"integer"}}`. |
22
- | `sources` | Where the universal resolver may fill this input from: `upstream`, `project`, `adapter`, `chat`, `default`, `user`, `external`. Omit to allow all. `default` covers only the declared default; values a person enters in the Step or a view passes to `StillMade.run` count as `user`, so leave `user` allowed for anything the interface fills in. |
22
+ | `sources` | Where the universal resolver may fill this input from: `upstream`, `project`, `adapter`, `chat`, `default`, `user`, `external`, `view`. Omit to allow all. `default` covers only the declared default; values a person enters in the Step or a view passes to `StillMade.run` count as `user`, so leave `user` allowed for anything the interface fills in. |
23
23
  | `sensitivity` | A label for sensitive information; values marked secret, credential, password or token are never offered in chat. |
24
24
  | `freshnessPolicy` | `{maxAgeMs}`: the resolver rejects values older than this. |
25
25
  | `cardinality` | `one` or `many` values. |
@@ -12,6 +12,8 @@ Standard and frame views receive the same `StillMade` object; members marked Fra
12
12
  | `removeFile(port: string, index: number): Promise<{files: number}>` | A saved project Step and the SDK preview | Removes the file at `index` (from 0) of a declared list input (`image[]`, `video[]`, `audio[]`, `asset[]` or `file[]`), as the remove button on the Step's own tile does, and resolves to how many files the list now holds. Call it from a click or key press in the view; the new list arrives through `onInput`. A `<button data-stillmade-remove="port" data-index="2">` does this with no code. Refused for an undeclared, non-file or single input, an input an earlier Step fills, a viewer, Chat, a file that changed since the view was given it, and without a gesture. |
13
13
  | `moveFile(port: string, from: number, to: number): Promise<{files: number}>` | A saved project Step and the SDK preview | Moves the file at `from` to the place `to` (both from 0) in a declared list input, as the Step's own move earlier and move later buttons do, so the person sets the order (a tour, a slideshow) on the Block's own screen. Same gesture and refusals as `removeFile`. A `<button data-stillmade-move="port" data-index="2" data-to="1">` does this with no code; a `data-to` past either end does nothing. |
14
14
  | `run(values: In): Promise<Out>` | Every view | Checks the named input values, runs the Block (its recipe, JavaScript, module or hosted operation, after any approval the host asks for) and resolves to the checked outputs. On a project Step and in the SDK preview, inputs it leaves out run with the Step's current values (its photos, an earlier Step's result), so a view passes only what it sets; what it passes is kept as the Step's input. One run at a time, 30 a minute. |
15
+ | `models(operation: string): Promise<StillMadeModelList>` | Every view | The models StillMade runs right now for a hosted operation the Block declares (`image.generate`, `video.generate`, `audio.speech`, `text.generate` and the rest; a Block with the `media.generate` action may ask for image, video, speech, music and sound effects): each model's `id`, label, credits per call, whether it is available, its settings as data (`{key, label, values, default}`: shape, size, quality, length, sound) and what it takes (reference photos, first and last frames), with `default`, the model a run uses when none is named. Never keep a list of models in a Block: draw the picker from this when the screen opens, so models StillMade adds or retires later reach it with no new version, and pass the chosen `id` as `settings.model`. Leave the model out when the person did not pick one. The SDK preview answers from the SDK's own catalog, without prices. |
16
+ | `nextRun(plan: StillMadeNextRun \| null): void` | Every view | Tells StillMade what the screen's next run makes, so StillMade writes its price on the screen's run button (`data-stillmade-action="run"`), as its own run buttons read: "Generate image · 4 credits". `items` are the `media.generate` items the next run asks for, as the module will pass them to `stillmade.actions.run` (leave the prompt out; the price never reads it); `hosted` names the hosted operations it calls, each once unless `count` says more. Call it when a choice that changes the price changes (the model, shape, size, length, how many, the photos given); StillMade waits until the choices settle and prices them as the run will be priced. The Block never receives or writes the number, the button never moves, and a viewer or a run in progress shows none. `null` takes the price off. The SDK preview shows 0 credits. |
15
17
  | `useOutput(outputs: Partial<Out>): Promise<Record<string, unknown>>` | A saved project Step | Chooses media the view already holds (input media or a desktop recording) as the Block outputs without running. Only complete references the host issued are accepted. |
16
18
  | `readonly schemaFields: Readonly<Record<string, unknown>>` | Every view | Helpers that build form fields from the input ports and their schemas, honoring inputs the host locked. |
17
19
 
@@ -38,6 +40,7 @@ Standard and frame views receive the same `StillMade` object; members marked Fra
38
40
  | `previewMedia(value: StillMadeVideo \| StillMadeAudio): Promise<{kind: "video" \| "audio"; url: string; mimeType: string; bytes: number; blob: Blob}>` | Every view | A playable blob: URL for audio or video the view may play, and its bytes (blob) for decoding, such as a waveform. Call releaseMedia when done; a standard view holds two at a time, a frame view 24. |
39
41
  | `releaseMedia(url: string): void` | Every view | Frees a URL previewMedia returned. |
40
42
  | `previewVoice(voice: string): Promise<{url: string}>` | Every view | A playable blob: URL of the free sample of a StillMade voice (a voice id from the generation models list), for a Block that declares audio.speech. StillMade reads it; the view has no network. |
43
+ | `download(media: StillMadeImage \| StillMadeVideo \| StillMadeAudio): Promise<void>` | Every view | Saves a picture, clip or sound StillMade gave this screen (its inputs, its outputs, or what `getShared().made` lists) to the person's device through StillMade's own download, under the export policy (a plan that marks exports gets the mark, or the offer to upgrade). Call it from a click or key press in the view. A `<button data-stillmade-action="download">` does this with no code: it saves the largest picture or clip shown on the `sm-stage` that holds it (one the view showed with `previewImage` or `previewMedia`), so a stage's Download sits beside its run button. Refused for media StillMade did not give this screen, Chat, and without a gesture; in the SDK preview the browser saves the file as it is. |
41
44
  | `allowSpending(request: {credits: number, label: string}): Promise<{allowed: boolean, credits: number}>` | Every view | From a person's press of a button that showed a total, ask StillMade once to approve it: the Step's later runs spend from it silently at StillMade's prices for 30 minutes. In a project Step only, never from Chat, up to VIEW_LIMITS.allowanceCredits. |
42
45
  | `asset(path: string): string` | Frame views | A blob: URL for one of the view's own asset files, for <img>, new FontFace() or <video>. |
43
46
  | `readonly live: {start(options: {camera?: boolean; microphone?: boolean; fps?: number; width?: number; sampleRate?: number}): Promise<{live: true; camera: boolean; microphone: boolean; fps: number; width: number; height: number; sampleRate: number}>; stop(): Promise<{live: false}>; status(): Promise<{live: boolean; [key: string]: unknown}>; onFrame(callback: (frame: ImageBitmap, info: {index: number; timestamp: number; width: number; height: number}) => void): () => void; onAudio(callback: (samples: Float32Array, info: {index: number; timestamp: number; sampleRate: number}) => void): () => void; onEnd(callback: (event: {reason: string}) => void): () => void}` | Every view | The camera, the microphone or both as they happen (declare camera.capture and microphone.capture). Nothing reaches the view until the person presses Start in StillMade, which then shows that the device is on and a Turn off button. Frames arrive as ImageBitmaps, closed when your callback returns, so draw or copy them there; a slow view gets fewer frames, never a backlog. Audio arrives as mono Float32Array chunks. Nothing is saved. In browsers and StillMade Desktop. |