stillmade-sdk 0.1.48 → 0.1.50

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 (60) hide show
  1. package/AGENTS.md +6 -2
  2. package/AGENT_CONTROL.md +15 -0
  3. package/FRAME_VIEWS.md +24 -0
  4. package/INTERFACE_DESIGN.md +40 -5
  5. package/MODULES.md +2 -2
  6. package/PASTE_TO_LLM.md +26 -19
  7. package/SDK.md +126 -39
  8. package/UI_AESTHETIC.md +15 -0
  9. package/blocks/generate/package.js +1 -1
  10. package/blocks/generate/versions/1.1.0/package.js +471 -0
  11. package/blocks/generate/versions/1.2.0/package.js +555 -0
  12. package/blocks/native/sm.board-editor/versions/3.0.0/package.js +50 -17
  13. package/blocks/native/sm.board-editor/versions/3.1.0/package.js +1037 -0
  14. package/blocks/native/sm.editor/versions/3.0.0/package.js +128 -83
  15. package/blocks/native/sm.editor/versions/3.1.0/package.js +1299 -0
  16. package/blocks/native/sm.panel-strip/versions/3.0.0/package.js +45 -12
  17. package/blocks/native/sm.panel-strip/versions/3.1.0/package.js +479 -0
  18. package/blocks/native/sm.voiceover/versions/3.0.0/package.js +9 -3
  19. package/docs/build/block-builder-contract.md +6 -2
  20. package/docs/build/frame-views.md +24 -0
  21. package/docs/build/interface-design.md +40 -5
  22. package/docs/build/modules.md +2 -2
  23. package/docs/reference/authoring-guide.md +4 -4
  24. package/docs/reference/execution.md +15 -1
  25. package/docs/reference/first-party-blocks.md +4 -4
  26. package/docs/reference/parity.md +1 -1
  27. package/docs/reference/ports.md +1 -1
  28. package/docs/reference/view-bridge.md +3 -0
  29. package/docs/tools/llm.md +26 -19
  30. package/package.json +1 -1
  31. package/packages/block-cli/dev-host/assets/host.js +161 -33
  32. package/packages/block-cli/dev-host/assets/index.css +1 -1
  33. package/packages/block-platform/block-readiness.js +16 -3
  34. package/packages/block-platform/canvas.js +13 -1
  35. package/packages/block-platform/catalog.js +32 -5
  36. package/packages/block-platform/flow-inspection.js +16 -2
  37. package/packages/block-platform/listings.js +17 -3
  38. package/packages/block-platform/steps.js +4 -1
  39. package/packages/block-sdk/admission.js +1 -1
  40. package/packages/block-sdk/agent-inputs.js +4 -2
  41. package/packages/block-sdk/builder-contract.js +7 -4
  42. package/packages/block-sdk/first-party-identities.js +9 -1
  43. package/packages/block-sdk/first-party-packages.js +11 -1
  44. package/packages/block-sdk/generation-catalog.js +55 -5
  45. package/packages/block-sdk/generation-models.js +60 -0
  46. package/packages/block-sdk/index.d.ts +15 -1
  47. package/packages/block-sdk/index.js +2 -1
  48. package/packages/block-sdk/input-source-policy.js +2 -1
  49. package/packages/block-sdk/module-hosted.js +13 -5
  50. package/packages/block-sdk/module-worker.js +1 -1
  51. package/packages/block-sdk/port-types.js +6 -2
  52. package/packages/block-sdk/semantic-contracts.d.ts +1 -1
  53. package/packages/block-sdk/semantic-relationships.js +3 -0
  54. package/packages/block-sdk/semantic-schema.js +4 -1
  55. package/packages/block-sdk/shared-controls.js +4 -1
  56. package/packages/block-sdk/staged-releases.js +2 -1
  57. package/packages/block-sdk/view-bridge.js +3 -0
  58. package/packages/block-sdk/view-design.js +54 -10
  59. package/packages/block-sdk/view-renderer.js +19 -6
  60. package/packages/design-system/block-kit.css +12 -3
@@ -148,6 +148,8 @@ export function inspectProjectFlow(type,canvas={nodes:[],edges:[]},{projectId=nu
148
148
  const archivedByStage=new Map();
149
149
  for(const entry of canvas.workflowArchive||[])if(entry.step?.id&&entry.node?.data?.blockLastRun){if(!archivedByStage.has(entry.step.id))archivedByStage.set(entry.step.id,[]);archivedByStage.get(entry.step.id).push(entry);}
150
150
  const manifestFor=(stage,node)=>node?.data?.blockPackage?.manifest||(packageForStage(type,stage,[...BUILTIN_BLOCKS,...catalog])?.manifest);
151
+ // An Automation (sm.when) runs the project's Steps with nobody there, so nothing is left for the person to give then.
152
+ const unattended=type.stages.some(stage=>stage.blockId==='sm.when'&&stage.enabled!==false);
151
153
  // The Step (job) a Block sits in, by the name the person sees (October 19, 2026: Chat read
152
154
  // Block names only and told a tester her "Upload sermon" Step did not exist).
153
155
  const stepOf=new Map((type.groups||[]).filter(group=>typeof group?.label==='string'&&group.label&&Array.isArray(group.stages)).flatMap(group=>group.stages.map(id=>[id,group.label])));
@@ -257,9 +259,21 @@ export function inspectProjectFlow(type,canvas={nodes:[],edges:[]},{projectId=nu
257
259
  const appliedActivity=last?.manualSelection?{applied:true,at:last.at||null,runId:null,...(selectedManually?{selection:selectedManually}:{})}:last?{applied:true,at:last.at||null,runId:last.executionRunId||last.semanticRunId||last.segmentRunId||last.runId||null,skipped:last.skipped===true&&!last.failureRecovery,execution:executionEvidence(last,{conditional:!!stage.condition,outputs:node?.data?.blockOutputs||{}})}:null;
258
260
  const condition=stage.condition?{inputKey:stage.condition.input,inputLabel:manifest?.inputs?.[stage.condition.input]?.description||stage.condition.input,path:stage.condition.path.slice(0,8),operator:stage.condition.operator,outcome:last?(last.skipped===true&&!last.failureRecovery?'skipped':'ran'):'unknown'}:null;
259
261
  const latestFailure=(node?.data?.blockFailedAttempts||[]).slice(-1)[0],failureIsLatest=typeof latestFailure?.runId==='string'&&Number.isFinite(Date.parse(latestFailure.at))&&(!last?.at||latestFailure.at>last.at)&&!failureRecovered(latestFailure),failedActivity=failureIsLatest?{runId:latestFailure.runId.slice(0,160),at:latestFailure.at,code:failureCode(latestFailure),durationMs:Number.isSafeInteger(latestFailure.diagnostic?.durationMs)?Math.max(0,Math.min(86400000,latestFailure.diagnostic.durationMs)):null,...Object.fromEntries(['attempt','batchIndex','batchCount'].filter(key=>failedCounter(latestFailure[key])).map(key=>[key,latestFailure[key]]))}:null;
262
+ // An empty input the person types or adds themself is asked where they run the Block, on its own screen or its Step's
263
+ // form, so it is how every Block starts, not something the project needs from them (November 8, 2026, owner on
264
+ // Generate's Prompt: "i dont like how this is in my face always, like no shit i have to give it a prompt", then "make
265
+ // sure it was platform fix not just this one block"). Only when nothing was chosen, a person may give it, no earlier
266
+ // Step could supply it (then which one is a real question), it is not project context, and no Automation runs the
267
+ // project. A chosen source that stopped working still needs a fix. The Step is then like any Step that has not run
268
+ // (waiting; Chat reads ready as "worked in, never run"), and its card says it is ready to fill in and run.
269
+ const askedWhenRun=unattended?new Set():new Set(inputs.filter(input=>{
270
+ const item=row.requirements.find(requirement=>requirement.key===input.key);
271
+ return input.state==='needs-choice'&&item?.state==='needs-user-input'&&!input.explicit&&!input.sourcePlacementId&&!item.requirement?.context&&(!item.requirement?.sources||item.requirement.sources.includes('user'))&&!(item.candidates||[]).some(candidate=>candidate.record?.source?.kind==='block'||String(candidate.kind||'').startsWith('conversion-block'));
272
+ }).map(input=>input.key));
273
+ const askedOnly=askedWhenRun.size>0&&row.state!=='broken'&&inputs.every(input=>input.state!=='needs-choice'||askedWhenRun.has(input.key));
260
274
  // A host workspace (the Editor) is worked in, never run, so with nothing to choose it is ready, not waiting (November 1,
261
275
  // 2026, live: Chat read "waiting" as the Editor having no clip and would not export).
262
- return {id:row.id,nodeId:stageNodeId(stage),label:row.label,...(stepOf.has(row.id)?{step:stepOf.get(row.id)}:{}),blockId:stage.blockId,version:stage.version,...(manifest?.permissions?.capabilities?.includes('connection.execute')?{connector:true}:{}),transformation:String(manifest?.description||'').slice(0,500),enabled:stage.enabled!==false,state:node?.data?.blockPrivacy?'restricted':active?'working':row.paused?'paused':node?.data?.blockStale||last&&last.digest!==node?.data?.blockDigest?'needs-updating':failureIsLatest?(failureCode(latestFailure)==='EXECUTION_FAILED'?'failed':'needs-recovery'):last?.skipped===true&&!last?.failureRecovery&&stage.condition?'skipped':last?'finished':row.state==='broken'||row.state==='needs-user-input'?'needs-choice':manifest?.kind==='workspace'&&manifest?.runtime==='host'?'ready':'waiting',...(condition?{condition}:{}),inputs:inputs.map(input=>({...input,valueType:manifest?.inputs?.[input.key]?.type||row.requirements.find(requirement=>requirement.key===input.key)?.requirement?.type||null})),outputs,unusedResults,unusedResultsRevision,unusedResultsCount,unusedResultsOffset,unusedResultsLimit,unusedResultsNextOffset,...(retainedPage?.candidateId?{inspectedRetainedResult}:{}),usedInputs,activity:active?{...(appliedActivity||{}),active,activeRuns,activeRunsOmitted:Math.max(0,(runsByPlacement.get(row.id)||[]).length-activeRuns.length)}:failedActivity?{...(appliedActivity||{}),failedAttempt:failedActivity}:appliedActivity,history:recentHistory,historyComplete,retainedLoopRuns:retainedWorkflowLoopSummaries(canvas,row.id)};
276
+ return {id:row.id,nodeId:stageNodeId(stage),label:row.label,...(stepOf.has(row.id)?{step:stepOf.get(row.id)}:{}),blockId:stage.blockId,version:stage.version,...(manifest?.permissions?.capabilities?.includes('connection.execute')?{connector:true}:{}),transformation:String(manifest?.description||'').slice(0,500),enabled:stage.enabled!==false,state:node?.data?.blockPrivacy?'restricted':active?'working':row.paused?'paused':node?.data?.blockStale||last&&last.digest!==node?.data?.blockDigest?'needs-updating':failureIsLatest?(failureCode(latestFailure)==='EXECUTION_FAILED'?'failed':'needs-recovery'):last?.skipped===true&&!last?.failureRecovery&&stage.condition?'skipped':last?'finished':row.state==='broken'||row.state==='needs-user-input'&&!askedOnly?'needs-choice':manifest?.kind==='workspace'&&manifest?.runtime==='host'?'ready':'waiting',...(condition?{condition}:{}),inputs:inputs.map(input=>({...input,...(askedWhenRun.has(input.key)?{askedWhenRun:true,source:'you, when you run it',reason:'The person gives this where they run the Block, on its screen or its Step’s form.'}:{}),valueType:manifest?.inputs?.[input.key]?.type||row.requirements.find(requirement=>requirement.key===input.key)?.requirement?.type||null})),outputs,unusedResults,unusedResultsRevision,unusedResultsCount,unusedResultsOffset,unusedResultsLimit,unusedResultsNextOffset,...(retainedPage?.candidateId?{inspectedRetainedResult}:{}),usedInputs,activity:active?{...(appliedActivity||{}),active,activeRuns,activeRunsOmitted:Math.max(0,(runsByPlacement.get(row.id)||[]).length-activeRuns.length)}:failedActivity?{...(appliedActivity||{}),failedAttempt:failedActivity}:appliedActivity,history:recentHistory,historyComplete,retainedLoopRuns:retainedWorkflowLoopSummaries(canvas,row.id)};
263
277
  });
264
278
  if(retainedResultPage){const placement=placements.find(item=>item.id===retainedResultPage.placementId);if(!placement||placement.unusedResultsRevision!==retainedResultPage.revision||retainedResultPage.operation==='list'&&retainedResultPage.offset>placement.unusedResultsCount||retainedResultPage.candidateId&&!placement.inspectedRetainedResult)throw new Error('This retained result list or exact candidate changed. Inspect Result again.');}
265
279
  if(receiptInputPage){const entry=placements.find(item=>item.id===receiptInputPage.placementId)?.history?.find(item=>projectFlowRunReceiptIdentity(item)===receiptInputPage.receiptId);if(!entry||entry.inputsRevision!==receiptInputPage.revision||!entry.inputs||receiptInputPage.offset>entry.inputsCount)throw new Error('This retained receipt or its captured inputs changed. Inspect the run again.');}
@@ -286,7 +300,7 @@ export function inspectProjectFlow(type,canvas={nodes:[],edges:[]},{projectId=nu
286
300
  if(placement.condition)conditionalLabels.push(`Conditional Block · last recorded ${placement.condition.outcome==='unknown'?'not run':placement.condition.outcome}`);
287
301
  return {from:input.sourcePlacementId,outputKey:input.sourceOutputKey,to:placement.id,inputKey:input.key,state:input.state,materialized:input.materialized===true,sourceRunId:input.pinRunId||sourceOutput?.runId||null,adapter:input.adapter,...(conditionalLabels.length?{conditionalLabel:conditionalLabels.join(' · ')}:{})};
288
302
  }));
289
- return projectFlowSnapshot({contractVersion:PROJECT_FLOW_CONTRACT_VERSION,projectId,definition:{id:type.id,version:type.version},...(type.groups?.length?{jobs:type.groups.map(group=>({id:group.id,label:group.label,parts:[...group.stages]}))}:{}),acceptanceCriteria:(type.acceptanceCriteria||[]).map(item=>({...item,verified:false})),goalSourceMaterials:validateGoalSourceMaterials(type),goalSourceInputs:currentGoalSourceInputs(type,[...BUILTIN_BLOCKS,...catalog]),placements,edges,issues:audit.issues.filter(issue=>!(issue.state==='needs-user-input'&&placements.find(placement=>placement.id===issue.stageId)?.inputs?.some(input=>input.key===issue.key&&input.state==='ready'))).slice(0,50).map(issue=>({placementId:issue.stageId||null,inputKey:issue.key||null,...(issue.state?{state:issue.state}:{}),message:issue.message||issue.reason||'Connection needs attention'})),partial:audit.requirementsInspected===false});
303
+ return projectFlowSnapshot({contractVersion:PROJECT_FLOW_CONTRACT_VERSION,projectId,definition:{id:type.id,version:type.version},...(type.groups?.length?{jobs:type.groups.map(group=>({id:group.id,label:group.label,parts:[...group.stages]}))}:{}),acceptanceCriteria:(type.acceptanceCriteria||[]).map(item=>({...item,verified:false})),goalSourceMaterials:validateGoalSourceMaterials(type),goalSourceInputs:currentGoalSourceInputs(type,[...BUILTIN_BLOCKS,...catalog]),placements,edges,issues:audit.issues.filter(issue=>!(issue.state==='needs-user-input'&&placements.find(placement=>placement.id===issue.stageId)?.inputs?.some(input=>input.key===issue.key&&(input.state==='ready'||input.askedWhenRun===true)))).slice(0,50).map(issue=>({placementId:issue.stageId||null,inputKey:issue.key||null,...(issue.state?{state:issue.state}:{}),message:issue.message||issue.reason||'Connection needs attention'})),partial:audit.requirementsInspected===false});
290
304
  }
291
305
 
292
306
  // A read-only explanation of the current handoff. Each hop is an inspected
@@ -9,9 +9,23 @@ export function listingLandingPath({kind,id,name}) {return `/${kind === 'type' ?
9
9
  export function isCanvasListing(item) {
10
10
  return (item.kind==='block' && item.id==='sm.pipeline') || (item.kind==='type' && item.id==='sm.blank');
11
11
  }
12
- export function listingPreviewPath(item){return `/api/public-listings/${item.kind}/${encodeURIComponent(item.id)}/${encodeURIComponent(item.version)}/preview.png?cover=working-2`;}
13
- export function listingThumbnailPath(item,width=640){return `/api/public-listings/${item.kind}/${encodeURIComponent(item.id)}/${encodeURIComponent(item.version)}/preview.webp?cover=working-2&width=${width}`;}
14
- export function listingThumbnailSrcSet(item){return [320,640,960].map(width=>`${listingThumbnailPath(item,width)} ${width}w`).join(', ');}
12
+ export function listingPreviewPath(item){return `/api/public-listings/${item.kind}/${encodeURIComponent(item.id)}/${encodeURIComponent(item.version)}/preview.png?cover=working-3`;}
13
+ // `image` picks a listing image other than the cover (index 1); the cover keeps its old URL.
14
+ export function listingThumbnailPath(item,width=640,image=0){return `/api/public-listings/${item.kind}/${encodeURIComponent(item.id)}/${encodeURIComponent(item.version)}/preview.webp?cover=working-3&width=${width}${image>0?`&image=${image}`:''}`;}
15
+ export function listingThumbnailSrcSet(item,image=0){return [320,640,960].map(width=>`${listingThumbnailPath(item,width,image)} ${width}w`).join(', ');}
16
+ // A listing's media as counts for its card ({listingMedia:{images,videos}}, or nothing): from the saved
17
+ // counts of a creator listing or the saved key lists of a built-in one. Storage keys never leave the server.
18
+ export function listingMediaCounts(media){
19
+ const count=(value,max)=>Math.min(max,Array.isArray(value)?value.filter(entry=>typeof entry==='string').length:Number.isInteger(value)&&value>0?value:0),images=count(media?.images,2),videos=count(media?.videos,4);
20
+ return images||videos?{listingMedia:{images,videos}}:{};
21
+ }
22
+ // What a Marketplace card slides through: the listing's own videos first (what it makes, moving), then its
23
+ // images as thumbnails. `listingMedia` holds counts only ({images, videos}); the paths are the public media routes.
24
+ export function listingCardSlides(item){
25
+ const media=item?.listingMedia||{},count=(value,max)=>Math.max(0,Math.min(max,Number.isInteger(value)?value:0)),base=`/api/public-listings/${item.kind}/${encodeURIComponent(item.id)}/${encodeURIComponent(item.version)}`;
26
+ return [...Array.from({length:count(media.videos,4)},(_,index)=>({kind:'video',key:`video-${index}`,src:`${base}/video-${index}`})),
27
+ ...Array.from({length:count(media.images,2)},(_,index)=>({kind:'image',key:`image-${index}`,src:listingThumbnailPath(item,640,index),srcSet:listingThumbnailSrcSet(item,index)}))];
28
+ }
15
29
 
16
30
  export function hostSourcePath({id,version}) {return `/block-sdk/host/${encodeURIComponent(id)}${version==='1.0.0' ? '' : '/'+encodeURIComponent(version)}.zip`;}
17
31
 
@@ -201,5 +201,8 @@ export function replaceStep(type,id,pkg,catalog=[]) {
201
201
  const packageIndex=packages.findIndex(item=>stableStringify(item)===stableStringify(pkg));
202
202
  if(packageIndex>=0)replacement.packageIndex=packageIndex;
203
203
  if(!Object.keys(config).length)delete replacement.config;
204
- return changeSteps({...type,packages},type.stages.map(step=>step.id===id?replacement:step),catalog);
204
+ // A wire to or from a port the new Block does not have cannot follow it (November 7, 2026: swapping Voiceover 3.0.0, whose
205
+ // voice and timings the Type wires on, failed with "Connection references missing stage or port").
206
+ const follows=edge=>edge.to?.stage===id?Object.hasOwn(manifest.inputs||{},edge.to.port):edge.from?.stage===id?Object.hasOwn(manifest.outputs||{},edge.from.port):true;
207
+ return changeSteps({...type,packages,connections:(type.connections||[]).filter(follows)},type.stages.map(step=>step.id===id?replacement:step),catalog);
205
208
  }
@@ -99,7 +99,7 @@ export function scanPackageSource(pkg) {
99
99
  else results.push({name:'Static code review',passed:true,message:pkg.manifest.runtime === 'comfyui' ? 'Only reviewed core image nodes and bounded typed bindings; no custom Python, dependencies, credentials, or paths' : pkg.manifest.runtime === 'capability' ? `Bounded declarative ${pkg.capability.operation} request only; provider, model, payment and approval belong to the host. No live execution has been verified.` : 'Only allowlisted recipe operations and bounded JSON are accepted'});
100
100
  if(pkg.view){
101
101
  results.push({name:'Sandbox interface review',passed:true,message:pkg.view.runtime==='frame'?'Frame view: any interface code, compiled with task guards and run in an opaque-origin frame with no network, generated code, navigation, popups or WebAssembly':'Bounded HTML controls and reviewed standalone UI JavaScript; opaque-origin frame, no navigation or remote resources'});
102
- const design=reviewViewDesign(pkg.view,{mode:interfaceMode(pkg.manifest)}),notes=[...design.issues,...design.warnings];
102
+ const design=reviewViewDesign(pkg.view,{mode:interfaceMode(pkg.manifest),manifest:pkg.manifest}),notes=[...design.issues,...design.warnings];
103
103
  results.push({name:'Task interface design',passed:design.passed,message:notes.length?notes.join(' '):'The task object leads, choices are bounded, and secondary detail stays progressively disclosed.'});
104
104
  }
105
105
  try {validateViewAppearance(pkg.view);results.push({name:'Appearance compatibility',passed:true,message:pkg.view?.appearance==='original'?'Interface preserves its declared original colors; StillMade theming is optional. Sandbox and shared-state checks still apply.':'Interface follows Light, Dark and White; original preview colors are preserved'});} catch(error) {results.push(pkg.view?.runtime==='frame'?{name:'Appearance compatibility',passed:true,advisory:true,message:`Advice: ${error.message} Frame views receive the theme variables; using them keeps the interface right in Light, Dark and White.`}:{name:'Appearance compatibility',passed:false,message:error.message});}
@@ -3,10 +3,12 @@ import {plain,safeDocument,valueMatches} from './contracts.js';
3
3
  const structured=['json','object','document','table','url','date','color','script','transcript','brief','bible','shot-plan','panel-document','board-document','scene-document','timeline','timeline-edit','workspace-edit','timeline-range','research','character','location','style','pipeline','scene','shot','mask','depth','pose','metadata','project-context'];
4
4
  const editable=new Set(['text','number','integer','boolean','text[]','number[]','integer[]','boolean[]',...structured,...structured.filter(type=>!['json','object'].includes(type)).map(type=>`${type}[]`)]);
5
5
  // Stable input inspection order survives JSONB persistence and source reload.
6
+ // An input only the Block's own screen fills (sources ['view']) is never Chat's to set.
7
+ const viewOnly=port=>Array.isArray(port.sources)&&!port.sources.some(source=>source!=='view');
6
8
  export const agentInputEntries=manifest=>Object.entries(manifest.inputs||{}).sort(([a],[b])=>a<b?-1:a>b?1:0);
7
9
  export function agentInputChoices(manifest,locked=[],{offset=0}={}){
8
10
  let schemaBudget=12000,optionBudget=12000;
9
- return agentInputEntries(manifest).slice(Math.max(0,Math.trunc(offset)||0),Math.max(0,Math.trunc(offset)||0)+50).filter(([id,port])=>editable.has(port.type)&&!port.context&&!locked.includes(id)).slice(0,50).map(([id,port])=>{
11
+ return agentInputEntries(manifest).slice(Math.max(0,Math.trunc(offset)||0),Math.max(0,Math.trunc(offset)||0)+50).filter(([id,port])=>editable.has(port.type)&&!port.context&&!viewOnly(port)&&!locked.includes(id)).slice(0,50).map(([id,port])=>{
10
12
  const control=manifest.ui?.find(row=>row.port===id);
11
13
  const offered=control?.options?.slice(0,50).map(option=>plain(option)?option.value:option),optionsText=offered?JSON.stringify(offered):'',options=optionsText&&optionsText.length<=Math.min(4000,optionBudget)?offered:undefined;
12
14
  if(options)optionBudget-=optionsText.length;
@@ -19,7 +21,7 @@ export function agentInputChoices(manifest,locked=[],{offset=0}={}){
19
21
  export function parseAgentInput(manifest,args,{locked=[],busy=false,disabled=false}={}){
20
22
  if(!plain(args)||Object.keys(args).length!==2||!Object.hasOwn(args,'field')||!Object.hasOwn(args,'value')||typeof args.field!=='string')throw new Error('Choose an input and provide its value.');
21
23
  const port=manifest.inputs?.[args.field];
22
- if(busy||disabled||!port||port.context||locked.includes(args.field))throw new Error('This input is locked or the Block is busy.');
24
+ if(busy||disabled||!port||port.context||viewOnly(port)||locked.includes(args.field))throw new Error('This input is locked or the Block is busy.');
23
25
  if(!editable.has(port.type))throw new Error('Use the matching media or project-item control for this input.');
24
26
  // A script or brief takes plain text too, stored the way its text control stores it.
25
27
  if(['script','brief'].includes(port.type)&&typeof args.value==='string')args={...args,value:{schemaVersion:1,text:args.value}};
@@ -9,7 +9,7 @@
9
9
  import {HOSTED_OPERATION_NAMES} from './contracts.js';
10
10
 
11
11
  export const BUILDER_CONTRACT_ID='stillmade.block-builder';
12
- export const BUILDER_CONTRACT_VERSION='1.18.0';
12
+ export const BUILDER_CONTRACT_VERSION='1.20.0';
13
13
  export const BUILDER_CONTRACT_STAMP=`${BUILDER_CONTRACT_ID}@${BUILDER_CONTRACT_VERSION}`;
14
14
  export const BUILDER_CONTRACT_DOCS_PATH='/docs/build/block-builder-contract';
15
15
  // Block names appear in step bars, cards and pickers; new releases keep them short.
@@ -86,14 +86,17 @@ const SECTIONS=Object.freeze([
86
86
  // pictures its full-screen button; a video without controls stays the Block's own. Also that day (owner: "there is no
87
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.
88
88
  rule('ui','block',
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) sets `manifest.kind: "workspace"` and works like that app: full canvas, panels, tools, undo, never a form; checks then use the app rules. 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 after the inputs. Actions and status sit on the stage (`sm-actions`, `sm-status` in `sm-stage`); the run button stays rightmost, so making again never moves.',
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.'),
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) once the screen says what its next run makes (`StillMade.nextRun`). A job fits one screen unscrolled and a 320px phone; an app (editor, configurator, CAD tool) sets `manifest.kind: "workspace"` and works like that app: full canvas, panels, tools, undo, never a form; checks then use the app rules. 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 after the inputs. Actions and status sit on the stage (`sm-actions`, `sm-status` in `sm-stage`); the run button stays rightmost, so making again never moves; a `data-stillmade-action="download"` button beside it saves what the stage shows.',
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 (`data-stillmade-action="download"`, which needs no code: StillMade saves the stage\'s picture or clip under the export policy) 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 adds it) once the view says what its next run makes (`StillMade.nextRun`: its `media.generate` items or hosted operations), 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
  rule('multiplayer','block',
92
92
  'StillMade shares every screen control (`data-stillmade-local` keeps one local); a view sharing its own state (`updateShared`) adds `onShared`, `canEdit`, no re-run on remote edits.',
93
93
  'Projects are multiplayer, and StillMade builds it: every ordinary control on a Block\'s screen (text, number, range, checkbox, color, select, textarea and a kit choice group) is shared by itself, under its `data-stillmade-share`, else its `name`, else its `id`, including controls the screen adds later; the Block writes no sharing code. `StillMade.bindShared(field, elementId)` shares a control under another field name. Mark a truly device-only control (playback, hover, device selection), or an element around several, with `data-stillmade-local`. A view that publishes its own structured state (`StillMade.updateShared(values, before, options)` or `StillMade.connectDocument`) shares its controls itself: StillMade then steps aside, and it subscribes through `StillMade.onShared(callback)`; private variables and canvas drawings are not shared unless the view publishes them. 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.'),
94
94
  rule('external-services','block',
95
95
  'External services only via hosted operations or `connection.execute`; the host owns providers, keys, BYOK, credits and consent.',
96
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.'),
97
+ rule('models-from-platform','block',
98
+ 'Never name or list models: ask for shape, size and length; the person picks the model, and a picker draws from `StillMade.models()`.',
99
+ 'A Block never names a model, in its capability, its module code or its screen, and never keeps a list of models: the person picks the model on the Step from the models StillMade runs that day, so models StillMade adds or retires later reach the Block with no new version (owner, November 8, 2026). Ask for what the result needs instead, in `settings` (`aspectRatio`, `resolution`, `quality`, `duration`, `generateAudio`): with no model picked StillMade runs its default model, or the first one that makes what was asked, and a value the chosen model does not make falls back to its own. A screen that offers its own model picker draws it from `StillMade.models(operation)` when it opens (each model\'s settings arrive as data, so a setting StillMade adds later draws too) and passes the picked `id` as `settings.model`. A model StillMade retires runs on the one that replaced it.'),
97
100
  rule('dependencies','block',
98
101
  '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
102
  'A Block must declare everything it requires and must not assume anything is installed. Portable packages carry no package dependencies (`portable.dependencies` is empty): inline small, license-compatible code and record its license and notice in `manifest.license` and `manifest.provenance`. The copyright holder in the Block\'s own `provenance.notice` is the person it is made for: replace the placeholder `create` writes with their name, never StillMade, the SDK or yourself. Desktop-only tools are reached solely through declared desktop permissions and the host desktop bridge. If a dependency cannot be delivered through the Block runtime, report the incompatibility instead of assuming the user\'s machine has it.'),
@@ -160,7 +163,7 @@ export const BUILDER_CONTRACT=Object.freeze({
160
163
  gapClasses:GAP_CLASSES,
161
164
  });
162
165
 
163
- const SECTION_TITLES=Object.freeze({'runtime-choice':'Choose the runtime','grounded-text':'Only supplied facts','source-of-truth':'Source of truth','reuse-first':'Reuse first','block-tier':'Three sizes of Block','block-scope':'One job per Step','capability-contract':'Define the capability first','feature-map':'Feature map','compose':'Blocks compose','platform-primitives':'Use platform primitives','no-workarounds':'No hidden workarounds','naming':'Name it briefly','input-words':'Name inputs in the job\'s words','job-words':'Speak the job','ui':'Interface','multiplayer':'Shared editing','external-services':'External services','dependencies':'Dependencies','security':'Security','lifecycle':'Execution lifecycle','errors':'Useful failures','adapt-to-platform':'Adapt to the platform','verification':'What StillMade verifies','project-types':'Project Types','readiness-report':'Readiness report'});
166
+ const SECTION_TITLES=Object.freeze({'runtime-choice':'Choose the runtime','grounded-text':'Only supplied facts','source-of-truth':'Source of truth','reuse-first':'Reuse first','block-tier':'Three sizes of Block','block-scope':'One job per Step','capability-contract':'Define the capability first','feature-map':'Feature map','compose':'Blocks compose','platform-primitives':'Use platform primitives','no-workarounds':'No hidden workarounds','naming':'Name it briefly','input-words':'Name inputs in the job\'s words','job-words':'Speak the job','ui':'Interface','multiplayer':'Shared editing','external-services':'External services','models-from-platform':'Models come from StillMade','dependencies':'Dependencies','security':'Security','lifecycle':'Execution lifecycle','errors':'Useful failures','adapt-to-platform':'Adapt to the platform','verification':'What StillMade verifies','project-types':'Project Types','readiness-report':'Readiness report'});
164
167
  const inScope=(scope,wanted)=>wanted==='all'||scope==='both'||scope===wanted;
165
168
 
166
169
  /** Render the contract for a model prompt ('compact'), a guide ('full') or an SDK folder ('agents-md'). */
@@ -5,6 +5,7 @@ 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
7
  "sm.board-editor.hosted@3.0.0",
8
+ "sm.board-editor.hosted@3.1.0",
8
9
  "sm.board-editor@2.0.0",
9
10
  "sm.board-editor@2.1.0",
10
11
  "sm.board-editor@2.2.0",
@@ -20,6 +21,7 @@ export const FIRST_PARTY_SDK_IDENTITIES=Object.freeze(new Set([
20
21
  "sm.board-editor@2.2.8",
21
22
  "sm.board-editor@2.2.9",
22
23
  "sm.board-editor@3.0.0",
24
+ "sm.board-editor@3.1.0",
23
25
  "sm.captions@2.0.0",
24
26
  "sm.character@2.0.0",
25
27
  "sm.character@2.0.1",
@@ -32,6 +34,7 @@ export const FIRST_PARTY_SDK_IDENTITIES=Object.freeze(new Set([
32
34
  "sm.edit-assistant@2.0.1",
33
35
  "sm.edit-assistant@2.0.2",
34
36
  "sm.editor.hosted@3.0.0",
37
+ "sm.editor.hosted@3.1.0",
35
38
  "sm.editor@2.0.0",
36
39
  "sm.editor@2.1.0",
37
40
  "sm.editor@2.2.0",
@@ -48,6 +51,7 @@ export const FIRST_PARTY_SDK_IDENTITIES=Object.freeze(new Set([
48
51
  "sm.editor@2.2.8",
49
52
  "sm.editor@2.2.9",
50
53
  "sm.editor@3.0.0",
54
+ "sm.editor@3.1.0",
51
55
  "sm.export-audio@2.0.0",
52
56
  "sm.export-timeline@2.0.0",
53
57
  "sm.export-video@2.0.0",
@@ -70,6 +74,7 @@ export const FIRST_PARTY_SDK_IDENTITIES=Object.freeze(new Set([
70
74
  "sm.meta-creative@1.0.0",
71
75
  "sm.motion-graphics@2.0.0",
72
76
  "sm.panel-strip.hosted@3.0.0",
77
+ "sm.panel-strip.hosted@3.1.0",
73
78
  "sm.panel-strip@2.0.0",
74
79
  "sm.panel-strip@2.1.0",
75
80
  "sm.panel-strip@2.2.0",
@@ -85,6 +90,7 @@ export const FIRST_PARTY_SDK_IDENTITIES=Object.freeze(new Set([
85
90
  "sm.panel-strip@2.4.1",
86
91
  "sm.panel-strip@2.4.2",
87
92
  "sm.panel-strip@3.0.0",
93
+ "sm.panel-strip@3.1.0",
88
94
  "sm.photo-editor@1.0.0",
89
95
  "sm.photo-notes@1.0.0",
90
96
  "sm.photo-notes@1.1.0",
@@ -125,6 +131,8 @@ export const FIRST_PARTY_SDK_IDENTITIES=Object.freeze(new Set([
125
131
  "sm.when@1.0.0",
126
132
  "sm.when@1.1.0",
127
133
  "sm.write-summary@1.0.0",
128
- "stillmade.generate@1.0.0"
134
+ "stillmade.generate@1.0.0",
135
+ "stillmade.generate@1.1.0",
136
+ "stillmade.generate@1.2.0"
129
137
  ]));
130
138
  export const isFirstPartySdkIdentity=manifest=>FIRST_PARTY_SDK_IDENTITIES.has(`${manifest?.id}@${manifest?.version}`);
@@ -1,4 +1,8 @@
1
1
  import generate from '../../blocks/generate/package.js';
2
+ // 1.1.0 (November 8, 2026) draws its models from StillMade.models(); 1.0.0 baked a copy of the catalog and stays for exact pins.
3
+ // 1.2.0 (the same day) is the generator studio; 1.1.0 stays for exact pins.
4
+ import generateV100 from '../../blocks/generate/versions/1.0.0/package.js';
5
+ import generateV110 from '../../blocks/generate/versions/1.1.0/package.js';
2
6
  import importMedia from '../../blocks/native/sm.import-media/versions/2.0.0/package.js';
3
7
  import importMediaV201 from '../../blocks/native/sm.import-media/versions/2.0.1/package.js';
4
8
  import importMediaV202 from '../../blocks/native/sm.import-media/versions/2.0.2/package.js';
@@ -6,6 +10,8 @@ import editorV229 from '../../blocks/native/sm.editor/versions/2.2.9/package.js'
6
10
  import editorV2210 from '../../blocks/native/sm.editor/versions/2.2.10/package.js';
7
11
  import editorV2211 from '../../blocks/native/sm.editor/versions/2.2.11/package.js';
8
12
  import editorV300 from '../../blocks/native/sm.editor/versions/3.0.0/package.js';
13
+ // 3.1.0 (November 8, 2026): the Sound tab is back and its Audio Score runs as hosted calls; 3.0.0 stays for exact pins.
14
+ import editorV310 from '../../blocks/native/sm.editor/versions/3.1.0/package.js';
9
15
  import editorV2212 from '../../blocks/native/sm.editor/versions/2.2.12/package.js';
10
16
  import captions from '../../blocks/native/sm.captions/versions/2.0.0/package.js';
11
17
  import exportTimeline from '../../blocks/native/sm.export-timeline/versions/2.0.0/package.js';
@@ -105,6 +111,8 @@ import panelStripV241 from '../../blocks/native/sm.panel-strip/versions/2.4.1/pa
105
111
  import panelStripV242 from '../../blocks/native/sm.panel-strip/versions/2.4.2/package.js';
106
112
  // 3.0.0 (November 6, 2026): the original Image Panels screen as an SDK module Block with a frame view.
107
113
  import panelStripV300 from '../../blocks/native/sm.panel-strip/versions/3.0.0/package.js';
114
+ // 3.1.0 (November 8, 2026): its panel, style and clip model pickers list the models StillMade runs now (StillMade.models).
115
+ import panelStripV310 from '../../blocks/native/sm.panel-strip/versions/3.1.0/package.js';
108
116
  import boardEditorV210 from '../../blocks/native/sm.board-editor/versions/2.1.0/package.js';
109
117
  import boardEditorV220 from '../../blocks/native/sm.board-editor/versions/2.2.0/package.js';
110
118
  import boardEditorV221 from '../../blocks/native/sm.board-editor/versions/2.2.1/package.js';
@@ -119,6 +127,8 @@ import boardEditorV229 from '../../blocks/native/sm.board-editor/versions/2.2.9/
119
127
  import boardEditorV2210 from '../../blocks/native/sm.board-editor/versions/2.2.10/package.js';
120
128
  import boardEditorV2211 from '../../blocks/native/sm.board-editor/versions/2.2.11/package.js';
121
129
  import boardEditorV300 from '../../blocks/native/sm.board-editor/versions/3.0.0/package.js';
130
+ // 3.1.0 (November 8, 2026): its "+ Character Pose" model picker lists the models StillMade runs now (StillMade.models).
131
+ import boardEditorV310 from '../../blocks/native/sm.board-editor/versions/3.1.0/package.js';
122
132
 
123
133
  import photoNotes from '../../blocks/native/sm.photo-notes/versions/1.0.0/package.js';
124
134
  import photoNotesV110 from '../../blocks/native/sm.photo-notes/versions/1.1.0/package.js';
@@ -128,5 +138,5 @@ import photoEditor from '../../blocks/native/sm.photo-editor/versions/1.0.0/pack
128
138
  // Exact, independently executable first-party releases. Host workspaces do not
129
139
  // belong here. Changing implementation requires a new immutable version.
130
140
  const freeze=value=>{if(value&&typeof value==='object'&&!Object.isFrozen(value)){Object.values(value).forEach(freeze);Object.freeze(value);}return value;};
131
- export const FIRST_PARTY_SDK_PACKAGES = Object.freeze([freeze(generate),freeze(photoEditor),freeze(photoNotesV110),freeze(photoNotes),freeze(exportVideoV210),freeze(exportVideo),freeze(exportAudio),freeze(motionGraphics),freeze(audioScore),freeze(importMediaV202),freeze(importMediaV201),freeze(editorV300),freeze(editorV2212),freeze(editorV2211),freeze(editorV2210),freeze(editorV229),freeze(editorV228),freeze(panelStripV300),freeze(panelStripV242),freeze(panelStripV241),freeze(panelStripV240),freeze(panelStripV237),freeze(panelStripV236),freeze(boardEditorV300),freeze(boardEditorV2211),freeze(boardEditorV2210),freeze(boardEditorV229),freeze(pipelineV226),freeze(pipelineV225),freeze(pipelineV224),freeze(videoAssetsV214),freeze(videoAssetsV213),freeze(voiceoverV300),freeze(voiceoverV216),freeze(voiceoverV215),freeze(voiceoverV214),freeze(voiceoverV213),freeze(roughCutV202),freeze(roughCutV201),freeze(finalEditV202),freeze(finalEditV201),freeze(editAssistantV202),freeze(editAssistantV201),freeze(gradeV202),freeze(gradeV201),freeze(transitionV202),freeze(transitionV201),freeze(compareV202),freeze(compareV201),freeze(compare),freeze(grade),freeze(transition),freeze(exportTimeline),freeze(captions),freeze(importMedia),freeze(generateImageV210),freeze(generateImage),freeze(writeSummary),freeze(automationV110),freeze(automationWhen),freeze(automationAiDecide),freeze(connectApp),freeze(automationContactFields),freeze(automationAiWrite),freeze(automationMetaCreative),freeze(voiceover),freeze(voiceoverV210),freeze(voiceoverV211),freeze(voiceoverV212),freeze(generateVideoV201),freeze(generateVideo),freeze(characterV201),freeze(character),freeze(locationV201),freeze(location),freeze(styleV201),freeze(style),freeze(videoAssets),freeze(videoAssetsV210),freeze(videoAssetsV211),freeze(videoAssetsV212),freeze(roughCut),freeze(finalEdit),freeze(editAssistant),freeze(boardEditor),freeze(boardEditorV210),freeze(boardEditorV220),freeze(boardEditorV221),freeze(boardEditorV222),freeze(boardEditorV223),freeze(boardEditorV224),freeze(boardEditorV225),freeze(boardEditorV226),freeze(boardEditorV227),freeze(boardEditorV228),freeze(panelStripV200),freeze(panelStrip),freeze(panelStripV220),freeze(panelStripV230),freeze(panelStripV231),freeze(panelStripV232),freeze(panelStripV233),freeze(panelStripV234),freeze(panelStripV235),freeze(editor),freeze(editorV210),freeze(editorV220),freeze(editorV221),freeze(editorV222),freeze(editorV223),freeze(editorV224),freeze(editorV225),freeze(editorV226),freeze(editorV227),freeze(pipelineV200),freeze(pipeline),freeze(pipelineV220),freeze(pipelineV221),freeze(pipelineV223),freeze(pipelineV222) ]);
141
+ export const FIRST_PARTY_SDK_PACKAGES = Object.freeze([freeze(generate),freeze(generateV110),freeze(generateV100),freeze(photoEditor),freeze(photoNotesV110),freeze(photoNotes),freeze(exportVideoV210),freeze(exportVideo),freeze(exportAudio),freeze(motionGraphics),freeze(audioScore),freeze(importMediaV202),freeze(importMediaV201),freeze(editorV310),freeze(editorV300),freeze(editorV2212),freeze(editorV2211),freeze(editorV2210),freeze(editorV229),freeze(editorV228),freeze(panelStripV310),freeze(panelStripV300),freeze(panelStripV242),freeze(panelStripV241),freeze(panelStripV240),freeze(panelStripV237),freeze(panelStripV236),freeze(boardEditorV310),freeze(boardEditorV300),freeze(boardEditorV2211),freeze(boardEditorV2210),freeze(boardEditorV229),freeze(pipelineV226),freeze(pipelineV225),freeze(pipelineV224),freeze(videoAssetsV214),freeze(videoAssetsV213),freeze(voiceoverV300),freeze(voiceoverV216),freeze(voiceoverV215),freeze(voiceoverV214),freeze(voiceoverV213),freeze(roughCutV202),freeze(roughCutV201),freeze(finalEditV202),freeze(finalEditV201),freeze(editAssistantV202),freeze(editAssistantV201),freeze(gradeV202),freeze(gradeV201),freeze(transitionV202),freeze(transitionV201),freeze(compareV202),freeze(compareV201),freeze(compare),freeze(grade),freeze(transition),freeze(exportTimeline),freeze(captions),freeze(importMedia),freeze(generateImageV210),freeze(generateImage),freeze(writeSummary),freeze(automationV110),freeze(automationWhen),freeze(automationAiDecide),freeze(connectApp),freeze(automationContactFields),freeze(automationAiWrite),freeze(automationMetaCreative),freeze(voiceover),freeze(voiceoverV210),freeze(voiceoverV211),freeze(voiceoverV212),freeze(generateVideoV201),freeze(generateVideo),freeze(characterV201),freeze(character),freeze(locationV201),freeze(location),freeze(styleV201),freeze(style),freeze(videoAssets),freeze(videoAssetsV210),freeze(videoAssetsV211),freeze(videoAssetsV212),freeze(roughCut),freeze(finalEdit),freeze(editAssistant),freeze(boardEditor),freeze(boardEditorV210),freeze(boardEditorV220),freeze(boardEditorV221),freeze(boardEditorV222),freeze(boardEditorV223),freeze(boardEditorV224),freeze(boardEditorV225),freeze(boardEditorV226),freeze(boardEditorV227),freeze(boardEditorV228),freeze(panelStripV200),freeze(panelStrip),freeze(panelStripV220),freeze(panelStripV230),freeze(panelStripV231),freeze(panelStripV232),freeze(panelStripV233),freeze(panelStripV234),freeze(panelStripV235),freeze(editor),freeze(editorV210),freeze(editorV220),freeze(editorV221),freeze(editorV222),freeze(editorV223),freeze(editorV224),freeze(editorV225),freeze(editorV226),freeze(editorV227),freeze(pipelineV200),freeze(pipeline),freeze(pipelineV220),freeze(pipelineV221),freeze(pipelineV223),freeze(pipelineV222) ]);
132
142
  export const firstPartySdkPackage = manifest => FIRST_PARTY_SDK_PACKAGES.find(pkg => pkg.manifest.id === manifest?.id && pkg.manifest.version === manifest?.version);
@@ -40,6 +40,19 @@ export const VIDEO_GENERATION_MODELS=Object.freeze({
40
40
  'wan-2.7':video('Wan 2.7',{resolutions:['720p','1080p'],durations:[2,3,4,5,6,7,8,9,10,11,12,13,14,15],lastFrame:true,requiresFirstFrame:true,seed:true}),
41
41
  });
42
42
 
43
+ // Model ids StillMade no longer offers, each pointing at the model that replaced it (owner, November 8, 2026: adding or
44
+ // retiring a model never makes anyone change a Block). A Block or a saved Step that names one runs on its replacement,
45
+ // with the replacement's nearest settings, and its run review shows the model that will run. Never take a model out of
46
+ // the lists above without adding it here: scripts/generation-model-history.test.js holds every id StillMade has offered.
47
+ // 'seedream-4.5' was never offered, but the Editor's own list used it for Seedream 4.5.
48
+ export const RETIRED_IMAGE_MODELS=Object.freeze({'seedream-4.5':'seedream-v4.5'});
49
+ export const RETIRED_VIDEO_MODELS=Object.freeze({});
50
+ const replacement=(models,retired,id)=>{for(let hops=0;typeof id==='string'&&!Object.hasOwn(models,id)&&Object.hasOwn(retired,id)&&hops<8;hops++)id=retired[id];return id;};
51
+ /** The image model that runs for this id: itself, or the model that replaced it. */
52
+ export const currentImageModel=id=>replacement(IMAGE_GENERATION_MODELS,RETIRED_IMAGE_MODELS,id);
53
+ /** The video model that runs for this id: itself, or the model that replaced it. */
54
+ export const currentVideoModel=id=>replacement(VIDEO_GENERATION_MODELS,RETIRED_VIDEO_MODELS,id);
55
+
43
56
  // Axes a model has no knob for are fixed to 'default' so a settings object
44
57
  // always has the same keys and its price stays deterministic.
45
58
  export const DEFAULT_SETTING='default';
@@ -58,14 +71,35 @@ function settingsObject(value,keys,label){
58
71
  return value;
59
72
  }
60
73
 
61
- /** Fill and check image settings. `strict` rejects a value the model does not accept instead of substituting its default. */
74
+ // The settings a Block asked for, by the list each axis is checked against.
75
+ const IMAGE_AXES=Object.freeze({aspectRatio:'aspectRatios',resolution:'resolutions',quality:'qualities'});
76
+ const VIDEO_AXES=Object.freeze({aspectRatio:'aspectRatios',resolution:'resolutions',duration:'durations'});
77
+ const asked=value=>value!==undefined&&value!==DEFAULT_SETTING;
78
+ /** Whether a catalog model takes every setting asked for (an axis left out or 'default' always fits). */
79
+ export function modelTakes(kind,model,settings={}){
80
+ const entry=(kind==='video'?VIDEO_GENERATION_MODELS:IMAGE_GENERATION_MODELS)[model];
81
+ if(!entry||!plain(settings))return false;
82
+ return Object.entries(kind==='video'?VIDEO_AXES:IMAGE_AXES).every(([key,field])=>!asked(settings[key])||entry[field].includes(settings[key]));
83
+ }
84
+
85
+ // November 8, 2026 (owner: "never name a model"): with no model named, a setting the default cannot make picks the first
86
+ // model here that makes it, as video does below. Nano Banana 2 makes every shape and size, so this is quality today.
87
+ export const IMAGE_SHAPE_MODELS=Object.freeze(['gpt-image-2.5-sunburst','gpt-image-2.5-flare','gpt-image-2.6-flare','gpt-image-2.6-sunburst','gpt-image-2','seedream-v4.5','kling-image-v3','nano-banana-pro','gpt-image-1.5']);
88
+ function defaultImageModel(raw){
89
+ if(modelTakes('image',DEFAULT_IMAGE_MODEL,raw))return DEFAULT_IMAGE_MODEL;
90
+ return IMAGE_SHAPE_MODELS.find(model=>IMAGE_GENERATION_MODELS[model]&&modelTakes('image',model,raw))??DEFAULT_IMAGE_MODEL;
91
+ }
92
+
93
+ /** Fill and check image settings. `strict` rejects a value the model does not accept instead of substituting its default.
94
+ * A retired model runs on its replacement with the nearest settings it takes; with no model named, the model is the default,
95
+ * or the first one that takes the settings asked for (IMAGE_SHAPE_MODELS). */
62
96
  export function imageSettings(value,{strict=false}={}){
63
97
  const raw=settingsObject(value,IMAGE_SETTING_KEYS,'Image');
64
- const model=raw.model??DEFAULT_IMAGE_MODEL,entry=IMAGE_GENERATION_MODELS[model];
98
+ const model=raw.model===undefined?defaultImageModel(raw):currentImageModel(raw.model),replaced=raw.model!==undefined&&model!==raw.model,entry=IMAGE_GENERATION_MODELS[model];
65
99
  assert(entry,'CAPABILITY_SETTINGS',`Choose an image model from: ${Object.keys(IMAGE_GENERATION_MODELS).join(', ')}`);
66
100
  const axis=(name,allowed,fallback)=>{
67
101
  const chosen=pick(allowed,raw[name],fallback);
68
- if(strict&&own(raw,name))assert(raw[name]===chosen,'CAPABILITY_SETTINGS',`${entry.label} accepts ${name} ${allowed.length?allowed.join(', '):DEFAULT_SETTING}`);
102
+ if(strict&&!replaced&&own(raw,name))assert(raw[name]===chosen,'CAPABILITY_SETTINGS',`${entry.label} accepts ${name} ${allowed.length?allowed.join(', '):DEFAULT_SETTING}`);
69
103
  return chosen;
70
104
  };
71
105
  return {model,aspectRatio:axis('aspectRatio',entry.aspectRatios,entry.aspectRatios.includes('1:1')?'1:1':undefined),resolution:axis('resolution',entry.resolutions),quality:axis('quality',entry.qualities,entry.qualities.includes('medium')?'medium':undefined)};
@@ -86,11 +120,11 @@ function defaultVideoModel(raw,{firstFrame,lastFrame}){
86
120
  * the one that makes the asked aspect ratio (VIDEO_SHAPE_MODELS). */
87
121
  export function videoSettings(value,{strict=false,firstFrame=false,lastFrame=false}={}){
88
122
  const raw=settingsObject(value,VIDEO_SETTING_KEYS,'Video');
89
- const model=raw.model??defaultVideoModel(raw,{firstFrame,lastFrame}),entry=VIDEO_GENERATION_MODELS[model];
123
+ const model=raw.model===undefined?defaultVideoModel(raw,{firstFrame,lastFrame}):currentVideoModel(raw.model),replaced=raw.model!==undefined&&model!==raw.model,entry=VIDEO_GENERATION_MODELS[model];
90
124
  assert(entry,'CAPABILITY_SETTINGS',`Choose a video model from: ${Object.keys(VIDEO_GENERATION_MODELS).join(', ')}`);
91
125
  const axis=(name,allowed,fallback)=>{
92
126
  const chosen=pick(allowed,raw[name],fallback);
93
- if(strict&&own(raw,name))assert(raw[name]===chosen,'CAPABILITY_SETTINGS',`${entry.label} accepts ${name} ${allowed.length?allowed.join(', '):DEFAULT_SETTING}`);
127
+ if(strict&&!replaced&&own(raw,name))assert(raw[name]===chosen,'CAPABILITY_SETTINGS',`${entry.label} accepts ${name} ${allowed.length?allowed.join(', '):DEFAULT_SETTING}`);
94
128
  return chosen;
95
129
  };
96
130
  const resolution=axis('resolution',entry.resolutions,entry.resolutions.includes('720p')?'720p':undefined);
@@ -101,6 +135,22 @@ export function videoSettings(value,{strict=false,firstFrame=false,lastFrame=fal
101
135
  return {model,duration,resolution,aspectRatio:axis('aspectRatio',entry.aspectRatios,entry.aspectRatios.includes('16:9')?'16:9':undefined),generateAudio:raw.generateAudio??true};
102
136
  }
103
137
 
138
+ /**
139
+ * A Block's settings as StillMade runs them (owner, November 8, 2026: a setting never breaks a run): only the keys the
140
+ * kind has, a retired model mapped forward, a model never offered left out, and a value the model that runs does not
141
+ * make left out so that model's own applies. Running it again changes nothing. Undefined when nothing is left.
142
+ */
143
+ export function runnableSettings(kind,value,{firstFrame=false,lastFrame=false}={}){
144
+ if(!plain(value))return undefined;
145
+ const video=kind==='video',keys=video?VIDEO_SETTING_KEYS:IMAGE_SETTING_KEYS,models=video?VIDEO_GENERATION_MODELS:IMAGE_GENERATION_MODELS;
146
+ const kept=Object.fromEntries(Object.entries(value).filter(([key,item])=>keys.includes(key)&&item!==undefined&&item!==null&&item!==''));
147
+ if(kept.model!==undefined){const model=typeof kept.model==='string'?(video?currentVideoModel(kept.model):currentImageModel(kept.model)):undefined;if(models[model])kept.model=model;else delete kept.model;}
148
+ if(kept.generateAudio!==undefined&&typeof kept.generateAudio!=='boolean')delete kept.generateAudio;
149
+ const runs=video?videoSettings(kept,{firstFrame,lastFrame}):imageSettings(kept);
150
+ for(const key of Object.keys(kept))if(key!=='model'&&runs[key]!==kept[key])delete kept[key];
151
+ return Object.keys(kept).length?kept:undefined;
152
+ }
153
+
104
154
  // Voices, music and sound effects on the app's own generators.
105
155
  export const AUDIO_GENERATION=Object.freeze({
106
156
  speech:Object.freeze({text:3800,defaultVoice:'asteria',speed:Object.freeze({min:.25,max:4,default:1})}),
@@ -0,0 +1,60 @@
1
+ // The models StillMade runs right now, for a Block's own screen: `StillMade.models(operation)` (owner, November 8, 2026:
2
+ // "as I add more models, it all gets updated without updating the Block"). A screen that offers a model picker asks for
3
+ // this list when it opens and draws it, instead of keeping a copy in its code that freezes the day the Block is built.
4
+ // StillMade answers from the same list a Step's model picker shows (the host's /options), so a model added or retired on
5
+ // StillMade appears or disappears on every Block at once. Each model lists its settings as data, so a screen draws a
6
+ // setting StillMade adds later without a new version.
7
+ import {CAPABILITY_RUN_OPERATIONS} from './contracts.js';
8
+ import {IMAGE_GENERATION_MODELS,VIDEO_GENERATION_MODELS,DEFAULT_SETTING,DEFAULT_IMAGE_MODEL,DEFAULT_VIDEO_MODEL,imageSettings,videoSettings} from './generation-catalog.js';
9
+
10
+ /** What a media.generate item's operation asks of the list. */
11
+ export const MEDIA_GENERATE_MODEL_OPERATIONS=Object.freeze({image:'image.generate',video:'video.generate',speech:'audio.speech',music:'audio.music',sfx:'audio.sfx'});
12
+ const SETTING_LABELS=Object.freeze({aspectRatio:'Shape',resolution:'Size',quality:'Quality',duration:'Length (seconds)',generateAudio:'Sound'});
13
+
14
+ /** The operations a Block may ask about: the hosted operations it declares, and what its media actions make. */
15
+ export function modelOperationsFor(manifest){
16
+ const permissions=manifest?.permissions||{},declared=new Set((permissions.capabilities||[]).filter(operation=>CAPABILITY_RUN_OPERATIONS.includes(operation)));
17
+ const actions=permissions.actions||[];
18
+ if(actions.includes('media.generate'))for(const operation of Object.values(MEDIA_GENERATE_MODEL_OPERATIONS))declared.add(operation);
19
+ if(actions.includes('image.generate')||actions.includes('image.generate.batch'))declared.add('image.generate');
20
+ return [...declared];
21
+ }
22
+
23
+ // One catalog model's settings, as data: each setting it takes, its values and the value a run uses when none is chosen.
24
+ function catalogEntry(kind,id){
25
+ const image=kind==='image',entry=(image?IMAGE_GENERATION_MODELS:VIDEO_GENERATION_MODELS)[id];if(!entry)return null;
26
+ const defaults=image?imageSettings({model:id}):videoSettings({model:id});
27
+ const axes=image?[['aspectRatio',entry.aspectRatios],['resolution',entry.resolutions],['quality',entry.qualities]]:[['duration',entry.durations],['resolution',entry.resolutions],['aspectRatio',entry.aspectRatios]];
28
+ const settings=axes.filter(([,values])=>values.length).map(([key,values])=>({key,label:SETTING_LABELS[key],values:[...values],default:defaults[key]===DEFAULT_SETTING?values[0]:defaults[key]}));
29
+ if(!image&&entry.nativeAudio)settings.push({key:'generateAudio',label:SETTING_LABELS.generateAudio,values:[true,false],default:true});
30
+ return image?{settings,references:entry.references}:{settings,firstFrame:entry.firstFrame,lastFrame:entry.lastFrame,needsFirstFrame:entry.requiresFirstFrame,sound:entry.nativeAudio};
31
+ }
32
+ const voiceList=voices=>Array.isArray(voices)?voices.filter(item=>typeof item?.id==='string'&&/^[a-z][a-z0-9_-]{0,63}$/.test(item.id)&&typeof item.label==='string').map(item=>({id:item.id,label:item.label.slice(0,120)})):[];
33
+ const credits=value=>Number.isSafeInteger(value)&&value>=0?value:null;
34
+
35
+ /**
36
+ * The list a screen receives, from the rows the host offers a Step for this operation (/options, after the Step's own
37
+ * filtering): `{operation, default, models:[{id, label, credits, available, settings, ...}]}`. `default` is the model a
38
+ * run uses when the Block names none. Image and video models carry their settings and what they take (reference photos,
39
+ * frames, sound); speech carries its voices.
40
+ */
41
+ export function modelList(operation,rows=[]){
42
+ const image=operation==='image.generate',video=operation==='video.generate',models=[];
43
+ for(const row of Array.isArray(rows)?rows:[]){
44
+ const id=row?.selection?.model;if(typeof id!=='string'||!id||models.some(model=>model.id===id))continue;
45
+ // Image and video come from StillMade's own catalog; a direct provider route (a fixed-size OpenAI picture) is not one.
46
+ const catalog=image||video?row.selection.provider==='stillmade'&&catalogEntry(image?'image':'video',id):null;if((image||video)&&!catalog)continue;
47
+ const fromCredits=credits(row.fromCredits),voices=operation==='audio.speech'?voiceList(row.voices):[];
48
+ models.push({id,label:typeof row.label==='string'&&row.label?row.label.slice(0,120):id,credits:credits(row.creditsPerCall),...(fromCredits!==null?{fromCredits}:{}),available:row.available!==false,...(row.available===false&&typeof row.reason==='string'?{reason:row.reason.slice(0,240)}:{}),...(catalog||{settings:[]}),...(voices.length?{voices}:{})});
49
+ }
50
+ return {operation,default:models.find(model=>model.available)?.id??null,models};
51
+ }
52
+
53
+ /** The same list from the SDK's own catalog, with no prices: the SDK preview and tests, where StillMade is not reached. */
54
+ export function catalogModelList(operation){
55
+ const image=operation==='image.generate',video=operation==='video.generate';
56
+ if(!image&&!video)return {operation,default:null,models:[]};
57
+ const models=image?IMAGE_GENERATION_MODELS:VIDEO_GENERATION_MODELS,first=image?DEFAULT_IMAGE_MODEL:DEFAULT_VIDEO_MODEL;
58
+ const ids=[first,...Object.keys(models).filter(id=>id!==first)];
59
+ return modelList(operation,ids.map(id=>({selection:{provider:'stillmade',model:id},label:models[id].label})));
60
+ }
@@ -275,6 +275,7 @@ export interface VideoGenerationModel {label:string;aspectRatios:readonly string
275
275
  export interface ImageSettings {model:string;aspectRatio:string;resolution:string;quality:string}
276
276
  export interface VideoSettings {model:string;duration:number;resolution:string;aspectRatio:string;generateAudio:boolean}
277
277
  /** Every image model a Block can use; the same catalog as the app's own Blocks. */
278
+ /** The image and video models this SDK version knew when it was published. A Block's screen lists the models StillMade runs now with `StillMade.models(operation)`; never copy this list into a Block. */
278
279
  export const IMAGE_GENERATION_MODELS: Readonly<Record<string,ImageGenerationModel>>;
279
280
  /** Every video model a Block can use; the same catalog as the app's own Blocks. */
280
281
  export const VIDEO_GENERATION_MODELS: Readonly<Record<string,VideoGenerationModel>>;
@@ -288,6 +289,19 @@ export function imageSettings(value?:Partial<ImageSettings>,options?:{strict?:bo
288
289
  /** Fill and check video settings, including Veo's 1080p-is-8-seconds rule. */
289
290
  /** With no model named, the default is the cheapest model that makes the asked aspect ratio and takes the frames given. */
290
291
  export function videoSettings(value?:Partial<VideoSettings>,options?:{strict?:boolean;firstFrame?:boolean;lastFrame?:boolean}):VideoSettings;
292
+ /** Model ids StillMade replaced, each pointing at its replacement: a Block or Step that names one runs on the replacement. */
293
+ export const RETIRED_IMAGE_MODELS: Readonly<Record<string,string>>;
294
+ export const RETIRED_VIDEO_MODELS: Readonly<Record<string,string>>;
295
+ /** The image model that runs for this id: itself, or the model that replaced it. */
296
+ export function currentImageModel(id:string):string;
297
+ /** The video model that runs for this id: itself, or the model that replaced it. */
298
+ export function currentVideoModel(id:string):string;
299
+ /** Whether a catalog model takes every setting asked for (a setting left out or 'default' always fits). */
300
+ export function modelTakes(kind:'image'|'video',model:string,settings?:Record<string,unknown>):boolean;
301
+ /** The hosted operations a Block may list models for: those it declares, and what its media actions make. */
302
+ export function modelOperationsFor(manifest:unknown):string[];
303
+ /** StillMade.models' answer from this SDK's own catalog, without prices (the SDK preview and tests). */
304
+ export function catalogModelList(operation:string):{operation:string;default:string|null;models:Array<{id:string;label:string;credits:number|null;available:boolean;settings:Array<{key:string;label:string;values:Array<string|number|boolean>;default:string|number|boolean}>;[key:string]:unknown}>};
291
305
  export function validateSeed(seed:number):number;
292
306
  export const MEDIA_GENERATE_LIMITS: Readonly<{items:50;prompt:4000}>;
293
307
  export const MEDIA_GENERATE_OPERATIONS: readonly ['image','video','speech','music','sfx'];
@@ -652,7 +666,7 @@ export function moduleRunClock(now?: () => number): Readonly<{progress(): void;
652
666
  export interface ModuleHost {
653
667
  input: Record<string, unknown>;
654
668
  signal: AbortSignal;
655
- progress(value: number, label?: string): void;
669
+ progress(value: number, label?: string, items?: {done: number; total: number}): void;
656
670
  log(...values: unknown[]): void;
657
671
  /** A copy of one of the module's own files, such as a .wasm build. */
658
672
  asset(path: string): ArrayBuffer;
@@ -39,7 +39,8 @@ export {isTimelineEdit} from './timeline-edits.js';
39
39
 
40
40
  export {isWorkspaceEdit,WORKSPACE_EDIT_FIELDS} from './workspace-edits.js';
41
41
  export {portableMediaAction,PORTABLE_MEDIA_ACTION_KIND,mediaGenerateRequest,MEDIA_GENERATE_LIMITS,MEDIA_GENERATE_OPERATIONS} from './portable-media-actions.js';
42
- export {IMAGE_GENERATION_MODELS,VIDEO_GENERATION_MODELS,DEFAULT_IMAGE_MODEL,DEFAULT_VIDEO_MODEL,DEFAULT_SETTING,GENERATION_LIMITS,AUDIO_GENERATION,imageSettings,videoSettings,audioSettings,validateSeed} from './generation-catalog.js';
42
+ export {IMAGE_GENERATION_MODELS,VIDEO_GENERATION_MODELS,DEFAULT_IMAGE_MODEL,DEFAULT_VIDEO_MODEL,DEFAULT_SETTING,GENERATION_LIMITS,AUDIO_GENERATION,imageSettings,videoSettings,audioSettings,validateSeed,RETIRED_IMAGE_MODELS,RETIRED_VIDEO_MODELS,currentImageModel,currentVideoModel,modelTakes} from './generation-catalog.js';
43
+ export {modelOperationsFor,catalogModelList} from './generation-models.js';
43
44
  export {JSON_SCHEMA_LIMITS,validateJsonSchema,jsonSchemaMismatch,jsonSchemaInstruction,parseJsonReply} from './json-schema.js';
44
45
  export {MODULE_RUNTIME_VERSION,MODULE_LIMITS,MODULE_MEDIA_TYPES,MODULE_HOST_API,validateModuleDescriptor,moduleFileDigest,describeModuleFiles,moduleMediaType,verifyModuleFile,moduleDeadline,moduleRunClock} from './module-runtime.js';
45
46
  export {MEDIA_TRANSFORM_LIMITS,MEDIA_TRANSFORM_OPERATIONS,mediaTransformRequest,mediaTransformSources,silentRanges,keepRanges,integratedLoudness,loudnessGain,loudnessMeter,gainFor,wavWriter,encodeWav,decodeWav} from './media-transform.js';
@@ -16,6 +16,7 @@ export function assertSuppliedInputSources(manifest,config,supplied,{connected=[
16
16
  if(port.context || connected.includes(name) && name!==selectedPrimary)continue;
17
17
  const sameSetting=Object.hasOwn(config,name) && Object.hasOwn(supplied,name) && stableStringify(config[name])===stableStringify(supplied[name]);
18
18
  if(name===selectedPrimary || Object.hasOwn(supplied,name) && !sameSetting)assertInputSource(port,'user',name);
19
- else if(Object.hasOwn(config,name))assert(inputSourceAllowed(port,'user') || inputSourceAllowed(port,'default'),'INPUT_SOURCE',`${name} requires a connected or resolved source, not a saved answer.`);
19
+ // A value the Block's own screen passed in its last run is kept with the Step like any run input.
20
+ else if(Object.hasOwn(config,name))assert(inputSourceAllowed(port,'user') || inputSourceAllowed(port,'default') || inputSourceAllowed(port,'view'),'INPUT_SOURCE',`${name} requires a connected or resolved source, not a saved answer.`);
20
21
  }
21
22
  }