@revola-ai/video-craft 0.2.1

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.
Files changed (340) hide show
  1. package/README.md +56 -0
  2. package/dist/anchors.js +69 -0
  3. package/dist/corpus.js +91 -0
  4. package/dist/projectDoc.js +83 -0
  5. package/dist/projectDocSync.js +39 -0
  6. package/dist/projectMeta.js +37 -0
  7. package/dist/provenance.js +41 -0
  8. package/dist/radar-check-ids.json +34 -0
  9. package/dist/registry.js +307 -0
  10. package/dist/rules/animatedBarChartTightBox.js +15 -0
  11. package/dist/rules/animatedBarChartWhenNotToUse.js +15 -0
  12. package/dist/rules/animatedBarChartWhenToUse.js +15 -0
  13. package/dist/rules/animatedLineChartWhenNotToUse.js +15 -0
  14. package/dist/rules/animatedLineChartWhenToUse.js +15 -0
  15. package/dist/rules/approvedSfxPaletteOnly.js +15 -0
  16. package/dist/rules/atmosphereGradientsFadeAtBothEnds.js +15 -0
  17. package/dist/rules/audioIsTheClock.js +16 -0
  18. package/dist/rules/beatsDeriveFromTheirOwnSequence.js +15 -0
  19. package/dist/rules/bestSceneSetsTheBar.js +15 -0
  20. package/dist/rules/bumpBeforeRefreeze.js +15 -0
  21. package/dist/rules/cameraIsTheNarrator.js +15 -0
  22. package/dist/rules/checkPinBeforeShip.js +16 -0
  23. package/dist/rules/coldViewerClarity.js +15 -0
  24. package/dist/rules/commitOrCutEffects.js +29 -0
  25. package/dist/rules/convertFramesAcrossClockBoundary.js +15 -0
  26. package/dist/rules/crossCutElementsLiveInTheHudLayer.js +15 -0
  27. package/dist/rules/cursorClicksRealControls.js +15 -0
  28. package/dist/rules/cutOnRealAudioPauses.js +15 -0
  29. package/dist/rules/darkButNeverDim.js +15 -0
  30. package/dist/rules/dataRadarTagsDeclared.js +193 -0
  31. package/dist/rules/declareAVerticalZoneMap.js +15 -0
  32. package/dist/rules/deleteRejectedRigsCompletely.js +15 -0
  33. package/dist/rules/deriveSeamsDontMeasure.js +17 -0
  34. package/dist/rules/deterministicSceneSource.js +65 -0
  35. package/dist/rules/diegeticSoundForOnscreenActions.js +15 -0
  36. package/dist/rules/distinctTransitionPerBeat.js +15 -0
  37. package/dist/rules/downloadOrDrawRichAssets.js +15 -0
  38. package/dist/rules/elementOnscreenForItsVoSentence.js +17 -0
  39. package/dist/rules/emitBuildNotes.js +15 -0
  40. package/dist/rules/energyFromTimingNotPartCount.js +15 -0
  41. package/dist/rules/evidenceBeforeClaimingFixed.js +15 -0
  42. package/dist/rules/exactPeerLockstep.js +15 -0
  43. package/dist/rules/firstPassIsADraft.js +15 -0
  44. package/dist/rules/fixCrowdingByRebandingNotNudging.js +15 -0
  45. package/dist/rules/fixesPreserveApprovedElements.js +15 -0
  46. package/dist/rules/floatingNotBoxed.js +15 -0
  47. package/dist/rules/focusBeatsRecedeTheContext.js +15 -0
  48. package/dist/rules/fontVarsBrandRemocn.js +15 -0
  49. package/dist/rules/geometricSeamsNotCrossfades.js +23 -0
  50. package/dist/rules/geometrySidecarExportsTyped.js +36 -0
  51. package/dist/rules/glowIsAFocalAccent.js +22 -0
  52. package/dist/rules/heroFillsTheFrame.js +17 -0
  53. package/dist/rules/htmlMasksForAnimatedCounters.js +15 -0
  54. package/dist/rules/impactStackOnPayoffs.js +15 -0
  55. package/dist/rules/interactiveTargetsAreDeclaredData.js +15 -0
  56. package/dist/rules/interpolateLiteralKeysMonotonic.js +79 -0
  57. package/dist/rules/latenessIsStructuralOverlapTheBeats.js +15 -0
  58. package/dist/rules/lessTextMoreVisualizations.js +15 -0
  59. package/dist/rules/liveLinkDuplicateReactAlias.js +15 -0
  60. package/dist/rules/make2dElements3d.js +15 -0
  61. package/dist/rules/masterMixLoudnessTarget.js +17 -0
  62. package/dist/rules/measureAlignmentDontEyeball.js +15 -0
  63. package/dist/rules/motionCarriesWeightAndMeaning.js +15 -0
  64. package/dist/rules/motionDensityFloor.js +15 -0
  65. package/dist/rules/neverEditTierBVendored.js +15 -0
  66. package/dist/rules/neverRestateDerivedFrameNumbersInComments.js +15 -0
  67. package/dist/rules/noAiGradientLook.js +15 -0
  68. package/dist/rules/noColoredFlashToHideACut.js +22 -0
  69. package/dist/rules/noDoubleFrameShift.js +126 -0
  70. package/dist/rules/noDuplicateMotionPrimitives.js +15 -0
  71. package/dist/rules/noLinearMotion.js +22 -0
  72. package/dist/rules/noNestedSvgFilters.js +93 -0
  73. package/dist/rules/noRepeatedAssetAcrossShots.js +15 -0
  74. package/dist/rules/noShorthandWithUndefinedLonghand.js +93 -0
  75. package/dist/rules/noStackedFadesAtACut.js +24 -0
  76. package/dist/rules/noStaticHolds.js +17 -0
  77. package/dist/rules/occlusionVisibleElements.js +17 -0
  78. package/dist/rules/oneContinuousVoTrack.js +15 -0
  79. package/dist/rules/onePunchPerMomentClass.js +15 -0
  80. package/dist/rules/oneRenderPerRound.js +15 -0
  81. package/dist/rules/openStaticAfterAHardCut.js +22 -0
  82. package/dist/rules/openingsAndClosingsCarryMore.js +15 -0
  83. package/dist/rules/persistentDeviceFrames.js +15 -0
  84. package/dist/rules/pixelDiffAnEmptyBandFirst.js +15 -0
  85. package/dist/rules/planIsPublishedNotGating.js +15 -0
  86. package/dist/rules/poweredByRevolaWatermark.js +15 -0
  87. package/dist/rules/predrawContentPresentAtTheCut.js +17 -0
  88. package/dist/rules/premiumSurfacesNotFlatBorders.js +15 -0
  89. package/dist/rules/radarDispositionsAreHumanOnly.js +17 -0
  90. package/dist/rules/realAssetsNotPlaceholders.js +15 -0
  91. package/dist/rules/rebuildUiFromReconNotScreenshots.js +15 -0
  92. package/dist/rules/reconStrandsRunInParallel.js +15 -0
  93. package/dist/rules/registryBeforeHandrolling.js +15 -0
  94. package/dist/rules/remapRateEndpointFormula.js +15 -0
  95. package/dist/rules/remocnContentTailwindChrome.js +15 -0
  96. package/dist/rules/retimeOnlyViaPlaybackOrDesignTokens.js +15 -0
  97. package/dist/rules/reviewTheRenderByFrameSweep.js +15 -0
  98. package/dist/rules/rollingNumberBoundedSequence.js +17 -0
  99. package/dist/rules/rollingNumberSequenceLayoutNone.js +16 -0
  100. package/dist/rules/rollingNumberTightBoxAlignment.js +15 -0
  101. package/dist/rules/rollingNumberWhenNotToUse.js +17 -0
  102. package/dist/rules/rollingNumberWhenToUse.js +17 -0
  103. package/dist/rules/sameFamilySameProject.js +15 -0
  104. package/dist/rules/sampleBrandFromProduct.js +15 -0
  105. package/dist/rules/sampleVelocityFromPreviousFramePosition.js +15 -0
  106. package/dist/rules/sceneRootTagDeclared.js +41 -0
  107. package/dist/rules/scrubDemoDataToGenericRoles.js +15 -0
  108. package/dist/rules/selfReviewThePlanForGaps.js +15 -0
  109. package/dist/rules/sourceArchivedBeforeBrief.js +17 -0
  110. package/dist/rules/statisticsSelfExplanatory.js +15 -0
  111. package/dist/rules/stopsAsRelativeSpacing.js +15 -0
  112. package/dist/rules/theWholeCanvasWorks.js +15 -0
  113. package/dist/rules/themePerSection.js +22 -0
  114. package/dist/rules/thesisDeclaredInBrief.js +17 -0
  115. package/dist/rules/tierARequiresPropUnreachableEdit.js +15 -0
  116. package/dist/rules/tinyBodyCopyAsRuledLines.js +15 -0
  117. package/dist/rules/titlesPinnedOutsideCameraPush.js +15 -0
  118. package/dist/rules/tokensNotRawHexes.js +60 -0
  119. package/dist/rules/typeTokenDefaultedParamsAsString.js +15 -0
  120. package/dist/rules/varyKitChoreographyAcrossSections.js +15 -0
  121. package/dist/rules/varyTheVisualizationGrammar.js +15 -0
  122. package/dist/rules/verifyCheapRenderDear.js +15 -0
  123. package/dist/rules/voAddressesTheViewer.js +15 -0
  124. package/dist/rules/voIsAPerformance.js +15 -0
  125. package/dist/rules/voScriptLocksSpokenWords.js +15 -0
  126. package/dist/rules/wrappersPreserveChildGeometry.js +15 -0
  127. package/dist/sourceCheck.js +47 -0
  128. package/dist/sourceCitations.js +68 -0
  129. package/dist/stageProjection.js +69 -0
  130. package/dist/stages.js +163 -0
  131. package/dist/testSupport.js +6 -0
  132. package/dist/types.js +0 -0
  133. package/dist/variants.js +8 -0
  134. package/dist/waiverProjection.js +20 -0
  135. package/package.json +32 -0
  136. package/src/anchors.test.ts +119 -0
  137. package/src/anchors.ts +110 -0
  138. package/src/corpus.test.ts +96 -0
  139. package/src/corpus.ts +149 -0
  140. package/src/corpusProject.test.ts +103 -0
  141. package/src/creativeEdge.test.ts +58 -0
  142. package/src/distBuild.test.ts +86 -0
  143. package/src/docStatus.test.ts +81 -0
  144. package/src/drift.test.ts +124 -0
  145. package/src/fixtures/data-radar-dynamic/Branches.tsx +14 -0
  146. package/src/fixtures/data-radar-dynamic/plan.fixture.yaml +16 -0
  147. package/src/fixtures/data-radar-forwarded/Scene.tsx +19 -0
  148. package/src/fixtures/data-radar-forwarded/plan.fixture.yaml +13 -0
  149. package/src/fixtures/data-radar-opaque/Scene.tsx +15 -0
  150. package/src/fixtures/data-radar-opaque/plan.fixture.yaml +10 -0
  151. package/src/fixtures/data-radar-tags/SceneClean.tsx +10 -0
  152. package/src/fixtures/data-radar-tags/SceneNullBinding.tsx +12 -0
  153. package/src/fixtures/data-radar-tags/SceneWithGap.tsx +11 -0
  154. package/src/fixtures/data-radar-tags/plan.fixture.yaml +29 -0
  155. package/src/fixtures/deterministic-scene.tsx +15 -0
  156. package/src/fixtures/double-shift/aliased.tsx +13 -0
  157. package/src/fixtures/double-shift/assigned.tsx +13 -0
  158. package/src/fixtures/double-shift/clean.tsx +19 -0
  159. package/src/fixtures/double-shift/destructured.tsx +12 -0
  160. package/src/fixtures/double-shift/elementAccess.tsx +12 -0
  161. package/src/fixtures/double-shift/layout.ts +24 -0
  162. package/src/fixtures/double-shift/mechanism-prefix.tsx.txt +458 -0
  163. package/src/fixtures/double-shift/wrapped.tsx +15 -0
  164. package/src/fixtures/geometry-sidecar-missing-export.ts +10 -0
  165. package/src/fixtures/geometry-sidecar-missing-type.ts +7 -0
  166. package/src/fixtures/geometry-sidecar-valid.ts +6 -0
  167. package/src/fixtures/interpolate-ascending.tsx +6 -0
  168. package/src/fixtures/interpolate-descending.tsx +8 -0
  169. package/src/fixtures/interpolate-identifier-keys.tsx +9 -0
  170. package/src/fixtures/nested-filters-broken.tsx +11 -0
  171. package/src/fixtures/nested-filters-fixed.tsx +8 -0
  172. package/src/fixtures/nested-filters-html-wrapper.tsx +14 -0
  173. package/src/fixtures/nested-filters-html.tsx +7 -0
  174. package/src/fixtures/nested-filters-siblings.tsx +12 -0
  175. package/src/fixtures/nondeterministic-scene.tsx +14 -0
  176. package/src/fixtures/nondeterministic-variants.tsx +16 -0
  177. package/src/fixtures/raw-hex-scene.tsx +10 -0
  178. package/src/fixtures/scene-root-declared/SceneHook.tsx +9 -0
  179. package/src/fixtures/scene-root-declared/plan.fixture.yaml +10 -0
  180. package/src/fixtures/scene-root-missing/SceneHook.tsx +11 -0
  181. package/src/fixtures/scene-root-missing/plan.fixture.yaml +10 -0
  182. package/src/fixtures/scene-root-no-names/SceneHook.tsx +6 -0
  183. package/src/fixtures/scene-root-no-names/plan.fixture.yaml +6 -0
  184. package/src/fixtures/scene-root-shell/SceneHook.tsx +8 -0
  185. package/src/fixtures/scene-root-shell/Shell.tsx +9 -0
  186. package/src/fixtures/scene-root-shell/plan.fixture.yaml +10 -0
  187. package/src/fixtures/shorthand-undefined-clean.tsx +23 -0
  188. package/src/fixtures/shorthand-undefined-longhand.tsx +17 -0
  189. package/src/fixtures/token-scene.tsx +9 -0
  190. package/src/includeCoverage.test.ts +176 -0
  191. package/src/learnedFrom.test.ts +32 -0
  192. package/src/projectDoc.test.ts +230 -0
  193. package/src/projectDoc.ts +152 -0
  194. package/src/projectDocSync.test.ts +152 -0
  195. package/src/projectDocSync.ts +79 -0
  196. package/src/projectMeta.test.ts +75 -0
  197. package/src/projectMeta.ts +53 -0
  198. package/src/provenance.ts +85 -0
  199. package/src/radar-check-ids.json +34 -0
  200. package/src/registry.test.ts +207 -0
  201. package/src/registry.ts +352 -0
  202. package/src/rules/animatedBarChartTightBox.ts +14 -0
  203. package/src/rules/animatedBarChartWhenNotToUse.ts +14 -0
  204. package/src/rules/animatedBarChartWhenToUse.ts +14 -0
  205. package/src/rules/animatedLineChartWhenNotToUse.ts +14 -0
  206. package/src/rules/animatedLineChartWhenToUse.ts +14 -0
  207. package/src/rules/approvedSfxPaletteOnly.ts +23 -0
  208. package/src/rules/atmosphereGradientsFadeAtBothEnds.ts +21 -0
  209. package/src/rules/audioIsTheClock.ts +22 -0
  210. package/src/rules/beatsDeriveFromTheirOwnSequence.ts +21 -0
  211. package/src/rules/bestSceneSetsTheBar.ts +21 -0
  212. package/src/rules/bumpBeforeRefreeze.ts +20 -0
  213. package/src/rules/cameraIsTheNarrator.ts +21 -0
  214. package/src/rules/checkPinBeforeShip.ts +22 -0
  215. package/src/rules/coldViewerClarity.ts +21 -0
  216. package/src/rules/commitOrCutEffects.ts +36 -0
  217. package/src/rules/convertFramesAcrossClockBoundary.ts +19 -0
  218. package/src/rules/crossCutElementsLiveInTheHudLayer.ts +21 -0
  219. package/src/rules/cursorClicksRealControls.ts +22 -0
  220. package/src/rules/cutOnRealAudioPauses.ts +20 -0
  221. package/src/rules/darkButNeverDim.ts +21 -0
  222. package/src/rules/dataRadarTagsDeclared.test.ts +60 -0
  223. package/src/rules/dataRadarTagsDeclared.ts +341 -0
  224. package/src/rules/declareAVerticalZoneMap.ts +19 -0
  225. package/src/rules/deleteRejectedRigsCompletely.ts +19 -0
  226. package/src/rules/deriveSeamsDontMeasure.ts +23 -0
  227. package/src/rules/deterministicSceneSource.test.ts +58 -0
  228. package/src/rules/deterministicSceneSource.ts +82 -0
  229. package/src/rules/diegeticSoundForOnscreenActions.ts +21 -0
  230. package/src/rules/distinctTransitionPerBeat.ts +21 -0
  231. package/src/rules/downloadOrDrawRichAssets.ts +22 -0
  232. package/src/rules/elementOnscreenForItsVoSentence.ts +25 -0
  233. package/src/rules/emitBuildNotes.ts +19 -0
  234. package/src/rules/energyFromTimingNotPartCount.ts +20 -0
  235. package/src/rules/evidenceBeforeClaimingFixed.ts +23 -0
  236. package/src/rules/exactPeerLockstep.ts +19 -0
  237. package/src/rules/firstPassIsADraft.ts +20 -0
  238. package/src/rules/fixCrowdingByRebandingNotNudging.ts +20 -0
  239. package/src/rules/fixesPreserveApprovedElements.ts +24 -0
  240. package/src/rules/floatingNotBoxed.ts +21 -0
  241. package/src/rules/focusBeatsRecedeTheContext.ts +20 -0
  242. package/src/rules/fontVarsBrandRemocn.ts +20 -0
  243. package/src/rules/geometricSeamsNotCrossfades.ts +28 -0
  244. package/src/rules/geometrySidecarExportsTyped.test.ts +37 -0
  245. package/src/rules/geometrySidecarExportsTyped.ts +59 -0
  246. package/src/rules/glowIsAFocalAccent.ts +28 -0
  247. package/src/rules/heroFillsTheFrame.ts +24 -0
  248. package/src/rules/htmlMasksForAnimatedCounters.ts +21 -0
  249. package/src/rules/impactStackOnPayoffs.ts +26 -0
  250. package/src/rules/interactiveTargetsAreDeclaredData.ts +21 -0
  251. package/src/rules/interpolateLiteralKeysMonotonic.test.ts +49 -0
  252. package/src/rules/interpolateLiteralKeysMonotonic.ts +98 -0
  253. package/src/rules/latenessIsStructuralOverlapTheBeats.ts +20 -0
  254. package/src/rules/lessTextMoreVisualizations.ts +22 -0
  255. package/src/rules/liveLinkDuplicateReactAlias.ts +22 -0
  256. package/src/rules/make2dElements3d.ts +21 -0
  257. package/src/rules/masterMixLoudnessTarget.ts +22 -0
  258. package/src/rules/measureAlignmentDontEyeball.ts +20 -0
  259. package/src/rules/motionCarriesWeightAndMeaning.ts +21 -0
  260. package/src/rules/motionDensityFloor.ts +20 -0
  261. package/src/rules/neverEditTierBVendored.ts +20 -0
  262. package/src/rules/neverRestateDerivedFrameNumbersInComments.ts +20 -0
  263. package/src/rules/noAiGradientLook.ts +22 -0
  264. package/src/rules/noColoredFlashToHideACut.ts +29 -0
  265. package/src/rules/noDoubleFrameShift.test.ts +95 -0
  266. package/src/rules/noDoubleFrameShift.ts +193 -0
  267. package/src/rules/noDuplicateMotionPrimitives.ts +19 -0
  268. package/src/rules/noLinearMotion.ts +29 -0
  269. package/src/rules/noNestedSvgFilters.test.ts +47 -0
  270. package/src/rules/noNestedSvgFilters.ts +138 -0
  271. package/src/rules/noRepeatedAssetAcrossShots.ts +19 -0
  272. package/src/rules/noShorthandWithUndefinedLonghand.test.ts +35 -0
  273. package/src/rules/noShorthandWithUndefinedLonghand.ts +140 -0
  274. package/src/rules/noStackedFadesAtACut.ts +30 -0
  275. package/src/rules/noStaticHolds.ts +24 -0
  276. package/src/rules/occlusionVisibleElements.ts +26 -0
  277. package/src/rules/oneContinuousVoTrack.ts +20 -0
  278. package/src/rules/onePunchPerMomentClass.ts +20 -0
  279. package/src/rules/oneRenderPerRound.ts +46 -0
  280. package/src/rules/openStaticAfterAHardCut.ts +29 -0
  281. package/src/rules/openingsAndClosingsCarryMore.ts +21 -0
  282. package/src/rules/persistentDeviceFrames.ts +20 -0
  283. package/src/rules/pixelDiffAnEmptyBandFirst.ts +20 -0
  284. package/src/rules/planIsPublishedNotGating.ts +32 -0
  285. package/src/rules/poweredByRevolaWatermark.ts +19 -0
  286. package/src/rules/predrawContentPresentAtTheCut.ts +21 -0
  287. package/src/rules/premiumSurfacesNotFlatBorders.ts +20 -0
  288. package/src/rules/radarDispositionsAreHumanOnly.ts +28 -0
  289. package/src/rules/realAssetsNotPlaceholders.ts +21 -0
  290. package/src/rules/rebuildUiFromReconNotScreenshots.ts +21 -0
  291. package/src/rules/reconStrandsRunInParallel.ts +63 -0
  292. package/src/rules/registryBeforeHandrolling.ts +20 -0
  293. package/src/rules/remapRateEndpointFormula.ts +19 -0
  294. package/src/rules/remocnContentTailwindChrome.ts +19 -0
  295. package/src/rules/retimeOnlyViaPlaybackOrDesignTokens.ts +18 -0
  296. package/src/rules/reviewTheRenderByFrameSweep.ts +23 -0
  297. package/src/rules/rollingNumberBoundedSequence.ts +21 -0
  298. package/src/rules/rollingNumberSequenceLayoutNone.ts +22 -0
  299. package/src/rules/rollingNumberTightBoxAlignment.ts +19 -0
  300. package/src/rules/rollingNumberWhenNotToUse.ts +24 -0
  301. package/src/rules/rollingNumberWhenToUse.ts +23 -0
  302. package/src/rules/sameFamilySameProject.ts +20 -0
  303. package/src/rules/sampleBrandFromProduct.ts +19 -0
  304. package/src/rules/sampleVelocityFromPreviousFramePosition.ts +21 -0
  305. package/src/rules/sceneRootTagDeclared.test.ts +43 -0
  306. package/src/rules/sceneRootTagDeclared.ts +78 -0
  307. package/src/rules/scrubDemoDataToGenericRoles.ts +19 -0
  308. package/src/rules/selfReviewThePlanForGaps.ts +47 -0
  309. package/src/rules/sourceArchivedBeforeBrief.ts +24 -0
  310. package/src/rules/statisticsSelfExplanatory.ts +18 -0
  311. package/src/rules/stopsAsRelativeSpacing.ts +21 -0
  312. package/src/rules/theWholeCanvasWorks.ts +21 -0
  313. package/src/rules/themePerSection.ts +28 -0
  314. package/src/rules/thesisDeclaredInBrief.ts +25 -0
  315. package/src/rules/tierARequiresPropUnreachableEdit.ts +20 -0
  316. package/src/rules/tinyBodyCopyAsRuledLines.ts +19 -0
  317. package/src/rules/titlesPinnedOutsideCameraPush.ts +22 -0
  318. package/src/rules/tokensNotRawHexes.test.ts +51 -0
  319. package/src/rules/tokensNotRawHexes.ts +89 -0
  320. package/src/rules/typeTokenDefaultedParamsAsString.ts +21 -0
  321. package/src/rules/varyKitChoreographyAcrossSections.ts +21 -0
  322. package/src/rules/varyTheVisualizationGrammar.ts +22 -0
  323. package/src/rules/verifyCheapRenderDear.ts +18 -0
  324. package/src/rules/voAddressesTheViewer.ts +18 -0
  325. package/src/rules/voIsAPerformance.ts +21 -0
  326. package/src/rules/voScriptLocksSpokenWords.ts +21 -0
  327. package/src/rules/wrappersPreserveChildGeometry.ts +22 -0
  328. package/src/sourceCheck.test.ts +50 -0
  329. package/src/sourceCheck.ts +55 -0
  330. package/src/sourceCitations.test.ts +747 -0
  331. package/src/sourceCitations.ts +217 -0
  332. package/src/stageProjection.ts +112 -0
  333. package/src/stages.test.ts +68 -0
  334. package/src/stages.ts +207 -0
  335. package/src/testSupport.ts +16 -0
  336. package/src/types.ts +181 -0
  337. package/src/variants.test.ts +24 -0
  338. package/src/variants.ts +13 -0
  339. package/src/waiverProjection.test.ts +59 -0
  340. package/src/waiverProjection.ts +45 -0
@@ -0,0 +1,93 @@
1
+ import { SyntaxKind } from "ts-morph";
2
+ const FAMILIES = {
3
+ padding: /^padding(Top|Right|Bottom|Left|Block|Inline)/,
4
+ margin: /^margin(Top|Right|Bottom|Left|Block|Inline)/,
5
+ // `background` resets image/position/size/repeat/origin/clip/attachment/color.
6
+ // It does NOT reset background-blend-mode (CSS Backgrounds L3).
7
+ background: /^background(Color|Image|Position|Size|Repeat|Attachment|Clip|Origin)/,
8
+ // Ordered before `border` so the corner spellings can never be claimed by it.
9
+ borderRadius: /^border(Top|Bottom)(Left|Right)Radius$/,
10
+ // The negative lookahead is load-bearing: without it `^border(Top` also
11
+ // matches `borderTopLeftRadius`, and `border` does not reset border-radius.
12
+ border: /^border(Top|Right|Bottom|Left|Width|Style|Color|Block|Inline)(?!.*Radius)/,
13
+ font: /^font(Size|Weight|Family|Style|Variant|Stretch)/,
14
+ flex: /^flex(Grow|Shrink|Basis)$/,
15
+ overflow: /^overflow(X|Y)$/,
16
+ transition: /^transition(Property|Duration|TimingFunction|Delay)$/,
17
+ animation: /^animation(Name|Duration|TimingFunction|Delay|IterationCount|Direction|FillMode|PlayState)$/,
18
+ gap: /^(row|column)Gap$/
19
+ };
20
+ const noShorthandWithUndefinedLonghand = {
21
+ id: "no-shorthand-with-undefined-longhand",
22
+ claim: "Never put a longhand style property explicitly set to `undefined` in the same object as its shorthand; build the style object from only the keys you actually have.",
23
+ why: "React writes style keys in order, so an undefined longhand clears the shorthand that preceded it - one forwarded style object silently removed the padding from every panel in a film, and the source read as correct.",
24
+ learnedFrom: "claude-sessions-digest/product-room-video.md",
25
+ tier: "gate",
26
+ verify: "`vitest run packages/video-craft` - machine-checked over scene and kit source",
27
+ scope: { kind: "pipeline" },
28
+ enforcement: {
29
+ kind: "source",
30
+ check: {
31
+ // Scene trees plus the kit: anywhere a style object is composed or
32
+ // forwarded. Same blast radius as the other geometry-class checks.
33
+ include: [
34
+ "revola-video/src/**/*.{ts,tsx}",
35
+ "workday-dple-video/src/**/*.{ts,tsx}",
36
+ "spec-videos/src/**/*.{ts,tsx}",
37
+ "qpa-video/03_scenes/**/*.{js,jsx,ts,tsx}",
38
+ "qse-qguard-video/src/**/*.{ts,tsx}",
39
+ "rcp-fund7-video/src/**/*.{ts,tsx}",
40
+ "expanse-video/src/**/*.{ts,tsx}",
41
+ "reverence-video/src/**/*.{ts,tsx}",
42
+ // Added Task 23: klaviyo-segments-video and radar-fixture-video were
43
+ // silently excluded - the same gap deterministic-scene-source had.
44
+ "klaviyo-segments-video/src/**/*.{ts,tsx}",
45
+ "radar-fixture-video/src/**/*.{ts,tsx}",
46
+ "packages/video-kit/src/**/*.{ts,tsx}"
47
+ ],
48
+ run(file, relPath) {
49
+ const findings = [];
50
+ for (const obj of file.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression)) {
51
+ const props = obj.getProperties().filter(
52
+ (p) => p.isKind(SyntaxKind.PropertyAssignment) || p.isKind(SyntaxKind.ShorthandPropertyAssignment)
53
+ );
54
+ const unquote = (n) => n.replace(/^["']|["']$/g, "");
55
+ const names = new Set(
56
+ props.map(
57
+ (p) => unquote(
58
+ p.isKind(SyntaxKind.PropertyAssignment) ? p.getName() : p.asKindOrThrow(SyntaxKind.ShorthandPropertyAssignment).getName()
59
+ )
60
+ )
61
+ );
62
+ for (const prop of props) {
63
+ const shorthandSyntax = prop.isKind(SyntaxKind.ShorthandPropertyAssignment);
64
+ const name = unquote(
65
+ shorthandSyntax ? prop.asKindOrThrow(SyntaxKind.ShorthandPropertyAssignment).getName() : prop.asKindOrThrow(SyntaxKind.PropertyAssignment).getName()
66
+ );
67
+ let possiblyUndefined = shorthandSyntax;
68
+ if (!shorthandSyntax) {
69
+ const init = prop.asKindOrThrow(SyntaxKind.PropertyAssignment).getInitializer();
70
+ possiblyUndefined = init?.getKind() === SyntaxKind.Identifier;
71
+ }
72
+ if (!possiblyUndefined) continue;
73
+ for (const [shorthand, longhands] of Object.entries(FAMILIES)) {
74
+ if (!longhands.test(name)) continue;
75
+ if (!names.has(shorthand)) continue;
76
+ findings.push({
77
+ file: relPath,
78
+ line: prop.getStartLineNumber(),
79
+ match: prop.getText().slice(0, 60),
80
+ message: `\`${name}\` may be undefined beside the \`${shorthand}\` shorthand in the same object, which clears the shorthand; omit the key instead of passing it undefined`
81
+ });
82
+ break;
83
+ }
84
+ }
85
+ }
86
+ return findings;
87
+ }
88
+ }
89
+ }
90
+ };
91
+ export {
92
+ noShorthandWithUndefinedLonghand
93
+ };
@@ -0,0 +1,24 @@
1
+ const noStackedFadesAtACut = {
2
+ id: "no-stacked-fades-at-a-cut",
3
+ claim: "Never stack the outgoing scene's fade-out with an incoming fade-in at a cut; at most one side of a seam may fade.",
4
+ why: "Two stacked fades multiply to near-zero and the film dips through a black hole at the seam; no easing can fix a cut that happens in black.",
5
+ learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
6
+ tier: "gate",
7
+ verify: "`video radar:film --project <dir> --video <mp4> --check fade-stack-at-cut`",
8
+ owner: "fade-stack-at-cut",
9
+ exceptions: [
10
+ {
11
+ project: "qse-qguard-video",
12
+ decision: "client chose ACT854's paired 14f exit-fade + 14f enter-fade at every seam for the QGuard restyle; the black-dip risk is accepted and checked with stills at each seam",
13
+ recordedIn: "qse-qguard-video/docs/motion-brand-spec.md"
14
+ }
15
+ ],
16
+ scope: { kind: "pipeline" },
17
+ enforcement: {
18
+ kind: "prose",
19
+ anchor: { file: "revola-video/src/scenes/craft/craftKit.tsx" }
20
+ }
21
+ };
22
+ export {
23
+ noStackedFadesAtACut
24
+ };
@@ -0,0 +1,17 @@
1
+ const noStaticHolds = {
2
+ id: "no-static-holds",
3
+ claim: "No shot holds fully static for more than about two seconds: the background always has subtle continuous motion, and idle hero objects get living motion (drift, rotate, breathe).",
4
+ why: "Every static hold in the corpus was eventually flagged as boring or empty; continuous low-amplitude motion is what separates a motion-graphics video from a slideshow.",
5
+ learnedFrom: "claude-sessions-digest/portfolio-reels.md",
6
+ tier: "gate",
7
+ verify: "`video radar:film --project <dir> --video <mp4> --check static-hold`",
8
+ owner: "static-hold",
9
+ scope: { kind: "pipeline" },
10
+ enforcement: {
11
+ kind: "prose",
12
+ unanchorable: "The rule is a motion-over-time judgment; it is not expressible as a single code location."
13
+ }
14
+ };
15
+ export {
16
+ noStaticHolds
17
+ };
@@ -0,0 +1,17 @@
1
+ const occlusionVisibleElements = {
2
+ id: "occlusion-visible-elements",
3
+ claim: "A tagged element stays visibly on top for the whole duration it is meant to be read; another element must never paint over enough of it, for long enough, that it reads as buried rather than merely overlapping.",
4
+ why: "A label that exists, sits in its correct band, and is fully painted over by a later layer looks identical to a correct scene in source and in every geometry check that only reasons about position - the QGuard station labels and S2 lane labels were both invisible in the shipped render while every positional check stayed clean.",
5
+ learnedFrom: "docs/superpowers/specs/2026-07-31-defect-radar-design.md",
6
+ tier: "gate",
7
+ verify: "`video radar:scene --project <dir> --comp <id> --check occlusion`",
8
+ owner: "occlusion",
9
+ scope: { kind: "pipeline" },
10
+ enforcement: {
11
+ kind: "prose",
12
+ unanchorable: "The rule is a paint-order judgment across a shot's whole sampled range; no single code location expresses it."
13
+ }
14
+ };
15
+ export {
16
+ occlusionVisibleElements
17
+ };
@@ -0,0 +1,15 @@
1
+ const oneContinuousVoTrack = {
2
+ id: "one-continuous-vo-track",
3
+ claim: "Play the narration as one continuous Audio track from frame 0 and make the film exactly its length; never slice the VO into per-scene chunks that start at scene boundaries.",
4
+ why: "Per-scene VO slices start at each scene's first frame while scenes run longer than their slices, silently inserting dead air at every seam - the sliced build padded the read with 9s across four seams and the film read as padded.",
5
+ learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
6
+ tier: "craft",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "revola-video/src/RevolaCraft.tsx" }
11
+ }
12
+ };
13
+ export {
14
+ oneContinuousVoTrack
15
+ };
@@ -0,0 +1,15 @@
1
+ const onePunchPerMomentClass = {
2
+ id: "one-punch-per-moment-class",
3
+ claim: "Use a beat punch (zoom-in, scale pop, flash) at most once per class of moment; never repeat the same punch on every keypress, card, or list item.",
4
+ why: "A punch repeated on every event stops reading as emphasis and starts reading as a bug; repeated beat zooms were rejected twice in caps.",
5
+ learnedFrom: "claude-sessions-digest/portfolio-reels.md",
6
+ tier: "craft",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ unanchorable: "The rule is an emphasis-budget judgment across a scene's events; no single code location expresses it."
11
+ }
12
+ };
13
+ export {
14
+ onePunchPerMomentClass
15
+ };
@@ -0,0 +1,15 @@
1
+ const oneRenderPerRound = {
2
+ id: "one-render-per-round",
3
+ claim: "Close every defect a render surfaced before rendering again, and verify each fix with stills spanning the whole window it changes, not a single frame.",
4
+ why: "Rendering per fix instead of per round cost QGuard round 2 two extra full renders (~14 minutes), one of them because a fix was verified at a single frame while the motion it altered spanned a hundred.",
5
+ learnedFrom: "qse-qguard-video/BUILD_NOTES.md",
6
+ tier: "process",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "packages/video-cli/src/review.ts" }
11
+ }
12
+ };
13
+ export {
14
+ oneRenderPerRound
15
+ };
@@ -0,0 +1,22 @@
1
+ const openStaticAfterAHardCut = {
2
+ id: "open-static-after-a-hard-cut",
3
+ claim: "After a hard cut, open the incoming scene static and at full opacity: no entry zoom, no entry blur, and the first two camera keys equal so nothing moves until the first deliberate travel.",
4
+ why: "A momentum entry is theoretically correct continuation but on screen reads as the camera drifting; the landing element must be the first thing the eye finds, and it must be still.",
5
+ learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
6
+ tier: "craft",
7
+ exceptions: [
8
+ {
9
+ project: "qse-qguard-video",
10
+ decision: "client chose ACT854's 0.97-to-1 entry settle at every seam for the QGuard restyle; scenes still hold their first camera key static underneath it",
11
+ recordedIn: "qse-qguard-video/docs/motion-brand-spec.md"
12
+ }
13
+ ],
14
+ scope: { kind: "pipeline" },
15
+ enforcement: {
16
+ kind: "prose",
17
+ anchor: { file: "revola-video/src/scenes/craft/S3Line.tsx" }
18
+ }
19
+ };
20
+ export {
21
+ openStaticAfterAHardCut
22
+ };
@@ -0,0 +1,15 @@
1
+ const openingsAndClosingsCarryMore = {
2
+ id: "openings-and-closings-carry-more",
3
+ claim: "The opening and closing scenes out-spectacle the middle: frame one already carries the film's biggest visual statement, and the close lands one deliberate, composed payoff instead of fizzling.",
4
+ why: "'First scene is too boring for an opening' and 'make it an amazing closing scene' recurred across builds as their own feedback rounds; a strong middle never rescued a film whose first seconds read as template.",
5
+ learnedFrom: "docs/style/CREATIVE_EDGE.md",
6
+ tier: "craft",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ unanchorable: "A relative-spectacle judgment across scene positions; no single code location expresses it."
11
+ }
12
+ };
13
+ export {
14
+ openingsAndClosingsCarryMore
15
+ };
@@ -0,0 +1,15 @@
1
+ const persistentDeviceFrames = {
2
+ id: "persistent-device-frames",
3
+ claim: "A device frame (laptop, phone, browser chrome) enters the video once; subsequent shots swap only the content inside it, never re-animate the frame's entrance.",
4
+ why: "A frame that re-enters on every shot reads as a reset and was hated on sight in two separate videos; persistence is what makes a sequence feel like one continuous product story.",
5
+ learnedFrom: "claude-sessions-digest/revola-intro-video.md",
6
+ tier: "craft",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ unanchorable: "The rule is a cross-shot continuity judgment; no single code location expresses it."
11
+ }
12
+ };
13
+ export {
14
+ persistentDeviceFrames
15
+ };
@@ -0,0 +1,15 @@
1
+ const pixelDiffAnEmptyBandFirst = {
2
+ id: "pixel-diff-an-empty-band-first",
3
+ claim: "When pixel-diffing rendered frames across a seam, measure an empty region of frame first; per-frame randomized layers (film grain, dust motes) produce large raw deltas by design.",
4
+ why: "A raw ~120/255 per-pixel delta at a verified-perfect seam was entirely FilmGrain/DustMotes re-randomizing; without the empty-band control you chase a phantom seam artifact.",
5
+ learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
6
+ tier: "process",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "revola-video/src/scenes/craft/craftKit.tsx" }
11
+ }
12
+ };
13
+ export {
14
+ pixelDiffAnEmptyBandFirst
15
+ };
@@ -0,0 +1,15 @@
1
+ const planIsPublishedNotGating = {
2
+ id: "plan-is-published-not-gating",
3
+ claim: "Publish the scene-flow plan with its open questions and keep building; never hold scene work waiting for sign-off, and when an answer arrives, change the plan and re-derive from it.",
4
+ why: "A plan used as a barrier stops all downstream work for feedback that is usually orthogonal to it - the QGuard build spent 40 minutes before its first scene against 8.9 in the reference sessions - while the things sign-off cannot catch (a plan the build silently does not honour) pass it anyway.",
5
+ learnedFrom: "qse-qguard-video/BUILD_NOTES.md",
6
+ tier: "process",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "packages/video-cli/src/planmd.ts" }
11
+ }
12
+ };
13
+ export {
14
+ planIsPublishedNotGating
15
+ };
@@ -0,0 +1,15 @@
1
+ const poweredByRevolaWatermark = {
2
+ id: "powered-by-revola-watermark",
3
+ claim: "Every video carries the 'Powered by Revola' watermark - the kit's RevolaWatermark, rendered in screen space at the film level - unless the video explicitly specifies otherwise.",
4
+ why: "It is the standing brand default for Revola videos; sourcing it from @revola-ai/video-kit keeps it one import instead of a per-project reimplementation, and a film-level screen-space placement holds it fixed through scene camera moves and off the very edge so a minor crop cannot shave it.",
5
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
6
+ tier: "process",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "packages/video-kit/src/react/bricks/RevolaWatermark.tsx", symbol: "RevolaWatermark" }
11
+ }
12
+ };
13
+ export {
14
+ poweredByRevolaWatermark
15
+ };
@@ -0,0 +1,17 @@
1
+ const predrawContentPresentAtTheCut = {
2
+ id: "predraw-content-present-at-the-cut",
3
+ claim: "Give anything that must be visible on a scene's first frame a negative draw-on start (e.g. start={-20}); start=0 evaluates to zero progress on frame 0 and the cut lands on an empty frame.",
4
+ why: "The S3 spine with start=0 made the cut land on a black frame; a negative start pre-draws it while frame still advances, keeping the line boil alive.",
5
+ learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
6
+ tier: "gate",
7
+ verify: "`video radar:film --project <dir> --video <mp4> --check dead-frame-at-cut`",
8
+ owner: "dead-frame-at-cut",
9
+ scope: { kind: "pipeline" },
10
+ enforcement: {
11
+ kind: "prose",
12
+ anchor: { file: "revola-video/src/scenes/craft/ink.tsx", symbol: "drawP" }
13
+ }
14
+ };
15
+ export {
16
+ predrawContentPresentAtTheCut
17
+ };
@@ -0,0 +1,15 @@
1
+ const premiumSurfacesNotFlatBorders = {
2
+ id: "premium-surfaces-not-flat-borders",
3
+ claim: "Give panels a layered surface - contact plus ambient plus long-throw shadow, a top sheen, and a gradient hairline ring - instead of a flat 1px border.",
4
+ why: "Flat borders are the single clearest cheap-glass tell, and the surface treatment is what separated the reference videos from a first draft that used the same layout and the same palette.",
5
+ learnedFrom: "claude-sessions-digest/product-room-video.md",
6
+ tier: "craft",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "revola-video/src/productroom/Glass.tsx" }
11
+ }
12
+ };
13
+ export {
14
+ premiumSurfacesNotFlatBorders
15
+ };
@@ -0,0 +1,17 @@
1
+ const radarDispositionsAreHumanOnly = {
2
+ id: "radar-dispositions-are-human-only",
3
+ claim: "Never accept or dismiss a radar claim; surface it and continue. Disposition authority is human-only.",
4
+ why: "An agent that quietly disposes its own findings is the exact single-pair-of-eyes failure the radar exists to remove - a judge claim an agent waved through on the operator's behalf is indistinguishable from one nobody ever reviewed.",
5
+ learnedFrom: "docs/superpowers/specs/2026-07-31-defect-radar-design.md",
6
+ tier: "gate",
7
+ verify: "`video radar:accept`/`radar:dismiss` refuse without a human `--operator <name>` and refuse outright under an agent process (`CLAUDECODE` set)",
8
+ owner: "coherence",
9
+ scope: { kind: "pipeline" },
10
+ enforcement: {
11
+ kind: "prose",
12
+ anchor: { file: "packages/video-cli/src/cli.ts" }
13
+ }
14
+ };
15
+ export {
16
+ radarDispositionsAreHumanOnly
17
+ };
@@ -0,0 +1,15 @@
1
+ const realAssetsNotPlaceholders = {
2
+ id: "real-assets-not-placeholders",
3
+ claim: "Mock UIs and cards carry real assets and realistic, detailed content: brand SVG logos and icons (never colored dots or gradient squares), full-length plausible copy, no empty placeholder cards.",
4
+ why: "Placeholder shapes and short generic copy are the second half of the AI-template read; every video needed a de-placeholder round, and detail density was the fix that made recreated UIs land.",
5
+ learnedFrom: "claude-sessions-digest/portfolio-reels.md",
6
+ tier: "craft",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ unanchorable: "The rule is a content-density judgment over scene JSX; no single code location expresses it."
11
+ }
12
+ };
13
+ export {
14
+ realAssetsNotPlaceholders
15
+ };
@@ -0,0 +1,15 @@
1
+ const rebuildUiFromReconNotScreenshots = {
2
+ id: "rebuild-ui-from-recon-not-screenshots",
3
+ claim: "Rebuild product pages 1:1 as React components from recon captures; never embed screenshots in scenes or ship a generic stub.",
4
+ why: "Screenshots cannot be re-themed, animated per-element, or data-scrubbed, and a stub misrepresents the product: the v1 BOM panels were a one-chart stub and the client bounced them.",
5
+ learnedFrom: "act854-video/BUILD_NOTES.md",
6
+ tier: "craft",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "qpa-video/03_scenes/act854/panels.jsx" }
11
+ }
12
+ };
13
+ export {
14
+ rebuildUiFromReconNotScreenshots
15
+ };
@@ -0,0 +1,15 @@
1
+ const reconStrandsRunInParallel = {
2
+ id: "recon-strands-run-in-parallel",
3
+ claim: "Run brand, product, artifact-accuracy and VO-script recon concurrently, each strand in its own worktree and owning exactly one declared output path; the plan is the join, and nothing before it runs in series.",
4
+ why: "The strands share no inputs and no outputs, so serialising them adds their durations instead of taking their maximum - 17 minutes of the QGuard build's 40-minute pre-scene phase was recon done one step at a time.",
5
+ learnedFrom: "qse-qguard-video/BUILD_NOTES.md",
6
+ tier: "process",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "packages/video-cli/src/schema.ts" }
11
+ }
12
+ };
13
+ export {
14
+ reconStrandsRunInParallel
15
+ };
@@ -0,0 +1,15 @@
1
+ const registryBeforeHandrolling = {
2
+ id: "registry-before-handrolling",
3
+ claim: "Reach for the remocn registry before hand-authoring any generic animated element (counter, bar chart, text reveal, backdrop, transition).",
4
+ why: "Hand-authoring what the registry already provides creates one-off implementations with no upstream, repeating the pre-registry _lib sprawl.",
5
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
6
+ tier: "process",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "revola-video/src/components/remocn/backdrop.tsx" }
11
+ }
12
+ };
13
+ export {
14
+ registryBeforeHandrolling
15
+ };
@@ -0,0 +1,15 @@
1
+ const remapRateEndpointFormula = {
2
+ id: "remap-rate-endpoint-formula",
3
+ claim: "Compute a scene's playback rate as (D-1)/(P-1), never D/P, so the last playback frame maps exactly onto the last design frame.",
4
+ why: "Seam contracts are derived off a scene's last design frame; D/P leaves an endpoint error and the match cut drifts.",
5
+ learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
6
+ tier: "craft",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "revola-video/src/tokensCraft.ts", symbol: "rate" }
11
+ }
12
+ };
13
+ export {
14
+ remapRateEndpointFormula
15
+ };
@@ -0,0 +1,15 @@
1
+ const remocnContentTailwindChrome = {
2
+ id: "remocn-content-tailwind-chrome",
3
+ claim: "Use remocn components for animated content (counters, charts, text reveals, backdrops, transitions) and hand-built Tailwind for all container chrome (card shells, browser chrome, grids, layout).",
4
+ why: "remocn has no card, spotlight, or panel primitive, so sourcing chrome from the registry stalls the build; hand-animating counters and charts wastes the registry. The split is the norm, not a workaround.",
5
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
6
+ tier: "craft",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "revola-video/src/scenes/V2Beat4DashboardRemocn.tsx" }
11
+ }
12
+ };
13
+ export {
14
+ remocnContentTailwindChrome
15
+ };
@@ -0,0 +1,15 @@
1
+ const retimeOnlyViaPlaybackOrDesignTokens = {
2
+ id: "retime-only-via-playback-or-design-tokens",
3
+ claim: "Retime by moving P_* (audio changed) or D_* (choreography changed) and let rates and seams re-derive; never edit a scene's internal beats to fit audio, and shorten a rushed beat in design space.",
4
+ why: "Editing internal beats to fit audio bypasses the remap layer, silently breaking every derived rate, seam contract, and cue anchor.",
5
+ learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
6
+ tier: "craft",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "revola-video/src/tokensCraft.ts" }
11
+ }
12
+ };
13
+ export {
14
+ retimeOnlyViaPlaybackOrDesignTokens
15
+ };
@@ -0,0 +1,15 @@
1
+ const reviewTheRenderByFrameSweep = {
2
+ id: "review-the-render-by-frame-sweep",
3
+ claim: "Before calling a round done, sweep the RENDERED file at ~2fps into labelled contact sheets and scan every frame; source review and hand-picked stills do not count.",
4
+ why: "Three defects in one build were invisible in source and obvious in the render - a wrapper that silently wiped every panel's padding, an animated sub-scene rendering as an empty box, and a click beat scheduled past its sequence end so it never drew - and all three survived rounds of per-beat stills because the stills were chosen where the author expected problems, not where they were.",
5
+ learnedFrom: "claude-sessions-digest/product-room-video.md",
6
+ tier: "process",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "packages/video-cli/src/review.ts" }
11
+ }
12
+ };
13
+ export {
14
+ reviewTheRenderByFrameSweep
15
+ };
@@ -0,0 +1,17 @@
1
+ const rollingNumberBoundedSequence = {
2
+ id: "rolling-number-bounded-sequence",
3
+ claim: "Wrap RollingNumber in a Sequence spanning the whole card window and use the speed prop to settle early.",
4
+ why: "RollingNumber reads useVideoConfig().durationInFrames, so it counts over its Sequence's duration rather than its own. Left unwrapped it rolls for the entire shot; wrapped in a short Sequence it unmounts and the number vanishes mid-card.",
5
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
6
+ tier: "craft",
7
+ scope: { kind: "component", slug: "rolling-number", field: "gotcha" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ // Anchored to the owned Tier A port (WS1); the guidance was originally
11
+ // learned on the vendored Tier B copy in revola-video.
12
+ anchor: { file: "packages/video-kit/src/motion/rolling-number.tsx" }
13
+ }
14
+ };
15
+ export {
16
+ rollingNumberBoundedSequence
17
+ };
@@ -0,0 +1,16 @@
1
+ const rollingNumberSequenceLayoutNone = {
2
+ id: "rolling-number-sequence-layout-none",
3
+ claim: 'When the Sequence around RollingNumber exists only to shift time, always pass layout="none".',
4
+ why: "The default Sequence wraps children in an AbsoluteFill, which silently breaks inline/flow layout inside a card - elements stack at the container origin.",
5
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
6
+ upstream: "remotion:rules/sequencing.md",
7
+ tier: "craft",
8
+ scope: { kind: "component", slug: "rolling-number", field: "gotcha" },
9
+ enforcement: {
10
+ kind: "prose",
11
+ anchor: { file: "packages/video-kit/src/motion/rolling-number.tsx" }
12
+ }
13
+ };
14
+ export {
15
+ rollingNumberSequenceLayoutNone
16
+ };
@@ -0,0 +1,15 @@
1
+ const rollingNumberTightBoxAlignment = {
2
+ id: "rolling-number-tight-box-alignment",
3
+ claim: "To flow-align RollingNumber (or any remocn primitive), wrap it in a position: relative box sized to roughly the content's natural width, never a full-width box.",
4
+ why: "remocn primitives are centered AbsoluteFills filling their nearest positioned ancestor; in a too-wide box the number floats to the middle or wraps mid-word.",
5
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
6
+ tier: "craft",
7
+ scope: { kind: "component", slug: "rolling-number", field: "gotcha" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "packages/video-kit/src/motion/rolling-number.tsx" }
11
+ }
12
+ };
13
+ export {
14
+ rollingNumberTightBoxAlignment
15
+ };
@@ -0,0 +1,17 @@
1
+ const rollingNumberWhenNotToUse = {
2
+ id: "rolling-number-when-not-to-use",
3
+ claim: "Do not use RollingNumber for multiple simultaneous stats (use a stat grid), for non-numeric emphasis, or when the value must be pixel-aligned to bespoke chrome without a wrapping box.",
4
+ why: "A grid of rolling odometers makes every stat move so nothing reads as focal, and without a wrapping position: relative box its AbsoluteFill centering fights any pixel-alignment to bespoke chrome.",
5
+ learnedFrom: "docs/plans/2026-07-18-ws1-manifest-check-before-build-plan.md",
6
+ tier: "craft",
7
+ scope: { kind: "component", slug: "rolling-number", field: "whenNotToUse" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ // Anchored to the owned Tier A port (WS1); the guidance was originally
11
+ // learned on the vendored Tier B copy in revola-video.
12
+ anchor: { file: "packages/video-kit/src/motion/rolling-number.tsx" }
13
+ }
14
+ };
15
+ export {
16
+ rollingNumberWhenNotToUse
17
+ };
@@ -0,0 +1,17 @@
1
+ const rollingNumberWhenToUse = {
2
+ id: "rolling-number-when-to-use",
3
+ claim: "Use RollingNumber as the animated odometer for a single headline number (users, revenue, count) that is the beat's focal point.",
4
+ why: "Skipping it means hand-rolling a counter (a duplicate motion primitive), and animating a number that is not the focal point turns the odometer into background noise competing with the real subject of the beat.",
5
+ learnedFrom: "docs/plans/2026-07-18-ws1-manifest-check-before-build-plan.md",
6
+ tier: "craft",
7
+ scope: { kind: "component", slug: "rolling-number", field: "whenToUse" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ // Anchored to the owned Tier A port (WS1); the guidance was originally
11
+ // learned on the vendored Tier B copy in revola-video.
12
+ anchor: { file: "packages/video-kit/src/motion/rolling-number.tsx" }
13
+ }
14
+ };
15
+ export {
16
+ rollingNumberWhenToUse
17
+ };
@@ -0,0 +1,15 @@
1
+ const sameFamilySameProject = {
2
+ id: "same-family-same-project",
3
+ claim: "Build a new video for the same product family as a new composition inside the existing npm project, never as a fresh repo.",
4
+ why: "A fresh repo forks _lib, fonts, and node_modules - the exact fork that left colors.js at three hashes and SceneTransition.jsx at five across the old projects.",
5
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
6
+ tier: "process",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ unanchorable: "Repo-layout doctrine; no code symbol encodes it."
11
+ }
12
+ };
13
+ export {
14
+ sameFamilySameProject
15
+ };
@@ -0,0 +1,15 @@
1
+ const sampleBrandFromProduct = {
2
+ id: "sample-brand-from-product",
3
+ claim: "Sample brand colors, fonts, and accents from the real product's recon capture; never guess them.",
4
+ why: "Guessed brand values produce a video the client immediately flags as off-brand, triggering an avoidable feedback round.",
5
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
6
+ tier: "craft",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "revola-video/src/tokens.ts" }
11
+ }
12
+ };
13
+ export {
14
+ sampleBrandFromProduct
15
+ };
@@ -0,0 +1,15 @@
1
+ const sampleVelocityFromPreviousFramePosition = {
2
+ id: "sample-velocity-from-previous-frame-position",
3
+ claim: "Compute motion-blur velocity from the previous frame's evaluated position, never from a delta in curve-parameter space.",
4
+ why: "Parameter-space sampling on a clamped bezier reports fake speed at the clamped tail, so the cursor would have blurred permanently while at rest.",
5
+ learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
6
+ tier: "craft",
7
+ scope: { kind: "pipeline" },
8
+ enforcement: {
9
+ kind: "prose",
10
+ anchor: { file: "revola-video/src/scenes/craft/S2Turn.tsx" }
11
+ }
12
+ };
13
+ export {
14
+ sampleVelocityFromPreviousFramePosition
15
+ };