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.
- package/.stillmade/sdk-baseline.json +6 -8
- package/AGENTS.md +15 -10
- package/FRAME_VIEWS.md +113 -0
- package/INTERFACE_DESIGN.md +62 -22
- package/MOBILE_UI.md +4 -2
- package/MODULES.md +152 -6
- package/PASTE_TO_LLM.md +35 -25
- package/README.md +3 -2
- package/SDK.md +994 -550
- package/UI_AESTHETIC.md +22 -2
- package/blocks/generate/package.js +1 -0
- package/blocks/generate/versions/1.0.0/package.js +463 -0
- package/docs/build/audio-to-editor.md +36 -0
- package/docs/build/block-builder-contract.md +150 -0
- package/docs/build/comfyui.md +227 -0
- package/docs/build/concepts.md +40 -0
- package/docs/build/context.md +159 -0
- package/docs/build/cookbook.md +57 -0
- package/docs/build/custom-interface.md +617 -0
- package/docs/build/data-types.md +83 -0
- package/docs/build/frame-views.md +259 -0
- package/docs/build/generate-media.md +59 -0
- package/docs/build/github-applications.md +314 -0
- package/docs/build/hosted-images.md +214 -0
- package/docs/build/hosted-music.md +39 -0
- package/docs/build/hosted-speech.md +157 -0
- package/docs/build/hosted-text.md +246 -0
- package/docs/build/image-description.md +49 -0
- package/docs/build/interface-design.md +343 -0
- package/docs/build/javascript.md +63 -0
- package/docs/build/local-preview.md +61 -0
- package/docs/build/media-analysis.md +27 -0
- package/docs/build/media.md +400 -0
- package/docs/build/mobile-ui.md +106 -0
- package/docs/build/modules.md +487 -0
- package/docs/build/original-blocks.md +70 -0
- package/docs/build/outside-apis.md +128 -0
- package/docs/build/production-context-templates.md +12 -0
- package/docs/build/project-storage.md +94 -0
- package/docs/build/quickstart.md +48 -0
- package/docs/build/recipes.md +103 -0
- package/docs/build/script-handoff.md +45 -0
- package/docs/build/script-writer.md +34 -0
- package/docs/build/state.md +223 -0
- package/docs/build/testing.md +112 -0
- package/docs/build/timeline-edits.md +116 -0
- package/docs/build/timeline-proposals.md +76 -0
- package/docs/build/transcription.md +59 -0
- package/docs/build/triggers.md +59 -0
- package/docs/build/tutorial.md +52 -0
- package/docs/build/typescript.md +34 -0
- package/docs/build/video-generation.md +61 -0
- package/docs/build/web-reading.md +37 -0
- package/docs/build/with-your-ai.md +27 -0
- package/docs/changelog.md +9 -0
- package/docs/creators.md +21 -0
- package/docs/distribute/discovery.md +43 -0
- package/docs/distribute/imports.md +58 -0
- package/docs/distribute/publishing.md +29 -0
- package/docs/distribute/sdk-versions.md +36 -0
- package/docs/distribute/selling.md +14 -0
- package/docs/distribute/teams.md +60 -0
- package/docs/distribute/versioning.md +28 -0
- package/docs/examples.md +23 -0
- package/docs/gallery.md +26 -0
- package/docs/index.md +50 -0
- package/docs/native-mcp.md +307 -0
- package/docs/project-types/automation.md +43 -0
- package/docs/project-types/batches.md +50 -0
- package/docs/project-types/build.md +42 -0
- package/docs/project-types/completion.md +15 -0
- package/docs/project-types/conditions.md +21 -0
- package/docs/project-types/hosted-review.md +115 -0
- package/docs/project-types/onboarding.md +75 -0
- package/docs/project-types/running.md +309 -0
- package/docs/project-types/testing.md +72 -0
- package/docs/reference/application-artifacts.md +87 -0
- package/docs/reference/authoring-guide.md +134 -0
- package/docs/reference/browser-applications.md +267 -0
- package/docs/reference/capabilities.md +45 -0
- package/docs/reference/cli.md +71 -0
- package/docs/reference/connection-api.md +59 -0
- package/docs/reference/cooperative-jobs.md +13 -0
- package/docs/reference/desktop-capabilities.md +172 -0
- package/docs/reference/desktop-execution.md +77 -0
- package/docs/reference/errors.md +233 -0
- package/docs/reference/execution.md +800 -0
- package/docs/reference/first-party-blocks.md +40 -0
- package/docs/reference/generation-models.md +59 -0
- package/docs/reference/limits.md +306 -0
- package/docs/reference/manifest-kinds-controls.md +36 -0
- package/docs/reference/manifest.md +181 -0
- package/docs/reference/operations.md +36 -0
- package/docs/reference/package-jobs.md +35 -0
- package/docs/reference/parity.md +55 -0
- package/docs/reference/permissions.md +86 -0
- package/docs/reference/ports.md +26 -0
- package/docs/reference/types.md +268 -0
- package/docs/reference/universal-resolver.md +303 -0
- package/docs/reference/view-bridge.md +60 -0
- package/docs/reference/view-rules.md +15 -0
- package/docs/reference/workspace-sync.md +25 -0
- package/docs/sdk.md +34 -0
- package/docs/tools/block-remix.md +211 -0
- package/docs/tools/llm.md +2213 -0
- package/docs/tools/repository-adaptation.md +613 -0
- package/examples/check-clip.stillmade.json +4 -1
- package/examples/generate-video.stillmade.json +6 -2
- package/examples/research-topic.stillmade.json +5 -1
- package/examples/text-workflow.stillmade.json +3 -32
- package/examples/timeline-polish.stillmade.json +11 -3
- package/examples/transcribe-audio.stillmade.json +11 -3
- package/package.json +1 -1
- package/packages/block-cli/cli.js +13 -6
- package/packages/block-cli/dev-host/assets/host.js +76 -38
- package/packages/block-cli/dev-host/assets/index.css +1 -1
- package/packages/block-cli/format.js +1 -1
- package/packages/block-cli/templates.js +31 -13
- package/packages/block-cli/type-packages.js +119 -0
- package/packages/block-platform/block-readiness.js +5 -0
- package/packages/block-platform/canvas.js +2 -1
- package/packages/block-platform/catalog.js +27 -48
- package/packages/block-platform/native-host-inventory.js +0 -226
- package/packages/block-platform/plain-descriptions.js +1 -25
- package/packages/block-platform/project-type-starters.js +13 -53
- package/packages/block-platform/script-handoff.js +2 -4
- package/packages/block-platform/type-workspace.js +2 -0
- package/packages/block-sdk/appearance.js +3 -1
- package/packages/block-sdk/block-tier.js +1 -1
- package/packages/block-sdk/builder-contract.js +10 -8
- package/packages/block-sdk/capabilities.js +1 -1
- package/packages/block-sdk/contracts.js +6 -2
- package/packages/block-sdk/first-party-identities.js +1 -84
- package/packages/block-sdk/first-party-packages.js +3 -85
- package/packages/block-sdk/generation-catalog.js +15 -3
- package/packages/block-sdk/index.d.ts +2 -1
- package/packages/block-sdk/media-analysis-example.js +1 -1
- package/packages/block-sdk/mock-host.js +7 -1
- package/packages/block-sdk/module-host-calls.js +35 -10
- package/packages/block-sdk/module-hosted.js +15 -2
- package/packages/block-sdk/module-media-access.js +71 -0
- package/packages/block-sdk/module-node.js +1 -1
- package/packages/block-sdk/portable-media-actions.js +1 -1
- package/packages/block-sdk/runtime.js +1 -1
- package/packages/block-sdk/timeline-proposal-example.js +1 -1
- package/packages/block-sdk/transcription-example.js +1 -1
- package/packages/block-sdk/video-generation-example.js +1 -1
- package/packages/block-sdk/view-bridge.js +4 -1
- package/packages/block-sdk/view-design.js +17 -14
- package/packages/block-sdk/web-example.js +1 -1
- package/packages/design-system/block-kit.css +48 -12
- package/packages/design-system/block-kit.html +3 -2
- package/blocks/mcp-motion-planner/versions/1.1.0/package.js +0 -97
- package/blocks/motion-designer/versions/1.0.1/package.js +0 -173318
- package/blocks/motion-designer/versions/1.0.2/package.js +0 -173318
- package/blocks/motion-frame-review/versions/1.1.1/package.js +0 -465
- package/blocks/motion-frame-review/versions/1.1.2/package.snapshot.js +0 -572
- package/blocks/narration/package.js +0 -1
- package/blocks/narration/versions/1.0.0/package.js +0 -221
- package/blocks/narration/versions/1.0.1/package.js +0 -221
- package/blocks/native/sm.animated-scene/versions/2.0.0/package.js +0 -475
- package/blocks/native/sm.animated-scene/versions/2.1.0/package.js +0 -498
- package/blocks/native/sm.animated-scene/versions/2.2.0/package.js +0 -524
- package/blocks/native/sm.animated-scene/versions/2.3.0/package.js +0 -618
- package/blocks/native/sm.animated-scene/versions/2.3.1/package.js +0 -618
- package/blocks/native/sm.animated-scene/versions/2.3.2/package.js +0 -723
- package/blocks/native/sm.animated-scene/versions/2.3.3/package.js +0 -723
- package/blocks/native/sm.animated-scene/versions/2.3.4/package.js +0 -676
- package/blocks/native/sm.animated-scene/versions/2.3.5/package.js +0 -693
- package/blocks/native/sm.animated-scene/versions/2.3.6/package.js +0 -700
- package/blocks/native/sm.animated-scene/versions/2.3.7/package.js +0 -747
- package/blocks/native/sm.animated-scene/versions/2.3.8/package.js +0 -769
- package/blocks/native/sm.animated-scene/versions/2.3.9/package.js +0 -769
- package/blocks/native/sm.animated-scene-assistant/versions/1.0.0/package.js +0 -183
- package/blocks/native/sm.blueprint/versions/2.0.0/package.js +0 -358
- package/blocks/native/sm.blueprint/versions/2.1.0/package.js +0 -358
- package/blocks/native/sm.blueprint/versions/2.1.1/package.js +0 -358
- package/blocks/native/sm.blueprint/versions/2.1.2/package.js +0 -358
- package/blocks/native/sm.blueprint/versions/2.1.3/package.js +0 -381
- package/blocks/native/sm.blueprint/versions/2.1.4/package.js +0 -381
- package/blocks/native/sm.blueprint/versions/2.1.5/package.js +0 -339
- package/blocks/native/sm.blueprint/versions/2.1.6/package.js +0 -356
- package/blocks/native/sm.blueprint/versions/2.1.7/package.js +0 -363
- package/blocks/native/sm.blueprint/versions/2.1.8/package.js +0 -410
- package/blocks/native/sm.blueprint/versions/2.1.9/package.js +0 -422
- package/blocks/native/sm.blueprint-assistant/versions/1.0.0/package.js +0 -172
- package/blocks/native/sm.chat/versions/2.0.0/package.js +0 -144
- package/blocks/native/sm.chat/versions/2.0.1/package.js +0 -97
- package/blocks/native/sm.chat/versions/2.0.2/package.js +0 -100
- package/blocks/native/sm.chat/versions/2.0.3/package.js +0 -100
- package/blocks/native/sm.continue-if/versions/1.0.0/package.js +0 -168
- package/blocks/native/sm.describe-media/versions/2.0.0/package.js +0 -153
- package/blocks/native/sm.describe-media/versions/2.0.1/package.js +0 -156
- package/blocks/native/sm.describe-media/versions/2.1.0/package.js +0 -140
- package/blocks/native/sm.image-editor/versions/2.0.0/package.js +0 -183
- package/blocks/native/sm.image-editor/versions/2.0.1/package.js +0 -190
- package/blocks/native/sm.new-items/versions/1.0.0/package.js +0 -191
- package/blocks/native/sm.pick-photos/versions/1.0.0/package.js +0 -334
- package/blocks/native/sm.quality-audit/versions/1.0.0/package.js +0 -192
- package/blocks/native/sm.quality-audit/versions/1.0.1/package.js +0 -192
- package/blocks/native/sm.quality-audit/versions/1.0.2/package.js +0 -195
- package/blocks/native/sm.research/versions/2.0.0/package.js +0 -175
- package/blocks/native/sm.research/versions/2.0.1/package.js +0 -184
- package/blocks/native/sm.research/versions/2.0.2/package.js +0 -187
- package/blocks/native/sm.script/versions/2.0.0/package.js +0 -144
- package/blocks/native/sm.script/versions/2.1.0/package.js +0 -144
- package/blocks/native/sm.script/versions/2.1.1/package.js +0 -144
- package/blocks/native/sm.script/versions/2.1.2/package.js +0 -105
- package/blocks/native/sm.script/versions/2.1.3/package.js +0 -152
- package/blocks/native/sm.script/versions/2.1.4/package.js +0 -156
- package/blocks/native/sm.script/versions/2.1.5/package.js +0 -156
- package/blocks/native/sm.shot-plan/versions/2.0.0/package.js +0 -301
- package/blocks/native/sm.shot-plan/versions/2.1.0/package.js +0 -528
- package/blocks/native/sm.shot-plan/versions/2.1.1/package.js +0 -528
- package/blocks/native/sm.shot-plan/versions/2.1.2/package.js +0 -528
- package/blocks/native/sm.shot-plan/versions/2.1.3/package.js +0 -536
- package/blocks/native/sm.shot-plan/versions/2.2.0/package.js +0 -800
- package/blocks/native/sm.shot-plan/versions/2.2.1/package.js +0 -920
- package/blocks/native/sm.shot-plan-assistant/versions/1.0.0/package.js +0 -173
- package/blocks/native/sm.stock-search/versions/2.0.0/package.js +0 -174
- package/blocks/native/sm.thumbnail/versions/2.0.0/package.js +0 -137
- package/blocks/native/sm.thumbnail/versions/2.0.1/package.js +0 -140
- package/blocks/native/sm.transcribe/versions/2.0.0/package.js +0 -163
- package/blocks/native/sm.transcribe/versions/2.0.1/package.js +0 -168
- package/blocks/native/sm.whiteboard-assistant/versions/1.0.0/package.js +0 -175
- package/blocks/native/sm.youtube/versions/2.0.0/package.js +0 -217
- package/blocks/occasion-story/package.js +0 -1
- package/blocks/occasion-story/versions/1.0.0/package.js +0 -1658
- package/blocks/occasion-story/versions/1.0.1/package.js +0 -1699
- package/blocks/occasion-story/versions/1.0.2/package.js +0 -1682
- package/blocks/photo-reel-composer/versions/1.0.0/package.js +0 -3043
- package/blocks/photo-reel-composer/versions/1.0.1/package.js +0 -3026
- package/blocks/photo-reel-composer/versions/1.0.2/package.js +0 -4149
- package/blocks/product-ad-variants/versions/1.0.0/package.js +0 -2911
- package/blocks/product-launch-director/versions/1.0.0/package.js +0 -103
- package/blocks/release-visuals/package.js +0 -1
- package/blocks/release-visuals/versions/1.0.0/package.js +0 -1492
- package/blocks/release-visuals/versions/1.0.1/package.js +0 -1517
- package/blocks/release-visuals/versions/1.0.2/package.js +0 -1500
- package/blocks/screen-recorder/package.js +0 -1
- package/blocks/screen-recorder/versions/1.0.0/package.js +0 -223
- package/blocks/screen-recorder/versions/1.0.1/package.js +0 -223
- package/blocks/short-form-finish/package.js +0 -1
- package/blocks/short-form-finish/versions/1.0.0/package.js +0 -1043
- package/blocks/short-form-finish/versions/1.0.1/package.js +0 -1043
- package/blocks/short-form-finish/versions/1.0.2/package.js +0 -1043
- package/blocks/short-form-finish/versions/1.0.3/package.js +0 -1026
- package/examples/saas-product-launch-reviewed-source.stillmade.json +0 -383
- package/examples/saas-product-launch.stillmade.json +0 -304
- package/packages/block-platform/dealer-inventory-video-project-type.js +0 -38
- package/packages/block-platform/first-revenue-project-types.js +0 -46
- package/packages/block-platform/fixture-starters.js +0 -13
- package/packages/block-platform/launch-project-source.js +0 -10
- package/packages/block-platform/launch-project.js +0 -30
- package/packages/block-platform/motion-project-types.js +0 -55
- package/packages/block-platform/motion-starters.js +0 -14
- package/packages/block-platform/product-ad-variants-project-type.js +0 -30
- package/packages/block-platform/product-launch-updated.js +0 -90
- package/packages/block-platform/revenue-review-project-types.js +0 -36
- package/packages/block-platform/revenue-review-starters.js +0 -13
- package/packages/block-platform/script-writer.js +0 -29
- package/packages/block-platform/website-source-block.js +0 -14
package/SDK.md
CHANGED
|
@@ -221,19 +221,20 @@ Pick the runtime from the job. No hosted AI: a recipe or isolated `javascript`,
|
|
|
221
221
|
|
|
222
222
|
### One job per Step
|
|
223
223
|
|
|
224
|
-
A Step is one job: the work between two moments where a person or the outside world must see the result. Start a new Step only at one of
|
|
224
|
+
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.
|
|
225
225
|
|
|
226
|
-
1. **A person works with the result**:
|
|
226
|
+
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.
|
|
227
227
|
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.
|
|
228
228
|
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.
|
|
229
|
+
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.
|
|
229
230
|
|
|
230
|
-
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 video
|
|
231
|
+
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.
|
|
231
232
|
|
|
232
|
-
A part that only runs in the desktop app cannot share a Step with cloud parts. Before adding a Step, name which of
|
|
233
|
+
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.
|
|
233
234
|
|
|
234
235
|
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.
|
|
235
236
|
|
|
236
|
-
Examples: "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".
|
|
237
|
+
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".
|
|
237
238
|
|
|
238
239
|
### Define the capability first
|
|
239
240
|
|
|
@@ -261,7 +262,7 @@ Give the Block a short, plain name of 40 characters or fewer that says what it d
|
|
|
261
262
|
|
|
262
263
|
### Name inputs in the job's words
|
|
263
264
|
|
|
264
|
-
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
|
|
265
|
+
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.
|
|
265
266
|
|
|
266
267
|
### Speak the job
|
|
267
268
|
|
|
@@ -269,7 +270,7 @@ These are StillMade's defaults: unless the person or the creator asked for somet
|
|
|
269
270
|
|
|
270
271
|
### Interface
|
|
271
272
|
|
|
272
|
-
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`, 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
|
|
273
|
+
If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, drawn before the first run in the shape and size of what is coming (`data-ratio="9:16"` for a vertical clip, lines for text, a player bar for sound) so every part of the screen is in place at once and the result fills a place the person already sees instead of popping in somewhere unexpected; StillMade draws it the same way on the Steps it draws; on a wide screen what goes in sits on the left and the result on the right, side by side, and they stack only on narrow screens and phones, the result's place 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.
|
|
273
274
|
|
|
274
275
|
### Shared editing
|
|
275
276
|
|
|
@@ -277,11 +278,11 @@ Projects are multiplayer. A custom view subscribes through `StillMade.onShared(c
|
|
|
277
278
|
|
|
278
279
|
### External services
|
|
279
280
|
|
|
280
|
-
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
|
|
281
|
+
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.
|
|
281
282
|
|
|
282
283
|
### Dependencies
|
|
283
284
|
|
|
284
|
-
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`. 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.
|
|
285
|
+
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.
|
|
285
286
|
|
|
286
287
|
### Security
|
|
287
288
|
|
|
@@ -363,19 +364,20 @@ An instance of a Block inside a Project Type. Each Step has its own instance ID,
|
|
|
363
364
|
An ordered production workflow. Its persisted `stages` array stores Steps for compatibility with existing projects. Users can add, remove, duplicate, configure, disable, or reorder Steps. The protected starting chat belongs to the host and cannot be replaced or remixed.
|
|
364
365
|
|
|
365
366
|
### One job per Step
|
|
366
|
-
A Step is one job: the work between two moments where a person or the outside world must see the result. Start a new Step only at one of
|
|
367
|
+
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.
|
|
367
368
|
|
|
368
|
-
1. **A person works with the result**:
|
|
369
|
+
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.
|
|
369
370
|
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.
|
|
370
371
|
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.
|
|
372
|
+
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.
|
|
371
373
|
|
|
372
|
-
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 video
|
|
374
|
+
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.
|
|
373
375
|
|
|
374
|
-
A part that only runs in the desktop app cannot share a Step with cloud parts. Before adding a Step, name which of
|
|
376
|
+
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.
|
|
375
377
|
|
|
376
378
|
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.
|
|
377
379
|
|
|
378
|
-
Examples: "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".
|
|
380
|
+
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".
|
|
379
381
|
|
|
380
382
|
### Typed connections
|
|
381
383
|
An image-producing Block can connect to an image-consuming Block. Primary ports make insertion predictable; semantic roles distinguish a source image from a character reference. Secondary inputs can bind to project context.
|
|
@@ -821,11 +823,15 @@ are an additional admission gate; they do not replace this browser boundary or
|
|
|
821
823
|
the separate computation sandbox. Keep the UI responsive: move computation into
|
|
822
824
|
`code` or `recipe`; DOM JavaScript is not the CPU-metered computation runtime.
|
|
823
825
|
|
|
824
|
-
A
|
|
825
|
-
|
|
826
|
-
`
|
|
827
|
-
input
|
|
828
|
-
|
|
826
|
+
A view adds files to an input with the + tile,
|
|
827
|
+
`<button class="sm-add" data-stillmade-add="<input>">`, or
|
|
828
|
+
`StillMade.addFiles("<input>")` from its own button, never an
|
|
829
|
+
`<input type="file">` of its own: StillMade's picker makes the working copies
|
|
830
|
+
and keeps device files on the device (see "Adding files to an input" in
|
|
831
|
+
[Frame views](/docs/build/frame-views)). To hand pixels the view drew to an
|
|
832
|
+
image input, draw them into a declared `<canvas>` and pass `getImageData` as
|
|
833
|
+
`{width,height,data:Array.from(pixels.data)}`, staying within the SDK image and
|
|
834
|
+
message budgets. Nothing is uploaded by the view.
|
|
829
835
|
|
|
830
836
|
The host supplies StillMade's shared Light, Dark and White theme variables and basic controls.
|
|
831
837
|
|
|
@@ -1071,6 +1077,19 @@ codec support. Remote assets need CORS support and an appropriate media content
|
|
|
1071
1077
|
The host omits credentials and rejects redirects. Network access remains disabled
|
|
1072
1078
|
inside the iframe; larger media should use the existing native Editor workspace.
|
|
1073
1079
|
|
|
1080
|
+
### A check, then the expensive run, on one page
|
|
1081
|
+
|
|
1082
|
+
A job the person fills in and gets a result from is one Block on one page
|
|
1083
|
+
(builder contract, "One job per Step"). When the expensive run needs a cheap
|
|
1084
|
+
check first (the script and a voice sample before the video), the view calls
|
|
1085
|
+
`StillMade.run({make: 'check'})`, shows what comes back, then calls
|
|
1086
|
+
`StillMade.run({make: 'video', script})` with what the person checked; `make`
|
|
1087
|
+
and `script` are optional inputs, and the outputs the check leaves out are
|
|
1088
|
+
`"required": false`. Each run is priced and approved by StillMade on its own:
|
|
1089
|
+
the view shows no price. The manifest, the module and what the person sees are
|
|
1090
|
+
in [Module Blocks](/docs/build/modules), "A check, then the expensive run, on
|
|
1091
|
+
one page".
|
|
1092
|
+
|
|
1074
1093
|
## Hosted text generation
|
|
1075
1094
|
|
|
1076
1095
|
Use `runtime: "capability"` to implement a portable Block that asks StillMade to
|
|
@@ -1691,139 +1710,6 @@ The authenticated host client must additionally verify account/run identity;
|
|
|
1691
1710
|
package code and custom interfaces cannot submit receipts as execution evidence.
|
|
1692
1711
|
Optional metadata remains bounded to 8 KiB and is not proof of generation.
|
|
1693
1712
|
|
|
1694
|
-
## Frame views: any interface code
|
|
1695
|
-
|
|
1696
|
-
A frame view (`"runtime": "frame"` in the view) is an interface written with
|
|
1697
|
-
ordinary web code: React, Svelte, Vue or Preact bundles, SVG, canvas and
|
|
1698
|
-
WebGL, pointer capture and drag, timers, loops, recursion, classes and Web
|
|
1699
|
-
Workers. Use it when the reviewed subset of standard views is too small for
|
|
1700
|
-
the interface you want.
|
|
1701
|
-
|
|
1702
|
-
### Folder layout
|
|
1703
|
-
|
|
1704
|
-
```
|
|
1705
|
-
my-block/
|
|
1706
|
-
stillmade.block.json
|
|
1707
|
-
src/run.js or another runtime (recipe, module, capability)
|
|
1708
|
-
src/view.html markup, for example <div id="root"></div>
|
|
1709
|
-
src/view.css styles; use StillMade's theme variables
|
|
1710
|
-
src/view.js ONE classic script: bundle with --format=iife
|
|
1711
|
-
src/view.config.json {"runtime": "frame"}
|
|
1712
|
-
tests/fixtures.json
|
|
1713
|
-
```
|
|
1714
|
-
|
|
1715
|
-
`src/view.config.json` may also set `"appearance"` and `"files"` (assets, see
|
|
1716
|
-
below). Bundle npm dependencies into `src/view.js` as an IIFE, for example:
|
|
1717
|
-
|
|
1718
|
-
```sh
|
|
1719
|
-
esbuild src/view.jsx --bundle --format=iife --minify --jsx=automatic \
|
|
1720
|
-
--define:process.env.NODE_ENV='"production"' --outfile=src/view.js
|
|
1721
|
-
```
|
|
1722
|
-
|
|
1723
|
-
Limits: markup and each stylesheet up to 256 KiB, `view.js` up to 4 MiB,
|
|
1724
|
-
and up to 32 asset files with 8 MiB in total.
|
|
1725
|
-
|
|
1726
|
-
### The `StillMade` object
|
|
1727
|
-
|
|
1728
|
-
Frame views get the same `StillMade` object as standard views: `input` and
|
|
1729
|
-
`onInput`, `run`, `useOutput`, `getShared`, `onShared`, `updateShared`,
|
|
1730
|
-
`bindShared`, `connectDocument`, `previewImage`, `previewMedia`, `render`
|
|
1731
|
-
and the rest of the interface bridge. In addition:
|
|
1732
|
-
|
|
1733
|
-
| Member | What it does |
|
|
1734
|
-
| --- | --- |
|
|
1735
|
-
| `StillMade.asset(path)` | A `blob:` URL for one of the view's asset files, for `<img>`, `new FontFace()` or `<video>`. |
|
|
1736
|
-
| `StillMade.assetBytes(path)` | A copy of an asset file's bytes as an `ArrayBuffer`. |
|
|
1737
|
-
|
|
1738
|
-
Shared state works as in every view: keep each independently edited value in
|
|
1739
|
-
its own field (for example `point0`, `point1`) so two people editing
|
|
1740
|
-
different values never conflict.
|
|
1741
|
-
|
|
1742
|
-
### Theming
|
|
1743
|
-
|
|
1744
|
-
The theme variables from StillMade's design system are always defined:
|
|
1745
|
-
`var(--bg)`, `var(--bg-elev-1)`, `var(--fg)`, `var(--fg-muted)`,
|
|
1746
|
-
`var(--accent)`, `var(--border-tok)`, `var(--divider)`, `var(--r-3)` and the
|
|
1747
|
-
rest. Colors written with them follow Light, Dark and White automatically.
|
|
1748
|
-
Frame views are not required to use them; the appearance check reports
|
|
1749
|
-
hard-coded colors as advice instead of refusing the view.
|
|
1750
|
-
|
|
1751
|
-
### Layout
|
|
1752
|
-
|
|
1753
|
-
StillMade zooms a view to fill the space its Step gives it (up to 150%), and
|
|
1754
|
-
Ctrl/Cmd +, - and 0 size the view while it has focus. It never centers a frame
|
|
1755
|
-
view; a standard view narrower than its Step is centered by its margins.
|
|
1756
|
-
|
|
1757
|
-
An app that lays itself out (a full-size canvas or WebGL, its own scroll, pan
|
|
1758
|
-
or zoom, its own Ctrl/Cmd +/- keys, as in a CAD or drawing tool) puts
|
|
1759
|
-
`data-stillmade-layout="own"` on its outermost element:
|
|
1760
|
-
|
|
1761
|
-
```html
|
|
1762
|
-
<div id="root" data-stillmade-layout="own"></div>
|
|
1763
|
-
```
|
|
1764
|
-
|
|
1765
|
-
StillMade then never centers or zooms it and leaves Ctrl/Cmd +, - and 0 to the
|
|
1766
|
-
app. Without the attribute StillMade also stays out of the way when the view
|
|
1767
|
-
looks like an app: no page padding, a clipped page, a root as tall as the frame,
|
|
1768
|
-
fixed over half of it or scrolling over half of it, a canvas, SVG or
|
|
1769
|
-
`touch-action: none` surface over 40% of it, or a canvas or SVG of 300x200 or
|
|
1770
|
-
more with `touch-action: none` (as Three.js's controls set it). Mark the root
|
|
1771
|
-
anyway, in `view.html` rather than from code: StillMade reads it before the view
|
|
1772
|
-
starts, so the app never shows StillMade's size control or loses a key to it,
|
|
1773
|
-
even while it is still loading.
|
|
1774
|
-
|
|
1775
|
-
A `<video controls>` plays in StillMade's player, the same one as everywhere in
|
|
1776
|
-
StillMade: play, timeline, time, sound and full screen along the bottom, shown
|
|
1777
|
-
while the pointer is on the video, with keys on the focused video (Space or K,
|
|
1778
|
-
the arrows, M, F) and touch. Full screen goes through StillMade, right after a
|
|
1779
|
-
click or key in the view; the frame itself has no `allow="fullscreen"`. A
|
|
1780
|
-
`<video>` without `controls` is the Block's own and StillMade never touches it,
|
|
1781
|
-
so a Block that drives playback (its own timeline, an editor or CAD viewer)
|
|
1782
|
-
leaves `controls` out, and `data-stillmade-player="native"` keeps the browser's
|
|
1783
|
-
controls. Result pictures from `StillMade.previewImage` in `sm-stage` or
|
|
1784
|
-
`sm-gallery`, and any element marked `data-stillmade-expand`, get a full-screen
|
|
1785
|
-
button in the bottom-right corner while the pointer is on them; pictures with
|
|
1786
|
-
the same value (`data-stillmade-expand="shots"`) open as one set, and
|
|
1787
|
-
`data-stillmade-expand="off"` removes the button. The keyboard reaches them
|
|
1788
|
-
too: such a picture without a `tabindex` of its own joins the tab order,
|
|
1789
|
-
keyboard focus on it shows the button, Enter or Space opens the viewer, and
|
|
1790
|
-
closing the viewer gives the focus back to the picture.
|
|
1791
|
-
|
|
1792
|
-
### Assets
|
|
1793
|
-
|
|
1794
|
-
Images, fonts, JSON, audio and video the view needs ship as files named by
|
|
1795
|
-
SHA-256, like module files:
|
|
1796
|
-
|
|
1797
|
-
```json
|
|
1798
|
-
{"runtime": "frame", "files": {"schemaVersion": 1, "files": {
|
|
1799
|
-
"fonts/inter.woff2": {"sha256": "…", "bytes": 48256, "mediaType": "font/woff2"}
|
|
1800
|
-
}}}
|
|
1801
|
-
```
|
|
1802
|
-
|
|
1803
|
-
`stillmade-block pack` fills in the digests and sizes. StillMade checks every
|
|
1804
|
-
file against its SHA-256 before the view starts.
|
|
1805
|
-
|
|
1806
|
-
### What the frame does not allow
|
|
1807
|
-
|
|
1808
|
-
The view runs in an opaque-origin frame with its own policy:
|
|
1809
|
-
|
|
1810
|
-
- no network (`fetch`, WebSocket and remote images, fonts or scripts fail);
|
|
1811
|
-
- no code from strings (`eval`, `new Function`, inline event attributes) and
|
|
1812
|
-
no extra script elements;
|
|
1813
|
-
- no navigation, popups, downloads, forms that submit, or dialogs;
|
|
1814
|
-
- no WebAssembly on the page (run heavy computation in the Block's runtime,
|
|
1815
|
-
for example a module Block, and call it with `StillMade.run`);
|
|
1816
|
-
- no storage or access to StillMade's page.
|
|
1817
|
-
|
|
1818
|
-
Workers created from `blob:` URLs are allowed and run off the page's thread.
|
|
1819
|
-
|
|
1820
|
-
### Run limits
|
|
1821
|
-
|
|
1822
|
-
StillMade compiles the view so every function and loop checks a time budget.
|
|
1823
|
-
One task may run for 250 ms; past that it is stopped with an error, and a view
|
|
1824
|
-
that keeps the page busy most of the time, stops answering, or navigates its
|
|
1825
|
-
frame is closed with a message. Move long computation into a Worker or the Block's runtime.
|
|
1826
|
-
|
|
1827
1713
|
## Outside APIs: declared origins, OAuth and published adapters
|
|
1828
1714
|
|
|
1829
1715
|
Module Blocks can call any outside API with the account of the person running
|
|
@@ -2192,312 +2078,73 @@ reads its text with pdf.js (bundled, its worker code imported directly so
|
|
|
2192
2078
|
nothing is fetched), plans the shots with one `text.generate` call that has a
|
|
2193
2079
|
JSON schema, and returns a `table` and a `shot[]` the Shots workspace accepts.
|
|
2194
2080
|
|
|
2195
|
-
##
|
|
2196
|
-
|
|
2197
|
-
A module Block (`"runtime": "module"`) is an ordinary ES module. It can use
|
|
2198
|
-
everything a modern browser Worker offers: `async`/`await`, timers, typed
|
|
2199
|
-
arrays, `WebAssembly`, `OffscreenCanvas`, `createImageBitmap`, WebCodecs, and
|
|
2200
|
-
any npm package you bundle into it, including C and C++ libraries compiled to
|
|
2201
|
-
WebAssembly (OpenCV, libvips builds, codecs, solvers), threaded WebAssembly
|
|
2202
|
-
(see Threads) and live WebSocket connections to declared origins (see Live
|
|
2203
|
-
connections). Use it when a recipe or the QuickJS `javascript` runtime is too
|
|
2204
|
-
small for the job.
|
|
2081
|
+
## Hosted media analysis
|
|
2205
2082
|
|
|
2206
|
-
|
|
2083
|
+
`media.analyze` asks StillMade's media vision a question about saved media and
|
|
2084
|
+
returns structured evidence. Declare one `video`, `image` or `image[]` (one to
|
|
2085
|
+
four images) input, one `text` or `json` output and
|
|
2086
|
+
`permissions.capabilities: ["media.analyze"]`. A video Block may bind two
|
|
2087
|
+
optional `number` inputs as a time range to look at closely (at most 12 seconds):
|
|
2207
2088
|
|
|
2208
|
-
```
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
main.js the entry (bundle npm dependencies into it)
|
|
2213
|
-
filters.wasm optional WebAssembly and data files
|
|
2214
|
-
module.json optional: {"entry": "other.js", "threads": true}
|
|
2215
|
-
tests/fixtures.json [{name, input, expected, mocks?}]
|
|
2089
|
+
```json
|
|
2090
|
+
{"schemaVersion":1,"operation":"media.analyze","media":{"$input":"clip"},
|
|
2091
|
+
"question":"Is the product logo clearly visible, and when?",
|
|
2092
|
+
"range":{"start":{"$input":"start"},"end":{"$input":"end"}},"output":"report"}
|
|
2216
2093
|
```
|
|
2217
2094
|
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2095
|
+
StillMade reads the media from the runner's own library (never from another
|
|
2096
|
+
account or an arbitrary URL), samples a video into a timestamped contact sheet
|
|
2097
|
+
or uses the still image, and asks the vision model the Block's question (1 to
|
|
2098
|
+
2,000 characters). A `json` output receives
|
|
2099
|
+
`{"schemaVersion":1,"items":[{"kind","summary","timeline","observations","detectedText","issues","confidence","range"?}]}`
|
|
2100
|
+
with timestamps in seconds; each list holds up to 20 entries. A `text` output
|
|
2101
|
+
receives the summaries (numbered when there are several images).
|
|
2222
2102
|
|
|
2223
|
-
|
|
2103
|
+
Each analyzed image or video is one priced call, paid with StillMade credits;
|
|
2104
|
+
the quote lists the price for every fixture and the sample. Media that cannot
|
|
2105
|
+
be read (not saved in the library, too large, or undecodable) is refunded. The
|
|
2106
|
+
offline SDK includes `packages/block-sdk/media-analysis-example.js`, exporting
|
|
2107
|
+
`mediaAnalysis`.
|
|
2224
2108
|
|
|
2225
|
-
|
|
2226
|
-
export default async function run(input, stillmade) {
|
|
2227
|
-
stillmade.progress(0.1, 'Loading');
|
|
2228
|
-
const { instance } = await WebAssembly.instantiate(stillmade.asset('filters.wasm'));
|
|
2229
|
-
const bytes = await stillmade.files.read(input.photo); // a saved image the Block received
|
|
2230
|
-
// ... process ...
|
|
2231
|
-
const saved = await stillmade.files.write(pngBlob, { mediaType: 'image/png', name: 'result.png' });
|
|
2232
|
-
return { result: saved };
|
|
2233
|
-
}
|
|
2234
|
-
```
|
|
2109
|
+
## Hosted web reading
|
|
2235
2110
|
|
|
2236
|
-
|
|
2237
|
-
|
|
2111
|
+
Two operations let a Block use the web without network permissions, cookies or
|
|
2112
|
+
keys. StillMade does the reading; the person running the Block pays with
|
|
2113
|
+
StillMade credits at the app's research prices, shown in the quote first.
|
|
2238
2114
|
|
|
2239
|
-
|
|
2115
|
+
**`web.fetch`** reads one public page and answers the Block's instruction from
|
|
2116
|
+
it with StillMade's fast model. Declare one `text` input holding the URL, one
|
|
2117
|
+
`text` output and `permissions.capabilities: ["web.fetch"]`:
|
|
2240
2118
|
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
| `signal` | An `AbortSignal` that fires when the person cancels. |
|
|
2245
|
-
| `progress(value, label)` | Reports progress from 0 to 1 with a short label. Each report extends the run limit. |
|
|
2246
|
-
| `log(...values)` | Adds a line to the run log (up to 500 lines). |
|
|
2247
|
-
| `asset(path)` | A copy of one of the module's own files, such as a `.wasm` build, as an `ArrayBuffer`. |
|
|
2248
|
-
| `files.read(ref)` | The bytes of a saved image, video, audio or asset the Block received as input, or that StillMade made in this run: files it wrote, transform results and generated media. Other files cannot be read, and transforms, hosted calls and actions accept only these references too. |
|
|
2249
|
-
| `files.write(data, {mediaType, name})` | Saves PNG, JPEG, WebP, GIF, MP4, WebM, MP3, WAV, M4A or OGG, or a PDF, TXT, CSV, Markdown, JSON or ZIP file, to the runner's library and returns a reference you can return as an output. At most 64 files per run. |
|
|
2250
|
-
| `media.transform(request)` | On-device media work done by StillMade: decode audio to samples, extract or loudness-normalise audio, cut, join or re-encode video, and take thumbnails. Results are saved like `files.write`. See [Media processing](#media-processing). |
|
|
2251
|
-
| `actions.run(action)` | Runs a declared portable action. `media.generate` asks the person to approve one StillMade credit maximum for the batch; `stock.search` runs directly. |
|
|
2252
|
-
| `hosted.run(operation, request)` | Calls a hosted operation the manifest lists in `permissions.capabilities` (for example `text.generate`, `image.generate`, `audio.speech`, `web.research`). Each call is quoted and approved like a capability Block and paid with StillMade credits. |
|
|
2253
|
-
| `net.fetch(url, {method, headers, body})` | Calls one of the origins declared in `permissions.network` through StillMade, which adds the person's own key or OAuth token on its servers. Returns a standard `Response`. See [Outside APIs](/docs/build/outside-apis). |
|
|
2254
|
-
| `net.connect(url, {protocols})` | Opens a live WebSocket to the `wss://` form of a declared origin and resolves with the open socket (`send`, messages, `for await`, `close`). No key is added. See [Live connections](#live-connections). |
|
|
2255
|
-
| `connections.call(appId, operationId, input)` | Calls an operation of a published adapter declared in `permissions.connections`, with the person's own account. Returns the checked JSON result. |
|
|
2256
|
-
| `storage.get/set/remove/list/info(...)` | The Block's own cloud storage in the current project: JSON values up to 1 MiB under folder-like keys. Kept across runs, devices and the Block's versions. See [Project storage, reads and assets](/docs/build/project-storage). |
|
|
2257
|
-
| `storage.putFile(key, data, {mediaType})` / `storage.getFile(key)` | Files up to 64 MiB in the same storage; `getFile` returns `{key, mediaType, bytes, data}` with `data` as an `ArrayBuffer`, or `null`. |
|
|
2258
|
-
| `project.read(fields?)` | A copy of the current project limited to the fields the manifest may read (`project.read` plus `context.<field>.read`), such as `timeline`, `shots` or `assets`. |
|
|
2259
|
-
| `assets.create(file, {name})` / `assets.appendVersion(asset, file)` | Adds a saved image, video or audio file to the project's media as a new asset (`asset.create`), or as the newest version of an asset the Block received, read or created (`asset.version.append`). |
|
|
2119
|
+
```json
|
|
2120
|
+
{"schemaVersion":1,"operation":"web.fetch","url":{"$input":"url"},"instruction":"Summarize the page in five bullet points.","output":"summary"}
|
|
2121
|
+
```
|
|
2260
2122
|
|
|
2261
|
-
|
|
2123
|
+
The URL must be a public `http` or `https` address of at most 2,048
|
|
2124
|
+
characters. StillMade refuses private, local and credentialed addresses,
|
|
2125
|
+
follows at most three redirects, reads HTML or plain text up to 5 MB, removes
|
|
2126
|
+
scripts and markup, and treats the page as untrusted data, never as
|
|
2127
|
+
instructions. A page that cannot be read is refunded. The instruction is 1 to
|
|
2128
|
+
4,000 characters.
|
|
2262
2129
|
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
}
|
|
2269
|
-
const voice = await stillmade.hosted.run('audio.speech', { text: plan.script, settings: { voice: 'adam' } });
|
|
2130
|
+
**`web.research`** searches the web for a topic and returns a cited summary.
|
|
2131
|
+
Declare one `text` or `brief` topic input (1 to 500 characters) and one `text`
|
|
2132
|
+
or `json` output with `permissions.capabilities: ["web.research"]`:
|
|
2133
|
+
|
|
2134
|
+
```json
|
|
2135
|
+
{"schemaVersion":1,"operation":"web.research","topic":{"$input":"topic"},"output":"findings"}
|
|
2270
2136
|
```
|
|
2271
2137
|
|
|
2272
|
-
|
|
2273
|
-
(
|
|
2274
|
-
|
|
2275
|
-
`
|
|
2138
|
+
A `json` output receives `{"schemaVersion":1,"summary":"...","sources":[{"url":"https://...","title":"..."}]}`
|
|
2139
|
+
(up to 12 sources); a `text` output receives the summary followed by a
|
|
2140
|
+
numbered source list. Fixture expectations are the usual text bounds, or
|
|
2141
|
+
`{"kind":"json"}` with optional `includes`.
|
|
2276
2142
|
|
|
2277
|
-
|
|
2143
|
+
The offline SDK includes `packages/block-sdk/web-example.js`, exporting
|
|
2144
|
+
`webFetch` and `webResearch`. Pair either with `text.generate` (as a `context`
|
|
2145
|
+
input) to write from what was read.
|
|
2278
2146
|
|
|
2279
|
-
|
|
2280
|
-
the person's device: Web Audio for audio, and the same renderer as Final
|
|
2281
|
-
Render for video (WebCodecs in the browser, the native renderer on desktop).
|
|
2282
|
-
It never runs on StillMade's servers and costs no credits. A transform reads
|
|
2283
|
-
only files the Block received, wrote or generated in this run, and saves its
|
|
2284
|
-
result to the runner's library, so you can return it as an output or pass it
|
|
2285
|
-
to the next transform.
|
|
2286
|
-
|
|
2287
|
-
| Request | Result |
|
|
2288
|
-
| --- | --- |
|
|
2289
|
-
| `{operation: 'audio.decode', source, sampleRate?}` | `{sampleRate, duration, samples}`: mono `Float32Array` PCM for analysis (default 16 kHz; 8000 to 48000). |
|
|
2290
|
-
| `{operation: 'audio.extract', source, format?}` | A saved WAV (16-bit, 48 kHz) or MP3 of the audio track. |
|
|
2291
|
-
| `{operation: 'audio.normalize', source, targetLufs?, format?}` | A saved WAV or MP3 at the target integrated loudness (default -16 LUFS, peaks at most -1 dBFS). |
|
|
2292
|
-
| `{operation: 'video.cut', source, keep, settings?}` | A saved MP4 with only the `keep` ranges (`[{start, end}]` seconds, ordered, up to 500). |
|
|
2293
|
-
| `{operation: 'video.concat', sources, settings?}` | A saved MP4 of 2 to 50 videos in order. |
|
|
2294
|
-
| `{operation: 'video.resize', source, settings}` | A saved MP4 re-encoded with new settings. |
|
|
2295
|
-
| `{operation: 'image.thumbnail', source, at?, width?}` | A saved PNG of one frame (default the first frame, 1280 px wide). |
|
|
2296
|
-
|
|
2297
|
-
`source`/`sources` are saved references (`{kind, assetId, versionId, url}`).
|
|
2298
|
-
Render `settings` are `{fps: 24|30|60, resolution: 720|1080|2160, quality:
|
|
2299
|
-
'low'|'medium'|'high'}`; the default is 30 fps, 1080, high. Video keeps the
|
|
2300
|
-
source's aspect ratio. Rendering follows the person's export plan, like any
|
|
2301
|
-
export.
|
|
2302
|
-
|
|
2303
|
-
Pure helpers ship as `@stillmade/block-sdk/media` for you to bundle:
|
|
2304
|
-
`silentRanges(samples, sampleRate, {thresholdDb, minSilence})`,
|
|
2305
|
-
`keepRanges(silent, duration, {padding})`, `integratedLoudness(channels,
|
|
2306
|
-
sampleRate)` (ITU-R BS.1770), `loudnessGain`, `encodeWav` and `decodeWav`.
|
|
2307
|
-
|
|
2308
|
-
```js
|
|
2309
|
-
import { silentRanges, keepRanges } from '@stillmade/block-sdk/media';
|
|
2310
|
-
|
|
2311
|
-
export default async function run(input, stillmade) {
|
|
2312
|
-
const audio = await stillmade.media.transform({ operation: 'audio.decode', source: input.video });
|
|
2313
|
-
const keep = keepRanges(silentRanges(audio.samples, audio.sampleRate), audio.duration);
|
|
2314
|
-
const trimmed = await stillmade.media.transform({ operation: 'video.cut', source: input.video, keep });
|
|
2315
|
-
return { trimmed };
|
|
2316
|
-
}
|
|
2317
|
-
```
|
|
2318
|
-
|
|
2319
|
-
For anything else, decode and encode in the Block itself: the Worker has
|
|
2320
|
-
WebCodecs (`VideoDecoder`, `VideoEncoder`, `AudioDecoder`), `OffscreenCanvas`
|
|
2321
|
-
and `createImageBitmap`, and you can bundle `mp4-muxer`, `mp4box` or a
|
|
2322
|
-
WebAssembly codec. Images arrive as saved references: read the bytes with
|
|
2323
|
-
`files.read` and decode them with `createImageBitmap`, at full resolution.
|
|
2324
|
-
|
|
2325
|
-
### Sandbox and limits
|
|
2326
|
-
|
|
2327
|
-
- The module runs in one dedicated Worker inside a response-sandboxed frame
|
|
2328
|
-
with an opaque origin. It has no network of its own (`fetch`, WebSocket and
|
|
2329
|
-
remote imports fail; `net.fetch` and `net.connect` go through StillMade), no
|
|
2330
|
-
storage of the app, no page or DOM access, and cannot read another Block's
|
|
2331
|
-
files.
|
|
2332
|
-
- Every file is fetched from StillMade's store and checked against its SHA-256
|
|
2333
|
-
before anything runs.
|
|
2334
|
-
- A run may take 2 minutes; each progress report allows 60 more seconds. Time
|
|
2335
|
-
StillMade spends on a host call (rendering a video, waiting for the person to
|
|
2336
|
-
approve credits) does not count. Every run stops after 30 minutes in total.
|
|
2337
|
-
When a limit passes, or the person cancels, the frame is removed and the
|
|
2338
|
-
Worker stops immediately, even inside an endless loop.
|
|
2339
|
-
- Generating code at run time (`new Function`, used by Emscripten bindings) is
|
|
2340
|
-
allowed inside the sandbox.
|
|
2341
|
-
|
|
2342
|
-
Module Blocks run in StillMade on the person's device: in the browser while
|
|
2343
|
-
StillMade is open, or in StillMade Desktop. StillMade has no server sandbox for
|
|
2344
|
-
them. A schedule or event that hands browser-only Steps to the owner's StillMade
|
|
2345
|
-
Desktop ("Run ... on my StillMade Desktop") pauses at a module Step until the
|
|
2346
|
-
owner's desktop app, open with "Run my automations on this computer" on, runs it
|
|
2347
|
-
with the Step's exact inputs; the workflow then continues from its result. Module
|
|
2348
|
-
Steps that call paid hosted operations, generate media or need a device grant
|
|
2349
|
-
still wait for the owner. Without that choice a background run stops before a
|
|
2350
|
-
module Step and the person runs it in the project. Marketplace listings show
|
|
2351
|
-
module Blocks as **Browser only**.
|
|
2352
|
-
|
|
2353
|
-
### Live connections
|
|
2354
|
-
|
|
2355
|
-
`stillmade.net.connect(url, {protocols})` opens a WebSocket to the `wss://` form
|
|
2356
|
-
of an origin in `permissions.network` (declare `https://stream.example.org` and
|
|
2357
|
-
connect to `wss://stream.example.org/...`) and resolves once it is open:
|
|
2358
|
-
|
|
2359
|
-
```js
|
|
2360
|
-
const socket = await stillmade.net.connect('wss://stream.example.org/v1/live', { protocols: ['json.v1'] });
|
|
2361
|
-
await socket.send(JSON.stringify({ subscribe: input.channel }));
|
|
2362
|
-
for await (const data of socket) { // text arrives as strings, binary as ArrayBuffers
|
|
2363
|
-
stillmade.progress(0.5, 'Listening'); // each progress report extends the run limit
|
|
2364
|
-
if (JSON.parse(data).done) break; // leaving the loop closes the connection (code 1000)
|
|
2365
|
-
}
|
|
2366
|
-
```
|
|
2367
|
-
|
|
2368
|
-
The socket also has `onmessage`, `onclose`, `addEventListener`, `readyState`,
|
|
2369
|
-
`protocol`, `close(code, reason)` (1000 or 3000 to 4999) and `closed`, a promise of
|
|
2370
|
-
`{code, reason, wasClean}`. `for await` yields every message not yet read, also ones
|
|
2371
|
-
that arrived before the loop began (up to 1,024 messages or 16 MiB; older ones are
|
|
2372
|
-
dropped). The module's own frame still has no network: StillMade
|
|
2373
|
-
checks each URL (`wss://`, the standard port, a declared origin, valid subprotocol
|
|
2374
|
-
names) and opens the connection in a separate relay frame that runs no Block code,
|
|
2375
|
-
then relays the messages. A run keeps at most 4 connections, sends or receives at
|
|
2376
|
-
most 1 MiB a message and 256 MiB in all, and every connection closes when the run
|
|
2377
|
-
ends. The service sees an opaque `Origin` (`null`). StillMade adds no key to a live
|
|
2378
|
-
connection: for a service that needs one, get a short-lived token with `net.fetch`
|
|
2379
|
-
first and pass it as the service documents.
|
|
2380
|
-
|
|
2381
|
-
### Threads (SharedArrayBuffer)
|
|
2382
|
-
|
|
2383
|
-
A module that runs its own Workers with shared memory (Emscripten pthreads,
|
|
2384
|
-
threaded WebAssembly builds) declares it in `module.json`:
|
|
2385
|
-
|
|
2386
|
-
```json
|
|
2387
|
-
{ "entry": "main.js", "threads": true }
|
|
2388
|
-
```
|
|
2389
|
-
|
|
2390
|
-
It then runs in a Worker that can share `SharedArrayBuffer` and
|
|
2391
|
-
`WebAssembly.Memory({shared: true})` with the Workers it starts from `blob:` URLs
|
|
2392
|
-
(module Workers and remote scripts stay unavailable, so bundle the thread code).
|
|
2393
|
-
That Worker is not a secure context, so it has no WebCodecs, WebGPU,
|
|
2394
|
-
`crypto.subtle` or `crypto.randomUUID`; a module without `threads` keeps those and
|
|
2395
|
-
cannot share memory between Workers. Threads work in Chrome and Edge (version 137
|
|
2396
|
-
or later) and in StillMade Desktop; in other browsers `self.crossOriginIsolated` is
|
|
2397
|
-
false and `SharedArrayBuffer` is missing, so check it and fall back to one thread.
|
|
2398
|
-
`files.write` and `storage.putFile` accept bytes in shared memory (they are copied).
|
|
2399
|
-
|
|
2400
|
-
### Test, pack and import
|
|
2401
|
-
|
|
2402
|
-
```sh
|
|
2403
|
-
stillmade-block test my-block # runs fixtures in a local Node worker
|
|
2404
|
-
stillmade-block preview my-block input.json
|
|
2405
|
-
stillmade-block dev my-block # the interface in your browser, reloaded on save
|
|
2406
|
-
stillmade-block gate my-block # StillMade's readiness check
|
|
2407
|
-
stillmade-block pack my-block # writes <id>-<version>.stillmade-module.json
|
|
2408
|
-
```
|
|
2409
|
-
|
|
2410
|
-
`pack my-block` is the only packing step for a module Block, and it never takes
|
|
2411
|
-
`--original` (original `.stillmade-block` archives hold recipe, JavaScript,
|
|
2412
|
-
capability and ComfyUI source only and refuse a module Block).
|
|
2413
|
-
|
|
2414
|
-
`test` and `preview` run your code on your machine in a disposable Node worker
|
|
2415
|
-
with the same host API and run limits (not a security sandbox; it is your own
|
|
2416
|
-
code). `files.read`, `files.write`, `media.transform`, actions and hosted calls need StillMade, or
|
|
2417
|
-
a host you pass to `createNodeModuleExecutor` in your own tests.
|
|
2418
|
-
|
|
2419
|
-
Import the `.stillmade-module.json` in StillMade (Create, then Import).
|
|
2420
|
-
StillMade verifies every file digest, uploads each file to its
|
|
2421
|
-
content-addressed store, and saves the Block. Fixtures then run in your
|
|
2422
|
-
browser's module sandbox.
|
|
2423
|
-
|
|
2424
|
-
Uploaded module files count against your cloud storage. A digest alone never
|
|
2425
|
-
grants access: a person can read a module file only when they uploaded those
|
|
2426
|
-
exact bytes or can open a saved Block that names it (yours, an installed or
|
|
2427
|
-
community version, or one shared with their team).
|
|
2428
|
-
|
|
2429
|
-
See `examples/module-opencv/` for a Block that runs OpenCV on a 12-megapixel
|
|
2430
|
-
photo, `examples/module-silence-trimmer/` for one that cuts the pauses out of a
|
|
2431
|
-
talking video, and `examples/module-audio-extract/` for one that saves a
|
|
2432
|
-
video's audio as WAV.
|
|
2433
|
-
|
|
2434
|
-
## Hosted media analysis
|
|
2435
|
-
|
|
2436
|
-
`media.analyze` asks StillMade's media vision a question about saved media and
|
|
2437
|
-
returns structured evidence. Declare one `video`, `image` or `image[]` (one to
|
|
2438
|
-
four images) input, one `text` or `json` output and
|
|
2439
|
-
`permissions.capabilities: ["media.analyze"]`. A video Block may bind two
|
|
2440
|
-
optional `number` inputs as a time range to look at closely (at most 12 seconds):
|
|
2441
|
-
|
|
2442
|
-
```json
|
|
2443
|
-
{"schemaVersion":1,"operation":"media.analyze","media":{"$input":"clip"},
|
|
2444
|
-
"question":"Is the product logo clearly visible, and when?",
|
|
2445
|
-
"range":{"start":{"$input":"start"},"end":{"$input":"end"}},"output":"report"}
|
|
2446
|
-
```
|
|
2447
|
-
|
|
2448
|
-
StillMade reads the media from the runner's own library (never from another
|
|
2449
|
-
account or an arbitrary URL), samples a video into a timestamped contact sheet
|
|
2450
|
-
or uses the still image, and asks the vision model the Block's question (1 to
|
|
2451
|
-
2,000 characters). A `json` output receives
|
|
2452
|
-
`{"schemaVersion":1,"items":[{"kind","summary","timeline","observations","detectedText","issues","confidence","range"?}]}`
|
|
2453
|
-
with timestamps in seconds; each list holds up to 20 entries. A `text` output
|
|
2454
|
-
receives the summaries (numbered when there are several images).
|
|
2455
|
-
|
|
2456
|
-
Each analyzed image or video is one priced call, paid with StillMade credits;
|
|
2457
|
-
the quote lists the price for every fixture and the sample. Media that cannot
|
|
2458
|
-
be read (not saved in the library, too large, or undecodable) is refunded. The
|
|
2459
|
-
offline SDK includes `packages/block-sdk/media-analysis-example.js`, exporting
|
|
2460
|
-
`mediaAnalysis`.
|
|
2461
|
-
|
|
2462
|
-
## Hosted web reading
|
|
2463
|
-
|
|
2464
|
-
Two operations let a Block use the web without network permissions, cookies or
|
|
2465
|
-
keys. StillMade does the reading; the person running the Block pays with
|
|
2466
|
-
StillMade credits at the app's research prices, shown in the quote first.
|
|
2467
|
-
|
|
2468
|
-
**`web.fetch`** reads one public page and answers the Block's instruction from
|
|
2469
|
-
it with StillMade's fast model. Declare one `text` input holding the URL, one
|
|
2470
|
-
`text` output and `permissions.capabilities: ["web.fetch"]`:
|
|
2471
|
-
|
|
2472
|
-
```json
|
|
2473
|
-
{"schemaVersion":1,"operation":"web.fetch","url":{"$input":"url"},"instruction":"Summarize the page in five bullet points.","output":"summary"}
|
|
2474
|
-
```
|
|
2475
|
-
|
|
2476
|
-
The URL must be a public `http` or `https` address of at most 2,048
|
|
2477
|
-
characters. StillMade refuses private, local and credentialed addresses,
|
|
2478
|
-
follows at most three redirects, reads HTML or plain text up to 5 MB, removes
|
|
2479
|
-
scripts and markup, and treats the page as untrusted data, never as
|
|
2480
|
-
instructions. A page that cannot be read is refunded. The instruction is 1 to
|
|
2481
|
-
4,000 characters.
|
|
2482
|
-
|
|
2483
|
-
**`web.research`** searches the web for a topic and returns a cited summary.
|
|
2484
|
-
Declare one `text` or `brief` topic input (1 to 500 characters) and one `text`
|
|
2485
|
-
or `json` output with `permissions.capabilities: ["web.research"]`:
|
|
2486
|
-
|
|
2487
|
-
```json
|
|
2488
|
-
{"schemaVersion":1,"operation":"web.research","topic":{"$input":"topic"},"output":"findings"}
|
|
2489
|
-
```
|
|
2490
|
-
|
|
2491
|
-
A `json` output receives `{"schemaVersion":1,"summary":"...","sources":[{"url":"https://...","title":"..."}]}`
|
|
2492
|
-
(up to 12 sources); a `text` output receives the summary followed by a
|
|
2493
|
-
numbered source list. Fixture expectations are the usual text bounds, or
|
|
2494
|
-
`{"kind":"json"}` with optional `includes`.
|
|
2495
|
-
|
|
2496
|
-
The offline SDK includes `packages/block-sdk/web-example.js`, exporting
|
|
2497
|
-
`webFetch` and `webResearch`. Pair either with `text.generate` (as a `context`
|
|
2498
|
-
input) to write from what was read.
|
|
2499
|
-
|
|
2500
|
-
## Hosted music and sound effects
|
|
2147
|
+
## Hosted music and sound effects
|
|
2501
2148
|
|
|
2502
2149
|
Use `runtime: "capability"` with `operation: "audio.music"` or
|
|
2503
2150
|
`operation: "audio.sfx"` to create music or a sound effect with the app's own
|
|
@@ -4129,7 +3776,10 @@ Standard and frame views receive the same `StillMade` object; members marked Fra
|
|
|
4129
3776
|
| --- | --- | --- |
|
|
4130
3777
|
| `readonly input: Partial<In>` | Every view | A copy of the current input values. Read it after `onInput` first fires. |
|
|
4131
3778
|
| `onInput(callback: (input: Partial<In>) => void): () => void` | Every view | Receives the input when the view starts and each time the host changes it. Returns a function that stops listening. Initialize controls here. |
|
|
4132
|
-
| `
|
|
3779
|
+
| `addFiles(port: string): Promise<{added: number}>` | A saved project Step and the SDK preview | Opens StillMade's own file picker for a declared `image`, `video`, `audio`, `asset` or `file` input (or a list of them) and saves what the person chooses through StillMade's upload, as the Step's own tiles do. Call it from a click or key press in the view; a list gets the files added, a single input the first. The saved values arrive through `onInput`; the view never receives a File. A `<button class="sm-add" data-stillmade-add="port">` does this with no code. Refused for an undeclared or non-file input, an input an earlier Step fills, a viewer, and without a gesture. |
|
|
3780
|
+
| `removeFile(port: string, index: number): Promise<{files: number}>` | A saved project Step and the SDK preview | Removes the file at `index` (from 0) of a declared list input (`image[]`, `video[]`, `audio[]`, `asset[]` or `file[]`), as the remove button on the Step's own tile does, and resolves to how many files the list now holds. Call it from a click or key press in the view; the new list arrives through `onInput`. A `<button data-stillmade-remove="port" data-index="2">` does this with no code. Refused for an undeclared, non-file or single input, an input an earlier Step fills, a viewer, Chat, a file that changed since the view was given it, and without a gesture. |
|
|
3781
|
+
| `moveFile(port: string, from: number, to: number): Promise<{files: number}>` | A saved project Step and the SDK preview | Moves the file at `from` to the place `to` (both from 0) in a declared list input, as the Step's own move earlier and move later buttons do, so the person sets the order (a tour, a slideshow) on the Block's own screen. Same gesture and refusals as `removeFile`. A `<button data-stillmade-move="port" data-index="2" data-to="1">` does this with no code; a `data-to` past either end does nothing. |
|
|
3782
|
+
| `run(values: In): Promise<Out>` | Every view | Checks the named input values, runs the Block (its recipe, JavaScript, module or hosted operation, after any approval the host asks for) and resolves to the checked outputs. On a project Step and in the SDK preview, inputs it leaves out run with the Step's current values (its photos, an earlier Step's result), so a view passes only what it sets; what it passes is kept as the Step's input. One run at a time, 30 a minute. |
|
|
4133
3783
|
| `useOutput(outputs: Partial<Out>): Promise<Record<string, unknown>>` | A saved project Step | Chooses media the view already holds (input media or a desktop recording) as the Block outputs without running. Only complete references the host issued are accepted. |
|
|
4134
3784
|
| `readonly schemaFields: Readonly<Record<string, unknown>>` | Every view | Helpers that build form fields from the input ports and their schemas, honoring inputs the host locked. |
|
|
4135
3785
|
|
|
@@ -4183,19 +3833,14 @@ StillMade's own Blocks run on host services. Each one is listed here with the pu
|
|
|
4183
3833
|
|
|
4184
3834
|
| What StillMade's Blocks do | How a creator Block does it | Notes |
|
|
4185
3835
|
| --- | --- | --- |
|
|
4186
|
-
| Animated scene assets: generated props and backgrounds, scene video slicing, sprite sheets. | hosted `image.generate`, hosted `video.generate`, action `media.generate`, `stillmade.media.transform`, `stillmade.files.write`, `stillmade.assets.create`, permission `context.shots.read`, permission `workspace.animation.propose` | Same capability |
|
|
4187
|
-
| Browses other projects' scripts the person can open. | `stillmade.project.read` | Gap: Blocks read only the current project; reading another project the person can open is first-party only. |
|
|
4188
3836
|
| Canvas generation: images, videos, variations, stock and recording the asset. | hosted `image.generate`, hosted `video.generate`, action `media.generate`, action `stock.search`, `stillmade.assets.create` | Same capability |
|
|
4189
3837
|
| Canvas media: stock search, uploads and generation with prices. | hosted `image.generate`, hosted `video.generate`, action `media.generate`, action `stock.search`, `stillmade.files.write`, control `file` | Same capability |
|
|
4190
3838
|
| Canvas node placement for shipped workflow nodes. | permission `context.canvas.read`, permission `workspace.canvas.propose` | Same capability |
|
|
4191
3839
|
| Canvas workspace chrome (low-balance ribbon, default model). | `IMAGE_GENERATION_MODELS` from the SDK | The credit balance ribbon is account UI; creator Blocks see prices in StillMade's own approval dialog before any paid run. |
|
|
4192
|
-
| Collaborative script editing. | `StillMade.getShared`, `StillMade.updateShared`, `StillMade.connectDocument` | Same capability |
|
|
4193
3840
|
| Composes the Canvas workspace from its native parts. | permission `workspace.canvas.propose` | StillMade React components and hooks only render inside the app; creator interfaces are frame views (any framework) or standard views with generated controls, styled with the same theme variables. |
|
|
4194
|
-
| Connects a YouTube channel. | `stillmade.connections.call`, `stillmade.net.fetch` | Same capability |
|
|
4195
3841
|
| Creates a visual style with generated samples. | hosted `image.generate`, permission `context.styles.read` | Same capability |
|
|
4196
3842
|
| Credits or the person's own key for image, video, chat, voice, music and effects. | hosted `text.generate`, hosted `image.generate`, hosted `video.generate`, hosted `audio.speech`, hosted `audio.music`, hosted `audio.sfx` | Same capability |
|
|
4197
3843
|
| Crops panel images. | `stillmade.files.read`, `stillmade.files.write`, `stillmade.media.transform` | Same capability |
|
|
4198
|
-
| Describes images and media for prompts and context. | hosted `image.describe`, hosted `media.analyze` | Same capability |
|
|
4199
3844
|
| Downloads the media a controller selected, under the export policy. | control `download`, port type `file-bundle`, action `final.render` | Same capability |
|
|
4200
3845
|
| Editor asset generation with a model, prompt, aspect, quality and reference images. | hosted `image.generate`, hosted `video.generate`, action `media.generate` | Same capability |
|
|
4201
3846
|
| Edits panel settings with local runs and confirmations. | `StillMade.run`, `StillMade.updateShared` | Same capability |
|
|
@@ -4219,23 +3864,15 @@ StillMade's own Blocks run on host services. Each one is listed here with the pu
|
|
|
4219
3864
|
| Runs a draft recipe or JavaScript Block locally while it is edited. | `StillMade.run` | Same capability |
|
|
4220
3865
|
| Runs Editor operations: renders, previews and timeline edits. | `stillmade.media.transform`, action `final.render`, permission `timeline.propose` | Same capability |
|
|
4221
3866
|
| Runs the production pipeline: assembly, generation, budgets, references and deliveries. | hosted `image.generate`, hosted `video.generate`, action `media.generate`, hosted `text.generate`, `stillmade.files.write`, permission `workspace.canvas.propose`, trigger `media.saved` | Same capability |
|
|
4222
|
-
| Runs the shot planning workspace in its sandbox. | `StillMade.run`, permission `workspace.shotPlan.propose` | Same capability |
|
|
4223
3867
|
| Score and sound effect lanes: generated music and effects placed in the Editor. | hosted `audio.music`, hosted `audio.sfx`, action `media.generate`, action `stock.search`, permission `timeline.propose` | Same capability |
|
|
4224
|
-
| Script services: drafts, models and project context. | hosted `text.generate`, permission `context.script.read`, `stillmade.project.read` | Same capability |
|
|
4225
|
-
| Shot planning services: shots, versions and planning with a model. | hosted `text.generate`, permission `context.shots.read`, permission `workspace.shotPlan.propose`, port type `shot-plan` | Same capability |
|
|
4226
3868
|
| Shows a panel image large with its narration and text. | `StillMade.previewImage` | Same capability |
|
|
4227
|
-
| Shows the script in its workspace layout. | port type `script`, control `document` | StillMade React components and hooks only render inside the app; creator interfaces are frame views (any framework) or standard views with generated controls, styled with the same theme variables. |
|
|
4228
3869
|
| StillMade AI presents a workspace and adds board elements. | action `presenter.generate`, permission `workspace.board.propose` | Same capability |
|
|
4229
3870
|
| StillMade dialogs and notices for Image Panels. | `StillMade.render` | StillMade's own dialogs stay host-owned so a Block cannot imitate account, payment or permission prompts; creator interfaces show their own confirmations inside their frame. |
|
|
4230
|
-
| Story bible editing with collaboration and the bible graph hash. | `StillMade.getShared`, `StillMade.updateShared`, `StillMade.connectDocument`, permission `context.blueprint.read`, permission `workspace.blueprint.propose`, hosted `text.generate` | Same capability |
|
|
4231
|
-
| Story bible workspace UI (selects, step header, bottom sheet, help). | `StillMade.render`, `StillMade.schemaFields` | StillMade React components and hooks only render inside the app; creator interfaces are frame views (any framework) or standard views with generated controls, styled with the same theme variables. |
|
|
4232
3871
|
| Text, story segments and voiceover inside pipeline nodes. | hosted `text.generate`, hosted `audio.speech` | Same capability |
|
|
4233
3872
|
| The Canvas preview shown for a Step. | `StillMade.render`, permission `context.canvas.read` | StillMade React components and hooks only render inside the app; creator interfaces are frame views (any framework) or standard views with generated controls, styled with the same theme variables. |
|
|
4234
3873
|
| The Editor media library: project assets and uploads. | permission `context.assets.read`, `stillmade.project.read`, `stillmade.assets.create`, `stillmade.assets.appendVersion` | Same capability |
|
|
4235
3874
|
| The Editor preview shown for a Step. | `StillMade.previewMedia`, permission `context.timeline.read` | StillMade React components and hooks only render inside the app; creator interfaces are frame views (any framework) or standard views with generated controls, styled with the same theme variables. |
|
|
4236
|
-
| The host side of the shot planning sandbox. | `StillMade.run`, permission `workspace.shotPlan.propose` | Same capability |
|
|
4237
3875
|
| The voiceover preview shown for a Step. | `StillMade.previewMedia` | StillMade React components and hooks only render inside the app; creator interfaces are frame views (any framework) or standard views with generated controls, styled with the same theme variables. |
|
|
4238
|
-
| Uploads a stored export to YouTube. | action `youtube.upload` | Same capability |
|
|
4239
3876
|
| Uploads and checks media files, thumbnails and downloads. | control `file`, `stillmade.files.write`, `stillmade.assets.create` | Same capability |
|
|
4240
3877
|
| Vectorizes and upscales panel images. | `stillmade.asset`, `stillmade.files.read`, `stillmade.files.write` | Gap: Upscaling with a hosted model is not a hosted operation yet; module Blocks can only upscale with a model they ship in WebAssembly. |
|
|
4241
3878
|
| Video assets workspace: model limits, durations, voices, prices and batch generation. | hosted `image.generate`, hosted `video.generate`, action `media.generate`, hosted `audio.speech`, `VIDEO_GENERATION_MODELS` from the SDK | Same capability |
|
|
@@ -4561,12 +4198,20 @@ page, bridge, response policy, theme, run limits and guard that run your
|
|
|
4561
4198
|
interface in the app, so what works here works there.
|
|
4562
4199
|
|
|
4563
4200
|
- Your interface (standard or frame view) runs with the first fixture's input.
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4201
|
+
Pick another fixture, switch Light, Dark and White, and try phone, tablet and
|
|
4202
|
+
full widths. The **Input data** tab of the panel below the Block shows that
|
|
4203
|
+
input as JSON. An edit there applies when you leave the box (click outside it
|
|
4204
|
+
or press Tab), not as you type; the view then hears it through `onInput`. Text
|
|
4205
|
+
that is not one JSON object is named under the box and not applied.
|
|
4206
|
+
- `StillMade.run` runs the Block on your machine with the SDK runtime. As on a
|
|
4207
|
+
project Step, the inputs the view leaves out are the current input's, so
|
|
4208
|
+
`StillMade.run({make: 'check'})` still gets the example's photos; what the
|
|
4209
|
+
view passes is kept in the input. Hosted
|
|
4567
4210
|
operations, files, network, connections and storage are answered by
|
|
4568
4211
|
[stand-ins](/docs/build/testing), and stand-in media (images, audio, video)
|
|
4569
4212
|
previews in the view. Shared view state (`updateShared`) is kept in the page.
|
|
4213
|
+
The view's `addFiles`, `removeFile` and `moveFile` change the input's files
|
|
4214
|
+
here as they change a Step's.
|
|
4570
4215
|
- Saving any file the package reads reloads the preview, keeping the input and
|
|
4571
4216
|
shared state. With `--build "npm run build"`, saving any other source file
|
|
4572
4217
|
(such as `src/view.jsx`) runs the build first, then reloads.
|
|
@@ -4730,6 +4375,8 @@ An optional `outcome` field declares the finished result required by this exact
|
|
|
4730
4375
|
|
|
4731
4376
|
`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.
|
|
4732
4377
|
|
|
4378
|
+
Which result reaches each type: `export` is StillMade's server render of the project's saved Editor timeline (the export in a full-project share), `delivery` is a run's result the person publishes with a share link, and `project_outcome` is an editable project handed over through a share. A Block's own `timeline.render` is that Block's run output, not an Editor export, so it never reaches `export`: a Type whose Block renders the finished video itself, with no Editor Step, declares `delivery`, which the published video reaches.
|
|
4379
|
+
|
|
4733
4380
|
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.
|
|
4734
4381
|
|
|
4735
4382
|
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.
|
|
@@ -4818,6 +4465,11 @@ items may also set `references` (images), `firstFrame`/`lastFrame` (video),
|
|
|
4818
4465
|
- `music`: `settings: {durationSec}`, 5 to 240 whole seconds (default 30).
|
|
4819
4466
|
- `sfx`: `settings: {durationSec}`, 0.5 to 22 seconds in tenths (default 4).
|
|
4820
4467
|
|
|
4468
|
+
Video items take `settings: {model, duration, resolution, aspectRatio, generateAudio}`.
|
|
4469
|
+
With no `model`, an `aspectRatio` the default model (Vidu Q3 Turbo, which takes none)
|
|
4470
|
+
cannot make picks the cheapest catalog model that makes it with the frames and
|
|
4471
|
+
`duration` given, so `{aspectRatio: "9:16"}` alone asks for a vertical clip.
|
|
4472
|
+
|
|
4821
4473
|
```js
|
|
4822
4474
|
return {request: {
|
|
4823
4475
|
schemaVersion: 1, kind: "stillmade.media-action", operation: "media.generate",
|
|
@@ -4846,8 +4498,9 @@ price, a provider, a key or another Block. References and frames must be images
|
|
|
4846
4498
|
saved in the user's own library. Up to 50 items per action.
|
|
4847
4499
|
|
|
4848
4500
|
When it finishes, the declared `resultPort` receives an array of
|
|
4849
|
-
`{key, kind, assetId, versionId, url}` in item order (`kind` is `image`,
|
|
4850
|
-
`video` or `audio`
|
|
4501
|
+
`{key, kind, assetId, versionId, url, duration?}` in item order (`kind` is `image`,
|
|
4502
|
+
`video` or `audio`; `duration`, in seconds, is the length a video, music or sound
|
|
4503
|
+
effect item asked for; speech has none, so decode it with `audio.decode` to time it); failed items are listed
|
|
4851
4504
|
separately and are not charged beyond what the provider actually received.
|
|
4852
4505
|
|
|
4853
4506
|
## Project defaults and onboarding
|
|
@@ -4950,7 +4603,21 @@ node packages/block-cli/cli.js test my-type.json
|
|
|
4950
4603
|
node packages/block-cli/cli.js preview my-type.json
|
|
4951
4604
|
node packages/block-cli/cli.js pack my-type.json my-type.stillmade.json
|
|
4952
4605
|
```
|
|
4953
|
-
The SDK includes `examples/text-workflow.stillmade.json
|
|
4606
|
+
The SDK includes `examples/text-workflow.stillmade.json`, the Type `create-type` writes. Host workspaces use existing project context and controls; switching to a Step does not automatically call a paid generation provider.
|
|
4607
|
+
|
|
4608
|
+
### A video and the Editor
|
|
4609
|
+
A Type whose own Block renders the finished video (`timeline.render`) needs no Editor Step: that Block's page holds the inputs, a cheap check and the video. The Editor is a Step only when the person cuts by hand; then the Type ends there, and since the Editor (1.0) has captions and export built in, Export video is never a Step beside it (builder contract, "One job per Step"). The `create-type` scaffold ends in the Editor that way. `validate` and `test` do not check where a Type ends.
|
|
4610
|
+
|
|
4611
|
+
### Module Blocks in a Project Type
|
|
4612
|
+
A Type carries a module Block in `packages` with its files as text: `{manifest, module, moduleSource, tests}`, where `moduleSource` maps each file in `module.files` to its JavaScript text, exactly the bytes `module.files` names by size and sha256. A packed `.stillmade-module.json` stores the same files as base64; never paste that base64 into `moduleSource`. `validate` refuses a `moduleSource` that does not match its digest.
|
|
4613
|
+
|
|
4614
|
+
Let the CLI embed the Block instead. An entry of `packages` may be the path of the Block's packed file or of its folder, relative to the Type file, or the packed bundle itself:
|
|
4615
|
+
```json
|
|
4616
|
+
{"packages": ["dist/creator.listing-voiceover-1.0.0.stillmade-module.json", "listing-video"]}
|
|
4617
|
+
```
|
|
4618
|
+
`validate`, `test`, `preview` and `pack` read those entries and check every file against its digest, and `pack` writes the Type with each Block embedded, the file to import in StillMade. Only JavaScript travels this way: a module Block with `.wasm` or data files, or a frame view with its own files, cannot be embedded in a Type.
|
|
4619
|
+
|
|
4620
|
+
`test`, `preview` and `pack` run module Blocks as `test` runs one Block: in the SDK's local module runner, each fixture answered by its own `mocks`. In the connected run a module Step is answered by its first fixture's mocks, or with `--mock` by recorded stand-ins. Nothing calls a provider, and the report lists every stand-in with `liveVerified: false`. A file a mock names by path is read from the Type file's folder.
|
|
4954
4621
|
|
|
4955
4622
|
### Protect existing projects
|
|
4956
4623
|
Saved projects retain their pinned type definition. Installing a newer type never silently replaces a workflow in progress. See [conditions](/docs/project-types/conditions), [testing](/docs/project-types/testing), and [versioning](/docs/distribute/versioning).
|
|
@@ -5748,7 +5415,7 @@ These indicators do not grant filesystem, process, system recording, or other
|
|
|
5748
5415
|
native permissions. Unsupported native APIs still fail admission/runtime checks.
|
|
5749
5416
|
|
|
5750
5417
|
### UI schema
|
|
5751
|
-
Use an array of `{control, port}` objects. Controls are `text`, `number`, `slider`, `checkbox`,
|
|
5418
|
+
Use an array of `{control, port}` objects. Controls are `text`, `number`, `slider`, `checkbox`, `asset`, `select`, `multiselect`, `file`, `date`, `color`, `url`, `richtext`, `list`, `grid` (the list StillMade validates). Every control binds to a declared input. A `text` control is a box that grows with its lines; add `"multiline": true` for anything typed one item per line or as a list (`{"control":"text","port":"listingFacts","multiline":true}`), so no host draws it as one line. Image, video, audio and file inputs, and their lists, take `asset`; a picture list shows photo tiles. The host renders these fallback controls in StillMade’s style. A separate package-level `view` may supply a [sandboxed custom interface](/docs/build/custom-interface); it never loads into the parent app DOM.
|
|
5752
5419
|
|
|
5753
5420
|
### Permissions
|
|
5754
5421
|
Supported families are `project`, `network`, `filesystem`, `secrets`, and `capabilities`. Network, filesystem, and secrets arrays must be empty. A capability runtime declares exactly one supported operation: `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`; reviewed ComfyUI declares `comfyui.execute`. These permissions do not grant JavaScript or a custom interface direct host access. Supported context read permissions are listed in [project context](/docs/build/context).
|
|
@@ -6356,7 +6023,8 @@ export const GENERATION_LIMITS: Readonly<{references:8;negativePrompt:2000;seed:
|
|
|
6356
6023
|
/** Fill and check image settings; `strict` rejects values the model does not accept. */
|
|
6357
6024
|
export function imageSettings(value?:Partial<ImageSettings>,options?:{strict?:boolean}):ImageSettings;
|
|
6358
6025
|
/** Fill and check video settings, including Veo's 1080p-is-8-seconds rule. */
|
|
6359
|
-
|
|
6026
|
+
/** With no model named, the default is the cheapest model that makes the asked aspect ratio and takes the frames given. */
|
|
6027
|
+
export function videoSettings(value?:Partial<VideoSettings>,options?:{strict?:boolean;firstFrame?:boolean;lastFrame?:boolean}):VideoSettings;
|
|
6360
6028
|
export function validateSeed(seed:number):number;
|
|
6361
6029
|
export const MEDIA_GENERATE_LIMITS: Readonly<{items:50;prompt:4000}>;
|
|
6362
6030
|
export const MEDIA_GENERATE_OPERATIONS: readonly ['image','video','speech','music','sfx'];
|
|
@@ -7475,19 +7143,20 @@ Pick the runtime from the job. No hosted AI: a recipe or isolated `javascript`,
|
|
|
7475
7143
|
|
|
7476
7144
|
### One job per Step
|
|
7477
7145
|
|
|
7478
|
-
A Step is one job: the work between two moments where a person or the outside world must see the result. Start a new Step only at one of
|
|
7146
|
+
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.
|
|
7479
7147
|
|
|
7480
|
-
1. **A person works with the result**:
|
|
7148
|
+
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.
|
|
7481
7149
|
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.
|
|
7482
7150
|
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.
|
|
7151
|
+
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.
|
|
7483
7152
|
|
|
7484
|
-
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 video
|
|
7153
|
+
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.
|
|
7485
7154
|
|
|
7486
|
-
A part that only runs in the desktop app cannot share a Step with cloud parts. Before adding a Step, name which of
|
|
7155
|
+
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.
|
|
7487
7156
|
|
|
7488
7157
|
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.
|
|
7489
7158
|
|
|
7490
|
-
Examples: "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".
|
|
7159
|
+
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".
|
|
7491
7160
|
|
|
7492
7161
|
### Define the capability first
|
|
7493
7162
|
|
|
@@ -7515,7 +7184,7 @@ Give the Block a short, plain name of 40 characters or fewer that says what it d
|
|
|
7515
7184
|
|
|
7516
7185
|
### Name inputs in the job's words
|
|
7517
7186
|
|
|
7518
|
-
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
|
|
7187
|
+
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.
|
|
7519
7188
|
|
|
7520
7189
|
### Speak the job
|
|
7521
7190
|
|
|
@@ -7523,7 +7192,7 @@ These are StillMade's defaults: unless the person or the creator asked for somet
|
|
|
7523
7192
|
|
|
7524
7193
|
### Interface
|
|
7525
7194
|
|
|
7526
|
-
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`, 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
|
|
7195
|
+
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.
|
|
7527
7196
|
|
|
7528
7197
|
### Shared editing
|
|
7529
7198
|
|
|
@@ -7531,11 +7200,11 @@ Projects are multiplayer. A custom view subscribes through `StillMade.onShared(c
|
|
|
7531
7200
|
|
|
7532
7201
|
### External services
|
|
7533
7202
|
|
|
7534
|
-
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
|
|
7203
|
+
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.
|
|
7535
7204
|
|
|
7536
7205
|
### Dependencies
|
|
7537
7206
|
|
|
7538
|
-
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`. 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.
|
|
7207
|
+
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.
|
|
7539
7208
|
|
|
7540
7209
|
### Security
|
|
7541
7210
|
|
|
@@ -7635,17 +7304,17 @@ For hosted text generation use {manifest,capability,tests}, runtime:"capability"
|
|
|
7635
7304
|
|
|
7636
7305
|
### Hosted media operations
|
|
7637
7306
|
|
|
7638
|
-
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},"language":{"type":"text","default":"auto"}},"outputs":{"transcript":{"type":"transcript","primary":true}},"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}}}]}.
|
|
7307
|
+
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}}}]}.
|
|
7639
7308
|
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}}}]}.
|
|
7640
7309
|
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}}}]}.
|
|
7641
|
-
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."}},"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"]}}}]}.
|
|
7642
|
-
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},"direction":{"type":"text","default":"Polish this cut conservatively."}},"outputs":{"edit":{"type":"timeline-edit","primary":true}},"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}}}]}.
|
|
7643
|
-
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}},"outputs":{"video":{"type":"video","primary":true}},"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}}}]}.
|
|
7310
|
+
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"]}}}]}.
|
|
7311
|
+
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}}}]}.
|
|
7312
|
+
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}}}]}.
|
|
7644
7313
|
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.
|
|
7645
7314
|
|
|
7646
7315
|
### Module Blocks
|
|
7647
7316
|
|
|
7648
|
-
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: let it fail the run rather than catching it and returning an empty result. 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).
|
|
7317
|
+
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).
|
|
7649
7318
|
|
|
7650
7319
|
### ComfyUI workflows
|
|
7651
7320
|
|
|
@@ -7681,7 +7350,7 @@ Write the description in third-grade language: one short sentence, ideally 8–1
|
|
|
7681
7350
|
|
|
7682
7351
|
### Compact interface
|
|
7683
7352
|
|
|
7684
|
-
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
|
|
7353
|
+
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.
|
|
7685
7354
|
|
|
7686
7355
|
### Generated controls first
|
|
7687
7356
|
|
|
@@ -7693,7 +7362,7 @@ Optional custom view interfaces are supported, but generated manifest.ui control
|
|
|
7693
7362
|
|
|
7694
7363
|
*When:* StillMade's builder adds this section when the person asks for an interface, preview or controls, or refines a Block that has one.
|
|
7695
7364
|
|
|
7696
|
-
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`). 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 a + tile. Never 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. 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 with the result first and the action within reach. 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.
|
|
7365
|
+
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.
|
|
7697
7366
|
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`:
|
|
7698
7367
|
|
|
7699
7368
|
- **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:`.
|
|
@@ -7739,7 +7408,7 @@ not proof of multiplayer operation. Test two interfaces exchanging actual edits
|
|
|
7739
7408
|
and saved outputs, permission loss, reconnect and conflicts before claiming
|
|
7740
7409
|
collaboration support. Never claim arbitrary imported JavaScript state syncs
|
|
7741
7410
|
merely because the package passed its computation fixtures.
|
|
7742
|
-
Example view for a package taking and returning text: {"html":"<main class=\"sm-block\"><
|
|
7411
|
+
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});"}.
|
|
7743
7412
|
|
|
7744
7413
|
<!-- authoring-guide:end -->
|
|
7745
7414
|
|
|
@@ -8498,11 +8167,15 @@ are an additional admission gate; they do not replace this browser boundary or
|
|
|
8498
8167
|
the separate computation sandbox. Keep the UI responsive: move computation into
|
|
8499
8168
|
`code`; DOM JavaScript is not the CPU-metered computation runtime.
|
|
8500
8169
|
|
|
8501
|
-
A
|
|
8502
|
-
|
|
8503
|
-
`
|
|
8504
|
-
input
|
|
8505
|
-
|
|
8170
|
+
A view adds files to an input with the + tile,
|
|
8171
|
+
`<button class="sm-add" data-stillmade-add="<input>">`, or
|
|
8172
|
+
`StillMade.addFiles("<input>")` from its own button, never an
|
|
8173
|
+
`<input type="file">` of its own: StillMade's picker makes the working copies
|
|
8174
|
+
and keeps device files on the device (see "Adding files to an input" in
|
|
8175
|
+
[Frame views](/docs/build/frame-views)). To hand pixels the view drew to an
|
|
8176
|
+
image input, draw them into a declared `<canvas>` and pass `getImageData` as
|
|
8177
|
+
`{width,height,data:Array.from(pixels.data)}`, staying within the SDK image and
|
|
8178
|
+
message budgets. Nothing is uploaded by the view.
|
|
8506
8179
|
|
|
8507
8180
|
The host supplies StillMade's shared Light, Dark and White theme variables and basic controls.
|
|
8508
8181
|
|
|
@@ -9474,9 +9147,14 @@ video), or `{key,operation:"speech",prompt,settings?:{voice,speed}}` (prompt is
|
|
|
9474
9147
|
the text to speak, up to 3,800 characters; a catalog voice id such as `adam`;
|
|
9475
9148
|
speed 0.25–4), `{key,operation:"music",prompt,settings?:{durationSec}}` (5–240
|
|
9476
9149
|
whole seconds) or `{key,operation:"sfx",prompt,settings?:{durationSec}}`
|
|
9477
|
-
(0.5–22 seconds in tenths).
|
|
9478
|
-
|
|
9479
|
-
|
|
9150
|
+
(0.5–22 seconds in tenths). A video item with `settings.aspectRatio` (`"9:16"`)
|
|
9151
|
+
and no `model` gets the cheapest catalog model that makes that shape. StillMade
|
|
9152
|
+
asks once for the whole batch, showing the most it can cost in StillMade
|
|
9153
|
+
credits, so put every item one run needs (one clip per photo) in one batch;
|
|
9154
|
+
never include prices, providers or keys. The result port receives
|
|
9155
|
+
`[{key,kind,assetId,versionId,url,duration?}]` with `kind` `image`, `video` or
|
|
9156
|
+
`audio`; `duration` (seconds) is the length a video, music or sound effect asked
|
|
9157
|
+
for, and speech has none (decode it with `audio.decode` to time it).
|
|
9480
9158
|
|
|
9481
9159
|
### Hosted video generation
|
|
9482
9160
|
|
|
@@ -9787,8 +9465,10 @@ For deterministic browser checks, mark a custom form control with
|
|
|
9787
9465
|
test fixture through the real UI before pressing its run action. This marker
|
|
9788
9466
|
does not change the Block's input contract or grant access to host services.
|
|
9789
9467
|
For a text-bearing object fixture such as `{brief:{text:"…"}}`, the profile
|
|
9790
|
-
enters its `text` into the marked text field
|
|
9791
|
-
`data-stillmade-action="run"`
|
|
9468
|
+
enters its `text` into the marked text field, then presses the button marked
|
|
9469
|
+
`data-stillmade-action="run"` (or the view's only button). Every button carries
|
|
9470
|
+
`data-stillmade-action` so Chat can press it: `run` on the button that runs the
|
|
9471
|
+
Block, and what it does on every other button (`data-stillmade-action="play sample"`).
|
|
9792
9472
|
The existing `data-stillmade-share` marker is also recognized for shared text
|
|
9793
9473
|
fields. A custom view without a reachable mapped control must not be counted
|
|
9794
9474
|
as having passed its fixture's success interaction.
|
|
@@ -9851,14 +9531,7 @@ wins. Open `packages/design-system/block-kit.html` in a browser to see every
|
|
|
9851
9531
|
piece in Light, Dark and White.
|
|
9852
9532
|
|
|
9853
9533
|
```html
|
|
9854
|
-
<main class="sm-block"
|
|
9855
|
-
<section class="sm-stage" id="stage" data-ratio="16:9" aria-live="polite">
|
|
9856
|
-
<div class="sm-empty" id="empty">
|
|
9857
|
-
<span class="sm-glyph" data-glyph="video" aria-hidden="true"></span>
|
|
9858
|
-
<p>Drop a clip</p>
|
|
9859
|
-
</div>
|
|
9860
|
-
</section>
|
|
9861
|
-
|
|
9534
|
+
<main class="sm-block">
|
|
9862
9535
|
<fieldset class="sm-choices" id="look" data-stillmade-share="look" data-ratio="16:9">
|
|
9863
9536
|
<legend>Look</legend>
|
|
9864
9537
|
<label><input type="radio" name="look" value="warm" checked><img src="data:..." alt=""><span>Warm</span></label>
|
|
@@ -9873,21 +9546,33 @@ piece in Light, Dark and White.
|
|
|
9873
9546
|
<input id="caption" data-stillmade-input="caption" placeholder="Optional">
|
|
9874
9547
|
</div>
|
|
9875
9548
|
|
|
9876
|
-
<div class="sm-actions">
|
|
9877
|
-
<button type="button" class="sm-primary" id="run" data-stillmade-action="make the clip">Make the clip</button>
|
|
9878
|
-
<p class="sm-status" id="status" role="status"></p>
|
|
9879
|
-
</div>
|
|
9880
|
-
|
|
9881
9549
|
<details class="sm-more">
|
|
9882
9550
|
<summary>More</summary>
|
|
9883
9551
|
<div class="sm-fields"><!-- secondary settings --></div>
|
|
9884
9552
|
</details>
|
|
9553
|
+
|
|
9554
|
+
<div class="sm-actions">
|
|
9555
|
+
<button type="button" class="sm-primary" id="run" data-stillmade-action="run">Make the clip</button>
|
|
9556
|
+
<p class="sm-status" id="status" role="status"></p>
|
|
9557
|
+
</div>
|
|
9558
|
+
|
|
9559
|
+
<section class="sm-stage" id="stage" data-ratio="16:9" aria-live="polite">
|
|
9560
|
+
<div class="sm-empty" id="empty">
|
|
9561
|
+
<span class="sm-glyph" data-glyph="video" aria-hidden="true"></span>
|
|
9562
|
+
<p>Your clip</p>
|
|
9563
|
+
</div>
|
|
9564
|
+
</section>
|
|
9885
9565
|
</main>
|
|
9886
9566
|
```
|
|
9887
9567
|
|
|
9568
|
+
With an `sm-stage` as its direct child and no `data-layout`, `sm-block` puts
|
|
9569
|
+
the inputs on the left and the stage on the right from 860 px wide. Under that
|
|
9570
|
+
everything stacks in the page's order, so the stage comes last in the markup:
|
|
9571
|
+
the inputs, the button, then the result's place under it.
|
|
9572
|
+
|
|
9888
9573
|
| Class | Use it for |
|
|
9889
9574
|
| --- | --- |
|
|
9890
|
-
| `sm-block` | The one workspace. `data-layout="split"`
|
|
9575
|
+
| `sm-block` | The one workspace. With an `sm-stage` child, the controls sit left and the stage right from 860 px; narrower, they stack in the page's order. `data-layout="stack"` keeps one column; `"split"` is an older layout with the stage on the left. |
|
|
9891
9576
|
| `sm-stage` | The thing being made: preview, media or result. `data-ratio="16:9"`, `"9:16"` or `"1:1"`. |
|
|
9892
9577
|
| `sm-empty` | An empty stage: a glyph, at most five words, one action. |
|
|
9893
9578
|
| `sm-choices` | Picks shown as pictures (`img`, `.sm-swatch`, `.sm-glyph`) and one or two words. `data-compact` for short text chips, `data-segmented` for one connected row of three or four short options. The pick is filled with the kit's ink, so it reads at a glance; a `disabled` fieldset keeps its pick and fades the rest. Chips and segments scroll sideways on a phone. Give the `fieldset` an `id` and `data-stillmade-share` so teammates see the same pick. |
|
|
@@ -9898,10 +9583,12 @@ piece in Light, Dark and White.
|
|
|
9898
9583
|
| `sm-actions` | One `sm-primary`, then `sm-quiet` buttons and `sm-icon` buttons (each with `aria-label` and `title`). |
|
|
9899
9584
|
| `sm-status` | Live state beside the action: `data-state="loading"`, `"success"`, `"waiting"` or `"error"`. |
|
|
9900
9585
|
| `sm-more` | A closed `<details>` for secondary settings. |
|
|
9901
|
-
| `sm-hint` | A closed `<details>`
|
|
9586
|
+
| `sm-hint` | A closed `<details>` (i) for an explanation that might help. Its words show on hover or focus, or a tap on touch screens. |
|
|
9902
9587
|
| `sm-gallery` | A grid of results or media. `data-size="small"` for input files: more tiles per row as the screen widens. |
|
|
9903
9588
|
| `sm-media-off` | A picture or clip that cannot show here (on another device, or it did not open): a quiet tile in its own shape (`--sm-photo-ratio`, the tile's shape in a gallery) holding the `image-off` glyph, the reason in `aria-label` and `title`, no words inside. |
|
|
9904
|
-
| `sm-
|
|
9589
|
+
| `sm-add` | The + tile that adds files to an input: `<button type="button" class="sm-add" data-stillmade-add="listingPhotos" aria-label="Add photos">`. StillMade opens its own picker and saves the files; they arrive through `onInput`. In an `sm-gallery` it takes the tile's shape. |
|
|
9590
|
+
| `sm-scrim` | Words over a photo or video, inside a positioned box such as an `sm-stage`: a see-through shade of the page under them, in every theme. `var(--scrim)` is the same shade for your own rules. |
|
|
9591
|
+
| `sm-glyph` | A small icon in `currentColor`: `play`, `pause`, `plus`, `close`, `check`, `chevron`, `chevron-left`, `chevron-right`, `upload`, `image`, `image-off`, `video`, `audio`, `text`, `spark`, `info`, `warning`, `spinner`. Works in standard views, which cannot use SVG. |
|
|
9905
9592
|
|
|
9906
9593
|
**Function first.** When the person building a Block does not ask for a look,
|
|
9907
9594
|
use the kit as is: they want it to work and be easy to use. When they name a
|
|
@@ -9970,6 +9657,14 @@ person or the creator has not asked for something else. What they ask for wins.
|
|
|
9970
9657
|
`details`, `notes` or `listingDetails`; when a note has to be split across
|
|
9971
9658
|
several fields StillMade does it free. Never build your own reference
|
|
9972
9659
|
picker or library upload.
|
|
9660
|
+
- **Files are tiles, added through StillMade.** A screen that holds an
|
|
9661
|
+
input's files shows them as tiles (`sm-gallery`) ending in the kit's + tile,
|
|
9662
|
+
`<button type="button" class="sm-add" data-stillmade-add="listingPhotos"
|
|
9663
|
+
aria-label="Add photos">`. StillMade opens its own picker and saves the files
|
|
9664
|
+
as its own tiles do; they arrive through `onInput`. From your own code call
|
|
9665
|
+
`StillMade.addFiles("listingPhotos")` in a click. Never an
|
|
9666
|
+
`<input type="file">` or an upload box of the Block's own. See
|
|
9667
|
+
[Frame views](/docs/build/frame-views#adding-files-to-an-input).
|
|
9973
9668
|
- **Days and times are picked, never typed.** Use the `date` type. StillMade
|
|
9974
9669
|
shows a calendar, and a list of times as well when the input is about a time
|
|
9975
9670
|
(a name such as `postAt` or `startsAt`, a label with Time, When or Starts, or
|
|
@@ -9979,8 +9674,11 @@ person or the creator has not asked for something else. What they ask for wins.
|
|
|
9979
9674
|
any app is a connector Step after the Block, and StillMade words its button
|
|
9980
9675
|
("Post to Instagram"). A Block never posts from inside itself.
|
|
9981
9676
|
- **Chat can do all of it.** Every button carries `data-stillmade-action`
|
|
9982
|
-
|
|
9983
|
-
and
|
|
9677
|
+
and every field a label, so the project's Chat can press and fill them. The
|
|
9678
|
+
button that runs the Block says `run` (StillMade's phone and desktop checks
|
|
9679
|
+
press it with the fixture); every other button names what it does
|
|
9680
|
+
(`data-stillmade-action="play sample"`). Chat reads the value with the
|
|
9681
|
+
button's own words.
|
|
9984
9682
|
|
|
9985
9683
|
### 4. Rules
|
|
9986
9684
|
|
|
@@ -9991,7 +9689,15 @@ exception, never unrelated technical UI.
|
|
|
9991
9689
|
1. Show the thing being made, not a technical representation of it. No JSON
|
|
9992
9690
|
dumps, source editors, manifests, debug consoles or "Technical result"
|
|
9993
9691
|
panels unless the task is about source. A collapsed disclosure is still an
|
|
9994
|
-
added surface.
|
|
9692
|
+
added surface. Every part shows at once (November 3, 2026): before the first
|
|
9693
|
+
run the result's place is drawn in the shape and size of what is coming
|
|
9694
|
+
(`<section class="sm-stage" data-ratio="9:16">` for a vertical clip, lines for
|
|
9695
|
+
text, a player bar for sound), so the result fills a place the person already
|
|
9696
|
+
sees instead of popping in somewhere unexpected. StillMade draws the Steps it
|
|
9697
|
+
draws the same way (`ResultPlace`). On a wide screen what goes in sits on the
|
|
9698
|
+
left and the result on the right, side by side (the kit's `sm-block` with an
|
|
9699
|
+
`sm-stage` does this from 860px); they stack only on narrow screens and phones,
|
|
9700
|
+
the result's place under the button (rule 16).
|
|
9995
9701
|
2. One obvious next action. Seven or fewer choices in view; strong defaults.
|
|
9996
9702
|
3. Choices people see or hear (voices, music, sounds, fonts, looks, colors,
|
|
9997
9703
|
templates, images) show a preview of each option: a thumbnail or a sample
|
|
@@ -10016,7 +9722,9 @@ exception, never unrelated technical UI.
|
|
|
10016
9722
|
12. Status is a dot or a few words ("No narration yet"), never a paragraph.
|
|
10017
9723
|
The reason sits behind an `sm-hint`.
|
|
10018
9724
|
13. Files are tiles: a saved file is a thumbnail or chip, and adding one is a
|
|
10019
|
-
+ tile
|
|
9725
|
+
+ tile, `<button class="sm-add" data-stillmade-add="<input>">`, which
|
|
9726
|
+
opens StillMade's picker (or `StillMade.addFiles("<input>")` from your own
|
|
9727
|
+
button). Never an `<input type="file">` or the browser's bare
|
|
10020
9728
|
"Choose File / No file chosen" picker, and no size limits in view until
|
|
10021
9729
|
a file is too big.
|
|
10022
9730
|
14. Rare tools (update, schedule, history, reload) are named icon buttons
|
|
@@ -10030,8 +9738,11 @@ exception, never unrelated technical UI.
|
|
|
10030
9738
|
16. Use the width. From 860 px wide, the kit puts the controls on the left
|
|
10031
9739
|
at a reading width (300–380 px) and the result (`sm-stage`, a direct
|
|
10032
9740
|
child of `sm-block`) fills the right, in its real shape. Under that the
|
|
10033
|
-
page is one column
|
|
10034
|
-
|
|
9741
|
+
page is one column in the page's order: the inputs, the action in
|
|
9742
|
+
normal flow (rule 5: never a sticky bar), then the result's place under
|
|
9743
|
+
it, so `sm-stage` comes after `sm-actions` in the markup. Keep the
|
|
9744
|
+
inputs above the button short enough that a phone shows the button
|
|
9745
|
+
without scrolling far; fold the rest into `sm-more`. Extra width goes to the result, never to wider text
|
|
10035
9746
|
fields. Before there is a result, the stage previews what will be made
|
|
10036
9747
|
from the inputs given so far, not an empty box. `data-layout="stack"`
|
|
10037
9748
|
keeps one column. StillMade zooms a view to fill its space and centers a
|
|
@@ -10074,6 +9785,15 @@ exception, never unrelated technical UI.
|
|
|
10074
9785
|
<span class="sm-glyph" data-glyph="image-off" aria-hidden="true"></span>
|
|
10075
9786
|
</span>
|
|
10076
9787
|
```
|
|
9788
|
+
23. One page per job (November 3, 2026). A job the person fills in and then
|
|
9789
|
+
gets a result from is one Block on one page: the inputs and settings, a
|
|
9790
|
+
cheap check before the expensive run (the script and a voice sample
|
|
9791
|
+
before the video), then the result. The check is a second
|
|
9792
|
+
`StillMade.run` on the same page, priced by StillMade on its own (Module
|
|
9793
|
+
Blocks, "A check, then the expensive run, on one page"); it never becomes
|
|
9794
|
+
a second Block or Step. A workspace the person works in for a while (a
|
|
9795
|
+
CAD tool, a canvas, an editor) is its own Block, and turning what it made
|
|
9796
|
+
into something else is the next one.
|
|
10077
9797
|
|
|
10078
9798
|
### 5. What StillMade checks
|
|
10079
9799
|
|
|
@@ -10134,6 +9854,756 @@ an explicit upgrade, following the
|
|
|
10134
9854
|
Never rewrite immutable releases or silently change a saved workflow's pins.
|
|
10135
9855
|
|
|
10136
9856
|
|
|
9857
|
+
## Frame views: any interface code
|
|
9858
|
+
|
|
9859
|
+
# Frame views: any interface code
|
|
9860
|
+
|
|
9861
|
+
A frame view (`"runtime": "frame"` in the view) is an interface written with
|
|
9862
|
+
ordinary web code: React, Svelte, Vue or Preact bundles, SVG, canvas and
|
|
9863
|
+
WebGL, pointer capture and drag, timers, loops, recursion, classes and Web
|
|
9864
|
+
Workers. Use it when the reviewed subset of standard views is too small for
|
|
9865
|
+
the interface you want.
|
|
9866
|
+
|
|
9867
|
+
### Folder layout
|
|
9868
|
+
|
|
9869
|
+
```
|
|
9870
|
+
my-block/
|
|
9871
|
+
stillmade.block.json
|
|
9872
|
+
src/run.js or another runtime (recipe, module, capability)
|
|
9873
|
+
src/view.html markup, for example <div id="root"></div>
|
|
9874
|
+
src/view.css styles; use StillMade's theme variables
|
|
9875
|
+
src/view.js ONE classic script: bundle with --format=iife
|
|
9876
|
+
src/view.config.json {"runtime": "frame"}
|
|
9877
|
+
tests/fixtures.json
|
|
9878
|
+
```
|
|
9879
|
+
|
|
9880
|
+
`src/view.config.json` may also set `"appearance"` and `"files"` (assets, see
|
|
9881
|
+
below). Bundle npm dependencies into `src/view.js` as an IIFE, for example:
|
|
9882
|
+
|
|
9883
|
+
```sh
|
|
9884
|
+
esbuild src/view.jsx --bundle --format=iife --minify --jsx=automatic \
|
|
9885
|
+
--define:process.env.NODE_ENV='"production"' --outfile=src/view.js
|
|
9886
|
+
```
|
|
9887
|
+
|
|
9888
|
+
Limits: markup and each stylesheet up to 256 KiB, `view.js` up to 4 MiB,
|
|
9889
|
+
and up to 32 asset files with 8 MiB in total.
|
|
9890
|
+
|
|
9891
|
+
### The `StillMade` object
|
|
9892
|
+
|
|
9893
|
+
Frame views get the same `StillMade` object as standard views: `input` and
|
|
9894
|
+
`onInput`, `run`, `useOutput`, `getShared`, `onShared`, `updateShared`,
|
|
9895
|
+
`bindShared`, `connectDocument`, `previewImage`, `previewMedia`, `render`
|
|
9896
|
+
and the rest of the interface bridge. In addition:
|
|
9897
|
+
|
|
9898
|
+
| Member | What it does |
|
|
9899
|
+
| --- | --- |
|
|
9900
|
+
| `StillMade.asset(path)` | A `blob:` URL for one of the view's asset files, for `<img>`, `new FontFace()` or `<video>`. |
|
|
9901
|
+
| `StillMade.assetBytes(path)` | A copy of an asset file's bytes as an `ArrayBuffer`. |
|
|
9902
|
+
|
|
9903
|
+
`StillMade.live` gives any view the camera and microphone as they happen, after the
|
|
9904
|
+
person presses Start (see Live camera and microphone in
|
|
9905
|
+
[Desktop capabilities](/docs/reference/desktop-capabilities)). A frame view can hand the frames to a
|
|
9906
|
+
Worker it starts from a `blob:` URL.
|
|
9907
|
+
|
|
9908
|
+
Shared state works as in every view: keep each independently edited value in
|
|
9909
|
+
its own field (for example `point0`, `point1`) so two people editing
|
|
9910
|
+
different values never conflict.
|
|
9911
|
+
|
|
9912
|
+
### Adding files to an input
|
|
9913
|
+
|
|
9914
|
+
A one-page Block shows its input's files on its own screen as tiles and adds
|
|
9915
|
+
more there. Never draw an upload box or an `<input type="file">` of your own:
|
|
9916
|
+
the + tile asks StillMade, which opens its own file picker and saves the
|
|
9917
|
+
files exactly as the Step's own tiles do (working copies, files kept on the
|
|
9918
|
+
person's device, the limits and the error messages).
|
|
9919
|
+
|
|
9920
|
+
```html
|
|
9921
|
+
<ul class="sm-gallery" data-size="small" id="photos">
|
|
9922
|
+
<!-- one <li> per photo, drawn from onInput -->
|
|
9923
|
+
<li><button type="button" class="sm-add" data-stillmade-add="listingPhotos"
|
|
9924
|
+
aria-label="Add photos" title="Add photos"></button></li>
|
|
9925
|
+
</ul>
|
|
9926
|
+
```
|
|
9927
|
+
|
|
9928
|
+
`data-stillmade-add` names a declared `image`, `video`, `audio`, `asset` or
|
|
9929
|
+
`file` input, or a list of one of them (`image[]`). It needs no code: a click
|
|
9930
|
+
or key press on the tile opens StillMade's picker. A list gets every chosen
|
|
9931
|
+
file added; a single input takes the first. The saved values reach the view
|
|
9932
|
+
the way every input does, so draw the tiles from `onInput`:
|
|
9933
|
+
|
|
9934
|
+
```js
|
|
9935
|
+
StillMade.onInput(async input => {
|
|
9936
|
+
for (const photo of input.listingPhotos || []) {
|
|
9937
|
+
const preview = await StillMade.previewImage(photo); // display copy
|
|
9938
|
+
// ...add an <li><img src="preview.url"></li> before the + tile
|
|
9939
|
+
}
|
|
9940
|
+
});
|
|
9941
|
+
```
|
|
9942
|
+
|
|
9943
|
+
From your own button, call `await StillMade.addFiles('listingPhotos')` inside
|
|
9944
|
+
its click handler. It resolves to `{added}` (0 when the person closes the
|
|
9945
|
+
picker) and rejects with a message for an undeclared input, an input that is
|
|
9946
|
+
not a file input, an input an earlier Step fills, a person who can only view
|
|
9947
|
+
the project, or a call that does not come from a click or key press in the
|
|
9948
|
+
view. The view never receives a `File` or a `blob:` URL, only the saved
|
|
9949
|
+
values. StillMade hides `[data-stillmade-add]` tiles from viewers and for
|
|
9950
|
+
inputs an earlier Step fills. In the SDK preview, the tile opens your
|
|
9951
|
+
computer's file chooser and the preview keeps the files like your own picks.
|
|
9952
|
+
|
|
9953
|
+
### Removing and reordering files
|
|
9954
|
+
|
|
9955
|
+
A list input's files are removed and put in order on the same screen, so a
|
|
9956
|
+
person drops a bad photo or sets the tour's order without leaving the Block.
|
|
9957
|
+
Put the buttons in each tile, with `data-index` (from 0) naming its place in
|
|
9958
|
+
the list `onInput` gave you:
|
|
9959
|
+
|
|
9960
|
+
```html
|
|
9961
|
+
<li>
|
|
9962
|
+
<img src="..." alt="Photo 3">
|
|
9963
|
+
<span class="sm-tile-tools">
|
|
9964
|
+
<button type="button" data-stillmade-move="listingPhotos" data-index="2" data-to="1"
|
|
9965
|
+
aria-label="Move earlier" title="Move earlier"><span class="sm-glyph" data-glyph="chevron-left" aria-hidden="true"></span></button>
|
|
9966
|
+
<button type="button" data-stillmade-move="listingPhotos" data-index="2" data-to="3"
|
|
9967
|
+
aria-label="Move later" title="Move later"><span class="sm-glyph" data-glyph="chevron-right" aria-hidden="true"></span></button>
|
|
9968
|
+
<button type="button" data-stillmade-remove="listingPhotos" data-index="2"
|
|
9969
|
+
aria-label="Remove" title="Remove"><span class="sm-glyph" data-glyph="close" aria-hidden="true"></span></button>
|
|
9970
|
+
</span>
|
|
9971
|
+
</li>
|
|
9972
|
+
```
|
|
9973
|
+
|
|
9974
|
+
`data-stillmade-remove` removes the file at `data-index`; `data-stillmade-move`
|
|
9975
|
+
moves it to the place `data-to`. A `data-to` past either end of the list does
|
|
9976
|
+
nothing, so the first tile's Move earlier is harmless (disable it if you like).
|
|
9977
|
+
From your own code, call `await StillMade.removeFile('listingPhotos', 2)` or
|
|
9978
|
+
`await StillMade.moveFile('listingPhotos', 2, 1)` inside a click handler; each
|
|
9979
|
+
resolves to `{files}`, how many the list now holds. StillMade changes the
|
|
9980
|
+
Step's list as its own tile buttons do, keeping each saved file's upload record
|
|
9981
|
+
with it, and the new list reaches the view through `onInput`: redraw the tiles
|
|
9982
|
+
from it, never from your own copy.
|
|
9983
|
+
|
|
9984
|
+
Both work only on list inputs (`image[]`, `video[]`, `audio[]`, `asset[]`,
|
|
9985
|
+
`file[]`); `addFiles` replaces a single input's file. They are refused, as
|
|
9986
|
+
`addFiles` is, for an undeclared input, an input an earlier Step fills, a
|
|
9987
|
+
viewer, Chat and a call that does not come from a click or key press in the
|
|
9988
|
+
view, and also when the file at that place changed after the view was given it
|
|
9989
|
+
(another person edited the list): the view gets the current list and the person
|
|
9990
|
+
tries again. StillMade hides these buttons wherever it hides the + tile. The SDK
|
|
9991
|
+
preview changes its example input the same way.
|
|
9992
|
+
|
|
9993
|
+
### Theming
|
|
9994
|
+
|
|
9995
|
+
The theme variables from StillMade's design system are always defined:
|
|
9996
|
+
`var(--bg)`, `var(--bg-elev-1)`, `var(--fg)`, `var(--fg-muted)`,
|
|
9997
|
+
`var(--accent)`, `var(--border-tok)`, `var(--divider)`, `var(--r-3)` and the
|
|
9998
|
+
rest. Colors written with them follow Light, Dark and White automatically.
|
|
9999
|
+
Frame views are not required to use them; the appearance check reports
|
|
10000
|
+
hard-coded colors as advice instead of refusing the view.
|
|
10001
|
+
|
|
10002
|
+
A color the check takes is a theme variable, `transparent` or `currentColor`,
|
|
10003
|
+
or a gradient of them (`linear-gradient(to top, var(--bg), transparent)`).
|
|
10004
|
+
`opacity` is not a color and is always allowed. `color-mix()`, `rgba()`,
|
|
10005
|
+
`#rrggbbaa` and other written colors are what it reports. For words over a
|
|
10006
|
+
photo or video, use the kit's shade: `var(--scrim)` is the page's background,
|
|
10007
|
+
partly see-through, in every theme, and `class="sm-scrim"` lays it over the
|
|
10008
|
+
bottom of a positioned box (an `sm-stage`) under the words inside it:
|
|
10009
|
+
|
|
10010
|
+
```html
|
|
10011
|
+
<figure class="sm-stage" data-fill>
|
|
10012
|
+
<img src="..." alt="">
|
|
10013
|
+
<figcaption class="sm-scrim">12 Elm Street · 3 bed</figcaption>
|
|
10014
|
+
</figure>
|
|
10015
|
+
```
|
|
10016
|
+
|
|
10017
|
+
`--scrim` and `sm-scrim` are the only shade a Block uses; StillMade's own
|
|
10018
|
+
`--scrim-top` and `--scrim-bottom` (theme.css) are for its pages and the check
|
|
10019
|
+
reports them.
|
|
10020
|
+
|
|
10021
|
+
### Layout
|
|
10022
|
+
|
|
10023
|
+
StillMade zooms a view to fill the space its Step gives it (up to 150%), and
|
|
10024
|
+
Ctrl/Cmd +, - and 0 size the view while it has focus. It never centers a frame
|
|
10025
|
+
view; a standard view narrower than its Step is centered by its margins.
|
|
10026
|
+
|
|
10027
|
+
An app that lays itself out (a full-size canvas or WebGL, its own scroll, pan
|
|
10028
|
+
or zoom, its own Ctrl/Cmd +/- keys, as in a CAD or drawing tool) puts
|
|
10029
|
+
`data-stillmade-layout="own"` on its outermost element:
|
|
10030
|
+
|
|
10031
|
+
```html
|
|
10032
|
+
<div id="root" data-stillmade-layout="own"></div>
|
|
10033
|
+
```
|
|
10034
|
+
|
|
10035
|
+
StillMade then never centers or zooms it and leaves Ctrl/Cmd +, - and 0 to the
|
|
10036
|
+
app. Without the attribute StillMade also stays out of the way when the view
|
|
10037
|
+
looks like an app: no page padding, a clipped page, a root as tall as the frame,
|
|
10038
|
+
fixed over half of it or scrolling over half of it, a canvas, SVG or
|
|
10039
|
+
`touch-action: none` surface over 40% of it, or a canvas or SVG of 300x200 or
|
|
10040
|
+
more with `touch-action: none` (as Three.js's controls set it). Mark the root
|
|
10041
|
+
anyway, in `view.html` rather than from code: StillMade reads it before the view
|
|
10042
|
+
starts, so the app never shows StillMade's size control or loses a key to it,
|
|
10043
|
+
even while it is still loading.
|
|
10044
|
+
|
|
10045
|
+
A `<video controls>` plays in StillMade's player, the same one as everywhere in
|
|
10046
|
+
StillMade: play, timeline, time, sound and full screen along the bottom, shown
|
|
10047
|
+
while the pointer is on the video, with keys on the focused video (Space or K,
|
|
10048
|
+
the arrows, M, F) and touch. Full screen goes through StillMade, right after a
|
|
10049
|
+
click or key in the view; the frame itself has no `allow="fullscreen"`. A
|
|
10050
|
+
`<video>` without `controls` is the Block's own and StillMade never touches it,
|
|
10051
|
+
so a Block that drives playback (its own timeline, an editor or CAD viewer)
|
|
10052
|
+
leaves `controls` out, and `data-stillmade-player="native"` keeps the browser's
|
|
10053
|
+
controls. Result pictures from `StillMade.previewImage` in `sm-stage` or
|
|
10054
|
+
`sm-gallery`, and any element marked `data-stillmade-expand`, get a full-screen
|
|
10055
|
+
button in the bottom-right corner while the pointer is on them; pictures with
|
|
10056
|
+
the same value (`data-stillmade-expand="shots"`) open as one set, and
|
|
10057
|
+
`data-stillmade-expand="off"` removes the button. The keyboard reaches them
|
|
10058
|
+
too: such a picture without a `tabindex` of its own joins the tab order,
|
|
10059
|
+
keyboard focus on it shows the button, Enter or Space opens the viewer, and
|
|
10060
|
+
closing the viewer gives the focus back to the picture.
|
|
10061
|
+
|
|
10062
|
+
### A job on one page: a check, then the run
|
|
10063
|
+
|
|
10064
|
+
A job the person fills in and gets a result from is one page: the inputs on the
|
|
10065
|
+
left and the result's place on the right on a wide screen; on a phone they
|
|
10066
|
+
stack in the page's order, the inputs, the button in normal flow (never a sticky
|
|
10067
|
+
bar), then the result's place under it. When the expensive run needs a cheap
|
|
10068
|
+
check first (the script and a voice sample before the video), keep it on the
|
|
10069
|
+
same page: call `StillMade.run({make: 'check'})`, show what comes back, then
|
|
10070
|
+
`StillMade.run({make: 'video', script})` with what the person checked. Each run
|
|
10071
|
+
is priced and approved by StillMade on its own, so the view shows no price. The
|
|
10072
|
+
module side and what the person sees are in MODULES.md, "A check, then the
|
|
10073
|
+
expensive run, on one page".
|
|
10074
|
+
|
|
10075
|
+
### Assets
|
|
10076
|
+
|
|
10077
|
+
Images, fonts, JSON, audio and video the view needs ship as files named by
|
|
10078
|
+
SHA-256, like module files:
|
|
10079
|
+
|
|
10080
|
+
```json
|
|
10081
|
+
{"runtime": "frame", "files": {"schemaVersion": 1, "files": {
|
|
10082
|
+
"fonts/inter.woff2": {"sha256": "…", "bytes": 48256, "mediaType": "font/woff2"}
|
|
10083
|
+
}}}
|
|
10084
|
+
```
|
|
10085
|
+
|
|
10086
|
+
`stillmade-block pack` fills in the digests and sizes. StillMade checks every
|
|
10087
|
+
file against its SHA-256 before the view starts.
|
|
10088
|
+
|
|
10089
|
+
### What the frame does not allow
|
|
10090
|
+
|
|
10091
|
+
The view runs in an opaque-origin frame with its own policy:
|
|
10092
|
+
|
|
10093
|
+
- no network (`fetch`, WebSocket and remote images, fonts or scripts fail);
|
|
10094
|
+
- no code from strings (`eval`, `new Function`, inline event attributes) and
|
|
10095
|
+
no extra script elements;
|
|
10096
|
+
- no navigation, popups, downloads, forms that submit, or dialogs;
|
|
10097
|
+
- no WebAssembly on the page (run heavy computation in the Block's runtime,
|
|
10098
|
+
for example a module Block, and call it with `StillMade.run`);
|
|
10099
|
+
- no storage or access to StillMade's page.
|
|
10100
|
+
|
|
10101
|
+
Workers created from `blob:` URLs are allowed and run off the page's thread.
|
|
10102
|
+
|
|
10103
|
+
### Run limits
|
|
10104
|
+
|
|
10105
|
+
StillMade compiles the view so every function and loop checks a time budget.
|
|
10106
|
+
One task may run for 250 ms; past that it is stopped with an error, and a view
|
|
10107
|
+
that keeps the page busy most of the time, stops answering, or navigates its
|
|
10108
|
+
frame is closed with a message. A single built-in operation that blocks for a
|
|
10109
|
+
second or more (a huge sort, or a regular expression that backtracks) also closes
|
|
10110
|
+
the view where the browser reports it. Move long computation into a Worker or the
|
|
10111
|
+
Block's runtime.
|
|
10112
|
+
|
|
10113
|
+
The host enforces every permission itself: a view's code can message the host
|
|
10114
|
+
directly, so a read-only viewer cannot run the Block or save outputs from any
|
|
10115
|
+
view.
|
|
10116
|
+
|
|
10117
|
+
|
|
10118
|
+
## Module Blocks: JavaScript and WebAssembly
|
|
10119
|
+
|
|
10120
|
+
# Module Blocks: modern JavaScript and WebAssembly
|
|
10121
|
+
|
|
10122
|
+
A module Block (`"runtime": "module"`) is an ordinary ES module. It can use
|
|
10123
|
+
everything a modern browser Worker offers: `async`/`await`, timers, typed
|
|
10124
|
+
arrays, `WebAssembly`, `OffscreenCanvas`, `createImageBitmap`, WebCodecs, and
|
|
10125
|
+
any npm package you bundle into it, including C and C++ libraries compiled to
|
|
10126
|
+
WebAssembly (OpenCV, libvips builds, codecs, solvers), threaded WebAssembly
|
|
10127
|
+
(see Threads) and live WebSocket connections to declared origins (see Live
|
|
10128
|
+
connections). Use it when a recipe or the QuickJS `javascript` runtime is too
|
|
10129
|
+
small for the job. (StillMade's in-app builder writes `main.js` as one file that
|
|
10130
|
+
imports nothing and uses no browser APIs; that single-file rule is the builder's,
|
|
10131
|
+
not the runtime's. A module Block you build with the SDK can use all of this.)
|
|
10132
|
+
|
|
10133
|
+
### Folder layout
|
|
10134
|
+
|
|
10135
|
+
```
|
|
10136
|
+
my-block/
|
|
10137
|
+
stillmade.block.json manifest with "runtime": "module"
|
|
10138
|
+
module/ everything the Block runs
|
|
10139
|
+
main.js the entry (bundle npm dependencies into it)
|
|
10140
|
+
filters.wasm optional WebAssembly and data files
|
|
10141
|
+
module.json optional: {"entry": "other.js", "threads": true}
|
|
10142
|
+
tests/fixtures.json [{name, input, expected, mocks?}]
|
|
10143
|
+
```
|
|
10144
|
+
|
|
10145
|
+
Start one with `stillmade-block create my-block module` (`module-hosted` for
|
|
10146
|
+
one that calls hosted operations). `stillmade-block` is the SDK's command line:
|
|
10147
|
+
`npx stillmade-block` with the npm package `stillmade-sdk`, or
|
|
10148
|
+
`node <sdk>/packages/block-cli/cli.js` where `<sdk>` is the extracted
|
|
10149
|
+
`/block-sdk/stillmade-sdk-0.1.0.zip`. Bundle npm
|
|
10150
|
+
dependencies into `module/main.js` with any ES module bundler (esbuild,
|
|
10151
|
+
rolldown, Vite). A module holds up to 64 files, 64 MiB each and 128 MiB in
|
|
10152
|
+
total.
|
|
10153
|
+
|
|
10154
|
+
### The entry
|
|
10155
|
+
|
|
10156
|
+
```js
|
|
10157
|
+
export default async function run(input, stillmade) {
|
|
10158
|
+
stillmade.progress(0.1, 'Loading');
|
|
10159
|
+
const { instance } = await WebAssembly.instantiate(stillmade.asset('filters.wasm'));
|
|
10160
|
+
const bytes = await stillmade.files.read(input.photo); // a saved image the Block received
|
|
10161
|
+
// ... process ...
|
|
10162
|
+
const saved = await stillmade.files.write(pngBlob, { mediaType: 'image/png', name: 'result.png' });
|
|
10163
|
+
return { result: saved };
|
|
10164
|
+
}
|
|
10165
|
+
```
|
|
10166
|
+
|
|
10167
|
+
Return an object whose keys are your declared outputs. Outputs are checked
|
|
10168
|
+
against the manifest's port types.
|
|
10169
|
+
|
|
10170
|
+
### The `stillmade` host API
|
|
10171
|
+
|
|
10172
|
+
| Member | What it does |
|
|
10173
|
+
| --- | --- |
|
|
10174
|
+
| `input` | The validated input values (the same object as the first argument). |
|
|
10175
|
+
| `signal` | An `AbortSignal` that fires when the person cancels. |
|
|
10176
|
+
| `progress(value, label)` | Reports progress from 0 to 1 with a short label. Each report extends the run limit. |
|
|
10177
|
+
| `log(...values)` | Adds a line to the run log (up to 500 lines). |
|
|
10178
|
+
| `asset(path)` | A copy of one of the module's own files, such as a `.wasm` build, as an `ArrayBuffer`. |
|
|
10179
|
+
| `files.read(ref)` | The bytes, as an `ArrayBuffer`, of a saved image, video, audio, asset or file (a PDF, text, CSV, Markdown or JSON file on a `file` input) the Block received as input, or that StillMade made in this run: files it wrote, transform results and generated media. Media the person kept on their device (a `sm-media://device/...` address in a project Step) reads the same way, without leaving the device. Other files cannot be read, and transforms, hosted calls and actions accept only these references too; a hosted call or action gets a cloud copy of device media, made once per version. A reference is the file its `kind`, `assetId`, `versionId` and `url` name, so `{kind, assetId, versionId, url}` rebuilt from a `media.generate` result item (without its `key`) is the same file; any other file is refused with `MODULE_FILE_READ`, by `stillmade-block test --mock`, `ready` and `gate` as by StillMade. |
|
|
10180
|
+
| `files.write(data, {mediaType, name})` | Saves PNG, JPEG, WebP, GIF, MP4, WebM, MP3, WAV, M4A or OGG media, or a PDF, TXT, CSV, Markdown, JSON or ZIP file (`application/pdf`, `text/plain`, `text/csv`, `text/markdown`, `application/json`, `application/zip`; a `{kind: 'file'}` reference), to the runner's library and returns a reference you can return as an output. At most 64 files per run. |
|
|
10181
|
+
| `media.transform(request)` | On-device media work done by StillMade: decode audio to samples, extract or loudness-normalise audio, cut, join or re-encode video, and take thumbnails. Results are saved like `files.write`. See [Media processing](#media-processing). |
|
|
10182
|
+
| `actions.run(action)` | Runs a declared portable action. `media.generate` opens one approval for its whole batch, naming the most it can cost in StillMade credits; `stock.search` runs directly. |
|
|
10183
|
+
| `hosted.run(operation, request)` | Calls a hosted operation the manifest lists in `permissions.capabilities` (for example `text.generate`, `image.generate`, `audio.speech`, `web.research`). Each call is quoted and approved like a capability Block and paid with StillMade credits. |
|
|
10184
|
+
| `net.fetch(url, {method, headers, body})` | Calls one of the origins declared in `permissions.network` through StillMade, which adds the person's own key or OAuth token. Returns a standard `Response`. See [Outside APIs](/docs/build/outside-apis). |
|
|
10185
|
+
| `net.connect(url, {protocols})` | Opens a live WebSocket to the `wss://` form of a declared origin and resolves with the open socket. No key is added. See [Live connections](#live-connections). |
|
|
10186
|
+
| `connections.call(appId, operationId, input)` | Calls an operation of a published adapter declared in `permissions.connections`, with the person's own account. Returns the checked JSON result. |
|
|
10187
|
+
| `storage.get/set/remove/list/info(...)` | The Block's own cloud storage in the current project: JSON values up to 1 MiB under folder-like keys. Kept across runs, devices and the Block's versions. See [Project storage, reads and assets](/docs/build/project-storage). |
|
|
10188
|
+
| `storage.putFile(key, data, {mediaType})` / `storage.getFile(key)` | Files up to 64 MiB in the same storage; `getFile` returns `{key, mediaType, bytes, data}` with `data` as an `ArrayBuffer`, or `null`. |
|
|
10189
|
+
| `project.read(fields?)` | A copy of the current project limited to the fields the manifest may read (`project.read` plus `context.<field>.read`), such as `timeline`, `shots` or `assets`. |
|
|
10190
|
+
| `assets.create(file, {name})` / `assets.appendVersion(asset, file)` | Adds a saved image, video or audio file to the project's media as a new asset (`asset.create`), or as the newest version of an asset the Block received, read or created (`asset.version.append`). |
|
|
10191
|
+
|
|
10192
|
+
Hosted calls take the operation's request fields, for example:
|
|
10193
|
+
|
|
10194
|
+
```js
|
|
10195
|
+
const plan = await stillmade.hosted.run('text.generate', {
|
|
10196
|
+
prompt: `Write a 20-second podcast intro about ${input.topic}.`,
|
|
10197
|
+
system: 'Warm, short sentences.', maxTokens: 400,
|
|
10198
|
+
schema: { type: 'object', required: ['script'], properties: { script: { type: 'string' } } },
|
|
10199
|
+
});
|
|
10200
|
+
const voice = await stillmade.hosted.run('audio.speech', { text: plan.script, settings: { voice: 'adam' } });
|
|
10201
|
+
```
|
|
10202
|
+
|
|
10203
|
+
`image.describe`, `media.analyze` and `audio.transcribe` take saved references
|
|
10204
|
+
(`image`/`images`, `media`, `audio`). `audio.transcribe` takes a saved audio or a
|
|
10205
|
+
saved video, whose own sound it transcribes, so there is no need to extract the
|
|
10206
|
+
audio first. `media.analyze` returns `{schemaVersion: 1, items}`, one item per
|
|
10207
|
+
picture or video, and for one video takes an optional `range: {start, end}` in
|
|
10208
|
+
seconds, at most 12 apart, to look closely at that part. `image.generate` takes `prompt` and optional
|
|
10209
|
+
`references`, one to eight saved images the new picture keeps. `video.generate` takes `prompt` and optional
|
|
10210
|
+
`firstFrame` (a saved image the video starts on, for image-to-video), `lastFrame` (one it ends on; needs
|
|
10211
|
+
`firstFrame`), `negativePrompt` (up to 2,000 characters) and `settings`; a model named in `settings` must take
|
|
10212
|
+
those frames (Kling 3.0 Turbo and Wan 2.6 Flash take no last frame; Wan 2.6 Flash and Wan 2.7 need a first frame).
|
|
10213
|
+
For a vertical or square clip, set `settings.aspectRatio` (`'9:16'`, `'1:1'`) and name no model: the default
|
|
10214
|
+
model (Vidu Q3 Turbo) takes no aspect ratio, so StillMade uses the cheapest model that makes that shape with the
|
|
10215
|
+
frames and `duration` given (Wan 2.6 Flash with a first frame, else Veo 3.1 Lite, else Seedance 2.0 Mini; see
|
|
10216
|
+
[Generation models](/docs/reference/generation-models)). A model you name is kept, and one that cannot make the shape
|
|
10217
|
+
is refused. Look at pictures with `media.analyze`:
|
|
10218
|
+
StillMade credits pay for it, while `image.describe` runs only on the person's
|
|
10219
|
+
own OpenRouter key. `web.fetch` takes `url` and `instruction`;
|
|
10220
|
+
`web.research` takes `topic`; music and sound effects take `prompt` and
|
|
10221
|
+
`durationSec`.
|
|
10222
|
+
|
|
10223
|
+
### Actions (`permissions.actions`)
|
|
10224
|
+
|
|
10225
|
+
`actions.run` runs only the actions listed in `permissions.actions`, the same list a
|
|
10226
|
+
JavaScript Block declares: `"actions": ["media.generate"]` (or `"stock.search"`). A
|
|
10227
|
+
module that runs actions and calls no hosted operation leaves
|
|
10228
|
+
`permissions.capabilities` out; an empty list means the same.
|
|
10229
|
+
|
|
10230
|
+
```js
|
|
10231
|
+
const { value, failures } = await stillmade.actions.run({
|
|
10232
|
+
schemaVersion: 1, kind: 'stillmade.media-action', operation: 'media.generate',
|
|
10233
|
+
resultPort: null,
|
|
10234
|
+
request: { items: [
|
|
10235
|
+
{ key: 'front', operation: 'image', prompt: `The front of ${input.address} at dusk`,
|
|
10236
|
+
settings: { model: 'nano-banana-pro', aspectRatio: '16:9' } },
|
|
10237
|
+
{ key: 'tour', operation: 'speech', prompt: input.script },
|
|
10238
|
+
] },
|
|
10239
|
+
});
|
|
10240
|
+
```
|
|
10241
|
+
|
|
10242
|
+
The action is the object a JavaScript Block returns, with every key present; its
|
|
10243
|
+
items, settings and limits are in [Generate media from
|
|
10244
|
+
JavaScript](/docs/build/generate-media). Pass `resultPort: null`: no
|
|
10245
|
+
output port receives the media, `actions.run` returns it to your code and `run()`
|
|
10246
|
+
returns what the Block outputs. Before anything is generated StillMade opens one
|
|
10247
|
+
approval for the whole batch with the most it can cost in StillMade credits; credits
|
|
10248
|
+
the batch does not use come back when it closes. Each `actions.run` call is its own
|
|
10249
|
+
approval, so put every item one run needs in one batch: one clip per photo, the
|
|
10250
|
+
voice and the music are one `media.generate` call of up to 50 items (StillMade makes
|
|
10251
|
+
four at a time), never a call per photo, and never `hosted.run('video.generate')` once
|
|
10252
|
+
per photo, which asks again from the second call. Video items take the same
|
|
10253
|
+
`settings` as `video.generate` above, `aspectRatio` included. `actions.run` resolves with:
|
|
10254
|
+
|
|
10255
|
+
- `value`: the items that were made, in request order, each
|
|
10256
|
+
`{key, kind, assetId, versionId, url, duration?}` with `kind` `image`, `video` or `audio`
|
|
10257
|
+
(speech, music and sound effects). `duration` is the length in seconds of a
|
|
10258
|
+
video, music or sound effect item: the length it asked for (`settings.duration`,
|
|
10259
|
+
`settings.durationSec`, or the model's default). A speech item has none, since
|
|
10260
|
+
its length is what the words take: decode it with
|
|
10261
|
+
`media.transform({operation: 'audio.decode', source})`, whose result has
|
|
10262
|
+
`duration`, before timing a cut to it. Return them as outputs, or pass them to
|
|
10263
|
+
`files.read`, `media.transform` or a hosted call.
|
|
10264
|
+
- `failures`: one `{key, message}` for each item that was not made; those items are
|
|
10265
|
+
not in `value`.
|
|
10266
|
+
|
|
10267
|
+
It throws when no item was made, when the person declines (`MODULE_ACTION_DECLINED`)
|
|
10268
|
+
and when the action is not declared (`MODULE_PERMISSION`). `stock.search` resolves
|
|
10269
|
+
with `{results}`. With `--mock` or a `tests/mocks.json`, `media.generate` answers
|
|
10270
|
+
with stand-in media and no failures; record your own answer under
|
|
10271
|
+
`actions["media.generate"]`.
|
|
10272
|
+
|
|
10273
|
+
### A check, then the expensive run, on one page
|
|
10274
|
+
|
|
10275
|
+
A job the person fills in and then gets a result from is one Block on one page
|
|
10276
|
+
(builder contract, "One job per Step"): the inputs, a cheap check before the
|
|
10277
|
+
expensive run, and the result. A listing video writes the script and voices a
|
|
10278
|
+
few seconds of it first, and makes the video only once the person has read the
|
|
10279
|
+
script and heard the voice. Build that as one module Block with a custom view,
|
|
10280
|
+
never as two Blocks or two Steps.
|
|
10281
|
+
|
|
10282
|
+
The manifest takes the mode and the checked values as optional inputs, and the
|
|
10283
|
+
outputs the check leaves out are optional (a run may leave out any output with
|
|
10284
|
+
`"required": false`). `action` names the main button, the expensive run:
|
|
10285
|
+
|
|
10286
|
+
```json
|
|
10287
|
+
"inputs": {
|
|
10288
|
+
"listingPhotos": { "type": "image[]", "primary": true, "description": "Photos of the home, in the order of the tour." },
|
|
10289
|
+
"homeFacts": { "type": "text", "description": "Address, rooms and what stands out." },
|
|
10290
|
+
"make": { "type": "text", "required": false, "default": "video", "description": "check: the script and a voice sample; video: the tour." },
|
|
10291
|
+
"script": { "type": "script", "required": false, "description": "The checked script, as the person left it." }
|
|
10292
|
+
},
|
|
10293
|
+
"outputs": {
|
|
10294
|
+
"tour": { "type": "video", "primary": true, "required": false },
|
|
10295
|
+
"script": { "type": "script", "required": false },
|
|
10296
|
+
"voiceSample": { "type": "audio", "required": false }
|
|
10297
|
+
},
|
|
10298
|
+
"action": { "label": "Generate tour", "running": "Generating tour…" },
|
|
10299
|
+
"permissions": { "capabilities": ["text.generate", "audio.speech"] }
|
|
10300
|
+
```
|
|
10301
|
+
|
|
10302
|
+
The module does only the cheap work when asked for the check:
|
|
10303
|
+
|
|
10304
|
+
```js
|
|
10305
|
+
export default async function run(input, stillmade) {
|
|
10306
|
+
const write = async () => (await stillmade.hosted.run('text.generate', {
|
|
10307
|
+
prompt: `A 40-second listing tour script from these facts: ${input.homeFacts}`,
|
|
10308
|
+
schema: { type: 'object', required: ['script'], properties: { script: { type: 'string' } } },
|
|
10309
|
+
})).script;
|
|
10310
|
+
if (input.make === 'check') {
|
|
10311
|
+
const script = await write();
|
|
10312
|
+
const voiceSample = await stillmade.hosted.run('audio.speech', { text: script.split(/(?<=[.!?])\s/)[0] });
|
|
10313
|
+
return { script, voiceSample };
|
|
10314
|
+
}
|
|
10315
|
+
const script = input.script || await write();
|
|
10316
|
+
const voice = await stillmade.hosted.run('audio.speech', { text: script });
|
|
10317
|
+
const { duration } = await stillmade.media.transform({ operation: 'audio.decode', source: voice });
|
|
10318
|
+
const each = duration / input.listingPhotos.length;
|
|
10319
|
+
const tour = await stillmade.media.transform({
|
|
10320
|
+
operation: 'timeline.render', aspect: '9:16',
|
|
10321
|
+
clips: input.listingPhotos.map((photo, index) => ({ source: photo, start: index * each, duration: each, motion: 'zoom-in' })),
|
|
10322
|
+
audio: [{ source: voice, start: 0, duration }],
|
|
10323
|
+
});
|
|
10324
|
+
return { tour, script };
|
|
10325
|
+
}
|
|
10326
|
+
```
|
|
10327
|
+
|
|
10328
|
+
The view runs it twice with `StillMade.run`, which takes the inputs the view
|
|
10329
|
+
sets (StillMade keeps them on the Step and fills the rest): `{make: 'check'}`
|
|
10330
|
+
first ("Write the script"), then `{make: 'video', script}` with the script the
|
|
10331
|
+
person checked or edited. One `sm-primary` at a time: the check until there is
|
|
10332
|
+
a script, then the expensive run, labelled with `manifest.action`, while
|
|
10333
|
+
"Write again" stays a quiet button. The result's place
|
|
10334
|
+
(`sm-stage data-ratio="9:16"`) is drawn before either run; the script and the sample sit in it or beside the inputs
|
|
10335
|
+
until the video replaces them. Each run starts empty: return the script again
|
|
10336
|
+
with the video so it stays on the Step. StillMade saves the inputs a view sets,
|
|
10337
|
+
so the mode last pressed stays on the Step; until the view first sets it, a run
|
|
10338
|
+
started elsewhere (Chat, an automation) takes the default and makes the video.
|
|
10339
|
+
|
|
10340
|
+
Each run is priced and approved by StillMade on its own. Beside the Block's
|
|
10341
|
+
page StillMade shows what each hosted call costs at the Step's saved or default
|
|
10342
|
+
model ("Write · 1 credit · Make voiceover · 3 credits"), with an (i) saying that
|
|
10343
|
+
pressing the Block's button is the approval. A run started by the person's own
|
|
10344
|
+
click or key in the view then runs each hosted operation once without a dialog,
|
|
10345
|
+
up to that price. StillMade checks the price with the provider first and opens
|
|
10346
|
+
its approval dialog, with the quote, only when the quote is higher, when the run
|
|
10347
|
+
calls the same operation a second time, or when the run did not come from the
|
|
10348
|
+
person's press (in a preview outside a project every call shows the dialog;
|
|
10349
|
+
`stillmade-block dev` answers with stand-ins and spends nothing). The view shows
|
|
10350
|
+
no prices. `media.transform`, `timeline.render` included, runs on the device
|
|
10351
|
+
and costs nothing; `actions.run` `media.generate` always asks first.
|
|
10352
|
+
|
|
10353
|
+
### Media processing
|
|
10354
|
+
|
|
10355
|
+
`stillmade.media.transform(request)` asks StillMade to do heavy media work on
|
|
10356
|
+
the person's device: Web Audio for audio, and the same renderer as Final
|
|
10357
|
+
Render for video (WebCodecs in the browser, the native renderer on desktop).
|
|
10358
|
+
It never runs on StillMade's servers and costs no credits. A transform reads
|
|
10359
|
+
only files the Block received, wrote or generated in this run (including media
|
|
10360
|
+
from `actions.run` `media.generate`; see `files.read` above), and saves its
|
|
10361
|
+
result to the runner's library, so you can return it as an output or pass it
|
|
10362
|
+
to the next transform.
|
|
10363
|
+
|
|
10364
|
+
| Request | Result |
|
|
10365
|
+
| --- | --- |
|
|
10366
|
+
| `{operation: 'audio.decode', source, sampleRate?}` | `{sampleRate, duration, samples}`: mono `Float32Array` PCM for analysis (default 16 kHz; 8000 to 48000). |
|
|
10367
|
+
| `{operation: 'audio.extract', source, format?}` | A saved WAV (16-bit, 48 kHz) or MP3 of the audio track. |
|
|
10368
|
+
| `{operation: 'audio.normalize', source, targetLufs?, format?}` | A saved WAV or MP3 at the target integrated loudness (default -16 LUFS, peaks at most -1 dBFS). |
|
|
10369
|
+
| `{operation: 'video.cut', source, keep, settings?}` | A saved MP4 with only the `keep` ranges (`[{start, end}]` seconds, ordered, up to 500). |
|
|
10370
|
+
| `{operation: 'video.concat', sources, settings?}` | A saved MP4 of 2 to 50 videos in order. |
|
|
10371
|
+
| `{operation: 'video.resize', source, settings}` | A saved MP4 re-encoded with new settings. |
|
|
10372
|
+
| `{operation: 'image.thumbnail', source, at?, width?}` | A saved PNG of one frame (default the first frame, 1280 px wide). |
|
|
10373
|
+
| `{operation: 'document.text', source}` | `{text, pages}`: all the words of a PDF or text file the person gave, and `pages`, a list holding each page's words as one string (up to 500 pages). A file with no words (a scanned PDF) is refused. |
|
|
10374
|
+
| `{operation: 'image.compose', width, height, layers, background?, format?, text?}` | A saved PNG (or JPEG) of saved images joined into one picture: side by side, a grid, a contact sheet, a poster with a title. |
|
|
10375
|
+
| `{operation: 'timeline.render', clips, kind?, aspect?, audio?, captions?, settings?}` | A saved MP4 (or, with `kind: 'audio'`, an MP3) of your own timeline, rendered the way the Editor exports. |
|
|
10376
|
+
|
|
10377
|
+
`timeline.render` is how a Block exports: there is no separate Export, Captions,
|
|
10378
|
+
Transition or Color grade Block to call. Each clip is `{source, start, duration,
|
|
10379
|
+
from?, transition?: {type, duration?}, look?, mute?, motion?, fit?, focus?}` (a saved
|
|
10380
|
+
image or video; a video keeps its own sound unless `mute`; `motion` is a slow
|
|
10381
|
+
camera move over the clip: `zoom-in`, `zoom-out`, `pan-left`, `pan-right`,
|
|
10382
|
+
`pan-up` or `pan-down`, which always keeps the picture filling the frame), each
|
|
10383
|
+
audio item `{source, start, duration, from?, volume?}` (`volume` is a percentage of the
|
|
10384
|
+
recording's own level, 0 to 200: 100, the default, plays it as recorded, 50 at half
|
|
10385
|
+
the level, 0 silent; put music under a voice at about 20 to 30), and each caption
|
|
10386
|
+
`{text, start, end, words?, position?, highlight?, animation?, size?}`, where
|
|
10387
|
+
`words` (`[{text, start, end}]`) highlights each word as it is spoken and `size`
|
|
10388
|
+
is the text height in pixels of the rendered frame (16 to 200; default 44, so
|
|
10389
|
+
give a 1080 by 1920 vertical video about 80). Times are seconds on the timeline: each
|
|
10390
|
+
clip starts where the one before it ends, less the transition on that earlier
|
|
10391
|
+
clip, which blends it into the next one; clips that overlap more are refused.
|
|
10392
|
+
`aspect` is `16:9`, `9:16`, `1:1` or `4:5`. A clip of another shape than the
|
|
10393
|
+
frame (a wide sermon in a `9:16` clip) is cropped to fill it, around its middle
|
|
10394
|
+
or around `focus: {x, y}`, the point to keep in view as fractions 0 to 1 across
|
|
10395
|
+
the picture (`{x: 0.3}` keeps a speaker standing left of centre), and
|
|
10396
|
+
`focus.scale` (1 to 2.5, instead of a motion) holds the clip that much closer on
|
|
10397
|
+
that point; `fit: 'contain'` fits it whole with bars instead. Looks, transitions and caption
|
|
10398
|
+
animations use the Editor's names (`TIMELINE_LOOKS`, `TIMELINE_TRANSITIONS`,
|
|
10399
|
+
`CAPTION_ANIMATIONS`). The render follows the person's export plan, like any
|
|
10400
|
+
export from StillMade.
|
|
10401
|
+
|
|
10402
|
+
`image.compose` draws on a canvas `width` by `height` pixels (16 to 4096),
|
|
10403
|
+
filled with `background` (`#rrggbb`, default white). Each layer is `{source, x,
|
|
10404
|
+
y, width, height, fit?}`: a saved image placed in that box, in order, cropped
|
|
10405
|
+
to fill it (`fit: 'cover'`, the default) or fitted whole inside it (`'contain'`).
|
|
10406
|
+
Each piece of text is `{text, x, y, size?, color?, background?, align?,
|
|
10407
|
+
weight?, width?}`, drawn on top: `y` is the top of the line, `size` is 8 to 400
|
|
10408
|
+
pixels (default 48), `color` defaults to black, `background` puts a box behind it
|
|
10409
|
+
(for a Before label over a photo; `color` and `background` are `#rrggbb`, or `#rrggbbaa`
|
|
10410
|
+
or `rgba()` for a see-through box such as `#00000099`), `align` is `left`, `center` or `right` around
|
|
10411
|
+
`x`, and `weight` is `bold` (default) or `normal`. `width` wraps the words onto
|
|
10412
|
+
lines no wider than it, 1.2 times `size` apart, breaking a word wider than a line
|
|
10413
|
+
(or a caption with no spaces) between its characters: give a caption under a
|
|
10414
|
+
frame the frame's width so it never runs into the next one. Up to 64 layers and 64 pieces of
|
|
10415
|
+
text; `format` is `png` (default) or `jpeg`. A grid is arithmetic in your code:
|
|
10416
|
+
for 9 photos on a 1800 by 1800 sheet, three columns of 600 pixels.
|
|
10417
|
+
|
|
10418
|
+
`source`/`sources` are saved references (`{kind, assetId, versionId, url}`).
|
|
10419
|
+
Render `settings` are `{fps: 24|30|60, resolution: 720|1080|2160, quality:
|
|
10420
|
+
'low'|'medium'|'high'}`; the default is 30 fps, 1080, high. `video.cut`,
|
|
10421
|
+
`video.concat` and `video.resize` keep the source's aspect ratio;
|
|
10422
|
+
`timeline.render` makes the `aspect` it is given. Rendering follows the person's export plan, like any
|
|
10423
|
+
export.
|
|
10424
|
+
|
|
10425
|
+
Pure helpers ship as `@stillmade/block-sdk/media` for you to bundle (the file is
|
|
10426
|
+
`stillmade-sdk/packages/block-sdk/media-transform.js` in the npm package and
|
|
10427
|
+
`<sdk>/packages/block-sdk/media-transform.js` in the extracted download; alias
|
|
10428
|
+
the name in your bundler or import that path):
|
|
10429
|
+
`silentRanges(samples, sampleRate, {thresholdDb, minSilence})`,
|
|
10430
|
+
`keepRanges(silent, duration, {padding})`, `integratedLoudness(channels,
|
|
10431
|
+
sampleRate)` (ITU-R BS.1770), `loudnessGain`, `encodeWav` and `decodeWav`.
|
|
10432
|
+
|
|
10433
|
+
```js
|
|
10434
|
+
import { silentRanges, keepRanges } from '@stillmade/block-sdk/media';
|
|
10435
|
+
|
|
10436
|
+
export default async function run(input, stillmade) {
|
|
10437
|
+
const audio = await stillmade.media.transform({ operation: 'audio.decode', source: input.video });
|
|
10438
|
+
const keep = keepRanges(silentRanges(audio.samples, audio.sampleRate), audio.duration);
|
|
10439
|
+
const trimmed = await stillmade.media.transform({ operation: 'video.cut', source: input.video, keep });
|
|
10440
|
+
return { trimmed };
|
|
10441
|
+
}
|
|
10442
|
+
```
|
|
10443
|
+
|
|
10444
|
+
For anything else, decode and encode in the Block itself: the Worker has
|
|
10445
|
+
WebCodecs (`VideoDecoder`, `VideoEncoder`, `AudioDecoder`), `OffscreenCanvas`
|
|
10446
|
+
and `createImageBitmap`, and you can bundle `mp4-muxer`, `mp4box` or a
|
|
10447
|
+
WebAssembly codec. Images arrive as saved references: read the bytes with
|
|
10448
|
+
`files.read` and decode them with `createImageBitmap`, at full resolution.
|
|
10449
|
+
|
|
10450
|
+
### Sandbox and limits
|
|
10451
|
+
|
|
10452
|
+
- The module runs in one dedicated Worker inside a response-sandboxed frame
|
|
10453
|
+
with an opaque origin. It has no network of its own (`fetch`, WebSocket and
|
|
10454
|
+
remote imports fail; `net.fetch` and `net.connect` go through StillMade), no
|
|
10455
|
+
storage of the app, no page or DOM access, and cannot read another Block's
|
|
10456
|
+
files.
|
|
10457
|
+
- Every file is fetched from StillMade's store and checked against its SHA-256
|
|
10458
|
+
before anything runs.
|
|
10459
|
+
- A run may take 2 minutes; each progress report allows 60 more seconds. Time
|
|
10460
|
+
StillMade spends on a host call (rendering a video, waiting for the person to
|
|
10461
|
+
approve credits) does not count. Every run stops after 30 minutes in total.
|
|
10462
|
+
When a limit passes, or the person cancels, the frame is removed and the
|
|
10463
|
+
Worker stops immediately, even inside an endless loop.
|
|
10464
|
+
- Generating code at run time (`new Function`, used by Emscripten bindings) is
|
|
10465
|
+
allowed inside the sandbox.
|
|
10466
|
+
|
|
10467
|
+
Module Blocks run in StillMade on the person's device: in the browser while
|
|
10468
|
+
StillMade is open, or in StillMade Desktop. StillMade has no server sandbox for
|
|
10469
|
+
them. A schedule or event that hands browser-only Steps to the owner's StillMade
|
|
10470
|
+
Desktop ("Run ... on my StillMade Desktop") pauses at a module Step until the
|
|
10471
|
+
owner's desktop app, open with "Run my automations on this computer" on, runs it
|
|
10472
|
+
with the Step's exact inputs; the workflow then continues from its result. Module
|
|
10473
|
+
Steps that call paid hosted operations, generate media or need a device grant
|
|
10474
|
+
still wait for the owner. Without that choice a background run stops before a
|
|
10475
|
+
module Step and the person runs it in the project. Marketplace listings show
|
|
10476
|
+
module Blocks as **Browser only**.
|
|
10477
|
+
|
|
10478
|
+
### Live connections
|
|
10479
|
+
|
|
10480
|
+
`stillmade.net.connect(url, {protocols})` opens a WebSocket to the `wss://` form
|
|
10481
|
+
of an origin in `permissions.network` (declare `https://stream.example.org` and
|
|
10482
|
+
connect to `wss://stream.example.org/...`) and resolves once it is open:
|
|
10483
|
+
|
|
10484
|
+
```js
|
|
10485
|
+
const socket = await stillmade.net.connect('wss://stream.example.org/v1/live', { protocols: ['json.v1'] });
|
|
10486
|
+
await socket.send(JSON.stringify({ subscribe: input.channel }));
|
|
10487
|
+
for await (const data of socket) { // text arrives as strings, binary as ArrayBuffers
|
|
10488
|
+
stillmade.progress(0.5, 'Listening'); // each progress report extends the run limit
|
|
10489
|
+
if (JSON.parse(data).done) break; // leaving the loop closes the connection (code 1000)
|
|
10490
|
+
}
|
|
10491
|
+
```
|
|
10492
|
+
|
|
10493
|
+
The socket also has `onmessage`, `onclose`, `addEventListener`, `readyState`,
|
|
10494
|
+
`protocol`, `close(code, reason)` (1000 or 3000 to 4999) and `closed`, a promise of
|
|
10495
|
+
`{code, reason, wasClean}`. `for await` yields every message not yet read, also ones
|
|
10496
|
+
that arrived before the loop began (up to 1,024 messages or 16 MiB; older ones are
|
|
10497
|
+
dropped). The module's own frame still has no network: StillMade
|
|
10498
|
+
checks each URL (`wss://`, the standard port, a declared origin, valid subprotocol
|
|
10499
|
+
names) and opens the connection in a separate relay frame that runs no Block code,
|
|
10500
|
+
then relays the messages. A run keeps at most 4 connections, sends or receives at
|
|
10501
|
+
most 1 MiB a message and 256 MiB in all, and every connection closes when the run
|
|
10502
|
+
ends. The service sees an opaque `Origin` (`null`). StillMade adds no key to a live
|
|
10503
|
+
connection: for a service that needs one, get a short-lived token with `net.fetch`
|
|
10504
|
+
first and pass it as the service documents.
|
|
10505
|
+
|
|
10506
|
+
### Threads (SharedArrayBuffer)
|
|
10507
|
+
|
|
10508
|
+
A module that runs its own Workers with shared memory (Emscripten pthreads,
|
|
10509
|
+
threaded WebAssembly builds) declares it in `module.json`:
|
|
10510
|
+
|
|
10511
|
+
```json
|
|
10512
|
+
{ "entry": "main.js", "threads": true }
|
|
10513
|
+
```
|
|
10514
|
+
|
|
10515
|
+
It then runs in a Worker that can share `SharedArrayBuffer` and
|
|
10516
|
+
`WebAssembly.Memory({shared: true})` with the Workers it starts from `blob:` URLs
|
|
10517
|
+
(module Workers and remote scripts stay unavailable, so bundle the thread code).
|
|
10518
|
+
That Worker is not a secure context, so it has no WebCodecs, WebGPU,
|
|
10519
|
+
`crypto.subtle` or `crypto.randomUUID`; a module without `threads` keeps those and
|
|
10520
|
+
cannot share memory between Workers. Threads work in Chrome and Edge (version 137
|
|
10521
|
+
or later) and in StillMade Desktop; in other browsers `self.crossOriginIsolated` is
|
|
10522
|
+
false and `SharedArrayBuffer` is missing, so check it and fall back to one thread.
|
|
10523
|
+
`files.write` and `storage.putFile` accept bytes in shared memory (they are copied).
|
|
10524
|
+
|
|
10525
|
+
### Test, pack and import
|
|
10526
|
+
|
|
10527
|
+
```sh
|
|
10528
|
+
stillmade-block test my-block # runs fixtures in a local Node worker
|
|
10529
|
+
stillmade-block preview my-block input.json
|
|
10530
|
+
stillmade-block dev my-block # the interface in your browser, reloaded on save
|
|
10531
|
+
stillmade-block gate my-block # StillMade's readiness check
|
|
10532
|
+
stillmade-block pack my-block # writes <id>-<version>.stillmade-module.json
|
|
10533
|
+
```
|
|
10534
|
+
|
|
10535
|
+
`dev` serves the Block's real controls or view at `http://127.0.0.1:4800`
|
|
10536
|
+
(`--port` picks another) and answers hosted calls with stand-ins, never credits.
|
|
10537
|
+
The Block is not ready until `gate` passes. Name the Block's own folder: `gate .`
|
|
10538
|
+
in a folder that also holds the extracted SDK checks the SDK's examples too.
|
|
10539
|
+
`pack my-block` is the only packing step for a module Block, and it never takes
|
|
10540
|
+
`--original` (original `.stillmade-block` archives hold recipe, JavaScript,
|
|
10541
|
+
capability and ComfyUI source only and refuse a module Block).
|
|
10542
|
+
|
|
10543
|
+
`test` and `preview` run your code on your machine in a disposable Node worker
|
|
10544
|
+
with the same host API and run limits (not a security sandbox; it is your own
|
|
10545
|
+
code). `files.read`, `files.write`, `media.transform`, actions and hosted calls need StillMade, or
|
|
10546
|
+
a host you pass to `createNodeModuleExecutor` in your own tests. The Node worker has no
|
|
10547
|
+
`OffscreenCanvas` or `createImageBitmap`: a Block that decodes or draws images there
|
|
10548
|
+
needs its own fallback for tests, or tests only what it does without drawing.
|
|
10549
|
+
|
|
10550
|
+
A fixture's `files` mock is a path inside the Block folder (local runs only: it does not
|
|
10551
|
+
travel in the pack), `{"text": "..."}` for a text file, or `{"base64": "..."}` for a small
|
|
10552
|
+
binary file (at most 256 KB), which travels in the pack and the import review. Text can
|
|
10553
|
+
never hold a NUL character (`\u0000`): StillMade cannot store it, and import refuses it.
|
|
10554
|
+
|
|
10555
|
+
A fixture's photo, recording, clip or file can be one of StillMade's example files,
|
|
10556
|
+
named by address: `/api/media/example/photo-1.jpg` to `photo-4.jpg`, `sheet.png`,
|
|
10557
|
+
`room.png`, `kitchen.wav`, `hall.wav`, `click-120.wav`, `audio.mp3`, `clip.mp4`,
|
|
10558
|
+
`notes.txt`, `sales.csv` and `lesson.pdf`, a two-page PDF of words (sizes in
|
|
10559
|
+
PASTE_TO_LLM.md). `test` reads their real bytes
|
|
10560
|
+
when the fixture gives no mock for them, and StillMade, its preview and its phone and
|
|
10561
|
+
browser check serve the same files, so the example a person sees is the one you tested.
|
|
10562
|
+
Point what a fixture's mocks answer at them too (`/api/media/example/clip.mp4` for a
|
|
10563
|
+
rendered clip, `kitchen.wav` for speech): `dev` then shows and plays the result, where a
|
|
10564
|
+
made-up address shows an empty player.
|
|
10565
|
+
|
|
10566
|
+
A fixture can record what the host answers, so a test of a Block that calls
|
|
10567
|
+
hosted operations costs nothing and gives the same result every run:
|
|
10568
|
+
|
|
10569
|
+
```json
|
|
10570
|
+
{
|
|
10571
|
+
"name": "Finds John 3:16",
|
|
10572
|
+
"input": { "sermon": { "kind": "audio", "assetId": "a1", "versionId": "v1", "url": "https://example.com/sermon.wav" } },
|
|
10573
|
+
"mocks": {
|
|
10574
|
+
"hosted": {
|
|
10575
|
+
"audio.transcribe": { "schemaVersion": 1, "text": "Turn to John 3:16", "words": [{ "text": "John", "start": 0.6, "end": 0.9 }] },
|
|
10576
|
+
"text.generate": { "verses": [{ "reference": "John 3:16", "word": 0 }] }
|
|
10577
|
+
}
|
|
10578
|
+
},
|
|
10579
|
+
"expected": { "verses": [{ "reference": "John 3:16", "start": 0.6 }] }
|
|
10580
|
+
}
|
|
10581
|
+
```
|
|
10582
|
+
|
|
10583
|
+
`mocks` has the shape of `tests/mocks.json` (`hosted`, `transforms`, `actions`,
|
|
10584
|
+
`files`, `storage`, `project`, `connections`, `network`); a list answers
|
|
10585
|
+
successive calls in order. `files` maps an input's asset ID to a file in the Block
|
|
10586
|
+
folder, or to `{"text": "..."}` for a text file, so a Block that reads a notes or
|
|
10587
|
+
CSV file the person gives it is tested without a folder (StillMade's builder has none). A fixture's own `mocks` take the place of
|
|
10588
|
+
`tests/mocks.json` for that fixture. StillMade's own builder writes module
|
|
10589
|
+
Blocks the same way and runs their tests before anyone sees them.
|
|
10590
|
+
|
|
10591
|
+
Import the `.stillmade-module.json` in StillMade (Create, then Import).
|
|
10592
|
+
StillMade verifies every file digest, uploads each file to its
|
|
10593
|
+
content-addressed store, and saves the Block. Fixtures then run in your
|
|
10594
|
+
browser's module sandbox.
|
|
10595
|
+
|
|
10596
|
+
Uploaded module files count against your cloud storage. A digest alone never
|
|
10597
|
+
grants access: a person can read a module file only when they uploaded those
|
|
10598
|
+
exact bytes or can open a saved Block that names it (yours, an installed or
|
|
10599
|
+
community version, or one shared with their team).
|
|
10600
|
+
|
|
10601
|
+
See `examples/module-opencv/` for a Block that runs OpenCV on a 12-megapixel
|
|
10602
|
+
photo, `examples/module-silence-trimmer/` for one that cuts the pauses out of a
|
|
10603
|
+
talking video, and `examples/module-audio-extract/` for one that saves a
|
|
10604
|
+
video's audio as WAV.
|
|
10605
|
+
|
|
10606
|
+
|
|
10137
10607
|
## Describe an image
|
|
10138
10608
|
|
|
10139
10609
|
Use `runtime: "capability"` with `operation: "image.describe"` when a Block must inspect one image and return plain text. This is the portable boundary for visual analysis: the Block declares its image input, analysis instructions, output, tests, and license; StillMade owns image loading, provider credentials, model/payment selection, approval, accounting, and the retained execution record.
|
|
@@ -12578,7 +13048,7 @@ Capability Blocks (`image.generate`, `video.generate`, `audio.music`, `audio.sfx
|
|
|
12578
13048
|
| `vidu-q3-turbo` | Vidu Q3 Turbo | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 | model default | model default | yes | yes | yes |
|
|
12579
13049
|
| `wan-2.7` | Wan 2.7 | 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 | 720p, 1080p | model default | required | yes | no |
|
|
12580
13050
|
|
|
12581
|
-
Veo 3.1 Lite at 1080p is always 8 seconds. Generated images are stored as measured PNGs (up to 8192 pixels a side); videos are stored as the provider's MP4.
|
|
13051
|
+
Veo 3.1 Lite at 1080p is always 8 seconds. With no model named, a video uses `vidu-q3-turbo`; when its `aspectRatio` is one that model cannot make (`9:16` for a vertical clip), it uses the first of `wan-2.6-flash`, `veo-3.1-lite`, `seedance-2.0-mini`, `kling-v3-turbo`, `seedance-2.0-fast`, `seedance-2.0` (cheapest first) that makes that shape with the frames and `duration` given. A model the Block names is kept, and refused if it cannot make the shape. Generated images are stored as measured PNGs (up to 8192 pixels a side); videos are stored as the provider's MP4.
|
|
12582
13052
|
|
|
12583
13053
|
### Music and sound effects
|
|
12584
13054
|
| Operation | Model | Length | Price |
|
|
@@ -12716,19 +13186,12 @@ The latest version of each Block bundled with the SDK, generated from the packag
|
|
|
12716
13186
|
| --- | --- | --- | --- | --- |
|
|
12717
13187
|
| `sm.ai-decide` 1.0.0 | AI decide | task | `data` text (external_data) · primary; `rules` text (decision_rules) | `decision` object (ai_decision) · primary |
|
|
12718
13188
|
| `sm.ai-write` 1.0.0 | AI write | task | `data` text (external_data) · primary; `instructions` text (writing_instructions) | `text` text (written_message) · primary |
|
|
12719
|
-
| `sm.animated-scene` 2.3.9 | Animated Scene | workspace | `animation` scene-document (animated_scene_document) · project context only · primary; `shotPlan` shot-plan (production_shot_plan) · project context only; `updatedAnimation` scene-document (animated_scene_document); `commit` boolean (apply_changes); `generatedImage` image (image); `targetShotId` text (target_shot); `targetLayerId` text (target_layer); `attachGeneratedImage` boolean (attach_image); `action` text (animated_scene_action); `versions` metadata[] (revision_history) · project context only; `presence` metadata[] (collaborator_presence); `assets` asset[] (project_media) · project context only | `animation` scene-document (animated_scene_document) · primary; `edit` workspace-edit (workspace_edit); `qualityAction` approval (portable_media_action); `renderAction` approval (portable_media_action); `qualityReport` metadata (quality_report); `editorTimeline` timeline (editor_handoff); `revision` metadata (revision_event) |
|
|
12720
|
-
| `sm.animated-scene-assistant` 1.0.0 | Plan Animated Scene | task | `animation` scene-document · project context only; `request` text · primary | `edit` workspace-edit · primary |
|
|
12721
|
-
| `sm.blueprint` 2.1.9 | Blueprint | workspace | `blueprint` bible (production_blueprint) · project context only · primary; `updatedBlueprint` bible (production_blueprint); `commit` boolean (apply_changes); `assistantEdit` workspace-edit (workspace_edit); `versions` metadata[] (revision_history) · project context only; `presence` metadata[] (collaborator_presence); `assets` asset[] (project_media) · project context only | `blueprint` bible (production_blueprint) · primary; `edit` workspace-edit (workspace_edit); `shotPlan` shot-plan (shot_plan_handoff); `revision` metadata (revision_event) |
|
|
12722
|
-
| `sm.blueprint-assistant` 1.0.0 | Improve Blueprint | task | `blueprint` bible · project context only; `request` text · primary | `edit` workspace-edit · primary |
|
|
12723
13189
|
| `sm.board-editor` 2.2.11 | Whiteboard | workspace | `board` board-document (whiteboard_document) · project context only · primary; `updatedBoard` board-document (whiteboard_document); `commit` boolean (apply_changes); `action` text (whiteboard_action); `presenterPrompt` text (presenter_prompt); `generatedImage` image (image); `versions` metadata[] (revision_history) · project context only; `presence` metadata[] (collaborator_presence); `assets` asset[] (project_media) · project context only | `board` board-document (whiteboard_document) · primary; `edit` workspace-edit (workspace_edit); `presenterAction` approval (portable_media_action); `qualityAction` approval (portable_media_action); `renderAction` approval (portable_media_action); `actionEdit` workspace-edit (workspace_edit); `qualityReport` metadata (quality_report); `editorTimeline` timeline (editor_handoff); `revision` metadata (revision_event) |
|
|
12724
13190
|
| `sm.captions` 2.0.0 | Captions | editor-extension | `transcript` transcript · primary; `projectTranscript` transcript · project context only; `timeline` timeline · project context only; `text` text; `start` number; `duration` number; `highlightColor` text; `animation` text | `edit` timeline-edit · primary |
|
|
12725
13191
|
| `sm.character` 2.0.1 | Character reference | workspace | `input` text (character_description) · primary; `name` text (character_name); `role` text (character_role); `appearance` text (character_appearance); `personality` text (character_personality) | `output` character (character_reference) · primary |
|
|
12726
|
-
| `sm.chat` 2.0.3 | Production chat | workspace | `brief` brief (production_brief) · primary | `script` script (narration_script) · primary |
|
|
12727
13192
|
| `sm.compare` 2.0.2 | Compare assets | workspace | `imageA` image (comparison_image_a) · primary; `imageB` image (comparison_image_b); `selection` text (comparison_selected_image); `position` integer (comparison_split_position) | `output` image (selected_comparison_image) · primary |
|
|
12728
13193
|
| `sm.connect-app` 1.0.0 | Connect an app | task | `app` text (connected_app) · primary | `result` object (external_data) · primary |
|
|
12729
13194
|
| `sm.contact-fields` 1.0.0 | Contact fields | task | `record` object (external_data) · primary | `email` text (contact_email) · primary; `first_name` text (contact_first_name); `last_name` text (contact_last_name); `full_name` text (contact_full_name); `phone` text (contact_phone); `company` text (contact_company); `city` text (contact_city); `other_answers` text (contact_other_answers) |
|
|
12730
|
-
| `sm.continue-if` 1.0.0 | Continue only if | task | `decision` object (ai_decision) · primary; `item` object (external_data); `minimum_score` integer (minimum_score) | `item` object (external_data) · primary |
|
|
12731
|
-
| `sm.describe-media` 2.1.0 | Describe media | task | `image` image (source_image) · primary | `description` text (visual_description) · primary |
|
|
12732
13195
|
| `sm.edit-assistant` 2.0.2 | Edit Assistant | task | `timeline` timeline (timeline) · project context only · primary; `selection` timeline-range (timeline_range); `request` text (edit_request) | `edit` timeline-edit (timeline_edit_proposal) · primary |
|
|
12733
13196
|
| `sm.editor` 2.2.12 | Editor | workspace | `editor` timeline (editor_timeline) · project context only · primary; `assets` asset[] (legacy.asset-list.assets) · project context only; `updatedEditor` timeline (legacy.timeline.updatededitor); `commit` boolean (legacy.boolean.commit); `action` text (legacy.text.action); `renderFps` integer (legacy.integer.renderfps); `renderResolution` integer (legacy.integer.renderresolution); `renderQuality` text (legacy.text.renderquality) | `editor` timeline (editor_timeline) · primary; `edit` workspace-edit (legacy.workspace-edit.edit); `mediaAction` approval (portable_media_action) |
|
|
12734
13197
|
| `sm.export-audio` 2.0.0 | Export audio | task | `input` timeline (timeline) · primary; `renderFps` text (render_fps); `renderResolution` text (render_resolution); `renderQuality` text (render_quality); `projectTimeline` timeline (timeline) · project context only | `mediaAction` approval (portable_media_action); `output` audio (rendered_audio) |
|
|
@@ -12738,41 +13201,22 @@ The latest version of each Block bundled with the SDK, generated from the packag
|
|
|
12738
13201
|
| `sm.generate-image` 2.1.0 | Generate image | task | `input` text · primary; `photos` image[] (reference_image) | `output` image · primary |
|
|
12739
13202
|
| `sm.generate-video` 2.0.1 | Generate video | task | `prompt` brief (video_generation_brief) · primary | `video` video (generated_video) · primary |
|
|
12740
13203
|
| `sm.grade` 2.0.2 | Color grade | editor-extension | `timeline` timeline (editor_timeline) · project context only · primary; `clipIds` text[] (timeline_clip_ids); `look` text (color_grade_look); `adjustments` json (color_grade_adjustments) | `edit` timeline-edit (timeline_edit) · primary |
|
|
12741
|
-
| `sm.image-editor` 2.0.1 | Image editor | workspace | `input` image (source_image) · primary; `brightness` number (brightness_adjustment); `contrast` number (contrast_adjustment) | `output` image (processed_image) · primary |
|
|
12742
13204
|
| `sm.import-media` 2.0.2 | Import media | task | `files` asset[] (legacy.asset-list.files) · primary; `include` text (legacy.text.include); `duplicates` text (legacy.text.duplicates) | `assets` asset[] (legacy.asset-list.assets) · primary |
|
|
12743
13205
|
| `sm.location` 2.0.1 | Location reference | workspace | `input` text (location_description) · primary; `name` text (location_name); `visualDetails` text (location_visual_details); `timeOfDay` text (location_time); `atmosphere` text (location_atmosphere) | `output` location (location_reference) · primary |
|
|
12744
13206
|
| `sm.meta-creative` 1.0.0 | Meta ad creative | task | `upload` object (external_data) · primary; `message` text (ad_primary_text); `website_url` text (ad_destination_url); `page_id` text (facebook_page_id); `call_to_action` text (ad_call_to_action) | `creative` object (meta_ad_creative) · primary |
|
|
12745
13207
|
| `sm.motion-graphics` 2.0.0 | Motion graphics | editor-extension | `timeline` timeline (timeline) · primary; `projectTimeline` timeline (timeline) · project context only; `plan` json (motion_graphics_plan) | `timeline` timeline (timeline) · primary; `edit` workspace-edit (editor_workspace_edit) |
|
|
12746
|
-
| `sm.new-items` 1.0.0 | Only new items | task | `items` object (external_data) · primary; `id_field` text (item_id_field) | `item` object (external_data) · primary; `waiting` integer (pending_item_count) |
|
|
12747
13208
|
| `sm.panel-strip` 2.4.2 | Image Panels | workspace | `panels` panel-document (image_panel_document) · project context only · primary; `transcript` transcript (timed_transcript) · project context only; `script` script (narration_script) · project context only; `narrationScript` script (narration_script); `narrationTiming` transcript (timed_transcript); `blueprint` bible (production_blueprint) · project context only; `updatedPanels` panel-document (image_panel_document); `commit` boolean (apply_changes); `generatedImage` image (image); `targetPanelId` text (panel_id); `attachGeneratedImage` boolean (boolean); `action` text (host_action); `stockQuery` text (stock_query); `generatedImages` image[] (image_list) | `panels` panel-document (image_panel_document) · primary; `edit` workspace-edit (workspace_edit); `stockAction` approval (portable_media_action); `qualityAction` approval (portable_media_action); `renderAction` approval (portable_media_action); `stockAsset` asset (licensed_asset); `stockImage` image (licensed_stock_image); `qualityReport` metadata (quality_report); `generationPlan` metadata (generation_plan); `generateAction` approval (portable_media_action); `generatedImage` image (generated_image); `batchGenerateAction` approval (portable_media_action); `generatedImages` image[] (generated_image_list) |
|
|
12748
13209
|
| `sm.photo-notes` 1.1.0 | Photo notes | task | `photos` image[] (photo_set) · primary | `notes` text (photo_notes) · primary |
|
|
12749
|
-
| `sm.pick-photos` 1.0.0 | Pick best photos | task | `photos` image[] (image) · primary; `keep` integer (photo_keep_count); `instruction` text (photo_selection_instruction); `notes` json (photo_notes) | `photos` image[] (image) · primary; `reasons` text (plain_text) |
|
|
12750
13210
|
| `sm.pipeline` 2.2.6 | Canvas | workspace | `canvas` pipeline (canvas_document) · project context only · primary; `assets` asset[] (project_assets) · project context only; `versions` metadata[] (asset_versions) · project context only; `updatedCanvas` pipeline (canvas_document); `commit` boolean (canvas_commit_intent); `generatedImage` image (generated_image_version); `addGeneratedImage` boolean (attach_generated_image); `generatedImageTitle` text (generated_image_title); `generatedVideo` video (generated_video_version); `addGeneratedVideo` boolean (attach_generated_video); `generatedVideoTitle` text (generated_video_title); `generatedAudio` audio (generated_audio_version); `addGeneratedAudio` boolean (attach_generated_audio); `generatedAudioTitle` text (generated_audio_title); `connectionProposal` json (context_connection_proposal); `applyContextConnections` boolean (context_connection_intent) | `canvas` pipeline (canvas_document) · primary; `edit` workspace-edit (canvas_edit_proposal) |
|
|
12751
|
-
| `sm.quality-audit` 1.0.2 | Quality review plan | task | `assets` asset[] (saved_asset_versions) · project context only · primary | `plan` metadata (visual_quality_review_plan) · primary |
|
|
12752
|
-
| `sm.research` 2.0.2 | Research | task | `input` text (research_question) · primary; `sourceTitle` text (source_title); `sourceUrl` text (source_url); `sourceNotes` text (source_notes) | `output` research (research_dossier) · primary |
|
|
12753
13211
|
| `sm.rough-cut` 2.0.2 | Rough Cut | task | `timeline` timeline (timeline) · project context only · primary; `clipIds` text (timeline_clip_selection); `startAt` number (timeline_start_seconds); `gap` number (timeline_clip_gap_seconds) | `edit` timeline-edit (timeline_edit_proposal) · primary |
|
|
12754
13212
|
| `sm.score` 2.0.0 | Audio scoring | workspace | `timeline` timeline (timeline) · primary; `projectTimeline` timeline (timeline) · project context only; `audio` audio; `stockAsset` asset (licensed_asset); `audios` audio[]; `plan` json (audio_score_plan) | `timeline` timeline (timeline) · primary; `edit` workspace-edit (editor_workspace_edit) |
|
|
12755
|
-
| `sm.script` 2.1.5 | Script Writer | workspace | `brief` brief (project_brief) · primary; `currentScript` script (narration_script) · project context only | `script` script (narration_script) · primary |
|
|
12756
|
-
| `sm.shot-plan` 2.2.1 | Shots | workspace | `shotPlan` shot-plan (production_shot_plan) · project context only · primary; `transcript` transcript (timed_transcript) · project context only; `script` script (narration_script) · project context only; `narrationScript` script (narration_script); `narrationTiming` transcript (timed_transcript); `updatedShotPlan` shot-plan (production_shot_plan); `commit` boolean (apply_changes) | `shotPlan` shot-plan (production_shot_plan) · primary; `edit` workspace-edit (workspace_edit) |
|
|
12757
|
-
| `sm.shot-plan-assistant` 1.0.0 | Plan Shots | task | `shotPlan` shot-plan · project context only; `request` text · primary | `edit` workspace-edit · primary |
|
|
12758
|
-
| `sm.stock-search` 2.0.0 | Stock media search | task | `query` text (stock_query) · primary; `mediaType` text (stock_media_type) | `stockAction` approval (portable_media_action); `asset` asset (licensed_asset) |
|
|
12759
13213
|
| `sm.style` 2.0.1 | Style reference | workspace | `input` text (visual_style_direction) · primary; `name` text (style_name); `palette` text (color_palette); `lighting` text (lighting_direction); `camera` text (camera_direction) | `output` style (visual_style_reference) · primary |
|
|
12760
|
-
| `sm.thumbnail` 2.0.1 | Thumbnail | task | `brief` brief (thumbnail_brief) · primary | `thumbnail` image (generated_thumbnail) · primary |
|
|
12761
|
-
| `sm.transcribe` 2.0.1 | Transcription | task | `audio` audio (source_audio) · primary; `language` text (transcription_language) | `transcript` transcript (transcript_text) · primary |
|
|
12762
13214
|
| `sm.transition` 2.0.2 | Transition | editor-extension | `timeline` timeline (editor_timeline) · project context only · primary; `clipIds` text[] (timeline_clip_ids); `transition` text (timeline_transition_type); `duration` number (timeline_transition_duration); `soundOn` boolean (timeline_transition_sound_enabled); `soundId` text (timeline_transition_sound_id) | `edit` timeline-edit (timeline_edit) · primary |
|
|
12763
13215
|
| `sm.video-assets` 2.1.4 | Video Assets | workspace | `assets` asset[] (media_collection) · project context only · primary; `versions` metadata[] (media_version_history) · project context only; `incoming` asset[] (media_collection); `kind` text (media_filter); `selectedIds` text (media_selection); `selectedVersions` text (media_version_selection) | `output` asset[] (production_media) · primary |
|
|
12764
13216
|
| `sm.voiceover` 2.1.6 | Voiceover | workspace | `input` script (narration_script) · primary; `take` audio (narration_audio); `library` asset[] (project_media) · project context only; `timing` transcript (timed_transcript) · project context only | `output` audio (narration_audio) · primary |
|
|
12765
13217
|
| `sm.when` 1.1.0 | Automation | task | `event` object (workflow_event); `start` object (automation_start); `only_if` text (automation_condition) | `item` object (external_data) · primary; `trigger` object (automation_trigger); `check` object (ai_decision) |
|
|
12766
|
-
| `sm.whiteboard-assistant` 1.0.0 | Build Whiteboard | task | `board` board-document · project context only; `request` text · primary | `edit` workspace-edit · primary |
|
|
12767
13218
|
| `sm.write-summary` 1.0.0 | Write summary | task | `data` text (external_data) · primary | `summary` text (summary_report) · primary |
|
|
12768
|
-
| `
|
|
12769
|
-
| `stillmade.narration` 1.0.1 | Narration | workspace | `brief` brief (project_brief) · primary; `script` script (narration_script); `tone` text (narration_tone); `rules` text (writing_rules); `voice` text (speech_voice); `speed` number (speech_speed); `take` audio (narration_audio) | `script` script (narration_script); `audio` audio (narration_audio) · primary |
|
|
12770
|
-
| `stillmade.occasion-story` 1.0.2 | Occasion Story | task | `assets` asset[] (legacy.asset-list.assets) · primary; `occasion` text (stillmade.occasion_story.occasion); `title` text (stillmade.occasion_story.title); `ending` text (stillmade.occasion_story.ending); `notes` text (stillmade.occasion_story.notes); `selections` text (stillmade.occasion_story.selections); `music` audio[] (stillmade.occasion_story.music); `musicVolume` number (stillmade.occasion_story.musicvolume); `muteVideoAudio` boolean (stillmade.occasion_story.mutevideoaudio); `aspect` text (stillmade.occasion_story.aspect); `disclosure` text (stillmade.occasion_story.disclosure) | `timeline` timeline (legacy.timeline.updatededitor) · primary; `review` json (stillmade.occasion_story.review) |
|
|
12771
|
-
| `stillmade.photo-reel-composer` 1.0.2 | Photo Reel Composer | task | `assets` asset[] (legacy.asset-list.assets) · primary; `purpose` text (stillmade.photo_reel.purpose); `title` text (stillmade.photo_reel.title); `captions` text (stillmade.photo_reel.captions); `cta` text (stillmade.photo_reel.cta); `contact` text (stillmade.photo_reel.contact); `demoDisclosure` text (stillmade.photo_reel.demodisclosure); `duration` number (stillmade.photo_reel.duration); `aspect` text (stillmade.photo_reel.aspect); `logo` asset[] (stillmade.photo_reel.logo); `music` asset[] (stillmade.photo_reel.music) | `timeline` timeline (legacy.timeline.updatededitor) · primary; `review` json (stillmade.photo_reel.review); `provenance` json (stillmade.photo_reel.provenance) |
|
|
12772
|
-
| `stillmade.product-ad-variants` 1.0.0 | Product Ad Variants | task | `timeline` timeline (legacy.timeline.updatededitor) · primary; `hookA` text (stillmade.product_ad_variants.hooka); `hookB` text (stillmade.product_ad_variants.hookb); `hookC` text (stillmade.product_ad_variants.hookc); `openingA` number (stillmade.product_ad_variants.openinga); `openingB` number (stillmade.product_ad_variants.openingb); `openingC` number (stillmade.product_ad_variants.openingc); `openingDuration` number (stillmade.product_ad_variants.openingduration); `copyApproved` boolean (stillmade.product_ad_variants.copyapproved) | `variantA` timeline (legacy.timeline.updatededitor) · primary; `variantB` timeline (legacy.timeline.updatededitor); `variantC` timeline (legacy.timeline.updatededitor); `review` json (stillmade.product_ad_variants.review) |
|
|
12773
|
-
| `stillmade.release-visuals` 1.0.2 | Release Visuals | task | `assets` asset[] (legacy.asset-list.assets) · primary; `purpose` text (stillmade.release_visuals.purpose); `title` text (stillmade.release_visuals.title); `artist` text (stillmade.release_visuals.artist); `cta` text (stillmade.release_visuals.cta); `duration` number (stillmade.release_visuals.duration); `songVolume` number (stillmade.release_visuals.songvolume); `offset` number (stillmade.release_visuals.offset); `lyrics` text (stillmade.release_visuals.lyrics); `aspect` text (stillmade.release_visuals.aspect); `disclosure` text (stillmade.release_visuals.disclosure) | `timeline` timeline (legacy.timeline.updatededitor) · primary; `review` json (stillmade.release_visuals.review) |
|
|
12774
|
-
| `stillmade.screen-recorder` 1.0.1 | Screen Recorder | task | `video` video (source_video) · primary; `original` video (source_video) | `video` video (edited_video) · primary; `original` video (source_video) |
|
|
12775
|
-
| `stillmade.short-form-finish` 1.0.3 | Short-form Finish | task | `assets` video[] (legacy.asset-list.assets) · primary; `music` audio[] (legacy.asset-list.assets); `audioMode` text (stillmade.short_form.audiomode); `speechVolume` number (stillmade.short_form.speechvolume); `purpose` text (stillmade.short_form.purpose); `cuts` text (stillmade.short_form.cuts); `subtitles` text (stillmade.short_form.subtitles); `title` text (stillmade.short_form.title); `byline` text (stillmade.short_form.byline); `cta` text (stillmade.short_form.cta); `disclosure` text (stillmade.short_form.disclosure); `captionStyle` text (stillmade.short_form.captionstyle); `aspect` text (stillmade.short_form.aspect); `fit` text (stillmade.short_form.fit); `cropX` number (stillmade.short_form.cropx) | `timeline` timeline (legacy.timeline.updatededitor) · primary; `review` json (stillmade.short_form.review) |
|
|
13219
|
+
| `stillmade.generate` 1.0.0 | Generate | task | `prompt` text (generation_prompt) · primary; `mode` text (generation_mode); `model` text (generation_model); `aspectRatio` text (aspect_ratio); `size` text (render_resolution); `quality` text (render_quality); `duration` integer (duration_seconds); `sound` boolean (generate_audio); `count` integer (generation_count); `avoid` text (negative_prompt); `seed` integer (generation_seed); `references` image[] (reference_image); `startFrame` image (first_frame); `endFrame` image (last_frame) | `media` asset[] (generated_media) · primary; `notMade` text (generation_failures) |
|
|
12776
13220
|
|
|
12777
13221
|
## Custom view rules
|
|
12778
13222
|
|
|
@@ -12819,17 +13263,17 @@ For hosted text generation use {manifest,capability,tests}, runtime:"capability"
|
|
|
12819
13263
|
|
|
12820
13264
|
### Hosted media operations
|
|
12821
13265
|
|
|
12822
|
-
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},"language":{"type":"text","default":"auto"}},"outputs":{"transcript":{"type":"transcript","primary":true}},"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}}}]}.
|
|
13266
|
+
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}}}]}.
|
|
12823
13267
|
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}}}]}.
|
|
12824
13268
|
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}}}]}.
|
|
12825
|
-
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."}},"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"]}}}]}.
|
|
12826
|
-
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},"direction":{"type":"text","default":"Polish this cut conservatively."}},"outputs":{"edit":{"type":"timeline-edit","primary":true}},"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}}}]}.
|
|
12827
|
-
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}},"outputs":{"video":{"type":"video","primary":true}},"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}}}]}.
|
|
13269
|
+
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"]}}}]}.
|
|
13270
|
+
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}}}]}.
|
|
13271
|
+
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}}}]}.
|
|
12828
13272
|
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.
|
|
12829
13273
|
|
|
12830
13274
|
### Module Blocks
|
|
12831
13275
|
|
|
12832
|
-
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: let it fail the run rather than catching it and returning an empty result. 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).
|
|
13276
|
+
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).
|
|
12833
13277
|
|
|
12834
13278
|
### ComfyUI workflows
|
|
12835
13279
|
|
|
@@ -12865,7 +13309,7 @@ Write the description in third-grade language: one short sentence, ideally 8–1
|
|
|
12865
13309
|
|
|
12866
13310
|
### Compact interface
|
|
12867
13311
|
|
|
12868
|
-
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
|
|
13312
|
+
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.
|
|
12869
13313
|
|
|
12870
13314
|
### Generated controls first
|
|
12871
13315
|
|
|
@@ -12877,7 +13321,7 @@ Optional custom view interfaces are supported, but generated manifest.ui control
|
|
|
12877
13321
|
|
|
12878
13322
|
*When:* StillMade's builder adds this section when the person asks for an interface, preview or controls, or refines a Block that has one.
|
|
12879
13323
|
|
|
12880
|
-
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`). 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 a + tile. Never 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. 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 with the result first and the action within reach. 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.
|
|
13324
|
+
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.
|
|
12881
13325
|
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`:
|
|
12882
13326
|
|
|
12883
13327
|
- **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:`.
|
|
@@ -12923,7 +13367,7 @@ not proof of multiplayer operation. Test two interfaces exchanging actual edits
|
|
|
12923
13367
|
and saved outputs, permission loss, reconnect and conflicts before claiming
|
|
12924
13368
|
collaboration support. Never claim arbitrary imported JavaScript state syncs
|
|
12925
13369
|
merely because the package passed its computation fixtures.
|
|
12926
|
-
Example view for a package taking and returning text: {"html":"<main class=\"sm-block\"><
|
|
13370
|
+
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});"}.
|
|
12927
13371
|
|
|
12928
13372
|
## Port fields
|
|
12929
13373
|
|