@gentbajko/slopify 4.0.0 → 4.1.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 (366) 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 +3 -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/providers.js +9 -10
  59. package/dist/edge/http/refusal.js +1 -1
  60. package/dist/edge/http/remote-access.js +4 -4
  61. package/dist/edge/http/remote-guard.js +2 -2
  62. package/dist/edge/http/reviews.js +1 -1
  63. package/dist/edge/http/revision-files.js +1 -1
  64. package/dist/edge/http/revisions.js +4 -4
  65. package/dist/edge/http/schedules.js +13 -11
  66. package/dist/edge/http/settings.js +6 -2
  67. package/dist/edge/http/social.js +5 -4
  68. package/dist/edge/http/staging.js +1 -1
  69. package/dist/edge/http/storage.js +12 -4
  70. package/dist/edge/http/studio.js +71 -19
  71. package/dist/edge/http/style-preview.js +2 -2
  72. package/dist/edge/http/template-studio.js +12 -9
  73. package/dist/edge/http/trash.js +1 -1
  74. package/dist/edge/http/youtube-edits.js +1 -1
  75. package/dist/edge/mcp/excluded-routes.js +4 -3
  76. package/dist/edge/mcp/instructions.js +3 -3
  77. package/dist/edge/mcp/library-tools.js +8 -5
  78. package/dist/edge/mcp/remake-tools.js +7 -4
  79. package/dist/edge/mcp/server.js +1 -1
  80. package/dist/edge/mcp/tools-channels.js +9 -9
  81. package/dist/edge/mcp/tools-designs.js +9 -9
  82. package/dist/edge/mcp/tools-library.js +7 -7
  83. package/dist/edge/mcp/tools-planning.js +18 -18
  84. package/dist/edge/mcp/tools-projects.js +10 -10
  85. package/dist/edge/mcp/tools-publishing.js +9 -9
  86. package/dist/edge/mcp/tools-runs.js +9 -9
  87. package/dist/edge/mcp/tools-schedules.js +6 -6
  88. package/dist/edge/mcp/tools-setup.js +20 -14
  89. package/dist/edge/mcp/tools-social.js +5 -4
  90. package/dist/edge/mcp/tools-storage.js +3 -3
  91. package/dist/edge/mcp/tools-template-studio.js +18 -18
  92. package/dist/edge/remote-access/model.js +1 -1
  93. package/dist/extension/slopify-studio-chrome.zip +0 -0
  94. package/dist/extension/slopify-studio-firefox.zip +0 -0
  95. package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
  96. package/dist/kernel/pipeline.js +12 -0
  97. package/dist/kernel/ports/settings-places.js +31 -0
  98. package/dist/kernel/ports/system-speech.js +31 -3
  99. package/dist/kernel/runner/attempt.js +11 -3
  100. package/dist/kernel/runner/providers.js +3 -2
  101. package/dist/kernel/runner/standalone.js +2 -2
  102. package/dist/main.js +5 -3
  103. package/dist/model-catalog.js +20 -0
  104. package/dist/patch-notes/4.0.0.md +13 -13
  105. package/dist/patch-notes/4.1.0.md +42 -0
  106. package/dist/patch-notes/index.json +6 -0
  107. package/dist/slices/admission/listing-facts.js +95 -0
  108. package/dist/slices/admission/repo.js +9 -6
  109. package/dist/slices/admission/rules.js +7 -7
  110. package/dist/slices/admission/state-words.js +34 -5
  111. package/dist/slices/assistant/cost.js +1 -1
  112. package/dist/slices/assistant/model.js +10 -3
  113. package/dist/slices/assistant/turn.js +5 -3
  114. package/dist/slices/autopilot/warnings.js +1 -1
  115. package/dist/slices/backups/service.js +5 -5
  116. package/dist/slices/channels/cast-images.js +1 -1
  117. package/dist/slices/channels/schema.js +1 -1
  118. package/dist/slices/channels/videos.js +1 -1
  119. package/dist/slices/control/providers.js +2 -2
  120. package/dist/slices/designs/budget.js +1 -1
  121. package/dist/slices/designs/calls.js +2 -2
  122. package/dist/slices/designs/check-context.js +4 -4
  123. package/dist/slices/designs/fields.js +4 -4
  124. package/dist/slices/designs/gate.js +7 -5
  125. package/dist/slices/designs/render-input.js +1 -1
  126. package/dist/slices/designs/requests.js +1 -1
  127. package/dist/slices/designs/runner.js +1 -1
  128. package/dist/slices/designs/sample.js +2 -1
  129. package/dist/slices/designs/service.js +1 -1
  130. package/dist/slices/designs/template-checks.js +2 -2
  131. package/dist/slices/designs/template-make.js +3 -3
  132. package/dist/slices/designs/templates.js +3 -3
  133. package/dist/slices/document/fonts.js +1 -1
  134. package/dist/slices/document/model.js +7 -4
  135. package/dist/slices/document/render.js +2 -1
  136. package/dist/slices/document/theme-schema.js +1 -1
  137. package/dist/slices/document/theme.js +49 -3
  138. package/dist/slices/episodes/summarize.js +2 -2
  139. package/dist/slices/estimate/index.js +28 -22
  140. package/dist/slices/estimate/requests.js +4 -4
  141. package/dist/slices/fixes/rules.js +3 -1
  142. package/dist/slices/fonts/catalog.js +2 -1
  143. package/dist/slices/fonts/coverage.js +2 -1
  144. package/dist/slices/images/appearance.js +2 -1
  145. package/dist/slices/images/scenes.js +2 -1
  146. package/dist/slices/keywords/digest.js +2 -1
  147. package/dist/slices/keywords/rules.js +2 -2
  148. package/dist/slices/keywords/specs.js +5 -5
  149. package/dist/slices/keywords/values.js +1 -1
  150. package/dist/slices/library/transfer.js +3 -3
  151. package/dist/slices/loudness/loudnorm.js +2 -1
  152. package/dist/slices/loudness/model.js +3 -2
  153. package/dist/slices/loudness/sentence-level.js +2 -1
  154. package/dist/slices/model-checks/rules.js +13 -0
  155. package/dist/slices/model-upkeep/model.js +1 -1
  156. package/dist/slices/model-upkeep/switch.js +3 -3
  157. package/dist/slices/model-upkeep/usage.js +13 -2
  158. package/dist/slices/narration/breaths.js +2 -1
  159. package/dist/slices/narration/clean-pieces.js +2 -1
  160. package/dist/slices/narration/pauses-model.js +3 -2
  161. package/dist/slices/narration/pronunciation.js +3 -2
  162. package/dist/slices/notifications/subject.js +2 -2
  163. package/dist/slices/onboarding/copy.js +19 -2
  164. package/dist/slices/onboarding/first-run.js +13 -38
  165. package/dist/slices/onboarding/install.js +2 -1
  166. package/dist/slices/onboarding/model.js +26 -21
  167. package/dist/slices/onboarding/preferred-model.js +14 -0
  168. package/dist/slices/onboarding/quick-short.js +103 -64
  169. package/dist/slices/onboarding/sample.js +4 -1
  170. package/dist/slices/onboarding/state.js +7 -0
  171. package/dist/slices/onboarding/template.js +6 -5
  172. package/dist/slices/outputs/add.js +3 -2
  173. package/dist/slices/outputs/service.js +2 -2
  174. package/dist/slices/patch-notes/library.js +1 -1
  175. package/dist/slices/play-drafts/readiness.js +3 -3
  176. package/dist/slices/play-drafts/review-inputs.js +1 -1
  177. package/dist/slices/play-drafts/service.js +17 -7
  178. package/dist/slices/project-templates/next-chapter.js +2 -1
  179. package/dist/slices/project-templates/provider-defaults.js +22 -0
  180. package/dist/slices/project-templates/setup-sections.js +26 -0
  181. package/dist/slices/project-templates/setup.js +6 -1
  182. package/dist/slices/rebuild/preview-details.js +5 -5
  183. package/dist/slices/rebuild/recipe-preparation.js +1 -1
  184. package/dist/slices/rebuild/recipe-save.js +2 -2
  185. package/dist/slices/rebuild/recipe-validation.js +5 -4
  186. package/dist/slices/rebuild/recipe-voices.js +3 -2
  187. package/dist/slices/rebuild/recipe-work.js +4 -4
  188. package/dist/slices/rebuild/recovery.js +6 -6
  189. package/dist/slices/rebuild/remake-outdated.js +5 -5
  190. package/dist/slices/rebuild/review-redo.js +1 -1
  191. package/dist/slices/rebuild/runtime-animate.js +2 -2
  192. package/dist/slices/rebuild/runtime-article.js +4 -3
  193. package/dist/slices/rebuild/runtime-document.js +5 -4
  194. package/dist/slices/rebuild/runtime-export-bed.js +2 -1
  195. package/dist/slices/rebuild/runtime-export-edit.js +2 -1
  196. package/dist/slices/rebuild/runtime-export-inputs.js +5 -4
  197. package/dist/slices/rebuild/runtime-export-short.js +1 -1
  198. package/dist/slices/rebuild/runtime-export.js +4 -3
  199. package/dist/slices/rebuild/runtime-image.js +4 -3
  200. package/dist/slices/rebuild/runtime-local.js +3 -2
  201. package/dist/slices/rebuild/runtime-narration-text.js +3 -2
  202. package/dist/slices/rebuild/runtime-provider.js +7 -6
  203. package/dist/slices/rebuild/runtime-review.js +6 -5
  204. package/dist/slices/rebuild/runtime-shorts.js +6 -5
  205. package/dist/slices/rebuild/runtime-store.js +17 -2
  206. package/dist/slices/rebuild/runtime-subtitles.js +13 -12
  207. package/dist/slices/rebuild/runtime-voices.js +2 -1
  208. package/dist/slices/rebuild/runtime-youtube.js +3 -2
  209. package/dist/slices/rebuild/service-readiness.js +11 -3
  210. package/dist/slices/research/synthesis.js +3 -2
  211. package/dist/slices/reviews/external-model.js +1 -1
  212. package/dist/slices/reviews/holds.js +5 -4
  213. package/dist/slices/reviews/model.js +8 -5
  214. package/dist/slices/reviews/rules.js +3 -3
  215. package/dist/slices/reviews/verdict.js +2 -1
  216. package/dist/slices/revisions/image-plan.js +1 -1
  217. package/dist/slices/revisions/mutation-assets.js +2 -1
  218. package/dist/slices/revisions/mutations.js +3 -2
  219. package/dist/slices/schedules/agenda.js +7 -5
  220. package/dist/slices/schedules/design-check.js +4 -4
  221. package/dist/slices/schedules/design-run.js +3 -3
  222. package/dist/slices/schedules/repo.js +6 -5
  223. package/dist/slices/schedules/schema.js +3 -0
  224. package/dist/slices/schedules/topics.js +5 -5
  225. package/dist/slices/settings/first-run.js +6 -32
  226. package/dist/slices/settings/health.js +30 -15
  227. package/dist/slices/settings/key-test.js +1 -1
  228. package/dist/slices/settings/model.js +4 -2
  229. package/dist/slices/settings/playback.js +2 -2
  230. package/dist/slices/shorts/keep.js +2 -1
  231. package/dist/slices/shorts/pick.js +4 -3
  232. package/dist/slices/shorts/prompts.js +2 -1
  233. package/dist/slices/shorts/render.js +3 -2
  234. package/dist/slices/social/media.js +5 -4
  235. package/dist/slices/social/pack.js +1 -1
  236. package/dist/slices/social/post-prompt.js +2 -1
  237. package/dist/slices/social/reshape.js +2 -2
  238. package/dist/slices/social/serve.js +1 -1
  239. package/dist/slices/social/service.js +3 -2
  240. package/dist/slices/social/state.js +1 -1
  241. package/dist/slices/storage/backup-import.js +2 -2
  242. package/dist/slices/storage/portable.js +1 -1
  243. package/dist/slices/storage/trim.js +23 -13
  244. package/dist/slices/studio/calendar.js +3 -1
  245. package/dist/slices/studio/disclosure.js +4 -4
  246. package/dist/slices/studio/model.js +1 -1
  247. package/dist/slices/studio/pack.js +9 -8
  248. package/dist/slices/studio/plan.js +1 -1
  249. package/dist/slices/studio/settings.js +6 -6
  250. package/dist/slices/studio/videos.js +23 -4
  251. package/dist/slices/style-preview/render.js +3 -3
  252. package/dist/slices/style-preview/schema.js +1 -1
  253. package/dist/slices/subtitles/captions.js +1 -1
  254. package/dist/slices/template-studio/anatomy-view.js +46 -3
  255. package/dist/slices/template-studio/bakeoff.js +3 -3
  256. package/dist/slices/template-studio/budget.js +6 -3
  257. package/dist/slices/template-studio/cards.js +78 -0
  258. package/dist/slices/template-studio/checks.js +99 -50
  259. package/dist/slices/template-studio/for-all.js +5 -5
  260. package/dist/slices/template-studio/free-video.js +1 -1
  261. package/dist/slices/template-studio/free.js +5 -5
  262. package/dist/slices/template-studio/model.js +19 -15
  263. package/dist/slices/template-studio/pack-import.js +14 -14
  264. package/dist/slices/template-studio/pack.js +9 -7
  265. package/dist/slices/template-studio/paid.js +12 -5
  266. package/dist/slices/template-studio/quote.js +18 -11
  267. package/dist/slices/template-studio/ready.js +7 -7
  268. package/dist/slices/template-studio/runner.js +1 -1
  269. package/dist/slices/template-studio/save.js +12 -12
  270. package/dist/slices/template-studio/say-it-as.js +3 -3
  271. package/dist/slices/template-studio/schedule-gate.js +1 -1
  272. package/dist/slices/template-studio/schedule-price.js +3 -1
  273. package/dist/slices/template-studio/schedule-sample.js +5 -1
  274. package/dist/slices/template-studio/schema.js +25 -0
  275. package/dist/slices/template-studio/service.js +3 -1
  276. package/dist/slices/template-studio/status.js +26 -9
  277. package/dist/slices/template-studio/topic.js +3 -3
  278. package/dist/slices/thumbnail/by-llm.js +1 -1
  279. package/dist/slices/uploads/repo.js +37 -1
  280. package/dist/slices/uploads/rules.js +3 -2
  281. package/dist/slices/video/edit-list.js +2 -2
  282. package/dist/slices/video/edit-settings.js +1 -1
  283. package/dist/slices/video/encoding.js +14 -5
  284. package/dist/slices/video/ffmpeg.js +2 -2
  285. package/dist/slices/video/plan.js +2 -1
  286. package/dist/slices/video/slideshow.js +1 -1
  287. package/dist/slices/voices/attribution.js +3 -2
  288. package/dist/slices/voices/model.js +9 -8
  289. package/dist/slices/voices/script.js +2 -1
  290. package/dist/slices/youtube/answer.js +2 -1
  291. package/dist/slices/youtube/edits-repo.js +1 -1
  292. package/dist/tutorials/AI-CLIs.md +25 -25
  293. package/dist/tutorials/Agents-MCP.md +32 -32
  294. package/dist/tutorials/Assistant.md +5 -5
  295. package/dist/tutorials/Backups.md +7 -7
  296. package/dist/tutorials/Calendar.md +17 -25
  297. package/dist/tutorials/Cast-Library.md +19 -19
  298. package/dist/tutorials/Channels.md +40 -35
  299. package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
  300. package/dist/tutorials/Create-Article.md +114 -0
  301. package/dist/tutorials/Create-Extras.md +112 -0
  302. package/dist/tutorials/Create-Images.md +165 -0
  303. package/dist/tutorials/Create-Narration.md +178 -0
  304. package/dist/tutorials/Create-Overview.md +244 -0
  305. package/dist/tutorials/Create-Video-and-Style.md +144 -0
  306. package/dist/tutorials/Docker.md +3 -3
  307. package/dist/tutorials/Document-Themes.md +32 -32
  308. package/dist/tutorials/Editing-a-Project.md +94 -94
  309. package/dist/tutorials/FAQ.md +18 -18
  310. package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
  311. package/dist/tutorials/Home-and-Projects.md +101 -76
  312. package/dist/tutorials/Home.md +33 -33
  313. package/dist/tutorials/Install.md +13 -13
  314. package/dist/tutorials/Intros-and-Outros.md +33 -34
  315. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
  316. package/dist/tutorials/Keywords-Slopify-Fills.md +21 -21
  317. package/dist/tutorials/Models.md +27 -27
  318. package/dist/tutorials/Multiple-Voices.md +41 -40
  319. package/dist/tutorials/Narration-Aliases-and-Glossary.md +14 -14
  320. package/dist/tutorials/Notifications.md +21 -17
  321. package/dist/tutorials/Other-Languages.md +15 -15
  322. package/dist/tutorials/PDF-Documents.md +38 -29
  323. package/dist/tutorials/Posting-to-Social-Sites.md +12 -12
  324. package/dist/tutorials/Project-Page.md +118 -120
  325. package/dist/tutorials/Prompts-and-Templates.md +84 -0
  326. package/dist/tutorials/Prompts.md +45 -45
  327. package/dist/tutorials/Providers-and-Keys.md +105 -98
  328. package/dist/tutorials/Publishing-to-YouTube.md +31 -31
  329. package/dist/tutorials/Recovery-and-Retries.md +31 -31
  330. package/dist/tutorials/Remote-Access.md +2 -2
  331. package/dist/tutorials/Reviews-and-Checkpoints.md +90 -93
  332. package/dist/tutorials/Schedules.md +27 -28
  333. package/dist/tutorials/Settings-Reference.md +182 -204
  334. package/dist/tutorials/Shorts.md +34 -33
  335. package/dist/tutorials/Start-at-Login.md +3 -3
  336. package/dist/tutorials/Studio-Extension.md +22 -24
  337. package/dist/tutorials/Template-Studio.md +45 -42
  338. package/dist/tutorials/Templates.md +50 -79
  339. package/dist/tutorials/Trash.md +4 -4
  340. package/dist/tutorials/Troubleshooting.md +44 -44
  341. package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
  342. package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
  343. package/dist/tutorials/Video-Editing.md +34 -34
  344. package/dist/tutorials/Where-Your-Files-Live.md +10 -10
  345. package/dist/tutorials/YouTube-Description.md +26 -26
  346. package/dist/tutorials/Your-First-Short.md +35 -35
  347. package/dist/tutorials/_Sidebar.md +15 -15
  348. package/dist/web/assets/index-BNftKQrA.css +1 -0
  349. package/dist/web/assets/index-Cpa58woP.js +459 -0
  350. package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
  351. package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
  352. package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
  353. package/dist/web/index.html +2 -2
  354. package/package.json +2 -2
  355. package/dist/tutorials/Library-Overview.md +0 -76
  356. package/dist/tutorials/Play-Images.md +0 -165
  357. package/dist/tutorials/Play-Narration.md +0 -167
  358. package/dist/tutorials/Play-Outputs.md +0 -109
  359. package/dist/tutorials/Play-Overview.md +0 -227
  360. package/dist/tutorials/Play-Title-and-Article.md +0 -136
  361. package/dist/tutorials/Play-Video-and-Style.md +0 -143
  362. package/dist/web/assets/index-CkAI_2E7.css +0 -1
  363. package/dist/web/assets/index-DcD0cdfo.js +0 -334
  364. package/dist/web/assets/pdf-TODRwF31.js +0 -54
  365. package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
  366. package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
@@ -1,4 +1,5 @@
1
1
  import { stageKinds } from "../../kernel/pipeline.js";
2
+ import { editSettings } from "../../kernel/ports/settings-places.js";
2
3
  import { sourceOf } from "../admission/model.js";
3
4
  import { defaultVoicesSettings } from "../voices/model.js";
4
5
  // Another output made from a project that already exists: the same brief, sources and
@@ -53,7 +54,7 @@ export function planAddedOutput(config, kind) {
53
54
  return {
54
55
  ok: false,
55
56
  reason: "narration-taken",
56
- message: `This project already has narration, and a project has one: ${kind === "podcast" ? "a podcast" : "an audiobook"} would replace it. Keep it, or change its format under Speakers in this project's Settings → Providers.`,
57
+ message: `This project already has narration, and a project has one: ${kind === "podcast" ? "a podcast" : "an audiobook"} would replace it. Keep it, or change its format under Speakers in ${editSettings.narration}.`,
57
58
  };
58
59
  const sources = switchedOn(config, kind);
59
60
  const next = {
@@ -78,7 +79,7 @@ export function planAddedOutput(config, kind) {
78
79
  reused: ["Brief: title, prompts and keywords", ...kept.map((stage) => stageResults[stage])],
79
80
  created: createdWords(kind, stages),
80
81
  textUse: kind === "podcast"
81
- ? "Adapted: the text model rewrites your article as a conversation between two hosts for the narration. The article stays as written; the conversation is kept beside it as the script."
82
+ ? "Adapted: the AI model rewrites your article as a conversation between two hosts for the narration. The article stays as written; the conversation is kept beside it as the script."
82
83
  : kind === "images"
83
84
  ? "Your text is not changed."
84
85
  : "Your text is used as written: nothing is rewritten.",
@@ -117,9 +117,9 @@ function newProblems(before, after) {
117
117
  .map((one) => ({ field: one.field, message: settingsHint(one) }));
118
118
  }
119
119
  function settingsHint(problem) {
120
- return /Settings|Edit project/.test(problem.message)
120
+ return /Settings|Edit settings/.test(problem.message)
121
121
  ? problem.message
122
- : `${problem.message} Set it in this project's Settings, save, then add the output again.`;
122
+ : `${problem.message} Set it in Edit settings, save, then add the output again.`;
123
123
  }
124
124
  function catalogueOf(deps) {
125
125
  return (deps.catalogue?.read() ?? {
@@ -4,7 +4,7 @@ import { join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { z } from "zod";
6
6
  import { hasErrnoCode } from "../../kernel/errors.js";
7
- // The patch notes the app shows in Settings → Patch notes: Markdown files kept in the
7
+ // The patch notes the app shows in Settings → What's new: Markdown files kept in the
8
8
  // repository's docs/patch-notes/ and copied into the build beside the compiled code
9
9
  // (scripts/copy-assets.mjs). index.json lists them with a title and a date; each note is
10
10
  // `<id>.md` beside it. Nothing here writes: the notes are part of the release.
@@ -63,7 +63,7 @@ export async function checkDraftReadiness(deps, runs) {
63
63
  field: c.field,
64
64
  message: provider !== undefined && provider.readiness.kind !== "keyed" && provider.readiness.issue
65
65
  ? provider.readiness.issue
66
- : "Set up this provider in Settings → Providers before starting.",
66
+ : "Set up this provider in Settings → Keys & services before starting.",
67
67
  });
68
68
  continue;
69
69
  }
@@ -88,12 +88,12 @@ export function localDraftReadiness(deps, runs, providers) {
88
88
  if (cliPathChanged(deps.db, provider))
89
89
  fields.push({
90
90
  field: `${c.field}.cliPath`,
91
- message: "This provider's command-line tool changed in Settings → Providers. Choose Review and start again.",
91
+ message: "This provider's command-line tool changed in Settings → Keys & services. Choose Review and start again.",
92
92
  });
93
93
  if (provider?.readiness.kind === "keyed" && !hasKey(deps.db, provider.id))
94
94
  fields.push({
95
95
  field: c.field,
96
- message: "Add this provider's API key in Settings → Providers before starting.",
96
+ message: "Add this provider's API key in Settings → Keys & services before starting.",
97
97
  });
98
98
  }
99
99
  for (const { draft } of runs)
@@ -161,7 +161,7 @@ unsaved = false) {
161
161
  : run.draft.sources[stage] !== "generate")
162
162
  fields.push({
163
163
  field: `checkpoints.${stage}`,
164
- message: "Checkpoints can only go before a step Slopify generates, or before export. Remove this checkpoint or turn that step on.",
164
+ message: "A pause can only go before a step Slopify makes, or before the video. Untick this pause or turn that step on.",
165
165
  });
166
166
  if (fields.length)
167
167
  return reviewRefusal(view, fields);
@@ -1,6 +1,7 @@
1
1
  import { statSync } from "node:fs";
2
2
  import { z } from "zod";
3
3
  import { transact } from "../../kernel/db/tx.js";
4
+ import { render } from "../admission/substitute.js";
4
5
  import { stagingPath } from "../storage/layout.js";
5
6
  import { stagedFileById } from "../storage/repo.js";
6
7
  import { releaseStagedFile } from "../storage/staging-refs.js";
@@ -114,14 +115,23 @@ export function readDraft(deps, id) {
114
115
  }),
115
116
  };
116
117
  }
118
+ // A draft is listed by its title as the run would have it, with the topic typed in ("Lore:
119
+ // Cleopatra", not "Lore: {{Topic}}"); a keyword still empty stays in braces.
117
120
  export function listDrafts(deps) {
118
- return draftRows(deps.db).map((row) => ({
119
- id: row.id,
120
- title: row.title,
121
- version: row.version,
122
- updatedAt: row.updated_at,
123
- readable: documentOf(row) !== null,
124
- }));
121
+ return draftRows(deps.db).map((row) => {
122
+ const document = documentOf(row);
123
+ return {
124
+ id: row.id,
125
+ title: document === null ? row.title : filledTitle(document),
126
+ version: row.version,
127
+ updatedAt: row.updated_at,
128
+ readable: document !== null,
129
+ };
130
+ });
131
+ }
132
+ function filledTitle(document) {
133
+ const { title, values } = document.form;
134
+ return render(title, Object.fromEntries(Object.entries(values).filter(([, value]) => value.trim() !== "")));
125
135
  }
126
136
  export function createDraft(deps, input) {
127
137
  const parsed = createDraftInputSchema.safeParse(input);
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { transact } from "../../kernel/db/tx.js";
3
+ import { createRows } from "../../kernel/ports/settings-places.js";
3
4
  import { projectById } from "../admission/repo.js";
4
5
  import { projectChannelId } from "../channels/repo.js";
5
6
  import { draftRow } from "../play-drafts/repo.js";
@@ -55,7 +56,7 @@ export function makeNextChapter(deps, input) {
55
56
  return {
56
57
  ok: false,
57
58
  reason: "not-an-audiobook",
58
- message: "Only an audiobook has chapters. Pick Audiobook under Speakers (Play → Narration) to start a book.",
59
+ message: `Only an audiobook has chapters. Pick Audiobook as the Format under Speakers (${createRows.narration}) to start a book.`,
59
60
  };
60
61
  const book = nextBook(revision.config.title, voices.book);
61
62
  const document = documentFromProject(deps, revision);
@@ -0,0 +1,22 @@
1
+ // A template nobody picked a writer or an image service in runs on this computer's defaults,
2
+ // kind by kind, by the same rule Create applies to a draft (web `play/draft-state.ts`
3
+ // withProviderDefaults): a writer nobody picked takes the default writer even where the images
4
+ // were picked, and the other way round; a choice someone made is never changed. Every place a
5
+ // template runs or is checked reads it through here (a draft made from it, a schedule's run, a
6
+ // sample, Edit template's checks and editors), so none of them can disagree about whether it
7
+ // can start.
8
+ export function withProviderDefaults(document, defaults) {
9
+ const { form } = document;
10
+ const llm = form.llm.provider === "" ? defaults.llm : undefined;
11
+ const images = form.images.provider === "" ? defaults.images : undefined;
12
+ if (llm === undefined && images === undefined)
13
+ return document;
14
+ return {
15
+ ...document,
16
+ form: {
17
+ ...form,
18
+ ...(llm === undefined ? {} : { llm: { ...llm } }),
19
+ ...(images === undefined ? {} : { images: { ...images } }),
20
+ },
21
+ };
22
+ }
@@ -0,0 +1,26 @@
1
+ // One setup, three editors: Create, Edit template and a project's Edit settings show the same
2
+ // setup, so they name its sections alike and list them in this order, the order a run makes
3
+ // them. Each editor keeps its own data model and its own ids; only the names and the order
4
+ // come from here. Browser-safe: the web app imports it too.
5
+ export const setupSectionNames = {
6
+ topic: "Title and keywords",
7
+ article: "Article",
8
+ narration: "Narration",
9
+ images: "Images",
10
+ video: "Video and style",
11
+ thumbnail: "Thumbnail",
12
+ shorts: "Shorts",
13
+ pdf: "PDF",
14
+ checks: "Pauses and checks",
15
+ youtube: "YouTube description and posts",
16
+ schedule: "Schedule",
17
+ };
18
+ // In run order.
19
+ export const setupSections = Object.keys(setupSectionNames);
20
+ // Create and a project's Edit settings keep the optional outputs together in one section, where
21
+ // Edit template gives each its own: the thumbnail, the shorts, the PDF and the YouTube
22
+ // description and posts.
23
+ export const extrasName = "Extras";
24
+ // Last in Create and a project's Edit settings: the channel the video is for. A template has no
25
+ // section for it; its schedules and Create pick the channel.
26
+ export const channelName = "Channel";
@@ -1,7 +1,9 @@
1
1
  import { templateChannelId } from "../channels/repo.js";
2
2
  import { snapshotEntry, snapshotPrompt } from "../library/snapshot.js";
3
+ import { readProviderDefaults } from "../settings/first-run.js";
3
4
  import { socialPlatforms } from "../social/model.js";
4
5
  import { templateDocument } from "./one-off.js";
6
+ import { withProviderDefaults } from "./provider-defaults.js";
5
7
  export function templateSetup(deps, input) {
6
8
  const prompts = [];
7
9
  const entries = [];
@@ -58,7 +60,10 @@ export function templateSetup(deps, input) {
58
60
  value: { ...document, fontUpload: null, librarySnapshot: { prompts, entries } },
59
61
  };
60
62
  }
61
- export function freshTemplateDraft(deps, document, source) {
63
+ // A draft made from a template, for Create or a schedule's run. A template nobody picked a writer
64
+ // or image service in gets this computer's defaults, as Edit template's checks expect.
65
+ export function freshTemplateDraft(deps, saved, source) {
66
+ const document = withProviderDefaults(saved, readProviderDefaults(deps.db));
62
67
  const provided = document.form.provided;
63
68
  const fresh = (file) => ({
64
69
  attachmentId: deps.uuid(),
@@ -72,7 +72,7 @@ export function previewDetails(deps, view, allRecipes, selected) {
72
72
  if (input.kind === "llm") {
73
73
  const peers = allRecipes.filter((one) => one.stage === row.stage && one.input.kind === "llm");
74
74
  const category = input.preparation !== undefined
75
- ? "Narration Preparation"
75
+ ? "Narration preparation"
76
76
  : row.key.startsWith("entry:intro")
77
77
  ? "Intro text"
78
78
  : row.key.startsWith("entry:outro")
@@ -154,9 +154,9 @@ function inputChanges(parent, view) {
154
154
  }
155
155
  add("Language", languageInfo(before.language).name, languageInfo(after.language).name);
156
156
  add("Narration voice", before.audio?.voice, after.audio?.voice);
157
- add("Narration Preparation", before.narrationPrompt, after.narrationPrompt);
157
+ add("Narration preparation", before.narrationPrompt, after.narrationPrompt);
158
158
  add("Narration chunking", before.chunking, after.chunking);
159
- add("Narration aliases", before.audio?.useNarrationAliases === true ? aliasLines(before.narrationAliases) : "Off", after.audio?.useNarrationAliases === true ? aliasLines(after.narrationAliases) : "Off");
159
+ add("Pronunciation list", before.audio?.useNarrationAliases === true ? aliasLines(before.narrationAliases) : "Off", after.audio?.useNarrationAliases === true ? aliasLines(after.narrationAliases) : "Off");
160
160
  const described = (audio) => audio?.describeFigures !== true ? "Off" : audio.skipCode === true ? "On, code left out" : "On";
161
161
  add("Describe tables and figures", described(before.audio), described(after.audio));
162
162
  add("Show tables and figures on screen", before.showFigures === true ? "On" : "Off", after.showFigures === true ? "On" : "Off");
@@ -215,11 +215,11 @@ function inputChanges(parent, view) {
215
215
  add(label, value, editAfter.get(label));
216
216
  add("Intro", before.intro, after.intro);
217
217
  add("Outro", before.outro, after.outro);
218
- add("Subtitle settings", before.subtitles, after.subtitles);
218
+ add("Caption settings", before.subtitles, after.subtitles);
219
219
  const themeBefore = documentThemeLabel(before.document);
220
220
  const themeAfter = documentThemeLabel(after.document);
221
221
  // A Library theme saved again under the same name still changes the document.
222
- add("Document theme", themeBefore, themeBefore === themeAfter &&
222
+ add("PDF look", themeBefore, themeBefore === themeAfter &&
223
223
  JSON.stringify(before.document?.custom?.values) !==
224
224
  JSON.stringify(after.document?.custom?.values)
225
225
  ? `${themeAfter} (updated settings)`
@@ -38,7 +38,7 @@ export function preparationForGroup(context, logicalKey, source, segment, depend
38
38
  return {
39
39
  preparation,
40
40
  requests: null,
41
- refusal: "Narration Preparation needs non-empty narration text.",
41
+ refusal: "Narration preparation needs narration text to work from.",
42
42
  };
43
43
  const logical = recipe(context, preparation.key, "audio", preparation.input.kind === "llm"
44
44
  ? { ...preparation.input, thinkingConfig: null }
@@ -286,7 +286,7 @@ export function validateRevisionEdit(config, content) {
286
286
  if (config.sources.images !== "off" && content.imageOrder.length === 0)
287
287
  fields.push({
288
288
  field: "content.imageOrder",
289
- message: "Every image is deleted, so the video has nothing to show. Press Discard changes to get them back, then press Regenerate all on the Images stage to make them again. To make the project without pictures, set Images and Video to Off on Inputs instead.",
289
+ message: "Every image is deleted, so the video has nothing to show. Press Discard changes to get them back, then press Make all images again in the project's Images section. To make the project without pictures, set Images source and Video source to Off in Edit settings instead.",
290
290
  });
291
291
  if (config.sources.images === "off" && config.sources.video !== "off")
292
292
  fields.push({ field: "sources.video", message: "Turn Video off when Images is off." });
@@ -328,7 +328,7 @@ export function validateRevisionEdit(config, content) {
328
328
  if (config.sources.audio === "off" &&
329
329
  config.subtitles !== undefined &&
330
330
  config.subtitles.mode !== "off")
331
- fields.push({ field: "subtitles.mode", message: "Subtitles need narration audio." });
331
+ fields.push({ field: "subtitles.mode", message: "Captions need narration audio." });
332
332
  fields.push(...voicesFields(config));
333
333
  return fields;
334
334
  }
@@ -1,3 +1,4 @@
1
+ import { editSettings } from "../../kernel/ports/settings-places.js";
1
2
  import { narrationPreparationFields, shortsFields, usesNarrationPreparation, usesReference, usesShorts, usesYoutubeDescription, videoEditFields, youtubeDescriptionFields, } from "../admission/rules.js";
2
3
  import { detectSlots, filledKeywords, render } from "../admission/substitute.js";
3
4
  import { imagesPerVideoMax } from "../images/scale.js";
@@ -33,10 +34,10 @@ export function validateRecipeInputs(config, content) {
33
34
  // The keyword digest and the scene keywords are written by the text model too.
34
35
  Object.keys(keywordUsesOf(keywordBodies(config, content))).some((cost) => cost === "digest" || cost === "scenes");
35
36
  if (llm && (!config.llm?.provider.trim() || !config.llm.model.trim()))
36
- fields.push({ field: "llm", message: "Pick a text provider and model." });
37
+ fields.push({ field: "llm", message: "Choose a Service and a Model under Written by." });
37
38
  if (config.sources.audio === "generate") {
38
39
  if (!config.audio?.provider.trim() || !config.audio.model.trim())
39
- fields.push({ field: "audio", message: "Pick a narration provider and model." });
40
+ fields.push({ field: "audio", message: "Choose a Service and a Model under Read by." });
40
41
  else if (!config.audio.voice.trim())
41
42
  fields.push({ field: "audio.voice", message: "Pick a narration voice." });
42
43
  }
@@ -48,7 +49,7 @@ export function validateRecipeInputs(config, content) {
48
49
  usesShorts(config) ||
49
50
  usesAnimation(config)) &&
50
51
  (!config.images?.provider.trim() || !config.images.model.trim()))
51
- fields.push({ field: "images", message: "Pick an image provider and model." });
52
+ fields.push({ field: "images", message: "Choose a Service and a Model under Images by." });
52
53
  // 60, or more for a project that scales its images with the narration (`images/scale.ts`).
53
54
  const imagesMax = imagesPerVideoMax(config);
54
55
  if (content.imageOrder.length > imagesMax)
@@ -91,7 +92,7 @@ export function validateRecipeInputs(config, content) {
91
92
  if (config.keywordFacts === undefined && usesFacts([text]))
92
93
  fields.push({
93
94
  field: prompt.field,
94
- message: "This prompt uses a keyword Slopify fills from the channel and its videos, but this project has no keyword facts yet. Use Refresh keyword facts in Edit project → Prompts, then save again.",
95
+ message: `This prompt uses a keyword Slopify fills from the channel and its videos, but this project has no keyword facts yet. Use Refresh keyword facts in ${editSettings.title}, then save again.`,
95
96
  });
96
97
  if (prompt.raw === null)
97
98
  continue;
@@ -1,4 +1,5 @@
1
1
  import { aliasMatches, applyAliases } from "../../kernel/ports/narration-aliases.js";
2
+ import { createRows, editSettings } from "../../kernel/ports/settings-places.js";
2
3
  import { splitText } from "../../kernel/ports/text.js";
3
4
  import { narrationAliasesOf, usesNarrationPreparation } from "../admission/rules.js";
4
5
  import { aliasSpans, withAliasSpans } from "../narration/aliases.js";
@@ -83,7 +84,7 @@ export function voiceBodyRecipes(context, voices, script, glossary) {
83
84
  parts.push(future());
84
85
  }
85
86
  else if (parsed !== undefined && !parsed.ok) {
86
- parts.push(refused(context, "audio:body:turn:1", script.dependsOn, `The script can't be narrated: ${parsed.reason} Fix it in Edit project → Article, then Try again.`));
87
+ parts.push(refused(context, "audio:body:turn:1", script.dependsOn, `The script can't be narrated: ${parsed.reason} Fix it in ${editSettings.article}, then Try again.`));
87
88
  }
88
89
  else if (pending) {
89
90
  parts.push(future());
@@ -251,7 +252,7 @@ function speakerGlossary(context, speaker, glossary) {
251
252
  if (!own.ok)
252
253
  return {
253
254
  ok: false,
254
- reason: `${speaker.name.trim()}'s pronunciations can't be used: ${own.reason.replace("Edit the glossary or turn off Use Pronunciation Glossary.", "Fix them under Speakers (Play → Narration, or Edit project → Providers).")}`,
255
+ reason: `${speaker.name.trim()}'s pronunciations can't be used: ${own.reason.replace("Edit the glossary or turn off Use Pronunciation Glossary.", `Fix them under Speakers (${createRows.narration}, or ${editSettings.narration}).`)}`,
255
256
  };
256
257
  const run = context.config.audio?.usePronunciationGlossary === true && glossary?.ok === true
257
258
  ? glossary.entries
@@ -255,7 +255,7 @@ config) {
255
255
  model: typeof llmModel === "string" ? llmModel : "",
256
256
  inputCharacters: (typeof style === "string" ? style.length : 0) + 3000,
257
257
  outputCharacters: 2400,
258
- detail: "One LLM call per short writes its image prompts; its length is estimated.",
258
+ detail: "One AI call per short writes its image prompts; its length is estimated.",
259
259
  })),
260
260
  ...Array.from({ length: images }, () => ({
261
261
  kind: "image",
@@ -312,13 +312,13 @@ export function priceRecipe(work, value, config) {
312
312
  return {
313
313
  kind: "unknown",
314
314
  stage: work.key,
315
- detail: "Narration Preparation: one LLM call per future logical chunk or entry; source length is not yet known.",
315
+ detail: "Narration preparation: one AI call per narration piece or intro or outro; how many is known once the text is written.",
316
316
  };
317
317
  if (input.kind === "deferred" && input.operation === "narration-description")
318
318
  return {
319
319
  kind: "unknown",
320
320
  stage: work.key,
321
- detail: "Narration descriptions: one LLM call per table, figure, equation or code block in the article; how many is known once the article is written.",
321
+ detail: "Narration descriptions: one AI call per table, figure, equation or code block in the article; how many is known once the article is written.",
322
322
  };
323
323
  if (input.kind === "llm")
324
324
  return {
@@ -421,7 +421,7 @@ function cliRequest(work, value, config) {
421
421
  detail: preparation
422
422
  ? value.input.operation === "narration-description"
423
423
  ? "Runs on your CLI plan, so it adds no charge. Narration descriptions make one call per table, figure, equation or code block; how many is known once the article is written, so no API figure is given."
424
- : "Runs on your CLI plan, so it adds no charge. Narration Preparation makes one call per logical chunk or entry; how many is known when the step runs, so no API figure is given."
424
+ : "Runs on your CLI plan, so it adds no charge. Narration preparation makes one call per narration piece or intro or outro; how many is known when the step runs, so no API figure is given."
425
425
  : "Runs on your CLI plan, so it adds no charge. The request is built when the step runs, so its API figure uses an estimated length.",
426
426
  };
427
427
  }
@@ -79,7 +79,7 @@ options = {}) {
79
79
  field: "stage",
80
80
  message: input.action.kind === "redo"
81
81
  ? "This item is no longer made by the project (it was removed, or its source changed), so it can't be made again. Reload the project page."
82
- : "This section has no generated work to rerun. Use Edit project to change its source.",
82
+ : "This section has nothing made by AI to make again. Change its source in Edit settings.",
83
83
  },
84
84
  ],
85
85
  };
@@ -121,7 +121,7 @@ options = {}) {
121
121
  {
122
122
  field: "stage",
123
123
  message: late === "running"
124
- ? "Wait for this section to finish, or Pause the project before rerunning it."
124
+ ? "Wait for this section to finish, or Pause the project before making it again."
125
125
  : acceptedJobMessage,
126
126
  },
127
127
  ],
@@ -154,7 +154,7 @@ options = {}) {
154
154
  fields: [
155
155
  {
156
156
  field: "stage",
157
- message: "This section has no enabled work. Use Edit project to change its source.",
157
+ message: "This section is switched off. Change its source in Edit settings.",
158
158
  },
159
159
  ],
160
160
  });
@@ -167,7 +167,7 @@ options = {}) {
167
167
  reason: "review-required",
168
168
  fields: preview.value.preview.providedReuseRequired.map((key) => ({
169
169
  field: key,
170
- message: "Review changed supplied content or manual captions in Edit project or Advanced rebuild review.",
170
+ message: "Look over the changed content you supplied, or the captions you edited, in Edit settings or in Choose what to make again.",
171
171
  })),
172
172
  });
173
173
  const blocked = preview.value.preview.work.filter((row) => row.disposition === "blocked");
@@ -187,11 +187,11 @@ options = {}) {
187
187
  input.action.stage === "video"
188
188
  ? {
189
189
  field: "video",
190
- message: "Source media is incomplete. Use Continue the run to recover it before rerunning the export.",
190
+ message: "Some of what the video is made from isn't finished. Use Continue the run to finish it before making the video again.",
191
191
  }
192
192
  : {
193
193
  field: "document",
194
- message: "The article or thumbnail the document is made from isn't finished. Use Continue the run to finish it, then More → make it again in its section on Document.",
194
+ message: "The article or thumbnail the document is made from isn't finished. Use Continue the run to finish it, then press Make the PDF again in the project's PDF section.",
195
195
  },
196
196
  ],
197
197
  });
@@ -3,19 +3,19 @@ export function remadeWork(work) {
3
3
  }
4
4
  export function remakeBlocker(status, sample) {
5
5
  if (status === "running")
6
- return "It is running now. Remake it once the run has finished.";
6
+ return "It is running now. Make its outdated parts again once the run has finished.";
7
7
  if (sample)
8
- return "It is a sample project, which stays as it came with Slopify. Open it, press Make my own copy at the top of its page, then remake the copy.";
8
+ return "It is a sample project, which stays as it came with Slopify. Open it, press Make my own copy at the top of its page, then change the copy.";
9
9
  return undefined;
10
10
  }
11
11
  export function previewBlocker(preview, label) {
12
12
  if (remadeWork(preview.work).length === 0)
13
- return "Nothing in it is outdated, so there is nothing to remake.";
13
+ return "Nothing in it is outdated, so there is nothing to make again.";
14
14
  const blocked = preview.work.find((work) => work.disposition === "blocked");
15
15
  if (blocked !== undefined)
16
- return `${label(blocked.key)} can't run: ${blocked.reason.trim() || "no reason was given."} Open the project and press Choose what to remake to see what to fix.`;
16
+ return `${label(blocked.key)} can't run: ${blocked.reason.trim() || "no reason was given."} Open the project and press Choose what to make again to see what to fix.`;
17
17
  if (preview.providedReuseRequired.length > 0)
18
- return "It uses content you supplied, which you confirm on the project itself: open it and press Choose what to remake.";
18
+ return "It uses content you supplied, which you confirm on the project itself: open it and press Choose what to make again.";
19
19
  return undefined;
20
20
  }
21
21
  export function remakeTotal(costs) {
@@ -128,7 +128,7 @@ export function redoRefusal(result) {
128
128
  case "running":
129
129
  return "Slopify couldn't make this item again automatically because work that depends on it was already running. Use Redo beside it once the run has finished.";
130
130
  case "readiness":
131
- return `Slopify couldn't make this item again automatically: ${detail ?? "a provider isn't ready"}. Fix that in Settings → Providers, then use Redo beside it.`;
131
+ return `Slopify couldn't make this item again automatically: ${detail ?? "a provider isn't ready"}. Fix that in Settings → Keys & services, then use Redo beside it.`;
132
132
  case "conflict":
133
133
  case "control-changed":
134
134
  return "Slopify couldn't make this item again automatically because the project changed at the same moment (an edit, Pause or Cancel). Use Redo beside it.";
@@ -36,7 +36,7 @@ export async function executeAnimateRecipe(deps, context, providers, piece) {
36
36
  return "done";
37
37
  };
38
38
  if (still === undefined)
39
- return fallback("its image wasn't ready to animate. Use More → Render the video again in the Video section after the image is made.");
39
+ return fallback("its image wasn't ready to animate. Press Make the video again in the project's Video section after the image is made.");
40
40
  const animate = providers.forPiece(piece.id).animate;
41
41
  if (animate === undefined)
42
42
  throw new Error(internalError("animating images isn't wired up in this build"));
@@ -56,7 +56,7 @@ export async function executeAnimateRecipe(deps, context, providers, piece) {
56
56
  catch (error) {
57
57
  if (context.signal.aborted)
58
58
  throw error;
59
- return fallback(`${errorText(error)} To try again, use More → Render the video again in the Video section.`);
59
+ return fallback(`${errorText(error)} To try again, press Make the video again in the project's Video section.`);
60
60
  }
61
61
  if (!made.ok)
62
62
  return "held";
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { transact } from "../../kernel/db/tx.js";
3
3
  import { internalError } from "../../kernel/errors.js";
4
+ import { editSettings } from "../../kernel/ports/settings-places.js";
4
5
  import { continuationLimit, continuationMessages } from "../article/continuation.js";
5
6
  import { currentRevisionId, revisionById } from "../revisions/repo.js";
6
7
  import { recipe } from "./recipe-model.js";
@@ -39,9 +40,9 @@ export async function executeArticleRequests(deps, context, providers, initial)
39
40
  webSearch: input.webSearch,
40
41
  previewLabel: part === 0 ? "Article" : `Continuation ${part}`,
41
42
  check: (value) => value.text.trim() === ""
42
- ? "The AI model returned an empty article. Try again; if it keeps happening, choose a different model in Edit project → Providers."
43
+ ? `The AI model returned an empty article. Try again; if it keeps happening, choose a different model in ${editSettings.writer}.`
43
44
  : part === continuationLimit && value.finishReason === "length"
44
- ? `The article was still unfinished after ${continuationLimit} continuations because the AI model kept stopping at its length limit. Ask for a shorter article in Edit project → Prompts, or choose a model with a larger output limit in Edit project → Providers, then Try again.`
45
+ ? `The article was still unfinished after ${continuationLimit} continuations because the AI model kept stopping at its length limit. Ask for a shorter article in the article prompt in ${editSettings.article}, or choose a model with a larger output limit under Written by there, then Try again.`
45
46
  : undefined,
46
47
  }, (event) => {
47
48
  if (event.type === "delta")
@@ -73,7 +74,7 @@ export async function executeArticleRequests(deps, context, providers, initial)
73
74
  return { ok: false, reason: "held" };
74
75
  piece = next;
75
76
  }
76
- throw new Error(`The article was still unfinished after ${continuationLimit} continuations because the AI model kept stopping at its length limit. Ask for a shorter article in Edit project → Prompts, or choose a model with a larger output limit in Edit project → Providers, then Try again.`);
77
+ throw new Error(`The article was still unfinished after ${continuationLimit} continuations because the AI model kept stopping at its length limit. Ask for a shorter article in the article prompt in ${editSettings.article}, or choose a model with a larger output limit under Written by there, then Try again.`);
77
78
  }
78
79
  function continuationPiece(deps, context, initial, part, text) {
79
80
  return transact(deps.db, () => {
@@ -1,5 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { errorText, internalError } from "../../kernel/errors.js";
3
+ import { editSettings } from "../../kernel/ports/settings-places.js";
3
4
  import { readDocumentAssets } from "../document/fonts.js";
4
5
  import { renderDocument } from "../document/render.js";
5
6
  import { resolvedDocumentTheme } from "../document/theme.js";
@@ -29,7 +30,7 @@ export async function executeDocumentRecipe(deps, context, piece) {
29
30
  ? view.articleMarkdown
30
31
  : text("article_md", "article:body");
31
32
  if (article === null)
32
- throw new Error("The article isn't finished yet, so there's nothing to make the document from. Let the Article stage finish (Resume, or Try again on Article), then use Try again on Document.");
33
+ throw new Error("The article isn't finished yet, so there's nothing to make the document from. Let the Article stage finish (Continue the run, or Try again on Article), then use Try again on PDF.");
33
34
  const thumbnail = ready(view, "thumbnail", "thumbnail:image");
34
35
  let cover = null;
35
36
  if (config.sources.thumbnail !== "off" && thumbnail !== undefined) {
@@ -37,7 +38,7 @@ export async function executeDocumentRecipe(deps, context, piece) {
37
38
  cover = new Uint8Array(readFileSync(outputPath(deps.paths, projectId, thumbnail.output.path)));
38
39
  }
39
40
  catch (error) {
40
- throw new Error(`The document's cover is the project's thumbnail, and its file can't be read (${errorText(error)}). Regenerate or upload the thumbnail again (More → make it again in its section on Thumbnail, or Edit project → Thumbnail), then use Try again on Document.`, { cause: error });
41
+ throw new Error(`The document's cover is the project's thumbnail, and its file can't be read (${errorText(error)}). Make the thumbnail again in the project's Images section, or upload it again in ${editSettings.extras}, then use Try again on PDF.`, { cause: error });
41
42
  }
42
43
  }
43
44
  const rendered = renderDocument({
@@ -70,10 +71,10 @@ function documentView(deps, context, piece) {
70
71
  .prepare("SELECT recipe_context FROM revision_work WHERE id=?")
71
72
  .get(context.work.workId);
72
73
  if (view === undefined || row === undefined)
73
- throw new Error(internalError("the project version the document belongs to is missing", "Use Try again on Document"));
74
+ throw new Error(internalError("the project version the document belongs to is missing", "Use Try again on PDF"));
74
75
  const recipe = executionPlan(deps, view, savedCatalogue(row.recipe_context)).recipes.find((one) => one.key === piece.key && one.fingerprint === piece.fingerprint);
75
76
  if (recipe === undefined || recipe.unresolved)
76
- throw new Error("The project changed after this document was queued, so it no longer matches the saved article. Use Choose what to remake (in the project's More menu) (or Try again on Document) to make it from the current version.");
77
+ throw new Error("The project changed after this document was queued, so it no longer matches the saved article. Use Choose what to make again (in the project's More menu) (or Try again on PDF) to make it from the current version.");
77
78
  return view;
78
79
  }
79
80
  function ready(view, role, workKey) {
@@ -1,4 +1,5 @@
1
1
  import { errorText } from "../../kernel/errors.js";
2
+ import { editSettings } from "../../kernel/ports/settings-places.js";
2
3
  import { assetPathOrUndefined } from "../revisions/mutation-assets.js";
3
4
  import { outputPath } from "../storage/layout.js";
4
5
  import { usesAmbientBed } from "../video/ambient-bed.js";
@@ -18,7 +19,7 @@ export async function exportBed(deps, context, view) {
18
19
  };
19
20
  if (bed.source !== "upload")
20
21
  return { source: { kind: "noise", preset: bed.source }, ...settings };
21
- const fix = "Make the video again from Play with another file under Video and style → Ambient sound, or pick Rain, Fireplace or Wind there instead.";
22
+ const fix = `Choose another file under Ambient sound in ${editSettings.sound}, or pick Rain, Fireplace or Wind there instead.`;
22
23
  const assetId = view.revision.content.ambientBed;
23
24
  const saved = assetId === undefined ? undefined : assetPathOrUndefined(deps, context.work.projectId, assetId);
24
25
  if (saved === undefined)
@@ -1,5 +1,6 @@
1
1
  import { extname } from "node:path";
2
2
  import { z } from "zod";
3
+ import { editSettings } from "../../kernel/ports/settings-places.js";
3
4
  import { usesFigureCards, usesYoutubeDescription } from "../admission/rules.js";
4
5
  import { captionFont, captionFontDeps } from "../fonts/coverage.js";
5
6
  import { resolveFont } from "../fonts/index.js";
@@ -135,7 +136,7 @@ async function clipSeconds(deps, context, path, place) {
135
136
  ms = 0;
136
137
  }
137
138
  if (!(ms > 0))
138
- throw new Error(`Image ${String(place)} is a video clip Slopify can't read, so the video can't show it. Replace it with another clip or an image in Edit project → Images, then Try again.`);
139
+ throw new Error(`Image ${String(place)} is a video clip Slopify can't read, so the video can't show it. Replace it with another clip or an image in ${editSettings.images}, then Try again.`);
139
140
  return ms / 1000;
140
141
  }
141
142
  // Each card in the video's frame, shown where its description is spoken: the description's