@takazudo/zudo-doc 2.2.2 → 2.4.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.
@@ -1,28 +1,7 @@
1
- import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
2
- import { Island } from "@takazudo/zfb";
3
- import { DocLayoutWithDefaults } from "../doclayout/index.js";
4
- import { SiteTreeNav } from "../site-tree-nav-island/index.js";
5
- import { loadCategoryMeta } from "../sidebar-tree/index.js";
6
- import {
7
- settings,
8
- getLocaleConfig,
9
- t,
10
- withBase,
11
- resolveNavSource,
12
- buildNavTree,
13
- groupSatelliteNodes,
14
- getCategoryOrder,
15
- collectTags,
16
- docsUrl,
17
- toRouteSlug
18
- } from "./_context.js";
19
- import {
20
- HeadWithDefaults,
21
- HeaderWithDefaults,
22
- FooterWithDefaults,
23
- BodyEndIslands,
24
- composeMetaTitle
25
- } from "./_chrome.js";
1
+ import { jsx } from "preact/jsx-runtime";
2
+ import { settings, routeCtx } from "./_context.js";
3
+ import { prepareHomeData } from "../home-page/prepare-home-data.js";
4
+ import { HomePageView } from "./_chrome.js";
26
5
  const frontmatter = { title: "Home" };
27
6
  function paths() {
28
7
  return Object.keys(settings.locales).map((locale) => ({
@@ -32,98 +11,8 @@ function paths() {
32
11
  }
33
12
  function LocaleIndexPage({ params }) {
34
13
  const locale = params.locale;
35
- const cfg = getLocaleConfig(locale);
36
- if (!cfg) {
37
- throw new Error(`LocaleIndexPage: locale "${locale}" is not configured in settings.locales`);
38
- }
39
- const { navDocs } = resolveNavSource(locale, void 0, {
40
- applyDefaultLocaleOnlyFilter: true,
41
- keepUnlisted: true
42
- });
43
- const categoryMeta = loadCategoryMeta(cfg.dir);
44
- const tree = buildNavTree(navDocs, locale, categoryMeta, (slug, loc) => docsUrl(slug, loc));
45
- const categoryOrder = getCategoryOrder();
46
- const groupedTree = groupSatelliteNodes(tree, categoryOrder);
47
- const tagCount = collectTags(
48
- navDocs.filter((d) => !d.data.category_no_page),
49
- (id, data) => data.slug ?? toRouteSlug(id)
50
- ).size;
51
- const ctaNav = settings.headerNav[0] ?? null;
52
- const overview = ctaNav ? withBase(`/${locale}${ctaNav.path}`) : null;
53
- const logoUrl = withBase("/img/logo.svg");
54
- return /* @__PURE__ */ jsxs(
55
- DocLayoutWithDefaults,
56
- {
57
- title: composeMetaTitle(settings.siteName),
58
- head: /* @__PURE__ */ jsx(HeadWithDefaults, { title: settings.siteName }),
59
- lang: locale,
60
- noindex: settings.noindex,
61
- hideSidebar: true,
62
- hideToc: true,
63
- sidebarOverride: /* @__PURE__ */ jsx(Fragment, {}),
64
- headerOverride: /* @__PURE__ */ jsx(HeaderWithDefaults, { lang: locale, currentPath: withBase(`/${locale}/`) }),
65
- footerOverride: /* @__PURE__ */ jsx(FooterWithDefaults, { lang: locale }),
66
- bodyEndComponents: /* @__PURE__ */ jsx(BodyEndIslands, { basePath: settings.base ?? "/" }),
67
- enableClientRouter: settings.dynamicPageTransition,
68
- children: [
69
- /* @__PURE__ */ jsx("div", { class: "flex justify-center mb-vsp-xl", children: /* @__PURE__ */ jsxs("div", { class: "flex flex-col items-center text-center gap-hsp-md lg:flex-row lg:text-left lg:gap-hsp-xl", children: [
70
- /* @__PURE__ */ jsx(
71
- "div",
72
- {
73
- class: "w-[320px] max-w-full aspect-[1200/630] bg-fg shrink-0",
74
- style: {
75
- WebkitMask: `url(${logoUrl}) center/contain no-repeat`,
76
- mask: `url(${logoUrl}) center/contain no-repeat`
77
- },
78
- "aria-hidden": "true"
79
- }
80
- ),
81
- /* @__PURE__ */ jsxs("div", { children: [
82
- /* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-2xs", children: settings.siteName }),
83
- /* @__PURE__ */ jsx("p", { class: "text-muted text-small mb-vsp-sm", children: settings.siteDescription }),
84
- /* @__PURE__ */ jsxs("div", { class: "flex items-center justify-center lg:justify-start gap-hsp-md text-small", children: [
85
- overview && /* @__PURE__ */ jsxs(Fragment, { children: [
86
- /* @__PURE__ */ jsx("a", { href: overview, class: "text-fg underline hover:text-accent", children: t("nav.overview", locale) }),
87
- /* @__PURE__ */ jsx("span", { class: "text-muted", children: "/" })
88
- ] }),
89
- settings.githubUrl && /* @__PURE__ */ jsx(
90
- "a",
91
- {
92
- href: settings.githubUrl,
93
- class: "inline-flex items-center gap-[0.3em] text-fg underline hover:text-accent",
94
- target: "_blank",
95
- rel: "noopener noreferrer",
96
- children: "GitHub"
97
- }
98
- )
99
- ] })
100
- ] })
101
- ] }) }),
102
- Island({
103
- when: "idle",
104
- children: /* @__PURE__ */ jsx(
105
- SiteTreeNav,
106
- {
107
- tree: groupedTree,
108
- categoryOrder,
109
- categoryIgnore: ["inbox", "develop"]
110
- }
111
- )
112
- }),
113
- settings.docTags && tagCount > 0 && /* @__PURE__ */ jsxs("section", { class: "mt-vsp-xl", children: [
114
- /* @__PURE__ */ jsx("h2", { class: "text-title font-bold mb-vsp-md", children: t("doc.allTags", locale) }),
115
- /* @__PURE__ */ jsx(
116
- "a",
117
- {
118
- href: withBase(`/${locale}/docs/tags`),
119
- class: "text-accent underline hover:text-accent-hover",
120
- children: t("doc.allTags", locale)
121
- }
122
- )
123
- ] })
124
- ]
125
- }
126
- );
14
+ const { tree, categoryOrder, tagCount } = prepareHomeData(routeCtx, locale);
15
+ return /* @__PURE__ */ jsx(HomePageView, { locale, tree, categoryOrder, tagCount });
127
16
  }
128
17
  export {
129
18
  LocaleIndexPage as default,
package/dist/safelist.css CHANGED
@@ -1,2 +1,2 @@
1
1
  /* generated by gen-safelist.mjs — do not edit by hand */
2
- @source inline("-link -mb-px -ml-hsp-sm -noscript -translate-x-full 2xl:w-[24px] [&::-webkit-details-marker]:hidden [&_a]:text-accent [&_a]:underline [&_nav]:mb-0 [data-admonition] [data-kbd-shortcut] [doc-history-meta] [doc-history] [doc-layout] [llms-txt] a a2 abbr about above absent absolute accent across activated active actual added admonition admonition- admonition-body admonition-title admonition/callout after after-breadcrumb after-content after-navigate after-sidebar after-title against agent agents ai-chat ai-chat-md ai-chat-trigger alert align-top all allow-same-origin allow-scripts alone already already-executed already-picked an anchor anchored and and/or animate-spin announce antialiased any application/json application/xml applies apply-css-vars approach are area arg aria-atomic aria-busy aria-controls aria-current aria-disabled aria-expanded aria-haspopup aria-hidden aria-label aria-live aria-orientation aria-pressed aria-selected aria-valuemax aria-valuemin aria-valuenow arm arms arrive arrows article as asc aside aspect-[1200/630] aspect-square asset- assets assistant async at attach attribute attributes auto autogenerated available avoid await away b back backdrop:bg-bg/30 backdrop:bg-bg/80 backdrop:bg-overlay/60 backdrop:z-modal-backdrop background background-color backtick backticks baked banner bare base base64 based batch be because before below best between bg bg-[#fff] bg-accent bg-bg bg-chat-assistant-bg bg-chat-user-bg bg-code-bg bg-fg bg-info/10 bg-info/5 bg-muted bg-overlay/30 bg-surface bg-transparent bg-warning/10 bg-warning/5 bi bigint bin blank blanks blob block blockquote blocks blur body body-end-components body-end-scripts bold boolean bootstrap border border-accent border-b border-b-2 border-b-[5px] border-bg/30 border-collapse border-danger border-dashed border-fg border-info/30 border-l border-l-0 border-l-[3px] border-left-width border-muted border-none border-r border-radius border-solid border-t border-t-[2px] border-t-[3px] border-transparent border-warning/30 border-width border-y both bottom-vsp-xl box-border br brand breadcrumb:end breadcrumb:start break-words browser browsers btn bug build bundler but button buttons by bypassed byte-identical bytes cached call caller calls can cancellation cannot canonical canvas caption card card-grid carry cases cat-nav- catch category catppuccin-latte caught caution center center/contain ch chains change changed changes checkbox child chrome ci circle cite class class-less claude claude-agents claude-commands claude-md claude-skills cleaned clear clear-css-vars clearing click client client-router client-side clip clobbering close closed closing code code-block-sr-announce code-group code-group-panel col col-resize colgroup collision color color-scheme color-scheme-changed color-scheme-provider color-tweak colorization colors column comma command commands commit compare component component:github-link component:language-switcher component:search component:theme-toggle component:version-switcher compute computed concrete configuration configure configured confuse connect const consumer consumes container containers containing content content-admonition content-link content-type content-wrapper:end content-wrapper:start contents context controller controls converts copy corners correct correctly corrupt count covered covers cp crashes created cross-component crumb- cs css ctx cur current cursor cursor-not-allowed cursor-pointer custom danger dark data data-active data-admonition data-base data-close-search data-group-id data-header data-header-logo data-header-nav data-header-right data-kbd-shortcut data-loading-index data-mermaid-enlarge-ready data-mermaid-rendered data-mermaid-src data-nav-active data-nav-item data-nav-item-dropdown data-nav-more data-nav-more-menu data-nav-more-toggle data-no-results data-open-search data-pan-active data-processed data-result-count-template data-search-count data-search-count-narrow data-search-dialog data-search-input data-search-placeholder data-search-results data-search-unavailable data-sidebar-hidden data-sidebar-resizer data-site-nav data-tab-btn data-tab-default data-tab-label data-tab-value data-tabs data-taglist-group data-testid data-theme data-theme/style data-variant data-version-banner data-version-menu data-version-switcher data-version-toggle data-zd-nosidebar data-zd-toc data-zfb-transition-persist dd debounced decimal declare decoration decoration-muted default defaults del delegated dependency depth desc description design design-token-panel design-token-trigger desktop desktop-sidebar desktop-sidebar-toggle desktop-sidebar-toggle-island destructive detach detached details deterministic develop dfn diagram diagrams dialog dieser diff diff-line-added diff-line-content diff-line-empty diff-line-num diff-line-removed diff-row dir directly directories directory disabled disabled:opacity-50 disc display:none dist distinct div dl do doc doc-card- doc-content-band doc-history doc-history-generate doc-history-panel doc-history-trigger doc-page doc-pager doc-prose doc-title docs docs- docs-v- document document-level documented does double-registration drag draggable drop dropdown dropdown-child dropdown-parent dropdowns dt duration-150 duration-200 during dynamically e e2e each ease-in-out edge eject ejected el element elements els else em emit emitting empty empty/undefined en enable end enlarged entire entities entries entry error escape escaped even eventually every exactly excerpt excludes existing exists exit expected export extends factories failed fall fallback fallbacks falls false family fast feature feed fg fields fieldset figcaption figure file fill fills finally find fire fires first fixed fixed-width fixtures flag flash flat flex flex-1 flex-col flex-wrap flip flipping flips flush-left focus focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2 focus-visible:underline focus:border-accent focus:outline-none focus:underline font font-bold font-family font-medium font-mono font-semibold font-weight footer footer- for form found free fresh from frontmatter frontmatter-preview frozen fs-extra full fully function further g gap-[0.3em] gap-[clamp(1.5rem,3vw,4rem)] gap-hsp-2xs gap-hsp-md gap-hsp-sm gap-hsp-xl gap-hsp-xs gap-vsp-2xs gap-vsp-lg gap-vsp-md gap-vsp-xs gap-x-hsp-2xs gap-x-hsp-lg gap-x-hsp-md gap-x-hsp-sm gap-x-hsp-xs gap-y-vsp-2xs gap-y-vsp-lg gap-y-vsp-md gap-y-vsp-xs gaps gate generate generation genuine geometry get github github-link go got graph gray-matter grid grid-cols-1 grid-cols-2 group group-focus-visible:text-accent group-focus-visible:underline group-focus-within:block group-hover:bg-fg group-hover:block group-hover:text-accent group-hover:text-bg group-hover:underline group-open:rotate-90 guard h-[0.5rem] h-[0.625rem] h-[0.875rem] h-[1.125rem] h-[1.25rem] h-[1.575rem] h-[14px] h-[1lh] h-[2rem] h-[3.5rem] h-[3rem] h-[90vh] h-[calc(100%-3rem)] h-[calc(100vh-3.5rem)] h-dvh h-full h-icon-lg h-icon-md h-icon-sm h-icon-xs h1 h1s h2 h2s h3 h4 h5 h6 half hand handle handled handler handlers has hash-link have head head-links head-scripts header header- header-call:end header-call:start heading heading-h2 heading-h3 heading-h4 headings height here hex hidden highlight highlighter history hit horizontal host hover:bg-[color-mix(in_srgb,var(--color-surface)_80%,var(--color-fg)_20%)] hover:bg-accent-hover hover:bg-accent/10 hover:bg-surface hover:border-accent hover:border-accent-hover hover:border-fg hover:text-accent hover:text-accent-hover hover:text-fg hover:underline hr href hrefs html i i18n/theme. i2 i3 i4 icon identical idle idx if iframe image image-enlarge image/png img import important imports in inactive inbox includes index index-load info inherit inherited initial initialised injected inline inline-block inline-flex inner input input-clear ins inset-0 inside install instance instanceof instead instructions intended intent into invalid inverse inversion invoke is issues it italic item item- items items-baseline items-center items-start its itself javascript justify-between justify-center justify-end justify-start katex kbd keep keeps kept keyboard keyboard-shortcut keydown keystroke keywords khroma label landing language-switcher last:border-b-0 later launch layout leading-relaxed leading-snug leading-tight leaf- leak leaves leaving left left-0 left:calc legend legitimate letter-spacing lg lg:block lg:border lg:border-fg lg:border-solid lg:flex lg:flex-col lg:flex-row lg:gap-hsp-xl lg:grid-cols-[repeat(auto-fit,minmax(12rem,1fr))] lg:h-[90vh] lg:hidden lg:justify-start lg:m-auto lg:max-h-[90vh] lg:max-w-[52.5rem] lg:ml-[var(--zd-sidebar-w)] lg:pt-vsp-2xl lg:px-hsp-2xl lg:py-vsp-2xl lg:text-left lg:w-[90vw] lg:w-[clamp(16rem,25%,22rem)] li li2 library light like likely line linger link link- links list-disc list-none listener literal literals lives llms llms-txt load local locale locales log longest-match look lostpointercapture lower lowercased luminance m m-0 m21 m6 main make malformed malicious maps mark marks matches matching math math-display math-inline max max-h-[80vh] max-h-[85vh] max-h-[90vh] max-h-full max-h-none max-w-[46rem] max-w-[85%] max-w-[85vw] max-w-[90vw] max-w-[clamp(50rem,75vw,90rem)] max-w-full max-w-none max-width may mb-0 mb-vsp-2xs mb-vsp-lg mb-vsp-md mb-vsp-sm mb-vsp-xl mb-vsp-xs measures measuring mechanism menu merged mermaid message messages meta meta-knob metadata migration min-h-[60vh] min-h-[calc(100vh-3.5rem)] min-h-screen min-w-0 min-w-[10rem] min-w-[8rem] mirror mirrors missing ml-auto ml-hsp-2xl ml-hsp-lg ml-hsp-sm ml-hsp-xl mod mode mounted mouseenter mouseleave mr-hsp-sm mt-0 mt-vsp-2xl mt-vsp-2xs mt-vsp-3xs mt-vsp-lg mt-vsp-md mt-vsp-sm mt-vsp-xl must mutates mutation mutations mx-auto my-vsp-lg my-vsp-md name native nav nav-active nav-card- nav/doc navigating navigation navigations near needs nested new newly-swapped next no no-enlarge no-op no-repeat no-underline node node:buffer node:fs node:fs/promises node:module node:path nodes nofollow noindex non-empty non-light-dark non-persisted non-string none noopener noreferrer normal noscript not not-object note now null number numeric object object-contain observe observer occurred of off og:description og:image og:image:alt og:image:height og:image:width og:title og:type og:url ol old older on once one only onto opacity-60 open open/close option or original other others out outline-none over overflow overflow-auto overflow-hidden overflow-x-auto overflow-y-auto overflow-y:auto overwrite own p p-0 p-hsp-lg p-hsp-md p-hsp-sm p-hsp-xl package package-owned packages padding page page-loading page-loading-overlay page-loading-spinner page-navigate-end page-title pages pages/. paint paint-and-read pan panel panels paren-balance-aware parent parse parsed pass passed passes path paths pattern pb-[50vh] pb-vsp-md pb-vsp-xl pb-vsp-xs per per-call per-link permanently persisted pi pick picked picks picocolors pins pipelines pl-[1.25rem] pl-hsp-lg pl-hsp-sm pl-hsp-xl place placeholder placeholder:text-muted plain plural plus pnpm pointer-events-none pointercancel pointerdown pointermove pointerup polite polygon polyline populates port position position:fixed pr-[4px] pr-hsp-lg pr-hsp-md pr-hsp-sm pr-hsp-xl pre pre-lowercased preact preact/compat preact/hooks preact/jsx-runtime preconnect prefix preload pres preserved preserving print produce produced produces production project properties property props prose provided proxy pt-[0.15rem] pt-[2px] pt-vsp-3xs pt-vsp-md pt-vsp-sm pt-vsp-xl pt-vsp-xs ptag- public purely px px-hsp-2xl px-hsp-2xs px-hsp-lg px-hsp-md px-hsp-sm px-hsp-xl px-hsp-xs py-0 py-[2px] py-[calc(var(--spacing-vsp-xs)+0.15rem)] py-hsp-2xs py-hsp-sm py-hsp-xs py-vsp-2xs py-vsp-3xs py-vsp-lg py-vsp-md py-vsp-sm py-vsp-xl py-vsp-xs q query question r radius raw re-encode/decode re-lowercase re-querying re-render re-renders re-run re-running re-selects reach reached reaches read reading reads ready real real-value received receives recorded recovers redefine ref- references refetch refreshes regenerate regenerates regex reinit reinits relative reload remove removed render rendered renders reorder repaint repeated repeating replaced replaces repopulate requires reserved resize resize-x resolve resolved resolves response restore restores result result-click results results-area retry return returns revision revisions rewrite right right- right-0 ro robots role root rotate-180 rotate-90 round round-trip rounded rounded-[0.75rem] rounded-bl-[0.25rem] rounded-bl-[1rem] rounded-br-[0.25rem] rounded-br-[1rem] rounded-full rounded-lg rounded-t-[1rem] rounds route router routes routes-src running runs runtime s safe safer same same-locale samp scale scanned schema-mismatch schema-missing scheme score scored script script- script-eval script-evaluation script-injection scripts scroll scrollbar scrolled scrollend search search-index searched section section- see sel-bg sel-fg select select-none self self-start semibold sentinel separator serialised serialize server server-rendered set sets setting setup shadow-[0_1px_3px_color-mix(in_srgb,var(--color-fg)_8%,transparent)] shadow-lg shape share shared sharing shiki ship ships short shortcut should show shown shrink-0 sidebar sidebar- sidebar-toggle-island sidebar-tree-island signal similarity single singular site-search site-tree-nav-island sitemap- sites size skill skills skipping skips slash slug sm:border sm:border-muted sm:flex-row sm:grid-cols-2 sm:h-auto sm:items-center sm:justify-between sm:max-h-[80vh] sm:max-w-[52rem] sm:mx-auto sm:my-[10vh] sm:rounded-lg small smooth snapshot snapshots so soft soft-nav solid some somehow source sources space-y-vsp-2xs spacing span spans spec specifier specifiers splitter square sr-only src stale start state status stay sticky still stop stored stray string strings strip stripe stroke-linecap stroke-linejoin stroke-width strong stronger style style-attribute styled styles stylesheet sub subagents subsequent success successful summary sup surfaces survives svg swap swapped swaps synchronous synchronously syntactically syntect t tab tab-item tab-panel tabindex table tablist tabpanel tabs tabs-container tabs-content tabs-nav tag tag- tag-item- tags tags:audit take tbody td temp-element template temporary temporary-element terminal terms test-results text text-accent text-bg text-body text-caption text-center text-chat-assistant-text text-chat-user-text text-code-fg text-danger text-decoration text-display text-fg text-heading text-info text-left text-micro text-muted text-muted/50 text-right text-small text-title text-warning text/plain textarea tfoot th that the thead their them theme theme-color theme-toggle theme/token then there these they this through throw tighten time tip title to toc toggle toggle-ai-chat toggle-design-token-panel toggles token tokens tolerates too toolbar top-0 top-[3.5rem] top-full top-level total touches tp tr tracked tracking-wider trade-off transition transition-[background,color,border-color] transition-[left,color] transition-colors transition-transform translate-x-0 translations transparent treats tree tree-child- tree-item- tree-top- trigger trigger:ai-chat trigger:design-token-panel triggers true truncate truncated try turn twitter:card twitter:creator twitter:description twitter:image twitter:site twitter:title two type typography u ul unavailable unchanged undefined under underline underlines understand unknown unmaintained unobserve unreadable unrelated unreleased unset unsupported up uppercase usage use used user uses utf-8 utf8 utilities utility v v2 val value value-reader values var variable variant version version- version-menu version-switcher vertical via video viewport virtual:zudo-doc-route-context visible vitesse-dark vocabulary w w-1/2 w-[0.5rem] w-[0.625rem] w-[0.875rem] w-[1.125rem] w-[1.575rem] w-[1.5rem] w-[1.75rem] w-[14px] w-[16px] w-[16rem] w-[18px] w-[280px] w-[2rem] w-[320px] w-[90vw] w-[var(--zd-sidebar-w)] w-dvw w-full w-icon-lg w-icon-md w-icon-sm w-icon-xs want warning was watching wbr wbr- we website weight went were what when where whereas whether which while whitespace-nowrap whitespace-pre whole wide-gamut width will with without word working worktrees would wrap wrapper wrappers written wrong wrote xl:flex xl:hidden y-scrollbar yet you your z-dropdown z-local-1 z-modal z-modal-backdrop z-sidebar z-toolbar zd-content zd-desktop-sidebar-toggle zd-doc-content-band zd-enlarge-btn zd-enlarge-dialog zd-enlarge-dialog-close zd-enlargeable zd-html-preview-code zd-mermaid-dialog zd-mermaid-enlargeable zd-mermaid-tool-btn zd-mermaid-toolbar zd-mermaid-transform zd-mermaid-viewport zd-sidebar-content-wrapper zd-sidebar-open zfb zfb:after-swap zfb:before-preparation zod zoom zudo-doc-design-tokens/v1 zudo-doc-sidebar-visible zudo-doc-sidebar-width zudo-doc-theme zudo-doc-theme-bridge");
2
+ @source inline("-link -mb-px -ml-hsp-sm -noscript -translate-x-full 2xl:w-[24px] [&::-webkit-details-marker]:hidden [&_a]:text-accent [&_a]:underline [&_nav]:mb-0 [data-admonition] [data-kbd-shortcut] [doc-history-meta] [doc-history] [doc-layout] [llms-txt] a a2 abbr about above absent absolute accent across activated active actual added admonition admonition- admonition-body admonition-title admonition/callout after after-breadcrumb after-content after-navigate after-sidebar after-title against agent agents ai-chat ai-chat-md ai-chat-trigger alert align-top all allow-same-origin allow-scripts alone already already-executed already-picked an anchor anchored and and/or animate-spin announce antialiased any application/json application/xml applies apply-css-vars approach are area arg aria-atomic aria-busy aria-controls aria-current aria-disabled aria-expanded aria-haspopup aria-hidden aria-label aria-live aria-orientation aria-pressed aria-selected aria-valuemax aria-valuemin aria-valuenow arm arms arrows article as asc aside aspect-[1200/630] aspect-square asset- assets assistant async at attach attribute attributes auto autogenerated available avoid await away b back backdrop:bg-bg/30 backdrop:bg-bg/80 backdrop:bg-overlay/60 backdrop:z-modal-backdrop background background-color backtick backticks baked banner bare base base64 based batch be because before below best between bg bg-[#fff] bg-accent bg-bg bg-chat-assistant-bg bg-chat-user-bg bg-code-bg bg-fg bg-info/10 bg-info/5 bg-muted bg-overlay/30 bg-surface bg-transparent bg-warning/10 bg-warning/5 bi bigint bin blank blanks blob block blockquote blocks blur body body-end-components body-end-scripts bold boolean bootstrap border border-accent border-b border-b-2 border-b-[5px] border-bg/30 border-collapse border-danger border-dashed border-fg border-info/30 border-l border-l-0 border-l-[3px] border-left-width border-muted border-none border-r border-radius border-solid border-t border-t-[2px] border-t-[3px] border-transparent border-warning/30 border-width border-y both bottom-vsp-xl box-border br brand breadcrumb:end breadcrumb:start break-words browser browsers btn bug build bundler but button buttons by bypassed byte-identical bytes cached call caller calls can cancellation cannot canonical canvas caption card card-grid carry cases cat-nav- catch category catppuccin-latte caught caution center center/contain ch chains change changed changes checkbox child chrome ci circle cite class class-less claude claude-agents claude-commands claude-md claude-skills cleaned clear clear-css-vars clearing click client client-router client-side clip clobbering close closed closing code code-block-sr-announce code-group code-group-panel col col-resize colgroup collision color color-scheme color-scheme-changed color-scheme-provider color-tweak colorization colors column comma command commands comment commit compare component component:github-link component:language-switcher component:search component:theme-toggle component:version-switcher compute computed concrete configuration configure configured confuse connect const construction consumer consumes container containers containing content content-admonition content-link content-type content-wrapper:end content-wrapper:start contents context controller controls converts copy corners correct correctly corrupt count covered covers cp crashes created cross-component crumb- cs css ctx cur current cursor cursor-not-allowed cursor-pointer custom danger dark data data-active data-admonition data-base data-close-search data-group-id data-header data-header-logo data-header-nav data-header-right data-kbd-shortcut data-loading-index data-mermaid-enlarge-ready data-mermaid-rendered data-mermaid-src data-nav-active data-nav-item data-nav-item-dropdown data-nav-more data-nav-more-menu data-nav-more-toggle data-no-results data-open-search data-pan-active data-processed data-result-count-template data-search-count data-search-count-narrow data-search-dialog data-search-input data-search-placeholder data-search-results data-search-unavailable data-sidebar-hidden data-sidebar-resizer data-site-nav data-tab-btn data-tab-default data-tab-label data-tab-value data-tabs data-taglist-group data-testid data-theme data-theme/style data-variant data-version-banner data-version-menu data-version-switcher data-version-toggle data-zd-nosidebar data-zd-toc data-zfb-transition-persist dd decimal declare decoration decoration-muted default defaults del delegated dependency depth desc description design design-token-panel design-token-trigger desktop desktop-sidebar desktop-sidebar-toggle desktop-sidebar-toggle-island destructive detach detached details deterministic develop dfn diagram diagrams dialog dieser diff diff-line-added diff-line-content diff-line-empty diff-line-num diff-line-removed diff-row dir directly directories directory disabled disabled:opacity-50 disc display:none dist distinct div dl do doc doc-card- doc-content-band doc-history doc-history-generate doc-history-panel doc-history-trigger doc-page doc-pager doc-prose doc-title docs docs- docs-v- document document-level documented does double-registration drag draggable drop dropdown dropdown-child dropdown-parent dropdowns dt duration-150 duration-200 during dynamically e2e each ease-in-out edge eject ejected el element elements els else em embedded emit emitting empty empty/undefined en enable end enlarged entire entities entries entry error escape escaped even eventually every exactly excerpt excludes existing exists exit expected export extends factories failed fall fallback fallbacks falls false family fast feature feed fg fields fieldset figcaption figure file fill fills finally find fire fires first fixed fixed-width fixtures flag flash flat flex flex-1 flex-col flex-wrap flip flipping flips flush-left focus focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2 focus-visible:underline focus:border-accent focus:outline-none focus:underline font font-bold font-family font-medium font-mono font-semibold font-weight footer footer- for form found free fresh from frontmatter frontmatter-preview frozen fs-extra full fully function further g gap-[0.3em] gap-[clamp(1.5rem,3vw,4rem)] gap-hsp-2xs gap-hsp-md gap-hsp-sm gap-hsp-xl gap-hsp-xs gap-vsp-2xs gap-vsp-lg gap-vsp-md gap-vsp-xs gap-x-hsp-2xs gap-x-hsp-lg gap-x-hsp-md gap-x-hsp-sm gap-x-hsp-xs gap-y-vsp-2xs gap-y-vsp-lg gap-y-vsp-md gap-y-vsp-xs gaps gate generate generation genuine geometry get github github-link go got graph gray-matter grid grid-cols-1 grid-cols-2 group group-focus-visible:text-accent group-focus-visible:underline group-focus-within:block group-hover:bg-fg group-hover:block group-hover:text-accent group-hover:text-bg group-hover:underline group-open:rotate-90 guard h-[0.5rem] h-[0.625rem] h-[0.875rem] h-[1.125rem] h-[1.25rem] h-[1.575rem] h-[14px] h-[1em] h-[1lh] h-[2rem] h-[3.5rem] h-[3rem] h-[90vh] h-[calc(100%-3rem)] h-[calc(100vh-3.5rem)] h-dvh h-full h-icon-lg h-icon-md h-icon-sm h-icon-xs h1 h1s h2 h2s h3 h4 h5 h6 half hand hand-copied handle handled handler handlers has hash-link have head head-links head-scripts header header- header-call:end header-call:start heading heading-h2 heading-h3 heading-h4 headings height here hex hidden highlight highlighter history hit horizontal host hover:bg-[color-mix(in_srgb,var(--color-surface)_80%,var(--color-fg)_20%)] hover:bg-accent-hover hover:bg-accent/10 hover:bg-surface hover:border-accent hover:border-accent-hover hover:border-fg hover:text-accent hover:text-accent-hover hover:text-fg hover:underline hr href hrefs html i i18n/theme. i2 i3 i4 icon identical idle idx if iframe image image-enlarge image/png img implementation import important imports in inactive inbox includes index info inherit inherited initial initialised injected inline inline-block inline-flex inner input input-clear ins inset-0 inside install instance instanceof instead instructions intended intent into invalid inverse inversion invoke is issues it italic item item- items items-baseline items-center items-start its itself javascript justify-between justify-center justify-end justify-start katex kbd keep keeps kept keyboard keyboard-shortcut keydown keystroke keywords khroma label landing language-switcher last:border-b-0 later launch layout leading-relaxed leading-snug leading-tight leaf- leak leaves leaving left left-0 left:calc legend legitimate letter-spacing lg lg:block lg:border lg:border-fg lg:border-solid lg:flex lg:flex-col lg:flex-row lg:gap-hsp-xl lg:grid-cols-[repeat(auto-fit,minmax(12rem,1fr))] lg:h-[90vh] lg:hidden lg:justify-start lg:m-auto lg:max-h-[90vh] lg:max-w-[52.5rem] lg:ml-[var(--zd-sidebar-w)] lg:pt-vsp-2xl lg:px-hsp-2xl lg:py-vsp-2xl lg:text-left lg:w-[90vw] lg:w-[clamp(16rem,25%,22rem)] li li2 library light like likely line linger link link- links list-disc list-none listener literal literals lives llms llms-txt load local locale locales log longest-match look lostpointercapture lower luminance m m-0 m21 m6 main make malformed malicious maps mark marks matches matching math math-display math-inline max max-h-[80vh] max-h-[85vh] max-h-[90vh] max-h-full max-h-none max-w-[46rem] max-w-[85%] max-w-[85vw] max-w-[90vw] max-w-[clamp(50rem,75vw,90rem)] max-w-full max-w-none max-width may mb-0 mb-vsp-2xs mb-vsp-lg mb-vsp-md mb-vsp-sm mb-vsp-xl mb-vsp-xs measures measuring mechanism menu merged mermaid message messages meta meta-knob metadata migration min-h-[60vh] min-h-[calc(100vh-3.5rem)] min-h-screen min-w-0 min-w-[10rem] min-w-[8rem] mirror mirrors missing ml-auto ml-hsp-2xl ml-hsp-lg ml-hsp-sm ml-hsp-xl mod mode module mounted mouseenter mouseleave mr-hsp-sm mt-0 mt-vsp-2xl mt-vsp-2xs mt-vsp-3xs mt-vsp-lg mt-vsp-md mt-vsp-sm mt-vsp-xl must mutates mutation mutations mx-auto my-vsp-lg my-vsp-md name named native nav nav-active nav-card- nav/doc navigating navigation navigations near needs nested new newly-swapped next no no-enlarge no-op no-repeat no-underline node node:buffer node:fs node:fs/promises node:module node:path nodes nofollow noindex non-empty non-light-dark non-persisted non-string none noopener noreferrer normal noscript not not-object note now null number numeric object object-contain observe observer occurred of off og:description og:image og:image:alt og:image:height og:image:width og:title og:type og:url ol old older on once one only onto opacity-60 open open/close option or original other others out outline-none over overflow overflow-auto overflow-hidden overflow-x-auto overflow-y-auto overflow-y:auto overwrite own p p-0 p-hsp-lg p-hsp-md p-hsp-sm p-hsp-xl package package-owned packages padding page page-loading page-loading-overlay page-loading-spinner page-navigate-end page-title pages pages/. paint paint-and-read pan panel panels paren-balance-aware parent parse parsed pass passed passes path paths pattern pb-[50vh] pb-vsp-md pb-vsp-xl pb-vsp-xs per per-link permanently persisted pi pick picked picks picocolors pins pipelines pl-[1.25rem] pl-hsp-lg pl-hsp-sm pl-hsp-xl place placeholder placeholder:text-muted plain plural plus pnpm pointer-events-none pointercancel pointerdown pointermove pointerup polite polygon polyline populates port position position:fixed pr-[4px] pr-hsp-lg pr-hsp-md pr-hsp-sm pr-hsp-xl pre pre-lowercased preact preact/compat preact/hooks preact/jsx-runtime preconnect prefix preload pres preserving print produce produced produces production project project-root-relative properties property props prose provided proxy pt-[0.15rem] pt-[2px] pt-vsp-3xs pt-vsp-md pt-vsp-sm pt-vsp-xl pt-vsp-xs ptag- public purely px px-hsp-2xl px-hsp-2xs px-hsp-lg px-hsp-md px-hsp-sm px-hsp-xl px-hsp-xs py-0 py-[2px] py-[calc(var(--spacing-vsp-xs)+0.15rem)] py-hsp-2xs py-hsp-sm py-hsp-xs py-vsp-2xs py-vsp-3xs py-vsp-lg py-vsp-md py-vsp-sm py-vsp-xl py-vsp-xs q query question r radius raw re-encode/decode re-querying re-render re-renders re-run re-running re-selects reach reached reaches read reading ready real real-value received receives recorded recovers redefine ref- references refetch refreshes regenerate regenerates regex reinit reinits relative reload relying remove removed render rendered renders reorder repaint repeated repeating replaced replaces repopulate requires reserved resize resize-x resolve resolved resolves response restore restores result result-click results results-area retry return returns revision revisions rewrite right right- right-0 ro robots role root rotate-180 rotate-90 round round-trip rounded rounded-[0.75rem] rounded-bl-[0.25rem] rounded-bl-[1rem] rounded-br-[0.25rem] rounded-br-[1rem] rounded-full rounded-lg rounded-t-[1rem] rounds route router routes routes-src running runs runtime s safe safer same same-locale samp scale scanned schema-mismatch schema-missing scheme scored script script- script-eval script-evaluation script-injection scripts scroll scrollbar scrolled scrollend search search-index section section- see sel-bg sel-fg select select-none self self-start semibold sentinel separator serialised serialize server server-rendered set sets setting setup shadow-[0_1px_3px_color-mix(in_srgb,var(--color-fg)_8%,transparent)] shadow-lg shape share shared sharing shiki ship shipped ships short shortcut should show shown shrink-0 sidebar sidebar- sidebar-toggle-island sidebar-tree-island signal similarity single singular site-search site-tree-nav-island sitemap- sites size skill skills skipping skips slash slug sm:border sm:border-muted sm:flex-row sm:grid-cols-2 sm:h-auto sm:items-center sm:justify-between sm:max-h-[80vh] sm:max-w-[52rem] sm:mx-auto sm:my-[10vh] sm:rounded-lg small smooth snapshot snapshots so soft soft-nav solid some somehow source sources space-y-vsp-2xs spacing span spans spec specifier specifiers splitter square sr-only src stale start state status stay staying sticky still stop stored stray string strings strip stripe stroke-linecap stroke-linejoin stroke-width strong stronger style style-attribute styled styles stylesheet sub subagents subsequent success successful summary sup surfaces survives svg swap swapped swaps synchronous synchronously syntactically syntect t tab tab-item tab-panel tabindex table tablist tabpanel tabs tabs-container tabs-content tabs-nav tag tag- tag-item- tags tags:audit take tbody td temp-element template temporary temporary-element terminal terms test-results tested text text-accent text-bg text-body text-caption text-center text-chat-assistant-text text-chat-user-text text-code-fg text-danger text-decoration text-display text-fg text-heading text-info text-left text-micro text-muted text-muted/50 text-right text-small text-title text-warning text/plain textarea tfoot th that the thead their them theme theme-color theme-toggle theme/token then there these they this through throw tighten time tip title to toc toggle toggle-ai-chat toggle-design-token-panel toggles token tokens tolerates too toolbar top-0 top-[3.5rem] top-full top-level total touches tp tr tracked tracking-wider trade-off transition transition-[background,color,border-color] transition-[left,color] transition-colors transition-transform translate-x-0 translations transparent treats tree tree-child- tree-item- tree-top- trigger trigger:ai-chat trigger:design-token-panel triggers true truncate truncated try turn twitter:card twitter:creator twitter:description twitter:image twitter:site twitter:title two type typography u ul unavailable unchanged undefined under underline underlines understand unit-tested unknown unmaintained unobserve unreadable unrelated unreleased unset unsupported up uppercase usage use used user uses utf-8 utf8 utilities utility v v2 val value value-reader values var variable variant verbatim version version- version-menu version-switcher vertical via video viewport virtual:zudo-doc-chrome-bindings virtual:zudo-doc-route-context visible vitesse-dark vocabulary w w-1/2 w-[0.5rem] w-[0.625rem] w-[0.875rem] w-[1.125rem] w-[1.575rem] w-[1.5rem] w-[1.75rem] w-[14px] w-[16px] w-[16rem] w-[18px] w-[1em] w-[280px] w-[2rem] w-[320px] w-[90vw] w-[var(--zd-sidebar-w)] w-dvw w-full w-icon-lg w-icon-md w-icon-sm w-icon-xs want warning was watching wbr wbr- we website weight went were what when where whereas whether which while whitespace-nowrap whitespace-pre whole wide-gamut width will with without word working worktrees would wrap wrapper wrappers written wrong wrote xl:flex xl:hidden y-scrollbar yet you your z-dropdown z-local-1 z-modal z-modal-backdrop z-sidebar z-toolbar zd-content zd-desktop-sidebar-toggle zd-doc-content-band zd-enlarge-btn zd-enlarge-dialog zd-enlarge-dialog-close zd-enlargeable zd-html-preview-code zd-mermaid-dialog zd-mermaid-enlargeable zd-mermaid-tool-btn zd-mermaid-toolbar zd-mermaid-transform zd-mermaid-viewport zd-sidebar-content-wrapper zd-sidebar-open zfb zfb:after-swap zfb:before-preparation zod zoom zudo-doc-design-tokens/v1 zudo-doc-sidebar-visible zudo-doc-sidebar-width zudo-doc-theme zudo-doc-theme-bridge");
@@ -1,4 +1,5 @@
1
1
  import { AFTER_NAVIGATE_EVENT } from "../transitions/index.js";
2
+ import { prepareLc, scoreEntry } from "./scoring.js";
2
3
  const SEARCH_WIDGET_SCRIPT = (
3
4
  /* javascript */
4
5
  `(function () {
@@ -36,34 +37,14 @@ const SEARCH_WIDGET_SCRIPT = (
36
37
  return query.trim().split(/\\s+/).filter(Boolean);
37
38
  }
38
39
 
39
- // scoreEntry reads pre-lowercased fields (_titleLc, _descLc, _bodyLc)
40
- // set by prepareLc() at index-load time. Terms arrive already lowercased
41
- // from search() so no per-call toLowerCase() is needed.
42
- function scoreEntry(entry, terms) {
43
- var score = 0;
44
- var titleLc = entry._titleLc;
45
- var descLc = entry._descLc;
46
- var bodyLc = entry._bodyLc;
47
- for (var i = 0; i < terms.length; i++) {
48
- var t = terms[i];
49
- if (titleLc.indexOf(t) !== -1) score += 3;
50
- if (descLc.indexOf(t) !== -1) score += 2;
51
- if (bodyLc.indexOf(t) !== -1) score += 1;
52
- }
53
- return score;
54
- }
40
+ // scoreEntry / prepareLc: embedded verbatim (via Function.prototype.toString())
41
+ // from the real, unit-tested implementation in ./scoring.ts \u2014 see the module
42
+ // header comment there and search-widget-script/__tests__/scoring.test.ts.
43
+ // This keeps the shipped inline script and the tested source identical by
44
+ // construction instead of relying on a hand-copied mirror staying in sync.
45
+ ${prepareLc.toString()}
55
46
 
56
- // Pre-lowercase the searched fields on each entry once at load time so that
57
- // scoreEntry() does not re-lowercase the entire ~162 KB index on every
58
- // debounced keystroke. Original-case fields are preserved for display.
59
- function prepareLc(entries) {
60
- for (var i = 0; i < entries.length; i++) {
61
- var e = entries[i];
62
- e._titleLc = (e.title || "").toLowerCase();
63
- e._descLc = (e.description || "").toLowerCase();
64
- e._bodyLc = (e.body || "").toLowerCase();
65
- }
66
- }
47
+ ${scoreEntry.toString()}
67
48
 
68
49
  function highlightTerms(text, terms) {
69
50
  if (!terms.length) return escapeHtml(text);
@@ -0,0 +1,16 @@
1
+ export interface SearchEntry {
2
+ title?: string;
3
+ description?: string;
4
+ body?: string;
5
+ _titleLc?: string;
6
+ _descLc?: string;
7
+ _bodyLc?: string;
8
+ }
9
+ /** An entry after prepareLc() has run — the three Lc fields are guaranteed present. */
10
+ export interface ScoredSearchEntry {
11
+ _titleLc: string;
12
+ _descLc: string;
13
+ _bodyLc: string;
14
+ }
15
+ export declare function prepareLc(entries: SearchEntry[]): void;
16
+ export declare function scoreEntry(entry: ScoredSearchEntry, terms: string[]): number;
@@ -0,0 +1,25 @@
1
+ function prepareLc(entries) {
2
+ for (var i = 0; i < entries.length; i++) {
3
+ var e = entries[i];
4
+ e._titleLc = (e.title || "").toLowerCase();
5
+ e._descLc = (e.description || "").toLowerCase();
6
+ e._bodyLc = (e.body || "").toLowerCase();
7
+ }
8
+ }
9
+ function scoreEntry(entry, terms) {
10
+ var score = 0;
11
+ var titleLc = entry._titleLc;
12
+ var descLc = entry._descLc;
13
+ var bodyLc = entry._bodyLc;
14
+ for (var i = 0; i < terms.length; i++) {
15
+ var t = terms[i];
16
+ if (titleLc.indexOf(t) !== -1) score += 3;
17
+ if (descLc.indexOf(t) !== -1) score += 2;
18
+ if (bodyLc.indexOf(t) !== -1) score += 1;
19
+ }
20
+ return score;
21
+ }
22
+ export {
23
+ prepareLc,
24
+ scoreEntry
25
+ };
@@ -323,4 +323,38 @@ export interface Settings {
323
323
  * flag a potentially-empty build (#2404). No throw — the build succeeds.
324
324
  */
325
325
  packageOwnedRoutes?: boolean;
326
+ /**
327
+ * Project-root-relative path to a host module that exports host-callable
328
+ * chrome bindings, e.g. `"./src/chrome-bindings.tsx"` (epic #2499, ADR
329
+ * `docs/adr/route-injection-seam.md`, "Host-callables channel"). Only
330
+ * consumed when `packageOwnedRoutes` is on: the routes plugin registers a
331
+ * second virtual module (`virtual:zudo-doc-chrome-bindings`) that
332
+ * RE-EXPORTS this module, so the injected chrome shim (`routes/_chrome.tsx`)
333
+ * can import real host callables — un-stranding `ChromeHostBindings` slots
334
+ * (`frontmatterRenderers`, `buildFrontmatterPreviewEntries`, `SearchWidget`,
335
+ * …) that today silently stay at stub defaults on injected routes.
336
+ *
337
+ * The target module MUST have a named export `chromeBindings` typed
338
+ * `ChromeHostBindings` (from `@takazudo/zudo-doc/factory-context`).
339
+ *
340
+ * - Absent (the default) → the virtual module emits
341
+ * `export const chromeBindings = {};` — behavior byte-identical to today.
342
+ * - Explicitly empty string → build fails loudly at plugin setup.
343
+ * - Present but the resolved file does not exist → the build fails loudly
344
+ * at plugin setup, naming the resolved absolute path (never a silent
345
+ * empty fallback).
346
+ * - Present but the resolved path is a directory (e.g. `"."` or `"./src"`)
347
+ * → the build fails loudly at plugin setup, naming the resolved path.
348
+ *
349
+ * Only the PATH travels through `settings` (a string is serializable data,
350
+ * consistent with the ADR's "virtual module = serializable data only"
351
+ * rule) — the bundler imports the actual callables from the re-exported
352
+ * module.
353
+ *
354
+ * SSR-presentational contract only: client islands defined inside the
355
+ * bindings module are NOT guaranteed to register on injected routes (the
356
+ * virtual re-export is outside zfb's static-import scanner reachability
357
+ * graph — see the ADR for detail).
358
+ */
359
+ chromeBindingsModule?: string;
326
360
  }
@@ -0,0 +1,4 @@
1
+ import type { SidebarNavNode } from "../sidebar/types.js";
2
+ export declare function normalizePath(p: string): string;
3
+ /** Find the slug of the node whose href matches the given pathname. */
4
+ export declare function findActiveSlug(nodes: SidebarNavNode[], pathname: string): string | undefined;
@@ -0,0 +1,15 @@
1
+ function normalizePath(p) {
2
+ return p.replace(/\/$/, "") || "/";
3
+ }
4
+ function findActiveSlug(nodes, pathname) {
5
+ for (const node of nodes) {
6
+ if (node.href && normalizePath(node.href) === pathname) return node.slug;
7
+ const found = findActiveSlug(node.children, pathname);
8
+ if (found !== void 0) return found;
9
+ }
10
+ return void 0;
11
+ }
12
+ export {
13
+ findActiveSlug,
14
+ normalizePath
15
+ };
@@ -0,0 +1,10 @@
1
+ import type { SidebarNavNode } from "../sidebar/types.js";
2
+ /**
3
+ * Filter a sidebar nav tree by a case-insensitive label match.
4
+ *
5
+ * When a node's own label matches the query, ALL of its descendants are kept
6
+ * (typing a category name should reveal its whole sub-tree). When only a
7
+ * descendant matches, the node is kept with just the matching descendants —
8
+ * siblings with no matching descendants are dropped entirely.
9
+ */
10
+ export declare function filterTree(nodes: SidebarNavNode[], query: string): SidebarNavNode[];
@@ -0,0 +1,16 @@
1
+ function filterTree(nodes, query) {
2
+ return nodes.reduce((acc, node) => {
3
+ const matchesLabel = node.label.toLowerCase().includes(query.toLowerCase());
4
+ const filteredChildren = node.children.length > 0 ? filterTree(node.children, query) : [];
5
+ if (matchesLabel || filteredChildren.length > 0) {
6
+ acc.push({
7
+ ...node,
8
+ children: matchesLabel ? node.children : filteredChildren
9
+ });
10
+ }
11
+ return acc;
12
+ }, []);
13
+ }
14
+ export {
15
+ filterTree
16
+ };
@@ -7,6 +7,8 @@ import { ChevronRight, ChevronLeft, Search } from "../icons/index.js";
7
7
  import { ThemeToggle } from "../theme-toggle/index.js";
8
8
  import { smartBreakToHtml } from "../smart-break/index.js";
9
9
  import { AFTER_NAVIGATE_EVENT, BEFORE_NAVIGATE_EVENT } from "../transitions/index.js";
10
+ import { filterTree } from "../sidebar-filter/index.js";
11
+ import { findActiveSlug, normalizePath } from "../sidebar-active-slug/index.js";
10
12
  function ToggleChevron({ isExpanded, className }) {
11
13
  return /* @__PURE__ */ jsx(
12
14
  ChevronRight,
@@ -36,17 +38,6 @@ function saveOpenSet(set) {
36
38
  } catch {
37
39
  }
38
40
  }
39
- function normalizePath(p) {
40
- return p.replace(/\/$/, "") || "/";
41
- }
42
- function findActiveSlug(nodes, pathname) {
43
- for (const node of nodes) {
44
- if (node.href && normalizePath(node.href) === pathname) return node.slug;
45
- const found = findActiveSlug(node.children, pathname);
46
- if (found !== void 0) return found;
47
- }
48
- return void 0;
49
- }
50
41
  function deriveActiveSlugFromUrl(nodes) {
51
42
  if (typeof window === "undefined") return void 0;
52
43
  const pathname = normalizePath(window.location.pathname);
@@ -96,19 +87,6 @@ function useSidebarScrollPreserve() {
96
87
  };
97
88
  }, []);
98
89
  }
99
- function filterTree(nodes, query) {
100
- return nodes.reduce((acc, node) => {
101
- const matchesLabel = node.label.toLowerCase().includes(query.toLowerCase());
102
- const filteredChildren = node.children.length > 0 ? filterTree(node.children, query) : [];
103
- if (matchesLabel || filteredChildren.length > 0) {
104
- acc.push({
105
- ...node,
106
- children: matchesLabel ? node.children : filteredChildren
107
- });
108
- }
109
- return acc;
110
- }, []);
111
- }
112
90
  function RootMenuItemEntry({ item }) {
113
91
  const [expanded, setExpanded] = useState(false);
114
92
  const hasChildren = item.children && item.children.length > 0;
@@ -16,6 +16,8 @@ import { smartBreakToHtml } from "../smart-break/index.js";
16
16
  // After zudolab/zudo-doc#1335 the host components also pull lifecycle event
17
17
  // names from the v2 transitions module rather than hard-coding literals.
18
18
  import { AFTER_NAVIGATE_EVENT, BEFORE_NAVIGATE_EVENT } from "../transitions/index.js";
19
+ import { filterTree } from "../sidebar-filter/index.js";
20
+ import { findActiveSlug, normalizePath } from "../sidebar-active-slug/index.js";
19
21
 
20
22
  function ToggleChevron({ isExpanded, className }: { isExpanded: boolean; className?: string }) {
21
23
  return (
@@ -51,22 +53,6 @@ function saveOpenSet(set: Set<string>) {
51
53
  }
52
54
  }
53
55
 
54
- function normalizePath(p: string): string {
55
- return p.replace(/\/$/, "") || "/";
56
- }
57
-
58
- /** Find the slug of the node whose href matches the given pathname */
59
- function findActiveSlug(nodes: SidebarNavNode[], pathname: string): string | undefined {
60
- for (const node of nodes) {
61
- if (node.href && normalizePath(node.href) === pathname) return node.slug;
62
- const found = findActiveSlug(node.children, pathname);
63
- // "" is the canonical root-index slug (#1891) — a truthiness check
64
- // would discard a legitimate root match.
65
- if (found !== undefined) return found;
66
- }
67
- return undefined;
68
- }
69
-
70
56
  /**
71
57
  * Derive the active slug from the current document URL. Used as a hydration-
72
58
  * time fallback when the parent island does not forward `currentSlug` through
@@ -140,23 +126,6 @@ function useSidebarScrollPreserve() {
140
126
  }, []);
141
127
  }
142
128
 
143
- function filterTree(nodes: SidebarNavNode[], query: string): SidebarNavNode[] {
144
- return nodes.reduce<SidebarNavNode[]>((acc, node) => {
145
- const matchesLabel = node.label.toLowerCase().includes(query.toLowerCase());
146
- const filteredChildren = node.children.length > 0
147
- ? filterTree(node.children, query)
148
- : [];
149
-
150
- if (matchesLabel || filteredChildren.length > 0) {
151
- acc.push({
152
- ...node,
153
- children: matchesLabel ? node.children : filteredChildren,
154
- });
155
- }
156
- return acc;
157
- }, []);
158
- }
159
-
160
129
  function RootMenuItemEntry({ item }: { item: SidebarRootMenuItem }) {
161
130
  const [expanded, setExpanded] = useState(false);
162
131
  const hasChildren = item.children && item.children.length > 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takazudo/zudo-doc",
3
- "version": "2.2.2",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "description": "zudo-doc framework primitives layer that sits on top of zfb's engine — sidebar, theme, TOC, breadcrumb, layouts, head injection, View Transitions, SSR-skip wrappers (per ADR-003).",
6
6
  "license": "MIT",
@@ -219,6 +219,10 @@
219
219
  "types": "./dist/chrome/index.d.ts",
220
220
  "default": "./dist/chrome/index.js"
221
221
  },
222
+ "./home-page": {
223
+ "types": "./dist/home-page/index.d.ts",
224
+ "default": "./dist/home-page/index.js"
225
+ },
222
226
  "./safelist.css": "./dist/safelist.css",
223
227
  "./safelist": "./dist/safelist.css",
224
228
  "./content.css": "./dist/content.css",
@@ -511,6 +515,14 @@
511
515
  "./eject": {
512
516
  "types": "./dist/eject/index.d.ts",
513
517
  "default": "./dist/eject/index.js"
518
+ },
519
+ "./sidebar-filter": {
520
+ "types": "./dist/sidebar-filter/index.d.ts",
521
+ "default": "./dist/sidebar-filter/index.js"
522
+ },
523
+ "./sidebar-active-slug": {
524
+ "types": "./dist/sidebar-active-slug/index.d.ts",
525
+ "default": "./dist/sidebar-active-slug/index.js"
514
526
  }
515
527
  },
516
528
  "bin": {
@@ -528,8 +540,8 @@
528
540
  ],
529
541
  "peerDependencies": {
530
542
  "preact": "^10.29.1",
531
- "@takazudo/zfb": "^0.1.0-next.74",
532
- "@takazudo/zfb-runtime": "^0.1.0-next.74",
543
+ "@takazudo/zfb": "^0.1.0-next.75",
544
+ "@takazudo/zfb-runtime": "^0.1.0-next.75",
533
545
  "@takazudo/zudo-doc-history-server": "^2.2.1",
534
546
  "@takazudo/zdtp": "^0.4.2",
535
547
  "shiki": "^4.0.2",
@@ -572,13 +584,15 @@
572
584
  "typescript": "^5.0.0",
573
585
  "vitest": "^4.1.0",
574
586
  "zod": "^4.3.6",
575
- "@takazudo/zfb": "0.1.0-next.74",
576
- "@takazudo/zfb-runtime": "0.1.0-next.74"
587
+ "@takazudo/zfb": "0.1.0-next.75",
588
+ "@takazudo/zfb-runtime": "0.1.0-next.75",
589
+ "@takazudo/zudo-doc-history-server": "2.4.0"
577
590
  },
578
591
  "scripts": {
579
592
  "build": "tsup && tsc -p tsconfig.build.json",
580
593
  "dev": "tsup --watch",
581
594
  "test": "vitest run --config vitest.config.ts",
595
+ "test:slow": "vitest run --config vitest.slow.config.ts",
582
596
  "typecheck": "tsc --noEmit"
583
597
  }
584
598
  }
@@ -15,6 +15,12 @@ import { createChrome } from "@takazudo/zudo-doc/chrome";
15
15
  import { DocHistory } from "@takazudo/zudo-doc/doc-history";
16
16
  import type { ChromeHostBindings } from "@takazudo/zudo-doc/factory-context";
17
17
  import type { DocNavNode } from "./_docs-helpers.js";
18
+ // Host-callables channel (#2501): re-exports `settings.chromeBindingsModule`
19
+ // when the host configured one, else `{}` — see
20
+ // `plugins/routes.ts` and `docs/adr/route-injection-seam.md`
21
+ // ("Host-callables channel — chromeBindingsModule"). Not present on disk; the
22
+ // package ships ambient typings for it (`routes/_virtual.d.ts`).
23
+ import { chromeBindings } from "virtual:zudo-doc-chrome-bindings";
18
24
 
19
25
  // Island-scanner contract (load-bearing): the injected doc routes reach the real
20
26
  // DocHistory client island ONLY through this static import → `createChrome`
@@ -28,8 +34,17 @@ import type { DocNavNode } from "./_docs-helpers.js";
28
34
  // vs the stub is byte-identical. Mirrors the host's `pages/lib/_chrome.ts`.
29
35
  // (The narrow real-island props signature isn't assignable to the structural
30
36
  // `FactoryComponent`, so cast — same as the host.)
37
+ //
38
+ // `...chromeBindings` is spread AFTER the `DocHistory` default so a host that
39
+ // configured `chromeBindingsModule` can override ANY slot — including
40
+ // DocHistory itself — while a host that didn't still gets the #2480 fix for
41
+ // free. Note the SSR-presentational-only limitation: a client island defined
42
+ // INSIDE the bindings module is not guaranteed to register on injected routes
43
+ // the way the static `DocHistory` import above is (the virtual re-export sits
44
+ // outside zfb's static-import scanner reachability graph) — see the ADR.
31
45
  const chrome = createChrome(routeCtx, {
32
46
  DocHistory: DocHistory as unknown as ChromeHostBindings["DocHistory"],
47
+ ...chromeBindings,
33
48
  });
34
49
 
35
50
  export const {
@@ -45,6 +60,7 @@ export const {
45
60
  TagsIndexPageView,
46
61
  SiteTreeNavWrapper,
47
62
  BodyEndIslands,
63
+ HomePageView,
48
64
  } = chrome;
49
65
 
50
66
  // The doc-route-entries builder lives on the route context; the doc entrypoints
@@ -17,3 +17,18 @@ declare module "virtual:zudo-doc-route-context" {
17
17
  colorSchemes: Record<string, unknown> | null;
18
18
  };
19
19
  }
20
+
21
+ // Ambient typing for the chrome-bindings virtual module emitted by the routes
22
+ // plugin (`addVirtualModule("virtual:zudo-doc-chrome-bindings", …)`, #2501).
23
+ // No on-disk source — materialised at build, either as a re-export of the
24
+ // host's `settings.chromeBindingsModule` file or as an empty-object fallback
25
+ // when the setting is absent. See `plugins/routes.ts` and
26
+ // `docs/adr/route-injection-seam.md` ("Host-callables channel —
27
+ // chromeBindingsModule").
28
+ //
29
+ // Typed via an INLINE `import(...)` type (not a top-level import, which would
30
+ // break this file's ambient module declarations) — see
31
+ // `../factory-context/index.js` for the `ChromeHostBindings` shape.
32
+ declare module "virtual:zudo-doc-chrome-bindings" {
33
+ export const chromeBindings: import("@takazudo/zudo-doc/factory-context").ChromeHostBindings;
34
+ }