stillmade-sdk 0.1.42 → 0.1.44

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 (262) hide show
  1. package/.stillmade/sdk-baseline.json +6 -8
  2. package/AGENTS.md +15 -10
  3. package/FRAME_VIEWS.md +113 -0
  4. package/INTERFACE_DESIGN.md +62 -22
  5. package/MOBILE_UI.md +4 -2
  6. package/MODULES.md +152 -6
  7. package/PASTE_TO_LLM.md +35 -25
  8. package/README.md +3 -2
  9. package/SDK.md +994 -550
  10. package/UI_AESTHETIC.md +22 -2
  11. package/blocks/generate/package.js +1 -0
  12. package/blocks/generate/versions/1.0.0/package.js +463 -0
  13. package/docs/build/audio-to-editor.md +36 -0
  14. package/docs/build/block-builder-contract.md +150 -0
  15. package/docs/build/comfyui.md +227 -0
  16. package/docs/build/concepts.md +40 -0
  17. package/docs/build/context.md +159 -0
  18. package/docs/build/cookbook.md +57 -0
  19. package/docs/build/custom-interface.md +617 -0
  20. package/docs/build/data-types.md +83 -0
  21. package/docs/build/frame-views.md +259 -0
  22. package/docs/build/generate-media.md +59 -0
  23. package/docs/build/github-applications.md +314 -0
  24. package/docs/build/hosted-images.md +214 -0
  25. package/docs/build/hosted-music.md +39 -0
  26. package/docs/build/hosted-speech.md +157 -0
  27. package/docs/build/hosted-text.md +246 -0
  28. package/docs/build/image-description.md +49 -0
  29. package/docs/build/interface-design.md +343 -0
  30. package/docs/build/javascript.md +63 -0
  31. package/docs/build/local-preview.md +61 -0
  32. package/docs/build/media-analysis.md +27 -0
  33. package/docs/build/media.md +400 -0
  34. package/docs/build/mobile-ui.md +106 -0
  35. package/docs/build/modules.md +487 -0
  36. package/docs/build/original-blocks.md +70 -0
  37. package/docs/build/outside-apis.md +128 -0
  38. package/docs/build/production-context-templates.md +12 -0
  39. package/docs/build/project-storage.md +94 -0
  40. package/docs/build/quickstart.md +48 -0
  41. package/docs/build/recipes.md +103 -0
  42. package/docs/build/script-handoff.md +45 -0
  43. package/docs/build/script-writer.md +34 -0
  44. package/docs/build/state.md +223 -0
  45. package/docs/build/testing.md +112 -0
  46. package/docs/build/timeline-edits.md +116 -0
  47. package/docs/build/timeline-proposals.md +76 -0
  48. package/docs/build/transcription.md +59 -0
  49. package/docs/build/triggers.md +59 -0
  50. package/docs/build/tutorial.md +52 -0
  51. package/docs/build/typescript.md +34 -0
  52. package/docs/build/video-generation.md +61 -0
  53. package/docs/build/web-reading.md +37 -0
  54. package/docs/build/with-your-ai.md +27 -0
  55. package/docs/changelog.md +9 -0
  56. package/docs/creators.md +21 -0
  57. package/docs/distribute/discovery.md +43 -0
  58. package/docs/distribute/imports.md +58 -0
  59. package/docs/distribute/publishing.md +29 -0
  60. package/docs/distribute/sdk-versions.md +36 -0
  61. package/docs/distribute/selling.md +14 -0
  62. package/docs/distribute/teams.md +60 -0
  63. package/docs/distribute/versioning.md +28 -0
  64. package/docs/examples.md +23 -0
  65. package/docs/gallery.md +26 -0
  66. package/docs/index.md +50 -0
  67. package/docs/native-mcp.md +307 -0
  68. package/docs/project-types/automation.md +43 -0
  69. package/docs/project-types/batches.md +50 -0
  70. package/docs/project-types/build.md +42 -0
  71. package/docs/project-types/completion.md +15 -0
  72. package/docs/project-types/conditions.md +21 -0
  73. package/docs/project-types/hosted-review.md +115 -0
  74. package/docs/project-types/onboarding.md +75 -0
  75. package/docs/project-types/running.md +309 -0
  76. package/docs/project-types/testing.md +72 -0
  77. package/docs/reference/application-artifacts.md +87 -0
  78. package/docs/reference/authoring-guide.md +134 -0
  79. package/docs/reference/browser-applications.md +267 -0
  80. package/docs/reference/capabilities.md +45 -0
  81. package/docs/reference/cli.md +71 -0
  82. package/docs/reference/connection-api.md +59 -0
  83. package/docs/reference/cooperative-jobs.md +13 -0
  84. package/docs/reference/desktop-capabilities.md +172 -0
  85. package/docs/reference/desktop-execution.md +77 -0
  86. package/docs/reference/errors.md +233 -0
  87. package/docs/reference/execution.md +800 -0
  88. package/docs/reference/first-party-blocks.md +40 -0
  89. package/docs/reference/generation-models.md +59 -0
  90. package/docs/reference/limits.md +306 -0
  91. package/docs/reference/manifest-kinds-controls.md +36 -0
  92. package/docs/reference/manifest.md +181 -0
  93. package/docs/reference/operations.md +36 -0
  94. package/docs/reference/package-jobs.md +35 -0
  95. package/docs/reference/parity.md +55 -0
  96. package/docs/reference/permissions.md +86 -0
  97. package/docs/reference/ports.md +26 -0
  98. package/docs/reference/types.md +268 -0
  99. package/docs/reference/universal-resolver.md +303 -0
  100. package/docs/reference/view-bridge.md +60 -0
  101. package/docs/reference/view-rules.md +15 -0
  102. package/docs/reference/workspace-sync.md +25 -0
  103. package/docs/sdk.md +34 -0
  104. package/docs/tools/block-remix.md +211 -0
  105. package/docs/tools/llm.md +2213 -0
  106. package/docs/tools/repository-adaptation.md +613 -0
  107. package/examples/check-clip.stillmade.json +4 -1
  108. package/examples/generate-video.stillmade.json +6 -2
  109. package/examples/research-topic.stillmade.json +5 -1
  110. package/examples/text-workflow.stillmade.json +3 -32
  111. package/examples/timeline-polish.stillmade.json +11 -3
  112. package/examples/transcribe-audio.stillmade.json +11 -3
  113. package/package.json +1 -1
  114. package/packages/block-cli/cli.js +13 -6
  115. package/packages/block-cli/dev-host/assets/host.js +76 -38
  116. package/packages/block-cli/dev-host/assets/index.css +1 -1
  117. package/packages/block-cli/format.js +1 -1
  118. package/packages/block-cli/templates.js +31 -13
  119. package/packages/block-cli/type-packages.js +119 -0
  120. package/packages/block-platform/block-readiness.js +5 -0
  121. package/packages/block-platform/canvas.js +2 -1
  122. package/packages/block-platform/catalog.js +27 -48
  123. package/packages/block-platform/native-host-inventory.js +0 -226
  124. package/packages/block-platform/plain-descriptions.js +1 -25
  125. package/packages/block-platform/project-type-starters.js +13 -53
  126. package/packages/block-platform/script-handoff.js +2 -4
  127. package/packages/block-platform/type-workspace.js +2 -0
  128. package/packages/block-sdk/appearance.js +3 -1
  129. package/packages/block-sdk/block-tier.js +1 -1
  130. package/packages/block-sdk/builder-contract.js +10 -8
  131. package/packages/block-sdk/capabilities.js +1 -1
  132. package/packages/block-sdk/contracts.js +6 -2
  133. package/packages/block-sdk/first-party-identities.js +1 -84
  134. package/packages/block-sdk/first-party-packages.js +3 -85
  135. package/packages/block-sdk/generation-catalog.js +15 -3
  136. package/packages/block-sdk/index.d.ts +2 -1
  137. package/packages/block-sdk/media-analysis-example.js +1 -1
  138. package/packages/block-sdk/mock-host.js +7 -1
  139. package/packages/block-sdk/module-host-calls.js +35 -10
  140. package/packages/block-sdk/module-hosted.js +15 -2
  141. package/packages/block-sdk/module-media-access.js +71 -0
  142. package/packages/block-sdk/module-node.js +1 -1
  143. package/packages/block-sdk/portable-media-actions.js +1 -1
  144. package/packages/block-sdk/runtime.js +1 -1
  145. package/packages/block-sdk/timeline-proposal-example.js +1 -1
  146. package/packages/block-sdk/transcription-example.js +1 -1
  147. package/packages/block-sdk/video-generation-example.js +1 -1
  148. package/packages/block-sdk/view-bridge.js +4 -1
  149. package/packages/block-sdk/view-design.js +17 -14
  150. package/packages/block-sdk/web-example.js +1 -1
  151. package/packages/design-system/block-kit.css +48 -12
  152. package/packages/design-system/block-kit.html +3 -2
  153. package/blocks/mcp-motion-planner/versions/1.1.0/package.js +0 -97
  154. package/blocks/motion-designer/versions/1.0.1/package.js +0 -173318
  155. package/blocks/motion-designer/versions/1.0.2/package.js +0 -173318
  156. package/blocks/motion-frame-review/versions/1.1.1/package.js +0 -465
  157. package/blocks/motion-frame-review/versions/1.1.2/package.snapshot.js +0 -572
  158. package/blocks/narration/package.js +0 -1
  159. package/blocks/narration/versions/1.0.0/package.js +0 -221
  160. package/blocks/narration/versions/1.0.1/package.js +0 -221
  161. package/blocks/native/sm.animated-scene/versions/2.0.0/package.js +0 -475
  162. package/blocks/native/sm.animated-scene/versions/2.1.0/package.js +0 -498
  163. package/blocks/native/sm.animated-scene/versions/2.2.0/package.js +0 -524
  164. package/blocks/native/sm.animated-scene/versions/2.3.0/package.js +0 -618
  165. package/blocks/native/sm.animated-scene/versions/2.3.1/package.js +0 -618
  166. package/blocks/native/sm.animated-scene/versions/2.3.2/package.js +0 -723
  167. package/blocks/native/sm.animated-scene/versions/2.3.3/package.js +0 -723
  168. package/blocks/native/sm.animated-scene/versions/2.3.4/package.js +0 -676
  169. package/blocks/native/sm.animated-scene/versions/2.3.5/package.js +0 -693
  170. package/blocks/native/sm.animated-scene/versions/2.3.6/package.js +0 -700
  171. package/blocks/native/sm.animated-scene/versions/2.3.7/package.js +0 -747
  172. package/blocks/native/sm.animated-scene/versions/2.3.8/package.js +0 -769
  173. package/blocks/native/sm.animated-scene/versions/2.3.9/package.js +0 -769
  174. package/blocks/native/sm.animated-scene-assistant/versions/1.0.0/package.js +0 -183
  175. package/blocks/native/sm.blueprint/versions/2.0.0/package.js +0 -358
  176. package/blocks/native/sm.blueprint/versions/2.1.0/package.js +0 -358
  177. package/blocks/native/sm.blueprint/versions/2.1.1/package.js +0 -358
  178. package/blocks/native/sm.blueprint/versions/2.1.2/package.js +0 -358
  179. package/blocks/native/sm.blueprint/versions/2.1.3/package.js +0 -381
  180. package/blocks/native/sm.blueprint/versions/2.1.4/package.js +0 -381
  181. package/blocks/native/sm.blueprint/versions/2.1.5/package.js +0 -339
  182. package/blocks/native/sm.blueprint/versions/2.1.6/package.js +0 -356
  183. package/blocks/native/sm.blueprint/versions/2.1.7/package.js +0 -363
  184. package/blocks/native/sm.blueprint/versions/2.1.8/package.js +0 -410
  185. package/blocks/native/sm.blueprint/versions/2.1.9/package.js +0 -422
  186. package/blocks/native/sm.blueprint-assistant/versions/1.0.0/package.js +0 -172
  187. package/blocks/native/sm.chat/versions/2.0.0/package.js +0 -144
  188. package/blocks/native/sm.chat/versions/2.0.1/package.js +0 -97
  189. package/blocks/native/sm.chat/versions/2.0.2/package.js +0 -100
  190. package/blocks/native/sm.chat/versions/2.0.3/package.js +0 -100
  191. package/blocks/native/sm.continue-if/versions/1.0.0/package.js +0 -168
  192. package/blocks/native/sm.describe-media/versions/2.0.0/package.js +0 -153
  193. package/blocks/native/sm.describe-media/versions/2.0.1/package.js +0 -156
  194. package/blocks/native/sm.describe-media/versions/2.1.0/package.js +0 -140
  195. package/blocks/native/sm.image-editor/versions/2.0.0/package.js +0 -183
  196. package/blocks/native/sm.image-editor/versions/2.0.1/package.js +0 -190
  197. package/blocks/native/sm.new-items/versions/1.0.0/package.js +0 -191
  198. package/blocks/native/sm.pick-photos/versions/1.0.0/package.js +0 -334
  199. package/blocks/native/sm.quality-audit/versions/1.0.0/package.js +0 -192
  200. package/blocks/native/sm.quality-audit/versions/1.0.1/package.js +0 -192
  201. package/blocks/native/sm.quality-audit/versions/1.0.2/package.js +0 -195
  202. package/blocks/native/sm.research/versions/2.0.0/package.js +0 -175
  203. package/blocks/native/sm.research/versions/2.0.1/package.js +0 -184
  204. package/blocks/native/sm.research/versions/2.0.2/package.js +0 -187
  205. package/blocks/native/sm.script/versions/2.0.0/package.js +0 -144
  206. package/blocks/native/sm.script/versions/2.1.0/package.js +0 -144
  207. package/blocks/native/sm.script/versions/2.1.1/package.js +0 -144
  208. package/blocks/native/sm.script/versions/2.1.2/package.js +0 -105
  209. package/blocks/native/sm.script/versions/2.1.3/package.js +0 -152
  210. package/blocks/native/sm.script/versions/2.1.4/package.js +0 -156
  211. package/blocks/native/sm.script/versions/2.1.5/package.js +0 -156
  212. package/blocks/native/sm.shot-plan/versions/2.0.0/package.js +0 -301
  213. package/blocks/native/sm.shot-plan/versions/2.1.0/package.js +0 -528
  214. package/blocks/native/sm.shot-plan/versions/2.1.1/package.js +0 -528
  215. package/blocks/native/sm.shot-plan/versions/2.1.2/package.js +0 -528
  216. package/blocks/native/sm.shot-plan/versions/2.1.3/package.js +0 -536
  217. package/blocks/native/sm.shot-plan/versions/2.2.0/package.js +0 -800
  218. package/blocks/native/sm.shot-plan/versions/2.2.1/package.js +0 -920
  219. package/blocks/native/sm.shot-plan-assistant/versions/1.0.0/package.js +0 -173
  220. package/blocks/native/sm.stock-search/versions/2.0.0/package.js +0 -174
  221. package/blocks/native/sm.thumbnail/versions/2.0.0/package.js +0 -137
  222. package/blocks/native/sm.thumbnail/versions/2.0.1/package.js +0 -140
  223. package/blocks/native/sm.transcribe/versions/2.0.0/package.js +0 -163
  224. package/blocks/native/sm.transcribe/versions/2.0.1/package.js +0 -168
  225. package/blocks/native/sm.whiteboard-assistant/versions/1.0.0/package.js +0 -175
  226. package/blocks/native/sm.youtube/versions/2.0.0/package.js +0 -217
  227. package/blocks/occasion-story/package.js +0 -1
  228. package/blocks/occasion-story/versions/1.0.0/package.js +0 -1658
  229. package/blocks/occasion-story/versions/1.0.1/package.js +0 -1699
  230. package/blocks/occasion-story/versions/1.0.2/package.js +0 -1682
  231. package/blocks/photo-reel-composer/versions/1.0.0/package.js +0 -3043
  232. package/blocks/photo-reel-composer/versions/1.0.1/package.js +0 -3026
  233. package/blocks/photo-reel-composer/versions/1.0.2/package.js +0 -4149
  234. package/blocks/product-ad-variants/versions/1.0.0/package.js +0 -2911
  235. package/blocks/product-launch-director/versions/1.0.0/package.js +0 -103
  236. package/blocks/release-visuals/package.js +0 -1
  237. package/blocks/release-visuals/versions/1.0.0/package.js +0 -1492
  238. package/blocks/release-visuals/versions/1.0.1/package.js +0 -1517
  239. package/blocks/release-visuals/versions/1.0.2/package.js +0 -1500
  240. package/blocks/screen-recorder/package.js +0 -1
  241. package/blocks/screen-recorder/versions/1.0.0/package.js +0 -223
  242. package/blocks/screen-recorder/versions/1.0.1/package.js +0 -223
  243. package/blocks/short-form-finish/package.js +0 -1
  244. package/blocks/short-form-finish/versions/1.0.0/package.js +0 -1043
  245. package/blocks/short-form-finish/versions/1.0.1/package.js +0 -1043
  246. package/blocks/short-form-finish/versions/1.0.2/package.js +0 -1043
  247. package/blocks/short-form-finish/versions/1.0.3/package.js +0 -1026
  248. package/examples/saas-product-launch-reviewed-source.stillmade.json +0 -383
  249. package/examples/saas-product-launch.stillmade.json +0 -304
  250. package/packages/block-platform/dealer-inventory-video-project-type.js +0 -38
  251. package/packages/block-platform/first-revenue-project-types.js +0 -46
  252. package/packages/block-platform/fixture-starters.js +0 -13
  253. package/packages/block-platform/launch-project-source.js +0 -10
  254. package/packages/block-platform/launch-project.js +0 -30
  255. package/packages/block-platform/motion-project-types.js +0 -55
  256. package/packages/block-platform/motion-starters.js +0 -14
  257. package/packages/block-platform/product-ad-variants-project-type.js +0 -30
  258. package/packages/block-platform/product-launch-updated.js +0 -90
  259. package/packages/block-platform/revenue-review-project-types.js +0 -36
  260. package/packages/block-platform/revenue-review-starters.js +0 -13
  261. package/packages/block-platform/script-writer.js +0 -29
  262. package/packages/block-platform/website-source-block.js +0 -14
@@ -0,0 +1,2213 @@
1
+ # Build with an AI coding tool
2
+
3
+ The fastest route is to send your coding agent [Build a Block with your AI](../build/with-your-ai.md) with your idea: it installs the SDK (`npm install --save-dev stillmade-sdk`), builds the Block and runs the readiness gate without you running anything.
4
+
5
+ ## Start with the SDK contract
6
+ Use the button below to copy the complete authoring guide, then add a short description such as “Make a Block that converts an image to grayscale while preserving alpha.” The guide includes the package format, supported runtime, typed ports, permissions, fixtures, and a complete example.
7
+
8
+ [Download the paste-ready guide](https://stillmade.shop/block-sdk/PASTE_TO_LLM.md) or [download the SDK](../sdk.md).
9
+
10
+ ## Review the result
11
+ Ask the assistant to return source files or one `.stillmade.json` package. Run the CLI validation, tests, and preview. Import it into StillMade and review before confirming. An LLM’s claim that tests passed is not a test report from the sandbox.
12
+
13
+ ## Complete authoring guide
14
+ ```markdown
15
+ # StillMade authoring contract for coding agents
16
+
17
+ <!-- builder-contract:start (generated by npm run build:sdk; do not edit) -->
18
+
19
+ ## Block builder contract (stillmade.block-builder@1.17.0)
20
+
21
+ 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.
22
+
23
+ ### Source of truth
24
+
25
+ Treat the current SDK (`stillmade.block.json` manifest, `packages/block-sdk` contracts, validators, examples and published docs) as the source of truth. Before writing code, read the manifest contract, at least two working first-party Blocks closest to the request, the runtime you will use, the typed port and resolver contracts, and the shared-interface contract when you need a custom view. Do not invent runtimes, operations, permissions, ports, host services or bridge calls. If the documentation and the validator disagree, the validator wins and the mismatch should be reported.
26
+
27
+ ### Reuse first
28
+
29
+ Before building, check what exists (`find_existing_blocks` over MCP, or the Marketplace and your library) by the goal's inputs, results and operations. Only a `likely` or `verified-contract` match (a Block or a chain of two) does the job: use it. `partial` is a close Block to look at: remix it (license, notices and credit stay attached) or build only the missing step. Never republish another creator's source without remix lineage: public copies are refused. Build new when nothing does the job or the person wants their own, and name similar Blocks you considered.
30
+
31
+ ### Three sizes of Block
32
+
33
+ Blocks come in three sizes and the manifest decides which (packages/block-sdk/block-tier.js): an app is a workspace a person works in (Editor, Canvas, Shots), placed in a Type and never wired automatically; a job is one deliverable a person would name (the narration, the captioned clip), built from the hosted operations it needs (`permissions.capabilities`) and reviewed in one place, the unit Chat picks for a Step; an ingredient is one hosted operation with nothing of its own (a declarative capability Block), what a job is made of. When two ingredients would stand in a row to make one deliverable (writing the words, then voicing them), build or pick the job that does both and keep the review point inside it. A job declares a second output when another Block consumes the intermediate (the script beside the voice); split into two jobs only when the intermediate has a life of its own or the halves have different owners or payment.
34
+
35
+ ### Choose the runtime
36
+
37
+ Pick the runtime from the job. No hosted AI: a recipe or isolated `javascript`, both able to run unattended. One text, script or brief prompt returning text, script or JSON: a `capability` Block. Hosted operations on other values (a transcript, media, structured data), several hosted operations in a row, or a result shaped in code: one `module` Block, `module/main.js` calling `stillmade.hosted.run(operation, request)` for each operation in `permissions.capabilities` (MODULES.md), never one Block per operation; it runs in the person's browser while StillMade is open. Build a new Block in a folder with `stillmade-block create`, `test`, `ready` and `pack`, and import the file `pack` writes (a module Block writes `<id>-<version>.stillmade-module.json`) through Create, then Import Block. A `.stillmade-block` archive is only for adapting a GitHub repository.
38
+
39
+ ### One job per Step
40
+
41
+ A Step is one job: the work between two moments where a person or the outside world must see the result. A guided job, one the person fills in and then gets a result from, is one Step with one Block whose own screen holds everything: the inputs and settings, then a cheap check before the expensive run (the script and a voice sample before the video), then the result. On a wide screen the inputs sit on the left and the result's place on the right; on a phone they stack, the result's place under the button. A cheap check or a review the person does on that screen never starts a new Step. Start a new Step only at one of these points; everything else is a part of the neighboring Step.
42
+
43
+ 1. **A person works with the result**: in a workspace they spend a while in (the Editor, a storyboard, a Canvas, a CAD or drawing tool), or someone else approves or takes it over (a hand-off). Turning what a workspace made into something else is the next Step: a CAD tool is its own Step, and the Block that takes pictures of its model and makes a video is the next one. So Bible, Canvas and Editor are separate Steps after Chat.
44
+ 2. **Finding new things turns into handling each one**: the trigger (a schedule or an app event), the app read, its filter and "only new" are the first job; what is done with each item is the next job. A trigger is a setting of the first job, never a Step of its own.
45
+ 3. **The result leaves StillMade**: it is sent, posted, published, paid or written into another app. Consecutive writes to the same app, with the glue that shapes data for them, are one job.
46
+ 4. **Setup or a wait**: a one-time setup that is not part of each job (a brand kit, capturing a voice or a likeness), or a render that takes minutes and is delivered later.
47
+
48
+ In a production the Steps are the workspaces a person opens: work feeding the next workspace is its part, else it joins the one before. A Block with its own interface (a workspace, editor or picker) is always its own Step; two such Steps are never one. A Type whose own Block renders the finished video (`timeline.render`) needs no Editor Step; the Editor (1.0, captions and export built in) is a Step only when the person edits the cut by hand, and Export video is never a Step beside it.
49
+
50
+ A part that only runs in the desktop app cannot share a Step with cloud parts. Before adding a Step, name which of these reasons makes it separate; if none applies, make it a part of a neighbor. A Step nobody would ever open must not exist on its own. Prefer an app task's own filter (a Gmail query such as `has:attachment filename:png`) to an AI check, and use AI decide only for judgement.
51
+
52
+ Build one Block for a job one Block can do: a module Block calls every hosted operation the job needs (it transcribes a sermon, then finds the verses said in it), a JavaScript Block does several local transforms, a workspace owns its whole document. A Block being built never takes an ingredient's result only so the ingredient can be its own part or Step (Transcribe in front of a verse finder): it calls that operation itself. Parts in one Step (`groups` in the Project Type) are for jobs made of Blocks that already exist and app tasks, each keeping its own permission, approval and credit rules. Never invent a combined runtime.
53
+
54
+ Examples: "a listing video from my photos and the home's facts" is one Step: one Block whose page takes the photos and facts, shows the script and a voice sample to check, then makes the video. "every new email with an image becomes a generated image" is two Steps, "Gmail · New emails with images" (When, fetch with an image filter, only new) and "Image from email" (AI write, Generate image); listing When, Fetch, Only new, AI decide, Continue only if, AI write and Generate image as seven Steps splits one job into its parts. "A new Meta lead goes to HubSpot" is three Steps: "Meta Ads · New leads", "Qualify lead" and "HubSpot · Add contact".
55
+
56
+ ### Define the capability first
57
+
58
+ Translate the request into an explicit capability contract before implementation: what the Block does; each input with its type, meaning and whether it is required (prefer optional settings with safe defaults over extra required questions); each output with its type and meaning; persistent state; project context it reads; permissions; UI; long-running behavior; expected failures; cancellation; determinism; and whether it needs hosted models, media, desktop tools or shared editing. Keep the public interface as small and general as possible (few required inputs, one primary output) and keep implementation details out of it; small means a small interface, never a fragment of a job. A Block that generates images, video or speech declares only its prompt and output: every Step that runs it gets the host’s generation settings (model, quality, length and resolution, voice, speed, how many), saved with the Step, changed on its page or by Chat, and shown with their cost before each run.
59
+
60
+ ### Blocks compose
61
+
62
+ A Block is one job: not a mini-app of many jobs and not a fragment of one. Its outputs become other Blocks' inputs and its inputs come from other Blocks, project context or the user. Use the typed port system: shared StillMade types (`image`, `video`, `audio`, `asset`, `script`, `shot[]`, ...), `required`, a short `description`, and a `semantic` only where it narrows what fits. Mark exactly one primary input and one primary output so Project Type Steps connect automatically, and use `kind: "task"`: a `workspace` Block owns a project document and is never connected automatically. Inputs marked `context` read the project and cannot receive a connection from another Step. Output reusable StillMade values: media references (`{kind,assetId,versionId,url}`) and structured production values with `schemaVersion: 1`, preserving provenance, versions and relationships downstream Blocks need. Never make ordinary users wire JSON, copy IDs, understand internal paths or translate one Block's output into another: `createUniversalResolver()` resolves inputs from compatible upstream outputs, permitted project context and, for optional non-context inputs, `StillMade.requestFromUser`.
63
+
64
+ ### Feature map
65
+
66
+ Every Block carries a feature map in `manifest.features`, written by whoever builds it while they build, so the person never writes it. Add one entry for each separate thing a person could ask for: a tool, a mode, a preset family or a kind of result (an editor's reaction GIFs, its motion presets, a recorder's screen recording), not one entry covering several tools. Each entry has a lowercase `id`, a `label` of 40 characters or fewer, `does` (what it does, 160 characters or fewer) and `asks`: 1–8 short requests in a customer's own words that this feature answers ("add a fire gif over my car"). `operation` names an id in the Block's own `manifest.operations` (omit it when there are none); `inputs`/`outputs` name declared ports. The gate fails a Block without one. Add or update the entry in the same change that adds or alters a tool, and remove it when the tool goes. List only what the code really does; listings are generated from this map, nobody can edit them, and StillMade matches requests to features. Features are not tested at publish: each shows as new until real use proves it.
67
+
68
+ ### Use platform primitives
69
+
70
+ Prefer, in order: an SDK capability; a documented host capability (the hosted operations are text.generate, audio.speech, audio.transcribe, audio.music, audio.sfx, image.generate, video.generate, image.describe, media.analyze, web.fetch, web.research, timeline.propose, workspace.propose, connection.execute; connected third-party apps go through `connection.execute`); an approved dependency the SDK already bundles; small Block-local code. Do not duplicate what StillMade provides. A Block never contains its own asset system, file picker, authentication, project-context system, input resolver, Block connection system, credit system, secrets store, permission system, progress system, multiplayer system, persistence system, model-provider abstraction or execution protocol.
71
+
72
+ ### No hidden workarounds
73
+
74
+ Never make a Block look functional through hard-coded local paths, developer-machine dependencies, undocumented environment variables, admin or database access, hidden core modifications, direct database writes, fake or mocked production results, hand-prepared test assets that hide incompatibilities, credentials in code, or assumptions about one user's machine. The finished Block must work through the same interfaces available to any installed Block; `permissions.filesystem` and `permissions.secrets` must be empty arrays, and so must `permissions.network`, except in a module Block, which lists the https origins it calls through `stillmade.net.fetch`.
75
+
76
+ ### Name it briefly
77
+
78
+ Give the Block a short, plain name of 40 characters or fewer that says what it does ("Captioned Clip", not "Captioned Business Clip Generator With Review"). The name appears in step bars, Project Type cards and pickers, so put the detail in the description instead. New releases with a longer name do not pass readiness.
79
+
80
+ ### Name inputs in the job's words
81
+
82
+ Name each input for what the person brings, in the job's words: `sermonVideo` for a sermon clip Block, `listingPhotos` for a listing, `lessonSlides` for a lesson podcast. StillMade titles the input from that name ("Sermon video") and words its drop area from it ("Drop your sermon video here", the formats, "Choose a video"), in StillMade and the SDK preview, so never draw your own upload box or `<input type="file">`: a Block's screen shows the files as tiles and adds more with `<button class="sm-add" data-stillmade-add="listingPhotos" aria-label="Add photos">`, which opens StillMade's picker and upload, and a list's tiles drop or reorder a file with `data-stillmade-remove="listingPhotos" data-index="2"` and `data-stillmade-move="listingPhotos" data-index="2" data-to="1"` buttons; the files arrive through `onInput`. Keep a bare type name (`video`, `file`) only when any file of that kind does the job. The `description` says what makes a good one.
83
+
84
+ ### Speak the job
85
+
86
+ These are StillMade's defaults: unless the person or the creator asked for something else, every screen speaks the job in its own words and adds nothing else. `manifest.action` names the Step's button, {label:"Plan the tour", running:"Planning the tour…"}, and StillMade adds the price ("Plan the tour · 1 credit"). Every Block whose Step shows StillMade's button declares it: any Block without its own screen, and any that calls operations. Without it StillMade can only guess from the Block's name or output ("Make clips"), and the readiness gate and StillMade's own builder send the Block back until it names the job. When the Block makes a picture, video or sound with AI the label starts with Generate and says what ("Generate tour", "Generate voiceover"); one that cuts or renders the person's own media says Make ("Make 6 clips"); never "Make it", "Run", "Submit", "Process" or a bare "Generate". The main button stands out: one solid `sm-primary` in the kit's ink, never restyled to the background; everything else is `sm-quiet` or an `sm-icon`, and close is an X icon. Report progress in the job's units with `stillmade.progress(fraction, label)` ("Moving through the kitchen, 4 of 9 rooms"), never "Processing". Head the result in the job's words ("9 rooms", "Your tour · 44 s"), never "Output". Say a problem in one line with its fix beside it ("No bathroom photo. Guests look for one." and Add photo). Stay quiet: no welcome text, tips under fields, tours or banners, and at most one line of guidance on a screen, only where the next move is not obvious; anything more goes in the input's `description`, which StillMade shows behind an (i). Say nothing about how the Block works (where it runs, 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. Every drop area offers "Use a reference", which fills the Step from the person's saved photos, videos and notes: media inputs fill by kind, so take `video` or `asset` where a clip works as well as a photo, and 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. Days and times are picked, never typed: use the `date` type, which StillMade shows as a calendar, with a list of times when the input is about a time (`postAt`, `startsAt`); a custom view uses `<input type="date">` or `type="datetime-local"`. Posting is a connector Step of its own, and StillMade words its button from the app and action ("Post to Instagram"). Before handing the Block back, clear every Wording note that `gate` and readiness list, unless the person asked for those words.
87
+
88
+ ### Interface
89
+
90
+ 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 under the button, 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; 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 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.
91
+
92
+ ### Shared editing
93
+
94
+ Projects are multiplayer. A custom view subscribes through `StillMade.onShared(callback)` and publishes every editable field: give static controls a unique `id` and `data-stillmade-share="field"` (the host owns their synchronization), use `StillMade.bindShared(field, elementId)` for controls created dynamically, and `StillMade.updateShared(values, before, options)` for structured values; private variables and DOM values are not shared. Mark a truly device-only control (playback, hover, device selection) with `data-stillmade-local`. Observe shared outputs as well as state, honor `canEdit` in every mutating callback (read-only participants cannot write), never run, click or call a paid integration in response to a remote update, and preserve conflicting drafts. Shared background work uses `sharedWork` with the `work.shared` permission.
95
+
96
+ ### External services
97
+
98
+ Reach external services only through StillMade's hosted operations, the connections service (`connection.execute`), or, in a module Block, `stillmade.net.fetch` to origins declared in `permissions.network` (with the key each needs), `stillmade.net.connect` for a live WebSocket to one of them, and `stillmade.connections.call` to adapters or catalog apps in `permissions.connections`; never put keys in code, inputs or headers: StillMade adds each person's own key on its servers. The host owns the model and provider choice, BYOK keys, StillMade credits, spending consent, authorization, token refresh, rate limits, retries and provider errors; a Block never receives a secret and never names a provider unless it must. Treat provider unavailability, timeout, cancellation and partial failure as outcomes the Block reports, not crashes: a call the result needs throws and fails the run (see Useful failures); an optional part's failure is caught, given its fallback and named in an optional `notes` text output. Generated images, video, voices, music and sound effects come from hosted operations or the `media.generate` action, paid with the user's StillMade credits at StillMade's prices; for structured model output declare a `json` output with `capability.schema` instead of parsing prose. When a Block needs npm libraries, WebAssembly (threaded: `"threads": true` in module.json) or heavy compute, use `runtime: "module"` and reach hosted operations through `stillmade.hosted.run` with the operations listed in `permissions.capabilities`. Decode, cut, join, re-encode, extract or normalise audio and video with `stillmade.media.transform` on the device; never ask for a server render. For a rich interactive interface (React or another framework, SVG, canvas, drag), use a frame view: `src/view.config.json` with `{"runtime":"frame"}` and `src/view.js` bundled as one IIFE, colored with StillMade's theme variables. For files and structured data use the `file`, `document`, `table`, `url`, `date` and `color` types and their generated controls and presentations, not JSON text.
99
+
100
+ ### Dependencies
101
+
102
+ A Block must declare everything it requires and must not assume anything is installed. Portable packages carry no package dependencies (`portable.dependencies` is empty): inline small, license-compatible code and record its license and notice in `manifest.license` and `manifest.provenance`. The copyright holder in the Block's own `provenance.notice` is the person it is made for: replace the placeholder `create` writes with their name, never StillMade, the SDK or yourself. Desktop-only tools are reached solely through declared desktop permissions and the host desktop bridge. If a dependency cannot be delivered through the Block runtime, report the incompatibility instead of assuming the user's machine has it.
103
+
104
+ ### Security
105
+
106
+ Treat Block code, external data, files, repositories, model output and network responses as untrusted. Request the minimum permissions and capabilities; `stillmade-block permissions --fix` declares exactly what the source uses. Do not weaken the sandbox (QuickJS has no fetch, DOM, timers, modules or Node APIs). Validate and bound every value crossing a boundary. Never expose secrets through logs, outputs, project context, UI, error messages or serialized state.
107
+
108
+ ### Execution lifecycle
109
+
110
+ A successful run means more than a returned value. Validate inputs before work; return only declared output ports with values matching their types; run long work as a cooperative job (`job.pending` with monotonic progress) instead of blocking; honor cancellation and the host timeout; keep persistent state in `manifest.state` (module Blocks: `stillmade.storage`) with migration routes so a reopened project and a repeated run behave the same; never leave orphaned jobs, temporary values or corrupted project state after cancellation or failure.
111
+
112
+ ### Useful failures
113
+
114
+ Errors must say what failed, why it likely failed, whether a retry makes sense and what the user can do, so the native project chat and controller can troubleshoot. In JavaScript, throw an `Error` with that message: the host reports it as a `JAVASCRIPT` run failure carrying your message (guest code cannot set its own error code). Reject invalid or oversized input up front with a message naming the input and the limit. Never reduce a failure to "Something went wrong".
115
+
116
+ ### Only supplied facts
117
+
118
+ A text Block that writes from supplied facts must not let its prompt template outrank its own "never invent" rule. Every section that needs a specific fact (phone, website, address, price, inspection, certification, warranty, features) is written only when the input supplies that fact; otherwise the section is left out or replaced by a generic line that asserts nothing (for example "Call us today to schedule your test drive."). Never default to "inspected", "certified", invented features or placeholder contact details such as 555 numbers or example websites. Prefer extract-then-generate: first list the facts the input actually contains, then write using only that list. Add a check after generation that rejects or removes sentences naming a claim, number or address not present in the input. Include fixtures with the facts omitted and with them supplied.
119
+
120
+ ### Adapt to the platform
121
+
122
+ Do not modify StillMade core because a request does not fit immediately. First implement the capability entirely through documented SDK and platform primitives. If that is impossible, classify why: A Block implementation problem (fix it), B missing SDK exposure of an existing host capability, C missing generic platform primitive, D security or permission restriction, E runtime or language incompatibility, F fundamentally incompatible capability. For B or C, name the smallest general primitive that would serve this Block and a meaningful class of future Blocks (shaped like `files.watch()` or `media.transform()`, never `supportThisSpecificBlock()`). Return `{"unsupported":"exact reason","gapClass":"B|C|D|E|F","primitive":"..."}` instead of a package that pretends to work.
123
+
124
+ ### What StillMade verifies
125
+
126
+ StillMade runs the readiness check on every Block it admits: manifest and contract validation, the source security scan, every fixture in the sandbox, wrong-type and over-limit inputs, declared output types, cancellation and timeout cleanup, state reopen and repeated runs, composition with a real upstream and downstream catalog Block through the resolver, a Project Type preview, the shared-interface contract with two participants editing a custom view in the real preview runtime (their edits must converge and a read-only participant must not write), and phone and desktop layout. Design for all of them and include normal and edge-case fixtures (1 to 50; stateful Blocks also declare `expectedState`). Test materially different inputs within the declared capability, and document limitations instead of overclaiming generality. Two of these checks need StillMade itself: two-participant shared editing and the phone and desktop UI profile run in StillMade's preview runtime when the Block is imported or released, so `ready` in the downloaded SDK reports them as not verified rather than passed.
127
+
128
+ ### Project Types
129
+
130
+ A Project Type is an ordered production workflow. Each Step's primary output must be a compatible input for the next Step, secondary values come from permitted project context, and every member Block obeys this contract. Install Blocks into a realistic Project Type: project chat must understand each Step, inputs must resolve intuitively, outputs and execution state must be visible, failures must be diagnosable, and the user must be able to continue through the workflow. Group the parts of one job into one Step instead of listing helper parts as Steps; most automations are two to four Steps.
131
+
132
+ ### Readiness report
133
+
134
+ Finish with a Block Readiness Report: capability, contract (inputs, outputs, permissions, dependencies), platform APIs used, tests actually performed and their results, the composition test (upstream, Block, downstream), known limitations, unsupported requirements and any platform gap with its smallest general primitive. Report exactly one status, the one StillMade's readiness check returned (`ready` in the SDK CLI, `check_block_readiness` over MCP), never one you chose: PRODUCTION READY (every applicable check passed), READY WITH DOCUMENTED LIMITATIONS (works reliably within stated boundaries), BLOCKED BY PLATFORM CAPABILITY (needs a missing generic primitive) or FAILED. Never label a Block production ready to satisfy a request, and never claim a check ran when it did not.
135
+
136
+ ### Definition of done
137
+
138
+ A Block is done only when every applicable item holds. Passing a compile, one unit test, a rendered UI or one happy-path sample is not done.
139
+
140
+ - DOD-01 Installs through the normal Block mechanism
141
+ - DOD-02 Manifest and contracts validate
142
+ - DOD-03 Runs through the normal runtime
143
+ - DOD-04 Required permissions are declared
144
+ - DOD-05 Required dependencies are reproducible
145
+ - DOD-06 Real inputs work
146
+ - DOD-07 Invalid inputs fail correctly
147
+ - DOD-08 Outputs conform to their declared types
148
+ - DOD-09 Outputs are usable by compatible downstream Blocks
149
+ - DOD-10 Universal resolver integration works
150
+ - DOD-11 Applicable project context works
151
+ - DOD-12 Applicable persistence works
152
+ - DOD-13 Applicable multiplayer behavior works
153
+ - DOD-14 Cancellation and failure cleanup work
154
+ - DOD-15 UI works in relevant states and screen sizes
155
+ - DOD-16 No hidden developer setup is required
156
+ - DOD-17 Security boundaries remain intact
157
+ - DOD-18 Relevant integration tests pass
158
+ - DOD-19 Works after application restart or project reopen
159
+ - DOD-20 A realistic Project Type execution succeeds when applicable
160
+
161
+ ### Platform gap classes
162
+
163
+ - A: Block implementation problem
164
+ - B: Missing SDK exposure of an existing host capability
165
+ - C: Missing generic platform primitive
166
+ - D: Security or permission restriction
167
+ - E: Runtime or language incompatibility
168
+ - F: Fundamentally incompatible capability
169
+
170
+ <!-- builder-contract:end -->
171
+
172
+ <!-- authoring-guide:start (generated by npm run build:sdk; do not edit) -->
173
+
174
+ ## SDK authoring guide
175
+
176
+ StillMade's own builders (describe a Block, refine, remix, GitHub adaptation and Project Types) are given these exact sections, rendered from `packages/block-sdk/authoring-guide.js` beside the builder contract. A section marked *When* reaches StillMade's builder only for requests that need it; follow it whenever it applies to your Block.
177
+
178
+ ### Remixable source
179
+
180
+ Every finished Block must support automatic remixing through the standard SDK contract. Include complete editable runtime source/configuration, typed inputs/outputs, UI controls, fixtures, and a supported permissive manifest.license with its complete real copyright/license notice in manifest.provenance.notice (or verified portable evidence). Do not disable remixing. The code stays private to its creator; others remix only through StillMade’s AI builder. No custom remix endpoint or host callback is needed. Preserve inherited source, notices and lineage. Never invent ownership, copyright holders or permission, or apply a new license to unresolved third-party source; return unsupported with a clear licensing reason instead. The shared SDK admission validator enforces this requirement.
181
+
182
+ ### Package format
183
+
184
+ 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.
185
+
186
+ ### Feature map
187
+
188
+ Every Block lists its separate tools in manifest.features, one entry per tool or mode a person could ask for: {"id":"reaction-gifs","label":"Reaction GIFs","does":"Search GIFs and drop one over a clip.","asks":["add a fire gif over my car","reaction gif when he laughs"]}. Use 1–24 entries; id is lowercase with hyphens, label at most 40 characters, does at most 160, asks 1–8 short requests in a customer's words. A Block that really does one job has one entry. Rewrite the starting entry create writes (it copies the description), or the gate fails. When refining or remixing, add, change or remove entries in the same change as the tool, and keep the ids of tools that stay.
189
+
190
+ ### Port meanings
191
+
192
+ For each newly created Block, declare the meaning of every input and output in its manifest contract, not just a physical type. Each input needs required:boolean, a short plain-language description and a semantic (import refuses an input without one, and gate checks it). An input accepts values declared with its meaning or a narrower one: an input for any photo a Step hands on uses source_image, as StillMade's Image editor does (generated_image and processed_image are kinds of source_image), and a photo editor's result is processed_image. Videos and sounds work the same way: an input for any video uses source_video (generated_video, edited_video and rendered_video are kinds of it) and one for any sound source_audio (narration_audio and rendered_audio are kinds of it); a video the Block renders is rendered_video, a sound it renders rendered_audio. Each output needs semantic and a short description. Use lowercase snake_case or dotted semantic names, such as company_profile or email_subject; different meanings are not interchangeable merely because both are text. If an existing role is present, semantic must equal that role. Reuse an exact existing meaning only when the information actually has that meaning. Describe structured fields with schema when their shape matters, and always on a json or object output (import refuses one without it): for example {name:"string",website:"string",industry:"string?"}. Supported schema fields are string/text, number, integer, boolean, object, json, arrays of those types, and optional fields with ?. A value of a structured type (transcript, script, brief, shot-plan and the like) is an object with schemaVersion:1, in code and fixtures; a transcript as the Transcription Block hands it on is {schemaVersion:1,text:"...",words:[{text:"Today",start:0,end:0.4}]}, times in seconds. Declare sources only when a requirement restricts where its answer may come from: upstream, project, adapter, chat, default, user or external. Do not mark a credential as ordinary text or put secrets in defaults. Contracts describe information; they never authorize network access, model calls, project edits, credit spending or new runtime APIs. Keep existing imported, installed and base Block contracts unchanged; do not silently relabel old published contracts to make a connection appear compatible. New contracts must match actual code and fixtures.
193
+ Custom inheritance is persisted in manifest.semantics, an array of {name,description?,parents?}. Use namespaced custom names, at most 64 definitions per Block, eight parents per definition, and 500 description characters. Never redefine unnamespaced standard meanings, create cycles, or combine conflicting parent sets. Preserve effective inherited meanings of all existing base inputs, outputs and accepts declarations; adding or changing a parent is a contract change even when port labels stay identical. New independent ports may declare new meanings.
194
+ In isolated computation code, StillMade.emit(outputName,value) collects declared final outputs without a return object; emit each name once and never combine it with a returned output object or job.pending result. StillMade.resolveRequirement(inputName) reads the fixed host-validated declared input snapshot as {resolved:true,requirement,value}, or {resolved:false,requirement} for an absent optional input. StillMade.getProjectContext() returns only context fields explicitly bound in manifest.inputs with their matching context.FIELD.read permissions; getProjectContext(field) reads one declared field and rejects undeclared fields. These snapshot readers do not run the resolver again, request Chat/user answers, read extra project data, expose credentials, write projects or grant permissions. StillMade.validate(value,schema) checks JSON values against SDK semantic schemas and returns a boolean; it does not replace host output validation, prove semantic meaning or authorize execution. These methods are not available in view.javascript.
195
+
196
+ ### Hosted text generation
197
+
198
+ For hosted text generation use {manifest,capability,tests}, runtime:"capability", entry:"src/capability.json", permissions.capabilities:["text.generate"]. Do not write fetch or async calls in JavaScript. The host owns model, provider, BYOK, credits and explicit run consent; never put those fields or keys in package source. One required primary input text|script|brief (structured values require text), one primary output text|script. capability:{schemaVersion:1,operation:"text.generate",prompt:{"$input":"promptPort"},system:"instructions",maxTokens:1200,output:"outputPort"}. Prompt max 32000 characters, system max 8000, maxTokens integer 1..4096. Include 1..3 tests with concrete input and expectations:{outputPort:{kind:"text" or "script",minLength:1,maxLength:12000,includes:["optional required literal"]}} instead of exact expected text. Hosted behavior tests require a separately confirmed model/payment quote; never claim they ran while authoring. Example: {"manifest":{"schemaVersion":1,"sdkVersion":"0.1.0","id":"example.script-draft","action":{"label":"Write script","running":"Writing script…"},"version":"1.0.0","name":"Draft a short script","description":"Turn a production brief into a short narration draft using an approved text-generation call.","kind":"task","runtime":"capability","entry":"src/capability.json","license":"MIT","provenance":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"inputs":{"prompt":{"type":"text","primary":true,"semantic":"production_brief","required":true,"description":"Production brief that guides the narration draft."}},"outputs":{"script":{"type":"script","primary":true,"semantic":"narration_script","description":"A concise narration script based on the supplied brief."}},"permissions":{"project":[],"capabilities":["text.generate"],"network":[],"filesystem":[],"secrets":[]},"ui":[{"control":"text","port":"prompt"}]},"capability":{"schemaVersion":1,"operation":"text.generate","prompt":{"$input":"prompt"},"system":"Write a concise narration script. Return only the script text.","maxTokens":1200,"output":"script"},"tests":[{"name":"A short opening narration","input":{"prompt":"Write two narration sentences about a quiet forest at sunrise."},"expectations":{"script":{"kind":"script","minLength":20,"maxLength":6000}}}]}.
199
+
200
+ ### Hosted media operations
201
+
202
+ For audio transcription, use runtime:"capability", operation:"audio.transcribe" and permissions.capabilities:["audio.transcribe"]. Declare one primary audio input with an asset control, one text language input defaulting to "auto", and one primary transcript output. Bind the two inputs through capability.audio and capability.language. Provider, model, payment and credentials belong to the host. Fixtures use transcript word-count bounds, never invented provider text. Shipped example: {"manifest":{"schemaVersion":1,"sdkVersion":"0.1.0","id":"example.transcribe-audio","action":{"label":"Transcribe audio","running":"Transcribing…"},"version":"1.0.0","name":"Transcribe audio","description":"Turn one persistent audio file into reviewed text with word timings.","kind":"task","runtime":"capability","entry":"src/capability.json","license":"MIT","provenance":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"inputs":{"audio":{"type":"audio","primary":true,"required":true,"semantic":"source_audio","description":"The recording to turn into text."},"language":{"type":"text","default":"auto","required":false,"semantic":"language_code","description":"auto, or a two-letter language code such as en."}},"outputs":{"transcript":{"type":"transcript","primary":true,"semantic":"timed_transcript","description":"The spoken words with their timings."}},"permissions":{"project":[],"capabilities":["audio.transcribe"],"network":[],"filesystem":[],"secrets":[]},"ui":[{"control":"asset","port":"audio"},{"control":"select","port":"language","options":["auto","en","es","fr","de","it","pt","ja","ko","zh"]}]},"capability":{"schemaVersion":1,"operation":"audio.transcribe","audio":{"$input":"audio"},"language":{"$input":"language"},"output":"transcript"},"tests":[{"name":"timed speech","input":{"audio":{"kind":"audio","assetId":"audio-1","versionId":"version-1","url":"/api/media/example/audio.mp3","mimeType":"audio/mpeg"},"language":"en"},"expectations":{"transcript":{"kind":"transcript","minWords":1,"maxWords":1000}}}]}.
203
+ For narration or speech synthesis, use runtime:"capability", operation:"audio.speech" and permissions.capabilities:["audio.speech"]. One required primary text or script input feeds capability.text via {$input:"portName"}; a script value contains schemaVersion:1 and text. One primary audio output is named by capability.output. Text must be at most 3800 characters. The host owns the voice, model, provider, billing and BYOK. Do not put those choices in package code. Tests use audio expectations (WAV, duration and byte bounds), never invented exact audio. Shipped example: {"manifest":{"schemaVersion":1,"sdkVersion":"0.1.0","id":"example.narrate-script","action":{"label":"Generate voiceover","running":"Generating voiceover…"},"version":"1.0.0","name":"Narrate a script","description":"Turn a short script into a reviewed speech recording using a host-selected voice.","kind":"task","runtime":"capability","entry":"src/capability.json","license":"MIT","provenance":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"inputs":{"script":{"type":"script","primary":true,"semantic":"narration_script","required":true,"description":"Narration script to render as speech."}},"outputs":{"audio":{"type":"audio","primary":true,"semantic":"speech_audio","description":"Speech audio generated from the narration script."}},"permissions":{"project":[],"capabilities":["audio.speech"],"network":[],"filesystem":[],"secrets":[]},"ui":[]},"capability":{"schemaVersion":1,"operation":"audio.speech","text":{"$input":"script"},"output":"audio"},"tests":[{"name":"Read a short welcome","input":{"script":{"schemaVersion":1,"text":"Welcome to the quiet forest."}},"expectations":{"audio":{"kind":"audio","format":"wav","minDuration":0.1,"maxDuration":30,"minBytes":46,"maxBytes":1500000}}}]}.
204
+ For image generation, use runtime:"capability", operation:"image.generate" and permissions.capabilities:["image.generate"]. One required primary text input feeds capability.prompt via {$input:"portName"}; one primary image output is named by capability.output. Prompt limit 32000 characters. The supported output is PNG 1024x1024, at most 12000000 bytes. Tests use image expectations, never fabricated generated URLs or pixels. Shipped example: {"manifest":{"schemaVersion":1,"sdkVersion":"0.1.0","id":"example.generate-image","action":{"label":"Generate image","running":"Generating image…"},"version":"1.0.0","name":"Generate an image","description":"Generate a square PNG from a short visual prompt using an approved host request.","kind":"task","runtime":"capability","entry":"src/capability.json","license":"MIT","provenance":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"inputs":{"prompt":{"type":"text","primary":true,"semantic":"image_prompt","required":true,"description":"Visual description of the image to generate."}},"outputs":{"image":{"type":"image","primary":true,"semantic":"generated_image","description":"The generated square PNG image."}},"permissions":{"project":[],"capabilities":["image.generate"],"network":[],"filesystem":[],"secrets":[]},"ui":[{"control":"text","port":"prompt"}]},"capability":{"schemaVersion":1,"operation":"image.generate","prompt":{"$input":"prompt"},"output":"image"},"tests":[{"name":"A simple forest illustration","input":{"prompt":"A simple illustration of a quiet forest with clear dark outlines and soft green colors."},"expectations":{"image":{"kind":"image","format":"png","width":1024,"height":1024,"minBytes":67,"maxBytes":12000000}}}]}.
205
+ For image or video analysis (what a photo shows, which photos show different angles, the words on a menu or sign), use runtime:"capability", operation:"media.analyze" and permissions.capabilities:["media.analyze"]: StillMade credits pay for it, while image.describe runs only on the person's own OpenRouter key, so never use it. Declare one primary image input, one image[] input of one to four images, or one video input (with optional start and end number inputs, bound through capability.range:{start:{"$input":"start"},end:{"$input":"end"}}), and one primary text or json output. capability:{schemaVersion:1,operation:"media.analyze",media:{"$input":"photo"},question:"what to look for, at most 2,000 characters",output:"notes"}; a text output gets each picture's summary, a json output {schemaVersion:1,items}. For a quick rating of photos (sorting or picking), add detail:"check": images only, one short answer each, one credit per call of up to four photos instead of four credits per picture. The host loads only owned media and controls provider, model, payment and approval. Tests give saved references and expect json that includes summary, or text length bounds, never invented live answers. Shipped example: {"manifest":{"schemaVersion":1,"sdkVersion":"0.1.0","id":"example.check-clip","action":{"label":"Check clip","running":"Checking clip…"},"version":"1.0.0","name":"Check a clip","description":"Answer a production question about a saved video clip, with timestamps.","kind":"task","runtime":"capability","entry":"src/capability.json","license":"MIT","provenance":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"inputs":{"clip":{"type":"video","primary":true,"semantic":"source_video","required":true,"description":"Saved video to check."},"start":{"type":"number","required":false,"min":0,"semantic":"range_start","description":"Optional start of the range to look at closely, in seconds."},"end":{"type":"number","required":false,"min":0,"semantic":"range_end","description":"Optional end of the range, in seconds (at most 12 seconds after the start)."}},"outputs":{"report":{"type":"json","primary":true,"semantic":"media_findings","description":"Summary, timeline, on-screen text and issues found.","schema":{"items":"json[]"}}},"permissions":{"project":[],"capabilities":["media.analyze"],"network":[],"filesystem":[],"secrets":[]},"ui":[{"control":"asset","port":"clip"},{"control":"number","port":"start"},{"control":"number","port":"end"}]},"capability":{"schemaVersion":1,"operation":"media.analyze","media":{"$input":"clip"},"question":"Is the product logo clearly visible, and when? List any on-screen text and visible defects.","range":{"start":{"$input":"start"},"end":{"$input":"end"}},"output":"report"},"tests":[{"name":"A saved clip","input":{"clip":{"kind":"video","assetId":"clip-1","versionId":"v1","url":"/api/media/owner/clip.mp4"}},"expectations":{"report":{"kind":"json","includes":["summary"]}}}]}.
206
+ For AI-directed editing, use runtime:"capability", operation:"timeline.propose" and permissions.capabilities:["timeline.propose"]. Declare one current timeline input, up to three additional typed context inputs, and one timeline-edit output. Bind every input exactly once through capability.context; declare context.timeline.read and timeline.propose. StillMade controls the provider, strict JSON validation, stale-before checks, preview and Apply. Tests declare timeline-edit command bounds, never fabricated provider results. Shipped example: {"manifest":{"schemaVersion":1,"sdkVersion":"0.1.0","id":"example.timeline-polish","action":{"label":"Suggest edit","running":"Suggesting edit…"},"version":"1.0.0","name":"Timeline polish","description":"Turn a timeline and editing direction into a bounded proposal that remains reviewable before Apply.","kind":"task","runtime":"capability","entry":"src/capability.json","license":"MIT","provenance":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"inputs":{"timeline":{"type":"timeline","context":"timeline","primary":true,"required":true,"semantic":"edit_timeline","description":"The cut to improve."},"direction":{"type":"text","default":"Polish this cut conservatively.","required":false,"semantic":"edit_direction","description":"How to change the cut, in a sentence."}},"outputs":{"edit":{"type":"timeline-edit","primary":true,"semantic":"timeline_edit","description":"The proposed changes to review before they apply."}},"permissions":{"project":["context.timeline.read","timeline.propose"],"capabilities":["timeline.propose"],"network":[],"filesystem":[],"secrets":[]},"ui":[{"control":"text","port":"direction"}]},"capability":{"schemaVersion":1,"operation":"timeline.propose","context":{"timeline":{"$input":"timeline"},"direction":{"$input":"direction"}},"instruction":"Use only supported timeline-edit commands, exact before snapshots, and the current activeTimelineId. Preserve media and locked items. Return strict JSON only.","maxTokens":2000,"output":"edit"},"tests":[{"name":"one restrained edit","input":{"timeline":{"schemaVersion":1,"activeTimelineId":"timeline-1","tracks":[{"id":"video"}],"clips":[{"id":"clip-1","trackId":"video","start":0,"duration":4}],"audioItems":[],"captions":[]},"direction":"Add a soft transition."},"expectations":{"edit":{"kind":"timeline-edit","minCommands":1,"maxCommands":10}}}]}.
207
+ For video generation use runtime:"capability", operation:"video.generate", permissions.capabilities:["video.generate"], one primary text input and one primary video output. capability.prompt references the input and capability.output names the output. Prompt limit 32000. Tests declare MP4 duration, dimensions and byte bounds; never fabricate video URLs. The enabled host selects its supported model, duration, resolution, payment and approved quote; packages cannot choose provider settings or dispatch jobs. Shipped example: {"manifest":{"schemaVersion":1,"id":"example.generate-video","action":{"label":"Generate video","running":"Generating video…"},"name":"Generate video","version":"1.0.0","sdkVersion":"0.1.0","description":"Turn a production prompt into a short video using an approved host provider.","kind":"task","runtime":"capability","entry":"src/capability.json","license":"MIT","provenance":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"inputs":{"prompt":{"type":"text","primary":true,"required":true,"semantic":"video_prompt","description":"What the clip shows: subject, action, setting and camera, in a sentence or two."}},"outputs":{"video":{"type":"video","primary":true,"semantic":"generated_video","description":"The generated clip."}},"permissions":{"project":[],"capabilities":["video.generate"],"network":[],"filesystem":[],"secrets":[]},"ui":[{"port":"prompt","control":"text"}]},"capability":{"schemaVersion":1,"operation":"video.generate","prompt":{"$input":"prompt"},"output":"video"},"tests":[{"name":"Produces a bounded MP4 sample","input":{"prompt":"A paper boat floating across a calm blue pond. Gentle camera movement."},"expectations":{"video":{"kind":"video","format":"mp4","minDuration":1,"maxDuration":60,"minBytes":32,"maxBytes":67108864,"minWidth":1,"maxWidth":3840,"minHeight":1,"maxHeight":3840}}}]}.
208
+ A capability package performs exactly one supported operation. A job that needs several (transcribe a recording, then find what is said in it; write words, then voice them), or code between two operations, is one module Block that calls each with stillmade.hosted.run (Module Blocks); never split one job into part Blocks, and never invent a combined capability runtime. Hosted image, speech and video source can be statically validated while authoring, but real fixture/sample generation requires the user's subsequent model/payment selection and quote confirmation. Do not claim those calls ran. Deterministic image transformations should remain sandboxed recipe or JavaScript code when supported; do not generate a new image as a substitute for processing an existing one.
209
+
210
+ ### Module Blocks
211
+
212
+ A job that needs several hosted operations, or code around one (reading a transcript's words and times, shaping a list), or media work on the person's device even with no hosted operation (rendering clips and photos into one video, joining pictures into one), is a module Block: {manifest,moduleSource,tests}, runtime:"module", moduleSource:{"main.js":"<the code>"}. StillMade names main.js by its digest and adds module. StillMade's in-app builder takes one file: give no other files, and main.js is plain JavaScript that imports nothing and uses no browser APIs (no fetch, TextEncoder, timers or WebCodecs). That single-file rule is the in-app builder's; a module Block built with the SDK may use timers, WebCodecs, bundled npm packages and .wasm files as MODULES.md describes. main.js: export default async function run(input, stillmade) returns the declared outputs. It calls each hosted operation with await stillmade.hosted.run(operation, request), listed in manifest.permissions.capabilities: text.generate, image.generate, video.generate, audio.speech, audio.music, audio.sfx, audio.transcribe, image.describe, media.analyze, web.fetch, web.research. Requests, and what each call returns (a test's mocks record exactly that value): text.generate {prompt, system, maxTokens} returns the text as a string, and with schema (a JSON schema object) returns that object, so ask for JSON with schema; one text.generate prompt holds at most 32,000 characters, so a longer file or transcript is read in parts (one call per part of about 25,000 characters, then one call that joins what they found), never cut off, and when the result is the whole text again (a cleaned transcript, a translation) each part's answer is kept and joined in order in main.js, since one answer holds at most 4,096 tokens; audio.transcribe {audio: the input's saved audio or video reference (a video's own sound is transcribed, so never extract it first), language:"auto"} returns {schemaVersion:1,text,words:[{text,start,end}]}; image.generate {prompt, references?: one to eight saved images the new picture keeps, such as the product, person or place in the person's photos} and audio.speech {text} return a saved image or audio reference (an audio reference always carries its measured duration); web.fetch {url, instruction} returns a string; media.analyze {media: one to four saved images or one saved video, question, range?: {start, end} in seconds of that video, at most 12 apart, to look closely at one part of it} returns {schemaVersion:1,items:[{kind,summary,observations,detectedText}]}, one item per image, and is how a Block looks at pictures (picking the photos that show different angles, reading a menu off a photo): StillMade credits pay for it; image.describe {image, instruction} returns a string but runs only on the person's own OpenRouter key, so never use it; web.research {topic} returns {summary,sources}. A file the person gives the Block (a text, CSV, Markdown or JSON file on a file input) is read with await stillmade.files.read(input.<port>), which returns its bytes; turn them into text in main.js as the worked file input below does, and give the test's input file an assetId whose text mocks.files records as {files:{"<assetId>":{text:"..."}}}. A Block that reads a file the person gives it is a module Block, never a text.generate capability package. A PDF (lesson slides, a lease, a contract) is read with await stillmade.media.transform({operation:"document.text",source:input.<port>}), which returns {text, pages}, all the words and pages, a list holding each page's words as one string, on the person's device and without credits (a text file reads the same way); a scanned PDF has no words to read. A call that fails throws. When the Block cannot make its result without that call, let it fail the run (StillMade shows its message), never catching it to return an empty result; catch it only around an optional part that has a fallback (a missing room photo, a skipped sample), and name what was left out in the result, as actions.run names its failures. Media work on the person's device (cut, join or resize video, decode or extract audio, thumbnails, joining images) is await stillmade.media.transform(request). Joining saved images into one picture (two photos side by side with Before and After labels, a grid or collage, a contact sheet, a storyboard sheet, a poster with a title) is {operation:"image.compose",width,height,background:"#ffffff",layers:[{source,x,y,width,height,fit:"cover" or "contain"}],text:[{text,x,y,size,color,background,align:"left" or "center" or "right",width}]}: a canvas up to 4096 pixels a side, positions in pixels worked out in main.js, layers drawn in order and text on top (y is the top of the line, background puts a box behind it, colours are #rrggbb or, see-through, #rrggbbaa such as #00000099 for a caption box, width wraps the words onto lines no wider than it: give a caption under a frame the frame's width so it never runs into the next one); it returns the saved image, and main.js never decodes pixels itself. Exporting is a transform too: {operation:"timeline.render",kind:"video" or "audio",aspect:"9:16",clips:[{source,start,duration,from,transition:{type:"Fade"},look:"Filmic",motion:"zoom-in"}],audio:[{source,start,duration}],captions:[{text,start,end,words:[{text,start,end}],highlight:"#ffd400",size:80}]} (motion, a slow camera move over a photo, is zoom-in, zoom-out, pan-left, pan-right, pan-up or pan-down; size is the caption height in pixels, 80 for a vertical video) renders the Block's own timeline with the Editor's transitions, looks and word-by-word captions under the person's export plan (start is a clip's place on that timeline, so each clip starts where the one before it ends, less the transition on the clip before it, which blends that clip into the next, and from is where it begins in its own file; overlapping clips are refused; a clip of another shape than the frame is cropped to fill it around its middle, or around focus:{x,y} on the clip, the point to keep in view as fractions 0 to 1 across the picture, such as where the speaker stands (focus.scale, 1 to 2.5 and instead of a motion, holds the clip that much closer on that point), or is fitted whole with fit:"contain"), so never ask for a separate export, captions or color Block; stillmade.log and stillmade.progress(fraction, label) report. One press of the Step's button approves each hosted operation once at the price shown beside it, and media.analyze for every picture or video the Step is given, across as many calls as that takes; any further call asks the person first. A module Block runs in the person's browser while StillMade is open, never on a schedule or event. Tests are {name,input,expected,mocks} and compare outputs, so every test is a case that runs to its outputs; an input the Block refuses (it throws a plain Error with a message for the person, such as a clip with no duration) is never a test. mocks records what each call answers, as {hosted:{"audio.transcribe":{schemaVersion:1,text:"...",words:[...]},"text.generate":{...}},transforms:{...}} (a list answers successive calls in order), and expected is the exact output main.js returns for those answers: exact values of each output's type, never capability bounds such as minDuration or maxBytes. In a module test, mock each media call with its complete saved reference (kind, assetId, versionId, url, mimeType and its measured fields: duration, bytes, sampleRate, channels for audio; width, height, bytes for images), its url an example file such as /api/media/example/clip.mp4 so the preview can play it, and repeat those references unchanged in expected; names or labels go in their own output. Worked outputs, each a passing test. A document output is the document object on a document port (not text): {"outputs":{"sheet":{"type":"document"}},"main.js":"export default async function run(input, stillmade) { const text = await stillmade.hosted.run('text.generate', {prompt: 'A Markdown care sheet for ' + input.plant, maxTokens: 800}); return {sheet: {schemaVersion: 1, format: 'markdown', text, title: input.plant + ' care'}}; }","test":{"name":"Fern","input":{"plant":"Fern"},"mocks":{"hosted":{"text.generate":"## Light\nBright, indirect light."}},"expected":{"sheet":{"schemaVersion":1,"format":"markdown","text":"## Light\nBright, indirect light.","title":"Fern care"}}}}. A file output is the reference await stillmade.files.write(data, {mediaType, name}) returns, handed on unchanged; mediaType is one of application/pdf, text/plain, text/csv, text/markdown, application/json, application/zip: {"outputs":{"report":{"type":"file"}},"main.js":"export default async function run(input, stillmade) { const csv = 'Room\\n' + input.rooms.join('\\n'); return {report: await stillmade.files.write(csv, {mediaType: 'text/csv', name: 'rooms.csv'})}; }","test":{"name":"Two rooms","input":{"rooms":["Kitchen","Hall"]},"expected":{"report":{"kind":"file","mimeType":"text/csv","name":"rooms.csv"}}}} (in a test, files.write answers with fields the host makes: assetId local-written-file-N, versionId, url /api/media/stillmade-mock/written-file-N.csv with the extension of its mediaType, and bytes; expected gives that reference as {kind:"file", mimeType, name} and it matches when those three match; passed on in an asset output as kind "asset", it is given as {kind:"asset", mimeType, name} and matches the same way). An audio[] output is the list of references audio.speech returned, unchanged; its mock is a list answering successive calls, and the labels go in a separate text[] output: {"outputs":{"tracks":{"type":"audio[]"},"names":{"type":"text[]"}},"main.js":"export default async function run(input, stillmade) { const tracks = []; for (const room of input.rooms) tracks.push(await stillmade.hosted.run('audio.speech', {text: 'Welcome to the ' + room + '.'})); return {tracks, names: input.rooms}; }","test":{"name":"Two rooms","input":{"rooms":["Kitchen","Hall"]},"mocks":{"hosted":{"audio.speech":[{"kind":"audio","assetId":"speech-1","versionId":"v1","url":"/api/media/example/kitchen.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1},{"kind":"audio","assetId":"speech-2","versionId":"v1","url":"/api/media/example/hall.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1}]}},"expected":{"tracks":[{"kind":"audio","assetId":"speech-1","versionId":"v1","url":"/api/media/example/kitchen.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1},{"kind":"audio","assetId":"speech-2","versionId":"v1","url":"/api/media/example/hall.wav","mimeType":"audio/wav","duration":2,"bytes":96044,"sampleRate":24000,"channels":1}],"names":["Kitchen","Hall"]}}}. A file input read as text: {"outputs":{"guide":{"type":"text"}},"main.js":"function textOf(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.length; i += 8192) binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 8192)); try { return decodeURIComponent(escape(binary)); } catch (error) { return binary; } }\nexport default async function run(input, stillmade) { const notes = textOf(await stillmade.files.read(input.notes)); const guide = await stillmade.hosted.run('text.generate', {prompt: 'Rewrite these notes as numbered steps, keeping every number as written:\\n' + notes, maxTokens: 1200}); return {guide}; }","test":{"name":"Two notes","input":{"notes":{"kind":"file","assetId":"notes-1","versionId":"v1","url":"/api/media/example/notes.txt","mimeType":"text/plain","name":"notes.txt","bytes":31}},"mocks":{"files":{"notes-1":{"text":"pull the probe\nwipe the tines"}},"hosted":{"text.generate":"1. Pull the probe.\n2. Wipe the tines."}},"expected":{"guide":"1. Pull the probe.\n2. Wipe the tines."}}}. Photos joined into one image, its transform mocked with the saved image it returns: {"outputs":{"sheet":{"type":"image"}},"main.js":"export default async function run(input, stillmade) { const photos = input.photos; if (!photos.length) throw new Error('Add at least one photo.'); const columns = Math.ceil(Math.sqrt(photos.length)), rows = Math.ceil(photos.length / columns), cell = 400, gap = 20, top = 160; const width = columns * cell + (columns + 1) * gap, height = top + rows * cell + (rows + 1) * gap; const layers = photos.map((source, i) => ({source, x: gap + (i % columns) * (cell + gap), y: top + gap + Math.floor(i / columns) * (cell + gap), width: cell, height: cell, fit: 'cover'})); const sheet = await stillmade.media.transform({operation: 'image.compose', width, height, background: '#ffffff', layers, text: [{text: input.title, x: width / 2, y: 50, size: 64, align: 'center', color: '#111111'}]}); return {sheet}; }","test":{"name":"Four photos","input":{"photos":[{"kind":"image","assetId":"photo-1","versionId":"v1","url":"/api/media/example/photo-1.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000},{"kind":"image","assetId":"photo-2","versionId":"v1","url":"/api/media/example/photo-2.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000},{"kind":"image","assetId":"photo-3","versionId":"v1","url":"/api/media/example/photo-3.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000},{"kind":"image","assetId":"photo-4","versionId":"v1","url":"/api/media/example/photo-4.jpg","mimeType":"image/jpeg","width":1600,"height":1200,"bytes":412000}],"title":"Saturday games"},"mocks":{"transforms":{"image.compose":{"kind":"image","assetId":"sheet-1","versionId":"v1","url":"/api/media/example/sheet.png","mimeType":"image/png","width":1300,"height":1440,"bytes":1820000}}},"expected":{"sheet":{"kind":"image","assetId":"sheet-1","versionId":"v1","url":"/api/media/example/sheet.png","mimeType":"image/png","width":1300,"height":1440,"bytes":1820000}}}}. StillMade runs every test before anyone sees the Block, without spending credits. With the SDK instead, the same code is module/main.js in a Block folder and the same tests are tests/fixtures.json; stillmade-block pack writes the file to import (MODULES.md).
213
+
214
+ ### ComfyUI workflows
215
+
216
+ *When:* StillMade's builder adds this section when the request names ComfyUI, a checkpoint, safetensors or KSampler, or refines a ComfyUI Block.
217
+
218
+ For an explicitly requested ComfyUI workflow use runtime:"comfyui", entry:"src/comfyui.json", permissions.capabilities:["comfyui.execute"], and package fields {manifest,comfyui,tests,view?}. Reviewed core contracts: {"CheckpointLoaderSimple":{"inputs":{"ckpt_name":"checkpoint"},"outputs":["MODEL","CLIP","VAE"],"outputNode":false},"CLIPTextEncode":{"inputs":{"text":{"type":"STRING","maxLength":4000},"clip":"CLIP"},"outputs":["CONDITIONING"],"outputNode":false},"EmptyLatentImage":{"inputs":{"width":{"type":"INT","min":64,"max":1024,"step":8},"height":{"type":"INT","min":64,"max":1024,"step":8},"batch_size":{"type":"INT","min":1,"max":1,"step":1}},"outputs":["LATENT"],"outputNode":false},"KSampler":{"inputs":{"model":"MODEL","seed":{"type":"INT","min":0,"max":4294967295,"step":1},"steps":{"type":"INT","min":1,"max":40,"step":1},"cfg":{"type":"FLOAT","min":0,"max":20},"sampler_name":["euler","euler_ancestral","dpmpp_2m"],"scheduler":["normal","karras","simple"],"positive":"CONDITIONING","negative":"CONDITIONING","latent_image":"LATENT","denoise":{"type":"FLOAT","min":0,"max":1}},"outputs":["LATENT"],"outputNode":false},"VAEDecode":{"inputs":{"samples":"LATENT","vae":"VAE"},"outputs":["IMAGE"],"outputNode":false},"VAEEncode":{"inputs":{"pixels":"IMAGE","vae":"VAE"},"outputs":["LATENT"],"outputNode":false},"LoadImage":{"inputs":{"image":"upload"},"outputs":["IMAGE","MASK"],"outputNode":false},"ImageInvert":{"inputs":{"image":"IMAGE"},"outputs":["IMAGE"],"outputNode":false},"ImageScale":{"inputs":{"image":"IMAGE","upscale_method":["nearest-exact","bilinear","area","bicubic","lanczos"],"width":"INT","height":"INT","crop":["disabled","center"]},"outputs":["IMAGE"],"outputNode":false},"PreviewImage":{"inputs":{"images":"IMAGE"},"outputs":["IMAGE"],"outputNode":true}}. Object scalar descriptors are internal validation limits; prompt.inputs contains actual scalar values or [nodeId,outputIndex] connections, not those descriptors. Text/integer/number/image ports bind via {node,input,encoding:"scalar" or "uploaded-image"}. Every LoadImage filename is an empty placeholder bound to an image input. Output is PreviewImage images[0], bound by {node,collection:"images",index:0}. No custom nodes, Python, URLs or downloads. One KSampler, one installed safetensors checkpoint basename, batch 1, up to 40 steps, 1024 pixels per generation side, four megapixels across working image/latent nodes. VAEEncode inputs require sides >=64 and multiples of 8; unchanged original media stays intact. Do not invent installed model names: preserve the caller's supplied name, or return unsupported asking for an installed checkpoint name. Example shape only (replace its placeholder with the user's exact installed checkpoint): {"manifest":{"schemaVersion":1,"id":"example.comfy-text-to-image","name":"ComfyUI image generation","version":"1.0.1","sdkVersion":"0.1.0","description":"Generate an image with an installed checkpoint on your reviewed ComfyUI connection.","kind":"task","runtime":"comfyui","entry":"src/comfyui.json","license":"MIT","provenance":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"inputs":{"prompt":{"type":"text","primary":true,"semantic":"image_prompt","required":true,"description":"Visual description used to generate the image."}},"outputs":{"image":{"type":"image","primary":true,"semantic":"generated_image","description":"The image generated from the prompt."}},"permissions":{"project":[],"capabilities":["comfyui.execute"]},"ui":[{"control":"text","port":"prompt"}]},"comfyui":{"schemaVersion":1,"prompt":{"1":{"class_type":"CheckpointLoaderSimple","inputs":{"ckpt_name":"your-model.safetensors"}},"2":{"class_type":"CLIPTextEncode","inputs":{"text":"","clip":["1",1]}},"3":{"class_type":"CLIPTextEncode","inputs":{"text":"","clip":["1",1]}},"4":{"class_type":"EmptyLatentImage","inputs":{"width":512,"height":512,"batch_size":1}},"5":{"class_type":"KSampler","inputs":{"model":["1",0],"seed":1,"steps":20,"cfg":7,"sampler_name":"euler","scheduler":"normal","positive":["2",0],"negative":["3",0],"latent_image":["4",0],"denoise":1}},"6":{"class_type":"VAEDecode","inputs":{"samples":["5",0],"vae":["1",2]}},"7":{"class_type":"PreviewImage","inputs":{"images":["6",0]}}},"inputs":{"prompt":{"node":"2","input":"text","encoding":"scalar"}},"outputs":{"image":{"node":"7","collection":"images","index":0}}},"tests":[{"name":"Generate one bounded image","input":{"prompt":"A small wooden boat beside a quiet lake."},"expectations":{"image":{"kind":"image","count":1,"width":512,"height":512,"maxBytes":16777216}}}]}. Samples use concrete inputs and expectations:{image:{kind:"image",count:1,width:512,height:512,maxBytes:16777216}}; never invent generated pixels or URLs. Authoring only checks source; fixtures/sample require the account's separately confirmed connection. Never claim backend availability, matching model hashes, GPU readiness or executed tests.
219
+
220
+ ### Project context
221
+
222
+ Project context is provided through declared input ports, not a global project API. Bind an input with context:"field" and its exact type from {"script":"script","transcript":"transcript","shots":"shot[]","shotPlan":"shot-plan","scenes":"scene[]","characters":"character[]","locations":"location[]","styles":"style[]","assets":"asset[]","files":"file[]","generations":"metadata[]","versions":"metadata[]","timeline":"timeline","editor":"timeline","canvas":"pipeline","blueprint":"bible","panels":"panel-document","board":"board-document","animation":"scene-document","metadata":"metadata"}; declare permissions.project:["context.field.read"] for each bound field (replace field with the actual name). The host supplies the user-selected scope and only authorized context; do not invent context fetch methods. Include concrete context inputs in fixtures. Read-only snapshots cannot be mutated to save project changes. Reuse existing Canvas characters, locations and styles for visual consistency. The SDK also supports optional Step-scoped memory and timeline-edit outputs ; request the corresponding contract only when the capability needs it.
223
+
224
+ ### Workspace proposals
225
+
226
+ *When:* StillMade's builder adds this section when the request is about a workspace, animation, canvas, blueprint, panels or board, or refines a Block with a workspace-edit output.
227
+
228
+ To propose changes to an existing blueprint, panels, board, canvas, or animation workspace, bind its matching context input ({"script":"script","transcript":"transcript","shots":"shot[]","shotPlan":"shot-plan","scenes":"scene[]","characters":"character[]","locations":"location[]","styles":"style[]","assets":"asset[]","files":"file[]","generations":"metadata[]","versions":"metadata[]","timeline":"timeline","editor":"timeline","canvas":"pipeline","blueprint":"bible","panels":"panel-document","board":"board-document","animation":"scene-document","metadata":"metadata"}). Declare context.FIELD.read and workspace.FIELD.propose in permissions.project. Return a primary workspace-edit output: {schemaVersion:1,title,field:"blueprint"|"panels"|"board"|"canvas"|"animation",before:theCompleteUnmodifiedInputDocument,after:theCompleteModifiedCopy}. Preserve unrelated fields and existing identities; Panels and Board retain strip items with panelId, Board also requires each item's type. Fixtures need concrete full before/after documents. These are reviewed proposals; the host checks current source and shared edit permission, then saves using collaboration and undo. Never claim runtime execution already saved it. Canvas proposals retain executable Step instances, host bindings, and connections touching those instances; change Steps through the Project Type builder. Only position, size, title and label may change on programmable nodes, with their host-owned execution state retained. Canvas viewport is local and unchanged. Animation proposals preserve shotId, start/end timing and voiceoverText for every scene record; modify its scene and versions. The collection and narration spine belong to Shots. Starting Chat writes are not supported by this proposal contract.
229
+
230
+ ### Step memory
231
+
232
+ *When:* StillMade's builder adds this section when the request needs memory, counting or persistence, or refines a stateful Block.
233
+
234
+ For memory use runtime:"javascript", manifest.state:{scope:"step",version:1,initial:{...}}, permissions.project including "state.step". Code receives mutable plain-object state separately from input. Mutate state and return only declared output bindings; never return a state envelope as an undeclared output. Each test needs expectedState:{version:1,value:{...}}; a test may supply state:{version:1,value:{...}} to verify continuation. Memory is bounded to 64 KiB and scoped to a placement. The host saves it only with accepted successful output; previews use disposable memory. No localStorage, files, global or account-shared state. Interactive selections support successive memory; unattended future-media execution is not supported for stateful Blocks. Preserve the state schema/version during refinement unless the user requests a schema change. For an explicit upgrade, the new version may declare manifest.state.migrations:[{fromVersion:1,operations:[{op:"rename",from:"oldField",to:"newField"},{op:"default",key:"labels",value:[]}]}], targeting its enclosing state.version (for example 2). Use only ordered top-level rename/default/remove operations; remove uses {op:"remove",key:"field"}. Renames require an existing source and absent destination, defaults never overwrite, removals require an existing field. At most 16 distinct older fromVersion routes, 64 operations each, and 64 KiB combined declarations and intermediate state. No chains, downgrade, arbitrary code or automatic application. Bump the Block version when source or migrations change, retain unrelated state, and never invent an old field layout. Owners preview and explicitly apply migrations to exact archived source-bound state through the project host; authoring does not mutate existing projects. New execution fixtures use the target state version. Exact example: {"manifest":{"schemaVersion":1,"sdkVersion":"0.1.0","id":"example.numbered-takes","action":{"label":"Number takes","running":"Numbering takes…"},"version":"1.0.0","name":"Numbered takes","description":"Label successive creative takes with an independent counter for each production placement.","kind":"task","runtime":"javascript","license":"MIT","provenance":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"inputs":{"text":{"type":"text","primary":true,"semantic":"draft_text","required":true,"description":"Creative take text to label with the next number."}},"outputs":{"text":{"type":"text","primary":true,"semantic":"numbered_take","description":"The supplied take prefixed with its saved sequence number."}},"permissions":{"project":["state.step"]},"state":{"scope":"step","version":1,"initial":{"count":0}},"ui":[{"port":"text","control":"text"}]},"code":"state.count += 1; return {text: \"Take \" + state.count + \": \" + input.text.trim()};","tests":[{"name":"First take","input":{"text":" Opening scene "},"expected":{"text":"Take 1: Opening scene"},"expectedState":{"version":1,"value":{"count":1}}},{"name":"Continues saved numbering","input":{"text":"Alternate ending"},"state":{"version":1,"value":{"count":4}},"expected":{"text":"Take 5: Alternate ending"},"expectedState":{"version":1,"value":{"count":5}}}]}.
235
+
236
+ ### Editor timeline proposals
237
+
238
+ *When:* StillMade's builder adds this section when the request is about the Editor, timeline, clips, captions, trimming, splitting or volume, or refines a Block with a timeline-edit output.
239
+
240
+ For Editor changes return a primary output {type:"timeline-edit"} and declare "timeline.propose" plus "context.timeline.read". Bind timeline input {type:"timeline",context:"timeline"}. A proposal is {schemaVersion:1,title,timelineId:input.timeline.activeTimelineId,commands:[...]}; 1..100 commands, exactly one per target. Command shape is exactly {collection,operation,before,values}. For collections clips or audioItems, before is the complete unmodified source item; operations and exact values are move:{start}, trim:{trimStart,duration}, volume:{volume} (0..200), mute:{muted}, remove:{}, split:{at,newId}. Split at is an absolute timeline time strictly inside the item; newId must be unused. For captions: caption.add has before:null and values:{id,trackId,text,start,end}; caption.update has the complete original before and values:{text,start,end}; remove has complete original before and values:{}. Caption text must be nonempty and at most 5000 characters, end greater than start. Use an unlocked existing caption track or default trackId:"captions" if absent. IDs must be unique across all timeline lanes. Preserve source snapshots, locked tracks, bounds and existing item identities. No arbitrary media, effect insertion, track mutation or direct project writes. The user reviews before/after and applies proposals through Editor undo; do not claim a proposal has already changed the project. Exact examples: [{"manifest":{"schemaVersion":1,"sdkVersion":"0.1.0","id":"example.quieter-clips","version":"1.0.0","name":"Quieter clips","description":"Propose a lower volume for existing video clips, then review and apply the changes in the Editor.","kind":"editor-extension","runtime":"javascript","license":"MIT","provenance":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"inputs":{"timeline":{"type":"timeline","primary":true,"context":"timeline","semantic":"timeline","required":true,"description":"Current Editor timeline containing the clips to adjust."},"volume":{"type":"number","min":0,"max":100,"default":50,"semantic":"clip_volume_percent","required":false,"description":"Target volume percentage for each video clip."}},"outputs":{"edit":{"type":"timeline-edit","primary":true,"semantic":"timeline_edit","description":"A reviewable proposal that changes clip volume."}},"permissions":{"project":["context.timeline.read","timeline.propose"]},"ui":[{"port":"volume","control":"slider"}]},"code":"if (!input.timeline.clips.length) throw new Error(\"Add clips in the Editor first\"); return {edit:{schemaVersion:1,title:\"Lower clip volume\",timelineId:input.timeline.activeTimelineId,commands:input.timeline.clips.slice(0,100).map(clip=>({collection:\"clips\",operation:\"volume\",before:clip,values:{volume:input.volume}}))}};","tests":[{"name":"Propose without changing the source","input":{"timeline":{"schemaVersion":1,"activeTimelineId":"timeline-01","clips":[{"id":"clip-1","trackId":"video","name":"Opening","start":0,"duration":3,"sourceDuration":3,"volume":100}]},"volume":50},"expected":{"edit":{"schemaVersion":1,"title":"Lower clip volume","timelineId":"timeline-01","commands":[{"collection":"clips","operation":"volume","before":{"id":"clip-1","trackId":"video","name":"Opening","start":0,"duration":3,"sourceDuration":3,"volume":100},"values":{"volume":50}}]}}}]},{"manifest":{"schemaVersion":1,"sdkVersion":"0.1.0","id":"example.caption-from-text","version":"1.0.0","name":"Caption from text","description":"Turn a short line of text into a timed caption proposal for the Editor.","kind":"editor-extension","runtime":"javascript","license":"MIT","provenance":{"notice":"MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"},"inputs":{"timeline":{"type":"timeline","context":"timeline","semantic":"timeline","required":true,"description":"Current Editor timeline that will receive the caption."},"text":{"type":"text","primary":true,"semantic":"caption_text","required":true,"description":"Text to place in the new caption."},"start":{"type":"number","min":0,"max":86300,"default":0,"semantic":"start_time_seconds","required":false,"description":"Timeline time in seconds at which the caption begins."},"duration":{"type":"number","min":0.05,"max":100,"default":3,"semantic":"duration_seconds","required":false,"description":"Number of seconds the caption remains visible."}},"outputs":{"edit":{"type":"timeline-edit","primary":true,"semantic":"timeline_edit","description":"A reviewable proposal that adds the timed caption."}},"permissions":{"project":["context.timeline.read","timeline.propose"]},"ui":[{"port":"text","control":"text"},{"port":"start","control":"number"},{"port":"duration","control":"number"}]},"code":"const text=input.text.trim();if(!text)throw new Error(\"Enter caption text\");const ids=new Set([...(input.timeline.clips||[]),...(input.timeline.audioItems||[]),...(input.timeline.captions||[]),...(input.timeline.textOverlays||[]),...(input.timeline.stickers||[]),...(input.timeline.effects||[])].map(item=>item.id));let n=1;while(ids.has(\"block-caption-\"+n))n++;const track=(input.timeline.tracks||[]).find(track=>track.type===\"caption\"&&!track.locked);return {edit:{schemaVersion:1,title:\"Add caption\",timelineId:input.timeline.activeTimelineId,commands:[{collection:\"captions\",operation:\"caption.add\",before:null,values:{id:\"block-caption-\"+n,trackId:track?track.id:\"captions\",text,start:input.start,end:input.start+input.duration}}]}};","tests":[{"name":"Create a timed caption","input":{"timeline":{"schemaVersion":1,"activeTimelineId":"timeline-01","clips":[],"captions":[],"tracks":[]},"text":"A new beginning","start":0,"duration":3},"expected":{"edit":{"schemaVersion":1,"title":"Add caption","timelineId":"timeline-01","commands":[{"collection":"captions","operation":"caption.add","before":null,"values":{"id":"block-caption-1","trackId":"captions","text":"A new beginning","start":0,"end":3}}]}}}]}].
241
+
242
+ ### Descriptions
243
+
244
+ Write the description in third-grade language: one short sentence, ideally 8–14 words and never more than 20. Use common words and say what the person can make or do. Start with an action. Leave out technical terms such as typed ports, scoped context, artifacts, pipeline, runtime, and handoff. Do not list every feature, repeat the name, add marketing claims, or say this Block lets you. Keep essential limits and describe only what the code actually does. For example: Cut clips, add sound and text, then save your video. Apply these No AI Slop editing rules to the description prose only; keep the required response format, code, and identifiers intact. Write for a person choosing a Block or Project Type. Use familiar words, direct verbs, and details supported by the saved code. Preserve useful limits and clear existing sentences. Use “version” or “saved version” instead of “release”; use “Public”, “Private” and “Draft” for visibility and readiness. Call release notes “What changed”. Check each sentence: could it describe almost any app? If so, replace it with a specific supported action or remove it. Never invent examples, numbers, features, reviews, or results to sound more specific. Cut hype, vague benefits, dramatic reveals, fake expert claims, rhetorical questions, introductions that delay the point, and closing lines that repeat the paragraph. Avoid phrases such as game changer, cutting-edge, unlock your potential, seamless, powerful solution, leverage, empower, streamline, and elevate. Say what the person can do. State the useful fact directly instead of using not-X-but-Y contrasts. Use complete sentences, with no decorative dashes, emoji, or emphasis. Keep names consistent; do not cycle through synonyms for a Block. Avoid repeated sentence shapes and forcing every listing into an identical template. Use search terms only where they describe the real task. Do not repeat keywords or stretch the copy to hit a word or sentence count. Before returning the copy, reread it for factual accuracy, simple language, specific detail, natural rhythm, and filler. Fix any failures silently and return only the requested output.
245
+
246
+ ### Compact interface
247
+
248
+ 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, the action in normal flow (never pinned), then the result's place under it, so put `sm-stage` after `sm-actions`. 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.
249
+
250
+ ### Generated controls first
251
+
252
+ *When:* StillMade's builder adds this section when the person did not ask for a custom interface.
253
+
254
+ Optional custom view interfaces are supported, but generated manifest.ui controls are sufficient unless the user asks for a custom interface. Shape the manifest so the generated controls stay compact: choices use control:"select" with a few short string options (shown as one row of tiles); bounded numbers use control:"slider" with min and max; on/off is a boolean; one-line text says "up to N characters" (160 or fewer) in its description so it sits beside its label, and multi-line text is kept for prompts and briefs. Inputs beyond the few that matter go in a uiLayout group with collapsed:true; they move there, never get deleted.
255
+
256
+ ### Custom interfaces
257
+
258
+ *When:* StillMade's builder adds this section when the person asks for an interface, preview or controls, or refines a Block that has one.
259
+
260
+ 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. 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 sits beside the action it describes (`sm-status`), in a few words. 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, the action in normal flow (never pinned), then the result's place under it, so put `sm-stage` after `sm-actions`. 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. 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.
261
+ 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`:
262
+
263
+ - **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:`.
264
+ - **No loops and no function declarations:** view JavaScript may not contain `import()`, `import`, `with`, `this`, `import.meta`/`new.target`, `while`, `do`/`while`, `for`, `for…in`, `for…of`, `function` declarations. Iterate with array `map`/`forEach`/`filter`/`reduce` and write helpers as `const name = (...) => ...`.
265
+ - **Never use these names in view JavaScript, not even for your own variables:** `eval`, `Function`, `AsyncFunction`, `GeneratorFunction`, `require`, `importScripts`, `process`, `globalThis`, `window`, `self`, `top`, `parent`, `opener`, `frames`, `location`, `navigation`, `history`, `navigator`, `localStorage`, `sessionStorage`, `indexedDB`, `caches`, `cookieStore`, `fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource`, `Worker`, `SharedWorker`, `ServiceWorker`, `WebAssembly`, `Deno`, `Bun`, `Reflect`, `Proxy`, `DOMParser`, `MutationObserver`, `postMessage`, `open`, `close`, `print`, `alert`, `confirm`, `prompt`, `setInterval`, `setTimeout`, `queueMicrotask`, `arguments`, `constructor`, `__proto__`, `prototype`, `__lookupGetter__`, `__lookupSetter__`, `__defineGetter__`, `__defineSetter__`, `getPrototypeOf`, `setPrototypeOf`, `getOwnPropertyDescriptor`, `getOwnPropertyDescriptors`, `getOwnPropertyNames`, `getOwnPropertySymbols`, `defineProperty`, `defineProperties`, `defaultView`, `ownerGlobal`, `contentWindow`, `contentDocument`, `cookie`, `domain`, `documentURI`, `URL`, `baseURI`, `referrer`, `write`, `writeln`, `innerHTML`, `outerHTML`, `insertAdjacentHTML`, `createElement`, `createElementNS`, `createContextualFragment`, `createRange`, `setAttribute`, `setAttributeNS`, `attributes`, `attributeStyleMap`, `adoptNode`, `importNode`, `execCommand`, `replaceChildren`, `setHTML`, `setHTMLUnsafe`, `parseHTML`, `parseHTMLUnsafe`, `showSaveFilePicker`, `showDirectoryPicker`, `saveAs`, `FileSystemWritableFileStream`, `createWritable`, `msSaveBlob`, `msSaveOrOpenBlob`, `download`.
266
+ - **Never use these property names:** `assign`, `values`, `entries`, `fromEntries` (so no `Object.assign`, `Object.values`, `Object.entries`, `Object.fromEntries`; use arrays and `map`/`forEach`).
267
+ - **These words may not appear anywhere in view JavaScript, even in strings or comments:** `style`, `cssText`, `setProperty`, `removeProperty`, `insertRule`, `deleteRule`, `styleSheets`, `adoptedStyleSheets`, `animate`. Change appearance with CSS classes and `hidden`; put colors in the stylesheet.
268
+ - **Property access with brackets needs a literal key:** `items[0]` and `record["title"]` are accepted; `items[index]` is rejected. Iterate with `map`/`forEach` or call `items.at(index)`.
269
+ - **No named recursive functions** and no listeners on the global frame; attach events to declared controls.
270
+ - **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.
271
+ - **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}`.
272
+ - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 30 runs and 60 image previews per minute.
273
+ ## Shared project interfaces
274
+ Every meaningful edit and generated result must be visible to collaborators.
275
+ Prefer ordinary manifest.ui controls and declared outputs when those express
276
+ the interaction. Repository-local DOM state is not automatically shared.
277
+ For a custom view, observe StillMade.onShared(shared => ...) to render saved
278
+ outputs (including another participant's result), interface state and canEdit.
279
+ For an ordinary static control, add a unique id and
280
+ data-stillmade-share="fieldName"; the host owns its complete synchronization
281
+ adapter. Use StillMade.bindShared(fieldName, elementId) only for a control created
282
+ or replaced dynamically. Account for every ordinary static control; mark a truly
283
+ device-only playback or filter control with data-stillmade-local.
284
+ Use StillMade.setSharedDefaults for input-derived defaults without replacing a
285
+ dirty draft. Do not overwrite bound control values during remote rendering or
286
+ replace a bound input during a gesture. For structured interface values, use
287
+ bounded StillMade.updateShared patches with stable named fields; observe them
288
+ through onShared instead of keeping the only copy in a private variable.
289
+ For nested objects, updateShared(values, exactBasePreconditions, {merge:true})
290
+ can merge independent fields and arrays with stable string item IDs. Always use
291
+ the actual base used to construct the edit. Overlapping changes still conflict;
292
+ this does not merge simultaneous text typing or expand the 384 KiB state limit.
293
+ Requests including before-values are limited to 800,000 UTF-8 bytes; queued and
294
+ in-flight requests share a 2 MiB budget. Preserve drafts when a limit is reached.
295
+ Give dynamic rows and controls stable IDs derived from item identity, not array
296
+ position. StillMade.render preserves matching nodes, focus and selection across
297
+ updates; it does not merge conflicting values or preserve an unshared draft.
298
+ Never dispatch a run, click or paid integration in response to a remote update.
299
+ Respect read-only access, preserve conflicting drafts, and surface write errors.
300
+ Only explicit user actions may run this Block. Shared acknowledgements are
301
+ locally queued, not proof of a durable server save. Preview localOnly state is
302
+ not proof of multiplayer operation. Test two interfaces exchanging actual edits
303
+ and saved outputs, permission loss, reconnect and conflicts before claiming
304
+ collaboration support. Never claim arbitrary imported JavaScript state syncs
305
+ merely because the package passed its computation fixtures.
306
+ Example view for a package taking and returning text: {"html":"<main class=\"sm-block\"><div class=\"sm-field\"><label for=\"source\">Draft</label><textarea id=\"source\"></textarea></div><div class=\"sm-actions\"><button class=\"sm-primary\" id=\"run\" data-stillmade-action=\"run\">Clean text</button><p class=\"sm-status\" id=\"status\" role=\"status\"></p></div><output class=\"sm-stage\" id=\"result\" aria-live=\"polite\"></output></main>","css":"#result{place-items:start;min-height:120px;white-space:pre-wrap;overflow-wrap:anywhere}","javascript":"const source=document.getElementById('source'),button=document.getElementById('run'),result=document.getElementById('result'),status=document.getElementById('status');\nlet busy=false;\nStillMade.bindShared('draft','source');\nStillMade.onInput(input=>{StillMade.setSharedDefaults({draft:input.text||''});});\nStillMade.onShared(shared=>{button.disabled=busy||!shared.canEdit;result.textContent=shared.outputs.text||'';});\nbutton.addEventListener('click',async()=>{\n if(busy||!StillMade.getShared().canEdit)return;\n busy=true;button.disabled=true;status.dataset.state='loading';status.textContent='Cleaning';\n try{await StillMade.run({text:source.value});status.dataset.state='success';status.textContent='Ready';}\n catch(error){status.dataset.state='error';status.textContent=error.message||'Could not clean this draft.';}\n finally{busy=false;button.disabled=!StillMade.getShared().canEdit;}\n});"}.
307
+
308
+ <!-- authoring-guide:end -->
309
+
310
+ ## Build new Blocks directly with the SDK
311
+
312
+ When the user supplies an idea (for example, a photo editor) and this guide, build
313
+ an original StillMade Block from scratch. A GitHub repository is not required.
314
+ Create the SDK folder as the working product from the first iteration:
315
+ `stillmade.block.json`, `src/run.js` (`module/main.js` for a module Block, or the
316
+ documented declarative runtime file), `tests/fixtures.json`, and optional
317
+ `src/view.html`, `src/view.css`, `src/view.js`.
318
+ Implement the task through typed SDK inputs/outputs and the `StillMade` UI bridge.
319
+ The exact finished folder must import with **Choose folder** without source edits
320
+ or adaptation. Keep development tools and extracted SDK examples outside it.
321
+
322
+ The SDK is one download, `/block-sdk/stillmade-sdk-0.1.0.zip` on the StillMade
323
+ site, with the same files as `npm install --save-dev stillmade-sdk`.
324
+ `stillmade-block` in every StillMade guide is its command line: `npx stillmade-block`
325
+ after the npm install, or `node <sdk>/packages/block-cli/cli.js` where `<sdk>` is
326
+ the folder you extracted the download into. `stillmade-block create my-block
327
+ javascript` scaffolds an isolated-JavaScript Block (`create --list` shows a
328
+ template for every runtime and hosted operation). Edit that source,
329
+ then run `validate my-block` and `test my-block` with the same CLI; add `--json`
330
+ for machine-readable results. Blocks that use hosted operations, files, network,
331
+ connections or storage test offline with `test my-block --mock` (recorded
332
+ stand-ins, or answers recorded in `tests/mocks.json`); a stand-in pass is never a
333
+ live verification. `stillmade-block dev my-block` shows the Block's real interface
334
+ in a browser (see "Start a local preview early, then iterate").
335
+
336
+ Before calling the Block ready, run `stillmade-block gate my-block` on the Block's
337
+ own folder: it runs StillMade's readiness check, including the admission
338
+ requirements (explicit semantic/required declarations) that `validate` does not
339
+ cover. When the Block sits outside the extracted SDK folder, always name its
340
+ folder: `gate .` in a folder that also holds the SDK checks the SDK's examples too.
341
+ Then `stillmade-block pack my-block` runs the tests again and writes the one file
342
+ to import, named by the Block's runtime:
343
+
344
+ | Runtime | `pack my-block` writes |
345
+ | --- | --- |
346
+ | `module`, or a frame view with files in `src/view-files/` | `<id>-<version>.stillmade-module.json` |
347
+ | `recipe`, `javascript`, `capability`, `comfyui` | `<id>-<version>.stillmade.json` |
348
+
349
+ Deliver that file (Create, then Create new, New Block, Import Block in StillMade) or the Block folder.
350
+
351
+ A test or example that needs a real photo, recording, clip or file can name one of
352
+ StillMade's example files instead of carrying its own: `/api/media/example/photo-1.jpg`
353
+ to `photo-4.jpg` (1600×1200, `photo-2.jpg` 1200×1600), `sheet.png` (1300×1440),
354
+ `room.png` (1200×900), `kitchen.wav`, `hall.wav` and `click-120.wav` (2 s, 24 kHz mono),
355
+ `audio.mp3` (2 s), `clip.mp4` (640×360, 2 s), `notes.txt`, `sales.csv` and `lesson.pdf`
356
+ (two pages of words a PDF Block reads with `document.text`), for example
357
+ `{"kind":"image","assetId":"photo-1","versionId":"v1","url":"/api/media/example/photo-1.jpg","mimeType":"image/jpeg","width":1600,"height":1200}`.
358
+ StillMade serves them, its preview and phone and browser check open them, and `test`
359
+ reads them when the test gives no mock for them. A Block whose interface needs a photo
360
+ before its controls work needs such an example: the phone and browser check opens the
361
+ interface with the first test's input and fails when it shows no usable control.
362
+ Never add `--original` to a module Block: original `.stillmade-block` archives take
363
+ only recipe, JavaScript, capability and ComfyUI source (PORTABLE.md), and a new
364
+ Block does not need one. An already-portable folder (with `portable.json`) uses
365
+ ordinary `pack`, which writes its `.stillmade-block`; keep its existing evidence aligned with its source. Include normal and edge-case fixtures and complete applicable license notices.
366
+ The folder remains a supported direct import; packaging is an alternate delivery
367
+ format, not a conversion to another implementation. Portable folders may use the
368
+ same split interface files or `src/view.json`, never both.
369
+
370
+ For a photo editor that keeps the photo's full size, build a `module` Block with a
371
+ frame view: `module/main.js` reads the photo with `stillmade.files.read`, edits its
372
+ pixels with `OffscreenCanvas` in the module's Worker and writes the result with
373
+ `stillmade.files.write`. The `javascript` runtime's `src/run.js` takes images only
374
+ as RGBA arrays of at most one megapixel, so it suits small images only. "Full size"
375
+ is the size of the photo the Block receives: with the account's media setting on (its
376
+ default), a photo over 2048 px or 4 MiB arrives as a 2048 px working copy. Either way,
377
+ return typed image outputs and show images through `StillMade.previewImage`. The UI
378
+ calls `StillMade.run` and shares edits using the documented shared-interface contract.
379
+ Observe saved image outputs through `onShared`; display them with
380
+ `StillMade.previewImage` just as you display run results. In saved Steps, shared
381
+ write promises wait for an acknowledged save before the run flush barrier proceeds.
382
+ Use host-controlled output delivery. Do not first build an unrelated React/Node
383
+ website and leave a future adaptation task to the user. When the requested feature
384
+ requires an unsupported runtime or host service, identify that specific SDK gap
385
+ before claiming the finished Block can import or run.
386
+
387
+ The repository-inspection and adaptation instructions below apply only when the
388
+ user actually supplies existing source to reuse. For an original Block, follow
389
+ the runtime and interface contracts directly; do not invent repository evidence.
390
+
391
+ ## Shared interfaces are required
392
+
393
+ <!-- shared-view:start (generated by npm run build:sdk; do not edit) -->
394
+
395
+ ### Shared project interfaces
396
+ Every meaningful edit and generated result must be visible to collaborators.
397
+ Prefer ordinary manifest.ui controls and declared outputs when those express
398
+ the interaction. Repository-local DOM state is not automatically shared.
399
+ For a custom view, observe StillMade.onShared(shared => ...) to render saved
400
+ outputs (including another participant's result), interface state and canEdit.
401
+ For an ordinary static control, add a unique id and
402
+ data-stillmade-share="fieldName"; the host owns its complete synchronization
403
+ adapter. Use StillMade.bindShared(fieldName, elementId) only for a control created
404
+ or replaced dynamically. Account for every ordinary static control; mark a truly
405
+ device-only playback or filter control with data-stillmade-local.
406
+ Use StillMade.setSharedDefaults for input-derived defaults without replacing a
407
+ dirty draft. Do not overwrite bound control values during remote rendering or
408
+ replace a bound input during a gesture. For structured interface values, use
409
+ bounded StillMade.updateShared patches with stable named fields; observe them
410
+ through onShared instead of keeping the only copy in a private variable.
411
+ For nested objects, updateShared(values, exactBasePreconditions, {merge:true})
412
+ can merge independent fields and arrays with stable string item IDs. Always use
413
+ the actual base used to construct the edit. Overlapping changes still conflict;
414
+ this does not merge simultaneous text typing or expand the 384 KiB state limit.
415
+ Requests including before-values are limited to 800,000 UTF-8 bytes; queued and
416
+ in-flight requests share a 2 MiB budget. Preserve drafts when a limit is reached.
417
+ Give dynamic rows and controls stable IDs derived from item identity, not array
418
+ position. StillMade.render preserves matching nodes, focus and selection across
419
+ updates; it does not merge conflicting values or preserve an unshared draft.
420
+ Never dispatch a run, click or paid integration in response to a remote update.
421
+ Respect read-only access, preserve conflicting drafts, and surface write errors.
422
+ Only explicit user actions may run this Block. Shared acknowledgements are
423
+ locally queued, not proof of a durable server save. Preview localOnly state is
424
+ not proof of multiplayer operation. Test two interfaces exchanging actual edits
425
+ and saved outputs, permission loss, reconnect and conflicts before claiming
426
+ collaboration support. Never claim arbitrary imported JavaScript state syncs
427
+ merely because the package passed its computation fixtures.
428
+
429
+ <!-- shared-view:end -->
430
+
431
+ New custom interfaces must subscribe through `StillMade.onShared(callback)` and
432
+ publish every editable field: `data-stillmade-share` on static controls,
433
+ `StillMade.bindShared(key, elementId)` on dynamic ones, or
434
+ `StillMade.updateShared(values, before, options)` for structured values; private
435
+ JavaScript variables and DOM values are not shared automatically. This applies equally to original,
436
+ remixed and GitHub-adapted Blocks. Observe shared outputs as well as state, and
437
+ respect `canEdit` in every mutating callback. Never replay a generation when a
438
+ remote update arrives. Playback, hover and device microphone selection may stay
439
+ local. Use direct SDK calls so the minimum source check can recognize them.
440
+
441
+ The SDK/local-admission source gate rejects interfaces missing a shared subscription
442
+ or detected editable controls missing publication calls. Passing it does NOT
443
+ verify reachability or coverage of every control. Test two separate interfaces:
444
+ each editable field, remote results, read-only access, independent simultaneous
445
+ changes, conflicting same-field drafts, reconnect and pending-edit navigation.
446
+ Do not describe fixture execution or the source check as realtime acceptance.
447
+ Use the complete shared interface API and limits in SDK.md. Preserve old pinned
448
+ releases; publish a new version when adding this contract.
449
+ Hosted creation/import paths also enforce this minimum source check; it does
450
+ not substitute for actual interface interaction tests.
451
+
452
+ ## Mandatory layout and review rules
453
+
454
+ Use the available workspace width with normal gutters, not a centered modal-like
455
+ card. Do not nest decorative boxes. Import/admission check lists start collapsed:
456
+ show passed, needs-attention and untested counts plus remaining actions up front.
457
+ Keep actual failures and required approvals visible; never relax validation.
458
+ These are requirements, separate from optional visual conventions.
459
+
460
+ ## Updating existing Blocks through the SDK
461
+
462
+ “Update these Blocks using the SDK” means edit their actual owned implementation
463
+ and preserve their SDK contracts—not reskin a host feature or make a separate
464
+ demo. This procedure applies to first-party and creator Blocks alike.
465
+
466
+ ### Mandatory update procedure
467
+
468
+ 1. Resolve each requested Block's actual identity, installed version, package
469
+ source, and workflow placement. Read its manifest, implementation, custom view,
470
+ typed inputs/outputs, state schema, permissions, dependencies, fixtures, and
471
+ license/lineage before editing. Inspect the adjacent Blocks' contracts when
472
+ changing a handoff. A name or screenshot is not enough to establish compatibility.
473
+ 2. Identify the smallest change that fixes the user's task. Preserve working
474
+ behavior, user media, draft state, selections, saved outputs, and approval
475
+ boundaries. Keep the existing Block-owned implementation; do not replace it
476
+ with a wrapper importing application internals or a new host-only branch.
477
+ 3. Apply the mandatory interface rules in `INTERFACE_DESIGN.md`: one task surface,
478
+ normal reachable scrolling, no sticky media/header by default, no repeated
479
+ identity/navigation, no decorative boxes inside boxes, and no unsolicited
480
+ technical-result/source panels, even collapsed. Show the real task output.
481
+ Keep necessary approvals and failure feedback. Show only relevant controls;
482
+ secondary task controls may use a clearly named disclosure, not a mystery gear.
483
+ 4. Use declared SDK context, typed results, sandbox execution, and supported host
484
+ services. Missing capability is a specific SDK gap to report or implement when
485
+ authorized—not permission to bypass the sandbox, credentials, billing, or
486
+ review. A UI change does not authorize paid generation or a production run.
487
+ 5. Preserve immutable published source and installed pins. Create a new release
488
+ for a changed published Block; retain its listing identity only with publishing
489
+ authority. Otherwise use the normal remix identity and lineage. Preserve old
490
+ releases and specify any state/input migration before upgrading. Do not silently
491
+ repin saved projects. Use the ordinary explicit update/import review path.
492
+ 6. Review the final package against its actual upstream/downstream contracts and
493
+ include fixtures for changed behavior. Run SDK validation, fixtures, and browser
494
+ review only when authorized; never disable runtime admission. Distinguish source
495
+ inspection, checks actually run, and interactions actually exercised. Record
496
+ untested scrolling, rendering, saved-state compatibility, or integrations.
497
+ 7. Report each Block's old/new version, changed behavior, compatible contracts or
498
+ migrations, and whether it is merely authored, imported, installed, or applied
499
+ to this project. A committed source change is not proof that the preview uses it.
500
+
501
+ When parallel agent work is authorized, give each agent a distinct Block or shared
502
+ SDK/host responsibility and this same contract. Agree shared input/output changes
503
+ before integration. Parallel authoring does not mean executing dependent workflow
504
+ steps simultaneously; keep Frame Review approval before Motion Designer consumes
505
+ those frames. Do not dispatch paid calls or replace pins merely to exercise an update.
506
+
507
+ ### Enforcement versus conventions
508
+
509
+ The procedure above and `BLOCK_INTERFACE_PRINCIPLES` (see
510
+ [Block interface design](INTERFACE_DESIGN.md), which starts every interface from
511
+ the Block kit) are mandatory instructions for authors, not a claim that the SDK
512
+ can automatically inspect every design choice. `INTERFACE_DESIGN_RULES` lists
513
+ implemented static admission checks separately. Passing checks does not prove
514
+ useful visuals, unclipped scrolling, or a working end-to-end workflow.
515
+
516
+ ## Host-controlled exports
517
+
518
+ Return typed outputs or a `file-bundle`; StillMade owns every download, media
519
+ export and external delivery action. Never create a download link, invoke a
520
+ save-file picker, or add a separate export, referral or payment bypass. Upload
521
+ admission requires the **Host-controlled exports** check (`host-export-1`). The
522
+ host can apply a referral step, payment or another access requirement to every
523
+ creator's Block without changing Block code. Creator pricing is separate and
524
+ cannot override this policy.
525
+
526
+ Completed project MP4 and MP3 files use the SDK `CompletedExportRecord` shape.
527
+ StillMade creates that record after delivery and places it in Library → Full
528
+ Exports. A Block must not write export-history records or classify intermediate
529
+ media as a completed export.
530
+
531
+ ## Keep the working UI focused on the task
532
+
533
+ The manifest name and description are listing metadata. Keep them accurate and
534
+ concise, but do not turn them into a hero, marketing introduction, or large empty
535
+ header inside the Block. Put the actual inputs, primary action, and useful output
536
+ first. StillMade already provides a compact name and an About disclosure around
537
+ the sandbox. Do not repeat that framing inside a custom view. Use short labels
538
+ and explain only what helps someone complete the task; put optional help behind
539
+ a disclosure. Apply this to local SDK previews as well as imported Blocks.
540
+
541
+ Treat transport and source as implementation detail. MCP, JSON, schemas, ports,
542
+ prompts, and logs must not become the main experience unless editing source is
543
+ the Block's advertised job. Lead with the object being made and one obvious
544
+ primary action. Prefer visual cards, previews, timelines, and direct manipulation.
545
+ Keep peer choices to seven or fewer; use a strong default, grouping, search, or a
546
+ labelled disclosure for the rest. Put advanced task controls behind progressive
547
+ disclosure. Do not add diagnostic controls unless explicitly requested or part
548
+ of an explicitly source/debug-oriented task; collapsing them is not an exception.
549
+
550
+ Use recognizable line icons for familiar actions and objects, following the
551
+ StillMade icon language. Never use emoji or decorative circular icon backgrounds.
552
+ Icon-only controls need an accessible name, tooltip, keyboard focus, and a generous
553
+ hit area; retain visible text when the symbol could be unclear. The host already
554
+ shows the Block name, so do not repeat it as a large hero inside the custom view.
555
+ SDK admission reports `Task interface design` and rejects a custom workspace that
556
+ is dominated by a large raw-text editor without a task visualization.
557
+
558
+ ## Understand StillMade and its Block ecosystem
559
+
560
+ StillMade AI is an AI-assisted platform where people make things by using and
561
+ combining reusable tools called Blocks. Its current production tools include
562
+ script writing, voiceover, shot planning, Blueprint references, Canvas, image
563
+ panels, video assets, and editing. These are examples of existing capabilities,
564
+ not a limit on what users may want to build. The goal is an inviting, easy-to-use
565
+ workspace where AI helps people make and adapt useful tools without writing code
566
+ or manually wiring technical ports.
567
+
568
+ A Block is one independently versioned tool with its own implementation, interface,
569
+ and declared SDK contract. A Step is an instance of a Block placed in a workflow.
570
+ A Project Type is a reusable workflow made from those steps and their settings.
571
+ A project is the user's actual work using that workflow, with its own inputs,
572
+ media, state, and results. Chat is available when planning is useful; it is not
573
+ required to be the first step of every workflow. Blank Canvas starts in Canvas.
574
+ Do not confuse creating a reusable Block with creating one user's project output.
575
+
576
+ Your Block will join other first-party and creator-made Blocks in StillMade. Before
577
+ building, identify what the user needs that existing tools do not already provide.
578
+ When network access is available, resolve these paths against the public origin
579
+ of the SDK URL the user supplied, not a guessed production hostname or localhost:
580
+
581
+ - Browse `/marketplace` for user-facing examples.
582
+ - Search `/api/public-listings/discover?kind=block&search=<encoded-query>` for
583
+ Blocks, or use `kind=type` for Project Types. Follow `nextCursor` with `after`
584
+ when more results are relevant; a single page is not the complete catalog.
585
+ - Use returned listing identities and versions to read
586
+ `/api/public-listings/<kind>/<id>/<version>/metadata.json`: its name, description
587
+ and typed inputs and outputs. A Block's code is private to its creator, so build
588
+ on what it takes and makes, never on its source.
589
+ - In an offline SDK checkout, `packages/block-platform/catalog.js` provides bundled
590
+ first-party examples. It is a snapshot, not the complete live Marketplace.
591
+
592
+ Existing Blocks can inspire the design or provide neighboring workflow steps.
593
+ Reuse or remix permitted source when it fits the user's request; preserve its
594
+ license, attribution, lineage, and pinned version. A listing is not permission to
595
+ copy restricted source. Do not silently substitute an existing tool for the
596
+ custom behavior the user asked for. Explain useful existing options briefly and
597
+ continue toward the user's chosen result. If discovery is unavailable, say so
598
+ and avoid claiming the Block is unique or compatible with an uninspected tool.
599
+
600
+ Design the new Block to cooperate through declared inputs, outputs, semantic
601
+ roles, and authorized project context. Describe a practical workflow using real
602
+ Blocks, then verify matching contracts and supported connections with the SDK.
603
+ Names such as "script" or "image" alone do not establish compatibility. Respect
604
+ required fields, scalar versus array types, media references, permissions, and
605
+ runtime availability. Pin versions; never invent Block IDs, ports, host services,
606
+ or an unrestricted call to another Block. A useful standalone Block is acceptable
607
+ when no compatible neighbors exist—do not fabricate a workflow for appearance.
608
+
609
+ StillMade supplies the shared host: identity, navigation, storage, permissions,
610
+ billing, and execution. The Block supplies its capability and UI through the SDK.
611
+ Keep the runtime, sandbox, source/license, input/output, import, and spending
612
+ constraints below authoritative. Knowing the product does not relax them. A local
613
+ preview or available Marketplace listing does not grant credentials, paid execution,
614
+ or extra host access. Explain what this Block does, where it fits, and any real
615
+ limitations in language the user understands while iterating with them.
616
+
617
+
618
+ ## The goal: a Block the user wants to use in StillMade
619
+
620
+ You are collaborating with the user to make a useful Block that will be imported
621
+ into StillMade. A running preview, passing tests, or a packaged archive alone does
622
+ not finish the task. The Block should do what the user needs, feel understandable
623
+ to them, and be something they want to keep using in their StillMade projects.
624
+
625
+ Keep the conversation going toward that outcome. Understand the result the user
626
+ wants and how they expect to use it. Ask short, concrete questions when a missing
627
+ answer affects the Block's behavior; make reasonable reversible choices and keep
628
+ building when you can. Explain each meaningful iteration in terms of what the
629
+ user can now do. Once a preview is available, invite them to try it and tell you
630
+ what feels wrong or is missing. Apply their feedback to the same Block and repeat.
631
+ Do not treat the first working version as accepted or stop at a technical handoff.
632
+ Do not require an unnecessary questionnaire or keep asking for approval of routine
633
+ edits. Respect an explicit request to finish, pause, or deliver without more review.
634
+
635
+ Throughout development, preserve the actual StillMade SDK contract: editable
636
+ Block source, typed inputs and outputs, its real interface, supported runtime,
637
+ declared permissions, and the required import evidence. The local preview exists
638
+ to help the user shape this Block; it is not a separate website or the final product.
639
+ Do not build a nice standalone demo that cannot become the requested StillMade Block.
640
+ If the user's desired behavior cannot be imported or run under the supported
641
+ contract, explain the specific gap early and work with them on a supported option.
642
+ Never silently replace their goal with an easier example.
643
+
644
+ When the user is happy with the result or asks to finalize, prepare the importable
645
+ file `stillmade-block pack` writes from the source they just reviewed. Perform the checks permitted
646
+ by the environment and the user's instructions; report any unfinished or unverified
647
+ behavior honestly. Deliver the Block for use in StillMade, not just a preview URL
648
+ or instructions to rebuild it themselves. The single-artifact final-delivery rule
649
+ does not prohibit questions, progress updates, preview links, or iterative discussion
650
+ while you are working together.
651
+
652
+
653
+ ## Start a local preview early, then iterate
654
+
655
+ The default authoring experience is a working preview before the final import file.
656
+ As soon as the first runnable Block exists, launch its frontend and any required
657
+ local backend automatically when your coding environment supports running servers.
658
+ Do not wait until packaging, or make the user start the servers manually when you
659
+ can do it. Open the browser/IDE preview if supported and give the user the working
660
+ local URL. Keep the processes running while the user tries it and asks for edits.
661
+
662
+ Start the preview with `stillmade-block dev my-block`, the SDK's browser preview.
663
+ It serves the Block's real generated controls or custom view on loopback
664
+ (`http://127.0.0.1:4800`; `--port` picks another), reloads on every save
665
+ (`--build "npm run build"` reruns a bundler first), runs the Block with an
666
+ example's input or the user's, and answers hosted operations, files and
667
+ connections with stand-ins (from `tests/mocks.json` when present), never with
668
+ credits. Show its URL in the coding tool's preview where it has one. With `--live`
669
+ it also prints a link that offers each saved version to the user's signed-in
670
+ StillMade account for the usual import review, where runs are real and paid steps
671
+ ask first. Only when `dev` cannot show what the Block needs,
672
+ create a small development-only preview harness around the actual Block source,
673
+ its declared input controls, and its outputs. Use the supplied SDK sandbox and
674
+ view bridge for execution and custom interfaces; do not run Block code with eval,
675
+ Node imports, or a new unrestricted execution endpoint. The preview must show the
676
+ real interface and results, not a screenshot, fake success, or a separate mock app.
677
+ A browser-only Block does not need a backend merely for appearance. If one is
678
+ needed to host the SDK or serve the preview, start it together with the frontend
679
+ using one documented development command. Keep that command reproducible.
680
+
681
+ `stillmade-block run <package> <input.json>` (`preview` is the same command)
682
+ runs a sample once and returns JSON; it is not a browser server. Do not invent
683
+ other SDK commands such as `serve`. A separate preview
684
+ harness is development tooling; keep it outside the final portable Block package.
685
+ For a custom view, render the actual authored view through the SDK's existing
686
+ sandboxed view contract, with controls connected to real validated inputs/outputs.
687
+
688
+ Bind development servers to loopback by default, choose available ports, and
689
+ publish the actual URL only after both UI and required backend are ready. Use the
690
+ coding environment's normal preview forwarding if necessary. Restart or reload
691
+ affected processes after edits so the user sees the current source. Never stop
692
+ unrelated servers or claim a preview was opened or exercised when it was not.
693
+
694
+ Run offline examples with ordinary sample inputs. Keep runtime admission, typed
695
+ input/output validation, sandbox isolation, and declared permissions intact.
696
+ Local preview does not authorize paid provider calls, credentials, or unsupported
697
+ host capabilities. If a capability needs StillMade services that are unavailable
698
+ locally, show that limitation in the preview; clearly label any illustrative sample
699
+ and do not report the provider-backed behavior as verified. Honor the user's
700
+ execution and testing restrictions.
701
+
702
+ Once the preview is ready, invite the user to try it and request changes. Apply
703
+ feedback to the same Block source and keep the preview available. If the user
704
+ already requested a final artifact without an interactive review, proceed after
705
+ available checks. Otherwise, package when they say it is ready to import. Then run
706
+ the permitted SDK validation, sandbox fixtures, and packaging checks against that
707
+ same final source and return the single file `pack` writes for its runtime
708
+ (the table under "Build new Blocks directly with the SDK"). The rule to
709
+ return one artifact applies to final delivery, not progress messages or preview URLs.
710
+
711
+ If the environment cannot run servers or open a browser, explain the precise
712
+ limitation and provide the exact local startup command instead. Do not describe
713
+ an unavailable preview as running. Preview availability is not proof that all
714
+ features or fixtures passed; report what was actually exercised.
715
+
716
+
717
+ Every finished Block must pass SDK **Remix readiness**: retain editable source,
718
+ typed I/O, controls, tests and complete license evidence; do not disable remixing
719
+ or hide source. No custom remix integration is needed. See [the remix contract](block-remix.md).
720
+
721
+ Use this public SDK with a GitHub repository URL to develop outside StillMade. Follow [the complete portable contract](https://stillmade.shop/block-sdk/PORTABLE.md)
722
+ and return ONE `.stillmade-block` archive. Independently inspect the repository,
723
+ licenses, implementation and dependencies; infer a useful capability without
724
+ requiring extra StillMade instructions or asking the user to open the app.
725
+
726
+ The JSON examples below describe the existing in-memory package and runtime
727
+ contracts. They are development source, not an alternative final deliverable for
728
+ repository adaptation. Add portable evidence/lineage, validate with the shared
729
+ CLI, and package the archive. No extra wrapping response object or setup steps.
730
+ Never claim tests passed unless the validator and sandbox actually ran them.
731
+ Unsupported runtime or unresolved licensing produces a clear blocked result.
732
+
733
+ A Block is a capability; a Step is its placement in a Project Type. Protected
734
+ starting Chat cannot be replaced. Use the existing sandbox and SDK interfaces.
735
+ Do not implement a new application shell or require manual connection wiring.
736
+
737
+ ## Exact package shape
738
+
739
+ For runtime `javascript` (a standalone JavaScript Block) the package is below. A
740
+ module Block is a folder instead: `module/main.js` exporting
741
+ `async function run(input, stillmade)`, with module fixtures (see "Module Blocks"
742
+ and MODULES.md).
743
+
744
+ ```json
745
+ {
746
+ "manifest": {
747
+ "schemaVersion": 1,
748
+ "sdkVersion": "0.1.0",
749
+ "id": "creator.my-block",
750
+ "version": "1.0.0",
751
+ "name": "My Block",
752
+ "description": "One concise sentence explaining its input, behavior and output.",
753
+ "kind": "task",
754
+ "runtime": "javascript",
755
+ "license": "MIT",
756
+ "inputs": {
757
+ "text": {"type": "text", "primary": true}
758
+ },
759
+ "outputs": {
760
+ "text": {"type": "text", "primary": true}
761
+ },
762
+ "permissions": {"project": [], "network": [], "filesystem": [], "secrets": []},
763
+ "ui": [{"control": "text", "port": "text"}]
764
+ },
765
+ "code": "return {text: input.text.trim()};",
766
+ "tests": [
767
+ {"name": "Trims surrounding spaces", "input": {"text": " Hello "}, "expected": {"text": "Hello"}},
768
+ {"name": "Handles empty text", "input": {"text": ""}, "expected": {"text": ""}}
769
+ ]
770
+ }
771
+ ```
772
+
773
+ The example demonstrates the format; implement the requested capability instead.
774
+ The top-level keys for this format are `manifest`, `code`, `tests`, and optional `view`.
775
+ Do not add `dependencies`, `packageJson`, `handler`, `run`, or a `recipe` field.
776
+ The code is a JSON string containing a synchronous **function body** receiving the
777
+ variable `input`. Do not include `export`, a module wrapper, a function declaration
778
+ around the whole body, or a Markdown fence inside the string. Return an object whose
779
+ keys exactly match declared outputs. Escape embedded quotes/newlines as valid JSON.
780
+
781
+ Manifest IDs use a lowercase namespace and name, such as `creator.grayscale-image`.
782
+ Do not use `sm.*` or `stillmade.starting-chat`. Versions are `major.minor.patch`.
783
+ Names are 1–120 characters; descriptions are 1–1,000 characters. For original code
784
+ in this exercise use MIT. If adapting someone else's source, preserve their license
785
+ and attribution rather than assuming you can relicense it.
786
+
787
+ Visibility and private team sharing are configured in StillMade after release,
788
+ not inside the package. Never add team IDs, sharing grants, credentials or API
789
+ tokens to source. A team can receive an exact private released version without a
790
+ public listing; its members still review and confirm installation. Previously
791
+ delivered source cannot be retracted. Preserve third-party licenses, including
792
+ any restrictions on distributing source to a team.
793
+
794
+ ## Runtime constraints
795
+
796
+ These constraints are for runtime `javascript`; a module Block runs as an ES module
797
+ in a browser Worker and can use what MODULES.md lists.
798
+ JavaScript runs in isolated QuickJS, not Node.js or the browser's JavaScript engine.
799
+ Ordinary synchronous JavaScript, arrays, objects, strings, and `Math` are available.
800
+ No network, DOM, files, modules, shell, timers, promises, or installed dependencies.
801
+ Do not use `fetch`, `require`, `process`, `globalThis`, `window`, `document`, `eval`,
802
+ `Function`, `WebAssembly`, dynamic constructors, `setTimeout`, or import/export.
803
+ Do not invent a `ctx` object or SDK host service. Do not return typed arrays, class
804
+ instances, `undefined`, NaN, Infinity, promises, or functions; use JSON values.
805
+
806
+ Computation code also has `StillMade.emit(outputName, value)` and
807
+ `StillMade.resolveRequirement(inputName)`. Emit only declared output names once,
808
+ and do not also return outputs or a cooperative pending result. The requirement
809
+ reader returns `{resolved:true, requirement, value}` from the fixed host-validated
810
+ input snapshot, or `{resolved:false, requirement}` for an absent optional input.
811
+ It does not query new sources, ask Chat or users, access credentials, or write a
812
+ project.
813
+
814
+ `StillMade.getProjectContext()` returns an isolated snapshot containing only
815
+ declared context-bound inputs, keyed by their `context` field names. Pass a
816
+ declared field name to read one value; an undeclared field throws, and an absent
817
+ optional value returns `undefined`. This is not unrestricted project access or
818
+ a live lookup. Host permissions still apply.
819
+
820
+ `StillMade.validate(value, schema)` returns a boolean using the shared supported
821
+ JSON/schema rules. It does not verify semantic meaning, permissions, provenance,
822
+ or media contents, and never replaces final host output validation. These methods
823
+ are not provided to `view.javascript`. Isolated computation code (not the view)
824
+ also has `StillMade.requestFromUser`, `StillMade.requestFromChat` (see
825
+ UNIVERSAL_RESOLVER.md) and `StillMade.patchProject` (see JAVASCRIPT_RESULTS.md);
826
+ none of these exist in `view.javascript`, and no other guest methods exist.
827
+
828
+ Persist custom semantic inheritance in `manifest.semantics`, for example
829
+ `[{name:"creator.example.spoken_copy",parents:["narration_script"]}]`.
830
+ Use namespaced names, at most 64 definitions per Block, eight parents per name,
831
+ and 500 description characters. Do not override standard meanings or introduce
832
+ cycles/conflicting parent definitions. Preserve the inherited meaning of existing
833
+ input/output contracts when refining a base; adding a parent also changes the
834
+ contract even if its semantic label stays the same. New independent ports may
835
+ declare new meanings. Changing a published meaning requires a new release.
836
+
837
+ Limits: 256 KiB source, 500 ms guest CPU time, 16 MiB guest heap, 32 MiB WASM memory,
838
+ 4 MiB serialized input/output. Use small fixtures and bounded algorithms. A Block
839
+ requiring Python, models, network APIs, or unavailable dependencies cannot
840
+ be implemented by this standalone JavaScript runtime. Hosted text generation, speech and image generation can use the separate declarative capability formats below. For other unavailable services, explain the limitation instead of
841
+ pretending those APIs exist or returning a successful-looking placeholder.
842
+
843
+ ## Typed inputs and outputs
844
+
845
+ Each input/output is a named port. Names start with a lowercase letter and contain
846
+ only letters, numbers and underscores. Every input of a new Block needs `semantic`,
847
+ `required` and `description`; every output needs `semantic` and `description`.
848
+ `validate` and `test` check structure and fixtures only; `ready` and `gate` run
849
+ full admission, which rejects a port missing these fields.
850
+
851
+ <!-- port-fields:start (generated by npm run build:sdk; do not edit) -->
852
+
853
+ Every port field the SDK accepts (generated from the validator; see [port fields](../reference/ports.md)):
854
+
855
+ | Field | Meaning |
856
+ | --- | --- |
857
+ | `type` | Required. A shared StillMade type such as `text`, `image`, `shot-plan`; add `[]` for a list (`image[]`). |
858
+ | `required` | Required on every input of a new Block: `true` or `false`. A required input needs a generated control, a `context` binding or a `default`, or `pack` and import reject it. |
859
+ | `default` | A JSON value matching the type. Satisfies a required input when nothing else supplies it. |
860
+ | `min` | Lowest allowed value for a `number` or `integer` port. |
861
+ | `max` | Highest allowed value for a `number` or `integer` port. |
862
+ | `description` | Required on every input and output of a new Block. One short plain-language sentence about what the value is for. |
863
+ | `role` | Legacy distinct role (for example `character_reference`). Use only when the meaning must not mix with others; downstream roles must match exactly. Prefer `semantic`. |
864
+ | `primary` | Mark exactly one main input and one main output `true`; Project Type Steps connect primary ports automatically. |
865
+ | `context` | The project field this input reads (for example `script`); requires the matching `context.FIELD.read` permission. |
866
+ | `imageMode` | JavaScript image inputs only: `pixels` delivers decoded `{width,height,data}`; `reference` delivers the media reference without pixels. |
867
+ | `key` | Set by the host for resolver requirements. Leave unset in manifests. |
868
+ | `semantic` | Required on every input and output of a new Block. The exact meaning in lowercase snake_case or dotted form (`company_profile`, `production_shot_plan`). Downstream matching uses it; different meanings never connect. |
869
+ | `accepts` | Additional meanings this input also accepts, besides its own `semantic`. |
870
+ | `schema` | Required on every `json` or `object` port (admission rejects one without it). Not JSON Schema: a field-type word or a map of field names to field-type words, nested for objects. Words are `string`, `text`, `number`, `integer`, `boolean`, `object`, `json`; add `[]` for a list and `?` for an optional field, for example `{title:"string",tags:"string[]?",size:{width:"integer",height:"integer"}}`. |
871
+ | `sources` | Where the universal resolver may fill this input from: `upstream`, `project`, `adapter`, `chat`, `default`, `user`, `external`. Omit to allow all. `default` covers only the declared default; values a person enters in the Step or a view passes to `StillMade.run` count as `user`, so leave `user` allowed for anything the interface fills in. |
872
+ | `sensitivity` | A label for sensitive information; values marked secret, credential, password or token are never offered in chat. |
873
+ | `freshnessPolicy` | `{maxAgeMs}`: the resolver rejects values older than this. |
874
+ | `cardinality` | `one` or `many` values. |
875
+ | `batchable` | `true` when the input can run once per item of a selected media batch. |
876
+
877
+ <!-- port-fields:end -->
878
+
879
+ Simple types: `text`, `number`, `integer`, `boolean`, `json`.
880
+ Media types: `image`, `video`, `audio`, `asset`.
881
+ Production types: `script`, `scene`, `shot`, `character`, `location`, `style`,
882
+ `timeline`, `mask`, `depth`, `pose`, `metadata`, `project-context`, `transcript`,
883
+ `brief`, `bible`, `shot-plan`, `panel-document`, `board-document`, `scene-document`,
884
+ `timeline-range`, `research`, `pipeline`, `approval`.
885
+ Arrays use a `[]` suffix, such as `shot[]` or `text[]`.
886
+
887
+ Text values are strings. Numbers must be finite, and integer values must be whole.
888
+ `json` accepts bounded plain JSON. Structured production values are objects with
889
+ `schemaVersion: 1`; define/document the fields your capability requires. Do not
890
+ invent fields supplied by StillMade. Media references are JSON objects like
891
+ `{"kind":"image","assetId":"asset-1","versionId":"v1","url":"..."}`;
892
+ they are references, not permission to fetch/read media inside guest code.
893
+
894
+ Declare your capability's actual types. StillMade can connect a media reference to
895
+ `asset`, check an `asset`'s real kind before using it as image/video/audio, resolve
896
+ a scene to its authorized ordered shots, or use a shot's selected production image.
897
+ These host conversions preserve semantic roles and require the receiving Block's
898
+ context permissions where applicable. They do not decode video, invent missing
899
+ images, or select a character reference as production media. Do not implement
900
+ manual wiring or claim a conversion that your declared contract does not support.
901
+ The full SDK reference documents these as **Safe connection conversions**.
902
+
903
+ ### Deterministic image processing
904
+
905
+ For a pixel-processing Block, declare type `image` and use decoded RGBA values:
906
+
907
+ ```json
908
+ {"width": 2, "height": 1, "data": [255, 0, 0, 255, 0, 0, 255, 128]}
909
+ ```
910
+
911
+ Width/height are positive integers; data is a plain array of exactly
912
+ `width * height * 4` integers in `[0,255]`, row-major RGBA order. The format allows
913
+ at most 1,048,576 pixels, with the tighter serialized/memory limits above still
914
+ applying. Use tiny fixtures (1–4 pixels where possible). Return the same decoded
915
+ shape for image outputs. Allocate new output data; do not mutate input pixels.
916
+ Preserve dimensions and alpha unless the requested behavior explicitly changes
917
+ them. The production host handles media decoding/storage around the sandbox.
918
+
919
+ ### Project context (only if the requested behavior needs it)
920
+
921
+ An input can request one of: `script` (type `script`), `shots` (`shot[]`),
922
+ `scenes` (`scene[]`), `characters` (`character[]`), `locations` (`location[]`),
923
+ `styles` (`style[]`), `assets` (`asset[]`), `generations` (`metadata[]`),
924
+ `versions` (`metadata[]`), `timeline` (`timeline`), or `metadata`
925
+ (`metadata`). For example an input
926
+ `"shots":{"type":"shot[]","context":"shots"}` requires
927
+ `permissions.project` to include `"context.shots.read"`. The host supplies a copy
928
+ at that named input; code still reads `input.shots`. Fixtures must provide the same
929
+ value explicitly. Request only the needed fields. Do not invent write APIs.
930
+
931
+ The host indexes saved Canvas, panel/board, voiceover and editor documents. Media
932
+ references preserve Canvas IDs and approved versions; `assets` contains current
933
+ media, while `versions` contains retained media and document-version metadata.
934
+ A media history record is `{schemaVersion:1,id,assetId,versionId,asset,source,active}`;
935
+ `asset` is a normal media reference. Document history records have `documentType`
936
+ and `versionId` but no `asset`. `generations` contains saved generated-media history
937
+ with `status:"completed"`; optional saved model/timestamp/duration may be absent.
938
+
939
+ For a scene/shot/asset selection, the host provides only related context: scoped
940
+ shots and scene `shotIds`, selected narration, selected timeline collections and
941
+ related visual references. It excludes unrelated transcript, timeline history and
942
+ unowned media. `metadata.scope` describes this selection. Packages cannot widen it.
943
+ Canvas-selected references remain the existing character-consistency authority;
944
+ do not create a competing identity store. Each new metadata binding needs its own
945
+ exact permission, such as `context.versions.read`.
946
+
947
+ ## Controls
948
+
949
+ `ui` is an array of `{ "control": "...", "port": "inputName" }` objects.
950
+ Control must be one of `text`, `number`, `slider`, `checkbox`, `asset`, `select`,
951
+ `multiselect`, `file`, `date`, `color`, `url`, `richtext`, `list` or `grid`, bound to an
952
+ existing input. A field's label is its `ui` entry's `label` when given, in the job's
953
+ words ("When to post"), else its port name; its (i) is the port's `description`. For a numeric slider,
954
+ give its input `type`, `min`, `max`, and `default`. Use `asset` for an image input.
955
+ For a custom visual interface, add the optional `view` object below. Do not supply React components, external dependencies, or a hosted UI URL.
956
+
957
+ ## Custom Block interfaces
958
+
959
+ A package can include an optional **`view`** beside `manifest`, `code`,
960
+ and `tests`. This is the Block's real interface in Preview: a user edits controls,
961
+ presses its action, and sees the code's result. Packages without `view` use
962
+ StillMade's generated controls and output viewer. `manifest.ui` remains the
963
+ fallback controls schema; it is not the custom interface source.
964
+
965
+ <!-- view-rules:start (generated by npm run build:sdk; do not edit) -->
966
+
967
+ 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`:
968
+
969
+ - **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:`.
970
+ - **No loops and no function declarations:** view JavaScript may not contain `import()`, `import`, `with`, `this`, `import.meta`/`new.target`, `while`, `do`/`while`, `for`, `for…in`, `for…of`, `function` declarations. Iterate with array `map`/`forEach`/`filter`/`reduce` and write helpers as `const name = (...) => ...`.
971
+ - **Never use these names in view JavaScript, not even for your own variables:** `eval`, `Function`, `AsyncFunction`, `GeneratorFunction`, `require`, `importScripts`, `process`, `globalThis`, `window`, `self`, `top`, `parent`, `opener`, `frames`, `location`, `navigation`, `history`, `navigator`, `localStorage`, `sessionStorage`, `indexedDB`, `caches`, `cookieStore`, `fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource`, `Worker`, `SharedWorker`, `ServiceWorker`, `WebAssembly`, `Deno`, `Bun`, `Reflect`, `Proxy`, `DOMParser`, `MutationObserver`, `postMessage`, `open`, `close`, `print`, `alert`, `confirm`, `prompt`, `setInterval`, `setTimeout`, `queueMicrotask`, `arguments`, `constructor`, `__proto__`, `prototype`, `__lookupGetter__`, `__lookupSetter__`, `__defineGetter__`, `__defineSetter__`, `getPrototypeOf`, `setPrototypeOf`, `getOwnPropertyDescriptor`, `getOwnPropertyDescriptors`, `getOwnPropertyNames`, `getOwnPropertySymbols`, `defineProperty`, `defineProperties`, `defaultView`, `ownerGlobal`, `contentWindow`, `contentDocument`, `cookie`, `domain`, `documentURI`, `URL`, `baseURI`, `referrer`, `write`, `writeln`, `innerHTML`, `outerHTML`, `insertAdjacentHTML`, `createElement`, `createElementNS`, `createContextualFragment`, `createRange`, `setAttribute`, `setAttributeNS`, `attributes`, `attributeStyleMap`, `adoptNode`, `importNode`, `execCommand`, `replaceChildren`, `setHTML`, `setHTMLUnsafe`, `parseHTML`, `parseHTMLUnsafe`, `showSaveFilePicker`, `showDirectoryPicker`, `saveAs`, `FileSystemWritableFileStream`, `createWritable`, `msSaveBlob`, `msSaveOrOpenBlob`, `download`.
972
+ - **Never use these property names:** `assign`, `values`, `entries`, `fromEntries` (so no `Object.assign`, `Object.values`, `Object.entries`, `Object.fromEntries`; use arrays and `map`/`forEach`).
973
+ - **These words may not appear anywhere in view JavaScript, even in strings or comments:** `style`, `cssText`, `setProperty`, `removeProperty`, `insertRule`, `deleteRule`, `styleSheets`, `adoptedStyleSheets`, `animate`. Change appearance with CSS classes and `hidden`; put colors in the stylesheet.
974
+ - **Property access with brackets needs a literal key:** `items[0]` and `record["title"]` are accepted; `items[index]` is rejected. Iterate with `map`/`forEach` or call `items.at(index)`.
975
+ - **No named recursive functions** and no listeners on the global frame; attach events to declared controls.
976
+ - **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.
977
+ - **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}`.
978
+ - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 30 runs and 60 image previews per minute.
979
+
980
+ <!-- view-rules:end -->
981
+
982
+ `view` optionally declares `appearance: "original"` or `"host"` and contains
983
+ `html` (required, up to 64 KiB), `css` (optional, up to 32 KiB),
984
+ and `javascript` (optional, up to 64 KiB). These are UTF-8 byte limits. For folder
985
+ packages, use separate `src/view.html`, `src/view.css`, and `src/view.js` files
986
+ (the last two are optional). Optional theme-specific CSS uses
987
+ `src/view.themed.css`. The older `src/view.json` object format is also supported,
988
+ but never combine it with separate interface files. CLI pack, folder import,
989
+ and ZIP import reconstruct the same `view` object and run the same validators.
990
+ Use **Choose folder** on the import page to select an edited SDK package directory.
991
+ `src/run.js` remains the production function; `src/view.js` controls the interface. No external framework, URL, npm module, or build step runs
992
+ inside the interface. If using React or another framework externally, adapt the
993
+ interface into these supported standalone controls before import.
994
+
995
+ ### Interface bridge
996
+
997
+ The host provides `StillMade` inside the sandbox:
998
+
999
+ | API | Behavior |
1000
+ | --- | --- |
1001
+ | `StillMade.input` | Copy of the current preview input bindings after initialization |
1002
+ | `StillMade.onInput(callback)` | Receives initial input and later host input changes; returns an unsubscribe function |
1003
+ | `await StillMade.previewImage(value)` | Resolves a currently authorized image to a bounded, display-only PNG copy; preserves the original run input |
1004
+ | `await StillMade.run(values)` | Validates the named input bindings, runs the package in the isolated computation worker, validates outputs, and resolves to the output bindings object |
1005
+
1006
+ Always use `onInput` to initialize controls; the interface may load before its
1007
+ initial values arrive. Catch run errors and display them in the interface. Disable
1008
+ the run control while a request is pending. One run can be active; the host limits
1009
+ the view to 30 requests per minute, 4 MiB and 500,000 JSON nodes per message, and
1010
+ 15 seconds for local execution. Host-confirmed ComfyUI, text and speech requests
1011
+ allow up to 6 minutes; image generation allows up to 15 minutes for confirmation
1012
+ and its bounded fixture/sample sequence. These are interface wait budgets, not
1013
+ permission to retry or extend provider deadlines. Raw RGBA messages must fit both
1014
+ message budgets (roughly 124,000 pixels after reserving nodes for other inputs);
1015
+ use smaller preview images.
1016
+ The existing computation limits (including the JavaScript 500 ms guest deadline)
1017
+ still apply. Reset discards the interface and aborts its pending computation.
1018
+ Closing Preview disposes the frame and aborts outstanding work.
1019
+
1020
+ For a port named `prompt`, initialize its control from the host input callback:
1021
+
1022
+ ```js
1023
+ const inputField = document.getElementById('prompt');
1024
+ StillMade.onInput(input => { inputField.value = input.prompt || ''; });
1025
+ ```
1026
+
1027
+ Inside the async run button handler, call
1028
+ `await StillMade.run({prompt: inputField.value})` and display its output.
1029
+ Use `inputField` or `promptInput` for local variables; `prompt` is also a browser
1030
+ API name and cannot be a local identifier. Its data-property exception applies
1031
+ only to the single, unshadowed and unreassigned parameter of a direct inline
1032
+ `StillMade.onInput` callback; declare that parameter name only once in the
1033
+ interface source. Use ordinary `.prompt` access there, rather than
1034
+ computed/destructured access or a property on an unrelated object. Object literal
1035
+ `{prompt: value}` keys are supported. Browser dialogs remain unavailable.
1036
+
1037
+ Use the DOM for interaction and rendering: `document.getElementById`,
1038
+ `querySelector`, `addEventListener`, `textContent`, `value`, `checked`, `disabled`,
1039
+ `hidden`, and canvas are supported. Declare elements in `html`; show/hide existing
1040
+ elements or draw into a declared canvas. Dynamic element creation, HTML insertion,
1041
+ reflection, computed dynamic property lookups, module imports, browser navigation,
1042
+ account APIs, parent/window access, and storage are rejected. Property access such
1043
+ as `result.text` and `image.data[0]` is supported; use array methods for iteration.
1044
+ Synchronous loops, function declarations, directly recursive helpers, timers, and
1045
+ microtask queues are rejected. Every callback/helper also checks a private task
1046
+ budget (10,000 calls or 50 ms between checks), including indirect recursion. This
1047
+ guard cannot interrupt a single native browser operation, so it is not a
1048
+ substitute for the computation worker. Use small native array operations such as `map`/`forEach` for
1049
+ rendering; heavy work belongs in the isolated Block runtime.
1050
+ Use `.textContent` for user text. Tags are ordinary controls, text, layout, media,
1051
+ and canvas; no script, iframe, SVG, link, form, or document metadata tags. Markup
1052
+ must use complete tags without HTML comments, inline event handlers, or navigation
1053
+ attributes. JavaScript belongs in `view.javascript`, not the HTML string.
1054
+
1055
+ The frame has an opaque origin and `sandbox="allow-scripts"`, without same-origin,
1056
+ popups, forms, downloads, or top navigation permissions. CSP denies connections,
1057
+ workers, frames, external scripts, and resources. Image and media sources must be
1058
+ local `data:` or `blob:` URLs. Styles cannot import resources. No token, account,
1059
+ project mutation API, filesystem, or parent DOM handle is passed into the frame.
1060
+ Only copied inputs, explicit run results, and authorized image display copies cross the bridge. Static checks
1061
+ are an additional admission gate; they do not replace this browser boundary or
1062
+ the separate computation sandbox. Keep the UI responsive: move computation into
1063
+ `code`; DOM JavaScript is not the CPU-metered computation runtime.
1064
+
1065
+ A view adds files to an input with the + tile,
1066
+ `<button class="sm-add" data-stillmade-add="<input>">`, or
1067
+ `StillMade.addFiles("<input>")` from its own button, never an
1068
+ `<input type="file">` of its own: StillMade's picker makes the working copies
1069
+ and keeps device files on the device (see "Adding files to an input" in
1070
+ [Frame views](../build/frame-views.md)). To hand pixels the view drew to an
1071
+ image input, draw them into a declared `<canvas>` and pass `getImageData` as
1072
+ `{width,height,data:Array.from(pixels.data)}`, staying within the SDK image and
1073
+ message budgets. Nothing is uploaded by the view.
1074
+
1075
+ The host supplies StillMade's shared Light, Dark and White theme variables and basic controls.
1076
+
1077
+ For optional StillMade appearance, inherit its typography or use
1078
+ `var(--font-body)` for controls and `var(--font-display)` with weight 200 and
1079
+ italic style for display headings. Use shared spacing, radius and color tokens
1080
+ such as `var(--bg)`, `var(--fg)`, `var(--fg-muted)`, `var(--border-tok)`, and
1081
+ `var(--r-3)`. Original appearance may keep its own typography and colors within
1082
+ the existing sandbox; importing external fonts or resources remains unavailable.
1083
+
1084
+ Use a clear input, primary action, and visible result. Preview runs
1085
+ synthetic samples or the user's explicitly chosen input; it does not commit to a
1086
+ production project. SDK fixture tests prove code behavior and input/output
1087
+ compatibility. They do not claim to test browser interaction: review the actual
1088
+ custom interface in Preview before **Confirm Import**.
1089
+
1090
+ ### Appearance compatibility
1091
+
1092
+ StillMade has three modes: **Light** (cream), **Dark** (ink), and **White**
1093
+ (neutral white surfaces with black text). Generated controls inherit the app mode.
1094
+ A custom interface may keep its original design by explicitly setting
1095
+ `view.appearance: "original"`. The surrounding StillMade app keeps its own theme.
1096
+ Omitting this field, or setting it to `"host"`, preserves the existing requirement
1097
+ to provide theme-compatible colors. Existing package sources and installed pins
1098
+ are never rewritten to change appearance.
1099
+
1100
+ A minimal original-style view is:
1101
+
1102
+ ```json
1103
+ {
1104
+ "appearance": "original",
1105
+ "html": "<output id=\"result\"></output>",
1106
+ "css": "body{background:#14213d;color:#fff}",
1107
+ "javascript": "StillMade.onShared(shared=>{document.getElementById('result').textContent=shared.outputs.text||'';});"
1108
+ }
1109
+ ```
1110
+
1111
+ Keep this object in `src/view.json` when using the explicit appearance field;
1112
+ do not combine it with split `src/view.html`/CSS/JavaScript files. Folder, archive
1113
+ and GitHub SDK-folder imports retain the field, as do source editing and export.
1114
+
1115
+ The stylesheets have separate purposes:
1116
+
1117
+ - `view.css` (up to 32 KiB) retains the original design. Original-style packages
1118
+ use it inside projects as well as in Original design previews.
1119
+ - `view.themedCss` (optional, up to 32 KiB) is a **complete replacement stylesheet**
1120
+ for authors who also want StillMade appearance. Include the same layout and
1121
+ responsive rules using host tokens. Preview offers this mode only when the
1122
+ complete view passes the host-appearance check.
1123
+
1124
+ For example, original CSS can use `body { background:#14213d; color:#fff; }`.
1125
+ Its optional themed stylesheet uses `body { background:var(--bg); color:var(--fg); }`.
1126
+ Move fixed inline colors into the stylesheets when offering both appearances.
1127
+ Never change image/video/canvas pixels to match the interface palette.
1128
+
1129
+ For StillMade appearance, use `var(--bg)`, `var(--bg-elev-1)`, `var(--bg-elev-2)`,
1130
+ `var(--fg)`, `var(--fg-muted)`, `var(--border-tok)`, `var(--signal)`, and
1131
+ `var(--shadow-1)`. The SDK exports `APPEARANCE_TOKENS`. Inherit host typography
1132
+ or use `var(--font-body)` and `var(--font-display)`. Do not redefine host tokens
1133
+ or use raw palette tokens such as `--cream-100` in theme-aware declarations.
1134
+ Original appearance does not require these color or typography conventions.
1135
+
1136
+ **The appearance declaration does not widen the interface runtime.** Existing
1137
+ HTML/JavaScript syntax restrictions, CSS restrictions on escapes, filters and
1138
+ color blending, resource-loading bans, size limits, shared-state requirements,
1139
+ permissions, CSP and typed input/output validation still apply. Use CSS classes
1140
+ for interface changes; dynamic element-style mutation remains unavailable.
1141
+ Static original colors are allowed only with the explicit original mode;
1142
+ any optional themed stylesheet still needs valid semantic colors.
1143
+
1144
+ `scanPackage`, `admitPackage`, and CLI validation check the declared appearance
1145
+ alongside all other admission gates. A styling pass does not prove working UI,
1146
+ accessibility or full repository compatibility. Review the actual interface on
1147
+ its declared devices and, when supplied, each themed mode. This option preserves
1148
+ styling within the existing restricted view format; it does not load framework
1149
+ bundles, arbitrary React applications, remote fonts or external scripts.
1150
+
1151
+ ### Display an authorized image
1152
+
1153
+ Use **`await StillMade.previewImage(value)`** for an image selected in
1154
+ StillMade, received from a connected Block, or present in explicitly granted
1155
+ project context. The host also supports its registered sample pixels, local
1156
+ uploads, and verified run outputs. A well-formed reference alone does not grant
1157
+ access: a changed URL, version, role, unrelated asset, or expired preview session
1158
+ is rejected. Catch and display the error; do not fall back to fetching the URL.
1159
+
1160
+ The result is exactly `{url, width, height}`. Its URL is a PNG data URL
1161
+ that can be assigned to a declared `<img>`. It is a display copy, at most
1162
+ 512 pixels on either edge, with aspect ratio preserved. It does not generate,
1163
+ upload, save, select, or replace an asset. Continue to pass the **original**
1164
+ image value to `StillMade.run`; processing uses the original input within
1165
+ the runtime's own limits. Do not use the display URL as a processing input.
1166
+
1167
+ Image display allows one pending request, up to 60 requests per minute, and a
1168
+ 15-second deadline. It is independent of the pending computation request, so an
1169
+ input can be displayed while its Block runs. Both directions retain the 4 MiB
1170
+ and 500,000-node message limits. Input changes, source/session reset, or closing
1171
+ the workspace cancel unfinished display requests. Use a local revision counter
1172
+ in asynchronous UI code so a late result cannot repaint an earlier selection.
1173
+
1174
+ StillMade's host image controls remain available with a custom interface.
1175
+ On a project Step the custom interface fills the Step: the host's file inputs and "Apply to"
1176
+ open from the Step's inputs button, and while a required file is missing a "Choose <input>"
1177
+ button sits above the interface. Show your own empty state
1178
+ in the view ("Add a photo") and never describe where the host's controls are.
1179
+ A project lets the user select a compatible authorized image or upload one;
1180
+ connected/context bindings remain read-only. A standalone preview keeps uploaded
1181
+ files local to that preview. Host uploads accept PNG, JPEG, WebP or GIF up to
1182
+ 12 MB and one megapixel. Above one megapixel the host makes a one-megapixel
1183
+ working copy for the sandbox when the account's media setting is on (its
1184
+ default); with the setting off, larger uploads are rejected with a request to
1185
+ resize a copy. Existing authorized project references can be displayed up to 16
1186
+ megapixels, but the selected runtime still applies its processing limit. The host validates permissions and exact media
1187
+ identity before resolving bytes; no account token, native file handle, or remote
1188
+ fetch capability enters the iframe.
1189
+
1190
+ This `view` example assumes one image input and one image output, both
1191
+ named `image`. It shows the actual selected input and processed result
1192
+ while retaining their original typed values:
1193
+
1194
+ ```json
1195
+ {
1196
+ "html": "<figure><img id=\"source\" alt=\"Selected input\" hidden><figcaption>Selected image</figcaption></figure><button id=\"run\">Run block</button><figure><img id=\"result\" alt=\"Processed result\" hidden></figure><p id=\"status\" role=\"status\"></p>",
1197
+ "javascript": "const source=document.getElementById('source'), result=document.getElementById('result'), button=document.getElementById('run'), status=document.getElementById('status');\nlet current={}, revision=0;\nStillMade.onInput(input=>{\n current=input;\n const started=++revision;\n source.hidden=true;\n result.hidden=true;\n if(!input.image)return;\n StillMade.previewImage(input.image).then(preview=>{\n if(started!==revision)return;\n source.src=preview.url;\n source.width=preview.width;\n source.height=preview.height;\n source.hidden=false;\n status.textContent='';\n }).catch(error=>{if(started===revision)status.textContent=error.message});\n});\nbutton.addEventListener('click',async()=>{\n const started=revision;\n button.disabled=true;\n try{\n const output=await StillMade.run(current);\n if(started!==revision)return;\n const preview=await StillMade.previewImage(output.image);\n if(started!==revision)return;\n result.src=preview.url;\n result.width=preview.width;\n result.height=preview.height;\n result.hidden=false;\n status.textContent='Finished';\n }catch(error){if(started===revision)status.textContent=error.message}\n finally{button.disabled=false}\n});"
1198
+ }
1199
+ ```
1200
+
1201
+ ### Complete custom interface example
1202
+
1203
+ Add the following `view` to the complete JavaScript text-trimming example above.
1204
+ The manifest, code, and fixtures stay unchanged. Adapt the controls to the requested Block:
1205
+
1206
+ ```json
1207
+ {
1208
+ "html": "<h1>Clean text</h1><label for=\"source\">Your text</label><textarea id=\"source\"></textarea><p><button id=\"run\">Clean text</button></p><h2>Result</h2><output id=\"result\" aria-live=\"polite\">Your result appears here.</output>",
1209
+ "css": "output { display:block; white-space:pre-wrap; padding:16px; border:1px solid var(--border-tok); border-radius:var(--r-3); }",
1210
+ "javascript": "const source=document.getElementById('source'), button=document.getElementById('run'), result=document.getElementById('result'); StillMade.onInput(input=>{source.value=input.text||''}); button.addEventListener('click',async()=>{button.disabled=true;try{const output=await StillMade.run({text:source.value});result.textContent=output.text}catch(error){result.textContent=error.message}finally{button.disabled=false}});"
1211
+ }
1212
+ ```
1213
+
1214
+ ### Dynamic lists and controls
1215
+
1216
+ Use `StillMade.render(containerId, nodes)` to replace the children of an existing
1217
+ container with structured controls. Use `StillMade.onAction(callback)` for their
1218
+ click, input, and change events; it returns an unsubscribe function. This supports
1219
+ variable-length lists without raw HTML injection or direct DOM creation.
1220
+
1221
+ For example, with `<div id="choices"></div><output id="selection"></output>`:
1222
+
1223
+ ```js
1224
+ StillMade.onInput(input => {
1225
+ StillMade.render('choices', (input.shots || []).map(shot => ({
1226
+ tag: 'button', text: shot.name || shot.id, value: shot.id, action: 'select'
1227
+ })));
1228
+ });
1229
+ StillMade.onAction(event => {
1230
+ document.getElementById('selection').textContent = event.value;
1231
+ });
1232
+ ```
1233
+
1234
+ Each node has `tag`, optional `text` or `children`, and optional `id`, `className`,
1235
+ `label` (accessible name), `action`, `value`, `type`, `checked`, `disabled`,
1236
+ `placeholder`, `min`, `max`, or `step`. Unknown fields are rejected. Supported tags
1237
+ are div, section, article, header, footer, p, span, strong, em, small, h1–h4,
1238
+ label, button, input, textarea, select, option, ul, ol, li, table, thead, tbody,
1239
+ tr, th, td, details, summary, output, and progress. Input types are text, number,
1240
+ range, checkbox, radio, and color. Use a div, section, article, main, aside, ul,
1241
+ ol, or tbody as the existing root. Style nodes with classes in your view CSS.
1242
+
1243
+ An action belongs to a button, input, textarea, or select. Its callback receives
1244
+ `{action, value, checked, event}`. Buttons emit click; checkbox, radio, and select
1245
+ emit change; other fields emit input. Rerendering replaces controls, so keep edited
1246
+ values in your own local variables and avoid rerendering a focused field on every
1247
+ keystroke. Call `StillMade.run` explicitly to execute production code. Rendering
1248
+ does not mutate project context or bypass execution permissions.
1249
+
1250
+ Limits: 1,000 nodes per render, 12 child levels, 256 KiB of serialized descriptors,
1251
+ 5,000 total frame elements, 16 action listeners, 10,000 characters per text/value,
1252
+ 120 per ID/action and 240 per class/name/placeholder. IDs must be unique. Invalid
1253
+ renders leave the previous controls intact. Text is always literal; scripts,
1254
+ inline handlers, links, file inputs, network attributes, and raw HTML are rejected.
1255
+
1256
+
1257
+ ### Audio and video playback
1258
+
1259
+ `await StillMade.previewMedia(reference)` opens an authorized `audio` or `video`
1260
+ reference as a temporary playback copy and returns `{kind, url, mimeType, bytes}`.
1261
+ Assign its URL to an existing `<audio controls>` or `<video controls>` element.
1262
+ The host requires an exact reference from this Block’s bound input, an explicitly
1263
+ selected project asset, permitted project context, or a host-recorded output.
1264
+ A URL alone is insufficient. Preview has host file pickers for audio/video inputs;
1265
+ project Steps can select existing project assets or receive connected media.
1266
+
1267
+ For `<audio id="player" controls></audio><p id="status"></p>`:
1268
+
1269
+ ```js
1270
+ const player = document.getElementById('player');
1271
+ const status = document.getElementById('status');
1272
+ let revision = 0, activeUrl = '';
1273
+ StillMade.onInput(input => {
1274
+ const started = ++revision;
1275
+ player.pause(); player.src = '';
1276
+ if (activeUrl) StillMade.releaseMedia(activeUrl);
1277
+ activeUrl = '';
1278
+ if (!input.audio) { status.textContent = 'Choose audio above.'; return; }
1279
+ status.textContent = 'Opening audio…';
1280
+ StillMade.previewMedia(input.audio).then(preview => {
1281
+ if (started !== revision) { StillMade.releaseMedia(preview.url); return; }
1282
+ activeUrl = preview.url;
1283
+ player.src = preview.url;
1284
+ status.textContent = 'Ready to play.';
1285
+ }).catch(error => {
1286
+ if (started === revision) status.textContent = error.message;
1287
+ });
1288
+ });
1289
+ ```
1290
+
1291
+ `StillMade.releaseMedia(url)` revokes a playback copy. Release old copies before
1292
+ loading another: at most two can remain active. Input changes and closing/resetting
1293
+ the interface revoke all copies and cancel pending reads automatically. Requests
1294
+ share the image-preview queue and its limit of 60 requests per minute; only one
1295
+ image/audio/video read can run at a time. Keep the original reference for production
1296
+ runs; temporary URLs must never be saved as project assets or returned as outputs.
1297
+ `await StillMade.useOutput(outputs)` selects complete saved media references as the Block outputs without dispatching a generation run. This is available in a saved project workspace only. Every output must match the manifest, and every media identity must come from the current authorized Block input or the current trusted desktop recording session. The host rejects changed URLs, unknown assets, and non-media outputs.
1298
+
1299
+
1300
+ Files must be at most 24 MB and load within 15 seconds. Supported containers include
1301
+ MP4, WebM, Ogg, QuickTime, MP3, AAC, WAV, and FLAC; actual playback depends on browser
1302
+ codec support. Remote assets need CORS support and an appropriate media content type.
1303
+ The host omits credentials and rejects redirects. Network access remains disabled
1304
+ inside the iframe; larger media should use the existing native Editor workspace.
1305
+
1306
+
1307
+ ## Tests and interconnection
1308
+
1309
+ Include 2 to 6 small deterministic fixtures covering normal behavior and edge cases
1310
+ (at most 50 are accepted; a capability Block takes 1–3). The fields of each fixture depend on the runtime:
1311
+
1312
+ | Runtime | Each fixture |
1313
+ | --- | --- |
1314
+ | `recipe`, `javascript` | `{ "name", "input", "expected" }`; a Block with `manifest.state` adds `expectedState` and may give a starting `state` |
1315
+ | `module` | `{ "name", "input", "expected", "mocks" }`; `mocks` is optional and records what each host call answers (MODULES.md) |
1316
+ | `capability`, `comfyui` | `{ "name", "input", "expectations" }`: bounded checks of each output, never `expected` |
1317
+
1318
+ `expected` is the **output bindings object**, not one bare value and not
1319
+ `{"outputs": ...}`. Fixture comparison is exact JSON, including numeric values,
1320
+ array lengths and output keys. Compute expected values carefully; do not weaken a
1321
+ test merely to make code pass. Test defaults by omitting a defaulted input in at
1322
+ least one fixture. Include useful boundary settings for controls.
1323
+
1324
+ Connections use declared types and semantic roles, not port names. Generic image
1325
+ output → generic image input connects automatically when each has a primary port.
1326
+ If a downstream input requires a role, the upstream output must declare the same
1327
+ role. Integer → number is safe; other differing types need explicit supported
1328
+ conversion. Test a transform as image → image or text → text when appropriate so
1329
+ it can sit between compatible Blocks without manual wiring. Secondary controls
1330
+ should have defaults when practical.
1331
+
1332
+ ## Deliver, test, and import
1333
+
1334
+ For a new Block, deliver the one file `stillmade-block pack` writes for its runtime
1335
+ (the table under "Build new Blocks directly with the SDK") after `gate` passes.
1336
+ For repository adaptations, deliver one `.stillmade-block` archive using the
1337
+ [portable packaging commands](https://stillmade.shop/block-sdk/PORTABLE.md). The agent runs validation,
1338
+ fixtures and packaging. The recipient uploads the file; StillMade repeats source,
1339
+ license, security and sandbox checks, shows a preview and offers **Install**.
1340
+ Development JSON examples and hosted capabilities retain their existing review
1341
+ contracts; they do not replace the required portable archive.
1342
+
1343
+ For future edits, keep the ID and increase the semantic version before releasing
1344
+ changed source. For a remix, use a new ID and preserve
1345
+ `manifest.provenance.remixedFrom` with the source ID, version and SHA-256 digest
1346
+ when available. Never guess a digest or erase attribution.
1347
+
1348
+
1349
+ ## Optional ComfyUI implementation
1350
+
1351
+ To adapt an existing API-format ComfyUI JSON, use `inspectComfyImport(raw)` and
1352
+ `buildComfyImportPackage(raw, {manifest, inputs, outputs, tests})` from the SDK
1353
+ index. Inspection returns candidates and `execution: "not-run"`; it never runs
1354
+ Python or chooses semantic roles. LoadImage, ImageInvert, ImageScale, PreviewImage and the reviewed core diffusion nodes described below are supported. The browser import wizard can make these
1355
+ mappings and small image fixtures for the user. A visual `nodes`/`links` export
1356
+ must first be exported as API JSON in ComfyUI. Preserve licensing; the generated
1357
+ manifest retains original API source in `provenance.comfyuiImport.source`.
1358
+ Static conversion is not proof of runtime success: real host-controlled backend
1359
+ tests, a sample preview and Confirm Import remain required.
1360
+
1361
+ Choose JavaScript or recipes for offline deterministic processing. A ComfyUI
1362
+ Block supports reviewed `LoadImage`, `ImageInvert`, `ImageScale`, `PreviewImage`, `CheckpointLoaderSimple`, `CLIPTextEncode`, `EmptyLatentImage`, `KSampler`, `VAEEncode` and `VAEDecode` core nodes. Do not invent custom nodes, installed model names, API calls,
1363
+ Python, dependency installers, local file access, or a backend URL in source.
1364
+ The account chooses a public HTTPS connection outside the package.
1365
+
1366
+ Model workflows use one installed safetensors checkpoint basename (no paths/downloads), one KSampler, batch 1, at most 40 steps, and generation sides from 64 to 1024 in multiples of 8. The four-megapixel aggregate image/latent working budget still applies; 1024-square image-to-image workflows can exceed it. Text is bounded to 4000 characters without embedding-file directives. Supported samplers: euler, euler_ancestral, dpmpp_2m; schedulers: normal, karras, simple. CLIPTextEncode text can bind to a text input; seed/steps and numeric controls bind to integer/number inputs. Model names stay fixed in source. Required model names are checked on the selected backend, not their hashes or GPU readiness. Use the SDK example `comfy-text-to-image.stillmade.json` and replace its placeholder checkpoint with the user’s actual installed model. No fixture execution occurs during authoring.
1367
+
1368
+ Use `runtime: "comfyui"`, `entry: "src/comfyui.json"`, and
1369
+ `permissions: {project: [], capabilities: ["comfyui.execute"]}` in the manifest.
1370
+ The package fields are `manifest`, `comfyui`, `tests`, optional `view`; no code or
1371
+ recipe. All existing typed-port, semantic-role, UI and identity rules apply.
1372
+ `comfyui` has `schemaVersion: 1`, `prompt`, `inputs`, `outputs`:
1373
+
1374
+ ```json
1375
+ {"schemaVersion":1,"prompt":{"1":{"class_type":"LoadImage","inputs":{"image":""}},"2":{"class_type":"ImageInvert","inputs":{"image":["1",0]}},"3":{"class_type":"PreviewImage","inputs":{"images":["2",0]}}},"inputs":{"image":{"node":"1","input":"image","encoding":"uploaded-image"}},"outputs":{"image":{"node":"3","collection":"images","index":0}}}
1376
+ ```
1377
+
1378
+ Declare image input/output ports named `image`, with `primary: true` and the
1379
+ same `role: "source_image"`. Use fixture `input.image` as a small RGBA image and
1380
+ `expectations.image: {kind: "image", count: 1, width: 1, height: 1}`. ComfyUI
1381
+ fixtures use `expectations`, not `expected`. Optional `maxBytes` and lowercase
1382
+ `sha256` check the actual host-reencoded PNG. Limits: 16 nodes, 8 ports, one
1383
+ megapixel/single frame per image, four megapixels total working images. Every
1384
+ node must contribute to a declared output. LoadImage filenames stay empty;
1385
+ StillMade uploads the selected image. No secrets or connection IDs in source.
1386
+
1387
+ Offline `validate` and `pack` work; ComfyUI `pack` says `reviewRequired: true`.
1388
+ Do not claim remote tests passed. Import into StillMade, explicitly select a
1389
+ connection and run all fixtures and a sample; preview before confirming import.
1390
+ Custom UI still uses `StillMade.run(input)` but the host asks the user to choose
1391
+ and confirm remote execution. Do not build your own connection or credential UI
1392
+ inside a Block. In a saved project, the host rechecks edit access, exact-source
1393
+ review evidence and backend compatibility, then asks for Run in project. Review
1394
+ fixtures are not silently rerun. A missing review needs an explicit test/sample
1395
+ preview before a separate project run. Never send project IDs or receipts from
1396
+ guest code. ComfyUI Project Types require an account review for every embedded
1397
+ ComfyUI package through the import wizard. Unattended automation is not
1398
+ available yet.
1399
+
1400
+
1401
+ ## Declare platform compatibility
1402
+
1403
+ Optional `manifest.platforms` declares all three targets: `phone` (phone browser),
1404
+ `browser` (desktop web browser), and `desktop` (downloaded StillMade app). Each is
1405
+ `{supported: boolean, reason?: string}`. Give a 1–240 character reason for every
1406
+ unsupported target, and support at least one target. Example:
1407
+
1408
+ ```json
1409
+ {"platforms":{"phone":{"supported":false,"reason":"This interface requires the StillMade desktop app."},"browser":{"supported":false,"reason":"This interface requires the StillMade desktop app."},"desktop":{"supported":true}}}
1410
+ ```
1411
+
1412
+ Declarations never verify support. All targets stay unverified until their
1413
+ working UI and runtime checks pass. Include a usable phone layout with reachable
1414
+ controls, 32 px touch targets and 16 px inputs, or exclude Phone. For a custom view
1415
+ with multiple buttons, mark its real sample action `data-stillmade-action="run"`.
1416
+ The host clicks it and validates the actual sandbox result. Declare interface
1417
+ restrictions accurately and test every supported target. These listing indicators
1418
+ do not grant filesystem, process, recording, or other native permissions and
1419
+ cannot bypass the sandbox. Project Types inherit their enabled Blocks’ restrictions.
1420
+
1421
+ ## Optional hosted text generation
1422
+
1423
+ When the requested Block generates text with an LLM, use the declarative hosted
1424
+ runtime below. Do not implement it with `fetch` or an invented JavaScript host
1425
+ API. Return one SDK JSON file; include no provider, model, endpoint, account key,
1426
+ payment choice, test report, or saved user data. StillMade handles those after
1427
+ source review and explicit user confirmation.
1428
+
1429
+ ```json
1430
+ {
1431
+ "manifest": {
1432
+ "schemaVersion": 1,
1433
+ "sdkVersion": "0.1.0",
1434
+ "id": "example.script-draft",
1435
+ "version": "1.0.0",
1436
+ "name": "Draft a short script",
1437
+ "description": "Turn a production brief into a short narration draft using an approved text-generation call.",
1438
+ "kind": "task",
1439
+ "runtime": "capability",
1440
+ "entry": "src/capability.json",
1441
+ "license": "MIT",
1442
+ "inputs": {
1443
+ "prompt": {
1444
+ "type": "text",
1445
+ "primary": true
1446
+ }
1447
+ },
1448
+ "outputs": {
1449
+ "script": {
1450
+ "type": "script",
1451
+ "primary": true
1452
+ }
1453
+ },
1454
+ "permissions": {
1455
+ "project": [],
1456
+ "capabilities": [
1457
+ "text.generate"
1458
+ ],
1459
+ "network": [],
1460
+ "filesystem": [],
1461
+ "secrets": []
1462
+ },
1463
+ "ui": [
1464
+ {
1465
+ "control": "text",
1466
+ "port": "prompt"
1467
+ }
1468
+ ]
1469
+ },
1470
+ "capability": {
1471
+ "schemaVersion": 1,
1472
+ "operation": "text.generate",
1473
+ "prompt": {
1474
+ "$input": "prompt"
1475
+ },
1476
+ "system": "Write a concise narration script. Return only the script text.",
1477
+ "maxTokens": 1200,
1478
+ "output": "script"
1479
+ },
1480
+ "tests": [
1481
+ {
1482
+ "name": "A short opening narration",
1483
+ "input": {
1484
+ "prompt": "Write two narration sentences about a quiet forest at sunrise."
1485
+ },
1486
+ "expectations": {
1487
+ "script": {
1488
+ "kind": "script",
1489
+ "minLength": 20,
1490
+ "maxLength": 6000
1491
+ }
1492
+ }
1493
+ }
1494
+ ]
1495
+ }
1496
+ ```
1497
+
1498
+ Implement the requested instructions and fixtures, choose a creator namespace,
1499
+ and preserve the contract. `capability` has schemaVersion, operation,
1500
+ prompt, system, maxTokens, output, and optionally `context` (a list of up to three
1501
+ other read-only input names whose values are sent after the prompt as delimited
1502
+ data; unlisted inputs are never sent). Each `context` input reads a project field
1503
+ (its port declares `"context":"FIELD"`), so it is not something the person types.
1504
+ Text the person types goes in the one prompt input, plus at most one other text
1505
+ input named by `prompt.$instructions`; a view with several fields joins them into
1506
+ the prompt input. `operation` is only `text.generate`; `prompt`
1507
+ is one named `$input` reference. One prompt input: text, script, or brief. One output:
1508
+ text or script. A script/brief input requires {schemaVersion:1,text}; a generated
1509
+ script uses that shape too. Prompt max 32,000 characters, system max 8,000,
1510
+ maxTokens 1–4096, output max 32,768 characters, full package max 256 KiB. Mark primary
1511
+ ports and semantic roles accurately. No automatic scene/character extraction.
1512
+
1513
+ If the requested result should feed StillMade's Voiceover Step, declare the
1514
+ output as `script`, as in this example, and return the structured
1515
+ `{schemaVersion:1,text}` value. A plain `text` output does not become narration
1516
+ implicitly. A Project Type must embed the exact package and connect its selected
1517
+ output to `sm.voiceover@1.1.0`'s `input`, for example:
1518
+
1519
+ ```json
1520
+ {"from":{"stage":"draft","port":"script"},"to":{"stage":"narration","port":"input"}}
1521
+ ```
1522
+
1523
+ Here `draft` and `narration` are the corresponding Step IDs, not Block IDs.
1524
+ Users run the source Block, continue to Voiceover, review the proposed narration,
1525
+ and choose **Apply script** or **Keep current**. The handoff preserves audio takes
1526
+ and history while invalidating stale timings; it never generates speech
1527
+ automatically. Do not invent a Canvas node, write to protected Chat, or call a
1528
+ project-update API from guest code. If asked only for a Block, return that Block;
1529
+ do not append an invented Project Type or claim that a live handoff was tested.
1530
+
1531
+ Include 1–3 fixtures with `expectations`, not `expected`. Each named output has
1532
+ kind text|script, minLength 1–32768 and maxLength between minLength and 32768.
1533
+ Optional includes is at most 8 literal strings, each 1–256 characters. These are
1534
+ checks to run, not a claim that the provider produced them. Include no regex.
1535
+
1536
+ Optional `view` uses the same sandbox contract and `StillMade.run(input)`. It
1537
+ cannot confirm host charges or read keys. Store separate source as
1538
+ stillmade.block.json, src/capability.json, tests/fixtures.json, and optionally
1539
+ src/view.html plus optional src/view.css and src/view.js (or the older src/view.json object), or return the complete packed JSON shown above. CLI create
1540
+ <folder> capability scaffolds it. Offline validate/pack work and report live
1541
+ review required; offline test/preview cannot generate text. In StillMade, choose
1542
+ a model/payment method, review the total cost for every fixture plus one sample,
1543
+ confirm execution, inspect actual results, then Confirm Import. No paid call
1544
+ should run merely because the file is uploaded or generated.
1545
+
1546
+ This declaration implements text.generate. For supplied text-to-speech use the
1547
+ separate audio.speech contract below. Image generation uses image.generate below. Hosted video generation, arbitrary
1548
+ tools, dependencies, endpoint overrides, and automatic multi-call workflows
1549
+ remain unavailable. Deterministic recipe/JavaScript Blocks remain appropriate
1550
+ for local text or pixel transformations.
1551
+
1552
+ ## Text conversations
1553
+
1554
+ To continue a conversation in `text.generate`, add `"history":{"$input":"history"}`
1555
+ bound to one optional `json` input holding up to 20 earlier turns of
1556
+ `{"role":"user"|"assistant","content":"..."}`. They are sent before the prompt.
1557
+
1558
+ ## Structured text replies
1559
+
1560
+ For a `text.generate` Block that must return data, declare one `json` output and
1561
+ add `"schema"` (a JSON Schema subset: `type`, `description`, `title`, `enum`,
1562
+ `properties`, `required`, `additionalProperties`, `items`, `minItems`,
1563
+ `maxItems`, `minLength`, `maxLength`, `pattern`, `format`, `minimum`,
1564
+ `maximum`; at most 8 levels and 8,192 characters) to the capability. StillMade
1565
+ asks the model for exactly that shape, parses the reply and rejects anything
1566
+ that does not match. Fixtures for a json output use `{"kind":"json"}` with
1567
+ optional `includes`. Downstream Steps receive the parsed value.
1568
+
1569
+ ## Module Blocks (modern JavaScript and WebAssembly)
1570
+
1571
+ When a Block needs npm libraries, WebAssembly (including C/C++ libraries such as
1572
+ OpenCV), async work, timers, OffscreenCanvas or WebCodecs, use
1573
+ `"runtime": "module"`. The folder holds `stillmade.block.json`, `module/main.js`
1574
+ (an ES module that bundles its npm dependencies), optional `.wasm` or data files
1575
+ in `module/`, and `tests/fixtures.json` with `{name,input,expected,mocks}` (`mocks`
1576
+ optional). The rule under "SDK authoring guide", "Module Blocks" above, that main.js
1577
+ imports nothing and uses no browser APIs (no fetch, TextEncoder, timers or
1578
+ WebCodecs), is the single-file rule of StillMade's in-app builder; a module Block
1579
+ built with the SDK bundles what it imports and can use everything MODULES.md lists. The entry is
1580
+ `export default async function run(input, stillmade)` and returns the declared
1581
+ outputs. Use `stillmade.progress(value,label)`, `stillmade.log()`,
1582
+ `stillmade.signal`, `stillmade.asset(path)` for the module's own files,
1583
+ `stillmade.files.read(ref)` for saved media the Block received,
1584
+ `stillmade.files.write(data,{mediaType,name})` to save results,
1585
+ `stillmade.media.transform(request)` for on-device media work (`audio.decode`
1586
+ to mono PCM samples, `audio.extract`, `audio.normalize`, `video.cut` with
1587
+ ordered `keep` ranges, `video.concat`, `video.resize`, `image.thumbnail`; the
1588
+ results are saved references), with helpers such as `silentRanges`,
1589
+ `keepRanges` and `integratedLoudness` from `@stillmade/block-sdk/media` (bundle
1590
+ `stillmade-sdk/packages/block-sdk/media-transform.js` from the npm package, or
1591
+ `<sdk>/packages/block-sdk/media-transform.js` from the extracted download),
1592
+ `stillmade.actions.run(action)` for declared actions such as `media.generate`,
1593
+ and `stillmade.hosted.run(operation,request)` for hosted operations listed in
1594
+ `permissions.capabilities`. There is no network, DOM or app storage. Never
1595
+ include keys or prices. Pack with `stillmade-block pack` and import the
1596
+ `.stillmade-module.json`. See `MODULES.md`.
1597
+
1598
+ ## Frame views (any interface code)
1599
+
1600
+ When the interface needs React or another framework, SVG, canvas or WebGL,
1601
+ pointer drag, timers or loops, make it a frame view: `src/view.html` (for
1602
+ example `<div id="root"></div>`), `src/view.css`, `src/view.js` bundled as ONE
1603
+ classic script (esbuild `--format=iife`), and `src/view.config.json` with
1604
+ `{"runtime":"frame"}`. It uses the same `StillMade` object (`getShared`,
1605
+ `onShared`, `updateShared`, `run`, `input`, `onInput`) plus
1606
+ `StillMade.asset(path)` for files in `src/view-files/`. Keep each independently
1607
+ edited value in its own shared field. Color with `var(--bg)`, `var(--fg)`,
1608
+ `var(--accent)` and the other theme variables. There is no network, no
1609
+ `eval`, no WebAssembly on the page and no navigation; one task may run 250 ms,
1610
+ so move long work into a Worker or the Block runtime. See `FRAME_VIEWS.md`.
1611
+
1612
+ ## Files, documents, tables, links, dates and colors
1613
+
1614
+ Use the data types instead of JSON text: `file` (a saved PDF, TXT, CSV,
1615
+ Markdown, JSON or ZIP: `{kind:"file",assetId,versionId,mimeType,url,name,bytes}`),
1616
+ `document` (`{schemaVersion:1,format:"markdown",text}`), `table`
1617
+ (`{columns:["Name"],rows:[{"Name":"x"}]}`), `url`, `date` (`2026-10-09`) and
1618
+ `color` (`#1a2b3c`). Bind controls in `manifest.ui`: `file`, `richtext`,
1619
+ `grid` (a table editor), `url`, `date`, `color`, `list`. Present outputs with `table`,
1620
+ `document`, `chart` (`x`, `y`, `chart:"bar"|"line"`), `code`, `player` or
1621
+ `download`. A module Block reads an uploaded file with
1622
+ `await stillmade.files.read(input.file)` and builds values with `createTable`
1623
+ and `createDocument` from `@stillmade/block-sdk/data` (bundle
1624
+ `stillmade-sdk/packages/block-sdk/data-values.js` from the npm package, or
1625
+ `<sdk>/packages/block-sdk/data-values.js` from the extracted download). To hand shots to the
1626
+ Shots workspace, output `{"type":"shot[]","semantic":"production_shot_plan"}`
1627
+ where each shot has `schemaVersion:1` and a unique `id`.
1628
+
1629
+ ## Outside APIs with the person's own account
1630
+
1631
+ A module Block may call outside APIs. List each origin in
1632
+ `permissions.network` (`"https://api.example.com"`, or
1633
+ `{"origin":"https://api.airtable.com","auth":{"scheme":"bearer","label":"Airtable personal access token","help":"https://airtable.com/create/tokens"}}`;
1634
+ schemes `bearer`, `header` with `name`, `query` with `name`, or `oauth2` with a
1635
+ reviewed `app` such as `notion`) and call
1636
+ `await stillmade.net.fetch(url, {method, headers, body})`, which returns a
1637
+ `Response`. Never put keys in code or headers: StillMade asks the person for
1638
+ theirs and adds it on its servers. For a published OpenAPI adapter, list
1639
+ `"connections":["app:openapi:notion"]` and call
1640
+ `await stillmade.connections.call('app:openapi:notion','retrievePage',{path_page_id:id})`.
1641
+
1642
+ ## Project storage, reads and assets
1643
+
1644
+ A module Block keeps its own data with `stillmade.storage`: `get(key)`,
1645
+ `set(key, json)` (up to 1 MiB), `putFile(key, bytes, {mediaType})` and
1646
+ `getFile(key)` (up to 64 MiB, returns `{data, mediaType, bytes}` or `null`),
1647
+ `list(prefix)`, `remove(key)`. Keys look like `brand/logo.png`. Set
1648
+ `"storage":{"schemaVersion":1,"scope":"project","desktop":"device-unmetered","cloud":"metered"}`
1649
+ to share one store across the whole project (`"workspace"` keeps one per Step).
1650
+ To read the project, declare `project.read` and `context.<field>.read` and call
1651
+ `await stillmade.project.read(['timeline'])`. To add media to the project,
1652
+ declare `asset.create` and call `stillmade.assets.create(await stillmade.files.write(bytes,{mediaType:'image/png'}),{name})`.
1653
+ Change the project only through proposals (`timeline-edit` or `workspace-edit`
1654
+ outputs) that people review.
1655
+
1656
+ ## Running on its own
1657
+
1658
+ A recipe, JavaScript or hosted Block may declare `triggers`:
1659
+ `{"event":"media.saved","input":"<primary media input>"}`,
1660
+ `{"event":"schedule","everySeconds":3600}`, `{"event":"webhook","eventKind":"order.created"}`,
1661
+ `{"event":"project.completed"}` or `{"event":"provider","appId":"stripe","eventKind":"charge.succeeded"}`.
1662
+ Nothing runs until the project owner turns a trigger on. Webhook and event
1663
+ Blocks read the event from an input with `"semantic":"workflow_event"` and
1664
+ `"sources":["project"]`. Hosted Blocks run unattended only inside the credits
1665
+ the owner approves; proposals always wait for a person.
1666
+
1667
+ ## Optional media analysis
1668
+
1669
+ To ask about saved media, use runtime `capability`, permission `media.analyze`,
1670
+ one `video`, `image` or `image[]` (one to four) input, one `text` or `json`
1671
+ output, and
1672
+ `{schemaVersion:1,operation:"media.analyze",media:{$input:"clip"},question:"what to check",output:"report"}`.
1673
+ A video Block may add `range:{start:{$input:"start"},end:{$input:"end"}}` bound
1674
+ to two optional `number` inputs (at most 12 seconds apart). A json output
1675
+ receives `{schemaVersion:1,items:[{kind,summary,timeline,observations,detectedText,issues,confidence}]}`.
1676
+ Each analyzed item is one StillMade-credit call. An `image` or `image[]` Block that only rates photos may add
1677
+ `detail:"check"`: each photo is read smaller and gets one short `summary`, and a call of up to four photos costs one photo check.
1678
+
1679
+ ## Optional web reading
1680
+
1681
+ To read a public page, use runtime `capability`, permission `web.fetch`, one
1682
+ `text` URL input and one `text` output, with
1683
+ `{schemaVersion:1,operation:"web.fetch",url:{$input:"url"},instruction:"what to extract",output:"summary"}`.
1684
+ To search, use permission `web.research`, one `text` or `brief` topic input and
1685
+ one `text` or `json` output, with
1686
+ `{schemaVersion:1,operation:"web.research",topic:{$input:"topic"},output:"findings"}`
1687
+ (a json output receives `{schemaVersion:1,summary,sources:[{url,title}]}`).
1688
+ StillMade reads the web; keep `permissions.network` empty and never include
1689
+ headers, cookies, keys or prices. Both are paid with StillMade credits.
1690
+
1691
+ ## Optional hosted transcription
1692
+
1693
+ For stored audio to timed text, use runtime `capability`, permission `audio.transcribe`, exactly one `audio` input, one `text` language input, and one `transcript` output. The descriptor is exactly `{schemaVersion:1,operation:"audio.transcribe",audio:{$input:"audio"},language:{$input:"language"},output:"transcript"}`. Give language the default `auto` and provide both an `asset` control for audio and a language `select`. Fixtures use `{kind:"transcript",minWords,maxWords}` expectations. Do not fabricate provider output, embed credentials, choose endpoints, or request network/filesystem/secrets. StillMade uses owner-scoped stored media; the person running it chooses StillMade transcription on credits or OpenAI Whisper with their own key. Return one complete package and validate it with the SDK. See `TRANSCRIPTION.md` and `examples/transcribe-audio.stillmade.json`.
1694
+
1695
+ ## Optional hosted music and sound effects
1696
+
1697
+ For music or a sound effect from a written prompt, use runtime `capability` with
1698
+ permission `audio.music` or `audio.sfx`, one `text`, `script` or `brief` input,
1699
+ and one `audio` output. The descriptor is
1700
+ `{schemaVersion:1,operation:"audio.music",prompt:{$input:"brief"},output:"music",settings:{durationSec:30}}`
1701
+ (or `operation:"audio.sfx"`). `settings.durationSec` is optional: music is 5 to
1702
+ 240 whole seconds, sound effects 0.5 to 22 seconds in tenths. The person running
1703
+ the Block can change the length and pays with StillMade credits at the app's
1704
+ music and sound-effect prices. Outputs are measured MP3 or WAV references.
1705
+ Fixtures use `{kind:"audio",format:"any"|"mp3"|"wav",minDuration,maxDuration,minBytes,maxBytes}`
1706
+ with durations up to 1,800 seconds and bytes from 45 to 134217728. Never include
1707
+ provider, model, payment, endpoint, credentials or fabricated output. See
1708
+ `packages/block-sdk/audio-generation-example.js` for `audioMusic` and `audioSfx`.
1709
+
1710
+ ## Optional hosted speech
1711
+
1712
+ When the request is to speak supplied text, use this complete declarative
1713
+ package shape. One input is `text` or `script`; one output is `audio`. A script
1714
+ value contains `{schemaVersion:1,text}`. Text must be nonblank and no longer
1715
+ than 3,800 characters. No script drafting, splitting, or multiple provider calls
1716
+ are implied by this operation.
1717
+
1718
+ ```json
1719
+ {
1720
+ "manifest": {
1721
+ "schemaVersion":1,"sdkVersion":"0.1.0",
1722
+ "id":"example.narrate-script","version":"1.0.0",
1723
+ "name":"Narrate a script",
1724
+ "description":"Turn a short script into a reviewed speech recording using a host-selected voice.",
1725
+ "kind":"task","runtime":"capability","entry":"src/capability.json","license":"MIT",
1726
+ "inputs":{"script":{"type":"script","primary":true}},
1727
+ "outputs":{"audio":{"type":"audio","primary":true}},
1728
+ "permissions":{"project":[],"capabilities":["audio.speech"],"network":[],"filesystem":[],"secrets":[]},
1729
+ "ui":[]
1730
+ },
1731
+ "capability":{"schemaVersion":1,"operation":"audio.speech","text":{"$input":"script"},"output":"audio"},
1732
+ "tests":[{
1733
+ "name":"Read a short welcome",
1734
+ "input":{"script":{"schemaVersion":1,"text":"Welcome to the quiet forest."}},
1735
+ "expectations":{"audio":{"kind":"audio","format":"wav","minDuration":0.1,"maxDuration":30,"minBytes":46,"maxBytes":1500000}}
1736
+ }]
1737
+ }
1738
+ ```
1739
+
1740
+ The capability object contains `schemaVersion`, `operation`, `text`, and
1741
+ `output`, and optionally `settings:{voice,speed}` suggesting a StillMade catalog
1742
+ voice id (for example `asteria`) and a speed from 0.25 to 4. The person running
1743
+ the Block can change both. Its text is one exact `$input` binding; its output
1744
+ names the single declared audio port. Do not include provider, payment,
1745
+ endpoint, credentials, actual output, or receipts anywhere in the package.
1746
+ Use your own creator ID and 1–3 fixtures. Every audio expectation requires exactly
1747
+ the six fields shown. Duration bounds are finite seconds,
1748
+ `0 <= minDuration <= maxDuration <= 1400`, with positive `maxDuration`. Byte
1749
+ bounds are integers, `46 <= minBytes <= maxBytes <= 67108864`. Do not use exact
1750
+ audio equality, hashes, transcript assertions, or regex as fixture expectations.
1751
+
1752
+ The host output is exactly a reference:
1753
+ `{kind:"audio",assetId,versionId,url,mimeType,duration,bytes,sampleRate,channels}`.
1754
+ StillMade catalog voices return measured MP3 or WAV; OpenAI TTS-1 returns
1755
+ 24,000 Hz mono WAV. The trusted host measures duration and size from the actual
1756
+ bytes and stores the file. The package does not create a plausible-looking media URL or metadata.
1757
+ Metadata alone cannot prove that a file exists or passes the audio checks.
1758
+
1759
+ Offline validate/pack check source and report live review required. Offline
1760
+ test/preview cannot generate speech. No live provider speech test has been
1761
+ completed for this implementation; schema checks and explicit mock tests are
1762
+ not provider evidence. In StillMade, choose StillMade voices (credits, the same
1763
+ catalog as Voiceover) or OpenAI TTS-1, a voice, and speed (0.25×–4×), review costs for all fixtures plus a sample,
1764
+ then explicitly run them. Listen to the AI-generated recordings, finish review,
1765
+ and Confirm Import. Project generation requires its own quote and confirmation.
1766
+ Nothing is billed merely because a package is uploaded or authored.
1767
+
1768
+ The result can feed compatible SDK audio inputs and is playable in the host.
1769
+ After a confirmed speech run in a saved project and **Use this audio** acceptance,
1770
+ the user can open **Media** in the Editor, find **Audio from Blocks**, listen,
1771
+ position the playhead, and choose **Add at playhead** on desktop or mobile. This explicitly
1772
+ adds an Editor audio item with the recording's measured duration; it does not
1773
+ generate speech again. New items require a current accepted project result.
1774
+ Earlier inserted items keep their chosen recording when the source Block is
1775
+ rerun or removed.
1776
+
1777
+ Editor insertion does not replace the native Voiceover master recording, select
1778
+ its takes, or change word timings, transcripts, or shared narration. Voiceover's
1779
+ primary input remains `script`. Declare the `audio.speech` package normally;
1780
+ do not invent an audio-to-Voiceover adapter or a guest timeline-writing API.
1781
+ An optional custom `view` uses the existing `StillMade.run` host flow; there is
1782
+ no `StillMade.previewAudio` API. Return one complete `.stillmade.json` file.
1783
+
1784
+
1785
+ ## Optional hosted image generation
1786
+
1787
+ For a visual prompt → image Block, return this declarative shape with your own
1788
+ id, name, description and realistic fixtures. Do not generate JavaScript that
1789
+ calls OpenAI or reads keys. This is the complete portable package shape:
1790
+
1791
+ ```json
1792
+ {
1793
+ "manifest": {
1794
+ "schemaVersion": 1,
1795
+ "sdkVersion": "0.1.0",
1796
+ "id": "example.generate-image",
1797
+ "version": "1.0.0",
1798
+ "name": "Generate an image",
1799
+ "description": "Generate a square PNG from a short visual prompt using an approved host request.",
1800
+ "kind": "task",
1801
+ "runtime": "capability",
1802
+ "entry": "src/capability.json",
1803
+ "license": "MIT",
1804
+ "inputs": {
1805
+ "prompt": {
1806
+ "type": "text",
1807
+ "primary": true
1808
+ }
1809
+ },
1810
+ "outputs": {
1811
+ "image": {
1812
+ "type": "image",
1813
+ "primary": true
1814
+ }
1815
+ },
1816
+ "permissions": {
1817
+ "project": [],
1818
+ "capabilities": [
1819
+ "image.generate"
1820
+ ],
1821
+ "network": [],
1822
+ "filesystem": [],
1823
+ "secrets": []
1824
+ },
1825
+ "ui": [
1826
+ {
1827
+ "control": "text",
1828
+ "port": "prompt"
1829
+ }
1830
+ ]
1831
+ },
1832
+ "capability": {
1833
+ "schemaVersion": 1,
1834
+ "operation": "image.generate",
1835
+ "prompt": {
1836
+ "$input": "prompt"
1837
+ },
1838
+ "output": "image"
1839
+ },
1840
+ "tests": [
1841
+ {
1842
+ "name": "A simple forest illustration",
1843
+ "input": {
1844
+ "prompt": "A simple illustration of a quiet forest with clear dark outlines and soft green colors."
1845
+ },
1846
+ "expectations": {
1847
+ "image": {
1848
+ "kind": "image",
1849
+ "format": "png",
1850
+ "width": 1024,
1851
+ "height": 1024,
1852
+ "minBytes": 67,
1853
+ "maxBytes": 12000000
1854
+ }
1855
+ }
1856
+ }
1857
+ ]
1858
+ }
1859
+ ```
1860
+
1861
+ Declare one input of type text, script, or brief, optionally one `image` or
1862
+ `image[]` input of reference images, and one image output. Text is a string;
1863
+ script/brief is `{schemaVersion:1,text}`. The prompt is nonblank and at most
1864
+ 32,000 characters. The descriptor has schemaVersion, operation, prompt and
1865
+ output, and optionally `settings` (default `{model,aspectRatio,resolution,quality}`),
1866
+ `references` (`{"$input":"<image input>"}`), `negativePrompt` (≤2,000 characters)
1867
+ and `seed` (0–2147483647). Keep network/filesystem/secrets empty; existing scoped
1868
+ project-context read permissions may be declared when needed.
1869
+
1870
+ Models: nano-banana-2, nano-banana-2-lite, nano-banana-pro, gpt-image-2,
1871
+ gpt-image-1.5, kling-image-v3, seedream-v4.5 — the same catalog the app uses
1872
+ (`packages/block-sdk/generation-catalog.js` lists each model's aspect ratios,
1873
+ resolutions, qualities and reference support; an axis a model lacks is
1874
+ "default"). Only the Nano Banana family and GPT Image 2 accept references, up to
1875
+ 8 saved images. Users pay with StillMade credits at StillMade's price; never put
1876
+ payment, keys, endpoints or prices in the package. The user may change model
1877
+ and settings before running.
1878
+
1879
+ The actual output is
1880
+ `{kind:"image",assetId,versionId,url,mimeType:"image/png",width,height,bytes}`.
1881
+ The host decodes and measures the actual image, saves one PNG and verifies the
1882
+ saved hash: up to 8192 pixels a side, 40 megapixels and 64 MiB. Each fixture
1883
+ expectation has kind image, format png, `minWidth`, `maxWidth`, `minHeight`,
1884
+ `maxHeight`, `minBytes` and `maxBytes` as ordered integer bounds.
1885
+ These checks establish dimensions/format/size, not whether an image depicts the
1886
+ requested subject; the user reviews the real picture.
1887
+
1888
+ Upload/schema validation/offline packaging performs no generation and grants
1889
+ no installation. Offline test/preview cannot execute this capability. In
1890
+ StillMade the user reviews model/quality/cost for every fixture plus a separate
1891
+ sample, confirms the provider run, previews results, finishes review, then
1892
+ confirms import. Project execution requires a separate current quote and
1893
+ **Use this image** acceptance. Do not claim a live provider test passed from
1894
+ static/offline checks. Uncertain submissions are not automatically repeated.
1895
+
1896
+ Accepted image references connect to compatible image inputs through the
1897
+ host's safe image resolver and appear in the Editor's visual media inventory.
1898
+ An optional view uses `StillMade.run` and the normal host preview flow; it gains
1899
+ no provider API or receipt-writing API. Return one complete `.stillmade.json`
1900
+ file. Its source, fixtures and SDK remain downloadable for outside editing.
1901
+
1902
+
1903
+ ## Test the whole Project Type in StillMade
1904
+
1905
+ After importing a Project Type, use **Preview connected workflow** in the builder.
1906
+ Provide sample inputs keyed by Step ID and sample project context for declared
1907
+ context ports. Actual upstream output overrides downstream fixture inputs.
1908
+ Hosted Blocks require an account-bound package review and a separately confirmed
1909
+ model/payment choice for each connected execution. Never hard-code review output
1910
+ as production output. Generated media is verified before recipe image decoding;
1911
+ recipe images must stay within one megapixel. Preview context cannot mutate a
1912
+ real project. Offline CLI checks do not execute hosted providers or certify the
1913
+ whole hosted workflow. Keep each Block's own fixtures and sample execution tests.
1914
+
1915
+ ### Optional saved memory
1916
+
1917
+ Only JavaScript Blocks can declare `manifest.state = {scope:"step", version:1,
1918
+ initial:{count:0}}` and include `state.step` in `permissions.project`. Their code
1919
+ receives a mutable `state` object alongside `input`: mutate its properties and
1920
+ return ordinary declared outputs. Do not reassign `state`. Each fixture must include
1921
+ `expectedState:{version:1,value:{count:1}}`; optionally provide an initial fixture
1922
+ `state` envelope to test continuation. State values must be plain JSON objects up
1923
+ to 64 KiB. Every production placement has independent memory pinned to its exact
1924
+ build, saved with accepted outputs. Preview memory is temporary and resets with
1925
+ Reset preview. Failed/cancelled runs cannot advance project memory. Single-Block batches use provisional sequential memory until the user accepts a
1926
+ result. Connected batches carry independent memory per placement in selection order; accepting the completed results validates and saves that memory together. Single-Block background automation supports saved memory when shared Canvas editing is enabled: outputs and memory commit atomically, with conflicts retried from current memory. Connected background workflows also support stateful sandboxed Blocks with shared Canvas enabled. Starting memory is pinned; all memory changes commit together when the complete job succeeds. A failed prefix retains provisional results without advancing shared memory. Do not store API keys or media
1927
+ bytes in this state. External hosts pass `{state:{version:1,value:{count:0}}}` to
1928
+ `runPackageAsync` and validate/accept the returned `result.state` with outputs.
1929
+
1930
+
1931
+ Offline Project Type packaging also supports ComfyUI-only and mixed ComfyUI,
1932
+ hosted-capability, and local packages. Use `stillmade-block pack
1933
+ workflow.json workflow.stillmade.json`. This statically checks every embedded
1934
+ package but executes no fixtures or providers; its report must stay unverified
1935
+ until StillMade runs the import review and the user confirms installation.
1936
+
1937
+
1938
+ ### Editor proposals
1939
+ To edit existing timeline clips, use output type `timeline-edit` and request
1940
+ `context.timeline.read` plus `timeline.propose`. Return
1941
+ `{schemaVersion:1,title,timelineId:input.timeline.activeTimelineId,commands}`.
1942
+ Each command is `{collection:"clips"|"audioItems",operation,before,values}`.
1943
+ `before` must be the exact unmodified target object from the scoped timeline
1944
+ input. Operations: move `{start}`, trim `{trimStart,duration}`, volume `{volume}`
1945
+ (0–200 percent), mute `{muted}`, remove `{}`. Maximum 100 commands, one per
1946
+ item. Split uses `operation:"split",values:{at,newId}` with an unused clip ID and a cut at least 0.05 seconds inside the clip. Users review and apply the proposal in the Editor; mismatched targets,
1947
+ locked tracks and invalid timings are rejected. Do not mutate the input or
1948
+ claim the proposal has already changed the project. Declare realistic fixtures
1949
+ with complete target snapshots and an activeTimelineId.
1950
+
1951
+ Caption proposals use `collection:"captions"`. Add with `operation:"caption.add"`,
1952
+ `before:null`, and `values:{id,trackId,text,start,end}`. Update with
1953
+ `operation:"caption.update"`, the complete original caption as `before`, and
1954
+ `values:{text,start,end}`; remove uses `{}`. Use an unused ID, an unlocked caption
1955
+ track from context (or `captions` to create the standard track if absent), nonempty
1956
+ text up to 5,000 characters, and valid seconds with end greater than start.
1957
+ New captions use standard Editor styling; updates preserve existing styles.
1958
+
1959
+
1960
+ ## On-demand desktop capabilities
1961
+
1962
+ Block UIs may call `StillMade.desktop(operation, args)` through the trusted host. Declare explicit `permissions.desktop` scopes: `screen.capture`, `cursor.track`, `camera.capture`, `microphone.capture`, `clipboard.read`, `clipboard.write`, `media.pick`, `notifications.show`, or `power.keep-awake`. Import/install never grants access; the host prompts on use, and access is revoked when the workspace closes. Use `record.start` with `{kind:"screen",cursor:true}`, then `record.stop` to receive `{asset,cursor,displays,coordinates}`; `record.cancel` discards capture and `session.close` revokes access. Records are bounded to 30 minutes/128 MiB and return device-local media references. System audio is Windows-only in this implementation. These APIs are unavailable in the browser and cannot be used from pure runtime code. See [desktop capability reference](../reference/desktop-capabilities.md) for all operations, limits, and a recording example.
1963
+
1964
+
1965
+ SDK folders can also be imported directly from public GitHub repositories. In StillMade choose SDK folder, enter its relative path (or `.` for the repository root), and select a branch, tag or commit. Keep the standard manifest, runtime, fixture, and optional split interface files in that folder. Files are pinned to one commit and checked before the ordinary sandbox review and confirmation. Keep combined source below 1 MB, or use folder/ZIP upload for larger packages.
1966
+
1967
+ ## Complete dynamic Editor interface example
1968
+
1969
+ The SDK includes `examples/timeline-mixer.stillmade.json` and its source in
1970
+ `packages/block-sdk/timeline-mixer.js`. Use it as a working reference for dynamic
1971
+ lists of project items: individual clip level controls, reset, empty states,
1972
+ input revision handling, isolated execution, and typed `timeline-edit` output.
1973
+ Its `levels` input is a JSON array of `{key, volume}` entries; keys identify a
1974
+ collection and clip ID. The host supplies the timeline through scoped context.
1975
+ The interface never applies edits itself: users review the resulting proposal
1976
+ before the Editor checks current clip versions and records normal undo history.
1977
+
1978
+ ## Project Type onboarding
1979
+
1980
+ A Project Type can declare project defaults and up to eight setup questions. These are optional additions to the schema-1 Project Type contract; they do not replace protected Chat, configure provider credentials or start production work.
1981
+
1982
+ ```json
1983
+ {
1984
+ "defaults": {"aspectRatio": "9:16", "brief": "Create a short product launch video."},
1985
+ "onboarding": [
1986
+ {"id": "product", "label": "What is the product?", "type": "text", "required": true},
1987
+ {"id": "audience", "label": "Who is it for?", "type": "textarea"},
1988
+ {"id": "tone", "label": "Tone", "type": "select", "options": ["Clear and direct", "Playful"], "default": "Clear and direct"}
1989
+ ]
1990
+ }
1991
+ ```
1992
+
1993
+ Merge these fields into an existing valid Project Type with `schemaVersion`, `id`, `version`, `name`, `description` and its enabled `stages`. This fragment is not a standalone package.
1994
+
1995
+ `defaults.aspectRatio` accepts `16:9`, `9:16` or `1:1`; `defaults.brief` is at most 4,000 characters. Question IDs are unique lowercase identifiers up to 40 characters. Labels are at most 120 characters. Text and textarea answers are at most 1,000 characters. A select question declares 2–12 unique options of at most 120 characters; its default must match an option. `required` is optional and boolean.
1996
+
1997
+ The Project Type Builder exposes these fields under **Project defaults & starting questions**. Using the Project Type opens one project setup dialog. The host records the chosen project name and format, and retains the brief plus labeled answers in the existing project's production context (`bible.extraInfo`). Script Writer receives this brief. Answers are project data, not edits to a marketplace release or its version identity; they are not published or sent to acquisition analytics. Read Blueprint/project context only through the permissions already documented by the SDK.
1998
+
1999
+ The empty Project Type starter is an editable draft. Add an enabled Block before validation, runtime execution, or publication; empty drafts are never installable releases.
2000
+
2001
+ ## Publishing and release management
2002
+
2003
+ Use **Publish version** from the existing builder. One review dialog covers Community, Marketplace review, or private access where the account permits it, plus runtime, license, preview and redistribution consent. Marketplace selection publishes a Community release and requests review; it does not grant curated status. If review submission fails, the successful Community release is kept and can be submitted again.
2004
+
2005
+ The same publishing panel controls public cover media, full description, category, tags, use cases and version notes. These change presentation only. The released title, short description, typed contracts, source and license remain tied to the immutable package version.
2006
+
2007
+ My builds includes original and forked drafts, imports, installed versions, every own release and private releases. A version can be marked deprecated or delisted, with a public notice and an optional replacement version. It stops appearing in discovery and becomes ineligible for indexing; direct version URLs, source downloads and existing pinned projects remain available unless a separate security revocation applies.
2008
+
2009
+ ## Project Type production completion
2010
+
2011
+ An optional `outcome` field declares the finished result required by this exact Project Type version:
2012
+
2013
+ ```json
2014
+ {"outcome":{"type":"export","dedupe":"material_project_state","minimumDurationSec":3}}
2015
+ ```
2016
+
2017
+ `type` is `export`, `delivery`, or `project_outcome`. `dedupe`, when present, must be `material_project_state`; that is also its default. `minimumDurationSec` is optional, finite, between 0 and 86,400 seconds. A positive minimum requires host-measured media duration. Omit it for text or editable project artifacts.
2018
+
2019
+ Configure this in **Project defaults & starting questions → Production completion**. Omitting `outcome` preserves the existing host completion behavior. These settings never launch exports, create revenue or mark a client action as successful. Version the Project Type when changing its production rule.
2020
+
2021
+ Only a trusted completion adapter can supply `materialStateHash`, measured duration, success, retained-source lineage and the actual outcome kind. The accounting qualifier reads the rule from the exact released Project Type; browser-provided contributors and success flags are not accepted. Matching material states deduplicate under the payer-period policy. Verified editable-project deliveries can satisfy `project_outcome`; a local MP4 upload alone does not prove an executed media export.
2022
+
2023
+ ## Hosted image analysis
2024
+
2025
+ For image analysis, use runtime `capability`, permission `image.describe`, exactly one `image` input (or one `image[]` input of one to four images to compare together), and exactly one `text` output. The descriptor is `{schemaVersion:1,operation:"image.describe",image:{$input:"imagePort"},instruction:"bounded package-owned instructions",maxTokens:1..4096,output:"textPort"}`. StillMade loads only owned media and controls the provider, model, payment, approval, and execution receipt. Do not add URLs, credentials, network permission, or executable code. Tests use bounded RGBA pixels and nondeterministic text expectations; they never claim a fabricated description is a live provider result. See `IMAGE_DESCRIPTION.md` and `examples/describe-image.stillmade.json` in the SDK.
2026
+
2027
+ ## Hosted timeline proposals
2028
+
2029
+ For LLM-directed editing, use runtime `capability`, permission `timeline.propose`, a current `timeline` input, up to three additional typed context inputs, and exactly one `timeline-edit` output. The descriptor is `{schemaVersion:1,operation:"timeline.propose",context:{inputName:{$input:"inputName"}},instruction:"bounded package-owned editing policy",maxTokens:1..4096,output:"editPort"}`. Bind every input exactly once. Declare `context.timeline.read` and `timeline.propose`. StillMade controls the provider, payment review, strict JSON request, schema validation, stale-before checks, preview, and Apply. Fixtures use `{kind:"timeline-edit",minCommands:1,maxCommands:100}` expectations. See `TIMELINE_PROPOSALS.md` and `examples/timeline-polish.stillmade.json` in the SDK.
2030
+
2031
+ ## Generate media from JavaScript
2032
+
2033
+ For a JavaScript Block that needs generated images, videos, voice recordings,
2034
+ music or sound effects (several at once, or decided by code), declare
2035
+ `permissions.actions: ["media.generate"]` and
2036
+ return `{schemaVersion:1,kind:"stillmade.media-action",operation:"media.generate",resultPort:"<asset[] output>",request:{items:[...]}}`
2037
+ from one json output. Items (1–50, unique `key`) are
2038
+ `{key,operation:"image"|"video",prompt,settings?,references?,firstFrame?,lastFrame?,negativePrompt?,seed?}`
2039
+ with catalog settings from `packages/block-sdk/generation-catalog.js`
2040
+ (references only for image models that edit from references; frames only for
2041
+ video), or `{key,operation:"speech",prompt,settings?:{voice,speed}}` (prompt is
2042
+ the text to speak, up to 3,800 characters; a catalog voice id such as `adam`;
2043
+ speed 0.25–4), `{key,operation:"music",prompt,settings?:{durationSec}}` (5–240
2044
+ whole seconds) or `{key,operation:"sfx",prompt,settings?:{durationSec}}`
2045
+ (0.5–22 seconds in tenths). A video item with `settings.aspectRatio` (`"9:16"`)
2046
+ and no `model` gets the cheapest catalog model that makes that shape. StillMade
2047
+ asks once for the whole batch, showing the most it can cost in StillMade
2048
+ credits, so put every item one run needs (one clip per photo) in one batch;
2049
+ never include prices, providers or keys. The result port receives
2050
+ `[{key,kind,assetId,versionId,url,duration?}]` with `kind` `image`, `video` or
2051
+ `audio`; `duration` (seconds) is the length a video, music or sound effect asked
2052
+ for, and speech has none (decode it with `audio.decode` to time it).
2053
+
2054
+ ## Hosted video generation
2055
+
2056
+ Use `runtime: "capability"` and `permissions.capabilities: ["video.generate"]`. Declare one text, script or brief prompt input, up to two `image` inputs for the first and last frame, and one video output. The capability file is:
2057
+
2058
+ ```json
2059
+ {"schemaVersion":1,"operation":"video.generate","prompt":{"$input":"prompt"},"firstFrame":{"$input":"start"},"settings":{"model":"kling-v3-turbo","duration":5,"aspectRatio":"16:9"},"output":"video"}
2060
+ ```
2061
+
2062
+ Optional fields: `settings` (default `{model,duration,resolution,aspectRatio,generateAudio}`), `firstFrame`, `lastFrame` (needs firstFrame and a model with last-frame support), `negativePrompt`, `seed`. Models: seedance-2.0, seedance-2.0-fast, seedance-2.0-mini, kling-v3-turbo, wan-2.6-flash, veo-3.1-lite, vidu-q3-turbo, wan-2.7 (needs a first frame) — see `packages/block-sdk/generation-catalog.js` for each model's durations, resolutions and aspect ratios. Never include endpoints, keys, prices or payment. Users pay with StillMade credits at StillMade's per-second price. Public trials use their separately reviewed acquisition policy and never account BYOK.
2063
+
2064
+ The canonical output has `kind:"video"`, bounded `assetId`/`versionId`, a stored HTTPS or host media `url`, `mimeType:"video/mp4"`, measured `width`, `height`, `duration` in seconds and `bytes`. SDK limits are 60 seconds, 256 MiB, at most 3,840 pixels on either axis and 8,294,400 pixels per frame. Validating this shape does not verify ownership or real video bytes; the host must inspect, store and verify them.
2065
+
2066
+ Fixtures use `kind:"video"`, `format:"mp4"` and explicit `minDuration`, `maxDuration`, `minBytes`, `maxBytes`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight` bounds. See `packages/block-sdk/video-generation-example.js`. Provider fixtures require approval; they are not deterministic equality tests. `validateGeneratedVideoReference`, `defaultCapabilityExpectations` and `validateCapabilityResult` are available through the SDK. Media receipts bind the owner, run, invocation, storage key and SHA-256 digest to the complete output reference. Guest code cannot issue those receipts.
2067
+
2068
+ A host advertises only enabled operations. A valid package does not imply every deployment has video generation configured; unavailable hosts must reject it before billing or dispatch. Public video results use the same private media/claim lifecycle as other trial outputs and are retained when continuing in an account.
2069
+
2070
+
2071
+ ## SDK compatibility declarations
2072
+
2073
+ The runtime remains SDK `0.1.0`; exact declarations remain valid. A Block may also declare a bounded stable range such as `^0.1.0`, `~0.1.0`, or `>=0.1.0 <0.2.0`. `sdkCompatibility(declared)` reports `supported`, `unsupported` or `invalid` against that actual runtime version. `validateManifest` enforces the same result. A supported range never bypasses permissions, runtime availability, source admission or fixtures.
2074
+
2075
+ Supported grammar is an exact stable three-part version, caret, tilde, or up to four space-separated comparator intersections (`=`, `>`, `>=`, `<`, `<=`). Numeric components are bounded to 999999. Tags, prereleases, wildcards, alternatives and hyphen ranges are unsupported. Major-zero caret rules apply: `^0.1.0` excludes `0.2.0`; `^0.0.3` excludes `0.0.4`. A declaration is preserved verbatim in its package identity; changing it requires a new immutable release. Do not widen it merely to silence an incompatibility.
2076
+
2077
+ This is compatibility checking, not automatic code adaptation or a promise of future SDK support. Explicit reviewed Step-state migration routes are documented separately; changing an SDK range never runs a migration or changes project pins.
2078
+
2079
+
2080
+ ### Authored state migration routes
2081
+
2082
+ A new JavaScript Block release can optionally declare `manifest.state.migrations`
2083
+ for explicit upgrades of its Step memory. A route's `fromVersion` is an older
2084
+ positive state schema version; its destination is the enclosing `state.version`.
2085
+ For example, a declaration for state version 2 can contain:
2086
+
2087
+ ```json
2088
+ "migrations": [{
2089
+ "fromVersion": 1,
2090
+ "operations": [
2091
+ { "op": "rename", "from": "count", "to": "completed" },
2092
+ { "op": "default", "key": "labels", "value": [] },
2093
+ { "op": "remove", "key": "temporary" }
2094
+ ]
2095
+ }]
2096
+ ```
2097
+
2098
+ Operations run in order on safe ASCII top-level field names. Rename requires an
2099
+ existing source and absent destination; default writes only if absent; remove
2100
+ requires the field to exist. Nested values remain intact. There are at most 16
2101
+ distinct older-version routes and 64 operations per route. Combined declarations
2102
+ and every intermediate state each fit within 64 KiB. An empty operation list
2103
+ explicitly advances only the schema version. There is no implicit chaining,
2104
+ downgrade, deep-path transformation, expression evaluation or code execution.
2105
+
2106
+ The portable SDK exports `validateStateMigrations(stateDeclaration)`,
2107
+ `stateMigrationRoute(manifest, fromVersion)`, and
2108
+ `migrateBlockState(previousManifest, nextManifest, savedState)`. The last returns
2109
+ `{state, operations, fromVersion, toVersion}` without modifying its inputs. Both
2110
+ manifests must describe the same JavaScript Block identity, with valid state
2111
+ contracts. Matching state versions use an identity transfer; changed versions
2112
+ require an exact direct route. Runtime execution does not invoke migrations.
2113
+
2114
+ Migration declarations change package source: publish a new immutable Block
2115
+ release and keep its fixtures on the target schema. Do not guess existing state
2116
+ fields or reset user memory. In StillMade, owners review exact archived/current
2117
+ source pins, operations and before/after memory, then explicitly apply. The host
2118
+ rechecks account/project/source/state, retains rollback history and invalidates
2119
+ downstream results. External hosts must provide equivalent ownership, source
2120
+ verification, approval and rollback boundaries before accepting the result.
2121
+
2122
+ ## Async SDK result and progress types
2123
+
2124
+ `runPackage` is synchronous and recipe-only. `runPackageAsync` dispatches the supported runtime and resolves `RunPackageResult`, containing final `outputs` and runtime-appropriate optional metadata/media receipts or JavaScript `state`. External hosts must validate and accept Step state atomically with outputs; the SDK does not persist it. A capability executor returns `CapabilityResult`, which cannot contain Step state: `validateCapabilityResult` rejects that field. Stateful JavaScript output is a different runtime result, not a provider response.
2125
+
2126
+ Both recipe entrypoints accept `onProgress({stepId, complete, total})`; asynchronous dispatch forwards this callback when executing a recipe. It is not a provider percentage or a new queued-job protocol. An `AbortSignal` requests cancellation through the existing runtime/host adapter. Provider job polling, progress UI, retries, costs and retained-result adoption remain host responsibilities. Do not return an invented `{pending: ...}` envelope as declared Block outputs or assume a canceled network wait undoes provider work.
2127
+
2128
+
2129
+ ## Connected failure recovery
2130
+
2131
+ The shared Step failure control supports last-valid recovery for eligible stateless sandbox Blocks and bounded hosted text, speech or image generation with immutable receipts for media. Recovery applies only to interactive connected runs. It is not automatic retry or an SDK pending-result envelope. A successful run must first be retained with this policy, bound to the same account, project, workflow, source and inputs.
2132
+
2133
+ Sandbox image recovery requires exact immutable PNG receipts and ordered pixel input evidence, with up to 16 images and 32 MiB of saved PNGs. Hosted text recovery requires only the text.generate capability permission, no other scopes or state, and text/structured ports. After a definite eligible output, expectation or provider-response failure, the host offers an explicit reuse action. It verifies both the failed request and the original successful project receipt, requires the same provider/model/payment selection, and repeats current source, context and access checks before adoption. Pending, uncertain, canceled and unclassified requests are not eligible.
2134
+
2135
+ Reused results preserve the original output/receipt identity and are marked skipped; they do not create another generation, reservation or qualifying production execution. Recovery does not change billing for the failed attempt. The original retained successful record must remain available. External hosts must supply equivalent authoritative verification and attribution handling; copying a saved output or setting skipped is not proof. Legacy speech/image receipts, video, ComfyUI, background and standalone recovery remain unsupported; eligible hosted speech/image recovery requires immutable version-2 receipts and the same explicit original-run verification.
2136
+
2137
+ ## Hosted media receipt versions
2138
+
2139
+ Speech and generated-image host receipts accept `schemaVersion: 1 | 2` (generated images also 3: a measured catalog image; generated video receipts are 1 or 2). Version 1 remains readable for existing runs. New StillMade-hosted speech/image output uses version 2: the exact SHA-256 digest is part of a reserved storage key, writes use conditional creation with a checksum, and generic uploads, mirroring and trash restoration cannot replace that object. The host still verifies current owner/project access, original run/source/input/selection and actual stored bytes. SDK shape validation alone never grants media access or establishes immutability.
2140
+
2141
+ Only version-2 hosted media receipts can participate in explicit connected last-valid recovery. The host offers reuse after a definite eligible failed run, independently verifies both original run receipts, preserves the original output receipt, and marks reuse skipped for new-execution attribution. No provider call or additional credit reservation occurs during reuse. Legacy media, video and ComfyUI outputs are not made eligible by changing a receipt field or URL. External hosts must supply equivalent authenticated immutable-storage and exact-byte checks before supporting this behavior.
2142
+
2143
+
2144
+ ### Authored text choices
2145
+
2146
+ `manifest.ui` supports `{port: "tone", control: "select", options: ["Calm", "Energetic"]}` for a `text` input and `{port: "tags", control: "multiselect", options: ["News", "Tutorial"]}` for a `text[]` input. Supply 1–100 unique strings, each at most 1,000 characters, and exactly one control for that input. Empty text is allowed; whitespace and order are significant. These are form choices, not a runtime enum: ordinary text/text[] validation still applies to defaults, upstream connections and custom views.
2147
+
2148
+ The shared Create authoring panel edits options locally with explicit Apply. Playground shows existing values outside the choices without silently replacing them. Multiselect preserves unlisted items until the user explicitly removes them and can deliberately supply an empty list. Context-bound and upstream-bound inputs retain their existing read-only presentation. Custom sandbox views continue to own their own controls.
2149
+
2150
+
2151
+ ### Authored image output presentation
2152
+
2153
+ Add `{control: "gallery", port: "images"}` to `manifest.ui` for an `image[]` output, or `{control: "before-after", port: "result", before: "source"}` for a scalar `image` output compared with a declared scalar `image` input. Each output has at most one presentation; an input and output can share a name without sharing their control. These declarations only affect presentation and cannot fetch external media or change outputs.
2154
+
2155
+ The shared Files editor exposes output presentation with explicit Apply for recipe, JavaScript, ComfyUI and capability packages. A completed preview captures the actual decoded comparison input before invocation. Before/After/Side by side buttons compare it with the validated result; gallery navigation uses bounded host result paging. Existing media URL/receipt checks still apply. Authored result controls are also available alongside a custom sandbox view; the package view itself is not rewritten.
2156
+
2157
+
2158
+ ### Host project item pickers
2159
+
2160
+ Shared Create provides searchable project choices for `shot`, `scene`, `character` and their array inputs. Declare the corresponding `context.shots.read`, `context.scenes.read` or `context.characters.read` permission to make scoped host snapshot items available; this does not add a context binding to the input. Selection explicitly copies the chosen snapshot into the ordinary input. Context-bound and upstream-connected ports stay locked. Shots omit selected media unless `context.assets.read` is also declared, matching the existing context resolver.
2161
+
2162
+ Array pickers allow explicit add, remove, reorder and empty-list actions, with paginated choices and selected values. The picker adds up to 100 items without truncating larger existing inputs or changing runtime validation. Missing permissions or scoped items are explained in the UI. No project document is edited, no global library is read, and custom sandbox views receive the same host controls for unlocked compatible ports.
2163
+
2164
+
2165
+ ### Shared preview tabs and sections
2166
+
2167
+ Optional `manifest.uiLayout` groups existing host controls: `{kind:"tabs",groups:[{id:"source",label:"Source",inputs:["prompt"],outputs:[]},{id:"result",label:"Result",inputs:[],outputs:["text"]}]}`. Use `kind:"sections"` and optional group `collapsed:true` for initially collapsed sections. Supply 1–8 groups with unique lowercase identifiers (up to 40 characters), labels up to 80 characters, and explicit input/output name lists. Assign each port at most once per direction. Empty groups are allowed for drafting and omitted in views where they contain no ports; unassigned ports stay visible.
2168
+
2169
+ The shared Files layout editor supports labels, assignments, ordering and initial collapse with explicit Apply. Input and output group navigation are independent host buttons; normal keyboard Tab/Enter/Space work. Existing control values survive tab switches. Package code, permissions, validation, Run, progress and approval UI remain unchanged. Custom sandbox views retain their own internal layout; their host result presentation can use output groups.
2170
+
2171
+
2172
+ ### Timeline range and workflow decisions
2173
+
2174
+ Shared Create offers explicit range and decision controls for scalar `timeline-range` and `approval` inputs. Standard version 2 range data is exactly `{"schemaVersion":2,"startMs":1000,"endMs":2500}`, with finite millisecond values satisfying `0 <= startMs <= endMs <= Number.MAX_SAFE_INTEGER`. Standard decision data is exactly `{"schemaVersion":2,"decision":"accepted"}`; decisions may be `accepted`, `rejected` or `deferred`. Missing input is not approval. These values are workflow data and never authorize spending, permissions, publishing or project edits.
2175
+
2176
+ Existing version 1 application-defined documents remain valid. The host preserves them until explicit conversion and Apply; the consuming Block must support version 2. Upstream/context locks remain in force. The SDK exports `isTimelineRange`, `isApprovalDecision`, their TypeScript interfaces and the range bound. The downloadable pack includes `INTERACTION_VALUES.md` with the full compatibility and authority contract.
2177
+
2178
+
2179
+ ### Cooperative pending jobs (JavaScript)
2180
+
2181
+ Opt in with `manifest.jobs = {kind:"cooperative",version:1}`. This reserves the `input`, `state`, and `job` function arguments; existing packages without opt-in keep their existing function signature. `job.continuation` is null on the first turn and a JSON copy of the previous pending payload thereafter. Its nested data is modifiable local input, not deeply immutable. Return `job.pending(nextContinuation, {complete, total})` to yield, then return the ordinary declared output object when finished. Progress is optional; supplied units must increase with a fixed positive integer total, at most 1,000,000. Omitted progress reports the continuation turn without inventing a percentage.
2182
+
2183
+ ```javascript
2184
+ // text input/output example; requires manifest.jobs opt-in.
2185
+ if (input.text.length > 4096) throw new Error("Use at most 4096 characters");
2186
+ const previous = job.continuation || {offset:0,text:""};
2187
+ const end = Math.min(previous.offset + 256, input.text.length);
2188
+ const next = {offset:end,text:previous.text + input.text.slice(previous.offset,end).toUpperCase()};
2189
+ if (end < input.text.length) return job.pending(next,{complete:end,total:input.text.length});
2190
+ return {text:next.text};
2191
+ ```
2192
+
2193
+ The host invokes the same code and original input in the same disposable QuickJS runtime for at most 32 turns. All turns share the existing 500 ms active execution budget, 16 MiB heap / 32 MiB fixed WASM memory, and a cumulative 4 MiB serialized continuation/state/final-output budget. Node keeps its existing 5-second worker wall deadline; browser hosts keep their existing deadlines (at most 15 seconds for sandbox work). Yielding never resets those budgets. Persistent Step state is carried privately between turns and returned for host review only with the final valid outputs; cancellation, failure or limits cannot commit intermediate state.
2194
+
2195
+ `job.pending` returns a reserved strict JSON envelope with `$stillmadeJob:1`, `continuation`, and `progress`. It is a request for another bounded sandbox turn, not a trusted capability or permission. No Promise, poll URL, cancel URL, network request, host function, provider job identifier or new timer API is accepted. There is no durable/reload job guarantee. The shared authoring editor exposes opt-in; preview, connected Steps and import sample UI use the existing job handle and cancellation controls. These cooperative jobs do not claim support for external asynchronous services.
2196
+
2197
+ ## Native tool execution is host-owned
2198
+
2199
+ A native `desktopTools` package cannot prove its transformation by returning its
2200
+ input from `src/run.js`. Generic execution requires a declared `src/desktop.json`
2201
+ argument/output mapping and a trusted desktop executor. The custom interface
2202
+ submits the original connected input plus settings to `StillMade.run`; only the
2203
+ host invokes the approved native action and registers the actual result. Preserve
2204
+ old immutable releases; introduce a new version for a new execution mapping.
2205
+ Offline native admission/package checks execute zero fixtures and remain
2206
+ `execution: "not-run"`, `liveVerified: false`, `reviewRequired: true`. Retain native
2207
+ receipts with successful connected results; conditional/failure skips stay
2208
+ explicitly skipped and cannot claim native completion. See DESKTOP_EXECUTION.md.
2209
+
2210
+ ```
2211
+
2212
+ ## Acceptance example
2213
+ The public examples include `examples/grayscale.stillmade.json`, a versioned update of a separate docs-only authoring exercise that passes current import admission. The unchanged first submission is retained as `examples/docs-only-grayscale-original.stillmade.json` for inspection.