stillmade-sdk 0.1.44 → 0.1.45

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.
@@ -33,14 +33,18 @@ The host provides `StillMade` inside the sandbox:
33
33
  | `await StillMade.run(values)` | Validates the named input bindings, runs the package in the isolated computation worker, validates outputs, and resolves to the output bindings object |
34
34
 
35
35
  `admitPackage` and local admission include a **Shared interface contract** source
36
- check. The lightest path for an ordinary form control is one HTML annotation:
37
- `<textarea id="draft" data-stillmade-share="draft"></textarea>`. The host turns
38
- that into the same checked, permission-aware binding as `bindShared`; the Block
39
- does not implement networking, cursors, batching, conflict transport or retries.
40
- A `connectDocument` content-store connection supplies the platform-owned
41
- receive/publication paths for an existing structured store. Otherwise an
42
- `onShared` subscription is required, and detected editable controls must call
43
- `bindShared` or `updateShared`. This is a minimum static prerequisite,
36
+ check. Multiplayer is built for a Block (November 4, 2026): StillMade shares
37
+ every ordinary control on its screen by itself (text, number, range, checkbox,
38
+ color, select, textarea and a kit choice group), under the control's
39
+ `data-stillmade-share`, else its `name`, else its `id`, including controls the
40
+ screen adds later, and releases a control when it leaves the page. The Block
41
+ writes no sharing code and does not implement networking, cursors, batching,
42
+ conflict transport or retries. `data-stillmade-local` on a control, or on an
43
+ element around several, keeps them on this device. A view that publishes its
44
+ own structured state (`updateShared` or a `connectDocument` content store)
45
+ shares its controls itself: StillMade steps aside for it, and the check then
46
+ asks for an `onShared` subscription (or the `connectDocument` connection) so
47
+ teammates' edits arrive. This is a minimum static prerequisite,
44
48
  not full collaboration verification. Its `runtimeVerified` remains false even
45
49
  when it passes. Calls may be unreachable or fail to cover all controls; actual
46
50
  two-participant acceptance remains required. Runtime safety checks for already
@@ -235,14 +239,12 @@ fresh review. An acknowledged reset may already have cleared the old shared
235
239
  overlay when a subsequent local/source change is detected; the adapter reports
236
240
  that failure and requires a new explicit confirmation. Reset never runs a Block.
237
241
 
238
- For an ordinary static editable control, prefer
239
- `data-stillmade-share="title"` on the element and give it a unique `id`. No
240
- authored JavaScript adapter is needed. For a dynamically created or replaced
241
- control, call `StillMade.bindShared('title','title-input')` once after its element
242
- exists. Mark a genuinely device-only playback/filter control with
243
- `data-stillmade-local`; admission rejects an unexplained static control when the
244
- interface does not use a structured `updateShared` or `connectDocument` adapter.
245
- Both shared forms use the same host-owned binding and share a text, numeric,
242
+ An ordinary editable control needs nothing: StillMade shares it under its
243
+ `name` or `id` (give it one; a control with neither stays on this device).
244
+ `data-stillmade-share="title"` names its shared field explicitly, and
245
+ `StillMade.bindShared('title','title-input')` shares a control under another
246
+ field name. Mark a genuinely device-only playback/filter control with
247
+ `data-stillmade-local`. Every shared form uses the same host-owned binding and share a text, numeric,
246
248
  checkbox, or select value with a 150 ms trailing debounce, a 500 ms maximum
247
249
  batch wait during continuous typing, and a blur flush. Unchanged/duplicate native
248
250
  events do not publish or restart the delay. An edit already in flight must settle
@@ -236,7 +236,9 @@ exception, never unrelated technical UI.
236
236
  from the inputs given so far, not an empty box. `data-layout="stack"`
237
237
  keeps one column. StillMade zooms a view to fill its space and centers a
238
238
  standard view narrower than its Step (by its margins only, so a margin
239
- the view sets wins). An app that lays itself out (full-size canvas or
239
+ the view sets wins). A `sm-stage` shows what it holds in its middle as
240
+ one group (a caption stays with its picture), and several results in a
241
+ stage fill its width as one row; the pages StillMade draws do the same. An app that lays itself out (full-size canvas or
240
242
  WebGL, its own scroll, pan or zoom, its own Ctrl/Cmd +/- keys) puts
241
243
  `data-stillmade-layout="own"` on its outermost element, and StillMade
242
244
  then never centers, zooms or takes those keys. StillMade plays every
@@ -85,7 +85,7 @@ Optional custom view interfaces are supported, but generated manifest.ui control
85
85
 
86
86
  *When:* StillMade's builder adds this section when the person asks for an interface, preview or controls, or refines a Block that has one.
87
87
 
88
- A custom interface is the optional package-level view:{html,css?,themedCss?,javascript?}; when refining, preserve and refine base.view. Design it by StillMade's interface principles, in order: Function first: unless the person asks for a design direction, build from the Block kit (`sm-block`, `sm-stage`, `sm-choices`, `sm-actions`, `sm-more`) and ship little or no CSS of your own. A direction the person names wins over the kit. Show the thing being made: the preview, media or result gets the largest area (`sm-stage`), and every part shows at once: before the first run the result's place is drawn in the shape and size of what is coming (a 9:16 frame for a vertical clip, lines for text, a player bar for sound), so the result fills it instead of popping in. On a wide screen what goes in sits on the left and the result on the right; they stack only on narrow screens and phones. A picture or clip the Block makes fills it in its own shape, as large as fits; several fill it as a grid, each big enough to watch. Small tiles are for the files a person gives the Block, never for what it makes. A clip is a `<video controls>`, which StillMade plays in its own player with full screen, and a picture in `sm-stage` or `sm-gallery` (or marked `data-stillmade-expand`) gets StillMade's full-screen button; leave out `controls` only when the Block drives playback itself. Never a JSON dump, source editor, manifest or diagnostic panel unless the task is about source. One obvious next action: one solid `sm-primary` in the kit's ink, never restyled to the background or a ghost outline; peers are `sm-quiet` buttons or named icons, and close is an X icon. Seven or fewer choices in view. Speak the job unless the person or creator asks otherwise. The action button names what it does in the job's words (`manifest.action`: "Plan the tour"; AI pictures, video and sound start with Generate and say what, the person's own media cut or rendered says Make: "Generate voiceover", "Make 6 clips"), never "Make it", "Run", "Run step", "Submit", "Process" or a bare "Generate"; progress counts the job's units ("4 of 9 rooms"), never "Processing"; results and problems use its words in one line. Show no credit numbers: StillMade puts the price beside the verb on its own run button ("Write · 1 credit"), with a chip for the model, quality or count when they can change. Pressing it runs at that price; a confirmation appears only when the server price is higher than the one shown. Pictures over words: choices people see or hear are thumbnails, swatches, samples or glyphs (`sm-choices`), not word lists. Icons carry an accessible name and tooltip; never emoji. A picked option reads at a glance: keep the kit's ink fill on an `sm-choices` pick, never restyled to an outline, a tint or the page's background. Media that cannot show here (on another device, or it did not open) keeps its place as a quiet `sm-media-off` tile in its own shape, never words in its place. Words only when needed. Drop what nobody needs to finish the task (intros, "how it works", restating the Block name). Words that might help start collapsed (`sm-hint`, `sm-more`). Errors, spending approvals, stale inputs and destructive steps stay visible, in few words. Strong defaults; secondary settings go in `sm-more`, closed. Compact: the whole Block fits one laptop screen without scrolling. A short setting is one row, its label beside the control; no card around a single control. Status is a dot or a few words ("No narration yet"), never a paragraph; the reason sits behind an `sm-hint`. Files are tiles: a saved file is a thumbnail or chip and adding one is the + tile (`<button class="sm-add" data-stillmade-add="<input>">`), which opens StillMade's picker. Never an `<input type="file">` or the bare browser file picker, and no size limits in view until a file is too big. Rare tools (update, schedule, history, reload) are named icon buttons beside the action, never sections under the work. An (i) beside anything a newcomer could misread: a setting, status or number whose effect is not obvious from its label gets a closed `sm-hint` with one or two sentences. Give every input that needs one a `description`; StillMade shows it as that (i). Obvious controls get none. One workspace: no cards inside cards, no second title, step counter or Back/Next bar; the host owns navigation. One page per job (November 3, 2026): a guided job's screen holds the inputs and settings, then a cheap check before the expensive run (the script and a voice sample before the video, a second `StillMade.run` that StillMade prices on its own), then the result; never a second Block or Step for the check. A workspace a person works in (a CAD tool, a canvas, an editor) is its own Block, and turning what it made into something else is the next one. Status sits beside the action it describes (`sm-status`), in a few words. Keep the page in normal scroll flow; no sticky media or control strips unless the editing task needs them. Use the width: from 860px wide the kit puts the controls on the left at a reading width (300–380px) and the result (`sm-stage`) fills the right, in its real shape; under that everything stacks in the page's order: the inputs, the action in normal flow (never pinned), then the result's place under it, so put `sm-stage` after `sm-actions`. Extra width goes to the result, never to wider text fields. Before there is a result, the stage previews what will be made from the inputs. `data-layout="stack"` opts out. Size with the kit, never fixed sizes: no fixed pixel widths or heights for the workspace or stage, no 100vh, no position:fixed panels. StillMade zooms a Block's screen to fill the space it has (up to 150%, never down to a phone layout on a computer), so a Block laid out with the kit's spacing fills its Step without empty space, and it centers a standard view narrower than its Step. An app that lays itself out (full-size canvas or WebGL, its own scroll, pan or zoom, its own Ctrl/Cmd +/- keys) puts `data-stillmade-layout="own"` on its outermost element, and StillMade then never centers, zooms or takes those keys. Group what the person sets in one `sm-fields` (or `sm-settings` for short ones): the kit gives the group its own surface, so the inputs, the settings and the result read as separate parts. Settings sit side by side (`sm-settings`), next to what they change. Three or four short options are one segmented row (`data-segmented`), not a row of big tiles. Lists fill the width at a small size: input files are small tiles (`sm-gallery data-size="small"`), many items are one line each (`sm-list`): a thumbnail, one line of text, a short fact. Tapping an item opens all of its fields in place. Extra width means more items per row, never bigger cards. On a phone chip and segment rows scroll sideways instead of wrapping; long lists show their first three with "N more" (`data-peek`) and long text five lines until tapped (`sm-clamp`). Nothing is lost when a Block gets smaller: every control stays on the screen, behind an (i), in `sm-more`, or inside the item it belongs to. Compacting moves controls; it never deletes them. Text scene descriptions are planning notes, never a storyboard or an approved frame. Say nothing about how the Block works: no notes on where it runs (this device, StillMade Desktop, the cloud), saving, syncing, recovery, accounts, credits or where a file is kept. StillMade tells the person what they need as one short notification, or nothing; never a sentence on the page, and never the same line under each item. References fill your inputs: every drop area in StillMade offers "Use a reference", and the person's saved photos, videos and notes fill the Step with no typing. Media inputs fill by kind, so take `video` or `asset` wherever a clip works as well as a photo; text inputs fill by plain names and labels (`productName`, `website`, `brandColors`, `fonts`, `voice`, and `details` or `notes` for the reference's note). Never build your own reference picker or library upload. Days and times are picked, never typed: a `date` input is a calendar, with a list of times when it is about a time (`postAt`, `startsAt`, a label with Time, When or Starts). In a custom view use `<input type="date">` or `type="datetime-local"`, never a text box for a date. Posting is its own Step: sending a result to Instagram, TikTok, email or any app is a connector Step after the Block, worded by StillMade ("Post to Instagram"). A Block never posts from inside itself. Its DOM controller is separate from manifest runtime code. The view may also declare appearance:"original" or "host". For an imported design, preserve its colors with appearance:"original" and view.css; no themed replacement is required. Preserve base.view.appearance when refining unless the user requests a change. Omitted/host appearance retains the existing Light, Dark and White requirement: supply semantic colors in view.css or a complete replacement view.themedCss. An original-style view may optionally supply valid themedCss for a StillMade appearance preview. Use StillMade variables (--bg,--fg,--fg-muted,--border-tok,--r-3) only when opting into host styling; do not redefine host tokens in the themed variant. Appearance never relaxes sandbox constraints: no CSS escapes/filters/blending, imports/url(), or element.style mutations in JS. Use classes for UI state; media pixels retain their colors. Both CSS fields have the same 32 KiB limit; all existing shared-state, security and permission checks still apply. Declare elements in html, bind with document.getElementById/querySelector and addEventListener; use literal property access, array methods, textContent, input.value, button.disabled and canvas. Initial values: StillMade.onInput(values=>{...}); await StillMade.run(inputBindings) returns validated output bindings from the isolated package runtime; catch errors and show results. No computation in the DOM controller; put it in code/recipe. Disable the action button while pending.
88
+ A custom interface is the optional package-level view:{html,css?,themedCss?,javascript?}; when refining, preserve and refine base.view. Design it by StillMade's interface principles, in order: Function first: unless the person asks for a design direction, build from the Block kit (`sm-block`, `sm-stage`, `sm-choices`, `sm-actions`, `sm-more`) and ship little or no CSS of your own. A direction the person names wins over the kit. Show the thing being made: the preview, media or result gets the largest area (`sm-stage`), and every part shows at once: before the first run the result's place is drawn in the shape and size of what is coming (a 9:16 frame for a vertical clip, lines for text, a player bar for sound), so the result fills it instead of popping in. On a wide screen what goes in sits on the left and the result on the right; they stack only on narrow screens and phones. A picture or clip the Block makes fills it in its own shape, as large as fits; several fill it as a grid, each big enough to watch. Small tiles are for the files a person gives the Block, never for what it makes. A clip is a `<video controls>`, which StillMade plays in its own player with full screen, and a picture in `sm-stage` or `sm-gallery` (or marked `data-stillmade-expand`) gets StillMade's full-screen button; leave out `controls` only when the Block drives playback itself. Never a JSON dump, source editor, manifest or diagnostic panel unless the task is about source. One obvious next action: one solid `sm-primary` in the kit's ink, never restyled to the background or a ghost outline; peers are `sm-quiet` buttons or named icons, and close is an X icon. Seven or fewer choices in view. Speak the job unless the person or creator asks otherwise. The action button names what it does in the job's words (`manifest.action`: "Plan the tour"; AI pictures, video and sound start with Generate and say what, the person's own media cut or rendered says Make: "Generate voiceover", "Make 6 clips"), never "Make it", "Run", "Run step", "Submit", "Process" or a bare "Generate"; progress counts the job's units ("4 of 9 rooms"), never "Processing"; results and problems use its words in one line. Show no credit numbers: StillMade puts the price beside the verb on its own run button ("Write · 1 credit"), with a chip for the model, quality or count when they can change. Pressing it runs at that price; a confirmation appears only when the server price is higher than the one shown. Pictures over words: choices people see or hear are thumbnails, swatches, samples or glyphs (`sm-choices`), not word lists. Icons carry an accessible name and tooltip; never emoji. A picked option reads at a glance: keep the kit's ink fill on an `sm-choices` pick, never restyled to an outline, a tint or the page's background. Media that cannot show here (on another device, or it did not open) keeps its place as a quiet `sm-media-off` tile in its own shape, never words in its place. Words only when needed. Drop what nobody needs to finish the task (intros, "how it works", restating the Block name). Words that might help start collapsed (`sm-hint`, `sm-more`). Errors, spending approvals, stale inputs and destructive steps stay visible, in few words. Strong defaults; secondary settings go in `sm-more`, closed. Compact: the whole Block fits one laptop screen without scrolling. A short setting is one row, its label beside the control; no card around a single control. Status is a dot or a few words ("No narration yet"), never a paragraph; the reason sits behind an `sm-hint`. Files are tiles: a saved file is a thumbnail or chip and adding one is the + tile (`<button class="sm-add" data-stillmade-add="<input>">`), which opens StillMade's picker. Never an `<input type="file">` or the bare browser file picker, and no size limits in view until a file is too big. Rare tools (update, schedule, history, reload) are named icon buttons beside the action, never sections under the work. An (i) beside anything a newcomer could misread: a setting, status or number whose effect is not obvious from its label gets a closed `sm-hint` with one or two sentences. Give every input that needs one a `description`; StillMade shows it as that (i). Obvious controls get none. One workspace: no cards inside cards, no second title, step counter or Back/Next bar; the host owns navigation. One page per job (November 3, 2026): a guided job's screen holds the inputs and settings, then a cheap check before the expensive run (the script and a voice sample before the video, a second `StillMade.run` that StillMade prices on its own), then the result; never a second Block or Step for the check. A workspace a person works in (a CAD tool, a canvas, an editor) is its own Block, and turning what it made into something else is the next one. Status sits beside the action it describes (`sm-status`), in a few words. Keep the page in normal scroll flow; no sticky media or control strips unless the editing task needs them. Use the width: from 860px wide the kit puts the controls on the left at a reading width (300–380px) and the result (`sm-stage`) fills the right, in its real shape; under that everything stacks in the page's order: the inputs, the action in normal flow (never pinned), then the result's place under it, so put `sm-stage` after `sm-actions`. Extra width goes to the result, never to wider text fields. Before there is a result, the stage previews what will be made from the inputs. `data-layout="stack"` opts out. Size with the kit, never fixed sizes: no fixed pixel widths or heights for the workspace or stage, no 100vh, no position:fixed panels. StillMade zooms a Block's screen to fill the space it has (up to 150%, never down to a phone layout on a computer), so a Block laid out with the kit's spacing fills its Step without empty space, and it centers a standard view narrower than its Step. The kit's `sm-stage` shows what it holds in its middle as one group (a caption stays with its picture), and several results in a stage fill its width as one row; the pages StillMade draws follow the same rule. An app that lays itself out (full-size canvas or WebGL, its own scroll, pan or zoom, its own Ctrl/Cmd +/- keys) puts `data-stillmade-layout="own"` on its outermost element, and StillMade then never centers, zooms or takes those keys. Group what the person sets in one `sm-fields` (or `sm-settings` for short ones): the kit gives the group its own surface, so the inputs, the settings and the result read as separate parts. Settings sit side by side (`sm-settings`), next to what they change. Three or four short options are one segmented row (`data-segmented`), not a row of big tiles. Lists fill the width at a small size: input files are small tiles (`sm-gallery data-size="small"`), many items are one line each (`sm-list`): a thumbnail, one line of text, a short fact. Tapping an item opens all of its fields in place. Extra width means more items per row, never bigger cards. On a phone chip and segment rows scroll sideways instead of wrapping; long lists show their first three with "N more" (`data-peek`) and long text five lines until tapped (`sm-clamp`). Nothing is lost when a Block gets smaller: every control stays on the screen, behind an (i), in `sm-more`, or inside the item it belongs to. Compacting moves controls; it never deletes them. Text scene descriptions are planning notes, never a storyboard or an approved frame. Say nothing about how the Block works: no notes on where it runs (this device, StillMade Desktop, the cloud), saving, syncing, recovery, accounts, credits or where a file is kept. StillMade tells the person what they need as one short notification, or nothing; never a sentence on the page, and never the same line under each item. References fill your inputs: every drop area in StillMade offers "Use a reference", and the person's saved photos, videos and notes fill the Step with no typing. Media inputs fill by kind, so take `video` or `asset` wherever a clip works as well as a photo; text inputs fill by plain names and labels (`productName`, `website`, `brandColors`, `fonts`, `voice`, and `details` or `notes` for the reference's note). Never build your own reference picker or library upload. Days and times are picked, never typed: a `date` input is a calendar, with a list of times when it is about a time (`postAt`, `startsAt`, a label with Time, When or Starts). In a custom view use `<input type="date">` or `type="datetime-local"`, never a text box for a date. Posting is its own Step: sending a result to Instagram, TikTok, email or any app is a connector Step after the Block, worded by StillMade ("Post to Instagram"). A Block never posts from inside itself. Its DOM controller is separate from manifest runtime code. The view may also declare appearance:"original" or "host". For an imported design, preserve its colors with appearance:"original" and view.css; no themed replacement is required. Preserve base.view.appearance when refining unless the user requests a change. Omitted/host appearance retains the existing Light, Dark and White requirement: supply semantic colors in view.css or a complete replacement view.themedCss. An original-style view may optionally supply valid themedCss for a StillMade appearance preview. Use StillMade variables (--bg,--fg,--fg-muted,--border-tok,--r-3) only when opting into host styling; do not redefine host tokens in the themed variant. Appearance never relaxes sandbox constraints: no CSS escapes/filters/blending, imports/url(), or element.style mutations in JS. Use classes for UI state; media pixels retain their colors. Both CSS fields have the same 32 KiB limit; all existing shared-state, security and permission checks still apply. Declare elements in html, bind with document.getElementById/querySelector and addEventListener; use literal property access, array methods, textContent, input.value, button.disabled and canvas. Initial values: StillMade.onInput(values=>{...}); await StillMade.run(inputBindings) returns validated output bindings from the isolated package runtime; catch errors and show results. No computation in the DOM controller; put it in code/recipe. Disable the action button while pending.
89
89
  Standard custom view code (no `view.config.json` frame runtime) is checked before it runs. These rules come straight from the validator; frame views run in their own sandbox and may build the page with ordinary DOM calls such as `createElement`:
90
90
 
91
91
  - **HTML elements:** only `div`, `section`, `article`, `header`, `footer`, `main`, `aside`, `nav`, `h1`, `h2`, `h3`, `h4`, `p`, `span`, `strong`, `em`, `i`, `small`, `br`, `hr`, `label`, `input`, `textarea`, `button`, `select`, `option`, `optgroup`, `fieldset`, `legend`, `output`, `progress`, `meter`, `ul`, `ol`, `li`, `dl`, `dt`, `dd`, `figure`, `figcaption`, `img`, `video`, `audio`, `source`, `canvas`, `table`, `thead`, `tbody`, `tfoot`, `tr`, `th`, `td`, `pre`, `code`, `details`, `summary`. No scripts, iframes, SVG, forms, comments or inline event attributes; media URLs must be `data:` or `blob:`.
package/docs/tools/llm.md CHANGED
@@ -83,15 +83,15 @@ Name each input for what the person brings, in the job's words: `sermonVideo` fo
83
83
 
84
84
  ### Speak the job
85
85
 
86
- These are StillMade's defaults: unless the person or the creator asked for something else, every screen speaks the job in its own words and adds nothing else. `manifest.action` names the Step's button, {label:"Plan the tour", running:"Planning the tour…"}, and StillMade adds the price ("Plan the tour · 1 credit"). Every Block whose Step shows StillMade's button declares it: any Block without its own screen, and any that calls operations. Without it StillMade can only guess from the Block's name or output ("Make clips"), and the readiness gate and StillMade's own builder send the Block back until it names the job. When the Block makes a picture, video or sound with AI the label starts with Generate and says what ("Generate tour", "Generate voiceover"); one that cuts or renders the person's own media says Make ("Make 6 clips"); never "Make it", "Run", "Submit", "Process" or a bare "Generate". The main button stands out: one solid `sm-primary` in the kit's ink, never restyled to the background; everything else is `sm-quiet` or an `sm-icon`, and close is an X icon. Report progress in the job's units with `stillmade.progress(fraction, label)` ("Moving through the kitchen, 4 of 9 rooms"), never "Processing". Head the result in the job's words ("9 rooms", "Your tour · 44 s"), never "Output". Say a problem in one line with its fix beside it ("No bathroom photo. Guests look for one." and Add photo). Stay quiet: no welcome text, tips under fields, tours or banners, and at most one line of guidance on a screen, only where the next move is not obvious; anything more goes in the input's `description`, which StillMade shows behind an (i). Say nothing about how the Block works (where it runs, saving, syncing, recovery, accounts, credits) or where a file is kept: StillMade tells the person what they need as one short notification, or nothing; never a sentence on the page, and never the same line under each item. Every drop area offers "Use a reference", which fills the Step from the person's saved photos, videos and notes: media inputs fill by kind, so take `video` or `asset` where a clip works as well as a photo, and text inputs fill by plain names and labels (`productName`, `website`, `brandColors`, `fonts`, `voice`, and `details` or `notes` for the reference's note); never build your own reference picker. Days and times are picked, never typed: use the `date` type, which StillMade shows as a calendar, with a list of times when the input is about a time (`postAt`, `startsAt`); a custom view uses `<input type="date">` or `type="datetime-local"`. Posting is a connector Step of its own, and StillMade words its button from the app and action ("Post to Instagram"). Before handing the Block back, clear every Wording note that `gate` and readiness list, unless the person asked for those words.
86
+ These are StillMade's defaults: unless the person or the creator asked for something else, every screen speaks the job in its own words and adds nothing else. `manifest.action` names the Step's button, {label:"Plan the tour", running:"Planning the tour…"}, and StillMade adds the price ("Plan the tour · 1 credit"). Every Block whose Step shows StillMade's button declares it: any Block without its own screen, and any that calls operations. Without it StillMade can only guess from the Block's name or output ("Make clips"), and the readiness gate and StillMade's own builder send the Block back until it names the job. When the Block makes a picture, video or sound with AI the label starts with Generate and says what ("Generate tour", "Generate voiceover"); one that cuts or renders the person's own media says Make ("Make 6 clips"); never "Make it", "Run", "Submit", "Process" or a bare "Generate". The main button stands out: one solid `sm-primary` in the kit's ink, never restyled to the background; everything else is `sm-quiet` or an `sm-icon`, and close is an X icon. Report progress in the job's units with `stillmade.progress(fraction, label)` ("Moving through the kitchen, 4 of 9 rooms"), never "Processing". Head the result in the job's words ("9 rooms", "Your tour · 44 s"), never "Output". Say a problem in one line with its fix beside it ("No bathroom photo. Guests look for one." and Add photo). A thrown error says what to add or change, never which button to press ("Add the photos to move, 1 to 12.", not "then press Make clips"): StillMade words the button, and on a one-page job it is the job's. Stay quiet: no welcome text, tips under fields, tours or banners, and at most one line of guidance on a screen, only where the next move is not obvious; anything more goes in the input's `description`, which StillMade shows behind an (i). Say nothing about how the Block works (where it runs, saving, syncing, recovery, accounts, credits) or where a file is kept: StillMade tells the person what they need as one short notification, or nothing; never a sentence on the page, and never the same line under each item. Every drop area offers "Use a reference", which fills the Step from the person's saved photos, videos and notes: media inputs fill by kind, so take `video` or `asset` where a clip works as well as a photo, and text inputs fill by plain names and labels (`productName`, `website`, `brandColors`, `fonts`, `voice`, and `details` or `notes` for the reference's note); never build your own reference picker. Days and times are picked, never typed: use the `date` type, which StillMade shows as a calendar, with a list of times when the input is about a time (`postAt`, `startsAt`); a custom view uses `<input type="date">` or `type="datetime-local"`. Posting is a connector Step of its own, and StillMade words its button from the app and action ("Post to Instagram"). Before handing the Block back, clear every Wording note that `gate` and readiness list, unless the person asked for those words.
87
87
 
88
88
  ### Interface
89
89
 
90
- If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, drawn before the first run in the shape and size of what is coming (`data-ratio="9:16"` for a vertical clip, lines for text, a player bar for sound) so every part of the screen is in place at once and the result fills a place the person already sees instead of popping in somewhere unexpected; StillMade draws it the same way on the Steps it draws; on a wide screen what goes in sits on the left and the result on the right, side by side, and they stack only on narrow screens and phones, the result's place 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.
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; 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 while the pointer is on the video, with keys and touch) and a full-screen button on result pictures in `sm-stage` or `sm-gallery` and on any element marked `data-stillmade-expand` (the same value makes one set of pictures, `off` removes it); `data-stillmade-player="native"` keeps the browser's controls, and a `<video>` without `controls` is the Block's own and is never touched. A picture or clip that cannot show here (kept on another device, or it did not open) keeps its place as a quiet `sm-media-off` tile in its own shape with the `image-off` glyph and the reason in its label, never words in its place. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Mark each button `data-stillmade-action` (`run` on the one that runs the Block, which the device checks press once; a page that checks first keeps `run` on that button for both presses; what it does on the others, `"play sample"`) and label each field: Chat presses and fills them. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Show no raw JSON, internal IDs, filesystem details, provider terms or developer settings unless the Block is a developer tool. When a person chooses among options they need to hear or see (voices, music, fonts, looks, templates, images), let them preview each option before choosing: a sample or thumbnail from the Block's own files or the host's free samples, never a paid generation. Skip previews only when there is nothing to preview. Choices StillMade owns (voices, models in the run review) already show samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (the inputs, the button in normal flow, never pinned, then the result's place under it), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.
91
91
 
92
92
  ### Shared editing
93
93
 
94
- Projects are multiplayer. A custom view subscribes through `StillMade.onShared(callback)` and publishes every editable field: give static controls a unique `id` and `data-stillmade-share="field"` (the host owns their synchronization), use `StillMade.bindShared(field, elementId)` for controls created dynamically, and `StillMade.updateShared(values, before, options)` for structured values; private variables and DOM values are not shared. Mark a truly device-only control (playback, hover, device selection) with `data-stillmade-local`. Observe shared outputs as well as state, honor `canEdit` in every mutating callback (read-only participants cannot write), never run, click or call a paid integration in response to a remote update, and preserve conflicting drafts. Shared background work uses `sharedWork` with the `work.shared` permission.
94
+ 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.
95
95
 
96
96
  ### External services
97
97
 
@@ -257,7 +257,7 @@ Optional custom view interfaces are supported, but generated manifest.ui control
257
257
 
258
258
  *When:* StillMade's builder adds this section when the person asks for an interface, preview or controls, or refines a Block that has one.
259
259
 
260
- A custom interface is the optional package-level view:{html,css?,themedCss?,javascript?}; when refining, preserve and refine base.view. Design it by StillMade's interface principles, in order: Function first: unless the person asks for a design direction, build from the Block kit (`sm-block`, `sm-stage`, `sm-choices`, `sm-actions`, `sm-more`) and ship little or no CSS of your own. A direction the person names wins over the kit. Show the thing being made: the preview, media or result gets the largest area (`sm-stage`), and every part shows at once: before the first run the result's place is drawn in the shape and size of what is coming (a 9:16 frame for a vertical clip, lines for text, a player bar for sound), so the result fills it instead of popping in. On a wide screen what goes in sits on the left and the result on the right; they stack only on narrow screens and phones. A picture or clip the Block makes fills it in its own shape, as large as fits; several fill it as a grid, each big enough to watch. Small tiles are for the files a person gives the Block, never for what it makes. A clip is a `<video controls>`, which StillMade plays in its own player with full screen, and a picture in `sm-stage` or `sm-gallery` (or marked `data-stillmade-expand`) gets StillMade's full-screen button; leave out `controls` only when the Block drives playback itself. Never a JSON dump, source editor, manifest or diagnostic panel unless the task is about source. One obvious next action: one solid `sm-primary` in the kit's ink, never restyled to the background or a ghost outline; peers are `sm-quiet` buttons or named icons, and close is an X icon. Seven or fewer choices in view. Speak the job unless the person or creator asks otherwise. The action button names what it does in the job's words (`manifest.action`: "Plan the tour"; AI pictures, video and sound start with Generate and say what, the person's own media cut or rendered says Make: "Generate voiceover", "Make 6 clips"), never "Make it", "Run", "Run step", "Submit", "Process" or a bare "Generate"; progress counts the job's units ("4 of 9 rooms"), never "Processing"; results and problems use its words in one line. Show no credit numbers: StillMade puts the price beside the verb on its own run button ("Write · 1 credit"), with a chip for the model, quality or count when they can change. Pressing it runs at that price; a confirmation appears only when the server price is higher than the one shown. Pictures over words: choices people see or hear are thumbnails, swatches, samples or glyphs (`sm-choices`), not word lists. Icons carry an accessible name and tooltip; never emoji. A picked option reads at a glance: keep the kit's ink fill on an `sm-choices` pick, never restyled to an outline, a tint or the page's background. Media that cannot show here (on another device, or it did not open) keeps its place as a quiet `sm-media-off` tile in its own shape, never words in its place. Words only when needed. Drop what nobody needs to finish the task (intros, "how it works", restating the Block name). Words that might help start collapsed (`sm-hint`, `sm-more`). Errors, spending approvals, stale inputs and destructive steps stay visible, in few words. Strong defaults; secondary settings go in `sm-more`, closed. Compact: the whole Block fits one laptop screen without scrolling. A short setting is one row, its label beside the control; no card around a single control. Status is a dot or a few words ("No narration yet"), never a paragraph; the reason sits behind an `sm-hint`. Files are tiles: a saved file is a thumbnail or chip and adding one is the + tile (`<button class="sm-add" data-stillmade-add="<input>">`), which opens StillMade's picker. Never an `<input type="file">` or the bare browser file picker, and no size limits in view until a file is too big. Rare tools (update, schedule, history, reload) are named icon buttons beside the action, never sections under the work. An (i) beside anything a newcomer could misread: a setting, status or number whose effect is not obvious from its label gets a closed `sm-hint` with one or two sentences. Give every input that needs one a `description`; StillMade shows it as that (i). Obvious controls get none. One workspace: no cards inside cards, no second title, step counter or Back/Next bar; the host owns navigation. One page per job (November 3, 2026): a guided job's screen holds the inputs and settings, then a cheap check before the expensive run (the script and a voice sample before the video, a second `StillMade.run` that StillMade prices on its own), then the result; never a second Block or Step for the check. A workspace a person works in (a CAD tool, a canvas, an editor) is its own Block, and turning what it made into something else is the next one. Status sits beside the action it describes (`sm-status`), in a few words. Keep the page in normal scroll flow; no sticky media or control strips unless the editing task needs them. Use the width: from 860px wide the kit puts the controls on the left at a reading width (300–380px) and the result (`sm-stage`) fills the right, in its real shape; under that everything stacks in the page's order: the inputs, the action in normal flow (never pinned), then the result's place under it, so put `sm-stage` after `sm-actions`. Extra width goes to the result, never to wider text fields. Before there is a result, the stage previews what will be made from the inputs. `data-layout="stack"` opts out. Size with the kit, never fixed sizes: no fixed pixel widths or heights for the workspace or stage, no 100vh, no position:fixed panels. StillMade zooms a Block's screen to fill the space it has (up to 150%, never down to a phone layout on a computer), so a Block laid out with the kit's spacing fills its Step without empty space, and it centers a standard view narrower than its Step. An app that lays itself out (full-size canvas or WebGL, its own scroll, pan or zoom, its own Ctrl/Cmd +/- keys) puts `data-stillmade-layout="own"` on its outermost element, and StillMade then never centers, zooms or takes those keys. Group what the person sets in one `sm-fields` (or `sm-settings` for short ones): the kit gives the group its own surface, so the inputs, the settings and the result read as separate parts. Settings sit side by side (`sm-settings`), next to what they change. Three or four short options are one segmented row (`data-segmented`), not a row of big tiles. Lists fill the width at a small size: input files are small tiles (`sm-gallery data-size="small"`), many items are one line each (`sm-list`): a thumbnail, one line of text, a short fact. Tapping an item opens all of its fields in place. Extra width means more items per row, never bigger cards. On a phone chip and segment rows scroll sideways instead of wrapping; long lists show their first three with "N more" (`data-peek`) and long text five lines until tapped (`sm-clamp`). Nothing is lost when a Block gets smaller: every control stays on the screen, behind an (i), in `sm-more`, or inside the item it belongs to. Compacting moves controls; it never deletes them. Text scene descriptions are planning notes, never a storyboard or an approved frame. Say nothing about how the Block works: no notes on where it runs (this device, StillMade Desktop, the cloud), saving, syncing, recovery, accounts, credits or where a file is kept. StillMade tells the person what they need as one short notification, or nothing; never a sentence on the page, and never the same line under each item. References fill your inputs: every drop area in StillMade offers "Use a reference", and the person's saved photos, videos and notes fill the Step with no typing. Media inputs fill by kind, so take `video` or `asset` wherever a clip works as well as a photo; text inputs fill by plain names and labels (`productName`, `website`, `brandColors`, `fonts`, `voice`, and `details` or `notes` for the reference's note). Never build your own reference picker or library upload. Days and times are picked, never typed: a `date` input is a calendar, with a list of times when it is about a time (`postAt`, `startsAt`, a label with Time, When or Starts). In a custom view use `<input type="date">` or `type="datetime-local"`, never a text box for a date. Posting is its own Step: sending a result to Instagram, TikTok, email or any app is a connector Step after the Block, worded by StillMade ("Post to Instagram"). A Block never posts from inside itself. Its DOM controller is separate from manifest runtime code. The view may also declare appearance:"original" or "host". For an imported design, preserve its colors with appearance:"original" and view.css; no themed replacement is required. Preserve base.view.appearance when refining unless the user requests a change. Omitted/host appearance retains the existing Light, Dark and White requirement: supply semantic colors in view.css or a complete replacement view.themedCss. An original-style view may optionally supply valid themedCss for a StillMade appearance preview. Use StillMade variables (--bg,--fg,--fg-muted,--border-tok,--r-3) only when opting into host styling; do not redefine host tokens in the themed variant. Appearance never relaxes sandbox constraints: no CSS escapes/filters/blending, imports/url(), or element.style mutations in JS. Use classes for UI state; media pixels retain their colors. Both CSS fields have the same 32 KiB limit; all existing shared-state, security and permission checks still apply. Declare elements in html, bind with document.getElementById/querySelector and addEventListener; use literal property access, array methods, textContent, input.value, button.disabled and canvas. Initial values: StillMade.onInput(values=>{...}); await StillMade.run(inputBindings) returns validated output bindings from the isolated package runtime; catch errors and show results. No computation in the DOM controller; put it in code/recipe. Disable the action button while pending.
260
+ A custom interface is the optional package-level view:{html,css?,themedCss?,javascript?}; when refining, preserve and refine base.view. Design it by StillMade's interface principles, in order: Function first: unless the person asks for a design direction, build from the Block kit (`sm-block`, `sm-stage`, `sm-choices`, `sm-actions`, `sm-more`) and ship little or no CSS of your own. A direction the person names wins over the kit. Show the thing being made: the preview, media or result gets the largest area (`sm-stage`), and every part shows at once: before the first run the result's place is drawn in the shape and size of what is coming (a 9:16 frame for a vertical clip, lines for text, a player bar for sound), so the result fills it instead of popping in. On a wide screen what goes in sits on the left and the result on the right; they stack only on narrow screens and phones. A picture or clip the Block makes fills it in its own shape, as large as fits; several fill it as a grid, each big enough to watch. Small tiles are for the files a person gives the Block, never for what it makes. A clip is a `<video controls>`, which StillMade plays in its own player with full screen, and a picture in `sm-stage` or `sm-gallery` (or marked `data-stillmade-expand`) gets StillMade's full-screen button; leave out `controls` only when the Block drives playback itself. Never a JSON dump, source editor, manifest or diagnostic panel unless the task is about source. One obvious next action: one solid `sm-primary` in the kit's ink, never restyled to the background or a ghost outline; peers are `sm-quiet` buttons or named icons, and close is an X icon. Seven or fewer choices in view. Speak the job unless the person or creator asks otherwise. The action button names what it does in the job's words (`manifest.action`: "Plan the tour"; AI pictures, video and sound start with Generate and say what, the person's own media cut or rendered says Make: "Generate voiceover", "Make 6 clips"), never "Make it", "Run", "Run step", "Submit", "Process" or a bare "Generate"; progress counts the job's units ("4 of 9 rooms"), never "Processing"; results and problems use its words in one line. Show no credit numbers: StillMade puts the price beside the verb on its own run button ("Write · 1 credit"), with a chip for the model, quality or count when they can change. Pressing it runs at that price; a confirmation appears only when the server price is higher than the one shown. Pictures over words: choices people see or hear are thumbnails, swatches, samples or glyphs (`sm-choices`), not word lists. Icons carry an accessible name and tooltip; never emoji. A picked option reads at a glance: keep the kit's ink fill on an `sm-choices` pick, never restyled to an outline, a tint or the page's background. Media that cannot show here (on another device, or it did not open) keeps its place as a quiet `sm-media-off` tile in its own shape, never words in its place. Words only when needed. Drop what nobody needs to finish the task (intros, "how it works", restating the Block name). Words that might help start collapsed (`sm-hint`, `sm-more`). Errors, spending approvals, stale inputs and destructive steps stay visible, in few words. Strong defaults; secondary settings go in `sm-more`, closed. Compact: the whole Block fits one laptop screen without scrolling. A short setting is one row, its label beside the control; no card around a single control. Status is a dot or a few words ("No narration yet"), never a paragraph; the reason sits behind an `sm-hint`. Files are tiles: a saved file is a thumbnail or chip and adding one is the + tile (`<button class="sm-add" data-stillmade-add="<input>">`), which opens StillMade's picker. Never an `<input type="file">` or the bare browser file picker, and no size limits in view until a file is too big. Rare tools (update, schedule, history, reload) are named icon buttons beside the action, never sections under the work. An (i) beside anything a newcomer could misread: a setting, status or number whose effect is not obvious from its label gets a closed `sm-hint` with one or two sentences. Give every input that needs one a `description`; StillMade shows it as that (i). Obvious controls get none. One workspace: no cards inside cards, no second title, step counter or Back/Next bar; the host owns navigation. One page per job (November 3, 2026): a guided job's screen holds the inputs and settings, then a cheap check before the expensive run (the script and a voice sample before the video, a second `StillMade.run` that StillMade prices on its own), then the result; never a second Block or Step for the check. A workspace a person works in (a CAD tool, a canvas, an editor) is its own Block, and turning what it made into something else is the next one. Status sits beside the action it describes (`sm-status`), in a few words. Keep the page in normal scroll flow; no sticky media or control strips unless the editing task needs them. Use the width: from 860px wide the kit puts the controls on the left at a reading width (300–380px) and the result (`sm-stage`) fills the right, in its real shape; under that everything stacks in the page's order: the inputs, the action in normal flow (never pinned), then the result's place under it, so put `sm-stage` after `sm-actions`. Extra width goes to the result, never to wider text fields. Before there is a result, the stage previews what will be made from the inputs. `data-layout="stack"` opts out. Size with the kit, never fixed sizes: no fixed pixel widths or heights for the workspace or stage, no 100vh, no position:fixed panels. StillMade zooms a Block's screen to fill the space it has (up to 150%, never down to a phone layout on a computer), so a Block laid out with the kit's spacing fills its Step without empty space, and it centers a standard view narrower than its Step. The kit's `sm-stage` shows what it holds in its middle as one group (a caption stays with its picture), and several results in a stage fill its width as one row; the pages StillMade draws follow the same rule. An app that lays itself out (full-size canvas or WebGL, its own scroll, pan or zoom, its own Ctrl/Cmd +/- keys) puts `data-stillmade-layout="own"` on its outermost element, and StillMade then never centers, zooms or takes those keys. Group what the person sets in one `sm-fields` (or `sm-settings` for short ones): the kit gives the group its own surface, so the inputs, the settings and the result read as separate parts. Settings sit side by side (`sm-settings`), next to what they change. Three or four short options are one segmented row (`data-segmented`), not a row of big tiles. Lists fill the width at a small size: input files are small tiles (`sm-gallery data-size="small"`), many items are one line each (`sm-list`): a thumbnail, one line of text, a short fact. Tapping an item opens all of its fields in place. Extra width means more items per row, never bigger cards. On a phone chip and segment rows scroll sideways instead of wrapping; long lists show their first three with "N more" (`data-peek`) and long text five lines until tapped (`sm-clamp`). Nothing is lost when a Block gets smaller: every control stays on the screen, behind an (i), in `sm-more`, or inside the item it belongs to. Compacting moves controls; it never deletes them. Text scene descriptions are planning notes, never a storyboard or an approved frame. Say nothing about how the Block works: no notes on where it runs (this device, StillMade Desktop, the cloud), saving, syncing, recovery, accounts, credits or where a file is kept. StillMade tells the person what they need as one short notification, or nothing; never a sentence on the page, and never the same line under each item. References fill your inputs: every drop area in StillMade offers "Use a reference", and the person's saved photos, videos and notes fill the Step with no typing. Media inputs fill by kind, so take `video` or `asset` wherever a clip works as well as a photo; text inputs fill by plain names and labels (`productName`, `website`, `brandColors`, `fonts`, `voice`, and `details` or `notes` for the reference's note). Never build your own reference picker or library upload. Days and times are picked, never typed: a `date` input is a calendar, with a list of times when it is about a time (`postAt`, `startsAt`, a label with Time, When or Starts). In a custom view use `<input type="date">` or `type="datetime-local"`, never a text box for a date. Posting is its own Step: sending a result to Instagram, TikTok, email or any app is a connector Step after the Block, worded by StillMade ("Post to Instagram"). A Block never posts from inside itself. Its DOM controller is separate from manifest runtime code. The view may also declare appearance:"original" or "host". For an imported design, preserve its colors with appearance:"original" and view.css; no themed replacement is required. Preserve base.view.appearance when refining unless the user requests a change. Omitted/host appearance retains the existing Light, Dark and White requirement: supply semantic colors in view.css or a complete replacement view.themedCss. An original-style view may optionally supply valid themedCss for a StillMade appearance preview. Use StillMade variables (--bg,--fg,--fg-muted,--border-tok,--r-3) only when opting into host styling; do not redefine host tokens in the themed variant. Appearance never relaxes sandbox constraints: no CSS escapes/filters/blending, imports/url(), or element.style mutations in JS. Use classes for UI state; media pixels retain their colors. Both CSS fields have the same 32 KiB limit; all existing shared-state, security and permission checks still apply. Declare elements in html, bind with document.getElementById/querySelector and addEventListener; use literal property access, array methods, textContent, input.value, button.disabled and canvas. Initial values: StillMade.onInput(values=>{...}); await StillMade.run(inputBindings) returns validated output bindings from the isolated package runtime; catch errors and show results. No computation in the DOM controller; put it in code/recipe. Disable the action button while pending.
261
261
  Standard custom view code (no `view.config.json` frame runtime) is checked before it runs. These rules come straight from the validator; frame views run in their own sandbox and may build the page with ordinary DOM calls such as `createElement`:
262
262
 
263
263
  - **HTML elements:** only `div`, `section`, `article`, `header`, `footer`, `main`, `aside`, `nav`, `h1`, `h2`, `h3`, `h4`, `p`, `span`, `strong`, `em`, `i`, `small`, `br`, `hr`, `label`, `input`, `textarea`, `button`, `select`, `option`, `optgroup`, `fieldset`, `legend`, `output`, `progress`, `meter`, `ul`, `ol`, `li`, `dl`, `dt`, `dd`, `figure`, `figcaption`, `img`, `video`, `audio`, `source`, `canvas`, `table`, `thead`, `tbody`, `tfoot`, `tr`, `th`, `td`, `pre`, `code`, `details`, `summary`. No scripts, iframes, SVG, forms, comments or inline event attributes; media URLs must be `data:` or `blob:`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stillmade-sdk",
3
- "version": "0.1.44",
3
+ "version": "0.1.45",
4
4
  "description": "StillMade Block SDK, CLI and examples",
5
5
  "license": "PolyForm-Shield-1.0.0",
6
6
  "type": "module",
@@ -334,6 +334,11 @@ export function auditProjectTypeConnections(type,catalog=[],{contextValues=[],re
334
334
  decision={requirement,state:'auto-connected',reason:'Uses your chosen source.',candidates:[],selected:{id:sourceId(upstream.id,edge.from.port),kind:match.adapter?'adapter':'upstream',record:{source:{kind:'block',blockId:upstream.blockId,stageId:upstream.id,port:edge.from.port}},...(match.adapter?{adapter:match.adapter}:{})}};
335
335
  connections.push(structuredClone(edge));
336
336
  } catch(error) {decision={requirement,state:'broken',reason:error.message,candidates:[],selected:null};}
337
+ } else if((stage.copy?.hidden || []).includes(key) && requirement.required===false) {
338
+ // The Type hides this optional input (its Step copy, project-target.js): nothing fills it, so no automatic
339
+ // connection is proposed (November 3, 2026: Real Estate's photo-motion frames were wired into Finish video's
340
+ // hidden photos input, and the import refused the Type as unsaved).
341
+ decision={requirement,state:'ready',reason:'This Project Type leaves this input out.',candidates:[],selected:null};
337
342
  } else if(port.context) {
338
343
  const permitted=!port.sources || port.sources.includes('project');
339
344
  decision={requirement,state:permitted?'needs-user-input':'broken',reason:permitted?`Provide ${labelFor(key,port)} in the project.`:'This input requires project information but does not allow project sources.',candidates:[],selected:null,environment:permitted};
@@ -29,6 +29,11 @@ export {UNATTENDED_HOSTED_OPERATIONS};
29
29
 
30
30
  const runtimes=new Set(['recipe','javascript']);
31
31
  const remoteRuntime=runtime=>['capability','comfyui'].includes(runtime);
32
+ // A module Block runs in the person's browser sandbox, so a run the person starts may include it (November 3, 2026:
33
+ // one page per guided job, made of shared module Blocks, has one button for the whole job). A module that calls hosted
34
+ // operations waits on them like a hosted Step: the clock pauses while they run. Unattended runs keep refusing modules
35
+ // (no browser), apart from the desktop hand-off above.
36
+ const hostedModule=manifest=>manifest?.runtime==='module'&&((manifest.permissions?.capabilities||[]).length>0||(manifest.permissions?.actions||[]).includes('media.generate'));
32
37
  // Remote Steps that may run in an unattended plan: granted connection actions
33
38
  // and granted hosted operations. AI text and image generation are the exception:
34
39
  // they run on their own only when the schedule approved a credit cap for them
@@ -100,7 +105,7 @@ export async function planSegment(projectType,canvas,startStepId,{interactive=fa
100
105
  const applicationOperation=isBrowserApplication(pkg)?experimentalBrowserOperation(experimentalBrowser,pkg,operations[placement.id]):undefined;
101
106
  if(requiresDesktopExecution(pkg)&&(!interactive||background||includeIndependent)){nextStageId=placement.id;stopReason='runtime-approval';break;}
102
107
  if(pkg?.manifest.state&&!interactive&&!background){nextStageId=placement.id;stopReason='runtime-approval';break;}
103
- if(!pkg || !(runtimes.has(pkg.manifest.runtime)||unattended&&(unattendedConnection(pkg)||unattendedText&&textGeneration(pkg)||unattendedImage&&imageGeneration(pkg))||desktopModules&&desktopModuleStep(pkg)&&!placement.failurePolicy||isBrowserApplication(pkg)&&interactive||interactive&&!includeIndependent&&(!reuseCurrentSources||hostedUpdate&&pkg.manifest.runtime==='capability')&&remoteRuntime(pkg.manifest.runtime))){nextStageId=placement.id;stopReason=pkg?'runtime-approval':'host-workspace';break;}
108
+ if(!pkg || !(runtimes.has(pkg.manifest.runtime)||unattended&&(unattendedConnection(pkg)||unattendedText&&textGeneration(pkg)||unattendedImage&&imageGeneration(pkg))||desktopModules&&desktopModuleStep(pkg)&&!placement.failurePolicy||isBrowserApplication(pkg)&&interactive||interactive&&!includeIndependent&&(!reuseCurrentSources||hostedUpdate&&pkg.manifest.runtime==='capability')&&remoteRuntime(pkg.manifest.runtime)||interactive&&!background&&!includeIndependent&&!reuseCurrentSources&&pkg.manifest.runtime==='module')){nextStageId=placement.id;stopReason=pkg?'runtime-approval':'host-workspace';break;}
104
109
  const incoming=await Promise.all((type.connections || []).filter(edge=>edge.to.stage===placement.id).map(async edge=>{
105
110
  // External converted values arrive raw. Pin the producing contract so the
106
111
  // executor can check its type/role without trusting a caller's descriptor.
@@ -234,7 +239,7 @@ async function runSegmentInternal(suppliedPlan,{runId=crypto.randomUUID(),source
234
239
  await fence(step);
235
240
  const pkg=step.package,m=pkg.manifest;
236
241
  const startingState=m.state?validateBlockState(m,memory[step.id]??step.state):undefined,stateSource=memorySources[step.id]??step.stateSource;
237
- assert((runtimes.has(m.runtime)||unattendedHost&&unattendedStep(pkg,plan)||plan.desktopModules===true&&background===true&&!interactive&&desktopModuleStep(pkg)||interactive&&plan.interactive&&(remoteRuntime(m.runtime)||isBrowserApplication(pkg))) && await wait(digest(pkg))===step.packageDigest,'SEGMENT_PIN','The Step package pin changed');
242
+ assert((runtimes.has(m.runtime)||unattendedHost&&unattendedStep(pkg,plan)||plan.desktopModules===true&&background===true&&!interactive&&desktopModuleStep(pkg)||interactive&&plan.interactive&&(remoteRuntime(m.runtime)||isBrowserApplication(pkg)||m.runtime==='module'&&!plan.includeIndependent)) && await wait(digest(pkg))===step.packageDigest,'SEGMENT_PIN','The Step package pin changed');
238
243
  if(step.failurePolicy?.action==='fallback_block'){validateFailurePolicy(pinnedFailurePolicy(step.failurePolicy),m,[step.fallbackPackage]);assert(await wait(digest(step.fallbackPackage))===step.fallbackPackageDigest,'SEGMENT_PIN','The fallback Block package pin changed');}
239
244
  else assert(step.fallbackPackage===undefined&&step.fallbackPackageDigest===undefined,'SEGMENT_PIN','Unexpected fallback package');
240
245
  const applicationOptions=isBrowserApplication(pkg)?{operation:step.applicationOperation,stepId:step.id,runId,planDigest:plan.planDigest,workflowDigest:plan.workflowDigest,signal:controller.signal,check:()=>fence(step)}:null;
@@ -451,7 +456,8 @@ async function runSegmentInternal(suppliedPlan,{runId=crypto.randomUUID(),source
451
456
  }finally{activeAttempt=0;}
452
457
  };
453
458
  const bypass=conditionalBypass(step.condition || undefined,m,bound);
454
- let execution=bypass || await (remoteRuntime(m.runtime)?hostedWait(invoke):wait(executeWithFailurePolicy(step,bound,invoke,{check:()=>fence(step),approve:approveFailure,resolveLastValid:()=>findLastValid(step,recoveryKey,recoveryRecords),verifyRecovery:record=>wait(verifyRecovery(structuredClone(pkg),record,{signal:controller.signal,check:()=>fence(step)})),invokeFallback:()=>invoke(step.fallbackPackage)})));
459
+ const withPolicy=()=>executeWithFailurePolicy(step,bound,invoke,{check:()=>fence(step),approve:approveFailure,resolveLastValid:()=>findLastValid(step,recoveryKey,recoveryRecords),verifyRecovery:record=>wait(verifyRecovery(structuredClone(pkg),record,{signal:controller.signal,check:()=>fence(step)})),invokeFallback:()=>invoke(step.fallbackPackage)});
460
+ let execution=bypass || await (remoteRuntime(m.runtime)?hostedWait(invoke):hostedModule(m)?hostedWait(withPolicy):wait(withPolicy()));
455
461
  if(!bypass && execution.skipped && ['skip','fallback','fallback_block','last_valid'].includes(step.failurePolicy?.action) && Object.keys(runtimeAnswers).length)execution={...execution,userAnswers:structuredClone(runtimeAnswers)};
456
462
  if(!bypass && execution.skipped && ['skip','fallback','fallback_block','last_valid'].includes(step.failurePolicy?.action) && Object.keys(runtimeChatValues).length)execution={...execution,chatValues:structuredClone(runtimeChatValues)};
457
463
  if(remoteRuntime(m.runtime)&&!bypass&&execution.skipped){
@@ -8,7 +8,7 @@ import {assert,plain} from '../block-sdk/contracts.js';
8
8
  export const BLOCK_NAME_MAX_LENGTH=40;
9
9
 
10
10
  export const MAX_GROUP_PARTS=12;
11
- const GROUP_KEYS=['id','label','stages','settings','result'];
11
+ const GROUP_KEYS=['id','label','stages','settings','result','action'];
12
12
 
13
13
  /** Checks `type.groups` against the stages and their manifests (Map stageId → manifest). */
14
14
  export function validateStepGroups(type,manifests){
@@ -27,6 +27,9 @@ export function validateStepGroups(type,manifests){
27
27
  // A When part starts its job; it is never in the middle of one.
28
28
  assert(group.stages.slice(1).every(id=>manifests.get(id)?.id!=='sm.when'),'STEP_GROUP','When starts its Step');
29
29
  assert(group.settings===undefined||Array.isArray(group.settings)&&group.settings.length<=16&&group.settings.every(item=>plain(item)&&group.stages.includes(item.stage)&&Object.hasOwn(manifests.get(item.stage)?.inputs||{},item.input)&&typeof item.label==='string'&&item.label.trim()&&item.label.length<=60),'STEP_GROUP','Step settings must name an input of one of its parts');
30
+ // The job's own button words, as a Block's manifest.action names its Step's (November 3, 2026: Product Ad Cuts made three
31
+ // ads behind a "Make video" button).
32
+ assert(group.action===undefined||plain(group.action)&&Object.keys(group.action).every(key=>['label','running'].includes(key))&&typeof group.action.label==='string'&&group.action.label.trim()&&group.action.label.length<=40&&(group.action.running===undefined||typeof group.action.running==='string'&&group.action.running.length<=60),'STEP_GROUP','A Step action names its button in 40 characters or fewer');
30
33
  assert(group.result===undefined||plain(group.result)&&group.stages.includes(group.result.stage)&&Object.hasOwn(manifests.get(group.result.stage)?.outputs||{},group.result.port),'STEP_GROUP','A Step result must be an output of one of its parts');
31
34
  }
32
35
  }
@@ -41,7 +44,7 @@ export function normalizeGroups(type){
41
44
  const stages=runs.reduce((best,run)=>run.length>best.length?run:best,[]);
42
45
  if(stages.length<2)return [];
43
46
  const settings=(group.settings||[]).filter(item=>stages.includes(item.stage)),result=group.result&&stages.includes(group.result.stage)?group.result:undefined;
44
- return [{id:group.id,label:group.label,stages,...(settings.length?{settings}:{}),...(result?{result}:{})}];
47
+ return [{id:group.id,label:group.label,stages,...(settings.length?{settings}:{}),...(result?{result}:{}),...(group.action?{action:group.action}:{})}];
45
48
  });
46
49
  if(!groups.length){const {groups:_dropped,...rest}=type;return rest;}
47
50
  // Unchanged groups keep the type's identity, so memo boundaries hold.
@@ -79,7 +79,7 @@ const SECTIONS=Object.freeze([
79
79
  // notification": state is one StillMade notification or nothing, never a sentence on the page or one per item.
80
80
  rule('job-words','block',
81
81
  'Speak the job unless the person or creator asks otherwise: `manifest.action` names the button, required whenever StillMade draws it ("Plan the tour"; AI pictures, video or sound start with Generate, never "Make it"), one solid `sm-primary`, progress in the job\'s units, one line per problem, no notes on how the Block works or its state (one StillMade notification at most, never one per item), `date` inputs for days and times, plain input names so "Use a reference" fills them, posting as its own connector Step. Clear every Wording note `gate` lists.',
82
- 'These are StillMade\'s defaults: unless the person or the creator asked for something else, every screen speaks the job in its own words and adds nothing else. `manifest.action` names the Step\'s button, {label:"Plan the tour", running:"Planning the tour…"}, and StillMade adds the price ("Plan the tour · 1 credit"). Every Block whose Step shows StillMade\'s button declares it: any Block without its own screen, and any that calls operations. Without it StillMade can only guess from the Block\'s name or output ("Make clips"), and the readiness gate and StillMade\'s own builder send the Block back until it names the job. When the Block makes a picture, video or sound with AI the label starts with Generate and says what ("Generate tour", "Generate voiceover"); one that cuts or renders the person\'s own media says Make ("Make 6 clips"); never "Make it", "Run", "Submit", "Process" or a bare "Generate". The main button stands out: one solid `sm-primary` in the kit\'s ink, never restyled to the background; everything else is `sm-quiet` or an `sm-icon`, and close is an X icon. Report progress in the job\'s units with `stillmade.progress(fraction, label)` ("Moving through the kitchen, 4 of 9 rooms"), never "Processing". Head the result in the job\'s words ("9 rooms", "Your tour · 44 s"), never "Output". Say a problem in one line with its fix beside it ("No bathroom photo. Guests look for one." and Add photo). Stay quiet: no welcome text, tips under fields, tours or banners, and at most one line of guidance on a screen, only where the next move is not obvious; anything more goes in the input\'s `description`, which StillMade shows behind an (i). Say nothing about how the Block works (where it runs, saving, syncing, recovery, accounts, credits) or where a file is kept: StillMade tells the person what they need as one short notification, or nothing; never a sentence on the page, and never the same line under each item. Every drop area offers "Use a reference", which fills the Step from the person\'s saved photos, videos and notes: media inputs fill by kind, so take `video` or `asset` where a clip works as well as a photo, and text inputs fill by plain names and labels (`productName`, `website`, `brandColors`, `fonts`, `voice`, and `details` or `notes` for the reference\'s note); never build your own reference picker. Days and times are picked, never typed: use the `date` type, which StillMade shows as a calendar, with a list of times when the input is about a time (`postAt`, `startsAt`); a custom view uses `<input type="date">` or `type="datetime-local"`. Posting is a connector Step of its own, and StillMade words its button from the app and action ("Post to Instagram"). Before handing the Block back, clear every Wording note that `gate` and readiness list, unless the person asked for those words.'),
82
+ 'These are StillMade\'s defaults: unless the person or the creator asked for something else, every screen speaks the job in its own words and adds nothing else. `manifest.action` names the Step\'s button, {label:"Plan the tour", running:"Planning the tour…"}, and StillMade adds the price ("Plan the tour · 1 credit"). Every Block whose Step shows StillMade\'s button declares it: any Block without its own screen, and any that calls operations. Without it StillMade can only guess from the Block\'s name or output ("Make clips"), and the readiness gate and StillMade\'s own builder send the Block back until it names the job. When the Block makes a picture, video or sound with AI the label starts with Generate and says what ("Generate tour", "Generate voiceover"); one that cuts or renders the person\'s own media says Make ("Make 6 clips"); never "Make it", "Run", "Submit", "Process" or a bare "Generate". The main button stands out: one solid `sm-primary` in the kit\'s ink, never restyled to the background; everything else is `sm-quiet` or an `sm-icon`, and close is an X icon. Report progress in the job\'s units with `stillmade.progress(fraction, label)` ("Moving through the kitchen, 4 of 9 rooms"), never "Processing". Head the result in the job\'s words ("9 rooms", "Your tour · 44 s"), never "Output". Say a problem in one line with its fix beside it ("No bathroom photo. Guests look for one." and Add photo). A thrown error says what to add or change, never which button to press ("Add the photos to move, 1 to 12.", not "then press Make clips"): StillMade words the button, and on a one-page job it is the job\'s. Stay quiet: no welcome text, tips under fields, tours or banners, and at most one line of guidance on a screen, only where the next move is not obvious; anything more goes in the input\'s `description`, which StillMade shows behind an (i). Say nothing about how the Block works (where it runs, saving, syncing, recovery, accounts, credits) or where a file is kept: StillMade tells the person what they need as one short notification, or nothing; never a sentence on the page, and never the same line under each item. Every drop area offers "Use a reference", which fills the Step from the person\'s saved photos, videos and notes: media inputs fill by kind, so take `video` or `asset` where a clip works as well as a photo, and text inputs fill by plain names and labels (`productName`, `website`, `brandColors`, `fonts`, `voice`, and `details` or `notes` for the reference\'s note); never build your own reference picker. Days and times are picked, never typed: use the `date` type, which StillMade shows as a calendar, with a list of times when the input is about a time (`postAt`, `startsAt`); a custom view uses `<input type="date">` or `type="datetime-local"`. Posting is a connector Step of its own, and StillMade words its button from the app and action ("Post to Instagram"). Before handing the Block back, clear every Wording note that `gate` and readiness list, unless the person asked for those words.'),
83
83
  // November 2, 2026 (owner): StillMade centers and zooms a screen; an app that lays itself out (a CAD tool) opts out with
84
84
  // data-stillmade-layout="own" and keeps its own layout and Ctrl/Cmd +/- keys. The same day (owner: "implement A absolutely
85
85
  // universally, like in app, platform, sdk, just everywhere"): every <video controls> gets StillMade's player and result
@@ -87,10 +87,10 @@ const SECTIONS=Object.freeze([
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
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.'),
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; 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 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
- 'Custom views share edits: `StillMade.onShared`, publish every editable field (`data-stillmade-share`, `bindShared`, `updateShared`), respect `canEdit`, never re-run on remote updates.',
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
+ '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
+ '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.'),
@@ -47,14 +47,15 @@ export function reviewCollaborationContract(view){
47
47
  if(node.type==='Property'&&(node.key?.name==='tag'||node.key?.value==='tag')&&['input','textarea','select'].includes(node.value?.value))interactive=true;
48
48
  for(const value of Object.values(node))for(const child of Array.isArray(value)?value:[value])if(child&&typeof child==='object')pending.push(child);
49
49
  }
50
- const hostBindings=annotated.bindings.length>0,receives=hostBindings||calls.has('onShared')||calls.has('connectDocument'),publishes=hostBindings||calls.has('updateShared')||calls.has('bindShared')||calls.has('connectDocument');
50
+ // November 4, 2026 (owner): StillMade shares every ordinary control by itself (shared-controls.js bindAutomatic), so a
51
+ // screen that does not publish its own shared state (updateShared, connectDocument) needs no wiring at all.
52
+ const automatic=!calls.has('updateShared')&&!calls.has('connectDocument');
53
+ const hostBindings=automatic||annotated.bindings.length>0,receives=hostBindings||calls.has('onShared')||calls.has('connectDocument'),publishes=hostBindings||calls.has('updateShared')||calls.has('bindShared')||calls.has('connectDocument');
51
54
  const missing=[];
52
55
  // November 2026: a React view not built yet (no src/view.js) failed with the share message below, which sent
53
56
  // outside agents to rewrite controls instead of running the build.
54
- if(!receives&&frame&&!(view.javascript||'').trim())return {...base,passed:false,code:'COLLABORATION_CONTRACT',message:'The interface has no script: src/view.js is missing or empty. If it is written in React or another framework (src/view.jsx), build it first (npm run build) so the bundle is src/view.js. A view with no script must mark its controls with data-stillmade-share.'};
57
+ if(frame&&!(view.javascript||'').trim())return {...base,passed:false,code:'COLLABORATION_CONTRACT',message:'The interface has no script: src/view.js is missing or empty. If it is written in React or another framework (src/view.jsx), build it first (npm run build) so the bundle is src/view.js. A view with no script must mark its controls with data-stillmade-share.'};
55
58
  if(!receives)missing.push('mark ordinary controls with data-stillmade-share, receive teammate state/results with StillMade.onShared, or connect a content store with StillMade.connectDocument');
56
59
  if(interactive&&!publishes)missing.push('publish editable controls through data-stillmade-share, StillMade.connectDocument, StillMade.bindShared or StillMade.updateShared');
57
- const uncovered=annotated.controls.filter(control=>!control.shared&&!control.local&&!(typeof control.id==='string'&&boundIds.has(control.id)));
58
- if(uncovered.length&&!calls.has('updateShared')&&!calls.has('connectDocument'))missing.push('bind every ordinary static control, or mark device-only controls with data-stillmade-local');
59
60
  return {...base,passed:missing.length===0,...(missing.length?{code:'COLLABORATION_CONTRACT'}:{}),message:missing.length?`Custom interfaces must ${missing.join(' and ')}. Local-only interfaces cannot finish new admission. Device-local playback or preview controls may remain local within an otherwise shared interface.`:hostBindings?'Host-owned shared control bindings found. The host supplies synchronization, permission changes, batching and recovery; runtime two-participant acceptance is still required.':'Shared SDK calls found. This is a minimum source check, not proof that all edits, results, permissions or conflicts work between participants.'};
60
61
  }
@@ -1,7 +1,7 @@
1
1
  import {plain,safeDocument} from './contracts.js';
2
+ import {SECRET_FIELD as secretPattern} from './shared-controls.js';
2
3
 
3
4
  const keyPattern=/^[a-zA-Z][a-zA-Z0-9_-]{0,99}$/;
4
- const secretPattern=/(?:password|secret|credential|authorization|cookie|token|api[_-]?key|private[_-]?key)/i;
5
5
 
6
6
  function attributes(source=''){
7
7
  const values=new Map();
@@ -36,17 +36,26 @@ export function choiceValue(control,value){
36
36
  }
37
37
 
38
38
  export function sharedAgentControls(view){
39
- const html=String(view?.html||'').replace(/<!--[\s\S]*?-->/g,''),controls=[];
39
+ const html=String(view?.html||'').replace(/<!--[\s\S]*?-->/g,''),controls=[],keys=new Set();
40
+ // November 4, 2026 (owner): StillMade shares every ordinary control by itself (shared-controls.js bindAutomatic), under
41
+ // its data-stillmade-share, else its name, else its id, unless the view publishes its own state; Chat reads the same.
42
+ const automatic=!/\bStillMade\s*(?:\.\s*|\[\s*["'])(?:updateShared|connectDocument)\b/.test(String(view?.javascript||''));
40
43
  for(const match of html.matchAll(/<(input|textarea|select|fieldset)\b([^>]*)>/gi)){
41
44
  const attrs=attributes(match[2]);
42
- if(!attrs.has('data-stillmade-share'))continue;
43
- const key=attrs.get('data-stillmade-share'),id=attrs.get('id'),element=match[1].toLowerCase(),inputType=(attrs.get('type')||'text').toLowerCase();
44
- if(!keyPattern.test(key||'')||!id||id.length>200)continue;
45
+ const declared=attrs.has('data-stillmade-share');
46
+ if(!declared&&(!automatic||attrs.has('data-stillmade-local')))continue;
47
+ const element=match[1].toLowerCase(),inputType=(attrs.get('type')||'text').toLowerCase();
48
+ const radioName=element==='fieldset'?attributes(/<input\b([^>]*type\s*=\s*["']?radio[^>]*)>/i.exec(html.slice(match.index))?.[1]||'').get('name'):'';
49
+ const key=declared?attrs.get('data-stillmade-share'):(attrs.get('name')||attrs.get('id')||radioName||'');
50
+ const id=declared?attrs.get('id'):(attrs.get('id')||`sm-shared-${key}`);
51
+ if(!keyPattern.test(key||'')||!id||id.length>200||keys.has(key)||!declared&&secretPattern.test(key))continue;
45
52
  // A shared fieldset holds radio picks; its value is the chosen option's value.
46
53
  const type=element==='input'?(['search','email','tel','url'].includes(inputType)?'text':inputType):element==='fieldset'?'choice':element;
47
54
  if(!['text','number','range','checkbox','color','textarea','select','choice'].includes(type))continue;
48
55
  const description=label(attrs.get('aria-label')||attrs.get('title')||key);
49
56
  const options=type==='choice'?choiceOptions(html.slice(match.index+match[0].length,html.indexOf('</fieldset>',match.index)>=0?html.indexOf('</fieldset>',match.index):undefined)):null;
57
+ if(type==='choice'&&!options?.length&&!declared)continue;
58
+ keys.add(key);
50
59
  controls.push({key,id,type,description,...(options?.length?{options}:{}),...(Number.isFinite(Number(attrs.get('min')))&&attrs.has('min')?{minimum:Number(attrs.get('min'))}:{}),...(Number.isFinite(Number(attrs.get('max')))&&attrs.has('max')?{maximum:Number(attrs.get('max'))}:{})});
51
60
  }
52
61
  return controls.slice(0,100);
@@ -1,5 +1,8 @@
1
1
  // Platform-owned bindings. Serialized into the opaque frame; no author code is
2
2
  // replayed when a collaborator changes a value (particularly no run/click).
3
+ // The pattern Chat and the frame both use to leave secret-named fields alone (inlined in installSharedControls, which
4
+ // runs serialized in the frame; shared-controls.test.js keeps the two the same).
5
+ export const SECRET_FIELD=/(?:password|secret|credential|authorization|cookie|token|api[_-]?key|private[_-]?key)/i;
3
6
  export function installSharedControls(document,api,onError,textControls=null,lockedInputKeys=[]){
4
7
  const bindings=new Map(),TEXT_TYPES=['text','search','email','tel','url'];
5
8
  const lockedInputs=new Set(lockedInputKeys);
@@ -26,7 +29,7 @@ export function installSharedControls(document,api,onError,textControls=null,loc
26
29
  entry.timer=setTimeout(()=>runDeferred(key,entry),Math.max(0,Math.min(150,500-(Date.now()-entry.started))));
27
30
  deferred.set(key,entry);
28
31
  };
29
- const bindShared=(key,id)=>{
32
+ const bindSharedCore=(key,id)=>{
30
33
  if(!/^[a-zA-Z][a-zA-Z0-9_-]{0,99}$/.test(key)||bindings.has(key))throw Error('Use one control per named shared field');
31
34
  const node=document.getElementById(id);
32
35
  // A fieldset of radio buttons (the Block kit's sm-choices picks) shares the checked value.
@@ -125,6 +128,61 @@ export function installSharedControls(document,api,onError,textControls=null,loc
125
128
  const dispose=()=>{disposed=true;composing=false;clearTimeout(timer);unsubscribe();node.removeEventListener('input',change);node.removeEventListener('change',change);node.removeEventListener(leave,flush);node.removeEventListener('compositionstart',compositionStart);node.removeEventListener('compositionend',compositionEnd);node.disabled=originallyDisabled;bindings.delete(key);};
126
129
  bindings.set(key,{dispose,flush,status(){return {dirty,saving,problem,canEdit,composing,interrupted:retainedComposition!==undefined};},setDefault(value){fallback=value;receive(api.getShared());},setInputDefault(value){if(shows(radios,node,value))this.setDefault(value);}});return dispose;
127
130
  };
131
+ // November 4, 2026 (owner: "the point of the SDK is to have certain things auto build for a block, which includes
132
+ // multiplayer"): every ordinary control on a Block's screen is shared without any wiring. Its shared field is its
133
+ // data-stillmade-share, else its name, else its id; a kit choice group (a fieldset of radios) shares its pick. A control
134
+ // the Block binds itself (bindShared) takes over from the automatic binding, and data-stillmade-local on a control or any
135
+ // element around it (the root for a whole screen) keeps it on this device. Controls added later are bound as they
136
+ // appear and released when they leave. Nothing here throws into the Block: a control that cannot be bound stays local.
137
+ const autoNodes=new Map(),autoKeys=new Map(),boundNodes=new WeakSet(),SHAREABLE=['text','search','email','tel','url','number','range','checkbox','color'],KEY=/^[a-zA-Z][a-zA-Z0-9_-]{0,99}$/,
138
+ // Never shared by itself: a field named like a secret or marked as a credential (the same pattern Chat skips).
139
+ SECRET=/(?:password|secret|credential|authorization|cookie|token|api[_-]?key|private[_-]?key)/i,CREDENTIAL=/^(?:current-password|new-password|one-time-code|cc-)/i;
140
+ let observer=null,scanTimer=null;
141
+ const dropAuto=node=>{const key=autoNodes.get(node);if(key===undefined)return;autoNodes.delete(node);autoKeys.delete(key);try{bindings.get(key)?.dispose();}catch{}bindings.delete(key);};
142
+ const bindShared=(key,id)=>{
143
+ const node=document.getElementById(id);
144
+ if(node&&autoNodes.has(node))dropAuto(node);
145
+ if(autoKeys.has(key))dropAuto(autoKeys.get(key));
146
+ const dispose=bindSharedCore(key,id);
147
+ if(node)boundNodes.add(node);
148
+ return dispose;
149
+ };
150
+ const autoKey=node=>{
151
+ const named=node.getAttribute('data-stillmade-share')||node.getAttribute('name')||node.id||(node.tagName==='FIELDSET'?node.querySelector('input[type="radio"][name]')?.getAttribute('name'):'')||'';
152
+ return KEY.test(named)?named:null;
153
+ };
154
+ const eligible=node=>{
155
+ if(!node.isConnected||node.closest('[data-stillmade-local]'))return false;
156
+ if(node.tagName==='FIELDSET')return !!node.querySelector('input[type="radio"]')&&!node.querySelector('fieldset');
157
+ if(node.tagName==='INPUT')return SHAREABLE.includes(node.type);
158
+ return node.tagName==='TEXTAREA'||node.tagName==='SELECT';
159
+ };
160
+ const autoBind=node=>{
161
+ if(boundNodes.has(node)||autoNodes.has(node)||!eligible(node))return;
162
+ const key=autoKey(node);if(!key||bindings.has(key)||bindings.size>=100)return;
163
+ if(!node.hasAttribute('data-stillmade-share')&&(SECRET.test(key)||SECRET.test(node.getAttribute('name')||'')||SECRET.test(node.id||'')||CREDENTIAL.test(node.getAttribute('autocomplete')||'')))return;
164
+ if(!node.id){let id=`sm-shared-${key}`,n=1;while(document.getElementById(id))id=`sm-shared-${key}-${++n}`;node.id=id;}
165
+ else if(document.getElementById(node.id)!==node)return;
166
+ try{bindSharedCore(key,node.id);autoNodes.set(node,key);autoKeys.set(key,node);}catch{bindings.delete(key);}
167
+ };
168
+ const scan=()=>{
169
+ scanTimer=null;if(closed||manual)return;
170
+ try{
171
+ for(const node of [...autoNodes.keys()])if(!node.isConnected||node.closest('[data-stillmade-local]'))dropAuto(node);
172
+ for(const node of document.querySelectorAll('input,textarea,select,fieldset'))autoBind(node);
173
+ }catch(error){onError(error.message);}
174
+ };
175
+ // A Block that publishes its own shared state (updateShared, connectDocument) owns its controls, as the admission check
176
+ // already reads it: the automatic binding steps aside and releases what it had bound.
177
+ let manual=false;
178
+ const ownSharing=()=>{if(manual)return;manual=true;observer?.disconnect();observer=null;if(scanTimer){clearTimeout(scanTimer);scanTimer=null;}for(const node of [...autoNodes.keys()])dropAuto(node);};
179
+ const bindAutomatic=()=>{
180
+ if(manual||closed||observer||typeof MutationObserver!=='function'||!document.documentElement)return;
181
+ scan();
182
+ observer=new MutationObserver(()=>{if(!scanTimer&&!closed)scanTimer=setTimeout(scan,30);});
183
+ observer.observe(document.documentElement,{childList:true,subtree:true,attributes:true,attributeFilter:['name','id','type','data-stillmade-local','data-stillmade-share']});
184
+ };
185
+ if(document.readyState==='loading')document.addEventListener?.('DOMContentLoaded',bindAutomatic,{once:true});else if(typeof setTimeout==='function'&&typeof MutationObserver==='function')setTimeout(bindAutomatic,0);
128
186
  const bindAnnotated=()=>{
129
187
  const nodes=[...document.querySelectorAll('[data-stillmade-share]')];
130
188
  if(nodes.length>100)throw Error('Use at most 100 shared controls');
@@ -160,7 +218,7 @@ export function installSharedControls(document,api,onError,textControls=null,loc
160
218
  }
161
219
  throw Error('Edits are still arriving. Finish editing before leaving this Block.');
162
220
  };
163
- return {bindShared,bindAnnotated,scheduleSharedEdit,flushAll,assertNoComposition,getBindingStatus:key=>bindings.get(key)?.status(),setSharedDefaults(values){
221
+ return {bindShared,bindAnnotated,bindAutomatic,ownSharing,scheduleSharedEdit,flushAll,assertNoComposition,getBindingStatus:key=>bindings.get(key)?.status(),setSharedDefaults(values){
164
222
  if(!values||typeof values!=='object'||Array.isArray(values)||Object.keys(values).length>100||JSON.stringify(values).length>65536)throw Error('Use bounded shared control defaults');
165
223
  for(const [key,value] of Object.entries(values))bindings.get(key)?.setDefault(value);
166
224
  },useInputDefaults(input){
@@ -169,5 +227,5 @@ export function installSharedControls(document,api,onError,textControls=null,loc
169
227
  const previous=inputDefaults;inputDefaults={};
170
228
  if(input&&typeof input==='object'&&!Array.isArray(input))for(const [key,value] of Object.entries(input))if(['string','number','boolean'].includes(typeof value))inputDefaults[key]=value;
171
229
  for(const [key,binding] of bindings)if(Object.hasOwn(inputDefaults,key)&&!(Object.hasOwn(previous,key)&&previous[key]===inputDefaults[key]))binding.setInputDefault(inputDefaults[key]);
172
- },dispose(){closed=true;for(const entry of deferred.values())clearTimeout(entry.timer);deferred.clear();for(const binding of [...bindings.values()])binding.dispose();}};
230
+ },dispose(){closed=true;observer?.disconnect();if(scanTimer)clearTimeout(scanTimer);for(const entry of deferred.values())clearTimeout(entry.timer);deferred.clear();for(const binding of [...bindings.values()])binding.dispose();}};
173
231
  }