@gentbajko/slopify 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. package/dist/adapters/account-limits.js +1 -1
  2. package/dist/adapters/alignment/audio.js +1 -1
  3. package/dist/adapters/alignment/cache.js +1 -1
  4. package/dist/adapters/alignment/multilingual.js +2 -1
  5. package/dist/adapters/alignment/runner.js +1 -1
  6. package/dist/adapters/alignment/worker.js +1 -1
  7. package/dist/adapters/explain.js +10 -9
  8. package/dist/adapters/host-cli/index.js +2 -1
  9. package/dist/adapters/host-cli/transport.js +2 -1
  10. package/dist/adapters/image/bytes.js +2 -1
  11. package/dist/adapters/image/codex.js +6 -5
  12. package/dist/adapters/image/fal.js +4 -3
  13. package/dist/adapters/image/google.js +3 -2
  14. package/dist/adapters/image/models.js +2 -2
  15. package/dist/adapters/image/openai.js +2 -1
  16. package/dist/adapters/image/replicate.js +3 -2
  17. package/dist/adapters/image/video.js +2 -1
  18. package/dist/adapters/key-probes.js +1 -1
  19. package/dist/adapters/llm/antigravity-models.js +1 -1
  20. package/dist/adapters/llm/antigravity.js +7 -6
  21. package/dist/adapters/llm/assistant-cli.js +1 -1
  22. package/dist/adapters/llm/claude-code-models.js +1 -1
  23. package/dist/adapters/llm/claude-code.js +3 -2
  24. package/dist/adapters/llm/codex-models.js +1 -1
  25. package/dist/adapters/llm/document-workspace.js +3 -2
  26. package/dist/adapters/llm/image-workspace.js +3 -2
  27. package/dist/adapters/llm/openrouter.js +2 -1
  28. package/dist/adapters/llm/run-cli.js +3 -2
  29. package/dist/adapters/tts/gemini.js +2 -1
  30. package/dist/adapters/tts/inworld-async.js +2 -1
  31. package/dist/adapters/tts/inworld.js +3 -2
  32. package/dist/adapters/tts/system.js +2 -1
  33. package/dist/catalog/registry.js +20 -13
  34. package/dist/catalog/schema.js +2 -1
  35. package/dist/catalog/validate.js +5 -2
  36. package/dist/edge/http/actions.js +7 -7
  37. package/dist/edge/http/app.js +5 -3
  38. package/dist/edge/http/assistant.js +3 -1
  39. package/dist/edge/http/backups.js +1 -1
  40. package/dist/edge/http/channel-memory.js +2 -2
  41. package/dist/edge/http/channels.js +1 -1
  42. package/dist/edge/http/checkpoints.js +7 -5
  43. package/dist/edge/http/design-templates.js +3 -3
  44. package/dist/edge/http/designs.js +1 -1
  45. package/dist/edge/http/document-themes.js +3 -3
  46. package/dist/edge/http/draft-problem.js +1 -1
  47. package/dist/edge/http/extension-auth.js +1 -1
  48. package/dist/edge/http/home.js +9 -1
  49. package/dist/edge/http/model-checks.js +4 -4
  50. package/dist/edge/http/onboarding.js +29 -12
  51. package/dist/edge/http/outputs.js +3 -2
  52. package/dist/edge/http/patch-notes.js +2 -2
  53. package/dist/edge/http/planning.js +1 -1
  54. package/dist/edge/http/project-create.js +1 -1
  55. package/dist/edge/http/project-templates.js +2 -2
  56. package/dist/edge/http/projects.js +22 -4
  57. package/dist/edge/http/prompts.js +2 -2
  58. package/dist/edge/http/pronunciation-check.js +133 -0
  59. package/dist/edge/http/pronunciations.js +26 -0
  60. package/dist/edge/http/providers.js +9 -10
  61. package/dist/edge/http/refusal.js +1 -1
  62. package/dist/edge/http/remote-access.js +4 -4
  63. package/dist/edge/http/remote-guard.js +2 -2
  64. package/dist/edge/http/reviews.js +1 -1
  65. package/dist/edge/http/revision-files.js +1 -1
  66. package/dist/edge/http/revisions.js +4 -4
  67. package/dist/edge/http/schedules.js +13 -11
  68. package/dist/edge/http/settings.js +6 -2
  69. package/dist/edge/http/social.js +5 -4
  70. package/dist/edge/http/staging.js +1 -1
  71. package/dist/edge/http/storage.js +12 -4
  72. package/dist/edge/http/studio.js +71 -19
  73. package/dist/edge/http/style-preview.js +2 -2
  74. package/dist/edge/http/template-studio.js +12 -9
  75. package/dist/edge/http/trash.js +1 -1
  76. package/dist/edge/http/youtube-edits.js +1 -1
  77. package/dist/edge/mcp/excluded-routes.js +4 -3
  78. package/dist/edge/mcp/instructions.js +3 -3
  79. package/dist/edge/mcp/library-tools.js +8 -5
  80. package/dist/edge/mcp/remake-tools.js +7 -4
  81. package/dist/edge/mcp/server.js +1 -1
  82. package/dist/edge/mcp/tools-channels.js +9 -9
  83. package/dist/edge/mcp/tools-designs.js +9 -9
  84. package/dist/edge/mcp/tools-library.js +31 -7
  85. package/dist/edge/mcp/tools-planning.js +18 -18
  86. package/dist/edge/mcp/tools-projects.js +10 -10
  87. package/dist/edge/mcp/tools-publishing.js +9 -9
  88. package/dist/edge/mcp/tools-runs.js +9 -9
  89. package/dist/edge/mcp/tools-schedules.js +6 -6
  90. package/dist/edge/mcp/tools-setup.js +20 -14
  91. package/dist/edge/mcp/tools-social.js +5 -4
  92. package/dist/edge/mcp/tools-storage.js +3 -3
  93. package/dist/edge/mcp/tools-template-studio.js +18 -18
  94. package/dist/edge/remote-access/model.js +1 -1
  95. package/dist/extension/slopify-studio-chrome.zip +0 -0
  96. package/dist/extension/slopify-studio-firefox.zip +0 -0
  97. package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
  98. package/dist/kernel/pipeline.js +12 -0
  99. package/dist/kernel/ports/settings-places.js +31 -0
  100. package/dist/kernel/ports/system-speech.js +31 -3
  101. package/dist/kernel/runner/attempt.js +11 -3
  102. package/dist/kernel/runner/providers.js +3 -2
  103. package/dist/kernel/runner/standalone.js +2 -2
  104. package/dist/main.js +5 -3
  105. package/dist/model-catalog.js +20 -0
  106. package/dist/patch-notes/4.0.0.md +13 -13
  107. package/dist/patch-notes/4.1.0.md +42 -0
  108. package/dist/patch-notes/4.2.0.md +28 -0
  109. package/dist/patch-notes/index.json +12 -0
  110. package/dist/slices/admission/listing-facts.js +95 -0
  111. package/dist/slices/admission/repo.js +9 -6
  112. package/dist/slices/admission/rules.js +7 -7
  113. package/dist/slices/admission/schema.js +2 -0
  114. package/dist/slices/admission/start.js +7 -0
  115. package/dist/slices/admission/state-words.js +34 -5
  116. package/dist/slices/assistant/cost.js +1 -1
  117. package/dist/slices/assistant/gate.js +1 -0
  118. package/dist/slices/assistant/model.js +10 -3
  119. package/dist/slices/assistant/turn.js +5 -3
  120. package/dist/slices/autopilot/warnings.js +1 -1
  121. package/dist/slices/backups/service.js +5 -5
  122. package/dist/slices/channels/cast-images.js +1 -1
  123. package/dist/slices/channels/schema.js +1 -1
  124. package/dist/slices/channels/videos.js +1 -1
  125. package/dist/slices/control/providers.js +2 -2
  126. package/dist/slices/designs/budget.js +1 -1
  127. package/dist/slices/designs/calls.js +2 -2
  128. package/dist/slices/designs/check-context.js +4 -4
  129. package/dist/slices/designs/fields.js +4 -4
  130. package/dist/slices/designs/gate.js +7 -5
  131. package/dist/slices/designs/render-input.js +1 -1
  132. package/dist/slices/designs/requests.js +1 -1
  133. package/dist/slices/designs/runner.js +1 -1
  134. package/dist/slices/designs/sample.js +2 -1
  135. package/dist/slices/designs/service.js +1 -1
  136. package/dist/slices/designs/template-checks.js +2 -2
  137. package/dist/slices/designs/template-make.js +3 -3
  138. package/dist/slices/designs/templates.js +3 -3
  139. package/dist/slices/document/fonts.js +1 -1
  140. package/dist/slices/document/model.js +7 -4
  141. package/dist/slices/document/render.js +2 -1
  142. package/dist/slices/document/theme-schema.js +1 -1
  143. package/dist/slices/document/theme.js +49 -3
  144. package/dist/slices/episodes/summarize.js +2 -2
  145. package/dist/slices/estimate/index.js +28 -22
  146. package/dist/slices/estimate/requests.js +4 -4
  147. package/dist/slices/fixes/rules.js +3 -1
  148. package/dist/slices/fonts/catalog.js +2 -1
  149. package/dist/slices/fonts/coverage.js +2 -1
  150. package/dist/slices/images/appearance.js +2 -1
  151. package/dist/slices/images/scenes.js +2 -1
  152. package/dist/slices/keywords/digest.js +2 -1
  153. package/dist/slices/keywords/rules.js +2 -2
  154. package/dist/slices/keywords/specs.js +5 -5
  155. package/dist/slices/keywords/values.js +1 -1
  156. package/dist/slices/library/transfer.js +3 -3
  157. package/dist/slices/loudness/loudnorm.js +2 -1
  158. package/dist/slices/loudness/model.js +3 -2
  159. package/dist/slices/loudness/sentence-level.js +2 -1
  160. package/dist/slices/model-checks/rules.js +13 -0
  161. package/dist/slices/model-upkeep/model.js +1 -1
  162. package/dist/slices/model-upkeep/switch.js +3 -3
  163. package/dist/slices/model-upkeep/usage.js +13 -2
  164. package/dist/slices/narration/breaths.js +2 -1
  165. package/dist/slices/narration/clean-pieces.js +2 -1
  166. package/dist/slices/narration/pauses-model.js +3 -2
  167. package/dist/slices/narration/pronunciation-check.js +226 -0
  168. package/dist/slices/narration/pronunciation.js +7 -2
  169. package/dist/slices/narration/pronunciations-library.js +94 -0
  170. package/dist/slices/notifications/subject.js +2 -2
  171. package/dist/slices/onboarding/copy.js +19 -2
  172. package/dist/slices/onboarding/first-run.js +13 -38
  173. package/dist/slices/onboarding/install.js +2 -1
  174. package/dist/slices/onboarding/model.js +26 -21
  175. package/dist/slices/onboarding/preferred-model.js +14 -0
  176. package/dist/slices/onboarding/quick-short.js +103 -64
  177. package/dist/slices/onboarding/sample.js +4 -1
  178. package/dist/slices/onboarding/state.js +7 -0
  179. package/dist/slices/onboarding/template.js +6 -5
  180. package/dist/slices/outputs/add.js +3 -2
  181. package/dist/slices/outputs/service.js +2 -2
  182. package/dist/slices/patch-notes/library.js +1 -1
  183. package/dist/slices/play-drafts/readiness.js +3 -3
  184. package/dist/slices/play-drafts/review-inputs.js +1 -1
  185. package/dist/slices/play-drafts/service.js +17 -7
  186. package/dist/slices/project-templates/next-chapter.js +2 -1
  187. package/dist/slices/project-templates/provider-defaults.js +22 -0
  188. package/dist/slices/project-templates/setup-sections.js +26 -0
  189. package/dist/slices/project-templates/setup.js +6 -1
  190. package/dist/slices/rebuild/preview-details.js +5 -5
  191. package/dist/slices/rebuild/recipe-preparation.js +1 -1
  192. package/dist/slices/rebuild/recipe-save.js +2 -2
  193. package/dist/slices/rebuild/recipe-text.js +16 -4
  194. package/dist/slices/rebuild/recipe-validation.js +5 -4
  195. package/dist/slices/rebuild/recipe-voices.js +3 -2
  196. package/dist/slices/rebuild/recipe-work.js +4 -4
  197. package/dist/slices/rebuild/recovery.js +6 -6
  198. package/dist/slices/rebuild/remake-outdated.js +5 -5
  199. package/dist/slices/rebuild/review-redo.js +1 -1
  200. package/dist/slices/rebuild/runtime-animate.js +2 -2
  201. package/dist/slices/rebuild/runtime-article.js +4 -3
  202. package/dist/slices/rebuild/runtime-document.js +5 -4
  203. package/dist/slices/rebuild/runtime-export-bed.js +2 -1
  204. package/dist/slices/rebuild/runtime-export-edit.js +2 -1
  205. package/dist/slices/rebuild/runtime-export-inputs.js +5 -4
  206. package/dist/slices/rebuild/runtime-export-short.js +1 -1
  207. package/dist/slices/rebuild/runtime-export.js +4 -3
  208. package/dist/slices/rebuild/runtime-image.js +4 -3
  209. package/dist/slices/rebuild/runtime-local.js +3 -2
  210. package/dist/slices/rebuild/runtime-narration-text.js +3 -2
  211. package/dist/slices/rebuild/runtime-provider.js +7 -6
  212. package/dist/slices/rebuild/runtime-review.js +6 -5
  213. package/dist/slices/rebuild/runtime-shorts.js +6 -5
  214. package/dist/slices/rebuild/runtime-store.js +17 -2
  215. package/dist/slices/rebuild/runtime-subtitles.js +13 -12
  216. package/dist/slices/rebuild/runtime-voices.js +2 -1
  217. package/dist/slices/rebuild/runtime-youtube.js +3 -2
  218. package/dist/slices/rebuild/service-readiness.js +11 -3
  219. package/dist/slices/research/synthesis.js +3 -2
  220. package/dist/slices/reviews/external-model.js +1 -1
  221. package/dist/slices/reviews/holds.js +5 -4
  222. package/dist/slices/reviews/model.js +8 -5
  223. package/dist/slices/reviews/rules.js +3 -3
  224. package/dist/slices/reviews/verdict.js +2 -1
  225. package/dist/slices/revisions/image-plan.js +1 -1
  226. package/dist/slices/revisions/mutation-assets.js +2 -1
  227. package/dist/slices/revisions/mutations.js +3 -2
  228. package/dist/slices/schedules/agenda.js +7 -5
  229. package/dist/slices/schedules/design-check.js +4 -4
  230. package/dist/slices/schedules/design-run.js +3 -3
  231. package/dist/slices/schedules/repo.js +6 -5
  232. package/dist/slices/schedules/schema.js +3 -0
  233. package/dist/slices/schedules/topics.js +5 -5
  234. package/dist/slices/settings/first-run.js +6 -32
  235. package/dist/slices/settings/health.js +30 -15
  236. package/dist/slices/settings/key-test.js +1 -1
  237. package/dist/slices/settings/model.js +4 -2
  238. package/dist/slices/settings/playback.js +2 -2
  239. package/dist/slices/shorts/keep.js +2 -1
  240. package/dist/slices/shorts/pick.js +4 -3
  241. package/dist/slices/shorts/prompts.js +2 -1
  242. package/dist/slices/shorts/render.js +3 -2
  243. package/dist/slices/social/media.js +5 -4
  244. package/dist/slices/social/pack.js +1 -1
  245. package/dist/slices/social/post-prompt.js +2 -1
  246. package/dist/slices/social/reshape.js +2 -2
  247. package/dist/slices/social/serve.js +1 -1
  248. package/dist/slices/social/service.js +3 -2
  249. package/dist/slices/social/state.js +1 -1
  250. package/dist/slices/storage/backup-import.js +2 -2
  251. package/dist/slices/storage/portable.js +1 -1
  252. package/dist/slices/storage/trim.js +23 -13
  253. package/dist/slices/studio/calendar.js +3 -1
  254. package/dist/slices/studio/disclosure.js +4 -4
  255. package/dist/slices/studio/model.js +1 -1
  256. package/dist/slices/studio/pack.js +9 -8
  257. package/dist/slices/studio/plan.js +1 -1
  258. package/dist/slices/studio/settings.js +6 -6
  259. package/dist/slices/studio/videos.js +23 -4
  260. package/dist/slices/style-preview/render.js +3 -3
  261. package/dist/slices/style-preview/schema.js +1 -1
  262. package/dist/slices/subtitles/captions.js +1 -1
  263. package/dist/slices/template-studio/anatomy-view.js +46 -3
  264. package/dist/slices/template-studio/bakeoff.js +3 -3
  265. package/dist/slices/template-studio/budget.js +6 -3
  266. package/dist/slices/template-studio/cards.js +78 -0
  267. package/dist/slices/template-studio/checks.js +99 -50
  268. package/dist/slices/template-studio/for-all.js +5 -5
  269. package/dist/slices/template-studio/free-video.js +1 -1
  270. package/dist/slices/template-studio/free.js +5 -5
  271. package/dist/slices/template-studio/model.js +19 -15
  272. package/dist/slices/template-studio/pack-import.js +14 -14
  273. package/dist/slices/template-studio/pack.js +9 -7
  274. package/dist/slices/template-studio/paid.js +12 -5
  275. package/dist/slices/template-studio/quote.js +18 -11
  276. package/dist/slices/template-studio/ready.js +7 -7
  277. package/dist/slices/template-studio/runner.js +1 -1
  278. package/dist/slices/template-studio/save.js +12 -12
  279. package/dist/slices/template-studio/say-it-as.js +3 -3
  280. package/dist/slices/template-studio/schedule-gate.js +1 -1
  281. package/dist/slices/template-studio/schedule-price.js +3 -1
  282. package/dist/slices/template-studio/schedule-sample.js +5 -1
  283. package/dist/slices/template-studio/schema.js +25 -0
  284. package/dist/slices/template-studio/service.js +3 -1
  285. package/dist/slices/template-studio/status.js +26 -9
  286. package/dist/slices/template-studio/topic.js +3 -3
  287. package/dist/slices/thumbnail/by-llm.js +1 -1
  288. package/dist/slices/uploads/repo.js +37 -1
  289. package/dist/slices/uploads/rules.js +3 -2
  290. package/dist/slices/video/edit-list.js +2 -2
  291. package/dist/slices/video/edit-settings.js +1 -1
  292. package/dist/slices/video/encoding.js +14 -5
  293. package/dist/slices/video/ffmpeg.js +2 -2
  294. package/dist/slices/video/plan.js +2 -1
  295. package/dist/slices/video/slideshow.js +1 -1
  296. package/dist/slices/voices/attribution.js +3 -2
  297. package/dist/slices/voices/model.js +9 -8
  298. package/dist/slices/voices/script.js +2 -1
  299. package/dist/slices/youtube/answer.js +2 -1
  300. package/dist/slices/youtube/edits-repo.js +1 -1
  301. package/dist/tutorials/AI-CLIs.md +25 -25
  302. package/dist/tutorials/Agents-MCP.md +32 -32
  303. package/dist/tutorials/Assistant.md +5 -5
  304. package/dist/tutorials/Backups.md +7 -7
  305. package/dist/tutorials/Calendar.md +17 -25
  306. package/dist/tutorials/Cast-Library.md +19 -19
  307. package/dist/tutorials/Channels.md +40 -35
  308. package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
  309. package/dist/tutorials/Create-Article.md +114 -0
  310. package/dist/tutorials/Create-Extras.md +112 -0
  311. package/dist/tutorials/Create-Images.md +165 -0
  312. package/dist/tutorials/Create-Narration.md +178 -0
  313. package/dist/tutorials/Create-Overview.md +244 -0
  314. package/dist/tutorials/Create-Video-and-Style.md +144 -0
  315. package/dist/tutorials/Docker.md +3 -3
  316. package/dist/tutorials/Document-Themes.md +32 -32
  317. package/dist/tutorials/Editing-a-Project.md +95 -94
  318. package/dist/tutorials/FAQ.md +18 -18
  319. package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
  320. package/dist/tutorials/Home-and-Projects.md +101 -76
  321. package/dist/tutorials/Home.md +33 -33
  322. package/dist/tutorials/Install.md +13 -13
  323. package/dist/tutorials/Intros-and-Outros.md +33 -34
  324. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
  325. package/dist/tutorials/Keywords-Slopify-Fills.md +21 -21
  326. package/dist/tutorials/Models.md +27 -27
  327. package/dist/tutorials/Multiple-Voices.md +41 -40
  328. package/dist/tutorials/Narration-Aliases-and-Glossary.md +101 -19
  329. package/dist/tutorials/Notifications.md +21 -17
  330. package/dist/tutorials/Other-Languages.md +15 -15
  331. package/dist/tutorials/PDF-Documents.md +38 -29
  332. package/dist/tutorials/Posting-to-Social-Sites.md +12 -12
  333. package/dist/tutorials/Project-Page.md +118 -120
  334. package/dist/tutorials/Prompts-and-Templates.md +84 -0
  335. package/dist/tutorials/Prompts.md +45 -45
  336. package/dist/tutorials/Providers-and-Keys.md +105 -98
  337. package/dist/tutorials/Publishing-to-YouTube.md +31 -31
  338. package/dist/tutorials/Recovery-and-Retries.md +31 -31
  339. package/dist/tutorials/Remote-Access.md +2 -2
  340. package/dist/tutorials/Reviews-and-Checkpoints.md +90 -93
  341. package/dist/tutorials/Schedules.md +27 -28
  342. package/dist/tutorials/Settings-Reference.md +183 -204
  343. package/dist/tutorials/Shorts.md +34 -33
  344. package/dist/tutorials/Start-at-Login.md +3 -3
  345. package/dist/tutorials/Studio-Extension.md +22 -24
  346. package/dist/tutorials/Template-Studio.md +45 -42
  347. package/dist/tutorials/Templates.md +50 -79
  348. package/dist/tutorials/Trash.md +4 -4
  349. package/dist/tutorials/Troubleshooting.md +44 -44
  350. package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
  351. package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
  352. package/dist/tutorials/Video-Editing.md +34 -34
  353. package/dist/tutorials/Where-Your-Files-Live.md +10 -10
  354. package/dist/tutorials/YouTube-Description.md +26 -26
  355. package/dist/tutorials/Your-First-Short.md +35 -35
  356. package/dist/tutorials/_Sidebar.md +15 -15
  357. package/dist/web/assets/index-BNftKQrA.css +1 -0
  358. package/dist/web/assets/index-CQASqwjT.js +460 -0
  359. package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
  360. package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
  361. package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
  362. package/dist/web/index.html +2 -2
  363. package/package.json +2 -2
  364. package/dist/tutorials/Library-Overview.md +0 -76
  365. package/dist/tutorials/Play-Images.md +0 -165
  366. package/dist/tutorials/Play-Narration.md +0 -167
  367. package/dist/tutorials/Play-Outputs.md +0 -109
  368. package/dist/tutorials/Play-Overview.md +0 -227
  369. package/dist/tutorials/Play-Title-and-Article.md +0 -136
  370. package/dist/tutorials/Play-Video-and-Style.md +0 -143
  371. package/dist/web/assets/index-CkAI_2E7.css +0 -1
  372. package/dist/web/assets/index-DcD0cdfo.js +0 -334
  373. package/dist/web/assets/pdf-TODRwF31.js +0 -54
  374. package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
  375. package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
@@ -2,6 +2,7 @@ import { zValidator } from "@hono/zod-validator";
2
2
  import { Hono } from "hono";
3
3
  import { HTTPException } from "hono/http-exception";
4
4
  import { z } from "zod";
5
+ import { setupSectionNames } from "../../slices/project-templates/setup-sections.js";
5
6
  import { calendarRange } from "../../slices/schedules/agenda.js";
6
7
  import { rejectHeldTopics, restoreRejectedTopics } from "../../slices/schedules/held-bulk.js";
7
8
  import { preparedTopics, prepareTopic } from "../../slices/schedules/prepare.js";
@@ -40,14 +41,14 @@ function refused(c, result) {
40
41
  conflict: "This schedule changed while you were editing, possibly because a run just started. Reload the page to see the latest, then make your change again.",
41
42
  "cancel-required": "This schedule is still active. Cancel it first, then delete it. Completed schedules can be deleted straight away.",
42
43
  "missing-template": "The template you picked was deleted or changed. Choose a template again, then save.",
43
- "unsupported-media": "This template uses audio, images, a thumbnail, shorts background music or an ambient sound file you supplied, so it cannot run on a schedule: a scheduled run has no one to attach the file again. Pick a template that generates these instead, or on Play remove the music under Outputs → More shorts options (or pick Rain, Fireplace or Wind under Video and style → Ambient sound) and save the template again.",
44
+ "unsupported-media": `This template uses audio, images, a thumbnail, shorts background music or an ambient sound file you supplied, so it cannot run on a schedule: a scheduled run has no one to attach the file again. Pick a template that generates these instead, or open this one in Edit template, remove the music under ${setupSectionNames.shorts} (or pick Rain, Fireplace or Wind as the Ambient sound under ${setupSectionNames.video}) and save it again.`,
44
45
  "not-due": "This one-off time has already passed. Choose a time in the future.",
45
46
  "spend-limit": "The estimated cost is above this schedule's spend limit, or some prices are unknown. Raise the spend limit or choose models with known prices.",
46
- readiness: "A provider this template needs is not ready, for example a missing API key, model or voice. Check Settings → Providers, then try again.",
47
+ readiness: "A provider this template needs is not ready, for example a missing API key, model or voice. Check Settings → Keys & services, then try again.",
47
48
  "queue-full": `A schedule's topic queue holds at most ${String(queueMax)} topics. Remove some under Schedules → Edit, then try again.`,
48
49
  "topic-not-found": "This topic was already approved, turned down or used. Reload the page to see the topics waiting now.",
49
50
  busy: "Slopify is already generating topics for this schedule. Wait for it to finish, then try again.",
50
- "not-ready": "This schedule's template has no version marked ready. Open it in Library → Templates → Open studio and press Mark ready.",
51
+ "not-ready": "This schedule's template has no version approved for schedules. Open it from Templates with Edit template, then choose More → Approve for schedules.",
51
52
  "invalid-topics": "Some topics name keywords this template does not use. Check each topic under Schedules → Edit and save again.",
52
53
  };
53
54
  return refusal(c, result.reason, {
@@ -165,7 +166,8 @@ function routes(deps) {
165
166
  })
166
167
  // Which queued topics are prepared ahead, and their projects.
167
168
  .get("/:id/prepared", zValidator("param", id, onInvalid), (c) => c.json({ prepared: preparedTopics(service(), c.req.valid("param").id) }))
168
- // Prepare one queued topic now: everything but the video, which its scheduled day renders.
169
+ // Make ahead one queued topic: everything but the video now, which its scheduled day puts
170
+ // together.
169
171
  .post("/:id/topics/prepare", zValidator("param", id, onInvalid), zValidator("json", prepareTopicBody, onInvalid), async (c) => {
170
172
  const result = await prepareTopic(service(), {
171
173
  scheduleId: c.req.valid("param").id,
@@ -175,19 +177,19 @@ function routes(deps) {
175
177
  return c.json({ projectId: result.projectId, title: result.title }, 201);
176
178
  const detail = {
177
179
  "not-found": "This topic is no longer in the schedule's queue. Reload the page to see the queue as it is now.",
178
- "already-prepared": "This topic is already prepared: its project is in Projects under the same title.",
179
- paused: "Its schedule is paused, so it makes nothing, ahead or on its day. Resume it under Calendar → Schedules, then press Prepare again.",
180
- "missing-template": "This schedule's template was deleted, so there is nothing to prepare from. Choose another template under Edit.",
181
- "spend-limit": "Preparing it would go over this schedule's spend limit. Raise the limit under Edit, or prepare it from Play instead.",
182
- design: "A design schedule makes each piece on its day, so there is nothing to prepare ahead. To have a piece now, open the design template (Library → Designs → Templates) and press Make a piece; to have it sooner on a schedule, give its row an earlier date in the Schedule section.",
180
+ "already-prepared": "This topic is already made ahead: its project is in Projects under the same title.",
181
+ paused: "Its schedule is paused, so it makes nothing, ahead or on its day. Resume it under Calendar → Schedules, then press Make ahead again.",
182
+ "missing-template": "This schedule's template was deleted, so there is nothing to make it from. Choose another template under Edit.",
183
+ "spend-limit": "Making it ahead would go over this schedule's spend limit. Raise the limit under Edit, or make it from Create instead.",
184
+ design: "A poster schedule makes each poster on its day, so there is nothing to make ahead. To have one now, open its poster template (Templates → Poster templates) and press Make a poster; to have it sooner on a schedule, give its row an earlier date in the Schedule section.",
183
185
  };
184
186
  return problem(c, {
185
187
  status: result.reason === "not-found" ? 404 : 409,
186
188
  detail: (result.reason === "not-ready" ? result.detail : undefined) ??
187
189
  detail[result.reason] ??
188
190
  (result.detail === undefined
189
- ? `This topic couldn't be prepared (${result.reason}). Open Play with the schedule's template to see what the setup needs, then try again.`
190
- : `This topic couldn't be prepared: ${result.detail} Fix it in the schedule's template (Library → Templates), then try again.`),
191
+ ? `This topic couldn't be made ahead (${result.reason}). Open Create with the schedule's template to see what the setup needs, then try again.`
192
+ : `This topic couldn't be made ahead: ${result.detail} Fix it in the schedule's template (Templates → Edit template), then try again.`),
191
193
  extensions: { reason: result.reason },
192
194
  });
193
195
  })
@@ -88,7 +88,11 @@ export function settingsRoutes(deps) {
88
88
  return c.json({
89
89
  nvidia: status.available
90
90
  ? { available: true }
91
- : { available: false, reason: status.reason },
91
+ : {
92
+ available: false,
93
+ reason: status.reason,
94
+ ...(status.detail === undefined ? {} : { detail: status.detail }),
95
+ },
92
96
  });
93
97
  })
94
98
  // The named links `{{Name}}` placeholders in YouTube descriptions fill from
@@ -100,7 +104,7 @@ export function settingsRoutes(deps) {
100
104
  if (refused !== undefined)
101
105
  return problem(c, {
102
106
  status: 400,
103
- detail: `The channel links weren't saved: ${refused} Then press Save in Settings → Channel links.`,
107
+ detail: `The channel links weren't saved: ${refused} Then press Save in Settings → YouTube Studio and posting.`,
104
108
  });
105
109
  writeChannelLinks(deps.db, links);
106
110
  return c.json({ links: readChannelLinks(deps.db) });
@@ -5,6 +5,7 @@ import { projectById } from "../../slices/admission/repo.js";
5
5
  import { webhookBody } from "../../slices/notifications/address.js";
6
6
  import { sendFailureText } from "../../slices/notifications/send.js";
7
7
  import { readNotificationUrl } from "../../slices/notifications/settings.js";
8
+ import { setupSectionNames } from "../../slices/project-templates/setup-sections.js";
8
9
  import { waitsForRelease } from "../../slices/social/due.js";
9
10
  import { postingExtension, writeExtensionSites } from "../../slices/social/extension.js";
10
11
  import { platformLabels } from "../../slices/social/model.js";
@@ -50,7 +51,7 @@ export function socialRoutes(deps) {
50
51
  : {
51
52
  ok: false,
52
53
  sentence: drawn?.message ??
53
- "The design couldn't be drawn. Open it in Library → Designs to check it, then reload Prepare upload.",
54
+ "The design couldn't be drawn. Open it in Designs to check it, then close Copy for YouTube Studio and open it again.",
54
55
  };
55
56
  },
56
57
  }),
@@ -110,7 +111,7 @@ export function socialRoutes(deps) {
110
111
  config?.text !== "prompt")
111
112
  return problem(c, {
112
113
  status: 409,
113
- detail: `A post written by a post prompt is a paid step, so it is picked in the template, in Template Studio → Upload pack, where Save shows its cost. For this project, pick a free text source in Prepare upload → ${platformLabels[platform]} → Settings.`,
114
+ detail: `A post written by a post prompt is a paid step, so it is picked in the template, in Edit template → ${setupSectionNames.youtube}, where Save shows its cost. For this project, pick a free text source in Copy for YouTube Studio → ${platformLabels[platform]} → Settings.`,
114
115
  });
115
116
  writeChoice(deps.db, { projectId, platform }, choice, deps.clock.now());
116
117
  changed(projectId);
@@ -204,7 +205,7 @@ export function socialRoutes(deps) {
204
205
  if (next === undefined)
205
206
  return problem(c, {
206
207
  status: 404,
207
- detail: "Nothing is waiting to be filled on this site. In Slopify, open the project, press Prepare upload, then Fill in the site's post.",
208
+ detail: "Nothing is waiting to be filled on this site. In Slopify, open the project, press Copy for YouTube Studio, then Fill in the site's post.",
208
209
  });
209
210
  changed(next.pack.projectId);
210
211
  return c.json(next);
@@ -277,6 +278,6 @@ function unknownProject(c) {
277
278
  function unknownPost(c) {
278
279
  return problem(c, {
279
280
  status: 404,
280
- detail: "That project no longer posts to this site. In Slopify, open the project's Prepare upload and press Fill again.",
281
+ detail: "That project no longer posts to this site. In Slopify, open the project's Copy for YouTube Studio and press Fill again.",
281
282
  });
282
283
  }
@@ -47,7 +47,7 @@ export function stagingRoutes(deps) {
47
47
  return problem(c, {
48
48
  status: result.reason === "in-use" ? 409 : 404,
49
49
  detail: result.reason === "in-use"
50
- ? "This file is still attached to a draft in Play. Remove it from the draft first."
50
+ ? "This file is still attached to a draft on Create. Remove it from the draft first."
51
51
  : "This uploaded file no longer exists; it may have been removed already. Reload the page.",
52
52
  });
53
53
  }
@@ -1,6 +1,7 @@
1
1
  import { zValidator } from "@hono/zod-validator";
2
2
  import { Hono } from "hono";
3
3
  import { projectExists } from "../../slices/admission/repo.js";
4
+ import { isSampleProject } from "../../slices/onboarding/sample.js";
4
5
  import { BackupBusyError, planBackup, streamBackup, } from "../../slices/storage/backup-export.js";
5
6
  import { BackupImportRefused, importBackup } from "../../slices/storage/backup-import.js";
6
7
  import { importPortable, portableMaxArchiveBytes, storageUsage, } from "../../slices/storage/portable.js";
@@ -19,14 +20,21 @@ export function storageRoutes(deps) {
19
20
  appVersion: deps.version,
20
21
  hasInflight: deps.runner.hasInflight,
21
22
  });
22
- // A template's studio sample keeps its files for the studio's bake-offs and resamples.
23
+ // A template's studio sample keeps its files for the studio's bake-offs and resamples, and a
24
+ // bundled sample is read-only.
23
25
  const studioSample = (id) => {
26
+ if (isSampleProject(deps.db, id))
27
+ return {
28
+ status: 409,
29
+ detail: "This is a sample project, which is read-only and keeps all its files. To free its space, delete it in Projects; Restore samples in Settings → Backup & storage brings it back.",
30
+ extensions: { reason: "sample-read-only" },
31
+ };
24
32
  const owner = sampleOwner(deps.db, id);
25
33
  return owner === undefined
26
34
  ? undefined
27
35
  : {
28
36
  status: 409,
29
- detail: `This is the studio sample of ${owner.name}, and the studio keeps its files to make its samples again without paying twice. Change it in its studio instead: Library → Templates → ${owner.name} → Open studio.`,
37
+ detail: `This is the sample project of the template ${owner.name}, kept with its files so Edit template can make its samples again without paying twice. Change it in Edit template instead: Templates → ${owner.name} → Edit template.`,
30
38
  extensions: { reason: "template-sample" },
31
39
  };
32
40
  };
@@ -55,11 +63,11 @@ export function storageRoutes(deps) {
55
63
  return result.reason === "no-project"
56
64
  ? problem(c, {
57
65
  status: 404,
58
- detail: "This project no longer exists. Reload Settings → Storage to see the current list.",
66
+ detail: "This project no longer exists. Reload Settings → Backup & storage to see the current list.",
59
67
  })
60
68
  : problem(c, {
61
69
  status: 409,
62
- detail: "Only a finished project can drop its working files, and this one is running, waiting or has unfinished steps. Let it finish (or cancel it on its project page), then drop the working files again from the project page or Settings → Storage.",
70
+ detail: "Only a finished project can drop its working files, and this one is running, waiting or has unfinished steps. Let it finish (or cancel it on its project page), then drop the working files again from the project page or Settings → Backup & storage.",
63
71
  });
64
72
  })
65
73
  // Delete old versions: one project's History files (`slices/storage/trim.ts`).
@@ -8,6 +8,7 @@ import { transact } from "../../kernel/db/tx.js";
8
8
  import { derive } from "../../kernel/runner/graph.js";
9
9
  import { listProjects, projectById, stagesOf } from "../../slices/admission/repo.js";
10
10
  import { channelById, projectChannels } from "../../slices/channels/repo.js";
11
+ import { isSampleProject } from "../../slices/onboarding/sample.js";
11
12
  import { scheduleById, scheduleRunOfProject } from "../../slices/schedules/repo.js";
12
13
  import { readSetting, writeSetting } from "../../slices/settings/repo.js";
13
14
  import { findDownload } from "../../slices/storage/downloads.js";
@@ -130,6 +131,16 @@ export function studioRoutes(deps) {
130
131
  });
131
132
  };
132
133
  const { allowOrigin, refused, allowed: extAllowed } = extensionAuth(deps.db);
134
+ // The bundled samples are read-only and never go to YouTube, so nothing here writes a
135
+ // release, a link, a pick or a fill for one, as the project routes refuse its writes
136
+ // (`app.ts`). Reading them stays open: the sample's page shows what a finished video offers.
137
+ const sampleRefused = (c, ...projectIds) => projectIds.some((one) => isSampleProject(deps.db, one))
138
+ ? problem(c, {
139
+ status: 409,
140
+ detail: "This is a sample project, which is read-only: it isn't on YouTube and can't be. Press Make my own copy at the top of its page to post your own.",
141
+ extensions: { reason: "sample-read-only" },
142
+ })
143
+ : undefined;
133
144
  // A project's place in the plan: its series, and the schedule and run day that made it.
134
145
  const whoOf = (projectId, series) => {
135
146
  const run = scheduleRunOfProject(deps.db, projectId);
@@ -145,7 +156,10 @@ export function studioRoutes(deps) {
145
156
  // A project ready to upload gets its release times from the posting plan when its upload is
146
157
  // prepared (`releases.ts`).
147
158
  // A long video already on YouTube keeps when it went up; its shorts still get times.
159
+ // A sample never gets a time: it isn't going to YouTube.
148
160
  const planned = (projectId) => {
161
+ if (isSampleProject(deps.db, projectId))
162
+ return;
149
163
  const long = videoOf(deps.db, projectId, null);
150
164
  const out = long?.uploadState === "done" ? long.recordedAt : undefined;
151
165
  const result = uploadPack(deps, projectId);
@@ -167,12 +181,13 @@ export function studioRoutes(deps) {
167
181
  .toSorted(),
168
182
  });
169
183
  const slotChoices = (projectId, series) => freeSlots(deps.db, readPlan(deps.db), whoOf(projectId, series), deps.clock.now(), 9);
170
- // Finished projects not marked uploaded: the popup's list and the calendar's candidates.
184
+ // Finished projects not marked uploaded: the popup's list and the calendar's candidates. The
185
+ // samples are never among them.
171
186
  const finishedProjects = () => {
172
187
  const uploads = uploadedProjects(deps.db);
173
188
  const aside = setAsideProjects(deps.db);
174
189
  return listProjects(deps.db).filter((project) => {
175
- if (uploads.has(project.id))
190
+ if (uploads.has(project.id) || isSampleProject(deps.db, project.id))
176
191
  return false;
177
192
  const state = derive(stagesOf(deps.db, project.id), project.paused === true);
178
193
  return (state === "done" || state === "partial" || (state === "pending" && aside.has(project.id)));
@@ -246,6 +261,9 @@ export function studioRoutes(deps) {
246
261
  return unknownProject(c);
247
262
  const leadMs = readLeadHours(deps.db) * 3_600_000;
248
263
  const releases = releasesOf(deps.db, projectId);
264
+ // The long video is on YouTube once its link is known or the project is marked
265
+ // uploaded (one fact, `videos.ts`); a short only by its own link.
266
+ const uploaded = uploadedProjects(deps.db).has(projectId);
249
267
  return c.json({
250
268
  leadHours: readLeadHours(deps.db),
251
269
  // The zone its times are read in: its line's schedule's, else the plan's.
@@ -260,7 +278,8 @@ export function studioRoutes(deps) {
260
278
  title: item.title,
261
279
  at: at === "" ? null : at,
262
280
  uploadBy: at === "" ? null : new Date(Date.parse(at) - leadMs).toISOString(),
263
- onYoutube: videoOf(deps.db, projectId, item.short ?? null)?.uploadState === "done",
281
+ onYoutube: videoOf(deps.db, projectId, item.short ?? null)?.uploadState === "done" ||
282
+ (item.kind === "video" && uploaded),
264
283
  };
265
284
  }),
266
285
  free: slotChoices(projectId, pack.pack.series),
@@ -273,6 +292,9 @@ export function studioRoutes(deps) {
273
292
  return denied;
274
293
  const { projectId } = c.req.valid("param");
275
294
  const other = c.req.valid("json").with;
295
+ const sample = sampleRefused(c, projectId, other);
296
+ if (sample !== undefined)
297
+ return sample;
276
298
  const swapped = transact(deps.db, () => swapReleases(deps.db, projectId, other, deps.clock.now()));
277
299
  if (!swapped)
278
300
  return problem(c, {
@@ -296,6 +318,9 @@ export function studioRoutes(deps) {
296
318
  if (denied !== undefined)
297
319
  return denied;
298
320
  const { projectId } = c.req.valid("param");
321
+ const sample = sampleRefused(c, projectId);
322
+ if (sample !== undefined)
323
+ return sample;
299
324
  if (uploadPack(deps, projectId).ok === false)
300
325
  return unknownProject(c);
301
326
  const { short, at, line } = c.req.valid("json");
@@ -311,7 +336,7 @@ export function studioRoutes(deps) {
311
336
  if (channelId !== undefined && channelById(deps.db, channelId) === undefined)
312
337
  return problem(c, {
313
338
  status: 404,
314
- detail: "The playlists weren't saved: that channel no longer exists. Reload Settings → YouTube Studio and pick the channel again.",
339
+ detail: "The playlists weren't saved: that channel no longer exists. Reload Settings → YouTube Studio and posting and pick the channel again.",
315
340
  });
316
341
  const invalid = studioPlaylistsProblem(raw);
317
342
  if (invalid !== undefined)
@@ -339,12 +364,15 @@ export function studioRoutes(deps) {
339
364
  return unknownProject(c);
340
365
  return c.json({ ...result.pack, slotChoices: slotChoices(projectId, result.pack.series) });
341
366
  })
342
- // Prepare upload's slot picker: another free slot of the plan, or none.
367
+ // Copy for YouTube Studio's Release picker: another free time of the plan, or none.
343
368
  .put("/packs/:projectId/slot", zValidator("param", projectParam, onInvalid), zValidator("json", slotBody, onInvalid), (c) => {
344
369
  const denied = samePage(c);
345
370
  if (denied !== undefined)
346
371
  return denied;
347
372
  const { projectId } = c.req.valid("param");
373
+ const sample = sampleRefused(c, projectId);
374
+ if (sample !== undefined)
375
+ return sample;
348
376
  const wanted = c.req.valid("json").slot;
349
377
  const before = uploadPack(deps, projectId);
350
378
  if (!before.ok)
@@ -356,7 +384,7 @@ export function studioRoutes(deps) {
356
384
  if (!free)
357
385
  return problem(c, {
358
386
  status: 409,
359
- detail: "That slot is taken or past. Reload Prepare upload and choose one of the slots it lists.",
387
+ detail: "That time is taken or past. Close Copy for YouTube Studio, open it again and choose one of the times it lists.",
360
388
  });
361
389
  setRelease(deps.db, projectId, 0, wanted.longAt, wanted.row, deps.clock.now());
362
390
  }
@@ -366,37 +394,47 @@ export function studioRoutes(deps) {
366
394
  ? c.json({ ...result.pack, slotChoices: slotChoices(projectId, result.pack.series) })
367
395
  : unknownProject(c);
368
396
  })
369
- // Prepare upload's AI use tick: the project's uploaded clips are real footage.
397
+ // Copy for YouTube Studio's AI use tick: the project's uploaded clips are real footage.
370
398
  .put("/packs/:projectId/real-footage", zValidator("param", projectParam, onInvalid), zValidator("json", realFootageBody, onInvalid), (c) => {
371
399
  const denied = samePage(c);
372
400
  if (denied !== undefined)
373
401
  return denied;
374
402
  const { projectId } = c.req.valid("param");
403
+ const sample = sampleRefused(c, projectId);
404
+ if (sample !== undefined)
405
+ return sample;
375
406
  if (!uploadPack(deps, projectId).ok)
376
407
  return unknownProject(c);
377
408
  saveRealFootage(deps.db, projectId, c.req.valid("json").realFootage);
378
409
  const result = uploadPack(deps, projectId);
379
410
  return result.ok ? c.json(result.pack) : unknownProject(c);
380
411
  })
381
- // Prepare upload's pick: which title and thumbnail the upload carries; the A/B test tries
412
+ // Copy for YouTube Studio's pick: which title and thumbnail the upload carries; the A/B test tries
382
413
  // the others once the video is public.
383
414
  .put("/packs/:projectId/pick", zValidator("param", projectParam, onInvalid), zValidator("json", pickBody, onInvalid), (c) => {
384
415
  const denied = samePage(c);
385
416
  if (denied !== undefined)
386
417
  return denied;
387
418
  const { projectId } = c.req.valid("param");
419
+ const sample = sampleRefused(c, projectId);
420
+ if (sample !== undefined)
421
+ return sample;
388
422
  if (!uploadPack(deps, projectId).ok)
389
423
  return unknownProject(c);
390
424
  writeUploadPick(deps.db, projectId, c.req.valid("json"));
391
425
  const result = uploadPack(deps, projectId);
392
426
  return result.ok ? c.json(result.pack) : unknownProject(c);
393
427
  })
394
- // Prepare upload's playlist ticks: which of the channel's playlists this project goes into.
428
+ // Copy for YouTube Studio's playlist ticks: which of the channel's playlists this project
429
+ // goes into.
395
430
  .put("/packs/:projectId/playlists", zValidator("param", projectParam, onInvalid), zValidator("json", projectPlaylistsBody, onInvalid), (c) => {
396
431
  const denied = samePage(c);
397
432
  if (denied !== undefined)
398
433
  return denied;
399
434
  const { projectId } = c.req.valid("param");
435
+ const sample = sampleRefused(c, projectId);
436
+ if (sample !== undefined)
437
+ return sample;
400
438
  if (!uploadPack(deps, projectId).ok)
401
439
  return unknownProject(c);
402
440
  saveProjectPlaylists(deps.db, projectId, c.req.valid("json").playlists);
@@ -408,6 +446,9 @@ export function studioRoutes(deps) {
408
446
  if (denied !== undefined)
409
447
  return denied;
410
448
  const { projectId } = c.req.valid("param");
449
+ const sample = sampleRefused(c, projectId);
450
+ if (sample !== undefined)
451
+ return sample;
411
452
  const { short } = c.req.valid("json");
412
453
  const result = uploadPack(deps, projectId);
413
454
  if (!result.ok)
@@ -415,7 +456,7 @@ export function studioRoutes(deps) {
415
456
  if (packItem(result.pack, short) === undefined)
416
457
  return problem(c, {
417
458
  status: 404,
418
- detail: `This project has no short ${String(short)}. Reload the project page and press Prepare upload again.`,
459
+ detail: `This project has no short ${String(short)}. Reload the project page and press Copy for YouTube Studio again.`,
419
460
  });
420
461
  const queue = enqueueFill(deps.db, projectId, short ?? null, deps.clock.now());
421
462
  return c.json({ chosen: { projectId, short: short ?? null }, queue: queueView(queue) });
@@ -426,15 +467,19 @@ export function studioRoutes(deps) {
426
467
  // The YouTube videos a project's uploads became, and their A/B tests.
427
468
  .get("/videos/:projectId", zValidator("param", projectParam, onInvalid), (c) => c.json({ videos: projectVideos(deps.db, c.req.valid("param").projectId) }))
428
469
  // A video's link pasted in Slopify, for an upload made without the extension; an empty
429
- // link forgets it.
470
+ // link forgets it. The long video's link marks the project uploaded, and forgetting it
471
+ // takes the mark off (`videos.ts`).
430
472
  .put("/videos/:projectId", zValidator("param", projectParam, onInvalid), zValidator("json", videoLinkBody, onInvalid), (c) => {
431
473
  const wrong = samePage(c);
432
474
  if (wrong !== undefined)
433
475
  return wrong;
434
476
  const { projectId } = c.req.valid("param");
477
+ const sample = sampleRefused(c, projectId);
478
+ if (sample !== undefined)
479
+ return sample;
435
480
  const { short, link } = c.req.valid("json");
436
481
  if (link.trim() === "") {
437
- forgetVideo(deps.db, projectId, short ?? null);
482
+ forgetVideo(deps.db, projectId, short ?? null, deps.clock.now().toISOString());
438
483
  return c.json({ videos: projectVideos(deps.db, projectId) });
439
484
  }
440
485
  const videoId = videoIdOf(link);
@@ -461,7 +506,7 @@ export function studioRoutes(deps) {
461
506
  queue: queueView(removeFill(deps.db, projectId, short ?? null, deps.clock.now())),
462
507
  });
463
508
  })
464
- // The extension's own zips, for Settings' and Prepare upload's Download.
509
+ // The extension's own zips, for Settings' and Copy for YouTube Studio's Download.
465
510
  .get("/extension/:file", zValidator("param", extensionParam, onInvalid), (c) => {
466
511
  const { file } = c.req.valid("param");
467
512
  const name = extensionFiles[file] ?? "";
@@ -496,7 +541,7 @@ export function studioRoutes(deps) {
496
541
  ? refused(c)
497
542
  : problem(c, {
498
543
  status: 403,
499
- detail: "Only the Slopify Studio browser extension can pair with Slopify. Install it (see Settings → YouTube Studio) and pair from its options page.",
544
+ detail: "Only the Slopify Studio browser extension can pair with Slopify. Install it (see Settings → YouTube Studio and posting) and pair from its options page.",
500
545
  });
501
546
  // The origin is paired now; the response carries its headers.
502
547
  allowOrigin(c, false);
@@ -523,8 +568,8 @@ export function studioRoutes(deps) {
523
568
  return problem(c, {
524
569
  status: 404,
525
570
  detail: gone > 0
526
- ? "The project chosen to fill in is gone. In Slopify, open the project again, press Prepare upload, then Fill in YouTube Studio."
527
- : "Nothing is waiting to be filled in. In Slopify, open the project, press Prepare upload, then Fill in YouTube Studio.",
571
+ ? "The project chosen to fill in is gone. In Slopify, open the project again, press Copy for YouTube Studio, then Fill in YouTube Studio."
572
+ : "Nothing is waiting to be filled in. In Slopify, open the project, press Copy for YouTube Studio, then Fill in YouTube Studio.",
528
573
  });
529
574
  })
530
575
  // The extension filled an item: it leaves the queue, so the next upload dialog takes the
@@ -595,6 +640,9 @@ export function studioRoutes(deps) {
595
640
  if (!extAllowed(c))
596
641
  return refused(c);
597
642
  const { projectId, short } = c.req.valid("json");
643
+ const sample = sampleRefused(c, projectId);
644
+ if (sample !== undefined)
645
+ return sample;
598
646
  const result = uploadPack(deps, projectId);
599
647
  if (!result.ok || packItem(result.pack, short ?? undefined) === undefined)
600
648
  return problem(c, {
@@ -612,6 +660,9 @@ export function studioRoutes(deps) {
612
660
  if (!extAllowed(c))
613
661
  return refused(c);
614
662
  const { projectId } = c.req.valid("json");
663
+ const sample = sampleRefused(c, projectId);
664
+ if (sample !== undefined)
665
+ return sample;
615
666
  planned(projectId);
616
667
  const result = uploadPack(deps, projectId);
617
668
  if (!result.ok)
@@ -715,7 +766,7 @@ export function studioRoutes(deps) {
715
766
  for (const { projectId, short, videoId } of body.videos) {
716
767
  if (videoOf(deps.db, projectId, short ?? null)?.videoId !== videoId)
717
768
  continue;
718
- forgetVideo(deps.db, projectId, short ?? null);
769
+ forgetVideo(deps.db, projectId, short ?? null, deps.clock.now().toISOString());
719
770
  deps.hub.emit(projectId, { type: "project.updated", projectId });
720
771
  forgotten++;
721
772
  }
@@ -798,8 +849,9 @@ export function studioRoutes(deps) {
798
849
  // (a short's related video, the long video's end screen and captions, unless they went in
799
850
  // during the upload) wait for the extension, the pinned comment for the video to be public.
800
851
  function confirmVideoUpload(deps, projectId, short, videoId, captionsAdded = false) {
801
- if (!confirmUpload(deps.db, projectId, short, videoId))
802
- recordVideo(deps.db, projectId, short, videoId, deps.clock.now().toISOString());
852
+ const at = deps.clock.now().toISOString();
853
+ if (!confirmUpload(deps.db, projectId, short, videoId, at))
854
+ recordVideo(deps.db, projectId, short, videoId, at);
803
855
  const confirmedPack = uploadPack(deps, projectId);
804
856
  const confirmedItem = confirmedPack.ok
805
857
  ? packItem(confirmedPack.pack, short ?? undefined)
@@ -41,7 +41,7 @@ export function stylePreviewRoutes(deps) {
41
41
  if (saved === undefined)
42
42
  return problem(c, {
43
43
  status: 404,
44
- detail: "This style preview is no longer saved; Slopify keeps only the most recent ones. Press Render again to make it.",
44
+ detail: "This style preview is no longer saved; Slopify keeps only the most recent ones. Press Make the preview again to make it.",
45
45
  });
46
46
  return fileResponse(c, saved.path, saved.bytes, {
47
47
  "content-type": "video/mp4",
@@ -53,6 +53,6 @@ export function stylePreviewRoutes(deps) {
53
53
  function unavailable(c) {
54
54
  return problem(c, {
55
55
  status: 503,
56
- detail: "The style preview isn't available because this Slopify started without its video renderer. Restart Slopify, then press Render again.",
56
+ detail: "The style preview isn't available because this Slopify started without its video renderer. Restart Slopify, then press Make the preview again.",
57
57
  });
58
58
  }
@@ -10,6 +10,7 @@ import { providerStatuses } from "../../slices/settings/readiness.js";
10
10
  import { getAnatomy } from "../../slices/template-studio/anatomy-view.js";
11
11
  import { bakeoffQuoteSchema, bakeoffStartSchema, bakeoffView, discardBakeoff, quoteBakeoff, startBakeoff, } from "../../slices/template-studio/bakeoff.js";
12
12
  import { budgetRoom, readStudioSettings, studioSettingsSchema, systemTimeZone, writeStudioSettings, } from "../../slices/template-studio/budget.js";
13
+ import { templateCards } from "../../slices/template-studio/cards.js";
13
14
  import { makeSamplesForAll, quoteSamplesForAll, samplesForAllSchema, } from "../../slices/template-studio/for-all.js";
14
15
  import { createFreeSamples } from "../../slices/template-studio/free.js";
15
16
  import { enqueueJob } from "../../slices/template-studio/jobs.js";
@@ -46,7 +47,7 @@ const quoteBody = z
46
47
  })
47
48
  .strict()
48
49
  .refine((body) => [body.document, body.topic, body.fullScript].filter((one) => one !== undefined).length === 1, { message: "Send one of document, topic or fullScript." });
49
- const unpriced = "This Slopify can't price or make paid samples because Play's review isn't available here. Restart Slopify; if it happens again, use Download diagnostics in Settings and report it.";
50
+ const unpriced = "This Slopify can't price or make paid samples because Create's review isn't available here. Restart Slopify; if it happens again, use Download diagnostics in Settings and report it.";
50
51
  // A refusal that carries the new quote (a higher price, unknown prices, past the budget), so the
51
52
  // page can show it on the button at once.
52
53
  function refusedWithQuote(c, result) {
@@ -60,7 +61,7 @@ function refusedWithQuote(c, result) {
60
61
  },
61
62
  });
62
63
  }
63
- const missingTemplate = "This template no longer exists; it may have been deleted. Go to Library → Templates to see your templates.";
64
+ const missingTemplate = "This template no longer exists; it may have been deleted. Go to Templates to see your templates.";
64
65
  function service(deps) {
65
66
  return (deps.templateStudio ?? {
66
67
  free: createFreeSamples({
@@ -110,13 +111,15 @@ export function templateStudioRoutes(deps) {
110
111
  "x-content-type-options": "nosniff",
111
112
  });
112
113
  return (new Hono()
113
- // Settings → Template studio: the samples' daily budgets, and what was spent today.
114
+ // Templates: what each template makes, its still and its facts, for picking one.
115
+ .get("/cards", (c) => c.json({ cards: templateCards({ db: deps.db, paths: deps.paths, log: deps.log }) }))
116
+ // Settings → Production defaults: the samples' daily budgets, and what was spent today.
114
117
  .get("/settings", (c) => c.json(settingsView()))
115
118
  .put("/settings", zValidator("json", studioSettingsSchema, onInvalid), (c) => {
116
119
  writeStudioSettings(deps.db, c.req.valid("json"));
117
120
  return c.json(settingsView());
118
121
  })
119
- // Settings → Template studio → Make samples for all: the total first, then each sample
122
+ // Settings → Production defaults → Make samples for all: the total first, then each sample
120
123
  // that fits today's budget queued at its price, the rest waiting on the budget.
121
124
  .post("/samples-for-all/quote", async (c) => {
122
125
  if (review === undefined)
@@ -157,12 +160,12 @@ export function templateStudioRoutes(deps) {
157
160
  return problem(c, {
158
161
  status: 413,
159
162
  title: "Content Too Large",
160
- detail: `This pack is larger than ${String(packLimits.uploadBytes / 1024 / 1024)} MB, more than a template pack ever is. Export it again from the template's studio, or ask whoever made it to.`,
163
+ detail: `This template file is larger than ${String(packLimits.uploadBytes / 1024 / 1024)} MB, more than an exported template ever is. Export it again from the template's Edit template page, or ask whoever made it to.`,
161
164
  });
162
165
  if (bytes === undefined || bytes.byteLength === 0)
163
166
  return problem(c, {
164
167
  status: 400,
165
- detail: "No pack arrived with the upload. Choose the pack's .zip file again.",
168
+ detail: "No file arrived with the upload. Choose the template's .zip file again.",
166
169
  });
167
170
  const result = importPack(review, new Uint8Array(bytes));
168
171
  if (!result.ok)
@@ -330,7 +333,7 @@ export function templateStudioRoutes(deps) {
330
333
  const template = templateById(deps.db, id);
331
334
  if (template === undefined)
332
335
  return gone(c, missingTemplate);
333
- const notMade = "This loudness sample isn't made yet. Open the template's studio and press Make samples.";
336
+ const notMade = "This loudness sample isn't made yet. Open Edit template and press Make sample.";
334
337
  if (file === "before.mp3") {
335
338
  const source = studio.loudnessSource;
336
339
  const size = source === undefined ? undefined : statSync(source, { throwIfNoEntry: false });
@@ -422,12 +425,12 @@ export function templateStudioRoutes(deps) {
422
425
  }), onInvalid), (c) => {
423
426
  const { id, file } = c.req.valid("param");
424
427
  const found = packSampleFileOf(deps.paths, id, file);
425
- return media(c, found, found?.type ?? "application/octet-stream", "This template has no such sample from a pack. Open its studio to see the samples it has.");
428
+ return media(c, found, found?.type ?? "application/octet-stream", "This template has no such sample from the file it came in. Open Edit template to see the samples it has.");
426
429
  })
427
430
  .get("/:id/document.pdf", zValidator("param", param, onInvalid), (c) => {
428
431
  const template = templateById(deps.db, c.req.valid("param").id);
429
432
  if (template === undefined)
430
433
  return gone(c, missingTemplate);
431
- return media(c, studio.documentFile?.(studio.free.documentHashOf(template.document)), "application/pdf", "This PDF sample isn't made yet. Open the template's studio and press Make samples.");
434
+ return media(c, studio.documentFile?.(studio.free.documentHashOf(template.document)), "application/pdf", "This PDF sample isn't made yet. Open Edit template and press Make sample.");
432
435
  }));
433
436
  }
@@ -32,7 +32,7 @@ const status = {
32
32
  "template-gone": 409,
33
33
  };
34
34
  const details = {
35
- "not-found": "This item is no longer in the trash: it was restored or removed for good. Reload Settings → Trash to see what is left.",
35
+ "not-found": "This item is no longer in the trash: it was restored or removed for good.",
36
36
  running: "This project is still running. Use Cancel run on the project page first, then try again.",
37
37
  files: "Some of this project's files could not be removed. Close any program using files in the project folder, then press Delete now again.",
38
38
  "template-in-trash": "This schedule's template is in the trash too. Restore the template first (it is listed here), then restore the schedule.",
@@ -79,7 +79,7 @@ export function youtubeEditRoutes(deps) {
79
79
  if (refused !== undefined)
80
80
  return problem(c, {
81
81
  status: 400,
82
- detail: `This project's links weren't saved: ${refused} Then press Save links under YouTube on the Video section.`,
82
+ detail: `This project's links weren't saved: ${refused} Then press Save beside Previous episode, under YouTube links in the project's YouTube section.`,
83
83
  });
84
84
  const saved = transact(deps.db, () => {
85
85
  const next = { ...readDescriptionEdits(deps.db, id), links };
@@ -10,11 +10,11 @@ const listening = "sound and pictures for the person watching the page";
10
10
  const personal = "the person's own tutorial, notices and reading state";
11
11
  const retired = "retired: answers revision-required; edits go through save_revision";
12
12
  const extension = "only the paired Slopify Studio extension calls these";
13
- const youtube = "acts on YouTube: agents stop at Ready to upload, and uploading is the person's (the extension's queue is filled only from Prepare upload)";
13
+ const youtube = "acts on YouTube: agents stop at Ready to upload, and uploading is the person's (the extension's queue is filled only from Copy for YouTube Studio)";
14
14
  const posting = "acts on X, LinkedIn, Instagram, TikTok, Reddit or Bluesky: agents stop at Ready to post, and filling a site's form and posting are the person's";
15
15
  const cleanup = "every project's cleanup is the person's: Delete all old versions and Clean up everything are in Settings → Backup & storage";
16
16
  const assistantChat = "the in-app assistant's chat is the person's own: its asks are answered only by a press in the page, never by an agent";
17
- const notStarted = "not a page action: projects start from Play drafts (start_draft) after their review and estimate";
17
+ const notStarted = "not a page action: projects start from drafts on Create (start_draft) after their review and estimate";
18
18
  export const excludedRoutes = {
19
19
  "GET /api/assistant": assistantChat,
20
20
  "POST /api/assistant/sessions": assistantChat,
@@ -48,6 +48,7 @@ export const excludedRoutes = {
48
48
  "GET /api/projects/:projectId/audio-preview/:previewId": listening,
49
49
  "GET /api/projects/:projectId/narration/peaks": listening,
50
50
  "POST /api/auditions": `${listening}; its price is quote_voice_sample`,
51
+ "GET /api/template-studio/cards": "the Templates page's cards, a still and one line of facts per template for picking by eye; agents list templates with list_templates and read one with get_template_studio",
51
52
  "GET /api/template-studio/:id/loudness/:file": `${listening}; get_template_studio gives both levels`,
52
53
  "GET /api/template-studio/:id/document.pdf": `${listening}; get_template_studio says whether it is made`,
53
54
  "GET /api/template-studio/:id/pack": "a file download to share, like a project's files: agents read the template with get_template_studio and get_template",
@@ -76,8 +77,8 @@ export const excludedRoutes = {
76
77
  "GET /api/tutorials/search": personal,
77
78
  "GET /api/tutorials/:page": personal,
78
79
  "GET /api/providers/first-run": personal,
79
- "POST /api/providers/first-run/dismiss": personal,
80
80
  "POST /api/onboarding/dismiss": personal,
81
+ "POST /api/onboarding/check": personal,
81
82
  "GET /api/settings/autostart": hostSetup,
82
83
  "PUT /api/settings/autostart": hostSetup,
83
84
  "PUT /api/settings/remote-access": remoteAccess,