stillmade-sdk 0.1.41 → 0.1.43

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 (285) hide show
  1. package/.stillmade/sdk-baseline.json +19 -21
  2. package/AGENTS.md +17 -12
  3. package/FRAME_VIEWS.md +113 -0
  4. package/INTERFACE_DESIGN.md +67 -22
  5. package/MOBILE_UI.md +4 -2
  6. package/MODULES.md +152 -6
  7. package/PASTE_TO_LLM.md +42 -32
  8. package/README.md +3 -2
  9. package/SDK.md +1028 -563
  10. package/UI_AESTHETIC.md +30 -3
  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 +8 -1
  108. package/examples/clean-text.stillmade.json +4 -0
  109. package/examples/custom-interface.stillmade.json +4 -0
  110. package/examples/describe-image.stillmade.json +4 -0
  111. package/examples/foley-cue.stillmade.json +4 -0
  112. package/examples/generate-image.stillmade.json +4 -0
  113. package/examples/generate-video.stillmade.json +10 -2
  114. package/examples/module-word-stats/stillmade.block.json +4 -0
  115. package/examples/narrate-script.stillmade.json +4 -0
  116. package/examples/numbered-takes.stillmade.json +4 -0
  117. package/examples/research-topic.stillmade.json +9 -1
  118. package/examples/score-a-scene.stillmade.json +4 -0
  119. package/examples/script-draft.stillmade.json +4 -0
  120. package/examples/summarize-page.stillmade.json +4 -0
  121. package/examples/text-workflow.stillmade.json +7 -32
  122. package/examples/thicken-lines.stillmade.json +4 -0
  123. package/examples/timeline-polish.stillmade.json +15 -3
  124. package/examples/transcribe-audio.stillmade.json +15 -3
  125. package/examples/word-count.stillmade.json +4 -0
  126. package/package.json +1 -1
  127. package/packages/block-cli/cli.js +13 -6
  128. package/packages/block-cli/dev-host/assets/host.js +81 -38
  129. package/packages/block-cli/dev-host/assets/index.css +1 -1
  130. package/packages/block-cli/format.js +1 -1
  131. package/packages/block-cli/ready-gate.js +4 -0
  132. package/packages/block-cli/templates.js +37 -19
  133. package/packages/block-cli/type-packages.js +119 -0
  134. package/packages/block-platform/block-readiness.js +8 -1
  135. package/packages/block-platform/canvas.js +2 -1
  136. package/packages/block-platform/catalog.js +27 -48
  137. package/packages/block-platform/native-host-inventory.js +0 -226
  138. package/packages/block-platform/plain-descriptions.js +1 -25
  139. package/packages/block-platform/project-type-starters.js +13 -53
  140. package/packages/block-platform/script-handoff.js +2 -4
  141. package/packages/block-platform/type-workspace.js +2 -0
  142. package/packages/block-sdk/appearance.js +3 -1
  143. package/packages/block-sdk/audio-generation-example.js +2 -2
  144. package/packages/block-sdk/block-tier.js +1 -1
  145. package/packages/block-sdk/builder-contract.js +13 -11
  146. package/packages/block-sdk/capabilities.js +1 -1
  147. package/packages/block-sdk/capability-example.js +1 -1
  148. package/packages/block-sdk/contracts.js +6 -2
  149. package/packages/block-sdk/examples.js +3 -3
  150. package/packages/block-sdk/first-party-identities.js +1 -84
  151. package/packages/block-sdk/first-party-packages.js +3 -85
  152. package/packages/block-sdk/generation-catalog.js +15 -3
  153. package/packages/block-sdk/image-description-example.js +1 -1
  154. package/packages/block-sdk/image-generation-example.js +1 -1
  155. package/packages/block-sdk/index.d.ts +2 -1
  156. package/packages/block-sdk/media-analysis-example.js +2 -2
  157. package/packages/block-sdk/mock-host.js +7 -1
  158. package/packages/block-sdk/module-example.js +1 -1
  159. package/packages/block-sdk/module-host-calls.js +35 -10
  160. package/packages/block-sdk/module-hosted.js +15 -2
  161. package/packages/block-sdk/module-media-access.js +71 -0
  162. package/packages/block-sdk/module-node.js +1 -1
  163. package/packages/block-sdk/portable-media-actions.js +1 -1
  164. package/packages/block-sdk/runtime.js +1 -1
  165. package/packages/block-sdk/speech-example.js +1 -1
  166. package/packages/block-sdk/state-example.js +1 -1
  167. package/packages/block-sdk/timeline-proposal-example.js +1 -1
  168. package/packages/block-sdk/transcription-example.js +1 -1
  169. package/packages/block-sdk/trigger-examples.js +2 -2
  170. package/packages/block-sdk/video-generation-example.js +1 -1
  171. package/packages/block-sdk/view-bridge.js +4 -1
  172. package/packages/block-sdk/view-design.js +44 -15
  173. package/packages/block-sdk/web-example.js +2 -2
  174. package/packages/design-system/block-kit.css +53 -12
  175. package/packages/design-system/block-kit.html +3 -2
  176. package/blocks/mcp-motion-planner/versions/1.1.0/package.js +0 -97
  177. package/blocks/motion-designer/versions/1.0.1/package.js +0 -173318
  178. package/blocks/motion-designer/versions/1.0.2/package.js +0 -173318
  179. package/blocks/motion-frame-review/versions/1.1.1/package.js +0 -465
  180. package/blocks/motion-frame-review/versions/1.1.2/package.snapshot.js +0 -572
  181. package/blocks/narration/package.js +0 -1
  182. package/blocks/narration/versions/1.0.0/package.js +0 -221
  183. package/blocks/narration/versions/1.0.1/package.js +0 -221
  184. package/blocks/native/sm.animated-scene/versions/2.0.0/package.js +0 -475
  185. package/blocks/native/sm.animated-scene/versions/2.1.0/package.js +0 -498
  186. package/blocks/native/sm.animated-scene/versions/2.2.0/package.js +0 -524
  187. package/blocks/native/sm.animated-scene/versions/2.3.0/package.js +0 -618
  188. package/blocks/native/sm.animated-scene/versions/2.3.1/package.js +0 -618
  189. package/blocks/native/sm.animated-scene/versions/2.3.2/package.js +0 -723
  190. package/blocks/native/sm.animated-scene/versions/2.3.3/package.js +0 -723
  191. package/blocks/native/sm.animated-scene/versions/2.3.4/package.js +0 -676
  192. package/blocks/native/sm.animated-scene/versions/2.3.5/package.js +0 -693
  193. package/blocks/native/sm.animated-scene/versions/2.3.6/package.js +0 -700
  194. package/blocks/native/sm.animated-scene/versions/2.3.7/package.js +0 -747
  195. package/blocks/native/sm.animated-scene/versions/2.3.8/package.js +0 -769
  196. package/blocks/native/sm.animated-scene/versions/2.3.9/package.js +0 -769
  197. package/blocks/native/sm.animated-scene-assistant/versions/1.0.0/package.js +0 -183
  198. package/blocks/native/sm.blueprint/versions/2.0.0/package.js +0 -358
  199. package/blocks/native/sm.blueprint/versions/2.1.0/package.js +0 -358
  200. package/blocks/native/sm.blueprint/versions/2.1.1/package.js +0 -358
  201. package/blocks/native/sm.blueprint/versions/2.1.2/package.js +0 -358
  202. package/blocks/native/sm.blueprint/versions/2.1.3/package.js +0 -381
  203. package/blocks/native/sm.blueprint/versions/2.1.4/package.js +0 -381
  204. package/blocks/native/sm.blueprint/versions/2.1.5/package.js +0 -339
  205. package/blocks/native/sm.blueprint/versions/2.1.6/package.js +0 -356
  206. package/blocks/native/sm.blueprint/versions/2.1.7/package.js +0 -363
  207. package/blocks/native/sm.blueprint/versions/2.1.8/package.js +0 -410
  208. package/blocks/native/sm.blueprint/versions/2.1.9/package.js +0 -422
  209. package/blocks/native/sm.blueprint-assistant/versions/1.0.0/package.js +0 -172
  210. package/blocks/native/sm.chat/versions/2.0.0/package.js +0 -144
  211. package/blocks/native/sm.chat/versions/2.0.1/package.js +0 -97
  212. package/blocks/native/sm.chat/versions/2.0.2/package.js +0 -100
  213. package/blocks/native/sm.chat/versions/2.0.3/package.js +0 -100
  214. package/blocks/native/sm.continue-if/versions/1.0.0/package.js +0 -168
  215. package/blocks/native/sm.describe-media/versions/2.0.0/package.js +0 -153
  216. package/blocks/native/sm.describe-media/versions/2.0.1/package.js +0 -156
  217. package/blocks/native/sm.describe-media/versions/2.1.0/package.js +0 -140
  218. package/blocks/native/sm.image-editor/versions/2.0.0/package.js +0 -183
  219. package/blocks/native/sm.image-editor/versions/2.0.1/package.js +0 -190
  220. package/blocks/native/sm.new-items/versions/1.0.0/package.js +0 -191
  221. package/blocks/native/sm.pick-photos/versions/1.0.0/package.js +0 -334
  222. package/blocks/native/sm.quality-audit/versions/1.0.0/package.js +0 -192
  223. package/blocks/native/sm.quality-audit/versions/1.0.1/package.js +0 -192
  224. package/blocks/native/sm.quality-audit/versions/1.0.2/package.js +0 -195
  225. package/blocks/native/sm.research/versions/2.0.0/package.js +0 -175
  226. package/blocks/native/sm.research/versions/2.0.1/package.js +0 -184
  227. package/blocks/native/sm.research/versions/2.0.2/package.js +0 -187
  228. package/blocks/native/sm.script/versions/2.0.0/package.js +0 -144
  229. package/blocks/native/sm.script/versions/2.1.0/package.js +0 -144
  230. package/blocks/native/sm.script/versions/2.1.1/package.js +0 -144
  231. package/blocks/native/sm.script/versions/2.1.2/package.js +0 -105
  232. package/blocks/native/sm.script/versions/2.1.3/package.js +0 -152
  233. package/blocks/native/sm.script/versions/2.1.4/package.js +0 -156
  234. package/blocks/native/sm.script/versions/2.1.5/package.js +0 -156
  235. package/blocks/native/sm.shot-plan/versions/2.0.0/package.js +0 -301
  236. package/blocks/native/sm.shot-plan/versions/2.1.0/package.js +0 -528
  237. package/blocks/native/sm.shot-plan/versions/2.1.1/package.js +0 -528
  238. package/blocks/native/sm.shot-plan/versions/2.1.2/package.js +0 -528
  239. package/blocks/native/sm.shot-plan/versions/2.1.3/package.js +0 -536
  240. package/blocks/native/sm.shot-plan/versions/2.2.0/package.js +0 -800
  241. package/blocks/native/sm.shot-plan/versions/2.2.1/package.js +0 -920
  242. package/blocks/native/sm.shot-plan-assistant/versions/1.0.0/package.js +0 -173
  243. package/blocks/native/sm.stock-search/versions/2.0.0/package.js +0 -174
  244. package/blocks/native/sm.thumbnail/versions/2.0.0/package.js +0 -137
  245. package/blocks/native/sm.thumbnail/versions/2.0.1/package.js +0 -140
  246. package/blocks/native/sm.transcribe/versions/2.0.0/package.js +0 -163
  247. package/blocks/native/sm.transcribe/versions/2.0.1/package.js +0 -168
  248. package/blocks/native/sm.whiteboard-assistant/versions/1.0.0/package.js +0 -175
  249. package/blocks/native/sm.youtube/versions/2.0.0/package.js +0 -217
  250. package/blocks/occasion-story/package.js +0 -1
  251. package/blocks/occasion-story/versions/1.0.0/package.js +0 -1658
  252. package/blocks/occasion-story/versions/1.0.1/package.js +0 -1699
  253. package/blocks/occasion-story/versions/1.0.2/package.js +0 -1682
  254. package/blocks/photo-reel-composer/versions/1.0.0/package.js +0 -3043
  255. package/blocks/photo-reel-composer/versions/1.0.1/package.js +0 -3026
  256. package/blocks/photo-reel-composer/versions/1.0.2/package.js +0 -4149
  257. package/blocks/product-ad-variants/versions/1.0.0/package.js +0 -2911
  258. package/blocks/product-launch-director/versions/1.0.0/package.js +0 -103
  259. package/blocks/release-visuals/package.js +0 -1
  260. package/blocks/release-visuals/versions/1.0.0/package.js +0 -1492
  261. package/blocks/release-visuals/versions/1.0.1/package.js +0 -1517
  262. package/blocks/release-visuals/versions/1.0.2/package.js +0 -1500
  263. package/blocks/screen-recorder/package.js +0 -1
  264. package/blocks/screen-recorder/versions/1.0.0/package.js +0 -223
  265. package/blocks/screen-recorder/versions/1.0.1/package.js +0 -223
  266. package/blocks/short-form-finish/package.js +0 -1
  267. package/blocks/short-form-finish/versions/1.0.0/package.js +0 -1043
  268. package/blocks/short-form-finish/versions/1.0.1/package.js +0 -1043
  269. package/blocks/short-form-finish/versions/1.0.2/package.js +0 -1043
  270. package/blocks/short-form-finish/versions/1.0.3/package.js +0 -1026
  271. package/examples/saas-product-launch-reviewed-source.stillmade.json +0 -383
  272. package/examples/saas-product-launch.stillmade.json +0 -304
  273. package/packages/block-platform/dealer-inventory-video-project-type.js +0 -38
  274. package/packages/block-platform/first-revenue-project-types.js +0 -46
  275. package/packages/block-platform/fixture-starters.js +0 -13
  276. package/packages/block-platform/launch-project-source.js +0 -10
  277. package/packages/block-platform/launch-project.js +0 -30
  278. package/packages/block-platform/motion-project-types.js +0 -55
  279. package/packages/block-platform/motion-starters.js +0 -14
  280. package/packages/block-platform/product-ad-variants-project-type.js +0 -30
  281. package/packages/block-platform/product-launch-updated.js +0 -90
  282. package/packages/block-platform/revenue-review-project-types.js +0 -36
  283. package/packages/block-platform/revenue-review-starters.js +0 -13
  284. package/packages/block-platform/script-writer.js +0 -29
  285. package/packages/block-platform/website-source-block.js +0 -14
@@ -0,0 +1,617 @@
1
+ # Custom Block interfaces
2
+
3
+ A package can include an optional **`view`** beside `manifest`, `code`/`recipe`,
4
+ and `tests`. This is the Block's real interface in Preview: a user edits controls,
5
+ presses its action, and sees the code's result. Packages without `view` use
6
+ StillMade's generated controls and output viewer. `manifest.ui` remains the
7
+ fallback controls schema; it is not the custom interface source.
8
+
9
+ `view` optionally declares `appearance: "original"` or `"host"` and contains
10
+ `html` (required, up to 64 KiB), `css` (optional, up to 32 KiB),
11
+ and `javascript` (optional, up to 64 KiB). These are UTF-8 byte limits. For folder
12
+ packages, use separate `src/view.html`, `src/view.css`, and `src/view.js` files
13
+ (the last two are optional). Optional theme-specific CSS uses
14
+ `src/view.themed.css`. The older `src/view.json` object format is also supported,
15
+ but never combine it with separate interface files. CLI pack, folder import,
16
+ and ZIP import reconstruct the same `view` object and run the same validators.
17
+ Use **Choose folder** on the import page to select an edited SDK package directory.
18
+ `src/run.js` remains the production function; `src/view.js` controls the interface. A standard view is a reviewed subset of standalone
19
+ controls: no framework, URL, npm module or build step runs inside it. For React
20
+ or another framework, SVG, canvas or WebGL, drag, timers and loops, use a
21
+ [frame view](frame-views.md) instead.
22
+
23
+ ### Interface bridge
24
+
25
+ The host provides `StillMade` inside the sandbox:
26
+
27
+ | API | Behavior |
28
+ | --- | --- |
29
+ | `StillMade.input` | Copy of the current preview input bindings after initialization |
30
+ | `StillMade.onInput(callback)` | Receives initial input and later host input changes; returns an unsubscribe function |
31
+ | `StillMade.connectDocument(options)` | Connects a declared JSON content store to shared edits; returns status, flush and recovery methods |
32
+ | `await StillMade.previewImage(value)` | Resolves a currently authorized image to a bounded, display-only PNG copy; preserves the original run input |
33
+ | `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 |
34
+
35
+ `admitPackage` and local admission include a **Shared interface contract** source
36
+ check. The lightest path for an ordinary form control is one HTML annotation:
37
+ `<textarea id="draft" data-stillmade-share="draft"></textarea>`. The host turns
38
+ that into the same checked, permission-aware binding as `bindShared`; the Block
39
+ does not implement networking, cursors, batching, conflict transport or retries.
40
+ A `connectDocument` content-store connection supplies the platform-owned
41
+ receive/publication paths for an existing structured store. Otherwise an
42
+ `onShared` subscription is required, and detected editable controls must call
43
+ `bindShared` or `updateShared`. This is a minimum static prerequisite,
44
+ not full collaboration verification. Its `runtimeVerified` remains false even
45
+ when it passes. Calls may be unreachable or fail to cover all controls; actual
46
+ two-participant acceptance remains required. Runtime safety checks for already
47
+ pinned immutable packages are separate and do not rewrite those releases.
48
+ The general `scanPackage` keeps compatibility by default; passing
49
+ `{collaboration:true}` requests this admission prerequisite. Creation and import
50
+ services use `scanAdmissionPackage` for the same mandatory source check,
51
+ including hosted Blocks. This is not yet an enforced two-user runtime acceptance
52
+ guarantee for every import route.
53
+
54
+ Project workspaces also expose `StillMade.onShared(callback)` and
55
+ `StillMade.getShared()`. The snapshot contains `state` (shared interface JSON),
56
+ `outputs` (the latest saved results, including another collaborator's generation),
57
+ `memory` (declared Block runtime memory), and `canEdit`. Use
58
+ `StillMade.updateShared({field: value})` to queue a shared interface edit. It
59
+ checks edit permission and per-field preconditions; the host sync status reports
60
+ durable acknowledgement. In saved Step workspaces, the promise resolves only
61
+ after an acknowledged shared save, so `run` waits for pending edits to reach
62
+ storage. Builder previews remain local. The `{queued:true}` result does not
63
+ itself expose a server receipt or establish durability in every host.
64
+
65
+ The unchanged original photo-editor folder has local behavioral acceptance:
66
+ independent owner/editor/viewer browsers share edits and saved image outputs,
67
+ reopen them, wait for a delayed save, preserve independent concurrent fields and
68
+ reject stale field preconditions. Actual isolated producer/editor/consumer
69
+ execution verifies its input/output handoff. Fixture identities and storage
70
+ transport are used; authors still need to exercise their own complete journey.
71
+ Saved declared outputs can be previewed with the current package pin and exact
72
+ host media receipt; a shared URL alone does not grant image access.
73
+ The host's existing guarded Step/Home exits also await pending direct
74
+ `updateShared` writes, including dependent promise continuations in the current
75
+ task. Rejected fields remain unconfirmed until an acknowledged replacement for
76
+ those fields; unrelated successful edits do not clear them. This internal
77
+ barrier is not exposed to Block code, does not cover every navigation route,
78
+ and does not wait for future provider results or arbitrary private callbacks.
79
+ The state is limited to 384 KiB of UTF-8 JSON per placement. Shared edit requests,
80
+ including before-values, are capped at 800,000 bytes; in-flight and queued requests
81
+ together are capped at 2 MiB. The project still has its existing 1 MB operation
82
+ envelope and 4 MB live-document limits. These limits are cumulative, not an
83
+ unlimited quota per Block. Use named fields and commit text
84
+ drafts on blur or a short trailing debounce; do not send an entire document on
85
+ every pointer movement. Observe `onShared` to render remote changes and results.
86
+ Conflicting edits must be reconciled with the latest snapshot, not silently
87
+ overwritten. Builder previews simulate this state locally, without network sharing.
88
+
89
+ For a structured object, opt into checked nested merging with
90
+ `StillMade.updateShared({plan: editedPlan}, {plan: {exists: true, value: basePlan}}, {merge: true})`.
91
+ `basePlan` must be the exact snapshot used to construct `editedPlan`, not a newer
92
+ snapshot read just before sending. Unchanged nested fields are retained from the
93
+ current shared state. Arrays of objects with distinct string `id` fields merge
94
+ by identity; independent edits and additions can coexist. Overlapping edits,
95
+ deleting an item someone changed, or incompatible reorderings reject the entire
96
+ patch. Scalars, non-keyed arrays and normal calls without `merge` stay strict.
97
+ This is not character-level text merging. Structured merges still obey the
98
+ 384 KiB state limit and 2,000-operation limit per structured field. Keep edits
99
+ bounded and retain a rejected local draft for explicit resolution. Initializing
100
+ an absent field is a strict write; do not assume concurrent initializations merge.
101
+
102
+ For a structured editor, `StillMade.scheduleSharedEdit('plan', callback)` replaces
103
+ the pending callback for that name, running after 150 ms without another call or
104
+ at 500 ms maximum wait. Capture the original edit base once, keep the local draft,
105
+ and serialize/publish in the callback. Do not recapture a newer remote base for
106
+ an old edit. There are at most 32 pending names; closing the interface cancels
107
+ them unless the host first completes an explicit flush. Step-header navigation
108
+ now requests that flush and waits for the bound controls and scheduled callbacks.
109
+ Return the write promise from a scheduled callback so it can be awaited. This is
110
+ not a server save acknowledgement or a durable queue. Keep a pending-edit
111
+ indicator and block Review until your writes settle; surface errors and retain
112
+ the draft. Check permission again before publishing. Do not schedule paid runs
113
+ or replay user actions from remote updates.
114
+
115
+ For a large object document, `StillMade.diffDocument(base, draft)` returns checked
116
+ field/identity operations and `StillMade.applyDocument(base, operations)` returns
117
+ a new document or throws on a stale precondition. Both are pure local helpers:
118
+ they do not save anything, mutate the supplied object, grant permissions, or run
119
+ a Block. They use the same operation implementation as project collaboration.
120
+ Each object document is limited to 4 MiB of UTF-8 JSON; operations are limited to
121
+ 384 KiB and 2,000 entries. Reorder operations carry IDs instead of copies of the
122
+ entire collection, retaining concurrently added items and supporting replay.
123
+ Unchanged executable source, runtime memory and nested interface state do not
124
+ appear in a field edit. Actual insertion/deletion/replacement values still count
125
+ toward all limits. Paths and collection identities remain validated.
126
+
127
+ `StillMade.indexById(items)` returns a detached `Map` for a JSON collection with
128
+ distinct nonempty string IDs (at most 10,000 items and 4 MiB). Changing the map or
129
+ its values cannot mutate the input. Use this bounded helper for identity lookup
130
+ instead of repeatedly scanning large collections in instrumented view callbacks.
131
+
132
+ These helpers let a workspace keep a bounded edit overlay rather than copying a
133
+ large source document into `state`. Publishing, overlay merging, original edit
134
+ bases, conflict recovery and `onShared` rendering are still the Block's job.
135
+ Never rebase an old edit by substituting newer before-values, and never treat a
136
+ successful local apply as a server acknowledgement. Call document helpers in a
137
+ scheduled edit, not on every pointer event. The helpers alone do not make an
138
+ existing local-only interface collaborative.
139
+
140
+ ### Connect an existing content store once
141
+
142
+ `StillMade.connectDocument` owns shared subscriptions, checked edit overlays,
143
+ 150 ms quiet / 500 ms maximum batching, in-flight rebasing and conflict status.
144
+ If two participants initialize an empty shared field together, a rejected
145
+ initializer waits up to five seconds for the actual winning snapshot before
146
+ rebasing its original edit intent. It does not invent a base or blindly retry;
147
+ conflicts, permission loss, disposal and missing-snapshot timeouts preserve the
148
+ local draft and require recovery.
149
+ Keep the Block's existing synchronous JSON store and renderer:
150
+
151
+ ```js
152
+ const shared = StillMade.connectDocument({
153
+ key: 'boardDraft',
154
+ source: initialBoard,
155
+ collections: [
156
+ {path: ['strip'], key: 'panelId'},
157
+ {path: ['strip', '*', 'elements'], key: 'id'},
158
+ ],
159
+ read: () => draft,
160
+ replace: value => { draft = value; draw(); },
161
+ });
162
+ // Add this to the existing content-change callback, not every individual control:
163
+ const changed = () => shared.changed();
164
+ // Optional: subscribe: notify => existingStore.subscribe(notify)
165
+ // replaces explicit changed() calls for stores with a change subscription.
166
+ StillMade.onInput(input => shared.replaceSource(input.board));
167
+ shared.onStatus(status => { save.disabled = !status.canEdit || !!status.error; });
168
+ ```
169
+
170
+ `replace` must synchronously replace content and redraw without saving, running
171
+ a Block or dispatching other side effects. Local selection, playback, preview
172
+ URLs, credentials and device state do not belong in the shared document. Source
173
+ refreshes use `replaceSource`; a user reset/undo is a content change and must
174
+ notify `changed`. Inputs, raw-text drafts and delayed result handlers that bypass
175
+ the store still require adaptation. Private variables are never discovered or
176
+ automatically synchronized.
177
+
178
+ Collection declarations address original document fields. `*` traverses an
179
+ explicitly declared parent collection. Declared IDs must be distinct nonempty
180
+ strings; missing IDs do not fall back to array indexes. `panelId`, `shotId`, `id`
181
+ and other safe declared keys preserve their original saved shapes. Renaming an
182
+ identity is a checked removal/insertion. The adapter shares small overlays, not
183
+ copies of unchanged documents. Existing 4 MiB document, 384 KiB shared-state and
184
+ 2,000-edit limits still apply; there are at most eight connected documents in a
185
+ view, each using a different shared field.
186
+
187
+ For legacy data whose accepted contract permits missing, duplicate or otherwise
188
+ unusable IDs, opt in explicitly with
189
+ `{path:['scenes'],key:'shotId',fallback:'atomic'}`. Use that same declaration for
190
+ every participant; never select a different codec based on the first loaded
191
+ document. The codec uses a canonical tagged representation: usable identities
192
+ still support small item-level edits, while legacy collections remain exact
193
+ checked atomic values. It never invents IDs or silently normalizes saved data.
194
+ Atomic collection changes can conflict or exceed the shared-state limit on large
195
+ documents; they are not a substitute for author-defined stable identities.
196
+ Changes between source representations keep the ordinary explicit
197
+ source-conflict/reset rules. Strict declarations retain their original encoding.
198
+
199
+ `getStatus()` and `onStatus(callback)` expose `phase`, `canEdit`, `dirty`,
200
+ `pending`, `sourceConflict` and `error`; `onStatus` returns an unsubscribe function. The host also
201
+ shows pending changes or the current adapter error. `await shared.flush()` waits
202
+ for local transport acknowledgement, **not a durable server receipt**. The
203
+ existing guarded workspace exits flush registered document connections before
204
+ the shared-write barrier. Before a run that depends on the final draft, await
205
+ `shared.flush()` and then read the current content. Incoming shared results do
206
+ not replay a run; use `onShared` to display results/progress separately.
207
+
208
+ Conflicting drafts stay local and visible. `await shared.retry()` retries against
209
+ the preserved edit baseline. `await shared.discard()` acknowledges an exact
210
+ no-op for this shared field before discarding its local draft, clearing that
211
+ field's rejected-write ledger. A racing teammate edit, a changed local draft,
212
+ pending writes or readonly/closed access refuse recovery rather than silently
213
+ erase content or claim a successful exit. Other failed fields still block exit.
214
+ `dispose()` refuses dirty, pending or failed connections; flush/discard first.
215
+ This adapter is not character-level text merging and does not establish runtime
216
+ coverage for arbitrary imported interfaces. No existing immutable Block release
217
+ is rewritten merely by exposing this API.
218
+
219
+ If a project input changes so an existing shared overlay no longer applies,
220
+ `replaceSource` retains the last valid source and remembers the incoming source
221
+ without publishing it. Source errors appear through adapter status, not a second
222
+ generic `onInput` exception that would outlive recovery. Ordinary retry/discard refuse to silently use that stale
223
+ source. Present a deliberate review/confirmation action before calling
224
+ `await shared.resetSharedSource({discardSharedEdits:true})`: **this clears the
225
+ shared draft for everyone**, not just the caller. The reset uses an exact current
226
+ field precondition and adopts the remembered source only after acknowledgement.
227
+ If a teammate acknowledges a compatible reset, the controller can adopt its
228
+ remembered incoming source without needing another input event. It rebases only
229
+ unconfirmed local intent, never reinstates the obsolete shared draft. A local
230
+ edit that conflicts with the reset remains visible and requires explicit discard;
231
+ ordinary retry cannot republish the stale shared content.
232
+ Readonly access, pending writes, racing teammate edits, newer local edits or a
233
+ newer incoming source prevent that adoption and preserve the local draft for
234
+ fresh review. An acknowledged reset may already have cleared the old shared
235
+ overlay when a subsequent local/source change is detected; the adapter reports
236
+ that failure and requires a new explicit confirmation. Reset never runs a Block.
237
+
238
+ For an ordinary static editable control, prefer
239
+ `data-stillmade-share="title"` on the element and give it a unique `id`. No
240
+ authored JavaScript adapter is needed. For a dynamically created or replaced
241
+ control, call `StillMade.bindShared('title','title-input')` once after its element
242
+ exists. Mark a genuinely device-only playback/filter control with
243
+ `data-stillmade-local`; admission rejects an unexplained static control when the
244
+ interface does not use a structured `updateShared` or `connectDocument` adapter.
245
+ Both shared forms use the same host-owned binding and share a text, numeric,
246
+ checkbox, or select value with a 150 ms trailing debounce, a 500 ms maximum
247
+ batch wait during continuous typing, and a blur flush. Unchanged/duplicate native
248
+ events do not publish or restart the delay. An edit already in flight must settle
249
+ before the next write; network and persistence latency are separate. It
250
+ observes remote changes, respects read-only access, and returns an unsubscribe
251
+ function. It never dispatches synthetic input/click events or runs a Block in
252
+ response to a teammate's edit. Derive non-DOM state and render saved outputs
253
+ through `onShared`; private variables are not synchronized by binding a control.
254
+ Do not bind credentials, file pickers, transient selection, or generation buttons.
255
+ If a bound control is replaced dynamically, unsubscribe before binding its replacement.
256
+ Text composition (IME) stays local until it ends; the debounce and maximum-wait
257
+ timer do not publish unfinished preedit. Incoming shared values/defaults cannot
258
+ overwrite the composing control. Its final edit retains the original field
259
+ precondition, so a same-field teammate edit still conflicts instead of being
260
+ silently overwritten. Workspace exit and `StillMade.run` reject while composition
261
+ is unfinished. If editing access changes during composition, the final draft
262
+ stays local even if access returns; duplicate native input events cannot submit
263
+ it. A genuinely changed later value may retry against the original checked base.
264
+ This does not provide character-level text merging or a general save-before-run
265
+ guarantee.
266
+ Use `StillMade.setSharedDefaults({title: initialTitle})` when input-derived
267
+ defaults change. This updates defaults only for existing bound fields; it does
268
+ not publish an edit, override a saved shared value, or replace a dirty/in-flight
269
+ draft. Do not assign to a bound control's value during remote rendering. Read
270
+ its current value for an explicit run so a preserved draft is not replaced by
271
+ an unrelated teammate's update. Keep slider/input elements mounted while a
272
+ gesture is active; update labels without rebuilding those controls.
273
+ Conflicting drafts stay visible with a validation error; do not automatically
274
+ overwrite the teammate's version. Dispose and rebind after the user chooses to
275
+ discard a conflicting draft. Builder previews use local-only shared state
276
+ (`localOnly: true`); this is not evidence of network collaboration.
277
+
278
+ `updateShared` queues up to 32 waiting updates and serializes their per-field
279
+ preconditions. A conflict rejects dependent queued updates instead of silently
280
+ rebasing them. An optional second argument supplies explicit field preconditions
281
+ captured when a draft began: `{title: {exists: true, value: 'original'}}`.
282
+ An acknowledgement timeout fails closed until the Block is reopened. This
283
+ protects against blindly retrying an edit whose acceptance is unknown.
284
+
285
+ Cursor forwarding is installed by the host in all admitted sandbox views and
286
+ does not require package code. Arbitrary private JavaScript variables and DOM
287
+ mutations are **not** shared state. When adapting a repository, move persistent
288
+ interface values onto this contract; importing a repository alone cannot make
289
+ its private state collaborative. The transport supports authenticated pushed
290
+ invalidations when configured, with polling fallback; concurrent
291
+ edits to the same text field can conflict; this is not character-level CRDT editing.
292
+
293
+ Always use `onInput` to initialize controls; the interface may load before its
294
+ initial values arrive. Catch run errors and display them in the interface. Disable
295
+ the run control while a request is pending. One run can be active; the host limits
296
+ the view to 30 requests per minute, 4 MiB and 500,000 JSON nodes per message, and
297
+ 15 seconds for local execution. Host-confirmed ComfyUI, text and speech requests
298
+ allow up to 6 minutes; image generation allows up to 15 minutes for confirmation
299
+ and its bounded fixture/sample sequence. These are interface wait budgets, not
300
+ permission to retry or extend provider deadlines. Raw RGBA messages must fit both
301
+ message budgets (roughly 124,000 pixels after reserving nodes for other inputs);
302
+ use smaller preview images.
303
+ The existing computation limits (including the JavaScript 500 ms guest deadline)
304
+ still apply. Reset discards the interface and aborts its pending computation.
305
+ Closing Preview disposes the frame and aborts outstanding work.
306
+
307
+ For a port named `prompt`, initialize its control from the host input callback:
308
+
309
+ ```js
310
+ const inputField = document.getElementById('prompt');
311
+ StillMade.onInput(input => { inputField.value = input.prompt || ''; });
312
+ ```
313
+
314
+ Inside the async run button handler, call
315
+ `await StillMade.run({prompt: inputField.value})` and display its output.
316
+ Use `inputField` or `promptInput` for local variables; `prompt` is also a browser
317
+ API name and cannot be a local identifier. Its data-property exception applies
318
+ only to the single, unshadowed and unreassigned parameter of a direct inline
319
+ `StillMade.onInput` callback; declare that parameter name only once in the
320
+ interface source. Use ordinary `.prompt` access there, rather than
321
+ computed/destructured access or a property on an unrelated object. Object literal
322
+ `{prompt: value}` keys are supported. Browser dialogs remain unavailable.
323
+
324
+ Use the DOM for interaction and rendering: `document.getElementById`,
325
+ `querySelector`, `addEventListener`, `textContent`, `value`, `checked`, `disabled`,
326
+ `hidden`, and canvas are supported. Declare elements in `html`; show/hide existing
327
+ elements or draw into a declared canvas. Dynamic element creation, HTML insertion,
328
+ reflection, computed dynamic property lookups, module imports, browser navigation,
329
+ account APIs, parent/window access, and storage are rejected. Property access such
330
+ as `result.text` and `image.data[0]` is supported; use array methods for iteration.
331
+ Synchronous loops, function declarations, directly recursive helpers, timers, and
332
+ microtask queues are rejected. Every callback/helper also checks a private task
333
+ budget (10,000 calls or 50 ms between checks), including indirect recursion. This
334
+ guard cannot interrupt a single native browser operation, so it is not a
335
+ substitute for the computation worker. Use small native array operations such as `map`/`forEach` for
336
+ rendering; heavy work belongs in the isolated Block runtime.
337
+ Use `.textContent` for user text. Tags are ordinary controls, text, layout, media,
338
+ and canvas; no script, iframe, SVG, link, form, or document metadata tags. Markup
339
+ must use complete tags without HTML comments, inline event handlers, or navigation
340
+ attributes. JavaScript belongs in `view.javascript`, not the HTML string.
341
+
342
+ The frame has an opaque origin and `sandbox="allow-scripts"`, without same-origin,
343
+ popups, forms, downloads, or top navigation permissions. CSP denies connections,
344
+ workers, frames, external scripts, and resources. Image and media sources must be
345
+ local `data:` or `blob:` URLs. Styles cannot import resources. No token, account,
346
+ project mutation API, filesystem, or parent DOM handle is passed into the frame.
347
+ Only copied inputs, explicit run results, and authorized image display copies cross the bridge. Static checks
348
+ are an additional admission gate; they do not replace this browser boundary or
349
+ the separate computation sandbox. Keep the UI responsive: move computation into
350
+ `code` or `recipe`; DOM JavaScript is not the CPU-metered computation runtime.
351
+
352
+ A view adds files to an input with the + tile,
353
+ `<button class="sm-add" data-stillmade-add="<input>">`, or
354
+ `StillMade.addFiles("<input>")` from its own button, never an
355
+ `<input type="file">` of its own: StillMade's picker makes the working copies
356
+ and keeps device files on the device (see "Adding files to an input" in
357
+ [Frame views](frame-views.md)). To hand pixels the view drew to an
358
+ image input, draw them into a declared `<canvas>` and pass `getImageData` as
359
+ `{width,height,data:Array.from(pixels.data)}`, staying within the SDK image and
360
+ message budgets. Nothing is uploaded by the view.
361
+
362
+ The host supplies StillMade's shared Light, Dark and White theme variables and basic controls.
363
+
364
+ For optional StillMade appearance, inherit its typography or use
365
+ `var(--font-body)` for controls and `var(--font-display)` with weight 200 and
366
+ italic style for display headings. Use shared spacing, radius and color tokens
367
+ such as `var(--bg)`, `var(--fg)`, `var(--fg-muted)`, `var(--border-tok)`, and
368
+ `var(--r-3)`. Original appearance may keep its own typography and colors within
369
+ the existing sandbox; importing external fonts or resources remains unavailable.
370
+
371
+ Use a clear input, primary action, and visible result. Preview runs
372
+ synthetic samples or the user's explicitly chosen input; it does not commit to a
373
+ production project. SDK fixture tests prove code behavior and input/output
374
+ compatibility. They do not claim to test browser interaction: review the actual
375
+ custom interface in Preview before **Confirm Import**.
376
+
377
+ ### Appearance compatibility
378
+
379
+ StillMade has three modes: **Light** (cream), **Dark** (ink), and **White**
380
+ (neutral white surfaces with black text). Generated controls inherit the app mode.
381
+ A custom interface may keep its original design by explicitly setting
382
+ `view.appearance: "original"`. The surrounding StillMade app keeps its own theme.
383
+ Omitting this field, or setting it to `"host"`, preserves the existing requirement
384
+ to provide theme-compatible colors. Existing package sources and installed pins
385
+ are never rewritten to change appearance.
386
+
387
+ A minimal original-style view is:
388
+
389
+ ```json
390
+ {
391
+ "appearance": "original",
392
+ "html": "<output id=\"result\"></output>",
393
+ "css": "body{background:#14213d;color:#fff}",
394
+ "javascript": "StillMade.onShared(shared=>{document.getElementById('result').textContent=shared.outputs.text||'';});"
395
+ }
396
+ ```
397
+
398
+ Keep this object in `src/view.json` when using the explicit appearance field;
399
+ do not combine it with split `src/view.html`/CSS/JavaScript files. Folder, archive
400
+ and GitHub SDK-folder imports retain the field, as do source editing and export.
401
+
402
+ The stylesheets have separate purposes:
403
+
404
+ - `view.css` (up to 32 KiB) retains the original design. Original-style packages
405
+ use it inside projects as well as in Original design previews.
406
+ - `view.themedCss` (optional, up to 32 KiB) is a **complete replacement stylesheet**
407
+ for authors who also want StillMade appearance. Include the same layout and
408
+ responsive rules using host tokens. Preview offers this mode only when the
409
+ complete view passes the host-appearance check.
410
+
411
+ For example, original CSS can use `body { background:#14213d; color:#fff; }`.
412
+ Its optional themed stylesheet uses `body { background:var(--bg); color:var(--fg); }`.
413
+ Move fixed inline colors into the stylesheets when offering both appearances.
414
+ Never change image/video/canvas pixels to match the interface palette.
415
+
416
+ For StillMade appearance, use `var(--bg)`, `var(--bg-elev-1)`, `var(--bg-elev-2)`,
417
+ `var(--fg)`, `var(--fg-muted)`, `var(--border-tok)`, `var(--signal)`, and
418
+ `var(--shadow-1)`. The SDK exports `APPEARANCE_TOKENS`. Inherit host typography
419
+ or use `var(--font-body)` and `var(--font-display)`. Do not redefine host tokens
420
+ or use raw palette tokens such as `--cream-100` in theme-aware declarations.
421
+ Original appearance does not require these color or typography conventions.
422
+
423
+ **The appearance declaration does not widen the interface runtime.** Existing
424
+ HTML/JavaScript syntax restrictions, CSS restrictions on escapes, filters and
425
+ color blending, resource-loading bans, size limits, shared-state requirements,
426
+ permissions, CSP and typed input/output validation still apply. Use CSS classes
427
+ for interface changes; dynamic element-style mutation remains unavailable.
428
+ Static original colors are allowed only with the explicit original mode;
429
+ any optional themed stylesheet still needs valid semantic colors.
430
+
431
+ `scanPackage`, `admitPackage`, and CLI validation check the declared appearance
432
+ alongside all other admission gates. A styling pass does not prove working UI,
433
+ accessibility or full repository compatibility. Review the actual interface on
434
+ its declared devices and, when supplied, each themed mode. This option preserves
435
+ styling within the existing restricted view format; it does not load framework
436
+ bundles, arbitrary React applications, remote fonts or external scripts.
437
+
438
+ ### Display an authorized image
439
+
440
+ Use **`await StillMade.previewImage(value)`** for an image selected in
441
+ StillMade, received from a connected Block, or present in explicitly granted
442
+ project context. The host also supports its registered sample pixels, local
443
+ uploads, and verified run outputs. A well-formed reference alone does not grant
444
+ access: a changed URL, version, role, unrelated asset, or expired preview session
445
+ is rejected. Catch and display the error; do not fall back to fetching the URL.
446
+
447
+ The result is exactly `{url, width, height}`. Its URL is a PNG data URL
448
+ that can be assigned to a declared `<img>`. It is a display copy, at most
449
+ 512 pixels on either edge, with aspect ratio preserved. It does not generate,
450
+ upload, save, select, or replace an asset. Continue to pass the **original**
451
+ image value to `StillMade.run`; processing uses the original input within
452
+ the runtime's own limits. Do not use the display URL as a processing input.
453
+
454
+ Image display allows one pending request, up to 60 requests per minute, and a
455
+ 15-second deadline. It is independent of the pending computation request, so an
456
+ input can be displayed while its Block runs. Both directions retain the 4 MiB
457
+ and 500,000-node message limits. Input changes, source/session reset, or closing
458
+ the workspace cancel unfinished display requests. Use a local revision counter
459
+ in asynchronous UI code so a late result cannot repaint an earlier selection.
460
+
461
+ StillMade's host image controls remain available with a custom interface.
462
+ On a project Step the custom interface fills the Step: the host's file inputs and "Apply to"
463
+ open from the Step's inputs button, and while a required file is missing a "Choose <input>"
464
+ button sits above the interface. Show your own empty state
465
+ in the view ("Add a photo") and never describe where the host's controls are.
466
+ A project lets the user select a compatible authorized image or upload one;
467
+ connected/context bindings remain read-only. A standalone preview keeps uploaded
468
+ files local to that preview. Host uploads accept PNG, JPEG, WebP or GIF up to
469
+ 12 MB and one megapixel. Above one megapixel the host makes a one-megapixel
470
+ working copy for the sandbox when the account's media setting is on (its
471
+ default); with the setting off, larger uploads are rejected with a request to
472
+ resize a copy. Existing authorized project references can be displayed up to 16
473
+ megapixels, but the selected runtime still applies its processing limit. The
474
+ host validates permissions and exact media identity before resolving bytes; no
475
+ account token, native file handle, or remote fetch capability enters the iframe.
476
+
477
+ This `view` example assumes one image input and one image output, both
478
+ named `image`. It shows the actual selected input and processed result
479
+ while retaining their original typed values:
480
+
481
+ ```json
482
+ {
483
+ "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>",
484
+ "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});"
485
+ }
486
+ ```
487
+
488
+ ### Complete custom interface example
489
+
490
+ Download [custom-interface.stillmade.json](https://stillmade.shop/block-sdk/examples/custom-interface.stillmade.json)
491
+ for the complete tested text-cleanup package. Its `view` is:
492
+
493
+ ```json
494
+ {
495
+ "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>",
496
+ "css": "output { display:block; white-space:pre-wrap; padding:16px; border:1px solid var(--border-tok); border-radius:var(--r-3); }",
497
+ "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}});"
498
+ }
499
+ ```
500
+
501
+ ### Dynamic lists and controls
502
+
503
+ Use `StillMade.render(containerId, nodes)` to replace the children of an existing
504
+ container with structured controls. Use `StillMade.onAction(callback)` for their
505
+ click, input, and change events; it returns an unsubscribe function. This supports
506
+ variable-length lists without raw HTML injection or direct DOM creation.
507
+
508
+ For example, with `<div id="choices"></div><output id="selection"></output>`:
509
+
510
+ ```js
511
+ StillMade.onInput(input => {
512
+ StillMade.render('choices', (input.shots || []).map(shot => ({
513
+ tag: 'button', text: shot.name || shot.id, value: shot.id, action: 'select'
514
+ })));
515
+ });
516
+ StillMade.onAction(event => {
517
+ document.getElementById('selection').textContent = event.value;
518
+ });
519
+ ```
520
+
521
+ Each node has `tag`, optional `text` or `children`, and optional `id`, `className`,
522
+ `label` (accessible name), `action`, `value`, `type`, `checked`, `disabled`,
523
+ `placeholder`, `min`, `max`, or `step`. Unknown fields are rejected. Supported tags
524
+ are div, section, article, header, footer, p, span, strong, em, small, h1–h4,
525
+ label, button, input, textarea, select, option, ul, ol, li, table, thead, tbody,
526
+ tr, th, td, details, summary, output, and progress. Input types are text, number,
527
+ range, checkbox, radio, and color. Use a div, section, article, main, aside, ul,
528
+ ol, or tbody as the existing root. Style nodes with classes in your view CSS.
529
+
530
+ An action belongs to a button, input, textarea, or select. Its callback receives
531
+ `{action, value, checked, event}`. Buttons emit click; checkbox, radio, and select
532
+ emit change; other fields emit input. Stable IDs preserve matching controls,
533
+ focus and selection during reconciliation. Keep edited values in your declared
534
+ content store, not only in the DOM. Text inputs emit one committed action at the
535
+ end of IME composition, not intermediate preedit or duplicate final input events.
536
+ Sibling redraws preserve active composition. If the same control's declared value
537
+ changes remotely during composition, the renderer retains local text and blocks
538
+ dispatch/exit rather than applying it against a silently advanced model base.
539
+ Copy the retained text before reopening the Block to review the shared version;
540
+ there is no automatic merge or retry. Disabling a composing control also retains
541
+ its draft and blocks run/exit after access returns. Removed or rebound controls
542
+ cannot commit their old composition to a different action; removed-control draft
543
+ recovery is not provided. Call `StillMade.run` explicitly
544
+ to execute production code. Rendering
545
+ does not mutate project context or bypass execution permissions.
546
+
547
+ Limits: 1,000 nodes per render, 12 child levels, 256 KiB of serialized descriptors,
548
+ 5,000 total frame elements, 16 action listeners, 10,000 characters per text/value,
549
+ 120 per ID/action and 240 per class/name/placeholder. IDs must be unique. Invalid
550
+ renders leave the previous controls intact. Text is always literal; scripts,
551
+ inline handlers, links, file inputs, network attributes, and raw HTML are rejected.
552
+
553
+
554
+ ### Audio and video playback
555
+
556
+ `await StillMade.previewMedia(reference)` opens an authorized `audio` or `video`
557
+ reference as a temporary playback copy and returns `{kind, url, mimeType, bytes}`.
558
+ Assign its URL to an existing `<audio controls>` or `<video controls>` element.
559
+ StillMade plays a `<video controls>` in its own player, with full screen; leave
560
+ out `controls` when the Block drives playback itself, or add
561
+ `data-stillmade-player="native"` to keep the browser's controls.
562
+ The host requires an exact reference from this Block’s bound input, an explicitly
563
+ selected project asset, permitted project context, or a host-recorded output.
564
+ A URL alone is insufficient. Preview has host file pickers for audio/video inputs;
565
+ project Steps can select existing project assets or receive connected media.
566
+
567
+ For `<audio id="player" controls></audio><p id="status"></p>`:
568
+
569
+ ```js
570
+ const player = document.getElementById('player');
571
+ const status = document.getElementById('status');
572
+ let revision = 0, activeUrl = '';
573
+ StillMade.onInput(input => {
574
+ const started = ++revision;
575
+ player.pause(); player.src = '';
576
+ if (activeUrl) StillMade.releaseMedia(activeUrl);
577
+ activeUrl = '';
578
+ if (!input.audio) { status.textContent = 'Choose audio above.'; return; }
579
+ status.textContent = 'Opening audio…';
580
+ StillMade.previewMedia(input.audio).then(preview => {
581
+ if (started !== revision) { StillMade.releaseMedia(preview.url); return; }
582
+ activeUrl = preview.url;
583
+ player.src = preview.url;
584
+ status.textContent = 'Ready to play.';
585
+ }).catch(error => {
586
+ if (started === revision) status.textContent = error.message;
587
+ });
588
+ });
589
+ ```
590
+
591
+ `StillMade.releaseMedia(url)` revokes a playback copy. Release old copies before
592
+ loading another: at most two can remain active. Input changes and closing/resetting
593
+ the interface revoke all copies and cancel pending reads automatically. Requests
594
+ share the image-preview queue and its limit of 60 requests per minute; only one
595
+ image/audio/video read can run at a time. Keep the original reference for production
596
+ runs; temporary URLs must never be saved as project assets or returned as outputs.
597
+ `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.
598
+
599
+
600
+ Files must be at most 24 MB and load within 15 seconds. Supported containers include
601
+ MP4, WebM, Ogg, QuickTime, MP3, AAC, WAV, and FLAC; actual playback depends on browser
602
+ codec support. Remote assets need CORS support and an appropriate media content type.
603
+ The host omits credentials and rejects redirects. Network access remains disabled
604
+ inside the iframe; larger media should use the existing native Editor workspace.
605
+
606
+ ### A check, then the expensive run, on one page
607
+
608
+ A job the person fills in and gets a result from is one Block on one page
609
+ (builder contract, "One job per Step"). When the expensive run needs a cheap
610
+ check first (the script and a voice sample before the video), the view calls
611
+ `StillMade.run({make: 'check'})`, shows what comes back, then calls
612
+ `StillMade.run({make: 'video', script})` with what the person checked; `make`
613
+ and `script` are optional inputs, and the outputs the check leaves out are
614
+ `"required": false`. Each run is priced and approved by StillMade on its own:
615
+ the view shows no price. The manifest, the module and what the person sees are
616
+ in [Module Blocks](modules.md), "A check, then the expensive run, on
617
+ one page".