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