@takazudo/zudo-doc 5.9.0 → 5.11.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 (66) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +1 -1
  3. package/dist/chrome/derive.d.ts +1 -1
  4. package/dist/chrome/derive.js +25 -2
  5. package/dist/config-assertions/index.d.ts +30 -0
  6. package/dist/config-assertions/index.js +41 -1
  7. package/dist/config.d.ts +9 -0
  8. package/dist/config.js +1 -0
  9. package/dist/doc-content-header/index.js +6 -0
  10. package/dist/doc-metainfo-area/index.js +1 -14
  11. package/dist/doc-page-renderer/index.d.ts +3 -3
  12. package/dist/doc-page-renderer/index.js +1 -1
  13. package/dist/doc-page-shell/index.d.ts +6 -0
  14. package/dist/doc-pager/index.d.ts +6 -0
  15. package/dist/doc-pager/index.js +5 -2
  16. package/dist/docs-schema/index.d.ts +11 -0
  17. package/dist/docs-schema/index.js +11 -1
  18. package/dist/format-date/index.d.ts +12 -0
  19. package/dist/format-date/index.js +64 -0
  20. package/dist/frontmatter-preview-defaults/index.js +6 -1
  21. package/dist/header/header.js +9 -1
  22. package/dist/home-page/index.js +5 -2
  23. package/dist/html-preview-wrapper/highlight-runtime.d.ts +7 -7
  24. package/dist/html-preview-wrapper/highlight-runtime.js +1 -1
  25. package/dist/integrations/changelog/emit.js +19 -2
  26. package/dist/mdx-components/index.d.ts +4 -1
  27. package/dist/mdx-components/index.js +3 -1
  28. package/dist/nav-indexing/index.d.ts +2 -0
  29. package/dist/nav-indexing/index.js +2 -0
  30. package/dist/nav-indexing/note-tray-index.d.ts +29 -0
  31. package/dist/nav-indexing/note-tray-index.js +120 -0
  32. package/dist/note-tray-index/index.d.ts +45 -0
  33. package/dist/note-tray-index/index.js +52 -0
  34. package/dist/note-tray-model/index.d.ts +31 -0
  35. package/dist/note-tray-model/index.js +74 -0
  36. package/dist/preset.d.ts +11 -0
  37. package/dist/preset.js +5 -1
  38. package/dist/safelist.css +1 -1
  39. package/dist/settings.d.ts +2 -0
  40. package/dist/sidebar/types.d.ts +6 -0
  41. package/dist/sidebar-tree/build-tree.js +8 -0
  42. package/dist/sidebar-tree/types.d.ts +12 -0
  43. package/dist/sidebar-tree-island/index.js +176 -1
  44. package/dist/sidebar-utils/index.d.ts +6 -0
  45. package/dist/site-schema/doc-route-entries.js +2 -0
  46. package/dist/site-schema/index.d.ts +1 -0
  47. package/dist/site-schema/index.js +3 -1
  48. package/dist/site-schema/nav-tree.js +17 -2
  49. package/dist/site-schema/note-tray-validate.d.ts +7 -0
  50. package/dist/site-schema/note-tray-validate.js +94 -0
  51. package/dist/site-schema/types.d.ts +11 -0
  52. package/dist/site-tree-nav/index.d.ts +7 -0
  53. package/dist/site-tree-nav/index.js +3 -2
  54. package/dist/site-tree-nav-island/index.d.ts +5 -1
  55. package/dist/site-tree-nav-island/index.js +119 -12
  56. package/dist/theme-packs/drift/pack.css +6 -1
  57. package/dist/theme-packs/hearth/pack.css +5 -0
  58. package/dist/theme-packs/sakura/pack.css +6 -0
  59. package/dist/theme-packs/scandi/pack.css +7 -7
  60. package/dist/theme-packs/timberline/pack.css +5 -0
  61. package/eject/doc-pager/index.tsx +9 -0
  62. package/eject/header/header.tsx +26 -1
  63. package/eject/sidebar/types.ts +6 -0
  64. package/eject/sidebar-tree-island/index.tsx +241 -6
  65. package/eject/site-tree-nav-island/index.tsx +166 -16
  66. package/package.json +20 -8
package/CHANGELOG.md CHANGED
@@ -4,6 +4,37 @@ 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
+ ## [5.11.0] - 2026-08-23
8
+
9
+ ### Features
10
+
11
+ - Added the `siteTreeNavIgnore` setting so selected site-tree categories can be omitted from sidebar and home-page navigation while their documents remain available. (`2d1aa78f`)
12
+
13
+ ### Bug Fixes
14
+
15
+ - Aligned grouped note-tray child items with the deeper indentation used by the corresponding expanded layout. (`5bbf365f`)
16
+
17
+ ### Other Changes
18
+
19
+ - Updated the zfb package family to 2.10.0 and documented `siteTreeNavIgnore` plus quoted date frontmatter guidance in English and Japanese. (`acaa01cf`, `3f15cc2f`, `c484754b`)
20
+
21
+ ## [5.10.0] - 2026-08-22
22
+
23
+ ### Features
24
+
25
+ - Added first-class multi-package changelogs, including nested package navigation, per-package release pages, bilingual guidance, and the `create-zudo-doc --changelog-packages` option. (`91f179c8`, `302eac4f`)
26
+ - Upgraded the zfb package family to 2.9.0 and moved HtmlPreview to the public highlight-only WASM runtime, reducing the emitted WASM payload by about 55%. (`52291ec5`, `2911a651`)
27
+
28
+ ### Bug Fixes
29
+
30
+ - Hardened nested changelog navigation and added diagnostics when an emitter targets a multi-package landing directory instead of a package release directory. (`302eac4f`, `c4266e4a`)
31
+ - Normalized non-pill sidebar current rows in Drift, Hearth, Sakura, Scandi, and Timberline while preserving each pack's nested active treatment. (`7c4e0444`)
32
+
33
+ ### Other Changes
34
+
35
+ - Added bilingual documentation, publish-contract coverage, and positive browser detectors for the highlight-only runtime. (`b97ef30c`, `92269d49`)
36
+ - Added package-test hook headroom for concurrent CI load without changing subprocess deadlines. (`4d1417a9`)
37
+
7
38
  ## [5.9.0] - 2026-08-20
8
39
 
9
40
  ### Features
package/README.md CHANGED
@@ -59,7 +59,7 @@ const breadcrumbs = buildBreadcrumbs(tree, "guides/color", homeHref);
59
59
 
60
60
  ## Optional peer dependency: `@takazudo/zfb-md-wasm`
61
61
 
62
- `./html-preview-wrapper`'s `<HighlightedCode>` lazily imports the package root and calls `highlightCode()` for client-side semantic syntax highlighting. `@takazudo/zfb-md-wasm` is declared as an **optional peerDependency** — install the same prerelease version as the rest of your zfb packages if you use that subpath:
62
+ `./html-preview-wrapper`'s `<HighlightedCode>` lazily imports the public `@takazudo/zfb-md-wasm/highlight` subpath and calls `highlightCode()` for client-side semantic syntax highlighting. `@takazudo/zfb-md-wasm` is declared as an **optional peerDependency** — install the same version as the rest of your zfb packages if you use that subpath:
63
63
 
64
64
  ```sh
65
65
  pnpm add @takazudo/zfb-md-wasm
@@ -152,7 +152,7 @@ export declare function deriveInlineVersionSwitcher(ctx: ChromeContext, getUnava
152
152
  * happens one layer up in `createMdxComponents()`, after the wrapper choice.
153
153
  */
154
154
  export declare function deriveMdxComponents(ctx: ChromeContext): {
155
- createMdxComponentsBound: (lang?: string, currentVersion?: string) => Record<string, unknown>;
155
+ createMdxComponentsBound: (lang?: string, currentVersion?: string, currentSlug?: string) => Record<string, unknown>;
156
156
  SiteTreeNavWrapper: FactoryComponent;
157
157
  };
158
158
  export type { Settings };
@@ -14,6 +14,7 @@ import { DEFAULT_THEME_PACK_SLUG } from "../theme-pack-switcher/theme-pack-sync.
14
14
  import { SearchWidget } from "../search-widget/index.js";
15
15
  import { createMdxComponents } from "../mdx-components/index.js";
16
16
  import { createCategoryNavWrapper } from "../category-nav/index.js";
17
+ import { createNoteTrayIndexWrapper } from "../note-tray-index/index.js";
17
18
  import { createCategoryTreeNavWrapper } from "../category-tree-nav/index.js";
18
19
  import { createSiteTreeNavWrapper } from "../site-tree-nav/index.js";
19
20
  import { Details } from "../details/index.js";
@@ -269,6 +270,26 @@ function deriveMdxComponents(ctx) {
269
270
  )),
270
271
  groupSatelliteNodes: ctx.groupSatelliteNodes,
271
272
  getCategoryOrder: ctx.getCategoryOrder,
273
+ versionedDocsUrl: ctx.versionedDocsUrl,
274
+ categoryIgnore: ctx.settings.siteTreeNavIgnore
275
+ });
276
+ const NoteTrayIndexWrapper = createNoteTrayIndexWrapper({
277
+ defaultLocale: ctx.defaultLocale,
278
+ docTags: ctx.settings.docTags,
279
+ resolveNavSource: ctx.resolveNavSource,
280
+ buildNavTree: ((docs, locale, categoryMeta) => ctx.buildNavTree(
281
+ docs,
282
+ locale,
283
+ categoryMeta,
284
+ (slug, loc) => ctx.docsUrl(slug, loc)
285
+ )),
286
+ findNode: ctx.findNode,
287
+ toRouteSlug: ctx.toRouteSlug,
288
+ resolveTag: (raw) => ctx.resolveTagBound(raw).canonical,
289
+ tagHref: (tag, locale) => ctx.withBase(
290
+ locale === ctx.defaultLocale ? `/docs/tags/${encodeURIComponent(tag)}` : `/${locale}/docs/tags/${encodeURIComponent(tag)}`
291
+ ),
292
+ t: (key, locale) => ctx.t(key, locale),
272
293
  versionedDocsUrl: ctx.versionedDocsUrl
273
294
  });
274
295
  function HtmlPreviewBound(props) {
@@ -286,15 +307,17 @@ function deriveMdxComponents(ctx) {
286
307
  PresetGenerator: (_props) => null
287
308
  };
288
309
  const mdxExtras = { ...mdxExtrasDefault, ...ctx.hostBindings.mdxExtras ?? {} };
289
- function createMdxComponentsBound(lang = ctx.defaultLocale, currentVersion) {
310
+ function createMdxComponentsBound(lang = ctx.defaultLocale, currentVersion, currentSlug = "") {
290
311
  return createMdxComponents({
291
312
  settings: ctx.settings,
292
313
  locale: lang,
293
314
  currentVersion,
315
+ currentSlug,
294
316
  navData: {
295
317
  CategoryNav: CategoryNavWrapper,
296
318
  CategoryTreeNav: CategoryTreeNavWrapper,
297
- SiteTreeNav: SiteTreeNavWrapper
319
+ SiteTreeNav: SiteTreeNavWrapper,
320
+ NoteTrayIndex: NoteTrayIndexWrapper
298
321
  },
299
322
  // Package-owned content components wired here so an INJECTED docs route
300
323
  // renders MDX using these tags without the "MDX requires '<X>' to be
@@ -1,4 +1,17 @@
1
1
  import type { FaviconConfig } from "../settings.js";
2
+ /** Structural subset of a header dropdown read by the category-match guard. */
3
+ export interface AmbiguousDropdownCategoryMatchItem {
4
+ label?: string;
5
+ categoryMatch?: string;
6
+ children?: Array<{
7
+ label?: string;
8
+ categoryMatch?: string;
9
+ }>;
10
+ }
11
+ /** Logger surface accepted by the non-throwing header-nav diagnostic. */
12
+ export interface ConfigAssertionLogger {
13
+ warn(message: string): void;
14
+ }
2
15
  /** Structural subset of `Settings` {@link assertNoEmptyStringFaviconOrLogo} reads. */
3
16
  export interface EmptyStringFaviconOrLogoSubject {
4
17
  logo?: string | false;
@@ -29,3 +42,20 @@ export interface EmptyStringFaviconOrLogoSubject {
29
42
  * object is not an empty string).
30
43
  */
31
44
  export declare function assertNoEmptyStringFaviconOrLogo(settings: EmptyStringFaviconOrLogoSubject): void;
45
+ /**
46
+ * Warn about `categoryMatch` values that cannot express the intended
47
+ * dropdown grouping.
48
+ *
49
+ * A category matcher is compared with the first slug segment by the nav-scope
50
+ * resolver, so a value containing `/` never matches. Likewise, duplicate
51
+ * child values make every matching child category-active. Children grouped
52
+ * under one top-level directory should omit `categoryMatch` and rely on the
53
+ * deepest matching child path; Learn-style children should use distinct
54
+ * top-level values.
55
+ *
56
+ * This is intentionally a diagnostic rather than a config assertion: malformed
57
+ * navigation should remain buildable, and each offending value is reported at
58
+ * most once per parent dropdown. The `"!"` matcher is the intentional default
59
+ * bucket and is excluded from both checks.
60
+ */
61
+ export declare function warnAmbiguousDropdownCategoryMatch(headerNav: readonly AmbiguousDropdownCategoryMatchItem[] | undefined, logger?: ConfigAssertionLogger): void;
@@ -19,6 +19,46 @@ function assertNoEmptyStringFaviconOrLogo(settings) {
19
19
  }
20
20
  }
21
21
  }
22
+ function warnAmbiguousDropdownCategoryMatch(headerNav, logger = console) {
23
+ if (!headerNav) return;
24
+ for (const item of headerNav) {
25
+ const children = item.children;
26
+ if (!children || children.length === 0) continue;
27
+ const childCounts = /* @__PURE__ */ new Map();
28
+ for (const child of children) {
29
+ const value = child.categoryMatch;
30
+ if (value == null || value === "!") continue;
31
+ childCounts.set(value, (childCounts.get(value) ?? 0) + 1);
32
+ }
33
+ const offendingValues = [];
34
+ const addOffendingValue = (value) => {
35
+ if (value == null || value === "!" || offendingValues.includes(value)) return;
36
+ offendingValues.push(value);
37
+ };
38
+ if (item.categoryMatch?.includes("/")) {
39
+ addOffendingValue(item.categoryMatch);
40
+ }
41
+ for (const child of children) {
42
+ if (child.categoryMatch?.includes("/")) {
43
+ addOffendingValue(child.categoryMatch);
44
+ }
45
+ }
46
+ for (const [value, count] of childCounts) {
47
+ if (count >= 2) addOffendingValue(value);
48
+ }
49
+ for (const value of offendingValues) {
50
+ const reasons = [
51
+ ...value.includes("/") ? ["contains `/` and never matches a top-level slug"] : [],
52
+ ...(childCounts.get(value) ?? 0) >= 2 ? ["is shared by multiple children"] : []
53
+ ].join("; ");
54
+ const parentLabel = item.label ?? "(unnamed)";
55
+ logger.warn(
56
+ `zudo-doc: header dropdown "${parentLabel}" has categoryMatch "${value}" that ${reasons}. Children grouped under one top-level directory should omit categoryMatch (active state follows the deepest matching child path); Learn-style children should use distinct top-level values.`
57
+ );
58
+ }
59
+ }
60
+ }
22
61
  export {
23
- assertNoEmptyStringFaviconOrLogo
62
+ assertNoEmptyStringFaviconOrLogo,
63
+ warnAmbiguousDropdownCategoryMatch
24
64
  };
package/dist/config.d.ts CHANGED
@@ -144,6 +144,15 @@ export interface ZudoDocConfig {
144
144
  * @default { wide: false }
145
145
  */
146
146
  home?: HomeConfig;
147
+ /**
148
+ * Top-level category slugs hidden from the package-owned home-page category
149
+ * grid on `/` and locale homes AND from the `<SiteTreeNav />` /
150
+ * `<SiteTreeNavDemo />` MDX tag. They still appear in header nav, sidebar,
151
+ * search and sitemap. Before this field the package hard-coded
152
+ * `["inbox", "develop"]` (#3641).
153
+ * @default []
154
+ */
155
+ siteTreeNavIgnore?: string[];
147
156
  /**
148
157
  * Minify production HTML output from `zfb build`.
149
158
  * @default true
package/dist/config.js CHANGED
@@ -20,6 +20,7 @@ const DEFAULT_SETTINGS = {
20
20
  base: "/",
21
21
  trailingSlash: false,
22
22
  home: { wide: false },
23
+ siteTreeNavIgnore: [],
23
24
  minifyHtml: true,
24
25
  docsDir: "src/content/docs",
25
26
  entryDocSlug: "getting-started",
@@ -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 { formatDate } from "../format-date/index.js";
6
7
  function createDocContentHeader(ctx) {
7
8
  assertChromeContext(ctx, "createDocContentHeader");
8
9
  const t = ctx.t;
@@ -27,6 +28,11 @@ function createDocContentHeader(ctx) {
27
28
  isFallback,
28
29
  version
29
30
  }),
31
+ (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,
33
+ entry.data.date && entry.data.updated ? " \xB7 " : null,
34
+ entry.data.updated ? `${t("doc.updated", locale)} ${formatDate(entry.data.updated, locale)}` : null
35
+ ] }),
30
36
  !version && /* @__PURE__ */ jsx(DocMetainfoArea, { slug, locale, isFallback }),
31
37
  !version && /* @__PURE__ */ jsx(DocTagsArea, { slug, locale, tags: entry.data.tags }),
32
38
  isFallback && !entry.data.generated && /* @__PURE__ */ jsx(
@@ -2,20 +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
- const LOCALE_TO_BCP47 = {
6
- en: "en-US",
7
- ja: "ja-JP",
8
- de: "de-DE"
9
- };
10
- function formatDate(isoDate, locale) {
11
- const d = new Date(isoDate);
12
- if (isNaN(d.getTime())) return isoDate;
13
- return d.toLocaleDateString(LOCALE_TO_BCP47[locale] ?? "en-US", {
14
- year: "numeric",
15
- month: "short",
16
- day: "numeric"
17
- });
18
- }
5
+ import { formatDate } from "../format-date/index.js";
19
6
  function createDocMetainfoArea(ctx) {
20
7
  assertChromeContext(ctx, "createDocMetainfoArea");
21
8
  const settings = ctx.settings;
@@ -48,10 +48,10 @@ export interface DocPageRendererDeps {
48
48
  * Build locale-aware MDX components bag.
49
49
  * Host passes `createMdxComponents` (from `pages/_mdx-components.ts`).
50
50
  * `currentVersion` (#3218) threads the active `/v/{version}` slug into the
51
- * nav-card wrappers (CategoryNav/CategoryTreeNav/SiteTreeNav) so their hrefs
52
- * resolve within the version undefined on latest/unversioned pages.
51
+ * nav wrappers so their data and hrefs resolve within the version. The
52
+ * current slug lets NoteTrayIndex infer its containing tray.
53
53
  */
54
- createMdxComponents: (locale: string, currentVersion?: string) => Record<string, unknown>;
54
+ createMdxComponents: (locale: string, currentVersion: string | undefined, currentSlug: string) => Record<string, unknown>;
55
55
  /** Translate a UI string key for a locale. */
56
56
  t: (key: string, locale: string) => string;
57
57
  /**
@@ -35,7 +35,7 @@ function createRenderDocPage(ctx) {
35
35
  const slug = props.kind === "autoIndex" ? props.autoIndex.slug : props.entry.data.slug ?? toRouteSlug(props.entry.slug);
36
36
  const title = props.kind === "autoIndex" ? props.autoIndex.label : props.entry.data.title;
37
37
  const description = props.kind === "autoIndex" ? props.autoIndex.description : props.entry.data.description;
38
- const components = createMdxComponents(locale, version?.slug);
38
+ const components = createMdxComponents(locale, version?.slug, slug);
39
39
  const autoIndexChildren = props.kind === "autoIndex" ? version ? props.autoIndex.children.filter((c) => c.hasPage || c.children.length > 0) : props.autoIndex.children.filter((c) => c.hasPage || c.children.length > 0).map((c) => ({
40
40
  ...c,
41
41
  href: c.href ?? docsUrl(c.slug, locale)
@@ -22,6 +22,12 @@ export interface DocPageNavNode {
22
22
  href?: string;
23
23
  hasPage: boolean;
24
24
  children: DocPageNavNode[];
25
+ shape?: "note-tray";
26
+ noteTrayDated?: boolean;
27
+ noteTraySidebar?: "index" | "year" | "month";
28
+ date?: string;
29
+ updated?: string;
30
+ rank?: number;
25
31
  }
26
32
  /** Slots and parameters that vary between the 4 doc routes. */
27
33
  export interface DocPageShellProps {
@@ -6,6 +6,12 @@ import type { Settings } from "../settings.js";
6
6
  interface PagerNode {
7
7
  href?: string;
8
8
  label: string;
9
+ shape?: "note-tray";
10
+ noteTrayDated?: boolean;
11
+ noteTraySidebar?: "index" | "year" | "month";
12
+ date?: string;
13
+ updated?: string;
14
+ rank?: number;
9
15
  }
10
16
  export interface DocPagerProps {
11
17
  /** Previous page node (null = no previous page → renders placeholder). */
@@ -1,6 +1,7 @@
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 { formatDate } from "../format-date/index.js";
4
5
  function createDocPager(ctx) {
5
6
  assertChromeContext(ctx, "createDocPager");
6
7
  const t = ctx.t;
@@ -16,7 +17,8 @@ function createDocPager(ctx) {
16
17
  /* @__PURE__ */ jsx(ChevronLeft, { className: "h-[1.125rem] w-[1.125rem]" }),
17
18
  /* @__PURE__ */ jsx("span", { class: "no-underline", children: t("nav.previous", locale) })
18
19
  ] }),
19
- /* @__PURE__ */ jsx("p", { class: "text-small font-semibold underline group-hover:text-accent", children: prev.label })
20
+ /* @__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) })
20
22
  ]
21
23
  }
22
24
  ) : /* @__PURE__ */ jsx("div", {}),
@@ -30,7 +32,8 @@ function createDocPager(ctx) {
30
32
  /* @__PURE__ */ jsx("span", { class: "no-underline", children: t("nav.next", locale) }),
31
33
  /* @__PURE__ */ jsx(ChevronRight, { className: "h-[1.125rem] w-[1.125rem]" })
32
34
  ] }),
33
- /* @__PURE__ */ jsx("p", { class: "text-small font-semibold underline group-hover:text-accent", children: next.label })
35
+ /* @__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) })
34
37
  ]
35
38
  }
36
39
  ) : /* @__PURE__ */ jsx("div", {})
@@ -58,6 +58,17 @@ export declare function buildDocsSchema(opts?: BuildDocsSchemaOptions): z.ZodObj
58
58
  asc: "asc";
59
59
  desc: "desc";
60
60
  }>>;
61
+ category_shape: z.ZodOptional<z.ZodEnum<{
62
+ "note-tray": "note-tray";
63
+ }>>;
64
+ note_tray_dated: z.ZodOptional<z.ZodBoolean>;
65
+ note_tray_sidebar: z.ZodOptional<z.ZodEnum<{
66
+ index: "index";
67
+ year: "year";
68
+ month: "month";
69
+ }>>;
70
+ date: z.ZodOptional<z.ZodString>;
71
+ updated: z.ZodOptional<z.ZodString>;
61
72
  }, z.core.$loose>;
62
73
  /**
63
74
  * TypeScript type inferred from the default docs frontmatter zod schema.
@@ -35,7 +35,17 @@ function buildDocsSchema(opts) {
35
35
  // `category_sort_order` sets the child sort direction. Frontmatter wins
36
36
  // over the sidecar.
37
37
  category_no_page: z.boolean().optional(),
38
- category_sort_order: z.enum(["asc", "desc"]).optional()
38
+ category_sort_order: z.enum(["asc", "desc"]).optional(),
39
+ category_shape: z.enum(["note-tray"]).optional(),
40
+ note_tray_dated: z.boolean().optional(),
41
+ note_tray_sidebar: z.enum(["index", "year", "month"]).optional(),
42
+ // zfb keeps scalar frontmatter values as strings whether quoted or not;
43
+ // the regex below validates those strings' YYYY-MM-DD format. Quoting is
44
+ // optional here and only aids interoperability with YAML 1.1 tooling,
45
+ // such as js-yaml, that consumes these fields and would coerce a bare
46
+ // date into a Date object (#3642).
47
+ date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
48
+ updated: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
39
49
  }).passthrough();
40
50
  }
41
51
  export {
@@ -0,0 +1,12 @@
1
+ export interface IsoDateParts {
2
+ year: number;
3
+ month: number;
4
+ day: number;
5
+ }
6
+ export declare function parseIsoDate(iso: string): IsoDateParts | undefined;
7
+ /** Format an ISO date for display while retaining the established locale map. */
8
+ export declare function formatDate(iso: string, locale: string): string;
9
+ /** Format a year/month label with the year first in every locale. */
10
+ export declare function formatYearMonth(iso: string, locale: string): string;
11
+ /** Return the stable numeric month/day portion of a calendar-valid ISO date. */
12
+ export declare function formatMonthDay(iso: string): string;
@@ -0,0 +1,64 @@
1
+ const LOCALE_TO_BCP47 = {
2
+ en: "en-US",
3
+ ja: "ja-JP",
4
+ de: "de-DE"
5
+ };
6
+ function parseIsoDate(iso) {
7
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
8
+ if (!match) return void 0;
9
+ const year = Number(match[1]);
10
+ const month = Number(match[2]);
11
+ const day = Number(match[3]);
12
+ const date = /* @__PURE__ */ new Date(0);
13
+ date.setUTCHours(0, 0, 0, 0);
14
+ date.setUTCFullYear(year, month - 1, day);
15
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
16
+ return void 0;
17
+ }
18
+ return { year, month, day };
19
+ }
20
+ function toUtcDate(iso) {
21
+ const parts = parseIsoDate(iso);
22
+ if (parts) {
23
+ const date = /* @__PURE__ */ new Date(0);
24
+ date.setUTCHours(0, 0, 0, 0);
25
+ date.setUTCFullYear(parts.year, parts.month - 1, parts.day);
26
+ return date;
27
+ }
28
+ if (!/^\d{4}-\d{2}-\d{2}T/.test(iso)) return void 0;
29
+ const timestamp = new Date(iso);
30
+ return Number.isNaN(timestamp.getTime()) ? void 0 : timestamp;
31
+ }
32
+ function formatDate(iso, locale) {
33
+ const date = toUtcDate(iso);
34
+ if (!date) return iso;
35
+ return new Intl.DateTimeFormat(LOCALE_TO_BCP47[locale] ?? "en-US", {
36
+ year: "numeric",
37
+ month: "short",
38
+ day: "numeric",
39
+ timeZone: "UTC"
40
+ }).format(date);
41
+ }
42
+ function formatYearMonth(iso, locale) {
43
+ const date = toUtcDate(iso.length === 7 ? `${iso}-01` : iso);
44
+ if (!date) return iso;
45
+ const year = new Intl.DateTimeFormat(LOCALE_TO_BCP47[locale] ?? "en-US", {
46
+ year: "numeric",
47
+ timeZone: "UTC"
48
+ }).format(date);
49
+ const month = new Intl.DateTimeFormat(LOCALE_TO_BCP47[locale] ?? "en-US", {
50
+ month: "long",
51
+ timeZone: "UTC"
52
+ }).format(date);
53
+ return locale === "ja" ? `${year}${month}` : `${year} ${month}`;
54
+ }
55
+ function formatMonthDay(iso) {
56
+ const parts = parseIsoDate(iso);
57
+ return parts ? `${String(parts.month).padStart(2, "0")}-${String(parts.day).padStart(2, "0")}` : iso;
58
+ }
59
+ export {
60
+ formatDate,
61
+ formatMonthDay,
62
+ formatYearMonth,
63
+ parseIsoDate
64
+ };
@@ -18,7 +18,12 @@ const defaultFrontmatterPreviewIgnoreKeys = [
18
18
  "slug",
19
19
  "generated",
20
20
  "category_no_page",
21
- "category_sort_order"
21
+ "category_sort_order",
22
+ "category_shape",
23
+ "note_tray_dated",
24
+ "note_tray_sidebar",
25
+ "date",
26
+ "updated"
22
27
  ];
23
28
  export {
24
29
  defaultFrontmatterPreviewIgnoreKeys
@@ -44,7 +44,8 @@ function Header(props) {
44
44
  } = props;
45
45
  const isNonDefaultLocale = lang != null && lang !== i18n.defaultLocale;
46
46
  const pathWithoutBase = urlHelpers.stripBase(currentPath);
47
- const matchPath = pathForMatch(pathWithoutBase, lang, i18n.defaultLocale);
47
+ const pathWithoutVersion = stripCurrentVersionPrefix(pathWithoutBase, currentVersion);
48
+ const matchPath = pathForMatch(pathWithoutVersion, lang, i18n.defaultLocale);
48
49
  const activeNavPath = computeActiveNavPath(headerNav, matchPath);
49
50
  const rightItemDispatch = createRightItemDispatch(headerRightComponents);
50
51
  return /* @__PURE__ */ jsxs(
@@ -249,6 +250,13 @@ function renderNavItem(item, activeNavPath, activeCategory, lang, currentVersion
249
250
  }
250
251
  );
251
252
  }
253
+ function stripCurrentVersionPrefix(path, currentVersion) {
254
+ if (currentVersion == null || currentVersion === "") return path;
255
+ const prefix = `/v/${currentVersion}`;
256
+ if (path === prefix) return "/";
257
+ if (path.startsWith(`${prefix}/`)) return path.slice(prefix.length);
258
+ return path;
259
+ }
252
260
  function TriggerButton({
253
261
  index,
254
262
  id,
@@ -18,6 +18,7 @@ function cssUrl(url) {
18
18
  function createHomePageView(ctx) {
19
19
  assertChromeContext(ctx, "createHomePageView");
20
20
  const settings = ctx.settings;
21
+ const categoryIgnore = settings.siteTreeNavIgnore ?? [];
21
22
  const t = ctx.t;
22
23
  const withBase = ctx.withBase;
23
24
  const defaultLocale = ctx.defaultLocale;
@@ -131,8 +132,10 @@ function createHomePageView(ctx) {
131
132
  {
132
133
  tree,
133
134
  categoryOrder,
134
- categoryIgnore: ["inbox", "develop"],
135
- initiallyCollapsedCategorySlugs
135
+ categoryIgnore,
136
+ initiallyCollapsedCategorySlugs,
137
+ locale,
138
+ updatedLabel: t("doc.updated", locale)
136
139
  }
137
140
  )
138
141
  }),
@@ -1,5 +1,5 @@
1
- import type { HighlightCodeOptions, HighlightCodeResult } from "@takazudo/zfb-md-wasm";
2
- type HighlightModule = Pick<typeof import("@takazudo/zfb-md-wasm"), "highlightCode">;
1
+ import type { HighlightCodeOptions, HighlightCodeResult } from "@takazudo/zfb-md-wasm/highlight";
2
+ type HighlightModule = Pick<typeof import("@takazudo/zfb-md-wasm/highlight"), "highlightCode">;
3
3
  export type HighlightModuleImporter = () => Promise<HighlightModule>;
4
4
  export interface HtmlPreviewHighlightRuntime {
5
5
  highlightCode(code: string, options: HighlightCodeOptions): Promise<HighlightCodeResult>;
@@ -7,11 +7,11 @@ export interface HtmlPreviewHighlightRuntime {
7
7
  /**
8
8
  * Build the HTML Preview's lazy zfb highlighting adapter.
9
9
  *
10
- * The cached value is only the public package-root module import. A rejected
11
- * import is evicted so a later source-panel mount can retry a transient chunk
12
- * load. Calls to `highlightCode` are deliberately not cached: the upstream
13
- * package owns WASM initialization and trap recovery, while the component owns
14
- * request cancellation and stale-result protection.
10
+ * The cached value is only the public `./highlight` subpath module import. A
11
+ * rejected import is evicted so a later source-panel mount can retry a transient
12
+ * chunk load. Calls to `highlightCode` are deliberately not cached: the
13
+ * upstream package owns WASM initialization and trap recovery, while the
14
+ * component owns request cancellation and stale-result protection.
15
15
  */
16
16
  export declare function createHighlightRuntime(importModule: HighlightModuleImporter): HtmlPreviewHighlightRuntime;
17
17
  /**
@@ -20,7 +20,7 @@ function createHighlightRuntime(importModule) {
20
20
  };
21
21
  }
22
22
  const defaultRuntime = createHighlightRuntime(
23
- () => import("@takazudo/zfb-md-wasm")
23
+ () => import("@takazudo/zfb-md-wasm/highlight")
24
24
  );
25
25
  function getUsableHighlightHtml(result) {
26
26
  if (result.html == null || result.diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
@@ -1,5 +1,5 @@
1
- import { mkdirSync, writeFileSync } from "node:fs";
2
- import { dirname, resolve } from "node:path";
1
+ import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
3
  import { generateChangelogMarkdown } from "./generate.js";
4
4
  import { loadChangelogEntries } from "./load.js";
5
5
  function emitChangelogs(options) {
@@ -8,6 +8,16 @@ function emitChangelogs(options) {
8
8
  const sourceDir = resolve(options.projectRoot, config.sourceDir);
9
9
  const outputFile = resolve(options.projectRoot, config.outputFile);
10
10
  const entries = loadChangelogEntries({ sourceDir });
11
+ if (entries.length === 0) {
12
+ const nestedMdxDirs = findNestedMdxDirs(sourceDir);
13
+ if (nestedMdxDirs.length > 0) {
14
+ options.logger?.warn?.(
15
+ `Changelog sourceDir "${config.sourceDir}" (${sourceDir}) yielded 0 releases but contains .mdx files in sub-directories: ${nestedMdxDirs.join(
16
+ ", "
17
+ )}. In a multi-changelog layout, each changelogs[] entry must point at a per-package directory (for example "${config.sourceDir}/<name>") whose per-release files are non-index .mdx files; the loader intentionally skips index.mdx.`
18
+ );
19
+ }
20
+ }
11
21
  const markdown = generateChangelogMarkdown(entries, {
12
22
  title: config.title,
13
23
  packageName: config.packageName
@@ -19,6 +29,13 @@ function emitChangelogs(options) {
19
29
  }
20
30
  return { written };
21
31
  }
32
+ function findNestedMdxDirs(sourceDir) {
33
+ return readdirSync(sourceDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter(
34
+ (entry) => readdirSync(join(sourceDir, entry.name), { withFileTypes: true }).some(
35
+ (nestedEntry) => nestedEntry.isFile() && nestedEntry.name.endsWith(".mdx")
36
+ )
37
+ ).map((entry) => entry.name).sort();
38
+ }
22
39
  export {
23
40
  emitChangelogs
24
41
  };
@@ -2,7 +2,7 @@ import type { Settings } from "../settings.js";
2
2
  /** Any Preact-renderable component (function returning a VNode/children). */
3
3
  type AnyComponent = (props: Record<string, unknown>) => unknown;
4
4
  /**
5
- * The 4 host-supplied, locale-aware nav wrappers. Each accepts a `lang` prop;
5
+ * The host-supplied, locale-aware nav wrappers. Each accepts a `lang` prop;
6
6
  * the factory injects the active `locale` so the wrapper queries the correct
7
7
  * collection. `SiteTreeNav` is reused for both the `SiteTreeNav` and
8
8
  * `SiteTreeNavDemo` MDX tags (matching the showcase mapping).
@@ -11,6 +11,7 @@ export interface MdxNavData {
11
11
  CategoryNav: AnyComponent;
12
12
  CategoryTreeNav: AnyComponent;
13
13
  SiteTreeNav: AnyComponent;
14
+ NoteTrayIndex: AnyComponent;
14
15
  }
15
16
  export interface CreateMdxComponentsOptions {
16
17
  /**
@@ -35,6 +36,8 @@ export interface CreateMdxComponentsOptions {
35
36
  * which wrapper `navData` holds.
36
37
  */
37
38
  currentVersion?: string;
39
+ /** Canonical slug of the page being rendered, used to infer its containing note tray. */
40
+ currentSlug: string;
38
41
  /** Host-supplied locale-aware nav wrappers. */
39
42
  navData: MdxNavData;
40
43
  /**