@nodaro/shared 1.4.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 (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,99 @@
1
+ /**
2
+ * Non-linear progress calculation using quadratic ease-out curve.
3
+ * Returns 0–99 during execution, never reaching 100 (that's set on completion).
4
+ *
5
+ * Formula: progress = 99 * (1 - (1 - t)^exponent)
6
+ * where t = clamp(elapsedMs / estimatedMs, 0, 1)
7
+ */
8
+ export function calculateProgress(
9
+ elapsedMs: number,
10
+ estimatedMs: number,
11
+ exponent: number = 2,
12
+ ): number {
13
+ if (estimatedMs <= 0) return 0
14
+ const t = Math.min(Math.max(elapsedMs / estimatedMs, 0), 1)
15
+ return Math.round(99 * (1 - Math.pow(1 - t, exponent)))
16
+ }
17
+
18
+ /** Category-based fallback durations (ms) when no historical data exists. */
19
+ export const CATEGORY_DURATION_DEFAULTS: Record<string, number> = {
20
+ image: 30_000,
21
+ video: 120_000,
22
+ "audio-tts": 15_000,
23
+ music: 60_000,
24
+ llm: 8_000,
25
+ upscale: 30_000,
26
+ inline: 500,
27
+ }
28
+
29
+ /**
30
+ * Compute weighted progress segments for a multi-node flow.
31
+ * Each node gets a proportional slice of 0–99% based on its estimated duration.
32
+ *
33
+ * Returns array of { nodeId, startPct, endPct, estimatedMs } in execution order.
34
+ */
35
+ export interface ProgressSegment {
36
+ nodeId: string
37
+ startPct: number
38
+ endPct: number
39
+ estimatedMs: number
40
+ }
41
+
42
+ export function buildProgressSegments(
43
+ nodeEstimates: { nodeId: string; estimatedMs: number }[],
44
+ ): ProgressSegment[] {
45
+ const totalMs = nodeEstimates.reduce((sum, n) => sum + n.estimatedMs, 0)
46
+ if (totalMs <= 0) return []
47
+
48
+ const segments: ProgressSegment[] = []
49
+ let cursor = 0
50
+
51
+ for (let i = 0; i < nodeEstimates.length; i++) {
52
+ const { nodeId, estimatedMs } = nodeEstimates[i]
53
+ const sliceWidth = (estimatedMs / totalMs) * 99
54
+ const isLast = i === nodeEstimates.length - 1
55
+ segments.push({
56
+ nodeId,
57
+ startPct: cursor,
58
+ // Last segment always stretches to exactly 99
59
+ endPct: isLast ? 99 : cursor + sliceWidth,
60
+ estimatedMs,
61
+ })
62
+ cursor += sliceWidth
63
+ }
64
+
65
+ return segments
66
+ }
67
+
68
+ /**
69
+ * Calculate combined progress for a multi-node flow given current node states.
70
+ */
71
+ export function calculateCombinedProgress(
72
+ segments: ProgressSegment[],
73
+ nodeStatuses: Record<string, {
74
+ status: "pending" | "running" | "completed" | "failed" | "skipped"
75
+ startedAt?: string
76
+ }>,
77
+ now: number = Date.now(),
78
+ ): number {
79
+ let progress = 0
80
+
81
+ for (const seg of segments) {
82
+ const state = nodeStatuses[seg.nodeId]
83
+ if (!state) continue
84
+
85
+ const sliceWidth = seg.endPct - seg.startPct
86
+
87
+ if (state.status === "completed" || state.status === "skipped") {
88
+ progress += sliceWidth
89
+ } else if (state.status === "running" && state.startedAt) {
90
+ const elapsed = now - new Date(state.startedAt).getTime()
91
+ const nodeProgress = calculateProgress(elapsed, seg.estimatedMs)
92
+ // Map node's 0–99% to this segment's slice
93
+ progress += (nodeProgress / 99) * sliceWidth
94
+ }
95
+ // pending/failed contribute 0
96
+ }
97
+
98
+ return Math.round(Math.min(progress, 99))
99
+ }
@@ -0,0 +1,69 @@
1
+ import type { ShotSpec } from "./scene-node-types.js"
2
+
3
+ /**
4
+ * Provider-specific directive defaults. Architecture spec §5.14.
5
+ *
6
+ * Each video model can have side-effects the user usually wants (Seedance baking in
7
+ * ambient music, VEO supporting loop seeds, etc.). The engine applies these defaults
8
+ * at provider-call time, allowing them to be overridden by the Scene Director's
9
+ * `shot_intent` field (provider-neutral) which we map below.
10
+ *
11
+ * Keys are canonical provider identifiers (matching VIDEO_MODEL_CAPS in
12
+ * model-constants.ts). Non-canonical keys would silently break downstream
13
+ * route validation + payload building.
14
+ */
15
+ export const PROVIDER_DIRECTIVE_DEFAULTS = {
16
+ seedance: { multishot: true, disable_internal_music: true, allow_sfx: true },
17
+ "seedance-2": { multishot: true, disable_internal_music: true, allow_sfx: true },
18
+ veo3: { reference_count: "auto" as const },
19
+ "veo3.1": { reference_count: "auto" as const },
20
+ kling: { reference_count: "auto" as const },
21
+ "kling-3-omni": { reference_count: "auto" as const },
22
+ "kling-turbo": { reference_count: "auto" as const },
23
+ hailuo: {} as Record<string, never>,
24
+ "hailuo-2.3-pro": {} as Record<string, never>,
25
+ "hailuo-standard": {} as Record<string, never>,
26
+ minimax: {} as Record<string, never>,
27
+ "bytedance-lite": {} as Record<string, never>,
28
+ } as const
29
+
30
+ /**
31
+ * Maps a Scene Director's provider-neutral shot_intent to provider-specific directives.
32
+ * Architecture spec §5.14 mapping table.
33
+ *
34
+ * Unknown providers fall through to an empty object — the directive degrades to a text
35
+ * hint in the motion_prompt (Scene Director includes it there as a fallback when
36
+ * `needs_music_suppression: true`).
37
+ */
38
+ export function mapShotIntentToProviderDirectives(
39
+ provider: string,
40
+ shotIntent: ShotSpec["shot_intent"],
41
+ ): Record<string, unknown> {
42
+ const directives: Record<string, unknown> = {
43
+ ...(PROVIDER_DIRECTIVE_DEFAULTS[provider as keyof typeof PROVIDER_DIRECTIVE_DEFAULTS] ?? {}),
44
+ }
45
+
46
+ if (shotIntent.needs_multishot_reference) {
47
+ if (provider.startsWith("seedance")) {
48
+ directives.multishot = true
49
+ }
50
+ // VEO + Kling: accepts reference_images array — engine attaches the refs separately.
51
+ // Hailuo: only 1 ref — silently ignored at engine layer.
52
+ }
53
+
54
+ if (shotIntent.is_loopable) {
55
+ if (provider.startsWith("veo")) {
56
+ directives.loop_seed = "stable"
57
+ }
58
+ // Other providers: no native loop_seed → degrade to motion_prompt frame-matching hint.
59
+ }
60
+
61
+ if (shotIntent.needs_music_suppression) {
62
+ if (provider.startsWith("seedance")) {
63
+ directives.disable_internal_music = true
64
+ }
65
+ // VEO / Kling / Hailuo don't bake in music; no directive needed.
66
+ }
67
+
68
+ return directives
69
+ }
@@ -0,0 +1,121 @@
1
+ import { z, type ZodType, type ZodTypeDef } from "zod"
2
+ // Reuse the canonical `OutputType` from presentation-utils so a single union
3
+ // is the source of truth across the package. Re-exporting it here would
4
+ // collide with the explicit named re-export in `index.ts`.
5
+ import type { OutputType } from "./presentation-utils.js"
6
+
7
+ // `ZodType<Output, Def, Input>` with `Input = any` lets us accept schemas
8
+ // like `z.object({ x: z.string().default("") })` where the parsed output is
9
+ // `{ x: string }` but the input is `{ x?: string }`. Pinning Input to TConfig
10
+ // would force input === output and reject `.default()` schemas.
11
+ export type ReduceStrategy<TConfig = unknown> = {
12
+ readonly id: string
13
+ readonly label: string
14
+ readonly description: string
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ readonly configSchema: ZodType<TConfig, ZodTypeDef, any>
17
+ readonly defaultConfig: TConfig
18
+ readonly outputType: OutputType
19
+ readonly creditCostKey: string
20
+ }
21
+
22
+ const PICK_BEST_LLM_STRATEGY = {
23
+ id: "pick-best-llm",
24
+ label: "Pick best (LLM judge)",
25
+ description: "Sonnet picks the best item against your criteria.",
26
+ configSchema: z.object({
27
+ // Default to the sensible "best quality" criteria when omitted (matches
28
+ // defaultConfig) so a reduce({strategyId:"pick-best-llm"}) call with no
29
+ // criteria degrades gracefully instead of erroring. An explicit "" still
30
+ // rejects via min(1).
31
+ criteria: z.string().min(1, "criteria cannot be empty").default("Pick the highest-quality result."),
32
+ inputKind: z.enum(["text", "image-url"]).default("text"),
33
+ }),
34
+ defaultConfig: { criteria: "Pick the highest-quality result.", inputKind: "text" as const },
35
+ outputType: "text" as OutputType,
36
+ creditCostKey: "reduce:pick-best-llm",
37
+ } as const satisfies ReduceStrategy<{ criteria: string; inputKind: "text" | "image-url" }>
38
+
39
+ const CONCAT_STRATEGY = {
40
+ id: "concat",
41
+ label: "Concatenate",
42
+ description: "Join all survivors with a separator.",
43
+ configSchema: z.object({ separator: z.string().default("\n\n") }),
44
+ defaultConfig: { separator: "\n\n" },
45
+ outputType: "text" as OutputType,
46
+ creditCostKey: "reduce:concat",
47
+ } as const satisfies ReduceStrategy<{ separator: string }>
48
+
49
+ const FIRST_NON_EMPTY_STRATEGY = {
50
+ id: "first-non-empty",
51
+ label: "First non-empty",
52
+ description: "Return the first survivor (empty strings filtered).",
53
+ configSchema: z.object({}),
54
+ defaultConfig: {},
55
+ outputType: "text" as OutputType,
56
+ creditCostKey: "reduce:first-non-empty",
57
+ } as const satisfies ReduceStrategy<Record<string, never>>
58
+
59
+ const COUNT_STRATEGY = {
60
+ id: "count",
61
+ label: "Count",
62
+ description: "Return how many survivors came through.",
63
+ configSchema: z.object({}),
64
+ defaultConfig: {},
65
+ outputType: "data" as OutputType,
66
+ creditCostKey: "reduce:count",
67
+ } as const satisfies ReduceStrategy<Record<string, never>>
68
+
69
+ const VOTE_STRATEGY = {
70
+ id: "vote",
71
+ label: "Majority vote",
72
+ description: "Return the most common survivor (ties → first).",
73
+ configSchema: z.object({ caseSensitive: z.boolean().default(false) }),
74
+ defaultConfig: { caseSensitive: false },
75
+ outputType: "text" as OutputType,
76
+ creditCostKey: "reduce:vote",
77
+ } as const satisfies ReduceStrategy<{ caseSensitive: boolean }>
78
+
79
+ const MERGE_JSON_STRATEGY = {
80
+ id: "merge-json",
81
+ label: "Merge JSON",
82
+ description: "Parse each survivor as JSON and merge into one object.",
83
+ configSchema: z.object({ strategy: z.enum(["deep", "shallow"]).default("deep") }),
84
+ defaultConfig: { strategy: "deep" as const },
85
+ outputType: "data" as OutputType,
86
+ creditCostKey: "reduce:merge-json",
87
+ } as const satisfies ReduceStrategy<{ strategy: "deep" | "shallow" }>
88
+
89
+ /**
90
+ * Result-meta shape returned by every reduce strategy. Shared between the
91
+ * backend route, the SDK client, and the frontend node so all three layers
92
+ * agree on field names. `selectedIndex` + `reasoning` are set by the
93
+ * `pick-best-llm` and `vote` strategies (the former also fills `reasoning`);
94
+ * `summary` is always populated.
95
+ */
96
+ export type ReduceMeta = {
97
+ readonly selectedIndex?: number
98
+ readonly reasoning?: string
99
+ readonly summary: string
100
+ }
101
+
102
+ export const REDUCE_STRATEGIES = [
103
+ PICK_BEST_LLM_STRATEGY,
104
+ CONCAT_STRATEGY,
105
+ FIRST_NON_EMPTY_STRATEGY,
106
+ COUNT_STRATEGY,
107
+ VOTE_STRATEGY,
108
+ MERGE_JSON_STRATEGY,
109
+ ] as const
110
+
111
+ export const REDUCE_STRATEGY_IDS = REDUCE_STRATEGIES.map((s) => s.id) as readonly ReduceStrategyId[]
112
+
113
+ export type ReduceStrategyId = typeof REDUCE_STRATEGIES[number]["id"]
114
+
115
+ const BY_ID = new Map<string, ReduceStrategy>(REDUCE_STRATEGIES.map((s) => [s.id, s]))
116
+
117
+ export function getStrategy(id: ReduceStrategyId): ReduceStrategy {
118
+ const s = BY_ID.get(id)
119
+ if (!s) throw new Error(`unknown reduce strategy: ${id}`)
120
+ return s
121
+ }
@@ -0,0 +1,81 @@
1
+ /** Gen providers for the board: strict subset of IMAGE_GEN_PROVIDERS that
2
+ * renders legible in-image text + accepts reference conditioning. */
3
+ export const REFERENCE_BOARD_PROVIDERS = ["nano-banana-pro", "gpt-image-2"] as const
4
+ export type ReferenceBoardProvider = (typeof REFERENCE_BOARD_PROVIDERS)[number]
5
+
6
+ export type BoardEntityKind = "character" | "location" | "object"
7
+
8
+ export interface BoardTemplate {
9
+ /** "<entityKind>/<slug>" */
10
+ readonly id: string
11
+ readonly entityKind: BoardEntityKind
12
+ readonly label: string
13
+ readonly build: (ctx: BoardPromptContext) => string
14
+ }
15
+
16
+ /** Optional metadata the resolver supplies from a connected entity. All
17
+ * optional — the prompt degrades gracefully (omits labels) when absent. */
18
+ export interface BoardPromptContext {
19
+ readonly name?: string
20
+ readonly description?: string
21
+ }
22
+
23
+ // ── Shared clauses (the three guide tricks) ──────────────────────────────
24
+ const ANTI_RIGID =
25
+ "Editorial reference-board layout, dark near-black background, thin yellow neon accent light, " +
26
+ "faint film-grain overlay, production-grade design UI. Organized but not rigidly locked: panels may " +
27
+ "shift position from generation to generation while staying readable, balanced and premium. Works " +
28
+ "cleanly in different aspect ratios without depending on a fixed left-column structure. All on-image labels in ENGLISH."
29
+ const PALETTE = "A COLOR PALETTE panel: 6 swatches with HEX codes derived from the dominant tones."
30
+ const CAPTION = (kind: string) =>
31
+ `Bottom caption: "Use this ${kind} board as a visual reference for consistent depiction across all generations." ` +
32
+ `Bottom-right tags: STYLE · Modern · Realistic · Cinematic. Style: photorealistic, no illustration. 8K, fine grain, cinematic color grading.`
33
+
34
+ const subj = (ctx: BoardPromptContext, fallback: string) =>
35
+ ctx.description?.trim() ? ctx.description.trim() : fallback
36
+
37
+ // ── Character ────────────────────────────────────────────────────────────
38
+ const characterFullBoard = (ctx: BoardPromptContext): string =>
39
+ `Create a single high-resolution, densely packed character reference sheet titled "CHARACTER BOARD" ` +
40
+ `using the attached photo as the single source of truth for face, hair, eyes, skin tone and body proportions ` +
41
+ `of ${subj(ctx, "the attached person")}. The same person appears in every panel — same age, same features. ${ANTI_RIGID} ` +
42
+ `Include a large hero portrait + metadata block: NAME · AGE · HEIGHT · BUILD · HAIR · EYES · FEATURES · OUTFIT · CHARACTER · MOOD. ` +
43
+ `Six panels: VIEWS (front · 3/4 · side · back) · EXPRESSIONS (5 headshots) · DETAILS (face/eyes, hand, outfit macro) · ` +
44
+ `OUTFIT FLAT-LAYS (5 isolated product shots) · LIGHTING/MOOD (4 lighting setups) · ${PALETTE} ${CAPTION("character")}`
45
+
46
+ // ── Location ─────────────────────────────────────────────────────────────
47
+ const locationFullBoard = (ctx: BoardPromptContext): string =>
48
+ `Create a single high-resolution, densely packed location reference sheet titled "LOCATION BOARD" ` +
49
+ `using the attached photo as the single source of truth for architecture, atmosphere, lighting, materials and color ` +
50
+ `of ${subj(ctx, "the attached location")}. The space is identical across every panel — same place, materials, era. ${ANTI_RIGID} ` +
51
+ `Include a hero establishing shot + metadata block: NAME · TYPE · ERA · SCALE · ARCHITECTURE · MATERIALS · ATMOSPHERE · DEFAULT TIME · DEFAULT WEATHER · PURPOSE. ` +
52
+ `Six panels: VIEWS (wide · mid · tight · alt · overhead) · TIME OF DAY (dawn · noon · dusk · night) · DETAILS (material, architectural) · ` +
53
+ `SET DRESSING / PROPS (5 studies) · WEATHER/MOOD (clear · overcast · rain · fog) · ${PALETTE} ${CAPTION("location")}`
54
+
55
+ // ── Object ───────────────────────────────────────────────────────────────
56
+ const objectFullBoard = (ctx: BoardPromptContext): string =>
57
+ `Create a single high-resolution, densely packed object reference sheet titled "OBJECT BOARD" ` +
58
+ `using the attached photo as the single source of truth for shape, materials, finish and proportions ` +
59
+ `of ${subj(ctx, "the attached object")}. The object is identical across every panel. ${ANTI_RIGID} ` +
60
+ `Include a hero shot + metadata block: NAME · TYPE · SCALE · MATERIALS · FINISH · KEY FEATURES · FUNCTION · MOOD. ` +
61
+ `Six panels: VIEWS (front · 3/4 · side · back · top) · MATERIALS (close studies) · DETAILS (key part macros) · ` +
62
+ `VARIATIONS (color/finish options) · LIGHTING/MOOD (4 setups) · ${PALETTE} ${CAPTION("object")}`
63
+
64
+ const t = (entityKind: BoardEntityKind, slug: string, label: string, build: BoardTemplate["build"]): BoardTemplate =>
65
+ ({ id: `${entityKind}/${slug}`, entityKind, label, build })
66
+
67
+ export const REFERENCE_BOARD_TEMPLATES: readonly BoardTemplate[] = [
68
+ t("character", "full-board", "Full Board — hero + 6 panels + palette", characterFullBoard),
69
+ t("location", "full-board", "Full Board — hero + 6 panels + palette", locationFullBoard),
70
+ t("object", "full-board", "Full Board — hero + 6 panels + palette", objectFullBoard),
71
+ ]
72
+
73
+ export function listBoardTemplates(kind: BoardEntityKind): BoardTemplate[] {
74
+ return REFERENCE_BOARD_TEMPLATES.filter((x) => x.entityKind === kind)
75
+ }
76
+
77
+ export function buildBoardPrompt(templateId: string, ctx: BoardPromptContext): string {
78
+ const tpl = REFERENCE_BOARD_TEMPLATES.find((x) => x.id === templateId)
79
+ if (!tpl) throw new Error(`Unknown reference-board template: ${templateId}`)
80
+ return tpl.build(ctx)
81
+ }
@@ -0,0 +1,153 @@
1
+ // packages/shared/src/reference-roles.ts
2
+ import { DEFAULT_LABEL_BY_SOURCE, type ReferenceSource } from "./types.js"
3
+
4
+ /**
5
+ * Per-source ORDERED preset role ids (most-useful first) for the editor's role
6
+ * menu. The single source of truth for the curated vocabulary — every menu and
7
+ * the resolver read from here. `Custom…` (free-form) is added by the UI, not
8
+ * listed here. Each list MUST contain the source's `DEFAULT_LABEL_BY_SOURCE`
9
+ * value (guarded by reference-roles.test.ts).
10
+ */
11
+ export const REFERENCE_ROLE_PRESETS: Record<ReferenceSource, readonly string[]> = {
12
+ "wired-character": ["person", "face", "clothes", "hair", "pose", "expression", "style"],
13
+ "wired-face": ["face", "person", "expression", "style"],
14
+ "wired-location": ["background", "atmosphere", "as-is", "empty background", "layout", "lighting", "style"],
15
+ "wired-object": ["object", "shape", "material", "color", "texture", "style"],
16
+ "wired-creature": ["creature", "anatomy", "markings", "pose", "color", "style"],
17
+ "wired-image": ["object", "person", "face", "clothes", "background", "style", "pose", "texture"],
18
+ "manual": ["object", "person", "face", "clothes", "background", "style", "pose", "texture"],
19
+ }
20
+
21
+ /** Canonical default role for a source — the value the editor pre-fills and the
22
+ * resolver falls back to. Aliased to `DEFAULT_LABEL_BY_SOURCE` so the two never
23
+ * drift. */
24
+ export function defaultRoleForSource(source: ReferenceSource): string {
25
+ return DEFAULT_LABEL_BY_SOURCE[source]
26
+ }
27
+
28
+ /**
29
+ * Render the inline reference phrase. `binding` is the caller-formatted slot
30
+ * string: `"reference image A"` (image, lettered) or `"@image_3"` (video).
31
+ * Most roles are nouns and slot into the default template; the two non-noun
32
+ * specials get a hand-tuned phrasing so the prompt reads naturally.
33
+ */
34
+ export function roleToPhrase(role: string, binding: string): string {
35
+ const r = role.trim()
36
+ if (!r) return binding
37
+ switch (r) {
38
+ case "as-is":
39
+ return `${binding}, used as-is`
40
+ case "empty background":
41
+ return `the background from ${binding} (without its foreground objects)`
42
+ default:
43
+ return `the ${r} from ${binding}`
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Map a location role SLUG — as it appears in a `@location:N:<slug>` mention
49
+ * token (lowercase, dash-joined, no spaces) — back to its canonical phrase key
50
+ * so `roleToPhrase` matches the non-noun specials. Only the multi-word
51
+ * `wired-location` presets need remapping (`empty-background` → `empty
52
+ * background`); single-token roles and `as-is` (whose phrase key keeps the
53
+ * hyphen) and free-form custom slugs pass through unchanged.
54
+ *
55
+ * Data-driven from `REFERENCE_ROLE_PRESETS["wired-location"]` (the single source
56
+ * of truth for the location vocabulary): a preset whose space→dash slug equals
57
+ * `slug` maps back to the preset's phrase form, so a future multi-word preset is
58
+ * handled with no extra wiring. Mention tokens are slug-form (the grammar
59
+ * segment is `[a-z][a-z0-9-]*`), so the role is stored verbatim on the token and
60
+ * normalized HERE at the consumption points — the location mention resolver
61
+ * (before `roleToPhrase`) and the location role pill (display label). The
62
+ * character side never needs this: character role slugs are single tokens that
63
+ * already equal their phrase key.
64
+ */
65
+ export function normalizeRoleSlug(slug: string): string {
66
+ const s = slug.trim()
67
+ if (!s) return s
68
+ for (const preset of REFERENCE_ROLE_PRESETS["wired-location"]) {
69
+ if (preset.toLowerCase().replace(/\s+/g, "-") === s) return preset
70
+ }
71
+ return s
72
+ }
73
+
74
+ /**
75
+ * Role for a FIRST-SIGHT character extra-ref in HYBRID assembly. The `segment`
76
+ * is the role when it is a curated preset for `source`; otherwise the source
77
+ * default. This mirrors the segment→role preset gate the mention-hybrid paths
78
+ * use, minus their custom-role-survival relaxation (an extra has no parsed
79
+ * `usageMode == null` signal to tell a role from a variant pick).
80
+ *
81
+ * Shared by BOTH the image (`renderExtraRefsHybrid` in `prompt-builder.ts`) and
82
+ * video (`resolveVideoReferenceCore` extras first-sight in
83
+ * `video-reference-resolver.ts`) resolvers — and, as of the Reference Roles
84
+ * deferred-follow-up, they share the HELPER **and** its INPUT, so they are
85
+ * fully converged:
86
+ * - image passes the COALESCED `defaultUsageMode`
87
+ * (`usageMode` → char-node default → "identical", folded by
88
+ * `expandExtraRefsToConnectedReferences`).
89
+ * - video passes its COALESCED `effectiveMode`
90
+ * (`ex.usageMode` → char-node default (`lookupCharacterBySlug`) →
91
+ * "identical") — the identical resolution chain, already computed for the
92
+ * legacy directive path.
93
+ * Both are always defined for a real character extra, so no `variantSlug`
94
+ * fallback is needed on either side. A character whose node default is
95
+ * Face/Pose/Style now resolves an un-overridden extra to that default on BOTH
96
+ * image and video (and consistently with the video legacy path). The single
97
+ * source of truth for the coalescing is the per-ref override → char-node
98
+ * default → "identical" chain, applied at both call sites.
99
+ */
100
+ export function firstSightExtraRole(
101
+ segment: string | null | undefined,
102
+ source: ReferenceSource,
103
+ ): string {
104
+ const s = (segment ?? "").trim()
105
+ return s && REFERENCE_ROLE_PRESETS[source].includes(s) ? s : defaultRoleForSource(source)
106
+ }
107
+
108
+ /**
109
+ * The effective default role for a wired entity, given the node's explicit
110
+ * hybrid `defaultRole` and its legacy `defaultUsageMode`. Single source of truth
111
+ * for the node-default precedence, read at every hybrid resolver site (image +
112
+ * video × extras / canonical / mention):
113
+ *
114
+ * 1. `defaultRole` (the hybrid dropdown pick) — used VERBATIM when non-blank,
115
+ * so a Custom role like `earrings` survives (mirrors the pill's Custom
116
+ * relaxation) and beats any legacy `defaultUsageMode`.
117
+ * 2. else `firstSightExtraRole(defaultUsageMode, source)` — the back-compat
118
+ * mapping (`face`/`pose`/`style` pass through; every other usage mode, and
119
+ * an absent one, collapse to the source default, e.g. `"person"`).
120
+ *
121
+ * A per-mention token role (the pill / autocomplete) still wins over this at the
122
+ * mention call sites; this is only the NODE-level fallback.
123
+ */
124
+ export function resolveDefaultRole(
125
+ defaultRole: string | null | undefined,
126
+ defaultUsageMode: string | null | undefined,
127
+ source: ReferenceSource,
128
+ ): string {
129
+ const explicit = defaultRole?.trim()
130
+ if (explicit) return explicit
131
+ return firstSightExtraRole(defaultUsageMode, source)
132
+ }
133
+
134
+ /**
135
+ * Sanitize a free-form Custom role into a character-variant-slug-safe token.
136
+ * Hoisted to shared (was `config-panels/prompt-editor/character-ref-roles.ts`)
137
+ * so BOTH the mention pill and the character node's role dropdown share one
138
+ * source of truth. Grammar is `[a-z][a-z0-9-]*` (the `variantSlug` token slot):
139
+ * lower-case, dash-join whitespace, drop out-of-grammar chars, force a leading
140
+ * letter, cap at 32, collapse dash runs, drop a trailing dash — keeping the
141
+ * emitted role re-parseable and matching `characterMentionSlug`'s slugification.
142
+ */
143
+ export function sanitizeRole(raw: string): string {
144
+ return raw
145
+ .trim()
146
+ .toLowerCase()
147
+ .replace(/\s+/g, "-")
148
+ .replace(/[^a-z0-9-]/g, "")
149
+ .replace(/^[^a-z]+/, "") // grammar requires a leading [a-z]
150
+ .slice(0, 32)
151
+ .replace(/-+/g, "-") // collapse dash runs (matches characterMentionSlug)
152
+ .replace(/-$/, "") // drop a trailing dash (incl. one left by the 32-cap)
153
+ }
@@ -0,0 +1,20 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { BOARD_TO_COLUMN, BOARD_VARIANTS } from "../catalog.js"
3
+ import type { EntityKind } from "../types.js"
4
+
5
+ const ENTITIES: EntityKind[] = ["character", "object", "location"]
6
+ describe("BOARD_TO_COLUMN", () => {
7
+ it("maps character boards to DB columns", () => {
8
+ expect(BOARD_TO_COLUMN.character.headAngles).toBe("angles")
9
+ expect(BOARD_TO_COLUMN.character.bodyAngles).toBe("body_angles")
10
+ expect(BOARD_TO_COLUMN.character.detail).toBe("detail_closeups")
11
+ expect(BOARD_TO_COLUMN.character.wardrobe).toBe("outfit_variations")
12
+ })
13
+ it("maps location camelCase boards to snake_case columns", () => {
14
+ expect(BOARD_TO_COLUMN.location.timeOfDay).toBe("time_of_day")
15
+ expect(BOARD_TO_COLUMN.location.detail).toBe("detail_closeups")
16
+ })
17
+ it("every board in BOARD_VARIANTS has a column (no orphan)", () => {
18
+ for (const e of ENTITIES) for (const b of Object.keys(BOARD_VARIANTS[e])) expect(BOARD_TO_COLUMN[e][b], `${e}/${b}`).toBeDefined()
19
+ })
20
+ })
@@ -0,0 +1,50 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import {
3
+ ENTITY_SECTIONS, BOARD_VARIANTS, SECTION_BOARD, DEFAULT_SECTIONS, DETAIL_VARIANTS, WARDROBE_VARIANTS,
4
+ } from "../catalog.js"
5
+ import { SECTION_KINDS, SHEET_TYPES, type EntityKind } from "../types.js"
6
+ import { CHARACTER_ASSET_VARIANTS, OBJECT_ASSET_VARIANTS, LOCATION_ASSET_VARIANTS } from "../../entity-asset-types.js"
7
+
8
+ const ENTITIES: EntityKind[] = ["character", "object", "location"]
9
+
10
+ describe("catalog invariants", () => {
11
+ it("every entity's available sections are valid SectionKinds", () => {
12
+ for (const e of ENTITIES) for (const s of ENTITY_SECTIONS[e]) expect(SECTION_KINDS).toContain(s)
13
+ })
14
+ it("character board variants come from the canonical CHARACTER_ASSET_VARIANTS", () => {
15
+ expect(BOARD_VARIANTS.character.headAngles).toEqual(CHARACTER_ASSET_VARIANTS.headAngles)
16
+ expect(BOARD_VARIANTS.character.bodyAngles).toEqual(CHARACTER_ASSET_VARIANTS.bodyAngles)
17
+ expect(BOARD_VARIANTS.character.expressions).toEqual(CHARACTER_ASSET_VARIANTS.expressions)
18
+ expect(BOARD_VARIANTS.character.poses).toEqual(CHARACTER_ASSET_VARIANTS.poses)
19
+ })
20
+ it("object & location board variants come from the hoisted lists", () => {
21
+ expect(BOARD_VARIANTS.object.angles).toEqual(OBJECT_ASSET_VARIANTS.angles)
22
+ expect(BOARD_VARIANTS.object.materials).toEqual(OBJECT_ASSET_VARIANTS.materials)
23
+ expect(BOARD_VARIANTS.location.timeOfDay).toEqual(LOCATION_ASSET_VARIANTS.timeOfDay)
24
+ expect(BOARD_VARIANTS.location.angles).toEqual(LOCATION_ASSET_VARIANTS.angles)
25
+ })
26
+ it("headAngles includes back-of-head, appended after the default-4 slice", () => {
27
+ const head = CHARACTER_ASSET_VARIANTS.headAngles
28
+ expect(head).toContain("back")
29
+ expect(head.indexOf("back")).toBeGreaterThanOrEqual(4) // keeps slice(0,4) stable
30
+ expect(head.slice(0, 4)).toEqual(["front", "3/4 left", "left profile", "right profile"])
31
+ })
32
+ it("new buckets (detail/wardrobe) have starter presets", () => {
33
+ expect(DETAIL_VARIANTS.character.length).toBeGreaterThan(0)
34
+ expect(WARDROBE_VARIANTS.length).toBeGreaterThan(0)
35
+ })
36
+ it("DEFAULT_SECTIONS exists for every (entity, type) and only references available sections", () => {
37
+ for (const e of ENTITIES) for (const t of SHEET_TYPES) {
38
+ const secs = DEFAULT_SECTIONS[e][t]
39
+ expect(Array.isArray(secs)).toBe(true)
40
+ for (const s of secs) expect(ENTITY_SECTIONS[e]).toContain(s.kind)
41
+ }
42
+ })
43
+ it("every board-typed section a default uses resolves to a real board with variants", () => {
44
+ for (const e of ENTITIES) for (const t of SHEET_TYPES) for (const s of DEFAULT_SECTIONS[e][t]) {
45
+ const board = s.board ?? SECTION_BOARD[s.kind]
46
+ if (board === null) continue // structural section (header/palette/notes/scale)
47
+ expect(BOARD_VARIANTS[e][board!], `${e}/${t}/${s.kind} -> ${board}`).toBeDefined()
48
+ }
49
+ })
50
+ })
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { estimateSheetCost } from "../cost.js"
3
+ import { SHEET_PRESETS } from "../presets.js"
4
+ import type { SheetFlavour, SheetSection } from "../types.js"
5
+
6
+ const fl: SheetFlavour = { outputFormat: "still", withText: true, showLabels: true, aspect: "landscape", background: "grey" }
7
+ const main = SHEET_PRESETS.find((p) => p.id === "studio-main")!
8
+
9
+ describe("estimateSheetCost", () => {
10
+ it("all panels missing → prepareCost = missing × perPanel, total += assembly", () => {
11
+ const e = estimateSheetCost("character", main.baseSections, fl, {}, "Kaia", 1, 4)
12
+ expect(e.missing).toHaveLength(7)
13
+ expect(e.present).toBe(0)
14
+ expect(e.prepareCost).toBe(7)
15
+ expect(e.total).toBe(11)
16
+ expect(e.overflow).toBe(false)
17
+ })
18
+ it("present panels are not re-charged", () => {
19
+ const buckets = { angles: [{ name: "front", url: "u/f" }], body_angles: [{ name: "front", url: "u/bf" }] }
20
+ const e = estimateSheetCost("character", main.baseSections, fl, buckets, "Kaia", 1, 4)
21
+ expect(e.present).toBe(2)
22
+ expect(e.prepareCost).toBe(5)
23
+ expect(e.total).toBe(9)
24
+ })
25
+ it("overflow (> MAX_PANELS) returns overflow:true instead of throwing", () => {
26
+ // 25 explicit entries > MAX_PANELS_PER_SHEET (24) → planSheetPanels throws;
27
+ // estimateSheetCost must catch it. (panelCount would clamp to the board
28
+ // length and never overflow, so use entries to force it.)
29
+ const huge: SheetSection[] = [{
30
+ kind: "head-turnaround",
31
+ entries: Array.from({ length: 25 }, (_, i) => ({ kind: "preset" as const, variant: `v${i}` })),
32
+ }]
33
+ const e = estimateSheetCost("character", huge, fl, {}, "Kaia", 1, 4)
34
+ expect(e.overflow).toBe(true)
35
+ expect(e.missing).toHaveLength(0)
36
+ expect(e.total).toBe(4) // assembly only
37
+ })
38
+ })
@@ -0,0 +1,28 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { BOARD_VARIANTS, DEFAULT_SECTIONS, SECTION_BOARD } from "../catalog.js"
3
+ import { planSheetPanels } from "../panel-plan.js"
4
+ import { SHEET_TYPES, SECTION_KINDS, type EntityKind, type SheetFlavour } from "../types.js"
5
+
6
+ const ENTITIES: EntityKind[] = ["character", "object", "location"]
7
+ const fl: SheetFlavour = { outputFormat: "still", withText: true, showLabels: true, aspect: "landscape", background: "grey" }
8
+
9
+ describe("no-drift: planned preset panels are real board variants", () => {
10
+ it("every default full-reference plans without throwing and only uses known variants", () => {
11
+ for (const e of ENTITIES) for (const t of SHEET_TYPES) {
12
+ const panels = planSheetPanels(e, DEFAULT_SECTIONS[e][t], fl)
13
+ for (const p of panels) {
14
+ if (p.custom) continue
15
+ expect(BOARD_VARIANTS[e][p.board], `${e}/${t} ${p.board}`).toContain(p.variant)
16
+ }
17
+ }
18
+ })
19
+ it("SECTION_BOARD has an entry for every SectionKind (no kind missing)", () => {
20
+ // Guards `resolveBoard`: adding a SectionKind without a SECTION_BOARD entry
21
+ // would make `SECTION_BOARD[kind]` undefined and silently mis-resolve. Iterate
22
+ // the full SECTION_KINDS list — not just the ones used in defaults — so a new
23
+ // kind can't slip in unmapped.
24
+ for (const k of SECTION_KINDS) {
25
+ expect(SECTION_BOARD, k).toHaveProperty(k)
26
+ }
27
+ })
28
+ })