@easysociable/cli 0.1.17 → 0.1.19
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/README.md +2 -1
- package/dist/easysociable.js +11408 -9432
- package/dist/easysociable.js.map +1 -1
- package/package.json +20 -17
- package/skills/ONBOARDING.md +2 -3
- package/skills/easysociable-slideshow/SKILL.md +254 -152
- package/skills/easysociable-slideshow/references/CONTENT_PACK.md +12 -15
- package/skills/easysociable-slideshow/references/PLATFORM_CAPS.md +1 -1
- package/skills/easysociable-slideshow/references/REFERENCES.md +97 -0
- package/skills/easysociable-slideshow/scripts/check-pages.mjs +136 -0
- package/skills/easysociable-slideshow/workflows/CREATE_RUN.md +12 -43
- package/skills/easysociable-slideshow/workflows/MCP_FIRST.md +1 -3
- package/skills/easysociable-slideshow/workflows/OUT_OF_SCOPE.md +1 -1
- package/skills/easysociable-template/SKILL.md +14 -14
- package/skills/easysociable-template/references/CATALOG_METADATA.md +2 -2
- package/skills/easysociable-template/references/GATEWAY.md +1 -3
- package/skills/easysociable-template/references/PLATFORM_SPECS.md +7 -9
- package/skills/easysociable-template/scripts/generate-theme-from-hex.mjs +0 -0
- package/skills/easysociable-template/workflows/AUTHORING_FROM_BRIEF.md +4 -4
- package/skills/easysociable-template/workflows/AUTHORING_FROM_IMAGE.md +1 -1
- package/skills/easysociable-template/workflows/AUTHORING_STUDIO.md +4 -4
- package/skills/easysociable-template/workflows/BIND_FORMULA_LOOK.md +39 -0
- package/skills/easysociable-template/workflows/DESIGN_BRIEF.md +2 -3
- package/skills/easysociable-tweet-image/SKILL.md +57 -0
- package/skills/easysociable-template/workflows/MULTI_PLATFORM_FAMILY.md +0 -81
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Reference designs
|
|
2
|
+
|
|
3
|
+
A reference is a published catalog design (one canvas: the source post's) made
|
|
4
|
+
from a real post. Pages are **adapted from references**: you keep a reference's
|
|
5
|
+
composition, type scale, spacing and chrome, and replace content, colours
|
|
6
|
+
(Brand) and canvas size. You never copy one verbatim and never draw a page with
|
|
7
|
+
no reference at all.
|
|
8
|
+
|
|
9
|
+
Find references for a page role, shuffled, as short summaries:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
easysociable templates references --role hook,cover [--no-image] [--style <style>] [--exclude <familyIds>] --json
|
|
13
|
+
easysociable templates source --family <familyId> --json # the design's Studio source
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Roles overlap in the catalog, so pass related ones together: `hook,cover` ·
|
|
17
|
+
`point,value,steps,checklist` · `metric,proof,evidence` · `compare` ·
|
|
18
|
+
`screenshot` (needs an image) · `recap` · `cta,follow`. Styles: `minimal`,
|
|
19
|
+
`editorial`, `swiss`, `dark`, `bold_promo`.
|
|
20
|
+
|
|
21
|
+
## Adapting a reference (the rules that make it look designed)
|
|
22
|
+
|
|
23
|
+
1. Start from the reference's source. Keep its chrome (header, pill, page
|
|
24
|
+
number, rule, footer) only if the deck keeps it on every page; keep its
|
|
25
|
+
margins (`left` of the first column), type scale and fonts.
|
|
26
|
+
2. Fit the Run's canvas: a reference made for another canvas (3:4, 9:16, 4:5)
|
|
27
|
+
keeps its left margin and type scale; re-space vertically, never squash text.
|
|
28
|
+
3. Change text, then fix each text box: explicit `<br />` line breaks, and
|
|
29
|
+
`height` = lines × lineHeight + ~12% of fontSize for descenders.
|
|
30
|
+
4. Remove what you do not need; add only elements copied from another reference
|
|
31
|
+
with the same `style` (rule rows, a stat row).
|
|
32
|
+
5. Brand chrome: the header label is the Brand name (or the topic when there is
|
|
33
|
+
no Brand); the CTA names the Brand handle. Never leave a reference's original
|
|
34
|
+
name or handle (e.g. "Will.AI.m") on a page. **Bind them** so a Brand change
|
|
35
|
+
updates every page: the element holding only the Brand name gets
|
|
36
|
+
`data-brand="name"`, only the handle `data-brand="handle"`, and an avatar /
|
|
37
|
+
logo `<img>` gets `data-brand="avatar"` with the Brand's `logoUrl` as `src`.
|
|
38
|
+
Without a Brand, leave them unbound (the topic is not the Brand).
|
|
39
|
+
6. Accent colour only on large text (≥ 36px, or ≥ 28px bold) and blocks. Small
|
|
40
|
+
labels use the text or muted colour.
|
|
41
|
+
7. Semantic `data-type` values (`title`, `subtitle`, `kicker`, `metric`,
|
|
42
|
+
`caption`) get Brand colours forced by Studio. Text on an accent or image
|
|
43
|
+
block uses `label`, `body` or `statement` with an explicit colour.
|
|
44
|
+
8. One deck = one `style`. No reference twice in a Run unless the page purpose
|
|
45
|
+
repeats (several numbered points), and no two adjacent pages with the same
|
|
46
|
+
composition.
|
|
47
|
+
|
|
48
|
+
## Avoid (what makes every Run look the same)
|
|
49
|
+
|
|
50
|
+
- The same cover composition as this Brand's previous Run, unless the user
|
|
51
|
+
asked for "same as last time".
|
|
52
|
+
- Header label + pill + page number + "SWIPE →" on every page by habit: chrome
|
|
53
|
+
comes from the chosen reference, or there is none.
|
|
54
|
+
- An accent-coloured second half of the headline on more than two pages.
|
|
55
|
+
- A deck that is only left-aligned text stacks: at least one page's focal point
|
|
56
|
+
is not a headline (a big number, an image, a quote, a comparison).
|
|
57
|
+
- A generic "Save this / Follow for more" CTA when the content suggests a
|
|
58
|
+
specific action (comment a word, try the prompt, read the source).
|
|
59
|
+
|
|
60
|
+
## Default palettes (no Brand)
|
|
61
|
+
|
|
62
|
+
Use one in `theme.json` as `{ "color": { "canvas", "text", "textMuted", "accent", "primary" } }`
|
|
63
|
+
(`primary` = `accent`), matched to the deck's style:
|
|
64
|
+
|
|
65
|
+
| Style | canvas / text / textMuted / accent |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `editorial` | `#F4EEE7` / `#121A2B` / `#4A4F5C` / `#D4450F` |
|
|
68
|
+
| `minimal`, `swiss` | `#FFFFFF` / `#15191E` / `#384352` / `#445164` |
|
|
69
|
+
| `dark` | `#06080C` / `#FFFFFF` / `#C7CBD3` / `#4C9BFF` |
|
|
70
|
+
| `bold_promo` | `#F9F9F9` / `#0A0A0A` / `#555555` / `#0066DD` |
|
|
71
|
+
|
|
72
|
+
## Image placeholder
|
|
73
|
+
|
|
74
|
+
A replaceable image slot for a real screenshot or photo the user will add.
|
|
75
|
+
Studio shows an upload button when it is selected.
|
|
76
|
+
|
|
77
|
+
```jsx
|
|
78
|
+
<img data-type="image" data-variable="image_0" data-image-mode="slot_default"
|
|
79
|
+
alt="Add: <what goes here>" src="data:image/svg+xml;base64,<SVG>"
|
|
80
|
+
style={{ position: 'absolute', left: 88, top: 760, width: 904, height: 388, objectFit: 'cover', borderRadius: 24 }} />
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`<SVG>` is base64 of a grey box with an image icon and the label, sized to the slot:
|
|
84
|
+
|
|
85
|
+
```xml
|
|
86
|
+
<svg xmlns='http://www.w3.org/2000/svg' width='904' height='388' viewBox='0 0 904 388'>
|
|
87
|
+
<rect width='904' height='388' fill='#DDD6CC'/>
|
|
88
|
+
<g transform='translate(416,124)' fill='none' stroke='#8C8781' stroke-width='5' stroke-linejoin='round'>
|
|
89
|
+
<rect width='72' height='56' rx='8'/><path d='M6 50 L26 28 L40 42 L50 34 L66 50'/><circle cx='52' cy='16' r='6'/>
|
|
90
|
+
</g>
|
|
91
|
+
<text x='50%' y='230' text-anchor='middle' font-family='Inter, Arial, sans-serif' font-size='32' font-weight='600' fill='#6F6A64'>Add: Grok Bot app screenshot</text>
|
|
92
|
+
</svg>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Put the same data URL in the page's `slots` under `image_N`. Number slots
|
|
96
|
+
`image_0`, `image_1`, … per page. Use the style's own colours for the box
|
|
97
|
+
(canvas-adjacent grey on light styles, `#1A1F27` on dark ones).
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Render every page of a content-pack.v1 with the system Chrome (no npm deps) and check it
|
|
3
|
+
// the way a phone viewer sees it. Exit 0 = pass, 1 = errors (fix before create), 2 = no Chrome.
|
|
4
|
+
//
|
|
5
|
+
// node check-pages.mjs pack.json --out ./shots [--brand brand.json | --theme theme.json]
|
|
6
|
+
//
|
|
7
|
+
// --brand takes `easysociable brands get --id <id> --json` output; without it the Studio
|
|
8
|
+
// default (dark) theme is used, same as a Run without a Brand.
|
|
9
|
+
import { execFileSync } from 'node:child_process'
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
11
|
+
import { join, resolve } from 'node:path'
|
|
12
|
+
import { pathToFileURL } from 'node:url'
|
|
13
|
+
|
|
14
|
+
const args = process.argv.slice(2)
|
|
15
|
+
const flag = name => { const i = args.indexOf(name); return i >= 0 ? args[i + 1] : undefined }
|
|
16
|
+
const packPath = args.find(a => !a.startsWith('--') && a !== flag('--out') && a !== flag('--brand') && a !== flag('--theme'))
|
|
17
|
+
if (!packPath) { console.error('usage: node check-pages.mjs pack.json --out <dir> [--brand brand.json]'); process.exit(1) }
|
|
18
|
+
const outDir = resolve(flag('--out') ?? 'shots')
|
|
19
|
+
mkdirSync(outDir, { recursive: true })
|
|
20
|
+
|
|
21
|
+
const pack = JSON.parse(readFileSync(packPath, 'utf8'))
|
|
22
|
+
const themeFile = flag('--brand') ?? flag('--theme')
|
|
23
|
+
const brandRaw = themeFile ? JSON.parse(readFileSync(themeFile, 'utf8')) : null
|
|
24
|
+
const brand = brandRaw?.data ?? brandRaw
|
|
25
|
+
const c = brand?.colors ?? brand?.color ?? {}
|
|
26
|
+
// Studio default theme (apps/easysociable/app/lib/themeTokens.ts DEFAULT_AUTHORING_THEME_TOKENS).
|
|
27
|
+
const colors = { canvas: c.canvas ?? '#0f1419', text: c.text ?? '#e8eef5', muted: c.textMuted ?? c.muted ?? '#8b9bb0', accent: c.accent ?? '#5b8cff', primary: c.primary ?? c.accent ?? '#5b8cff' }
|
|
28
|
+
|
|
29
|
+
const chrome = [process.env.CHROME_PATH,
|
|
30
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
31
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
32
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
33
|
+
'/usr/bin/google-chrome', '/usr/bin/chromium', '/usr/bin/chromium-browser',
|
|
34
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'].find(p => p && existsSync(p))
|
|
35
|
+
if (!chrome) { console.error('No Chrome/Chromium found (set CHROME_PATH). Render checks skipped.'); process.exit(2) }
|
|
36
|
+
|
|
37
|
+
// ponytail: mirrors Studio's forced semantic colors by data-type; update if themeTokens.ts changes.
|
|
38
|
+
const themeCss = `[data-authoring-theme]{--es-color-canvas:${colors.canvas};--es-color-text:${colors.text};--es-color-muted:${colors.muted};--es-color-accent:${colors.accent};--es-color-primary:${colors.primary}}
|
|
39
|
+
[data-authoring-theme] [data-type="post"]{background-color:var(--es-color-canvas);color:var(--es-color-text)}
|
|
40
|
+
[data-authoring-theme] [data-type="title"],[data-authoring-theme] [data-type="subtitle"]{color:var(--es-color-text)!important}
|
|
41
|
+
[data-authoring-theme] [data-type="kicker"]{color:var(--es-color-accent)!important}
|
|
42
|
+
[data-authoring-theme] [data-type="metric"]{color:var(--es-color-primary)!important}
|
|
43
|
+
[data-authoring-theme] [data-type="caption"]{color:var(--es-color-muted)!important}
|
|
44
|
+
html,body{margin:0;background:#fff}#report{display:none}`
|
|
45
|
+
|
|
46
|
+
// Runs inside the Chrome page: JSX source → DOM, then measure. Source is evaluated in the page sandbox, not in Node.
|
|
47
|
+
const pageScript = String.raw`
|
|
48
|
+
const UNITLESS = new Set(['fontWeight','lineHeight','opacity','zIndex','flex','flexGrow','flexShrink'])
|
|
49
|
+
const toCss = o => Object.entries(o).map(([k,v]) => k.replace(/[A-Z]/g, m => '-' + m.toLowerCase()) + ':' + (typeof v === 'number' && !UNITLESS.has(k) ? v + 'px' : v)).join(';')
|
|
50
|
+
const toHtml = jsx => jsx
|
|
51
|
+
.replace(/style=\{\{([\s\S]*?)\}\}/g, (_, body) => 'style="' + toCss(Function('return {' + body + '}')()).replace(/"/g, "'") + '"')
|
|
52
|
+
.replace(/style="([^"]*)"/g, (_, body) => 'style="' + body.replace(/(^|;)\s*([a-z]+[A-Z][A-Za-z]*)\s*:/g, (m, sep, k) => sep + k.replace(/[A-Z]/g, c => '-' + c.toLowerCase()) + ':') + '"')
|
|
53
|
+
.replace(/className=/g, 'class=').replace(/\{' '\}/g, ' ')
|
|
54
|
+
.replace(/<(div|p|h1|h2|h3|span|img)(\s[^>]*?)\s*\/>/g, (_, t, a) => t === 'img' ? '<img' + a + '>' : '<' + t + a + '></' + t + '>')
|
|
55
|
+
const stage = document.getElementById('stage'), GRID = location.hash === '#grid', S = 0.5
|
|
56
|
+
for (const [i, src] of SOURCES.entries()) {
|
|
57
|
+
const box = document.createElement('div')
|
|
58
|
+
box.setAttribute('data-authoring-theme', '')
|
|
59
|
+
box.innerHTML = toHtml(src.json)
|
|
60
|
+
if (GRID) { box.style.cssText = 'width:' + src.width * S + 'px;height:' + src.height * S + 'px;overflow:hidden'; box.firstElementChild.style.transform = 'scale(' + S + ')'; box.firstElementChild.style.transformOrigin = '0 0' }
|
|
61
|
+
stage.appendChild(box)
|
|
62
|
+
}
|
|
63
|
+
if (GRID) stage.style.cssText = 'display:grid;grid-template-columns:repeat(' + COLS + ',max-content);gap:16px;padding:16px;background:#888'
|
|
64
|
+
const lum = rgb => { const [r,g,b] = rgb.map(v => { v /= 255; return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4 }); return 0.2126*r + 0.7152*g + 0.0722*b }
|
|
65
|
+
const parse = s => { const m = s.match(/rgba?\(([^)]+)\)/); if (!m) return null; const p = m[1].split(/[ ,\/]+/).filter(Boolean).map(Number); return { rgb: p.slice(0,3), a: p[3] ?? 1 } }
|
|
66
|
+
const ratio = (a, b) => { const [x,y] = [lum(a), lum(b)].sort((m,n) => n - m); return (x + 0.05) / (y + 0.05) }
|
|
67
|
+
function measure(root) {
|
|
68
|
+
const R = root.getBoundingClientRect(), W = R.width, H = R.height, k = W / 1080
|
|
69
|
+
const errors = [], warnings = []
|
|
70
|
+
const texts = [...root.querySelectorAll('[data-variable^="text"], p, h1, h2, h3')].filter((e, i, all) => e.textContent.trim() && !all.some(o => o !== e && o.contains(e)))
|
|
71
|
+
const label = e => (e.dataset.variable ? e.dataset.variable + ' ' : '') + JSON.stringify(e.textContent.trim().slice(0, 40))
|
|
72
|
+
let words = 0
|
|
73
|
+
for (const e of texts) {
|
|
74
|
+
const cs = getComputedStyle(e), r = e.getBoundingClientRect(), size = parseFloat(cs.fontSize), n = e.textContent.trim().split(/\s+/).length
|
|
75
|
+
if (e.dataset.type !== 'decoration' && e.dataset.type !== 'label') words += n
|
|
76
|
+
if (e.scrollHeight > e.clientHeight + 2 || e.scrollWidth > e.clientWidth + 2) errors.push('overflow: ' + label(e) + ' needs ' + e.scrollWidth + 'x' + e.scrollHeight + ', box ' + e.clientWidth + 'x' + e.clientHeight)
|
|
77
|
+
if (r.left < R.left - 2 || r.top < R.top - 2 || r.right > R.right + 2 || r.bottom > R.bottom + 2) errors.push('off-canvas: ' + label(e))
|
|
78
|
+
if (n >= 6 && size < 28 * k) warnings.push('small text (' + size + 'px, min ' + Math.round(28 * k) + '): ' + label(e))
|
|
79
|
+
// Contrast: the element and each coloured span against the first opaque background under its centre.
|
|
80
|
+
const stack = document.elementsFromPoint(r.left + r.width / 2, r.top + Math.min(r.height / 2, size))
|
|
81
|
+
let bg = null
|
|
82
|
+
for (const el of stack) { if (el.tagName === 'IMG') break; const p = parse(getComputedStyle(el).backgroundColor); if (p && p.a > 0.5) { bg = p.rgb; break } }
|
|
83
|
+
if (bg) for (const part of [e, ...e.querySelectorAll('span')]) {
|
|
84
|
+
if (part !== e && !part.textContent.trim()) continue
|
|
85
|
+
const pc = getComputedStyle(part), fg = parse(pc.color), fs = parseFloat(pc.fontSize), large = fs >= 36 * k || (fs >= 28 * k && Number(pc.fontWeight) >= 700)
|
|
86
|
+
const cr = fg && ratio(fg.rgb, bg)
|
|
87
|
+
if (cr && cr < 3) errors.push('contrast ' + cr.toFixed(2) + ':1 (min ' + (large ? 3 : 4.5) + '): ' + label(part))
|
|
88
|
+
else if (cr && !large && cr < 4.5) warnings.push('contrast ' + cr.toFixed(2) + ':1 on small text (min 4.5): ' + label(part))
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (let i = 0; i < texts.length; i++) for (let j = i + 1; j < texts.length; j++) {
|
|
92
|
+
const a = texts[i].getBoundingClientRect(), b = texts[j].getBoundingClientRect()
|
|
93
|
+
const ix = Math.min(a.right, b.right) - Math.max(a.left, b.left), iy = Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top)
|
|
94
|
+
if (ix > 4 && iy > 4) errors.push('text overlap: ' + label(texts[i]) + ' / ' + label(texts[j]))
|
|
95
|
+
}
|
|
96
|
+
if (words > 60) warnings.push(words + ' words on one page (aim <= 45): split it')
|
|
97
|
+
// Largest empty horizontal band, ignoring full-bleed backgrounds.
|
|
98
|
+
const spans = [...root.querySelectorAll('*')].map(e => e.getBoundingClientRect()).filter(r => r.width * r.height > 0 && r.width * r.height < 0.8 * W * H).map(r => [r.top - R.top, r.bottom - R.top]).sort((a, b) => a[0] - b[0])
|
|
99
|
+
let cursor = 0, gap = 0
|
|
100
|
+
for (const [t, b] of spans) { gap = Math.max(gap, t - cursor); cursor = Math.max(cursor, b) }
|
|
101
|
+
gap = Math.max(gap, H - cursor)
|
|
102
|
+
if (gap > 0.25 * H) warnings.push('empty band ' + Math.round(gap) + 'px (' + Math.round(100 * gap / H) + '% of height): rebalance the layout')
|
|
103
|
+
return { errors, warnings }
|
|
104
|
+
}
|
|
105
|
+
if (!GRID) document.fonts.ready.then(() => {
|
|
106
|
+
document.getElementById('report').textContent = JSON.stringify([...stage.children].map(box => { try { return measure(box.firstElementChild) } catch (e) { return { errors: ['render failed: ' + e.message], warnings: [] } } }))
|
|
107
|
+
})
|
|
108
|
+
`
|
|
109
|
+
|
|
110
|
+
const pages = pack.pages.map((page, i) => ({ n: i + 1, role: page.role, json: page.layout?.studioJson, ...(page.layout?.canvas ?? { width: 1080, height: 1350 }) })).filter(p => p.json)
|
|
111
|
+
for (const page of pack.pages) if (!page.layout?.studioJson) console.log(`page ${pack.pages.indexOf(page) + 1}: no layout.studioJson (catalog page) — skipped`)
|
|
112
|
+
const COLS = 4
|
|
113
|
+
const html = `<!doctype html><meta charset="utf-8"><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=block">
|
|
114
|
+
<style>${themeCss}</style><div id="stage"></div><pre id="report"></pre>
|
|
115
|
+
<script>const SOURCES = ${JSON.stringify(pages).replace(/</g, '\\u003c')}, COLS = ${COLS};${pageScript}</script>`
|
|
116
|
+
const file = join(outDir, 'pages.html')
|
|
117
|
+
writeFileSync(file, html)
|
|
118
|
+
const W = Math.max(...pages.map(p => p.width)), Hs = pages.map(p => p.height)
|
|
119
|
+
const base = ['--headless=new', '--disable-gpu', '--hide-scrollbars', '--virtual-time-budget=5000']
|
|
120
|
+
// Measure at full size (all pages stacked, so every page is inside the viewport).
|
|
121
|
+
const dom = execFileSync(chrome, [...base, `--window-size=${W},${Hs.reduce((a, b) => a + b, 0)}`, '--dump-dom', pathToFileURL(file).href], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 64 << 20 })
|
|
122
|
+
// One contact sheet at 50% for the visual review.
|
|
123
|
+
const rows = Math.ceil(pages.length / COLS)
|
|
124
|
+
execFileSync(chrome, [...base, `--window-size=${Math.min(COLS, pages.length) * (W / 2 + 16) + 16},${rows * (Math.max(...Hs) / 2 + 16) + 16}`, `--screenshot=${join(outDir, 'grid.png')}`, pathToFileURL(file).href + '#grid'], { stdio: 'ignore' })
|
|
125
|
+
const raw = dom.match(/<pre id="report">([\s\S]*?)<\/pre>/)?.[1]?.replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&')
|
|
126
|
+
const reports = raw ? JSON.parse(raw) : pages.map(() => ({ errors: ['no report (page script did not finish)'], warnings: [] }))
|
|
127
|
+
let failed = false
|
|
128
|
+
for (const [i, page] of pages.entries()) {
|
|
129
|
+
const report = reports[i]
|
|
130
|
+
if (report.errors.length) failed = true
|
|
131
|
+
console.log(`page ${page.n} (${page.role}): ${report.errors.length ? 'FAIL' : 'ok'}`)
|
|
132
|
+
for (const e of report.errors) console.log(` ERROR ${e}`)
|
|
133
|
+
for (const w of report.warnings) console.log(` warn ${w}`)
|
|
134
|
+
}
|
|
135
|
+
console.log(`contact sheet: ${join(outDir, 'grid.png')} — look at it before creating the Run.`)
|
|
136
|
+
process.exit(failed ? 1 : 0)
|
|
@@ -1,45 +1,14 @@
|
|
|
1
1
|
# Create Run
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
5. No Look and no Template Selection → shortlist with
|
|
16
|
-
`easysociable slideshows recommend --file contracts.json --seed <slug> --json`
|
|
17
|
-
(MCP `recommend_slideshow_templates`). Input is the Page Contracts:
|
|
18
|
-
`{ platform, pages: [{ role, density?, slotSummary?: { text, number, image }, requiresImage? }] }`.
|
|
19
|
-
Pick one candidate `familyId` per page and record the reason; `supportedRoles` is a
|
|
20
|
-
weak tag. `pages[0]` must be cover/hook class. Do not use that class on
|
|
21
|
-
later pages. W- judgments (`fits` / `avoid`) with comparable evidence may
|
|
22
|
-
override the recommender's order. Surface `gaps` to the user.
|
|
23
|
-
6. If `contentSlug` already has a Run: update only when topology matches
|
|
24
|
-
(same page count and Layout Families). Otherwise create a new Run.
|
|
25
|
-
7. **Before** `create_run` / `slideshows create` (every create except
|
|
26
|
-
Formula handoff Runs): check the knowledge hard gate — Identity Role / What I build /
|
|
27
|
-
Point of view have no `(fill)` and are non-empty; `boundaries.md` exists
|
|
28
|
-
with Not covering and Never claim non-empty; if `needs/` exists, ≥3 `N-`
|
|
29
|
-
rows, else skip. Read the files or run
|
|
30
|
-
`easysociable engine validate --run <run-dir> --require-knowledge`.
|
|
31
|
-
Fail → hand back to **easysociable-content** / refuse create; list missing
|
|
32
|
-
files. User must say **强制继续** / **force continue** to call Gateway.
|
|
33
|
-
No new Gateway or MCP params.
|
|
34
|
-
7b. **Identity chain (ADR-0036):** confirm CLI auth is the user's Dashboard
|
|
35
|
-
Workspace API key (not `workspace_local` / `test-key`). If needed run
|
|
36
|
-
`easysociable auth login` (browser approval; `--api-key <key>` only when
|
|
37
|
-
headless). Refuse create on fixture
|
|
38
|
-
identity unless the user asked for `--allow-local-fixture`.
|
|
39
|
-
8. Call `create_run` / `slideshows create` through Gateway with the explicit
|
|
40
|
-
`layoutFamilyId`s from the pack. `LAYOUT_FAMILY_NOT_FOUND` → the family has
|
|
41
|
-
no template for this platform; tell the user, do not guess.
|
|
42
|
-
9. Save `runId`, `studioUrl`, and `status=draft`. Tell the user to open
|
|
43
|
-
`studioUrl` **while logged into the same Studio account**.
|
|
44
|
-
10. Do not call a Deck API, create a carousel template, or send a product
|
|
45
|
-
`pageSequence` / `arc` field.
|
|
3
|
+
Follow the Workflow in `SKILL.md` (login → Brand → facts → hook/structure →
|
|
4
|
+
look + pages from references → images → `scripts/check-pages.mjs` → create).
|
|
5
|
+
|
|
6
|
+
Notes for updates:
|
|
7
|
+
|
|
8
|
+
- Revise the same Run (SKILL.md step 8): `slideshows get`, then `slideshows
|
|
9
|
+
update` with `baseRevision` and only the changed `pages[]` (`pageIndex`,
|
|
10
|
+
`layout`, `slots`). Page count and order stay fixed; a new Run only for a
|
|
11
|
+
new topic or an alternative the user asked for.
|
|
12
|
+
- Save `runId` and `studioUrl`; the user opens `studioUrl` while logged into
|
|
13
|
+
the same account.
|
|
14
|
+
- Do not call a Deck API or send a product `pageSequence` / `arc` field.
|
|
@@ -5,9 +5,7 @@ Fallback commands use the same Gateway client:
|
|
|
5
5
|
|
|
6
6
|
```bash
|
|
7
7
|
easysociable slideshows create --file ./pack.json --json
|
|
8
|
-
easysociable slideshows
|
|
9
|
-
easysociable renders wait --id job_xxx --json
|
|
10
|
-
easysociable renders export --id job_xxx --output ./exports/run_xxx/tiktok --json
|
|
8
|
+
easysociable slideshows get --id run_xxx --json
|
|
11
9
|
```
|
|
12
10
|
|
|
13
11
|
Neither path may call a private service, Local API, Directus, R2 or a separate
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Out of scope
|
|
2
2
|
|
|
3
3
|
- DeckTemplate creation, `list_decks`, `CREATE_DECK`, or `BIND_P0_DECKS`.
|
|
4
|
-
- Product-level `arc` / Deck `pageSequence` SKUs
|
|
4
|
+
- Product-level `arc` / Deck `pageSequence` SKUs. Structure stays in the Content Pack.
|
|
5
5
|
- Defaulting every Run to Cover → Point → Steps → Recap → CTA.
|
|
6
6
|
- Cover/hook class templates on page 2+ (they belong on `pages[0]` only).
|
|
7
7
|
- ContentDocument as a render input (ADR-0021).
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: easysociable-template
|
|
3
3
|
description: >
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
TikTok 9:16
|
|
8
|
-
to author a
|
|
4
|
+
Internal tool (opt-in install): turn a real post's page (reference image) or a
|
|
5
|
+
text brief into a single-canvas reference design for the slideshow skill's
|
|
6
|
+
reference library, then bind it to a Formula Look. One canvas only (the
|
|
7
|
+
source post's size, e.g. TikTok 9:16); no multi-platform variants. Use when the
|
|
8
|
+
maintainer wants to author a reference from a reference image or text brief, write a shared visual
|
|
9
9
|
design philosophy, explore layout concepts, co-create Theme/preview tokens, pull icons
|
|
10
10
|
via search_icons/get_icon, open Authoring Studio, or publish. Do not use for
|
|
11
11
|
fill-slots hosted PNG jobs (`create_render_job` is retired), multi-slide slideshow
|
|
@@ -21,7 +21,7 @@ server. Do not install or invoke `easysociable-template`, `easysociable-slidesho
|
|
|
21
21
|
|
|
22
22
|
**This skill owns template authoring:**
|
|
23
23
|
|
|
24
|
-
1. **Author**
|
|
24
|
+
1. **Author** reference designs: **one canvas**, the source post's size → Studio + Publish, then bind to a Formula Look (`workflows/BIND_FORMULA_LOOK.md`). Other platforms are re-laid out by the slideshow agent at generation time, so never derive variants.
|
|
25
25
|
2. Fill-slots Hosted Render (`create_render_job`) is **retired** as a paid delivery path. Customer PNG packs come from Slideshow Studio Download.
|
|
26
26
|
|
|
27
27
|
It is a template factory. It is **not** a multi-slide slideshow compositor and it is **not** a PNG API.
|
|
@@ -41,9 +41,9 @@ Out of scope (hand off):
|
|
|
41
41
|
2. Submit only public Gateway DTOs. Never invent or request private Safe AST / Binding Map / RenderPlan.
|
|
42
42
|
3. Do not start Local Runtime, read content library SQLite, or call Content MCP/CLI for Template work.
|
|
43
43
|
4. Catalog and create targets are **single-page** only. Never treat site Carousel packs as Agent templates.
|
|
44
|
-
5. After `create` / `createDraft`, the primary authoring path is `authoringStudioUrl` (`/authoring/{draftId}`).
|
|
44
|
+
5. After `create` / `createDraft`, the primary authoring path is `authoringStudioUrl` (`/authoring/{draftId}`). One canvas only (never `families derive`). Always send the handoff in `AUTHORING_STUDIO.md` (edit → **Save** → tell Agent to publish). Do not require a Hosted Preview Job for interactive authoring.
|
|
45
45
|
6. Studio has **Save only** — no Publish button. Publish only after the user saves and **explicitly** asks to publish; then run `workflows/PUBLISH_FROM_DRAFT.md`.
|
|
46
|
-
7. **Do not Publish without complete P0 selection metadata** (`CATALOG_METADATA.md`). Gateway enforces this.
|
|
46
|
+
7. **Do not Publish without complete P0 selection metadata** (`CATALOG_METADATA.md`). Gateway enforces this. What the slideshow agent filters on must be right: `supportedRoles`, `contentArchetypes`, `requiresImage`, `visualStyle`, `bestFor`.
|
|
47
47
|
8. Theme/preview: co-create `ThemeTokens` on draft create (`theme.create`) → Family `preview_tokens` (ADR-0033). No authoring TemplateTheme row. Colors/fonts via semantic `--es-*` on page nodes.
|
|
48
48
|
9. Layout model is **absolute positioning** on a **flat canvas tree** (see prior authoring rules / `AUTHORING_SOURCE.md`).
|
|
49
49
|
10. **Runtime layout CSS:** put exact geometry in JSX inline `style={{...}}`. Never depend on Tailwind arbitrary-value classes for geometry.
|
|
@@ -76,12 +76,12 @@ easysociable auth status --json
|
|
|
76
76
|
|
|
77
77
|
| User intent | Workflow |
|
|
78
78
|
|---|---|
|
|
79
|
-
| Text description →
|
|
80
|
-
| Reference image →
|
|
81
|
-
|
|
|
79
|
+
| Text description → reference | `DESIGN_BRIEF.md` → … → `AUTHORING_STUDIO.md` → publish |
|
|
80
|
+
| Reference image (a real post's page) → reference | `AUTHORING_FROM_IMAGE.md` → … → `AUTHORING_STUDIO.md` (reference-compare QA) → publish |
|
|
81
|
+
| A viral post → its Formula's Look | each page via `AUTHORING_FROM_IMAGE.md` → publish → **`BIND_FORMULA_LOOK.md`** |
|
|
82
82
|
| Need gradient / photo surface / icons only | `SURFACE_REVIEW.md` |
|
|
83
83
|
| **Add / change an icon** (arrow, chevron, check, …) | **MUST** `SURFACE_REVIEW.md` § Icons — search → confirm → get_icon. **Do not** hand-draw SVG paths. |
|
|
84
|
-
| Already have authoring payload / continue Studio | `workflows/AUTHORING_STUDIO.md`
|
|
84
|
+
| Already have authoring payload / continue Studio | `workflows/AUTHORING_STUDIO.md` |
|
|
85
85
|
| User says publish / 发布该模板 (after Save) | `workflows/PUBLISH_FROM_DRAFT.md` |
|
|
86
86
|
| Browse published single-page templates | `list` / `get_template` + public metadata |
|
|
87
87
|
| **Fill a published template → PNG/JPEG** | `workflows/FILL_AND_RENDER.md` |
|
|
@@ -89,14 +89,14 @@ easysociable auth status --json
|
|
|
89
89
|
|
|
90
90
|
## Core model
|
|
91
91
|
|
|
92
|
-
- `workflows/
|
|
92
|
+
- `workflows/BIND_FORMULA_LOOK.md` — published references → a Formula's Look (role → design)
|
|
93
93
|
- `references/PLATFORM_SPECS.md`
|
|
94
94
|
- `references/CATALOG_METADATA.md`
|
|
95
95
|
- `references/SLOTS.md` — public slot contract for fill-slots render
|
|
96
96
|
- `references/AUTHORING_SOURCE.md`
|
|
97
97
|
- `references/THEME_TOKENS.md` — Family `preview_tokens` (no TemplateTheme)
|
|
98
98
|
- `references/EXTERNAL_RESOURCES.md`
|
|
99
|
-
- `references/GATEWAY.md` — CLI / MCP
|
|
99
|
+
- `references/GATEWAY.md` — CLI / MCP
|
|
100
100
|
- `workflows/FILL_AND_RENDER.md` — get_template → slots → Hosted Render
|
|
101
101
|
- Authoring V2.1 workflows + validators as before
|
|
102
102
|
|
|
@@ -5,7 +5,7 @@ Private AST/bindings stay server-side; this document is the **public selection c
|
|
|
5
5
|
|
|
6
6
|
## Design choices (locked)
|
|
7
7
|
|
|
8
|
-
1. **One closed `pageRoles` set** (stored as `supportedRoles`) — a **retrieval tag for layout kind**, not page position and not a story recipe. `cover` means poster-like slots, not “must be page 1”. Do not add a parallel `arc` / `pageSequence` taxonomy
|
|
8
|
+
1. **One closed `pageRoles` set** (stored as `supportedRoles`) — a **retrieval tag for layout kind**, not page position and not a story recipe. `cover` means poster-like slots, not “must be page 1”. Do not add a parallel `arc` / `pageSequence` taxonomy.
|
|
9
9
|
2. **`platforms` is a multi-select array**. Include `generic` when the page fits any platform at that aspect.
|
|
10
10
|
3. **P0 fields are required to Publish.** P1 should be filled when known; P2 optional.
|
|
11
11
|
4. **Family `name` describes layout**, not a narrative beat. Prefer Poster / Stack / Count / Stair / Close (plus platform). Do not name families Cover / Point / Steps / Recap / CTA — choosers will glue those five names into one default pack.
|
|
@@ -99,7 +99,7 @@ hero | screenshot | avatar | product
|
|
|
99
99
|
|
|
100
100
|
| Field | Type | Notes |
|
|
101
101
|
|---|---|---|
|
|
102
|
-
| `name` | string |
|
|
102
|
+
| `name` | string | Public card title: what the layout is + style, then the ratio (e.g. `Five-Step How-To List — Quiet Ledger 9:16`). No internal prefixes (`P0`), no single platform name — the family spans platforms; the ratio suffix is stripped on cards. Keep equal to the page `name` |
|
|
103
103
|
| `shortDescription` | string | One sentence for chooser UI |
|
|
104
104
|
| `platforms` | `Platform[]` | Multi-select; use `generic` when truly cross-platform |
|
|
105
105
|
| `canvasPreset` | preset enum | Plus exact `canvas` pixels |
|
|
@@ -12,15 +12,13 @@ easysociable templates update-draft --id <id> --file ./patch.json --json
|
|
|
12
12
|
easysociable templates validate --id <id> --json
|
|
13
13
|
easysociable templates publish --id <id> --json
|
|
14
14
|
easysociable templates families get --id <familyId> --json
|
|
15
|
-
easysociable templates families derive --id <familyId> --presets 4:5,9:16,1:1 --json
|
|
16
|
-
easysociable templates families migrate-legacy --json
|
|
17
15
|
easysociable icons search --query "arrow" --json
|
|
18
16
|
easysociable icons get --id <id> --idempotency-key <key> --json
|
|
19
17
|
easysociable renders create --template-id <id> --slots ./slots.json --format png --idempotency-key render_1 --json
|
|
20
18
|
easysociable renders wait --id <id> --json
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
One canvas per reference; binding to a Formula: `workflows/BIND_FORMULA_LOOK.md`.
|
|
24
22
|
|
|
25
23
|
All requests go through Gateway. The client receives public summaries, slot
|
|
26
24
|
contracts, stable Image references and render Manifests only. It never receives
|
|
@@ -120,20 +120,18 @@ No platform chrome assumptions. Still pick a concrete preset (`1:1`, `4:5`, `3:4
|
|
|
120
120
|
|
|
121
121
|
---
|
|
122
122
|
|
|
123
|
-
##
|
|
123
|
+
## One canvas per reference
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
Typical vertical bucket when master is `3:4` (Xiaohongshu): also derive `4:5`, `9:16`, `1:1`. Absolute layout cannot share one AST across those presets — hand-tune each variant after derive.
|
|
125
|
+
A reference design has exactly one canvas: the source post's (a TikTok post → `9:16`).
|
|
126
|
+
Never derive other presets; the slideshow agent re-lays a reference out for the
|
|
127
|
+
Run's platform.
|
|
130
128
|
|
|
131
129
|
## Selection rules for Agents
|
|
132
130
|
|
|
133
|
-
1.
|
|
134
|
-
2.
|
|
131
|
+
1. Canvas = the source post's platform **Recommended** row (text brief with no post → TikTok 9:16).
|
|
132
|
+
2. One canvas only; no `families derive`.
|
|
135
133
|
3. Never mix presets across slides of one deck without an explicit user choice (IG/Threads/TikTok crop to first slide).
|
|
136
|
-
4.
|
|
134
|
+
4. P0 `selectionMetadata` lists only platforms that match the page's `canvasPreset`.
|
|
137
135
|
5. Specs above are **practical authoring targets** (2026 guides + Meta help for IG). Re-verify if a platform announces breaking crop changes.
|
|
138
136
|
|
|
139
137
|
## Sources (research snapshot)
|
|
File without changes
|
|
@@ -4,12 +4,12 @@ Primary intake when the user describes a single-page template in words (no refer
|
|
|
4
4
|
|
|
5
5
|
## Goal
|
|
6
6
|
|
|
7
|
-
One publishable **single-page
|
|
7
|
+
One publishable **single-page, single-canvas** reference design: Design Brief → Design Philosophy → 2–3 Concepts → Asset Plan → Theme lock → layout → Authoring Studio → publish.
|
|
8
8
|
|
|
9
9
|
## Steps
|
|
10
10
|
|
|
11
|
-
1. Confirm this is **one page**, not a slideshow. If they want a multi-slide deck, stop and hand off (`OUT_OF_SCOPE.md`).
|
|
12
|
-
2. Read `references/PLATFORM_SPECS.md`. Lock
|
|
11
|
+
1. Confirm this is **one page**, not a slideshow. If they want a multi-slide deck, stop and hand off (`OUT_OF_SCOPE.md`).
|
|
12
|
+
2. Read `references/PLATFORM_SPECS.md`. Lock platform + recommended canvas (one canvas).
|
|
13
13
|
3. **Mandatory — V2.1 design plan first** (no reference image):
|
|
14
14
|
- Follow `workflows/DESIGN_BRIEF.md`
|
|
15
15
|
- Follow `workflows/DESIGN_PHILOSOPHY.md`; lock shared `philosophy` (+ companion `design-philosophy.md`)
|
|
@@ -30,7 +30,7 @@ One publishable **single-page** template **Family** (master canvas + multi-platf
|
|
|
30
30
|
- `authoringSource` per `references/AUTHORING_SOURCE.md` (flat tree + text-nesting exception)
|
|
31
31
|
- Theme per `references/THEME_TOKENS.md` — use `themeTokens` from validated `theme-tokens.json` for `theme.create`
|
|
32
32
|
5. Run `scripts/validate-authoring-source.mjs <authoring-source.json>` and fix every error.
|
|
33
|
-
6. Continue with `workflows/AUTHORING_STUDIO.md` (`source.kind = text_brief`) →
|
|
33
|
+
6. Continue with `workflows/AUTHORING_STUDIO.md` (`source.kind = text_brief`) → Visual QA before handoff.
|
|
34
34
|
|
|
35
35
|
## Checklist pages
|
|
36
36
|
|
|
@@ -79,7 +79,7 @@ If unsure whether a region is “illustration” vs “icon”: prefer **image s
|
|
|
79
79
|
9. Propose Theme reuse or `ThemeTokens` create from `theme-tokens.json` (`references/THEME_TOKENS.md`).
|
|
80
80
|
10. Show the user: geometry summary, **image inventory → slot map**, selection metadata, Theme. Call out any region you could not match and why. Proceed when they accept.
|
|
81
81
|
11. Run `scripts/validate-authoring-source.mjs <authoring-source.json>` and fix every error.
|
|
82
|
-
12. Continue with `workflows/AUTHORING_STUDIO.md` (`source.kind = image_reference`, brief = path or short note)
|
|
82
|
+
12. Continue with `workflows/AUTHORING_STUDIO.md` (`source.kind = image_reference`, brief = path or short note) Run Visual QA **including reference compare** before handoff. One canvas only; after publish, bind it with `BIND_FORMULA_LOOK.md` when it comes from a Formula's post.
|
|
83
83
|
|
|
84
84
|
## Limits (state upfront when relevant)
|
|
85
85
|
|
|
@@ -18,11 +18,11 @@ Complete intake first:
|
|
|
18
18
|
## Layout Family (multi-platform)
|
|
19
19
|
|
|
20
20
|
New drafts create a **Layout Family** (`familyId`) plus a master canvas page.
|
|
21
|
-
|
|
21
|
+
One canvas per reference: never derive platform variants.
|
|
22
22
|
|
|
23
23
|
- Authoring URL: `/authoring/{draftId}?platform=…`. Top bar **platform chips** switch variants (do not resize one AST).
|
|
24
24
|
- Preview skin = Family `preview_tokens` from `theme.create`; fill/render uses Brand snapshot first (ADR-0033).
|
|
25
|
-
- Legacy drafts without `familyId` auto-wrap on `getDraft` / `listDrafts`.
|
|
25
|
+
- Legacy drafts without `familyId` auto-wrap on `getDraft` / `listDrafts`.
|
|
26
26
|
|
|
27
27
|
## Flow
|
|
28
28
|
|
|
@@ -47,9 +47,9 @@ AST or Binding Maps.
|
|
|
47
47
|
|
|
48
48
|
3. Read `data.id` / `data.templateId` / `data.familyId` / `data.authoringStudioUrl` (and `data.variants` when present).
|
|
49
49
|
|
|
50
|
-
4.
|
|
50
|
+
4. One canvas only: do not run `families derive`.
|
|
51
51
|
|
|
52
|
-
5. **Before handoff, run `workflows/VISUAL_QA.md`** on the
|
|
52
|
+
5. **Before handoff, run `workflows/VISUAL_QA.md`** on the page. Open the actual Studio render; inspect screenshots, not only source. When a draft fails, fix authoring JSON, validate, update that page id, and re-render:
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
55
|
node scripts/validate-authoring-source.mjs ./authoring-source.json
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Bind references to a Formula Look
|
|
2
|
+
|
|
3
|
+
A Formula is the structure distilled from a real viral post; its **Look** says
|
|
4
|
+
which reference design each slide uses. The slideshow skill reads it
|
|
5
|
+
(`look.slides["<n>"]`, else `look.layouts[<role>]`) whenever that Formula is used.
|
|
6
|
+
|
|
7
|
+
## When
|
|
8
|
+
|
|
9
|
+
After publishing the references for a post's pages (one reference per distinct
|
|
10
|
+
page layout, via `AUTHORING_FROM_IMAGE.md`).
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. `easysociable formulas get --id <F-…> --json` — note its `beats` (slide order
|
|
15
|
+
and roles) and existing `looks`.
|
|
16
|
+
2. Map each beat to a published design's `familyId`
|
|
17
|
+
(`easysociable templates list --canvas 9:16 --json` shows `familyId`):
|
|
18
|
+
- `layouts`: one design per role (`hook`, `point`, `cta`, …);
|
|
19
|
+
- `slides`: only for a slide whose design differs from its role's default
|
|
20
|
+
(1-based slide number → `familyId`).
|
|
21
|
+
3. Write the Look:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{ "id": "L-<short-name>",
|
|
25
|
+
"ref": { "platform": "tiktok", "platform_post_id": "<the post's id>" },
|
|
26
|
+
"layouts": { "hook": "fam_…", "point": "fam_…", "cta": "fam_…" },
|
|
27
|
+
"slides": { "2": "fam_…" } }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
4. There is no public write API for Formulas: hand the JSON to the maintainer
|
|
31
|
+
to append to that Formula's `looks` in the Directus admin (a new Look goes
|
|
32
|
+
last, so the Formula's default Look — the first — does not change unless
|
|
33
|
+
they choose to reorder).
|
|
34
|
+
5. Check: `easysociable formulas get --id <F-…> --json` shows the Look, and
|
|
35
|
+
every `familyId` in it resolves with
|
|
36
|
+
`easysociable templates source --family <familyId> --json`.
|
|
37
|
+
|
|
38
|
+
Never bind an agent-authored design that was not made from a real post: the
|
|
39
|
+
reference library only holds proven designs.
|
|
@@ -16,7 +16,7 @@ Skip this workflow when `AUTHORING_FROM_IMAGE.md` applies and a reference image
|
|
|
16
16
|
|
|
17
17
|
### 1. Gather intent (batch questions; skip known answers)
|
|
18
18
|
|
|
19
|
-
-
|
|
19
|
+
- Platform + canvas from `references/PLATFORM_SPECS.md` (one canvas: the source post's)
|
|
20
20
|
- Page roles (1–2) + contentFamily / archetypes / density (`CATALOG_METADATA.md`)
|
|
21
21
|
- Content job: what must the viewer understand in one glance?
|
|
22
22
|
- Must-have slots: title / metric / image / CTA / fixed N checklist lines…
|
|
@@ -31,8 +31,7 @@ Use this structure (Chinese or English to match the user):
|
|
|
31
31
|
|
|
32
32
|
## 1. Job to be done
|
|
33
33
|
- One-sentence purpose of this page
|
|
34
|
-
-
|
|
35
|
-
- Multi-platform targets (default vertical bucket unless user limited) — `MULTI_PLATFORM_FAMILY.md`
|
|
34
|
+
- Platform + canvas (preset + pixels) — cite PLATFORM_SPECS recommended row
|
|
36
35
|
|
|
37
36
|
## 2. Visual constraints
|
|
38
37
|
- Desired/forbidden moods or brand rules
|