@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
@@ -0,0 +1,42 @@
1
+ # Slopify 4.1.0
2
+
3
+ Released 7 November 2026.
4
+
5
+ This release is about making Slopify easy to use without reading anything first: every button says what it does, each screen shows more and wastes less space, and the same thing has the same name everywhere.
6
+
7
+ ## Finding your way around
8
+
9
+ - **A new rail.** The left rail is narrow, with a word under every icon: Create, Home, Projects, Prompts, Templates, Calendar, Channels (once you have two or more) and Settings, then Search, Assistant, Help and Support Slopify at the foot. On a phone the bottom bar has Create in the middle.
10
+ - **Library is split in two.** **Prompts** holds your prompts and intros and outros, with every kind listed down the left. **Templates** holds video and poster templates. PDF looks are under Settings → Production defaults and Create's PDF choice, Pronunciation is under Settings → Voices, and What worked on YouTube is on the channel page. Old addresses still open the right page.
11
+ - The channel picker reads **Showing: All channels** in the header of Home, Projects and Calendar, and only appears with two or more channels.
12
+
13
+ ## Your first video
14
+
15
+ - The welcome is a checklist of what a short needs (Article, Narration, Images), each with its state and one button to fix it. The usage notice is a single line at the bottom of the window instead of a dialog, and start at login is asked once.
16
+ - **Create is one screen.** Pick what you're making, type the topic (the box has the cursor already) and press **Make the video**. Each part of the run is one sentence with Change, and anything missing is named once, with Fix. On a phone the Make button stays pinned within reach.
17
+ - **New prompt** opens beside Create and picks the prompt when you save, so you never leave the page. If you skipped the starter templates, Create offers them.
18
+ - **Everything this run will do** shows the whole run in plain sentences before you start.
19
+
20
+ ## A finished project
21
+
22
+ - The video, its downloads, the chapters, the shorts and every stage's status are on the first screen. Each section (Article, Narration, Images, Video, Shorts, YouTube, PDF, All files) leads with what it made and a named download.
23
+ - **Edit settings** uses Create's sections and names. **Versions** replaces History.
24
+ - Every action is a visible button: Mark uploaded, Rename, Save as template and Delete. Paid work says so on its button ("Make this image again").
25
+
26
+ ## Lists and settings
27
+
28
+ - **Home** shows your finished videos first, with Watch and Download, then what needs you and what's running, step by step.
29
+ - **Projects** rows show a picture, the length, what was made and when, with Open and Download.
30
+ - **Templates** are cards that say what each one makes, with **Make a video** and **Edit template**. Edit template's sections match Create's.
31
+ - **Prompts** searches every kind at once. The editor reads like text, **Insert a keyword** shows exactly what goes in, and every kind has examples to start from.
32
+ - **Settings** is one list of pages. **Keys & services** opens on three lines (who writes, who reads, who draws), each Ready or with a button to set it up. Trash has its own page, and Projects and Prompts show **Trash (n)** when something is in it.
33
+
34
+ ## One word per thing
35
+
36
+ - Captions (not subtitles), Narration, Voice, Images, Make … again (not Remake or Regenerate), Copy for YouTube Studio, Pauses and checks, PDF looks, and US spelling throughout.
37
+ - Info buttons are gone where a short line under the setting says it better. A status dot always has its word next to it, and each area has at most one lime button.
38
+
39
+ ## Fixes
40
+
41
+ - Typing the topic the moment Create opens no longer drops the starter template's writer and image service.
42
+ - Make the video is ready about a second after you stop typing, instead of three or four.
@@ -0,0 +1,28 @@
1
+ # Slopify 4.2.0
2
+
3
+ Released 8 November 2026.
4
+
5
+ This release is about names: deciding once how each one is said, and finding out — before you pay for a narration, and before you upload — whether the video said them right.
6
+
7
+ ## One list decides how a name is said
8
+
9
+ - **Settings → Voices → Name pronunciations** takes a Pronunciation Glossary of your own, one `Term: /IPA/` per line, and its names come before the glossary each article writes. A name listed there sounds the same in every video, however that video's article spelled it out, so `Gygax` never drifts to `Gigax` between episodes. The page says how many names the list pronounces and names any line narration has to skip, by number and reason.
10
+ - **Each project keeps the copy it started with,** like the narration aliases, so editing the list never changes a project behind its back. **Edit settings → Narration → Use pronunciations → Copy the list again** brings the new list in, and only the narration where a changed name appears is made again.
11
+ - **The order, where two disagree:** an alias (it changes the words), then your Name pronunciations, then the article's own Pronunciation Glossary, then other projects' pronunciations while they are shared.
12
+ - **"Use the article's pronunciation glossary" is now "Use pronunciations",** because the one switch covers both your list and the article's glossary. Inworld TTS-2 and TTS-2 Flash read IPA, as before; for any other voice an alias is still the way to fix a word.
13
+ - **One save never replaces another.** Each save carries the version of the list it was made from. When a second window or an agent saved meanwhile, the save is refused with the list as it stands: the page names what changed, keeps what you typed, and offers **Save mine anyway** or **Load theirs instead**.
14
+
15
+ ## Does this video say the names right?
16
+
17
+ Two checks on the project page, over files it already has. Neither blocks the run or the upload, neither costs anything, and neither changes a thing.
18
+
19
+ - **Before the narration is paid for,** the **Narration** section says in one line which names this article uses that nothing pronounces — not its own Pronunciation Glossary, not your Name pronunciations list, not the pooled copy of the other projects' glossaries — and **Every mention** opens the table: each name, how often it is said as written and how often as a possessive, proper names first. It is the cheapest moment to settle a name, because adding it to the list costs nothing and the voice has not been paid yet.
20
+ - **It catches the names a glossary looks like it covers and doesn't.** A two-word term is never matched by one of its words said alone, so an article that lists `Belwar Dissengulp` and then says `Belwar` on its own is named. So are possessives and hyphenated names in a project narrated under the old pronunciation rules, which never wrapped either: a project is judged by the rules it was voiced under, not by today's.
21
+ - **After the render, before the upload,** the same line sits on the **YouTube** section and says two things. What the voice was actually sent, counted from the voice script itself — a name with no IPA in front of it there was guessed, whatever the glossary says. And how the finished narration was heard saying each name, read from the per-word confidence the subtitle timing already wrote when the captions were made.
22
+ - **The spread is the signal, not a low score.** A rare name is heard poorly even when it is said perfectly, so only a name whose own mentions disagree with each other is flagged: eight mentions or more, and a spread of 0.30 or more between its tenth and ninetieth percentile. Nothing is listened to again and nothing is re-aligned.
23
+ - **Ordinary English words are left out of both.** A one-word name of four letters or more, written with a capital, that the same text also writes in lower case is a word there and not a name — so a pooled `Hall` stays quiet in a video that also says "hall", while a god called `Torm` does not.
24
+
25
+ ## For agents
26
+
27
+ - `get_library_pronunciations` and `set_library_pronunciations` read and write the list, the save carrying `{text, version}`; a 409 names what the list is now and hands it back to save from.
28
+ - `check_pronunciations` answers before the narration, `check_narration_names` after the render.
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "id": "4.2.0",
4
+ "title": "Slopify 4.2.0",
5
+ "version": "4.2.0",
6
+ "date": "2026-11-08"
7
+ },
8
+ {
9
+ "id": "4.1.0",
10
+ "title": "Slopify 4.1.0",
11
+ "version": "4.1.0",
12
+ "date": "2026-11-07"
13
+ },
2
14
  {
3
15
  "id": "4.0.0",
4
16
  "title": "Slopify 4.0.0",
@@ -0,0 +1,95 @@
1
+ import { z } from "zod";
2
+ import { stageKinds } from "../../kernel/pipeline.js";
3
+ const outputRow = z.object({
4
+ project_id: z.string(),
5
+ role: z.string(),
6
+ count: z.number(),
7
+ duration_ms: z.number().nullable(),
8
+ });
9
+ const stageRow = z.object({
10
+ project_id: z.string(),
11
+ kind: z.enum(stageKinds),
12
+ state: z.string(),
13
+ finished_at: z.string().nullable(),
14
+ });
15
+ const outdatedRow = z.object({ project_id: z.string() });
16
+ // The roles a row names. A sound-only run's length comes from its export.
17
+ const namedRoles = [
18
+ "video",
19
+ "short_video",
20
+ "document_pdf",
21
+ "audio_mp3",
22
+ "audio_m4b",
23
+ "audio_export",
24
+ ];
25
+ const audioRoles = new Set(["audio_mp3", "audio_m4b", "audio_export"]);
26
+ export function listingFacts(db) {
27
+ const gathered = new Map();
28
+ const of = (projectId) => {
29
+ let one = gathered.get(projectId);
30
+ if (one === undefined) {
31
+ one = {
32
+ outputs: { video: false, shorts: 0, pdf: false, audio: false },
33
+ videoMs: null,
34
+ audioMs: null,
35
+ finishedAt: null,
36
+ failed: [],
37
+ };
38
+ gathered.set(projectId, one);
39
+ }
40
+ return one;
41
+ };
42
+ const roles = namedRoles.map(() => "?").join(",");
43
+ for (const row of db
44
+ .prepare(`SELECT project_id, role, COUNT(*) AS count, MAX(duration_ms) AS duration_ms FROM outputs WHERE role IN (${roles}) GROUP BY project_id, role`)
45
+ .all(...namedRoles)) {
46
+ const parsed = outputRow.parse(row);
47
+ const one = of(parsed.project_id);
48
+ if (parsed.role === "video") {
49
+ one.outputs.video = true;
50
+ one.videoMs = parsed.duration_ms;
51
+ }
52
+ else if (parsed.role === "short_video")
53
+ one.outputs.shorts = parsed.count;
54
+ else if (parsed.role === "document_pdf")
55
+ one.outputs.pdf = true;
56
+ else if (audioRoles.has(parsed.role)) {
57
+ one.outputs.audio = true;
58
+ if (parsed.duration_ms !== null)
59
+ one.audioMs = Math.max(one.audioMs ?? 0, parsed.duration_ms);
60
+ }
61
+ }
62
+ for (const row of db
63
+ .prepare("SELECT project_id, kind, state, finished_at FROM stages WHERE state IN ('done','failed','provided') ORDER BY rowid")
64
+ .all()) {
65
+ const parsed = stageRow.parse(row);
66
+ const one = of(parsed.project_id);
67
+ if (parsed.finished_at !== null &&
68
+ (one.finishedAt === null || parsed.finished_at > one.finishedAt))
69
+ one.finishedAt = parsed.finished_at;
70
+ if (parsed.state === "failed")
71
+ one.failed.push(parsed.kind);
72
+ }
73
+ const outdated = new Set(db
74
+ .prepare("SELECT DISTINCT revision_outputs.project_id FROM revision_outputs JOIN project_heads ON project_heads.project_id = revision_outputs.project_id AND project_heads.revision_id = revision_outputs.revision_id WHERE revision_outputs.selected = 1 AND revision_outputs.state = 'outdated'")
75
+ .all()
76
+ .map((row) => outdatedRow.parse(row).project_id));
77
+ const facts = new Map();
78
+ for (const [projectId, one] of gathered) {
79
+ const made = one.outputs;
80
+ const ms = one.videoMs ?? one.audioMs;
81
+ facts.set(projectId, {
82
+ ...(one.finishedAt === null ? {} : { finishedAt: one.finishedAt }),
83
+ ...(ms === null || ms <= 0 ? {} : { durationSeconds: Math.round(ms / 1000) }),
84
+ ...(made.video || made.shorts > 0 || made.pdf || made.audio
85
+ ? { outputs: { ...made } }
86
+ : {}),
87
+ ...(one.failed.length === 0 ? {} : { failedSteps: one.failed }),
88
+ ...(outdated.has(projectId) ? { outdated: true } : {}),
89
+ });
90
+ }
91
+ for (const projectId of outdated)
92
+ if (!facts.has(projectId))
93
+ facts.set(projectId, { outdated: true });
94
+ return facts;
95
+ }
@@ -21,6 +21,7 @@ const standingRow = z.object({
21
21
  progress_current: z.number().nullable(),
22
22
  progress_total: z.number().nullable(),
23
23
  source: z.enum(stageSources),
24
+ retry_at: z.string().nullable(),
24
25
  admitted: z.number(),
25
26
  parked: z.number(),
26
27
  });
@@ -83,14 +84,15 @@ export function listProjects(db) {
83
84
  .all()
84
85
  .map((row) => toProject(projectRow.parse(row)));
85
86
  }
86
- // What 07 Projects needs of every project's stages and no more: the state each one is in
87
- // and how far through itself a running one is. One statement for the whole list rather
88
- // than `stagesOf` per row, and five columns rather than the whole stage table, because
89
- // the screen shows a status word and a meter and reads nothing else from a stage. One project's
90
- // alone when `projectId` is given.
87
+ // What 07 Projects needs of every project's stages and no more: the state each one is in,
88
+ // how far through itself a running one is, and when one waiting to run again by itself tries
89
+ // (without it, `derive` reads that run as waiting its turn while its own page says Running).
90
+ // One statement for the whole list rather than `stagesOf` per row, and a few columns rather
91
+ // than the whole stage table, because the screen shows a status word and a meter and reads
92
+ // nothing else from a stage. One project's alone when `projectId` is given.
91
93
  export function stageStandingsByProject(db, projectId) {
92
94
  const grouped = new Map();
93
- const select = `SELECT project_id, kind, state, progress_current, progress_total, source, ${admittedInItsState("stages")} AS admitted, ${parkedOnReview("stages")} AS parked FROM stages${projectId === undefined ? "" : " WHERE project_id=?"} ORDER BY rowid`;
95
+ const select = `SELECT project_id, kind, state, progress_current, progress_total, source, retry_at, ${admittedInItsState("stages")} AS admitted, ${parkedOnReview("stages")} AS parked FROM stages${projectId === undefined ? "" : " WHERE project_id=?"} ORDER BY rowid`;
94
96
  const rows = projectId === undefined ? db.prepare(select).all() : db.prepare(select).all(projectId);
95
97
  for (const row of rows) {
96
98
  const parsed = standingRow.parse(row);
@@ -100,6 +102,7 @@ export function stageStandingsByProject(db, projectId) {
100
102
  progressCurrent: parsed.progress_current,
101
103
  progressTotal: parsed.progress_total,
102
104
  source: parsed.source,
105
+ ...(parsed.retry_at === null ? {} : { retryAt: parsed.retry_at }),
103
106
  ...(parsed.admitted === 1 ? { admitted: true } : {}),
104
107
  ...(parsed.parked === 1 ? { parked: true } : {}),
105
108
  };
@@ -88,7 +88,7 @@ export function admit(input) {
88
88
  }
89
89
  }
90
90
  if (needsLlmFor(draft, input.keywordUses) && !chosen(draft.llm)) {
91
- fields.push({ field: "llm", message: "Choose a text (LLM) provider and model." });
91
+ fields.push({ field: "llm", message: "Choose a Service and a Model under Written by." });
92
92
  }
93
93
  if (sources.article === "generate" && blank(draft.articlePrompt)) {
94
94
  fields.push({ field: "articlePrompt", message: "Pick an article prompt." });
@@ -98,7 +98,7 @@ export function admit(input) {
98
98
  fields.push(...voicesFields(draft));
99
99
  if (sources.audio === "generate") {
100
100
  if (voiced === undefined || !chosen(voiced)) {
101
- fields.push({ field: "audio", message: "Choose a narration provider and model." });
101
+ fields.push({ field: "audio", message: "Choose a Service and a Model under Read by." });
102
102
  }
103
103
  else if (voiced.voice.trim() === "") {
104
104
  fields.push({ field: "audio.voice", message: "Choose a voice for the narration." });
@@ -113,7 +113,7 @@ export function admit(input) {
113
113
  usesShorts(draft) ||
114
114
  usesAnimation(draft)) &&
115
115
  !chosen(draft.images)) {
116
- fields.push({ field: "images", message: "Choose an image provider and model." });
116
+ fields.push({ field: "images", message: "Choose a Service and a Model under Images by." });
117
117
  }
118
118
  if ((sources.thumbnail === "from_prompt" || sources.thumbnail === "prompt_by_llm") &&
119
119
  blank(draft.thumbnailPrompt)) {
@@ -122,7 +122,7 @@ export function admit(input) {
122
122
  if (sources.audio === "off" && draft.subtitles !== undefined && draft.subtitles.mode !== "off") {
123
123
  fields.push({
124
124
  field: "subtitles.mode",
125
- message: "Subtitles need narration. Turn narration on, or turn subtitles off.",
125
+ message: "Captions need narration. Turn narration on, or turn captions off.",
126
126
  });
127
127
  }
128
128
  fields.push(...youtubeDescriptionFields(draft));
@@ -381,7 +381,7 @@ export function voicesFields(draft) {
381
381
  !draft.voices.speakers.some((speaker) => speaker.voice.provider === "inworld" && speaker.voice.model === "inworld-tts-2"))
382
382
  fields.push({
383
383
  field: "narrationPrompt",
384
- message: "Narration preparation adds delivery cues only to speakers on Inworld's TTS-2 model, and no speaker uses it. Choose that model for a speaker under Speakers, or clear Narration Preparation under Narration → Audio Advanced.",
384
+ message: "Narration preparation adds delivery cues only to speakers on Inworld's TTS-2 model, and no speaker uses it. Choose that model for a speaker under Speakers, or set Narration preparation to Off under Narration → More narration settings.",
385
385
  });
386
386
  return fields;
387
387
  }
@@ -489,8 +489,8 @@ export function ambientBedFields(draft, staged) {
489
489
  function chosen(choice) {
490
490
  return choice !== undefined && choice.provider.trim() !== "" && choice.model.trim() !== "";
491
491
  }
492
- // The aliases a run narrates with: its copied Library → Aliases while Use narration aliases is
493
- // on for generated audio, otherwise none. Any voice provider: an alias is plain text.
492
+ // The aliases a run narrates with: its copied Pronunciation list while Use your pronunciation
493
+ // list is on for generated audio, otherwise none. Any voice provider: an alias is plain text.
494
494
  export function narrationAliasesOf(draft) {
495
495
  return draft.sources.audio === "generate" && draft.audio?.useNarrationAliases === true
496
496
  ? (draft.narrationAliases ?? [])
@@ -7,6 +7,7 @@ import { documentSettingsSchema } from "../document/theme-schema.js";
7
7
  import { keywordFactsSchema } from "../keywords/schema.js";
8
8
  import { narrationAliasesSchema } from "../narration/aliases-schema.js";
9
9
  import { chunkModes } from "../narration/chunk.js";
10
+ import { libraryPronunciationsMax } from "../narration/pronunciation.js";
10
11
  import { reviewModes, reviewStages } from "../reviews/model.js";
11
12
  import { socialSettingsSchema } from "../social/schema.js";
12
13
  import { subtitleConfigSchema } from "../subtitles/model.js";
@@ -72,6 +73,7 @@ export const runDraftSchema = z.object({
72
73
  })
73
74
  .optional(),
74
75
  narrationAliases: narrationAliasesSchema.optional(),
76
+ libraryPronunciations: z.string().max(libraryPronunciationsMax).optional(),
75
77
  pronunciationRules: z.union([z.literal(1), z.literal(2)]).optional(),
76
78
  sharedGlossary: z
77
79
  .array(z.object({
@@ -8,6 +8,7 @@ import { keywordFactsFor } from "../keywords/facts.js";
8
8
  import { usesFacts } from "../keywords/rules.js";
9
9
  import { listNarrationAliases } from "../narration/aliases-library.js";
10
10
  import { currentPronunciationRules } from "../narration/pronunciation.js";
11
+ import { readLibraryPronunciations } from "../narration/pronunciations-library.js";
11
12
  import { collectSharedGlossary } from "../narration/shared-glossary.js";
12
13
  import { admitInitialRevision } from "../rebuild/runtime-admission.js";
13
14
  import { adoptBaseline } from "../revisions/adopt.js";
@@ -117,6 +118,11 @@ projectId) {
117
118
  const aliases = draft.sources.audio === "generate" && draft.audio?.useNarrationAliases === true
118
119
  ? listNarrationAliases(deps.db)
119
120
  : [];
121
+ // Settings → Voices → Pronunciation's own glossary as it is now: the names this channel
122
+ // always says the same way, ahead of whatever this article's glossary spells out.
123
+ const pronunciations = usesPronunciationGlossary(draft)
124
+ ? readLibraryPronunciations(deps.db).text
125
+ : "";
120
126
  // The title may name keywords too ("History: {{Topic}}"), filled like a prompt's, so a
121
127
  // template or Play keeps the pattern and each project gets its own title.
122
128
  const title = render(draft.title, draft.values).trim() || draft.title;
@@ -142,6 +148,7 @@ projectId) {
142
148
  rendered,
143
149
  ...(shared.length === 0 ? {} : { sharedGlossary: shared }),
144
150
  ...(aliases.length === 0 ? {} : { narrationAliases: aliases }),
151
+ ...(pronunciations === "" ? {} : { libraryPronunciations: pronunciations }),
145
152
  pronunciationRules: currentPronunciationRules,
146
153
  ...(earlier === undefined ? {} : { earlierEpisodes: earlier }),
147
154
  ...(facts === undefined ? {} : { keywordFacts: facts }),
@@ -28,8 +28,13 @@ export const stageStateWords = {
28
28
  export const heldWord = "Waiting for your review";
29
29
  export const waitingForAgentWord = "Waiting for the agent";
30
30
  export const stoppedWord = "Stopped";
31
- export const preparedWord = "Prepared";
31
+ // Made ahead by its schedule (Make ahead): everything but the video is made, and the video is
32
+ // put together on the run's day.
33
+ export const preparedWord = "Waiting for its day";
32
34
  export const keptAsIsWord = "Kept as is";
35
+ // A running run whose only movement is a step that failed on something time can fix (a rate
36
+ // limit, a dropped connection) and runs again by itself at a set time.
37
+ export const retryingWord = "Waiting to try again";
33
38
  export const heldStateWords = [
34
39
  heldWord,
35
40
  waitingForAgentWord,
@@ -38,15 +43,25 @@ export const heldStateWords = [
38
43
  keptAsIsWord,
39
44
  ];
40
45
  // A run that ran and is held: a waiting project with some of its run done (`progress` is the
41
- // run's share done, `progressOf`) or a step failed, or one stopped or parked on an agent's review
42
- // before it got going. One with none done that waits for neither is Queued, waiting its turn.
46
+ // run's share done, `progressOf`) or a step failed, one a checkpoint holds (it reached it with
47
+ // every step before it supplied), or one stopped or parked on an agent's review before it got
48
+ // going. One with none done that waits for none of these is Queued, waiting its turn.
43
49
  export function heldRun(project) {
44
50
  return (project.status === "pending" &&
45
51
  (project.progress > 0 ||
46
52
  project.stepFailed === true ||
53
+ (project.holding?.length ?? 0) > 0 ||
47
54
  project.stopped === true ||
48
55
  project.waitingForAgent === true));
49
56
  }
57
+ // When the run tries again by itself: the soonest wait of a step that failed on something time
58
+ // can fix, while no step runs. Undefined while one runs (the run reads Running) or none waits.
59
+ export function retryingAt(stages) {
60
+ if (stages.some((stage) => stage.state === "running"))
61
+ return undefined;
62
+ const waits = stages.flatMap((stage) => stage.state === "pending" && typeof stage.retryAt === "string" ? [stage.retryAt] : []);
63
+ return waits.toSorted()[0];
64
+ }
50
65
  // Whether the person is the one it waits for (to approve, or to Continue the run): not when it
51
66
  // is Kept as is, waits for an agent or for its scheduled day.
52
67
  function heldForPerson(project) {
@@ -78,9 +93,18 @@ export function runningNow(project) {
78
93
  export function queuedRun(project) {
79
94
  return project.status === "pending" && !heldRun(project);
80
95
  }
81
- // An uploaded project already reads "done" from the projects route (`uploadedAndSettled`).
96
+ // A settled project already reads "done" from the projects route (`settledProjects`). A
97
+ // running run that only waits to try a step again says so.
82
98
  export function projectStateWord(project) {
83
- return heldRun(project) ? heldStateWord(project) : projectStateWords[project.status];
99
+ if (heldRun(project))
100
+ return heldStateWord(project);
101
+ if (retryingRun(project))
102
+ return retryingWord;
103
+ return projectStateWords[project.status];
104
+ }
105
+ // Running, and the one thing it does is wait to try a failed step again at a set time.
106
+ export function retryingRun(project) {
107
+ return project.status === "running" && project.retryAt !== undefined;
84
108
  }
85
109
  // A run that ran and now waits for the person, held or paused: Home's Needs you, the Projects
86
110
  // "Needs you" filter and its waiting-first sort.
@@ -108,3 +132,8 @@ export function stepFailedWhileWaiting(status, stages) {
108
132
  export function failedRun(project) {
109
133
  return project.status === "failed" || project.stepFailed === true;
110
134
  }
135
+ // Everything a run asks of the person, the one set Home's Needs you lists and the Projects
136
+ // "Needs you" filter holds: a decision (approve, Continue the run, resume) or a failure to fix.
137
+ export function needsYou(project) {
138
+ return waitsForPerson(project) || failedRun(project);
139
+ }
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- // The cost sentence on an ask card, from the estimate the preview (Choose what to remake) or
2
+ // The cost sentence on an ask card, from the estimate the preview (Choose what to make again) or
3
3
  // Play's review already showed for this exact work.
4
4
  const money = (value) => `$${value.toFixed(2)}`;
5
5
  function range(low, high) {
@@ -159,6 +159,7 @@ export const assistantFreeTools = [
159
159
  "create_document_theme",
160
160
  "update_document_theme",
161
161
  "set_narration_aliases",
162
+ "set_library_pronunciations",
162
163
  "update_settings",
163
164
  "set_default_channel_links",
164
165
  "add_voice",
@@ -37,7 +37,7 @@ export const turnStates = ["running", "done", "failed", "stopped"];
37
37
  const providersFix = {
38
38
  kind: "settings",
39
39
  section: "providers",
40
- label: "Open Settings → Providers",
40
+ label: "Open Settings → Keys & services",
41
41
  };
42
42
  const signIn = {
43
43
  "claude-code": "claude auth login",
@@ -46,13 +46,20 @@ const signIn = {
46
46
  function blocked(cli, state) {
47
47
  const name = assistantCliNames[cli];
48
48
  if (!state.installed)
49
- return `The assistant can't start: ${name} isn't installed on this computer. Install it, then set it up in Settings → Providers.`;
49
+ return `The assistant can't start: ${name} isn't installed on this computer. Install it, then set it up in Settings → Keys & services.`;
50
50
  if (state.issue !== undefined)
51
51
  return `The assistant can't start: ${state.issue}`;
52
52
  if (state.login === "signed-out")
53
53
  return `The assistant can't start: ${name} is signed out. Run "${signIn[cli]}" in a terminal, then send your message again.`;
54
54
  return undefined;
55
55
  }
56
+ // The CLIs that could answer now, in Auto's order: the panel offers "Answer with" only when
57
+ // there are two to choose between.
58
+ export function readyClis(states, container) {
59
+ if (container)
60
+ return [];
61
+ return assistantClis.filter((cli) => blocked(cli, states[cli]) === undefined);
62
+ }
56
63
  // Auto takes Claude Code when it is ready, else Codex. A sign-in that couldn't be read is not
57
64
  // a refusal: the CLI says so itself if it really is signed out.
58
65
  export function assistantCliOf(states, preference, container) {
@@ -73,7 +80,7 @@ export function assistantCliOf(states, preference, container) {
73
80
  return { ok: true, cli };
74
81
  return {
75
82
  ok: false,
76
- reason: "The assistant runs on Claude Code or Codex, and neither is ready on this computer. Set up Claude Code or Codex in Settings → Providers.",
83
+ reason: "The assistant runs on Claude Code or Codex, and neither is ready on this computer. Set up Claude Code or Codex in Settings → Keys & services.",
77
84
  fix: providersFix,
78
85
  };
79
86
  }
@@ -1,7 +1,7 @@
1
1
  import { causedBy } from "../../kernel/errors.js";
2
2
  import { redact } from "../../kernel/log.js";
3
3
  import { asksOf, consumeGrant, decideAsk, openAsk } from "./asks.js";
4
- import { assistantCliNames, assistantCliOf, } from "./model.js";
4
+ import { assistantCliNames, assistantCliOf, readyClis, } from "./model.js";
5
5
  import { screenNames } from "./names.js";
6
6
  import { assistantSystemPrompt, turnPrompt } from "./prompt.js";
7
7
  import { addMessage, cliPreferenceOf, createSession, latestSessionId, messagesOf, sessionExists, transcriptOf, } from "./repo.js";
@@ -182,7 +182,7 @@ export function createAssistant(deps) {
182
182
  if (self.stoppedBy === undefined) {
183
183
  const detail = redact(causedBy(error));
184
184
  deps.log.write("warn", "assistant.turn", { detail });
185
- say(sessionId, turnId, "error", `Slopify couldn't run the assistant's CLI: ${detail}. Check it in Settings → Providers, then send your message again.`, { kind: "cli" });
185
+ say(sessionId, turnId, "error", `Slopify couldn't run the assistant's CLI: ${detail}. Check it in Settings → Keys & services, then send your message again.`, { kind: "cli" });
186
186
  }
187
187
  }
188
188
  if (self.stoppedBy !== undefined) {
@@ -264,9 +264,11 @@ export function createAssistant(deps) {
264
264
  tokens: deps.tokens,
265
265
  status: async () => {
266
266
  const preference = cliPreferenceOf(deps.db);
267
+ const states = await deps.clis();
267
268
  return {
268
269
  preference,
269
- choice: assistantCliOf(await deps.clis(), preference, deps.container),
270
+ choice: assistantCliOf(states, preference, deps.container),
271
+ ready: readyClis(states, deps.container),
270
272
  };
271
273
  },
272
274
  session: (id) => {
@@ -51,7 +51,7 @@ export function warningsOf(signals, snoozes, now) {
51
51
  found.push({
52
52
  key: `template-not-ready:${schedule.id}`,
53
53
  severity: "problem",
54
- text: `“${schedule.name}” skips its runs because its template “${schedule.notReadyTemplate}” has no version marked ready. Open it in Library → Templates → Open studio and press Mark ready.`,
54
+ text: `“${schedule.name}” skips its runs because its template “${schedule.notReadyTemplate}” has no version approved for schedules. Open it from Templates with Edit template, then choose More → Approve for schedules.`,
55
55
  fix: { kind: "templates", label: "Open Templates" },
56
56
  });
57
57
  if (schedule.topicsError !== null && schedule.generation !== "off")
@@ -60,7 +60,7 @@ export function createBackupService(deps) {
60
60
  }
61
61
  catch (error) {
62
62
  deps.log.write("warn", "backups.prune", { detail: String(error) });
63
- detail = `The backup was saved, but older backups in ${folder} could not be removed (${errnoCode(error) ?? "unknown error"}), so more than ${settings.keep} are kept. Check Slopify can delete files in that folder, or pick another Folder in Settings → Backups.`;
63
+ detail = `The backup was saved, but older backups in ${folder} could not be removed (${errnoCode(error) ?? "unknown error"}), so more than ${settings.keep} are kept. Check Slopify can delete files in that folder, or pick another Folder in Settings → Backup & storage.`;
64
64
  }
65
65
  record({
66
66
  ...attempt,
@@ -273,17 +273,17 @@ function named(projects) {
273
273
  .join(", ");
274
274
  return projects.length > 3 ? `${titles} and ${projects.length - 3} more` : titles;
275
275
  }
276
- // What failed, why, and what to change - the sentence Settings → Backups shows.
276
+ // What failed, why, and what to change - the sentence Settings → Backup & storage shows.
277
277
  export function explain(error, folder, retries, signal) {
278
- const fix = "Pick another Folder in Settings → Backups, or leave it empty to use the default";
278
+ const fix = "Pick another Folder in Settings → Backup & storage, or leave it empty to use the default";
279
279
  if (signal?.aborted === true && error === signal.reason)
280
280
  return "The backup was not saved: Slopify stopped while writing it, and the unfinished file was removed. It runs again after Slopify starts.";
281
281
  if (error instanceof NoSpaceError)
282
- return `The backup was not saved: it needs about ${bytes(error.needed)}, but the disk holding ${folder} has ${bytes(error.free)} free. Free up space there, lower "Keep last" in Settings → Backups, or pick another Folder.`;
282
+ return `The backup was not saved: it needs about ${bytes(error.needed)}, but the disk holding ${folder} has ${bytes(error.free)} free. Free up space there, lower "Keep last" in Settings → Backup & storage, or pick another Folder.`;
283
283
  switch (errnoCode(error)) {
284
284
  case "ENOSPC":
285
285
  case "EDQUOT":
286
- return `The backup was not saved: the disk holding ${folder} ran out of space. Free up space there, lower "Keep last" in Settings → Backups, or pick another Folder.`;
286
+ return `The backup was not saved: the disk holding ${folder} ran out of space. Free up space there, lower "Keep last" in Settings → Backup & storage, or pick another Folder.`;
287
287
  case "EACCES":
288
288
  case "EPERM":
289
289
  case "EROFS":
@@ -61,7 +61,7 @@ export function generateCastImage(deps, memberId, input) {
61
61
  }, (error) => {
62
62
  const reason = errorText(error);
63
63
  deps.log?.write("warn", "channels.cast-image.failed", { detail: reason.slice(0, 500) });
64
- fail(deps, id, `The picture couldn't be made: ${reason.slice(0, 500)} Check the image provider in Settings → Providers, then press Generate again.`);
64
+ fail(deps, id, `The picture couldn't be made: ${reason.slice(0, 500)} Check the image service in Settings → Keys & services, then press Make the picture again.`);
65
65
  });
66
66
  return { ok: true, value: imageRow(deps, id) };
67
67
  }
@@ -10,7 +10,7 @@ import { castKinds } from "./model.js";
10
10
  const id = z.uuid();
11
11
  const colour = z
12
12
  .string()
13
- .regex(/^#[0-9a-fA-F]{6}$/, "Use a colour like #FFD700.")
13
+ .regex(/^#[0-9a-fA-F]{6}$/, "Use a color like #FFD700.")
14
14
  .transform((value) => value.toUpperCase());
15
15
  const optionalText = (max) => z.string().trim().max(max).optional();
16
16
  const fontId = z
@@ -17,7 +17,7 @@ const importSchema = z
17
17
  // The preview's "Keep only titles containing…" text, remembered for the next import.
18
18
  filter: z
19
19
  .string()
20
- .max(importFilterMax, `The filter is too long to remember. Shorten "Keep only titles containing…" on the channel's Existing videos tab to at most ${importFilterMax} characters.`)
20
+ .max(importFilterMax, `The filter is too long to remember. Shorten "Keep only titles containing…" on the channel's Titles to avoid tab to at most ${importFilterMax} characters.`)
21
21
  .optional(),
22
22
  })
23
23
  .strict();
@@ -24,8 +24,8 @@ export function validateLocalProviderChanges(changes, current) {
24
24
  "Install this command-line tool on your computer and sign in to it, then try again.")
25
25
  : provider.readiness.kind === "local"
26
26
  ? (provider.readiness.issue ??
27
- "No speech program was found on this computer. Install espeak-ng, or choose a voice provider with a key in Settings → Providers.")
28
- : "Add this provider's API key in Settings → Providers first.",
27
+ "No speech program was found on this computer. Install the free program eSpeak NG, or choose a voice service with a key in Settings → Keys & services.")
28
+ : "Add this provider's API key in Settings → Keys & services first.",
29
29
  });
30
30
  }
31
31
  }
@@ -14,7 +14,7 @@ export function dailySampleBudget(db, log) {
14
14
  export function budgetSentence(total, budget) {
15
15
  return `Waiting: this sample would take today's sample spending to $${total.toFixed(2)}, over the $${budget.toFixed(2)} a day samples may spend. Press Make sample to make it anyway; the limit starts again at midnight.`;
16
16
  }
17
- // Settings → Template studio shows it beside the budget: what design samples spent since local
17
+ // Settings → Production defaults shows it beside the budget: what design samples spent since local
18
18
  // midnight and what is left of today's budget.
19
19
  export function designBudgetRoom(db, now, timeZone) {
20
20
  const budget = dailySampleBudget(db);
@@ -11,7 +11,7 @@ import { settleStep, stepById } from "./steps-repo.js";
11
11
  // The background half of the runner: each "making" step's provider call, at most two at once,
12
12
  // settled into its step (and made the design's current version when it lands).
13
13
  export const maxConcurrentDesignCalls = 2;
14
- export const noProviderMessage = "No image provider is set up. Add one in Settings → Providers, then press the button again.";
14
+ export const noProviderMessage = "No image service is set up. Add one in Settings → Keys & services, then press the button again.";
15
15
  export function createDesignCalls(deps) {
16
16
  let running = 0;
17
17
  const waiting = [];
@@ -119,6 +119,6 @@ function failedSentence(error, reason) {
119
119
  if (kind === "unsupported")
120
120
  return `The picture couldn't be made: ${reason}${reason.includes("Made with") ? "" : " Choose another image model under Made with … · change."}`;
121
121
  if (kind === "auth" || kind === "missing_key")
122
- return `The picture couldn't be made: ${reason}${/Settings/.test(reason) ? "" : " Check the provider's key in Settings → Providers, then press Try again."}`;
122
+ return `The picture couldn't be made: ${reason}${/Settings/.test(reason) ? "" : " Check the provider's key in Settings → Keys & services, then press Try again."}`;
123
123
  return `The picture couldn't be made: ${reason} Press Try again, or choose another image model under Made with … · change.`;
124
124
  }