@jant/core 0.6.14 → 0.6.16

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 (350) hide show
  1. package/bin/commands/import-site.js +88 -13
  2. package/bin/commands/migrate.js +55 -0
  3. package/bin/commands/posts/rebuild-html.js +184 -0
  4. package/bin/commands/site/snapshot/import.js +14 -10
  5. package/bin/lib/d1-query.js +23 -5
  6. package/bin/lib/migration-runner.js +29 -7
  7. package/bin/lib/node-database.js +32 -0
  8. package/bin/lib/site-snapshot.js +347 -46
  9. package/bin/lib/sql-export.js +1 -1
  10. package/bin/lib/thread-collection-migration.js +326 -0
  11. package/dist/app-C75Zc8dD.js +6 -0
  12. package/dist/{app-2i7-WQHg.js → app-C7_4oab9.js} +7774 -6425
  13. package/dist/client/.vite/manifest.json +8 -8
  14. package/dist/client/_assets/chunks/{url-pLre2DM_.js → url-CU9pEbk5.js} +1 -1
  15. package/dist/client/_assets/{client-BvID5ucz.js → client-Bq0vre8Y.js} +59 -38
  16. package/dist/client/_assets/client-D3VZyG4L.css +2 -0
  17. package/dist/client/_assets/client-auth-F2rb9WfF.js +5571 -0
  18. package/dist/{env-OHRKGcMj.js → env-BPYViDJJ.js} +1 -1
  19. package/dist/{export-DWn149b7.js → export-Dl5KCiEs.js} +1348 -128
  20. package/dist/{github-api-BgSiE71w.js → github-api-BNF35Lkx.js} +1 -1
  21. package/dist/{github-app-BbklkFmU.js → github-app-DdiD-bC4.js} +1 -1
  22. package/dist/{github-sync-C0zyF6XS.js → github-sync-Bqg62IvV.js} +3 -3
  23. package/dist/{github-sync-Ckk0sJxK.js → github-sync-HdK2EgvJ.js} +3 -3
  24. package/dist/index.js +5 -5
  25. package/dist/node.js +7 -7
  26. package/dist/{url-BMYO-Zlt.js → url-CbLAtlZe.js} +517 -12
  27. package/package.json +11 -10
  28. package/src/__tests__/bin/posts-rebuild-html.test.ts +137 -0
  29. package/src/__tests__/dev-navigation-defaults.test.ts +22 -0
  30. package/src/__tests__/export-hugo-build.test.ts +63 -4
  31. package/src/__tests__/export-import-roundtrip.test.ts +6 -4
  32. package/src/__tests__/export-service.test.ts +142 -9
  33. package/src/__tests__/helpers/app.ts +3 -0
  34. package/src/__tests__/helpers/export-fixtures.ts +2 -0
  35. package/src/__tests__/import-site-command.test.ts +65 -0
  36. package/src/__tests__/mise-config.test.ts +22 -0
  37. package/src/__tests__/vite-config.test.ts +45 -0
  38. package/src/app.tsx +16 -3
  39. package/src/client/__tests__/collection-created-notice.test.ts +36 -0
  40. package/src/client/__tests__/collection-form-bridge.test.ts +10 -1
  41. package/src/client/__tests__/collection-page-actions.test.ts +71 -2
  42. package/src/client/__tests__/compose-bridge.test.ts +316 -2
  43. package/src/client/__tests__/compose-shortcuts.test.ts +20 -0
  44. package/src/client/__tests__/feed-video-player.test.ts +16 -1
  45. package/src/client/__tests__/footnote-rail.test.ts +472 -0
  46. package/src/client/__tests__/hydrate-partial.test.ts +3 -0
  47. package/src/client/__tests__/toast.test.ts +82 -1
  48. package/src/client/collection-created-notice.ts +66 -0
  49. package/src/client/collection-form-bridge.ts +17 -0
  50. package/src/client/collection-navigation.ts +44 -0
  51. package/src/client/collection-page-actions.ts +57 -1
  52. package/src/client/components/__tests__/compose-format-convert.test.ts +0 -19
  53. package/src/client/components/__tests__/jant-collection-directory.test.ts +155 -6
  54. package/src/client/components/__tests__/jant-command-palette.test.ts +51 -0
  55. package/src/client/components/__tests__/jant-compose-dialog.test.ts +2268 -206
  56. package/src/client/components/__tests__/jant-compose-editor.test.ts +147 -59
  57. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +114 -64
  58. package/src/client/components/__tests__/jant-config-editor.test.ts +405 -0
  59. package/src/client/components/__tests__/jant-media-lightbox.test.ts +121 -0
  60. package/src/client/components/__tests__/jant-nav-manager.test.ts +538 -2
  61. package/src/client/components/__tests__/jant-post-form.test.ts +2 -2
  62. package/src/client/components/__tests__/jant-post-menu.test.ts +47 -5
  63. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
  64. package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
  65. package/src/client/components/collection-manager-types.ts +9 -3
  66. package/src/client/components/compose-format-convert.ts +5 -1
  67. package/src/client/components/compose-types.ts +55 -3
  68. package/src/client/components/jant-collection-directory.ts +165 -30
  69. package/src/client/components/jant-command-palette.ts +14 -7
  70. package/src/client/components/jant-compose-dialog.ts +1542 -694
  71. package/src/client/components/jant-compose-editor.ts +237 -99
  72. package/src/client/components/jant-compose-fullscreen.ts +27 -9
  73. package/src/client/components/jant-config-editor.ts +722 -0
  74. package/src/client/components/jant-media-lightbox.ts +100 -18
  75. package/src/client/components/jant-nav-manager.ts +1283 -54
  76. package/src/client/components/jant-post-form.ts +3 -3
  77. package/src/client/components/jant-post-menu.ts +177 -95
  78. package/src/client/components/nav-manager-types.ts +51 -2
  79. package/src/client/components/post-form-types.ts +1 -1
  80. package/src/client/compose-bridge.ts +147 -123
  81. package/src/client/compose-launch.ts +13 -0
  82. package/src/client/compose-shortcuts.ts +5 -1
  83. package/src/client/feed-video-player.ts +62 -2
  84. package/src/client/footnote-rail.ts +478 -0
  85. package/src/client/hydrate-partial.ts +2 -0
  86. package/src/client/media-lightbox-events.ts +63 -0
  87. package/src/client/post-refresh.ts +135 -0
  88. package/src/client/site-header-fragment.ts +38 -0
  89. package/src/client/tiptap/__tests__/blockquote-editing.test.ts +201 -0
  90. package/src/client/tiptap/__tests__/clear-formatting.test.ts +168 -0
  91. package/src/client/tiptap/__tests__/link-input-rules.test.ts +121 -0
  92. package/src/client/tiptap/__tests__/list-editing.test.ts +447 -0
  93. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +727 -4
  94. package/src/client/tiptap/__tests__/table-controls.test.ts +263 -0
  95. package/src/client/tiptap/__tests__/table-size-picker.test.ts +179 -0
  96. package/src/client/tiptap/bubble-menu.ts +88 -10
  97. package/src/client/tiptap/continuous-lists.ts +102 -0
  98. package/src/client/tiptap/create-editor.ts +3 -0
  99. package/src/client/tiptap/extensions.ts +14 -5
  100. package/src/client/tiptap/link-input-rules.ts +4 -4
  101. package/src/client/tiptap/markdown-clipboard.ts +608 -11
  102. package/src/client/tiptap/slash-commands.ts +8 -1
  103. package/src/client/tiptap/structural-keymap.ts +211 -0
  104. package/src/client/tiptap/tab-indent.ts +8 -22
  105. package/src/client/tiptap/table-control-labels.ts +30 -0
  106. package/src/client/tiptap/table-controls.ts +537 -0
  107. package/src/client/tiptap/table-size-picker.ts +254 -0
  108. package/src/client/toast.ts +133 -39
  109. package/src/client-auth.ts +1 -0
  110. package/src/client.ts +1 -0
  111. package/src/db/__tests__/backfill-thread-activity.test.ts +234 -0
  112. package/src/db/__tests__/d1-query.test.ts +43 -1
  113. package/src/db/__tests__/migration-runner.test.ts +63 -0
  114. package/src/db/__tests__/migrations.test.ts +469 -0
  115. package/src/db/__tests__/thread-activity.test.ts +113 -0
  116. package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
  117. package/src/db/backfills/0005_split_thread_activity_from_quiet_replies.sql +68 -0
  118. package/src/db/migrations/0027_old_blue_blade.sql +39 -0
  119. package/src/db/migrations/0028_superb_post.sql +2 -0
  120. package/src/db/migrations/0029_boring_magma.sql +1 -0
  121. package/src/db/migrations/0030_typical_vivisector.sql +50 -0
  122. package/src/db/migrations/0031_many_ego.sql +3 -0
  123. package/src/db/migrations/meta/0027_snapshot.json +2511 -0
  124. package/src/db/migrations/meta/0028_snapshot.json +2511 -0
  125. package/src/db/migrations/meta/0029_snapshot.json +2519 -0
  126. package/src/db/migrations/meta/0030_snapshot.json +2541 -0
  127. package/src/db/migrations/meta/0031_snapshot.json +2562 -0
  128. package/src/db/migrations/meta/_journal.json +35 -0
  129. package/src/db/migrations/pg/0025_moaning_epoch.sql +39 -0
  130. package/src/db/migrations/pg/0026_talented_killer_shrike.sql +2 -0
  131. package/src/db/migrations/pg/0027_short_cannonball.sql +1 -0
  132. package/src/db/migrations/pg/0028_perfect_sharon_carter.sql +27 -0
  133. package/src/db/migrations/pg/0029_rare_hairball.sql +3 -0
  134. package/src/db/migrations/pg/meta/0025_snapshot.json +3204 -0
  135. package/src/db/migrations/pg/meta/0026_snapshot.json +3204 -0
  136. package/src/db/migrations/pg/meta/0027_snapshot.json +3211 -0
  137. package/src/db/migrations/pg/meta/0028_snapshot.json +3248 -0
  138. package/src/db/migrations/pg/meta/0029_snapshot.json +3289 -0
  139. package/src/db/migrations/pg/meta/_journal.json +35 -0
  140. package/src/db/pg/schema.ts +46 -20
  141. package/src/db/rehearsal-fixtures/demo-current.json +39 -4
  142. package/src/db/rehearsal-fixtures/demo-current.sql +121 -6
  143. package/src/db/schema.ts +48 -20
  144. package/src/db/thread-activity.ts +101 -0
  145. package/src/i18n/__tests__/detect.test.ts +8 -2
  146. package/src/i18n/detect.ts +14 -5
  147. package/src/i18n/locales/public/en.po +222 -51
  148. package/src/i18n/locales/public/en.ts +1 -1
  149. package/src/i18n/locales/public/zh-Hans.po +216 -45
  150. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  151. package/src/i18n/locales/public/zh-Hant.po +216 -45
  152. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  153. package/src/i18n/locales/settings/en.po +513 -10
  154. package/src/i18n/locales/settings/en.ts +1 -1
  155. package/src/i18n/locales/settings/zh-Hans.po +515 -12
  156. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  157. package/src/i18n/locales/settings/zh-Hant.po +515 -12
  158. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  159. package/src/lib/__tests__/api-settings.test.ts +299 -0
  160. package/src/lib/__tests__/constants.test.ts +5 -0
  161. package/src/lib/__tests__/feed-policy.test.ts +25 -0
  162. package/src/lib/__tests__/feed.test.ts +168 -0
  163. package/src/lib/__tests__/footnotes-upgrade.test.ts +181 -0
  164. package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +61 -0
  165. package/src/lib/__tests__/markdown-to-tiptap.test.ts +32 -4
  166. package/src/lib/__tests__/markdown.test.ts +24 -6
  167. package/src/lib/__tests__/navigation.test.ts +59 -2
  168. package/src/lib/__tests__/post-body-html.test.ts +176 -0
  169. package/src/lib/__tests__/post-display.test.ts +64 -0
  170. package/src/lib/__tests__/resolve-config.test.ts +113 -1
  171. package/src/lib/__tests__/slug.test.ts +18 -0
  172. package/src/lib/__tests__/summary.test.ts +82 -0
  173. package/src/lib/__tests__/timeline.test.ts +109 -70
  174. package/src/lib/__tests__/timezones.test.ts +31 -3
  175. package/src/lib/__tests__/tiptap-render.test.ts +268 -9
  176. package/src/lib/__tests__/tiptap-to-markdown.test.ts +6 -0
  177. package/src/lib/__tests__/translit.test.ts +87 -0
  178. package/src/lib/__tests__/url.test.ts +53 -0
  179. package/src/lib/__tests__/view.test.ts +53 -0
  180. package/src/lib/__tests__/worker-response-cache.test.ts +18 -1
  181. package/src/lib/api-settings.ts +239 -12
  182. package/src/lib/collection-sort.ts +7 -5
  183. package/src/lib/constants.ts +2 -0
  184. package/src/lib/feed-path.ts +25 -0
  185. package/src/lib/feed-policy.ts +64 -0
  186. package/src/lib/feed.ts +113 -11
  187. package/src/lib/footnotes.ts +264 -0
  188. package/src/lib/github-sync-site-config.ts +2 -0
  189. package/src/lib/hugo-markdown.ts +15 -3
  190. package/src/lib/jant-branding.ts +1 -0
  191. package/src/lib/link-preview.ts +25 -0
  192. package/src/lib/markdown-manager.ts +193 -5
  193. package/src/lib/markdown.ts +10 -3
  194. package/src/lib/navigation.ts +16 -3
  195. package/src/lib/post-body-html.ts +131 -0
  196. package/src/lib/post-display.ts +47 -8
  197. package/src/lib/render.tsx +9 -2
  198. package/src/lib/resolve-config.ts +100 -16
  199. package/src/lib/schemas.ts +133 -1
  200. package/src/lib/settings-paths.ts +5 -0
  201. package/src/lib/site-skill.ts +78 -0
  202. package/src/lib/summary.ts +16 -5
  203. package/src/lib/timeline.ts +137 -131
  204. package/src/lib/timezones.ts +54 -14
  205. package/src/lib/tiptap-render.ts +359 -83
  206. package/src/lib/translit.ts +288 -0
  207. package/src/lib/url.ts +123 -12
  208. package/src/lib/view.ts +58 -20
  209. package/src/lib/worker-response-cache.ts +11 -18
  210. package/src/middleware/__tests__/public-content-access.test.ts +91 -0
  211. package/src/middleware/config.ts +2 -2
  212. package/src/middleware/public-content-access.ts +87 -0
  213. package/src/node/__tests__/cli-export.test.ts +14 -0
  214. package/src/node/__tests__/cli-migrate.test.ts +6 -0
  215. package/src/node/__tests__/cli-site-snapshot.test.ts +205 -2
  216. package/src/node/__tests__/cli-snapshot-meta.test.ts +100 -0
  217. package/src/node/index.ts +2 -1
  218. package/src/preset.css +16 -2
  219. package/src/routes/__tests__/compose.test.ts +51 -0
  220. package/src/routes/__tests__/site-skill.test.ts +112 -0
  221. package/src/routes/api/__tests__/collections.test.ts +91 -19
  222. package/src/routes/api/__tests__/mcp.test.ts +65 -1
  223. package/src/routes/api/__tests__/nav-items.test.ts +91 -0
  224. package/src/routes/api/__tests__/palette.test.ts +44 -0
  225. package/src/routes/api/__tests__/public-access.test.ts +81 -0
  226. package/src/routes/api/__tests__/settings.test.ts +217 -0
  227. package/src/routes/api/collections.ts +22 -26
  228. package/src/routes/api/export.ts +2 -0
  229. package/src/routes/api/internal/__tests__/post-body-html.test.ts +163 -0
  230. package/src/routes/api/internal/__tests__/sites.test.ts +35 -2
  231. package/src/routes/api/internal/post-body-html.ts +43 -0
  232. package/src/routes/api/internal/sites.ts +24 -0
  233. package/src/routes/api/nav-items.ts +24 -1
  234. package/src/routes/api/posts.ts +14 -10
  235. package/src/routes/api/public/__tests__/archive.test.ts +35 -0
  236. package/src/routes/api/public/__tests__/posts.test.ts +246 -0
  237. package/src/routes/api/public/archive.ts +4 -0
  238. package/src/routes/api/public/posts.ts +48 -24
  239. package/src/routes/api/search.ts +3 -0
  240. package/src/routes/api/settings.ts +33 -0
  241. package/src/routes/auth/__tests__/setup.test.ts +7 -4
  242. package/src/routes/compose.tsx +8 -3
  243. package/src/routes/dash/settings.tsx +45 -2
  244. package/src/routes/feed/__tests__/feed.test.ts +210 -6
  245. package/src/routes/feed/__tests__/sitemap.test.ts +68 -1
  246. package/src/routes/feed/feed.ts +23 -14
  247. package/src/routes/pages/__tests__/archive-params.test.ts +384 -3
  248. package/src/routes/pages/__tests__/collection-routing.test.ts +63 -5
  249. package/src/routes/pages/__tests__/collections.test.ts +9 -9
  250. package/src/routes/pages/__tests__/featured.test.ts +6 -2
  251. package/src/routes/pages/__tests__/preview.test.ts +176 -0
  252. package/src/routes/pages/__tests__/search.test.ts +69 -0
  253. package/src/routes/pages/archive.tsx +90 -22
  254. package/src/routes/pages/collection.tsx +32 -19
  255. package/src/routes/pages/collections.tsx +6 -1
  256. package/src/routes/pages/page.tsx +86 -11
  257. package/src/routes/site-skill.ts +18 -0
  258. package/src/services/__tests__/auth.test.ts +7 -5
  259. package/src/services/__tests__/collection.test.ts +190 -44
  260. package/src/services/__tests__/navigation.test.ts +229 -21
  261. package/src/services/__tests__/post-timeline.test.ts +249 -39
  262. package/src/services/__tests__/post.test.ts +844 -5
  263. package/src/services/__tests__/search.test.ts +48 -1
  264. package/src/services/__tests__/settings.test.ts +8 -0
  265. package/src/services/__tests__/site-admin.test.ts +121 -1
  266. package/src/services/auth.ts +2 -2
  267. package/src/services/bootstrap.ts +3 -2
  268. package/src/services/collection.ts +224 -167
  269. package/src/services/export-theme/assets/client-site.css +1 -1
  270. package/src/services/export-theme/assets/client-site.js +46 -25
  271. package/src/services/export-theme/layouts/_default/list.html +18 -4
  272. package/src/services/export-theme/layouts/_default/rss.xml +8 -12
  273. package/src/services/export-theme/layouts/featured/list.html +5 -4
  274. package/src/services/export-theme/layouts/partials/featured-thread.html +56 -0
  275. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  276. package/src/services/export-theme/layouts/partials/media-gallery.html +1 -0
  277. package/src/services/export-theme/styles/main.css +30 -2
  278. package/src/services/export.ts +94 -85
  279. package/src/services/mcp.ts +37 -27
  280. package/src/services/media.ts +1 -1
  281. package/src/services/navigation.ts +185 -46
  282. package/src/services/path.ts +4 -1
  283. package/src/services/post.ts +1142 -342
  284. package/src/services/search.ts +17 -2
  285. package/src/services/settings.ts +2 -10
  286. package/src/services/site-admin.ts +63 -43
  287. package/src/style-cjk-jp.css +1 -1
  288. package/src/style-cjk-kr.css +1 -1
  289. package/src/style-cjk-tc.css +1 -1
  290. package/src/style-cjk.css +1 -1
  291. package/src/styles/components.css +540 -14
  292. package/src/styles/site-media.css +111 -19
  293. package/src/styles/tokens.css +76 -10
  294. package/src/styles/ui.css +2227 -1347
  295. package/src/types/bindings.ts +3 -0
  296. package/src/types/config.ts +243 -12
  297. package/src/types/constants.ts +22 -1
  298. package/src/types/entities.ts +14 -8
  299. package/src/types/operations.ts +18 -10
  300. package/src/types/props.ts +20 -2
  301. package/src/types/raw-assets.d.ts +5 -0
  302. package/src/types/views.ts +22 -4
  303. package/src/ui/__tests__/font-themes.test.ts +42 -30
  304. package/src/ui/compose/ComposeDialog.tsx +109 -19
  305. package/src/ui/dash/appearance/NavigationContent.tsx +332 -4
  306. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +23 -0
  307. package/src/ui/dash/settings/ConfigEditorContent.tsx +578 -0
  308. package/src/ui/dash/settings/GeneralContent.tsx +15 -5
  309. package/src/ui/dash/settings/SettingsRootContent.tsx +21 -1
  310. package/src/ui/dash/settings/__tests__/ConfigEditorContent.test.tsx +98 -0
  311. package/src/ui/dash/settings/__tests__/SettingsRootContent.test.tsx +15 -0
  312. package/src/ui/feed/CuratedThreadPreview.tsx +16 -4
  313. package/src/ui/feed/LinkCard.tsx +2 -12
  314. package/src/ui/feed/LinkPreview.tsx +2 -7
  315. package/src/ui/feed/NoteCard.tsx +10 -20
  316. package/src/ui/feed/PostStatusBadges.tsx +42 -3
  317. package/src/ui/feed/QuoteCard.tsx +2 -12
  318. package/src/ui/feed/ThreadPreview.tsx +26 -34
  319. package/src/ui/feed/TimelineFeed.tsx +2 -2
  320. package/src/ui/feed/__tests__/thread-preview.test.ts +197 -56
  321. package/src/ui/feed/__tests__/timeline-cards.test.ts +178 -3
  322. package/src/ui/feed/thread-preview-state.ts +5 -7
  323. package/src/ui/font-themes.ts +74 -26
  324. package/src/ui/layouts/BaseLayout.tsx +22 -8
  325. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +64 -1
  326. package/src/ui/pages/ArchivePage.tsx +131 -24
  327. package/src/ui/pages/CollectionPage.tsx +101 -22
  328. package/src/ui/pages/CollectionsPage.tsx +3 -1
  329. package/src/ui/pages/PostPage.tsx +39 -12
  330. package/src/ui/pages/SearchPage.tsx +6 -14
  331. package/src/ui/pages/__tests__/ArchivePage.test.tsx +70 -0
  332. package/src/ui/pages/__tests__/PostPage.test.tsx +88 -0
  333. package/src/ui/shared/CollectionDirectory.tsx +13 -9
  334. package/src/ui/shared/CollectionsManager.tsx +17 -7
  335. package/src/ui/shared/DraftPreviewBar.tsx +49 -0
  336. package/src/ui/shared/HomePageBranding.tsx +2 -2
  337. package/src/ui/shared/MediaGallery.tsx +1 -0
  338. package/src/ui/shared/PostFooter.tsx +38 -13
  339. package/src/ui/shared/__tests__/home-page-branding.test.tsx +1 -1
  340. package/src/ui/shared/__tests__/media-gallery.test.ts +1 -0
  341. package/src/ui/shared/__tests__/navigation-labels.test.ts +2 -2
  342. package/src/ui/shared/__tests__/post-footer.test.ts +15 -0
  343. package/src/ui/shared/collection-management-labels.ts +33 -2
  344. package/src/ui/shared/custom-icons.ts +5 -0
  345. package/src/ui/shared/navigation-labels.ts +2 -4
  346. package/src/ui/shared/post-article-attributes.ts +52 -0
  347. package/dist/app-DZDlmh7C.js +0 -6
  348. package/dist/client/_assets/client-Cevfpm8H.css +0 -2
  349. package/dist/client/_assets/client-auth-v3yvhbgZ.js +0 -4736
  350. package/src/client/tiptap/wrapping-input-rules.ts +0 -102
@@ -5,14 +5,19 @@
5
5
  * Must be registered last.
6
6
  */
7
7
 
8
+ import { msg } from "@lingui/core/macro";
8
9
  import { Hono, type Context } from "hono";
9
10
  import type { Bindings } from "../../types.js";
10
11
  import type { AppVariables } from "../../types/app-context.js";
12
+ import { getI18n } from "../../i18n/index.js";
13
+ import { requireAuth } from "../../middleware/auth.js";
11
14
  import { PostPage } from "../../ui/pages/PostPage.js";
15
+ import { DraftPreviewBar } from "../../ui/shared/DraftPreviewBar.js";
12
16
  import { getNavigationData } from "../../lib/navigation.js";
13
17
  import { renderPublicPage } from "../../lib/render.js";
14
18
  import { buildPageTitle } from "../../lib/page-title.js";
15
19
  import { buildPostMeta } from "../../lib/post-meta.js";
20
+ import { isReservedPath } from "../../lib/constants.js";
16
21
  import {
17
22
  assemblePostPageDisplay,
18
23
  type PostPageDisplayData,
@@ -203,7 +208,7 @@ function canRenderDraftAboutEditor(
203
208
  async function renderPost(
204
209
  c: Context<Env>,
205
210
  post: Post,
206
- options: { allowDraft?: boolean } = {},
211
+ options: { allowDraft?: boolean; isPreview?: boolean } = {},
207
212
  ) {
208
213
  // Start navData fetch immediately — it's independent of thread/media queries
209
214
  const navDataPromise = getNavigationData(c);
@@ -211,6 +216,7 @@ async function renderPost(
211
216
  // Private-post access is validated before renderPost() is called.
212
217
  isAuthenticated: true,
213
218
  allowDraft: options.allowDraft,
219
+ includeDraftThread: options.isPreview,
214
220
  });
215
221
  if (!display) {
216
222
  return c.notFound();
@@ -218,14 +224,51 @@ async function renderPost(
218
224
 
219
225
  const navData = await navDataPromise;
220
226
  const meta = buildPostMeta(post, navData.siteName);
227
+ const postView =
228
+ options.isPreview && display.postView.status === "draft"
229
+ ? {
230
+ ...display.postView,
231
+ permalink: toPublicPath(
232
+ `/preview/${display.postView.slug}`,
233
+ c.var.appConfig.sitePathPrefix,
234
+ ),
235
+ }
236
+ : display.postView;
237
+ const threadPostViews = options.isPreview
238
+ ? display.threadPostViews?.map((threadPost) =>
239
+ threadPost.status === "draft"
240
+ ? {
241
+ ...threadPost,
242
+ permalink: toPublicPath(
243
+ `/preview/${threadPost.slug}`,
244
+ c.var.appConfig.sitePathPrefix,
245
+ ),
246
+ }
247
+ : threadPost,
248
+ )
249
+ : display.threadPostViews;
250
+ const previewTitle = options.isPreview
251
+ ? getI18n(c)._(
252
+ msg({
253
+ message: "Draft preview",
254
+ comment: "@context: Browser title prefix for a draft preview page",
255
+ }),
256
+ )
257
+ : null;
221
258
  const canonicalHref = buildPostCanonicalHref(
222
259
  display.postView,
223
260
  display.threadPostViews,
224
261
  c.var.appConfig.siteUrl,
225
262
  );
263
+ const draftEditHref = `${toPublicPath(
264
+ `/preview/${post.slug}`,
265
+ c.var.appConfig.sitePathPrefix,
266
+ )}?edit=1`;
226
267
 
227
268
  return renderPublicPage(c, {
228
- title: buildPageTitle(meta.title, navData.siteName),
269
+ title: previewTitle
270
+ ? buildPageTitle(previewTitle, meta.title, navData.siteName)
271
+ : buildPageTitle(meta.title, navData.siteName),
229
272
  description: meta.description,
230
273
  ...(post.status === "published"
231
274
  ? {
@@ -247,18 +290,58 @@ async function renderPost(
247
290
  }
248
291
  : {}),
249
292
  navData,
293
+ ...(options.isPreview
294
+ ? {
295
+ pageChrome: <DraftPreviewBar editHref={draftEditHref} />,
296
+ noindex: true,
297
+ }
298
+ : {}),
250
299
  content: (
251
- <PostPage post={display.postView} threadPosts={display.threadPostViews} />
300
+ <PostPage
301
+ post={postView}
302
+ threadPosts={threadPostViews}
303
+ isPreview={options.isPreview}
304
+ />
252
305
  ),
253
306
  });
254
307
  }
255
308
 
309
+ pageRoutes.get("/preview/:slug", requireAuth(), async (c) => {
310
+ const post = await c.var.services.posts.getBySlug(c.req.param("slug"));
311
+ if (!post) return c.notFound();
312
+
313
+ if (post.status === "published") {
314
+ return c.redirect(
315
+ toPublicPath(`/${post.slug}`, c.var.appConfig.sitePathPrefix),
316
+ );
317
+ }
318
+
319
+ c.header("Cache-Control", "private, no-store");
320
+ c.header("X-Robots-Tag", "noindex, nofollow");
321
+ return renderPost(c, post, { allowDraft: true, isPreview: true });
322
+ });
323
+
256
324
  // Catch-all for path-registry backed post URLs, aliases, and redirects
257
325
  pageRoutes.get("/*", async (c) => {
258
326
  const fullPath = c.req.path.slice(1); // Remove leading /
259
327
  if (!fullPath) return c.notFound();
260
328
  const sitePathPrefix = c.var.appConfig.sitePathPrefix;
261
329
 
330
+ // Explicit application routes are registered before this catch-all. Any
331
+ // unmatched path under a reserved prefix is therefore an application 404,
332
+ // never a legacy alias or redirect from path_registry.
333
+ if (isReservedPath(fullPath)) return c.notFound();
334
+
335
+ // Stored redirects outrank the normal post/collection/deep-link resolvers,
336
+ // but can never shadow an explicit or reserved application route.
337
+ const resolved = await c.var.services.paths.resolve(fullPath);
338
+ if (resolved?.kind === "redirect" && resolved.redirectToPath) {
339
+ return c.redirect(
340
+ toPublicHref(`/${resolved.redirectToPath}`, sitePathPrefix),
341
+ resolved.redirectType ?? 301,
342
+ );
343
+ }
344
+
262
345
  if (fullPath.endsWith("/feed")) {
263
346
  const collectionPath = fullPath.slice(0, -"/feed".length);
264
347
  if (!collectionPath) return c.notFound();
@@ -360,16 +443,8 @@ pageRoutes.get("/*", async (c) => {
360
443
  }
361
444
  }
362
445
 
363
- const resolved = await c.var.services.paths.resolve(fullPath);
364
446
  if (!resolved) return c.notFound();
365
447
 
366
- if (resolved.kind === "redirect" && resolved.redirectToPath) {
367
- return c.redirect(
368
- toPublicHref(`/${resolved.redirectToPath}`, sitePathPrefix),
369
- resolved.redirectType ?? 301,
370
- );
371
- }
372
-
373
448
  if (resolved.kind === "archive" && resolved.archiveQuery) {
374
449
  const overrides = Object.fromEntries(
375
450
  new URLSearchParams(resolved.archiveQuery),
@@ -0,0 +1,18 @@
1
+ import { Hono } from "hono";
2
+ import { renderSiteSkill } from "../lib/site-skill.js";
3
+ import type { Bindings } from "../types.js";
4
+ import type { AppVariables } from "../types/app-context.js";
5
+
6
+ type Env = { Bindings: Bindings; Variables: AppVariables };
7
+
8
+ export const siteSkillRoutes = new Hono<Env>();
9
+
10
+ siteSkillRoutes.get("/skill.md", (c) => {
11
+ return new Response(renderSiteSkill(c.var.appConfig.siteUrl), {
12
+ headers: {
13
+ "Cache-Control": "public, max-age=300, s-maxage=3600",
14
+ "Content-Type": "text/markdown; charset=utf-8",
15
+ "X-Robots-Tag": "noindex",
16
+ },
17
+ });
18
+ });
@@ -11,7 +11,7 @@ import {
11
11
  media,
12
12
  navItems,
13
13
  pathRegistry,
14
- postCollections,
14
+ threadCollections,
15
15
  posts,
16
16
  session,
17
17
  settings as settingsTable,
@@ -165,9 +165,9 @@ describe("AuthService", () => {
165
165
  updatedAt: timestamp,
166
166
  });
167
167
 
168
- await db.insert(postCollections).values({
168
+ await db.insert(threadCollections).values({
169
169
  siteId: DEFAULT_TEST_SITE_ID,
170
- postId: "pst_01km9authdelete00000000000",
170
+ threadId: "pst_01km9authdelete00000000000",
171
171
  collectionId: "col_01km9authdelete00000000000",
172
172
  createdAt: timestamp,
173
173
  });
@@ -245,7 +245,9 @@ describe("AuthService", () => {
245
245
  const remainingDirectoryItems = await db
246
246
  .select()
247
247
  .from(collectionDirectoryItems);
248
- const remainingPostCollections = await db.select().from(postCollections);
248
+ const remainingThreadCollections = await db
249
+ .select()
250
+ .from(threadCollections);
249
251
  const remainingNavItems = await db.select().from(navItems);
250
252
  const remainingApiTokens = await db.select().from(apiTokens);
251
253
  const existingSites = await db.select().from(sites);
@@ -262,7 +264,7 @@ describe("AuthService", () => {
262
264
  expect(remainingCollections).toHaveLength(0);
263
265
  expect(remainingPaths).toHaveLength(0);
264
266
  expect(remainingDirectoryItems).toHaveLength(0);
265
- expect(remainingPostCollections).toHaveLength(0);
267
+ expect(remainingThreadCollections).toHaveLength(0);
266
268
  expect(remainingNavItems).toHaveLength(0);
267
269
  expect(remainingApiTokens).toHaveLength(0);
268
270
  expect(existingSites).toHaveLength(0);
@@ -281,7 +281,7 @@ describe("CollectionService", () => {
281
281
  format: "note",
282
282
  bodyMarkdown: "Book note",
283
283
  });
284
- await collectionService.addPost(reading.id, post.id);
284
+ await collectionService.addThread(reading.id, post.id);
285
285
 
286
286
  const directory = await collectionService.listDirectoryData();
287
287
 
@@ -297,7 +297,7 @@ describe("CollectionService", () => {
297
297
  type: "collection",
298
298
  collection: expect.objectContaining({
299
299
  id: reading.id,
300
- postCount: 1,
300
+ threadCount: 1,
301
301
  recentActivityAt: post.lastActivityAt,
302
302
  }),
303
303
  }),
@@ -309,6 +309,61 @@ describe("CollectionService", () => {
309
309
  ]);
310
310
  });
311
311
 
312
+ // The directory's "recent activity" uses the same definition as every
313
+ // other surface: the Thread gained a post. Editing one is not activity,
314
+ // or fixing a typo would reshuffle the whole collection directory.
315
+ it("does not treat a later Root edit as recent activity", async () => {
316
+ vi.useFakeTimers();
317
+ try {
318
+ vi.setSystemTime(new Date("2024-01-01T00:00:00Z"));
319
+ const collection = await collectionService.create({
320
+ slug: "edited-thread",
321
+ title: "Edited Thread",
322
+ });
323
+ const root = await postService.create({
324
+ format: "note",
325
+ bodyMarkdown: "before",
326
+ });
327
+ await collectionService.addThread(collection.id, root.id);
328
+
329
+ vi.setSystemTime(new Date("2024-01-01T00:01:00Z"));
330
+ await postService.update(root.id, { bodyMarkdown: "after" });
331
+
332
+ const directory = await collectionService.listDirectoryData();
333
+ expect(directory.collections[0]?.recentActivityAt).toBe(1704067200);
334
+ } finally {
335
+ vi.useRealTimers();
336
+ }
337
+ });
338
+
339
+ it("treats a new reply as recent activity", async () => {
340
+ vi.useFakeTimers();
341
+ try {
342
+ vi.setSystemTime(new Date("2024-01-01T00:00:00Z"));
343
+ const collection = await collectionService.create({
344
+ slug: "growing-thread",
345
+ title: "Growing Thread",
346
+ });
347
+ const root = await postService.create({
348
+ format: "note",
349
+ bodyMarkdown: "root",
350
+ });
351
+ await collectionService.addThread(collection.id, root.id);
352
+
353
+ vi.setSystemTime(new Date("2024-01-01T00:01:00Z"));
354
+ await postService.create({
355
+ format: "note",
356
+ bodyMarkdown: "reply",
357
+ replyToId: root.id,
358
+ });
359
+
360
+ const directory = await collectionService.listDirectoryData();
361
+ expect(directory.collections[0]?.recentActivityAt).toBe(1704067260);
362
+ } finally {
363
+ vi.useRealTimers();
364
+ }
365
+ });
366
+
312
367
  it("includes custom links in directory items", async () => {
313
368
  const link = await collectionService.createDirectoryItem({
314
369
  type: "link",
@@ -451,7 +506,7 @@ describe("CollectionService", () => {
451
506
  bodyMarkdown: "test post",
452
507
  });
453
508
 
454
- await collectionService.addPost(collection.id, post.id);
509
+ await collectionService.addThread(collection.id, post.id);
455
510
 
456
511
  // Verify association exists
457
512
  const before = await collectionService.getCollectionsByPostId(post.id);
@@ -736,15 +791,15 @@ describe("CollectionService", () => {
736
791
  });
737
792
  });
738
793
 
739
- describe("getPostCounts", () => {
794
+ describe("getThreadCounts", () => {
740
795
  it("returns empty map when no posts exist", async () => {
741
796
  await collectionService.create({ slug: "empty", title: "Empty" });
742
797
 
743
- const counts = await collectionService.getPostCounts();
798
+ const counts = await collectionService.getThreadCounts();
744
799
  expect(counts.size).toBe(0);
745
800
  });
746
801
 
747
- it("returns correct counts for collections with posts", async () => {
802
+ it("returns correct counts for collections with Threads", async () => {
748
803
  const col1 = await collectionService.create({
749
804
  slug: "col1",
750
805
  title: "Col 1",
@@ -767,16 +822,16 @@ describe("CollectionService", () => {
767
822
  bodyMarkdown: "post 3",
768
823
  });
769
824
 
770
- await collectionService.addPost(col1.id, p1.id);
771
- await collectionService.addPost(col1.id, p2.id);
772
- await collectionService.addPost(col2.id, p3.id);
825
+ await collectionService.addThread(col1.id, p1.id);
826
+ await collectionService.addThread(col1.id, p2.id);
827
+ await collectionService.addThread(col2.id, p3.id);
773
828
 
774
- const counts = await collectionService.getPostCounts();
829
+ const counts = await collectionService.getThreadCounts();
775
830
  expect(counts.get(col1.id)).toBe(2);
776
831
  expect(counts.get(col2.id)).toBe(1);
777
832
  });
778
833
 
779
- it("does not count posts without a collection", async () => {
834
+ it("does not count Threads without a collection", async () => {
780
835
  const col = await collectionService.create({
781
836
  slug: "col",
782
837
  title: "Col",
@@ -791,14 +846,14 @@ describe("CollectionService", () => {
791
846
  bodyMarkdown: "no collection",
792
847
  });
793
848
 
794
- await collectionService.addPost(col.id, p1.id);
849
+ await collectionService.addThread(col.id, p1.id);
795
850
 
796
- const counts = await collectionService.getPostCounts();
851
+ const counts = await collectionService.getThreadCounts();
797
852
  expect(counts.get(col.id)).toBe(1);
798
853
  expect(counts.size).toBe(1);
799
854
  });
800
855
 
801
- it("does not count soft-deleted posts", async () => {
856
+ it("does not count deleted Threads", async () => {
802
857
  const col = await collectionService.create({
803
858
  slug: "col",
804
859
  title: "Col",
@@ -813,19 +868,18 @@ describe("CollectionService", () => {
813
868
  bodyMarkdown: "still alive",
814
869
  });
815
870
 
816
- await collectionService.addPost(col.id, post.id);
817
- await collectionService.addPost(col.id, post2.id);
871
+ await collectionService.addThread(col.id, post.id);
872
+ await collectionService.addThread(col.id, post2.id);
818
873
 
819
- // Soft-delete one post
820
874
  await postService.delete(post.id);
821
875
 
822
- const counts = await collectionService.getPostCounts();
876
+ const counts = await collectionService.getThreadCounts();
823
877
  expect(counts.get(col.id)).toBe(1);
824
878
  });
825
879
  });
826
880
 
827
- describe("addPost / removePost", () => {
828
- it("adds a post to a collection", async () => {
881
+ describe("addThread / removeThread", () => {
882
+ it("adds a Thread to a collection", async () => {
829
883
  const col = await collectionService.create({
830
884
  slug: "test",
831
885
  title: "Test",
@@ -835,7 +889,7 @@ describe("CollectionService", () => {
835
889
  bodyMarkdown: "test",
836
890
  });
837
891
 
838
- await collectionService.addPost(col.id, post.id);
892
+ await collectionService.addThread(col.id, post.id);
839
893
 
840
894
  const collections = await collectionService.getCollectionsByPostId(
841
895
  post.id,
@@ -854,14 +908,14 @@ describe("CollectionService", () => {
854
908
  bodyMarkdown: "test",
855
909
  });
856
910
 
857
- await collectionService.addPost(col.id, post.id);
858
- await collectionService.addPost(col.id, post.id); // duplicate
911
+ await collectionService.addThread(col.id, post.id);
912
+ await collectionService.addThread(col.id, post.id); // duplicate
859
913
 
860
- const postIds = await collectionService.getPostIds(col.id);
861
- expect(postIds).toHaveLength(1);
914
+ const threadIds = await collectionService.getThreadIds(col.id);
915
+ expect(threadIds).toHaveLength(1);
862
916
  });
863
917
 
864
- it("removes a post from a collection", async () => {
918
+ it("removes a Thread from a collection", async () => {
865
919
  const col = await collectionService.create({
866
920
  slug: "test",
867
921
  title: "Test",
@@ -871,14 +925,43 @@ describe("CollectionService", () => {
871
925
  bodyMarkdown: "test",
872
926
  });
873
927
 
874
- await collectionService.addPost(col.id, post.id);
875
- await collectionService.removePost(col.id, post.id);
928
+ await collectionService.addThread(col.id, post.id);
929
+ await collectionService.removeThread(col.id, post.id);
876
930
 
877
931
  const collections = await collectionService.getCollectionsByPostId(
878
932
  post.id,
879
933
  );
880
934
  expect(collections).toHaveLength(0);
881
935
  });
936
+
937
+ it("normalizes Child mutations to the shared Thread root", async () => {
938
+ const collection = await collectionService.create({
939
+ slug: "child-mutation",
940
+ title: "Child Mutation",
941
+ });
942
+ const root = await postService.create({
943
+ format: "note",
944
+ bodyMarkdown: "root",
945
+ });
946
+ const child = await postService.create({
947
+ format: "note",
948
+ bodyMarkdown: "child",
949
+ replyToId: root.id,
950
+ });
951
+
952
+ await collectionService.addThread(collection.id, child.id);
953
+ expect(await collectionService.getThreadIds(collection.id)).toEqual([
954
+ root.id,
955
+ ]);
956
+ expect(
957
+ (await collectionService.getCollectionsByPostId(root.id)).map(
958
+ (item) => item.id,
959
+ ),
960
+ ).toEqual([collection.id]);
961
+
962
+ await collectionService.removeThread(collection.id, child.id);
963
+ expect(await collectionService.getThreadIds(collection.id)).toEqual([]);
964
+ });
882
965
  });
883
966
 
884
967
  describe("getCollectionsByPostId", () => {
@@ -899,8 +982,8 @@ describe("CollectionService", () => {
899
982
  bodyMarkdown: "test",
900
983
  });
901
984
 
902
- await collectionService.addPost(cols[0]?.id ?? "", post.id);
903
- await collectionService.addPost(cols[1]?.id ?? "", post.id);
985
+ await collectionService.addThread(cols[0]?.id ?? "", post.id);
986
+ await collectionService.addThread(cols[1]?.id ?? "", post.id);
904
987
 
905
988
  const collections = await collectionService.getCollectionsByPostId(
906
989
  post.id,
@@ -919,10 +1002,70 @@ describe("CollectionService", () => {
919
1002
  );
920
1003
  expect(collections).toHaveLength(0);
921
1004
  });
1005
+
1006
+ it("projects one shared membership set onto the root and every child", async () => {
1007
+ const collection = await collectionService.create({
1008
+ slug: "shared-thread",
1009
+ title: "Shared Thread",
1010
+ });
1011
+ const root = await postService.create({
1012
+ format: "note",
1013
+ bodyMarkdown: "root",
1014
+ });
1015
+ const child = await postService.create({
1016
+ format: "note",
1017
+ bodyMarkdown: "child",
1018
+ replyToId: root.id,
1019
+ });
1020
+
1021
+ await collectionService.addThread(collection.id, root.id);
1022
+
1023
+ const [rootCollections, childCollections, batch] = await Promise.all([
1024
+ collectionService.getCollectionsByPostId(root.id),
1025
+ collectionService.getCollectionsByPostId(child.id),
1026
+ collectionService.getCollectionsByPostIds([root.id, child.id]),
1027
+ ]);
1028
+
1029
+ expect(rootCollections.map((item) => item.id)).toEqual([collection.id]);
1030
+ expect(childCollections.map((item) => item.id)).toEqual([collection.id]);
1031
+ expect(batch.get(root.id)?.map((item) => item.id)).toEqual([
1032
+ collection.id,
1033
+ ]);
1034
+ expect(batch.get(child.id)?.map((item) => item.id)).toEqual([
1035
+ collection.id,
1036
+ ]);
1037
+ });
1038
+
1039
+ it("keeps shared membership when a Child is deleted and removes it with the Root", async () => {
1040
+ const collection = await collectionService.create({
1041
+ slug: "thread-lifecycle",
1042
+ title: "Thread Lifecycle",
1043
+ });
1044
+ const root = await postService.create({
1045
+ format: "note",
1046
+ bodyMarkdown: "root",
1047
+ });
1048
+ const child = await postService.create({
1049
+ format: "note",
1050
+ bodyMarkdown: "child",
1051
+ replyToId: root.id,
1052
+ });
1053
+ await collectionService.addThread(collection.id, root.id);
1054
+
1055
+ await postService.delete(child.id);
1056
+ expect(
1057
+ (await collectionService.getCollectionsByPostId(root.id)).map(
1058
+ (item) => item.id,
1059
+ ),
1060
+ ).toEqual([collection.id]);
1061
+
1062
+ await postService.delete(root.id);
1063
+ expect(await collectionService.getThreadIds(collection.id)).toEqual([]);
1064
+ });
922
1065
  });
923
1066
 
924
- describe("getPostIds", () => {
925
- it("returns all post IDs in a collection", async () => {
1067
+ describe("getThreadIds", () => {
1068
+ it("returns all Thread root IDs in a collection", async () => {
926
1069
  const col = await collectionService.create({
927
1070
  slug: "test",
928
1071
  title: "Test",
@@ -936,18 +1079,18 @@ describe("CollectionService", () => {
936
1079
  bodyMarkdown: "two",
937
1080
  });
938
1081
 
939
- await collectionService.addPost(col.id, p1.id);
940
- await collectionService.addPost(col.id, p2.id);
1082
+ await collectionService.addThread(col.id, p1.id);
1083
+ await collectionService.addThread(col.id, p2.id);
941
1084
 
942
- const ids = await collectionService.getPostIds(col.id);
1085
+ const ids = await collectionService.getThreadIds(col.id);
943
1086
  expect(ids).toHaveLength(2);
944
1087
  expect(ids).toContain(p1.id);
945
1088
  expect(ids).toContain(p2.id);
946
1089
  });
947
1090
  });
948
1091
 
949
- describe("syncPostCollections", () => {
950
- it("replaces all collection memberships for a post", async () => {
1092
+ describe("syncThreadCollections", () => {
1093
+ it("replaces all collection memberships for a Thread", async () => {
951
1094
  const col1 = await collectionService.create({
952
1095
  slug: "col1",
953
1096
  title: "Col 1",
@@ -967,11 +1110,14 @@ describe("CollectionService", () => {
967
1110
  });
968
1111
 
969
1112
  // Initially in col1 and col2
970
- await collectionService.addPost(col1.id, post.id);
971
- await collectionService.addPost(col2.id, post.id);
1113
+ await collectionService.addThread(col1.id, post.id);
1114
+ await collectionService.addThread(col2.id, post.id);
972
1115
 
973
1116
  // Sync to col2 and col3
974
- await collectionService.syncPostCollections(post.id, [col2.id, col3.id]);
1117
+ await collectionService.syncThreadCollections(post.id, [
1118
+ col2.id,
1119
+ col3.id,
1120
+ ]);
975
1121
 
976
1122
  const collections = await collectionService.getCollectionsByPostId(
977
1123
  post.id,
@@ -993,8 +1139,8 @@ describe("CollectionService", () => {
993
1139
  bodyMarkdown: "test",
994
1140
  });
995
1141
 
996
- await collectionService.addPost(col.id, post.id);
997
- await collectionService.syncPostCollections(post.id, []);
1142
+ await collectionService.addThread(col.id, post.id);
1143
+ await collectionService.syncThreadCollections(post.id, []);
998
1144
 
999
1145
  const collections = await collectionService.getCollectionsByPostId(
1000
1146
  post.id,
@@ -1018,13 +1164,13 @@ describe("CollectionService", () => {
1018
1164
  const originalTransaction = dbWithoutTransaction.transaction.bind(db);
1019
1165
  dbWithoutTransaction.transaction = async () => {
1020
1166
  throw new Error(
1021
- "sqlite syncPostCollections() should not call transaction()",
1167
+ "sqlite syncThreadCollections() should not call transaction()",
1022
1168
  );
1023
1169
  };
1024
1170
 
1025
1171
  try {
1026
1172
  await expect(
1027
- collectionService.syncPostCollections(post.id, [col.id]),
1173
+ collectionService.syncThreadCollections(post.id, [col.id]),
1028
1174
  ).resolves.toBeUndefined();
1029
1175
  } finally {
1030
1176
  dbWithoutTransaction.transaction = originalTransaction;