stillmade-sdk 0.1.47 → 0.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/AGENTS.md +6 -2
  2. package/FRAME_VIEWS.md +71 -5
  3. package/INTERFACE_DESIGN.md +65 -2
  4. package/MODULES.md +26 -3
  5. package/PASTE_TO_LLM.md +25 -10
  6. package/README.md +15 -0
  7. package/SDK.md +349 -51
  8. package/blocks/native/sm.board-editor/versions/3.0.0/package.js +921 -0
  9. package/blocks/native/sm.editor/versions/3.0.0/package.js +751 -0
  10. package/blocks/native/sm.panel-strip/versions/3.0.0/package.js +350 -0
  11. package/blocks/native/sm.photo-editor/versions/1.0.0/package.js +6693 -0
  12. package/blocks/native/sm.voiceover/versions/3.0.0/package.js +513 -0
  13. package/docs/build/block-builder-contract.md +2 -2
  14. package/docs/build/custom-interface.md +23 -3
  15. package/docs/build/frame-views.md +71 -5
  16. package/docs/build/hosted-text.md +8 -3
  17. package/docs/build/interface-design.md +65 -2
  18. package/docs/build/media-analysis.md +18 -1
  19. package/docs/build/modules.md +26 -3
  20. package/docs/build/with-your-ai.md +14 -0
  21. package/docs/build/workspaces.md +42 -0
  22. package/docs/reference/authoring-guide.md +4 -4
  23. package/docs/reference/cli.md +8 -3
  24. package/docs/reference/execution.md +13 -5
  25. package/docs/reference/first-party-blocks.md +5 -4
  26. package/docs/reference/generation-models.md +4 -0
  27. package/docs/reference/limits.md +9 -2
  28. package/docs/reference/permissions.md +4 -0
  29. package/docs/reference/view-bridge.md +6 -3
  30. package/docs/reference/view-rules.md +1 -1
  31. package/docs/tools/llm.md +25 -10
  32. package/package.json +1 -1
  33. package/packages/block-cli/cli.js +124 -12
  34. package/packages/block-cli/dev-host/assets/host.js +55 -30
  35. package/packages/block-cli/dev-host/assets/index.css +1 -1
  36. package/packages/block-cli/dev-server.js +74 -23
  37. package/packages/block-cli/format.js +20 -1
  38. package/packages/block-cli/ready-gate.js +34 -2
  39. package/packages/block-cli/templates.js +61 -0
  40. package/packages/block-cli/type-packages.js +7 -4
  41. package/packages/block-cli/workspace-dev.js +124 -0
  42. package/packages/block-cli/workspace-pack.js +62 -0
  43. package/packages/block-cli/workspace.js +304 -0
  44. package/packages/block-platform/block-readiness.js +5 -4
  45. package/packages/block-platform/canvas.js +9 -0
  46. package/packages/block-platform/catalog.js +5 -3
  47. package/packages/block-platform/listings.js +2 -2
  48. package/packages/block-platform/node-defaults.js +2 -2
  49. package/packages/block-platform/plain-descriptions.js +1 -0
  50. package/packages/block-platform/type-workspace.js +8 -0
  51. package/packages/block-sdk/admission.js +2 -2
  52. package/packages/block-sdk/builder-contract.js +3 -3
  53. package/packages/block-sdk/capabilities.js +32 -12
  54. package/packages/block-sdk/contracts.js +11 -2
  55. package/packages/block-sdk/first-party-identities.js +9 -0
  56. package/packages/block-sdk/first-party-packages.js +9 -1
  57. package/packages/block-sdk/folder-package.js +34 -2
  58. package/packages/block-sdk/generation-catalog.js +7 -0
  59. package/packages/block-sdk/index.d.ts +13 -5
  60. package/packages/block-sdk/index.js +1 -1
  61. package/packages/block-sdk/mock-host.js +7 -0
  62. package/packages/block-sdk/module-hosted.js +11 -1
  63. package/packages/block-sdk/module-media-access.js +5 -3
  64. package/packages/block-sdk/permission-summary.js +1 -1
  65. package/packages/block-sdk/portable-media-actions.d.ts +4 -1
  66. package/packages/block-sdk/portable-media-actions.js +16 -1
  67. package/packages/block-sdk/remix-policy.js +2 -2
  68. package/packages/block-sdk/staged-releases.js +5 -0
  69. package/packages/block-sdk/stillmade-pack.js +64 -0
  70. package/packages/block-sdk/testing.js +2 -2
  71. package/packages/block-sdk/view-bridge.js +6 -3
  72. package/packages/block-sdk/view-design.js +59 -12
  73. package/packages/block-sdk/view.js +47 -9
  74. package/packages/design-system/block-kit.css +6 -0
  75. package/packages/design-system/theme.css +2 -0
@@ -10,13 +10,17 @@ That is the form after `npm install --save-dev stillmade-sdk`. From a downloaded
10
10
  | --- | --- | --- |
11
11
  | create ./folder | A template (create --list shows them); omit for the text recipe | Write a complete folder: manifest, source, fixtures, a README and, where needed, tests/mocks.json and package.json |
12
12
  | create-type ./type.json | None | Write an editable connected Project Type example |
13
+ | init ./folder | Optional --name "Listing kit" | Start a [workspace](../build/workspaces.md): blocks/, shared/, workflows/, the agents' guides and gate hooks |
14
+ | add name | A template; --app gmail with the connector template | Add blocks/name to the workspace you are in |
15
+ | add-type name | Block folder names, else all; optional --when schedule, webhook or app:name/EVENT | Add workflows/name.json made of the workspace's Blocks |
16
+ | build | None | Bundle each module Block's src/main.js and the shared/ code it imports into module/main.js |
13
17
  | validate target | Optional --mobile | Check the contract without executing behavior; --mobile adds the 320-pixel phone layout profile to the report |
14
18
  | inspect target | None | Print manifest/runtime or workflow support |
15
19
  | test target | Optional --mock | Execute fixtures and return the report; --mock (or a tests/mocks.json file) answers hosted calls with [stand-ins](../build/testing.md) |
16
20
  | run target | Optional input.json, --mock; Blocks also accept --state file and --save-state new-file | Execute once and print the outputs (preview is the same command) |
17
- | dev folder | Optional --port 4800, --build "npm run build", and --live (with --site) to review each saved version in your StillMade account | [Preview the interface with hot reload](../build/local-preview.md) |
21
+ | dev folder | Optional --port 4800, --build "npm run build", and --live (with --site) to review each saved version in your StillMade account | [Preview the interface with hot reload](../build/local-preview.md); in a workspace folder, every Block and workflow in one preview |
18
22
  | ready target | None | Run the [Block builder contract](../build/block-builder-contract.md) readiness check: fixtures, bad input, cancellation, reopen, composition with real catalog Blocks; exits non-zero when FAILED or BLOCKED |
19
- | gate folder | None | Run ready on every new or edited Block under the folder; a Block is not ready until it passes. Name the Block's own folder: `gate .` in a folder that also holds the extracted SDK checks the SDK's examples too |
23
+ | gate folder | None | Run ready on every new or edited Block (and, in a workspace, every workflow) under the folder; a Block is not ready until it passes. Name the Block's own folder: `gate .` in a folder that also holds the extracted SDK checks the SDK's examples too |
20
24
  | pack target | Optional output path. A module Block (or a frame view with src/view-files/) writes `<id>-<version>.stillmade-module.json`, other runtimes `<id>-<version>.stillmade.json`; --original with an output.stillmade-block path writes an archive of your own recipe, JavaScript, capability or ComfyUI code and refuses a module Block | Require passing tests, then write the file to import |
21
25
  | remix target | Optional output path | Write an editable draft copy with retained license and pinned lineage; nothing is executed |
22
26
 
@@ -46,6 +50,7 @@ That is the form after `npm install --save-dev stillmade-sdk`. From a downloaded
46
50
  | timeline-proposal | capability | Propose Editor timeline edits the person reviews (timeline.propose). |
47
51
  | outside-api | module | Call an outside https API with the person's own key (net.fetch). |
48
52
  | connection | module | Call a published OpenAPI adapter with the person's sign-in. |
53
+ | connector | module | Call a signed-in app (Gmail, Slack, Notion, Google Sheets...) by its action name; --app picks the app. |
49
54
  | storage | module | Remember values across runs in the project's Block storage. |
50
55
  | trigger-media | capability | Runs on its own when a video is saved (media.saved trigger). |
51
56
  | trigger-webhook | javascript | Runs when another service sends a webhook. |
@@ -68,4 +73,4 @@ node packages/block-cli/cli.js preview ./workflow.json
68
73
  ```
69
74
 
70
75
  ## Importable formats
71
- The CLI emits JSON (and `.stillmade-module.json` bundles for module Blocks), while StillMade also accepts reviewed ZIP source directories. Files outside the supported manifest, source, and fixture layout are not installed as executable dependencies.
76
+ The CLI emits JSON (and `.stillmade-module.json` bundles for module Blocks; a workspace's `pack` writes one `.stillmade-pack` of them all), while StillMade also accepts reviewed ZIP source directories. Files outside the supported manifest, source, and fixture layout are not installed as executable dependencies.
@@ -103,6 +103,8 @@ export function testPackage(pkg: BlockPackage, options?: { timeoutMs?: number; c
103
103
  export const SDK_VERSION: '0.1.0';
104
104
  export const ROOT_CHAT_ID: 'stillmade.starting-chat';
105
105
  export function stableStringify(value: unknown): string;
106
+ /** Whether `candidate` is the published first-party package `published` (its carried module text, moduleSource, left out). */
107
+ export function sameFirstPartySource(published: unknown, candidate: unknown): boolean;
106
108
  /** Throws when any text or field name in a package holds the NUL character (\u0000), which StillMade cannot store; the message names where it is (tests.1.mocks.files.tiny-1.text). Import review, validate, test, pack and ready run it. */
107
109
  export function assertStorableText(value: unknown): void;
108
110
  export function validateManifest(value: unknown): { valid: boolean; errors: { path: string; message: string }[] };
@@ -183,7 +185,7 @@ export function resolveContextInputs(manifest: Manifest, values: Record<string,
183
185
  export function projectReadSnapshot(manifest: Manifest, context?: Record<string, unknown>, fields?: string[]): Record<string, unknown>;
184
186
 
185
187
  export function validateView(view: unknown): true;
186
- export const FRAME_VIEW_LIMITS: Readonly<{htmlBytes:262144;cssBytes:262144;javascriptBytes:4194304;assetFiles:32;assetBytes:8388608;taskMs:250;longTaskMs:1000}>;
188
+ export const FRAME_VIEW_LIMITS: Readonly<{htmlBytes:262144;cssBytes:262144;javascriptBytes:4194304;assetFiles:32;assetBytes:8388608;codeFiles:64;codeFileBytes:4194304;codeBytes:16777216;mediaPreviews:24;taskMs:250;longTaskMs:1000}>;
187
189
  export const FRAME_VIEW_ASSET_TYPES: readonly string[];
188
190
  export function isFrameView(view: unknown): view is FrameView;
189
191
  export function validateFrameView(view: FrameView): true;
@@ -193,11 +195,13 @@ export function parseFrameViewJavaScript(source: string): {ast: unknown};
193
195
  export function instrumentFrameViewJavaScript(source: string, guard: string): string;
194
196
  export function describeViewFiles(files: Record<string, Uint8Array|ArrayBuffer|string>): Promise<NonNullable<FrameView['files']>>;
195
197
  /** In a frame view, the StillMade object also offers its asset files. */
196
- export interface FrameViewAssets { asset(path: string): string; assetBytes(path: string): ArrayBuffer; }
197
- export const VIEW_LIMITS: Readonly<{htmlBytes:65536;cssBytes:32768;javascriptBytes:65536;messageBytes:4194304;runsPerMinute:30;imagePreviewsPerMinute:60;imagePreviewMs:15000;imagePreviewEdge:512}>;
198
+ export interface FrameViewAssets { asset(path: string): string; assetBytes(path: string): ArrayBuffer; loadCode(path: string): Promise<void>; }
199
+ export const VIEW_LIMITS: Readonly<{htmlBytes:65536;cssBytes:32768;javascriptBytes:65536;messageBytes:4194304;runsPerMinute:120;imagePreviewsPerMinute:240;voicePreviewsPerMinute:30;allowanceCredits:2000;imagePreviewMs:15000;imagePreviewEdge:512;imagePreviewMaxEdge:2048}>;
198
200
 
199
201
  /** A display-only copy. Never substitute this URL for the original run input. */
200
- export interface BlockImagePreview { url: `data:image/png;base64,${string}`; width: number; height: number; }
202
+ export interface BlockImagePreview { url: `data:image/png;base64,${string}`|`data:image/jpeg;base64,${string}`; width: number; height: number; }
203
+ /** `edge`: the longest side of the display copy, 64 to 2048 pixels (default 512). */
204
+ export interface BlockImagePreviewOptions { edge?: number; }
201
205
  export interface BlockImagePixels { width: number; height: number; data: number[]; role?: string; }
202
206
  export interface BlockImageReference { kind: 'image'; assetId: string; versionId: string; url: string; role?: string; }
203
207
  export type DesktopPermission = "screen.capture"|"cursor.track"|"camera.capture"|"microphone.capture"|"clipboard.read"|"clipboard.write"|"media.pick"|"notifications.show"|"power.keep-awake"|"native.tools"|"folder.read"|"folder.write";
@@ -225,7 +229,7 @@ export interface BlockViewNode { tag: BlockViewTag; text?: string; children?: Bl
225
229
  export interface BlockViewAction { action: string; value: string; checked: boolean; event: 'click'|'input'|'change'; }
226
230
  export interface BlockMediaReference { kind: 'video'|'audio'; assetId: string; versionId: string; url: string; role?: string; }
227
231
  export interface BlockMediaPreview { kind: 'video'|'audio'; url: string; mimeType: string; bytes: number; }
228
- export interface BlockViewBridge { useOutput(outputs: Record<string, BlockMediaReference|BlockMediaReference[]>): Promise<Record<string, unknown>>; previewMedia(value: BlockMediaReference): Promise<BlockMediaPreview>; releaseMedia(url: string): void; render(targetId: string, tree: BlockViewNode[]): void; onAction(callback: (event: BlockViewAction) => void|Promise<void>): () => void; desktop(operation: DesktopPermission|DesktopToolOperation|import('./block-storage.js').BlockStorageOperation|"record.start"|"record.stop"|"record.cancel"|"session.close"|"live.start"|"live.stop"|"live.status"|"folder.pick"|"folder.shared"|"folder.forget"|"folder.list", args?: Record<string,unknown>): Promise<Record<string,unknown>>; readonly input: Record<string, unknown>; onInput(callback: (input: Record<string, unknown>) => void): () => void; run(input: Record<string, unknown>): Promise<Record<string, unknown>>; previewImage(value: BlockImageReference|BlockImagePixels): Promise<BlockImagePreview>; }
232
+ export interface BlockViewBridge { useOutput(outputs: Record<string, BlockMediaReference|BlockMediaReference[]>): Promise<Record<string, unknown>>; previewMedia(value: BlockMediaReference): Promise<BlockMediaPreview>; releaseMedia(url: string): void; render(targetId: string, tree: BlockViewNode[]): void; onAction(callback: (event: BlockViewAction) => void|Promise<void>): () => void; desktop(operation: DesktopPermission|DesktopToolOperation|import('./block-storage.js').BlockStorageOperation|"record.start"|"record.stop"|"record.cancel"|"session.close"|"live.start"|"live.stop"|"live.status"|"folder.pick"|"folder.shared"|"folder.forget"|"folder.list", args?: Record<string,unknown>): Promise<Record<string,unknown>>; readonly input: Record<string, unknown>; onInput(callback: (input: Record<string, unknown>) => void): () => void; run(input: Record<string, unknown>): Promise<Record<string, unknown>>; previewImage(value: BlockImageReference|BlockImagePixels, options?: BlockImagePreviewOptions): Promise<BlockImagePreview>; }
229
233
 
230
234
  /** Validate hosted UI colors independently of the preserved original stylesheet. */
231
235
  export function validateViewAppearance(view?: BlockView, options?: {appearance?:'host'|'original'}): true;
@@ -308,6 +312,10 @@ export type CapabilityExecutor = (pkg: BlockPackage, input: Record<string, unkno
308
312
  export const CAPABILITY_LIMITS: Readonly<{prompt:32000;system:8000;output:32768;maxTokens:4096;fixtures:3;includes:8;includeLength:256;packageBytes:262144;metadataBytes:8192}>;
309
313
  /** media.analyze bounds: images per run, question length and the closer-inspected video range in seconds. */
310
314
  export const MEDIA_ANALYZE_LIMITS: Readonly<{images:4;question:2000;range:12}>;
315
+ /** media.analyze reply:"text": the Block's own question and instructions about one to ten pictures, answered as text. */
316
+ export const MEDIA_REPLY_LIMITS: Readonly<{images:10;question:8000;instructions:8000;maxTokens:4000}>;
317
+ /** text.generate's limits: long structured jobs are priced by their size. */
318
+ export const TEXT_GENERATE_LIMITS: Readonly<{system:32000;prompt:64000;maxTokens:24000;output:131072}>;
311
319
  /** web.fetch and web.research bounds: URL, instruction and topic lengths, sources kept and answer size. */
312
320
  export const WEB_CAPABILITY_LIMITS: Readonly<{url:2048;instruction:4000;topic:500;sources:12;answer:32768}>;
313
321
  /** image.describe with an image[] input sends one to four images together. */
@@ -7,14 +7,14 @@ The latest version of each Block bundled with the SDK, generated from the packag
7
7
  | --- | --- | --- | --- | --- |
8
8
  | `sm.ai-decide` 1.0.0 | AI decide | task | `data` text (external_data) · primary; `rules` text (decision_rules) | `decision` object (ai_decision) · primary |
9
9
  | `sm.ai-write` 1.0.0 | AI write | task | `data` text (external_data) · primary; `instructions` text (writing_instructions) | `text` text (written_message) · primary |
10
- | `sm.board-editor` 2.2.11 | Whiteboard | workspace | `board` board-document (whiteboard_document) · project context only · primary; `updatedBoard` board-document (whiteboard_document); `commit` boolean (apply_changes); `action` text (whiteboard_action); `presenterPrompt` text (presenter_prompt); `generatedImage` image (image); `versions` metadata[] (revision_history) · project context only; `presence` metadata[] (collaborator_presence); `assets` asset[] (project_media) · project context only | `board` board-document (whiteboard_document) · primary; `edit` workspace-edit (workspace_edit); `presenterAction` approval (portable_media_action); `qualityAction` approval (portable_media_action); `renderAction` approval (portable_media_action); `actionEdit` workspace-edit (workspace_edit); `qualityReport` metadata (quality_report); `editorTimeline` timeline (editor_handoff); `revision` metadata (revision_event) |
10
+ | `sm.board-editor` 3.0.0 | White Board Motion | workspace | `narrationTiming` transcript (timed_transcript) · primary; `narrationAudio` audio (narration_audio); `board` board-document (whiteboard_document); `files` asset[] (export_media); `make` text (board_action); `request` json (board_request); `references` image[] (reference_image); `image` image (board_image); `firstFrame` image (first_frame); `lastFrame` image (last_frame); `dataUrl` text (file_data); `key` text (storage_key); `value` json (storage_value) | `output` board-document (whiteboard_document) · primary; `editorTimeline` timeline (editor_handoff); `export` asset (exported_video); `made` asset[] (made_media); `result` json (board_answer) |
11
11
  | `sm.captions` 2.0.0 | Captions | editor-extension | `transcript` transcript · primary; `projectTranscript` transcript · project context only; `timeline` timeline · project context only; `text` text; `start` number; `duration` number; `highlightColor` text; `animation` text | `edit` timeline-edit · primary |
12
12
  | `sm.character` 2.0.1 | Character reference | workspace | `input` text (character_description) · primary; `name` text (character_name); `role` text (character_role); `appearance` text (character_appearance); `personality` text (character_personality) | `output` character (character_reference) · primary |
13
13
  | `sm.compare` 2.0.2 | Compare assets | workspace | `imageA` image (comparison_image_a) · primary; `imageB` image (comparison_image_b); `selection` text (comparison_selected_image); `position` integer (comparison_split_position) | `output` image (selected_comparison_image) · primary |
14
14
  | `sm.connect-app` 1.0.0 | Connect an app | task | `app` text (connected_app) · primary | `result` object (external_data) · primary |
15
15
  | `sm.contact-fields` 1.0.0 | Contact fields | task | `record` object (external_data) · primary | `email` text (contact_email) · primary; `first_name` text (contact_first_name); `last_name` text (contact_last_name); `full_name` text (contact_full_name); `phone` text (contact_phone); `company` text (contact_company); `city` text (contact_city); `other_answers` text (contact_other_answers) |
16
16
  | `sm.edit-assistant` 2.0.2 | Edit Assistant | task | `timeline` timeline (timeline) · project context only · primary; `selection` timeline-range (timeline_range); `request` text (edit_request) | `edit` timeline-edit (timeline_edit_proposal) · primary |
17
- | `sm.editor` 2.2.12 | Editor | workspace | `editor` timeline (editor_timeline) · project context only · primary; `assets` asset[] (legacy.asset-list.assets) · project context only; `updatedEditor` timeline (legacy.timeline.updatededitor); `commit` boolean (legacy.boolean.commit); `action` text (legacy.text.action); `renderFps` integer (legacy.integer.renderfps); `renderResolution` integer (legacy.integer.renderresolution); `renderQuality` text (legacy.text.renderquality) | `editor` timeline (editor_timeline) · primary; `edit` workspace-edit (legacy.workspace-edit.edit); `mediaAction` approval (portable_media_action) |
17
+ | `sm.editor` 3.0.0 | Editor | workspace | `editor` timeline (editor_timeline) · primary; `cut` timeline (editor_handoff); `media` asset[] (source_media); `narrationAudio` audio (narration_audio); `narrationTiming` transcript (timed_transcript); `narrationScript` script (narration_script); `panelImages` asset[] (panel_images); `panels` panel-document (image_panel_document); `make` text (editor_action); `request` json (editor_request); `references` image[] (reference_image); `firstFrame` image (first_frame); `lastFrame` image (last_frame); `audio` asset (source_audio); `files` asset[] (export_media); `dataUrl` text (file_data); `key` text (storage_key); `value` json (storage_value) | `editor` timeline (editor_timeline) · primary; `export` asset (exported_video); `made` asset[] (made_media); `result` json (editor_answer) |
18
18
  | `sm.export-audio` 2.0.0 | Export audio | task | `input` timeline (timeline) · primary; `renderFps` text (render_fps); `renderResolution` text (render_resolution); `renderQuality` text (render_quality); `projectTimeline` timeline (timeline) · project context only | `mediaAction` approval (portable_media_action); `output` audio (rendered_audio) |
19
19
  | `sm.export-timeline` 2.0.0 | Export timeline | editor-extension | `timeline` timeline · project context only · primary; `assets` asset[] · project context only; `blueprint` bible · project context only; `shotPlan` shot-plan · project context only; `metadata` metadata · project context only; `title` text; `format` text | `download` file-bundle · primary |
20
20
  | `sm.export-video` 2.1.0 | Export video | task | `input` timeline (timeline) · primary; `media` asset[] (project_media); `narration` audio (narration_audio); `renderAspect` text (render_aspect); `renderFps` text (render_fps); `renderResolution` text (render_resolution); `renderQuality` text (render_quality); `projectTimeline` timeline (timeline) · project context only; `projectShotPlan` shot-plan (production_shot_plan) · project context only | `mediaAction` approval (portable_media_action); `output` video (rendered_video) |
@@ -26,7 +26,8 @@ The latest version of each Block bundled with the SDK, generated from the packag
26
26
  | `sm.location` 2.0.1 | Location reference | workspace | `input` text (location_description) · primary; `name` text (location_name); `visualDetails` text (location_visual_details); `timeOfDay` text (location_time); `atmosphere` text (location_atmosphere) | `output` location (location_reference) · primary |
27
27
  | `sm.meta-creative` 1.0.0 | Meta ad creative | task | `upload` object (external_data) · primary; `message` text (ad_primary_text); `website_url` text (ad_destination_url); `page_id` text (facebook_page_id); `call_to_action` text (ad_call_to_action) | `creative` object (meta_ad_creative) · primary |
28
28
  | `sm.motion-graphics` 2.0.0 | Motion graphics | editor-extension | `timeline` timeline (timeline) · primary; `projectTimeline` timeline (timeline) · project context only; `plan` json (motion_graphics_plan) | `timeline` timeline (timeline) · primary; `edit` workspace-edit (editor_workspace_edit) |
29
- | `sm.panel-strip` 2.4.2 | Image Panels | workspace | `panels` panel-document (image_panel_document) · project context only · primary; `transcript` transcript (timed_transcript) · project context only; `script` script (narration_script) · project context only; `narrationScript` script (narration_script); `narrationTiming` transcript (timed_transcript); `blueprint` bible (production_blueprint) · project context only; `updatedPanels` panel-document (image_panel_document); `commit` boolean (apply_changes); `generatedImage` image (image); `targetPanelId` text (panel_id); `attachGeneratedImage` boolean (boolean); `action` text (host_action); `stockQuery` text (stock_query); `generatedImages` image[] (image_list) | `panels` panel-document (image_panel_document) · primary; `edit` workspace-edit (workspace_edit); `stockAction` approval (portable_media_action); `qualityAction` approval (portable_media_action); `renderAction` approval (portable_media_action); `stockAsset` asset (licensed_asset); `stockImage` image (licensed_stock_image); `qualityReport` metadata (quality_report); `generationPlan` metadata (generation_plan); `generateAction` approval (portable_media_action); `generatedImage` image (generated_image); `batchGenerateAction` approval (portable_media_action); `generatedImages` image[] (generated_image_list) |
29
+ | `sm.panel-strip` 3.0.0 | Image Panels | workspace | `narrationTiming` transcript (timed_transcript) · primary; `narrationAudio` audio (narration_audio); `narrationScript` script (narration_script); `panels` panel-document (image_panel_document); `make` text (panel_action); `request` json (panel_request); `pictures` asset[] (panel_media); `references` image[] (reference_image); `image` image (panel_image); `firstFrame` image (first_frame); `lastFrame` image (last_frame); `dataUrl` text (picture_data); `key` text (storage_key); `value` json (storage_value) | `panels` panel-document (image_panel_document) · primary; `images` asset[] (panel_images); `made` asset[] (made_media); `result` json (panel_answer) |
30
+ | `sm.photo-editor` 1.0.0 | Photo editor | workspace | `photo` image (source_image) · primary; `edits` json (photo_edit_settings) | `image` image (processed_image) · primary |
30
31
  | `sm.photo-notes` 1.1.0 | Photo notes | task | `photos` image[] (photo_set) · primary | `notes` text (photo_notes) · primary |
31
32
  | `sm.pipeline` 2.2.6 | Canvas | workspace | `canvas` pipeline (canvas_document) · project context only · primary; `assets` asset[] (project_assets) · project context only; `versions` metadata[] (asset_versions) · project context only; `updatedCanvas` pipeline (canvas_document); `commit` boolean (canvas_commit_intent); `generatedImage` image (generated_image_version); `addGeneratedImage` boolean (attach_generated_image); `generatedImageTitle` text (generated_image_title); `generatedVideo` video (generated_video_version); `addGeneratedVideo` boolean (attach_generated_video); `generatedVideoTitle` text (generated_video_title); `generatedAudio` audio (generated_audio_version); `addGeneratedAudio` boolean (attach_generated_audio); `generatedAudioTitle` text (generated_audio_title); `connectionProposal` json (context_connection_proposal); `applyContextConnections` boolean (context_connection_intent) | `canvas` pipeline (canvas_document) · primary; `edit` workspace-edit (canvas_edit_proposal) |
32
33
  | `sm.rough-cut` 2.0.2 | Rough Cut | task | `timeline` timeline (timeline) · project context only · primary; `clipIds` text (timeline_clip_selection); `startAt` number (timeline_start_seconds); `gap` number (timeline_clip_gap_seconds) | `edit` timeline-edit (timeline_edit_proposal) · primary |
@@ -34,7 +35,7 @@ The latest version of each Block bundled with the SDK, generated from the packag
34
35
  | `sm.style` 2.0.1 | Style reference | workspace | `input` text (visual_style_direction) · primary; `name` text (style_name); `palette` text (color_palette); `lighting` text (lighting_direction); `camera` text (camera_direction) | `output` style (visual_style_reference) · primary |
35
36
  | `sm.transition` 2.0.2 | Transition | editor-extension | `timeline` timeline (editor_timeline) · project context only · primary; `clipIds` text[] (timeline_clip_ids); `transition` text (timeline_transition_type); `duration` number (timeline_transition_duration); `soundOn` boolean (timeline_transition_sound_enabled); `soundId` text (timeline_transition_sound_id) | `edit` timeline-edit (timeline_edit) · primary |
36
37
  | `sm.video-assets` 2.1.4 | Video Assets | workspace | `assets` asset[] (media_collection) · project context only · primary; `versions` metadata[] (media_version_history) · project context only; `incoming` asset[] (media_collection); `kind` text (media_filter); `selectedIds` text (media_selection); `selectedVersions` text (media_version_selection) | `output` asset[] (production_media) · primary |
37
- | `sm.voiceover` 2.1.6 | Voiceover | workspace | `input` script (narration_script) · primary; `take` audio (narration_audio); `library` asset[] (project_media) · project context only; `timing` transcript (timed_transcript) · project context only | `output` audio (narration_audio) · primary |
38
+ | `sm.voiceover` 3.0.0 | Voiceover | workspace | `input` script (narration_script) · primary; `script` text (voiceover_text); `voice` text (voice_choice); `speed` number (speech_speed); `take` audio (narration_audio); `takes` audio[] (voiceover_takes); `words` transcript (timed_transcript); `transcript` text (pasted_transcript); `make` text (voiceover_action) | `output` audio (narration_audio) · primary; `timing` transcript (timed_transcript); `spokenScript` script (narration_script); `takes` audio[] (voiceover_takes) |
38
39
  | `sm.when` 1.1.0 | Automation | task | `event` object (workflow_event); `start` object (automation_start); `only_if` text (automation_condition) | `item` object (external_data) · primary; `trigger` object (automation_trigger); `check` object (ai_decision) |
39
40
  | `sm.write-summary` 1.0.0 | Write summary | task | `data` text (external_data) · primary | `summary` text (summary_report) · primary |
40
41
  | `stillmade.generate` 1.0.0 | Generate | task | `prompt` text (generation_prompt) · primary; `mode` text (generation_mode); `model` text (generation_model); `aspectRatio` text (aspect_ratio); `size` text (render_resolution); `quality` text (render_quality); `duration` integer (duration_seconds); `sound` boolean (generate_audio); `count` integer (generation_count); `avoid` text (negative_prompt); `seed` integer (generation_seed); `references` image[] (reference_image); `startFrame` image (first_frame); `endFrame` image (last_frame) | `media` asset[] (generated_media) · primary; `notMade` text (generation_failures) |
@@ -10,6 +10,10 @@ Capability Blocks (`image.generate`, `video.generate`, `audio.music`, `audio.sfx
10
10
  | `nano-banana-2-lite` | Nano Banana 2 Lite | auto, 21:9, 16:9, 3:2, 4:3, 1:1, 4:5, 3:4, 2:3, 9:16 | 1K, 2K, 4K | model default | up to 8 | yes |
11
11
  | `nano-banana-pro` | Nano Banana Pro | auto, 21:9, 16:9, 3:2, 4:3, 1:1, 4:5, 3:4, 2:3, 9:16 | 1K, 2K, 4K | model default | up to 8 | yes |
12
12
  | `gpt-image-2` | GPT Image 2 | auto, 16:9, 9:16, 1:1, 4:3, 3:4 | 1K, 2K, 4K | low, medium, high | up to 8 | no |
13
+ | `gpt-image-2.5-flare` | GPT Image 2.5 Flare | auto, 16:9, 9:16, 1:1, 4:3, 3:4 | 1K, 2K, 4K | low, medium, high | up to 8 | no |
14
+ | `gpt-image-2.5-sunburst` | GPT Image 2.5 Sunburst | auto, 16:9, 9:16, 1:1, 4:3, 3:4 | 1K, 2K, 4K | low, medium, high | up to 8 | no |
15
+ | `gpt-image-2.6-flare` | GPT Image 2.6 Flare | auto, 16:9, 9:16, 1:1, 4:3, 3:4 | 1K, 2K, 4K | low, medium, high | up to 8 | no |
16
+ | `gpt-image-2.6-sunburst` | GPT Image 2.6 Sunburst | auto, 16:9, 9:16, 1:1, 4:3, 3:4 | 1K, 2K, 4K | low, medium, high | up to 8 | no |
13
17
  | `gpt-image-1.5` | GPT-Image 1.5 | 16:9, 9:16, 1:1 | model default | low, medium, high | no | no |
14
18
  | `kling-image-v3` | Kling Image V3 | 21:9, 16:9, 9:16, 1:1, 4:3, 3:4, 3:2, 2:3 | 1K, 2K | model default | no | no |
15
19
  | `seedream-v4.5` | Seedream 4.5 | 16:9, 9:16, 1:1, 4:3, 3:4 | 2K, 4K | model default | no | yes |
@@ -40,10 +40,13 @@ Every number below is read from the SDK constant named under its heading, the sa
40
40
  | Styles | 32 KiB |
41
41
  | Script | 64 KiB |
42
42
  | One message to the host | 4 MiB |
43
- | StillMade.run calls per minute | 30 |
44
- | Image previews per minute | 60 |
43
+ | StillMade.run calls per minute | 120 |
44
+ | Picture and media previews per minute | 240 |
45
+ | Voice samples per minute | 30 |
46
+ | Largest spending approval (credits) | 2,000 |
45
47
  | One image preview | 15 s |
46
48
  | Preview image side (pixels) | 512 |
49
+ | Sharper preview image side, for editors (pixels) | 2,048 |
47
50
 
48
51
  ## Frame views
49
52
 
@@ -56,6 +59,10 @@ Every number below is read from the SDK constant named under its heading, the sa
56
59
  | Script bundle (view.js) | 4 MiB |
57
60
  | Asset files | 32 |
58
61
  | All asset files | 8 MiB |
62
+ | Code files (StillMade.loadCode) | 64 |
63
+ | One code file | 4 MiB |
64
+ | All code files | 16 MiB |
65
+ | Playable media previews open at once (previewMedia) | 24 |
59
66
  | One task before it is stopped | 250 ms |
60
67
  | A blocking native operation before the view is closed | 1 s |
61
68
 
@@ -60,6 +60,10 @@ A JavaScript Block returns a `{schemaVersion: 1, kind: "stillmade.media-action",
60
60
  | --- | --- |
61
61
  | `final.render` | Ask the host to render the project timeline to a final video. |
62
62
  | `stock.search` | Search approved stock photos, videos or audio by query. |
63
+ | `stock.select` | Keep one stock.search result as media the Block returns, charged as StillMade's stock picker. |
64
+ | `stock.auto` | Find and keep stock media for up to 10 searches, skipping addresses already used. |
65
+ | `gif.search` | Search Giphy GIFs by query (free). |
66
+ | `gif.save` | Keep a gif.search result in StillMade's storage (free). |
63
67
  | `presenter.generate` | Generate presenter poses for a board from a prompt and its voiceover. |
64
68
  | `quality.analyze` | Analyze up to 16 owned images for production quality. |
65
69
  | `image.generate` | Generate one image from a bounded visual prompt through the host. |
@@ -19,7 +19,7 @@ Standard and frame views receive the same `StillMade` object; members marked Fra
19
19
 
20
20
  | Member | Where | What it does |
21
21
  | --- | --- | --- |
22
- | `getShared(): {state: Record<string, unknown>; outputs: Record<string, unknown>; memory: unknown; canEdit: boolean}` | Every view | A copy of the view state shared by everyone in the project, the last outputs, the Block memory and whether this person may edit. |
22
+ | `getShared(): {state: Record<string, unknown>; outputs: Record<string, unknown>; made: Array<StillMadeImage \| StillMadeVideo \| StillMadeAudio>; memory: unknown; canEdit: boolean}` | Every view | A copy of the view state shared by everyone in the project, the last outputs, every picture, clip or sound this Step's runs made (newest first, up to 400, which the view may preview and pass to a run), the Block memory and whether this person may edit. |
23
23
  | `onShared(callback: (shared: ReturnType<StillMadeView["getShared"]>) => void): () => void` | Every view | Receives the shared state now and after every change by anyone. Returns a function that stops listening. |
24
24
  | `updateShared(values: Record<string, unknown>, preconditions?: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>` | Every view | Changes shared fields. Keep each independently edited value in its own field so two people never conflict; a field someone else changed first is refused. |
25
25
  | `bindShared(key: string, id: string): () => void` | Every view | Binds a form element (by id) to one shared field: it shows other people's edits and saves this person's edits, keeping drafts while they type. |
@@ -34,12 +34,15 @@ Standard and frame views receive the same `StillMade` object; members marked Fra
34
34
 
35
35
  | Member | Where | What it does |
36
36
  | --- | --- | --- |
37
- | `previewImage(value: StillMadeImage): Promise<{url: string; width: number; height: number}>` | Every view | A display-only PNG copy (at most 512 pixels a side) of an image the view may show. Keep passing the original value to run. |
38
- | `previewMedia(value: StillMadeVideo \| StillMadeAudio): Promise<{kind: "video" \| "audio"; url: string}>` | Every view | A playable blob: URL for audio or video the view may play. Call releaseMedia when done. |
37
+ | `previewImage(value: StillMadeImage, options?: {edge?: number}): Promise<{url: string; width: number; height: number}>` | Every view | A display-only copy of an image the view may show: 512 pixels on its longest side, or `edge` (64 to 2048) for an editor that needs more. PNG, or JPEG for a large opaque copy. Keep passing the original value to run. |
38
+ | `previewMedia(value: StillMadeVideo \| StillMadeAudio): Promise<{kind: "video" \| "audio"; url: string; mimeType: string; bytes: number; blob: Blob}>` | Every view | A playable blob: URL for audio or video the view may play, and its bytes (blob) for decoding, such as a waveform. Call releaseMedia when done; a standard view holds two at a time, a frame view 24. |
39
39
  | `releaseMedia(url: string): void` | Every view | Frees a URL previewMedia returned. |
40
+ | `previewVoice(voice: string): Promise<{url: string}>` | Every view | A playable blob: URL of the free sample of a StillMade voice (a voice id from the generation models list), for a Block that declares audio.speech. StillMade reads it; the view has no network. |
41
+ | `allowSpending(request: {credits: number, label: string}): Promise<{allowed: boolean, credits: number}>` | Every view | From a person's press of a button that showed a total, ask StillMade once to approve it: the Step's later runs spend from it silently at StillMade's prices for 30 minutes. In a project Step only, never from Chat, up to VIEW_LIMITS.allowanceCredits. |
40
42
  | `asset(path: string): string` | Frame views | A blob: URL for one of the view's own asset files, for <img>, new FontFace() or <video>. |
41
43
  | `readonly live: {start(options: {camera?: boolean; microphone?: boolean; fps?: number; width?: number; sampleRate?: number}): Promise<{live: true; camera: boolean; microphone: boolean; fps: number; width: number; height: number; sampleRate: number}>; stop(): Promise<{live: false}>; status(): Promise<{live: boolean; [key: string]: unknown}>; onFrame(callback: (frame: ImageBitmap, info: {index: number; timestamp: number; width: number; height: number}) => void): () => void; onAudio(callback: (samples: Float32Array, info: {index: number; timestamp: number; sampleRate: number}) => void): () => void; onEnd(callback: (event: {reason: string}) => void): () => void}` | Every view | The camera, the microphone or both as they happen (declare camera.capture and microphone.capture). Nothing reaches the view until the person presses Start in StillMade, which then shows that the device is on and a Turn off button. Frames arrive as ImageBitmaps, closed when your callback returns, so draw or copy them there; a slow view gets fewer frames, never a backlog. Audio arrives as mono Float32Array chunks. Nothing is saved. In browsers and StillMade Desktop. |
42
44
  | `assetBytes(path: string): ArrayBuffer` | Frame views | A copy of one of the view's own asset files. |
45
+ | `loadCode(path: string): Promise<void>` | Frame views | Runs one of the view's own code files (a .js file in src/view-files/) once, when the view needs it, so a large interface starts with only the code its first screen uses. StillMade checks the file against its digest first. The file is one classic script; what it defines is the view's to use. |
43
46
 
44
47
  ### Generated interface
45
48
 
@@ -13,4 +13,4 @@ Standard custom view code (no `view.config.json` frame runtime) is checked befor
13
13
  - **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.
14
14
  - **Preview controls:** a button whose whole text is `Preview` shows the StillMade preview icon alone (an eye in viewfinder corners), with `Preview` as its accessible name; give it a `label` such as `Preview clip 2` when several sit together. Do not draw your own eye or play icon for a preview.
15
15
  - **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}`.
16
- - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 30 runs and 60 image previews per minute.
16
+ - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 120 runs and 240 image previews per minute.
package/docs/tools/llm.md CHANGED
@@ -16,9 +16,9 @@ Ask the assistant to return source files or one `.stillmade.json` package. Run t
16
16
 
17
17
  <!-- builder-contract:start (generated by npm run build:sdk; do not edit) -->
18
18
 
19
- ## Block builder contract (stillmade.block-builder@1.17.0)
19
+ ## Block builder contract (stillmade.block-builder@1.18.0)
20
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.
21
+ Contract `stillmade.block-builder@1.18.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
22
 
23
23
  ### Source of truth
24
24
 
@@ -87,7 +87,7 @@ These are StillMade's defaults: unless the person or the creator asked for somet
87
87
 
88
88
  ### Interface
89
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 after the inputs with the job's action row on it (`sm-actions` and `sm-status` inside `sm-stage`: the kit puts the status top-left and the actions top-right in the player's cream blocks, over a band above the media; the run button is always the rightmost, so a button that spends credits never moves and making again is pressed where it made the first time, with Download and other result actions to its left), 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; a stage shows what it holds in its middle as one group (a caption stays with its picture) and several results fill its width as one row, on a Block's screen and on the pages StillMade draws; 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 for 3 s the first time it comes into view, then 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.
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 after the inputs with the job's action row on it (`sm-actions` and `sm-status` inside `sm-stage`: the kit puts the status top-left and the actions top-right in the player's cream blocks, over a band above the media; the run button is always the rightmost, so a button that spends credits never moves and making again is pressed where it made the first time, with Download and other result actions to its left), 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, photo editor), declares `manifest.kind: "workspace"` and looks and works like that kind of app instead of a compact page, and StillMade's checks (admission, readiness, its own builder) then apply the app rules instead of the compact ones: no limit on controls in view, no Block kit page, its own layout, the craft's own words, and only its run button (`data-stillmade-action="run"`) held to naming what it makes: 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; a stage shows what it holds in its middle as one group (a caption stays with its picture) and several results fill its width as one row, on a Block's screen and on the pages StillMade draws; 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 for 3 s the first time it comes into view, then 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
91
 
92
92
  ### Shared editing
93
93
 
@@ -181,7 +181,7 @@ Every finished Block must support automatic remixing through the standard SDK co
181
181
 
182
182
  ### Package format
183
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.
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" (or "workspace" for an app a person works in, such as a CAD tool or photo editor),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
185
 
186
186
  ### Feature map
187
187
 
@@ -245,7 +245,7 @@ Write the description in third-grade language: one short sentence, ideally 8–1
245
245
 
246
246
  ### Compact interface
247
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, then the result's place with its actions on it (never a pinned bar), so `sm-stage` comes after the inputs. 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.
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, then the result's place with its actions on it (never a pinned bar), so `sm-stage` comes after the inputs. 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 is different: when the request is for software a person works in (an editor, a canvas, a 3D or CAD tool, a photo editor, a studio), set manifest.kind:"workspace", which makes it an app, and lay it 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
249
 
250
250
  ### Generated controls first
251
251
 
@@ -257,7 +257,7 @@ Optional custom view interfaces are supported, but generated manifest.ui control
257
257
 
258
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
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. Actions on the stage (owner, November 5, 2026): a job's `sm-actions` and `sm-status` go inside its `sm-stage`, and the kit lays them over it, the status top-left and the actions top-right, in the cream blocks of StillMade's player, with a band kept above the media so they never cover it; the media fills the rest of the stage. A button that spends credits never moves: the run button (`data-stillmade-action="run"`) is always the rightmost and stays in that spot when it makes again ("Make video", then "Make video again"), and what the result adds (Download, Share) goes to its left. Controls on media (StillMade's player, the full-screen square) show for 3 seconds the first time it comes into view, then only while it is hovered or tapped. StillMade's own Step pages do the same. An app that lays itself out keeps its own toolbar. 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 goes in `sm-status`, in a few words: top-left on the stage when the actions are there, otherwise beside the action it describes. 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, then the result's place with its actions on it (never a pinned bar), so `sm-stage` comes after the inputs. 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. The kit's `sm-stage` shows what it holds in its middle as one group (a caption stays with its picture), and several results in a stage fill its width as one row; the pages StillMade draws follow the same rule. 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.
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. Every Block: 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. 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. 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. A job (the default; manifest.kind "task" or none) also: 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. Actions on the stage (owner, November 5, 2026): a job's `sm-actions` and `sm-status` go inside its `sm-stage`, and the kit lays them over it, the status top-left and the actions top-right, in the cream blocks of StillMade's player, with a band kept above the media so they never cover it; the media fills the rest of the stage. A button that spends credits never moves: the run button (`data-stillmade-action="run"`) is always the rightmost and stays in that spot when it makes again ("Make video", then "Make video again"), and what the result adds (Download, Share) goes to its left. Controls on media (StillMade's player, the full-screen square) show for 3 seconds the first time it comes into view, then only while it is hovered or tapped. StillMade's own Step pages do the same. An app that lays itself out keeps its own toolbar. 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. Rare tools (update, schedule, history, reload) are named icon buttons beside the action, never sections under the work. 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 goes in `sm-status`, in a few words: top-left on the stage when the actions are there, otherwise beside the action it describes. 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, then the result's place with its actions on it (never a pinned bar), so `sm-stage` comes after the inputs. 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. The kit's `sm-stage` shows what it holds in its middle as one group (a caption stays with its picture), and several results in a stage fill its width as one row; the pages StillMade draws follow the same rule. 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`). An app (manifest.kind "workspace"; software a person works in, like a CAD tool or a photo editor) follows these instead of the job-only ones, and StillMade's checks hold it to them: An app (manifest.kind "workspace") is software a person works in for a while: a CAD or 3D tool, a photo editor, a timeline, a canvas, a studio. Its work surface (viewport, canvas, image, timeline) owns the page, laid out like the best apps of its kind, with the tool rail, panels, inspector, layers, timeline and menus the craft needs and as many controls as it needs. Never shrink it into a form of fields and one button. It lays itself out: `data-stillmade-layout="own"` on its outermost element, so StillMade never centers or zooms it and leaves Ctrl/Cmd +/- to it. Full-size panels, fixed toolbars and its own scroll, pan and zoom are fine. Work happens on the surface: select, drag, zoom and pan, keyboard shortcuts for the common tools, undo and redo. Speak the craft: tools and panels use the words people of that craft use (Exposure, Keyframe, Extrude, Layers, Mask), never a plain-language rewrite. A button that spends credits still names what it makes ("Generate fill"), carries `data-stillmade-action="run"` and shows no price; StillMade adds it. Order, not sparseness: tools grouped in panels that fold, the most used in view, rare ones in menus. No intros or paragraphs on the surface; a tool that needs explaining gets a tooltip or an (i). StillMade's appearance (Light, Dark and White) and the Block kit's controls (buttons, fields, sliders, `sm-choices` with thumbnails for presets) wherever they fit; the layout is the app's own. A look the person names wins. On a phone the work surface stays usable and the panels fold into sheets or one tool strip. What the person makes leaves the app as the Block's outputs (and, while they work, its shared state), so the next Step and the other people in the project get it. 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
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
262
 
263
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:`.
@@ -270,7 +270,7 @@ Standard custom view code (no `view.config.json` frame runtime) is checked befor
270
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
271
  - **Preview controls:** a button whose whole text is `Preview` shows the StillMade preview icon alone (an eye in viewfinder corners), with `Preview` as its accessible name; give it a `label` such as `Preview clip 2` when several sit together. Do not draw your own eye or play icon for a preview.
272
272
  - **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}`.
273
- - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 30 runs and 60 image previews per minute.
273
+ - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 120 runs and 240 image previews per minute.
274
274
  ## Shared project interfaces
275
275
  Every meaningful edit and generated result must be visible to collaborators.
276
276
  Prefer ordinary manifest.ui controls and declared outputs when those express
@@ -977,7 +977,7 @@ Standard custom view code (no `view.config.json` frame runtime) is checked befor
977
977
  - **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.
978
978
  - **Preview controls:** a button whose whole text is `Preview` shows the StillMade preview icon alone (an eye in viewfinder corners), with `Preview` as its accessible name; give it a `label` such as `Preview clip 2` when several sit together. Do not draw your own eye or play icon for a preview.
979
979
  - **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}`.
980
- - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 30 runs and 60 image previews per minute.
980
+ - **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 120 runs and 240 image previews per minute.
981
981
 
982
982
  <!-- view-rules:end -->
983
983
 
@@ -1256,10 +1256,25 @@ renders leave the previous controls intact. Text is always literal; scripts,
1256
1256
  inline handlers, links, file inputs, network attributes, and raw HTML are rejected.
1257
1257
 
1258
1258
 
1259
+ ### Spending approvals
1260
+
1261
+ A press of the Block's own button pays for one run's hosted calls at StillMade's
1262
+ price. A screen whose button starts many runs (a build that makes thirty pictures,
1263
+ one run each) asks once instead: from that press, call
1264
+ `await StillMade.allowSpending({credits, label})` with the total the button showed
1265
+ and its words. StillMade shows its own dialog; when the person allows it, that
1266
+ Step's later runs spend from the total silently, each call at StillMade's own
1267
+ price, for 30 minutes, and a call past what is left asks as usual. It resolves
1268
+ with `{allowed, credits}`. It works only from a person's click or key press in a
1269
+ project Step (never from Chat), up to 2,000 credits (`VIEW_LIMITS.allowanceCredits`).
1270
+
1259
1271
  ### Audio and video playback
1260
1272
 
1261
- `await StillMade.previewMedia(reference)` opens an authorized `audio` or `video`
1262
- reference as a temporary playback copy and returns `{kind, url, mimeType, bytes}`.
1273
+ `await StillMade.previewMedia(reference)` opens an authorized `image`, `audio` or `video`
1274
+ reference as a temporary copy and returns `{kind, url, mimeType, bytes}`. A picture comes back as the
1275
+ original file at full size (an animated GIF keeps moving); `previewImage` gives a small still instead.
1276
+ A frame view keeps up to `FRAME_VIEW_LIMITS.mediaPreviews` media previews open at once (a standard view two);
1277
+ release one with `StillMade.releaseMedia(url)` when it scrolls away or closes.
1263
1278
  Assign its URL to an existing `<audio controls>` or `<video controls>` element.
1264
1279
  The host requires an exact reference from this Block’s bound input, an explicitly
1265
1280
  selected project asset, permitted project context, or a host-recorded output.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stillmade-sdk",
3
- "version": "0.1.47",
3
+ "version": "0.1.49",
4
4
  "description": "StillMade Block SDK, CLI and examples",
5
5
  "license": "PolyForm-Shield-1.0.0",
6
6
  "type": "module",