@slatesvideo/shared 0.6.2 → 0.6.4
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/dist/api-url.d.ts +9 -0
- package/dist/api-url.js +9 -0
- package/dist/auth.d.ts +13 -1
- package/dist/auth.js +9 -5
- package/dist/clients/cloud.d.ts +3 -0
- package/dist/clients/cloud.js +34 -3
- package/dist/clients/desktop.js +3 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +44 -1
- package/dist/operations/index.d.ts +243 -31
- package/dist/operations/index.js +1483 -154
- package/dist/operations/surface.d.ts +69 -0
- package/dist/operations/surface.js +227 -0
- package/dist/prompts/agent-doctrine.d.ts +36 -0
- package/dist/prompts/agent-doctrine.js +201 -0
- package/dist/prompts/asset-label.d.ts +23 -0
- package/dist/prompts/asset-label.js +70 -0
- package/dist/prompts/banned-tokens.d.ts +40 -0
- package/dist/prompts/banned-tokens.js +219 -0
- package/dist/prompts/character-sheet.d.ts +0 -2
- package/dist/prompts/character-sheet.js +0 -2
- package/dist/prompts/craft-cards.d.ts +20 -0
- package/dist/prompts/craft-cards.js +82 -0
- package/dist/prompts/environment-sheet.js +16 -0
- package/dist/prompts/index.d.ts +1 -0
- package/dist/prompts/index.js +4 -0
- package/dist/prompts/model-capabilities.d.ts +65 -1
- package/dist/prompts/model-capabilities.js +139 -2
- package/dist/prompts/model-facts.d.ts +20 -4
- package/dist/prompts/model-facts.js +95 -27
- package/dist/prompts/partials.generated.js +2 -1
- package/dist/prompts/prompting-tips.d.ts +1 -1
- package/dist/prompts/prompting-tips.js +123 -0
- package/dist/prompts/reference-composer.d.ts +36 -7
- package/dist/prompts/reference-composer.js +75 -20
- package/dist/prompts/reference-rules.d.ts +15 -26
- package/dist/prompts/reference-rules.js +15 -93
- package/dist/prompts/shot-grammar.d.ts +154 -0
- package/dist/prompts/shot-grammar.js +184 -0
- package/dist/prompts/shot-spec.d.ts +265 -0
- package/dist/prompts/shot-spec.js +303 -0
- package/dist/skills/content.js +25 -22
- package/exports/slates-prompt-builder/generated/SKILL.md +3 -3
- package/exports/slates-prompt-builder/generated/reference-content-policy.md +6 -0
- package/exports/slates-prompt-builder/generated/reference-kling.md +22 -0
- package/exports/slates-prompt-builder/generated/reference-nano-banana.md +17 -0
- package/exports/slates-prompt-builder/generated/reference-seedance.md +19 -1
- package/exports/slates-prompt-builder/generated/slates-prompt-builder-manifest.json +17 -17
- package/exports/slates-prompt-builder/generated/slates-prompt-builder.skill +0 -0
- package/package.json +83 -73
- package/skills/_partials/decision-log.md +5 -4
- package/skills/_partials/thresholds.md +19 -0
- package/skills/slates-content-policy.md +15 -1
- package/skills/slates-cost-discipline.md +26 -4
- package/skills/slates-model-selection.md +2 -2
- package/skills/slates-one-prompt-film.md +20 -12
- package/skills/slates-project-organization.md +1 -1
- package/skills/slates-prompting-elevenlabs.md +61 -2
- package/skills/slates-prompting-flux-2-max.md +39 -0
- package/skills/slates-prompting-gpt-image-2.md +109 -70
- package/skills/slates-prompting-inworld-tts.md +166 -0
- package/skills/slates-prompting-kling-v3.md +39 -0
- package/skills/slates-prompting-lip-sync.md +38 -0
- package/skills/slates-prompting-ltx-2-5.md +218 -0
- package/skills/slates-prompting-minimax-h3.md +39 -0
- package/skills/slates-prompting-motion-transfer.md +38 -0
- package/skills/slates-prompting-nano-banana-2.md +36 -0
- package/skills/slates-prompting-omni-flash.md +41 -0
- package/skills/slates-prompting-seed-audio.md +38 -0
- package/skills/slates-prompting-seedance-2-5.md +38 -0
- package/skills/slates-prompting-seedance.md +36 -1
- package/skills/slates-prompting-seedream-5-lite.md +38 -0
- package/skills/slates-prompting-veo-3.md +39 -0
- package/skills/slates-shot-variety.md +53 -0
- package/skills/slates-storyboard-from-script.md +31 -15
- package/skills/slates-style-prompting.md +1 -1
- package/skills/slates-vision-feedback-loop.md +1 -1
|
@@ -640,6 +640,127 @@ const MINIMAX_H3 = {
|
|
|
640
640
|
'Slates disables the provider\'s prompt expander, so what you write is what the model reads — nothing will pad a thin prompt. Aim for a 350-500 word body on a reference-carrying shot, and let dialogue-heavy scenes run longer if that is what fits the spoken timeline.',
|
|
641
641
|
],
|
|
642
642
|
};
|
|
643
|
+
const LTX_2_5 = {
|
|
644
|
+
label: 'LTX-2.5',
|
|
645
|
+
intro: [
|
|
646
|
+
'LTX-2.5 scores the picture on the same pass that draws it, so SOUND IS THE FIRST THING YOU WRITE, not the last. Lightricks ranks the six parts of a prompt in this order: sound, camera, character detail, shot type and scene, then scene dressing — and scene dressing is the first thing to cut when a prompt sprawls. Everything goes in ONE flowing paragraph, not a list of labelled sections.',
|
|
647
|
+
'Two seats. Base LTX-2.5 is the distilled build: 720p / 1080p / 1440p / 4K and clips from 6 to 20 seconds, and it is the cheapest native 1080p second in Slates. LTX-2.5 Pro is the full diffusion build ("Diffusion Fidelity Rendering" spends extra compute on busy frames) but reaches a SHORTER ladder — 1080p and 10 seconds maximum — while costing about a third more. Pro is for a dense final render; base is for iteration, long takes and 4K.',
|
|
648
|
+
],
|
|
649
|
+
columns: [
|
|
650
|
+
[
|
|
651
|
+
{
|
|
652
|
+
heading: 'Anchor every sound to something in frame',
|
|
653
|
+
example: 'the rope creaks against the cleat, gulls somewhere off the port bow',
|
|
654
|
+
note: 'Write the audio line last, then check each cue has a visible or at least locatable source. Anything unanchored gets invented for you. "Not visible but locatable" passes — a whistle is fine if you name the marshal post it comes from.',
|
|
655
|
+
critical: true,
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
heading: 'Never write mood words for sound',
|
|
659
|
+
example: 'Bad: "tense atmosphere, sense of dread"\nGood: "a loose shutter knocks twice against the frame"',
|
|
660
|
+
note: 'Atmosphere adjectives produce nothing. If a scene feels thin, add one more MOVING OBJECT with a sound attached to it rather than another adjective.',
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
heading: 'Dialogue takes quotes, language and accent',
|
|
664
|
+
example: '"We should not have come back," in English with a slight German accent.',
|
|
665
|
+
note: 'Give the character a beat of stillness before they speak so the lip sync has something to lock against. Describe the beat: looks, waits, speaks, looks away.',
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
heading: 'Emotion is physical, not abstract',
|
|
669
|
+
example: 'Bad: "she looks anxious"\nGood: "her jaw sets, she turns the ring on her finger twice"',
|
|
670
|
+
note: 'The model renders actions, not adjectives. Tension in the jaw, weight shifts, fidgeting hands — these read; "anxious" does not.',
|
|
671
|
+
},
|
|
672
|
+
],
|
|
673
|
+
[
|
|
674
|
+
{
|
|
675
|
+
heading: 'Multishot: two to four shots, and re-establish at every cut',
|
|
676
|
+
example: 'wide establishing shot — hard cut — macro close-up — match cut — medium shot',
|
|
677
|
+
note: 'One generation can carry several connected shots holding character, light and voice across the cuts. Name the edit ("hard cut", "dissolve") in the prose, then RESET scale, angle, lens and light. Two to four is the working range.',
|
|
678
|
+
critical: true,
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
heading: 'Re-identify characters at every cut',
|
|
682
|
+
example: 'Good: "the woman in the bronze gown"\nBad: "she"',
|
|
683
|
+
note: 'Pronouns lose the character across a cut. Repeat the original descriptor every time. Also state what the SOUND does at the cut — silence is not assumed.',
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
heading: 'Write the camera into the sentence',
|
|
687
|
+
example: 'a slow push-in settles as she reaches the door, then holds',
|
|
688
|
+
note: 'Slates does not expose the camera_motion enum, and prose is the better tool anyway: a written move can be tied to a specific moment, an enum value cannot. Name lens, framing and the moment the move resolves.',
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
heading: 'Durations are even numbers only, from six',
|
|
692
|
+
note: '6, 8, 10, 12, 14, 16, 18 or 20 seconds — there is no 5s or 7s LTX clip. And the long end is 1080p-and-below only: at 1440p and 4K the ceiling drops to 10s. Slates always sends an explicit length rather than letting the model pick one, so what you choose is what you are billed for.',
|
|
693
|
+
critical: true,
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
heading: 'Do not ask for text on screen',
|
|
697
|
+
note: 'Neither the spelling nor its stability frame to frame can be relied on. Signage, labels and captions belong in post.',
|
|
698
|
+
},
|
|
699
|
+
],
|
|
700
|
+
],
|
|
701
|
+
footer: [
|
|
702
|
+
'Frames, not references. LTX takes a start frame and an optional end frame (which generates a transition between the two) — it has no reference endpoint at all, so identity, style and environment reference images are not available on this model. For character consistency across separate shots, use MiniMax H3 or Kling.',
|
|
703
|
+
'Aspect ratios are 16:9 and 9:16 only, and native audio is included free at every resolution — there is no sound surcharge on either seat.',
|
|
704
|
+
'In image-to-video, do not cut away from the opening frame too early: you have paid for that frame, so let it play before the first move.',
|
|
705
|
+
],
|
|
706
|
+
};
|
|
707
|
+
const INWORLD_TTS = {
|
|
708
|
+
label: 'Inworld TTS-2',
|
|
709
|
+
intro: [
|
|
710
|
+
'The voice seat: one named voice saying one line. Unlike every other surface in Slates, the prompt is not a description of what you want — it IS the words that get spoken, verbatim, and its length is what you are billed for.',
|
|
711
|
+
'A voice belongs to a character, the same way a face does. Build it once from a clip or a description, then send it lines.',
|
|
712
|
+
],
|
|
713
|
+
columns: [
|
|
714
|
+
[
|
|
715
|
+
{
|
|
716
|
+
heading: 'Direction goes in SQUARE BRACKETS',
|
|
717
|
+
example: '\u2717 (quietly) I hope nobody notices\n\u2713 [whispering] I hope nobody notices',
|
|
718
|
+
note: 'Brackets are read as direction and never spoken. PARENTHESES ARE SPOKEN ALOUD \u2014 a parenthetical stage direction comes back with the narrator saying the word "quietly". This is the single easiest way to ruin a take.',
|
|
719
|
+
critical: true,
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
heading: 'Plain English inside the brackets',
|
|
723
|
+
example: '[very quiet] \u00b7 [very slow] \u00b7 [say excitedly] \u00b7 [whisper in a hushed style]',
|
|
724
|
+
note: 'It is natural-language steering across emotion, volume, pitch, speed, articulation and vocal style \u2014 not a fixed vocabulary. Write the direction the way you would say it to an actor.',
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
heading: 'Non-verbals are their own tags',
|
|
728
|
+
example: '[laugh] \u00b7 [sigh] \u00b7 [breathe] \u00b7 [cough] \u00b7 [yawn] \u00b7 [clear throat]',
|
|
729
|
+
note: 'They land inline, where they occur in the line.',
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
heading: 'A mistyped tag fails silently',
|
|
733
|
+
note: 'An instruction it does not recognise is still swallowed and still changes the delivery \u2014 it is never spoken and never errors. So a typo produces a strange read with no warning. If a take sounds off, suspect the tag before the voice.',
|
|
734
|
+
critical: true,
|
|
735
|
+
},
|
|
736
|
+
],
|
|
737
|
+
[
|
|
738
|
+
{
|
|
739
|
+
heading: 'Tags persist until changed',
|
|
740
|
+
example: '[very slow] First line. Second line is still slow. [reset] Third is normal.',
|
|
741
|
+
note: 'A direction governs everything after it, across sentences. Use [reset] to return to a normal read rather than assuming the next sentence starts clean.',
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
heading: 'Punctuation is the timing',
|
|
745
|
+
example: 'Wait. Stop. \u2260 Wait, stop.',
|
|
746
|
+
note: 'Full stops buy a beat; commas do not. Write the line the way it is said.',
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
heading: 'One line, one take',
|
|
750
|
+
note: 'Split a paragraph into separate generations so a bad clause costs one re-roll instead of the whole speech. Max 2,000 characters per take.',
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
heading: 'Spell out anything ambiguous',
|
|
754
|
+
example: 'twenty twenty-six \u00b7 Doctor Reyes',
|
|
755
|
+
note: 'Numbers, dates and abbreviations are read literally. Write them as they should sound.',
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
heading: 'Know its seat',
|
|
759
|
+
note: 'One voice, cleanly. Dialogue mixed with effects and room tone in one pass is Seed Audio; a single non-speech sound is Sound Effects.',
|
|
760
|
+
},
|
|
761
|
+
],
|
|
762
|
+
],
|
|
763
|
+
};
|
|
643
764
|
export const PROMPTING_TIPS = {
|
|
644
765
|
seedance: SEEDANCE,
|
|
645
766
|
'seedance-2-5': SEEDANCE_25,
|
|
@@ -650,10 +771,12 @@ export const PROMPTING_TIPS = {
|
|
|
650
771
|
'omni-flash': OMNI_FLASH,
|
|
651
772
|
'omni-flash-edit': OMNI_FLASH_EDIT,
|
|
652
773
|
'minimax-h3': MINIMAX_H3,
|
|
774
|
+
'ltx-2-5': LTX_2_5,
|
|
653
775
|
'nano-banana': NANO_BANANA,
|
|
654
776
|
'nano-banana-lite': NANO_BANANA_LITE,
|
|
655
777
|
'seed-audio': SEED_AUDIO,
|
|
656
778
|
'eleven-sfx': ELEVEN_SFX,
|
|
779
|
+
'inworld-tts-2': INWORLD_TTS,
|
|
657
780
|
};
|
|
658
781
|
/** Null when no tips exist for the key — callers render an honest fallback. */
|
|
659
782
|
export function getPromptingTips(key) {
|
|
@@ -50,16 +50,45 @@ export interface ComposedReferences {
|
|
|
50
50
|
* Tokens written in the prompt that matched NO reference group, as authored
|
|
51
51
|
* (`'#noir'`, `'@bob'`), first-appearance order, deduped case-insensitively.
|
|
52
52
|
*
|
|
53
|
-
* 🚨
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
53
|
+
* 🚨 IT IS A REPORT, NOT A RECEIPT FOR A DELETION. These tokens are left in
|
|
54
|
+
* `prompt` EXACTLY as the user typed them (see step 2) — nothing is removed
|
|
55
|
+
* and nothing is humanised. What the field says is narrower and more useful:
|
|
56
|
+
* "no reference is attached for this word", which is the difference between a
|
|
57
|
+
* mistyped mention and a `@handle` you meant to typeset. Callers that render a
|
|
58
|
+
* composed-prompt preview SHOULD surface it — as a note, never as an error,
|
|
59
|
+
* because nothing has gone wrong.
|
|
60
60
|
*/
|
|
61
61
|
unresolvedTokens: string[];
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* 🚨 A HEX COLOUR IS NOT A TAG. `#000000` is to `#` what `eric@gmail.com` is to
|
|
65
|
+
* `@`: an everyday literal that happens to open with our sigil, written by
|
|
66
|
+
* someone who was not reaching for our feature at all.
|
|
67
|
+
*
|
|
68
|
+
* The TEXT was already safe — an unresolved token is returned byte-identical
|
|
69
|
+
* (see TOKEN_RE) — so what this guards is the REPORT. The `@` half of that
|
|
70
|
+
* grammar gets its everyday literal excluded for free, because an email's `@`
|
|
71
|
+
* sits after a word character and the boundary lookbehind kills it. A hex
|
|
72
|
+
* colour gets no such help: `#` at a word boundary is exactly how a real tag is
|
|
73
|
+
* written too, so `#000000` reaches `noteUnresolved` and a palette prompt
|
|
74
|
+
* ("pure black #000000 with #c8ff00 accents") comes back with every colour
|
|
75
|
+
* listed as *matching nothing saved* — an accusation about words that were
|
|
76
|
+
* never mentions. That is the asymmetry this closes, and it is the same
|
|
77
|
+
* complaint, in the same place, as `#3a3a3c` being eaten for months.
|
|
78
|
+
*
|
|
79
|
+
* 🚨 IT GATES THE REPORT, NOT THE GRAMMAR, and that is the whole design.
|
|
80
|
+
* `fade`, `cafe`, `dead`, `beef`, `decade` and `facade` are all valid hex
|
|
81
|
+
* digits AND plausible style names, so excluding hex shapes from TOKEN_RE would
|
|
82
|
+
* quietly stop `#fade` from attaching a style called "Fade" — trading a noisy
|
|
83
|
+
* note for a silent drop, which is the worse half of the trade every time.
|
|
84
|
+
* Resolution is checked first and always wins; only a token that binds to
|
|
85
|
+
* nothing ever reaches here.
|
|
86
|
+
*
|
|
87
|
+
* Lengths are CSS's: 3 (`#fff`), 4 (`#fff8`), 6 (`#c8ff00`), 8 (`#c8ff00ff`).
|
|
88
|
+
* `#1` and `#2026` are deliberately NOT covered — an ordinal is not a colour,
|
|
89
|
+
* and widening this to "anything numeric" would start swallowing tags.
|
|
90
|
+
*/
|
|
91
|
+
export declare function isHexColorToken(sigil: string, token: string): boolean;
|
|
63
92
|
/**
|
|
64
93
|
* Compose the raw prompt (mentions intact) + an ORDERED list of reference groups
|
|
65
94
|
* into the named prompt text + ordered media the API receives. The group order
|
|
@@ -44,13 +44,66 @@ function joinNums(nums) {
|
|
|
44
44
|
return `${nums[0]} and ${nums[1]}`;
|
|
45
45
|
return `${nums.slice(0, -1).join(', ')} and ${nums[nums.length - 1]}`;
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
/**
|
|
48
|
+
* 🚨 A SIGIL IS A MENTION ONLY IF IT RESOLVES. Everything else is prose, and
|
|
49
|
+
* prose is not ours to edit.
|
|
50
|
+
*
|
|
51
|
+
* This grammar used to be `/([@#])([\w-]+)/g` with "unresolved" as a rewrite
|
|
52
|
+
* branch: an unknown `@word` was humanised (`@woodland.candle` → "Woodland" +
|
|
53
|
+
* ".candle") and an unknown `#word` was deleted outright. Both assumed anyone
|
|
54
|
+
* typing a sigil was reaching for OUR feature, so the app quietly rewrote the
|
|
55
|
+
* one thing the composer exists to protect — and it failed precisely where the
|
|
56
|
+
* literal characters matter most: a poster's `@handle`, an email address, a hex
|
|
57
|
+
* colour. That last one is not hypothetical: `#3a3a3c` was eaten out of every
|
|
58
|
+
* identity-sheet prompt for months (`reference-rules.ts`, 2026-07-30), which is
|
|
59
|
+
* the same bug wearing a different sigil.
|
|
60
|
+
*
|
|
61
|
+
* Two changes make the sigil safe to type:
|
|
62
|
+
*
|
|
63
|
+
* 1. THE GRAMMAR IS NARROW. A sigil only opens a token at a boundary, so
|
|
64
|
+
* `eric@gmail.com` is never even a candidate, and a token may carry interior
|
|
65
|
+
* dots, so `@woodland.candle` is ONE token rather than a mention with debris
|
|
66
|
+
* stuck to it. Interior only — a trailing `.` stays with the sentence.
|
|
67
|
+
* 2. AN UNRESOLVED TOKEN IS RETURNED UNTOUCHED. A mention is a BINDING; when it
|
|
68
|
+
* binds to nothing there is nothing to translate, so there is nothing to
|
|
69
|
+
* rewrite. It is reported through `unresolvedTokens` and sent as written.
|
|
70
|
+
*
|
|
71
|
+
* There is deliberately NO escape syntax (`\@`, quoting). Making someone learn
|
|
72
|
+
* our grammar to opt OUT of a feature they never invoked is the shoehorn this
|
|
73
|
+
* change exists to remove — the sandbox rule from the root `CLAUDE.md`: a
|
|
74
|
+
* capability attaches to the primitive, it does not stand in the way of it.
|
|
75
|
+
*/
|
|
76
|
+
const TOKEN_RE = /(?<![\w.@#])([@#])([\w-]+(?:\.[\w-]+)*)/g;
|
|
77
|
+
/**
|
|
78
|
+
* 🚨 A HEX COLOUR IS NOT A TAG. `#000000` is to `#` what `eric@gmail.com` is to
|
|
79
|
+
* `@`: an everyday literal that happens to open with our sigil, written by
|
|
80
|
+
* someone who was not reaching for our feature at all.
|
|
81
|
+
*
|
|
82
|
+
* The TEXT was already safe — an unresolved token is returned byte-identical
|
|
83
|
+
* (see TOKEN_RE) — so what this guards is the REPORT. The `@` half of that
|
|
84
|
+
* grammar gets its everyday literal excluded for free, because an email's `@`
|
|
85
|
+
* sits after a word character and the boundary lookbehind kills it. A hex
|
|
86
|
+
* colour gets no such help: `#` at a word boundary is exactly how a real tag is
|
|
87
|
+
* written too, so `#000000` reaches `noteUnresolved` and a palette prompt
|
|
88
|
+
* ("pure black #000000 with #c8ff00 accents") comes back with every colour
|
|
89
|
+
* listed as *matching nothing saved* — an accusation about words that were
|
|
90
|
+
* never mentions. That is the asymmetry this closes, and it is the same
|
|
91
|
+
* complaint, in the same place, as `#3a3a3c` being eaten for months.
|
|
92
|
+
*
|
|
93
|
+
* 🚨 IT GATES THE REPORT, NOT THE GRAMMAR, and that is the whole design.
|
|
94
|
+
* `fade`, `cafe`, `dead`, `beef`, `decade` and `facade` are all valid hex
|
|
95
|
+
* digits AND plausible style names, so excluding hex shapes from TOKEN_RE would
|
|
96
|
+
* quietly stop `#fade` from attaching a style called "Fade" — trading a noisy
|
|
97
|
+
* note for a silent drop, which is the worse half of the trade every time.
|
|
98
|
+
* Resolution is checked first and always wins; only a token that binds to
|
|
99
|
+
* nothing ever reaches here.
|
|
100
|
+
*
|
|
101
|
+
* Lengths are CSS's: 3 (`#fff`), 4 (`#fff8`), 6 (`#c8ff00`), 8 (`#c8ff00ff`).
|
|
102
|
+
* `#1` and `#2026` are deliberately NOT covered — an ordinal is not a colour,
|
|
103
|
+
* and widening this to "anything numeric" would start swallowing tags.
|
|
104
|
+
*/
|
|
105
|
+
export function isHexColorToken(sigil, token) {
|
|
106
|
+
return sigil === '#' && /^(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(token);
|
|
54
107
|
}
|
|
55
108
|
export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
56
109
|
// ── 1. Assign global numbers by walking the list in order ──
|
|
@@ -99,11 +152,16 @@ export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
|
99
152
|
const seenFirst = new Set();
|
|
100
153
|
const matchedInPrompt = new Set();
|
|
101
154
|
// Every token that named nothing, recorded as authored and deduped by the
|
|
102
|
-
// same normalisation used for matching.
|
|
103
|
-
//
|
|
155
|
+
// same normalisation used for matching. Nothing is removed on its account —
|
|
156
|
+
// it is the "no reference is attached for this word" report. See
|
|
157
|
+
// ComposedReferences.unresolvedTokens.
|
|
104
158
|
const unresolvedTokens = [];
|
|
105
159
|
const unresolvedSeen = new Set();
|
|
106
160
|
const noteUnresolved = (sigil, tok) => {
|
|
161
|
+
// A hex colour that binds to no style is a colour, not a mistyped tag.
|
|
162
|
+
// See isHexColorToken — the text is unchanged either way; this is the note.
|
|
163
|
+
if (isHexColorToken(sigil, tok))
|
|
164
|
+
return;
|
|
107
165
|
const key = normToken(`${sigil}${tok}`);
|
|
108
166
|
if (unresolvedSeen.has(key))
|
|
109
167
|
return;
|
|
@@ -112,27 +170,24 @@ export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
|
112
170
|
};
|
|
113
171
|
// First strip "in/with the style of #tag" phrases so the style reads as a
|
|
114
172
|
// clean trailing clause, not a dangling preposition (legacy cleanPrompt
|
|
115
|
-
// behaviour).
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
let body = rawPrompt.replace(/\s+(with|in)\s+the\s+style\s+of\s+([@#])([\w-]+)/gi, (_full, _prep, sigil, tok) => {
|
|
173
|
+
// behaviour). ONLY when the tag resolves: an unresolved one leaves the whole
|
|
174
|
+
// phrase exactly as authored and falls through to the token pass below, which
|
|
175
|
+
// reports it and sends it as written.
|
|
176
|
+
let body = rawPrompt.replace(/\s+(with|in)\s+the\s+style\s+of\s+([@#])([\w-]+(?:\.[\w-]+)*)/gi, (_full, _prep, sigil, tok) => {
|
|
120
177
|
const g = byNorm.get(normToken(`${sigil}${tok}`));
|
|
121
178
|
if (g && g.kind === 'style') {
|
|
122
179
|
matchedInPrompt.add(normToken(`${sigil}${tok}`));
|
|
123
180
|
return '';
|
|
124
181
|
}
|
|
125
|
-
|
|
126
|
-
return '';
|
|
182
|
+
return _full;
|
|
127
183
|
});
|
|
128
|
-
body = body.replace(
|
|
184
|
+
body = body.replace(TOKEN_RE, (_full, _sigil, tok) => {
|
|
129
185
|
const key = normToken(`${_sigil}${tok}`);
|
|
130
186
|
const g = byNorm.get(key);
|
|
131
187
|
if (!g) {
|
|
132
|
-
//
|
|
133
|
-
// Both are edits the user never asked for, so both are reported.
|
|
188
|
+
// Not a mention — see TOKEN_RE. Reported, returned byte-identical.
|
|
134
189
|
noteUnresolved(_sigil, tok);
|
|
135
|
-
return
|
|
190
|
+
return _full;
|
|
136
191
|
}
|
|
137
192
|
matchedInPrompt.add(key);
|
|
138
193
|
if (g.kind === 'style')
|
|
@@ -1,17 +1,4 @@
|
|
|
1
1
|
export { PARTIALS } from './partials.generated.js';
|
|
2
|
-
export type SourceGrade = 'Eric-test' | 'community' | 'code-verified' | 'creator-demo';
|
|
3
|
-
export interface ReferenceRule {
|
|
4
|
-
id: string;
|
|
5
|
-
title: string;
|
|
6
|
-
rule: string;
|
|
7
|
-
why: string;
|
|
8
|
-
grade: SourceGrade;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* The 10 verified reference rules. These are the WHY; the fragments below
|
|
12
|
-
* are the reusable text the templates compose.
|
|
13
|
-
*/
|
|
14
|
-
export declare const REFERENCE_RULES: ReferenceRule[];
|
|
15
2
|
/** Flat, even, shadowless identity lighting on a deep neutral-grey plate. */
|
|
16
3
|
export declare const IDENTITY_LIGHTING = "flat, even, shadowless lighting";
|
|
17
4
|
/**
|
|
@@ -21,21 +8,23 @@ export declare const IDENTITY_LIGHTING = "flat, even, shadowless lighting";
|
|
|
21
8
|
*/
|
|
22
9
|
export declare const IDENTITY_PLATE_HEX = "#3a3a3c";
|
|
23
10
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* model: fal echoed back `deep neutral-grey background ()` on a real 2026-07-30
|
|
30
|
-
* request. The plate value has been doing nothing since the composer shipped.
|
|
11
|
+
* THE HEX IS EMITTED WITHOUT ITS `#`. This USED to be load-bearing (2026-07-30):
|
|
12
|
+
* `#` is a reference-token sigil in the prompt composer, and an unresolved
|
|
13
|
+
* `#token` was SILENTLY DELETED, so `#3a3a3c` never survived to any model — fal
|
|
14
|
+
* echoed back `deep neutral-grey background ()` on a real 2026-07-30 request and
|
|
15
|
+
* the plate value had been doing nothing since the composer shipped.
|
|
31
16
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
17
|
+
* 🚨 THE HAZARD IS GONE, AND THE OLD GENERAL RULE WITH IT. The composer now
|
|
18
|
+
* treats a sigil as a mention ONLY when it resolves and passes every other
|
|
19
|
+
* `@`/`#` through byte-identically (`reference-composer.ts` → `TOKEN_RE`) — the
|
|
20
|
+
* exact "HOW YOU'D KNOW THIS IS BEATEN" this comment used to name. Emitting a
|
|
21
|
+
* `#hex` or an `@handle` into prompt text is safe again.
|
|
34
22
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
23
|
+
* It stays bare here anyway: "hex 3a3a3c" reads at least as clearly to a model,
|
|
24
|
+
* and rewording it would churn the generated skills/docs corpus for no gain.
|
|
25
|
+
* That is now a style choice, NOT a workaround — do not re-derive a "never emit
|
|
26
|
+
* a sigil" law from it. `IDENTITY_PLATE_HEX` keeps its `#` because it is a
|
|
27
|
+
* colour constant with possible non-prompt consumers.
|
|
39
28
|
*/
|
|
40
29
|
export declare const IDENTITY_BACKGROUND: string;
|
|
41
30
|
export declare const IDENTITY_LIGHTING_CLAUSE: string;
|
|
@@ -1,90 +1,10 @@
|
|
|
1
1
|
// Reference best-practices — the canonical "how to use reference images"
|
|
2
2
|
// knowledge for every Slates surface (desktop templates, MCP skills, the
|
|
3
3
|
// lead-magnet .skill). Authored ONCE here; consumers derive from it.
|
|
4
|
-
//
|
|
5
|
-
// Source grades: [Eric-test] = Eric's own hands-on result (doctrine-grade);
|
|
6
|
-
// [community] = multi-guide consensus; [code-verified] = verified against
|
|
7
|
-
// the slate codebase; [creator-demo] = single creator demonstration.
|
|
8
4
|
// The generated partial store — one entry per skills/_partials/*.md file.
|
|
9
5
|
// Re-exported so consumers can reach any partial, not just the rules block.
|
|
10
6
|
export { PARTIALS } from './partials.generated.js';
|
|
11
7
|
import { PARTIALS } from './partials.generated.js';
|
|
12
|
-
/**
|
|
13
|
-
* The 10 verified reference rules. These are the WHY; the fragments below
|
|
14
|
-
* are the reusable text the templates compose.
|
|
15
|
-
*/
|
|
16
|
-
export const REFERENCE_RULES = [
|
|
17
|
-
{
|
|
18
|
-
id: 'two-to-four-refs',
|
|
19
|
-
title: '2-4 strong references beat both extremes',
|
|
20
|
-
rule: 'Use 2-4 strong, focused references — not 1 (warps), not 12 (averages worse). Start with 2-3.',
|
|
21
|
-
why: 'One reference warps toward itself; a dozen averages everyone into mush. One tester cut drift ~60% going 6→2.',
|
|
22
|
-
grade: 'community',
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: 'one-ref-per-role',
|
|
26
|
-
title: 'One reference per ROLE, labeled in the prompt',
|
|
27
|
-
rule: 'Use one reference per role — identity, style-grade, environment — and label each role explicitly in the prompt text. The model does not infer roles from order.',
|
|
28
|
-
why: 'Same-role competitors drift; two "identity" refs of different people blend into a third face.',
|
|
29
|
-
grade: 'community',
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
id: 'identity-name-as-one-entity',
|
|
33
|
-
title: 'One identity sheet per character, named inline',
|
|
34
|
-
rule: 'Attach the character\'s single identity sheet (dominant portrait + body panels) rather than a pile of views — fewer competing renderings of a face is better, because the model cannot tell which is authoritative and averages them. Cite it inline as the subject ("Marcus (image 1)"). Do not inject a role essay ("use for identity, ignore the outfit/lighting, render neutral"): the user\'s prompt owns wardrobe, expression, and lighting.',
|
|
35
|
-
why: 'Distinct inline naming is each model\'s own consistency lever — NB2 "assign a distinct name to each character/object"; Seedance "Reference <Subject_N> in <Image_N>"; Kling "reuse a fixed label verbatim". One canonical identity image removes same-role competition before it starts.',
|
|
36
|
-
grade: 'Eric-test',
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: 'flat-light-identity',
|
|
40
|
-
title: 'Flat-light identity refs',
|
|
41
|
-
rule: 'Prep identity refs with flat, even, shadowless lighting on a plain neutral background. Studio-lit or scene-lit sheets bleed their lighting into every generation.',
|
|
42
|
-
why: "Eric's Norse-woman test: a studio-lit sheet produced a subject that looked green-screen-pasted in front of mountains. Reference prep beats prompting here.",
|
|
43
|
-
grade: 'Eric-test',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
id: 'describe-environment',
|
|
47
|
-
title: 'Environment: describe it, don\'t feed a grid',
|
|
48
|
-
rule: 'Default to describing the environment in words. Reserve an environment reference for a mandatory exact-match location, and when you do, use ONE clean establishing image — never a multi-panel grid fed whole.',
|
|
49
|
-
why: "Eric's test: character sheet + 3x3 mountain grid → pasted-in mess; the same character + a described background → believable scene with natural lighting the model invented to fit.",
|
|
50
|
-
grade: 'Eric-test',
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
id: 'grids-explore-not-input',
|
|
54
|
-
title: 'Grids: generation/exploration = fine; INPUT reference = bad',
|
|
55
|
-
rule: 'Use grids to EXPLORE compositions (cheap multi-angle generation), then pick a cell. Do NOT feed a grid back in as a reference image — cells share a split detail budget and generate jointly, so flaws propagate.',
|
|
56
|
-
why: 'A picked cell re-generated with a "preserve the exact composition" prompt keeps its flaws faithfully (that is the budget path, kept on purpose). A loose prompt can fix anatomy but drifts off the picked composition.',
|
|
57
|
-
grade: 'code-verified',
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
id: 'reuse-refs',
|
|
61
|
-
title: 'Reuse the same refs across all shots',
|
|
62
|
-
rule: 'Lock a reference set and reuse it across every shot in a sequence. Swapping refs mid-sequence causes drift.',
|
|
63
|
-
why: 'The model adapts environment refs to each prompt rather than copying them, so swapping compounds inconsistency shot to shot.',
|
|
64
|
-
grade: 'community',
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: 'text-as-start-frame',
|
|
68
|
-
title: 'Legible in-shot text → image start-frame, never trust text-to-video',
|
|
69
|
-
rule: 'When a shot needs legible on-screen text, bake it into a still start frame (NB2) and animate from that. Do not expect a text-to-video model to render clean text.',
|
|
70
|
-
why: 'Video models smear text; an image model holds it, and the video inherits the locked frame.',
|
|
71
|
-
grade: 'community',
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
id: 'i2v-own-footage',
|
|
75
|
-
title: 'I2V / own-footage superpower — describe only what changes',
|
|
76
|
-
rule: 'Restyle your own clip while keeping the performance; "video one" delayed-VFX; marker-object insertion; video-as-ref for a series. In all cases describe ONLY what changes, not the whole scene.',
|
|
77
|
-
why: 'The source clip already carries motion, timing, and performance; re-describing them fights the model. Narrate the delta.',
|
|
78
|
-
grade: 'creator-demo',
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
id: 'style-transform-nl',
|
|
82
|
-
title: 'Style transform by natural language',
|
|
83
|
-
rule: 'Default: keep the source\'s artistic medium/style. To change it, add a plain-text instruction ("anime → real person") — no preset pickers.',
|
|
84
|
-
why: 'Paste/natural-language over preset menus is the product philosophy; the medium is inherited unless the user explicitly asks to transform it.',
|
|
85
|
-
grade: 'Eric-test',
|
|
86
|
-
},
|
|
87
|
-
];
|
|
88
8
|
// ── Reusable text fragments (the template-assembly building blocks) ──
|
|
89
9
|
// These are the exact strings the desktop prompt templates, MCP skills,
|
|
90
10
|
// and lead-magnet compose. Change a rule HERE and every consumer follows.
|
|
@@ -97,21 +17,23 @@ export const IDENTITY_LIGHTING = 'flat, even, shadowless lighting';
|
|
|
97
17
|
*/
|
|
98
18
|
export const IDENTITY_PLATE_HEX = '#3a3a3c';
|
|
99
19
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* model: fal echoed back `deep neutral-grey background ()` on a real 2026-07-30
|
|
106
|
-
* request. The plate value has been doing nothing since the composer shipped.
|
|
20
|
+
* THE HEX IS EMITTED WITHOUT ITS `#`. This USED to be load-bearing (2026-07-30):
|
|
21
|
+
* `#` is a reference-token sigil in the prompt composer, and an unresolved
|
|
22
|
+
* `#token` was SILENTLY DELETED, so `#3a3a3c` never survived to any model — fal
|
|
23
|
+
* echoed back `deep neutral-grey background ()` on a real 2026-07-30 request and
|
|
24
|
+
* the plate value had been doing nothing since the composer shipped.
|
|
107
25
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
26
|
+
* 🚨 THE HAZARD IS GONE, AND THE OLD GENERAL RULE WITH IT. The composer now
|
|
27
|
+
* treats a sigil as a mention ONLY when it resolves and passes every other
|
|
28
|
+
* `@`/`#` through byte-identically (`reference-composer.ts` → `TOKEN_RE`) — the
|
|
29
|
+
* exact "HOW YOU'D KNOW THIS IS BEATEN" this comment used to name. Emitting a
|
|
30
|
+
* `#hex` or an `@handle` into prompt text is safe again.
|
|
110
31
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
32
|
+
* It stays bare here anyway: "hex 3a3a3c" reads at least as clearly to a model,
|
|
33
|
+
* and rewording it would churn the generated skills/docs corpus for no gain.
|
|
34
|
+
* That is now a style choice, NOT a workaround — do not re-derive a "never emit
|
|
35
|
+
* a sigil" law from it. `IDENTITY_PLATE_HEX` keeps its `#` because it is a
|
|
36
|
+
* colour constant with possible non-prompt consumers.
|
|
115
37
|
*/
|
|
116
38
|
export const IDENTITY_BACKGROUND = `a plain, deep neutral-grey background (hex ${IDENTITY_PLATE_HEX.replace('#', '')})`;
|
|
117
39
|
export const IDENTITY_LIGHTING_CLAUSE = `Render on ${IDENTITY_BACKGROUND} with ${IDENTITY_LIGHTING} so the sheet captures the character's identity, not scene lighting.`;
|