@jant/core 0.6.16 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (592) hide show
  1. package/README.md +11 -11
  2. package/bin/commands/assets/upload.js +192 -15
  3. package/bin/commands/export.js +3 -1
  4. package/bin/commands/import-site.js +749 -220
  5. package/bin/commands/reset-password.js +21 -8
  6. package/bin/commands/setup.js +136 -0
  7. package/bin/commands/site/export.js +71 -34
  8. package/bin/commands/site/pull-media.js +2 -6
  9. package/bin/commands/site/snapshot/export.js +19 -61
  10. package/bin/commands/site/snapshot/import.js +23 -24
  11. package/bin/lib/d1-query.js +92 -18
  12. package/bin/lib/hugo-markdown.js +4 -0
  13. package/bin/lib/node-env.js +40 -4
  14. package/bin/lib/site-pull-media.js +21 -28
  15. package/bin/lib/site-selection.js +94 -29
  16. package/bin/lib/site-snapshot.js +338 -3
  17. package/bin/lib/sql-export.js +68 -5
  18. package/bin/lib/wrangler-cli.js +37 -2
  19. package/bin/lib/wrangler-json.js +35 -0
  20. package/bin/lib/zip-archive.js +187 -0
  21. package/dist/{app-C7_4oab9.js → app-ZcaI1kPN.js} +53190 -13529
  22. package/dist/client/.vite/manifest.json +255 -90
  23. package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
  24. package/dist/client/_assets/chunks/create-editor-B-m7X7S5.js +261 -0
  25. package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
  26. package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
  27. package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
  28. package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
  29. package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
  30. package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
  31. package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
  32. package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
  33. package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
  34. package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
  35. package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
  36. package/dist/client/_assets/chunks/sortable-list-BJyd-LXE.js +2 -0
  37. package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
  38. package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
  39. package/dist/client/_assets/chunks/unsafe-svg-BkscJx69.js +2 -0
  40. package/dist/client/_assets/client-DAhoqPdr.css +2 -0
  41. package/dist/client/_assets/client-auth-CAMfTrKW.js +828 -0
  42. package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
  43. package/dist/client/_assets/client-compose-D6rExKVK.js +2004 -0
  44. package/dist/client/_assets/client-knSEyUJO.js +295 -0
  45. package/dist/client/_assets/client-manage-sVOkqVbH.js +2170 -0
  46. package/dist/client/_assets/client-settings-CGR_nZ78.js +1112 -0
  47. package/dist/github-sync-Gw4orAHk.js +8398 -0
  48. package/dist/index.js +2 -5
  49. package/dist/node.js +11 -9
  50. package/package.json +15 -5
  51. package/src/__tests__/auth-session-freshness.test.ts +57 -0
  52. package/src/__tests__/basecoat-variants.test.ts +121 -0
  53. package/src/__tests__/bin/assets-upload.test.ts +68 -0
  54. package/src/__tests__/dev-scripts.test.ts +203 -0
  55. package/src/__tests__/export-collection-order.test.ts +276 -0
  56. package/src/__tests__/export-feed-ids.test.ts +184 -0
  57. package/src/__tests__/export-feed-order.test.ts +294 -0
  58. package/src/__tests__/export-hugo-build.test.ts +390 -0
  59. package/src/__tests__/export-import-roundtrip.test.ts +94 -0
  60. package/src/__tests__/export-service.test.ts +842 -28
  61. package/src/__tests__/export-smart-collection.test.ts +329 -0
  62. package/src/__tests__/helpers/component-css.ts +91 -0
  63. package/src/__tests__/helpers/db.ts +33 -0
  64. package/src/__tests__/helpers/export-fixtures.ts +3 -0
  65. package/src/__tests__/helpers/hugo-site.ts +146 -0
  66. package/src/__tests__/import-site-command.test.ts +487 -1
  67. package/src/__tests__/mise-config.test.ts +75 -4
  68. package/src/__tests__/node-dev-tasks.test.ts +264 -0
  69. package/src/__tests__/site-export-canonical-import.test.ts +149 -0
  70. package/src/__tests__/snapshot-canonical-replay.test.ts +179 -0
  71. package/src/__tests__/snapshot-settings.test.ts +97 -0
  72. package/src/__tests__/snapshot-tables.test.ts +219 -0
  73. package/src/__tests__/sql-export.test.ts +173 -0
  74. package/src/__tests__/stylesheet-audience.test.ts +159 -0
  75. package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
  76. package/src/__tests__/zip-archive.test.ts +106 -0
  77. package/src/app.tsx +86 -22
  78. package/src/auth.ts +18 -1
  79. package/src/client/__tests__/collection-page-actions.test.ts +51 -2
  80. package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
  81. package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
  82. package/src/client/__tests__/compose-bridge.test.ts +272 -0
  83. package/src/client/__tests__/compose-launch.test.ts +62 -1
  84. package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
  85. package/src/client/__tests__/compose-triggers.test.ts +90 -0
  86. package/src/client/__tests__/copy-field.test.ts +162 -0
  87. package/src/client/__tests__/custom-url-menu.test.ts +127 -6
  88. package/src/client/__tests__/image-processor.test.ts +324 -2
  89. package/src/client/__tests__/lazy-entries.test.ts +92 -0
  90. package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
  91. package/src/client/__tests__/runtime-paths.test.ts +84 -0
  92. package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
  93. package/src/client/__tests__/toast.test.ts +23 -0
  94. package/src/client/__tests__/video-processor-probe.test.ts +175 -0
  95. package/src/client/__tests__/video-processor.test.ts +181 -0
  96. package/src/client/audio-processor.ts +24 -15
  97. package/src/client/audio-waveform.ts +49 -0
  98. package/src/client/collection-dialog-host.ts +72 -0
  99. package/src/client/collection-navigation.ts +51 -16
  100. package/src/client/collection-page-actions.ts +26 -0
  101. package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
  102. package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
  103. package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
  104. package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
  105. package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
  106. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
  107. package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
  108. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
  109. package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
  110. package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
  111. package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
  112. package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
  113. package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
  114. package/src/client/components/__tests__/jant-settings-general.test.ts +561 -172
  115. package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
  116. package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
  117. package/src/client/components/collection-dialog-types.ts +49 -0
  118. package/src/client/components/collection-manager-types.ts +32 -1
  119. package/src/client/components/collection-types.ts +1 -15
  120. package/src/client/components/compose-types.ts +75 -1
  121. package/src/client/components/jant-collection-dialog.ts +722 -0
  122. package/src/client/components/jant-collection-directory.ts +567 -182
  123. package/src/client/components/jant-collection-form.ts +53 -232
  124. package/src/client/components/jant-command-palette.ts +100 -49
  125. package/src/client/components/jant-compose-dialog.ts +1486 -707
  126. package/src/client/components/jant-compose-editor.ts +673 -336
  127. package/src/client/components/jant-compose-fullscreen.ts +51 -43
  128. package/src/client/components/jant-config-editor.ts +65 -53
  129. package/src/client/components/jant-confirm-dialog.ts +13 -11
  130. package/src/client/components/jant-locale-picker.ts +418 -0
  131. package/src/client/components/jant-media-lightbox.ts +146 -136
  132. package/src/client/components/jant-nav-manager.ts +690 -366
  133. package/src/client/components/jant-post-menu.ts +1086 -282
  134. package/src/client/components/jant-post-picker.ts +324 -0
  135. package/src/client/components/jant-repo-picker-types.ts +6 -1
  136. package/src/client/components/jant-repo-picker.ts +137 -108
  137. package/src/client/components/jant-settings-avatar.ts +31 -27
  138. package/src/client/components/jant-settings-general.ts +622 -543
  139. package/src/client/components/jant-settings-language.ts +1087 -0
  140. package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
  141. package/src/client/components/jant-text-preview.ts +94 -86
  142. package/src/client/components/nav-manager-types.ts +62 -1
  143. package/src/client/components/settings-types.ts +14 -29
  144. package/src/client/components/smart-collection-conditions.ts +285 -0
  145. package/src/client/components/smart-collection-dialog-types.ts +68 -0
  146. package/src/client/compose-bridge.ts +214 -141
  147. package/src/client/compose-launch.ts +76 -2
  148. package/src/client/compose-shortcuts.ts +6 -8
  149. package/src/client/compose-triggers.ts +50 -0
  150. package/src/client/copy-field.ts +108 -0
  151. package/src/client/custom-url-menu.ts +60 -4
  152. package/src/client/icons.ts +14 -0
  153. package/src/client/image-plan.ts +165 -0
  154. package/src/client/image-processor.ts +195 -117
  155. package/src/client/image-worker-client.ts +45 -0
  156. package/src/client/image-worker.ts +135 -0
  157. package/src/client/lazy-entries.ts +95 -0
  158. package/src/client/lazy-slugify.ts +2 -2
  159. package/src/client/media-scroll-hint.ts +43 -2
  160. package/src/client/mediabunny.ts +29 -0
  161. package/src/client/post-picker.ts +41 -0
  162. package/src/client/runtime-paths.ts +74 -1
  163. package/src/client/settings-bridge.ts +4 -10
  164. package/src/client/site-header-nav.js +18 -6
  165. package/src/client/smart-collection-dialog-host.ts +116 -0
  166. package/src/client/smart-collection-page-actions.ts +208 -0
  167. package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
  168. package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
  169. package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
  170. package/src/client/tiptap/__tests__/list-editing.test.ts +224 -86
  171. package/src/client/tiptap/__tests__/mark-exit.test.ts +1 -2
  172. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +26 -0
  173. package/src/client/tiptap/bubble-menu.ts +47 -16
  174. package/src/client/tiptap/extensions.ts +0 -3
  175. package/src/client/tiptap/floating-position.ts +216 -2
  176. package/src/client/tiptap/inline-image-upload.ts +7 -3
  177. package/src/client/tiptap/link-toolbar.ts +22 -9
  178. package/src/client/tiptap/structural-keymap.ts +158 -47
  179. package/src/client/toast.ts +13 -0
  180. package/src/client/types/vite-worker.d.ts +12 -0
  181. package/src/client/upload-with-metadata.ts +3 -13
  182. package/src/client/video-processor.ts +351 -107
  183. package/src/client-auth.ts +14 -31
  184. package/src/client-compose.ts +19 -0
  185. package/src/client-manage.ts +13 -0
  186. package/src/client-settings.ts +14 -0
  187. package/src/client-site.ts +10 -3
  188. package/src/client.ts +4 -0
  189. package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
  190. package/src/db/__tests__/d1-query.test.ts +56 -1
  191. package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
  192. package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
  193. package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
  194. package/src/db/migrations/0032_married_tomas.sql +3 -0
  195. package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
  196. package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
  197. package/src/db/migrations/meta/0032_snapshot.json +2582 -0
  198. package/src/db/migrations/meta/0033_snapshot.json +2816 -0
  199. package/src/db/migrations/meta/0034_snapshot.json +2816 -0
  200. package/src/db/migrations/meta/_journal.json +21 -0
  201. package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
  202. package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
  203. package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
  204. package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
  205. package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
  206. package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
  207. package/src/db/migrations/pg/meta/_journal.json +21 -0
  208. package/src/db/pg/schema.ts +195 -27
  209. package/src/db/post-visibility.ts +117 -0
  210. package/src/db/schema.ts +222 -27
  211. package/src/i18n/__tests__/detect.test.ts +26 -81
  212. package/src/i18n/__tests__/supported-locales.test.ts +83 -0
  213. package/src/i18n/context.tsx +12 -2
  214. package/src/i18n/detect.ts +7 -66
  215. package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
  216. package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
  217. package/src/i18n/locales/public/en.po +490 -158
  218. package/src/i18n/locales/public/en.ts +1 -1
  219. package/src/i18n/locales/public/zh-Hans.po +487 -155
  220. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  221. package/src/i18n/locales/public/zh-Hant.po +487 -155
  222. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  223. package/src/i18n/locales/settings/en.po +555 -73
  224. package/src/i18n/locales/settings/en.ts +1 -1
  225. package/src/i18n/locales/settings/zh-Hans.po +601 -119
  226. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  227. package/src/i18n/locales/settings/zh-Hant.po +614 -132
  228. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  229. package/src/i18n/locales.ts +94 -0
  230. package/src/i18n/middleware.ts +12 -2
  231. package/src/i18n/supported-locales.ts +80 -0
  232. package/src/lib/__tests__/api-settings.test.ts +11 -2
  233. package/src/lib/__tests__/asset-path.test.ts +92 -0
  234. package/src/lib/__tests__/deferred.test.ts +90 -0
  235. package/src/lib/__tests__/discover-ping.test.ts +89 -0
  236. package/src/lib/__tests__/discover.test.ts +410 -0
  237. package/src/lib/__tests__/feed-policy.test.ts +26 -0
  238. package/src/lib/__tests__/feed.test.ts +1526 -15
  239. package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
  240. package/src/lib/__tests__/github-sync-repo-name.test.ts +40 -0
  241. package/src/lib/__tests__/hosted-signin.test.ts +1 -1
  242. package/src/lib/__tests__/http-cache.test.ts +162 -0
  243. package/src/lib/__tests__/image.test.ts +27 -1
  244. package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
  245. package/src/lib/__tests__/jant-docs.test.ts +23 -0
  246. package/src/lib/__tests__/lang-detect.test.ts +231 -0
  247. package/src/lib/__tests__/markdown-to-tiptap.test.ts +105 -0
  248. package/src/lib/__tests__/markdown.test.ts +10 -0
  249. package/src/lib/__tests__/media-helpers.test.ts +81 -0
  250. package/src/lib/__tests__/navigation.test.ts +135 -1
  251. package/src/lib/__tests__/resolve-config.test.ts +131 -3
  252. package/src/lib/__tests__/schemas.test.ts +69 -5
  253. package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
  254. package/src/lib/__tests__/slug.test.ts +70 -0
  255. package/src/lib/__tests__/slugify.test.ts +85 -0
  256. package/src/lib/__tests__/storage.test.ts +32 -0
  257. package/src/lib/__tests__/thread-fold.test.ts +113 -0
  258. package/src/lib/__tests__/timeline.test.ts +87 -0
  259. package/src/lib/__tests__/tiptap-to-markdown.test.ts +172 -4
  260. package/src/lib/__tests__/upload-sideload.test.ts +14 -0
  261. package/src/lib/__tests__/url-fetch.test.ts +1 -2
  262. package/src/lib/__tests__/url.test.ts +78 -72
  263. package/src/lib/__tests__/view-language.test.ts +413 -0
  264. package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
  265. package/src/lib/address.ts +57 -0
  266. package/src/lib/api-posts.ts +12 -0
  267. package/src/lib/asset-path.ts +98 -1
  268. package/src/lib/build-env.ts +16 -0
  269. package/src/lib/collection-groups.ts +5 -1
  270. package/src/lib/collection-paths.ts +3 -10
  271. package/src/lib/constants.ts +28 -3
  272. package/src/lib/copy-field.ts +36 -0
  273. package/src/lib/crypto.ts +40 -0
  274. package/src/lib/csp-builder.ts +2 -1
  275. package/src/lib/deferred.ts +79 -0
  276. package/src/lib/discover-ping.ts +141 -0
  277. package/src/lib/discover.ts +439 -0
  278. package/src/lib/env.ts +134 -0
  279. package/src/lib/errors.ts +33 -0
  280. package/src/lib/feed-policy.ts +236 -2
  281. package/src/lib/feed.ts +926 -73
  282. package/src/lib/filter-dimensions.ts +1349 -0
  283. package/src/lib/github-sync-repo-name.ts +45 -0
  284. package/src/lib/github-sync-site-config.ts +3 -0
  285. package/src/lib/hosted-signin.ts +1 -1
  286. package/src/lib/http-cache.ts +148 -0
  287. package/src/lib/hugo-markdown.ts +62 -0
  288. package/src/lib/icons.ts +9 -2
  289. package/src/lib/ids.ts +3 -0
  290. package/src/lib/image.ts +17 -4
  291. package/src/lib/jant-docs.ts +42 -0
  292. package/src/lib/lang-detect.ts +308 -0
  293. package/src/lib/markdown-manager.ts +392 -2
  294. package/src/lib/media-helpers.ts +13 -1
  295. package/src/lib/navigation.ts +78 -11
  296. package/src/lib/pagination.ts +28 -24
  297. package/src/lib/per-language-surfaces.ts +67 -0
  298. package/src/lib/post-body-html.ts +11 -4
  299. package/src/lib/post-meta.ts +19 -0
  300. package/src/lib/render.tsx +55 -7
  301. package/src/lib/resolve-config.ts +111 -6
  302. package/src/lib/schemas.ts +251 -15
  303. package/src/lib/site-header-fragment.tsx +2 -1
  304. package/src/lib/site-unavailable-page.ts +47 -0
  305. package/src/lib/slug.ts +51 -7
  306. package/src/lib/slugify.ts +113 -0
  307. package/src/lib/startup-config.ts +4 -6
  308. package/src/lib/storage.ts +7 -0
  309. package/src/lib/summary.ts +50 -0
  310. package/src/lib/thread-fold.ts +161 -0
  311. package/src/lib/time.ts +32 -0
  312. package/src/lib/timeline.ts +43 -2
  313. package/src/lib/tiptap-render.ts +1 -2
  314. package/src/lib/tiptap-to-markdown.ts +13 -7
  315. package/src/lib/upload.ts +16 -13
  316. package/src/lib/url.ts +159 -86
  317. package/src/lib/version.ts +10 -8
  318. package/src/lib/view-language.ts +436 -0
  319. package/src/lib/view.ts +53 -25
  320. package/src/lib/viewer-context.ts +62 -0
  321. package/src/middleware/__tests__/auth.test.ts +70 -2
  322. package/src/middleware/__tests__/cache-control.test.ts +71 -3
  323. package/src/middleware/__tests__/onboarding.test.ts +76 -5
  324. package/src/middleware/__tests__/secure-headers.test.ts +1 -1
  325. package/src/middleware/__tests__/session.test.ts +181 -33
  326. package/src/middleware/auth.ts +50 -31
  327. package/src/middleware/cache-control.ts +75 -10
  328. package/src/middleware/config.ts +6 -6
  329. package/src/middleware/error-handler.ts +12 -0
  330. package/src/middleware/onboarding.ts +32 -10
  331. package/src/middleware/secure-headers.ts +1 -1
  332. package/src/middleware/session.ts +96 -8
  333. package/src/node/__tests__/cli-migrate.test.ts +21 -19
  334. package/src/node/__tests__/cli-reset-password.test.ts +81 -8
  335. package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
  336. package/src/node/__tests__/cli-setup.test.ts +163 -0
  337. package/src/node/__tests__/cli-site-selection.test.ts +128 -38
  338. package/src/node/__tests__/cli-site-snapshot.test.ts +67 -0
  339. package/src/node/__tests__/cli-snapshot-meta.test.ts +20 -0
  340. package/src/node/__tests__/runtime.test.ts +75 -0
  341. package/src/node/index.ts +3 -0
  342. package/src/node/request-handler.ts +3 -1
  343. package/src/preset.css +76 -22
  344. package/src/routes/__tests__/feed-validator.test.ts +156 -0
  345. package/src/routes/api/__tests__/collections.test.ts +114 -0
  346. package/src/routes/api/__tests__/discover.test.ts +222 -0
  347. package/src/routes/api/__tests__/nav-items.test.ts +212 -2
  348. package/src/routes/api/__tests__/post-translations.test.ts +829 -0
  349. package/src/routes/api/__tests__/posts.test.ts +24 -0
  350. package/src/routes/api/__tests__/settings.test.ts +24 -3
  351. package/src/routes/api/__tests__/telegram.test.ts +6 -5
  352. package/src/routes/api/__tests__/upload.test.ts +34 -0
  353. package/src/routes/api/collections.ts +49 -6
  354. package/src/routes/api/custom-urls.ts +0 -1
  355. package/src/routes/api/discover.ts +95 -0
  356. package/src/routes/api/export.ts +6 -3
  357. package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
  358. package/src/routes/api/internal/sites.ts +0 -1
  359. package/src/routes/api/nav-items.ts +55 -8
  360. package/src/routes/api/posts.ts +161 -0
  361. package/src/routes/api/public/__tests__/archive.test.ts +111 -0
  362. package/src/routes/api/public/archive.ts +77 -81
  363. package/src/routes/api/public/posts.ts +44 -7
  364. package/src/routes/api/smart-collections.ts +132 -0
  365. package/src/routes/api/telegram.ts +2 -40
  366. package/src/routes/api/upload.ts +10 -3
  367. package/src/routes/api/uploads.ts +1 -2
  368. package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
  369. package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
  370. package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
  371. package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
  372. package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
  373. package/src/routes/auth/__tests__/setup.test.ts +278 -26
  374. package/src/routes/auth/__tests__/signin.test.ts +135 -0
  375. package/src/routes/auth/setup.tsx +934 -186
  376. package/src/routes/auth/signin.tsx +10 -8
  377. package/src/routes/compose.tsx +9 -0
  378. package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
  379. package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
  380. package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
  381. package/src/routes/dash/__tests__/github-sync-app.test.ts +365 -0
  382. package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
  383. package/src/routes/dash/custom-urls.tsx +1 -37
  384. package/src/routes/dash/settings.tsx +714 -111
  385. package/src/routes/discover-announce.ts +96 -0
  386. package/src/routes/feed/__tests__/feed.test.ts +165 -2
  387. package/src/routes/feed/feed.ts +69 -40
  388. package/src/routes/feed/sitemap.ts +95 -11
  389. package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
  390. package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
  391. package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
  392. package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
  393. package/src/routes/pages/__tests__/collections.test.ts +9 -3
  394. package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
  395. package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
  396. package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
  397. package/src/routes/pages/__tests__/post-page-round-trips.test.ts +211 -0
  398. package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
  399. package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
  400. package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
  401. package/src/routes/pages/__tests__/thread-order.test.ts +179 -0
  402. package/src/routes/pages/archive.tsx +487 -379
  403. package/src/routes/pages/collection.tsx +94 -96
  404. package/src/routes/pages/collections.tsx +38 -50
  405. package/src/routes/pages/featured.tsx +43 -18
  406. package/src/routes/pages/home.tsx +25 -6
  407. package/src/routes/pages/language.tsx +113 -0
  408. package/src/routes/pages/latest.tsx +26 -16
  409. package/src/routes/pages/new.tsx +3 -0
  410. package/src/routes/pages/page.tsx +228 -45
  411. package/src/routes/pages/partials.tsx +41 -0
  412. package/src/routes/pages/search.tsx +22 -5
  413. package/src/routes/pages/smart-collection.tsx +340 -0
  414. package/src/routes/pages/subscribe.tsx +182 -0
  415. package/src/runtime/__tests__/index.test.ts +19 -0
  416. package/src/runtime/__tests__/node.test.ts +129 -4
  417. package/src/runtime/__tests__/readiness.test.ts +23 -0
  418. package/src/runtime/cloudflare.ts +21 -2
  419. package/src/runtime/index.ts +27 -1
  420. package/src/runtime/node.ts +67 -1
  421. package/src/runtime/readiness.ts +15 -0
  422. package/src/runtime/site.ts +95 -24
  423. package/src/services/__tests__/auth.test.ts +116 -7
  424. package/src/services/__tests__/bootstrap-setup-instance.test.ts +230 -0
  425. package/src/services/__tests__/collection.test.ts +108 -4
  426. package/src/services/__tests__/custom-url.test.ts +34 -0
  427. package/src/services/__tests__/github-app-installations.test.ts +153 -0
  428. package/src/services/__tests__/github-sync-push.test.ts +46 -0
  429. package/src/services/__tests__/language.test.ts +495 -0
  430. package/src/services/__tests__/media.test.ts +31 -0
  431. package/src/services/__tests__/navigation.test.ts +213 -10
  432. package/src/services/__tests__/path.test.ts +236 -0
  433. package/src/services/__tests__/post-language.test.ts +830 -0
  434. package/src/services/__tests__/post-timeline.test.ts +127 -0
  435. package/src/services/__tests__/post.test.ts +120 -9
  436. package/src/services/__tests__/settings.test.ts +228 -17
  437. package/src/services/__tests__/site-admin.test.ts +5 -5
  438. package/src/services/__tests__/smart-collection.test.ts +651 -0
  439. package/src/services/about-page.ts +22 -1
  440. package/src/services/auth.ts +51 -49
  441. package/src/services/bootstrap.ts +408 -39
  442. package/src/services/collection-directory-position.ts +76 -0
  443. package/src/services/collection.ts +331 -46
  444. package/src/services/custom-url.ts +29 -24
  445. package/src/services/export-theme/assets/client-site.css +1 -1
  446. package/src/services/export-theme/assets/client-site.js +123 -123
  447. package/src/services/export-theme/layouts/_default/alias.html +27 -1
  448. package/src/services/export-theme/layouts/_default/list.html +2 -63
  449. package/src/services/export-theme/layouts/_default/rss.xml +103 -38
  450. package/src/services/export-theme/layouts/collections/list.html +3 -3
  451. package/src/services/export-theme/layouts/featured/list.html +1 -4
  452. package/src/services/export-theme/layouts/index.html +40 -26
  453. package/src/services/export-theme/layouts/partials/collection-members.html +100 -0
  454. package/src/services/export-theme/layouts/partials/collection-threads.html +33 -0
  455. package/src/services/export-theme/layouts/partials/featured-members.html +35 -0
  456. package/src/services/export-theme/layouts/partials/featured-thread.html +1 -1
  457. package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
  458. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  459. package/src/services/export-theme/layouts/partials/head.html +1 -1
  460. package/src/services/export-theme/layouts/partials/header.html +5 -3
  461. package/src/services/export-theme/layouts/partials/jant-data.html +23 -0
  462. package/src/services/export-theme/layouts/partials/latest-members.html +48 -0
  463. package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
  464. package/src/services/export-theme/layouts/partials/pagination.html +14 -13
  465. package/src/services/export-theme/layouts/partials/post-card.html +8 -4
  466. package/src/services/export-theme/layouts/partials/reply.html +2 -1
  467. package/src/services/export-theme/layouts/partials/smart-collection-members.html +129 -0
  468. package/src/services/export-theme/layouts/partials/thread-preview.html +41 -31
  469. package/src/services/export-theme/layouts/post/list.html +1 -1
  470. package/src/services/export-theme/layouts/smart_collection/list.html +24 -0
  471. package/src/services/export-theme/styles/main.css +13 -2
  472. package/src/services/export-theme/theme.toml +1 -1
  473. package/src/services/export.ts +880 -72
  474. package/src/services/github-app-installations.ts +171 -4
  475. package/src/services/github-sync.ts +69 -15
  476. package/src/services/index.ts +34 -3
  477. package/src/services/language.ts +412 -0
  478. package/src/services/mcp.ts +22 -2
  479. package/src/services/media.ts +22 -7
  480. package/src/services/navigation.ts +429 -67
  481. package/src/services/path.ts +254 -6
  482. package/src/services/post.ts +1234 -190
  483. package/src/services/search.ts +39 -2
  484. package/src/services/settings.ts +238 -43
  485. package/src/services/site-admin.ts +27 -14
  486. package/src/services/site.ts +160 -32
  487. package/src/services/smart-collection.ts +636 -0
  488. package/src/services/telegram.ts +20 -22
  489. package/src/services/upload-session.ts +1 -3
  490. package/src/style-author.css +27 -0
  491. package/src/styles/components-author.css +1622 -0
  492. package/src/styles/components.css +24 -1571
  493. package/src/styles/site-media.css +16 -2
  494. package/src/styles/tokens.css +82 -12
  495. package/src/styles/ui-author.css +7602 -0
  496. package/src/styles/ui.css +1090 -7216
  497. package/src/types/app-context.ts +23 -0
  498. package/src/types/bindings.ts +8 -0
  499. package/src/types/config.ts +94 -15
  500. package/src/types/constants.ts +188 -16
  501. package/src/types/entities.ts +87 -1
  502. package/src/types/operations.ts +74 -2
  503. package/src/types/props.ts +157 -31
  504. package/src/types/views.ts +55 -1
  505. package/src/ui/__tests__/font-themes.test.ts +52 -12
  506. package/src/ui/compose/ComposeDialog.tsx +201 -7
  507. package/src/ui/compose/ComposePrompt.tsx +1 -5
  508. package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
  509. package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
  510. package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
  511. package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
  512. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
  513. package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
  514. package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
  515. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  516. package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
  517. package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
  518. package/src/ui/dash/settings/GeneralContent.tsx +277 -113
  519. package/src/ui/dash/settings/LanguageContent.tsx +409 -0
  520. package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
  521. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
  522. package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
  523. package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
  524. package/src/ui/dash/settings/discover-copy.ts +116 -0
  525. package/src/ui/feed/LinkCard.tsx +55 -45
  526. package/src/ui/feed/NoteCard.tsx +9 -4
  527. package/src/ui/feed/PostStatusBadges.tsx +90 -43
  528. package/src/ui/feed/QuoteCard.tsx +55 -30
  529. package/src/ui/feed/ThreadPreview.tsx +6 -2
  530. package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
  531. package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
  532. package/src/ui/feed/thread-preview-state.ts +20 -6
  533. package/src/ui/font-themes.ts +39 -29
  534. package/src/ui/layouts/BaseLayout.tsx +155 -10
  535. package/src/ui/layouts/SiteLayout.tsx +192 -34
  536. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
  537. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
  538. package/src/ui/pages/ArchivePage.tsx +282 -289
  539. package/src/ui/pages/CollectionPage.tsx +86 -76
  540. package/src/ui/pages/CollectionsPage.tsx +5 -0
  541. package/src/ui/pages/ComposePage.tsx +8 -0
  542. package/src/ui/pages/PostPage.tsx +47 -0
  543. package/src/ui/pages/SearchPage.tsx +3 -3
  544. package/src/ui/pages/SmartCollectionPage.tsx +434 -0
  545. package/src/ui/pages/SubscribePage.tsx +236 -0
  546. package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
  547. package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
  548. package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
  549. package/src/ui/shared/CollectionDirectory.tsx +178 -3
  550. package/src/ui/shared/CollectionsManager.tsx +70 -17
  551. package/src/ui/shared/CopyField.tsx +127 -0
  552. package/src/ui/shared/EmptyPostContent.tsx +23 -0
  553. package/src/ui/shared/MediaGallery.tsx +125 -46
  554. package/src/ui/shared/Pagination.tsx +20 -5
  555. package/src/ui/shared/PostFooter.tsx +20 -11
  556. package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
  557. package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
  558. package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
  559. package/src/ui/shared/__tests__/pagination.test.ts +38 -24
  560. package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
  561. package/src/ui/shared/archive-labels.ts +98 -0
  562. package/src/ui/shared/collection-management-labels.ts +94 -16
  563. package/src/ui/shared/navigation-labels.ts +45 -71
  564. package/src/ui/shared/post-article-attributes.ts +2 -0
  565. package/src/ui/shared/smart-collection-labels.ts +343 -0
  566. package/dist/app-C75Zc8dD.js +0 -6
  567. package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
  568. package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
  569. package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
  570. package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
  571. package/dist/client/_assets/client-D3VZyG4L.css +0 -2
  572. package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
  573. package/dist/env-BPYViDJJ.js +0 -281
  574. package/dist/export-Dl5KCiEs.js +0 -5562
  575. package/dist/github-api-BNF35Lkx.js +0 -176
  576. package/dist/github-app-DdiD-bC4.js +0 -275
  577. package/dist/github-sync-Bqg62IvV.js +0 -4
  578. package/dist/github-sync-HdK2EgvJ.js +0 -431
  579. package/dist/url-CbLAtlZe.js +0 -900
  580. package/src/client/__tests__/collection-created-notice.test.ts +0 -36
  581. package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
  582. package/src/client/__tests__/post-form-bridge.test.ts +0 -82
  583. package/src/client/collection-created-notice.ts +0 -66
  584. package/src/client/collection-form-bridge.ts +0 -191
  585. package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
  586. package/src/client/components/jant-post-form.ts +0 -422
  587. package/src/client/components/post-form-template.ts +0 -352
  588. package/src/client/components/post-form-types.ts +0 -96
  589. package/src/client/media-metadata.ts +0 -247
  590. package/src/client/post-form-bridge.ts +0 -193
  591. package/src/client/tiptap/exitable-marks.ts +0 -73
  592. package/src/ui/pages/CollectionEditorPage.tsx +0 -175
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Snapshot Table Registry Guard
3
+ *
4
+ * `jant site snapshot export/import` walks two hand-written arrays in
5
+ * `bin/lib/site-snapshot.js`. Nothing connects them to `db/schema.ts`: `bin/`
6
+ * is plain JavaScript, outside the type system, so a new site-scoped table
7
+ * compiles, lints and tests clean while the snapshot silently skips it.
8
+ *
9
+ * That already happened. `smart_collection` arrived with migration 0033, and
10
+ * the commit that added it updated the schema, both dialects, five services, a
11
+ * route and three test files — but not these arrays. The damage was silent in
12
+ * both directions: export dropped smart collections, and `--replace` could not
13
+ * delete the ones a visitor created, so they outlived every nightly demo
14
+ * rebuild as rows with no slug and no nav entry.
15
+ *
16
+ * These tests close the gap. Every site-scoped table has to be named in one of
17
+ * the two registries, so a new table forces a decision instead of defaulting to
18
+ * "forgotten". The order assertions pin the arrays' other implicit contract:
19
+ * `SNAPSHOT_TABLES` is an INSERT order and `SNAPSHOT_CLEAR_TABLES` is a DELETE
20
+ * order, and both have to respect foreign keys — which are enforced, on D1 and
21
+ * on Node alike (`bin/lib/node-sqlite.js` sets `foreign_keys = ON`).
22
+ *
23
+ * Reading `db/schema.ts` alone covers both dialects: the registries are shared
24
+ * (only the dumped SQL is dialect-specific), and `db/pg/schema.ts` is required
25
+ * to declare the same tables.
26
+ */
27
+
28
+ import { is } from "drizzle-orm";
29
+ import { SQLiteTable, getTableConfig } from "drizzle-orm/sqlite-core";
30
+ import { describe, expect, it } from "vitest";
31
+ import {
32
+ SNAPSHOT_CLEAR_TABLES,
33
+ SNAPSHOT_EXCLUDED_TABLES,
34
+ SNAPSHOT_TABLES,
35
+ getSnapshotSelectSql,
36
+ } from "../../bin/lib/site-snapshot.js";
37
+ import {
38
+ TABLE_EXPORT_ORDER,
39
+ sortExportTables,
40
+ } from "../../bin/lib/sql-export.js";
41
+ import * as schema from "../db/schema.js";
42
+
43
+ /** `site_setting` is cleared by key, not wholesale — see `buildReplaceSql`. */
44
+ const CLEARED_BY_KEY = "site_setting";
45
+
46
+ type SchemaTable = {
47
+ /** Physical table name, as it appears in SQL. */
48
+ name: string;
49
+ /** Tables this one points at with a foreign key, excluding self-references. */
50
+ dependsOn: string[];
51
+ };
52
+
53
+ /**
54
+ * Reads every site-scoped table out of the Drizzle schema with its foreign-key
55
+ * dependencies. "Site-scoped" means the table carries a `site_id` column, which
56
+ * is what makes a row belong to one site's content rather than the instance.
57
+ *
58
+ * @returns One entry per site-scoped table, in declaration order.
59
+ */
60
+ function readSiteScopedTables(): SchemaTable[] {
61
+ const tables: SchemaTable[] = [];
62
+
63
+ for (const exported of Object.values(schema)) {
64
+ if (!is(exported, SQLiteTable)) continue;
65
+ const config = getTableConfig(exported);
66
+ if (!config.columns.some((column) => column.name === "site_id")) continue;
67
+
68
+ const dependsOn = [
69
+ ...new Set(
70
+ config.foreignKeys.map(
71
+ (foreignKey) =>
72
+ getTableConfig(foreignKey.reference().foreignTable).name,
73
+ ),
74
+ ),
75
+ ].filter((target) => target !== config.name);
76
+
77
+ tables.push({ name: config.name, dependsOn });
78
+ }
79
+
80
+ return tables;
81
+ }
82
+
83
+ const siteScopedTables = readSiteScopedTables();
84
+ const siteScopedNames = siteScopedTables.map((table) => table.name);
85
+
86
+ describe("snapshot table registry", () => {
87
+ it("covers every site-scoped table as content or an explicit exclusion", () => {
88
+ const registered = new Set<string>([
89
+ ...SNAPSHOT_TABLES,
90
+ ...SNAPSHOT_EXCLUDED_TABLES,
91
+ ]);
92
+ const unregistered = siteScopedNames.filter(
93
+ (name) => !registered.has(name),
94
+ );
95
+
96
+ expect(
97
+ unregistered,
98
+ [
99
+ "These site-scoped tables are in db/schema.ts but in neither snapshot registry.",
100
+ "A table the snapshot does not know about is dropped on export and survives",
101
+ "`--replace` on import. Add it to SNAPSHOT_TABLES if it is site content, or to",
102
+ "SNAPSHOT_EXCLUDED_TABLES with the reason if it is shell, credential or",
103
+ "transient state, in bin/lib/site-snapshot.js.",
104
+ ].join("\n"),
105
+ ).toEqual([]);
106
+ });
107
+
108
+ it("does not register a table as both content and excluded", () => {
109
+ const excluded = new Set<string>(SNAPSHOT_EXCLUDED_TABLES);
110
+ const both = SNAPSHOT_TABLES.filter((name) => excluded.has(name));
111
+
112
+ expect(both).toEqual([]);
113
+ });
114
+
115
+ it("can build an export query for every content table", () => {
116
+ // `getSnapshotSelectSql` has no default branch: a table listed as content
117
+ // without a matching statement throws "Unsupported snapshot table" at
118
+ // export time, which is the same silent-until-used failure the registry
119
+ // itself had.
120
+ for (const table of SNAPSHOT_TABLES) {
121
+ expect(() =>
122
+ getSnapshotSelectSql(table, "sit_00000000000000000000000000"),
123
+ ).not.toThrow();
124
+ }
125
+ });
126
+
127
+ it("does not keep registry entries for tables the schema dropped", () => {
128
+ const known = new Set(siteScopedNames);
129
+ const stale = [...SNAPSHOT_TABLES, ...SNAPSHOT_EXCLUDED_TABLES].filter(
130
+ (name) => !known.has(name),
131
+ );
132
+
133
+ expect(stale).toEqual([]);
134
+ });
135
+ });
136
+
137
+ describe("snapshot table ordering", () => {
138
+ it("registers every content table for export ordering", () => {
139
+ // A table missing here is not merely unordered: `sortExportTables` places
140
+ // unknown names after every known one, so a new child table is dumped
141
+ // behind its own parents and the import fails the first time a row exists
142
+ // to violate the constraint.
143
+ const ordered = new Set<string>(TABLE_EXPORT_ORDER);
144
+ expect(SNAPSHOT_TABLES.filter((name) => !ordered.has(name))).toEqual([]);
145
+ });
146
+
147
+ it("inserts a table only after every table it references", () => {
148
+ // The order an export actually follows. `SNAPSHOT_TABLES` is re-sorted by
149
+ // `TABLE_EXPORT_ORDER` on the way out, so asserting on the raw list would
150
+ // check something the dump ignores.
151
+ const position = new Map(
152
+ sortExportTables(SNAPSHOT_TABLES).map((name, index) => [name, index]),
153
+ );
154
+ const violations: string[] = [];
155
+
156
+ for (const table of siteScopedTables) {
157
+ const self = position.get(table.name);
158
+ if (self === undefined) continue;
159
+
160
+ for (const dependency of table.dependsOn) {
161
+ const parent = position.get(dependency);
162
+ if (parent === undefined) continue;
163
+ if (parent > self) {
164
+ violations.push(
165
+ `${table.name} (${self}) references ${dependency} (${parent})`,
166
+ );
167
+ }
168
+ }
169
+ }
170
+
171
+ expect(
172
+ violations,
173
+ [
174
+ "TABLE_EXPORT_ORDER in sql-export.js is the order rows are dumped, and so the",
175
+ "order they are inserted on import. A table has to come after everything it",
176
+ "points at with a foreign key, or the child row lands before its parent exists",
177
+ "and the insert fails the constraint.",
178
+ ].join("\n"),
179
+ ).toEqual([]);
180
+ });
181
+
182
+ it("deletes a table before every table it references", () => {
183
+ const position = new Map(
184
+ SNAPSHOT_CLEAR_TABLES.map((name, index) => [name, index]),
185
+ );
186
+ const violations: string[] = [];
187
+
188
+ for (const table of siteScopedTables) {
189
+ const self = position.get(table.name);
190
+ if (self === undefined) continue;
191
+
192
+ for (const dependency of table.dependsOn) {
193
+ const parent = position.get(dependency);
194
+ if (parent === undefined) continue;
195
+ if (parent < self) {
196
+ violations.push(
197
+ `${table.name} (${self}) references ${dependency} (${parent})`,
198
+ );
199
+ }
200
+ }
201
+ }
202
+
203
+ expect(
204
+ violations,
205
+ [
206
+ "SNAPSHOT_CLEAR_TABLES is the order rows are deleted by `--replace`, so a table",
207
+ "has to come before everything it points at. Deleting the parent first only",
208
+ "appears to work because the cascade takes the children with it — which is",
209
+ "implicit, and stops being true the moment a reference is not ON DELETE CASCADE.",
210
+ ].join("\n"),
211
+ ).toEqual([]);
212
+ });
213
+
214
+ it("clears every content table except the one cleared by key", () => {
215
+ expect([...SNAPSHOT_CLEAR_TABLES].sort()).toEqual(
216
+ SNAPSHOT_TABLES.filter((name) => name !== CLEARED_BY_KEY).sort(),
217
+ );
218
+ });
219
+ });
@@ -0,0 +1,173 @@
1
+ /**
2
+ * The SQL dump behind `db export` and `site snapshot export`.
3
+ *
4
+ * On D1 every query goes through `wrangler d1 execute --json`, whose output
5
+ * the CLI buffers. One `SELECT *` of a real site's `post` table overflowed
6
+ * that buffer, so the dump reads D1 a page at a time.
7
+ */
8
+
9
+ import { describe, expect, it } from "vitest";
10
+ import Database from "better-sqlite3";
11
+ import { dumpDatabaseToSql } from "../../bin/lib/sql-export.js";
12
+ import { orderSnapshotPostRows } from "../../bin/lib/site-snapshot.js";
13
+ import { describeWranglerFailure } from "../../bin/lib/d1-query.js";
14
+ import { WRANGLER_MAX_BUFFER } from "../../bin/lib/wrangler-cli.js";
15
+
16
+ function createQueryRunner() {
17
+ const db = new Database(":memory:");
18
+ db.exec(`
19
+ CREATE TABLE "post" ("id" TEXT PRIMARY KEY, "site_id" TEXT, "body" TEXT);
20
+ `);
21
+ const insert = db.prepare(
22
+ `INSERT INTO "post" ("id", "site_id", "body") VALUES (?, ?, ?)`,
23
+ );
24
+ for (let index = 0; index < 7; index += 1) {
25
+ insert.run(`pst_${index}`, "site", `body ${index}`);
26
+ }
27
+
28
+ const statements: string[] = [];
29
+ return {
30
+ statements,
31
+ runner: {
32
+ async query(sql: string) {
33
+ statements.push(sql);
34
+ return db.prepare(sql).all() as Record<string, unknown>[];
35
+ },
36
+ },
37
+ };
38
+ }
39
+
40
+ describe("dumpDatabaseToSql", () => {
41
+ it("reads a table in pages when given a page size", async () => {
42
+ const { runner, statements } = createQueryRunner();
43
+
44
+ const sql = await dumpDatabaseToSql(runner, {
45
+ source: "local",
46
+ dialect: "sqlite",
47
+ tables: ["post"],
48
+ pageSize: 3,
49
+ });
50
+
51
+ const reads = statements.filter((statement) =>
52
+ statement.includes('FROM "post"'),
53
+ );
54
+ expect(reads).toEqual([
55
+ 'SELECT * FROM "post" ORDER BY rowid LIMIT 3 OFFSET 0',
56
+ 'SELECT * FROM "post" ORDER BY rowid LIMIT 3 OFFSET 3',
57
+ 'SELECT * FROM "post" ORDER BY rowid LIMIT 3 OFFSET 6',
58
+ ]);
59
+ for (let index = 0; index < 7; index += 1) {
60
+ expect(sql).toContain(`'pst_${index}'`);
61
+ }
62
+ });
63
+
64
+ it("pages a table's own ordered select", async () => {
65
+ const { runner, statements } = createQueryRunner();
66
+
67
+ const sql = await dumpDatabaseToSql(runner, {
68
+ source: "local",
69
+ dialect: "sqlite",
70
+ tables: ["post"],
71
+ pageSize: 5,
72
+ selectSqlByTable: {
73
+ post: `SELECT * FROM "post" WHERE "site_id" = 'site' ORDER BY "id"\n`,
74
+ },
75
+ });
76
+
77
+ expect(
78
+ statements.filter((statement) => statement.includes('FROM "post"')),
79
+ ).toEqual([
80
+ `SELECT * FROM "post" WHERE "site_id" = 'site' ORDER BY "id" LIMIT 5 OFFSET 0`,
81
+ `SELECT * FROM "post" WHERE "site_id" = 'site' ORDER BY "id" LIMIT 5 OFFSET 5`,
82
+ ]);
83
+ expect(sql.match(/INSERT INTO "post"/g)).toHaveLength(7);
84
+ });
85
+
86
+ it("reads a table in one query without a page size", async () => {
87
+ const { runner, statements } = createQueryRunner();
88
+
89
+ await dumpDatabaseToSql(runner, {
90
+ source: "node",
91
+ dialect: "sqlite",
92
+ tables: ["post"],
93
+ });
94
+
95
+ expect(
96
+ statements.filter((statement) => statement.includes('FROM "post"')),
97
+ ).toEqual(['SELECT * FROM "post"']);
98
+ });
99
+ });
100
+
101
+ describe("describeWranglerFailure", () => {
102
+ it("names the buffer overflow instead of echoing the rows", () => {
103
+ const error = Object.assign(new Error("spawnSync node ENOBUFS"), {
104
+ code: "ENOBUFS",
105
+ stdout: '[{"results":[' + '{"id":"pst_x","body":"…"},'.repeat(50_000),
106
+ stderr: "",
107
+ });
108
+
109
+ const message = describeWranglerFailure(
110
+ ["d1", "execute", "DB", "--local", "--json"],
111
+ error,
112
+ );
113
+
114
+ expect(message).toBe(
115
+ `\`wrangler d1 execute DB\` printed more than ${WRANGLER_MAX_BUFFER} bytes, the most the CLI buffers.`,
116
+ );
117
+ });
118
+
119
+ it("keeps a long failure's stdout to an excerpt", () => {
120
+ const message = describeWranglerFailure(["d1", "execute", "DB"], {
121
+ name: "Error",
122
+ message: "Command failed",
123
+ stdout: "x".repeat(10_000),
124
+ stderr: "",
125
+ } as Error & { stdout: string; stderr: string });
126
+
127
+ expect(message.length).toBeLessThan(2_200);
128
+ expect(message).toContain("(10000 characters)");
129
+ });
130
+ });
131
+
132
+ describe("snapshot post order", () => {
133
+ it("puts every post after its root and the post it replies to", () => {
134
+ const rows = [
135
+ { id: "reply-2", thread_id: "root", reply_to_id: "reply-1" },
136
+ { id: "reply-1", thread_id: "root", reply_to_id: "root" },
137
+ { id: "other", thread_id: "other", reply_to_id: null },
138
+ { id: "root", thread_id: "root", reply_to_id: null },
139
+ ];
140
+
141
+ expect(orderSnapshotPostRows(rows).map((row) => row.id)).toEqual([
142
+ "root",
143
+ "reply-1",
144
+ "reply-2",
145
+ "other",
146
+ ]);
147
+ });
148
+
149
+ it("keeps the dump order when parents already come first", () => {
150
+ const rows = [
151
+ { id: "a", thread_id: "a", reply_to_id: null },
152
+ { id: "b", thread_id: "a", reply_to_id: "a" },
153
+ { id: "c", thread_id: "c", reply_to_id: null },
154
+ ];
155
+
156
+ expect(orderSnapshotPostRows(rows)).toEqual(rows);
157
+ });
158
+
159
+ it("applies a table's row order to the dump", async () => {
160
+ const { runner } = createQueryRunner();
161
+
162
+ const sql = await dumpDatabaseToSql(runner, {
163
+ source: "node",
164
+ dialect: "sqlite",
165
+ tables: ["post"],
166
+ orderRowsByTable: {
167
+ post: (rows: Record<string, unknown>[]) => [...rows].reverse(),
168
+ },
169
+ });
170
+
171
+ expect(sql.indexOf("'pst_6'")).toBeLessThan(sql.indexOf("'pst_0'"));
172
+ });
173
+ });
@@ -0,0 +1,159 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ readAuthorCss,
4
+ readComponentCss,
5
+ readReaderCss,
6
+ } from "./helpers/component-css.js";
7
+
8
+ /**
9
+ * `client.css` is what a signed-out visitor waits on before the page paints,
10
+ * and `styles/ui.css` plus `styles/components.css` are the hand-written half
11
+ * of it. Author-only component CSS belongs in `styles/ui-author.css` and
12
+ * `styles/components-author.css`, which `BaseLayout` links only on
13
+ * authenticated pages.
14
+ *
15
+ * Class families that only ever appear in authenticated markup. The editor's
16
+ * `.tiptap-*` chrome is here too, minus the names TipTap also writes into
17
+ * published post HTML — readers see those.
18
+ */
19
+ const AUTHOR_ONLY = [
20
+ "compose",
21
+ "command",
22
+ "settings",
23
+ "picker",
24
+ "confirm",
25
+ "draft",
26
+ "theme-preview",
27
+ // The floating compose button on phones, and the page modifier that keeps
28
+ // room for it. Reader layout renders both, but only for a signed-in author.
29
+ "site-mobile-compose",
30
+ "site-page-mobile-compose-enabled",
31
+ // From `components.css`: the config editor, the navigation manager, the
32
+ // custom URL manager, the dash chrome and the form skeletons. `.nav-*` here
33
+ // is `jant-nav-manager`, not the site header — that is `.site-header-nav`.
34
+ // `.collection-picker-*` is spelled out because plain `.collection-*` is a
35
+ // reader family (the collection pages).
36
+ "config",
37
+ "nav",
38
+ "custom",
39
+ "dash",
40
+ "skel",
41
+ "collection-picker",
42
+ ];
43
+
44
+ /** TipTap class names that land in reader-rendered post HTML. */
45
+ const READER_TIPTAP = [
46
+ "tiptap-table",
47
+ "tiptap-html-block",
48
+ "tiptap-more-break",
49
+ "tiptap-embed-figure",
50
+ "tiptap-embed-frame",
51
+ "tiptap-embed-fallback",
52
+ "tiptap-footnote-reference",
53
+ "tiptap-footnote-definition",
54
+ ];
55
+
56
+ /**
57
+ * `.compose-thread-layout` shares the compose rail's marker vocabulary with
58
+ * the reading rail's `.thread-group` in one declaration, so that the two
59
+ * cannot drift apart. That single grouped selector is the documented
60
+ * exception; see the comment on the rule in `styles/ui.css`.
61
+ */
62
+ const READER_SHEET_EXCEPTIONS = [".compose-thread-layout"];
63
+
64
+ /** Every class named in a selector, ignoring declaration bodies. */
65
+ function selectorClasses(css: string): string[] {
66
+ const found: string[] = [];
67
+ // Selectors are what precedes a `{` that opens a block; declarations end in
68
+ // `;`, so anything after the last `;` or `}` is the selector text.
69
+ for (const chunk of css.split("{")) {
70
+ const selector = chunk.slice(
71
+ Math.max(chunk.lastIndexOf(";"), chunk.lastIndexOf("}")) + 1,
72
+ );
73
+ const withoutComments = selector.replace(/\/\*[\s\S]*?\*\//g, " ");
74
+ for (const match of withoutComments.matchAll(
75
+ /\.(-?[A-Za-z_][A-Za-z0-9_-]*)/g,
76
+ )) {
77
+ found.push(match[1]!);
78
+ }
79
+ }
80
+ return found;
81
+ }
82
+
83
+ describe("stylesheet audience", () => {
84
+ it("keeps author-only component CSS out of the reader stylesheet", () => {
85
+ const css = readReaderCss();
86
+ const exceptions = new Set(
87
+ READER_SHEET_EXCEPTIONS.map((name) => name.replace(/^\./, "")),
88
+ );
89
+
90
+ const leaked = [
91
+ ...new Set(
92
+ selectorClasses(css).filter(
93
+ (name) =>
94
+ !exceptions.has(name) &&
95
+ AUTHOR_ONLY.some(
96
+ (family) => name === family || name.startsWith(`${family}-`),
97
+ ),
98
+ ),
99
+ ),
100
+ ].sort();
101
+
102
+ expect(leaked).toEqual([]);
103
+ });
104
+
105
+ it("keeps the editor chrome out of the reader stylesheet", () => {
106
+ const readerTiptap = new Set(READER_TIPTAP);
107
+ const leaked = [
108
+ ...new Set(
109
+ selectorClasses(readReaderCss()).filter(
110
+ (name) =>
111
+ (name === "tiptap" || name.startsWith("tiptap-")) &&
112
+ !readerTiptap.has(name),
113
+ ),
114
+ ),
115
+ ].sort();
116
+
117
+ expect(leaked).toEqual([]);
118
+ });
119
+
120
+ it("still styles the TipTap output a published post carries", () => {
121
+ // `lib/embed-render.ts` and `lib/markdown-manager.ts` write these into post
122
+ // HTML, and they are the ones with rules of their own — a reader holding
123
+ // only `client.css` has to be able to style them. The rest of
124
+ // READER_TIPTAP reaches published posts too but is styled by element
125
+ // through `.prose`, so there is no rule here to keep.
126
+ const css = readReaderCss();
127
+ for (const name of [
128
+ "tiptap-embed-figure",
129
+ "tiptap-embed-frame",
130
+ "tiptap-embed-fallback",
131
+ "tiptap-more-break",
132
+ ]) {
133
+ expect(selectorClasses(css)).toContain(name);
134
+ }
135
+ });
136
+
137
+ it("moved real weight, not a handful of rules", () => {
138
+ // The split is only worth its complexity while the author half is
139
+ // substantial. If it ever shrinks to a rounding error, fold it back in.
140
+ expect(readAuthorCss().length).toBeGreaterThan(readReaderCss().length / 2);
141
+ });
142
+
143
+ it("keeps the author half of components.css out of the reader sheet", () => {
144
+ // `preset.css` imports `components.css` on every page, so the settings
145
+ // pages and the config editor must not be left in it.
146
+ const author = readAuthorCss();
147
+ expect(author).toContain(".settings-root");
148
+ expect(author).toContain(".config-editor-page");
149
+ });
150
+
151
+ it("leaves the reader-side families of components.css in place", () => {
152
+ // Verified against every `class=` usage in the tree: `BaseLayout` renders
153
+ // the toast container on every page, and `.page-intro-*` is the header of
154
+ // the archive and collections pages, both public.
155
+ const reader = readReaderCss();
156
+ expect(reader).toContain(".toast-container");
157
+ expect(reader).toContain(".page-intro");
158
+ });
159
+ });