@takazudo/zudo-doc 5.19.1 → 5.21.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 (95) hide show
  1. package/CHANGELOG.md +34 -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 +95 -0
  9. package/dist/config.d.ts +14 -2
  10. package/dist/config.js +2 -1
  11. package/dist/content.css +55 -0
  12. package/dist/date-format-resolve/index.d.ts +10 -0
  13. package/dist/date-format-resolve/index.js +22 -0
  14. package/dist/doc-content-header/index.js +4 -2
  15. package/dist/doc-history/index.d.ts +17 -1
  16. package/dist/doc-history/index.js +19 -13
  17. package/dist/doc-history-area/index.d.ts +3 -1
  18. package/dist/doc-history-area/index.js +7 -2
  19. package/dist/doc-metainfo-area/index.js +4 -2
  20. package/dist/doc-pager/index.js +4 -2
  21. package/dist/docs-schema/index.d.ts +1 -1
  22. package/dist/eject/index.js +1 -1
  23. package/dist/factory-context/index.d.ts +5 -6
  24. package/dist/format-date/index.d.ts +15 -6
  25. package/dist/format-date/index.js +79 -15
  26. package/dist/header-with-defaults/index.js +5 -1
  27. package/dist/home-intro/index.d.ts +10 -0
  28. package/dist/home-intro/index.js +20 -0
  29. package/dist/home-intro/prepare.d.ts +9 -0
  30. package/dist/home-intro/prepare.js +93 -0
  31. package/dist/home-intro/resolve.d.ts +5 -0
  32. package/dist/home-intro/resolve.js +10 -0
  33. package/dist/home-intro/types.d.ts +10 -0
  34. package/dist/home-intro/types.js +0 -0
  35. package/dist/home-page/index.js +43 -21
  36. package/dist/i18n-defaults/index.js +2 -0
  37. package/dist/nav-indexing/note-tray-index-parts/card-list.js +18 -5
  38. package/dist/nav-indexing/note-tray-index-parts/date-line.d.ts +3 -1
  39. package/dist/nav-indexing/note-tray-index-parts/date-line.js +4 -3
  40. package/dist/nav-indexing/note-tray-index-parts/index-list.js +9 -1
  41. package/dist/nav-indexing/note-tray-index-parts/timeline.js +2 -2
  42. package/dist/nav-indexing/note-tray-index.d.ts +9 -0
  43. package/dist/note-tray-index/index.d.ts +9 -0
  44. package/dist/note-tray-index/index.js +3 -1
  45. package/dist/note-tray-model/index.d.ts +13 -3
  46. package/dist/note-tray-model/index.js +9 -2
  47. package/dist/plugins/internal/claude-resources/generate.js +3 -2
  48. package/dist/plugins/internal/codex-resources/overview.js +3 -2
  49. package/dist/plugins/internal/resource-docs-shared/fs.d.ts +1 -1
  50. package/dist/plugins/internal/resource-docs-shared/fs.js +3 -2
  51. package/dist/plugins/routes.js +4 -1
  52. package/dist/preset.d.ts +5 -1
  53. package/dist/preset.js +3 -1
  54. package/dist/route-context/index.js +1 -0
  55. package/dist/route-context-payload/index.d.ts +2 -0
  56. package/dist/route-context-payload/index.js +2 -1
  57. package/dist/route-context-payload/types.d.ts +2 -0
  58. package/dist/routes/_context.d.ts +1 -4
  59. package/dist/safelist.css +1 -1
  60. package/dist/settings.d.ts +45 -0
  61. package/dist/sidebar-toggle-island/index.d.ts +16 -1
  62. package/dist/sidebar-toggle-island/index.js +6 -2
  63. package/dist/sidebar-tree-island/index.d.ts +15 -1
  64. package/dist/sidebar-tree-island/index.js +19 -8
  65. package/dist/sidebar-with-defaults/index.js +5 -2
  66. package/dist/site-tree-nav/index.d.ts +13 -0
  67. package/dist/site-tree-nav/index.js +6 -2
  68. package/dist/site-tree-nav-island/index.d.ts +9 -1
  69. package/dist/site-tree-nav-island/index.js +19 -3
  70. package/dist/theme-no-reset.css +379 -0
  71. package/dist/theme-packs/academia/pack.css +12 -2
  72. package/dist/theme-packs/blueprint/pack.css +7 -1
  73. package/dist/theme-packs/broadsheet/pack.css +7 -1
  74. package/dist/theme-packs/brutalist/pack.css +21 -3
  75. package/dist/theme-packs/drift/pack.css +5 -1
  76. package/dist/theme-packs/foundry/pack.css +13 -2
  77. package/dist/theme-packs/hearth/pack.css +7 -1
  78. package/dist/theme-packs/ledger/pack.css +12 -2
  79. package/dist/theme-packs/observatory/pack.css +5 -1
  80. package/dist/theme-packs/riso/pack.css +9 -6
  81. package/dist/theme-packs/sakura/pack.css +6 -2
  82. package/dist/theme-packs/sumi/pack.css +7 -1
  83. package/dist/theme-packs/swissgrid/pack.css +7 -1
  84. package/dist/theme-packs/tidepool/pack.css +8 -1
  85. package/dist/theme-packs/washi/pack.css +5 -2
  86. package/dist/theme.css +60 -0
  87. package/eject/breadcrumb/breadcrumb.tsx +8 -3
  88. package/eject/doc-history/index.tsx +41 -16
  89. package/eject/doc-pager/index.tsx +12 -2
  90. package/eject/sidebar-toggle-island/index.tsx +19 -0
  91. package/eject/sidebar-tree-island/index.tsx +40 -4
  92. package/eject/site-tree-nav-island/index.tsx +31 -5
  93. package/package.json +13 -3
  94. package/routes-src/_virtual.d.ts +3 -1
  95. package/virtual-modules.d.ts +3 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,40 @@ 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.21.0] - 2026-09-09
12
+
13
+ ### Features
14
+
15
+ - Added compact Markdown introductions to the home pages. `home.introMarkdown` renders serializable Markdown below the home identity on `/`, and each non-default locale can override it with `LocaleConfig.introMarkdown`. Empty values keep the previous layout, so existing sites are unaffected. (`77ed96b3f`, `8e10ecbc1`)
16
+ - Added `home.sitemapHeading` and `LocaleConfig.sitemapHeading` to override the home sitemap section title. A blank value restores the translated package default. (`77ed96b3f`)
17
+
18
+ ### Bug Fixes
19
+
20
+ - Rendered the home sitemap and tag section headings through one shared component, so both align with the full-width content band instead of the sitemap heading sitting on the narrower inner column. (`849e04a57`)
21
+
22
+ ## [5.20.0] - 2026-09-09
23
+
24
+ ### Features
25
+
26
+ - 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`)
27
+ - 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`)
28
+ - Added namespaced `--color-zd-*` aliases and `theme-no-reset.css`, which preserves host color namespaces when stylesheet import order cannot be controlled. (`f3aae4442`, `ecdc23800`)
29
+
30
+ ### Bug Fixes
31
+
32
+ - 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`)
33
+ - 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`)
34
+ - Let home hero links wrap without squeezing Japanese labels, and wrapped long terminal breadcrumb titles while preserving the version pill's spacing. (`be7b3de3b`, `173fdb77f`)
35
+ - Corrected theme-pack navigation hover contrast, including active items and text inside brutalist link cards. (`6e561da45`, `791ad2c92`, `c83b8bda5`)
36
+
37
+ ### Other Changes
38
+
39
+ - 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`)
40
+
7
41
  ## [5.19.1] - 2026-09-08
8
42
 
9
43
  ### 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
@@ -2155,6 +2155,11 @@
2155
2155
  max-width: 52.5rem;
2156
2156
  }
2157
2157
  }
2158
+ .lg\:flex-1 {
2159
+ @media (width >= 1024px) {
2160
+ flex: 1;
2161
+ }
2162
+ }
2158
2163
  .lg\:grid-cols-3 {
2159
2164
  @media (width >= 1024px) {
2160
2165
  grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -2342,6 +2347,29 @@
2342
2347
  --radius-DEFAULT: 0.25rem;
2343
2348
  --radius-lg: 0.5rem;
2344
2349
  --radius-full: 9999px;
2350
+ --color-zd-bg: var(--zd-bg);
2351
+ --color-zd-fg: var(--zd-fg);
2352
+ --color-zd-sel-bg: var(--zd-selection-bg);
2353
+ --color-zd-sel-fg: var(--zd-selection-fg);
2354
+ --color-zd-surface: var(--zd-surface);
2355
+ --color-zd-muted: var(--zd-muted);
2356
+ --color-zd-accent: var(--zd-accent);
2357
+ --color-zd-accent-hover: var(--zd-accent-hover);
2358
+ --color-zd-code-bg: var(--zd-code-bg);
2359
+ --color-zd-code-fg: var(--zd-code-fg);
2360
+ --color-zd-success: var(--zd-success);
2361
+ --color-zd-danger: var(--zd-danger);
2362
+ --color-zd-warning: var(--zd-warning);
2363
+ --color-zd-info: var(--zd-info);
2364
+ --color-zd-overlay: #000;
2365
+ --color-zd-image-overlay-bg: var(--zd-image-overlay-bg);
2366
+ --color-zd-image-overlay-fg: var(--zd-image-overlay-fg);
2367
+ --color-zd-chat-user-bg: var(--zd-chat-user-bg);
2368
+ --color-zd-chat-user-text: var(--zd-chat-user-text);
2369
+ --color-zd-chat-assistant-bg: var(--zd-chat-assistant-bg);
2370
+ --color-zd-chat-assistant-text: var(--zd-chat-assistant-text);
2371
+ --color-zd-matched-keyword-bg: var(--zd-matched-keyword-bg);
2372
+ --color-zd-matched-keyword-fg: var(--zd-matched-keyword-fg);
2345
2373
  --z-index-content: 0;
2346
2374
  --z-index-local-1: 1;
2347
2375
  --z-index-local-2: 2;
@@ -2704,6 +2732,73 @@ a.text-accent.underline {
2704
2732
  border-radius: var(--zdc-admonition-radius, 0 var(--radius-DEFAULT) var(--radius-DEFAULT) 0);
2705
2733
  border-left-width: var(--zdc-admonition-border-width, 4px);
2706
2734
  }
2735
+ .zd-home-inner {
2736
+ inline-size: 100%;
2737
+ max-inline-size: 60rem;
2738
+ min-inline-size: 0;
2739
+ margin-inline: auto;
2740
+ overflow-wrap: anywhere;
2741
+ }
2742
+ .zd-home-copy, .zd-home-links {
2743
+ min-inline-size: 0;
2744
+ }
2745
+ .zd-home-links {
2746
+ overflow-wrap: anywhere;
2747
+ }
2748
+ .zd-home-rule {
2749
+ inline-size: 100%;
2750
+ margin-block: var(--spacing-vsp-xl);
2751
+ margin-inline: 0;
2752
+ border: 0;
2753
+ border-block-start: 1px solid var(--color-muted);
2754
+ }
2755
+ .zd-compact-prose {
2756
+ min-inline-size: 0;
2757
+ text-align: left;
2758
+ font-size: 1rem;
2759
+ line-height: 1.75;
2760
+ overflow-wrap: anywhere;
2761
+ }
2762
+ .zd-compact-prose > :where(* + *) {
2763
+ margin-top: var(--flow-space, 1rem);
2764
+ }
2765
+ .zd-compact-prose :where(h2, h3, h4, h5, h6) {
2766
+ font-weight: var(--font-weight-bold, 700);
2767
+ line-height: 1.4;
2768
+ padding: 0;
2769
+ border: 0;
2770
+ background: none;
2771
+ }
2772
+ .zd-compact-prose :where(h2) {
2773
+ font-size: 1.3rem;
2774
+ --flow-space: 1.75rem;
2775
+ }
2776
+ .zd-compact-prose :where(h3) {
2777
+ font-size: 1.125rem;
2778
+ --flow-space: 1.5rem;
2779
+ }
2780
+ .zd-compact-prose :where(h4) {
2781
+ font-size: 1rem;
2782
+ --flow-space: 1.25rem;
2783
+ }
2784
+ .zd-compact-prose :where(h5, h6) {
2785
+ font-size: .9375rem;
2786
+ --flow-space: 1.25rem;
2787
+ }
2788
+ .zd-compact-prose > :where(h2, h3, h4, h5, h6) + * {
2789
+ margin-top: .5rem;
2790
+ }
2791
+ .zd-compact-prose > :first-child {
2792
+ margin-top: 0;
2793
+ }
2794
+ .zd-compact-prose :where(pre) {
2795
+ max-inline-size: 100%;
2796
+ overflow-x: auto;
2797
+ }
2798
+ .zd-compact-prose :where(img) {
2799
+ max-inline-size: 100%;
2800
+ block-size: auto;
2801
+ }
2707
2802
  .page-loading-overlay {
2708
2803
  position: fixed;
2709
2804
  inset: 0;
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;
@@ -143,7 +143,7 @@ export interface ZudoDocConfig {
143
143
  /**
144
144
  * Package-owned home-page layout. Set `wide: true` to let the category grid
145
145
  * fill most of the viewport on both `/` and locale home routes.
146
- * @default { wide: false }
146
+ * @default { wide: false, introMarkdown: "", sitemapHeading: "" }
147
147
  */
148
148
  home?: HomeConfig;
149
149
  /**
@@ -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
@@ -45,11 +45,12 @@ const DEFAULT_SETTINGS = {
45
45
  favicon: void 0,
46
46
  base: "/",
47
47
  trailingSlash: false,
48
- home: { wide: false },
48
+ home: { wide: false, introMarkdown: "", sitemapHeading: "" },
49
49
  siteTreeNavIgnore: [],
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,
package/dist/content.css CHANGED
@@ -533,3 +533,58 @@ a.text-accent.underline {
533
533
  }
534
534
 
535
535
  /* END --zdc-* component tokens */
536
+
537
+ /* Compact homepage Markdown uses the same non-heading components and tokens.
538
+ * Shared width primitive is also available to the home identity group. */
539
+ .zd-home-inner {
540
+ inline-size: 100%;
541
+ max-inline-size: 60rem;
542
+ min-inline-size: 0;
543
+ margin-inline: auto;
544
+ overflow-wrap: anywhere;
545
+ }
546
+
547
+ .zd-home-copy,
548
+ .zd-home-links {
549
+ min-inline-size: 0;
550
+ }
551
+
552
+ .zd-home-links {
553
+ overflow-wrap: anywhere;
554
+ }
555
+
556
+ .zd-home-rule {
557
+ inline-size: 100%;
558
+ margin-block: var(--spacing-vsp-xl);
559
+ margin-inline: 0;
560
+ border: 0;
561
+ border-block-start: 1px solid var(--color-muted);
562
+ }
563
+
564
+ .zd-compact-prose {
565
+ min-inline-size: 0;
566
+ text-align: left;
567
+ font-size: 1rem;
568
+ line-height: 1.75;
569
+ overflow-wrap: anywhere;
570
+ }
571
+
572
+ .zd-compact-prose > :where(* + *) {
573
+ margin-top: var(--flow-space, 1rem);
574
+ }
575
+
576
+ .zd-compact-prose :where(h2, h3, h4, h5, h6) {
577
+ font-weight: var(--font-weight-bold, 700);
578
+ line-height: 1.4;
579
+ padding: 0;
580
+ border: 0;
581
+ background: none;
582
+ }
583
+ .zd-compact-prose :where(h2) { font-size: 1.3rem; --flow-space: 1.75rem; }
584
+ .zd-compact-prose :where(h3) { font-size: 1.125rem; --flow-space: 1.5rem; }
585
+ .zd-compact-prose :where(h4) { font-size: 1rem; --flow-space: 1.25rem; }
586
+ .zd-compact-prose :where(h5, h6) { font-size: .9375rem; --flow-space: 1.25rem; }
587
+ .zd-compact-prose > :where(h2, h3, h4, h5, h6) + * { margin-top: .5rem; }
588
+ .zd-compact-prose > :first-child { margin-top: 0; }
589
+ .zd-compact-prose :where(pre) { max-inline-size: 100%; overflow-x: auto; }
590
+ .zd-compact-prose :where(img) { max-inline-size: 100%; block-size: auto; }
@@ -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
  }