@jant/core 0.6.8 → 0.6.10

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 (77) hide show
  1. package/bin/commands/uploads/cleanup.js +1 -0
  2. package/dist/{app-9P4rVCe2.js → app-CGHkOdme.js} +3450 -3121
  3. package/dist/app-D24n0DoH.js +6 -0
  4. package/dist/client/.vite/manifest.json +3 -3
  5. package/dist/client/_assets/{client-CXnEhyyv.js → client-DYrWuaIk.js} +1 -1
  6. package/dist/client/_assets/{client-auth-CSItbyU8.js → client-auth-B5Re0uCd.js} +187 -167
  7. package/dist/client/_assets/client-xWDl78yi.css +2 -0
  8. package/dist/{export-Be082J0n.js → export-DY1v5Iqu.js} +2 -2
  9. package/dist/{github-sync-D1Cw8mOY.js → github-sync-2_T7nbOv.js} +1 -1
  10. package/dist/{github-sync-_kPWM4m9.js → github-sync-LefaslGJ.js} +2 -2
  11. package/dist/index.js +3 -3
  12. package/dist/node.js +4 -4
  13. package/package.json +1 -1
  14. package/src/client/components/__tests__/jant-settings-avatar.test.ts +8 -2
  15. package/src/client/components/__tests__/jant-settings-general.test.ts +64 -12
  16. package/src/client/components/jant-compose-dialog.ts +12 -0
  17. package/src/client/components/jant-settings-general.ts +74 -21
  18. package/src/client/components/settings-types.ts +13 -0
  19. package/src/client/settings-bridge.ts +3 -0
  20. package/src/client/tiptap/__tests__/link-toolbar.test.ts +41 -0
  21. package/src/client/tiptap/__tests__/mark-exit.test.ts +99 -0
  22. package/src/client/tiptap/bubble-menu.ts +37 -4
  23. package/src/client/tiptap/link-toolbar.ts +63 -1
  24. package/src/db/migrations/0026_absent_rhodey.sql +14 -0
  25. package/src/db/migrations/meta/0026_snapshot.json +2511 -0
  26. package/src/db/migrations/meta/_journal.json +7 -0
  27. package/src/db/migrations/pg/0024_high_violations.sql +14 -0
  28. package/src/db/migrations/pg/meta/0024_snapshot.json +3204 -0
  29. package/src/db/migrations/pg/meta/_journal.json +7 -0
  30. package/src/db/pg/schema.ts +36 -0
  31. package/src/db/schema.ts +36 -0
  32. package/src/i18n/__tests__/middleware.test.ts +46 -0
  33. package/src/i18n/locales/settings/en.po +282 -27
  34. package/src/i18n/locales/settings/en.ts +1 -1
  35. package/src/i18n/locales/settings/zh-Hans.po +282 -27
  36. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  37. package/src/i18n/locales/settings/zh-Hant.po +282 -27
  38. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  39. package/src/i18n/middleware.ts +17 -8
  40. package/src/i18n/supported-locales.ts +5 -4
  41. package/src/lib/__tests__/feed.test.ts +5 -1
  42. package/src/lib/feed.ts +6 -3
  43. package/src/lib/ids.ts +1 -0
  44. package/src/lib/resolve-config.ts +1 -0
  45. package/src/lib/upload.ts +14 -0
  46. package/src/routes/api/__tests__/settings.test.ts +1 -4
  47. package/src/routes/api/__tests__/upload.test.ts +2 -0
  48. package/src/routes/api/internal/__tests__/uploads.test.ts +19 -1
  49. package/src/routes/api/settings.ts +2 -1
  50. package/src/routes/auth/__tests__/setup.test.ts +14 -0
  51. package/src/routes/dash/__tests__/settings-avatar.test.ts +35 -17
  52. package/src/routes/dash/settings.tsx +22 -4
  53. package/src/routes/feed/__tests__/feed.test.ts +58 -19
  54. package/src/routes/feed/feed.ts +37 -28
  55. package/src/routes/pages/featured.tsx +17 -0
  56. package/src/routes/pages/latest.tsx +25 -0
  57. package/src/services/__tests__/media.test.ts +191 -30
  58. package/src/services/__tests__/settings.test.ts +55 -0
  59. package/src/services/bootstrap.ts +7 -0
  60. package/src/services/export-theme/layouts/_default/baseof.html +2 -1
  61. package/src/services/media.ts +169 -42
  62. package/src/services/post.ts +1 -1
  63. package/src/services/settings.ts +49 -15
  64. package/src/services/upload-session.ts +13 -3
  65. package/src/styles/tokens.css +21 -4
  66. package/src/styles/ui.css +44 -1
  67. package/src/types/bindings.ts +1 -0
  68. package/src/types/config.ts +13 -0
  69. package/src/ui/__tests__/color-themes.test.ts +2 -2
  70. package/src/ui/color-themes.ts +32 -0
  71. package/src/ui/dash/appearance/ColorThemeContent.tsx +264 -29
  72. package/src/ui/dash/settings/GeneralContent.tsx +54 -4
  73. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +3 -2
  74. package/src/ui/layouts/BaseLayout.tsx +3 -2
  75. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +17 -4
  76. package/dist/app-DaxS_Cz-.js +0 -6
  77. package/dist/client/_assets/client-C6peCkkD.css +0 -2
@@ -2,6 +2,7 @@
2
2
  * Color theme picker
3
3
  */
4
4
 
5
+ import type { MessageDescriptor } from "@lingui/core";
5
6
  import { msg } from "@lingui/core/macro";
6
7
  import { useLingui } from "../../../i18n/context.js";
7
8
  import { toPublicPath } from "../../../lib/url.js";
@@ -119,8 +120,260 @@ function ThemeModeCard({
119
120
  );
120
121
  }
121
122
 
123
+ interface ThemeCopy {
124
+ title: MessageDescriptor;
125
+ body: MessageDescriptor;
126
+ help: MessageDescriptor;
127
+ }
128
+
129
+ /**
130
+ * Per-theme preview copy: a plain description of each palette — what the
131
+ * background and accent look like, and when it fits. Keyed by theme id;
132
+ * unknown ids fall back to FALLBACK_COPY.
133
+ */
134
+ const THEME_COPY: Record<string, ThemeCopy> = {
135
+ tufte: {
136
+ title: msg({
137
+ message: "Warm ivory",
138
+ comment: "@context: Tufte color theme preview title",
139
+ }),
140
+ body: msg({
141
+ message: "Off-white paper with a deep forest-green accent.",
142
+ comment: "@context: Tufte color theme preview body",
143
+ }),
144
+ help: msg({
145
+ message: "The default. Calm and easy for long reading.",
146
+ comment: "@context: Tufte color theme preview help line",
147
+ }),
148
+ },
149
+ linen: {
150
+ title: msg({
151
+ message: "Warm cream",
152
+ comment: "@context: Linen color theme preview title",
153
+ }),
154
+ body: msg({
155
+ message: "Soft cream background with a muted green accent.",
156
+ comment: "@context: Linen color theme preview body",
157
+ }),
158
+ help: msg({
159
+ message: "The original Jant palette, and a solid everyday pick.",
160
+ comment: "@context: Linen color theme preview help line",
161
+ }),
162
+ },
163
+ frost: {
164
+ title: msg({
165
+ message: "Cool white",
166
+ comment: "@context: Frost color theme preview title",
167
+ }),
168
+ body: msg({
169
+ message: "Pale cool-white with deep indigo text. High contrast.",
170
+ comment: "@context: Frost color theme preview body",
171
+ }),
172
+ help: msg({
173
+ message: "When you want a cooler, sharper look.",
174
+ comment: "@context: Frost color theme preview help line",
175
+ }),
176
+ },
177
+ cotton: {
178
+ title: msg({
179
+ message: "Soft ivory",
180
+ comment: "@context: Cotton color theme preview title",
181
+ }),
182
+ body: msg({
183
+ message: "Very light ivory with a faint tea-green accent.",
184
+ comment: "@context: Cotton color theme preview body",
185
+ }),
186
+ help: msg({
187
+ message: "Nearly white, with a touch of warmth.",
188
+ comment: "@context: Cotton color theme preview help line",
189
+ }),
190
+ },
191
+ bone: {
192
+ title: msg({
193
+ message: "Warm off-white",
194
+ comment: "@context: Bone color theme preview title",
195
+ }),
196
+ body: msg({
197
+ message: "Pale bone-white with a muted green accent.",
198
+ comment: "@context: Bone color theme preview body",
199
+ }),
200
+ help: msg({
201
+ message: "Warm-neutral and easy for long reading.",
202
+ comment: "@context: Bone color theme preview help line",
203
+ }),
204
+ },
205
+ parchment: {
206
+ title: msg({
207
+ message: "Warm parchment",
208
+ comment: "@context: Parchment color theme preview title",
209
+ }),
210
+ body: msg({
211
+ message: "Yellow-tinted paper with an olive-green accent.",
212
+ comment: "@context: Parchment color theme preview body",
213
+ }),
214
+ help: msg({
215
+ message: "A warmer look, like slightly aged paper.",
216
+ comment: "@context: Parchment color theme preview help line",
217
+ }),
218
+ },
219
+ dune: {
220
+ title: msg({
221
+ message: "Warm sand",
222
+ comment: "@context: Dune color theme preview title",
223
+ }),
224
+ body: msg({
225
+ message: "Sandy background with a green accent.",
226
+ comment: "@context: Dune color theme preview body",
227
+ }),
228
+ help: msg({
229
+ message: "Warm and earthy, but still light.",
230
+ comment: "@context: Dune color theme preview help line",
231
+ }),
232
+ },
233
+ ink: {
234
+ title: msg({
235
+ message: "Neutral gray",
236
+ comment: "@context: Ink color theme preview title",
237
+ }),
238
+ body: msg({
239
+ message: "Near-neutral light gray with a steel-blue accent.",
240
+ comment: "@context: Ink color theme preview body",
241
+ }),
242
+ help: msg({
243
+ message: "Minimal color, low distraction.",
244
+ comment: "@context: Ink color theme preview help line",
245
+ }),
246
+ },
247
+ slate: {
248
+ title: msg({
249
+ message: "Cool blue-gray",
250
+ comment: "@context: Slate color theme preview title",
251
+ }),
252
+ body: msg({
253
+ message: "Light blue-gray background, cool and even.",
254
+ comment: "@context: Slate color theme preview body",
255
+ }),
256
+ help: msg({
257
+ message: "A calm, cool backdrop.",
258
+ comment: "@context: Slate color theme preview help line",
259
+ }),
260
+ },
261
+ sage: {
262
+ title: msg({
263
+ message: "Soft green",
264
+ comment: "@context: Sage color theme preview title",
265
+ }),
266
+ body: msg({
267
+ message: "Light sage-green with a matching green accent.",
268
+ comment: "@context: Sage color theme preview body",
269
+ }),
270
+ help: msg({
271
+ message: "Calm and natural, easy on the eyes.",
272
+ comment: "@context: Sage color theme preview help line",
273
+ }),
274
+ },
275
+ clay: {
276
+ title: msg({
277
+ message: "Warm terracotta",
278
+ comment: "@context: Clay color theme preview title",
279
+ }),
280
+ body: msg({
281
+ message: "Muted red-brown background, earthy and warm.",
282
+ comment: "@context: Clay color theme preview body",
283
+ }),
284
+ help: msg({
285
+ message: "An earthier, warmer tone.",
286
+ comment: "@context: Clay color theme preview help line",
287
+ }),
288
+ },
289
+ ember: {
290
+ title: msg({
291
+ message: "Warm orange",
292
+ comment: "@context: Ember color theme preview title",
293
+ }),
294
+ body: msg({
295
+ message: "Orange-tinted background. The warmest palette.",
296
+ comment: "@context: Ember color theme preview body",
297
+ }),
298
+ help: msg({
299
+ message: "Cozy and bold among the warm tones.",
300
+ comment: "@context: Ember color theme preview help line",
301
+ }),
302
+ },
303
+ paper: {
304
+ title: msg({
305
+ message: "Bright paper white",
306
+ comment: "@context: Paper color theme preview title",
307
+ }),
308
+ body: msg({
309
+ message: "Bright warm-white with a moss-green accent.",
310
+ comment: "@context: Paper color theme preview body",
311
+ }),
312
+ help: msg({
313
+ message: "Clean and high-contrast, close to white.",
314
+ comment: "@context: Paper color theme preview help line",
315
+ }),
316
+ },
317
+ snow: {
318
+ title: msg({
319
+ message: "Pure white",
320
+ comment: "@context: Snow color theme preview title",
321
+ }),
322
+ body: msg({
323
+ message: "Pure white with neutral grays. No color tint.",
324
+ comment: "@context: Snow color theme preview body",
325
+ }),
326
+ help: msg({
327
+ message: "The cleanest, most neutral option.",
328
+ comment: "@context: Snow color theme preview help line",
329
+ }),
330
+ },
331
+ espresso: {
332
+ title: msg({
333
+ message: "Cream and coffee brown",
334
+ comment: "@context: Espresso color theme preview title",
335
+ }),
336
+ body: msg({
337
+ message: "Warm cream background with deep coffee-brown accents.",
338
+ comment: "@context: Espresso color theme preview body",
339
+ }),
340
+ help: msg({
341
+ message: "Warm and rich, with strong brown tones.",
342
+ comment: "@context: Espresso color theme preview help line",
343
+ }),
344
+ },
345
+ };
346
+
347
+ const EXAMPLE_LINK = msg({
348
+ message: "Example link",
349
+ comment:
350
+ "@context: Placeholder link label shown in color theme preview cards",
351
+ });
352
+
353
+ const ACCENT_LABEL = msg({
354
+ message: "Accent",
355
+ comment:
356
+ "@context: Label next to the theme accent-color swatch in a color theme preview card",
357
+ });
358
+
359
+ const FALLBACK_COPY: ThemeCopy = {
360
+ title: msg({
361
+ message: "This palette",
362
+ comment: "@context: Fallback color theme preview title",
363
+ }),
364
+ body: msg({
365
+ message: "Headings, body text, and links in this theme.",
366
+ comment: "@context: Fallback color theme preview body",
367
+ }),
368
+ help: msg({
369
+ message: "Pick the one that fits your writing.",
370
+ comment: "@context: Fallback color theme preview help line",
371
+ }),
372
+ };
373
+
122
374
  function ThemePreview({ theme }: { theme: ColorTheme }) {
123
375
  const { i18n } = useLingui();
376
+ const copy = THEME_COPY[theme.id] ?? FALLBACK_COPY;
124
377
 
125
378
  return (
126
379
  <div class="min-w-0">
@@ -129,39 +382,21 @@ function ThemePreview({ theme }: { theme: ColorTheme }) {
129
382
  </div>
130
383
 
131
384
  <div class="theme-preview-divider mt-2 border-t pt-2">
132
- <h3 class="theme-preview-title text-[0.98rem]">
133
- {i18n._(
134
- msg({
135
- message: "Field notes on quiet design",
136
- comment: "@context: Color theme preview card title",
137
- }),
138
- )}
139
- </h3>
385
+ <h3 class="theme-preview-title text-[0.98rem]">{i18n._(copy.title)}</h3>
140
386
 
141
387
  <p class="theme-preview-body mt-2 text-[0.84rem]">
142
- {i18n._(
143
- msg({
144
- message: "Soft color should still carry a clear reading rhythm.",
145
- comment: "@context: Color theme preview card body sentence",
146
- }),
147
- )}
388
+ {i18n._(copy.body)}{" "}
389
+ <a class="theme-preview-link" tabIndex={-1}>
390
+ {i18n._(EXAMPLE_LINK)}
391
+ </a>{" "}
392
+ <span aria-hidden="true">&middot;</span>{" "}
393
+ <span class="theme-preview-accent">
394
+ <span class="theme-preview-swatch" aria-hidden="true"></span>
395
+ {i18n._(ACCENT_LABEL)}
396
+ </span>
148
397
  </p>
149
398
  <p class="theme-preview-meta mt-1.5 text-[0.8rem]">
150
- {i18n._(
151
- msg({
152
- message: "Quiet surfaces let writing lead.",
153
- comment: "@context: Color theme preview card secondary sentence",
154
- }),
155
- )}{" "}
156
- <a class="theme-preview-link" tabIndex={-1}>
157
- {i18n._(
158
- msg({
159
- message: "Read why",
160
- comment: "@context: Color theme preview inline link label",
161
- }),
162
- )}
163
- </a>
164
- .
399
+ {i18n._(copy.help)}
165
400
  </p>
166
401
 
167
402
  <div class="theme-preview-divider mt-3 border-t pt-2">
@@ -15,6 +15,7 @@ export function GeneralContent({
15
15
  siteName,
16
16
  siteDescription,
17
17
  siteLanguage,
18
+ dashboardLanguage,
18
19
  cjkSerifFont,
19
20
  siteNameFallback,
20
21
  siteDescriptionFallback,
@@ -22,6 +23,7 @@ export function GeneralContent({
22
23
  mainFeedUrl,
23
24
  latestFeedUrl,
24
25
  featuredFeedUrl,
26
+ archiveFeedUrl,
25
27
  timeZone,
26
28
  siteFooter,
27
29
  showJantBrandingOnHome,
@@ -32,6 +34,7 @@ export function GeneralContent({
32
34
  siteName: string;
33
35
  siteDescription: string;
34
36
  siteLanguage: string;
37
+ dashboardLanguage: string;
35
38
  cjkSerifFont: string;
36
39
  siteNameFallback: string;
37
40
  siteDescriptionFallback: string;
@@ -39,6 +42,7 @@ export function GeneralContent({
39
42
  mainFeedUrl: string;
40
43
  latestFeedUrl: string;
41
44
  featuredFeedUrl: string;
45
+ archiveFeedUrl: string;
42
46
  timeZone: string;
43
47
  siteFooter: string;
44
48
  showJantBrandingOnHome: boolean;
@@ -101,15 +105,37 @@ export function GeneralContent({
101
105
  ),
102
106
  siteLanguage: i18n._(
103
107
  msg({
104
- message: "Language",
105
- comment: "@context: Settings form field for site/admin language",
108
+ message: "Content language",
109
+ comment:
110
+ "@context: Settings form field for the public content language",
106
111
  }),
107
112
  ),
108
113
  siteLanguageHelp: i18n._(
109
114
  msg({
110
115
  message:
111
- "Sets the content language announced to readers (HTML lang, RSS) and the dashboard language. Any BCP 47 tag is accepted; tags without a dashboard translation fall back to English.",
112
- comment: "@context: Help text under the site language input",
116
+ "The language your posts are written in. Announced to readers and search engines through HTML lang and your RSS feed. Any BCP 47 tag works.",
117
+ comment: "@context: Help text under the content language picker",
118
+ }),
119
+ ),
120
+ contentLanguagePreview: i18n._(
121
+ msg({
122
+ message: "Readers and search engines see",
123
+ comment:
124
+ "@context: Lead text before a live <html lang> preview of the content language",
125
+ }),
126
+ ),
127
+ dashboardLanguage: i18n._(
128
+ msg({
129
+ message: "Dashboard language",
130
+ comment:
131
+ "@context: Settings form field for the admin interface language",
132
+ }),
133
+ ),
134
+ dashboardLanguageHelp: i18n._(
135
+ msg({
136
+ message:
137
+ "The language this admin dashboard shows in. Available in English, 简体中文, and 繁體中文.",
138
+ comment: "@context: Help text under the dashboard language picker",
113
139
  }),
114
140
  ),
115
141
  siteLanguageSearchPlaceholder: i18n._(
@@ -210,6 +236,19 @@ export function GeneralContent({
210
236
  comment: "@context: Label for the explicit featured RSS feed URL",
211
237
  }),
212
238
  ),
239
+ archiveFeedUrl: i18n._(
240
+ msg({
241
+ message: "Archive feed",
242
+ comment: "@context: Label for the full-archive RSS feed URL",
243
+ }),
244
+ ),
245
+ archiveFeedUrlHelp: i18n._(
246
+ msg({
247
+ message: "Every published post, including ones hidden from Latest.",
248
+ comment:
249
+ "@context: Help text under the archive feed URL, explaining it is the complete feed",
250
+ }),
251
+ ),
213
252
  latestFeedOption: i18n._(
214
253
  msg({
215
254
  message: "Latest",
@@ -308,6 +347,14 @@ export function GeneralContent({
308
347
  timezones.map((tz) => ({ value: tz.value, label: tz.label })),
309
348
  ).replace(/</g, "\\u003c");
310
349
 
350
+ // The 3 catalog locales Jant's dashboard is translated into. Native-script
351
+ // labels so each reads in its own language, like the CJK font options.
352
+ const dashboardLanguagesJson = JSON.stringify([
353
+ { value: "en", label: "English" },
354
+ { value: "zh-Hans", label: "简体中文" },
355
+ { value: "zh-Hant", label: "繁體中文" },
356
+ ]).replace(/</g, "\\u003c");
357
+
311
358
  const cjkFontsJson = JSON.stringify([
312
359
  { value: "off", label: "None" },
313
360
  {
@@ -326,6 +373,7 @@ export function GeneralContent({
326
373
  siteName,
327
374
  siteDescription,
328
375
  siteLanguage,
376
+ dashboardLanguage,
329
377
  cjkSerifFont,
330
378
  mainRssFeed,
331
379
  timeZone,
@@ -341,11 +389,13 @@ export function GeneralContent({
341
389
  labels={labels}
342
390
  timezones={timezonesJson}
343
391
  cjk-fonts={cjkFontsJson}
392
+ dashboard-languages={dashboardLanguagesJson}
344
393
  sitename-fallback={siteNameFallback}
345
394
  sitedescription-fallback={siteDescriptionFallback}
346
395
  main-feed-url={mainFeedUrl}
347
396
  latest-feed-url={latestFeedUrl}
348
397
  featured-feed-url={featuredFeedUrl}
398
+ archive-feed-url={archiveFeedUrl}
349
399
  demo-mode={demoMode || undefined}
350
400
  >
351
401
  {/* SSR fallback skeleton */}
@@ -41,8 +41,9 @@ function createProps(
41
41
  siteDescriptionFallback: "Fallback Description",
42
42
  mainRssFeed: "featured" as const,
43
43
  mainFeedUrl: "/feed",
44
- latestFeedUrl: "/feed/latest",
45
- featuredFeedUrl: "/feed/featured",
44
+ latestFeedUrl: "/latest/feed",
45
+ featuredFeedUrl: "/featured/feed",
46
+ archiveFeedUrl: "/archive/feed",
46
47
  timeZone: "UTC",
47
48
  siteFooter: "Footer text",
48
49
  showJantBrandingOnHome: false,
@@ -239,10 +239,10 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
239
239
  socialImageWidthValue >= 300;
240
240
  const mainFeedHref = appConfig ? toPublicPath("/feed", sitePathPrefix) : null;
241
241
  const latestFeedHref = appConfig
242
- ? toPublicPath("/feed/latest", sitePathPrefix)
242
+ ? toPublicPath("/latest/feed", sitePathPrefix)
243
243
  : null;
244
244
  const featuredFeedHref = appConfig
245
- ? toPublicPath("/feed/featured", sitePathPrefix)
245
+ ? toPublicPath("/featured/feed", sitePathPrefix)
246
246
  : null;
247
247
  const mainFeedTitle =
248
248
  i18n?._(
@@ -277,6 +277,7 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
277
277
  {raw("<!DOCTYPE html>")}
278
278
  <html
279
279
  lang={resolvedLang}
280
+ data-theme={appConfig?.themeId}
280
281
  data-theme-mode={themeMode}
281
282
  data-site-path-prefix={sitePathPrefix}
282
283
  data-asset-base-path={assetBasePath}
@@ -279,8 +279,8 @@ describe("BaseLayout", () => {
279
279
  html.match(/rel="alternate" type="application\/atom\+xml"/g) ?? [],
280
280
  ).toHaveLength(2);
281
281
  expect(html).toContain('href="/feed"');
282
- expect(html).toContain('href="/feed/latest"');
283
- expect(html).not.toContain('href="/feed/featured"');
282
+ expect(html).toContain('href="/latest/feed"');
283
+ expect(html).not.toContain('href="/featured/feed"');
284
284
  });
285
285
 
286
286
  it("switches the alternate feed link when latest is the main feed", async () => {
@@ -297,8 +297,8 @@ describe("BaseLayout", () => {
297
297
  html.match(/rel="alternate" type="application\/atom\+xml"/g) ?? [],
298
298
  ).toHaveLength(2);
299
299
  expect(html).toContain('href="/feed"');
300
- expect(html).toContain('href="/feed/featured"');
301
- expect(html).not.toContain('href="/feed/latest"');
300
+ expect(html).toContain('href="/featured/feed"');
301
+ expect(html).not.toContain('href="/latest/feed"');
302
302
  });
303
303
 
304
304
  it("uses the public asset base path from appConfig in production", async () => {
@@ -320,6 +320,19 @@ describe("BaseLayout", () => {
320
320
  expect(html).toContain('data-asset-base-path="/blog/_assets"');
321
321
  });
322
322
 
323
+ it("exposes the active theme id on the root html element", async () => {
324
+ const { BaseLayout } = await loadBaseLayout();
325
+ const html = renderToString(
326
+ BaseLayout({
327
+ title: "Jant",
328
+ c: createContext("featured", { themeId: "frost" }),
329
+ children: "Test",
330
+ }),
331
+ );
332
+
333
+ expect(html).toContain('data-theme="frost"');
334
+ });
335
+
323
336
  it("renders theme-color tags that follow the active theme in auto mode", async () => {
324
337
  const { BaseLayout } = await loadBaseLayout();
325
338
  const html = renderToString(
@@ -1,6 +0,0 @@
1
- import "./url-BMYO-Zlt.js";
2
- import { t as createApp } from "./app-9P4rVCe2.js";
3
- import "./export-Be082J0n.js";
4
- import "./env-OHRKGcMj.js";
5
- import "./github-sync-D1Cw8mOY.js";
6
- export { createApp };