@takazudo/zudo-doc 5.9.0 → 5.10.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 (61) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -1
  3. package/dist/chrome/derive.d.ts +1 -1
  4. package/dist/chrome/derive.js +24 -2
  5. package/dist/config-assertions/index.d.ts +30 -0
  6. package/dist/config-assertions/index.js +41 -1
  7. package/dist/doc-content-header/index.js +6 -0
  8. package/dist/doc-metainfo-area/index.js +1 -14
  9. package/dist/doc-page-renderer/index.d.ts +3 -3
  10. package/dist/doc-page-renderer/index.js +1 -1
  11. package/dist/doc-page-shell/index.d.ts +6 -0
  12. package/dist/doc-pager/index.d.ts +6 -0
  13. package/dist/doc-pager/index.js +5 -2
  14. package/dist/docs-schema/index.d.ts +11 -0
  15. package/dist/docs-schema/index.js +8 -1
  16. package/dist/format-date/index.d.ts +12 -0
  17. package/dist/format-date/index.js +64 -0
  18. package/dist/frontmatter-preview-defaults/index.js +6 -1
  19. package/dist/header/header.js +9 -1
  20. package/dist/home-page/index.js +3 -1
  21. package/dist/html-preview-wrapper/highlight-runtime.d.ts +7 -7
  22. package/dist/html-preview-wrapper/highlight-runtime.js +1 -1
  23. package/dist/integrations/changelog/emit.js +19 -2
  24. package/dist/mdx-components/index.d.ts +4 -1
  25. package/dist/mdx-components/index.js +3 -1
  26. package/dist/nav-indexing/index.d.ts +2 -0
  27. package/dist/nav-indexing/index.js +2 -0
  28. package/dist/nav-indexing/note-tray-index.d.ts +29 -0
  29. package/dist/nav-indexing/note-tray-index.js +120 -0
  30. package/dist/note-tray-index/index.d.ts +45 -0
  31. package/dist/note-tray-index/index.js +52 -0
  32. package/dist/note-tray-model/index.d.ts +31 -0
  33. package/dist/note-tray-model/index.js +74 -0
  34. package/dist/preset.d.ts +11 -0
  35. package/dist/preset.js +5 -1
  36. package/dist/safelist.css +1 -1
  37. package/dist/sidebar/types.d.ts +6 -0
  38. package/dist/sidebar-tree/build-tree.js +8 -0
  39. package/dist/sidebar-tree/types.d.ts +12 -0
  40. package/dist/sidebar-tree-island/index.js +175 -1
  41. package/dist/sidebar-utils/index.d.ts +6 -0
  42. package/dist/site-schema/doc-route-entries.js +2 -0
  43. package/dist/site-schema/index.d.ts +1 -0
  44. package/dist/site-schema/index.js +3 -1
  45. package/dist/site-schema/nav-tree.js +17 -2
  46. package/dist/site-schema/note-tray-validate.d.ts +7 -0
  47. package/dist/site-schema/note-tray-validate.js +94 -0
  48. package/dist/site-schema/types.d.ts +11 -0
  49. package/dist/site-tree-nav-island/index.d.ts +5 -1
  50. package/dist/site-tree-nav-island/index.js +119 -12
  51. package/dist/theme-packs/drift/pack.css +6 -1
  52. package/dist/theme-packs/hearth/pack.css +5 -0
  53. package/dist/theme-packs/sakura/pack.css +6 -0
  54. package/dist/theme-packs/scandi/pack.css +7 -7
  55. package/dist/theme-packs/timberline/pack.css +5 -0
  56. package/eject/doc-pager/index.tsx +9 -0
  57. package/eject/header/header.tsx +26 -1
  58. package/eject/sidebar/types.ts +6 -0
  59. package/eject/sidebar-tree-island/index.tsx +238 -6
  60. package/eject/site-tree-nav-island/index.tsx +166 -16
  61. package/package.json +20 -8
package/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ 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.10.0] - 2026-08-22
8
+
9
+ ### Features
10
+
11
+ - 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`)
12
+ - 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`)
13
+
14
+ ### Bug Fixes
15
+
16
+ - Hardened nested changelog navigation and added diagnostics when an emitter targets a multi-package landing directory instead of a package release directory. (`302eac4f`, `c4266e4a`)
17
+ - Normalized non-pill sidebar current rows in Drift, Hearth, Sakura, Scandi, and Timberline while preserving each pack's nested active treatment. (`7c4e0444`)
18
+
19
+ ### Other Changes
20
+
21
+ - Added bilingual documentation, publish-contract coverage, and positive browser detectors for the highlight-only runtime. (`b97ef30c`, `92269d49`)
22
+ - Added package-test hook headroom for concurrent CI load without changing subprocess deadlines. (`4d1417a9`)
23
+
7
24
  ## [5.9.0] - 2026-08-20
8
25
 
9
26
  ### 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";
@@ -271,6 +272,25 @@ function deriveMdxComponents(ctx) {
271
272
  getCategoryOrder: ctx.getCategoryOrder,
272
273
  versionedDocsUrl: ctx.versionedDocsUrl
273
274
  });
275
+ const NoteTrayIndexWrapper = createNoteTrayIndexWrapper({
276
+ defaultLocale: ctx.defaultLocale,
277
+ docTags: ctx.settings.docTags,
278
+ resolveNavSource: ctx.resolveNavSource,
279
+ buildNavTree: ((docs, locale, categoryMeta) => ctx.buildNavTree(
280
+ docs,
281
+ locale,
282
+ categoryMeta,
283
+ (slug, loc) => ctx.docsUrl(slug, loc)
284
+ )),
285
+ findNode: ctx.findNode,
286
+ toRouteSlug: ctx.toRouteSlug,
287
+ resolveTag: (raw) => ctx.resolveTagBound(raw).canonical,
288
+ tagHref: (tag, locale) => ctx.withBase(
289
+ locale === ctx.defaultLocale ? `/docs/tags/${encodeURIComponent(tag)}` : `/${locale}/docs/tags/${encodeURIComponent(tag)}`
290
+ ),
291
+ t: (key, locale) => ctx.t(key, locale),
292
+ versionedDocsUrl: ctx.versionedDocsUrl
293
+ });
274
294
  function HtmlPreviewBound(props) {
275
295
  return HtmlPreviewWrapper({
276
296
  globalConfig: ctx.settings.htmlPreview ?? null,
@@ -286,15 +306,17 @@ function deriveMdxComponents(ctx) {
286
306
  PresetGenerator: (_props) => null
287
307
  };
288
308
  const mdxExtras = { ...mdxExtrasDefault, ...ctx.hostBindings.mdxExtras ?? {} };
289
- function createMdxComponentsBound(lang = ctx.defaultLocale, currentVersion) {
309
+ function createMdxComponentsBound(lang = ctx.defaultLocale, currentVersion, currentSlug = "") {
290
310
  return createMdxComponents({
291
311
  settings: ctx.settings,
292
312
  locale: lang,
293
313
  currentVersion,
314
+ currentSlug,
294
315
  navData: {
295
316
  CategoryNav: CategoryNavWrapper,
296
317
  CategoryTreeNav: CategoryTreeNavWrapper,
297
- SiteTreeNav: SiteTreeNavWrapper
318
+ SiteTreeNav: SiteTreeNavWrapper,
319
+ NoteTrayIndex: NoteTrayIndexWrapper
298
320
  },
299
321
  // Package-owned content components wired here so an INJECTED docs route
300
322
  // 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
  };
@@ -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,14 @@ 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
+ // Quote these values in YAML (for example, date: "2026-08-22") so
43
+ // parsers do not coerce them into Date objects before schema validation.
44
+ date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
45
+ updated: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
39
46
  }).passthrough();
40
47
  }
41
48
  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,
@@ -132,7 +132,9 @@ function createHomePageView(ctx) {
132
132
  tree,
133
133
  categoryOrder,
134
134
  categoryIgnore: ["inbox", "develop"],
135
- initiallyCollapsedCategorySlugs
135
+ initiallyCollapsedCategorySlugs,
136
+ locale,
137
+ updatedLabel: t("doc.updated", locale)
136
138
  }
137
139
  )
138
140
  }),
@@ -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
  /**
@@ -109,7 +109,7 @@ function makeEnlargeableParagraph(imageEnlarge, ContentImg) {
109
109
  };
110
110
  }
111
111
  function createMdxComponents(options) {
112
- const { settings, locale, currentVersion, navData, extras } = options;
112
+ const { settings, locale, currentVersion, currentSlug, navData, extras } = options;
113
113
  const ContentImg = makeContentImg(settings.base);
114
114
  const EnlargeableParagraph = makeEnlargeableParagraph(
115
115
  settings.imageEnlarge,
@@ -118,6 +118,7 @@ function createMdxComponents(options) {
118
118
  const CategoryNavBound = (props) => navData.CategoryNav({ ...props, lang: locale, currentVersion });
119
119
  const CategoryTreeNavBound = (props) => navData.CategoryTreeNav({ ...props, lang: locale, currentVersion });
120
120
  const SiteTreeNavBound = (props) => navData.SiteTreeNav({ ...props, lang: locale, currentVersion });
121
+ const NoteTrayIndexBound = (props) => navData.NoteTrayIndex({ ...props, lang: locale, currentVersion, currentSlug });
121
122
  return {
122
123
  ...defaultComponents,
123
124
  // img override: rewrites root-relative src to include settings.base.
@@ -151,6 +152,7 @@ function createMdxComponents(options) {
151
152
  CategoryTreeNav: CategoryTreeNavBound,
152
153
  SiteTreeNav: SiteTreeNavBound,
153
154
  SiteTreeNavDemo: SiteTreeNavBound,
155
+ NoteTrayIndex: NoteTrayIndexBound,
154
156
  // Host extras win over any same-named default above (Details, HtmlPreview,
155
157
  // Island, PresetGenerator, showcase stubs, etc.).
156
158
  ...extras ?? {}
@@ -34,4 +34,6 @@ export { DocCardGrid } from "./doc-card-grid.js";
34
34
  export type { DocCardGridProps, DocCardItem } from "./doc-card-grid.js";
35
35
  export { VersionsPageContent } from "./versions-page-content.js";
36
36
  export type { VersionsPageContentProps } from "./versions-page-content.js";
37
+ export { NoteTrayIndex } from "./note-tray-index.js";
38
+ export type { NoteTrayIndexItem, NoteTrayIndexProps, NoteTrayIndexStyle, } from "./note-tray-index.js";
37
39
  export type { NavNode, TagItem, TagLink, TagNavLabels, VersionPageEntry, VersionsPageLabels, } from "./types.js";
@@ -6,12 +6,14 @@ import { DocsSitemap } from "./docs-sitemap.js";
6
6
  import { NavCardGrid } from "./nav-card-grid.js";
7
7
  import { DocCardGrid } from "./doc-card-grid.js";
8
8
  import { VersionsPageContent } from "./versions-page-content.js";
9
+ import { NoteTrayIndex } from "./note-tray-index.js";
9
10
  export {
10
11
  CategoryNav,
11
12
  CategoryTreeNav,
12
13
  DocCardGrid,
13
14
  DocsSitemap,
14
15
  NavCardGrid,
16
+ NoteTrayIndex,
15
17
  SiteTreeNavDemo,
16
18
  TagNav,
17
19
  VersionsPageContent