stillmade-sdk 0.1.42 → 0.1.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/.stillmade/sdk-baseline.json +6 -8
  2. package/AGENTS.md +15 -10
  3. package/FRAME_VIEWS.md +113 -0
  4. package/INTERFACE_DESIGN.md +62 -22
  5. package/MOBILE_UI.md +4 -2
  6. package/MODULES.md +152 -6
  7. package/PASTE_TO_LLM.md +35 -25
  8. package/README.md +3 -2
  9. package/SDK.md +994 -550
  10. package/UI_AESTHETIC.md +22 -2
  11. package/blocks/generate/package.js +1 -0
  12. package/blocks/generate/versions/1.0.0/package.js +463 -0
  13. package/docs/build/audio-to-editor.md +36 -0
  14. package/docs/build/block-builder-contract.md +150 -0
  15. package/docs/build/comfyui.md +227 -0
  16. package/docs/build/concepts.md +40 -0
  17. package/docs/build/context.md +159 -0
  18. package/docs/build/cookbook.md +57 -0
  19. package/docs/build/custom-interface.md +617 -0
  20. package/docs/build/data-types.md +83 -0
  21. package/docs/build/frame-views.md +259 -0
  22. package/docs/build/generate-media.md +59 -0
  23. package/docs/build/github-applications.md +314 -0
  24. package/docs/build/hosted-images.md +214 -0
  25. package/docs/build/hosted-music.md +39 -0
  26. package/docs/build/hosted-speech.md +157 -0
  27. package/docs/build/hosted-text.md +246 -0
  28. package/docs/build/image-description.md +49 -0
  29. package/docs/build/interface-design.md +343 -0
  30. package/docs/build/javascript.md +63 -0
  31. package/docs/build/local-preview.md +61 -0
  32. package/docs/build/media-analysis.md +27 -0
  33. package/docs/build/media.md +400 -0
  34. package/docs/build/mobile-ui.md +106 -0
  35. package/docs/build/modules.md +487 -0
  36. package/docs/build/original-blocks.md +70 -0
  37. package/docs/build/outside-apis.md +128 -0
  38. package/docs/build/production-context-templates.md +12 -0
  39. package/docs/build/project-storage.md +94 -0
  40. package/docs/build/quickstart.md +48 -0
  41. package/docs/build/recipes.md +103 -0
  42. package/docs/build/script-handoff.md +45 -0
  43. package/docs/build/script-writer.md +34 -0
  44. package/docs/build/state.md +223 -0
  45. package/docs/build/testing.md +112 -0
  46. package/docs/build/timeline-edits.md +116 -0
  47. package/docs/build/timeline-proposals.md +76 -0
  48. package/docs/build/transcription.md +59 -0
  49. package/docs/build/triggers.md +59 -0
  50. package/docs/build/tutorial.md +52 -0
  51. package/docs/build/typescript.md +34 -0
  52. package/docs/build/video-generation.md +61 -0
  53. package/docs/build/web-reading.md +37 -0
  54. package/docs/build/with-your-ai.md +27 -0
  55. package/docs/changelog.md +9 -0
  56. package/docs/creators.md +21 -0
  57. package/docs/distribute/discovery.md +43 -0
  58. package/docs/distribute/imports.md +58 -0
  59. package/docs/distribute/publishing.md +29 -0
  60. package/docs/distribute/sdk-versions.md +36 -0
  61. package/docs/distribute/selling.md +14 -0
  62. package/docs/distribute/teams.md +60 -0
  63. package/docs/distribute/versioning.md +28 -0
  64. package/docs/examples.md +23 -0
  65. package/docs/gallery.md +26 -0
  66. package/docs/index.md +50 -0
  67. package/docs/native-mcp.md +307 -0
  68. package/docs/project-types/automation.md +43 -0
  69. package/docs/project-types/batches.md +50 -0
  70. package/docs/project-types/build.md +42 -0
  71. package/docs/project-types/completion.md +15 -0
  72. package/docs/project-types/conditions.md +21 -0
  73. package/docs/project-types/hosted-review.md +115 -0
  74. package/docs/project-types/onboarding.md +75 -0
  75. package/docs/project-types/running.md +309 -0
  76. package/docs/project-types/testing.md +72 -0
  77. package/docs/reference/application-artifacts.md +87 -0
  78. package/docs/reference/authoring-guide.md +134 -0
  79. package/docs/reference/browser-applications.md +267 -0
  80. package/docs/reference/capabilities.md +45 -0
  81. package/docs/reference/cli.md +71 -0
  82. package/docs/reference/connection-api.md +59 -0
  83. package/docs/reference/cooperative-jobs.md +13 -0
  84. package/docs/reference/desktop-capabilities.md +172 -0
  85. package/docs/reference/desktop-execution.md +77 -0
  86. package/docs/reference/errors.md +233 -0
  87. package/docs/reference/execution.md +800 -0
  88. package/docs/reference/first-party-blocks.md +40 -0
  89. package/docs/reference/generation-models.md +59 -0
  90. package/docs/reference/limits.md +306 -0
  91. package/docs/reference/manifest-kinds-controls.md +36 -0
  92. package/docs/reference/manifest.md +181 -0
  93. package/docs/reference/operations.md +36 -0
  94. package/docs/reference/package-jobs.md +35 -0
  95. package/docs/reference/parity.md +55 -0
  96. package/docs/reference/permissions.md +86 -0
  97. package/docs/reference/ports.md +26 -0
  98. package/docs/reference/types.md +268 -0
  99. package/docs/reference/universal-resolver.md +303 -0
  100. package/docs/reference/view-bridge.md +60 -0
  101. package/docs/reference/view-rules.md +15 -0
  102. package/docs/reference/workspace-sync.md +25 -0
  103. package/docs/sdk.md +34 -0
  104. package/docs/tools/block-remix.md +211 -0
  105. package/docs/tools/llm.md +2213 -0
  106. package/docs/tools/repository-adaptation.md +613 -0
  107. package/examples/check-clip.stillmade.json +4 -1
  108. package/examples/generate-video.stillmade.json +6 -2
  109. package/examples/research-topic.stillmade.json +5 -1
  110. package/examples/text-workflow.stillmade.json +3 -32
  111. package/examples/timeline-polish.stillmade.json +11 -3
  112. package/examples/transcribe-audio.stillmade.json +11 -3
  113. package/package.json +1 -1
  114. package/packages/block-cli/cli.js +13 -6
  115. package/packages/block-cli/dev-host/assets/host.js +76 -38
  116. package/packages/block-cli/dev-host/assets/index.css +1 -1
  117. package/packages/block-cli/format.js +1 -1
  118. package/packages/block-cli/templates.js +31 -13
  119. package/packages/block-cli/type-packages.js +119 -0
  120. package/packages/block-platform/block-readiness.js +5 -0
  121. package/packages/block-platform/canvas.js +2 -1
  122. package/packages/block-platform/catalog.js +27 -48
  123. package/packages/block-platform/native-host-inventory.js +0 -226
  124. package/packages/block-platform/plain-descriptions.js +1 -25
  125. package/packages/block-platform/project-type-starters.js +13 -53
  126. package/packages/block-platform/script-handoff.js +2 -4
  127. package/packages/block-platform/type-workspace.js +2 -0
  128. package/packages/block-sdk/appearance.js +3 -1
  129. package/packages/block-sdk/block-tier.js +1 -1
  130. package/packages/block-sdk/builder-contract.js +10 -8
  131. package/packages/block-sdk/capabilities.js +1 -1
  132. package/packages/block-sdk/contracts.js +6 -2
  133. package/packages/block-sdk/first-party-identities.js +1 -84
  134. package/packages/block-sdk/first-party-packages.js +3 -85
  135. package/packages/block-sdk/generation-catalog.js +15 -3
  136. package/packages/block-sdk/index.d.ts +2 -1
  137. package/packages/block-sdk/media-analysis-example.js +1 -1
  138. package/packages/block-sdk/mock-host.js +7 -1
  139. package/packages/block-sdk/module-host-calls.js +35 -10
  140. package/packages/block-sdk/module-hosted.js +15 -2
  141. package/packages/block-sdk/module-media-access.js +71 -0
  142. package/packages/block-sdk/module-node.js +1 -1
  143. package/packages/block-sdk/portable-media-actions.js +1 -1
  144. package/packages/block-sdk/runtime.js +1 -1
  145. package/packages/block-sdk/timeline-proposal-example.js +1 -1
  146. package/packages/block-sdk/transcription-example.js +1 -1
  147. package/packages/block-sdk/video-generation-example.js +1 -1
  148. package/packages/block-sdk/view-bridge.js +4 -1
  149. package/packages/block-sdk/view-design.js +17 -14
  150. package/packages/block-sdk/web-example.js +1 -1
  151. package/packages/design-system/block-kit.css +48 -12
  152. package/packages/design-system/block-kit.html +3 -2
  153. package/blocks/mcp-motion-planner/versions/1.1.0/package.js +0 -97
  154. package/blocks/motion-designer/versions/1.0.1/package.js +0 -173318
  155. package/blocks/motion-designer/versions/1.0.2/package.js +0 -173318
  156. package/blocks/motion-frame-review/versions/1.1.1/package.js +0 -465
  157. package/blocks/motion-frame-review/versions/1.1.2/package.snapshot.js +0 -572
  158. package/blocks/narration/package.js +0 -1
  159. package/blocks/narration/versions/1.0.0/package.js +0 -221
  160. package/blocks/narration/versions/1.0.1/package.js +0 -221
  161. package/blocks/native/sm.animated-scene/versions/2.0.0/package.js +0 -475
  162. package/blocks/native/sm.animated-scene/versions/2.1.0/package.js +0 -498
  163. package/blocks/native/sm.animated-scene/versions/2.2.0/package.js +0 -524
  164. package/blocks/native/sm.animated-scene/versions/2.3.0/package.js +0 -618
  165. package/blocks/native/sm.animated-scene/versions/2.3.1/package.js +0 -618
  166. package/blocks/native/sm.animated-scene/versions/2.3.2/package.js +0 -723
  167. package/blocks/native/sm.animated-scene/versions/2.3.3/package.js +0 -723
  168. package/blocks/native/sm.animated-scene/versions/2.3.4/package.js +0 -676
  169. package/blocks/native/sm.animated-scene/versions/2.3.5/package.js +0 -693
  170. package/blocks/native/sm.animated-scene/versions/2.3.6/package.js +0 -700
  171. package/blocks/native/sm.animated-scene/versions/2.3.7/package.js +0 -747
  172. package/blocks/native/sm.animated-scene/versions/2.3.8/package.js +0 -769
  173. package/blocks/native/sm.animated-scene/versions/2.3.9/package.js +0 -769
  174. package/blocks/native/sm.animated-scene-assistant/versions/1.0.0/package.js +0 -183
  175. package/blocks/native/sm.blueprint/versions/2.0.0/package.js +0 -358
  176. package/blocks/native/sm.blueprint/versions/2.1.0/package.js +0 -358
  177. package/blocks/native/sm.blueprint/versions/2.1.1/package.js +0 -358
  178. package/blocks/native/sm.blueprint/versions/2.1.2/package.js +0 -358
  179. package/blocks/native/sm.blueprint/versions/2.1.3/package.js +0 -381
  180. package/blocks/native/sm.blueprint/versions/2.1.4/package.js +0 -381
  181. package/blocks/native/sm.blueprint/versions/2.1.5/package.js +0 -339
  182. package/blocks/native/sm.blueprint/versions/2.1.6/package.js +0 -356
  183. package/blocks/native/sm.blueprint/versions/2.1.7/package.js +0 -363
  184. package/blocks/native/sm.blueprint/versions/2.1.8/package.js +0 -410
  185. package/blocks/native/sm.blueprint/versions/2.1.9/package.js +0 -422
  186. package/blocks/native/sm.blueprint-assistant/versions/1.0.0/package.js +0 -172
  187. package/blocks/native/sm.chat/versions/2.0.0/package.js +0 -144
  188. package/blocks/native/sm.chat/versions/2.0.1/package.js +0 -97
  189. package/blocks/native/sm.chat/versions/2.0.2/package.js +0 -100
  190. package/blocks/native/sm.chat/versions/2.0.3/package.js +0 -100
  191. package/blocks/native/sm.continue-if/versions/1.0.0/package.js +0 -168
  192. package/blocks/native/sm.describe-media/versions/2.0.0/package.js +0 -153
  193. package/blocks/native/sm.describe-media/versions/2.0.1/package.js +0 -156
  194. package/blocks/native/sm.describe-media/versions/2.1.0/package.js +0 -140
  195. package/blocks/native/sm.image-editor/versions/2.0.0/package.js +0 -183
  196. package/blocks/native/sm.image-editor/versions/2.0.1/package.js +0 -190
  197. package/blocks/native/sm.new-items/versions/1.0.0/package.js +0 -191
  198. package/blocks/native/sm.pick-photos/versions/1.0.0/package.js +0 -334
  199. package/blocks/native/sm.quality-audit/versions/1.0.0/package.js +0 -192
  200. package/blocks/native/sm.quality-audit/versions/1.0.1/package.js +0 -192
  201. package/blocks/native/sm.quality-audit/versions/1.0.2/package.js +0 -195
  202. package/blocks/native/sm.research/versions/2.0.0/package.js +0 -175
  203. package/blocks/native/sm.research/versions/2.0.1/package.js +0 -184
  204. package/blocks/native/sm.research/versions/2.0.2/package.js +0 -187
  205. package/blocks/native/sm.script/versions/2.0.0/package.js +0 -144
  206. package/blocks/native/sm.script/versions/2.1.0/package.js +0 -144
  207. package/blocks/native/sm.script/versions/2.1.1/package.js +0 -144
  208. package/blocks/native/sm.script/versions/2.1.2/package.js +0 -105
  209. package/blocks/native/sm.script/versions/2.1.3/package.js +0 -152
  210. package/blocks/native/sm.script/versions/2.1.4/package.js +0 -156
  211. package/blocks/native/sm.script/versions/2.1.5/package.js +0 -156
  212. package/blocks/native/sm.shot-plan/versions/2.0.0/package.js +0 -301
  213. package/blocks/native/sm.shot-plan/versions/2.1.0/package.js +0 -528
  214. package/blocks/native/sm.shot-plan/versions/2.1.1/package.js +0 -528
  215. package/blocks/native/sm.shot-plan/versions/2.1.2/package.js +0 -528
  216. package/blocks/native/sm.shot-plan/versions/2.1.3/package.js +0 -536
  217. package/blocks/native/sm.shot-plan/versions/2.2.0/package.js +0 -800
  218. package/blocks/native/sm.shot-plan/versions/2.2.1/package.js +0 -920
  219. package/blocks/native/sm.shot-plan-assistant/versions/1.0.0/package.js +0 -173
  220. package/blocks/native/sm.stock-search/versions/2.0.0/package.js +0 -174
  221. package/blocks/native/sm.thumbnail/versions/2.0.0/package.js +0 -137
  222. package/blocks/native/sm.thumbnail/versions/2.0.1/package.js +0 -140
  223. package/blocks/native/sm.transcribe/versions/2.0.0/package.js +0 -163
  224. package/blocks/native/sm.transcribe/versions/2.0.1/package.js +0 -168
  225. package/blocks/native/sm.whiteboard-assistant/versions/1.0.0/package.js +0 -175
  226. package/blocks/native/sm.youtube/versions/2.0.0/package.js +0 -217
  227. package/blocks/occasion-story/package.js +0 -1
  228. package/blocks/occasion-story/versions/1.0.0/package.js +0 -1658
  229. package/blocks/occasion-story/versions/1.0.1/package.js +0 -1699
  230. package/blocks/occasion-story/versions/1.0.2/package.js +0 -1682
  231. package/blocks/photo-reel-composer/versions/1.0.0/package.js +0 -3043
  232. package/blocks/photo-reel-composer/versions/1.0.1/package.js +0 -3026
  233. package/blocks/photo-reel-composer/versions/1.0.2/package.js +0 -4149
  234. package/blocks/product-ad-variants/versions/1.0.0/package.js +0 -2911
  235. package/blocks/product-launch-director/versions/1.0.0/package.js +0 -103
  236. package/blocks/release-visuals/package.js +0 -1
  237. package/blocks/release-visuals/versions/1.0.0/package.js +0 -1492
  238. package/blocks/release-visuals/versions/1.0.1/package.js +0 -1517
  239. package/blocks/release-visuals/versions/1.0.2/package.js +0 -1500
  240. package/blocks/screen-recorder/package.js +0 -1
  241. package/blocks/screen-recorder/versions/1.0.0/package.js +0 -223
  242. package/blocks/screen-recorder/versions/1.0.1/package.js +0 -223
  243. package/blocks/short-form-finish/package.js +0 -1
  244. package/blocks/short-form-finish/versions/1.0.0/package.js +0 -1043
  245. package/blocks/short-form-finish/versions/1.0.1/package.js +0 -1043
  246. package/blocks/short-form-finish/versions/1.0.2/package.js +0 -1043
  247. package/blocks/short-form-finish/versions/1.0.3/package.js +0 -1026
  248. package/examples/saas-product-launch-reviewed-source.stillmade.json +0 -383
  249. package/examples/saas-product-launch.stillmade.json +0 -304
  250. package/packages/block-platform/dealer-inventory-video-project-type.js +0 -38
  251. package/packages/block-platform/first-revenue-project-types.js +0 -46
  252. package/packages/block-platform/fixture-starters.js +0 -13
  253. package/packages/block-platform/launch-project-source.js +0 -10
  254. package/packages/block-platform/launch-project.js +0 -30
  255. package/packages/block-platform/motion-project-types.js +0 -55
  256. package/packages/block-platform/motion-starters.js +0 -14
  257. package/packages/block-platform/product-ad-variants-project-type.js +0 -30
  258. package/packages/block-platform/product-launch-updated.js +0 -90
  259. package/packages/block-platform/revenue-review-project-types.js +0 -36
  260. package/packages/block-platform/revenue-review-starters.js +0 -13
  261. package/packages/block-platform/script-writer.js +0 -29
  262. package/packages/block-platform/website-source-block.js +0 -14
@@ -1,58 +1,20 @@
1
- import {REVENUE_REVIEW_STARTERS} from './revenue-review-starters.js';
2
1
  import {CURRENT_BUILTIN_TYPES,WORKSPACE_BLOCKS} from './catalog.js';
3
2
 
4
- // The motion starters (Motion Graphics, Product Launch Video, Screenshot to Motion, Product Photo Ad) carry the
5
- // Motion Designer packages, 4.2 MB of source each. They live in motion-starters.js so this module, imported at
6
- // startup for the Empty starter, stays small (November 2, 2026: they were 8.5 MB of the 17 MB the studio parsed
7
- // before Home showed). newProjectTypeFromStarter takes them as its third argument; loadProjectTypeStarter loads
8
- // them when one is chosen; project-type-starters-full.js is the sync form for Node.
9
- export const MOTION_STARTER_IDS=Object.freeze(['motion-graphics','saas-launch','screenshot-motion','product-photo-ad']);
10
- // The fixture Types (fixture-starters.js: the revenue-review Types, Product Ad Variants, the photo reels) embed exact
11
- // Block releases too; they resolve the same way, by id, for the acceptance drivers.
12
- export const FIXTURE_STARTER_IDS=Object.freeze([...REVENUE_REVIEW_STARTERS.map(item=>item.id),'product-ad-variants','property-listing-reel','product-promo-reel']);
13
- export const ON_DEMAND_STARTER_IDS=Object.freeze([...MOTION_STARTER_IDS,...FIXTURE_STARTER_IDS]);
3
+ // November 4, 2026 (owner): the Motion starters (Motion Graphics, Product Launch Video, Screenshot to Motion, Product
4
+ // Photo Ad) and their Motion Designer packages were deleted. No starter loads Blocks on demand any more; the names
5
+ // stay so a later on-demand starter plugs in the same way.
6
+ export const MOTION_STARTER_IDS=Object.freeze([]);
7
+ export const ON_DEMAND_STARTER_IDS=Object.freeze([...MOTION_STARTER_IDS]);
14
8
 
15
9
  // These are editable starting configurations of the existing production Blocks,
16
10
  // not separate generators or new marketplace identities.
17
- // October 20, 2026 (owner): the revenue-review, ad-variant and photo-reel starters are off the
18
- // Create page; their definitions stay as acceptance fixtures, so newProjectTypeFromStarter still
19
- // resolves them by id for those drivers and for the creator experiments.
11
+ // November 4, 2026 (owner): the Guided-style video starters, the revenue-review, ad-variant and photo-reel
12
+ // fixtures and the Blocks they used (Script Writer, Shots, Blueprint and the rest) were deleted.
20
13
  export const PROJECT_TYPE_STARTERS=Object.freeze([
21
- {id:'faceless-youtube',name:'Faceless YouTube',description:'Write a narrated story, plan the visuals, and assemble a complete video.',blocks:['script','voiceover','shot-plan','blueprint','pipeline','editor'],brief:'Create a faceless YouTube video. Start with a clear hook, develop the story with concrete visual beats, and end with a useful conclusion. Ask for the topic, audience, target duration and sources. Do not invent factual claims.'},
22
- {id:'short-form',name:'Short-form vertical',description:'Build a concise story with a strong opening and visuals for a vertical screen.',blocks:['script','voiceover','shot-plan','pipeline','editor'],brief:'Create a short-form vertical video. Ask for the subject and intended platform. Keep narration concise, place the hook immediately, and plan readable 9:16 compositions with room for captions. Confirm the target length before writing.'},
23
- {id:'ai-commercial',name:'AI commercial',description:'Develop a campaign idea, maintain its visual direction, and edit the final spot.',blocks:['script','voiceover','shot-plan','blueprint','pipeline','editor'],brief:'Develop an AI commercial from the supplied campaign brief. Establish the audience, promise, tone, product proof and call to action. Ask for brand assets and target duration. Use only supported product claims; preserve a consistent visual direction.'},
24
- {id:'anime-episode',name:'Anime episode',description:'Develop the episode, cast and locations, then produce its shots and final cut.',blocks:['script','voiceover','blueprint','shot-plan','pipeline','editor'],brief:'Develop an anime episode with clear scene progression and character motivations. Ask for the premise, cast, intended duration and visual references. Prepare dialogue that can be reviewed for voice production. Maintain existing character and location references in Blueprint and Canvas.'},
25
- {id:'product-ad',name:'Product ad',description:'Show a product’s value with a focused script, visual proof and a clear call to action.',blocks:['script','voiceover','shot-plan','blueprint','pipeline','editor'],brief:'Create a product advertisement using supplied product facts and assets. Ask for the audience, channel, duration and call to action. Lead with a concrete customer problem, demonstrate verified features and benefits, and avoid invented testimonials, statistics or prices.'},
26
- {id:'music-video',name:'Music video',description:'Bring your track, plan a visual sequence, and cut the images and clips to the music.',blocks:['blueprint','shot-plan','pipeline','editor']},
27
- {id:'documentary',name:'Documentary',description:'Research and write the story, organize its visual references, and build the film.',blocks:['script','voiceover','blueprint','shot-plan','pipeline','editor'],brief:'Develop a documentary from the supplied subject and sources. Separate verified facts, attributed statements and open questions. Ask about the intended audience, length and available footage. Structure an evidence-based narrative and identify the visuals required for each section.'},
28
14
  {id:'empty',name:'Empty',description:'Start with protected Chat and add the Blocks your workflow needs.',blocks:[]},
29
- {id:'motion-graphics',name:'Motion Graphics',description:'Direct an idea, approve styleframes, and build editable coded motion scenes and a finished export.'},
30
- {id:'saas-launch',name:'Product Launch Video',description:'Turn product evidence and real UI into approved styleframes, an editable coded launch video, and a finished export.'},
31
- {id:'screenshot-motion',name:'Screenshot to Motion',description:'Turn exact product screenshots into an editable motion walkthrough without inventing product behavior.'},
32
- {id:'product-photo-ad',name:'Product Photo Ad',description:'Turn rights-cleared product photos and verified offer facts into an editable short advertisement.'},
33
15
  ]);
34
16
 
35
- // Business hypotheses stay separate from the reusable workflow definitions.
36
- // Selecting one prepares a private experiment draft; it never publishes,
37
- // spends credits, or upgrades its demand evidence label.
38
- export const REVENUE_LOOP_CANDIDATES=Object.freeze([
39
- ['captioned-business-clip','Service owners and consultants with an unposted recording','Finish a selected recording with accurate captions and personal first-project help.'],
40
- ['customer-story-clip','Businesses with an authorized customer interview','Preserve a real customer quote and finish a captioned excerpt.'],
41
- ['product-demonstration-clip','Small brands with real demonstration footage','Show the actual product in use with approved copy and a clear next step.'],
42
- ['farewell-story','Teams with already-collected farewell messages','Assemble supplied memories and messages into a reviewed farewell video.'],
43
- ['birthday-story','Gift buyers with collected photos and messages','Make a personal birthday story from their supplied memories.'],
44
- ['anniversary-story','Couples with supplied photos and messages','Arrange real memories and exact dedications into a short anniversary story.'],
45
- ['single-release-teaser','Artists with finished audio and cover artwork','Finish a short release visual using the original track and artwork.'],
46
- ['lyric-excerpt','Artists with approved lyrics and phrase timings','Create a short lyric visual with the exact supplied song and text.'],
47
- ].map(([id,buyer,job],index)=>{
48
- const starter=REVENUE_REVIEW_STARTERS.find(item=>item.id===id);
49
- return {id,starterId:id,rank:index+1,name:starter.name,buyer,job,promise:starter.description,evidence:'hypothesis',
50
- hypothesis:'A reviewed output and bounded first-project help can earn a genuine external payment. Demand and acquisition cost remain unverified.',
51
- primaryMetric:'Paid and delivered customer orders',
52
- decisionRule:'Approve the representative result and verify payment and export before acquisition. Test one offer within the agreed budget. Gift and music purchases need an explicit decision on one-project versus recurring payment.'};
53
- }));
54
-
55
- // `onDemand` holds the loaded definitions of motion and fixture starters by id (MOTION_STARTERS, FIXTURE_STARTERS).
17
+ // `onDemand` holds the loaded definitions of on-demand starters by id.
56
18
  export function newProjectTypeFromStarter(starter,id,onDemand=null){
57
19
  const selected=PROJECT_TYPE_STARTERS.find(item=>item.id===starter);
58
20
  if(ON_DEMAND_STARTER_IDS.includes(starter)){
@@ -61,23 +23,21 @@ export function newProjectTypeFromStarter(starter,id,onDemand=null){
61
23
  return {...structuredClone(definition),id};
62
24
  }
63
25
  if(!selected){
64
- const base=structuredClone(CURRENT_BUILTIN_TYPES.find(type=>type.legacyMode==='guided')||CURRENT_BUILTIN_TYPES[0]);
26
+ const base=structuredClone(CURRENT_BUILTIN_TYPES.find(type=>type.legacyMode==='blank')||CURRENT_BUILTIN_TYPES[0]);
65
27
  return {...base,id,name:'Untitled Project Type',version:'0.1.0',description:'A production workflow you can customize.'};
66
28
  }
67
29
  const stages=selected.blocks.map(key=>{
68
30
  const block=WORKSPACE_BLOCKS.find(item=>item.manifest.id==='sm.'+key);
69
31
  if(!block)throw Error('The '+key+' production Block is unavailable.');
70
- return {id:key,blockId:block.manifest.id,version:block.manifest.version,label:block.manifest.name,...key==='script'&&selected.brief?{config:{input:{schemaVersion:1,topic:selected.brief}}}:{}};
32
+ return {id:key,blockId:block.manifest.id,version:block.manifest.version,label:block.manifest.name};
71
33
  });
72
34
  return {schemaVersion:1,id,version:'0.1.0',name:selected.name,description:selected.description,license:'MIT',startingChat:'native',stages,
73
- ...(selected.id!=='empty'?{defaults:{aspectRatio:['short-form','product-ad'].includes(selected.id)?'9:16':'16:9'},onboarding:[{id:'audience',label:'Who is this for?',type:'text'},{id:'tone',label:'What tone fits?',type:'select',options:['Clear and direct','Playful','Cinematic','Documentary']}]}:{}),
74
- connections:stages.some(step=>step.id==='script')&&stages.some(step=>step.id==='voiceover')?[{from:{stage:'script',port:'output'},to:{stage:'voiceover',port:'input'}}]:[],
35
+ ...(selected.id!=='empty'?{defaults:{aspectRatio:'16:9'},onboarding:[{id:'audience',label:'Who is this for?',type:'text'},{id:'tone',label:'What tone fits?',type:'select',options:['Clear and direct','Playful','Cinematic','Documentary']}]}:{}),
36
+ connections:[],
75
37
  summary:{text:selected.description,origin:'llm',generation:'build-time',generator:'Codex',status:'ready'}};
76
38
  }
77
39
 
78
- // Resolve any starter, loading the motion starters' packages first when one of them is chosen.
40
+ // Resolve any starter.
79
41
  export async function loadProjectTypeStarter(starter,id){
80
- if(MOTION_STARTER_IDS.includes(starter))return newProjectTypeFromStarter(starter,id,(await import('./motion-starters.js')).MOTION_STARTERS);
81
- if(FIXTURE_STARTER_IDS.includes(starter))return newProjectTypeFromStarter(starter,id,(await import('./fixture-starters.js')).FIXTURE_STARTERS);
82
42
  return newProjectTypeFromStarter(starter,id);
83
43
  }
@@ -1,5 +1,4 @@
1
1
  import {assert} from '../block-sdk/contracts.js';
2
- import {isScriptWriterHandoff} from './script-writer.js';
3
2
  import {packageForStage} from './stage-package.js';
4
3
 
5
4
  const SDK_SCRIPT_RUNTIMES = new Set(['recipe', 'javascript', 'capability']);
@@ -14,14 +13,13 @@ export function resolveScriptHandoff(from, to, edge, pinnedManifest) {
14
13
  to.blockId !== 'sm.voiceover' || !VOICEOVER_VERSIONS.has(to.version) ||
15
14
  edge.to.port !== 'input' || Object.hasOwn(edge, 'adapter')) return null;
16
15
 
17
- const builtin = isScriptWriterHandoff(from, to, edge);
18
- if (!builtin && (pinnedManifest?.id !== from.blockId || pinnedManifest.version !== from.version ||
16
+ if ((pinnedManifest?.id !== from.blockId || pinnedManifest.version !== from.version ||
19
17
  !SDK_SCRIPT_RUNTIMES.has(pinnedManifest.runtime) ||
20
18
  !Object.hasOwn(pinnedManifest.outputs || {}, edge.from.port) ||
21
19
  pinnedManifest.outputs[edge.from.port]?.type !== 'script')) return null;
22
20
 
23
21
  return {
24
- kind: builtin ? 'script-writer' : 'sdk',
22
+ kind: 'sdk',
25
23
  sourceStageId: from.id, sourcePort: edge.from.port,
26
24
  targetStageId: to.id, targetPort: edge.to.port,
27
25
  blockId: from.blockId, version: from.version,
@@ -80,6 +80,8 @@ export async function compileTypeWorkspace(type,{experimentalBrowser,experimenta
80
80
  });
81
81
  return { definition, digest: await digest(definition), canvas: { nodes, edges }, activeStage: definition.stages.find(stage=>stage.enabled !== false)?.id || null };
82
82
  }
83
+ // A test fixture many tests assert on (its stage ids and order), not a model Type: its Export video Step beside the
84
+ // Editor breaks the builder contract ("One job per Step"). The create-type scaffold (TYPE_SCAFFOLD) is the model.
83
85
  export const RECIPE_TYPE_EXAMPLE = {
84
86
  schemaVersion: 1, id: 'example.text-workflow', version: '1.0.0', name: 'Text workflow',
85
87
  description: 'Clean text through two connected blocks, then use the shared editor to assemble media.',
@@ -10,7 +10,9 @@ export const APPEARANCE_TOKENS = Object.freeze([
10
10
  '--signal','--signal-dim','--signal-glow','--accent','--accent-hover','--accent-dim','--accent-glow','--accent-contrast',
11
11
  '--bg-primary','--bg-secondary','--bg-tertiary','--bg-elevated','--bg-hover','--border','--border-light',
12
12
  '--text-primary','--text-secondary','--text-muted','--success','--warning','--danger','--shadow','--shadow-lg','--shadow-1','--shadow-2',
13
- '--editor-standout-bg','--editor-standout-fg'
13
+ '--editor-standout-bg','--editor-standout-fg',
14
+ // The Block kit's see-through page background, for words over a photo (block-kit.css, November 3, 2026).
15
+ '--scrim'
14
16
  ]);
15
17
  const tokens = new Set(APPEARANCE_TOKENS);
16
18
  const colorProperty = /^(?:color|background(?:-color|-image)?|(?:border|outline)(?:-(?:top|right|bottom|left|inline|block|inline-start|inline-end|block-start|block-end))?(?:-color)?|(?:box|text)-shadow|fill|stroke|caret-color|accent-color|text-decoration(?:-color)?|column-rule(?:-color)?|-webkit-text-fill-color)$/;
@@ -7,7 +7,7 @@
7
7
  // extension an Editor tool (editor-extension)
8
8
  // glue the parts any project may use between Steps (When, Only new items, AI write, Import media)
9
9
  export const BLOCK_TIERS=Object.freeze(['app','job','ingredient','extension','glue']);
10
- const GLUE=new Set(['sm.when','sm.new-items','sm.continue-if','sm.connect-app','sm.ai-write','sm.ai-decide','sm.write-summary','sm.import-media']);
10
+ const GLUE=new Set(['sm.when','sm.connect-app','sm.ai-write','sm.ai-decide','sm.write-summary','sm.import-media']);
11
11
  const hostedOperations=manifest=>(manifest?.permissions?.capabilities||[]).filter(name=>name!=='connection.execute'&&name!=='comfyui.execute');
12
12
 
13
13
  /** The tier of a Block, from its manifest. */
@@ -47,9 +47,11 @@ const SECTIONS=Object.freeze([
47
47
  // on module Blocks and refused a job that needs one).
48
48
  rule('runtime-choice','block',null,
49
49
  'Pick the runtime from the job. No hosted AI: a recipe or isolated `javascript`, both able to run unattended. One text, script or brief prompt returning text, script or JSON: a `capability` Block. Hosted operations on other values (a transcript, media, structured data), several hosted operations in a row, or a result shaped in code: one `module` Block, `module/main.js` calling `stillmade.hosted.run(operation, request)` for each operation in `permissions.capabilities` (MODULES.md), never one Block per operation; it runs in the person\'s browser while StillMade is open. Build a new Block in a folder with `stillmade-block create`, `test`, `ready` and `pack`, and import the file `pack` writes (a module Block writes `<id>-<version>.stillmade-module.json`) through Create, then Import Block. A `.stillmade-block` archive is only for adapting a GitHub repository.'),
50
+ // November 3, 2026 (owner, "Yes, one page"): a guided job is one Block on one page, its cheap check before the
51
+ // expensive run on that page too; a workspace (CAD, the Editor) stays its own Step, and using what it made is the next.
50
52
  rule('block-scope','both',
51
- 'One Step per job: start one only where a person works with the result, where finding new items turns into handling each, or where the result leaves StillMade; all else is a part.',
52
- 'A Step is one job: the work between two moments where a person or the outside world must see the result. Start a new Step only at one of three points; everything else is a part of the neighboring Step.\n\n1. **A person works with the result**: reads, edits, picks or approves it (a workspace Block such as a script, a Canvas or an Editor, or an approval). So Bible, Canvas and Editor are separate Steps after Chat.\n2. **Finding new things turns into handling each one**: the trigger (a schedule or an app event), the app read, its filter and "only new" are the first job; what is done with each item is the next job. A trigger is a setting of the first job, never a Step of its own.\n3. **The result leaves StillMade**: it is sent, posted, published, paid or written into another app. Consecutive writes to the same app, with the glue that shapes data for them, are one job.\n\nIn a production the Steps are the workspaces a person opens: work feeding the next workspace is its part, else it joins the one before. A Block with its own interface (a workspace, editor or picker) is always its own Step; two such Steps are never one. A video ends in the Editor (1.0), which has captions and export built in, so Export video is never a Step beside it.\n\nA part that only runs in the desktop app cannot share a Step with cloud parts. Before adding a Step, name which of the three reasons makes it separate; if none applies, make it a part of a neighbor. A Step nobody would ever open must not exist on its own. Prefer an app task\'s own filter (a Gmail query such as `has:attachment filename:png`) to an AI check, and use AI decide only for judgement.\n\nBuild one Block for a job one Block can do: a module Block calls every hosted operation the job needs (it transcribes a sermon, then finds the verses said in it), a JavaScript Block does several local transforms, a workspace owns its whole document. A Block being built never takes an ingredient\'s result only so the ingredient can be its own part or Step (Transcribe in front of a verse finder): it calls that operation itself. Parts in one Step (`groups` in the Project Type) are for jobs made of Blocks that already exist and app tasks, each keeping its own permission, approval and credit rules. Never invent a combined runtime.\n\nExamples: "every new email with an image becomes a generated image" is two Steps, "Gmail · New emails with images" (When, fetch with an image filter, only new) and "Image from email" (AI write, Generate image); listing When, Fetch, Only new, AI decide, Continue only if, AI write and Generate image as seven Steps splits one job into its parts. "A new Meta lead goes to HubSpot" is three Steps: "Meta Ads · New leads", "Qualify lead" and "HubSpot · Add contact".'),
53
+ 'One Step per job: a guided job is one Block whose page holds the inputs, a cheap check and the result. New Steps only for a workspace (CAD, Editor; using its work is the next Step), a one-time setup, a later render, a hand-off, handling each new item, or leaving StillMade.',
54
+ 'A Step is one job: the work between two moments where a person or the outside world must see the result. A guided job, one the person fills in and then gets a result from, is one Step with one Block whose own screen holds everything: the inputs and settings, then a cheap check before the expensive run (the script and a voice sample before the video), then the result. On a wide screen the inputs sit on the left and the result\'s place on the right; on a phone they stack, the result\'s place under the button. A cheap check or a review the person does on that screen never starts a new Step. Start a new Step only at one of these points; everything else is a part of the neighboring Step.\n\n1. **A person works with the result**: in a workspace they spend a while in (the Editor, a storyboard, a Canvas, a CAD or drawing tool), or someone else approves or takes it over (a hand-off). Turning what a workspace made into something else is the next Step: a CAD tool is its own Step, and the Block that takes pictures of its model and makes a video is the next one. So Bible, Canvas and Editor are separate Steps after Chat.\n2. **Finding new things turns into handling each one**: the trigger (a schedule or an app event), the app read, its filter and "only new" are the first job; what is done with each item is the next job. A trigger is a setting of the first job, never a Step of its own.\n3. **The result leaves StillMade**: it is sent, posted, published, paid or written into another app. Consecutive writes to the same app, with the glue that shapes data for them, are one job.\n4. **Setup or a wait**: a one-time setup that is not part of each job (a brand kit, capturing a voice or a likeness), or a render that takes minutes and is delivered later.\n\nIn a production the Steps are the workspaces a person opens: work feeding the next workspace is its part, else it joins the one before. A Block with its own interface (a workspace, editor or picker) is always its own Step; two such Steps are never one. A Type whose own Block renders the finished video (`timeline.render`) needs no Editor Step; the Editor (1.0, captions and export built in) is a Step only when the person edits the cut by hand, and Export video is never a Step beside it.\n\nA part that only runs in the desktop app cannot share a Step with cloud parts. Before adding a Step, name which of these reasons makes it separate; if none applies, make it a part of a neighbor. A Step nobody would ever open must not exist on its own. Prefer an app task\'s own filter (a Gmail query such as `has:attachment filename:png`) to an AI check, and use AI decide only for judgement.\n\nBuild one Block for a job one Block can do: a module Block calls every hosted operation the job needs (it transcribes a sermon, then finds the verses said in it), a JavaScript Block does several local transforms, a workspace owns its whole document. A Block being built never takes an ingredient\'s result only so the ingredient can be its own part or Step (Transcribe in front of a verse finder): it calls that operation itself. Parts in one Step (`groups` in the Project Type) are for jobs made of Blocks that already exist and app tasks, each keeping its own permission, approval and credit rules. Never invent a combined runtime.\n\nExamples: "a listing video from my photos and the home\'s facts" is one Step: one Block whose page takes the photos and facts, shows the script and a voice sample to check, then makes the video. "every new email with an image becomes a generated image" is two Steps, "Gmail · New emails with images" (When, fetch with an image filter, only new) and "Image from email" (AI write, Generate image); listing When, Fetch, Only new, AI decide, Continue only if, AI write and Generate image as seven Steps splits one job into its parts. "A new Meta lead goes to HubSpot" is three Steps: "Meta Ads · New leads", "Qualify lead" and "HubSpot · Add contact".'),
53
55
  rule('capability-contract','block',
54
56
  'Define the contract first: typed inputs (optional with safe defaults where possible), outputs, state, permissions, UI, failures and cancellation. Keep the interface small; keep the job whole.',
55
57
  'Translate the request into an explicit capability contract before implementation: what the Block does; each input with its type, meaning and whether it is required (prefer optional settings with safe defaults over extra required questions); each output with its type and meaning; persistent state; project context it reads; permissions; UI; long-running behavior; expected failures; cancellation; determinism; and whether it needs hosted models, media, desktop tools or shared editing. Keep the public interface as small and general as possible (few required inputs, one primary output) and keep implementation details out of it; small means a small interface, never a fragment of a job. A Block that generates images, video or speech declares only its prompt and output: every Step that runs it gets the host’s generation settings (model, quality, length and resolution, voice, speed, how many), saved with the Step, changed on its page or by Chat, and shown with their cost before each run.'),
@@ -71,7 +73,7 @@ const SECTIONS=Object.freeze([
71
73
  // November 2, 2026 (owner): a sermon Block's video input read "Video"; it should read "Drop your sermon video here".
72
74
  rule('input-words','block',
73
75
  'Name each input in the job\'s words (`sermonVideo`, not `video`): StillMade words its drop area from the name.',
74
- 'Name each input for what the person brings, in the job\'s words: `sermonVideo` for a sermon clip Block, `listingPhotos` for a listing, `lessonSlides` for a lesson podcast. StillMade titles the input from that name ("Sermon video") and words its drop area from it ("Drop your sermon video here", the formats, "Choose a video"), in StillMade and the SDK preview, so never draw your own upload box for an input. Keep a bare type name (`video`, `file`) only when any file of that kind does the job. The `description` says what makes a good one.'),
76
+ 'Name each input for what the person brings, in the job\'s words: `sermonVideo` for a sermon clip Block, `listingPhotos` for a listing, `lessonSlides` for a lesson podcast. StillMade titles the input from that name ("Sermon video") and words its drop area from it ("Drop your sermon video here", the formats, "Choose a video"), in StillMade and the SDK preview, so never draw your own upload box or `<input type="file">`: a Block\'s screen shows the files as tiles and adds more with `<button class="sm-add" data-stillmade-add="listingPhotos" aria-label="Add photos">`, which opens StillMade\'s picker and upload, and a list\'s tiles drop or reorder a file with `data-stillmade-remove="listingPhotos" data-index="2"` and `data-stillmade-move="listingPhotos" data-index="2" data-to="1"` buttons; the files arrive through `onInput`. Keep a bare type name (`video`, `file`) only when any file of that kind does the job. The `description` says what makes a good one.'),
75
77
  // November 2, 2026 (owner): every screen speaks the job, as a product made only for it would, and stays quiet. The same
76
78
  // day (owner, of four tiles each saying "Only on the device it was added from"): "if it's a notification make it a
77
79
  // notification": state is one StillMade notification or nothing, never a sentence on the page or one per item.
@@ -84,17 +86,17 @@ const SECTIONS=Object.freeze([
84
86
  // pictures its full-screen button; a video without controls stays the Block's own. Also that day (owner: "there is no
85
87
  // separation and I cannot tell what is going on"): a pick is the kit's ink fill, and media that cannot show is a quiet tile.
86
88
  rule('ui','block',
87
- 'Prefer `manifest.ui`; custom views use the Block kit (`sm-*`) unless a look is named. Pictures over words: preview each option before choosing; few words, fold the rest; buttons carry `data-stillmade-action`; the start button names the action (never "Run" or "Make it") and shows no price (StillMade adds it). A job fits one screen unscrolled and a 320px phone; an app (editor, configurator, CAD tool) works like that app: full canvas, panels, tools, undo, never a form. An app puts `data-stillmade-layout="own"` on its root: StillMade never centers or zooms it. `<video controls>` gets StillMade\'s player; leave out `controls` when the Block drives playback. A pick keeps the kit\'s ink fill; media that cannot show is a wordless `sm-media-off` tile.',
88
- 'If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, picks as pictures in `sm-choices`, the picked one in the kit\'s ink fill (never restyled to an outline, a tint or the page\'s background) so it reads at a glance, one solid `sm-primary` naming the action in the job\'s words ("Plan the tour", "Generate voiceover"; never "Run" or "Make it") and no price (StillMade\'s run button adds it), extras in a closed `sm-more`; a look the person names wins. Fit one laptop screen without scrolling: label beside control, status in a few words, files as tiles, rare tools as icons, (i) on unclear controls. These compact rules are for a job or an ingredient. Size the view to the Block\'s tier (block-tier.js): an app, a workspace a person works in for a while (video editor, 3D configurator, CAD or drawing tool), looks and works like that kind of app instead of a compact page: its canvas or stage fills the view, with the panels, toolbars, inspector or timeline such an app has, direct manipulation (select, drag, zoom), keyboard shortcuts and undo; it keeps the host appearance and Block kit controls, its panels may scroll, and on a phone its tools fold into sheets while the canvas stays usable. Never shrink an app into a form of fields and one button. StillMade centers a standard view narrower than its Step and zooms a view to fill its space; an app that lays itself out (full-size canvas or WebGL, its own scroll, pan or zoom, its own Ctrl/Cmd +/- keys) puts `data-stillmade-layout="own"` on its outermost element, and StillMade then never centers, zooms or takes those keys. StillMade draws one player for every `<video controls>` (play, timeline, time, sound and full screen, shown while the pointer is on the video, with keys and touch) and a full-screen button on result pictures in `sm-stage` or `sm-gallery` and on any element marked `data-stillmade-expand` (the same value makes one set of pictures, `off` removes it); `data-stillmade-player="native"` keeps the browser\'s controls, and a `<video>` without `controls` is the Block\'s own and is never touched. A picture or clip that cannot show here (kept on another device, or it did not open) keeps its place as a quiet `sm-media-off` tile in its own shape with the `image-off` glyph and the reason in its label, never words in its place. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Mark each button `data-stillmade-action="what it does"` 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 (result first, action pinned), 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.'),
89
+ 'Prefer `manifest.ui`; custom views use the Block kit (`sm-*`) unless a look is named. Pictures over words: preview each option before choosing; few words, fold the rest; buttons carry `data-stillmade-action`; the start button names the action (never "Run" or "Make it") and shows no price (StillMade adds it). A job fits one screen unscrolled and a 320px phone; an app (editor, configurator, CAD tool) works like that app: full canvas, panels, tools, undo, never a form. An app puts `data-stillmade-layout="own"` on its root: StillMade never centers or zooms it. `<video controls>` gets StillMade\'s player; leave out `controls` when the Block drives playback. A pick keeps the kit\'s ink fill; media that cannot show is a wordless `sm-media-off` tile. Every part shows at once: the result\'s place is drawn before the first run in its own shape and size, so nothing pops in; on wide screens inputs sit left and the result right; on phones they stack, the result under the button.',
90
+ 'If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, drawn before the first run in the shape and size of what is coming (`data-ratio="9:16"` for a vertical clip, lines for text, a player bar for sound) so every part of the screen is in place at once and the result fills a place the person already sees instead of popping in somewhere unexpected; StillMade draws it the same way on the Steps it draws; on a wide screen what goes in sits on the left and the result on the right, side by side, and they stack only on narrow screens and phones, the result\'s place under the button, picks as pictures in `sm-choices`, the picked one in the kit\'s ink fill (never restyled to an outline, a tint or the page\'s background) so it reads at a glance, one solid `sm-primary` naming the action in the job\'s words ("Plan the tour", "Generate voiceover"; never "Run" or "Make it") and no price (StillMade\'s run button adds it), extras in a closed `sm-more`; a look the person names wins. Fit one laptop screen without scrolling: label beside control, status in a few words, files as tiles, rare tools as icons, (i) on unclear controls. These compact rules are for a job or an ingredient. Size the view to the Block\'s tier (block-tier.js): an app, a workspace a person works in for a while (video editor, 3D configurator, CAD or drawing tool), looks and works like that kind of app instead of a compact page: its canvas or stage fills the view, with the panels, toolbars, inspector or timeline such an app has, direct manipulation (select, drag, zoom), keyboard shortcuts and undo; it keeps the host appearance and Block kit controls, its panels may scroll, and on a phone its tools fold into sheets while the canvas stays usable. Never shrink an app into a form of fields and one button. StillMade centers a standard view narrower than its Step and zooms a view to fill its space; an app that lays itself out (full-size canvas or WebGL, its own scroll, pan or zoom, its own Ctrl/Cmd +/- keys) puts `data-stillmade-layout="own"` on its outermost element, and StillMade then never centers, zooms or takes those keys. StillMade draws one player for every `<video controls>` (play, timeline, time, sound and full screen, shown while the pointer is on the video, with keys and touch) and a full-screen button on result pictures in `sm-stage` or `sm-gallery` and on any element marked `data-stillmade-expand` (the same value makes one set of pictures, `off` removes it); `data-stillmade-player="native"` keeps the browser\'s controls, and a `<video>` without `controls` is the Block\'s own and is never touched. A picture or clip that cannot show here (kept on another device, or it did not open) keeps its place as a quiet `sm-media-off` tile in its own shape with the `image-off` glyph and the reason in its label, never words in its place. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Mark each button `data-stillmade-action` (`run` on the one that runs the Block, which the device checks press once; a page that checks first keeps `run` on that button for both presses; what it does on the others, `"play sample"`) and label each field: Chat presses and fills them. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Show no raw JSON, internal IDs, filesystem details, provider terms or developer settings unless the Block is a developer tool. When a person chooses among options they need to hear or see (voices, music, fonts, looks, templates, images), let them preview each option before choosing: a sample or thumbnail from the Block\'s own files or the host\'s free samples, never a paid generation. Skip previews only when there is nothing to preview. Choices StillMade owns (voices, models in the run review) already show samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (the inputs, the button in normal flow, never pinned, then the result\'s place under it), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.'),
89
91
  rule('multiplayer','block',
90
92
  'Custom views share edits: `StillMade.onShared`, publish every editable field (`data-stillmade-share`, `bindShared`, `updateShared`), respect `canEdit`, never re-run on remote updates.',
91
93
  'Projects are multiplayer. A custom view subscribes through `StillMade.onShared(callback)` and publishes every editable field: give static controls a unique `id` and `data-stillmade-share="field"` (the host owns their synchronization), use `StillMade.bindShared(field, elementId)` for controls created dynamically, and `StillMade.updateShared(values, before, options)` for structured values; private variables and DOM values are not shared. Mark a truly device-only control (playback, hover, device selection) with `data-stillmade-local`. Observe shared outputs as well as state, honor `canEdit` in every mutating callback (read-only participants cannot write), never run, click or call a paid integration in response to a remote update, and preserve conflicting drafts. Shared background work uses `sharedWork` with the `work.shared` permission.'),
92
94
  rule('external-services','block',
93
95
  'External services only via hosted operations or `connection.execute`; the host owns providers, keys, BYOK, credits and consent.',
94
- 'Reach external services only through StillMade\'s hosted operations, the connections service (`connection.execute`), or, in a module Block, `stillmade.net.fetch` to origins declared in `permissions.network` (with the key each needs), `stillmade.net.connect` for a live WebSocket to one of them, and `stillmade.connections.call` to adapters or catalog apps in `permissions.connections`; never put keys in code, inputs or headers: StillMade adds each person\'s own key on its servers. The host owns the model and provider choice, BYOK keys, StillMade credits, spending consent, authorization, token refresh, rate limits, retries and provider errors; a Block never receives a secret and never names a provider unless it must. Treat provider unavailability, timeout, cancellation and partial failure as typed outcomes the Block reports, not crashes. Generated images, video, voices, music and sound effects come from hosted operations or the `media.generate` action, paid with the user\'s StillMade credits at StillMade\'s prices; for structured model output declare a `json` output with `capability.schema` instead of parsing prose. When a Block needs npm libraries, WebAssembly (threaded: `"threads": true` in module.json) or heavy compute, use `runtime: "module"` and reach hosted operations through `stillmade.hosted.run` with the operations listed in `permissions.capabilities`. Decode, cut, join, re-encode, extract or normalise audio and video with `stillmade.media.transform` on the device; never ask for a server render. For a rich interactive interface (React or another framework, SVG, canvas, drag), use a frame view: `src/view.config.json` with `{"runtime":"frame"}` and `src/view.js` bundled as one IIFE, colored with StillMade\'s theme variables. For files and structured data use the `file`, `document`, `table`, `url`, `date` and `color` types and their generated controls and presentations, not JSON text.'),
96
+ 'Reach external services only through StillMade\'s hosted operations, the connections service (`connection.execute`), or, in a module Block, `stillmade.net.fetch` to origins declared in `permissions.network` (with the key each needs), `stillmade.net.connect` for a live WebSocket to one of them, and `stillmade.connections.call` to adapters or catalog apps in `permissions.connections`; never put keys in code, inputs or headers: StillMade adds each person\'s own key on its servers. The host owns the model and provider choice, BYOK keys, StillMade credits, spending consent, authorization, token refresh, rate limits, retries and provider errors; a Block never receives a secret and never names a provider unless it must. Treat provider unavailability, timeout, cancellation and partial failure as outcomes the Block reports, not crashes: a call the result needs throws and fails the run (see Useful failures); an optional part\'s failure is caught, given its fallback and named in an optional `notes` text output. Generated images, video, voices, music and sound effects come from hosted operations or the `media.generate` action, paid with the user\'s StillMade credits at StillMade\'s prices; for structured model output declare a `json` output with `capability.schema` instead of parsing prose. When a Block needs npm libraries, WebAssembly (threaded: `"threads": true` in module.json) or heavy compute, use `runtime: "module"` and reach hosted operations through `stillmade.hosted.run` with the operations listed in `permissions.capabilities`. Decode, cut, join, re-encode, extract or normalise audio and video with `stillmade.media.transform` on the device; never ask for a server render. For a rich interactive interface (React or another framework, SVG, canvas, drag), use a frame view: `src/view.config.json` with `{"runtime":"frame"}` and `src/view.js` bundled as one IIFE, colored with StillMade\'s theme variables. For files and structured data use the `file`, `document`, `table`, `url`, `date` and `color` types and their generated controls and presentations, not JSON text.'),
95
97
  rule('dependencies','block',
96
- 'No npm or system dependencies; inline small licensed code and record its license and notice in the manifest.',
97
- 'A Block must declare everything it requires and must not assume anything is installed. Portable packages carry no package dependencies (`portable.dependencies` is empty): inline small, license-compatible code and record its license and notice in `manifest.license` and `manifest.provenance`. Desktop-only tools are reached solely through declared desktop permissions and the host desktop bridge. If a dependency cannot be delivered through the Block runtime, report the incompatibility instead of assuming the user\'s machine has it.'),
98
+ 'No npm or system dependencies; inline small licensed code and record its license and notice in the manifest; the person is the copyright holder.',
99
+ 'A Block must declare everything it requires and must not assume anything is installed. Portable packages carry no package dependencies (`portable.dependencies` is empty): inline small, license-compatible code and record its license and notice in `manifest.license` and `manifest.provenance`. The copyright holder in the Block\'s own `provenance.notice` is the person it is made for: replace the placeholder `create` writes with their name, never StillMade, the SDK or yourself. Desktop-only tools are reached solely through declared desktop permissions and the host desktop bridge. If a dependency cannot be delivered through the Block runtime, report the incompatibility instead of assuming the user\'s machine has it.'),
98
100
  rule('security','both',
99
101
  'Treat inputs, files and responses as untrusted; request minimum permissions; never leak secrets in outputs, state, errors or UI.',
100
102
  'Treat Block code, external data, files, repositories, model output and network responses as untrusted. Request the minimum permissions and capabilities; `stillmade-block permissions --fix` declares exactly what the source uses. Do not weaken the sandbox (QuickJS has no fetch, DOM, timers, modules or Node APIs). Validate and bound every value crossing a boundary. Never expose secrets through logs, outputs, project context, UI, error messages or serialized state.'),
@@ -187,7 +187,7 @@ export function validateCapability(capability,manifest){
187
187
  assert(frames.length<2||capability.firstFrame.$input!==capability.lastFrame.$input,'CAPABILITY_BINDING','Use different image inputs for the first and last frame');
188
188
  assert(!own(capability,'lastFrame')||own(capability,'firstFrame'),'CAPABILITY_BINDING','A last frame needs a first frame');
189
189
  assert(Object.values(manifest.inputs).filter(port=>port.type==='image').length===frames.length,'CAPABILITY_BINDING','Bind every image input as the first or last frame');
190
- if(own(capability,'settings')){const settings=videoSettings(capability.settings,{strict:true,firstFrame:own(capability,'firstFrame')});const entry=VIDEO_GENERATION_MODELS[settings.model];assert(!own(capability,'lastFrame')||entry.lastFrame,'CAPABILITY_SETTINGS',`${entry.label} does not accept a last frame`);}
190
+ if(own(capability,'settings')){const settings=videoSettings(capability.settings,{strict:true,firstFrame:own(capability,'firstFrame'),lastFrame:own(capability,'lastFrame')});const entry=VIDEO_GENERATION_MODELS[settings.model];assert(!own(capability,'lastFrame')||entry.lastFrame,'CAPABILITY_SETTINGS',`${entry.label} does not accept a last frame`);}
191
191
  generationText(capability);
192
192
  assert(typeof capability.output==='string'&&own(manifest.outputs,capability.output),'CAPABILITY_BINDING','Use the declared video output port');return true;
193
193
  }
@@ -235,9 +235,11 @@ export function validateManifest(manifest, { builtIn = false } = {}) {
235
235
  check(plain(permissions), 'permissions', 'Declare permissions explicitly');
236
236
  for (const [key, scopes] of Object.entries(plain(permissions) ? permissions : {})) {
237
237
  check(['project', 'network', 'connections', 'filesystem', 'secrets', 'capabilities', 'actions', 'desktop'].includes(key), `permissions.${key}`, 'Unknown permission family');
238
- // Network entries may be {origin, auth} objects; every other family is a string list.
238
+ // Network entries may be {origin, auth} objects; every other family is a string list. A module Block's empty
239
+ // capabilities list is the same as none (November 3, 2026: a module that only calls actions.run was refused for
240
+ // `capabilities: []` with a message about capability and ComfyUI Blocks).
239
241
  check(Array.isArray(scopes) && scopes.every(s => typeof s === 'string' || key === 'network' && plain(s)), `permissions.${key}`, 'Expected string array');
240
- if (Array.isArray(scopes)) check(key === 'desktop' ? scopes.length<=DESKTOP_PERMISSIONS.length && new Set(scopes).size===scopes.length && scopes.every(s=>DESKTOP_PERMISSIONS.includes(s)) : key === 'project' ? scopes.every(s => SCOPES.includes(s)) : key === 'actions' ? ['javascript','module'].includes(manifest.runtime)&&scopes.length<=PORTABLE_ACTIONS.length&&new Set(scopes).size===scopes.length&&scopes.every(s=>PORTABLE_ACTIONS.includes(s)) : key === 'capabilities' ? (manifest.runtime === 'module' ? scopes.length >= 1 && scopes.length <= CAPABILITY_RUN_OPERATIONS.length && new Set(scopes).size === scopes.length && scopes.every(s => CAPABILITY_RUN_OPERATIONS.includes(s)) : scopes.length === 1 && (manifest.runtime === 'comfyui' && scopes[0] === 'comfyui.execute' || manifest.runtime === 'capability' && HOSTED_OPERATION_NAMES.includes(scopes[0]))) : key === 'network' && manifest.runtime === 'module' ? networkValid(scopes) : key === 'connections' ? manifest.runtime === 'module' && connectionsValid(scopes) : scopes.length === 0, `permissions.${key}`, key === 'connections' ? 'Unsupported permission: only module Blocks declare connections, up to 8 published adapters by app ID such as app:openapi:notion' : key === 'network' ? (manifest.runtime === 'module' ? `Declare network origins as https origins, optionally with {origin, auth}: ${networkProblem(scopes)}` : 'Unsupported permission: only module Blocks declare network origins; other runtimes reach services through hosted operations and connections') : key === 'capabilities' ? (['module','capability','comfyui'].includes(manifest.runtime) ? 'Declare each hosted operation once, from the supported list (a capability or ComfyUI Block declares exactly one)' : 'Unsupported permission: only capability, ComfyUI and module Blocks declare hosted operations. A javascript or recipe Block declares none: remove permissions.capabilities, or make the AI work its own capability Block') : 'Unsupported permission; Blocks have no ambient filesystem or secrets');
242
+ if (Array.isArray(scopes)) check(key === 'desktop' ? scopes.length<=DESKTOP_PERMISSIONS.length && new Set(scopes).size===scopes.length && scopes.every(s=>DESKTOP_PERMISSIONS.includes(s)) : key === 'project' ? scopes.every(s => SCOPES.includes(s)) : key === 'actions' ? ['javascript','module'].includes(manifest.runtime)&&scopes.length<=PORTABLE_ACTIONS.length&&new Set(scopes).size===scopes.length&&scopes.every(s=>PORTABLE_ACTIONS.includes(s)) : key === 'capabilities' ? (manifest.runtime === 'module' ? scopes.length <= CAPABILITY_RUN_OPERATIONS.length && new Set(scopes).size === scopes.length && scopes.every(s => CAPABILITY_RUN_OPERATIONS.includes(s)) : scopes.length === 1 && (manifest.runtime === 'comfyui' && scopes[0] === 'comfyui.execute' || manifest.runtime === 'capability' && HOSTED_OPERATION_NAMES.includes(scopes[0]))) : key === 'network' && manifest.runtime === 'module' ? networkValid(scopes) : key === 'connections' ? manifest.runtime === 'module' && connectionsValid(scopes) : scopes.length === 0, `permissions.${key}`, key === 'connections' ? 'Unsupported permission: only module Blocks declare connections, up to 8 published adapters by app ID such as app:openapi:notion' : key === 'network' ? (manifest.runtime === 'module' ? `Declare network origins as https origins, optionally with {origin, auth}: ${networkProblem(scopes)}` : 'Unsupported permission: only module Blocks declare network origins; other runtimes reach services through hosted operations and connections') : key === 'capabilities' ? (['module','capability','comfyui'].includes(manifest.runtime) ? 'Declare each hosted operation once, from the supported list (a capability or ComfyUI Block declares exactly one)' : 'Unsupported permission: only capability, ComfyUI and module Blocks declare hosted operations. A javascript or recipe Block declares none: remove permissions.capabilities, or make the AI work its own capability Block') : 'Unsupported permission; Blocks have no ambient filesystem or secrets');
241
243
  }
242
244
  if(Object.values(plain(manifest.outputs)?manifest.outputs:{}).some(port=>['workspace-edit','workspace-edit[]'].includes(port?.type))) {
243
245
  check(WORKSPACE_EDIT_FIELDS.some(field=>canProposeWorkspace(manifest,field)), 'permissions.project', 'Workspace edits require matching context.FIELD.read and workspace.FIELD.propose permissions');
@@ -257,6 +259,8 @@ export function validateManifest(manifest, { builtIn = false } = {}) {
257
259
  continue;
258
260
  }
259
261
  check(plain(c) && UI_INPUT_CONTROLS.includes(c.control) && Object.hasOwn(manifest.inputs || {}, c.port), 'ui', 'Control must bind to a declared input');
262
+ // A text input of several lines (one fact per line, a list) declares it, so every host draws a box that keeps its line breaks.
263
+ if (plain(c) && c.multiline !== undefined) check(c.multiline === true && c.control === 'text' && manifest.inputs?.[c.port]?.type === 'text', 'ui', 'multiline: true goes on a text control bound to a text input');
260
264
  if (plain(c) && Object.hasOwn(UI_CONTROL_TYPES, c.control)) check(UI_CONTROL_TYPES[c.control].includes(manifest.inputs?.[c.port]?.type), 'ui', `The ${c.control} control binds a ${UI_CONTROL_TYPES[c.control].join(', ')} input`);
261
265
  if (plain(c) && ['select','multiselect'].includes(c.control)) {
262
266
  check(manifest.inputs?.[c.port]?.type === (c.control === 'select' ? 'text' : 'text[]'), 'ui', 'Select binds text; multiselect binds text[]');
@@ -4,32 +4,6 @@
4
4
  export const FIRST_PARTY_SDK_IDENTITIES=Object.freeze(new Set([
5
5
  "sm.ai-decide@1.0.0",
6
6
  "sm.ai-write@1.0.0",
7
- "sm.animated-scene-assistant@1.0.0",
8
- "sm.animated-scene@2.0.0",
9
- "sm.animated-scene@2.1.0",
10
- "sm.animated-scene@2.2.0",
11
- "sm.animated-scene@2.3.0",
12
- "sm.animated-scene@2.3.1",
13
- "sm.animated-scene@2.3.2",
14
- "sm.animated-scene@2.3.3",
15
- "sm.animated-scene@2.3.4",
16
- "sm.animated-scene@2.3.5",
17
- "sm.animated-scene@2.3.6",
18
- "sm.animated-scene@2.3.7",
19
- "sm.animated-scene@2.3.8",
20
- "sm.animated-scene@2.3.9",
21
- "sm.blueprint-assistant@1.0.0",
22
- "sm.blueprint@2.0.0",
23
- "sm.blueprint@2.1.0",
24
- "sm.blueprint@2.1.1",
25
- "sm.blueprint@2.1.2",
26
- "sm.blueprint@2.1.3",
27
- "sm.blueprint@2.1.4",
28
- "sm.blueprint@2.1.5",
29
- "sm.blueprint@2.1.6",
30
- "sm.blueprint@2.1.7",
31
- "sm.blueprint@2.1.8",
32
- "sm.blueprint@2.1.9",
33
7
  "sm.board-editor@2.0.0",
34
8
  "sm.board-editor@2.1.0",
35
9
  "sm.board-editor@2.2.0",
@@ -47,19 +21,11 @@ export const FIRST_PARTY_SDK_IDENTITIES=Object.freeze(new Set([
47
21
  "sm.captions@2.0.0",
48
22
  "sm.character@2.0.0",
49
23
  "sm.character@2.0.1",
50
- "sm.chat@2.0.0",
51
- "sm.chat@2.0.1",
52
- "sm.chat@2.0.2",
53
- "sm.chat@2.0.3",
54
24
  "sm.compare@2.0.0",
55
25
  "sm.compare@2.0.1",
56
26
  "sm.compare@2.0.2",
57
27
  "sm.connect-app@1.0.0",
58
28
  "sm.contact-fields@1.0.0",
59
- "sm.continue-if@1.0.0",
60
- "sm.describe-media@2.0.0",
61
- "sm.describe-media@2.0.1",
62
- "sm.describe-media@2.1.0",
63
29
  "sm.edit-assistant@2.0.0",
64
30
  "sm.edit-assistant@2.0.1",
65
31
  "sm.edit-assistant@2.0.2",
@@ -92,8 +58,6 @@ export const FIRST_PARTY_SDK_IDENTITIES=Object.freeze(new Set([
92
58
  "sm.grade@2.0.0",
93
59
  "sm.grade@2.0.1",
94
60
  "sm.grade@2.0.2",
95
- "sm.image-editor@2.0.0",
96
- "sm.image-editor@2.0.1",
97
61
  "sm.import-media@2.0.0",
98
62
  "sm.import-media@2.0.1",
99
63
  "sm.import-media@2.0.2",
@@ -101,7 +65,6 @@ export const FIRST_PARTY_SDK_IDENTITIES=Object.freeze(new Set([
101
65
  "sm.location@2.0.1",
102
66
  "sm.meta-creative@1.0.0",
103
67
  "sm.motion-graphics@2.0.0",
104
- "sm.new-items@1.0.0",
105
68
  "sm.panel-strip@2.0.0",
106
69
  "sm.panel-strip@2.1.0",
107
70
  "sm.panel-strip@2.2.0",
@@ -118,7 +81,6 @@ export const FIRST_PARTY_SDK_IDENTITIES=Object.freeze(new Set([
118
81
  "sm.panel-strip@2.4.2",
119
82
  "sm.photo-notes@1.0.0",
120
83
  "sm.photo-notes@1.1.0",
121
- "sm.pick-photos@1.0.0",
122
84
  "sm.pipeline@2.0.0",
123
85
  "sm.pipeline@2.1.0",
124
86
  "sm.pipeline@2.2.0",
@@ -128,38 +90,12 @@ export const FIRST_PARTY_SDK_IDENTITIES=Object.freeze(new Set([
128
90
  "sm.pipeline@2.2.4",
129
91
  "sm.pipeline@2.2.5",
130
92
  "sm.pipeline@2.2.6",
131
- "sm.quality-audit@1.0.0",
132
- "sm.quality-audit@1.0.1",
133
- "sm.quality-audit@1.0.2",
134
- "sm.research@2.0.0",
135
- "sm.research@2.0.1",
136
- "sm.research@2.0.2",
137
93
  "sm.rough-cut@2.0.0",
138
94
  "sm.rough-cut@2.0.1",
139
95
  "sm.rough-cut@2.0.2",
140
96
  "sm.score@2.0.0",
141
- "sm.script@2.0.0",
142
- "sm.script@2.1.0",
143
- "sm.script@2.1.1",
144
- "sm.script@2.1.2",
145
- "sm.script@2.1.3",
146
- "sm.script@2.1.4",
147
- "sm.script@2.1.5",
148
- "sm.shot-plan-assistant@1.0.0",
149
- "sm.shot-plan@2.0.0",
150
- "sm.shot-plan@2.1.0",
151
- "sm.shot-plan@2.1.1",
152
- "sm.shot-plan@2.1.2",
153
- "sm.shot-plan@2.1.3",
154
- "sm.shot-plan@2.2.0",
155
- "sm.shot-plan@2.2.1",
156
- "sm.stock-search@2.0.0",
157
97
  "sm.style@2.0.0",
158
98
  "sm.style@2.0.1",
159
- "sm.thumbnail@2.0.0",
160
- "sm.thumbnail@2.0.1",
161
- "sm.transcribe@2.0.0",
162
- "sm.transcribe@2.0.1",
163
99
  "sm.transition@2.0.0",
164
100
  "sm.transition@2.0.1",
165
101
  "sm.transition@2.0.2",
@@ -179,26 +115,7 @@ export const FIRST_PARTY_SDK_IDENTITIES=Object.freeze(new Set([
179
115
  "sm.voiceover@2.1.6",
180
116
  "sm.when@1.0.0",
181
117
  "sm.when@1.1.0",
182
- "sm.whiteboard-assistant@1.0.0",
183
118
  "sm.write-summary@1.0.0",
184
- "sm.youtube@2.0.0",
185
- "stillmade.narration@1.0.0",
186
- "stillmade.narration@1.0.1",
187
- "stillmade.occasion-story@1.0.0",
188
- "stillmade.occasion-story@1.0.1",
189
- "stillmade.occasion-story@1.0.2",
190
- "stillmade.photo-reel-composer@1.0.0",
191
- "stillmade.photo-reel-composer@1.0.1",
192
- "stillmade.photo-reel-composer@1.0.2",
193
- "stillmade.product-ad-variants@1.0.0",
194
- "stillmade.release-visuals@1.0.0",
195
- "stillmade.release-visuals@1.0.1",
196
- "stillmade.release-visuals@1.0.2",
197
- "stillmade.screen-recorder@1.0.0",
198
- "stillmade.screen-recorder@1.0.1",
199
- "stillmade.short-form-finish@1.0.0",
200
- "stillmade.short-form-finish@1.0.1",
201
- "stillmade.short-form-finish@1.0.2",
202
- "stillmade.short-form-finish@1.0.3"
119
+ "stillmade.generate@1.0.0"
203
120
  ]));
204
121
  export const isFirstPartySdkIdentity=manifest=>FIRST_PARTY_SDK_IDENTITIES.has(`${manifest?.id}@${manifest?.version}`);