stillmade-sdk 0.1.49 → 0.1.51

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 (64) hide show
  1. package/AGENTS.md +6 -2
  2. package/AGENT_CONTROL.md +15 -0
  3. package/FRAME_VIEWS.md +30 -0
  4. package/INTERFACE_DESIGN.md +84 -7
  5. package/MODULES.md +2 -2
  6. package/PASTE_TO_LLM.md +26 -19
  7. package/SDK.md +180 -45
  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 +30 -0
  21. package/docs/build/interface-design.md +84 -7
  22. package/docs/build/modules.md +2 -2
  23. package/docs/reference/authoring-guide.md +4 -4
  24. package/docs/reference/errors.md +1 -1
  25. package/docs/reference/execution.md +18 -4
  26. package/docs/reference/first-party-blocks.md +4 -4
  27. package/docs/reference/parity.md +1 -1
  28. package/docs/reference/ports.md +1 -1
  29. package/docs/reference/view-bridge.md +3 -0
  30. package/docs/tools/llm.md +26 -19
  31. package/package.json +1 -1
  32. package/packages/block-cli/dev-host/assets/host.js +161 -33
  33. package/packages/block-cli/dev-host/assets/index.css +1 -1
  34. package/packages/block-platform/block-readiness.js +16 -3
  35. package/packages/block-platform/canvas.js +4 -1
  36. package/packages/block-platform/catalog.js +32 -5
  37. package/packages/block-platform/flow-inspection.js +30 -2
  38. package/packages/block-platform/listings.js +16 -2
  39. package/packages/block-platform/steps.js +4 -1
  40. package/packages/block-sdk/admission.js +2 -2
  41. package/packages/block-sdk/agent-inputs.js +4 -2
  42. package/packages/block-sdk/appearance.js +19 -4
  43. package/packages/block-sdk/builder-contract.js +7 -4
  44. package/packages/block-sdk/first-party-identities.js +9 -1
  45. package/packages/block-sdk/first-party-packages.js +11 -1
  46. package/packages/block-sdk/generation-catalog.js +55 -5
  47. package/packages/block-sdk/generation-models.js +60 -0
  48. package/packages/block-sdk/index.d.ts +18 -4
  49. package/packages/block-sdk/index.js +2 -1
  50. package/packages/block-sdk/input-source-policy.js +2 -1
  51. package/packages/block-sdk/module-hosted.js +13 -5
  52. package/packages/block-sdk/module-worker.js +1 -1
  53. package/packages/block-sdk/port-types.js +6 -2
  54. package/packages/block-sdk/semantic-contracts.d.ts +1 -1
  55. package/packages/block-sdk/semantic-relationships.js +3 -0
  56. package/packages/block-sdk/semantic-schema.js +4 -1
  57. package/packages/block-sdk/shared-controls.js +4 -1
  58. package/packages/block-sdk/staged-releases.js +2 -1
  59. package/packages/block-sdk/testing.js +3 -0
  60. package/packages/block-sdk/view-bridge.js +3 -0
  61. package/packages/block-sdk/view-design.js +56 -12
  62. package/packages/block-sdk/view-renderer.js +19 -6
  63. package/packages/block-sdk/view.js +3 -2
  64. 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.21.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 unless it sets view `appearance: "none"` (`src/view.config.json`), which only an app may: StillMade then adds none of its theme, colors, fonts, page or Block kit, the app's own CSS styles everything from the browser's defaults and the style checks are its own call, while shared state, inputs and outputs, hosted operations, the run button's price and StillMade's picker work as before; 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:
@@ -207,6 +231,12 @@ rest. Colors written with them follow Light, Dark and White automatically.
207
231
  Frame views are not required to use them; the appearance check reports
208
232
  hard-coded colors as advice instead of refusing the view.
209
233
 
234
+ An app (`"kind": "workspace"`) that wants none of StillMade's style sets
235
+ `"appearance": "none"` in `src/view.config.json`: StillMade then defines none of
236
+ these variables and adds no fonts, page styles or Block kit, so the app's own CSS
237
+ styles everything
238
+ ([An app with its own style](INTERFACE_DESIGN.md#an-app-with-its-own-style)).
239
+
210
240
  A color the check takes is a theme variable, `transparent` or `currentColor`,
211
241
  or a gradient of them (`linear-gradient(to top, var(--bg), transparent)`).
212
242
  `opacity` is not a color and is always allowed. `color-mix()`, `rgba()`,
@@ -32,10 +32,14 @@ operations and credits, StillMade's file picker, no prices on screen, a run
32
32
  button that names what it makes. The mode changes how the screen is laid out
33
33
  and worded; it grants no extra runtime or host access.
34
34
 
35
+ An app can also drop StillMade's style: see
36
+ [An app with its own style](#an-app-with-its-own-style). A job always keeps it.
37
+
35
38
  ## 1. Start from the Block kit
36
39
 
37
40
  StillMade adds the Block kit stylesheet (`packages/design-system/block-kit.css`)
38
- to every Block view, standard and frame. Use its classes and ship little or no
41
+ to every Block view, standard and frame, except an app that sets
42
+ `appearance: "none"`. Use its classes and ship little or no
39
43
  CSS of your own. Every kit rule has zero specificity, so your own CSS always
40
44
  wins. Open `packages/design-system/block-kit.html` in a browser to see every
41
45
  piece in Light, Dark and White.
@@ -101,7 +105,7 @@ place (rule 24).
101
105
  | `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
106
  | `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
107
  | `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. |
108
+ | `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
109
 
106
110
  **Function first.** When the person building a Block does not ask for a look,
107
111
  use the kit as is: they want it to work and be easy to use. When they name a
@@ -133,6 +137,17 @@ the same single priced button. Pressing a priced button runs at that price; a
133
137
  confirmation appears only when the server's price is higher than the one shown.
134
138
  Do not write credit numbers in the Block.
135
139
 
140
+ A custom view's own run button gets its price the same way (November 8, 2026):
141
+ the view tells StillMade what its next run makes, and StillMade writes the price
142
+ after the button's words, "Generate image · 4 credits". Call
143
+ `StillMade.nextRun({items})` with the `media.generate` items the run will ask for
144
+ (the prompt left out), or `StillMade.nextRun({hosted: ["text.generate"]})` for
145
+ the hosted operations it calls, whenever a choice that changes the price changes.
146
+ StillMade waits until the choices settle, prices them as the run will be priced,
147
+ leaves the button where it is, shows no price while the run works or to a
148
+ viewer, and the Block never sees the number. `StillMade.nextRun(null)` takes it
149
+ off.
150
+
136
151
  ## 3. Speak the job
137
152
 
138
153
  These are StillMade's defaults for every Block, and they apply wherever the
@@ -289,7 +304,9 @@ exception, never unrelated technical UI.
289
304
  segmented control in StillMade); never restyle it to an outline, a tint or
290
305
  the page's background. A locked row (`disabled`, or a person who can only
291
306
  view) keeps its pick filled, fades the other options and does not react to
292
- the pointer.
307
+ the pointer. A picture, or a pick shown by its picture (a thumbnail, a
308
+ shot, a swatch), is a rounded rectangle in the kit's radii, never a circle,
309
+ unless the person asks for one; readiness notes a circle.
293
310
  22. Media that cannot show here (kept on another device, or it did not open)
294
311
  keeps its place as a quiet `sm-media-off` tile in its own shape with the
295
312
  `image-off` glyph, the reason in its `aria-label` and `title`. Never words
@@ -320,8 +337,12 @@ exception, never unrelated technical UI.
320
337
  then "Make video again"); what the result adds (Download, Share) goes to
321
338
  its left. Controls on media (StillMade's player, the full-screen square)
322
339
  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.
340
+ while it is hovered or tapped. Under a rendered video the stage shows its
341
+ shots with their times and the line being said: a video from
342
+ `timeline.render` carries them as `parts` (`shots` with `start`, `end` and
343
+ the `source` each shows, `lines` with `start`, `end` and `text`), and the
344
+ status carries its length. StillMade's own Step pages do the same. An app
345
+ that lays itself out keeps its own toolbar.
325
346
 
326
347
  ```html
327
348
  <section class="sm-stage" data-ratio="9:16">
@@ -334,6 +355,17 @@ exception, never unrelated technical UI.
334
355
  </section>
335
356
  ```
336
357
 
358
+ The Download button needs no code (November 8, 2026): StillMade saves the
359
+ largest picture or clip the stage shows (one the view showed with
360
+ `previewImage` or `previewMedia`) through its own download, under the
361
+ export policy, as the Library does. It works in frame views. An icon fits
362
+ as well: `<button type="button" class="sm-icon" data-stillmade-action="download"
363
+ aria-label="Download" title="Download"><span class="sm-glyph"
364
+ data-glyph="download" aria-hidden="true"></span></button>`. From the view's
365
+ own code, `StillMade.download(media)` saves one picture, clip or sound
366
+ StillMade gave the screen. The run button's price is StillMade's too
367
+ (`StillMade.nextRun`, under "Decide every sentence").
368
+
337
369
  ## 4a. App rules
338
370
 
339
371
  An app (`"kind": "workspace"`) follows these instead of the job-only rules
@@ -355,12 +387,48 @@ An app (`"kind": "workspace"`) follows these instead of the job-only rules
355
387
  view, rare ones in menus. No intros or paragraphs on the surface.
356
388
  6. StillMade's appearance (Light, Dark and White) and the kit's controls
357
389
  wherever they fit; the layout is the app's own. A look the person names wins.
390
+ An app may drop StillMade's style instead (below).
358
391
  7. On a phone the work surface stays usable and the panels fold into sheets or
359
392
  one tool strip.
360
393
  8. What the person makes leaves the app as the Block's outputs (and, while they
361
394
  work, its shared state), so the next Step and the other people in the
362
395
  project get it.
363
396
 
397
+ ### An app with its own style
398
+
399
+ An app that should look like nothing but itself (a photo editor in its own
400
+ dark chrome, a CAD tool with its own palette) sets the view's appearance to
401
+ `"none"`: in `src/view.config.json` for an SDK folder, or `view.appearance` in
402
+ a package.
403
+
404
+ ```json
405
+ {"runtime": "frame", "appearance": "none"}
406
+ ```
407
+
408
+ StillMade then adds none of its style to the view: no theme variables (`--bg`,
409
+ `--fg` and the rest are undefined unless the app defines them), no StillMade
410
+ fonts, no page padding or background, no control styles and no Block kit, in
411
+ the local preview and in the app alike. The page starts from the browser's own
412
+ defaults, as if it were opened on its own (an opaque page in the canvas color
413
+ of its `color-scheme`, so the Step behind it never shows through), and the
414
+ app's CSS styles everything. The preview's appearance picker does not apply to
415
+ it. `<html data-mode>` still says whether the person uses Light, Dark or White,
416
+ for an app that wants to follow it.
417
+
418
+ Only an app may set it: `validatePackage` refuses `appearance: "none"` on a job
419
+ (`"kind": "task"`, or a capability Block with one hosted operation), which
420
+ keeps StillMade's style. The style rules are then the app's own call: its own
421
+ colors and tokens, filters, blending and animations are fine, the appearance
422
+ check passes, and the kit checks on round pictures and the page heading do not
423
+ apply. Everything else stays exactly as it is: shared state and multiplayer
424
+ controls, inputs and outputs, hosted operations and credits, the run button's
425
+ name and the price StillMade writes on it, StillMade's file picker
426
+ (`data-stillmade-add`), StillMade's player on `<video controls>`, the wording
427
+ rules and every security check. A standard (non-frame) view still uses no CSS
428
+ escapes and sets no styles from code: both guard its no-remote-resources rule.
429
+ Layout is separate: `data-stillmade-layout="own"` still decides whether
430
+ StillMade centers or zooms the view.
431
+
364
432
  ## 5. What StillMade checks
365
433
 
366
434
  For an app (`"kind": "workspace"`) only the shared checks run: a file input of
@@ -370,7 +438,9 @@ is kept), and the action-name rules on its run button
370
438
  (`data-stillmade-action="run"`) alone, so "OK" and "Go" are fine on its dialogs
371
439
  and tools. It is never asked for the Block kit page, a control or row count,
372
440
  labels beside fields, `data-stillmade-action` on every tool, or a visual
373
- surface instead of text (a code editor is an app). Everything below is the job
441
+ surface instead of text (a code editor is an app). An app with
442
+ `appearance: "none"` is also not asked for theme colors, rounded rather than
443
+ round pictures, or no page heading. Everything below is the job
374
444
  check list.
375
445
 
376
446
  - Admission rejects a primary textarea of 12 or more rows when no task
@@ -397,7 +467,14 @@ check list.
397
467
  - Admission also warns, and `stillmade-block ready` fails the "UI works" check
398
468
  (DOD-15), when a button says "Run", "Run step" or "Submit", when the
399
469
  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
470
+ (`blockKitProblems`; the failing rules are `BLOCK_KIT_READINESS_RULES`).
471
+ The same goes, standard and frame views alike, for a job page built from the
472
+ kit (`sm-block`) whose result is a picture, clip, sound, asset or document
473
+ and that has no `sm-stage`, or whose own run button
474
+ (`data-stillmade-action="run"`) is not in the stage's own `sm-actions` with
475
+ an `sm-status` beside it on the stage (rule 24; November 8, 2026). A page not
476
+ built from the kit is held to the kit rule instead, and a frame view that
477
+ builds its stage in code is not checked by the source. StillMade's own Block builder also sends back a custom
401
478
  view that is not built from `sm-block`, unless the person asked for their
402
479
  own look (`appearance: "original"`).
403
480
  - 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. An app may drop StillMade's style instead: view `appearance: "none"`, and StillMade adds none of its theme, colors, fonts or Block kit, so the app's own CSS styles everything and the style rules (the kit's ink fill, its radii, Light, Dark and White) are its own call. Shared state, inputs and outputs, hosted operations, the run button and StillMade's picker work as before. 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", or, in an app only, "none": StillMade then adds none of its theme, colors, fonts, page or Block kit and the app's own CSS styles everything. 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, imports/url(), or element.style mutations in JS, and no filters or blending except under appearance:"none". 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:`.
@@ -220,7 +220,7 @@ Generated from the SDK source (199 codes). The message is one example; the actua
220
220
  | TIMEOUT | JavaScript exceeded its cumulative 500 ms execution limit | `javascript`, `local-admission`, `package-jobs` and 2 more |
221
221
  | TYPE | … values must be an object | `contracts`, `javascript`, `recipe` |
222
222
  | VIDEO_ASSETS_HOST | Prompt recommendations require an image URL and text prompts | `video-assets-host` |
223
- | VIEW | View JavaScript exceeds review complexity | `view` |
223
+ | VIEW | Only an app (manifest.kind "workspace") can set view appearance "none"; a job keeps StillMade's style. Use "host", or "original" for colors of its own. | `testing`, `view` |
224
224
  | VIEW_STATE | Shared edit exceeds 800,000 bytes | `shared-view` |
225
225
  | WORKER_REQUIRED | Run JavaScript blocks through a worker | `runtime` |
226
226
  | WORKFLOW_APPROVAL | Workflow approval must be a required direct-user decision with no saved default or context source. | `semantic-contracts`, `workflow-approvals` |
@@ -69,9 +69,9 @@ export interface SdkCompatibility { status:'supported'|'unsupported'|'invalid';
69
69
  export function sdkCompatibility(declared:unknown,runtimeVersion?:string):SdkCompatibility;
70
70
  export type Port = { type: string; imageMode?: 'pixels'|'reference'; role?: string; context?: keyof typeof CONTEXT_FIELDS; primary?: boolean; required?: boolean; default?: unknown; min?: number; max?: number; description?: string } & Partial<import('./semantic-contracts.js').SemanticContract>;
71
71
  export interface Manifest { schemaVersion: 1; id: string; name: string; version: string; sdkVersion: SdkVersionRange; description: string; action?: { label: string; running?: string }; kind: 'task'|'workspace'|'editor-extension'|'composite'; inputs: Record<string, Port>; outputs: Record<string, Port>; runtime: 'recipe'|'javascript'|'module'|'comfyui'|'capability'; jobs?: {kind:'cooperative';version:1}; state?: {scope:'step';version:number;initial:Record<string,unknown>;migrations?:StateMigration[]}; entry?: string; permissions: { project: string[]; desktop?: DesktopPermission[]; capabilities?: ['comfyui.execute']|['text.generate']|['audio.speech']|['audio.transcribe']|['image.generate']|['video.generate']|['image.describe']|['timeline.propose']|['workspace.propose']|['connection.execute']; network?: never[]; filesystem?: never[]; secrets?: never[] }; desktopTools?:DesktopToolDeclaration[]; uiLayout?: {kind:'tabs'|'sections';groups:{id:string;label:string;inputs:string[];outputs:string[];collapsed?:boolean}[]}; ui?: ({ control: 'text'|'number'|'slider'|'checkbox'|'asset'; port: string; label?: string; when?: {port:string;equals:string|boolean} } | {control:'select'|'multiselect';port:string;options:string[];label?:string;when?:{port:string;equals:string|boolean}} | {control:'gallery';port:string;label?:string} | {control:'before-after';port:string;before:string;label?:string})[]; }
72
- export interface BlockView { html: string; css?: string; themedCss?: string; javascript?: string; appearance?: 'host'|'original'; }
72
+ export interface BlockView { html: string; css?: string; themedCss?: string; javascript?: string; appearance?: 'host'|'original'|'none'; }
73
73
  /** A frame view: any interface code (frameworks, SVG, canvas, drag) with assets named by SHA-256. */
74
- export interface FrameView { runtime: 'frame'; html: string; css?: string; themedCss?: string; javascript?: string; appearance?: 'host'|'original'; files?: {schemaVersion: 1; files: Record<string, {sha256: string; bytes: number; mediaType: string}>}; }
74
+ export interface FrameView { runtime: 'frame'; html: string; css?: string; themedCss?: string; javascript?: string; appearance?: 'host'|'original'|'none'; files?: {schemaVersion: 1; files: Record<string, {sha256: string; bytes: number; mediaType: string}>}; }
75
75
  export interface BlockPackage { portable?: PortableMetadata; manifest: Manifest; view?: BlockView|FrameView; code?: string; module?: ModuleDescriptor; comfyui?: ComfyWorkflow; capability?: ConnectedCapability|TextGenerationCapability|SpeechCapability|TranscriptionCapability|ImageGenerationCapability|VideoGenerationCapability|ImageDescriptionCapability|TimelineProposalCapability; recipe?: { schemaVersion: 1; steps: { id: string; op: string; args: Record<string, unknown> }[]; outputs: Record<string, unknown> }; tests: { name: string; input: Record<string, unknown>; expected?: Record<string, unknown>; state?: BlockState; expectedState?: BlockState; expectations?: Record<string, ConnectedResultExpectation|ComfyImageExpectation|CapabilityTextExpectation|CapabilityAudioExpectation|CapabilityTranscriptExpectation|CapabilityImageExpectation|CapabilityVideoExpectation|CapabilityTimelineExpectation> }[]; }
76
76
  /** Named sandbox action with fixed declared input settings and produced ports. */
77
77
  export interface BlockOperation {id:string;label:string;description:string;inputs:Record<string,string|number|boolean>;outputs:string[]}
@@ -232,7 +232,7 @@ export interface BlockMediaPreview { kind: 'video'|'audio'; url: string; mimeTyp
232
232
  export interface BlockViewBridge { useOutput(outputs: Record<string, BlockMediaReference|BlockMediaReference[]>): Promise<Record<string, unknown>>; previewMedia(value: BlockMediaReference): Promise<BlockMediaPreview>; releaseMedia(url: string): void; render(targetId: string, tree: BlockViewNode[]): void; onAction(callback: (event: BlockViewAction) => void|Promise<void>): () => void; desktop(operation: DesktopPermission|DesktopToolOperation|import('./block-storage.js').BlockStorageOperation|"record.start"|"record.stop"|"record.cancel"|"session.close"|"live.start"|"live.stop"|"live.status"|"folder.pick"|"folder.shared"|"folder.forget"|"folder.list", args?: Record<string,unknown>): Promise<Record<string,unknown>>; readonly input: Record<string, unknown>; onInput(callback: (input: Record<string, unknown>) => void): () => void; run(input: Record<string, unknown>): Promise<Record<string, unknown>>; previewImage(value: BlockImageReference|BlockImagePixels, options?: BlockImagePreviewOptions): Promise<BlockImagePreview>; }
233
233
 
234
234
  /** Validate hosted UI colors independently of the preserved original stylesheet. */
235
- export function validateViewAppearance(view?: BlockView, options?: {appearance?:'host'|'original'}): true;
235
+ export function validateViewAppearance(view?: BlockView, options?: {appearance?:'host'|'original'|'none'}): true;
236
236
  export const APPEARANCE_TOKENS: readonly string[];
237
237
 
238
238
  /** Inert API prompt for reviewed core image nodes. Endpoints and credentials belong to the host. */
@@ -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;