# StillMade developer documentation URL: https://www.stillmade.shop/docs ## Build for StillMade A Block is a reusable production capability. Place it in a Project Type and it becomes a Step, with its own configuration and pinned version. StillMade handles the production workspace, typed connections, and persistent project context. ## Build an original Block Give your coding agent [the SDK authoring contract](/docs/tools/llm) and your idea. Build on the SDK contracts from the first iteration, then select the finished source folder in StillMade. The implementation and interface import unchanged; no adaptation phase is needed. See [original SDK folders](/docs/build/original-blocks). ## Adapt an existing repository This public SDK contains the contracts, examples and tooling needed to adapt a GitHub repository outside StillMade. Coding agents can inspect the repository, retain licensing and return one installable .stillmade-block file. [Portable packages and repository adaptation](/docs/tools/repository-adaptation). ## Choose what to build - [Build your first Block](/docs/build/quickstart): create, test, preview, and package a working capability. - [Block builder contract](/docs/build/block-builder-contract): the rules every Block follows, however it is made, and how readiness is judged. - [Tutorial: a React Block in 30 minutes](/docs/build/tutorial): build, preview with hot reload, test offline, import and publish. - [What you can build](/docs/gallery): templates and complete examples across video, images, music, data, documents, integrations, education and e-commerce. - [Preview with hot reload](/docs/build/local-preview): start from a template for any runtime, preview the interface in StillMade's own view host, and test offline with stand-ins for every hosted operation. - [Generate images](/docs/build/hosted-images): create a reviewed prompt-to-image Block with quality and payment chosen in StillMade. - [Generate speech from a script](/docs/build/hosted-speech): declare typed audio, suggest a catalog voice, review cost, and listen before accepting. - [Frame views](/docs/build/frame-views): interfaces written with React or another framework, SVG, canvas and drag, with theming and shared editing. - [Outside APIs](/docs/build/outside-apis): call any API with the person's own key or OAuth sign-in, through declared origins and published OpenAPI adapters; keys never reach Block code. - [Project storage, reads and assets](/docs/build/project-storage): keep values and files of up to 64 MiB per project in the Block's cloud storage, read the project, add media assets and propose edits people review and undo. - [Triggers and unattended runs](/docs/build/triggers): run on new media, a schedule, a webhook, another project's completion or a provider event, once the owner turns it on; hosted Blocks stay inside an approved budget. - [Data types](/docs/build/data-types): files (PDF, text, CSV, JSON, ZIP), documents, tables, links, dates and colors, with their controls, result layouts and conversions. - [Module Blocks](/docs/build/modules): modern JavaScript with npm libraries and WebAssembly (OpenCV and other C/C++ builds), sandboxed in the browser, with on-device media processing (decode, cut, join, extract and normalise audio and video). - [Analyze media](/docs/build/media-analysis): ask about a saved video (optionally one time range) or up to four images and get timestamped evidence. - [Read the web](/docs/build/web-reading): read a public page or research a topic with cited sources, paid with StillMade credits. - [Generate music and sound effects](/docs/build/hosted-music): create music or a sound effect with the app's generators, paid with StillMade credits. - [Transcribe stored audio](/docs/build/transcription): return reviewed text and word timings through the host-owned BYOK boundary. - [Add speech to the Editor](/docs/build/audio-to-editor): place an accepted project recording on the audio lane at the playhead. - [Build a Project Type](/docs/project-types/build): assemble an ordered workflow from compatible Blocks. - [Build with your own AI coding tool](/docs/tools/llm): take the complete authoring guide to Claude, Codex, Gemini, or another coding assistant. ## SDK and documentation The **SDK** is the code, validators, isolated runtime, TypeScript declarations, examples, and command-line tools you download and run. This **documentation** explains their contracts, workflows, and integration boundaries. Both are versioned for SDK 0.1.0. [Download the SDK](/docs/sdk) or explore the [API reference](/docs/reference/execution). ## Development lifecycle 1. Describe the capability and declare its typed inputs and outputs. 2. Implement a recipe, standalone JavaScript function body, or supported declarative host request. 3. Test realistic synthetic examples, including edge cases. 4. Preview the interface with `stillmade-block dev` (hot reload, stand-ins for hosted calls), package the tested source, and import it into StillMade. 5. Place the Block into a Project Type and rehearse its connections. 6. Release an immutable version when you are ready to share. ## Production model Normal users work through ordered Steps. They add a capability at a position in the workflow; compatible primary ports connect automatically. Secondary values can come from scoped project context. A technical graph is not required to build a Project Type. Read [Blocks, Steps, and Project Types](/docs/build/concepts) before designing a package. --- # Build an original SDK folder URL: https://www.stillmade.shop/docs/build/original-blocks Extract `/block-sdk/sdk-docs.zip` into a development directory, then run: ```sh node packages/block-cli/cli.js create my-block javascript node packages/block-cli/cli.js validate my-block node packages/block-cli/cli.js test my-block node packages/block-cli/cli.js pack my-block my-block.stillmade-block --original ``` `create` supplies an editable starter. Replace its identity, task behavior, controls and fixtures with your Block's requirements, retaining applicable SDK license notices. The directory layout is: ```text my-block/ stillmade.block.json src/run.js src/view.html optional custom interface src/view.css optional styles src/view.js optional interface behavior tests/fixtures.json ``` `src/run.js` is an isolated function body operating on `input`; it returns declared outputs. The custom interface uses the supplied `StillMade` bridge. A photo editor can read typed image pixels, apply its edits in `run.js`, show before/after images through `StillMade.previewImage`, and call `StillMade.run` from its controls. Author those files directly under the SDK contract; external framework packages, Node services and a separate standalone website are not this runtime's source. No build/install command executes during import. Select **my-block** with Choose folder to import that source unchanged. Development tools may live beside it; select the Block directory, not an SDK checkout containing several example builds. Use at least two fixtures (normal and edge cases) for the canonical archive, and use `--original` only for your own original implementation without reused repository/dependency code. Direct folder import and archive import retain the same runtime and interface; packaging adds explicit retained-source evidence. Security, source-rights confirmation and hosted execution approvals still use the ordinary review path. They do not adapt the Block. This is the implemented SDK, not the full future runtime catalog. It supports data-only recipes, isolated JavaScript, reviewed hosted text and speech generation, and a reviewed ComfyUI core-image adapter, typed inputs/outputs, schema-driven controls, optional sandboxed HTML interfaces, behavior fixtures, package validation, standalone execution, and app import. Python source, external modules, native dependencies, custom ComfyUI nodes and per-frame editor renderers are not executable through this SDK version. ComfyUI requires an explicitly selected account connection and a real remote review; recipe and JavaScript examples continue to run offline. --- # Build your first Block URL: https://www.stillmade.shop/docs/build/quickstart ## Prerequisites Download and extract the [SDK 0.1.0](/block-sdk/stillmade-sdk-0.1.0.zip). Install Node.js 22 or later. Run commands from the extracted SDK folder. The runtime and its dependencies are bundled; the following example needs no account, API key, npm install, or network request. ## Create a Block ```sh node packages/block-cli/cli.js create ./my-block javascript ``` The scaffold creates these editable files: ```text my-block/ stillmade.block.json src/run.js tests/fixtures.json ``` ## Understand the source The manifest declares the contract. The source is a synchronous function body receiving validated inputs through `input`: ```js const text = input.text.trim(); return { text, words: text ? text.split(/\s+/).length : 0 }; ``` The exact input/output names come from the [manifest](/docs/reference/manifest). There is no server to configure and no hidden credential to supply. ## Validate and test ```sh node packages/block-cli/cli.js validate ./my-block node packages/block-cli/cli.js test ./my-block node packages/block-cli/cli.js preview ./my-block ``` Validation checks package structure. Tests execute every fixture through the isolated runtime and compare the entire returned output with its expected value. Preview executes the first fixture and prints its outputs. A failed test must be corrected before packaging. ## Import the finished folder Open [Create import](/create/import), choose **Choose folder**, and select `my-block`. Its manifest, implementation, fixtures and optional `src/view.html`, `src/view.css`, `src/view.js` interface load unchanged. Review checks and the actual preview, then confirm import after the required source-rights and device review. The SDK contracts are the foundation from the first development step; there is no source adaptation phase. ## Package an alternate delivery file For an original implementation, retain complete license notices and at least two fixtures (normal and edge cases), then run: ```sh node packages/block-cli/cli.js pack ./my-block ./my-block.stillmade-block --original ``` Select that file with **Choose package** for the same source and interface. Packaging adds retained source evidence and reruns applicable checks; it does not rewrite the implementation. Output files are never silently overwritten. Use repository evidence instead of `--original` when reusing third-party code. ## Photo-editor folder example The extracted SDK contains `examples/original-photo-editor/`, including its actual custom interface, pixel processing and fixtures. Select that folder directly or try [its packaged original](/block-sdk/examples/original-photo-editor.stillmade-block). This bounded example adjusts brightness/color and rotates pixels with transparency. See [the supported original-authoring contract](/docs/build/original-blocks). ## Use and iterate Add the Block to a Project Type, configure its inputs, and run it. Keep source changes in checkpoints; publish a new release version when the behavior changes. See [versioning and remixing](/docs/distribute/versioning). --- # Block builder contract URL: https://www.stillmade.shop/docs/build/block-builder-contract Contract `stillmade.block-builder@1.0.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. ## Source of truth 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. ## Reuse first Before building, check what already exists (`find_existing_blocks` over MCP, or the Marketplace and your library) by the inputs, results and operations the goal needs. Use an installed or reviewed Block that already does it; remix a close one so its license, notices and creator credit stay attached; chain existing Blocks and build only the missing step. Never republish another creator's source without remix lineage: public copies are refused. Build a new Block when nothing fits or the person asks for their own version, and say which similar Blocks you considered. ## Define the capability first 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 and keep implementation details out of it. ## Blocks compose A Block is not a mini-app. 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[]`, ...), a `semantic` meaning on every port, `required` and a short `description`. 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()` (the same engine as the Project Type composer) resolves inputs from compatible upstream outputs, permitted project context and, for optional non-context inputs, `StillMade.requestFromUser`. ## Use platform primitives 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. ## No hidden workarounds 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.network`, `permissions.filesystem` and `permissions.secrets` must be empty arrays. ## Interface 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 genuinely needs one. Expose the capability, not the implementation: meaningful controls, sensible defaults, preview, direct manipulation, progress, clear errors and visible outputs. Do not show raw JSON, internal IDs, filesystem details, provider terminology or developer settings unless the Block is itself a developer tool. Follow the host appearance (Light, Dark and White) and the mobile layout contract: one interface for every screen size that fits a 320 CSS-pixel phone without horizontal scrolling. ## Shared editing 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. ## External services 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) 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 typed outcomes the Block reports, not crashes. 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 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. ## Dependencies A Block must declare everything it requires and must not assume anything is installed. Portable packages carry no package dependencies (`portable.dependencies` is empty): inline small, license-compatible code and record its license and notice in `manifest.license` and `manifest.provenance`. Desktop-only tools are reached solely through declared desktop permissions and the host desktop bridge. If a dependency cannot be delivered through the Block runtime, report the incompatibility instead of assuming the user's machine has it. ## Security Treat Block code, external data, files, repositories, model output and network responses as untrusted. Request the minimum permissions and capabilities. 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. ## Execution lifecycle 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. ## Useful failures 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". ## Adapt to the platform 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. ## What StillMade verifies 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. ## Project Types 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. ## Readiness report 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. Choose exactly one status: 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. ## Definition of done 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. - DOD-01 Installs through the normal Block mechanism - DOD-02 Manifest and contracts validate - DOD-03 Runs through the normal runtime - DOD-04 Required permissions are declared - DOD-05 Required dependencies are reproducible - DOD-06 Real inputs work - DOD-07 Invalid inputs fail correctly - DOD-08 Outputs conform to their declared types - DOD-09 Outputs are usable by compatible downstream Blocks - DOD-10 Universal resolver integration works - DOD-11 Applicable project context works - DOD-12 Applicable persistence works - DOD-13 Applicable multiplayer behavior works - DOD-14 Cancellation and failure cleanup work - DOD-15 UI works in relevant states and screen sizes - DOD-16 No hidden developer setup is required - DOD-17 Security boundaries remain intact - DOD-18 Relevant integration tests pass - DOD-19 Works after application restart or project reopen - DOD-20 A realistic Project Type execution succeeds when applicable ## Platform gap classes - A: Block implementation problem - 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 --- # Blocks, Steps, and Project Types URL: https://www.stillmade.shop/docs/build/concepts ## Block A reusable production workspace or capability. The built-in catalog consists of full screens: Script Writer, Voiceover, Shots, Blueprint, Canvas, Editor, Image Panels, White Board Motion, and Assets. Chat is the protected starting workspace. Internal canvas nodes and individual editor controls are implementation details inside their workspace, not separate built-in Blocks. External processing Blocks, such as Thicken Line Art or lip sync, add a capability to the workflow. A Block declares input and output ports, permissions, controls, and an implementation runtime. ## Step An instance of a Block inside a Project Type. Each Step has its own instance ID, pinned Block ID and version, configuration, enabled state, and optional condition. Reusing a Block in two positions creates two independent Steps. ## Project Type An ordered production workflow. Its persisted `stages` array stores Steps for compatibility with existing projects. Users can add, remove, duplicate, configure, disable, or reorder Steps. The protected starting chat belongs to the host and cannot be replaced or remixed. ## Typed connections An image-producing Block can connect to an image-consuming Block. Primary ports make insertion predictable; semantic roles distinguish a source image from a character reference. Secondary inputs can bind to project context. See [types and compatibility](/docs/reference/types), [context](/docs/build/context), and [Project Type construction](/docs/project-types/build). ## Complete workspace Blocks Built-in Blocks are the existing complete production screens. A Guided Project Type follows Chat → Voiceover → Shots → Blueprint → Canvas → Editor; export belongs to Editor. The reusable Script Writer is the full Narrative Compiler; it and the legacy Script adapter do not replace Chat. Canvas remains the home of persistent visual references and character consistency. The Block boundary follows the complete user-placeable Step. Package and remix the complete workspace, including its related tools. For example, Motion Graphics remains inside the Editor; Image Panels, Blueprint, and the Whiteboard Step are each one Block. The White Board Motion Project Type contains protected Chat followed by Voiceover, Whiteboard, and Editor: four complete Blocks, with no extra Project-Type Block. Historical component IDs may remain resolvable for saved projects without becoming discoverable Blocks. A source snapshot or host alias alone is not portable. A portable workspace owns its interface and executes through an installable SDK runtime. The first-party sm.blueprint@2.0.0, sm.board-editor@2.0.0, sm.editor@2.0.0, and sm.pipeline@2.0.0 packages in the downloadable SDK demonstrate complete sandboxed workspaces using typed documents and the public before/after workspace proposal contract. Whiteboard retains all boards, placed media, timing, transitions, motion settings, and custom fields as one remixable Block. Editor retains Motion Graphics and every other editing tool inside the Editor Block. Canvas retains its native content and layout while the Project Type builder owns Step placement and wiring. Package-supplied React bundles are not exposed; use [custom sandboxed HTML interfaces](/docs/build/custom-interface). --- # Script Writer workspace URL: https://www.stillmade.shop/docs/build/script-writer Script Writer (`sm.script@1.1.0`) reuses the full Narrative Compiler: reference transcripts, writing and optional research, review, inline edits, revision chat, saved scripts, learned voices, and version history. Open its listing in Marketplace or the Block library, then choose **Use in studio → Open workspace**. It is no longer an account-menu utility. Starting Chat remains separate and protected. The manifest and adapter are in `packages/block-platform/script-writer.js`. They use the same SDK `validateValues` and `resolveContextInputs` contract as other Blocks. `client/src/components/ScriptWriterBlock.jsx` mounts the existing UI. - Primary `input`: optional `brief` document (schemaVersion 1) with production planning context. The app supplies the project title and conversation. A Step can prefill the topic with `config.input: {schemaVersion: 1, topic: "Your topic"}`. - Secondary `script`: optional `script` document from `context.script.read`, containing `{schemaVersion: 1, text: "Current narration"}`. - Primary `output`: `script`, semantic role `narration_script`, containing `{schemaVersion: 1, text: "Accepted narration"}`. Empty scripts, non-text values, and text over 200,000 characters are rejected. **Use this script** validates the output and updates the shared project narration. The recommended workflow is **Script Writer → Voiceover**; its compatible typed connection is implemented through the shared narration document, without graph nodes or wires. Opening the Block does not generate a script or charge credits. Existing writing and research actions retain their displayed prices and server billing checks. A viewer cannot write; accepting a stale draft cannot overwrite a teammate's changed narration. The version-addressed source download includes the current wrapper, Narrative Compiler, local source dependencies, and this SDK. This is a trusted built-in host workspace, not an arbitrary React package admitted by the external sandbox. External imports still support recipe and isolated JavaScript runtimes; changing an imported manifest to `runtime: host` cannot load privileged workspace code. --- # Send a script to Voiceover URL: https://www.stillmade.shop/docs/build/script-handoff A recipe, JavaScript, or hosted text-generation Block can supply narration to a Voiceover Step through an explicit Project Type connection. Add both Steps, then use **Advanced connections and legacy workflows → Connect steps** to connect the Block's declared `script` output to Voiceover's `input`. Placing the Steps next to one another alone does not create this document handoff. Production users then work through the normal Step screens; no Canvas node or wire is required. The [Script draft example](/block-sdk/examples/script-draft.stillmade.json) declares `outputs: {"script":{"type":"script","primary":true}}`. Embed that exact package in the Project Type's `packages` array, with these Steps and connection: ```json { "stages": [ {"id":"draft","blockId":"example.script-draft","version":"1.0.0","label":"Draft narration"}, {"id":"narration","blockId":"sm.voiceover","version":"1.1.0","label":"Voiceover"} ], "connections": [ {"from":{"stage":"draft","port":"script"},"to":{"stage":"narration","port":"input"}} ] } ``` This is the relevant portion of a Project Type, not a complete package. The selected output must contain `{"schemaVersion":1,"text":"Your narration"}` with non-empty text of at most 200,000 characters. The hosted text runtime has its smaller 32,768-character output limit. A plain `text` port is not automatically converted to `script`; authors must declare and return the structured type. Run the source Block, then continue to Voiceover. Review **Use script from [Step]** and choose **Apply script**, or **Keep current** to leave the existing narration. Applying rechecks the exact source placement, pinned version, completed result, current narration and edit access. Stale, skipped or unverifiable results are not substituted; older saved outputs may need a fresh run. A new library release does not replace the pinned Block. Multiple results are not concatenated or selected arbitrarily for narration. Applying preserves existing audio takes and history, clears stale word timings and transcript text, and marks the narration changed. Shared narration uses the normal collaboration and conflict controls. Voice generation and timing analysis remain separate explicit actions; opening Voiceover or applying a script does not start a paid audio request. Protected Chat and the built-in Script Writer's Narrative Compiler remain unchanged. --- # Custom Block interfaces URL: https://www.stillmade.shop/docs/build/custom-interface A package can include an optional **`view`** beside `manifest`, `code`/`recipe`, and `tests`. This is the Block's real interface in Preview: a user edits controls, presses its action, and sees the code's result. Packages without `view` use StillMade's generated controls and output viewer. `manifest.ui` remains the fallback controls schema; it is not the custom interface source. `view` optionally declares `appearance: "original"` or `"host"` and contains `html` (required, up to 64 KiB), `css` (optional, up to 32 KiB), and `javascript` (optional, up to 64 KiB). These are UTF-8 byte limits. For folder packages, use separate `src/view.html`, `src/view.css`, and `src/view.js` files (the last two are optional). Optional theme-specific CSS uses `src/view.themed.css`. The older `src/view.json` object format is also supported, but never combine it with separate interface files. CLI pack, folder import, and ZIP import reconstruct the same `view` object and run the same validators. Use **Choose folder** on the import page to select an edited SDK package directory. `src/run.js` remains the production function; `src/view.js` controls the interface. A standard view is a reviewed subset of standalone controls: no framework, URL, npm module or build step runs inside it. For React or another framework, SVG, canvas or WebGL, drag, timers and loops, use a [frame view](/docs/build/frame-views) instead. ### Interface bridge The host provides `StillMade` inside the sandbox: | API | Behavior | | --- | --- | | `StillMade.input` | Copy of the current preview input bindings after initialization | | `StillMade.onInput(callback)` | Receives initial input and later host input changes; returns an unsubscribe function | | `StillMade.connectDocument(options)` | Connects a declared JSON content store to shared edits; returns status, flush and recovery methods | | `await StillMade.previewImage(value)` | Resolves a currently authorized image to a bounded, display-only PNG copy; preserves the original run input | | `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 | `admitPackage` and local admission include a **Shared interface contract** source check. The lightest path for an ordinary form control is one HTML annotation: ``. The host turns that into the same checked, permission-aware binding as `bindShared`; the Block does not implement networking, cursors, batching, conflict transport or retries. A `connectDocument` content-store connection supplies the platform-owned receive/publication paths for an existing structured store. Otherwise an `onShared` subscription is required, and detected editable controls must call `bindShared` or `updateShared`. This is a minimum static prerequisite, not full collaboration verification. Its `runtimeVerified` remains false even when it passes. Calls may be unreachable or fail to cover all controls; actual two-participant acceptance remains required. Runtime safety checks for already pinned immutable packages are separate and do not rewrite those releases. The general `scanPackage` keeps compatibility by default; passing `{collaboration:true}` requests this admission prerequisite. Creation and import services use `scanAdmissionPackage` for the same mandatory source check, including hosted Blocks. This is not yet an enforced two-user runtime acceptance guarantee for every import route. Project workspaces also expose `StillMade.onShared(callback)` and `StillMade.getShared()`. The snapshot contains `state` (shared interface JSON), `outputs` (the latest saved results, including another collaborator's generation), `memory` (declared Block runtime memory), and `canEdit`. Use `StillMade.updateShared({field: value})` to queue a shared interface edit. It checks edit permission and per-field preconditions; the host sync status reports durable acknowledgement. In saved Step workspaces, the promise resolves only after an acknowledged shared save, so `run` waits for pending edits to reach storage. Builder previews remain local. The `{queued:true}` result does not itself expose a server receipt or establish durability in every host. The unchanged original photo-editor folder has local behavioral acceptance: independent owner/editor/viewer browsers share edits and saved image outputs, reopen them, wait for a delayed save, preserve independent concurrent fields and reject stale field preconditions. Actual isolated producer/editor/consumer execution verifies its input/output handoff. Fixture identities and storage transport are used; authors still need to exercise their own complete journey. Saved declared outputs can be previewed with the current package pin and exact host media receipt; a shared URL alone does not grant image access. The host's existing guarded Step/Home exits also await pending direct `updateShared` writes, including dependent promise continuations in the current task. Rejected fields remain unconfirmed until an acknowledged replacement for those fields; unrelated successful edits do not clear them. This internal barrier is not exposed to Block code, does not cover every navigation route, and does not wait for future provider results or arbitrary private callbacks. The state is limited to 384 KiB of UTF-8 JSON per placement. Shared edit requests, including before-values, are capped at 800,000 bytes; in-flight and queued requests together are capped at 2 MiB. The project still has its existing 1 MB operation envelope and 4 MB live-document limits. These limits are cumulative, not an unlimited quota per Block. Use named fields and commit text drafts on blur or a short trailing debounce; do not send an entire document on every pointer movement. Observe `onShared` to render remote changes and results. Conflicting edits must be reconciled with the latest snapshot, not silently overwritten. Builder previews simulate this state locally, without network sharing. For a structured object, opt into checked nested merging with `StillMade.updateShared({plan: editedPlan}, {plan: {exists: true, value: basePlan}}, {merge: true})`. `basePlan` must be the exact snapshot used to construct `editedPlan`, not a newer snapshot read just before sending. Unchanged nested fields are retained from the current shared state. Arrays of objects with distinct string `id` fields merge by identity; independent edits and additions can coexist. Overlapping edits, deleting an item someone changed, or incompatible reorderings reject the entire patch. Scalars, non-keyed arrays and normal calls without `merge` stay strict. This is not character-level text merging. Structured merges still obey the 384 KiB state limit and 2,000-operation limit per structured field. Keep edits bounded and retain a rejected local draft for explicit resolution. Initializing an absent field is a strict write; do not assume concurrent initializations merge. For a structured editor, `StillMade.scheduleSharedEdit('plan', callback)` replaces the pending callback for that name, running after 150 ms without another call or at 500 ms maximum wait. Capture the original edit base once, keep the local draft, and serialize/publish in the callback. Do not recapture a newer remote base for an old edit. There are at most 32 pending names; closing the interface cancels them unless the host first completes an explicit flush. Step-header navigation now requests that flush and waits for the bound controls and scheduled callbacks. Return the write promise from a scheduled callback so it can be awaited. This is not a server save acknowledgement or a durable queue. Keep a pending-edit indicator and block Review until your writes settle; surface errors and retain the draft. Check permission again before publishing. Do not schedule paid runs or replay user actions from remote updates. For a large object document, `StillMade.diffDocument(base, draft)` returns checked field/identity operations and `StillMade.applyDocument(base, operations)` returns a new document or throws on a stale precondition. Both are pure local helpers: they do not save anything, mutate the supplied object, grant permissions, or run a Block. They use the same operation implementation as project collaboration. Each object document is limited to 4 MiB of UTF-8 JSON; operations are limited to 384 KiB and 2,000 entries. Reorder operations carry IDs instead of copies of the entire collection, retaining concurrently added items and supporting replay. Unchanged executable source, runtime memory and nested interface state do not appear in a field edit. Actual insertion/deletion/replacement values still count toward all limits. Paths and collection identities remain validated. `StillMade.indexById(items)` returns a detached `Map` for a JSON collection with distinct nonempty string IDs (at most 10,000 items and 4 MiB). Changing the map or its values cannot mutate the input. Use this bounded helper for identity lookup instead of repeatedly scanning large collections in instrumented view callbacks. These helpers let a workspace keep a bounded edit overlay rather than copying a large source document into `state`. Publishing, overlay merging, original edit bases, conflict recovery and `onShared` rendering are still the Block's job. Never rebase an old edit by substituting newer before-values, and never treat a successful local apply as a server acknowledgement. Call document helpers in a scheduled edit, not on every pointer event. The helpers alone do not make an existing local-only interface collaborative. ### Connect an existing content store once `StillMade.connectDocument` owns shared subscriptions, checked edit overlays, 150 ms quiet / 500 ms maximum batching, in-flight rebasing and conflict status. If two participants initialize an empty shared field together, a rejected initializer waits up to five seconds for the actual winning snapshot before rebasing its original edit intent. It does not invent a base or blindly retry; conflicts, permission loss, disposal and missing-snapshot timeouts preserve the local draft and require recovery. Keep the Block's existing synchronous JSON store and renderer: ```js const shared = StillMade.connectDocument({ key: 'boardDraft', source: initialBoard, collections: [ {path: ['strip'], key: 'panelId'}, {path: ['strip', '*', 'elements'], key: 'id'}, ], read: () => draft, replace: value => { draft = value; draw(); }, }); // Add this to the existing content-change callback, not every individual control: const changed = () => shared.changed(); // Optional: subscribe: notify => existingStore.subscribe(notify) // replaces explicit changed() calls for stores with a change subscription. StillMade.onInput(input => shared.replaceSource(input.board)); shared.onStatus(status => { save.disabled = !status.canEdit || !!status.error; }); ``` `replace` must synchronously replace content and redraw without saving, running a Block or dispatching other side effects. Local selection, playback, preview URLs, credentials and device state do not belong in the shared document. Source refreshes use `replaceSource`; a user reset/undo is a content change and must notify `changed`. Inputs, raw-text drafts and delayed result handlers that bypass the store still require adaptation. Private variables are never discovered or automatically synchronized. Collection declarations address original document fields. `*` traverses an explicitly declared parent collection. Declared IDs must be distinct nonempty strings; missing IDs do not fall back to array indexes. `panelId`, `shotId`, `id` and other safe declared keys preserve their original saved shapes. Renaming an identity is a checked removal/insertion. The adapter shares small overlays, not copies of unchanged documents. Existing 4 MiB document, 384 KiB shared-state and 2,000-edit limits still apply; there are at most eight connected documents in a view, each using a different shared field. For legacy data whose accepted contract permits missing, duplicate or otherwise unusable IDs, opt in explicitly with `{path:['scenes'],key:'shotId',fallback:'atomic'}`. Use that same declaration for every participant; never select a different codec based on the first loaded document. The codec uses a canonical tagged representation: usable identities still support small item-level edits, while legacy collections remain exact checked atomic values. It never invents IDs or silently normalizes saved data. Atomic collection changes can conflict or exceed the shared-state limit on large documents; they are not a substitute for author-defined stable identities. Changes between source representations keep the ordinary explicit source-conflict/reset rules. Strict declarations retain their original encoding. `getStatus()` and `onStatus(callback)` expose `phase`, `canEdit`, `dirty`, `pending`, `sourceConflict` and `error`; `onStatus` returns an unsubscribe function. The host also shows pending changes or the current adapter error. `await shared.flush()` waits for local transport acknowledgement, **not a durable server receipt**. The existing guarded workspace exits flush registered document connections before the shared-write barrier. Before a run that depends on the final draft, await `shared.flush()` and then read the current content. Incoming shared results do not replay a run; use `onShared` to display results/progress separately. Conflicting drafts stay local and visible. `await shared.retry()` retries against the preserved edit baseline. `await shared.discard()` acknowledges an exact no-op for this shared field before discarding its local draft, clearing that field's rejected-write ledger. A racing teammate edit, a changed local draft, pending writes or readonly/closed access refuse recovery rather than silently erase content or claim a successful exit. Other failed fields still block exit. `dispose()` refuses dirty, pending or failed connections; flush/discard first. This adapter is not character-level text merging and does not establish runtime coverage for arbitrary imported interfaces. No existing immutable Block release is rewritten merely by exposing this API. If a project input changes so an existing shared overlay no longer applies, `replaceSource` retains the last valid source and remembers the incoming source without publishing it. Source errors appear through adapter status, not a second generic `onInput` exception that would outlive recovery. Ordinary retry/discard refuse to silently use that stale source. Present a deliberate review/confirmation action before calling `await shared.resetSharedSource({discardSharedEdits:true})`: **this clears the shared draft for everyone**, not just the caller. The reset uses an exact current field precondition and adopts the remembered source only after acknowledgement. If a teammate acknowledges a compatible reset, the controller can adopt its remembered incoming source without needing another input event. It rebases only unconfirmed local intent, never reinstates the obsolete shared draft. A local edit that conflicts with the reset remains visible and requires explicit discard; ordinary retry cannot republish the stale shared content. Readonly access, pending writes, racing teammate edits, newer local edits or a newer incoming source prevent that adoption and preserve the local draft for fresh review. An acknowledged reset may already have cleared the old shared overlay when a subsequent local/source change is detected; the adapter reports that failure and requires a new explicit confirmation. Reset never runs a Block. For an ordinary static editable control, prefer `data-stillmade-share="title"` on the element and give it a unique `id`. No authored JavaScript adapter is needed. For a dynamically created or replaced control, call `StillMade.bindShared('title','title-input')` once after its element exists. Mark a genuinely device-only playback/filter control with `data-stillmade-local`; admission rejects an unexplained static control when the interface does not use a structured `updateShared` or `connectDocument` adapter. Both shared forms use the same host-owned binding and share a text, numeric, checkbox, or select value with a 150 ms trailing debounce, a 500 ms maximum batch wait during continuous typing, and a blur flush. Unchanged/duplicate native events do not publish or restart the delay. An edit already in flight must settle before the next write; network and persistence latency are separate. It observes remote changes, respects read-only access, and returns an unsubscribe function. It never dispatches synthetic input/click events or runs a Block in response to a teammate's edit. Derive non-DOM state and render saved outputs through `onShared`; private variables are not synchronized by binding a control. Do not bind credentials, file pickers, transient selection, or generation buttons. If a bound control is replaced dynamically, unsubscribe before binding its replacement. Text composition (IME) stays local until it ends; the debounce and maximum-wait timer do not publish unfinished preedit. Incoming shared values/defaults cannot overwrite the composing control. Its final edit retains the original field precondition, so a same-field teammate edit still conflicts instead of being silently overwritten. Workspace exit and `StillMade.run` reject while composition is unfinished. If editing access changes during composition, the final draft stays local even if access returns; duplicate native input events cannot submit it. A genuinely changed later value may retry against the original checked base. This does not provide character-level text merging or a general save-before-run guarantee. Use `StillMade.setSharedDefaults({title: initialTitle})` when input-derived defaults change. This updates defaults only for existing bound fields; it does not publish an edit, override a saved shared value, or replace a dirty/in-flight draft. Do not assign to a bound control's value during remote rendering. Read its current value for an explicit run so a preserved draft is not replaced by an unrelated teammate's update. Keep slider/input elements mounted while a gesture is active; update labels without rebuilding those controls. Conflicting drafts stay visible with a validation error; do not automatically overwrite the teammate's version. Dispose and rebind after the user chooses to discard a conflicting draft. Builder previews use local-only shared state (`localOnly: true`); this is not evidence of network collaboration. `updateShared` queues up to 32 waiting updates and serializes their per-field preconditions. A conflict rejects dependent queued updates instead of silently rebasing them. An optional second argument supplies explicit field preconditions captured when a draft began: `{title: {exists: true, value: 'original'}}`. An acknowledgement timeout fails closed until the Block is reopened. This protects against blindly retrying an edit whose acceptance is unknown. Cursor forwarding is installed by the host in all admitted sandbox views and does not require package code. Arbitrary private JavaScript variables and DOM mutations are **not** shared state. When adapting a repository, move persistent interface values onto this contract; importing a repository alone cannot make its private state collaborative. The transport supports authenticated pushed invalidations when configured, with polling fallback; concurrent edits to the same text field can conflict; this is not character-level CRDT editing. Always use `onInput` to initialize controls; the interface may load before its initial values arrive. Catch run errors and display them in the interface. Disable the run control while a request is pending. One run can be active; the host limits the view to 30 requests per minute, 4 MiB and 500,000 JSON nodes per message, and 15 seconds for local execution. Host-confirmed ComfyUI, text and speech requests allow up to 6 minutes; image generation allows up to 15 minutes for confirmation and its bounded fixture/sample sequence. These are interface wait budgets, not permission to retry or extend provider deadlines. Raw RGBA messages must fit both message budgets (roughly 124,000 pixels after reserving nodes for other inputs); use smaller preview images. The existing computation limits (including the JavaScript 500 ms guest deadline) still apply. Reset discards the interface and aborts its pending computation. Closing Preview disposes the frame and aborts outstanding work. For a port named `prompt`, initialize its control from the host input callback: ```js const inputField = document.getElementById('prompt'); StillMade.onInput(input => { inputField.value = input.prompt || ''; }); ``` Inside the async run button handler, call `await StillMade.run({prompt: inputField.value})` and display its output. Use `inputField` or `promptInput` for local variables; `prompt` is also a browser API name and cannot be a local identifier. Its data-property exception applies only to the single, unshadowed and unreassigned parameter of a direct inline `StillMade.onInput` callback; declare that parameter name only once in the interface source. Use ordinary `.prompt` access there, rather than computed/destructured access or a property on an unrelated object. Object literal `{prompt: value}` keys are supported. Browser dialogs remain unavailable. Use the DOM for interaction and rendering: `document.getElementById`, `querySelector`, `addEventListener`, `textContent`, `value`, `checked`, `disabled`, `hidden`, and canvas are supported. Declare elements in `html`; show/hide existing elements or draw into a declared canvas. Dynamic element creation, HTML insertion, reflection, computed dynamic property lookups, module imports, browser navigation, account APIs, parent/window access, and storage are rejected. Property access such as `result.text` and `image.data[0]` is supported; use array methods for iteration. Synchronous loops, function declarations, directly recursive helpers, timers, and microtask queues are rejected. Every callback/helper also checks a private task budget (10,000 calls or 50 ms between checks), including indirect recursion. This guard cannot interrupt a single native browser operation, so it is not a substitute for the computation worker. Use small native array operations such as `map`/`forEach` for rendering; heavy work belongs in the isolated Block runtime. Use `.textContent` for user text. Tags are ordinary controls, text, layout, media, and canvas; no script, iframe, SVG, link, form, or document metadata tags. Markup must use complete tags without HTML comments, inline event handlers, or navigation attributes. JavaScript belongs in `view.javascript`, not the HTML string. The frame has an opaque origin and `sandbox="allow-scripts"`, without same-origin, popups, forms, downloads, or top navigation permissions. CSP denies connections, workers, frames, external scripts, and resources. Image and media sources must be local `data:` or `blob:` URLs. Styles cannot import resources. No token, account, project mutation API, filesystem, or parent DOM handle is passed into the frame. Only copied inputs, explicit run results, and authorized image display copies cross the bridge. Static checks are an additional admission gate; they do not replace this browser boundary or the separate computation sandbox. Keep the UI responsive: move computation into `code` or `recipe`; DOM JavaScript is not the CPU-metered computation runtime. A file input can read a user-selected image with `FileReader.readAsDataURL`, load it into an existing ``, and draw it into a declared ``. Use `getImageData` to pass `{width,height,data:Array.from(pixels.data)}` to an image input, staying within the SDK image and message budgets. Nothing is uploaded by the view. Use the host's file controls when you do not need a custom picker. The host supplies StillMade's shared Light, Dark and White theme variables and basic controls. For optional StillMade appearance, inherit its typography or use `var(--font-body)` for controls and `var(--font-display)` with weight 200 and italic style for display headings. Use shared spacing, radius and color tokens such as `var(--bg)`, `var(--fg)`, `var(--fg-muted)`, `var(--border-tok)`, and `var(--r-3)`. Original appearance may keep its own typography and colors within the existing sandbox; importing external fonts or resources remains unavailable. Use a clear input, primary action, and visible result. Preview runs synthetic samples or the user's explicitly chosen input; it does not commit to a production project. SDK fixture tests prove code behavior and input/output compatibility. They do not claim to test browser interaction: review the actual custom interface in Preview before **Confirm Import**. ### Appearance compatibility StillMade has three modes: **Light** (cream), **Dark** (ink), and **White** (neutral white surfaces with black text). Generated controls inherit the app mode. A custom interface may keep its original design by explicitly setting `view.appearance: "original"`. The surrounding StillMade app keeps its own theme. Omitting this field, or setting it to `"host"`, preserves the existing requirement to provide theme-compatible colors. Existing package sources and installed pins are never rewritten to change appearance. A minimal original-style view is: ```json { "appearance": "original", "html": "", "css": "body{background:#14213d;color:#fff}", "javascript": "StillMade.onShared(shared=>{document.getElementById('result').textContent=shared.outputs.text||'';});" } ``` Keep this object in `src/view.json` when using the explicit appearance field; do not combine it with split `src/view.html`/CSS/JavaScript files. Folder, archive and GitHub SDK-folder imports retain the field, as do source editing and export. The stylesheets have separate purposes: - `view.css` (up to 32 KiB) retains the original design. Original-style packages use it inside projects as well as in Original design previews. - `view.themedCss` (optional, up to 32 KiB) is a **complete replacement stylesheet** for authors who also want StillMade appearance. Include the same layout and responsive rules using host tokens. Preview offers this mode only when the complete view passes the host-appearance check. For example, original CSS can use `body { background:#14213d; color:#fff; }`. Its optional themed stylesheet uses `body { background:var(--bg); color:var(--fg); }`. Move fixed inline colors into the stylesheets when offering both appearances. Never change image/video/canvas pixels to match the interface palette. For StillMade appearance, use `var(--bg)`, `var(--bg-elev-1)`, `var(--bg-elev-2)`, `var(--fg)`, `var(--fg-muted)`, `var(--border-tok)`, `var(--signal)`, and `var(--shadow-1)`. The SDK exports `APPEARANCE_TOKENS`. Inherit host typography or use `var(--font-body)` and `var(--font-display)`. Do not redefine host tokens or use raw palette tokens such as `--cream-100` in theme-aware declarations. Original appearance does not require these color or typography conventions. **The appearance declaration does not widen the interface runtime.** Existing HTML/JavaScript syntax restrictions, CSS restrictions on escapes, filters and color blending, resource-loading bans, size limits, shared-state requirements, permissions, CSP and typed input/output validation still apply. Use CSS classes for interface changes; dynamic element-style mutation remains unavailable. Static original colors are allowed only with the explicit original mode; any optional themed stylesheet still needs valid semantic colors. `scanPackage`, `admitPackage`, and CLI validation check the declared appearance alongside all other admission gates. A styling pass does not prove working UI, accessibility or full repository compatibility. Review the actual interface on its declared devices and, when supplied, each themed mode. This option preserves styling within the existing restricted view format; it does not load framework bundles, arbitrary React applications, remote fonts or external scripts. ### Display an authorized image Use **`await StillMade.previewImage(value)`** for an image selected in StillMade, received from a connected Block, or present in explicitly granted project context. The host also supports its registered sample pixels, local uploads, and verified run outputs. A well-formed reference alone does not grant access: a changed URL, version, role, unrelated asset, or expired preview session is rejected. Catch and display the error; do not fall back to fetching the URL. The result is exactly `{url, width, height}`. Its URL is a PNG data URL that can be assigned to a declared ``. It is a display copy, at most 512 pixels on either edge, with aspect ratio preserved. It does not generate, upload, save, select, or replace an asset. Continue to pass the **original** image value to `StillMade.run`; processing uses the original input within the runtime's own limits. Do not use the display URL as a processing input. Image display allows one pending request, up to 60 requests per minute, and a 15-second deadline. It is independent of the pending computation request, so an input can be displayed while its Block runs. Both directions retain the 4 MiB and 500,000-node message limits. Input changes, source/session reset, or closing the workspace cancel unfinished display requests. Use a local revision counter in asynchronous UI code so a late result cannot repaint an earlier selection. StillMade's host image controls remain available with a custom interface. A project lets the user select a compatible authorized image or upload one; connected/context bindings remain read-only. A standalone preview keeps uploaded files local to that preview. Host uploads accept PNG, JPEG, WebP or GIF up to 12 MB and one megapixel; larger uploads are rejected with a request to resize a copy. Existing authorized project references can be displayed up to 16 megapixels, but the selected runtime still applies its processing limit. No automatic processing resize occurs. The host validates permissions and exact media identity before resolving bytes; no account token, native file handle, or remote fetch capability enters the iframe. This `view` example assumes one image input and one image output, both named `image`. It shows the actual selected input and processed result while retaining their original typed values: ```json { "html": "
Selected image

", "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});" } ``` ### Complete custom interface example Download [custom-interface.stillmade.json](/block-sdk/examples/custom-interface.stillmade.json) for the complete tested text-cleanup package. Its `view` is: ```json { "html": "

Clean text

Result

Your result appears here.", "css": "output { display:block; white-space:pre-wrap; padding:16px; border:1px solid var(--border-tok); border-radius:var(--r-3); }", "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}});" } ``` ### Dynamic lists and controls Use `StillMade.render(containerId, nodes)` to replace the children of an existing container with structured controls. Use `StillMade.onAction(callback)` for their click, input, and change events; it returns an unsubscribe function. This supports variable-length lists without raw HTML injection or direct DOM creation. For example, with `
`: ```js StillMade.onInput(input => { StillMade.render('choices', (input.shots || []).map(shot => ({ tag: 'button', text: shot.name || shot.id, value: shot.id, action: 'select' }))); }); StillMade.onAction(event => { document.getElementById('selection').textContent = event.value; }); ``` Each node has `tag`, optional `text` or `children`, and optional `id`, `className`, `label` (accessible name), `action`, `value`, `type`, `checked`, `disabled`, `placeholder`, `min`, `max`, or `step`. Unknown fields are rejected. Supported tags are div, section, article, header, footer, p, span, strong, em, small, h1–h4, label, button, input, textarea, select, option, ul, ol, li, table, thead, tbody, tr, th, td, details, summary, output, and progress. Input types are text, number, range, checkbox, radio, and color. Use a div, section, article, main, aside, ul, ol, or tbody as the existing root. Style nodes with classes in your view CSS. An action belongs to a button, input, textarea, or select. Its callback receives `{action, value, checked, event}`. Buttons emit click; checkbox, radio, and select emit change; other fields emit input. Stable IDs preserve matching controls, focus and selection during reconciliation. Keep edited values in your declared content store, not only in the DOM. Text inputs emit one committed action at the end of IME composition, not intermediate preedit or duplicate final input events. Sibling redraws preserve active composition. If the same control's declared value changes remotely during composition, the renderer retains local text and blocks dispatch/exit rather than applying it against a silently advanced model base. Copy the retained text before reopening the Block to review the shared version; there is no automatic merge or retry. Disabling a composing control also retains its draft and blocks run/exit after access returns. Removed or rebound controls cannot commit their old composition to a different action; removed-control draft recovery is not provided. Call `StillMade.run` explicitly to execute production code. Rendering does not mutate project context or bypass execution permissions. Limits: 1,000 nodes per render, 12 child levels, 256 KiB of serialized descriptors, 5,000 total frame elements, 16 action listeners, 10,000 characters per text/value, 120 per ID/action and 240 per class/name/placeholder. IDs must be unique. Invalid renders leave the previous controls intact. Text is always literal; scripts, inline handlers, links, file inputs, network attributes, and raw HTML are rejected. ### Audio and video playback `await StillMade.previewMedia(reference)` opens an authorized `audio` or `video` reference as a temporary playback copy and returns `{kind, url, mimeType, bytes}`. Assign its URL to an existing `