emdash 0.30.0 → 0.31.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 (392) hide show
  1. package/dist/api/route-utils.mjs +13 -13
  2. package/dist/api/schemas/index.d.mts +1 -1
  3. package/dist/api/schemas/index.mjs +3 -3
  4. package/dist/{api-b8WIiGU4.mjs → api-BkqFzIu4.mjs} +23 -23
  5. package/dist/api-BkqFzIu4.mjs.map +1 -0
  6. package/dist/{apply-CmIJK9j8.mjs → apply-DuXsqll7.mjs} +26 -25
  7. package/dist/apply-DuXsqll7.mjs.map +1 -0
  8. package/dist/astro/index.d.mts +2 -2
  9. package/dist/astro/index.mjs +1 -1
  10. package/dist/astro/middleware/auth.d.mts +2 -2
  11. package/dist/astro/middleware/auth.mjs +2 -2
  12. package/dist/astro/middleware/redirect.mjs +5 -5
  13. package/dist/astro/middleware/request-context.mjs +1 -1
  14. package/dist/astro/middleware.d.mts +2 -2
  15. package/dist/astro/middleware.mjs +146 -70
  16. package/dist/astro/middleware.mjs.map +1 -1
  17. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
  18. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
  19. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  20. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  21. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
  22. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +7 -7
  23. package/dist/astro/routes/api/admin/byline-fields/index.mjs +7 -7
  24. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +7 -7
  25. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +14 -14
  26. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +14 -14
  27. package/dist/astro/routes/api/admin/bylines/index.d.mts.map +1 -1
  28. package/dist/astro/routes/api/admin/bylines/index.mjs +18 -17
  29. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  30. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +10 -10
  31. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  32. package/dist/astro/routes/api/admin/comments/bulk.mjs +9 -9
  33. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  34. package/dist/astro/routes/api/admin/comments/index.mjs +9 -9
  35. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  36. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  37. package/dist/astro/routes/api/admin/media-usage/repair.mjs +10 -10
  38. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  39. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  40. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +34 -34
  41. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +34 -34
  42. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +34 -34
  43. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +4 -4
  44. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +34 -34
  45. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +34 -34
  46. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +34 -34
  47. package/dist/astro/routes/api/admin/plugins/index.mjs +34 -34
  48. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  49. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +34 -34
  50. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +34 -34
  51. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +34 -34
  52. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +34 -34
  53. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +35 -35
  54. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +34 -34
  55. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +35 -35
  56. package/dist/astro/routes/api/admin/plugins/updates.mjs +34 -34
  57. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +34 -34
  58. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  59. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +34 -34
  60. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  61. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  62. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  63. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  64. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  65. package/dist/astro/routes/api/auth/dev-bypass.mjs +4 -4
  66. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  67. package/dist/astro/routes/api/auth/invite/complete.mjs +6 -6
  68. package/dist/astro/routes/api/auth/invite/index.mjs +6 -6
  69. package/dist/astro/routes/api/auth/invite/register-options.mjs +6 -6
  70. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  71. package/dist/astro/routes/api/auth/magic-link/send.mjs +7 -7
  72. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  73. package/dist/astro/routes/api/auth/me.mjs +7 -7
  74. package/dist/astro/routes/api/auth/mode.mjs +2 -2
  75. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  76. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  77. package/dist/astro/routes/api/auth/passkey/options.mjs +7 -7
  78. package/dist/astro/routes/api/auth/passkey/register/options.mjs +6 -6
  79. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +6 -6
  80. package/dist/astro/routes/api/auth/passkey/verify.mjs +6 -6
  81. package/dist/astro/routes/api/auth/signup/complete.mjs +6 -6
  82. package/dist/astro/routes/api/auth/signup/request.mjs +7 -7
  83. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  84. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +10 -10
  85. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +10 -10
  86. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  87. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  88. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  89. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  90. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +9 -9
  91. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +7 -7
  92. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +14 -14
  93. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +14 -14
  94. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  95. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  96. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +7 -7
  97. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +13 -13
  98. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  99. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  100. package/dist/astro/routes/api/content/_collection_/_id_.mjs +7 -7
  101. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  102. package/dist/astro/routes/api/content/_collection_/index.mjs +7 -7
  103. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  104. package/dist/astro/routes/api/dashboard.mjs +7 -7
  105. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  106. package/dist/astro/routes/api/import/probe.mjs +12 -12
  107. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  108. package/dist/astro/routes/api/import/wordpress/execute.d.mts +2 -2
  109. package/dist/astro/routes/api/import/wordpress/execute.mjs +11 -11
  110. package/dist/astro/routes/api/import/wordpress/media.d.mts +2 -2
  111. package/dist/astro/routes/api/import/wordpress/media.mjs +7 -7
  112. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  113. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +2 -2
  114. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +9 -9
  115. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +12 -12
  116. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +2 -2
  117. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +19 -19
  118. package/dist/astro/routes/api/manifest.mjs +3 -3
  119. package/dist/astro/routes/api/mcp.mjs +39 -39
  120. package/dist/astro/routes/api/media/_id_/confirm.mjs +7 -7
  121. package/dist/astro/routes/api/media/_id_/usage.mjs +11 -11
  122. package/dist/astro/routes/api/media/_id_.mjs +10 -10
  123. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  124. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  125. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  126. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  127. package/dist/astro/routes/api/media/upload-url.mjs +7 -7
  128. package/dist/astro/routes/api/media.mjs +10 -10
  129. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +8 -8
  130. package/dist/astro/routes/api/menus/_name_/items.mjs +8 -8
  131. package/dist/astro/routes/api/menus/_name_/reorder.mjs +8 -8
  132. package/dist/astro/routes/api/menus/_name_/translations.mjs +8 -8
  133. package/dist/astro/routes/api/menus/_name_.mjs +8 -8
  134. package/dist/astro/routes/api/menus/index.mjs +8 -8
  135. package/dist/astro/routes/api/oauth/authorize.d.mts.map +1 -1
  136. package/dist/astro/routes/api/oauth/authorize.mjs +23 -9
  137. package/dist/astro/routes/api/oauth/authorize.mjs.map +1 -1
  138. package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
  139. package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
  140. package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
  141. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  142. package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
  143. package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
  144. package/dist/astro/routes/api/oauth/token.mjs +2 -2
  145. package/dist/astro/routes/api/openapi.json.mjs +4 -4
  146. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  147. package/dist/astro/routes/api/redirects/404s/index.mjs +10 -10
  148. package/dist/astro/routes/api/redirects/404s/summary.mjs +10 -10
  149. package/dist/astro/routes/api/redirects/_id_.mjs +11 -11
  150. package/dist/astro/routes/api/redirects/index.mjs +11 -11
  151. package/dist/astro/routes/api/relations/_id_/index.mjs +14 -14
  152. package/dist/astro/routes/api/relations/_id_/translations.mjs +10 -10
  153. package/dist/astro/routes/api/relations/index.mjs +14 -14
  154. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  155. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  156. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +34 -34
  157. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +34 -34
  158. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +34 -34
  159. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +34 -34
  160. package/dist/astro/routes/api/schema/collections/index.mjs +34 -34
  161. package/dist/astro/routes/api/schema/index.mjs +8 -8
  162. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +34 -34
  163. package/dist/astro/routes/api/schema/orphans/index.mjs +34 -34
  164. package/dist/astro/routes/api/search/enable.mjs +11 -11
  165. package/dist/astro/routes/api/search/index.mjs +10 -10
  166. package/dist/astro/routes/api/search/rebuild.mjs +11 -11
  167. package/dist/astro/routes/api/search/stats.mjs +7 -7
  168. package/dist/astro/routes/api/search/suggest.mjs +10 -10
  169. package/dist/astro/routes/api/sections/_slug_.mjs +10 -10
  170. package/dist/astro/routes/api/sections/index.mjs +10 -10
  171. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +4 -4
  172. package/dist/astro/routes/api/settings/backups/archives/index.mjs +4 -4
  173. package/dist/astro/routes/api/settings/backups/export.mjs +4 -4
  174. package/dist/astro/routes/api/settings/backups/index.mjs +5 -5
  175. package/dist/astro/routes/api/settings/email.mjs +4 -4
  176. package/dist/astro/routes/api/settings.mjs +12 -12
  177. package/dist/astro/routes/api/setup/admin-verify.mjs +6 -6
  178. package/dist/astro/routes/api/setup/admin.mjs +6 -6
  179. package/dist/astro/routes/api/setup/dev-bypass.mjs +18 -18
  180. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  181. package/dist/astro/routes/api/setup/index.mjs +22 -22
  182. package/dist/astro/routes/api/setup/status.mjs +3 -3
  183. package/dist/astro/routes/api/snapshot.mjs +4 -4
  184. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +13 -13
  185. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +13 -13
  186. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +13 -13
  187. package/dist/astro/routes/api/taxonomies/index.mjs +13 -13
  188. package/dist/astro/routes/api/themes/preview.mjs +4 -4
  189. package/dist/astro/routes/api/typegen.mjs +5 -5
  190. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +7 -7
  191. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +10 -10
  192. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +10 -10
  193. package/dist/astro/routes/api/widget-areas/_name_.mjs +6 -6
  194. package/dist/astro/routes/api/widget-areas/index.mjs +10 -10
  195. package/dist/astro/routes/api/widget-components.mjs +2 -2
  196. package/dist/astro/routes/robots.txt.mjs +5 -5
  197. package/dist/astro/routes/sitemap-_collection_.xml.mjs +7 -7
  198. package/dist/astro/routes/sitemap.xml.mjs +5 -5
  199. package/dist/astro/types.d.mts +2 -2
  200. package/dist/{authorize-WxsiePzy.mjs → authorize-6WPZa1xZ.mjs} +2 -2
  201. package/dist/{authorize-WxsiePzy.mjs.map → authorize-6WPZa1xZ.mjs.map} +1 -1
  202. package/dist/{backup-S8kFWtwD.mjs → backup-DoW-H0r4.mjs} +2 -2
  203. package/dist/{backup-S8kFWtwD.mjs.map → backup-DoW-H0r4.mjs.map} +1 -1
  204. package/dist/{byline-XEjchwzZ.mjs → byline-BTOxUlFF.mjs} +6 -6
  205. package/dist/{byline-XEjchwzZ.mjs.map → byline-BTOxUlFF.mjs.map} +1 -1
  206. package/dist/{bylines-czseViYo.mjs → bylines-Bc-3xtpz.mjs} +6 -6
  207. package/dist/{bylines-czseViYo.mjs.map → bylines-Bc-3xtpz.mjs.map} +1 -1
  208. package/dist/{bylines-BJbT4gKS.mjs → bylines-oVN0iPwX.mjs} +12 -8
  209. package/dist/bylines-oVN0iPwX.mjs.map +1 -0
  210. package/dist/{cache-CGCd6AVM.mjs → cache-DDwy3JDW.mjs} +2 -2
  211. package/dist/{cache-CGCd6AVM.mjs.map → cache-DDwy3JDW.mjs.map} +1 -1
  212. package/dist/{chunks-BxXyunY-.mjs → chunks-Bq-5DPrD.mjs} +2 -2
  213. package/dist/{chunks-BxXyunY-.mjs.map → chunks-Bq-5DPrD.mjs.map} +1 -1
  214. package/dist/cli/index.mjs +18 -18
  215. package/dist/{comment-DPT0WKyd.mjs → comment-B9cUud8H.mjs} +2 -2
  216. package/dist/{comment-DPT0WKyd.mjs.map → comment-B9cUud8H.mjs.map} +1 -1
  217. package/dist/{comment-reaction-C65MldIB.mjs → comment-reaction-VUXO981F.mjs} +2 -2
  218. package/dist/{comment-reaction-C65MldIB.mjs.map → comment-reaction-VUXO981F.mjs.map} +1 -1
  219. package/dist/{comments-Bz6sCbgD.mjs → comments-PXj1E9Si.mjs} +3 -3
  220. package/dist/{comments-Bz6sCbgD.mjs.map → comments-PXj1E9Si.mjs.map} +1 -1
  221. package/dist/{config-CVssduLe.mjs → config-B6b_BobR.mjs} +6 -2
  222. package/dist/config-B6b_BobR.mjs.map +1 -0
  223. package/dist/{content-Ci04z2z-.mjs → content-BC43pICM.mjs} +4 -4
  224. package/dist/{content-Ci04z2z-.mjs.map → content-BC43pICM.mjs.map} +1 -1
  225. package/dist/{content-refresh-D4khvC0R.mjs → content-refresh-CPMSauD3.mjs} +4 -4
  226. package/dist/{content-refresh-D4khvC0R.mjs.map → content-refresh-CPMSauD3.mjs.map} +1 -1
  227. package/dist/{context-B6hc7zJL.mjs → context-DOurRIrb.mjs} +10 -10
  228. package/dist/{context-B6hc7zJL.mjs.map → context-DOurRIrb.mjs.map} +1 -1
  229. package/dist/{dashboard-C5NkXFbi.mjs → dashboard-CBm4zhc3.mjs} +4 -4
  230. package/dist/{dashboard-C5NkXFbi.mjs.map → dashboard-CBm4zhc3.mjs.map} +1 -1
  231. package/dist/database/pg-migration-lock.d.mts.map +1 -1
  232. package/dist/database/pg-migration-lock.mjs +1 -4
  233. package/dist/database/pg-migration-lock.mjs.map +1 -1
  234. package/dist/db/index.mjs +2 -2
  235. package/dist/{dialect-helpers-HxEQGl_1.mjs → dialect-helpers-BtOwo2us.mjs} +4 -4
  236. package/dist/dialect-helpers-BtOwo2us.mjs.map +1 -0
  237. package/dist/{error-CEGF6UZb.mjs → error-DJ98TRbQ.mjs} +2 -2
  238. package/dist/{error-CEGF6UZb.mjs.map → error-DJ98TRbQ.mjs.map} +1 -1
  239. package/dist/{field-defs-cache-DvmlgP-D.mjs → field-defs-cache-KfNJ_qLQ.mjs} +2 -2
  240. package/dist/{field-defs-cache-DvmlgP-D.mjs.map → field-defs-cache-KfNJ_qLQ.mjs.map} +1 -1
  241. package/dist/{fts-manager-DzqIBrrW.mjs → fts-manager-hyCU9OOp.mjs} +3 -3
  242. package/dist/{fts-manager-DzqIBrrW.mjs.map → fts-manager-hyCU9OOp.mjs.map} +1 -1
  243. package/dist/{hreflang-db2Tpb2o.mjs → hreflang-CbYeCzl9.mjs} +7 -7
  244. package/dist/{hreflang-db2Tpb2o.mjs.map → hreflang-CbYeCzl9.mjs.map} +1 -1
  245. package/dist/{import-Dmkm8S1W.mjs → import-oDYCgNYQ.mjs} +2 -2
  246. package/dist/{import-Dmkm8S1W.mjs.map → import-oDYCgNYQ.mjs.map} +1 -1
  247. package/dist/{index-wYnC6u4o.d.mts → index-BZJ_5FOX.d.mts} +50 -4
  248. package/dist/index-BZJ_5FOX.d.mts.map +1 -0
  249. package/dist/index.d.mts +2 -2
  250. package/dist/index.mjs +48 -48
  251. package/dist/{load-Cx27ki1l.mjs → load-Dg5gtWma.mjs} +2 -2
  252. package/dist/{load-Cx27ki1l.mjs.map → load-Dg5gtWma.mjs.map} +1 -1
  253. package/dist/{loader-Be3ouI5L.mjs → loader-D4rigvjR.mjs} +4 -4
  254. package/dist/{loader-Be3ouI5L.mjs.map → loader-D4rigvjR.mjs.map} +1 -1
  255. package/dist/media/local-runtime.d.mts +2 -2
  256. package/dist/media/local-runtime.mjs +5 -5
  257. package/dist/{media-kIV1IxFf.mjs → media-BJ6SRY3g.mjs} +8 -4
  258. package/dist/media-BJ6SRY3g.mjs.map +1 -0
  259. package/dist/{media-BjhhENaJ.mjs → media-its1WLYv.mjs} +2 -2
  260. package/dist/{media-BjhhENaJ.mjs.map → media-its1WLYv.mjs.map} +1 -1
  261. package/dist/{media-upload-D2wk3EIt.mjs → media-upload-DNu3QySc.mjs} +4 -4
  262. package/dist/{media-upload-D2wk3EIt.mjs.map → media-upload-DNu3QySc.mjs.map} +1 -1
  263. package/dist/{media-usage-CljdO1mc.mjs → media-usage-DPQ3K4xB.mjs} +6 -6
  264. package/dist/{media-usage-CljdO1mc.mjs.map → media-usage-DPQ3K4xB.mjs.map} +1 -1
  265. package/dist/{menus-CZyG6rvx.mjs → menus-BMGzWyWH.mjs} +15 -8
  266. package/dist/menus-BMGzWyWH.mjs.map +1 -0
  267. package/dist/{menus-BcyElFtI.mjs → menus-CPf_SW7u.mjs} +86 -8
  268. package/dist/menus-CPf_SW7u.mjs.map +1 -0
  269. package/dist/{parse-C_-6klII.mjs → parse-53zyxlpv.mjs} +2 -2
  270. package/dist/{parse-C_-6klII.mjs.map → parse-53zyxlpv.mjs.map} +1 -1
  271. package/dist/plugin-utils.d.mts +2 -2
  272. package/dist/plugins/adapt-sandbox-entry.d.mts +2 -2
  273. package/dist/{query-DR73ZNfm.mjs → query-Bz9bWZfm.mjs} +14 -14
  274. package/dist/{query-DR73ZNfm.mjs.map → query-Bz9bWZfm.mjs.map} +1 -1
  275. package/dist/{query-Di7DOmPV.mjs → query-DdFGHzQF.mjs} +3 -3
  276. package/dist/{query-Di7DOmPV.mjs.map → query-DdFGHzQF.mjs.map} +1 -1
  277. package/dist/{rate-limit-CMj83JNW.mjs → rate-limit-D3IEiUi2.mjs} +2 -2
  278. package/dist/{rate-limit-CMj83JNW.mjs.map → rate-limit-D3IEiUi2.mjs.map} +1 -1
  279. package/dist/{redirect-CgLPYflR.mjs → redirect-BoUac4NT.mjs} +3 -3
  280. package/dist/{redirect-CgLPYflR.mjs.map → redirect-BoUac4NT.mjs.map} +1 -1
  281. package/dist/{redirects-B7t9bKsL.mjs → redirects-ChbpqKiS.mjs} +3 -3
  282. package/dist/{redirects-B7t9bKsL.mjs.map → redirects-ChbpqKiS.mjs.map} +1 -1
  283. package/dist/{redirects-BBq3n2Rj.mjs → redirects-D457qwt0.mjs} +8 -8
  284. package/dist/redirects-D457qwt0.mjs.map +1 -0
  285. package/dist/{registry-FV15nLge.mjs → registry-y21kjhfG.mjs} +6 -6
  286. package/dist/{registry-FV15nLge.mjs.map → registry-y21kjhfG.mjs.map} +1 -1
  287. package/dist/{relation-9M9j_UWo.mjs → relation-Du0r04E0.mjs} +3 -3
  288. package/dist/{relation-9M9j_UWo.mjs.map → relation-Du0r04E0.mjs.map} +1 -1
  289. package/dist/{relations-5_avdrN_.mjs → relations-CZMpBqEd.mjs} +3 -3
  290. package/dist/{relations-5_avdrN_.mjs.map → relations-CZMpBqEd.mjs.map} +1 -1
  291. package/dist/{relations-BKvO39-9.mjs → relations-DtX1UMBH.mjs} +13 -7
  292. package/dist/relations-DtX1UMBH.mjs.map +1 -0
  293. package/dist/{relations-Bh8jlFsU.d.mts → relations-qKmSj9KM.d.mts} +27 -23
  294. package/dist/relations-qKmSj9KM.d.mts.map +1 -0
  295. package/dist/{resolve-Cd9dzclN.mjs → resolve-DFr4kq72.mjs} +2 -2
  296. package/dist/{resolve-Cd9dzclN.mjs.map → resolve-DFr4kq72.mjs.map} +1 -1
  297. package/dist/{runner-DfnZ5eUr.mjs → runner-BdeLEgad.mjs} +3 -3
  298. package/dist/{runner-DfnZ5eUr.mjs.map → runner-BdeLEgad.mjs.map} +1 -1
  299. package/dist/runtime.d.mts +2 -2
  300. package/dist/runtime.mjs +3 -3
  301. package/dist/{schema-BXxlHeAf.mjs → schema-BH8P8vaC.mjs} +3 -3
  302. package/dist/{schema-BXxlHeAf.mjs.map → schema-BH8P8vaC.mjs.map} +1 -1
  303. package/dist/{scopes-Bl4IwHA-.mjs → scopes-DTIkIaGA.mjs} +2 -2
  304. package/dist/{scopes-Bl4IwHA-.mjs.map → scopes-DTIkIaGA.mjs.map} +1 -1
  305. package/dist/{search-Bff-7jFt.mjs → search-D-Yhpmvs.mjs} +8 -7
  306. package/dist/search-D-Yhpmvs.mjs.map +1 -0
  307. package/dist/{secrets-CSwQIl4q.mjs → secrets-CQ-69kF9.mjs} +23 -17
  308. package/dist/secrets-CQ-69kF9.mjs.map +1 -0
  309. package/dist/{sections-CwW4s1al.mjs → sections-CzVBAKjw.mjs} +3 -3
  310. package/dist/{sections-CwW4s1al.mjs.map → sections-CzVBAKjw.mjs.map} +1 -1
  311. package/dist/seed/index.mjs +16 -16
  312. package/dist/seo/index.mjs +12 -12
  313. package/dist/{seo-2cmFHZns.mjs → seo-kJzVAmmu.mjs} +2 -2
  314. package/dist/{seo-2cmFHZns.mjs.map → seo-kJzVAmmu.mjs.map} +1 -1
  315. package/dist/{service-DkGTWGIi.mjs → service-CQnc276a.mjs} +2 -2
  316. package/dist/{service-DkGTWGIi.mjs.map → service-CQnc276a.mjs.map} +1 -1
  317. package/dist/{settings-CpA4lQFt.mjs → settings-C2PaBS49.mjs} +3 -3
  318. package/dist/{settings-CpA4lQFt.mjs.map → settings-C2PaBS49.mjs.map} +1 -1
  319. package/dist/{settings-C4s8hFQm.mjs → settings-Dtc0DU31.mjs} +3 -3
  320. package/dist/{settings-C4s8hFQm.mjs.map → settings-Dtc0DU31.mjs.map} +1 -1
  321. package/dist/{taxonomies-Ce49uIzY.mjs → taxonomies-BN6M9Nrv.mjs} +26 -17
  322. package/dist/taxonomies-BN6M9Nrv.mjs.map +1 -0
  323. package/dist/{taxonomies-DjSKBZpq.mjs → taxonomies-r7pqLBCn.mjs} +53 -24
  324. package/dist/taxonomies-r7pqLBCn.mjs.map +1 -0
  325. package/dist/{taxonomy-DfVooU4W.mjs → taxonomy-0mCI25HN.mjs} +3 -3
  326. package/dist/{taxonomy-DfVooU4W.mjs.map → taxonomy-0mCI25HN.mjs.map} +1 -1
  327. package/dist/{types-D1iJ3DpO.mjs → types-CrRUFdej.mjs} +2 -2
  328. package/dist/{types-D1iJ3DpO.mjs.map → types-CrRUFdej.mjs.map} +1 -1
  329. package/dist/{user-Bh-L1qo6.mjs → user-aTXqmXBz.mjs} +3 -3
  330. package/dist/{user-Bh-L1qo6.mjs.map → user-aTXqmXBz.mjs.map} +1 -1
  331. package/dist/{validate-V9nCwq_-.mjs → validate-CDT1jl6_.mjs} +11 -6
  332. package/dist/validate-CDT1jl6_.mjs.map +1 -0
  333. package/dist/validate-DULfgpJP.d.mts.map +1 -1
  334. package/dist/{validation-BsVUJfsP.mjs → validation-DlD0E35W.mjs} +8 -8
  335. package/dist/{validation-BsVUJfsP.mjs.map → validation-DlD0E35W.mjs.map} +1 -1
  336. package/dist/version-BRMngh9I.mjs +7 -0
  337. package/dist/{version-907opKac.mjs.map → version-BRMngh9I.mjs.map} +1 -1
  338. package/dist/{widgets-DGv1Z04V.mjs → widgets-5fipNkMT.mjs} +2 -2
  339. package/dist/{widgets-DGv1Z04V.mjs.map → widgets-5fipNkMT.mjs.map} +1 -1
  340. package/dist/{wxr-taxonomies-Cs1N6O8z.mjs → wxr-taxonomies-DrihupaC.mjs} +4 -4
  341. package/dist/{wxr-taxonomies-Cs1N6O8z.mjs.map → wxr-taxonomies-DrihupaC.mjs.map} +1 -1
  342. package/dist/{zod-generator-B5prQ5M4.mjs → zod-generator-CwkHWz4f.mjs} +2 -2
  343. package/dist/{zod-generator-B5prQ5M4.mjs.map → zod-generator-CwkHWz4f.mjs.map} +1 -1
  344. package/package.json +7 -7
  345. package/src/api/handlers/bylines.ts +5 -4
  346. package/src/api/handlers/content.ts +25 -7
  347. package/src/api/handlers/menus.ts +11 -6
  348. package/src/api/handlers/relations.ts +7 -2
  349. package/src/api/handlers/taxonomies.ts +22 -14
  350. package/src/api/schemas/bylines.ts +4 -4
  351. package/src/api/schemas/common.ts +7 -6
  352. package/src/api/schemas/menus.ts +2 -1
  353. package/src/api/schemas/relations.ts +3 -1
  354. package/src/api/schemas/taxonomies.ts +4 -2
  355. package/src/astro/prefetch.ts +7 -2
  356. package/src/astro/routes/api/admin/bylines/index.ts +5 -8
  357. package/src/astro/routes/api/oauth/authorize.ts +42 -10
  358. package/src/components/Gallery.astro +141 -26
  359. package/src/components/widgets/Categories.astro +1 -1
  360. package/src/components/widgets/Tags.astro +1 -1
  361. package/src/config/secrets.ts +25 -19
  362. package/src/content/converters/gallery.ts +61 -0
  363. package/src/content/converters/portable-text-to-prosemirror.ts +19 -0
  364. package/src/content/converters/prosemirror-to-portable-text.ts +18 -0
  365. package/src/content/converters/types.ts +36 -0
  366. package/src/database/dialect-helpers.ts +3 -4
  367. package/src/database/pg-migration-lock.ts +1 -4
  368. package/src/emdash-runtime.ts +55 -17
  369. package/src/i18n/config.ts +8 -0
  370. package/src/i18n/repair-locale-casing.ts +51 -0
  371. package/src/search/query.ts +3 -2
  372. package/src/seed/apply.ts +9 -7
  373. package/src/seed/validate.ts +15 -4
  374. package/src/taxonomies/index.ts +64 -23
  375. package/dist/api-b8WIiGU4.mjs.map +0 -1
  376. package/dist/apply-CmIJK9j8.mjs.map +0 -1
  377. package/dist/bylines-BJbT4gKS.mjs.map +0 -1
  378. package/dist/config-CVssduLe.mjs.map +0 -1
  379. package/dist/dialect-helpers-HxEQGl_1.mjs.map +0 -1
  380. package/dist/index-wYnC6u4o.d.mts.map +0 -1
  381. package/dist/media-kIV1IxFf.mjs.map +0 -1
  382. package/dist/menus-BcyElFtI.mjs.map +0 -1
  383. package/dist/menus-CZyG6rvx.mjs.map +0 -1
  384. package/dist/redirects-BBq3n2Rj.mjs.map +0 -1
  385. package/dist/relations-BKvO39-9.mjs.map +0 -1
  386. package/dist/relations-Bh8jlFsU.d.mts.map +0 -1
  387. package/dist/search-Bff-7jFt.mjs.map +0 -1
  388. package/dist/secrets-CSwQIl4q.mjs.map +0 -1
  389. package/dist/taxonomies-Ce49uIzY.mjs.map +0 -1
  390. package/dist/taxonomies-DjSKBZpq.mjs.map +0 -1
  391. package/dist/validate-V9nCwq_-.mjs.map +0 -1
  392. package/dist/version-907opKac.mjs +0 -7
@@ -30,7 +30,7 @@ import { UserRepository } from "../../database/repositories/user.js";
30
30
  import { withTransaction } from "../../database/transaction.js";
31
31
  import type { Database } from "../../database/types.js";
32
32
  import { validateIdentifier } from "../../database/validate.js";
33
- import { getI18nConfig, isI18nEnabled } from "../../i18n/config.js";
33
+ import { getI18nConfig, isI18nEnabled, resolveConfiguredLocale } from "../../i18n/config.js";
34
34
  import { invalidateRedirectCache } from "../../redirects/cache.js";
35
35
  import { FTSManager } from "../../search/fts-manager.js";
36
36
  import { invalidateTermCache } from "../../taxonomies/index.js";
@@ -269,7 +269,11 @@ async function resolveId(
269
269
  identifier: string,
270
270
  locale?: string,
271
271
  ): Promise<string | null> {
272
- const item = await repo.findByIdOrSlug(collection, identifier, locale);
272
+ const item = await repo.findByIdOrSlug(
273
+ collection,
274
+ identifier,
275
+ locale ? resolveConfiguredLocale(locale) : undefined,
276
+ );
273
277
  return item?.id ?? null;
274
278
  }
275
279
 
@@ -283,7 +287,11 @@ async function resolveIdIncludingTrashed(
283
287
  identifier: string,
284
288
  locale?: string,
285
289
  ): Promise<string | null> {
286
- const item = await repo.findByIdOrSlugIncludingTrashed(collection, identifier, locale);
290
+ const item = await repo.findByIdOrSlugIncludingTrashed(
291
+ collection,
292
+ identifier,
293
+ locale ? resolveConfiguredLocale(locale) : undefined,
294
+ );
287
295
  return item?.id ?? null;
288
296
  }
289
297
 
@@ -427,7 +435,7 @@ export async function handleContentList(
427
435
  const repo = new ContentRepository(db);
428
436
  const where: FindManyOptions["where"] = {};
429
437
  if (params.status) where.status = params.status;
430
- if (params.locale) where.locale = params.locale;
438
+ if (params.locale) where.locale = resolveConfiguredLocale(params.locale);
431
439
  if (params.authorId) where.authorId = params.authorId;
432
440
 
433
441
  // A date range requires a target column; ignore stray from/to without
@@ -579,7 +587,11 @@ export async function handleContentGet(
579
587
  ): Promise<ApiResult<ContentResponse>> {
580
588
  try {
581
589
  const repo = new ContentRepository(db);
582
- const item = await repo.findByIdOrSlug(collection, id, locale);
590
+ const item = await repo.findByIdOrSlug(
591
+ collection,
592
+ id,
593
+ locale ? resolveConfiguredLocale(locale) : undefined,
594
+ );
583
595
 
584
596
  if (!item) {
585
597
  return {
@@ -624,7 +636,11 @@ export async function handleContentGetIncludingTrashed(
624
636
  ): Promise<ApiResult<ContentResponse>> {
625
637
  try {
626
638
  const repo = new ContentRepository(db);
627
- const item = await repo.findByIdOrSlugIncludingTrashed(collection, id, locale);
639
+ const item = await repo.findByIdOrSlugIncludingTrashed(
640
+ collection,
641
+ id,
642
+ locale ? resolveConfiguredLocale(locale) : undefined,
643
+ );
628
644
 
629
645
  if (!item) {
630
646
  return {
@@ -706,7 +722,9 @@ export async function handleContentCreate(
706
722
  // Default to the configured site locale rather than the repo's
707
723
  // hard-coded "en" — otherwise non-English default-locale sites
708
724
  // silently create entries in a locale the editor never chose.
709
- const effectiveLocale = body.locale ?? getI18nConfig()?.defaultLocale;
725
+ const effectiveLocale = body.locale
726
+ ? resolveConfiguredLocale(body.locale)
727
+ : getI18nConfig()?.defaultLocale;
710
728
 
711
729
  let slug: string | null | undefined = body.slug;
712
730
  if (!slug) {
@@ -25,7 +25,7 @@ import {
25
25
  type UpdateMenuItemInput as UpdateMenuItemRepoInput,
26
26
  } from "../../database/repositories/menu.js";
27
27
  import type { Database } from "../../database/types.js";
28
- import { getI18nConfig } from "../../i18n/config.js";
28
+ import { getI18nConfig, resolveConfiguredLocale } from "../../i18n/config.js";
29
29
  import type { ApiResult } from "../types.js";
30
30
 
31
31
  // Re-export entity types so route files and tests can import them from the
@@ -89,7 +89,8 @@ async function resolveMenu(
89
89
  name: string,
90
90
  options: { locale?: string },
91
91
  ): Promise<ResolveMenuResult> {
92
- const matches = await repo.findByName(name, options);
92
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
93
+ const matches = await repo.findByName(name, { locale });
93
94
  if (matches.length === 0) {
94
95
  return {
95
96
  success: false,
@@ -124,7 +125,8 @@ export async function handleMenuList(
124
125
  ): Promise<ApiResult<MenuListItem[]>> {
125
126
  try {
126
127
  const repo = new MenuRepository(db);
127
- const items = await repo.findMany(options);
128
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
129
+ const items = await repo.findMany({ locale });
128
130
  return { success: true, data: items };
129
131
  } catch {
130
132
  return {
@@ -175,7 +177,9 @@ export async function handleMenuCreate(
175
177
 
176
178
  // Duplicate guard: same (name, locale). Falls back to the configured
177
179
  // defaultLocale to match the column DEFAULT set by migration 036.
178
- const effectiveLocale = input.locale ?? getI18nConfig()?.defaultLocale ?? "en";
180
+ const effectiveLocale = input.locale
181
+ ? resolveConfiguredLocale(input.locale)
182
+ : (getI18nConfig()?.defaultLocale ?? "en");
179
183
  if (await repo.existsByNameAndLocale(input.name, effectiveLocale)) {
180
184
  return {
181
185
  success: false,
@@ -188,7 +192,7 @@ export async function handleMenuCreate(
188
192
  };
189
193
  }
190
194
 
191
- const menu = await repo.create(input);
195
+ const menu = await repo.create({ ...input, locale: effectiveLocale });
192
196
  return { success: true, data: menu };
193
197
  } catch {
194
198
  return {
@@ -212,7 +216,8 @@ export async function handleMenuGet(
212
216
  ): Promise<ApiResult<MenuWithItems>> {
213
217
  try {
214
218
  const repo = new MenuRepository(db);
215
- const matches = await repo.findByName(name, options);
219
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
220
+ const matches = await repo.findByName(name, { locale });
216
221
  if (matches.length === 0) {
217
222
  return {
218
223
  success: false,
@@ -10,6 +10,7 @@ import {
10
10
  import { InvalidCursorError } from "../../database/repositories/types.js";
11
11
  import type { ContentItem } from "../../database/repositories/types.js";
12
12
  import type { Database } from "../../database/types.js";
13
+ import { resolveConfiguredLocale } from "../../i18n/config.js";
13
14
  import { SchemaRegistry } from "../../schema/registry.js";
14
15
  import type { ApiResult } from "../types.js";
15
16
 
@@ -70,7 +71,10 @@ export async function handleRelationCreate(
70
71
  }
71
72
  }
72
73
 
73
- const relation = await repo.create(input);
74
+ const relation = await repo.create({
75
+ ...input,
76
+ locale: input.locale ? resolveConfiguredLocale(input.locale) : undefined,
77
+ });
74
78
  return { success: true, data: { relation } };
75
79
  } catch (error) {
76
80
  // A bad `translationOf` makes the repo throw loudly rather than mint an
@@ -127,7 +131,8 @@ export async function handleRelationList(
127
131
  ): Promise<ApiResult<{ relations: Relation[] }>> {
128
132
  try {
129
133
  const repo = new RelationRepository(db);
130
- const relations = await repo.list(opts.locale);
134
+ const locale = opts.locale ? resolveConfiguredLocale(opts.locale) : undefined;
135
+ const relations = await repo.list(locale);
131
136
  return { success: true, data: { relations } };
132
137
  } catch {
133
138
  return {
@@ -12,6 +12,7 @@ import { ulid } from "ulidx";
12
12
 
13
13
  import { TaxonomyRepository } from "../../database/repositories/taxonomy.js";
14
14
  import type { Database, TaxonomyDefTable } from "../../database/types.js";
15
+ import { resolveConfiguredLocale } from "../../i18n/config.js";
15
16
  import { invalidateTaxonomyDefsCache, invalidateTermCache } from "../../taxonomies/index.js";
16
17
  import { fetchVisibleTermCounts } from "../../taxonomies/term-counts.js";
17
18
  import type { ApiResult } from "../types.js";
@@ -174,8 +175,9 @@ export async function handleTaxonomyList(
174
175
  options: { locale?: string } = {},
175
176
  ): Promise<ApiResult<TaxonomyListResponse>> {
176
177
  try {
178
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
177
179
  let query = db.selectFrom("_emdash_taxonomy_defs").selectAll();
178
- if (options.locale !== undefined) query = query.where("locale", "=", options.locale);
180
+ if (locale !== undefined) query = query.where("locale", "=", locale);
179
181
  const [rows, collectionRows] = await Promise.all([
180
182
  query.execute(),
181
183
  db.selectFrom("_emdash_collections").select("slug").execute(),
@@ -216,6 +218,7 @@ export async function handleTaxonomyCreate(
216
218
  },
217
219
  ): Promise<ApiResult<{ taxonomy: TaxonomyDef }>> {
218
220
  try {
221
+ const locale = input.locale ? resolveConfiguredLocale(input.locale) : undefined;
219
222
  if (!NAME_PATTERN.test(input.name)) {
220
223
  return {
221
224
  success: false,
@@ -265,19 +268,19 @@ export async function handleTaxonomyCreate(
265
268
 
266
269
  // Duplicate guard scoped to locale (so the same name can exist in ES
267
270
  // and EN).
268
- if (input.locale !== undefined) {
271
+ if (locale !== undefined) {
269
272
  const existing = await db
270
273
  .selectFrom("_emdash_taxonomy_defs")
271
274
  .select("id")
272
275
  .where("name", "=", input.name)
273
- .where("locale", "=", input.locale)
276
+ .where("locale", "=", locale)
274
277
  .executeTakeFirst();
275
278
  if (existing) {
276
279
  return {
277
280
  success: false,
278
281
  error: {
279
282
  code: "CONFLICT",
280
- message: `Taxonomy '${input.name}' already exists in locale '${input.locale}'`,
283
+ message: `Taxonomy '${input.name}' already exists in locale '${locale}'`,
281
284
  },
282
285
  };
283
286
  }
@@ -293,7 +296,7 @@ export async function handleTaxonomyCreate(
293
296
  label_singular: input.labelSingular ?? null,
294
297
  hierarchical: input.hierarchical ? 1 : 0,
295
298
  collections: JSON.stringify(collections),
296
- ...(input.locale !== undefined ? { locale: input.locale } : {}),
299
+ ...(locale !== undefined ? { locale } : {}),
297
300
  translation_group: translationGroup ?? id,
298
301
  })
299
302
  .execute();
@@ -390,7 +393,8 @@ export async function handleTermList(
390
393
  if (!lookup.success) return lookup;
391
394
 
392
395
  const repo = new TaxonomyRepository(db);
393
- const terms = await repo.findByName(taxonomyName, { locale: options.locale });
396
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
397
+ const terms = await repo.findByName(taxonomyName, { locale });
394
398
 
395
399
  // Counts match what visitors see on the public site: published (or
396
400
  // scheduled-and-due) entries that aren't soft-deleted, scoped to the
@@ -535,6 +539,7 @@ export async function handleTermCreate(
535
539
  },
536
540
  ): Promise<ApiResult<TermResponse>> {
537
541
  try {
542
+ const locale = input.locale ? resolveConfiguredLocale(input.locale) : undefined;
538
543
  // Taxonomy definitions are per-locale, but terms can exist in any locale
539
544
  // regardless of whether the def has been translated there. Look up the
540
545
  // def across all locales — we only care that it *exists*.
@@ -548,14 +553,14 @@ export async function handleTermCreate(
548
553
  input.parentId === "" || input.parentId === undefined ? undefined : input.parentId;
549
554
 
550
555
  // Conflict check is scoped to locale (per-locale slugs are unique).
551
- const existing = await repo.findBySlug(taxonomyName, input.slug, input.locale);
556
+ const existing = await repo.findBySlug(taxonomyName, input.slug, locale);
552
557
  if (existing) {
553
558
  return {
554
559
  success: false,
555
560
  error: {
556
561
  code: "CONFLICT",
557
- message: input.locale
558
- ? `Term '${input.slug}' already exists in '${taxonomyName}' (${input.locale})`
562
+ message: locale
563
+ ? `Term '${input.slug}' already exists in '${taxonomyName}' (${locale})`
559
564
  : `Term with slug '${input.slug}' already exists in taxonomy '${taxonomyName}'`,
560
565
  },
561
566
  };
@@ -594,7 +599,7 @@ export async function handleTermCreate(
594
599
  label: input.label,
595
600
  parentId: parentId ?? undefined,
596
601
  data: input.description ? { description: input.description } : undefined,
597
- locale: input.locale,
602
+ locale,
598
603
  translationOf: input.translationOf,
599
604
  });
600
605
 
@@ -635,7 +640,8 @@ export async function handleTermGet(
635
640
  ): Promise<ApiResult<TermGetResponse>> {
636
641
  try {
637
642
  const repo = new TaxonomyRepository(db);
638
- const term = await repo.findBySlug(taxonomyName, termSlug, options.locale);
643
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
644
+ const term = await repo.findBySlug(taxonomyName, termSlug, locale);
639
645
 
640
646
  if (!term) {
641
647
  return {
@@ -743,7 +749,8 @@ export async function handleTermUpdate(
743
749
  ): Promise<ApiResult<TermResponse>> {
744
750
  try {
745
751
  const repo = new TaxonomyRepository(db);
746
- const term = await repo.findBySlug(taxonomyName, termSlug, options.locale);
752
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
753
+ const term = await repo.findBySlug(taxonomyName, termSlug, locale);
747
754
 
748
755
  if (!term) {
749
756
  return {
@@ -763,7 +770,7 @@ export async function handleTermUpdate(
763
770
 
764
771
  // Check if new slug conflicts (per-locale uniqueness).
765
772
  if (newSlug !== undefined && newSlug !== termSlug) {
766
- const existing = await repo.findBySlug(taxonomyName, newSlug, options.locale);
773
+ const existing = await repo.findBySlug(taxonomyName, newSlug, locale);
767
774
  if (existing && existing.id !== term.id) {
768
775
  return {
769
776
  success: false,
@@ -832,7 +839,8 @@ export async function handleTermDelete(
832
839
  ): Promise<ApiResult<{ deleted: true }>> {
833
840
  try {
834
841
  const repo = new TaxonomyRepository(db);
835
- const term = await repo.findBySlug(taxonomyName, termSlug, options.locale);
842
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
843
+ const term = await repo.findBySlug(taxonomyName, termSlug, locale);
836
844
 
837
845
  if (!term) {
838
846
  return {
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
 
3
- import { cursorPaginationQuery, httpUrl } from "./common.js";
3
+ import { cursorPaginationQuery, httpUrl, localeCode } from "./common.js";
4
4
 
5
5
  /** Slug pattern: lowercase letters, digits, and hyphens; must start with a letter */
6
6
  const bylineSlugPattern = /^[a-z][a-z0-9-]*$/;
@@ -81,7 +81,7 @@ export const bylinesListQuery = cursorPaginationQuery
81
81
  * Rejects empty strings so the picker can't silently fetch the
82
82
  * unfiltered list when the admin URL has `?locale=` with no value.
83
83
  */
84
- locale: z.string().min(1).optional(),
84
+ locale: localeCode.optional(),
85
85
  })
86
86
  .meta({ id: "BylinesListQuery" });
87
87
 
@@ -102,7 +102,7 @@ export const bylineCreateBody = z
102
102
  * configured `defaultLocale`) is used. Rejects empty strings — an
103
103
  * empty locale would create rows no resolver requests.
104
104
  */
105
- locale: z.string().min(1).optional(),
105
+ locale: localeCode.optional(),
106
106
  /**
107
107
  * When set, the new row joins the source byline's translation_group
108
108
  * rather than minting a fresh one. Requires `locale`.
@@ -126,7 +126,7 @@ export const bylineCreateBody = z
126
126
 
127
127
  export const bylineTranslationCreateBody = z
128
128
  .object({
129
- locale: z.string().min(1),
129
+ locale: localeCode,
130
130
  slug: z
131
131
  .string()
132
132
  .min(1)
@@ -53,16 +53,17 @@ export const httpUrl = z
53
53
  .url()
54
54
  .refine((url) => HTTP_SCHEME_RE.test(url), "URL must use http or https");
55
55
 
56
- /** BCP 47 locale code — language with optional script/region subtags (e.g. en, en-US, pt-BR, es-419, zh-Hant) */
57
- export const localeCode = z
58
- .string()
59
- .regex(/^[a-z]{2,3}(-[a-z0-9]{2,8})*$/i, "Invalid locale code")
60
- .transform((v) => v.toLowerCase());
56
+ /**
57
+ * BCP 47 locale code — language with optional script/region subtags (e.g. en, en-US, pt-BR, es-419, zh-Hant).
58
+ * Validation is case-insensitive, but the value is preserved verbatim because the site config, stored
59
+ * `locale` columns, and public query path all keep the raw BCP-47 casing.
60
+ */
61
+ export const localeCode = z.string().regex(/^[a-z]{2,3}(-[a-z0-9]{2,8})*$/i, "Invalid locale code");
61
62
 
62
63
  /** Shared `?locale=xx` query shape for endpoints that filter by locale. */
63
64
  export const localeFilterQuery = z
64
65
  .object({
65
- locale: z.string().min(1).optional(),
66
+ locale: localeCode.optional(),
66
67
  })
67
68
  .meta({ id: "LocaleFilterQuery" });
68
69
 
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  import { isSafeHref } from "../../utils/url.js";
4
+ import { localeCode } from "./common.js";
4
5
 
5
6
  // ---------------------------------------------------------------------------
6
7
  // Menus: Input schemas
@@ -24,7 +25,7 @@ export const createMenuBody = z
24
25
  .object({
25
26
  name: z.string().min(1),
26
27
  label: z.string().min(1),
27
- locale: z.string().min(1).optional(),
28
+ locale: localeCode.optional(),
28
29
  /** When set, clones the items from the source menu. The new menu joins
29
30
  * the source's translation_group. */
30
31
  translationOf: z.string().min(1).optional(),
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
+ import { localeCode } from "./common.js";
4
+
3
5
  const slugPattern = /^[a-z][a-z0-9_]*$/;
4
6
  const collectionSlug = z
5
7
  .string()
@@ -18,7 +20,7 @@ export const createRelationBody = z
18
20
  childCollection: collectionSlug.optional(),
19
21
  parentLabel: z.string().min(1).max(200),
20
22
  childLabel: z.string().min(1).max(200),
21
- locale: z.string().min(1).optional(),
23
+ locale: localeCode.optional(),
22
24
  translationOf: z.string().min(1).optional(),
23
25
  })
24
26
  // A translation inherits its structural fields (name, parentCollection,
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
+ import { localeCode } from "./common.js";
4
+
3
5
  // ---------------------------------------------------------------------------
4
6
  // Taxonomy definitions: Input schemas
5
7
  // ---------------------------------------------------------------------------
@@ -24,7 +26,7 @@ export const createTaxonomyDefBody = z
24
26
  .max(100)
25
27
  .optional()
26
28
  .default([]),
27
- locale: z.string().min(1).optional(),
29
+ locale: localeCode.optional(),
28
30
  translationOf: z.string().min(1).optional(),
29
31
  })
30
32
  .meta({ id: "CreateTaxonomyDefBody" });
@@ -39,7 +41,7 @@ export const createTermBody = z
39
41
  label: z.string().min(1),
40
42
  parentId: z.string().nullish(),
41
43
  description: z.string().optional(),
42
- locale: z.string().min(1).optional(),
44
+ locale: localeCode.optional(),
43
45
  translationOf: z.string().min(1).optional(),
44
46
  })
45
47
  .meta({ id: "CreateTermBody" });
@@ -40,10 +40,15 @@ async function prefetchWidgetAreas(): Promise<void> {
40
40
  }
41
41
  }
42
42
 
43
- /** Warm every taxonomy's term list via the real helper (primes per-name keys). */
43
+ /**
44
+ * Warm every taxonomy's term list via the real helper (primes per-name keys).
45
+ * Counts are left out: they cost an aggregate over the whole assignment pivot
46
+ * per taxonomy, and only a consumer that renders one can say it's needed. A
47
+ * consumer that does asks for it and reuses the term list warmed here.
48
+ */
44
49
  async function prefetchTaxonomyTerms(): Promise<void> {
45
50
  const defs = await getTaxonomyDefs();
46
- await Promise.allSettled(defs.map((def) => getTaxonomyTerms(def.name)));
51
+ await Promise.allSettled(defs.map((def) => getTaxonomyTerms(def.name, { includeCounts: false })));
47
52
  }
48
53
 
49
54
  /** Warm every menu via the real helper (primes `menu:${name}:${locale}`). */
@@ -8,7 +8,7 @@ import { bylineCreateBody, bylinesListQuery } from "#api/schemas.js";
8
8
  import { invalidateBylineCache } from "#bylines/index.js";
9
9
  import { BylineRepository } from "#db/repositories/byline.js";
10
10
 
11
- import { getI18nConfig } from "../../../../../i18n/config.js";
11
+ import { getI18nConfig, resolveConfiguredLocale } from "../../../../../i18n/config.js";
12
12
 
13
13
  export const prerender = false;
14
14
 
@@ -25,13 +25,10 @@ export const GET: APIRoute = async ({ url, locals }) => {
25
25
  const query = parseQuery(url, bylinesListQuery);
26
26
  if (isParseError(query)) return query;
27
27
 
28
+ const locale = query.locale ? resolveConfiguredLocale(query.locale) : undefined;
28
29
  const i18n = getI18nConfig();
29
- if (query.locale && i18n && !i18n.locales.includes(query.locale)) {
30
- return apiError(
31
- "VALIDATION_ERROR",
32
- `Locale "${query.locale}" is not configured for this site`,
33
- 400,
34
- );
30
+ if (locale && i18n && !i18n.locales.includes(locale)) {
31
+ return apiError("VALIDATION_ERROR", `Locale "${locale}" is not configured for this site`, 400);
35
32
  }
36
33
 
37
34
  try {
@@ -40,7 +37,7 @@ export const GET: APIRoute = async ({ url, locals }) => {
40
37
  search: query.search,
41
38
  isGuest: query.isGuest,
42
39
  userId: query.userId,
43
- locale: query.locale,
40
+ locale,
44
41
  cursor: query.cursor,
45
42
  limit: query.limit,
46
43
  });
@@ -69,11 +69,16 @@ function getCsrfCookie(request: Request): string | null {
69
69
 
70
70
  const SCOPE_LABELS: Record<string, string> = {
71
71
  "content:read": "Read content (posts, pages, etc.)",
72
- "content:write": "Create, edit, and delete content",
72
+ "content:write": "Create, edit, and delete content; manage menus and taxonomies",
73
73
  "media:read": "View media files",
74
74
  "media:write": "Upload and manage media files",
75
75
  "schema:read": "View collection schemas",
76
76
  "schema:write": "Create and modify collection schemas",
77
+ "taxonomies:manage": "Manage taxonomies",
78
+ "menus:manage": "Manage navigation menus",
79
+ "settings:read": "View site settings",
80
+ "settings:manage": "Modify site settings",
81
+ "mcp:tools": "Use MCP tools from all enabled plugins",
77
82
  admin: "Full administrative access",
78
83
  };
79
84
 
@@ -141,7 +146,9 @@ export const GET: APIRoute = async ({ url, request, locals }) => {
141
146
  }
142
147
 
143
148
  // Parse and validate scopes
144
- const requestedScopes = (scope ?? "").split(" ").filter(Boolean).filter(isValidScope);
149
+ const requestedScopes = [
150
+ ...new Set((scope ?? "").split(" ").filter(Boolean).filter(isValidScope)),
151
+ ];
145
152
 
146
153
  if (requestedScopes.length === 0) {
147
154
  return new Response(renderErrorPage("No valid scopes requested."), {
@@ -201,6 +208,21 @@ export const POST: APIRoute = async ({ request, locals }) => {
201
208
  const v = formData.get(name);
202
209
  return typeof v === "string" ? v : fallback;
203
210
  };
211
+ const requestedScopes = new Set(
212
+ (new URL(request.url).searchParams.get("scope") ?? "")
213
+ .split(" ")
214
+ .filter(Boolean)
215
+ .filter(isValidScope),
216
+ );
217
+ const selectedScopes = [
218
+ ...new Set(
219
+ formData
220
+ .getAll("scope")
221
+ .filter((value): value is string => typeof value === "string")
222
+ .filter(isValidScope)
223
+ .filter((scope) => requestedScopes.has(scope)),
224
+ ),
225
+ ];
204
226
 
205
227
  // SEC-18: Validate CSRF token (double-submit cookie pattern).
206
228
  // The form includes a hidden csrf_token field; the cookie has the same value.
@@ -285,7 +307,7 @@ export const POST: APIRoute = async ({ request, locals }) => {
285
307
  response_type: field("response_type", "code"),
286
308
  client_id: field("client_id"),
287
309
  redirect_uri: redirectUri,
288
- scope: field("scope"),
310
+ scope: selectedScopes.join(" "),
289
311
  state,
290
312
  code_challenge: field("code_challenge"),
291
313
  code_challenge_method: field("code_challenge_method", "S256"),
@@ -294,12 +316,16 @@ export const POST: APIRoute = async ({ request, locals }) => {
294
316
 
295
317
  if (!result.success) {
296
318
  const errMsg = result.error?.message ?? "Authorization failed";
319
+ const invalidScope = result.error?.code === "INVALID_SCOPE";
297
320
  // On error, redirect back with error params — use generic description to avoid
298
321
  // leaking internal error details to the (already-validated) redirect target
299
322
  try {
300
323
  const errorUrl = new URL(redirectUri);
301
- errorUrl.searchParams.set("error", "server_error");
302
- errorUrl.searchParams.set("error_description", "Authorization failed");
324
+ errorUrl.searchParams.set("error", invalidScope ? "invalid_scope" : "server_error");
325
+ errorUrl.searchParams.set(
326
+ "error_description",
327
+ invalidScope ? "No selected permission can be granted" : "Authorization failed",
328
+ );
303
329
  if (state) errorUrl.searchParams.set("state", state);
304
330
  return Response.redirect(errorUrl.toString(), 302);
305
331
  } catch {
@@ -331,8 +357,8 @@ function renderConsentPage(params: {
331
357
  }): string {
332
358
  const scopeList = params.scopes
333
359
  .map((s) => {
334
- const label = SCOPE_LABELS[s] ?? s;
335
- return `<li>${escapeHtml(label)}</li>`;
360
+ const label = SCOPE_LABELS[s] ?? getPluginScopeLabel(s) ?? s;
361
+ return `<li><label><input type="checkbox" name="scope" value="${escapeHtml(s)}" checked><span>${escapeHtml(label)}</span></label></li>`;
336
362
  })
337
363
  .join("\n");
338
364
 
@@ -353,6 +379,8 @@ function renderConsentPage(params: {
353
379
  ul { list-style: none; margin-bottom: 1.5rem; }
354
380
  li { padding: 0.5rem 0; border-bottom: 1px solid #262626; font-size: 0.875rem; }
355
381
  li:last-child { border-bottom: none; }
382
+ label { display: flex; align-items: flex-start; gap: 0.625rem; cursor: pointer; }
383
+ input[type="checkbox"] { margin-top: 0.125rem; accent-color: #2563eb; }
356
384
  .actions { display: flex; gap: 0.75rem; }
357
385
  button { flex: 1; padding: 0.625rem 1rem; border-radius: 8px; border: none; font-size: 0.875rem; font-weight: 500; cursor: pointer; }
358
386
  .approve { background: #2563eb; color: white; }
@@ -366,14 +394,13 @@ function renderConsentPage(params: {
366
394
  <h1>Authorize Application</h1>
367
395
  <p class="client-id">${escapeHtml(params.clientId)}</p>
368
396
  <p class="user">Signed in as <strong>${escapeHtml(params.userName)}</strong></p>
369
- <h2>Permissions requested</h2>
370
- <ul>${scopeList}</ul>
371
397
  <form method="POST">
398
+ <h2>Permissions requested</h2>
399
+ <ul>${scopeList}</ul>
372
400
  <input type="hidden" name="csrf_token" value="${escapeHtml(params.csrfToken)}">
373
401
  <input type="hidden" name="response_type" value="${escapeHtml(params.responseType)}">
374
402
  <input type="hidden" name="client_id" value="${escapeHtml(params.clientId)}">
375
403
  <input type="hidden" name="redirect_uri" value="${escapeHtml(params.redirectUri)}">
376
- <input type="hidden" name="scope" value="${escapeHtml(params.scopes.join(" "))}">
377
404
  <input type="hidden" name="state" value="${escapeHtml(params.state)}">
378
405
  <input type="hidden" name="code_challenge" value="${escapeHtml(params.codeChallenge)}">
379
406
  <input type="hidden" name="code_challenge_method" value="${escapeHtml(params.codeChallengeMethod)}">
@@ -388,6 +415,11 @@ function renderConsentPage(params: {
388
415
  </html>`;
389
416
  }
390
417
 
418
+ function getPluginScopeLabel(scope: string): string | null {
419
+ const pluginId = scope.startsWith("mcp:tools:") ? scope.slice("mcp:tools:".length) : "";
420
+ return pluginId ? `Use MCP tools from ${pluginId}` : null;
421
+ }
422
+
391
423
  function renderErrorPage(message: string): string {
392
424
  return `<!DOCTYPE html>
393
425
  <html lang="en">