stillmade-sdk 0.1.29 → 0.1.30
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.
package/AGENTS.md
CHANGED
|
@@ -6,7 +6,7 @@ This folder is the StillMade Block SDK. Read this file before writing code; it i
|
|
|
6
6
|
|
|
7
7
|
Do not tell the user a Block is ready, done or working until `node packages/block-cli/cli.js gate .` passes after your last edit, and report the exact status it prints. In this folder Claude Code (`.claude/settings.json`) and Cursor (`.cursor/hooks.json`) run the gate when you finish a turn and send any failures back to you, but only in a session started in this folder: if you downloaded the SDK during the current session, run the gate yourself. Other agents must also run it themselves. A module Block runs code, so the gate a hook starts does not run it and says so: run `gate` or `ready` on it yourself. If you cannot fix a failure, say plainly that the Block is not ready and list the failing checks.
|
|
8
8
|
|
|
9
|
-
Contract `stillmade.block-builder@1.
|
|
9
|
+
Contract `stillmade.block-builder@1.7.0`. Humans, coding agents, imported source, GitHub repositories and generated code all adapt to the Block platform; anything that becomes a StillMade Block obeys this contract.
|
|
10
10
|
|
|
11
11
|
## Source of truth
|
|
12
12
|
|
|
@@ -62,7 +62,7 @@ Give the Block a short, plain name of 40 characters or fewer that says what it d
|
|
|
62
62
|
|
|
63
63
|
## Interface
|
|
64
64
|
|
|
65
|
-
If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction genuinely needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, picks as pictures in `sm-choices`, one `sm-primary` naming the action (Write, Generate; never "Run") 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. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Do not show raw JSON, internal IDs, filesystem details, provider terminology or developer settings unless the Block is itself a developer tool. When a person chooses among options they need to hear or see (voices, music, sounds, fonts, looks, colors, templates or images), let them preview each option before choosing: a sample to play or a thumbnail, from the Block's own files or the host's free samples, never a paid generation. Leave previews out only when the builder states there is nothing to preview. Choices StillMade owns, such as voices and models in the run review, already show their samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (result first, action pinned), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.
|
|
65
|
+
If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction genuinely needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, picks as pictures in `sm-choices`, one `sm-primary` naming the action (Write, Generate; never "Run") 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 (a video editor, a 3D configurator, a 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. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Do not show raw JSON, internal IDs, filesystem details, provider terminology or developer settings unless the Block is itself a developer tool. When a person chooses among options they need to hear or see (voices, music, sounds, fonts, looks, colors, templates or images), let them preview each option before choosing: a sample to play or a thumbnail, from the Block's own files or the host's free samples, never a paid generation. Leave previews out only when the builder states there is nothing to preview. Choices StillMade owns, such as voices and models in the run review, already show their samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (result first, action pinned), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.
|
|
66
66
|
|
|
67
67
|
## Shared editing
|
|
68
68
|
|
package/PASTE_TO_LLM.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- builder-contract:start (generated by npm run build:sdk; do not edit) -->
|
|
4
4
|
|
|
5
|
-
## Block builder contract (stillmade.block-builder@1.
|
|
5
|
+
## Block builder contract (stillmade.block-builder@1.7.0)
|
|
6
6
|
|
|
7
|
-
Contract `stillmade.block-builder@1.
|
|
7
|
+
Contract `stillmade.block-builder@1.7.0`. Humans, coding agents, imported source, GitHub repositories and generated code all adapt to the Block platform; anything that becomes a StillMade Block obeys this contract.
|
|
8
8
|
|
|
9
9
|
### Source of truth
|
|
10
10
|
|
|
@@ -60,7 +60,7 @@ Give the Block a short, plain name of 40 characters or fewer that says what it d
|
|
|
60
60
|
|
|
61
61
|
### Interface
|
|
62
62
|
|
|
63
|
-
If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction genuinely needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, picks as pictures in `sm-choices`, one `sm-primary` naming the action (Write, Generate; never "Run") 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. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Do not show raw JSON, internal IDs, filesystem details, provider terminology or developer settings unless the Block is itself a developer tool. When a person chooses among options they need to hear or see (voices, music, sounds, fonts, looks, colors, templates or images), let them preview each option before choosing: a sample to play or a thumbnail, from the Block's own files or the host's free samples, never a paid generation. Leave previews out only when the builder states there is nothing to preview. Choices StillMade owns, such as voices and models in the run review, already show their samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (result first, action pinned), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.
|
|
63
|
+
If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction genuinely needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, picks as pictures in `sm-choices`, one `sm-primary` naming the action (Write, Generate; never "Run") 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 (a video editor, a 3D configurator, a 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. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Do not show raw JSON, internal IDs, filesystem details, provider terminology or developer settings unless the Block is itself a developer tool. When a person chooses among options they need to hear or see (voices, music, sounds, fonts, looks, colors, templates or images), let them preview each option before choosing: a sample to play or a thumbnail, from the Block's own files or the host's free samples, never a paid generation. Leave previews out only when the builder states there is nothing to preview. Choices StillMade owns, such as voices and models in the run review, already show their samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (result first, action pinned), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.
|
|
64
64
|
|
|
65
65
|
### Shared editing
|
|
66
66
|
|
package/SDK.md
CHANGED
|
@@ -182,7 +182,7 @@ The archive needs Node.js 22 or later and nothing else; its runtime is bundled,
|
|
|
182
182
|
|
|
183
183
|
## Block builder contract
|
|
184
184
|
|
|
185
|
-
Contract `stillmade.block-builder@1.
|
|
185
|
+
Contract `stillmade.block-builder@1.7.0`. Humans, coding agents, imported source, GitHub repositories and generated code all adapt to the Block platform; anything that becomes a StillMade Block obeys this contract.
|
|
186
186
|
|
|
187
187
|
### Source of truth
|
|
188
188
|
|
|
@@ -238,7 +238,7 @@ Give the Block a short, plain name of 40 characters or fewer that says what it d
|
|
|
238
238
|
|
|
239
239
|
### Interface
|
|
240
240
|
|
|
241
|
-
If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction genuinely needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, picks as pictures in `sm-choices`, one `sm-primary` naming the action (Write, Generate; never "Run") 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. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Do not show raw JSON, internal IDs, filesystem details, provider terminology or developer settings unless the Block is itself a developer tool. When a person chooses among options they need to hear or see (voices, music, sounds, fonts, looks, colors, templates or images), let them preview each option before choosing: a sample to play or a thumbnail, from the Block's own files or the host's free samples, never a paid generation. Leave previews out only when the builder states there is nothing to preview. Choices StillMade owns, such as voices and models in the run review, already show their samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (result first, action pinned), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.
|
|
241
|
+
If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction genuinely needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, picks as pictures in `sm-choices`, one `sm-primary` naming the action (Write, Generate; never "Run") 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 (a video editor, a 3D configurator, a 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. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Do not show raw JSON, internal IDs, filesystem details, provider terminology or developer settings unless the Block is itself a developer tool. When a person chooses among options they need to hear or see (voices, music, sounds, fonts, looks, colors, templates or images), let them preview each option before choosing: a sample to play or a thumbnail, from the Block's own files or the host's free samples, never a paid generation. Leave previews out only when the builder states there is nothing to preview. Choices StillMade owns, such as voices and models in the run review, already show their samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (result first, action pinned), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.
|
|
242
242
|
|
|
243
243
|
### Shared editing
|
|
244
244
|
|
|
@@ -7241,9 +7241,9 @@ Ask the assistant to return source files or one `.stillmade.json` package. Run t
|
|
|
7241
7241
|
|
|
7242
7242
|
<!-- builder-contract:start (generated by npm run build:sdk; do not edit) -->
|
|
7243
7243
|
|
|
7244
|
-
### Block builder contract (stillmade.block-builder@1.
|
|
7244
|
+
### Block builder contract (stillmade.block-builder@1.7.0)
|
|
7245
7245
|
|
|
7246
|
-
Contract `stillmade.block-builder@1.
|
|
7246
|
+
Contract `stillmade.block-builder@1.7.0`. Humans, coding agents, imported source, GitHub repositories and generated code all adapt to the Block platform; anything that becomes a StillMade Block obeys this contract.
|
|
7247
7247
|
|
|
7248
7248
|
### Source of truth
|
|
7249
7249
|
|
|
@@ -7299,7 +7299,7 @@ Give the Block a short, plain name of 40 characters or fewer that says what it d
|
|
|
7299
7299
|
|
|
7300
7300
|
### Interface
|
|
7301
7301
|
|
|
7302
|
-
If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction genuinely needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, picks as pictures in `sm-choices`, one `sm-primary` naming the action (Write, Generate; never "Run") 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. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Do not show raw JSON, internal IDs, filesystem details, provider terminology or developer settings unless the Block is itself a developer tool. When a person chooses among options they need to hear or see (voices, music, sounds, fonts, looks, colors, templates or images), let them preview each option before choosing: a sample to play or a thumbnail, from the Block's own files or the host's free samples, never a paid generation. Leave previews out only when the builder states there is nothing to preview. Choices StillMade owns, such as voices and models in the run review, already show their samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (result first, action pinned), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.
|
|
7302
|
+
If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction genuinely needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, picks as pictures in `sm-choices`, one `sm-primary` naming the action (Write, Generate; never "Run") 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 (a video editor, a 3D configurator, a 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. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Do not show raw JSON, internal IDs, filesystem details, provider terminology or developer settings unless the Block is itself a developer tool. When a person chooses among options they need to hear or see (voices, music, sounds, fonts, looks, colors, templates or images), let them preview each option before choosing: a sample to play or a thumbnail, from the Block's own files or the host's free samples, never a paid generation. Leave previews out only when the builder states there is nothing to preview. Choices StillMade owns, such as voices and models in the run review, already show their samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (result first, action pinned), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.
|
|
7303
7303
|
|
|
7304
7304
|
### Shared editing
|
|
7305
7305
|
|
|
@@ -181,7 +181,7 @@ export default {
|
|
|
181
181
|
"runtime": "frame",
|
|
182
182
|
"html": "<main class=\"sm-block nr\">\n <section class=\"sm-stage nr-stage\" id=\"stage\" aria-live=\"polite\">\n <div class=\"sm-empty\" id=\"empty\">\n <span class=\"sm-glyph\" data-glyph=\"audio\" aria-hidden=\"true\"></span>\n <p>No narration yet</p>\n </div>\n <div class=\"nr-take\" id=\"take\" hidden>\n <audio id=\"player\" controls preload=\"metadata\"></audio>\n <p class=\"nr-meta\"><span id=\"length\"></span><span id=\"from\"></span></p>\n </div>\n </section>\n <div class=\"sm-field\">\n <span class=\"sm-label\"><label for=\"facts\">Facts</label><details class=\"sm-hint\"><summary aria-label=\"About the facts\" title=\"About the facts\"></summary><p>What the voice may say: the thing, its details, who it is for, how long, and how to reach you. Chat fills this from your conversation; change it here.</p></details><span class=\"sm-status\" id=\"facts-state\"></span></span>\n <textarea id=\"facts\" rows=\"3\" maxlength=\"6000\" data-stillmade-share=\"facts\" placeholder=\"What it is, its details, who it is for, and how to reach you.\"></textarea>\n </div>\n <div class=\"sm-field\">\n <span class=\"sm-label\"><label for=\"words\">Script</label><span class=\"sm-status\" id=\"words-state\"></span></span>\n <textarea id=\"words\" rows=\"7\" maxlength=\"3800\" data-stillmade-share=\"words\" placeholder=\"Written from the facts, or type what the voice says.\"></textarea>\n </div>\n <fieldset class=\"sm-choices\" data-compact id=\"voice\" data-stillmade-share=\"voice\">\n <legend>Voice</legend>\n <label><input type=\"radio\" name=\"voice\" value=\"onyx\" checked><span class=\"sm-glyph\" data-glyph=\"audio\" aria-hidden=\"true\"></span><span>Onyx <small>deep</small></span></label>\n <label><input type=\"radio\" name=\"voice\" value=\"nova\"><span class=\"sm-glyph\" data-glyph=\"audio\" aria-hidden=\"true\"></span><span>Nova <small>bright</small></span></label>\n <label><input type=\"radio\" name=\"voice\" value=\"alloy\"><span class=\"sm-glyph\" data-glyph=\"audio\" aria-hidden=\"true\"></span><span>Alloy <small>even</small></span></label>\n </fieldset>\n <div class=\"sm-actions\">\n <button type=\"button\" class=\"sm-primary\" id=\"action\" data-stillmade-action=\"run\">Write script</button>\n <button type=\"button\" class=\"sm-quiet\" id=\"again\" hidden>Write again</button>\n <p class=\"sm-status\" id=\"status\" role=\"status\"></p>\n <details class=\"sm-hint\" id=\"why\" hidden><summary aria-label=\"Why\" title=\"Why\"></summary><p id=\"why-text\"></p></details>\n </div>\n <details class=\"sm-more\" id=\"more\">\n <summary>More</summary>\n <div class=\"sm-settings\">\n <fieldset class=\"sm-choices\" data-compact id=\"tone\" data-stillmade-share=\"tone\">\n <legend>Tone</legend>\n <label><input type=\"radio\" name=\"tone\" value=\"warm\" checked><span>Warm</span></label>\n <label><input type=\"radio\" name=\"tone\" value=\"friendly\"><span>Friendly</span></label>\n <label><input type=\"radio\" name=\"tone\" value=\"no-nonsense\"><span>No-nonsense</span></label>\n <label><input type=\"radio\" name=\"tone\" value=\"luxury\"><span>Luxury</span></label>\n </fieldset>\n <fieldset class=\"sm-choices\" data-segmented id=\"speed\" data-stillmade-share=\"speed\">\n <legend>Speed</legend>\n <label><input type=\"radio\" name=\"speed\" value=\"0.9\"><span>Slower</span></label>\n <label><input type=\"radio\" name=\"speed\" value=\"1\" checked><span>Normal</span></label>\n <label><input type=\"radio\" name=\"speed\" value=\"1.15\"><span>Faster</span></label>\n </fieldset>\n <div class=\"sm-field\"><label for=\"rules\">Own rules</label><input id=\"rules\" type=\"text\" maxlength=\"300\" data-stillmade-share=\"rules\" placeholder=\"Short sentences, no exclamation marks\"></div>\n </div>\n </details>\n</main>\n",
|
|
183
183
|
"css": "/* The Block kit (injected by the host) carries the layout; this file only draws\n the voiceover on the stage: the player and its two facts. */\n.nr [hidden] { display: none !important; }\n.nr-stage { display: grid; align-content: center; justify-items: stretch; gap: var(--s-2, 8px); min-height: 180px; }\n/* A voiceover is short: on a wide screen the stage hugs the player instead of stretching down the column. */\n@container (min-width: 860px) { .sm-block:not([data-layout]) > .nr-stage { align-self: start; min-height: 220px; } }\n.nr-stage > .sm-empty { justify-self: center; }\n.nr-take { display: grid; gap: var(--s-2, 8px); width: 100%; }\n.nr-take audio { width: 100%; }\n.nr-meta { display: flex; justify-content: space-between; gap: var(--s-3, 12px); margin: 0; color: var(--fg-muted); font-size: var(--fs-body-s, .875rem); font-variant-numeric: tabular-nums; }\n#voice { --sm-tile: 120px; }\n#voice small { color: var(--fg-subtle); font-size: var(--fs-caption, .8125rem); margin-inline-start: 4px; }\n#tone { --sm-tile: 110px; }\n",
|
|
184
|
-
"javascript": "(()=>{\n\"use strict\";\n// The Narration interface on the Block kit: the voiceover on the stage, the\n// facts and the script on the left, the voice as chips, and one button that\n// names the next step: Write script, then Make voiceover, or Use my recording.\n// The status beside it says the state in a few words; the price is StillMade's.\nconst $ = id => document.getElementById(id);\nconst state = { input: {}, outputs: {}, busy: false, previewUrl: null, previewFor: null, factsFromChat: false };\nconst message = error => String(error && error.message || error || 'Something went wrong.');\nconst say = (id, text) => { $(id).textContent = text || ''; };\nconst trimmed = value => String(value || '').trim();\nconst wordCount = text => trimmed(text) ? trimmed(text).split(/\\s+/).length : 0;\nconst clock = seconds => { const whole = Math.max(0, Math.round(seconds)); return Math.floor(whole / 60) + ':' + String(whole % 60).padStart(2, '0'); };\nconst spoken = text => 'about ' + clock(wordCount(text) / 150 * 60);\nconst picked = name => { const node = document.querySelector('input[name=\"' + name + '\"]:checked'); return node ? node.value : ''; };\nconst pick = (name, value) => { const node = document.querySelector('input[name=\"' + name + '\"][value=\"' + value + '\"]'); if (node) node.checked = true; };\n// A brief or script value is {schemaVersion: 1, text}; older briefs carry a topic.\nconst words = value => typeof value === 'string' ? value : value && typeof value === 'object' ? trimmed(value.text || value.topic || value.summary || '') : '';\nconst facts = () => trimmed($('facts').value);\nconst script = () => trimmed($('words').value);\nconst sameWords = (a, b) => trimmed(a).replace(/\\s+/g, ' ') === trimmed(b).replace(/\\s+/g, ' ');\n\nfunction status(text, tone, why) {\n const node = $('status');\n node.textContent = text || '';\n if (tone) node.dataset.state = tone; else delete node.dataset.state;\n $('why').hidden = !why; $('why').open = false; say('why-text', why || '');\n}\n// A field the host shares is written through its own input event, so the edit is kept.\nfunction setField(id, value) {\n const node = $(id);\n if (node.value === value) return;\n node.value = value;\n node.dispatchEvent(new Event('input', { bubbles: true }));\n node.dispatchEvent(new Event('change', { bubbles: true }));\n}\n\n// ── What the person is about to do ──────────────────────────────────────\nfunction phase() {\n if (!script()) return 'write';\n if (state.input.take) return 'take';\n const audio = state.outputs.audio, made = state.outputs.script;\n return audio && made && sameWords(words(made), script()) ? 'ready' : 'voice';\n}\nfunction render() {\n const p = phase(), count = wordCount(script());\n say('words-state', count ? count + ' words · ' + spoken(script()) : '');\n say('facts-state', facts() ? (state.factsFromChat ? 'From Chat' : '') : 'Needed');\n const labels = { write: 'Write script', voice: 'Make voiceover', ready: 'Make voiceover again', take: 'Use my recording' };\n say('action', labels[p]);\n $('again').hidden = p === 'write';\n $('action').disabled = state.busy || (p === 'write' && !facts());\n $('again').disabled = state.busy;\n if (state.busy) return;\n if (p === 'write') status(facts() ? 'Written from the facts' : 'Add the facts first', facts() ? '' : 'waiting', facts() ? 'The script uses only what the facts say; you can change every word before the voice runs.' : 'Chat usually fills the facts from your conversation. Type them here when it has not.');\n else if (p === 'take') status('Your recording will be used', '', 'The audio you supplied becomes the voiceover; nothing is generated.');\n else if (p === 'voice') status(state.outputs.audio ? 'Script changed, so the voice runs again' : 'Script is done, so only the voice runs', '', 'Only the voice is generated now; the words stay as written.');\n else status('Voiceover ready', 'success', '');\n}\n\n// ── The stage: the voiceover, when there is one ───────────────────────────\nasync function showAudio(reference, from) {\n if (!reference) { $('take').hidden = true; $('empty').hidden = false; return; }\n $('empty').hidden = true; $('take').hidden = false;\n say('from', from || '');\n say('length', Number.isFinite(reference.duration) ? clock(reference.duration) : spoken(script()));\n const key = reference.assetId + '/' + reference.versionId;\n if (state.previewFor === key) return;\n if (state.previewUrl) { try { StillMade.releaseMedia(state.previewUrl); } catch {} state.previewUrl = null; }\n state.previewFor = key;\n try {\n const preview = await StillMade.previewMedia(reference);\n if (state.previewFor !== key) { try { StillMade.releaseMedia(preview.url); } catch {} return; }\n state.previewUrl = preview.url; $('player').src = preview.url;\n } catch (error) {\n if (typeof reference.url === 'string' && /^https?:|^\\//.test(reference.url)) $('player').src = reference.url;\n else status(message(error), 'error', '');\n }\n}\n\n// ── Running ───────────────────────────────────────────────────────────────\nfunction values(forVoice) {\n const value = {};\n if (facts()) value.brief = { schemaVersion: 1, text: facts() };\n value.tone = picked('tone') || 'warm';\n const rules = trimmed($('rules').value); if (rules) value.rules = rules;\n if (forVoice) {\n value.script = { schemaVersion: 1, text: script() };\n value.voice = picked('voice') || 'onyx';\n value.speed = Number(picked('speed')) || 1;\n if (state.input.take) value.take = state.input.take;\n }\n return value;\n}\nasync function act(writeAgain) {\n if (state.busy) return;\n const p = writeAgain ? 'write' : phase();\n state.busy = true; render();\n status(p === 'write' ? 'Writing the script' : p === 'take' ? 'Keeping your recording' : 'Making the voice', 'loading', '');\n try {\n const result = await StillMade.run(values(p !== 'write'));\n state.outputs = { ...state.outputs, ...result };\n if (p === 'write') { setField('words', words(result.script)); await showAudio(null); }\n else await showAudio(result.audio, p === 'take' ? 'Your recording' : voiceName());\n } catch (error) {\n
|
|
184
|
+
"javascript": "(()=>{\n\"use strict\";\n// The Narration interface on the Block kit: the voiceover on the stage, the\n// facts and the script on the left, the voice as chips, and one button that\n// names the next step: Write script, then Make voiceover, or Use my recording.\n// The status beside it says the state in a few words; the price is StillMade's.\nconst $ = id => document.getElementById(id);\nconst state = { input: {}, outputs: {}, busy: false, previewUrl: null, previewFor: null, factsFromChat: false };\nconst message = error => String(error && error.message || error || 'Something went wrong.');\nconst say = (id, text) => { $(id).textContent = text || ''; };\nconst trimmed = value => String(value || '').trim();\nconst wordCount = text => trimmed(text) ? trimmed(text).split(/\\s+/).length : 0;\nconst clock = seconds => { const whole = Math.max(0, Math.round(seconds)); return Math.floor(whole / 60) + ':' + String(whole % 60).padStart(2, '0'); };\nconst spoken = text => 'about ' + clock(wordCount(text) / 150 * 60);\nconst picked = name => { const node = document.querySelector('input[name=\"' + name + '\"]:checked'); return node ? node.value : ''; };\nconst pick = (name, value) => { const node = document.querySelector('input[name=\"' + name + '\"][value=\"' + value + '\"]'); if (node) node.checked = true; };\n// A brief or script value is {schemaVersion: 1, text}; older briefs carry a topic.\nconst words = value => typeof value === 'string' ? value : value && typeof value === 'object' ? trimmed(value.text || value.topic || value.summary || '') : '';\nconst facts = () => trimmed($('facts').value);\nconst script = () => trimmed($('words').value);\nconst sameWords = (a, b) => trimmed(a).replace(/\\s+/g, ' ') === trimmed(b).replace(/\\s+/g, ' ');\n\nfunction status(text, tone, why) {\n const node = $('status');\n node.textContent = text || '';\n if (tone) node.dataset.state = tone; else delete node.dataset.state;\n $('why').hidden = !why; $('why').open = false; say('why-text', why || '');\n}\n// A field the host shares is written through its own input event, so the edit is kept.\nfunction setField(id, value) {\n const node = $(id);\n if (node.value === value) return;\n node.value = value;\n node.dispatchEvent(new Event('input', { bubbles: true }));\n node.dispatchEvent(new Event('change', { bubbles: true }));\n}\n\n// ── What the person is about to do ──────────────────────────────────────\nfunction phase() {\n if (!script()) return 'write';\n if (state.input.take) return 'take';\n const audio = state.outputs.audio, made = state.outputs.script;\n return audio && made && sameWords(words(made), script()) ? 'ready' : 'voice';\n}\nfunction render() {\n const p = phase(), count = wordCount(script());\n say('words-state', count ? count + ' words · ' + spoken(script()) : '');\n say('facts-state', facts() ? (state.factsFromChat ? 'From Chat' : '') : 'Needed');\n const labels = { write: 'Write script', voice: 'Make voiceover', ready: 'Make voiceover again', take: 'Use my recording' };\n say('action', labels[p]);\n $('again').hidden = p === 'write';\n $('action').disabled = state.busy || (p === 'write' && !facts());\n $('again').disabled = state.busy;\n if (state.busy) return;\n if (p === 'write') status(facts() ? 'Written from the facts' : 'Add the facts first', facts() ? '' : 'waiting', facts() ? 'The script uses only what the facts say; you can change every word before the voice runs.' : 'Chat usually fills the facts from your conversation. Type them here when it has not.');\n else if (p === 'take') status('Your recording will be used', '', 'The audio you supplied becomes the voiceover; nothing is generated.');\n else if (p === 'voice') status(state.outputs.audio ? 'Script changed, so the voice runs again' : 'Script is done, so only the voice runs', '', 'Only the voice is generated now; the words stay as written.');\n else status('Voiceover ready', 'success', '');\n}\n\n// ── The stage: the voiceover, when there is one ───────────────────────────\nasync function showAudio(reference, from) {\n if (!reference) { $('take').hidden = true; $('empty').hidden = false; return; }\n $('empty').hidden = true; $('take').hidden = false;\n say('from', from || '');\n say('length', Number.isFinite(reference.duration) ? clock(reference.duration) : spoken(script()));\n const key = reference.assetId + '/' + reference.versionId;\n if (state.previewFor === key) return;\n if (state.previewUrl) { try { StillMade.releaseMedia(state.previewUrl); } catch {} state.previewUrl = null; }\n state.previewFor = key;\n try {\n const preview = await StillMade.previewMedia(reference);\n if (state.previewFor !== key) { try { StillMade.releaseMedia(preview.url); } catch {} return; }\n state.previewUrl = preview.url; $('player').src = preview.url;\n } catch (error) {\n if (typeof reference.url === 'string' && /^https?:|^\\//.test(reference.url)) $('player').src = reference.url;\n else status(message(error), 'error', '');\n }\n}\n\n// ── Running ───────────────────────────────────────────────────────────────\nfunction values(forVoice) {\n const value = {};\n if (facts()) value.brief = { schemaVersion: 1, text: facts() };\n value.tone = picked('tone') || 'warm';\n const rules = trimmed($('rules').value); if (rules) value.rules = rules;\n if (forVoice) {\n value.script = { schemaVersion: 1, text: script() };\n value.voice = picked('voice') || 'onyx';\n value.speed = Number(picked('speed')) || 1;\n if (state.input.take) value.take = state.input.take;\n }\n return value;\n}\nasync function act(writeAgain) {\n if (state.busy) return;\n const p = writeAgain ? 'write' : phase();\n state.busy = true; render();\n status(p === 'write' ? 'Writing the script' : p === 'take' ? 'Keeping your recording' : 'Making the voice', 'loading', '');\n try {\n const result = await StillMade.run(values(p !== 'write'));\n state.outputs = { ...state.outputs, ...result };\n if (p === 'write') { setField('words', words(result.script)); await showAudio(null); }\n else await showAudio(result.audio, p === 'take' ? 'Your recording' : voiceName());\n } catch (error) {\n // render() writes the phase's status, so the error goes after it and stays.\n state.busy = false; render();\n status(message(error), 'error', ''); return;\n }\n state.busy = false; render();\n}\nconst voiceName = () => { const node = document.querySelector('input[name=\"voice\"]:checked'); return node ? node.parentElement.textContent.trim().split(/\\s+/)[0] : 'Voice'; };\n$('action').addEventListener('click', () => act(false));\n$('again').addEventListener('click', () => act(true));\nfor (const id of ['facts', 'words', 'rules']) $(id).addEventListener('input', () => { if (id === 'facts') state.factsFromChat = false; render(); });\nfor (const name of ['tone', 'voice', 'speed']) for (const node of document.querySelectorAll('input[name=\"' + name + '\"]')) node.addEventListener('change', render);\n\n// ── What the host gives: the Step's inputs and what was saved before ──────\nStillMade.onInput(input => {\n state.input = input || {};\n fillFromInput();\n if (typeof state.input.tone === 'string' && state.input.tone in { warm: 1, friendly: 1, 'no-nonsense': 1, luxury: 1 }) pick('tone', state.input.tone);\n if (typeof state.input.voice === 'string') pick('voice', state.input.voice);\n render();\n});\n// The host's shared fields start from the saved shared state, which can arrive after the\n// Step's input did and leave a bound field empty; what the input gave is put back then.\nfunction fillFromInput() {\n const brief = words(state.input.brief);\n if (brief && !facts()) { setField('facts', brief); state.factsFromChat = true; }\n const given = words(state.input.script);\n if (given && !script()) setField('words', given);\n}\nfunction adopt(snapshot) {\n const outputs = snapshot && snapshot.outputs || {};\n state.outputs = outputs;\n fillFromInput();\n if (outputs.script && !script()) setField('words', words(outputs.script));\n if (outputs.audio) showAudio(outputs.audio, state.input.take ? 'Your recording' : voiceName()); else showAudio(null);\n render();\n}\nif (typeof StillMade.onShared === 'function') StillMade.onShared(adopt);\ntry { if (typeof StillMade.getShared === 'function') { const shared = StillMade.getShared(); if (shared && typeof shared.then === 'function') shared.then(adopt, () => {}); else adopt(shared); } } catch {}\nrender();\n\n})();\n"
|
|
185
185
|
},
|
|
186
186
|
"tests": [
|
|
187
187
|
{
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import {HOSTED_OPERATION_NAMES} from './contracts.js';
|
|
10
10
|
|
|
11
11
|
export const BUILDER_CONTRACT_ID='stillmade.block-builder';
|
|
12
|
-
export const BUILDER_CONTRACT_VERSION='1.
|
|
12
|
+
export const BUILDER_CONTRACT_VERSION='1.7.0';
|
|
13
13
|
export const BUILDER_CONTRACT_STAMP=`${BUILDER_CONTRACT_ID}@${BUILDER_CONTRACT_VERSION}`;
|
|
14
14
|
export const BUILDER_CONTRACT_DOCS_PATH='/docs/build/block-builder-contract';
|
|
15
15
|
// Block names appear in step bars, cards and pickers; new releases keep them short.
|
|
@@ -62,8 +62,8 @@ const SECTIONS=Object.freeze([
|
|
|
62
62
|
`Name the Block in ${BLOCK_NAME_MAX_LENGTH} characters or fewer, in plain words.`,
|
|
63
63
|
`Give the Block a short, plain name of ${BLOCK_NAME_MAX_LENGTH} characters or fewer that says what it does ("Captioned Clip", not "Captioned Business Clip Generator With Review"). The name appears in step bars, Project Type cards and pickers, so put the detail in the description instead. New releases with a longer name do not pass readiness.`),
|
|
64
64
|
rule('ui','block',
|
|
65
|
-
'Prefer `manifest.ui`; custom views use the Block kit (`sm-*` classes) unless a look is named. Pictures over words: preview each option before choosing; cut needless words, fold the rest; the start button names the action (never "Run") and shows no price (StillMade adds it)
|
|
66
|
-
'If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction genuinely needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, picks as pictures in `sm-choices`, one `sm-primary` naming the action (Write, Generate; never "Run") 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. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Do not show raw JSON, internal IDs, filesystem details, provider terminology or developer settings unless the Block is itself a developer tool. When a person chooses among options they need to hear or see (voices, music, sounds, fonts, looks, colors, templates or images), let them preview each option before choosing: a sample to play or a thumbnail, from the Block\'s own files or the host\'s free samples, never a paid generation. Leave previews out only when the builder states there is nothing to preview. Choices StillMade owns, such as voices and models in the run review, already show their samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (result first, action pinned), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.'),
|
|
65
|
+
'Prefer `manifest.ui`; custom views use the Block kit (`sm-*` classes) unless a look is named. Pictures over words: preview each option before choosing; cut needless words, fold the rest; the start button names the action (never "Run") 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.',
|
|
66
|
+
'If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction genuinely needs one. Build custom views from the Block kit the host injects (`block-kit.css`): the result in `sm-stage`, picks as pictures in `sm-choices`, one `sm-primary` naming the action (Write, Generate; never "Run") 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 (a video editor, a 3D configurator, a 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. Drop words nobody needs; put helpful ones in a closed `sm-hint`. Expose the capability, not the implementation: sensible defaults, previews, progress, clear errors, visible outputs. Do not show raw JSON, internal IDs, filesystem details, provider terminology or developer settings unless the Block is itself a developer tool. When a person chooses among options they need to hear or see (voices, music, sounds, fonts, looks, colors, templates or images), let them preview each option before choosing: a sample to play or a thumbnail, from the Block\'s own files or the host\'s free samples, never a paid generation. Leave previews out only when the builder states there is nothing to preview. Choices StillMade owns, such as voices and models in the run review, already show their samples. Follow the host appearance (Light, Dark and White) and one elastic layout: stacked on a 320px phone with no sideways scroll (result first, action pinned), and from 860px the controls left (≤380px), the result filling the right. Short settings sit side by side, each label beside its control (`sm-field`, `sm-settings`; three or four short options as one `data-segmented` row); many items are one line each (`sm-list`) that open in place; input files are small tiles (`sm-gallery data-size="small"`); on a phone chip rows scroll sideways and long lists show three. Compacting moves controls, never deletes them.'),
|
|
67
67
|
rule('multiplayer','block',
|
|
68
68
|
'Custom views share edits: `StillMade.onShared`, publish every editable field (`data-stillmade-share`, `bindShared`, `updateShared`), respect `canEdit`, never re-run on remote updates.',
|
|
69
69
|
'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.'),
|