stillmade-sdk 0.1.47 → 0.1.49

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 (75) hide show
  1. package/AGENTS.md +6 -2
  2. package/FRAME_VIEWS.md +71 -5
  3. package/INTERFACE_DESIGN.md +65 -2
  4. package/MODULES.md +26 -3
  5. package/PASTE_TO_LLM.md +25 -10
  6. package/README.md +15 -0
  7. package/SDK.md +349 -51
  8. package/blocks/native/sm.board-editor/versions/3.0.0/package.js +921 -0
  9. package/blocks/native/sm.editor/versions/3.0.0/package.js +751 -0
  10. package/blocks/native/sm.panel-strip/versions/3.0.0/package.js +350 -0
  11. package/blocks/native/sm.photo-editor/versions/1.0.0/package.js +6693 -0
  12. package/blocks/native/sm.voiceover/versions/3.0.0/package.js +513 -0
  13. package/docs/build/block-builder-contract.md +2 -2
  14. package/docs/build/custom-interface.md +23 -3
  15. package/docs/build/frame-views.md +71 -5
  16. package/docs/build/hosted-text.md +8 -3
  17. package/docs/build/interface-design.md +65 -2
  18. package/docs/build/media-analysis.md +18 -1
  19. package/docs/build/modules.md +26 -3
  20. package/docs/build/with-your-ai.md +14 -0
  21. package/docs/build/workspaces.md +42 -0
  22. package/docs/reference/authoring-guide.md +4 -4
  23. package/docs/reference/cli.md +8 -3
  24. package/docs/reference/execution.md +13 -5
  25. package/docs/reference/first-party-blocks.md +5 -4
  26. package/docs/reference/generation-models.md +4 -0
  27. package/docs/reference/limits.md +9 -2
  28. package/docs/reference/permissions.md +4 -0
  29. package/docs/reference/view-bridge.md +6 -3
  30. package/docs/reference/view-rules.md +1 -1
  31. package/docs/tools/llm.md +25 -10
  32. package/package.json +1 -1
  33. package/packages/block-cli/cli.js +124 -12
  34. package/packages/block-cli/dev-host/assets/host.js +55 -30
  35. package/packages/block-cli/dev-host/assets/index.css +1 -1
  36. package/packages/block-cli/dev-server.js +74 -23
  37. package/packages/block-cli/format.js +20 -1
  38. package/packages/block-cli/ready-gate.js +34 -2
  39. package/packages/block-cli/templates.js +61 -0
  40. package/packages/block-cli/type-packages.js +7 -4
  41. package/packages/block-cli/workspace-dev.js +124 -0
  42. package/packages/block-cli/workspace-pack.js +62 -0
  43. package/packages/block-cli/workspace.js +304 -0
  44. package/packages/block-platform/block-readiness.js +5 -4
  45. package/packages/block-platform/canvas.js +9 -0
  46. package/packages/block-platform/catalog.js +5 -3
  47. package/packages/block-platform/listings.js +2 -2
  48. package/packages/block-platform/node-defaults.js +2 -2
  49. package/packages/block-platform/plain-descriptions.js +1 -0
  50. package/packages/block-platform/type-workspace.js +8 -0
  51. package/packages/block-sdk/admission.js +2 -2
  52. package/packages/block-sdk/builder-contract.js +3 -3
  53. package/packages/block-sdk/capabilities.js +32 -12
  54. package/packages/block-sdk/contracts.js +11 -2
  55. package/packages/block-sdk/first-party-identities.js +9 -0
  56. package/packages/block-sdk/first-party-packages.js +9 -1
  57. package/packages/block-sdk/folder-package.js +34 -2
  58. package/packages/block-sdk/generation-catalog.js +7 -0
  59. package/packages/block-sdk/index.d.ts +13 -5
  60. package/packages/block-sdk/index.js +1 -1
  61. package/packages/block-sdk/mock-host.js +7 -0
  62. package/packages/block-sdk/module-hosted.js +11 -1
  63. package/packages/block-sdk/module-media-access.js +5 -3
  64. package/packages/block-sdk/permission-summary.js +1 -1
  65. package/packages/block-sdk/portable-media-actions.d.ts +4 -1
  66. package/packages/block-sdk/portable-media-actions.js +16 -1
  67. package/packages/block-sdk/remix-policy.js +2 -2
  68. package/packages/block-sdk/staged-releases.js +5 -0
  69. package/packages/block-sdk/stillmade-pack.js +64 -0
  70. package/packages/block-sdk/testing.js +2 -2
  71. package/packages/block-sdk/view-bridge.js +6 -3
  72. package/packages/block-sdk/view-design.js +59 -12
  73. package/packages/block-sdk/view.js +47 -9
  74. package/packages/design-system/block-kit.css +6 -0
  75. package/packages/design-system/theme.css +2 -0
package/SDK.md CHANGED
@@ -197,11 +197,25 @@ The archive needs Node.js 22 or later and nothing else; its runtime is bundled,
197
197
  4. **Run the readiness gate before you call anything ready:** `npx stillmade-block gate <name>` on the Block's own folder (`gate .` in a folder that also holds the extracted SDK checks the SDK's examples too). Report the exact status it prints. If it fails, fix the listed checks and run it again. If you cannot fix them, tell the person plainly that the Block is not ready and list the failing checks. A browser application reports NEEDS STILLMADE REVIEW: tell the person to import it so StillMade can run its isolated review.
198
198
  5. **Hand it over.** Give the person the full path of the `<name>` folder. In StillMade they choose **Import folder** and select that folder.
199
199
 
200
+ ### Several Blocks, connectors, automations and workflows
201
+ When the person asks for more than one Block, a Block that uses a signed-in app (Gmail, Slack, Notion...), something that runs by itself, or a workflow that joins Blocks, build them in one [workspace](/docs/build/workspaces) instead of separate folders:
202
+ ```sh
203
+ npx stillmade-block init <kit-name> --name "<Kit name>"
204
+ cd <kit-name> && npm install
205
+ npx stillmade-block add <block-name> <template> # once per Block
206
+ npx stillmade-block add <name> connector --app gmail # a Block that uses a signed-in app
207
+ npx stillmade-block add <name> trigger-schedule # a Block that runs on a schedule
208
+ npx stillmade-block add-type <workflow-name> <block-name> ... [--when schedule]
209
+ npx stillmade-block test && npx stillmade-block gate # every Block and workflow
210
+ npx stillmade-block pack # one .stillmade-pack file
211
+ ```
212
+ Read the workspace's own `AGENTS.md` first; every Block in it follows the same contract. Keep each later Block for this person in the same workspace with `add`. `npx stillmade-block dev` previews all of them in one page. Report the gate's status for each Block, then give the person the full path of the `.stillmade-pack` file: in StillMade they choose **Import package**, pick it, and choose which items to bring in.
213
+
200
214
  `stillmade-blocks` contains hooks that run the gate automatically when a Claude Code or Cursor session started in that folder finishes a turn. In the session that downloaded the SDK those hooks are not active yet, so run the gate yourself as in step 4.
201
215
 
202
216
  ## Block builder contract
203
217
 
204
- Contract `stillmade.block-builder@1.17.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.
218
+ 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.
205
219
 
206
220
  ### Source of truth
207
221
 
@@ -270,7 +284,7 @@ These are StillMade's defaults: unless the person or the creator asked for somet
270
284
 
271
285
  ### Interface
272
286
 
273
- 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), looks and works like that kind of app instead of a compact page: 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.
287
+ 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.
274
288
 
275
289
  ### Shared editing
276
290
 
@@ -922,7 +936,12 @@ is rejected. Catch and display the error; do not fall back to fetching the URL.
922
936
 
923
937
  The result is exactly `{url, width, height}`. Its URL is a PNG data URL
924
938
  that can be assigned to a declared `<img>`. It is a display copy, at most
925
- 512 pixels on either edge, with aspect ratio preserved. It does not generate,
939
+ 512 pixels on either edge, with aspect ratio preserved. An editor that works on
940
+ the picture itself (a photo editor, a mask or crop tool) asks for a sharper copy
941
+ with `await StillMade.previewImage(value, {edge: 2048})`: `edge` is the longest
942
+ side, from 64 to 2048 pixels, and never more than the image's own size. A large
943
+ copy that is over 3 MB as a PNG comes back as a JPEG data URL when the picture
944
+ has no transparency, and as a smaller PNG when it has some; draw it the same way. It does not generate,
926
945
  upload, save, select, or replace an asset. Continue to pass the **original**
927
946
  image value to `StillMade.run`; processing uses the original input within
928
947
  the runtime's own limits. Do not use the display URL as a processing input.
@@ -1027,10 +1046,25 @@ renders leave the previous controls intact. Text is always literal; scripts,
1027
1046
  inline handlers, links, file inputs, network attributes, and raw HTML are rejected.
1028
1047
 
1029
1048
 
1049
+ ### Spending approvals
1050
+
1051
+ A press of the Block's own button pays for one run's hosted calls at StillMade's
1052
+ price. A screen whose button starts many runs (a build that makes thirty pictures,
1053
+ one run each) asks once instead: from that press, call
1054
+ `await StillMade.allowSpending({credits, label})` with the total the button showed
1055
+ and its words. StillMade shows its own dialog; when the person allows it, that
1056
+ Step's later runs spend from the total silently, each call at StillMade's own
1057
+ price, for 30 minutes, and a call past what is left asks as usual. It resolves
1058
+ with `{allowed, credits}`. It works only from a person's click or key press in a
1059
+ project Step (never from Chat), up to 2,000 credits (`VIEW_LIMITS.allowanceCredits`).
1060
+
1030
1061
  ### Audio and video playback
1031
1062
 
1032
- `await StillMade.previewMedia(reference)` opens an authorized `audio` or `video`
1033
- reference as a temporary playback copy and returns `{kind, url, mimeType, bytes}`.
1063
+ `await StillMade.previewMedia(reference)` opens an authorized `image`, `audio` or `video`
1064
+ reference as a temporary copy and returns `{kind, url, mimeType, bytes}`. A picture comes back as the
1065
+ original file at full size (an animated GIF keeps moving); `previewImage` gives a small still instead.
1066
+ A frame view keeps up to `FRAME_VIEW_LIMITS.mediaPreviews` media previews open at once (a standard view two);
1067
+ release one with `StillMade.releaseMedia(url)` when it scrolls away or closes.
1034
1068
  Assign its URL to an existing `<audio controls>` or `<video controls>` element.
1035
1069
  StillMade plays a `<video controls>` in its own player, with full screen; leave
1036
1070
  out `controls` when the Block drives playback itself, or add
@@ -1217,11 +1251,16 @@ The supported subset is `type` (one or a list of `object`, `array`, `string`,
1217
1251
  `maxItems`, `minLength`, `maxLength`, `pattern`, `format` (`date`,
1218
1252
  `date-time`, `email`, `uri`), `minimum` and `maximum`; up to 8 levels deep and
1219
1253
  8,192 characters. The system instructions plus the schema instruction must fit
1220
- in 8,000 characters. Fixture expectations for a json output are
1254
+ in 32,000 characters. Fixture expectations for a json output are
1221
1255
  `{"kind":"json"}` with optional `includes` literal phrases.
1222
1256
 
1223
- Limits: package 256 KiB; prompt 32,000 characters; system instructions 8,000
1224
- characters; generated text 32,768 characters; `maxTokens` integer 1–4096.
1257
+ Limits: package 256 KiB; prompt 64,000 characters; system instructions 32,000
1258
+ characters; generated text 131,072 characters; `maxTokens` integer 1–24,000.
1259
+ A call within the flat size (prompt and system together up to 40,000 bytes,
1260
+ `maxTokens` up to 4,096) costs the model's flat price. A longer call (a long
1261
+ structured job such as a plan) costs more: the larger of 4.5 times a typical
1262
+ call of its size and the most it can cost, and never less than the flat price.
1263
+ StillMade quotes it before it runs.
1225
1264
  The operation is one text generation call. Image, audio, video, tool calling,
1226
1265
  streaming, multi-call agent loops, and arbitrary API requests are not supported
1227
1266
  by this declaration.
@@ -2103,7 +2142,24 @@ with timestamps in seconds; each list holds up to 20 entries. A `text` output
2103
2142
  receives the summaries (numbered when there are several images).
2104
2143
 
2105
2144
  Each analyzed image or video is one priced call, paid with StillMade credits;
2106
- the quote lists the price for every fixture and the sample. Media that cannot
2145
+ the quote lists the price for every fixture and the sample.
2146
+
2147
+ **Your own question over several pictures (`reply: "text"`).** To ask about one
2148
+ to ten pictures together in your own words and get the model's answer as text
2149
+ (where labels should sit on a frame, whether frames share one style), add
2150
+ `"reply":"text"`, optional `"instructions"` (the model's instructions, up to
2151
+ 8,000 characters) and optional `"maxTokens"` (1 to 4,000), and declare one
2152
+ `image` or `image[]` input and one `text` output:
2153
+
2154
+ ```json
2155
+ {"schemaVersion":1,"operation":"media.analyze","media":{"$input":"frames"},
2156
+ "question":"Do these frames share one art style? Answer as JSON.",
2157
+ "instructions":"You check art-style consistency.","reply":"text","maxTokens":900,"output":"answer"}
2158
+ ```
2159
+
2160
+ The question may be up to 8,000 characters. All the pictures go in one vision
2161
+ call, priced per picture as an analysis is. A module calls it as
2162
+ `stillmade.hosted.run('media.analyze', {media, question, reply: 'text', instructions, maxTokens})`. Media that cannot
2107
2163
  be read (not saved in the library, too large, or undecodable) is refunded. The
2108
2164
  offline SDK includes `packages/block-sdk/media-analysis-example.js`, exporting
2109
2165
  `mediaAnalysis`.
@@ -3789,7 +3845,7 @@ Standard and frame views receive the same `StillMade` object; members marked Fra
3789
3845
 
3790
3846
  | Member | Where | What it does |
3791
3847
  | --- | --- | --- |
3792
- | `getShared(): {state: Record<string, unknown>; outputs: Record<string, unknown>; memory: unknown; canEdit: boolean}` | Every view | A copy of the view state shared by everyone in the project, the last outputs, the Block memory and whether this person may edit. |
3848
+ | `getShared(): {state: Record<string, unknown>; outputs: Record<string, unknown>; made: Array<StillMadeImage \| StillMadeVideo \| StillMadeAudio>; memory: unknown; canEdit: boolean}` | Every view | A copy of the view state shared by everyone in the project, the last outputs, every picture, clip or sound this Step's runs made (newest first, up to 400, which the view may preview and pass to a run), the Block memory and whether this person may edit. |
3793
3849
  | `onShared(callback: (shared: ReturnType<StillMadeView["getShared"]>) => void): () => void` | Every view | Receives the shared state now and after every change by anyone. Returns a function that stops listening. |
3794
3850
  | `updateShared(values: Record<string, unknown>, preconditions?: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>` | Every view | Changes shared fields. Keep each independently edited value in its own field so two people never conflict; a field someone else changed first is refused. |
3795
3851
  | `bindShared(key: string, id: string): () => void` | Every view | Binds a form element (by id) to one shared field: it shows other people's edits and saves this person's edits, keeping drafts while they type. |
@@ -3804,12 +3860,15 @@ Standard and frame views receive the same `StillMade` object; members marked Fra
3804
3860
 
3805
3861
  | Member | Where | What it does |
3806
3862
  | --- | --- | --- |
3807
- | `previewImage(value: StillMadeImage): Promise<{url: string; width: number; height: number}>` | Every view | A display-only PNG copy (at most 512 pixels a side) of an image the view may show. Keep passing the original value to run. |
3808
- | `previewMedia(value: StillMadeVideo \| StillMadeAudio): Promise<{kind: "video" \| "audio"; url: string}>` | Every view | A playable blob: URL for audio or video the view may play. Call releaseMedia when done. |
3863
+ | `previewImage(value: StillMadeImage, options?: {edge?: number}): Promise<{url: string; width: number; height: number}>` | Every view | A display-only copy of an image the view may show: 512 pixels on its longest side, or `edge` (64 to 2048) for an editor that needs more. PNG, or JPEG for a large opaque copy. Keep passing the original value to run. |
3864
+ | `previewMedia(value: StillMadeVideo \| StillMadeAudio): Promise<{kind: "video" \| "audio"; url: string; mimeType: string; bytes: number; blob: Blob}>` | Every view | A playable blob: URL for audio or video the view may play, and its bytes (blob) for decoding, such as a waveform. Call releaseMedia when done; a standard view holds two at a time, a frame view 24. |
3809
3865
  | `releaseMedia(url: string): void` | Every view | Frees a URL previewMedia returned. |
3866
+ | `previewVoice(voice: string): Promise<{url: string}>` | Every view | A playable blob: URL of the free sample of a StillMade voice (a voice id from the generation models list), for a Block that declares audio.speech. StillMade reads it; the view has no network. |
3867
+ | `allowSpending(request: {credits: number, label: string}): Promise<{allowed: boolean, credits: number}>` | Every view | From a person's press of a button that showed a total, ask StillMade once to approve it: the Step's later runs spend from it silently at StillMade's prices for 30 minutes. In a project Step only, never from Chat, up to VIEW_LIMITS.allowanceCredits. |
3810
3868
  | `asset(path: string): string` | Frame views | A blob: URL for one of the view's own asset files, for <img>, new FontFace() or <video>. |
3811
3869
  | `readonly live: {start(options: {camera?: boolean; microphone?: boolean; fps?: number; width?: number; sampleRate?: number}): Promise<{live: true; camera: boolean; microphone: boolean; fps: number; width: number; height: number; sampleRate: number}>; stop(): Promise<{live: false}>; status(): Promise<{live: boolean; [key: string]: unknown}>; onFrame(callback: (frame: ImageBitmap, info: {index: number; timestamp: number; width: number; height: number}) => void): () => void; onAudio(callback: (samples: Float32Array, info: {index: number; timestamp: number; sampleRate: number}) => void): () => void; onEnd(callback: (event: {reason: string}) => void): () => void}` | Every view | The camera, the microphone or both as they happen (declare camera.capture and microphone.capture). Nothing reaches the view until the person presses Start in StillMade, which then shows that the device is on and a Turn off button. Frames arrive as ImageBitmaps, closed when your callback returns, so draw or copy them there; a slow view gets fewer frames, never a backlog. Audio arrives as mono Float32Array chunks. Nothing is saved. In browsers and StillMade Desktop. |
3812
3870
  | `assetBytes(path: string): ArrayBuffer` | Frame views | A copy of one of the view's own asset files. |
3871
+ | `loadCode(path: string): Promise<void>` | Frame views | Runs one of the view's own code files (a .js file in src/view-files/) once, when the view needs it, so a large interface starts with only the code its first screen uses. StillMade checks the file against its digest first. The file is one classic script; what it defines is the view's to use. |
3813
3872
 
3814
3873
  ### Generated interface
3815
3874
 
@@ -3927,10 +3986,13 @@ Every number below is read from the SDK constant named under its heading, the sa
3927
3986
  | Styles | 32 KiB |
3928
3987
  | Script | 64 KiB |
3929
3988
  | One message to the host | 4 MiB |
3930
- | StillMade.run calls per minute | 30 |
3931
- | Image previews per minute | 60 |
3989
+ | StillMade.run calls per minute | 120 |
3990
+ | Picture and media previews per minute | 240 |
3991
+ | Voice samples per minute | 30 |
3992
+ | Largest spending approval (credits) | 2,000 |
3932
3993
  | One image preview | 15 s |
3933
3994
  | Preview image side (pixels) | 512 |
3995
+ | Sharper preview image side, for editors (pixels) | 2,048 |
3934
3996
 
3935
3997
  ### Frame views
3936
3998
 
@@ -3943,6 +4005,10 @@ Every number below is read from the SDK constant named under its heading, the sa
3943
4005
  | Script bundle (view.js) | 4 MiB |
3944
4006
  | Asset files | 32 |
3945
4007
  | All asset files | 8 MiB |
4008
+ | Code files (StillMade.loadCode) | 64 |
4009
+ | One code file | 4 MiB |
4010
+ | All code files | 16 MiB |
4011
+ | Playable media previews open at once (previewMedia) | 24 |
3946
4012
  | One task before it is stopped | 250 ms |
3947
4013
  | A blocking native operation before the view is closed | 1 s |
3948
4014
 
@@ -4192,6 +4258,49 @@ Every number below is read from the SDK constant named under its heading, the sa
4192
4258
  | Values per enum | 64 |
4193
4259
  | Characters in a schema string | 4,000 |
4194
4260
 
4261
+ ## Several Blocks in one folder
4262
+
4263
+ ### A workspace
4264
+ One folder holds several Blocks, the code they share and the workflows (Project Types) that join them. Every Block in it is still its own Block: it passes its own readiness gate and imports on its own.
4265
+
4266
+ ```text
4267
+ listing-kit/
4268
+ stillmade.workspace.json {"schemaVersion":1,"name":"Listing kit","version":"1.0.0"}
4269
+ blocks/<name>/ one Block folder each, as create makes it
4270
+ shared/ code module Blocks share
4271
+ workflows/<name>.json a Project Type whose Steps name Blocks by folder
4272
+ AGENTS.md, CLAUDE.md the workspace guide and the Block builder contract, for coding agents
4273
+ ```
4274
+
4275
+ ### Start one and add Blocks
4276
+ ```sh
4277
+ npx stillmade-block init listing-kit --name "Listing kit"
4278
+ cd listing-kit && npm install
4279
+ npx stillmade-block add listing-facts module
4280
+ npx stillmade-block add listing-digest connector --app gmail
4281
+ npx stillmade-block add price-drop-alert trigger-schedule
4282
+ npx stillmade-block add-type listing-video listing-facts listing-digest
4283
+ ```
4284
+ `init` writes the folders, a package.json with the SDK and esbuild, the agents' guides (AGENTS.md for Codex, CLAUDE.md for Claude Code, a Cursor rule) and the readiness gate as their end-of-turn hook. The guides tell any agent that new Blocks go in this folder with `add`, so asking for another Block puts it here and the running preview shows it. `add` takes every template `create` takes; it works only inside a workspace.
4285
+
4286
+ ### Shared code
4287
+ A module Block in a workspace keeps its code in `src/main.js`. It may import from `shared/` (`import {x} from '../../../shared/x.js'`); `npx stillmade-block build` bundles it into the Block's `module/main.js` with esbuild, so every packed Block stands alone. Code that imports nothing needs no bundler and is copied as it is. Never edit a built `module/main.js`: `test`, `gate` and `pack` build first, and refuse a `module/main.js` that imports another file.
4288
+
4289
+ ### Workflows
4290
+ A workflow file is a Project Type whose Steps may name a Block by its folder: `{"id":"facts","block":"listing-facts"}`. Its id and version come from that Block, and `test`, `run` and `pack` embed the Block. `add-type` connects consecutive Steps whose ports share a name, or a meaning and type; connect any other output to an input yourself in `connections`: `{"from":{"stage":"facts","port":"facts"},"to":{"stage":"voice","port":"facts"}}` (Step ids, then port names). Inside a workspace, commands take a Block's folder name alone (`test listing-facts`). `--when schedule`, `--when webhook` or `--when app:gmail/GMAIL_NEW_GMAIL_MESSAGE` starts it with StillMade's When Step, which makes it an automation: what starts each run is turned on in the project's Automation page. Workflow checks answer each Block's hosted and app calls from that Block's `tests/mocks.json`. Inputs for the preview's run can live in `workflows/<name>.samples.json`, keyed by Step id.
4291
+
4292
+ ### Connector Blocks
4293
+ `add <name> connector --app <app>` starts a Block of your own that runs a signed-in app's action (`permissions.connections`: `app:<app>`) with `stillmade.connections.call`. Gmail, Slack, Notion and Google Sheets start from a working action with a recorded answer; any other app starts from a placeholder action name to replace. People connect the app on the Block's first run and see each action's price; the account never reaches the Block.
4294
+
4295
+ ### Preview everything
4296
+ `npx stillmade-block dev` in the workspace folder opens one preview (http://127.0.0.1:4800) of every Block, workflow and shared file. The side list shows each item's state (gate passed, needs a fix, not checked since it changed, being built) and folds to a rail of icons. The Block the agent last saved opens by itself; switch off Follow the agent to stay where you are. Each Block has the usual preview with a Checks tab: what it still needs (description, license holder, feature map), its build from `src/main.js`, and the readiness gate's verdict with a button to run it. A workflow shows its Steps and runs them all on this machine with stand-ins, Step by Step: it proves the Steps fit together, never the real output, which runs only in StillMade.
4297
+
4298
+ ### Check and pack everything
4299
+ `test`, `validate` and `gate` in the workspace folder cover every Block and workflow; the gate judges only what changed. `npx stillmade-block pack` writes one `<name>-<version>.stillmade-pack`: a ZIP holding `pack.json` (each item's kind, id, version, file and SHA-256) and each Block and workflow exactly as `pack` writes it alone.
4300
+
4301
+ ### Import the ones you choose
4302
+ In StillMade, Create, then Import takes the `.stillmade-pack`, several package files at once, a workspace folder, or a ZIP of one. StillMade lists what it found (Blocks, connector and automation Blocks, workflows), says which are new, updates of yours, or already yours, and asks which to bring in. Ticking a workflow ticks the Blocks it needs. Each chosen item then opens in the usual import review, one after another, with its own checks. A workspace workflow comes in through the pack, which embeds its Blocks.
4303
+
4195
4304
  ## Preview with hot reload
4196
4305
 
4197
4306
  `stillmade-block dev <folder>` opens a local preview at `http://127.0.0.1:4800`
@@ -5796,6 +5905,8 @@ export function testPackage(pkg: BlockPackage, options?: { timeoutMs?: number; c
5796
5905
  export const SDK_VERSION: '0.1.0';
5797
5906
  export const ROOT_CHAT_ID: 'stillmade.starting-chat';
5798
5907
  export function stableStringify(value: unknown): string;
5908
+ /** Whether `candidate` is the published first-party package `published` (its carried module text, moduleSource, left out). */
5909
+ export function sameFirstPartySource(published: unknown, candidate: unknown): boolean;
5799
5910
  /** Throws when any text or field name in a package holds the NUL character (\u0000), which StillMade cannot store; the message names where it is (tests.1.mocks.files.tiny-1.text). Import review, validate, test, pack and ready run it. */
5800
5911
  export function assertStorableText(value: unknown): void;
5801
5912
  export function validateManifest(value: unknown): { valid: boolean; errors: { path: string; message: string }[] };
@@ -5876,7 +5987,7 @@ export function resolveContextInputs(manifest: Manifest, values: Record<string,
5876
5987
  export function projectReadSnapshot(manifest: Manifest, context?: Record<string, unknown>, fields?: string[]): Record<string, unknown>;
5877
5988
 
5878
5989
  export function validateView(view: unknown): true;
5879
- export const FRAME_VIEW_LIMITS: Readonly<{htmlBytes:262144;cssBytes:262144;javascriptBytes:4194304;assetFiles:32;assetBytes:8388608;taskMs:250;longTaskMs:1000}>;
5990
+ export const FRAME_VIEW_LIMITS: Readonly<{htmlBytes:262144;cssBytes:262144;javascriptBytes:4194304;assetFiles:32;assetBytes:8388608;codeFiles:64;codeFileBytes:4194304;codeBytes:16777216;mediaPreviews:24;taskMs:250;longTaskMs:1000}>;
5880
5991
  export const FRAME_VIEW_ASSET_TYPES: readonly string[];
5881
5992
  export function isFrameView(view: unknown): view is FrameView;
5882
5993
  export function validateFrameView(view: FrameView): true;
@@ -5886,11 +5997,13 @@ export function parseFrameViewJavaScript(source: string): {ast: unknown};
5886
5997
  export function instrumentFrameViewJavaScript(source: string, guard: string): string;
5887
5998
  export function describeViewFiles(files: Record<string, Uint8Array|ArrayBuffer|string>): Promise<NonNullable<FrameView['files']>>;
5888
5999
  /** In a frame view, the StillMade object also offers its asset files. */
5889
- export interface FrameViewAssets { asset(path: string): string; assetBytes(path: string): ArrayBuffer; }
5890
- export const VIEW_LIMITS: Readonly<{htmlBytes:65536;cssBytes:32768;javascriptBytes:65536;messageBytes:4194304;runsPerMinute:30;imagePreviewsPerMinute:60;imagePreviewMs:15000;imagePreviewEdge:512}>;
6000
+ export interface FrameViewAssets { asset(path: string): string; assetBytes(path: string): ArrayBuffer; loadCode(path: string): Promise<void>; }
6001
+ export const VIEW_LIMITS: Readonly<{htmlBytes:65536;cssBytes:32768;javascriptBytes:65536;messageBytes:4194304;runsPerMinute:120;imagePreviewsPerMinute:240;voicePreviewsPerMinute:30;allowanceCredits:2000;imagePreviewMs:15000;imagePreviewEdge:512;imagePreviewMaxEdge:2048}>;
5891
6002
 
5892
6003
  /** A display-only copy. Never substitute this URL for the original run input. */
5893
- export interface BlockImagePreview { url: `data:image/png;base64,${string}`; width: number; height: number; }
6004
+ export interface BlockImagePreview { url: `data:image/png;base64,${string}`|`data:image/jpeg;base64,${string}`; width: number; height: number; }
6005
+ /** `edge`: the longest side of the display copy, 64 to 2048 pixels (default 512). */
6006
+ export interface BlockImagePreviewOptions { edge?: number; }
5894
6007
  export interface BlockImagePixels { width: number; height: number; data: number[]; role?: string; }
5895
6008
  export interface BlockImageReference { kind: 'image'; assetId: string; versionId: string; url: string; role?: string; }
5896
6009
  export type DesktopPermission = "screen.capture"|"cursor.track"|"camera.capture"|"microphone.capture"|"clipboard.read"|"clipboard.write"|"media.pick"|"notifications.show"|"power.keep-awake"|"native.tools"|"folder.read"|"folder.write";
@@ -5918,7 +6031,7 @@ export interface BlockViewNode { tag: BlockViewTag; text?: string; children?: Bl
5918
6031
  export interface BlockViewAction { action: string; value: string; checked: boolean; event: 'click'|'input'|'change'; }
5919
6032
  export interface BlockMediaReference { kind: 'video'|'audio'; assetId: string; versionId: string; url: string; role?: string; }
5920
6033
  export interface BlockMediaPreview { kind: 'video'|'audio'; url: string; mimeType: string; bytes: number; }
5921
- 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): Promise<BlockImagePreview>; }
6034
+ 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>; }
5922
6035
 
5923
6036
  /** Validate hosted UI colors independently of the preserved original stylesheet. */
5924
6037
  export function validateViewAppearance(view?: BlockView, options?: {appearance?:'host'|'original'}): true;
@@ -6001,6 +6114,10 @@ export type CapabilityExecutor = (pkg: BlockPackage, input: Record<string, unkno
6001
6114
  export const CAPABILITY_LIMITS: Readonly<{prompt:32000;system:8000;output:32768;maxTokens:4096;fixtures:3;includes:8;includeLength:256;packageBytes:262144;metadataBytes:8192}>;
6002
6115
  /** media.analyze bounds: images per run, question length and the closer-inspected video range in seconds. */
6003
6116
  export const MEDIA_ANALYZE_LIMITS: Readonly<{images:4;question:2000;range:12}>;
6117
+ /** media.analyze reply:"text": the Block's own question and instructions about one to ten pictures, answered as text. */
6118
+ export const MEDIA_REPLY_LIMITS: Readonly<{images:10;question:8000;instructions:8000;maxTokens:4000}>;
6119
+ /** text.generate's limits: long structured jobs are priced by their size. */
6120
+ export const TEXT_GENERATE_LIMITS: Readonly<{system:32000;prompt:64000;maxTokens:24000;output:131072}>;
6004
6121
  /** web.fetch and web.research bounds: URL, instruction and topic lengths, sources kept and answer size. */
6005
6122
  export const WEB_CAPABILITY_LIMITS: Readonly<{url:2048;instruction:4000;topic:500;sources:12;answer:32768}>;
6006
6123
  /** image.describe with an image[] input sends one to four images together. */
@@ -6541,13 +6658,17 @@ That is the form after `npm install --save-dev stillmade-sdk`. From a downloaded
6541
6658
  | --- | --- | --- |
6542
6659
  | create ./folder | A template (create --list shows them); omit for the text recipe | Write a complete folder: manifest, source, fixtures, a README and, where needed, tests/mocks.json and package.json |
6543
6660
  | create-type ./type.json | None | Write an editable connected Project Type example |
6661
+ | init ./folder | Optional --name "Listing kit" | Start a [workspace](/docs/build/workspaces): blocks/, shared/, workflows/, the agents' guides and gate hooks |
6662
+ | add name | A template; --app gmail with the connector template | Add blocks/name to the workspace you are in |
6663
+ | add-type name | Block folder names, else all; optional --when schedule, webhook or app:name/EVENT | Add workflows/name.json made of the workspace's Blocks |
6664
+ | build | None | Bundle each module Block's src/main.js and the shared/ code it imports into module/main.js |
6544
6665
  | validate target | Optional --mobile | Check the contract without executing behavior; --mobile adds the 320-pixel phone layout profile to the report |
6545
6666
  | inspect target | None | Print manifest/runtime or workflow support |
6546
6667
  | test target | Optional --mock | Execute fixtures and return the report; --mock (or a tests/mocks.json file) answers hosted calls with [stand-ins](/docs/build/testing) |
6547
6668
  | run target | Optional input.json, --mock; Blocks also accept --state file and --save-state new-file | Execute once and print the outputs (preview is the same command) |
6548
- | dev folder | Optional --port 4800, --build "npm run build", and --live (with --site) to review each saved version in your StillMade account | [Preview the interface with hot reload](/docs/build/local-preview) |
6669
+ | dev folder | Optional --port 4800, --build "npm run build", and --live (with --site) to review each saved version in your StillMade account | [Preview the interface with hot reload](/docs/build/local-preview); in a workspace folder, every Block and workflow in one preview |
6549
6670
  | ready target | None | Run the [Block builder contract](/docs/build/block-builder-contract) readiness check: fixtures, bad input, cancellation, reopen, composition with real catalog Blocks; exits non-zero when FAILED or BLOCKED |
6550
- | gate folder | None | Run ready on every new or edited Block under the folder; a Block is not ready until it passes. Name the Block's own folder: `gate .` in a folder that also holds the extracted SDK checks the SDK's examples too |
6671
+ | gate folder | None | Run ready on every new or edited Block (and, in a workspace, every workflow) under the folder; a Block is not ready until it passes. Name the Block's own folder: `gate .` in a folder that also holds the extracted SDK checks the SDK's examples too |
6551
6672
  | pack target | Optional output path. A module Block (or a frame view with src/view-files/) writes `<id>-<version>.stillmade-module.json`, other runtimes `<id>-<version>.stillmade.json`; --original with an output.stillmade-block path writes an archive of your own recipe, JavaScript, capability or ComfyUI code and refuses a module Block | Require passing tests, then write the file to import |
6552
6673
  | remix target | Optional output path | Write an editable draft copy with retained license and pinned lineage; nothing is executed |
6553
6674
 
@@ -6577,6 +6698,7 @@ That is the form after `npm install --save-dev stillmade-sdk`. From a downloaded
6577
6698
  | timeline-proposal | capability | Propose Editor timeline edits the person reviews (timeline.propose). |
6578
6699
  | outside-api | module | Call an outside https API with the person's own key (net.fetch). |
6579
6700
  | connection | module | Call a published OpenAPI adapter with the person's sign-in. |
6701
+ | connector | module | Call a signed-in app (Gmail, Slack, Notion, Google Sheets...) by its action name; --app picks the app. |
6580
6702
  | storage | module | Remember values across runs in the project's Block storage. |
6581
6703
  | trigger-media | capability | Runs on its own when a video is saved (media.saved trigger). |
6582
6704
  | trigger-webhook | javascript | Runs when another service sends a webhook. |
@@ -6599,7 +6721,7 @@ node packages/block-cli/cli.js preview ./workflow.json
6599
6721
  ```
6600
6722
 
6601
6723
  ### Importable formats
6602
- The CLI emits JSON (and `.stillmade-module.json` bundles for module Blocks), while StillMade also accepts reviewed ZIP source directories. Files outside the supported manifest, source, and fixture layout are not installed as executable dependencies.
6724
+ The CLI emits JSON (and `.stillmade-module.json` bundles for module Blocks; a workspace's `pack` writes one `.stillmade-pack` of them all), while StillMade also accepts reviewed ZIP source directories. Files outside the supported manifest, source, and fixture layout are not installed as executable dependencies.
6603
6725
 
6604
6726
  ## Errors and troubleshooting
6605
6727
 
@@ -7123,9 +7245,9 @@ Ask the assistant to return source files or one `.stillmade.json` package. Run t
7123
7245
 
7124
7246
  <!-- builder-contract:start (generated by npm run build:sdk; do not edit) -->
7125
7247
 
7126
- ### Block builder contract (stillmade.block-builder@1.17.0)
7248
+ ### Block builder contract (stillmade.block-builder@1.18.0)
7127
7249
 
7128
- Contract `stillmade.block-builder@1.17.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.
7250
+ 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.
7129
7251
 
7130
7252
  ### Source of truth
7131
7253
 
@@ -7194,7 +7316,7 @@ These are StillMade's defaults: unless the person or the creator asked for somet
7194
7316
 
7195
7317
  ### Interface
7196
7318
 
7197
- 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), looks and works like that kind of app instead of a compact page: 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.
7319
+ 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.
7198
7320
 
7199
7321
  ### Shared editing
7200
7322
 
@@ -7288,7 +7410,7 @@ Every finished Block must support automatic remixing through the standard SDK co
7288
7410
 
7289
7411
  ### Package format
7290
7412
 
7291
- 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",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.
7413
+ 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.
7292
7414
 
7293
7415
  ### Feature map
7294
7416
 
@@ -7352,7 +7474,7 @@ Write the description in third-grade language: one short sentence, ideally 8–1
7352
7474
 
7353
7475
  ### Compact interface
7354
7476
 
7355
- Every job or ingredient Block interface, generated or custom, follows StillMade's compact rules: 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. 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. 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. StillMade draws generated manifest.ui controls this way itself (label beside control, files as tiles, the result filling the right on wide screens); you supply at most four inputs that matter in view, sensible defaults, and a short `description` on every input whose effect is not obvious from its name, which StillMade shows as its (i). An app-sized Block is different: when the request is for something a person works in (an editor, a canvas, a 3D or CAD tool, a studio), build it as a workspace Block laid out like that app: its work surface (viewport, canvas, timeline, panels, tool rails) owns the page with as much tooling as the app needs, and the compact rules apply to its settings, dialogs and status, never to the work surface. Never shrink an app to one screen of four inputs.
7477
+ Every job or ingredient Block interface, generated or custom, follows StillMade's compact rules: 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. 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. 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. StillMade draws generated manifest.ui controls this way itself (label beside control, files as tiles, the result filling the right on wide screens); you supply at most four inputs that matter in view, sensible defaults, and a short `description` on every input whose effect is not obvious from its name, which StillMade shows as its (i). An app is different: when the request is for software a person works in (an editor, a canvas, a 3D or CAD tool, a photo editor, a studio), set manifest.kind:"workspace", which makes it an app, and lay it out like that app: its work surface (viewport, canvas, timeline, panels, tool rails) owns the page with as much tooling as the app needs, and the compact rules apply to its settings, dialogs and status, never to the work surface. Never shrink an app to one screen of four inputs.
7356
7478
 
7357
7479
  ### Generated controls first
7358
7480
 
@@ -7364,7 +7486,7 @@ Optional custom view interfaces are supported, but generated manifest.ui control
7364
7486
 
7365
7487
  *When:* StillMade's builder adds this section when the person asks for an interface, preview or controls, or refines a Block that has one.
7366
7488
 
7367
- 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, in order: 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. 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. 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. 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. Rare tools (update, schedule, history, reload) are named icon buttons beside the action, never sections under the work. 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. 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`). 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. 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.
7489
+ 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.
7368
7490
  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`:
7369
7491
 
7370
7492
  - **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:`.
@@ -7377,7 +7499,7 @@ Standard custom view code (no `view.config.json` frame runtime) is checked befor
7377
7499
  - **Dynamic elements:** `StillMade.render` can create `div`, `section`, `article`, `header`, `footer`, `p`, `span`, `strong`, `em`, `i`, `small`, `h1`, `h2`, `h3`, `h4`, `label`, `button`, `input`, `textarea`, `select`, `option`, `ul`, `ol`, `li`, `table`, `thead`, `tbody`, `tr`, `th`, `td`, `details`, `summary`, `output`, `progress`, `img`; give repeated items stable IDs derived from their identity. An `img` gets its picture when you set its `src` to the data URL `StillMade.previewImage` returns, after rendering; the renderer keeps Block-owned `src`. `previewImage` works on items of an `image[]` input.
7378
7500
  - **Preview controls:** a button whose whole text is `Preview` shows the StillMade preview icon alone (an eye in viewfinder corners), with `Preview` as its accessible name; give it a `label` such as `Preview clip 2` when several sit together. Do not draw your own eye or play icon for a preview.
7379
7501
  - **Shared writes are queued in order:** if one `StillMade.updateShared` write is rejected (conflict, read-only or timeout), the writes queued behind it are rejected too. Send dependent edits one at a time or through `StillMade.scheduleSharedEdit`, keep the person's draft, and retry from the latest `onShared` value. `{merge: true}` merges independent fields of an object value; create an absent field with the precondition `{exists: false}`.
7380
- - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 30 runs and 60 image previews per minute.
7502
+ - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 120 runs and 240 image previews per minute.
7381
7503
  ### Shared project interfaces
7382
7504
  Every meaningful edit and generated result must be visible to collaborators.
7383
7505
  Prefer ordinary manifest.ui controls and declared outputs when those express
@@ -8084,7 +8206,7 @@ Standard custom view code (no `view.config.json` frame runtime) is checked befor
8084
8206
  - **Dynamic elements:** `StillMade.render` can create `div`, `section`, `article`, `header`, `footer`, `p`, `span`, `strong`, `em`, `i`, `small`, `h1`, `h2`, `h3`, `h4`, `label`, `button`, `input`, `textarea`, `select`, `option`, `ul`, `ol`, `li`, `table`, `thead`, `tbody`, `tr`, `th`, `td`, `details`, `summary`, `output`, `progress`, `img`; give repeated items stable IDs derived from their identity. An `img` gets its picture when you set its `src` to the data URL `StillMade.previewImage` returns, after rendering; the renderer keeps Block-owned `src`. `previewImage` works on items of an `image[]` input.
8085
8207
  - **Preview controls:** a button whose whole text is `Preview` shows the StillMade preview icon alone (an eye in viewfinder corners), with `Preview` as its accessible name; give it a `label` such as `Preview clip 2` when several sit together. Do not draw your own eye or play icon for a preview.
8086
8208
  - **Shared writes are queued in order:** if one `StillMade.updateShared` write is rejected (conflict, read-only or timeout), the writes queued behind it are rejected too. Send dependent edits one at a time or through `StillMade.scheduleSharedEdit`, keep the person's draft, and retry from the latest `onShared` value. `{merge: true}` merges independent fields of an object value; create an absent field with the precondition `{exists: false}`.
8087
- - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 30 runs and 60 image previews per minute.
8209
+ - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 120 runs and 240 image previews per minute.
8088
8210
 
8089
8211
  <!-- view-rules:end -->
8090
8212
 
@@ -8363,10 +8485,25 @@ renders leave the previous controls intact. Text is always literal; scripts,
8363
8485
  inline handlers, links, file inputs, network attributes, and raw HTML are rejected.
8364
8486
 
8365
8487
 
8488
+ ### Spending approvals
8489
+
8490
+ A press of the Block's own button pays for one run's hosted calls at StillMade's
8491
+ price. A screen whose button starts many runs (a build that makes thirty pictures,
8492
+ one run each) asks once instead: from that press, call
8493
+ `await StillMade.allowSpending({credits, label})` with the total the button showed
8494
+ and its words. StillMade shows its own dialog; when the person allows it, that
8495
+ Step's later runs spend from the total silently, each call at StillMade's own
8496
+ price, for 30 minutes, and a call past what is left asks as usual. It resolves
8497
+ with `{allowed, credits}`. It works only from a person's click or key press in a
8498
+ project Step (never from Chat), up to 2,000 credits (`VIEW_LIMITS.allowanceCredits`).
8499
+
8366
8500
  ### Audio and video playback
8367
8501
 
8368
- `await StillMade.previewMedia(reference)` opens an authorized `audio` or `video`
8369
- reference as a temporary playback copy and returns `{kind, url, mimeType, bytes}`.
8502
+ `await StillMade.previewMedia(reference)` opens an authorized `image`, `audio` or `video`
8503
+ reference as a temporary copy and returns `{kind, url, mimeType, bytes}`. A picture comes back as the
8504
+ original file at full size (an animated GIF keeps moving); `previewImage` gives a small still instead.
8505
+ A frame view keeps up to `FRAME_VIEW_LIMITS.mediaPreviews` media previews open at once (a standard view two);
8506
+ release one with `StillMade.releaseMedia(url)` when it scrolls away or closes.
8370
8507
  Assign its URL to an existing `<audio controls>` or `<video controls>` element.
8371
8508
  The host requires an exact reference from this Block’s bound input, an explicitly
8372
8509
  selected project asset, permitted project context, or a host-recorded output.
@@ -9526,6 +9663,27 @@ Most Blocks need no custom view: generated `manifest.ui` controls already
9526
9663
  follow these rules. Write a custom view only when the core interaction needs
9527
9664
  one, and start it from the Block kit.
9528
9665
 
9666
+ ### Two modes: job and app
9667
+
9668
+ A Block's interface is one of two kinds, and its manifest says which.
9669
+
9670
+ - **Job** (the default, `"kind": "task"`): one thing a person makes in a few
9671
+ steps, often in words that belong to their trade: a listing tour, a sermon
9672
+ clip, a weekly caption. Everything in this guide applies: the result biggest,
9673
+ one action, a compact page from the Block kit, the job's own words.
9674
+ - **App** (`"kind": "workspace"`): software a person works in for a while: a
9675
+ CAD or 3D tool, a photo editor, a timeline, a canvas, a studio. It keeps the
9676
+ rules every Block shares (the job's words, pictures over words, files through
9677
+ StillMade, dates picked, no notes on how it works), and the
9678
+ [app rules](#4a-app-rules) replace the job-only ones: one action, one compact
9679
+ screen, the Block kit page, settings folded away, the result filling the
9680
+ right. StillMade's checks hold it to the app rules, never to the compact ones.
9681
+
9682
+ Both modes keep what the SDK is for: inputs and outputs, multiplayer, hosted
9683
+ operations and credits, StillMade's file picker, no prices on screen, a run
9684
+ button that names what it makes. The mode changes how the screen is laid out
9685
+ and worded; it grants no extra runtime or host access.
9686
+
9529
9687
  ### 1. Start from the Block kit
9530
9688
 
9531
9689
  StillMade adds the Block kit stylesheet (`packages/design-system/block-kit.css`)
@@ -9828,8 +9986,45 @@ exception, never unrelated technical UI.
9828
9986
  </section>
9829
9987
  ```
9830
9988
 
9989
+ ### 4a. App rules
9990
+
9991
+ An app (`"kind": "workspace"`) follows these instead of the job-only rules
9992
+ (`APP_INTERFACE_PRINCIPLES`):
9993
+
9994
+ 1. Its work surface (viewport, canvas, image, timeline) owns the page, laid
9995
+ out like the best apps of its kind: tool rail, panels, inspector, layers,
9996
+ timeline and menus, with as many controls as the craft needs. Never a form
9997
+ of fields and one button.
9998
+ 2. It lays itself out: `data-stillmade-layout="own"` on its outermost element,
9999
+ so StillMade never centers or zooms it and leaves Ctrl/Cmd +/- to it.
10000
+ Full-size panels, fixed toolbars and its own scroll, pan and zoom are fine.
10001
+ 3. Work happens on the surface: select, drag, zoom and pan, keyboard shortcuts
10002
+ for the common tools, undo and redo.
10003
+ 4. Speak the craft: Exposure, Keyframe, Extrude, Layers, Mask, as people of that
10004
+ craft say them. A button that spends credits still names what it makes
10005
+ ("Generate fill"), carries `data-stillmade-action="run"` and shows no price.
10006
+ 5. Order, not sparseness: tools grouped in panels that fold, the most used in
10007
+ view, rare ones in menus. No intros or paragraphs on the surface.
10008
+ 6. StillMade's appearance (Light, Dark and White) and the kit's controls
10009
+ wherever they fit; the layout is the app's own. A look the person names wins.
10010
+ 7. On a phone the work surface stays usable and the panels fold into sheets or
10011
+ one tool strip.
10012
+ 8. What the person makes leaves the app as the Block's outputs (and, while they
10013
+ work, its shared state), so the next Step and the other people in the
10014
+ project get it.
10015
+
9831
10016
  ### 5. What StillMade checks
9832
10017
 
10018
+ For an app (`"kind": "workspace"`) only the shared checks run: a file input of
10019
+ its own, the price and page-heading rules, the wording notes that are not about
10020
+ layout (a vague progress line, how the Block works, a typed date, where a file
10021
+ is kept), and the action-name rules on its run button
10022
+ (`data-stillmade-action="run"`) alone, so "OK" and "Go" are fine on its dialogs
10023
+ and tools. It is never asked for the Block kit page, a control or row count,
10024
+ labels beside fields, `data-stillmade-action` on every tool, or a visual
10025
+ surface instead of text (a code editor is an app). Everything below is the job
10026
+ check list.
10027
+
9833
10028
  - Admission rejects a primary textarea of 12 or more rows when no task
9834
10029
  presentation (stage, media, list or `StillMade.render`) is detected
9835
10030
  (`INTERFACE_DESIGN_RULES`).
@@ -9873,8 +10068,13 @@ exception, never unrelated technical UI.
9873
10068
  - HTML, JavaScript, permission and appearance validation are separate checks.
9874
10069
  Simplicity never bypasses safety or approval.
9875
10070
 
9876
- The SDK exports the ordered principles as `BLOCK_INTERFACE_PRINCIPLES` and the
9877
- kit vocabulary as `BLOCK_KIT_CLASSES`. `INTERFACE_DESIGN_CONVENTIONS`,
10071
+ The SDK exports the ordered principles as `BLOCK_INTERFACE_PRINCIPLES` (a job's
10072
+ full list), split into `SHARED_INTERFACE_PRINCIPLES` and
10073
+ `JOB_INTERFACE_PRINCIPLES`, the app rules as `APP_INTERFACE_PRINCIPLES`,
10074
+ `interfaceMode(manifest)` (one of `INTERFACE_MODES`: `"job"` or `"app"`) and
10075
+ `interfacePrinciples(mode)`;
10076
+ `reviewViewDesign(view, {mode})` and `blockKitProblems(view, {mode})` take the
10077
+ mode. The kit vocabulary is `BLOCK_KIT_CLASSES`. `INTERFACE_DESIGN_CONVENTIONS`,
9878
10078
  `INTERFACE_DESIGN_PRINCIPLES` and `INTERFACE_AUTHORING_RULES` remain as
9879
10079
  compatibility names for the same list.
9880
10080
 
@@ -9919,7 +10119,8 @@ esbuild src/view.jsx --bundle --format=iife --minify --jsx=automatic \
9919
10119
  ```
9920
10120
 
9921
10121
  Limits: markup and each stylesheet up to 256 KiB, `view.js` up to 4 MiB,
9922
- and up to 32 asset files with 8 MiB in total.
10122
+ up to 32 asset files with 8 MiB in total, and up to 64 code files (below) of
10123
+ up to 4 MiB each and 16 MiB in total.
9923
10124
 
9924
10125
  ### The `StillMade` object
9925
10126
 
@@ -9932,6 +10133,57 @@ and the rest of the interface bridge. In addition:
9932
10133
  | --- | --- |
9933
10134
  | `StillMade.asset(path)` | A `blob:` URL for one of the view's asset files, for `<img>`, `new FontFace()` or `<video>`. |
9934
10135
  | `StillMade.assetBytes(path)` | A copy of an asset file's bytes as an `ArrayBuffer`. |
10136
+ | `StillMade.loadCode(path)` | Runs one of the view's code files once and resolves when it has run (see Code files). |
10137
+
10138
+ ### A full-screen app
10139
+
10140
+ A frame view grows with its content: StillMade sizes the frame to the page.
10141
+ An app that lays itself out in a fixed area (an editor with a timeline at the
10142
+ bottom) marks its root `data-stillmade-layout="own"` and sizes it to the space
10143
+ StillMade gives the Step, which the frame receives as the CSS variable
10144
+ `--stillmade-space-height`:
10145
+
10146
+ ```css
10147
+ #root { height: max(560px, var(--stillmade-space-height, 100vh)); }
10148
+ ```
10149
+
10150
+ ### Code files: load the rest of a large interface when it is needed
10151
+
10152
+ An interface too large for one `view.js` (a full editor with its export and
10153
+ effects code) starts with a small `view.js` and keeps the rest as code files:
10154
+ `.js` files in `src/view-files/`, for example `src/view-files/code/editor.js`.
10155
+ `await StillMade.loadCode('code/editor.js')` runs one when the view needs it.
10156
+ The frame still has no network and runs no generated code: StillMade reads
10157
+ the file, checks it against the SHA-256 the package names, checks that it is
10158
+ one classic script without `import()`, compiles the task guard into it, and
10159
+ runs it in the frame. A file runs once; asking again resolves at once. A file
10160
+ that is not one of the view's code files, does not match its digest or stops
10161
+ with an error rejects the call with the reason.
10162
+
10163
+ Each code file is a classic script that registers what it defines, for
10164
+ example on a global your `view.js` set up:
10165
+
10166
+ ```js
10167
+ // src/view.js
10168
+ globalThis.parts = {};
10169
+ StillMade.loadCode('code/chart.js').then(() => parts.drawChart(document.getElementById('root')));
10170
+
10171
+ // src/view-files/code/chart.js
10172
+ parts.drawChart = root => { root.textContent = 'Chart'; };
10173
+ ```
10174
+
10175
+ A bundler can write them: split at the `import()` boundaries of your
10176
+ interface and wrap each chunk so it registers itself (StillMade's own Editor
10177
+ is built this way, `scripts/frame-blocks/bundle.mjs` in the StillMade
10178
+ repository). Code files are not asset files: `StillMade.asset` does not open
10179
+ them and they do not count toward the asset limits. `validate`, `test` and
10180
+ `pack` check each one as StillMade does.
10181
+
10182
+ An original screen ported into a frame view can use the SDK's helpers in
10183
+ `packages/block-sdk/frame-hosts/`: `media-urls.js` loads the saved media its
10184
+ markup names through `previewMedia`, and `dialogs.jsx` draws the screen's alerts and
10185
+ confirms; a confirm that names a total in credits ("Build 30 panels (~60 credits)?")
10186
+ becomes one `StillMade.allowSpending`, so StillMade's own dialog asks.
9935
10187
 
9936
10188
  `StillMade.live` gives any view the camera and microphone as they happen, after the
9937
10189
  person presses Start (see Live camera and microphone in
@@ -9983,6 +10235,18 @@ values. StillMade hides `[data-stillmade-add]` tiles from viewers and for
9983
10235
  inputs an earlier Step fills. In the SDK preview, the tile opens your
9984
10236
  computer's file chooser and the preview keeps the files like your own picks.
9985
10237
 
10238
+ ### File inputs your view does not add itself
10239
+
10240
+ StillMade draws nothing beside or over a Block's own screen for its file inputs.
10241
+ A file input the view does not fill with a `data-stillmade-add` tile or
10242
+ `StillMade.addFiles` opens from the inputs icon on the Step's pill. That icon
10243
+ shows only when an earlier Step makes something the Block takes: there the
10244
+ person picks which earlier item, or items, this Block works on (for example one
10245
+ photo of several for a one-photo editor, or several to edit and hand all on),
10246
+ and adds their own file to an optional input. A required file still missing is
10247
+ asked for in one row beside the screen until it is there. To let people add
10248
+ files on the screen itself, use the add tile or `addFiles` above.
10249
+
9986
10250
  ### Removing and reordering files
9987
10251
 
9988
10252
  A list input's files are removed and put in order on the same screen, so a
@@ -10065,15 +10329,17 @@ or zoom, its own Ctrl/Cmd +/- keys, as in a CAD or drawing tool) puts
10065
10329
  <div id="root" data-stillmade-layout="own"></div>
10066
10330
  ```
10067
10331
 
10068
- StillMade then never centers or zooms it and leaves Ctrl/Cmd +, - and 0 to the
10069
- app. Without the attribute StillMade also stays out of the way when the view
10332
+ StillMade then never centers or zooms it, gives its page no padding, makes the
10333
+ frame as tall as the space its Step has (so a root at `height: 100vh` fills the
10334
+ Step, and follows the window) and leaves Ctrl/Cmd +, - and 0 to the app. Without the attribute StillMade also stays out of the way when the view
10070
10335
  looks like an app: no page padding, a clipped page, a root as tall as the frame,
10071
10336
  fixed over half of it or scrolling over half of it, a canvas, SVG or
10072
10337
  `touch-action: none` surface over 40% of it, or a canvas or SVG of 300x200 or
10073
10338
  more with `touch-action: none` (as Three.js's controls set it). Mark the root
10074
10339
  anyway, in `view.html` rather than from code: StillMade reads it before the view
10075
- starts, so the app never shows StillMade's size control or loses a key to it,
10076
- even while it is still loading.
10340
+ starts, so the app never loses a key to it, even while it is still loading.
10341
+ StillMade draws no size control over any view; it sizes the view to fit, and
10342
+ Ctrl/Cmd +, - and 0 change that size for the visit unless the view keeps them.
10077
10343
 
10078
10344
  A `<video controls>` plays in StillMade's player, the same one as everywhere in
10079
10345
  StillMade: play, timeline, time, sound and full screen along the bottom, shown
@@ -10212,7 +10478,7 @@ against the manifest's port types.
10212
10478
  | `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. |
10213
10479
  | `files.write(data, {mediaType, name})` | Saves PNG, JPEG, WebP, GIF, MP4, WebM, MP3, WAV, M4A or OGG media, or a PDF, TXT, CSV, Markdown, JSON or ZIP file (`application/pdf`, `text/plain`, `text/csv`, `text/markdown`, `application/json`, `application/zip`; a `{kind: 'file'}` reference), to the runner's library and returns a reference you can return as an output. At most 64 files per run. |
10214
10480
  | `media.transform(request)` | On-device media work done by StillMade: decode audio to samples, extract or loudness-normalise audio, cut, join or re-encode video, and take thumbnails. Results are saved like `files.write`. See [Media processing](#media-processing). |
10215
- | `actions.run(action)` | Runs a declared portable action. `media.generate` opens one approval for its whole batch, naming the most it can cost in StillMade credits; `stock.search` runs directly. |
10481
+ | `actions.run(action)` | Runs a declared portable action. `media.generate` opens one approval for its whole batch, naming the most it can cost in StillMade credits; `stock.search`, `stock.select`, `stock.auto`, `gif.search`, `gif.save` and `final.render` run directly. |
10216
10482
  | `hosted.run(operation, request)` | Calls a hosted operation the manifest lists in `permissions.capabilities` (for example `text.generate`, `image.generate`, `audio.speech`, `web.research`). Each call is quoted and approved like a capability Block and paid with StillMade credits. |
10217
10483
  | `net.fetch(url, {method, headers, body})` | Calls one of the origins declared in `permissions.network` through StillMade, which adds the person's own key or OAuth token. Returns a standard `Response`. See [Outside APIs](/docs/build/outside-apis). |
10218
10484
  | `net.connect(url, {protocols})` | Opens a live WebSocket to the `wss://` form of a declared origin and resolves with the open socket. No key is added. See [Live connections](#live-connections). |
@@ -10238,7 +10504,10 @@ const voice = await stillmade.hosted.run('audio.speech', { text: plan.script, se
10238
10504
  saved video, whose own sound it transcribes, so there is no need to extract the
10239
10505
  audio first. `media.analyze` returns `{schemaVersion: 1, items}`, one item per
10240
10506
  picture or video, and for one video takes an optional `range: {start, end}` in
10241
- seconds, at most 12 apart, to look closely at that part. `image.generate` takes `prompt` and optional
10507
+ seconds, at most 12 apart, to look closely at that part. With `reply: 'text'` it asks your own `question`
10508
+ (and optional `instructions`, `maxTokens` up to 4,000) about one to ten saved pictures in one call and returns the
10509
+ answer as text, priced per picture. `text.generate` takes `system` up to 32,000 and `prompt` up to 64,000 characters
10510
+ and `maxTokens` up to 24,000; a call past the flat size is priced by its size. `image.generate` takes `prompt` and optional
10242
10511
  `references`, one to eight saved images the new picture keeps. `video.generate` takes `prompt` and optional
10243
10512
  `firstFrame` (a saved image the video starts on, for image-to-video), `lastFrame` (one it ends on; needs
10244
10513
  `firstFrame`), `negativePrompt` (up to 2,000 characters) and `settings`; a model named in `settings` must take
@@ -10299,7 +10568,27 @@ per photo, which asks again from the second call. Video items take the same
10299
10568
 
10300
10569
  It throws when no item was made, when the person declines (`MODULE_ACTION_DECLINED`)
10301
10570
  and when the action is not declared (`MODULE_PERMISSION`). `stock.search` resolves
10302
- with `{results}`. With `--mock` or a `tests/mocks.json`, `media.generate` answers
10571
+ with `{results}`.
10572
+
10573
+ Stock media and GIFs:
10574
+
10575
+ | Action | Request | Resolves with | Credits |
10576
+ | --- | --- | --- | --- |
10577
+ | `stock.search` | `{query, type}` (`photo`, `video` or `audio`) | `{results}` | free |
10578
+ | `stock.select` | `{result}`, one of those results | `{asset}`: the media to return as an output, with its license and receipt | as StillMade's own stock picker: at most 1 per pull bucket |
10579
+ | `stock.auto` | `{queries, kind, excludeUrls}`: 1 to 10 searches, `photo` or `video`, up to 300 addresses to skip | `{asset, result}`, or `{asset: null, noMatch: true}` (or `transient: true` when the sources are busy) | as `stock.select`, only when something is found |
10580
+ | `gif.search` | `{query, limit}`: up to 200 characters, 1 to 24 results | `{enabled, results: [{id, title, url, previewUrl, width, height}]}` | free |
10581
+ | `gif.save` | `{url}`, a result's `url` | `{asset}`: the GIF kept in StillMade's storage | free |
10582
+
10583
+ A result's own address is not media the Block can show or return; keep it first with
10584
+ `stock.select` or `gif.save`.
10585
+
10586
+ Rendering a video or a sound: `final.render` with `{schemaVersion: 1, kind: 'video' | 'audio', timeline, settings,
10587
+ source}` (the Editor's render plan: clips, audio, overlays, `targetDuration`; settings `fps` 24/30/60, `resolution`
10588
+ 720/1080/2160, `quality`) resolves with `{asset}`, the MP4 or MP3 kept like `files.write`, to return as an output.
10589
+ StillMade renders it on the person's device under its export rules (plan, watermark). Every address the
10590
+ timeline holds, in any field, must be a file the run received or made; StillMade's own built-in sounds
10591
+ (`/sounds/...`, `/sfx/...`) need nothing. With `--mock` or a `tests/mocks.json`, `media.generate` answers
10303
10592
  with stand-in media and no failures; record your own answer under
10304
10593
  `actions["media.generate"]`.
10305
10594
 
@@ -13028,6 +13317,10 @@ A JavaScript Block returns a `{schemaVersion: 1, kind: "stillmade.media-action",
13028
13317
  | --- | --- |
13029
13318
  | `final.render` | Ask the host to render the project timeline to a final video. |
13030
13319
  | `stock.search` | Search approved stock photos, videos or audio by query. |
13320
+ | `stock.select` | Keep one stock.search result as media the Block returns, charged as StillMade's stock picker. |
13321
+ | `stock.auto` | Find and keep stock media for up to 10 searches, skipping addresses already used. |
13322
+ | `gif.search` | Search Giphy GIFs by query (free). |
13323
+ | `gif.save` | Keep a gif.search result in StillMade's storage (free). |
13031
13324
  | `presenter.generate` | Generate presenter poses for a board from a prompt and its voiceover. |
13032
13325
  | `quality.analyze` | Analyze up to 16 owned images for production quality. |
13033
13326
  | `image.generate` | Generate one image from a bounded visual prompt through the host. |
@@ -13065,6 +13358,10 @@ Capability Blocks (`image.generate`, `video.generate`, `audio.music`, `audio.sfx
13065
13358
  | `nano-banana-2-lite` | Nano Banana 2 Lite | auto, 21:9, 16:9, 3:2, 4:3, 1:1, 4:5, 3:4, 2:3, 9:16 | 1K, 2K, 4K | model default | up to 8 | yes |
13066
13359
  | `nano-banana-pro` | Nano Banana Pro | auto, 21:9, 16:9, 3:2, 4:3, 1:1, 4:5, 3:4, 2:3, 9:16 | 1K, 2K, 4K | model default | up to 8 | yes |
13067
13360
  | `gpt-image-2` | GPT Image 2 | auto, 16:9, 9:16, 1:1, 4:3, 3:4 | 1K, 2K, 4K | low, medium, high | up to 8 | no |
13361
+ | `gpt-image-2.5-flare` | GPT Image 2.5 Flare | auto, 16:9, 9:16, 1:1, 4:3, 3:4 | 1K, 2K, 4K | low, medium, high | up to 8 | no |
13362
+ | `gpt-image-2.5-sunburst` | GPT Image 2.5 Sunburst | auto, 16:9, 9:16, 1:1, 4:3, 3:4 | 1K, 2K, 4K | low, medium, high | up to 8 | no |
13363
+ | `gpt-image-2.6-flare` | GPT Image 2.6 Flare | auto, 16:9, 9:16, 1:1, 4:3, 3:4 | 1K, 2K, 4K | low, medium, high | up to 8 | no |
13364
+ | `gpt-image-2.6-sunburst` | GPT Image 2.6 Sunburst | auto, 16:9, 9:16, 1:1, 4:3, 3:4 | 1K, 2K, 4K | low, medium, high | up to 8 | no |
13068
13365
  | `gpt-image-1.5` | GPT-Image 1.5 | 16:9, 9:16, 1:1 | model default | low, medium, high | no | no |
13069
13366
  | `kling-image-v3` | Kling Image V3 | 21:9, 16:9, 9:16, 1:1, 4:3, 3:4, 3:2, 2:3 | 1K, 2K | model default | no | no |
13070
13367
  | `seedream-v4.5` | Seedream 4.5 | 16:9, 9:16, 1:1, 4:3, 3:4 | 2K, 4K | model default | no | yes |
@@ -13219,14 +13516,14 @@ The latest version of each Block bundled with the SDK, generated from the packag
13219
13516
  | --- | --- | --- | --- | --- |
13220
13517
  | `sm.ai-decide` 1.0.0 | AI decide | task | `data` text (external_data) · primary; `rules` text (decision_rules) | `decision` object (ai_decision) · primary |
13221
13518
  | `sm.ai-write` 1.0.0 | AI write | task | `data` text (external_data) · primary; `instructions` text (writing_instructions) | `text` text (written_message) · primary |
13222
- | `sm.board-editor` 2.2.11 | Whiteboard | workspace | `board` board-document (whiteboard_document) · project context only · primary; `updatedBoard` board-document (whiteboard_document); `commit` boolean (apply_changes); `action` text (whiteboard_action); `presenterPrompt` text (presenter_prompt); `generatedImage` image (image); `versions` metadata[] (revision_history) · project context only; `presence` metadata[] (collaborator_presence); `assets` asset[] (project_media) · project context only | `board` board-document (whiteboard_document) · primary; `edit` workspace-edit (workspace_edit); `presenterAction` approval (portable_media_action); `qualityAction` approval (portable_media_action); `renderAction` approval (portable_media_action); `actionEdit` workspace-edit (workspace_edit); `qualityReport` metadata (quality_report); `editorTimeline` timeline (editor_handoff); `revision` metadata (revision_event) |
13519
+ | `sm.board-editor` 3.0.0 | White Board Motion | workspace | `narrationTiming` transcript (timed_transcript) · primary; `narrationAudio` audio (narration_audio); `board` board-document (whiteboard_document); `files` asset[] (export_media); `make` text (board_action); `request` json (board_request); `references` image[] (reference_image); `image` image (board_image); `firstFrame` image (first_frame); `lastFrame` image (last_frame); `dataUrl` text (file_data); `key` text (storage_key); `value` json (storage_value) | `output` board-document (whiteboard_document) · primary; `editorTimeline` timeline (editor_handoff); `export` asset (exported_video); `made` asset[] (made_media); `result` json (board_answer) |
13223
13520
  | `sm.captions` 2.0.0 | Captions | editor-extension | `transcript` transcript · primary; `projectTranscript` transcript · project context only; `timeline` timeline · project context only; `text` text; `start` number; `duration` number; `highlightColor` text; `animation` text | `edit` timeline-edit · primary |
13224
13521
  | `sm.character` 2.0.1 | Character reference | workspace | `input` text (character_description) · primary; `name` text (character_name); `role` text (character_role); `appearance` text (character_appearance); `personality` text (character_personality) | `output` character (character_reference) · primary |
13225
13522
  | `sm.compare` 2.0.2 | Compare assets | workspace | `imageA` image (comparison_image_a) · primary; `imageB` image (comparison_image_b); `selection` text (comparison_selected_image); `position` integer (comparison_split_position) | `output` image (selected_comparison_image) · primary |
13226
13523
  | `sm.connect-app` 1.0.0 | Connect an app | task | `app` text (connected_app) · primary | `result` object (external_data) · primary |
13227
13524
  | `sm.contact-fields` 1.0.0 | Contact fields | task | `record` object (external_data) · primary | `email` text (contact_email) · primary; `first_name` text (contact_first_name); `last_name` text (contact_last_name); `full_name` text (contact_full_name); `phone` text (contact_phone); `company` text (contact_company); `city` text (contact_city); `other_answers` text (contact_other_answers) |
13228
13525
  | `sm.edit-assistant` 2.0.2 | Edit Assistant | task | `timeline` timeline (timeline) · project context only · primary; `selection` timeline-range (timeline_range); `request` text (edit_request) | `edit` timeline-edit (timeline_edit_proposal) · primary |
13229
- | `sm.editor` 2.2.12 | Editor | workspace | `editor` timeline (editor_timeline) · project context only · primary; `assets` asset[] (legacy.asset-list.assets) · project context only; `updatedEditor` timeline (legacy.timeline.updatededitor); `commit` boolean (legacy.boolean.commit); `action` text (legacy.text.action); `renderFps` integer (legacy.integer.renderfps); `renderResolution` integer (legacy.integer.renderresolution); `renderQuality` text (legacy.text.renderquality) | `editor` timeline (editor_timeline) · primary; `edit` workspace-edit (legacy.workspace-edit.edit); `mediaAction` approval (portable_media_action) |
13526
+ | `sm.editor` 3.0.0 | Editor | workspace | `editor` timeline (editor_timeline) · primary; `cut` timeline (editor_handoff); `media` asset[] (source_media); `narrationAudio` audio (narration_audio); `narrationTiming` transcript (timed_transcript); `narrationScript` script (narration_script); `panelImages` asset[] (panel_images); `panels` panel-document (image_panel_document); `make` text (editor_action); `request` json (editor_request); `references` image[] (reference_image); `firstFrame` image (first_frame); `lastFrame` image (last_frame); `audio` asset (source_audio); `files` asset[] (export_media); `dataUrl` text (file_data); `key` text (storage_key); `value` json (storage_value) | `editor` timeline (editor_timeline) · primary; `export` asset (exported_video); `made` asset[] (made_media); `result` json (editor_answer) |
13230
13527
  | `sm.export-audio` 2.0.0 | Export audio | task | `input` timeline (timeline) · primary; `renderFps` text (render_fps); `renderResolution` text (render_resolution); `renderQuality` text (render_quality); `projectTimeline` timeline (timeline) · project context only | `mediaAction` approval (portable_media_action); `output` audio (rendered_audio) |
13231
13528
  | `sm.export-timeline` 2.0.0 | Export timeline | editor-extension | `timeline` timeline · project context only · primary; `assets` asset[] · project context only; `blueprint` bible · project context only; `shotPlan` shot-plan · project context only; `metadata` metadata · project context only; `title` text; `format` text | `download` file-bundle · primary |
13232
13529
  | `sm.export-video` 2.1.0 | Export video | task | `input` timeline (timeline) · primary; `media` asset[] (project_media); `narration` audio (narration_audio); `renderAspect` text (render_aspect); `renderFps` text (render_fps); `renderResolution` text (render_resolution); `renderQuality` text (render_quality); `projectTimeline` timeline (timeline) · project context only; `projectShotPlan` shot-plan (production_shot_plan) · project context only | `mediaAction` approval (portable_media_action); `output` video (rendered_video) |
@@ -13238,7 +13535,8 @@ The latest version of each Block bundled with the SDK, generated from the packag
13238
13535
  | `sm.location` 2.0.1 | Location reference | workspace | `input` text (location_description) · primary; `name` text (location_name); `visualDetails` text (location_visual_details); `timeOfDay` text (location_time); `atmosphere` text (location_atmosphere) | `output` location (location_reference) · primary |
13239
13536
  | `sm.meta-creative` 1.0.0 | Meta ad creative | task | `upload` object (external_data) · primary; `message` text (ad_primary_text); `website_url` text (ad_destination_url); `page_id` text (facebook_page_id); `call_to_action` text (ad_call_to_action) | `creative` object (meta_ad_creative) · primary |
13240
13537
  | `sm.motion-graphics` 2.0.0 | Motion graphics | editor-extension | `timeline` timeline (timeline) · primary; `projectTimeline` timeline (timeline) · project context only; `plan` json (motion_graphics_plan) | `timeline` timeline (timeline) · primary; `edit` workspace-edit (editor_workspace_edit) |
13241
- | `sm.panel-strip` 2.4.2 | Image Panels | workspace | `panels` panel-document (image_panel_document) · project context only · primary; `transcript` transcript (timed_transcript) · project context only; `script` script (narration_script) · project context only; `narrationScript` script (narration_script); `narrationTiming` transcript (timed_transcript); `blueprint` bible (production_blueprint) · project context only; `updatedPanels` panel-document (image_panel_document); `commit` boolean (apply_changes); `generatedImage` image (image); `targetPanelId` text (panel_id); `attachGeneratedImage` boolean (boolean); `action` text (host_action); `stockQuery` text (stock_query); `generatedImages` image[] (image_list) | `panels` panel-document (image_panel_document) · primary; `edit` workspace-edit (workspace_edit); `stockAction` approval (portable_media_action); `qualityAction` approval (portable_media_action); `renderAction` approval (portable_media_action); `stockAsset` asset (licensed_asset); `stockImage` image (licensed_stock_image); `qualityReport` metadata (quality_report); `generationPlan` metadata (generation_plan); `generateAction` approval (portable_media_action); `generatedImage` image (generated_image); `batchGenerateAction` approval (portable_media_action); `generatedImages` image[] (generated_image_list) |
13538
+ | `sm.panel-strip` 3.0.0 | Image Panels | workspace | `narrationTiming` transcript (timed_transcript) · primary; `narrationAudio` audio (narration_audio); `narrationScript` script (narration_script); `panels` panel-document (image_panel_document); `make` text (panel_action); `request` json (panel_request); `pictures` asset[] (panel_media); `references` image[] (reference_image); `image` image (panel_image); `firstFrame` image (first_frame); `lastFrame` image (last_frame); `dataUrl` text (picture_data); `key` text (storage_key); `value` json (storage_value) | `panels` panel-document (image_panel_document) · primary; `images` asset[] (panel_images); `made` asset[] (made_media); `result` json (panel_answer) |
13539
+ | `sm.photo-editor` 1.0.0 | Photo editor | workspace | `photo` image (source_image) · primary; `edits` json (photo_edit_settings) | `image` image (processed_image) · primary |
13242
13540
  | `sm.photo-notes` 1.1.0 | Photo notes | task | `photos` image[] (photo_set) · primary | `notes` text (photo_notes) · primary |
13243
13541
  | `sm.pipeline` 2.2.6 | Canvas | workspace | `canvas` pipeline (canvas_document) · project context only · primary; `assets` asset[] (project_assets) · project context only; `versions` metadata[] (asset_versions) · project context only; `updatedCanvas` pipeline (canvas_document); `commit` boolean (canvas_commit_intent); `generatedImage` image (generated_image_version); `addGeneratedImage` boolean (attach_generated_image); `generatedImageTitle` text (generated_image_title); `generatedVideo` video (generated_video_version); `addGeneratedVideo` boolean (attach_generated_video); `generatedVideoTitle` text (generated_video_title); `generatedAudio` audio (generated_audio_version); `addGeneratedAudio` boolean (attach_generated_audio); `generatedAudioTitle` text (generated_audio_title); `connectionProposal` json (context_connection_proposal); `applyContextConnections` boolean (context_connection_intent) | `canvas` pipeline (canvas_document) · primary; `edit` workspace-edit (canvas_edit_proposal) |
13244
13542
  | `sm.rough-cut` 2.0.2 | Rough Cut | task | `timeline` timeline (timeline) · project context only · primary; `clipIds` text (timeline_clip_selection); `startAt` number (timeline_start_seconds); `gap` number (timeline_clip_gap_seconds) | `edit` timeline-edit (timeline_edit_proposal) · primary |
@@ -13246,7 +13544,7 @@ The latest version of each Block bundled with the SDK, generated from the packag
13246
13544
  | `sm.style` 2.0.1 | Style reference | workspace | `input` text (visual_style_direction) · primary; `name` text (style_name); `palette` text (color_palette); `lighting` text (lighting_direction); `camera` text (camera_direction) | `output` style (visual_style_reference) · primary |
13247
13545
  | `sm.transition` 2.0.2 | Transition | editor-extension | `timeline` timeline (editor_timeline) · project context only · primary; `clipIds` text[] (timeline_clip_ids); `transition` text (timeline_transition_type); `duration` number (timeline_transition_duration); `soundOn` boolean (timeline_transition_sound_enabled); `soundId` text (timeline_transition_sound_id) | `edit` timeline-edit (timeline_edit) · primary |
13248
13546
  | `sm.video-assets` 2.1.4 | Video Assets | workspace | `assets` asset[] (media_collection) · project context only · primary; `versions` metadata[] (media_version_history) · project context only; `incoming` asset[] (media_collection); `kind` text (media_filter); `selectedIds` text (media_selection); `selectedVersions` text (media_version_selection) | `output` asset[] (production_media) · primary |
13249
- | `sm.voiceover` 2.1.6 | Voiceover | workspace | `input` script (narration_script) · primary; `take` audio (narration_audio); `library` asset[] (project_media) · project context only; `timing` transcript (timed_transcript) · project context only | `output` audio (narration_audio) · primary |
13547
+ | `sm.voiceover` 3.0.0 | Voiceover | workspace | `input` script (narration_script) · primary; `script` text (voiceover_text); `voice` text (voice_choice); `speed` number (speech_speed); `take` audio (narration_audio); `takes` audio[] (voiceover_takes); `words` transcript (timed_transcript); `transcript` text (pasted_transcript); `make` text (voiceover_action) | `output` audio (narration_audio) · primary; `timing` transcript (timed_transcript); `spokenScript` script (narration_script); `takes` audio[] (voiceover_takes) |
13250
13548
  | `sm.when` 1.1.0 | Automation | task | `event` object (workflow_event); `start` object (automation_start); `only_if` text (automation_condition) | `item` object (external_data) · primary; `trigger` object (automation_trigger); `check` object (ai_decision) |
13251
13549
  | `sm.write-summary` 1.0.0 | Write summary | task | `data` text (external_data) · primary | `summary` text (summary_report) · primary |
13252
13550
  | `stillmade.generate` 1.0.0 | Generate | task | `prompt` text (generation_prompt) · primary; `mode` text (generation_mode); `model` text (generation_model); `aspectRatio` text (aspect_ratio); `size` text (render_resolution); `quality` text (render_quality); `duration` integer (duration_seconds); `sound` boolean (generate_audio); `count` integer (generation_count); `avoid` text (negative_prompt); `seed` integer (generation_seed); `references` image[] (reference_image); `startFrame` image (first_frame); `endFrame` image (last_frame) | `media` asset[] (generated_media) · primary; `notMade` text (generation_failures) |
@@ -13266,7 +13564,7 @@ Standard custom view code (no `view.config.json` frame runtime) is checked befor
13266
13564
  - **Dynamic elements:** `StillMade.render` can create `div`, `section`, `article`, `header`, `footer`, `p`, `span`, `strong`, `em`, `i`, `small`, `h1`, `h2`, `h3`, `h4`, `label`, `button`, `input`, `textarea`, `select`, `option`, `ul`, `ol`, `li`, `table`, `thead`, `tbody`, `tr`, `th`, `td`, `details`, `summary`, `output`, `progress`, `img`; give repeated items stable IDs derived from their identity. An `img` gets its picture when you set its `src` to the data URL `StillMade.previewImage` returns, after rendering; the renderer keeps Block-owned `src`. `previewImage` works on items of an `image[]` input.
13267
13565
  - **Preview controls:** a button whose whole text is `Preview` shows the StillMade preview icon alone (an eye in viewfinder corners), with `Preview` as its accessible name; give it a `label` such as `Preview clip 2` when several sit together. Do not draw your own eye or play icon for a preview.
13268
13566
  - **Shared writes are queued in order:** if one `StillMade.updateShared` write is rejected (conflict, read-only or timeout), the writes queued behind it are rejected too. Send dependent edits one at a time or through `StillMade.scheduleSharedEdit`, keep the person's draft, and retry from the latest `onShared` value. `{merge: true}` merges independent fields of an object value; create an absent field with the precondition `{exists: false}`.
13269
- - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 30 runs and 60 image previews per minute.
13567
+ - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 120 runs and 240 image previews per minute.
13270
13568
 
13271
13569
  ## Authoring guide
13272
13570
 
@@ -13279,7 +13577,7 @@ Every finished Block must support automatic remixing through the standard SDK co
13279
13577
 
13280
13578
  ### Package format
13281
13579
 
13282
- 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",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.
13580
+ 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.
13283
13581
 
13284
13582
  ### Feature map
13285
13583
 
@@ -13343,7 +13641,7 @@ Write the description in third-grade language: one short sentence, ideally 8–1
13343
13641
 
13344
13642
  ### Compact interface
13345
13643
 
13346
- Every job or ingredient Block interface, generated or custom, follows StillMade's compact rules: 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. 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. 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. StillMade draws generated manifest.ui controls this way itself (label beside control, files as tiles, the result filling the right on wide screens); you supply at most four inputs that matter in view, sensible defaults, and a short `description` on every input whose effect is not obvious from its name, which StillMade shows as its (i). An app-sized Block is different: when the request is for something a person works in (an editor, a canvas, a 3D or CAD tool, a studio), build it as a workspace Block laid out like that app: its work surface (viewport, canvas, timeline, panels, tool rails) owns the page with as much tooling as the app needs, and the compact rules apply to its settings, dialogs and status, never to the work surface. Never shrink an app to one screen of four inputs.
13644
+ Every job or ingredient Block interface, generated or custom, follows StillMade's compact rules: 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. 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. 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. StillMade draws generated manifest.ui controls this way itself (label beside control, files as tiles, the result filling the right on wide screens); you supply at most four inputs that matter in view, sensible defaults, and a short `description` on every input whose effect is not obvious from its name, which StillMade shows as its (i). An app is different: when the request is for software a person works in (an editor, a canvas, a 3D or CAD tool, a photo editor, a studio), set manifest.kind:"workspace", which makes it an app, and lay it out like that app: its work surface (viewport, canvas, timeline, panels, tool rails) owns the page with as much tooling as the app needs, and the compact rules apply to its settings, dialogs and status, never to the work surface. Never shrink an app to one screen of four inputs.
13347
13645
 
13348
13646
  ### Generated controls first
13349
13647
 
@@ -13355,7 +13653,7 @@ Optional custom view interfaces are supported, but generated manifest.ui control
13355
13653
 
13356
13654
  *When:* StillMade's builder adds this section when the person asks for an interface, preview or controls, or refines a Block that has one.
13357
13655
 
13358
- 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, in order: 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. 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. 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. 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. Rare tools (update, schedule, history, reload) are named icon buttons beside the action, never sections under the work. 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. 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`). 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. 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.
13656
+ 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.
13359
13657
  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`:
13360
13658
 
13361
13659
  - **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:`.
@@ -13368,7 +13666,7 @@ Standard custom view code (no `view.config.json` frame runtime) is checked befor
13368
13666
  - **Dynamic elements:** `StillMade.render` can create `div`, `section`, `article`, `header`, `footer`, `p`, `span`, `strong`, `em`, `i`, `small`, `h1`, `h2`, `h3`, `h4`, `label`, `button`, `input`, `textarea`, `select`, `option`, `ul`, `ol`, `li`, `table`, `thead`, `tbody`, `tr`, `th`, `td`, `details`, `summary`, `output`, `progress`, `img`; give repeated items stable IDs derived from their identity. An `img` gets its picture when you set its `src` to the data URL `StillMade.previewImage` returns, after rendering; the renderer keeps Block-owned `src`. `previewImage` works on items of an `image[]` input.
13369
13667
  - **Preview controls:** a button whose whole text is `Preview` shows the StillMade preview icon alone (an eye in viewfinder corners), with `Preview` as its accessible name; give it a `label` such as `Preview clip 2` when several sit together. Do not draw your own eye or play icon for a preview.
13370
13668
  - **Shared writes are queued in order:** if one `StillMade.updateShared` write is rejected (conflict, read-only or timeout), the writes queued behind it are rejected too. Send dependent edits one at a time or through `StillMade.scheduleSharedEdit`, keep the person's draft, and retry from the latest `onShared` value. `{merge: true}` merges independent fields of an object value; create an absent field with the precondition `{exists: false}`.
13371
- - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 30 runs and 60 image previews per minute.
13669
+ - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 120 runs and 240 image previews per minute.
13372
13670
  ### Shared project interfaces
13373
13671
  Every meaningful edit and generated result must be visible to collaborators.
13374
13672
  Prefer ordinary manifest.ui controls and declared outputs when those express