@takazudo/zudo-doc 5.19.1 → 5.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/asset-page/index.d.ts +3 -1
  3. package/dist/asset-page/index.js +5 -4
  4. package/dist/breadcrumb/breadcrumb.js +21 -14
  5. package/dist/chrome/derive.d.ts +23 -1
  6. package/dist/chrome/derive.js +17 -2
  7. package/dist/chrome-bindings.d.ts +6 -1
  8. package/dist/compiled.css +23 -0
  9. package/dist/config.d.ts +13 -1
  10. package/dist/config.js +1 -0
  11. package/dist/date-format-resolve/index.d.ts +10 -0
  12. package/dist/date-format-resolve/index.js +22 -0
  13. package/dist/doc-content-header/index.js +4 -2
  14. package/dist/doc-history/index.d.ts +17 -1
  15. package/dist/doc-history/index.js +19 -13
  16. package/dist/doc-history-area/index.d.ts +3 -1
  17. package/dist/doc-history-area/index.js +7 -2
  18. package/dist/doc-metainfo-area/index.js +4 -2
  19. package/dist/doc-pager/index.js +4 -2
  20. package/dist/docs-schema/index.d.ts +1 -1
  21. package/dist/eject/index.js +1 -1
  22. package/dist/factory-context/index.d.ts +3 -6
  23. package/dist/format-date/index.d.ts +15 -6
  24. package/dist/format-date/index.js +79 -15
  25. package/dist/header-with-defaults/index.js +5 -1
  26. package/dist/home-page/index.js +9 -7
  27. package/dist/nav-indexing/note-tray-index-parts/card-list.js +18 -5
  28. package/dist/nav-indexing/note-tray-index-parts/date-line.d.ts +3 -1
  29. package/dist/nav-indexing/note-tray-index-parts/date-line.js +4 -3
  30. package/dist/nav-indexing/note-tray-index-parts/index-list.js +9 -1
  31. package/dist/nav-indexing/note-tray-index-parts/timeline.js +2 -2
  32. package/dist/nav-indexing/note-tray-index.d.ts +9 -0
  33. package/dist/note-tray-index/index.d.ts +9 -0
  34. package/dist/note-tray-index/index.js +3 -1
  35. package/dist/note-tray-model/index.d.ts +13 -3
  36. package/dist/note-tray-model/index.js +9 -2
  37. package/dist/plugins/internal/claude-resources/generate.js +3 -2
  38. package/dist/plugins/internal/codex-resources/overview.js +3 -2
  39. package/dist/plugins/internal/resource-docs-shared/fs.d.ts +1 -1
  40. package/dist/plugins/internal/resource-docs-shared/fs.js +3 -2
  41. package/dist/routes/_context.d.ts +1 -4
  42. package/dist/safelist.css +1 -1
  43. package/dist/settings.d.ts +37 -0
  44. package/dist/sidebar-toggle-island/index.d.ts +16 -1
  45. package/dist/sidebar-toggle-island/index.js +6 -2
  46. package/dist/sidebar-tree-island/index.d.ts +15 -1
  47. package/dist/sidebar-tree-island/index.js +19 -8
  48. package/dist/sidebar-with-defaults/index.js +5 -2
  49. package/dist/site-tree-nav/index.d.ts +13 -0
  50. package/dist/site-tree-nav/index.js +6 -2
  51. package/dist/site-tree-nav-island/index.d.ts +9 -1
  52. package/dist/site-tree-nav-island/index.js +19 -3
  53. package/dist/theme-no-reset.css +379 -0
  54. package/dist/theme-packs/academia/pack.css +12 -2
  55. package/dist/theme-packs/blueprint/pack.css +7 -1
  56. package/dist/theme-packs/broadsheet/pack.css +7 -1
  57. package/dist/theme-packs/brutalist/pack.css +21 -3
  58. package/dist/theme-packs/drift/pack.css +5 -1
  59. package/dist/theme-packs/foundry/pack.css +13 -2
  60. package/dist/theme-packs/hearth/pack.css +7 -1
  61. package/dist/theme-packs/ledger/pack.css +12 -2
  62. package/dist/theme-packs/observatory/pack.css +5 -1
  63. package/dist/theme-packs/riso/pack.css +9 -6
  64. package/dist/theme-packs/sakura/pack.css +6 -2
  65. package/dist/theme-packs/sumi/pack.css +7 -1
  66. package/dist/theme-packs/swissgrid/pack.css +7 -1
  67. package/dist/theme-packs/tidepool/pack.css +8 -1
  68. package/dist/theme-packs/washi/pack.css +5 -2
  69. package/dist/theme.css +60 -0
  70. package/eject/breadcrumb/breadcrumb.tsx +8 -3
  71. package/eject/doc-history/index.tsx +41 -16
  72. package/eject/doc-pager/index.tsx +12 -2
  73. package/eject/sidebar-toggle-island/index.tsx +19 -0
  74. package/eject/sidebar-tree-island/index.tsx +40 -4
  75. package/eject/site-tree-nav-island/index.tsx +31 -5
  76. package/package.json +4 -3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ All notable changes to `@takazudo/zudo-doc` are documented in this file.
4
4
 
5
5
  The format is based on Keep a Changelog, and release notes are generated from the changelog MDX pages.
6
6
 
7
+ ## [Unreleased]
8
+
9
+ No unreleased changes.
10
+
11
+ ## [5.20.0] - 2026-09-09
12
+
13
+ ### Features
14
+
15
+ - Added `dateFormat` patterns with per-role and per-locale overrides for page metadata, history, note trays, and navigation. Patterns support `YYYY`, `YY`, `MMMM`, `MMM`, `MM`, `M`, `DD`, `D`, and bracketed literal text; omitted roles retain locale-based formatting. (`2eeb42ce2`, `c126ecf8d`, `1d8ba74fa`, `10d5a90dc`, `3a2dc81fe`)
16
+ - Added optional `LocaleConfig.description` for each non-default locale's home hero. Omitted values use `settings.siteDescription`; an empty string intentionally leaves the description blank. This changes hero text only; `llms.txt` retains the global description. (`0ce449ba0`)
17
+ - Added namespaced `--color-zd-*` aliases and `theme-no-reset.css`, which preserves host color namespaces when stylesheet import order cannot be controlled. (`f3aae4442`, `ecdc23800`)
18
+
19
+ ### Bug Fixes
20
+
21
+ - Rendered sidebar and site-tree dates in the page locale, including single-locale sites without language-switcher links. Japanese month groups now read `2026年8月` rather than `2026 August`; default numeric month/day and year-only labels retain their existing output. (`47bd00ff4`, `3c6c81c38`)
22
+ - Separated history display locale from its storage path and formatted history timestamps in UTC, preventing browser locale or timezone from changing the displayed revision date. (`30bcf88c8`)
23
+ - Let home hero links wrap without squeezing Japanese labels, and wrapped long terminal breadcrumb titles while preserving the version pill's spacing. (`be7b3de3b`, `173fdb77f`)
24
+ - Corrected theme-pack navigation hover contrast, including active items and text inside brutalist link cards. (`6e561da45`, `791ad2c92`, `c83b8bda5`)
25
+
26
+ ### Other Changes
27
+
28
+ - Expanded the optional `@takazudo/zdtp` peer range to `^0.5.2 || ^0.6.0 || ^0.7.0`. Existing panel integrations need no configuration or storage migration, and downstream pnpm peer-range exceptions can be removed. Continue importing `@takazudo/zdtp/styles.css` explicitly. (`e0fc27fbd`, `9554dbbea`)
29
+
7
30
  ## [5.19.1] - 2026-09-08
8
31
 
9
32
  ### Other Changes
@@ -2,6 +2,7 @@
2
2
  /** @jsxImportSource preact */
3
3
  import type { ComponentChildren, JSX, VNode } from "preact";
4
4
  import type { ChromeContext } from "../factory-context/index.js";
5
+ import type { DateFormatPattern } from "../format-date/index.js";
5
6
  import type { AssetRecord } from "../plugins/internal/asset-viewer/types.js";
6
7
  import type { Settings } from "../settings.js";
7
8
  export { ASSET_DETAILS_HIDDEN_ATTR, ASSET_DETAILS_PREPAINT_SCRIPT, ASSET_DETAILS_STORAGE_KEY, ASSET_PAGE_SCRIPT, } from "./script.js";
@@ -14,12 +15,13 @@ export declare function AssetEyebrow({ asset, badge }: {
14
15
  asset: AssetRecord;
15
16
  badge: string;
16
17
  }): VNode;
17
- export declare function AssetHeader({ asset, locale, badge, updatedLabel, linesLabel }: {
18
+ export declare function AssetHeader({ asset, locale, badge, updatedLabel, linesLabel, fullPattern }: {
18
19
  asset: AssetRecord;
19
20
  locale: string;
20
21
  badge: string;
21
22
  updatedLabel: string;
22
23
  linesLabel?: string;
24
+ fullPattern?: DateFormatPattern;
23
25
  }): VNode;
24
26
  export declare function AssetActions({ rawUrl, downloadLabel, openRawLabel, copyLabel, wrapLabel, code, bottom }: {
25
27
  rawUrl: string;
@@ -1,6 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
2
2
  import { BodyFootUtilArea } from "../body-foot-util/index.js";
3
- import { deriveBodyEndIslands } from "../chrome/derive.js";
3
+ import { deriveBodyEndIslands, deriveDateFormats } from "../chrome/derive.js";
4
4
  import { deriveComposeMetaTitle } from "../chrome/derive.js";
5
5
  import { derivePrimaryChromeSlots } from "../chrome/primary-slots.js";
6
6
  import { assertChromeContext } from "../chrome/assert-chrome-context.js";
@@ -44,7 +44,7 @@ function AssetEyebrow({ asset, badge }) {
44
44
  /* @__PURE__ */ jsx("span", { class: "rounded-full border border-muted px-hsp-sm py-vsp-3xs text-muted", children: kindLabel(asset) })
45
45
  ] });
46
46
  }
47
- function AssetHeader({ asset, locale, badge, updatedLabel, linesLabel }) {
47
+ function AssetHeader({ asset, locale, badge, updatedLabel, linesLabel, fullPattern }) {
48
48
  const facet = facetLabel(asset, linesLabel);
49
49
  return /* @__PURE__ */ jsxs("header", { children: [
50
50
  /* @__PURE__ */ jsx(AssetEyebrow, { asset, badge }),
@@ -56,7 +56,7 @@ function AssetHeader({ asset, locale, badge, updatedLabel, linesLabel }) {
56
56
  asset.updatedDate && /* @__PURE__ */ jsxs("span", { children: [
57
57
  updatedLabel,
58
58
  " ",
59
- formatDate(asset.updatedDate, locale)
59
+ formatDate(asset.updatedDate, locale, fullPattern)
60
60
  ] }),
61
61
  asset.author && /* @__PURE__ */ jsx("span", { children: asset.author })
62
62
  ] }),
@@ -202,6 +202,7 @@ function createAssetPageView(ctx) {
202
202
  assertChromeContext(ctx, "createAssetPageView");
203
203
  const settings = ctx.settings;
204
204
  const t = ctx.t;
205
+ const dateFormatsFor = deriveDateFormats(ctx);
205
206
  const composeMetaTitle = deriveComposeMetaTitle(ctx);
206
207
  const HeadWithDefaults = createHeadWithDefaults(ctx);
207
208
  const { Header: HeaderWithDefaults, Footer: FooterWithDefaults, Breadcrumb: BreadcrumbWithDefaults } = derivePrimaryChromeSlots(ctx);
@@ -269,7 +270,7 @@ function createAssetPageView(ctx) {
269
270
  " ",
270
271
  backLink.title
271
272
  ] }) }),
272
- /* @__PURE__ */ jsx(AssetHeader, { asset, locale, badge: t("asset.badge", locale), updatedLabel: t("doc.updated", locale), linesLabel }),
273
+ /* @__PURE__ */ jsx(AssetHeader, { asset, locale, badge: t("asset.badge", locale), updatedLabel: t("doc.updated", locale), linesLabel, fullPattern: dateFormatsFor(locale).full }),
273
274
  /* @__PURE__ */ jsx(AssetActions, { rawUrl, downloadLabel: t("asset.download", locale), openRawLabel: t("asset.openRaw", locale), copyLabel: t("asset.copy", locale), wrapLabel: t("asset.wrap", locale), code: !isMedia && asset.previewable && asset.sniffOk }),
274
275
  body,
275
276
  /* @__PURE__ */ jsx(AssetActions, { rawUrl, downloadLabel: t("asset.download", locale), openRawLabel: t("asset.openRaw", locale), copyLabel: t("asset.copy", locale), wrapLabel: t("asset.wrap", locale), bottom: true }),
@@ -70,20 +70,27 @@ function HomeIcon() {
70
70
  function Breadcrumb(props) {
71
71
  const items = props.items ?? (props.tree && props.currentId !== void 0 ? buildBreadcrumbItems(props.tree, props.currentId, props.homeHref ?? "/") : []);
72
72
  if (items.length === 0) return null;
73
- const nav = /* @__PURE__ */ jsx("nav", { class: "mb-vsp-md text-small", "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsx("ol", { class: "flex flex-wrap items-center gap-x-hsp-xs", children: items.map((item, i) => /* @__PURE__ */ jsxs("li", { class: "flex items-center gap-x-hsp-xs", children: [
74
- i > 0 && /* @__PURE__ */ jsx(ChevronIcon, {}),
75
- item.href ? /* @__PURE__ */ jsxs(
76
- "a",
77
- {
78
- href: item.href,
79
- class: "text-muted underline hover:text-fg flex items-center gap-x-hsp-2xs",
80
- children: [
81
- i === 0 && /* @__PURE__ */ jsx(HomeIcon, {}),
82
- /* @__PURE__ */ jsx(SmartLabel, { label: item.label })
83
- ]
84
- }
85
- ) : /* @__PURE__ */ jsx("span", { class: "text-fg", children: /* @__PURE__ */ jsx(SmartLabel, { label: item.label }) })
86
- ] }, `crumb-${i}`)) }) });
73
+ const nav = /* @__PURE__ */ jsx(
74
+ "nav",
75
+ {
76
+ class: `mb-vsp-md text-small${props.rightSlot !== void 0 ? " min-w-0 w-full" : ""}`,
77
+ "aria-label": "Breadcrumb",
78
+ children: /* @__PURE__ */ jsx("ol", { class: "flex flex-wrap items-center gap-x-hsp-xs", children: items.map((item, i) => /* @__PURE__ */ jsxs("li", { class: "min-w-0 flex items-center gap-x-hsp-xs", children: [
79
+ i > 0 && /* @__PURE__ */ jsx(ChevronIcon, {}),
80
+ item.href ? /* @__PURE__ */ jsxs(
81
+ "a",
82
+ {
83
+ href: item.href,
84
+ class: "text-muted underline hover:text-fg flex items-center gap-x-hsp-2xs",
85
+ children: [
86
+ i === 0 && /* @__PURE__ */ jsx(HomeIcon, {}),
87
+ /* @__PURE__ */ jsx(SmartLabel, { label: item.label })
88
+ ]
89
+ }
90
+ ) : /* @__PURE__ */ jsx("span", { class: "text-fg min-w-0 break-words", children: /* @__PURE__ */ jsx(SmartLabel, { label: item.label }) })
91
+ ] }, `crumb-${i}`)) })
92
+ }
93
+ );
87
94
  if (props.rightSlot === void 0) return nav;
88
95
  return /* @__PURE__ */ jsxs("div", { class: "mb-vsp-sm flex flex-col items-start gap-vsp-xs sm:flex-row sm:items-center sm:justify-between [&_nav]:mb-0", children: [
89
96
  nav,
@@ -2,7 +2,7 @@
2
2
  /** @jsxImportSource preact */
3
3
  import type { JSX, VNode } from "preact";
4
4
  import type { ChromeContext, FactoryComponent } from "../factory-context/index.js";
5
- import type { Settings } from "../settings.js";
5
+ import type { ResolvedDateFormats, Settings } from "../settings.js";
6
6
  import { type ColorScheme } from "../color-scheme-utils.js";
7
7
  /** Package-default color scheme used when `colorSchemes` is `null` or a key is
8
8
  * missing. Exported so `createChrome` keeps the identical fallback. Base roles
@@ -16,7 +16,29 @@ declare function DocHistoryStub(_props: {
16
16
  slug: string;
17
17
  locale?: string;
18
18
  basePath?: string;
19
+ displayLocale?: string;
20
+ dateFormats?: ResolvedDateFormats;
19
21
  }): VNode;
22
+ /**
23
+ * Derive the single settings -> resolved-roles seam for the `dateFormat`
24
+ * setting (#4075).
25
+ *
26
+ * `dateFormat` is per-role AND per-locale, so it can only collapse to a
27
+ * concrete set of patterns once the render locale is known — hence a
28
+ * `(locale) => ResolvedDateFormats` lookup rather than a plain value. Every
29
+ * chrome surface that renders a date resolves through THIS helper so the
30
+ * layering has exactly one implementation.
31
+ *
32
+ * It matters most for islands: `virtual:zudo-doc-route-context` is SSR-only,
33
+ * so an island has no ambient access to settings. The only channel is zfb's
34
+ * `<Island>` boundary, which JSON-stringifies the wrapper's props into
35
+ * `data-props`. Resolving here and passing the RESULT keeps the browser free
36
+ * of a second copy of the precedence rules.
37
+ *
38
+ * Results are memoized per locale: a page renders several dated surfaces and
39
+ * the roles are locale-invariant within one render.
40
+ */
41
+ export declare function deriveDateFormats(ctx: ChromeContext): (locale: string) => ResolvedDateFormats;
20
42
  /** Derive the meta-title composer from the context's site name. */
21
43
  export declare function deriveComposeMetaTitle(ctx: ChromeContext): (title: string) => string;
22
44
  /** Derive the single-scheme / light-dark CSS-property generators bound to the
@@ -31,6 +31,7 @@ import {
31
31
  getThemeDefaultMode as getThemeDefaultModeBase
32
32
  } from "../nav-data-prep/index.js";
33
33
  import { buildSidebarForSection } from "../sidebar-utils/index.js";
34
+ import { resolveDateFormats } from "../date-format-resolve/index.js";
34
35
  const GREY_RAMPS = {
35
36
  base: [
36
37
  "oklch(0.965 0.000 0)",
@@ -68,6 +69,17 @@ function DocHistoryStub(_props) {
68
69
  function IslandPassthrough(props) {
69
70
  return props.children ?? null;
70
71
  }
72
+ function deriveDateFormats(ctx) {
73
+ const setting = ctx.settings.dateFormat;
74
+ const cache = /* @__PURE__ */ new Map();
75
+ return (locale) => {
76
+ const cached = cache.get(locale);
77
+ if (cached) return cached;
78
+ const resolved = resolveDateFormats(setting, locale);
79
+ cache.set(locale, resolved);
80
+ return resolved;
81
+ };
82
+ }
71
83
  function deriveComposeMetaTitle(ctx) {
72
84
  return createComposeMetaTitle(ctx.settings.siteName);
73
85
  }
@@ -293,7 +305,8 @@ function deriveMdxComponents(ctx) {
293
305
  groupSatelliteNodes: ctx.groupSatelliteNodes,
294
306
  getCategoryOrder: ctx.getCategoryOrder,
295
307
  versionedDocsUrl: ctx.versionedDocsUrl,
296
- categoryIgnore: ctx.settings.siteTreeNavIgnore
308
+ categoryIgnore: ctx.settings.siteTreeNavIgnore,
309
+ dateFormat: ctx.settings.dateFormat
297
310
  });
298
311
  const NoteTrayIndexWrapper = createNoteTrayIndexWrapper({
299
312
  defaultLocale: ctx.defaultLocale,
@@ -312,7 +325,8 @@ function deriveMdxComponents(ctx) {
312
325
  locale === ctx.defaultLocale ? `/docs/tags/${encodeURIComponent(tag)}` : `/${locale}/docs/tags/${encodeURIComponent(tag)}`
313
326
  ),
314
327
  t: (key, locale) => ctx.t(key, locale),
315
- versionedDocsUrl: ctx.versionedDocsUrl
328
+ versionedDocsUrl: ctx.versionedDocsUrl,
329
+ dateFormat: ctx.settings.dateFormat
316
330
  });
317
331
  const assetComponentContext = {
318
332
  base: ctx.settings.base,
@@ -395,6 +409,7 @@ export {
395
409
  deriveBodyEndIslands,
396
410
  deriveColorSchemeGenerators,
397
411
  deriveComposeMetaTitle,
412
+ deriveDateFormats,
398
413
  deriveDocHistorySlot,
399
414
  deriveGetUnavailableVersions,
400
415
  deriveInlineVersionSwitcher,
@@ -4,6 +4,7 @@ import type { DocPageNavNode } from "./doc-page-shell/index.js";
4
4
  import type { HeadingItem } from "./toc/index.js";
5
5
  import type { ChromeHostBindings } from "./factory-context/index.js";
6
6
  import type { HeaderRightComponentRegistry } from "./header/types.js";
7
+ import type { ResolvedDateFormats } from "./settings.js";
7
8
  /**
8
9
  * Props the header passes to the `SearchWidget` slot — the 7 locale-aware
9
10
  * strings from `header-with-defaults`'s `<SearchWidget … />` render (via
@@ -36,12 +37,16 @@ export interface BodyEndIslandsSlotProps {
36
37
  * Props the chrome passes to the `DocHistory` island slot — matches
37
38
  * `DocHistoryProps` (`doc-history/index.tsx`) / `DocHistoryComponent`
38
39
  * (`doc-history-area`), rendered as
39
- * `<DocHistory slug=… locale=… basePath=… />`.
40
+ * `<DocHistory slug=… locale=… basePath=… displayLocale=… dateFormats=… />`.
40
41
  */
41
42
  export interface DocHistorySlotProps {
42
43
  slug: string;
43
44
  locale?: string;
44
45
  basePath?: string;
46
+ /** Display locale for revision dates — NOT the storage-path `locale` (#4073). */
47
+ displayLocale?: string;
48
+ /** Per-role date patterns resolved for `displayLocale` at SSR (#4075). */
49
+ dateFormats?: ResolvedDateFormats;
45
50
  }
46
51
  /**
47
52
  * Props the chrome passes to the `DesignTokenPanelBootstrap` slot — NONE. It is
package/dist/compiled.css CHANGED
@@ -2342,6 +2342,29 @@
2342
2342
  --radius-DEFAULT: 0.25rem;
2343
2343
  --radius-lg: 0.5rem;
2344
2344
  --radius-full: 9999px;
2345
+ --color-zd-bg: var(--zd-bg);
2346
+ --color-zd-fg: var(--zd-fg);
2347
+ --color-zd-sel-bg: var(--zd-selection-bg);
2348
+ --color-zd-sel-fg: var(--zd-selection-fg);
2349
+ --color-zd-surface: var(--zd-surface);
2350
+ --color-zd-muted: var(--zd-muted);
2351
+ --color-zd-accent: var(--zd-accent);
2352
+ --color-zd-accent-hover: var(--zd-accent-hover);
2353
+ --color-zd-code-bg: var(--zd-code-bg);
2354
+ --color-zd-code-fg: var(--zd-code-fg);
2355
+ --color-zd-success: var(--zd-success);
2356
+ --color-zd-danger: var(--zd-danger);
2357
+ --color-zd-warning: var(--zd-warning);
2358
+ --color-zd-info: var(--zd-info);
2359
+ --color-zd-overlay: #000;
2360
+ --color-zd-image-overlay-bg: var(--zd-image-overlay-bg);
2361
+ --color-zd-image-overlay-fg: var(--zd-image-overlay-fg);
2362
+ --color-zd-chat-user-bg: var(--zd-chat-user-bg);
2363
+ --color-zd-chat-user-text: var(--zd-chat-user-text);
2364
+ --color-zd-chat-assistant-bg: var(--zd-chat-assistant-bg);
2365
+ --color-zd-chat-assistant-text: var(--zd-chat-assistant-text);
2366
+ --color-zd-matched-keyword-bg: var(--zd-matched-keyword-bg);
2367
+ --color-zd-matched-keyword-fg: var(--zd-matched-keyword-fg);
2345
2368
  --z-index-content: 0;
2346
2369
  --z-index-local-1: 1;
2347
2370
  --z-index-local-2: 2;
package/dist/config.d.ts CHANGED
@@ -67,7 +67,7 @@ import type { ZfbConfig, BundleConfig } from "@takazudo/zfb/config";
67
67
  import type { ZodType } from "zod";
68
68
  import type { DirectiveVocabulary, PresetTranslations, PresetTagVocabularyEntry } from "./preset.js";
69
69
  import type { ColorScheme } from "./color-scheme-utils.js";
70
- import type { Settings, ColorModeConfig, LocaleConfig, MetaTagsConfig, SiteHeadConfig, TagPlacement, TagGovernanceMode, VersionConfig, FooterConfig, HeaderNavItem, HeaderRightItem, HomeConfig, AssetViewerIndexingConfig, FrontmatterPreviewConfig, BodyFootUtilAreaConfig, HtmlPreviewConfig, FaviconConfig } from "./settings.js";
70
+ import type { Settings, ColorModeConfig, LocaleConfig, MetaTagsConfig, SiteHeadConfig, TagPlacement, TagGovernanceMode, VersionConfig, FooterConfig, HeaderNavItem, HeaderRightItem, HomeConfig, AssetViewerIndexingConfig, FrontmatterPreviewConfig, BodyFootUtilAreaConfig, HtmlPreviewConfig, FaviconConfig, DateFormatSetting } from "./settings.js";
71
71
  /** The `settings.claudeResources` block (or `false` when disabled). */
72
72
  type ClaudeResourcesConfig = {
73
73
  claudeDir: string;
@@ -174,6 +174,18 @@ export interface ZudoDocConfig {
174
174
  * @default "getting-started"
175
175
  */
176
176
  entryDocSlug?: string;
177
+ /**
178
+ * Date-display format. `"locale"` keeps today's `Intl`-based rendering; a
179
+ * token pattern string (e.g. `"YYYY-MM-DD"`) sets the `full` role only; a
180
+ * `DateFormatConfig` object sets roles (`full`/`monthDay`/`year`/
181
+ * `yearMonth`/`numericMonthDay`) — and per-locale overrides — individually.
182
+ * This is a top-level field, so a supplied object REPLACES the default
183
+ * wholesale (shallow merge, not deep-merged) — see `zudoDoc()`'s merge note
184
+ * above; harmless here since the resolver fills every omitted role with
185
+ * `"locale"`.
186
+ * @default "locale"
187
+ */
188
+ dateFormat?: DateFormatSetting;
177
189
  /**
178
190
  * Default locale code (unprefixed routes).
179
191
  * @default "en"
package/dist/config.js CHANGED
@@ -50,6 +50,7 @@ const DEFAULT_SETTINGS = {
50
50
  minifyHtml: true,
51
51
  docsDir: "src/content/docs",
52
52
  entryDocSlug: "getting-started",
53
+ dateFormat: "locale",
53
54
  defaultLocale: "en",
54
55
  locales: {},
55
56
  mermaid: true,
@@ -0,0 +1,10 @@
1
+ import type { DateFormatSetting, ResolvedDateFormats } from "../settings.js";
2
+ /**
3
+ * Resolve a `dateFormat` setting into every role's pattern for one locale.
4
+ *
5
+ * Precedence per role: `locales[locale][role]` → top-level `[role]` →
6
+ * `"locale"`. `undefined` input (the setting is optional) resolves to every
7
+ * role defaulting to `"locale"`. A bare pattern string normalises to `{ full:
8
+ * <pattern> }` before the same layering applies.
9
+ */
10
+ export declare function resolveDateFormats(setting: DateFormatSetting | undefined, locale: string): ResolvedDateFormats;
@@ -0,0 +1,22 @@
1
+ const DATE_FORMAT_ROLE_PRESENCE = {
2
+ full: true,
3
+ monthDay: true,
4
+ year: true,
5
+ yearMonth: true,
6
+ numericMonthDay: true
7
+ };
8
+ const DATE_FORMAT_ROLE_KEYS = Object.keys(
9
+ DATE_FORMAT_ROLE_PRESENCE
10
+ );
11
+ function resolveDateFormats(setting, locale) {
12
+ const topLevelRoles = setting === void 0 ? {} : typeof setting === "string" ? { full: setting } : setting;
13
+ const localeRoles = setting !== void 0 && typeof setting !== "string" ? setting.locales?.[locale] : void 0;
14
+ const resolved = {};
15
+ for (const role of DATE_FORMAT_ROLE_KEYS) {
16
+ resolved[role] = localeRoles?.[role] ?? topLevelRoles[role] ?? "locale";
17
+ }
18
+ return resolved;
19
+ }
20
+ export {
21
+ resolveDateFormats
22
+ };
@@ -3,6 +3,7 @@ import { FrontmatterPreview } from "../metainfo/index.js";
3
3
  import { createDocMetainfoArea } from "../doc-metainfo-area/index.js";
4
4
  import { createDocTagsArea } from "../doc-tags-area/index.js";
5
5
  import { assertChromeContext } from "../chrome/assert-chrome-context.js";
6
+ import { deriveDateFormats } from "../chrome/derive.js";
6
7
  import { formatDate } from "../format-date/index.js";
7
8
  function createDocContentHeader(ctx) {
8
9
  assertChromeContext(ctx, "createDocContentHeader");
@@ -12,6 +13,7 @@ function createDocContentHeader(ctx) {
12
13
  const docContentHeaderExtras = ctx.hostBindings.docContentHeaderExtras;
13
14
  const DocMetainfoArea = createDocMetainfoArea(ctx);
14
15
  const DocTagsArea = createDocTagsArea(ctx);
16
+ const dateFormatsFor = deriveDateFormats(ctx);
15
17
  function DocContentHeader({
16
18
  entry,
17
19
  slug,
@@ -29,9 +31,9 @@ function createDocContentHeader(ctx) {
29
31
  version
30
32
  }),
31
33
  (entry.data.date || entry.data.updated) && /* @__PURE__ */ jsxs("p", { class: "text-caption text-muted mb-vsp-md", "data-doc-date": true, children: [
32
- entry.data.date ? formatDate(entry.data.date, locale) : null,
34
+ entry.data.date ? formatDate(entry.data.date, locale, dateFormatsFor(locale).full) : null,
33
35
  entry.data.date && entry.data.updated ? " \xB7 " : null,
34
- entry.data.updated ? `${t("doc.updated", locale)} ${formatDate(entry.data.updated, locale)}` : null
36
+ entry.data.updated ? `${t("doc.updated", locale)} ${formatDate(entry.data.updated, locale, dateFormatsFor(locale).full)}` : null
35
37
  ] }),
36
38
  !version && /* @__PURE__ */ jsx(DocMetainfoArea, { slug, locale, isFallback }),
37
39
  !version && /* @__PURE__ */ jsx(DocTagsArea, { slug, locale, tags: entry.data.tags }),
@@ -1,9 +1,25 @@
1
+ import type { ResolvedDateFormats } from "../settings.js";
1
2
  interface DocHistoryProps {
2
3
  slug: string;
3
4
  locale?: string;
5
+ /**
6
+ * Display locale for revision dates, e.g. "en", "ja". Distinct from
7
+ * `locale` above, which is a storage-path parameter used only to build
8
+ * the fetch URL (see doc-history-area's `effectiveHistoryLocale` /
9
+ * "omitted for the default locale" comment — do not conflate the two).
10
+ * Populated from the page locale by doc-history-area (#4073).
11
+ */
12
+ displayLocale?: string;
13
+ /**
14
+ * Per-role date patterns already resolved for `displayLocale` by
15
+ * `doc-history-area`. Serialized into the island's `data-props` — this
16
+ * island has no ambient access to settings. Optional and absent-safe:
17
+ * omitted means every role behaves as `"locale"` (#4075).
18
+ */
19
+ dateFormats?: ResolvedDateFormats;
4
20
  basePath?: string;
5
21
  }
6
- export declare function DocHistory({ slug, locale, basePath }: DocHistoryProps): import("preact").JSX.Element;
22
+ export declare function DocHistory({ slug, locale, basePath, displayLocale, dateFormats, }: DocHistoryProps): import("preact").JSX.Element;
7
23
  export declare namespace DocHistory {
8
24
  var displayName: string;
9
25
  }
@@ -5,6 +5,7 @@ import { SmartBreak } from "../smart-break/index.js";
5
5
  import { History, Close, ArrowLeft } from "../icons/index.js";
6
6
  import { AFTER_NAVIGATE_EVENT } from "../transitions/index.js";
7
7
  import { useModalDialog } from "../use-modal-dialog/index.js";
8
+ import { formatDate } from "../format-date/index.js";
8
9
  function Spinner() {
9
10
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-vsp-xl", children: /* @__PURE__ */ jsx(
10
11
  "span",
@@ -176,7 +177,9 @@ function DiffViewer({
176
177
  }
177
178
  function RevisionList({
178
179
  entries,
179
- onSelectDiff
180
+ onSelectDiff,
181
+ displayLocale,
182
+ dateFormats
180
183
  }) {
181
184
  const [selectedA, setSelectedA] = useState(1);
182
185
  const [selectedB, setSelectedB] = useState(0);
@@ -213,7 +216,11 @@ function RevisionList({
213
216
  /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto", children: entries.map((entry, idx) => {
214
217
  const isA = selectedA === idx;
215
218
  const isB = selectedB === idx;
216
- const dateStr = formatDate(entry.date);
219
+ const dateStr = formatDate(
220
+ entry.date,
221
+ displayLocale ?? "en",
222
+ dateFormats?.full
223
+ );
217
224
  return /* @__PURE__ */ jsx(
218
225
  "div",
219
226
  {
@@ -256,16 +263,13 @@ function RevisionList({
256
263
  }) })
257
264
  ] });
258
265
  }
259
- function formatDate(dateStr) {
260
- const d = new Date(dateStr);
261
- if (isNaN(d.getTime())) return dateStr;
262
- return d.toLocaleDateString(void 0, {
263
- year: "numeric",
264
- month: "short",
265
- day: "numeric"
266
- });
267
- }
268
- function DocHistory({ slug, locale, basePath = "/" }) {
266
+ function DocHistory({
267
+ slug,
268
+ locale,
269
+ basePath = "/",
270
+ displayLocale,
271
+ dateFormats
272
+ }) {
269
273
  const [view, setView] = useState("closed");
270
274
  const [data, setData] = useState(null);
271
275
  const [loading, setLoading] = useState(false);
@@ -380,7 +384,9 @@ function DocHistory({ slug, locale, basePath = "/" }) {
380
384
  RevisionList,
381
385
  {
382
386
  entries: data.entries,
383
- onSelectDiff: handleSelectDiff
387
+ onSelectDiff: handleSelectDiff,
388
+ displayLocale,
389
+ dateFormats
384
390
  }
385
391
  )
386
392
  }
@@ -2,7 +2,7 @@
2
2
  /** @jsxImportSource preact */
3
3
  import type { VNode } from "preact";
4
4
  import type { ChromeContext } from "../factory-context/index.js";
5
- import type { Settings } from "../settings.js";
5
+ import type { ResolvedDateFormats, Settings } from "../settings.js";
6
6
  /** Per-entry metadata shape from the doc-history manifest. */
7
7
  export interface DocHistoryMetaEntry {
8
8
  author: string;
@@ -28,6 +28,8 @@ export type DocHistoryComponent = (props: {
28
28
  slug: string;
29
29
  locale?: string;
30
30
  basePath?: string;
31
+ displayLocale?: string;
32
+ dateFormats?: ResolvedDateFormats;
31
33
  }) => VNode;
32
34
  export interface DocHistoryAreaProps {
33
35
  /** Page slug, e.g. "getting-started/intro". */
@@ -4,7 +4,7 @@ import { compileExclude } from "../doc-history-exclude/index.js";
4
4
  import { BodyFootUtilArea } from "../body-foot-util/index.js";
5
5
  import { toHistorySlug } from "../slug/index.js";
6
6
  import { buildGitHubSourceUrl as buildGitHubSourceUrlBase } from "../github-helpers/index.js";
7
- import { deriveDocHistorySlot } from "../chrome/derive.js";
7
+ import { deriveDateFormats, deriveDocHistorySlot } from "../chrome/derive.js";
8
8
  import { assertChromeContext } from "../chrome/assert-chrome-context.js";
9
9
  function createDocHistoryArea(ctx) {
10
10
  assertChromeContext(ctx, "createDocHistoryArea");
@@ -19,6 +19,7 @@ function createDocHistoryArea(ctx) {
19
19
  entryId
20
20
  );
21
21
  const DocHistory = deriveDocHistorySlot(ctx);
22
+ const dateFormatsFor = deriveDateFormats(ctx);
22
23
  DocHistory.displayName = "DocHistory";
23
24
  function DocHistoryArea({
24
25
  slug,
@@ -39,6 +40,8 @@ function createDocHistoryArea(ctx) {
39
40
  const historyLabel = t("doc.history", locale);
40
41
  const docHistoryLocale = effectiveHistoryLocale === defaultLocale ? void 0 : effectiveHistoryLocale;
41
42
  const docHistoryBasePath = settings.base ?? "/";
43
+ const docHistoryDisplayLocale = locale;
44
+ const docHistoryDateFormats = dateFormatsFor(docHistoryDisplayLocale);
42
45
  const author = meta?.author;
43
46
  const createdDate = meta?.createdDate;
44
47
  const updatedDate = meta?.updatedDate;
@@ -61,7 +64,9 @@ function createDocHistoryArea(ctx) {
61
64
  {
62
65
  slug: historySlug,
63
66
  locale: docHistoryLocale,
64
- basePath: docHistoryBasePath
67
+ basePath: docHistoryBasePath,
68
+ displayLocale: docHistoryDisplayLocale,
69
+ dateFormats: docHistoryDateFormats
65
70
  }
66
71
  )
67
72
  });
@@ -2,6 +2,7 @@ import { jsx } from "preact/jsx-runtime";
2
2
  import { DocMetainfo } from "../metainfo/index.js";
3
3
  import { toHistorySlug } from "../slug/index.js";
4
4
  import { assertChromeContext } from "../chrome/assert-chrome-context.js";
5
+ import { deriveDateFormats } from "../chrome/derive.js";
5
6
  import { formatDate } from "../format-date/index.js";
6
7
  function createDocMetainfoArea(ctx) {
7
8
  assertChromeContext(ctx, "createDocMetainfoArea");
@@ -9,6 +10,7 @@ function createDocMetainfoArea(ctx) {
9
10
  const defaultLocale = ctx.defaultLocale;
10
11
  const docHistoryMeta = ctx.hostBindings.docHistoryMeta ?? {};
11
12
  const t = ctx.t;
13
+ const dateFormatsFor = deriveDateFormats(ctx);
12
14
  function DocMetainfoArea({ slug, locale, isFallback }) {
13
15
  if (!settings.docMetainfo) return null;
14
16
  const historySlug = toHistorySlug(slug);
@@ -19,8 +21,8 @@ function createDocMetainfoArea(ctx) {
19
21
  return /* @__PURE__ */ jsx(
20
22
  DocMetainfo,
21
23
  {
22
- createdAt: meta.createdDate ? formatDate(meta.createdDate, locale) : null,
23
- updatedAt: meta.updatedDate ? formatDate(meta.updatedDate, locale) : null,
24
+ createdAt: meta.createdDate ? formatDate(meta.createdDate, locale, dateFormatsFor(locale).full) : null,
25
+ updatedAt: meta.updatedDate ? formatDate(meta.updatedDate, locale, dateFormatsFor(locale).full) : null,
24
26
  author: meta.author || null,
25
27
  createdLabel: t("doc.created", locale),
26
28
  updatedLabel: t("doc.updated", locale)
@@ -1,10 +1,12 @@
1
1
  import { jsx, jsxs } from "preact/jsx-runtime";
2
2
  import { ChevronLeft, ChevronRight } from "../icons/index.js";
3
3
  import { assertChromeContext } from "../chrome/assert-chrome-context.js";
4
+ import { deriveDateFormats } from "../chrome/derive.js";
4
5
  import { formatDate } from "../format-date/index.js";
5
6
  function createDocPager(ctx) {
6
7
  assertChromeContext(ctx, "createDocPager");
7
8
  const t = ctx.t;
9
+ const dateFormatsFor = deriveDateFormats(ctx);
8
10
  function DocPager({ prev, next, locale }) {
9
11
  return /* @__PURE__ */ jsxs("nav", { class: "mt-vsp-2xl grid grid-cols-2 gap-hsp-xl", "data-doc-pager": true, children: [
10
12
  prev ? /* @__PURE__ */ jsxs(
@@ -18,7 +20,7 @@ function createDocPager(ctx) {
18
20
  /* @__PURE__ */ jsx("span", { class: "no-underline", children: t("nav.previous", locale) })
19
21
  ] }),
20
22
  /* @__PURE__ */ jsx("p", { class: "text-small font-semibold underline group-hover:text-accent", children: prev.label }),
21
- prev.date && /* @__PURE__ */ jsx("p", { class: "text-caption text-muted", children: formatDate(prev.date, locale) })
23
+ prev.date && /* @__PURE__ */ jsx("p", { class: "text-caption text-muted", children: formatDate(prev.date, locale, dateFormatsFor(locale).full) })
22
24
  ]
23
25
  }
24
26
  ) : /* @__PURE__ */ jsx("div", {}),
@@ -33,7 +35,7 @@ function createDocPager(ctx) {
33
35
  /* @__PURE__ */ jsx(ChevronRight, { className: "h-[1.125rem] w-[1.125rem]" })
34
36
  ] }),
35
37
  /* @__PURE__ */ jsx("p", { class: "text-small font-semibold underline group-hover:text-accent", children: next.label }),
36
- next.date && /* @__PURE__ */ jsx("p", { class: "text-caption text-muted", children: formatDate(next.date, locale) })
38
+ next.date && /* @__PURE__ */ jsx("p", { class: "text-caption text-muted", children: formatDate(next.date, locale, dateFormatsFor(locale).full) })
37
39
  ]
38
40
  }
39
41
  ) : /* @__PURE__ */ jsx("div", {})
@@ -63,9 +63,9 @@ export declare function buildDocsSchema(opts?: BuildDocsSchemaOptions): z.ZodObj
63
63
  }>>;
64
64
  note_tray_dated: z.ZodOptional<z.ZodBoolean>;
65
65
  note_tray_sidebar: z.ZodOptional<z.ZodEnum<{
66
- index: "index";
67
66
  year: "year";
68
67
  month: "month";
68
+ index: "index";
69
69
  }>>;
70
70
  date: z.ZodOptional<z.ZodString>;
71
71
  updated: z.ZodOptional<z.ZodString>;
@@ -178,7 +178,7 @@ Customization guide: ${CUSTOMIZING_GUIDE_URL}
178
178
  return true;
179
179
  }
180
180
  async function rewireImports(dir) {
181
- const re = /(from\s+)(["'])\.\.\/([\w-]+)\/[^"']+\2/g;
181
+ const re = /(from\s+)(["'])\.\.\/([\w-]+)(?:\/[^"']+|\.[cm]?[jt]sx?)\2/g;
182
182
  const entries = await fs.readdir(dir, { withFileTypes: true });
183
183
  for (const entry of entries) {
184
184
  if (entry.isDirectory()) continue;
@@ -1,4 +1,4 @@
1
- import type { Settings } from "../settings.js";
1
+ import type { LocaleConfig, Settings } from "../settings.js";
2
2
  import type { AssetManifest, ColorScheme, ThemePackRegistry } from "../route-context-payload/types.js";
3
3
  import type { UrlHelpers } from "../url-helpers/index.js";
4
4
  import type { NavSourceDocsAPI } from "../nav-source-docs/index.js";
@@ -130,11 +130,8 @@ export interface RouteContext<S = Settings> extends UrlHelpers, NavSourceDocsAPI
130
130
  defaultLocale: string;
131
131
  /** All supported locale codes, default first. */
132
132
  locales: readonly string[];
133
- /** Per-locale `{ label, dir }` config lookup. */
134
- getLocaleConfig(locale: string): {
135
- label: string;
136
- dir: string;
137
- } | undefined;
133
+ /** Per-locale configuration lookup, including an optional home description. */
134
+ getLocaleConfig(locale: string): LocaleConfig | undefined;
138
135
  /** Display label for a locale. */
139
136
  getLocaleLabel(locale: string): string;
140
137
  /**