@nodaro/shared 1.4.2

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 (665) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +32 -0
  3. package/dist/index.cjs +12506 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +12269 -0
  6. package/dist/index.d.ts +12269 -0
  7. package/dist/index.js +11831 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +54 -0
  10. package/src/__tests__/__snapshots__/prompt-builder-segments.test.ts.snap +65 -0
  11. package/src/__tests__/ai-avatar-pricing.test.ts +321 -0
  12. package/src/__tests__/apply-video-negative-prompt.test.ts +76 -0
  13. package/src/__tests__/attach-columns.test.ts +28 -0
  14. package/src/__tests__/audio-crossfade-curves.test.ts +33 -0
  15. package/src/__tests__/build-model-menu.test.ts +58 -0
  16. package/src/__tests__/character-aspect-defaults.test.ts +142 -0
  17. package/src/__tests__/character-mention-slug.test.ts +199 -0
  18. package/src/__tests__/character-reference-photo-kinds.test.ts +32 -0
  19. package/src/__tests__/character-usage-mode.test.ts +147 -0
  20. package/src/__tests__/character-variant-assets.test.ts +142 -0
  21. package/src/__tests__/character-voice.test.ts +130 -0
  22. package/src/__tests__/cinematic-avatar-pricing.test.ts +119 -0
  23. package/src/__tests__/combine-transitions.test.ts +137 -0
  24. package/src/__tests__/component-types.test.ts +87 -0
  25. package/src/__tests__/condition-variables.test.ts +98 -0
  26. package/src/__tests__/credit-identifiers.test.ts +811 -0
  27. package/src/__tests__/default-video-provider.test.ts +63 -0
  28. package/src/__tests__/entity-image-handle.test.ts +43 -0
  29. package/src/__tests__/entity-variants.test.ts +33 -0
  30. package/src/__tests__/fal-lip-sync-sets.test.ts +31 -0
  31. package/src/__tests__/featured-entities.test.ts +33 -0
  32. package/src/__tests__/film-pricing.test.ts +64 -0
  33. package/src/__tests__/filter-condition.test.ts +285 -0
  34. package/src/__tests__/freecut-protocol.test.ts +35 -0
  35. package/src/__tests__/generate-text-items.test.ts +19 -0
  36. package/src/__tests__/generated-results.test.ts +50 -0
  37. package/src/__tests__/graph-utils.test.ts +618 -0
  38. package/src/__tests__/group-aggregation.test.ts +83 -0
  39. package/src/__tests__/i18n-locale-completeness.test.ts +156 -0
  40. package/src/__tests__/image-critic-node.test.ts +86 -0
  41. package/src/__tests__/image-mask-mode.test.ts +16 -0
  42. package/src/__tests__/image-ref-limit.test.ts +78 -0
  43. package/src/__tests__/inpaint-region.test.ts +24 -0
  44. package/src/__tests__/input-field-map-parity.test.ts +30 -0
  45. package/src/__tests__/json-evaluator.test.ts +289 -0
  46. package/src/__tests__/json-path.test.ts +64 -0
  47. package/src/__tests__/lip-sync-pricing.test.ts +142 -0
  48. package/src/__tests__/list-merge.test.ts +96 -0
  49. package/src/__tests__/list-sort.test.ts +134 -0
  50. package/src/__tests__/llm-models.test.ts +445 -0
  51. package/src/__tests__/location-mention-slug.test.ts +246 -0
  52. package/src/__tests__/locations-coverage-critic-schema.test.ts +92 -0
  53. package/src/__tests__/loop-delimiter.test.ts +39 -0
  54. package/src/__tests__/lottie-slots.test.ts +245 -0
  55. package/src/__tests__/model-catalog-flux2-resolutions.test.ts +21 -0
  56. package/src/__tests__/model-constants-location.test.ts +20 -0
  57. package/src/__tests__/model-constants.test.ts +122 -0
  58. package/src/__tests__/model-tree.test.ts +86 -0
  59. package/src/__tests__/no-internal-imports.test.ts +25 -0
  60. package/src/__tests__/node-default-mappings.test.ts +175 -0
  61. package/src/__tests__/node-mappable-fields.test.ts +7 -0
  62. package/src/__tests__/node-preset-export.test.ts +46 -0
  63. package/src/__tests__/node-preset-extract.test.ts +166 -0
  64. package/src/__tests__/node-refs-canonical.test.ts +54 -0
  65. package/src/__tests__/node-refs-combine.test.ts +54 -0
  66. package/src/__tests__/node-refs.test.ts +187 -0
  67. package/src/__tests__/node-runtime-keys.test.ts +99 -0
  68. package/src/__tests__/object-aspect-defaults.test.ts +59 -0
  69. package/src/__tests__/parameter-node-value.test.ts +137 -0
  70. package/src/__tests__/pinterest-url.test.ts +35 -0
  71. package/src/__tests__/pipeline-chat.test.ts +110 -0
  72. package/src/__tests__/pipeline-validation.test.ts +98 -0
  73. package/src/__tests__/presentation-utils.test.ts +1137 -0
  74. package/src/__tests__/producer-types.test.ts +47 -0
  75. package/src/__tests__/prompt-length-limits.test.ts +156 -0
  76. package/src/__tests__/provider-directive-defaults.test.ts +46 -0
  77. package/src/__tests__/reduce-strategy-registry.test.ts +43 -0
  78. package/src/__tests__/reference-board-templates.test.ts +33 -0
  79. package/src/__tests__/reference-roles.test.ts +36 -0
  80. package/src/__tests__/resolve-field-mappings.test.ts +110 -0
  81. package/src/__tests__/resolve-pipeline-model.test.ts +76 -0
  82. package/src/__tests__/scene-node-types.test.ts +80 -0
  83. package/src/__tests__/scraper-actors.test.ts +67 -0
  84. package/src/__tests__/seedance-2-catalog.test.ts +18 -0
  85. package/src/__tests__/seedance-2-credit-id.test.ts +23 -0
  86. package/src/__tests__/seedance-aspects.test.ts +52 -0
  87. package/src/__tests__/selector.test.ts +1151 -0
  88. package/src/__tests__/settled-with-limit.test.ts +214 -0
  89. package/src/__tests__/social-media-specs.test.ts +129 -0
  90. package/src/__tests__/sub-workflow-validation.test.ts +88 -0
  91. package/src/__tests__/switchx-pricing.test.ts +28 -0
  92. package/src/__tests__/to-connected-references.test.ts +153 -0
  93. package/src/__tests__/utilities.test.ts +541 -0
  94. package/src/__tests__/video-analysis-pricing.test.ts +58 -0
  95. package/src/__tests__/video-analysis.test.ts +63 -0
  96. package/src/__tests__/video-audio-capability.test.ts +204 -0
  97. package/src/__tests__/video-gen-providers.test.ts +28 -0
  98. package/src/__tests__/video-mode-aliases.test.ts +150 -0
  99. package/src/__tests__/video-ref-limits.test.ts +72 -0
  100. package/src/__tests__/video-utils.test.ts +303 -0
  101. package/src/__tests__/voice-changer-models.test.ts +33 -0
  102. package/src/__tests__/workflow-export-strip.test.ts +46 -0
  103. package/src/ai-avatar-pricing.ts +213 -0
  104. package/src/ancestor-refs.ts +48 -0
  105. package/src/animals.ts +231 -0
  106. package/src/audio-crossfade-curves.ts +73 -0
  107. package/src/audio-fx-presets.ts +64 -0
  108. package/src/avatar-payload-validators.ts +214 -0
  109. package/src/caption-styles.ts +31 -0
  110. package/src/character-aspect-defaults.ts +102 -0
  111. package/src/character-facets.ts +37 -0
  112. package/src/character-mention-slug.ts +166 -0
  113. package/src/character-usage-mode.ts +109 -0
  114. package/src/character-variant-assets.ts +121 -0
  115. package/src/character-voice.ts +151 -0
  116. package/src/cinematic-avatar-pricing.ts +111 -0
  117. package/src/clone-utils.ts +46 -0
  118. package/src/combine-transitions.ts +482 -0
  119. package/src/community.ts +112 -0
  120. package/src/component-types.ts +127 -0
  121. package/src/condition-variables.ts +65 -0
  122. package/src/credit-estimators/index.ts +15 -0
  123. package/src/credit-estimators/video-utils.ts +165 -0
  124. package/src/credit-identifiers.ts +304 -0
  125. package/src/entity-approval-types.ts +31 -0
  126. package/src/entity-aspect-defaults.ts +82 -0
  127. package/src/entity-asset-types.ts +296 -0
  128. package/src/entity-image-handle.ts +36 -0
  129. package/src/extra-refs.ts +139 -0
  130. package/src/featured-entities.ts +136 -0
  131. package/src/film-pricing.ts +117 -0
  132. package/src/filter-condition.ts +250 -0
  133. package/src/flux2-pricing.ts +18 -0
  134. package/src/freecut-protocol.ts +52 -0
  135. package/src/furniture.ts +167 -0
  136. package/src/generate-text-items.ts +15 -0
  137. package/src/generated-results.ts +63 -0
  138. package/src/group-aggregation.ts +75 -0
  139. package/src/hint-graph-types.ts +18 -0
  140. package/src/i18n/action-fx.ar.ts +89 -0
  141. package/src/i18n/action-fx.de.ts +89 -0
  142. package/src/i18n/action-fx.es.ts +89 -0
  143. package/src/i18n/action-fx.fr.ts +89 -0
  144. package/src/i18n/action-fx.he.ts +89 -0
  145. package/src/i18n/action-fx.hi.ts +89 -0
  146. package/src/i18n/action-fx.ja.ts +89 -0
  147. package/src/i18n/action-fx.ko.ts +89 -0
  148. package/src/i18n/action-fx.pt-BR.ts +89 -0
  149. package/src/i18n/action-fx.ru.ts +89 -0
  150. package/src/i18n/action-fx.zh-CN.ts +89 -0
  151. package/src/i18n/aesthetic.ar.ts +170 -0
  152. package/src/i18n/aesthetic.de.ts +48 -0
  153. package/src/i18n/aesthetic.es.ts +169 -0
  154. package/src/i18n/aesthetic.fr.ts +52 -0
  155. package/src/i18n/aesthetic.he.ts +169 -0
  156. package/src/i18n/aesthetic.hi.ts +55 -0
  157. package/src/i18n/aesthetic.ja.ts +165 -0
  158. package/src/i18n/aesthetic.ko.ts +169 -0
  159. package/src/i18n/aesthetic.pt-BR.ts +148 -0
  160. package/src/i18n/aesthetic.ru.ts +155 -0
  161. package/src/i18n/aesthetic.zh-CN.ts +172 -0
  162. package/src/i18n/animals.ar.ts +145 -0
  163. package/src/i18n/animals.de.ts +144 -0
  164. package/src/i18n/animals.es.ts +158 -0
  165. package/src/i18n/animals.fr.ts +144 -0
  166. package/src/i18n/animals.he.ts +155 -0
  167. package/src/i18n/animals.hi.ts +157 -0
  168. package/src/i18n/animals.ja.ts +159 -0
  169. package/src/i18n/animals.ko.ts +159 -0
  170. package/src/i18n/animals.pt-BR.ts +157 -0
  171. package/src/i18n/animals.ru.ts +155 -0
  172. package/src/i18n/animals.zh-CN.ts +155 -0
  173. package/src/i18n/atmosphere.ar.ts +49 -0
  174. package/src/i18n/atmosphere.de.ts +47 -0
  175. package/src/i18n/atmosphere.es.ts +50 -0
  176. package/src/i18n/atmosphere.fr.ts +47 -0
  177. package/src/i18n/atmosphere.he.ts +47 -0
  178. package/src/i18n/atmosphere.hi.ts +49 -0
  179. package/src/i18n/atmosphere.ja.ts +49 -0
  180. package/src/i18n/atmosphere.ko.ts +49 -0
  181. package/src/i18n/atmosphere.pt-BR.ts +49 -0
  182. package/src/i18n/atmosphere.ru.ts +47 -0
  183. package/src/i18n/atmosphere.zh-CN.ts +152 -0
  184. package/src/i18n/backdrop.ar.ts +53 -0
  185. package/src/i18n/backdrop.de.ts +46 -0
  186. package/src/i18n/backdrop.es.ts +54 -0
  187. package/src/i18n/backdrop.fr.ts +52 -0
  188. package/src/i18n/backdrop.he.ts +51 -0
  189. package/src/i18n/backdrop.hi.ts +59 -0
  190. package/src/i18n/backdrop.ja.ts +61 -0
  191. package/src/i18n/backdrop.ko.ts +59 -0
  192. package/src/i18n/backdrop.pt-BR.ts +59 -0
  193. package/src/i18n/backdrop.ru.ts +57 -0
  194. package/src/i18n/backdrop.zh-CN.ts +57 -0
  195. package/src/i18n/camera-format.ar.ts +40 -0
  196. package/src/i18n/camera-format.de.ts +37 -0
  197. package/src/i18n/camera-format.es.ts +41 -0
  198. package/src/i18n/camera-format.fr.ts +37 -0
  199. package/src/i18n/camera-format.he.ts +37 -0
  200. package/src/i18n/camera-format.hi.ts +42 -0
  201. package/src/i18n/camera-format.ja.ts +37 -0
  202. package/src/i18n/camera-format.ko.ts +37 -0
  203. package/src/i18n/camera-format.pt-BR.ts +46 -0
  204. package/src/i18n/camera-format.ru.ts +42 -0
  205. package/src/i18n/camera-format.zh-CN.ts +42 -0
  206. package/src/i18n/camera-motions.ar.ts +86 -0
  207. package/src/i18n/camera-motions.de.ts +74 -0
  208. package/src/i18n/camera-motions.es.ts +86 -0
  209. package/src/i18n/camera-motions.fr.ts +86 -0
  210. package/src/i18n/camera-motions.he.ts +86 -0
  211. package/src/i18n/camera-motions.hi.ts +99 -0
  212. package/src/i18n/camera-motions.ja.ts +99 -0
  213. package/src/i18n/camera-motions.ko.ts +99 -0
  214. package/src/i18n/camera-motions.pt-BR.ts +99 -0
  215. package/src/i18n/camera-motions.ru.ts +99 -0
  216. package/src/i18n/camera-motions.zh-CN.ts +99 -0
  217. package/src/i18n/character-fx.ar.ts +72 -0
  218. package/src/i18n/character-fx.de.ts +72 -0
  219. package/src/i18n/character-fx.es.ts +72 -0
  220. package/src/i18n/character-fx.fr.ts +72 -0
  221. package/src/i18n/character-fx.he.ts +72 -0
  222. package/src/i18n/character-fx.hi.ts +72 -0
  223. package/src/i18n/character-fx.ja.ts +72 -0
  224. package/src/i18n/character-fx.ko.ts +72 -0
  225. package/src/i18n/character-fx.pt-BR.ts +72 -0
  226. package/src/i18n/character-fx.ru.ts +72 -0
  227. package/src/i18n/character-fx.zh-CN.ts +72 -0
  228. package/src/i18n/color-look.ar.ts +50 -0
  229. package/src/i18n/color-look.de.ts +47 -0
  230. package/src/i18n/color-look.es.ts +50 -0
  231. package/src/i18n/color-look.fr.ts +50 -0
  232. package/src/i18n/color-look.he.ts +49 -0
  233. package/src/i18n/color-look.hi.ts +52 -0
  234. package/src/i18n/color-look.ja.ts +54 -0
  235. package/src/i18n/color-look.ko.ts +54 -0
  236. package/src/i18n/color-look.pt-BR.ts +54 -0
  237. package/src/i18n/color-look.ru.ts +52 -0
  238. package/src/i18n/color-look.zh-CN.ts +53 -0
  239. package/src/i18n/composition-effects.ar.ts +26 -0
  240. package/src/i18n/composition-effects.de.ts +25 -0
  241. package/src/i18n/composition-effects.es.ts +27 -0
  242. package/src/i18n/composition-effects.fr.ts +25 -0
  243. package/src/i18n/composition-effects.he.ts +25 -0
  244. package/src/i18n/composition-effects.hi.ts +27 -0
  245. package/src/i18n/composition-effects.ja.ts +27 -0
  246. package/src/i18n/composition-effects.ko.ts +27 -0
  247. package/src/i18n/composition-effects.pt-BR.ts +25 -0
  248. package/src/i18n/composition-effects.ru.ts +82 -0
  249. package/src/i18n/composition-effects.zh-CN.ts +25 -0
  250. package/src/i18n/era.ar.ts +37 -0
  251. package/src/i18n/era.de.ts +34 -0
  252. package/src/i18n/era.es.ts +37 -0
  253. package/src/i18n/era.fr.ts +37 -0
  254. package/src/i18n/era.he.ts +36 -0
  255. package/src/i18n/era.hi.ts +39 -0
  256. package/src/i18n/era.ja.ts +41 -0
  257. package/src/i18n/era.ko.ts +39 -0
  258. package/src/i18n/era.pt-BR.ts +41 -0
  259. package/src/i18n/era.ru.ts +121 -0
  260. package/src/i18n/era.zh-CN.ts +39 -0
  261. package/src/i18n/exposure-settings.ar.ts +28 -0
  262. package/src/i18n/exposure-settings.de.ts +26 -0
  263. package/src/i18n/exposure-settings.es.ts +28 -0
  264. package/src/i18n/exposure-settings.fr.ts +29 -0
  265. package/src/i18n/exposure-settings.he.ts +28 -0
  266. package/src/i18n/exposure-settings.hi.ts +31 -0
  267. package/src/i18n/exposure-settings.ja.ts +31 -0
  268. package/src/i18n/exposure-settings.ko.ts +31 -0
  269. package/src/i18n/exposure-settings.pt-BR.ts +31 -0
  270. package/src/i18n/exposure-settings.ru.ts +31 -0
  271. package/src/i18n/exposure-settings.zh-CN.ts +31 -0
  272. package/src/i18n/framing.ar.ts +78 -0
  273. package/src/i18n/framing.de.ts +73 -0
  274. package/src/i18n/framing.es.ts +78 -0
  275. package/src/i18n/framing.fr.ts +78 -0
  276. package/src/i18n/framing.he.ts +77 -0
  277. package/src/i18n/framing.hi.ts +82 -0
  278. package/src/i18n/framing.ja.ts +84 -0
  279. package/src/i18n/framing.ko.ts +82 -0
  280. package/src/i18n/framing.pt-BR.ts +84 -0
  281. package/src/i18n/framing.ru.ts +82 -0
  282. package/src/i18n/framing.zh-CN.ts +82 -0
  283. package/src/i18n/furniture.ar.ts +92 -0
  284. package/src/i18n/furniture.de.ts +93 -0
  285. package/src/i18n/furniture.es.ts +101 -0
  286. package/src/i18n/furniture.fr.ts +93 -0
  287. package/src/i18n/furniture.he.ts +101 -0
  288. package/src/i18n/furniture.hi.ts +101 -0
  289. package/src/i18n/furniture.ja.ts +101 -0
  290. package/src/i18n/furniture.ko.ts +101 -0
  291. package/src/i18n/furniture.pt-BR.ts +101 -0
  292. package/src/i18n/furniture.ru.ts +101 -0
  293. package/src/i18n/furniture.zh-CN.ts +101 -0
  294. package/src/i18n/held-prop.ar.ts +74 -0
  295. package/src/i18n/held-prop.de.ts +65 -0
  296. package/src/i18n/held-prop.es.ts +83 -0
  297. package/src/i18n/held-prop.fr.ts +74 -0
  298. package/src/i18n/held-prop.he.ts +73 -0
  299. package/src/i18n/held-prop.hi.ts +82 -0
  300. package/src/i18n/held-prop.ja.ts +84 -0
  301. package/src/i18n/held-prop.ko.ts +84 -0
  302. package/src/i18n/held-prop.pt-BR.ts +84 -0
  303. package/src/i18n/held-prop.ru.ts +82 -0
  304. package/src/i18n/held-prop.zh-CN.ts +82 -0
  305. package/src/i18n/index.ts +208 -0
  306. package/src/i18n/instrumentation.ar.ts +195 -0
  307. package/src/i18n/instrumentation.de.ts +195 -0
  308. package/src/i18n/instrumentation.es.ts +195 -0
  309. package/src/i18n/instrumentation.fr.ts +195 -0
  310. package/src/i18n/instrumentation.he.ts +195 -0
  311. package/src/i18n/instrumentation.hi.ts +195 -0
  312. package/src/i18n/instrumentation.ja.ts +195 -0
  313. package/src/i18n/instrumentation.ko.ts +195 -0
  314. package/src/i18n/instrumentation.pt-BR.ts +195 -0
  315. package/src/i18n/instrumentation.ru.ts +195 -0
  316. package/src/i18n/instrumentation.zh-CN.ts +195 -0
  317. package/src/i18n/lens.ar.ts +24 -0
  318. package/src/i18n/lens.de.ts +24 -0
  319. package/src/i18n/lens.es.ts +24 -0
  320. package/src/i18n/lens.fr.ts +24 -0
  321. package/src/i18n/lens.he.ts +24 -0
  322. package/src/i18n/lens.hi.ts +24 -0
  323. package/src/i18n/lens.ja.ts +24 -0
  324. package/src/i18n/lens.ko.ts +24 -0
  325. package/src/i18n/lens.pt-BR.ts +24 -0
  326. package/src/i18n/lens.ru.ts +25 -0
  327. package/src/i18n/lens.zh-CN.ts +24 -0
  328. package/src/i18n/lighting.ar.ts +86 -0
  329. package/src/i18n/lighting.de.ts +84 -0
  330. package/src/i18n/lighting.es.ts +89 -0
  331. package/src/i18n/lighting.fr.ts +84 -0
  332. package/src/i18n/lighting.he.ts +88 -0
  333. package/src/i18n/lighting.hi.ts +90 -0
  334. package/src/i18n/lighting.ja.ts +90 -0
  335. package/src/i18n/lighting.ko.ts +90 -0
  336. package/src/i18n/lighting.pt-BR.ts +90 -0
  337. package/src/i18n/lighting.ru.ts +88 -0
  338. package/src/i18n/lighting.zh-CN.ts +92 -0
  339. package/src/i18n/loop-subject.ar.ts +39 -0
  340. package/src/i18n/loop-subject.de.ts +39 -0
  341. package/src/i18n/loop-subject.es.ts +39 -0
  342. package/src/i18n/loop-subject.fr.ts +39 -0
  343. package/src/i18n/loop-subject.he.ts +39 -0
  344. package/src/i18n/loop-subject.hi.ts +39 -0
  345. package/src/i18n/loop-subject.ja.ts +39 -0
  346. package/src/i18n/loop-subject.ko.ts +39 -0
  347. package/src/i18n/loop-subject.pt-BR.ts +39 -0
  348. package/src/i18n/loop-subject.ru.ts +39 -0
  349. package/src/i18n/loop-subject.zh-CN.ts +39 -0
  350. package/src/i18n/materials.ar.ts +80 -0
  351. package/src/i18n/materials.de.ts +80 -0
  352. package/src/i18n/materials.es.ts +87 -0
  353. package/src/i18n/materials.fr.ts +80 -0
  354. package/src/i18n/materials.he.ts +86 -0
  355. package/src/i18n/materials.hi.ts +86 -0
  356. package/src/i18n/materials.ja.ts +88 -0
  357. package/src/i18n/materials.ko.ts +88 -0
  358. package/src/i18n/materials.pt-BR.ts +88 -0
  359. package/src/i18n/materials.ru.ts +86 -0
  360. package/src/i18n/materials.zh-CN.ts +86 -0
  361. package/src/i18n/mood.ar.ts +61 -0
  362. package/src/i18n/mood.de.ts +61 -0
  363. package/src/i18n/mood.es.ts +66 -0
  364. package/src/i18n/mood.fr.ts +61 -0
  365. package/src/i18n/mood.he.ts +64 -0
  366. package/src/i18n/mood.hi.ts +66 -0
  367. package/src/i18n/mood.ja.ts +66 -0
  368. package/src/i18n/mood.ko.ts +66 -0
  369. package/src/i18n/mood.pt-BR.ts +64 -0
  370. package/src/i18n/mood.ru.ts +64 -0
  371. package/src/i18n/mood.zh-CN.ts +64 -0
  372. package/src/i18n/music-genre.ar.ts +302 -0
  373. package/src/i18n/music-genre.de.ts +302 -0
  374. package/src/i18n/music-genre.es.ts +302 -0
  375. package/src/i18n/music-genre.fr.ts +302 -0
  376. package/src/i18n/music-genre.he.ts +302 -0
  377. package/src/i18n/music-genre.hi.ts +302 -0
  378. package/src/i18n/music-genre.ja.ts +302 -0
  379. package/src/i18n/music-genre.ko.ts +302 -0
  380. package/src/i18n/music-genre.pt-BR.ts +302 -0
  381. package/src/i18n/music-genre.ru.ts +302 -0
  382. package/src/i18n/music-genre.zh-CN.ts +302 -0
  383. package/src/i18n/music-mood.ar.ts +75 -0
  384. package/src/i18n/music-mood.de.ts +75 -0
  385. package/src/i18n/music-mood.es.ts +75 -0
  386. package/src/i18n/music-mood.fr.ts +75 -0
  387. package/src/i18n/music-mood.he.ts +75 -0
  388. package/src/i18n/music-mood.hi.ts +75 -0
  389. package/src/i18n/music-mood.ja.ts +75 -0
  390. package/src/i18n/music-mood.ko.ts +75 -0
  391. package/src/i18n/music-mood.pt-BR.ts +75 -0
  392. package/src/i18n/music-mood.ru.ts +75 -0
  393. package/src/i18n/music-mood.zh-CN.ts +75 -0
  394. package/src/i18n/person.ar.ts +638 -0
  395. package/src/i18n/person.de.ts +629 -0
  396. package/src/i18n/person.es.ts +645 -0
  397. package/src/i18n/person.fr.ts +621 -0
  398. package/src/i18n/person.he.ts +656 -0
  399. package/src/i18n/person.hi.ts +654 -0
  400. package/src/i18n/person.ja.ts +662 -0
  401. package/src/i18n/person.ko.ts +662 -0
  402. package/src/i18n/person.pt-BR.ts +656 -0
  403. package/src/i18n/person.ru.ts +656 -0
  404. package/src/i18n/person.zh-CN.ts +656 -0
  405. package/src/i18n/photo-genre.ar.ts +58 -0
  406. package/src/i18n/photo-genre.de.ts +52 -0
  407. package/src/i18n/photo-genre.es.ts +64 -0
  408. package/src/i18n/photo-genre.fr.ts +58 -0
  409. package/src/i18n/photo-genre.he.ts +62 -0
  410. package/src/i18n/photo-genre.hi.ts +63 -0
  411. package/src/i18n/photo-genre.ja.ts +65 -0
  412. package/src/i18n/photo-genre.ko.ts +63 -0
  413. package/src/i18n/photo-genre.pt-BR.ts +65 -0
  414. package/src/i18n/photo-genre.ru.ts +63 -0
  415. package/src/i18n/photo-genre.zh-CN.ts +63 -0
  416. package/src/i18n/photographer.ar.ts +75 -0
  417. package/src/i18n/photographer.de.ts +74 -0
  418. package/src/i18n/photographer.es.ts +77 -0
  419. package/src/i18n/photographer.fr.ts +77 -0
  420. package/src/i18n/photographer.he.ts +78 -0
  421. package/src/i18n/photographer.hi.ts +80 -0
  422. package/src/i18n/photographer.ja.ts +84 -0
  423. package/src/i18n/photographer.ko.ts +84 -0
  424. package/src/i18n/photographer.pt-BR.ts +83 -0
  425. package/src/i18n/photographer.ru.ts +78 -0
  426. package/src/i18n/photographer.zh-CN.ts +78 -0
  427. package/src/i18n/pose.ar.ts +95 -0
  428. package/src/i18n/pose.de.ts +96 -0
  429. package/src/i18n/pose.es.ts +104 -0
  430. package/src/i18n/pose.fr.ts +96 -0
  431. package/src/i18n/pose.he.ts +104 -0
  432. package/src/i18n/pose.hi.ts +104 -0
  433. package/src/i18n/pose.ja.ts +104 -0
  434. package/src/i18n/pose.ko.ts +104 -0
  435. package/src/i18n/pose.pt-BR.ts +104 -0
  436. package/src/i18n/pose.ru.ts +104 -0
  437. package/src/i18n/pose.zh-CN.ts +104 -0
  438. package/src/i18n/post-process-effects.ar.ts +25 -0
  439. package/src/i18n/post-process-effects.de.ts +24 -0
  440. package/src/i18n/post-process-effects.es.ts +26 -0
  441. package/src/i18n/post-process-effects.fr.ts +24 -0
  442. package/src/i18n/post-process-effects.he.ts +24 -0
  443. package/src/i18n/post-process-effects.hi.ts +26 -0
  444. package/src/i18n/post-process-effects.ja.ts +26 -0
  445. package/src/i18n/post-process-effects.ko.ts +26 -0
  446. package/src/i18n/post-process-effects.pt-BR.ts +24 -0
  447. package/src/i18n/post-process-effects.ru.ts +24 -0
  448. package/src/i18n/post-process-effects.zh-CN.ts +24 -0
  449. package/src/i18n/render-quality.ar.ts +34 -0
  450. package/src/i18n/render-quality.de.ts +30 -0
  451. package/src/i18n/render-quality.es.ts +35 -0
  452. package/src/i18n/render-quality.fr.ts +34 -0
  453. package/src/i18n/render-quality.he.ts +33 -0
  454. package/src/i18n/render-quality.hi.ts +39 -0
  455. package/src/i18n/render-quality.ja.ts +39 -0
  456. package/src/i18n/render-quality.ko.ts +39 -0
  457. package/src/i18n/render-quality.pt-BR.ts +37 -0
  458. package/src/i18n/render-quality.ru.ts +37 -0
  459. package/src/i18n/render-quality.zh-CN.ts +37 -0
  460. package/src/i18n/seasons.ar.ts +10 -0
  461. package/src/i18n/seasons.de.ts +10 -0
  462. package/src/i18n/seasons.es.ts +10 -0
  463. package/src/i18n/seasons.fr.ts +10 -0
  464. package/src/i18n/seasons.he.ts +10 -0
  465. package/src/i18n/seasons.hi.ts +10 -0
  466. package/src/i18n/seasons.ja.ts +10 -0
  467. package/src/i18n/seasons.ko.ts +10 -0
  468. package/src/i18n/seasons.pt-BR.ts +10 -0
  469. package/src/i18n/seasons.ru.ts +10 -0
  470. package/src/i18n/seasons.zh-CN.ts +10 -0
  471. package/src/i18n/setting.ar.ts +73 -0
  472. package/src/i18n/setting.de.ts +73 -0
  473. package/src/i18n/setting.es.ts +77 -0
  474. package/src/i18n/setting.fr.ts +73 -0
  475. package/src/i18n/setting.he.ts +76 -0
  476. package/src/i18n/setting.hi.ts +76 -0
  477. package/src/i18n/setting.ja.ts +78 -0
  478. package/src/i18n/setting.ko.ts +76 -0
  479. package/src/i18n/setting.pt-BR.ts +78 -0
  480. package/src/i18n/setting.ru.ts +76 -0
  481. package/src/i18n/setting.zh-CN.ts +76 -0
  482. package/src/i18n/style.ar.ts +55 -0
  483. package/src/i18n/style.de.ts +54 -0
  484. package/src/i18n/style.es.ts +54 -0
  485. package/src/i18n/style.fr.ts +54 -0
  486. package/src/i18n/style.he.ts +54 -0
  487. package/src/i18n/style.hi.ts +54 -0
  488. package/src/i18n/style.ja.ts +54 -0
  489. package/src/i18n/style.ko.ts +54 -0
  490. package/src/i18n/style.pt-BR.ts +56 -0
  491. package/src/i18n/style.ru.ts +54 -0
  492. package/src/i18n/style.zh-CN.ts +54 -0
  493. package/src/i18n/styling.ar.ts +295 -0
  494. package/src/i18n/styling.de.ts +285 -0
  495. package/src/i18n/styling.es.ts +317 -0
  496. package/src/i18n/styling.fr.ts +285 -0
  497. package/src/i18n/styling.he.ts +307 -0
  498. package/src/i18n/styling.hi.ts +323 -0
  499. package/src/i18n/styling.ja.ts +317 -0
  500. package/src/i18n/styling.ko.ts +311 -0
  501. package/src/i18n/styling.pt-BR.ts +301 -0
  502. package/src/i18n/styling.ru.ts +301 -0
  503. package/src/i18n/styling.zh-CN.ts +307 -0
  504. package/src/i18n/temporal.ar.ts +31 -0
  505. package/src/i18n/temporal.de.ts +24 -0
  506. package/src/i18n/temporal.es.ts +27 -0
  507. package/src/i18n/temporal.fr.ts +28 -0
  508. package/src/i18n/temporal.he.ts +31 -0
  509. package/src/i18n/temporal.hi.ts +31 -0
  510. package/src/i18n/temporal.ja.ts +31 -0
  511. package/src/i18n/temporal.ko.ts +31 -0
  512. package/src/i18n/temporal.pt-BR.ts +31 -0
  513. package/src/i18n/temporal.ru.ts +31 -0
  514. package/src/i18n/temporal.zh-CN.ts +31 -0
  515. package/src/i18n/transitions.ar.ts +97 -0
  516. package/src/i18n/transitions.de.ts +97 -0
  517. package/src/i18n/transitions.es.ts +97 -0
  518. package/src/i18n/transitions.fr.ts +97 -0
  519. package/src/i18n/transitions.he.ts +97 -0
  520. package/src/i18n/transitions.hi.ts +97 -0
  521. package/src/i18n/transitions.ja.ts +97 -0
  522. package/src/i18n/transitions.ko.ts +97 -0
  523. package/src/i18n/transitions.pt-BR.ts +97 -0
  524. package/src/i18n/transitions.ru.ts +97 -0
  525. package/src/i18n/transitions.zh-CN.ts +97 -0
  526. package/src/i18n/types.ts +158 -0
  527. package/src/i18n/vehicles.ar.ts +137 -0
  528. package/src/i18n/vehicles.de.ts +125 -0
  529. package/src/i18n/vehicles.es.ts +138 -0
  530. package/src/i18n/vehicles.fr.ts +125 -0
  531. package/src/i18n/vehicles.he.ts +136 -0
  532. package/src/i18n/vehicles.hi.ts +138 -0
  533. package/src/i18n/vehicles.ja.ts +138 -0
  534. package/src/i18n/vehicles.ko.ts +138 -0
  535. package/src/i18n/vehicles.pt-BR.ts +136 -0
  536. package/src/i18n/vehicles.ru.ts +136 -0
  537. package/src/i18n/vehicles.zh-CN.ts +136 -0
  538. package/src/i18n/voice-character.ar.ts +126 -0
  539. package/src/i18n/voice-character.de.ts +126 -0
  540. package/src/i18n/voice-character.es.ts +126 -0
  541. package/src/i18n/voice-character.fr.ts +126 -0
  542. package/src/i18n/voice-character.he.ts +126 -0
  543. package/src/i18n/voice-character.hi.ts +126 -0
  544. package/src/i18n/voice-character.ja.ts +126 -0
  545. package/src/i18n/voice-character.ko.ts +126 -0
  546. package/src/i18n/voice-character.pt-BR.ts +126 -0
  547. package/src/i18n/voice-character.ru.ts +126 -0
  548. package/src/i18n/voice-character.zh-CN.ts +126 -0
  549. package/src/i18n/voice-delivery.ar.ts +77 -0
  550. package/src/i18n/voice-delivery.de.ts +77 -0
  551. package/src/i18n/voice-delivery.es.ts +77 -0
  552. package/src/i18n/voice-delivery.fr.ts +77 -0
  553. package/src/i18n/voice-delivery.he.ts +77 -0
  554. package/src/i18n/voice-delivery.hi.ts +77 -0
  555. package/src/i18n/voice-delivery.ja.ts +77 -0
  556. package/src/i18n/voice-delivery.ko.ts +77 -0
  557. package/src/i18n/voice-delivery.pt-BR.ts +77 -0
  558. package/src/i18n/voice-delivery.ru.ts +77 -0
  559. package/src/i18n/voice-delivery.zh-CN.ts +77 -0
  560. package/src/i18n/weapons.ar.ts +115 -0
  561. package/src/i18n/weapons.de.ts +114 -0
  562. package/src/i18n/weapons.es.ts +116 -0
  563. package/src/i18n/weapons.fr.ts +103 -0
  564. package/src/i18n/weapons.he.ts +114 -0
  565. package/src/i18n/weapons.hi.ts +116 -0
  566. package/src/i18n/weapons.ja.ts +116 -0
  567. package/src/i18n/weapons.ko.ts +116 -0
  568. package/src/i18n/weapons.pt-BR.ts +114 -0
  569. package/src/i18n/weapons.ru.ts +114 -0
  570. package/src/i18n/weapons.zh-CN.ts +114 -0
  571. package/src/image-critic-node.ts +61 -0
  572. package/src/image-critic-types.ts +40 -0
  573. package/src/index.ts +866 -0
  574. package/src/inject-upstream.ts +15 -0
  575. package/src/inpaint-region.ts +51 -0
  576. package/src/json-evaluator.ts +732 -0
  577. package/src/json-path.ts +52 -0
  578. package/src/lip-sync-pricing.ts +90 -0
  579. package/src/list-merge.ts +40 -0
  580. package/src/list-sort.ts +122 -0
  581. package/src/list-source-resolver.ts +50 -0
  582. package/src/llm-models.ts +245 -0
  583. package/src/location-mention-slug.ts +291 -0
  584. package/src/location-preset-catalog-map.ts +128 -0
  585. package/src/loop-delimiter.ts +73 -0
  586. package/src/lora-routing.ts +95 -0
  587. package/src/lottie-overlay-catalog.ts +175 -0
  588. package/src/lottie-slots.ts +309 -0
  589. package/src/model-catalog.ts +2386 -0
  590. package/src/model-constants.ts +1910 -0
  591. package/src/model-tree.ts +120 -0
  592. package/src/monetization.ts +15 -0
  593. package/src/multi-pick.ts +42 -0
  594. package/src/node-default-mappings.ts +262 -0
  595. package/src/node-mappable-fields.ts +70 -0
  596. package/src/node-preset-export.ts +56 -0
  597. package/src/node-preset-extract.ts +147 -0
  598. package/src/node-refs.ts +253 -0
  599. package/src/node-runtime-keys.ts +117 -0
  600. package/src/object-aspect-defaults.ts +72 -0
  601. package/src/object-picker-types.ts +22 -0
  602. package/src/parameter-node-value.ts +284 -0
  603. package/src/pinterest-url.ts +28 -0
  604. package/src/pipeline-chat.ts +72 -0
  605. package/src/pipeline-defaults.ts +318 -0
  606. package/src/pipeline-events.ts +162 -0
  607. package/src/pipeline-state-types.ts +196 -0
  608. package/src/pipeline-types.ts +647 -0
  609. package/src/pipeline-validation.ts +87 -0
  610. package/src/presentation-types.ts +23 -0
  611. package/src/presentation-utils.ts +397 -0
  612. package/src/producer-types.ts +188 -0
  613. package/src/progress-curve.ts +99 -0
  614. package/src/provider-directive-defaults.ts +69 -0
  615. package/src/reduce-strategy-registry.ts +121 -0
  616. package/src/reference-board-templates.ts +81 -0
  617. package/src/reference-roles.ts +153 -0
  618. package/src/reference-sheet/__tests__/board-to-column.test.ts +20 -0
  619. package/src/reference-sheet/__tests__/catalog.test.ts +50 -0
  620. package/src/reference-sheet/__tests__/cost.test.ts +38 -0
  621. package/src/reference-sheet/__tests__/no-drift.test.ts +28 -0
  622. package/src/reference-sheet/__tests__/panel-plan.test.ts +61 -0
  623. package/src/reference-sheet/__tests__/panel-prompts.test.ts +17 -0
  624. package/src/reference-sheet/__tests__/plan-generation.test.ts +72 -0
  625. package/src/reference-sheet/__tests__/presets.test.ts +54 -0
  626. package/src/reference-sheet/__tests__/types.test.ts +24 -0
  627. package/src/reference-sheet/catalog.ts +144 -0
  628. package/src/reference-sheet/cost.ts +37 -0
  629. package/src/reference-sheet/credit-id.ts +7 -0
  630. package/src/reference-sheet/index.ts +8 -0
  631. package/src/reference-sheet/panel-plan.ts +65 -0
  632. package/src/reference-sheet/panel-prompts.ts +14 -0
  633. package/src/reference-sheet/plan-generation.ts +79 -0
  634. package/src/reference-sheet/presets.ts +77 -0
  635. package/src/reference-sheet/types.ts +92 -0
  636. package/src/repeat-types.ts +97 -0
  637. package/src/resolve-field-mappings.ts +119 -0
  638. package/src/route-filter.ts +80 -0
  639. package/src/scene-helper-types.ts +249 -0
  640. package/src/scene-node-types.ts +281 -0
  641. package/src/scraper-actors.ts +57 -0
  642. package/src/scraper-output-schemas.ts +25 -0
  643. package/src/seasons.ts +45 -0
  644. package/src/selector.ts +864 -0
  645. package/src/settled-with-limit.ts +42 -0
  646. package/src/shot-element.ts +43 -0
  647. package/src/social-media-specs.ts +121 -0
  648. package/src/social-post.ts +24 -0
  649. package/src/sub-workflow-validation.ts +78 -0
  650. package/src/supported-fonts.ts +40 -0
  651. package/src/surround.ts +113 -0
  652. package/src/switchx-pricing.ts +33 -0
  653. package/src/text-separators.ts +59 -0
  654. package/src/to-connected-references.ts +77 -0
  655. package/src/types.ts +272 -0
  656. package/src/variant-job-id.ts +17 -0
  657. package/src/vehicles.ts +213 -0
  658. package/src/video-analysis-pricing.ts +74 -0
  659. package/src/video-analysis.ts +127 -0
  660. package/src/video-duration.ts +25 -0
  661. package/src/voice-changer-models.ts +36 -0
  662. package/src/voice-matcher-types.ts +37 -0
  663. package/src/voices.ts +92 -0
  664. package/src/weapons.ts +190 -0
  665. package/src/workflow-export.ts +153 -0
@@ -0,0 +1,47 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import {
3
+ AUDIO_PRODUCER_TYPES,
4
+ VIDEO_PRODUCER_TYPES,
5
+ DYNAMIC_PRODUCER_TYPES,
6
+ } from "../producer-types.js"
7
+
8
+ /**
9
+ * Producer-set membership is what every downstream node's typed-handle
10
+ * `accepts(sourceType)` predicate consults to decide whether an edge LEAVING
11
+ * a source node may land on its input. A node with audio/video OUTPUT handles
12
+ * that is absent from these sets can't connect its outputs anywhere — the
13
+ * canvas validator hard-rejects every edge (drift bug class, see file header).
14
+ *
15
+ * This guards the recurrence where `voice-changer-pro` (renamed from
16
+ * voice-recast in #3581) shipped with output handles but was never added to
17
+ * the producer sets, so "cannot connect the outputs of voice-changer-pro".
18
+ */
19
+ describe("producer-types", () => {
20
+ const ALL_SETS = {
21
+ AUDIO_PRODUCER_TYPES,
22
+ VIDEO_PRODUCER_TYPES,
23
+ DYNAMIC_PRODUCER_TYPES,
24
+ } as const
25
+
26
+ it("registers voice-changer-pro as an audio producer (its default output)", () => {
27
+ expect(AUDIO_PRODUCER_TYPES.has("voice-changer-pro")).toBe(true)
28
+ })
29
+
30
+ it("registers voice-changer-pro as a dynamic producer (dual-mode → accepted on BOTH audio and video input handles)", () => {
31
+ expect(DYNAMIC_PRODUCER_TYPES.has("voice-changer-pro")).toBe(true)
32
+ })
33
+
34
+ // voice-changer-pro is a behavioral twin of voice-changer (identical
35
+ // dual-mode output: audio in → audio out; video in → video out). Their
36
+ // producer-set membership must never drift apart — if voice-changer is
37
+ // later added to/removed from any set, the same must happen for the Pro
38
+ // variant, or its outputs silently stop connecting in exactly one mode.
39
+ it("keeps voice-changer-pro's producer membership identical to its twin voice-changer", () => {
40
+ for (const [name, set] of Object.entries(ALL_SETS)) {
41
+ expect(
42
+ set.has("voice-changer-pro"),
43
+ `voice-changer-pro must match voice-changer in ${name}`,
44
+ ).toBe(set.has("voice-changer"))
45
+ }
46
+ })
47
+ })
@@ -0,0 +1,156 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import {
3
+ IMAGE_PROMPT_MAX,
4
+ VIDEO_PROMPT_MAX,
5
+ PROMPT_HARD_CEILING,
6
+ NEGATIVE_PROMPT_MAX,
7
+ TTS_TEXT_MAX,
8
+ SUNO_TITLE_MAX,
9
+ MAX_IMAGE_PROMPT_CHARS_BY_PROVIDER,
10
+ MAX_VIDEO_PROMPT_CHARS_BY_PROVIDER,
11
+ MAX_NEGATIVE_PROMPT_CHARS_BY_PROVIDER,
12
+ MAX_TTS_CHARS_BY_PROVIDER,
13
+ getMaxImagePromptChars,
14
+ getMaxVideoPromptChars,
15
+ getMaxNegativePromptChars,
16
+ getMaxTtsChars,
17
+ getMaxSunoPromptChars,
18
+ getMaxSunoStyleChars,
19
+ IMAGE_GEN_PROVIDERS,
20
+ IMAGE_I2I_PROVIDERS,
21
+ IMAGE_EDIT_PROVIDERS,
22
+ VIDEO_GEN_PROVIDERS,
23
+ VIDEO_TO_VIDEO_PROVIDERS,
24
+ EXTEND_VIDEO_PROVIDERS,
25
+ } from "../model-constants.js"
26
+
27
+ describe("per-model prompt length limits", () => {
28
+ describe("getMaxImagePromptChars", () => {
29
+ it("returns the verified higher caps (provider supports more than 5000)", () => {
30
+ expect(getMaxImagePromptChars("nano-banana-2")).toBe(20000)
31
+ expect(getMaxImagePromptChars("nano-banana-pro")).toBe(20000)
32
+ expect(getMaxImagePromptChars("gpt-image-2-i2i")).toBe(20000)
33
+ })
34
+ it("returns the verified LOWER caps (over-send risk if left at 5000)", () => {
35
+ expect(getMaxImagePromptChars("seedream")).toBe(3000)
36
+ expect(getMaxImagePromptChars("seedream-5-lite")).toBe(1000)
37
+ expect(getMaxImagePromptChars("qwen")).toBe(3000)
38
+ expect(getMaxImagePromptChars("qwen-edit")).toBe(2000)
39
+ })
40
+ it("siblings differ — does not copy the t2i number to i2i/edit", () => {
41
+ // seedream t2i 3000, 5-lite t2i 1000 but its i2i is 3000
42
+ expect(getMaxImagePromptChars("seedream-5-lite-i2i")).toBe(3000)
43
+ })
44
+ it("falls back to IMAGE_PROMPT_MAX for verified-default + unknown providers", () => {
45
+ expect(getMaxImagePromptChars("flux")).toBe(IMAGE_PROMPT_MAX)
46
+ expect(getMaxImagePromptChars("imagen4")).toBe(IMAGE_PROMPT_MAX)
47
+ expect(getMaxImagePromptChars("grok-i2i")).toBe(IMAGE_PROMPT_MAX) // 390000 doc = sanity-capped
48
+ expect(getMaxImagePromptChars("flux-kontext")).toBe(IMAGE_PROMPT_MAX) // UNVERIFIED
49
+ expect(getMaxImagePromptChars(undefined)).toBe(IMAGE_PROMPT_MAX)
50
+ expect(getMaxImagePromptChars("totally-made-up")).toBe(IMAGE_PROMPT_MAX)
51
+ })
52
+ })
53
+
54
+ describe("getMaxVideoPromptChars", () => {
55
+ it("returns verified per-model caps (mostly far below the old flat 8000)", () => {
56
+ expect(getMaxVideoPromptChars("kling")).toBe(1000)
57
+ expect(getMaxVideoPromptChars("kling-turbo")).toBe(2500)
58
+ expect(getMaxVideoPromptChars("kling-master")).toBe(5000)
59
+ expect(getMaxVideoPromptChars("minimax")).toBe(1500)
60
+ expect(getMaxVideoPromptChars("runway-kie")).toBe(1800)
61
+ expect(getMaxVideoPromptChars("grok-imagine-video-1.5")).toBe(4096)
62
+ })
63
+ it("returns verified higher caps", () => {
64
+ expect(getMaxVideoPromptChars("seedance-2")).toBe(20000)
65
+ expect(getMaxVideoPromptChars("gemini-omni-video")).toBe(20000)
66
+ expect(getMaxVideoPromptChars("bytedance-pro")).toBe(10000)
67
+ })
68
+ it("falls back to VIDEO_PROMPT_MAX for UNVERIFIED + unknown providers", () => {
69
+ expect(getMaxVideoPromptChars("veo3")).toBe(VIDEO_PROMPT_MAX) // no limit in schema
70
+ expect(getMaxVideoPromptChars("kling-3.0")).toBe(VIDEO_PROMPT_MAX)
71
+ expect(getMaxVideoPromptChars(undefined)).toBe(VIDEO_PROMPT_MAX)
72
+ })
73
+ })
74
+
75
+ describe("getMaxNegativePromptChars", () => {
76
+ it("returns verified native negative caps", () => {
77
+ expect(getMaxNegativePromptChars("ideogram-v3")).toBe(500)
78
+ expect(getMaxNegativePromptChars("qwen")).toBe(500)
79
+ expect(getMaxNegativePromptChars("kling-master")).toBe(500)
80
+ expect(getMaxNegativePromptChars("imagen4")).toBe(5000)
81
+ })
82
+ it("defaults to NEGATIVE_PROMPT_MAX otherwise", () => {
83
+ expect(getMaxNegativePromptChars("flux")).toBe(NEGATIVE_PROMPT_MAX)
84
+ expect(getMaxNegativePromptChars(undefined)).toBe(NEGATIVE_PROMPT_MAX)
85
+ })
86
+ })
87
+
88
+ describe("getMaxTtsChars", () => {
89
+ it("returns verified per-model TTS caps", () => {
90
+ expect(getMaxTtsChars("elevenlabs-turbo")).toBe(40000)
91
+ expect(getMaxTtsChars("elevenlabs-multilingual")).toBe(10000)
92
+ expect(getMaxTtsChars("elevenlabs-v3")).toBe(3000) // conservative
93
+ expect(getMaxTtsChars("elevenlabs-dialogue")).toBe(2000)
94
+ })
95
+ it("defaults to TTS_TEXT_MAX for the legacy/unknown provider", () => {
96
+ expect(getMaxTtsChars("elevenlabs")).toBe(TTS_TEXT_MAX)
97
+ expect(getMaxTtsChars(undefined)).toBe(TTS_TEXT_MAX)
98
+ })
99
+ })
100
+
101
+ describe("Suno per-version caps", () => {
102
+ it("prompt: 500 non-custom; 3000 V4 custom; 5000 V4.5+/V5 custom", () => {
103
+ expect(getMaxSunoPromptChars("V4", false)).toBe(500)
104
+ expect(getMaxSunoPromptChars("V5", false)).toBe(500)
105
+ expect(getMaxSunoPromptChars("V4", true)).toBe(3000)
106
+ expect(getMaxSunoPromptChars("V4_5", true)).toBe(5000)
107
+ expect(getMaxSunoPromptChars("V5", true)).toBe(5000)
108
+ expect(getMaxSunoPromptChars("V5_5", true)).toBe(5000)
109
+ })
110
+ it("style: 200 for V4, 1000 for V4.5+; title 80", () => {
111
+ expect(getMaxSunoStyleChars("V4")).toBe(200)
112
+ expect(getMaxSunoStyleChars("V5")).toBe(1000)
113
+ expect(SUNO_TITLE_MAX).toBe(80)
114
+ })
115
+ })
116
+
117
+ describe("drift guards (registry hygiene)", () => {
118
+ const imageProviders = new Set<string>([
119
+ ...IMAGE_GEN_PROVIDERS,
120
+ ...IMAGE_I2I_PROVIDERS,
121
+ ...IMAGE_EDIT_PROVIDERS,
122
+ ])
123
+ const videoProviders = new Set<string>([
124
+ ...VIDEO_GEN_PROVIDERS,
125
+ ...VIDEO_TO_VIDEO_PROVIDERS,
126
+ ...EXTEND_VIDEO_PROVIDERS,
127
+ ])
128
+
129
+ it("every image-cap key is a real image provider id", () => {
130
+ for (const key of Object.keys(MAX_IMAGE_PROMPT_CHARS_BY_PROVIDER)) {
131
+ expect(imageProviders.has(key), `${key} is not a known image provider`).toBe(true)
132
+ }
133
+ })
134
+ it("every video-cap key is a real video provider id", () => {
135
+ for (const key of Object.keys(MAX_VIDEO_PROMPT_CHARS_BY_PROVIDER)) {
136
+ expect(videoProviders.has(key), `${key} is not a known video provider`).toBe(true)
137
+ }
138
+ })
139
+ it("all caps are positive integers within the hard ceiling", () => {
140
+ const all = [
141
+ ...Object.values(MAX_IMAGE_PROMPT_CHARS_BY_PROVIDER),
142
+ ...Object.values(MAX_VIDEO_PROMPT_CHARS_BY_PROVIDER),
143
+ ...Object.values(MAX_NEGATIVE_PROMPT_CHARS_BY_PROVIDER),
144
+ ]
145
+ for (const v of all) {
146
+ expect(Number.isInteger(v) && v > 0).toBe(true)
147
+ expect(v).toBeLessThanOrEqual(PROMPT_HARD_CEILING)
148
+ }
149
+ })
150
+ it("TTS caps are positive integers (turbo may exceed the image/video ceiling)", () => {
151
+ for (const v of Object.values(MAX_TTS_CHARS_BY_PROVIDER)) {
152
+ expect(Number.isInteger(v) && v > 0).toBe(true)
153
+ }
154
+ })
155
+ })
156
+ })
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { mapShotIntentToProviderDirectives } from "../provider-directive-defaults.js"
3
+
4
+ const baseIntent = {
5
+ needs_multishot_reference: false,
6
+ is_loopable: false,
7
+ needs_music_suppression: true,
8
+ is_match_cut: false,
9
+ }
10
+
11
+ describe("mapShotIntentToProviderDirectives", () => {
12
+ it("seedance-2 with default intent gets disable_internal_music + allow_sfx", () => {
13
+ const dirs = mapShotIntentToProviderDirectives("seedance-2", baseIntent)
14
+ expect(dirs.disable_internal_music).toBe(true)
15
+ expect(dirs.allow_sfx).toBe(true)
16
+ })
17
+
18
+ it("seedance-2 with multishot intent adds multishot: true", () => {
19
+ const dirs = mapShotIntentToProviderDirectives("seedance-2", {
20
+ ...baseIntent,
21
+ needs_multishot_reference: true,
22
+ })
23
+ expect(dirs.multishot).toBe(true)
24
+ })
25
+
26
+ it("veo3.1 with loopable intent adds loop_seed: stable", () => {
27
+ const dirs = mapShotIntentToProviderDirectives("veo3.1", {
28
+ ...baseIntent,
29
+ is_loopable: true,
30
+ })
31
+ expect(dirs.loop_seed).toBe("stable")
32
+ })
33
+
34
+ it("hailuo with multishot intent doesn't add multishot (provider doesn't support it)", () => {
35
+ const dirs = mapShotIntentToProviderDirectives("hailuo-2.3-pro", {
36
+ ...baseIntent,
37
+ needs_multishot_reference: true,
38
+ })
39
+ expect(dirs.multishot).toBeUndefined()
40
+ })
41
+
42
+ it("unknown provider returns an empty object", () => {
43
+ const dirs = mapShotIntentToProviderDirectives("made-up-model", baseIntent)
44
+ expect(dirs).toEqual({})
45
+ })
46
+ })
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import { REDUCE_STRATEGIES, REDUCE_STRATEGY_IDS, getStrategy } from "../reduce-strategy-registry"
3
+
4
+ describe("reduce-strategy-registry", () => {
5
+ it("registers exactly the 6 v1 strategies (snapshot)", () => {
6
+ expect(REDUCE_STRATEGY_IDS).toEqual([
7
+ "pick-best-llm",
8
+ "concat",
9
+ "first-non-empty",
10
+ "count",
11
+ "vote",
12
+ "merge-json",
13
+ ])
14
+ })
15
+
16
+ it("every strategy has id, label, description, configSchema, defaultConfig, outputType, creditCostKey", () => {
17
+ for (const s of REDUCE_STRATEGIES) {
18
+ expect(s.id).toBeTruthy()
19
+ expect(s.label).toBeTruthy()
20
+ expect(s.description).toBeTruthy()
21
+ expect(s.configSchema).toBeTruthy()
22
+ expect(s.defaultConfig).toBeDefined()
23
+ expect(s.outputType).toMatch(/^(image|video|audio|text|data)$/)
24
+ expect(s.creditCostKey).toMatch(/^reduce:/)
25
+ }
26
+ })
27
+
28
+ it("every strategy's configSchema accepts its defaultConfig", () => {
29
+ for (const s of REDUCE_STRATEGIES) {
30
+ const result = s.configSchema.safeParse(s.defaultConfig)
31
+ expect(result.success, `${s.id} defaultConfig must satisfy configSchema`).toBe(true)
32
+ }
33
+ })
34
+
35
+ it("getStrategy returns the right strategy by id", () => {
36
+ expect(getStrategy("concat").id).toBe("concat")
37
+ expect(getStrategy("pick-best-llm").id).toBe("pick-best-llm")
38
+ })
39
+
40
+ it("getStrategy throws on unknown id", () => {
41
+ expect(() => getStrategy("nope" as never)).toThrow(/unknown reduce strategy/i)
42
+ })
43
+ })
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import {
3
+ REFERENCE_BOARD_PROVIDERS,
4
+ REFERENCE_BOARD_TEMPLATES,
5
+ buildBoardPrompt,
6
+ listBoardTemplates,
7
+ } from "../reference-board-templates.js"
8
+
9
+ describe("reference-board templates", () => {
10
+ it("ships gen providers as a subset of image providers", () => {
11
+ expect(REFERENCE_BOARD_PROVIDERS).toContain("nano-banana-pro")
12
+ expect(REFERENCE_BOARD_PROVIDERS).toContain("gpt-image-2")
13
+ })
14
+
15
+ it("every entity kind has a full-board template", () => {
16
+ for (const kind of ["character", "location", "object"] as const) {
17
+ const ids = listBoardTemplates(kind).map((t) => t.id)
18
+ expect(ids).toContain(`${kind}/full-board`)
19
+ }
20
+ })
21
+
22
+ it("full-board prompt carries the three guide tricks", () => {
23
+ const p = buildBoardPrompt("character/full-board", {})
24
+ expect(p.toLowerCase()).toContain("palette") // 6-HEX palette panel
25
+ expect(p.toLowerCase()).toContain("hex")
26
+ expect(p).toContain("visual reference for consistent depiction") // self-referential caption
27
+ expect(p.toLowerCase()).toContain("aspect ratio") // anti-rigid AR clause
28
+ })
29
+
30
+ it("unknown template id throws (no silent empty prompt)", () => {
31
+ expect(() => buildBoardPrompt("character/nope", {})).toThrow()
32
+ })
33
+ })
@@ -0,0 +1,36 @@
1
+ // packages/shared/src/__tests__/reference-roles.test.ts
2
+ import { describe, it, expect } from "vitest"
3
+ import { REFERENCE_ROLE_PRESETS, roleToPhrase, defaultRoleForSource } from "../reference-roles.js"
4
+ import { DEFAULT_LABEL_BY_SOURCE } from "../types.js"
5
+
6
+ describe("reference-roles registry", () => {
7
+ it("every source's default role is present in its preset list", () => {
8
+ for (const source of Object.keys(DEFAULT_LABEL_BY_SOURCE) as Array<keyof typeof DEFAULT_LABEL_BY_SOURCE>) {
9
+ const presets = REFERENCE_ROLE_PRESETS[source]
10
+ expect(presets, source).toBeTruthy()
11
+ expect(presets, source).toContain(defaultRoleForSource(source))
12
+ }
13
+ })
14
+
15
+ it("defaultRoleForSource mirrors DEFAULT_LABEL_BY_SOURCE", () => {
16
+ expect(defaultRoleForSource("wired-character")).toBe("person")
17
+ expect(defaultRoleForSource("wired-location")).toBe("background")
18
+ expect(defaultRoleForSource("wired-object")).toBe("object")
19
+ expect(defaultRoleForSource("wired-creature")).toBe("creature")
20
+ })
21
+
22
+ it("renders noun roles as 'the {role} from {binding}'", () => {
23
+ expect(roleToPhrase("person", "reference image A")).toBe("the person from reference image A")
24
+ expect(roleToPhrase("face", "@image_3")).toBe("the face from @image_3")
25
+ })
26
+
27
+ it("renders the non-noun specials naturally", () => {
28
+ expect(roleToPhrase("as-is", "reference image A")).toBe("reference image A, used as-is")
29
+ expect(roleToPhrase("empty background", "reference image A"))
30
+ .toBe("the background from reference image A (without its foreground objects)")
31
+ })
32
+
33
+ it("renders a custom single-word label via the default template", () => {
34
+ expect(roleToPhrase("hoodie", "reference image B")).toBe("the hoodie from reference image B")
35
+ })
36
+ })
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Tests for shared field-resolver helpers in `resolve-field-mappings.ts`.
3
+ *
4
+ * `resolveLocationFields(loc)` flattens a location's anchor image plus every
5
+ * variant bucket (timeOfDay/weather/seasons/angles/lighting/atmosphereMotions)
6
+ * into a single `Array<{ key, label, url }>` for picker UIs and ref-resolvers.
7
+ */
8
+
9
+ import { describe, it, expect } from "vitest"
10
+ import { resolveLocationFields } from "../resolve-field-mappings.js"
11
+ import { resolveFieldMappings, fieldKeyFromHandle } from "../index.js"
12
+
13
+ describe("resolveLocationFields", () => {
14
+ it("returns just the main image entry when no buckets are set", () => {
15
+ const loc = { sourceImageUrl: "https://r2/loc/main.png" }
16
+ const result = resolveLocationFields(loc)
17
+ expect(result).toEqual([
18
+ { key: "sourceImageUrl", label: "Main image", url: "https://r2/loc/main.png" },
19
+ ])
20
+ })
21
+
22
+ it("flattens every bucket into key/label/url entries with per-bucket indices", () => {
23
+ const loc = {
24
+ sourceImageUrl: "https://r2/loc/main.png",
25
+ timeOfDay: [
26
+ { name: "dawn", url: "https://r2/loc/dawn.png" },
27
+ { name: "dusk", url: "https://r2/loc/dusk.png" },
28
+ ],
29
+ weather: [{ name: "rainy", url: "https://r2/loc/rainy.png" }],
30
+ seasons: [{ name: "winter", url: "https://r2/loc/winter.png" }],
31
+ angles: [{ name: "wide", url: "https://r2/loc/wide.png" }],
32
+ lighting: [{ name: "neon", url: "https://r2/loc/neon.png" }],
33
+ atmosphereMotions: [
34
+ { name: "smoke", url: "https://r2/loc/smoke.png" },
35
+ { name: "rain-fall", url: "https://r2/loc/rain-fall.png" },
36
+ ],
37
+ }
38
+ const result = resolveLocationFields(loc)
39
+ expect(result).toEqual([
40
+ { key: "sourceImageUrl", label: "Main image", url: "https://r2/loc/main.png" },
41
+ { key: "timeOfDay[0]", label: "timeOfDay / dawn", url: "https://r2/loc/dawn.png" },
42
+ { key: "timeOfDay[1]", label: "timeOfDay / dusk", url: "https://r2/loc/dusk.png" },
43
+ { key: "weather[0]", label: "weather / rainy", url: "https://r2/loc/rainy.png" },
44
+ { key: "seasons[0]", label: "seasons / winter", url: "https://r2/loc/winter.png" },
45
+ { key: "angles[0]", label: "angles / wide", url: "https://r2/loc/wide.png" },
46
+ { key: "lighting[0]", label: "lighting / neon", url: "https://r2/loc/neon.png" },
47
+ { key: "atmosphereMotions[0]", label: "atmosphereMotions / smoke", url: "https://r2/loc/smoke.png" },
48
+ { key: "atmosphereMotions[1]", label: "atmosphereMotions / rain-fall", url: "https://r2/loc/rain-fall.png" },
49
+ ])
50
+ })
51
+
52
+ it("skips sourceImageUrl when missing", () => {
53
+ const loc = {
54
+ seasons: [{ name: "spring", url: "https://r2/loc/spring.png" }],
55
+ }
56
+ const result = resolveLocationFields(loc)
57
+ expect(result).toEqual([
58
+ { key: "seasons[0]", label: "seasons / spring", url: "https://r2/loc/spring.png" },
59
+ ])
60
+ })
61
+
62
+ it("treats missing buckets and non-array buckets as empty", () => {
63
+ const loc = {
64
+ sourceImageUrl: "https://r2/loc/main.png",
65
+ // bucket omitted entirely
66
+ weather: undefined,
67
+ // non-array values should not throw
68
+ seasons: null as unknown as Array<{ name: string; url: string }>,
69
+ }
70
+ const result = resolveLocationFields(loc)
71
+ expect(result).toEqual([
72
+ { key: "sourceImageUrl", label: "Main image", url: "https://r2/loc/main.png" },
73
+ ])
74
+ })
75
+
76
+ it("returns an empty array when the location has no fields at all", () => {
77
+ expect(resolveLocationFields({})).toEqual([])
78
+ })
79
+ })
80
+
81
+ describe("resolveFieldMappings — edgeSourceForField precedence", () => {
82
+ it("prefers an edge source over data.fieldMappings and over the typed value", () => {
83
+ const data = { style: "typed-style", fieldMappings: { style: { sourceNodeId: "mapped" } } }
84
+ const out = resolveFieldMappings(
85
+ data,
86
+ undefined,
87
+ ["style"],
88
+ (id) => (id === "edge" ? "EDGE TEXT" : id === "mapped" ? "MAPPED TEXT" : undefined),
89
+ (field) => (field === "style" ? "edge" : undefined),
90
+ )
91
+ expect(out.style).toBe("EDGE TEXT")
92
+ })
93
+
94
+ it("falls back to data.fieldMappings when no edge source", () => {
95
+ const data = { style: "x", fieldMappings: { style: { sourceNodeId: "mapped" } } }
96
+ const out = resolveFieldMappings(data, undefined, ["style"], () => "MAPPED TEXT", () => undefined)
97
+ expect(out.style).toBe("MAPPED TEXT")
98
+ })
99
+
100
+ it("leaves the field untouched when neither edge nor mapping resolves", () => {
101
+ const data = { style: "keep" }
102
+ const out = resolveFieldMappings(data, undefined, ["style"], () => undefined, () => undefined)
103
+ expect(out.style).toBe("keep")
104
+ })
105
+ })
106
+
107
+ describe("fieldKeyFromHandle", () => {
108
+ it("strips the field- prefix", () => { expect(fieldKeyFromHandle("field-style")).toBe("style") })
109
+ it("returns null for non-field handles", () => { expect(fieldKeyFromHandle("prompt")).toBeNull() })
110
+ })
@@ -0,0 +1,76 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { resolvePipelineModel, type PipelineConfig } from "../pipeline-types.js"
3
+
4
+ describe("resolvePipelineModel", () => {
5
+ it("returns undefined when config is null/undefined", () => {
6
+ expect(resolvePipelineModel(undefined, "characters_image")).toBeUndefined()
7
+ expect(resolvePipelineModel(null, "shots_video")).toBeUndefined()
8
+ })
9
+
10
+ it("returns undefined when neither stage_models nor global field is set", () => {
11
+ const config: Partial<PipelineConfig> = {}
12
+ expect(resolvePipelineModel(config, "characters_image")).toBeUndefined()
13
+ expect(resolvePipelineModel(config, "shots_video")).toBeUndefined()
14
+ expect(resolvePipelineModel(config, "script_llm")).toBeUndefined()
15
+ })
16
+
17
+ it("returns the global image_model for any *_image stage when stage_models is absent", () => {
18
+ const config: Partial<PipelineConfig> = { image_model: "flux" }
19
+ expect(resolvePipelineModel(config, "characters_image")).toBe("flux")
20
+ expect(resolvePipelineModel(config, "locations_image")).toBe("flux")
21
+ expect(resolvePipelineModel(config, "objects_image")).toBe("flux")
22
+ expect(resolvePipelineModel(config, "scene_keyframes_image")).toBe("flux")
23
+ })
24
+
25
+ it("returns the global video_model for *_video stages", () => {
26
+ const config: Partial<PipelineConfig> = { video_model: "kling" }
27
+ expect(resolvePipelineModel(config, "shots_video")).toBe("kling")
28
+ })
29
+
30
+ it("returns the global script_llm for the script_llm stage", () => {
31
+ const config: Partial<PipelineConfig> = { script_llm: "claude-opus-4-6" }
32
+ expect(resolvePipelineModel(config, "script_llm")).toBe("claude-opus-4-6")
33
+ })
34
+
35
+ it("per-stage override beats the matching global field", () => {
36
+ const config: Partial<PipelineConfig> = {
37
+ image_model: "flux",
38
+ stage_models: { characters_image: "nano-banana-pro" },
39
+ }
40
+ expect(resolvePipelineModel(config, "characters_image")).toBe("nano-banana-pro")
41
+ // Sibling stages still get the global pick.
42
+ expect(resolvePipelineModel(config, "locations_image")).toBe("flux")
43
+ expect(resolvePipelineModel(config, "objects_image")).toBe("flux")
44
+ expect(resolvePipelineModel(config, "scene_keyframes_image")).toBe("flux")
45
+ })
46
+
47
+ it("per-stage overrides work independently across kinds", () => {
48
+ const config: Partial<PipelineConfig> = {
49
+ image_model: "flux",
50
+ video_model: "kling",
51
+ script_llm: "claude-sonnet-4-6",
52
+ stage_models: {
53
+ scene_keyframes_image: "gpt-image",
54
+ shots_video: "veo3",
55
+ script_llm: "claude-opus-4-6",
56
+ },
57
+ }
58
+ expect(resolvePipelineModel(config, "scene_keyframes_image")).toBe("gpt-image")
59
+ expect(resolvePipelineModel(config, "shots_video")).toBe("veo3")
60
+ expect(resolvePipelineModel(config, "script_llm")).toBe("claude-opus-4-6")
61
+ // Entity image stages keep the global pick.
62
+ expect(resolvePipelineModel(config, "characters_image")).toBe("flux")
63
+ })
64
+
65
+ it("an explicit empty string in stage_models is treated as 'not set' so the global wins (defense-in-depth)", () => {
66
+ // The Zod schema now rejects empty strings (the enum doesn't include ""),
67
+ // but the resolver still defensively treats falsy stage values as
68
+ // unset — in case a record bypasses Zod (DB hand-edit, legacy row, etc.).
69
+ // The cast below acknowledges we're testing a state Zod won't produce.
70
+ const config = {
71
+ image_model: "flux",
72
+ stage_models: { characters_image: "" },
73
+ } as unknown as Partial<PipelineConfig>
74
+ expect(resolvePipelineModel(config, "characters_image")).toBe("flux")
75
+ })
76
+ })
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { ShotSpecSchema, MatchCutVerdictSchema } from "../scene-node-types.js"
3
+
4
+ // ─── minimal valid ShotSpec fixture ─────────────────────────────────────────
5
+ const baseShotSpec = {
6
+ shot_id: "shot_01",
7
+ camera: {
8
+ shot_type: "wide" as const,
9
+ angle: "eye_level" as const,
10
+ motion: "static" as const,
11
+ },
12
+ shot_intensity_kind: "establishing_shot" as const,
13
+ action: "Character walks through door",
14
+ dialogue_line: null,
15
+ duration_seconds: 3,
16
+ motion_prompt: "Slow push forward",
17
+ start_state: "Character at door",
18
+ end_state: "Character inside room",
19
+ continuity_with_previous: null,
20
+ shot_intent: {
21
+ needs_multishot_reference: false,
22
+ is_loopable: false,
23
+ needs_music_suppression: true,
24
+ is_match_cut: true,
25
+ },
26
+ visual_keyframe_prompt: "Wide shot of character walking through a doorway",
27
+ }
28
+
29
+ // ─── A1: ShotSpec.accepted_match_cut_break ───────────────────────────────────
30
+
31
+ describe("ShotSpec.accepted_match_cut_break (Phase 1D.1 Method 7)", () => {
32
+ it("accepts accepted_match_cut_break=true", () => {
33
+ const result = ShotSpecSchema.safeParse({
34
+ ...baseShotSpec,
35
+ accepted_match_cut_break: true,
36
+ })
37
+ expect(result.success).toBe(true)
38
+ if (result.success) {
39
+ expect(result.data.accepted_match_cut_break).toBe(true)
40
+ }
41
+ })
42
+
43
+ it("defaults to undefined when omitted", () => {
44
+ const result = ShotSpecSchema.safeParse(baseShotSpec)
45
+ expect(result.success).toBe(true)
46
+ if (result.success) {
47
+ expect(result.data.accepted_match_cut_break).toBeUndefined()
48
+ }
49
+ })
50
+ })
51
+
52
+ // ─── A2: MatchCutVerdictSchema ────────────────────────────────────────────────
53
+
54
+ describe("MatchCutVerdictSchema (Phase 1D.1 Method 7)", () => {
55
+ it("parses a valid match-cut verdict", () => {
56
+ const verdict = {
57
+ shot_pair: ["shot_01", "shot_02"] as [string, string],
58
+ match_strength: "strong" as const,
59
+ suggested_adjustments: "Align the door frame position in both shots.",
60
+ checked_at: new Date().toISOString(),
61
+ }
62
+ const result = MatchCutVerdictSchema.safeParse(verdict)
63
+ expect(result.success).toBe(true)
64
+ if (result.success) {
65
+ expect(result.data.shot_pair).toEqual(["shot_01", "shot_02"])
66
+ expect(result.data.match_strength).toBe("strong")
67
+ }
68
+ })
69
+
70
+ it("rejects an invalid match_strength value", () => {
71
+ const verdict = {
72
+ shot_pair: ["shot_01", "shot_02"],
73
+ match_strength: "excellent", // not in the enum
74
+ suggested_adjustments: "Some suggestion",
75
+ checked_at: new Date().toISOString(),
76
+ }
77
+ const result = MatchCutVerdictSchema.safeParse(verdict)
78
+ expect(result.success).toBe(false)
79
+ })
80
+ })