@devinilabs/reelstack 1.3.1 → 1.4.0
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/cli/lint.js +20 -14
- package/cli/scaffold.js +124 -16
- package/package.json +1 -1
- package/reference/dark/claudedispatch.tsx +1 -1
- package/reference/dark/codedrop.tsx +908 -0
- package/reference/dark/gpt55.tsx +2608 -0
- package/reference/dark/notebooklm.tsx +1 -1
- package/reference/dark/resourcescta.tsx +609 -0
- package/reference/dark/skills.tsx +1460 -0
- package/reference/dark/stitch.tsx +1 -1
- package/reference/dark/stitch2.tsx +162 -0
- package/reference/forbidden/heretic.tsx +1 -2
- package/reference/glass/claudewatch.tsx +4 -4
- package/reference/glass/claudewatchcta.tsx +599 -0
- package/reference/glass/graphify.tsx +1 -2
- package/reference/glass/gstack.tsx +3020 -0
- package/reference/glass/jcode.tsx +2267 -0
- package/reference/glass/lilagents.tsx +2649 -0
- package/reference/glass/paperclip.tsx +1 -2
- package/reference/paper/designreel.tsx +1 -1
- package/reference/paper/devini3d.tsx +1799 -0
- package/reference/paper/justdrop.tsx +1 -1
- package/reference/paper/opus.tsx +1 -1
- package/reference/warm/huashu.tsx +1 -2
- package/reference/warm/mempalace.tsx +1 -2
- package/skill/SKILL.md +25 -16
package/cli/lint.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* `reelstack lint <file> [--critique]` — static-analysis pass over a reel composition.
|
|
3
3
|
*
|
|
4
4
|
* Reports (default mode — list of violations with line numbers):
|
|
5
|
-
* - SAFE_ZONE_BREACH: element y-coords overlap reserved bands.
|
|
6
|
-
* - MOTION_FLOOR_VIOLATION: scenes with too few simultaneous animation layers.
|
|
7
|
-
* - HERO_TEXT_OVERFLOW: counter or hero text wider than canvas - 64px gutter.
|
|
5
|
+
* - SAFE_ZONE_BREACH: (warn) element y-coords overlap reserved bands — decorative elements OK with data-reelstack-safe-override.
|
|
6
|
+
* - MOTION_FLOOR_VIOLATION: (warn) scenes with too few simultaneous animation layers — nested-motion components produce false positives.
|
|
7
|
+
* - HERO_TEXT_OVERFLOW: (warn) counter or hero text wider than canvas - 64px gutter.
|
|
8
8
|
* - AUDIO_NOT_LOCKED: file imports <Audio> but defines no BEAT const block.
|
|
9
|
-
* - HAND_DRAWN_BRAND: inline <svg> longer than 800 chars
|
|
9
|
+
* - HAND_DRAWN_BRAND: (warn) inline <svg> longer than 800 chars — intentional brand SVGs are fine.
|
|
10
10
|
* - ACCENT_ALLOWLIST_VIOLATION: (warn) hex color outside the resolved family's ALLOWED_ACCENTS list — intentional sponsor/custom accents are fine, the rule catches accidental token typos.
|
|
11
|
-
* - SPACING_NON_GRID: padding/margin/gap not on the 4px grid.
|
|
12
|
-
* - MISSING_REDUCE_MOTION: component imports useCurrentFrame but lacks a `reduceMotion` prop.
|
|
11
|
+
* - SPACING_NON_GRID: (warn) padding/margin/gap not on the 4px grid — optical-spacing exceptions OK.
|
|
12
|
+
* - MISSING_REDUCE_MOTION: (warn) component imports useCurrentFrame but lacks a `reduceMotion` prop — accessibility hygiene, not render-blocking.
|
|
13
13
|
* - GENERIC_PLACEHOLDER: "Lorem ipsum", "John Doe", "Acme Corp" — leftover stub copy.
|
|
14
14
|
* - HOOK_LATENCY: earliest <Sequence> starts after frame 30 — first second of reel is empty.
|
|
15
15
|
* - CTA_MISSING: (warn) no CTA component or CTA keyword (follow/subscribe/etc.) found anywhere.
|
|
@@ -86,14 +86,14 @@ function lint(file) {
|
|
|
86
86
|
if (topMatch) {
|
|
87
87
|
const y = Number(topMatch[1]);
|
|
88
88
|
if (y >= 0 && y < TOP_SAFE && !line.includes("data-reelstack-safe-override")) {
|
|
89
|
-
violations.push({ line: i + 1, code: "SAFE_ZONE_BREACH", msg: `top: ${y} overlaps top reserved band (0..${TOP_SAFE}).` });
|
|
89
|
+
violations.push({ line: i + 1, code: "SAFE_ZONE_BREACH", severity: "warning", msg: `top: ${y} overlaps top reserved band (0..${TOP_SAFE}). Add data-reelstack-safe-override if decorative.` });
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
const bottomMatch = line.match(/bottom:\s*(-?\d+)\s*,?/);
|
|
93
93
|
if (bottomMatch) {
|
|
94
94
|
const b = Number(bottomMatch[1]);
|
|
95
95
|
if (b >= 0 && b < BOTTOM_SAFE && !line.includes("data-reelstack-safe-override")) {
|
|
96
|
-
violations.push({ line: i + 1, code: "SAFE_ZONE_BREACH", msg: `bottom: ${b} overlaps bottom reserved band (0..${BOTTOM_SAFE}).` });
|
|
96
|
+
violations.push({ line: i + 1, code: "SAFE_ZONE_BREACH", severity: "warning", msg: `bottom: ${b} overlaps bottom reserved band (0..${BOTTOM_SAFE}). Add data-reelstack-safe-override if decorative.` });
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
});
|
|
@@ -114,7 +114,8 @@ function lint(file) {
|
|
|
114
114
|
violations.push({
|
|
115
115
|
line: lineNum,
|
|
116
116
|
code: "HAND_DRAWN_BRAND",
|
|
117
|
-
|
|
117
|
+
severity: "warning",
|
|
118
|
+
msg: `Inline <svg> is ${svg.length} chars — could be hand-drawn. For brand marks, use /reelstack-icons; intentional inline brand SVGs are fine.`,
|
|
118
119
|
});
|
|
119
120
|
}
|
|
120
121
|
}
|
|
@@ -197,7 +198,8 @@ function lint(file) {
|
|
|
197
198
|
violations.push({
|
|
198
199
|
line: lineOf(openIdx),
|
|
199
200
|
code: "MOTION_FLOOR_VIOLATION",
|
|
200
|
-
|
|
201
|
+
severity: "warning",
|
|
202
|
+
msg: `Sequence at frame ${fromValue} has ${motionCalls} motion calls, family rule is ≥3 (≥4 in opener). If motion lives in a nested component, this warning is a false positive.`,
|
|
201
203
|
});
|
|
202
204
|
}
|
|
203
205
|
|
|
@@ -219,7 +221,8 @@ function lint(file) {
|
|
|
219
221
|
violations.push({
|
|
220
222
|
line: i + 1,
|
|
221
223
|
code: "HERO_TEXT_OVERFLOW",
|
|
222
|
-
|
|
224
|
+
severity: "warning",
|
|
225
|
+
msg: `fontSize ${px}px on apparent hero-counter; verify it fits within ${HERO_TEXT_MAX}px (canvas - 64px gutter) on real devices.`,
|
|
223
226
|
});
|
|
224
227
|
}
|
|
225
228
|
});
|
|
@@ -265,7 +268,8 @@ function lint(file) {
|
|
|
265
268
|
violations.push({
|
|
266
269
|
line: lineOfIdx(idx),
|
|
267
270
|
code: "SPACING_NON_GRID",
|
|
268
|
-
|
|
271
|
+
severity: "warning",
|
|
272
|
+
msg: `${rawProp}: ${val} is off the 4px grid. Round to ${Math.round(val / 4) * 4} — or accept the warning if this is intentional optical spacing.`,
|
|
269
273
|
});
|
|
270
274
|
}
|
|
271
275
|
};
|
|
@@ -309,7 +313,8 @@ function lint(file) {
|
|
|
309
313
|
violations.push({
|
|
310
314
|
line: lineOfIdx(mFC.index),
|
|
311
315
|
code: "MISSING_REDUCE_MOTION",
|
|
312
|
-
|
|
316
|
+
severity: "warning",
|
|
317
|
+
msg: `React.FC component uses useCurrentFrame but prop type lacks "reduceMotion". Add reduceMotion?: boolean for accessibility (warning — not render-blocking).`,
|
|
313
318
|
});
|
|
314
319
|
}
|
|
315
320
|
}
|
|
@@ -317,7 +322,8 @@ function lint(file) {
|
|
|
317
322
|
violations.push({
|
|
318
323
|
line: 1,
|
|
319
324
|
code: "MISSING_REDUCE_MOTION",
|
|
320
|
-
|
|
325
|
+
severity: "warning",
|
|
326
|
+
msg: `Component uses useCurrentFrame but no reduceMotion prop found anywhere. Add reduceMotion?: boolean for accessibility (warning — not render-blocking).`,
|
|
321
327
|
});
|
|
322
328
|
}
|
|
323
329
|
}
|
package/cli/scaffold.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `reelstack scaffold` —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* `reelstack scaffold` — produces a production-quality reel scaffold by
|
|
3
|
+
* preferring the reference reel (cloned from a real Devini Labs production
|
|
4
|
+
* reel) over the generic family template. Substitutes preset metadata,
|
|
5
|
+
* does a mechanical export rename, strips the reference header, and
|
|
6
|
+
* patches src/Root.tsx to register the new composition.
|
|
7
|
+
*
|
|
8
|
+
* v1.4 flow:
|
|
9
|
+
* 1. Resolve reference reel for family + preset.
|
|
10
|
+
* 2. If reference exists (and --minimal not passed):
|
|
11
|
+
* - Read reference verbatim (2000-3800 lines of production motion).
|
|
12
|
+
* - Strip the reference header JSDoc.
|
|
13
|
+
* - Rename `export const <PresetClass>Reel` → `export const <Name>Reel`.
|
|
14
|
+
* - Inject a /** SCAFFOLDED FROM: <ref> *\/ marker.
|
|
15
|
+
* 3. Else fall back to generic template + placeholder substitution.
|
|
16
|
+
* 4. Write to src/<Name>Reel.tsx.
|
|
17
|
+
* 5. Register in src/Root.tsx.
|
|
6
18
|
*/
|
|
7
19
|
const fs = require("node:fs");
|
|
8
20
|
const path = require("node:path");
|
|
@@ -127,6 +139,77 @@ function injectReferenceComment(tsx, refPath) {
|
|
|
127
139
|
return tsx.slice(0, after) + block + tsx.slice(after);
|
|
128
140
|
}
|
|
129
141
|
|
|
142
|
+
/**
|
|
143
|
+
* v1.4: Strip the reference reel's leading JSDoc header block. References
|
|
144
|
+
* start with `/** REFERENCE — <ClassName> (Family: <fam>) ... *\/`.
|
|
145
|
+
* When the reference becomes a buyer's reel, that header doesn't apply.
|
|
146
|
+
*/
|
|
147
|
+
function stripReferenceHeader(tsx) {
|
|
148
|
+
if (!tsx.startsWith("/**")) return tsx;
|
|
149
|
+
if (!/^\/\*\*\s*\n\s*\*\s*REFERENCE\b/.test(tsx)) return tsx;
|
|
150
|
+
const end = tsx.indexOf("*/");
|
|
151
|
+
if (end === -1) return tsx;
|
|
152
|
+
// Skip the closing `*/` plus any trailing newline.
|
|
153
|
+
let cut = end + 2;
|
|
154
|
+
while (tsx[cut] === "\n") cut++;
|
|
155
|
+
return tsx.slice(cut);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* v1.4: Mechanical export rename — replace `export const <X>Reel` with
|
|
160
|
+
* `export const <NewName>Reel`. The reference reel has the production
|
|
161
|
+
* class name (e.g. GraphifyReel); we rewrite it to the buyer's chosen
|
|
162
|
+
* name (e.g. MyLaunchReel). Returns the rewritten source and the original
|
|
163
|
+
* class name (so we can announce what changed in the scaffold summary).
|
|
164
|
+
*/
|
|
165
|
+
function renameReelExport(tsx, newName) {
|
|
166
|
+
const m = tsx.match(/export\s+const\s+(\w+Reel)\b/);
|
|
167
|
+
if (!m) return { tsx, originalClassName: null };
|
|
168
|
+
const originalClassName = m[1];
|
|
169
|
+
const targetClassName = `${newName}Reel`;
|
|
170
|
+
if (originalClassName === targetClassName) {
|
|
171
|
+
return { tsx, originalClassName };
|
|
172
|
+
}
|
|
173
|
+
// Replace ALL occurrences of the original class name with the new one.
|
|
174
|
+
// This catches the export, any internal references (helper components
|
|
175
|
+
// named after the reel, JSDoc, comments), and keeps the file coherent.
|
|
176
|
+
const re = new RegExp(`\\b${originalClassName}\\b`, "g");
|
|
177
|
+
return {
|
|
178
|
+
tsx: tsx.replace(re, targetClassName),
|
|
179
|
+
originalClassName,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* v1.4: Inject a top-of-file marker so the buyer (and Claude on subsequent
|
|
185
|
+
* iteration) knows this file was scaffolded from a reference reel and the
|
|
186
|
+
* canonical hooks/CTAs are Devini Labs strings that should be replaced.
|
|
187
|
+
*/
|
|
188
|
+
function injectScaffoldHeader({ name, family, preset, refPath, originalClassName }) {
|
|
189
|
+
const rel = path.relative(REELSTACK_PKG, refPath) || refPath;
|
|
190
|
+
return `/**
|
|
191
|
+
* ${name}Reel — scaffolded by ReelStack v1.4
|
|
192
|
+
*
|
|
193
|
+
* Family: ${family}
|
|
194
|
+
* Preset: ${preset}
|
|
195
|
+
* Source: ${rel}
|
|
196
|
+
* Cloned at: ${new Date().toISOString()}
|
|
197
|
+
*
|
|
198
|
+
* This file is a faithful clone of the production ${originalClassName || preset + "Reel"}
|
|
199
|
+
* — same motion vocabulary, palette, and scene structure. Canonical Devini
|
|
200
|
+
* Labs hook / sub / CTA strings are still in place. REPLACE them with your
|
|
201
|
+
* own content before publishing. The motion floor (≥4 layers in opener,
|
|
202
|
+
* ≥3 in anchor scenes) is preserved.
|
|
203
|
+
*
|
|
204
|
+
* Next steps:
|
|
205
|
+
* 1. Replace the hero hook, sub, and CTA copy with your reel's narrative.
|
|
206
|
+
* 2. Run \`/reelstack-beats <vo.wav>\` to lock motion to your voiceover.
|
|
207
|
+
* 3. Look for REFERENCE-STRIP markers and swap in your assets.
|
|
208
|
+
* 4. Run \`/reelstack-lint src/${name}Reel.tsx\` and address any errors.
|
|
209
|
+
*/
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
|
|
130
213
|
function patchRoot(rootPath, name, durationFrames) {
|
|
131
214
|
if (!fs.existsSync(rootPath)) {
|
|
132
215
|
fail(`src/Root.tsx not found at ${rootPath}. Skipping registration.`);
|
|
@@ -193,15 +276,35 @@ async function run(argv, opts = {}) {
|
|
|
193
276
|
}
|
|
194
277
|
|
|
195
278
|
const presetMeta = loadPreset(family, preset);
|
|
196
|
-
const template = loadTemplate(family);
|
|
197
|
-
let tsx = substitute(template, presetMeta, name);
|
|
198
|
-
|
|
199
|
-
// v1.1.1+ — resolve the reference reel for this preset (or buyer's override
|
|
200
|
-
// via --reference=<preset>) so a `/** REFERENCE: <path> */` comment can be
|
|
201
|
-
// injected into the scaffold, and a hint surfaced after Studio.
|
|
202
279
|
const ref = resolveReference(family, preset, args.reference);
|
|
203
|
-
|
|
204
|
-
|
|
280
|
+
|
|
281
|
+
// v1.4 flow: prefer reference reel (production-grade DNA) over the generic
|
|
282
|
+
// template. Buyer can opt into the generic template via --minimal.
|
|
283
|
+
let tsx;
|
|
284
|
+
let scaffoldedFromReference = false;
|
|
285
|
+
let originalClassName = null;
|
|
286
|
+
|
|
287
|
+
if (ref && !args.minimal) {
|
|
288
|
+
let refSrc = fs.readFileSync(ref.path, "utf8");
|
|
289
|
+
refSrc = stripReferenceHeader(refSrc);
|
|
290
|
+
const renamed = renameReelExport(refSrc, name);
|
|
291
|
+
refSrc = renamed.tsx;
|
|
292
|
+
originalClassName = renamed.originalClassName;
|
|
293
|
+
const header = injectScaffoldHeader({
|
|
294
|
+
name,
|
|
295
|
+
family,
|
|
296
|
+
preset,
|
|
297
|
+
refPath: ref.path,
|
|
298
|
+
originalClassName,
|
|
299
|
+
});
|
|
300
|
+
tsx = header + refSrc;
|
|
301
|
+
scaffoldedFromReference = true;
|
|
302
|
+
} else {
|
|
303
|
+
const template = loadTemplate(family);
|
|
304
|
+
tsx = substitute(template, presetMeta, name);
|
|
305
|
+
if (ref) {
|
|
306
|
+
tsx = injectReferenceComment(tsx, ref.path);
|
|
307
|
+
}
|
|
205
308
|
}
|
|
206
309
|
|
|
207
310
|
const outFile = path.join(cwd, "src", `${name}Reel.tsx`);
|
|
@@ -225,13 +328,18 @@ async function run(argv, opts = {}) {
|
|
|
225
328
|
console.log(c.gray(` Family: `) + family);
|
|
226
329
|
console.log(c.gray(` Preset: `) + `${preset} (from ${presetMeta.source})`);
|
|
227
330
|
console.log(c.gray(` Frame count: `) + `${presetMeta.durationFrames} (${(presetMeta.durationFrames / 30).toFixed(1)}s @ 30fps)`);
|
|
331
|
+
console.log(c.gray(` Source: `) + (scaffoldedFromReference
|
|
332
|
+
? `reference reel (${path.basename(ref.path)})${originalClassName ? ` — renamed ${originalClassName} → ${name}Reel` : ""}`
|
|
333
|
+
: `generic template`));
|
|
228
334
|
console.log(c.gray(` Output: `) + path.relative(cwd, outFile));
|
|
229
335
|
console.log("");
|
|
230
336
|
console.log(c.cyan("→ ") + `Open Remotion Studio with ${c.bold("npm run dev")} and select "${name}Reel" from the comp list.`);
|
|
231
|
-
if (
|
|
232
|
-
console.log(c.cyan("ℹ ") + `
|
|
233
|
-
console.log(c.gray("
|
|
234
|
-
console.log(c.gray("
|
|
337
|
+
if (scaffoldedFromReference) {
|
|
338
|
+
console.log(c.cyan("ℹ ") + `Scaffolded from production reference. Canonical Devini Labs hook/sub/CTA copy is in the file —`);
|
|
339
|
+
console.log(c.gray(" replace those strings with your own narrative before publishing. The motion, palette, and scene"));
|
|
340
|
+
console.log(c.gray(" structure are production-grade and preserved verbatim."));
|
|
341
|
+
} else if (ref) {
|
|
342
|
+
console.log(c.cyan("ℹ ") + `Reference reel available for study: ${ref.path}`);
|
|
235
343
|
}
|
|
236
344
|
console.log("");
|
|
237
345
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devinilabs/reelstack",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Premium 9:16 Reel OS for Remotion. 5 cinematic style families, 22 production-tested presets, audio-locked motion, IG-safe by default. v1.1+ bakes in leonxlnx/taste-skill design discipline + huashu-design productivity patterns.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"remotion",
|
|
@@ -2424,7 +2424,7 @@ export const ClaudeDispatchReel: React.FC = () => {
|
|
|
2424
2424
|
|
|
2425
2425
|
return (
|
|
2426
2426
|
<AbsoluteFill style={{ backgroundColor: C.bg, fontFamily: FONT }}>
|
|
2427
|
-
{/* REFERENCE-STRIP:
|
|
2427
|
+
{/* REFERENCE-STRIP: voiceover tag removed — bring your own voiceover */}
|
|
2428
2428
|
|
|
2429
2429
|
<Background frame={frame} fps={fps} />
|
|
2430
2430
|
|