@takazudo/zudo-doc 6.1.0 → 6.2.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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,31 @@ The format is based on Keep a Changelog, and release notes are generated from th
8
8
 
9
9
  No unreleased changes.
10
10
 
11
+ ## [6.2.0] - 2026-10-14
12
+
13
+ ### Features
14
+
15
+ - Adds optional `SidebarDefinition.scopeRoot?: "configured" | "editorial"` (default `"editorial"`, unchanged behavior). `"configured"` caps Broaden, branch focus, Restore, and route reconciliation at the authored forest: empty `items` stop at the category-filtered tree, one `autogenerated` directory stops at that directory, and an explicit list stops at that list. `initialPath` remains the starting view and the Restore baseline. At the cap the scope toolbar is omitted. An unsupported value fails the build. An omitted entry or an array stays editorial. (418b2f29c)
16
+
17
+ ### Bug Fixes
18
+
19
+ - The shared `tsconfig.base.json` no longer sets `baseUrl`, which TypeScript 7 rejects with TS5102 in every project that extends it. Projects that relied on the inherited `baseUrl` for bare `paths` targets should write them as explicitly relative paths (for example `"@/*": ["./src/*"]`), which TypeScript 5.9 and 7 both resolve against the declaring tsconfig. (4d5e801ed)
20
+ - `buildSidebarForSection` now applies `initialPath` and `initialPathMode` to an object sidebar definition with empty `items`, matching `buildSidebarNavigation`. Previously the empty-items case returned the plain category-filtered tree and ignored those options. (31fe01015)
21
+ - Updates the lazy browser Mermaid URL from 11.15.0 to the exact 11.17.2 release, including the advisory fixes introduced in 11.16.1. Local npm audit does not cover the separately delivered CDN graph. (76d637663)
22
+ - The desktop sidebar and the mobile drawer each keep their scroll offset across client navigation, a full document load, reload, and Back/Forward, including after Broaden. Restoration runs after the destination tree can hold the offset, so a replaced sidebar or a temporary empty tree does not snap back to the top. A saved zero is restored. A missing snapshot is not written as zero. A closed drawer keeps its saved offset in history until it opens again. Article scroll, focus, filtering, and disclosure are left unchanged. (418b2f29c, 2b38ccf0a)
23
+ - The Appearance menu is a compact list of three rows (Light, Dark, System). The visible heading and the bottom helper are removed. The System row shows the device's current Light/Dark mode inline, and that status stays truthful while a manual Light or Dark preference is selected. The full localized helper remains the System option's accessible description. The popup is sized from its natural height at its final width, so its border no longer creates a spurious inner scrollbar. It opens above the trigger near the bottom edge and repositions when its content size changes. Opening, arrow keys, and focus return no longer scroll the page, and Tab / Shift+Tab leave the menu instead of cycling its rows. (37e3b8b42, 7aba63e28)
24
+
25
+ ### Other Changes
26
+
27
+ - Requires zfb 4.5.0: the `@takazudo/zfb`, `zfb-runtime` and `zfb-md-wasm` peer floors move from `^4.3.3` to `^4.5.0`. zfb 4.4.0 updates its YAML frontmatter parser and fixes fragment Back/Forward navigation. zfb 4.5.0 also restores a previously rendered same-path query entry on Back/Forward, fixes sibling radio-group state on form reset, and adds opt-in CSS compiler and `ErrorBoundary` APIs. Upgrade the zfb family (and `zfb-adapter-cloudflare` when used) to 4.5.0 together with this release. (48a224468, 4f9d0c26f)
28
+ - Updates the Codex resource TOML parser to smol-toml `^1.9.1`, preserving configuration dates and agent metadata while including its bounded key-scanning fix. (05e3fb5e7)
29
+
30
+ ## [6.1.1] - 2026-10-12
31
+
32
+ ### Other Changes
33
+
34
+ - Requires zfb 4.3.3: the `@takazudo/zfb`, `zfb-runtime` and `zfb-md-wasm` peer floors move from `^4.3.2` to `^4.3.3`. zfb 4.3.3 moves its YAML frontmatter parser past [RUSTSEC-2026-0333](https://rustsec.org/advisories/RUSTSEC-2026-0333); frontmatter accepted by 4.3.2 is still accepted. Upgrade the zfb family (and `zfb-adapter-cloudflare` when used) to 4.3.3 together with this release. (bbc70837e)
35
+
11
36
  ## [6.1.0] - 2026-10-12
12
37
 
13
38
  ### Features
package/README.md CHANGED
@@ -108,8 +108,8 @@ If your project renders **semi-trusted or user-submitted** HTML in a preview, ov
108
108
  ## 6.0 migration and peers
109
109
 
110
110
  **6.0.0** is the coordinated major release of `@takazudo/zudo-doc`,
111
- `@takazudo/zudo-doc-history-server` and `create-zudo-doc`. It targets the
112
- published **zfb 4.3.2** family (peer floor **^4.3.2**).
111
+ `@takazudo/zudo-doc-history-server` and `create-zudo-doc`. The current workspace
112
+ targets the published **zfb 4.5.0** family (peer floor **^4.5.0**).
113
113
  [Upstream #4097](https://github.com/Takazudo/zudo-front-builder/issues/4097) is
114
114
  resolved; its strict published 4.3.1 consumer control passed 3/3 with no skips or
115
115
  retries in the [source-bound verification](../../docs/findings/zfb-4.3.1/verification.md).
@@ -3,7 +3,7 @@
3
3
  * resolves npm package specifiers to publishable ESM modules.
4
4
  *
5
5
  * Pinned to a specific minor.patch (not the bare `@11` major) so the
6
- * runtime version is byte-stable across deploys; floating on the major
6
+ * top-level package version is stable across deploys; floating on the major
7
7
  * would silently pull in upstream theme-API or behaviour changes. Bump
8
8
  * policy: there is no `mermaid` package.json dependency to keep in sync
9
9
  * with — this constant is the sole source of truth for the mermaid
@@ -15,7 +15,7 @@
15
15
  * tests can override the URL — e.g. self-hosted mirrors, version-
16
16
  * locked deployments — without re-templating the whole script.
17
17
  */
18
- export declare const MERMAID_CDN_MODULE_URL = "https://esm.sh/mermaid@11.15.0";
18
+ export declare const MERMAID_CDN_MODULE_URL = "https://esm.sh/mermaid@11.17.2";
19
19
  /**
20
20
  * Build the inline init script with a caller-supplied module URL.
21
21
  *
@@ -1,5 +1,5 @@
1
1
  import { AFTER_NAVIGATE_EVENT } from "../transitions/page-events.js";
2
- const MERMAID_CDN_MODULE_URL = "https://esm.sh/mermaid@11.15.0";
2
+ const MERMAID_CDN_MODULE_URL = "https://esm.sh/mermaid@11.17.2";
3
3
  function buildMermaidInitScript(cdnUrl) {
4
4
  const safeUrlLiteral = JSON.stringify(cdnUrl).replace(
5
5
  /<\/(script)/gi,
package/dist/compiled.css CHANGED
@@ -2375,6 +2375,9 @@ site-search [data-search-results] > :is([data-search-placeholder], p) {
2375
2375
  .w-\[1\.125rem\] {
2376
2376
  width: 1.125rem;
2377
2377
  }
2378
+ .w-\[1\.25rem\] {
2379
+ width: 1.25rem;
2380
+ }
2378
2381
  .w-\[1\.575rem\] {
2379
2382
  width: 1.575rem;
2380
2383
  }
@@ -3428,6 +3431,11 @@ site-search [data-search-results] > :is([data-search-placeholder], p) {
3428
3431
  background-color: color-mix(in oklab, var(--zw-color-accent) 10%, transparent);
3429
3432
  }
3430
3433
  }
3434
+ @media (hover: hover) {
3435
+ .hover\:bg-accent\/5:hover {
3436
+ background-color: color-mix(in oklab, var(--zw-color-accent) 5%, transparent);
3437
+ }
3438
+ }
3431
3439
  @media (hover: hover) {
3432
3440
  .hover\:bg-bg\/10:hover {
3433
3441
  background-color: color-mix(in oklab, var(--zw-color-bg) 10%, transparent);
@@ -3,6 +3,7 @@ import { Fragment, jsx, jsxs } from "@takazudo/zfb/zudo-react/jsx-runtime";
3
3
  import { computed, getScope, signal } from "@takazudo/zfb/zudo-react";
4
4
  import { AFTER_NAVIGATE_EVENT, ensureNestedIslandPropsRefresh } from "../transitions/index.js";
5
5
  import { SidebarTree } from "../sidebar-tree-island/index.js";
6
+ import { notifySidebarScrollReady } from "../sidebar-tree-island/sidebar-scroll-preserve.js";
6
7
  ensureNestedIslandPropsRefresh();
7
8
  const cx = (...classes) => classes.filter(Boolean).join(" ");
8
9
  const HIDDEN_ICON_STYLE = "display:none";
@@ -29,6 +30,10 @@ function SidebarToggle({
29
30
  document.body.style.overflow = isOpen ? "hidden" : "";
30
31
  if (isOpen) closeIconRef.current?.style.removeProperty("display");
31
32
  else closeIconRef.current?.style.setProperty("display", "none");
33
+ if (isOpen) {
34
+ const scroller = document.querySelector("aside[data-zd-mobile-sidebar] .flex-1.overflow-y-auto");
35
+ if (scroller) notifySidebarScrollReady(scroller);
36
+ }
32
37
  if (isOpen) menuIconRef.current?.style.setProperty("display", "none");
33
38
  else menuIconRef.current?.style.removeProperty("display");
34
39
  return () => {
@@ -25,7 +25,7 @@ import {
25
25
  CURRENT_PATH_DATASET_KEY,
26
26
  readCurrentPath
27
27
  } from "../current-path/index.js";
28
- import { ensureSidebarScrollPreserve } from "./sidebar-scroll-preserve.js";
28
+ import { ensureSidebarScrollPreserve, notifySidebarScrollReady } from "./sidebar-scroll-preserve.js";
29
29
  import {
30
30
  formatYearLabel,
31
31
  formatYearMonthLabel,
@@ -237,6 +237,13 @@ function SidebarTree({
237
237
  const filteredNodes = computed(
238
238
  () => query.value ? filterTree(selectedNodes.value, query.value) : selectedNodes.value
239
239
  );
240
+ scope.effect(() => {
241
+ void selected.value;
242
+ void selectedNodes.value;
243
+ void query.value;
244
+ const nav = navRef.current;
245
+ if (nav) notifySidebarScrollReady(nav);
246
+ });
240
247
  const locale = localeProp ?? localeLinks?.find((link) => link.active)?.code ?? "en";
241
248
  const footer = /* @__PURE__ */ jsx(
242
249
  SidebarFooter,
@@ -258,97 +265,106 @@ function SidebarTree({
258
265
  when: computed(
259
266
  () => activeSlug.value === void 0 && !!rootMenuItems
260
267
  ),
261
- fallback: () => /* @__PURE__ */ jsxs("nav", { ref: navRef, tabindex: -1, children: [
262
- rootMenuItems ? /* @__PURE__ */ jsxs(
263
- "button",
264
- {
265
- type: "button",
266
- "on:click": () => {
267
- showingRootMenu.value = true;
268
- },
269
- class: "lg:hidden flex w-full items-center gap-hsp-xs px-hsp-sm py-vsp-xs text-left text-small text-muted hover:text-fg border-b border-muted",
270
- children: [
271
- /* @__PURE__ */ jsx(ChevronLeft, { class: "h-icon-sm w-icon-sm shrink-0" }),
272
- backToMenuLabel ?? "Back to main menu"
273
- ]
274
- }
275
- ) : null,
276
- /* @__PURE__ */ jsx("div", { class: "px-hsp-sm py-vsp-xs border-b border-muted", "data-sidebar-filter-region": true, children: /* @__PURE__ */ jsxs("div", { class: "flex items-center gap-hsp-xs bg-surface rounded px-hsp-sm py-vsp-2xs", children: [
277
- /* @__PURE__ */ jsx(Search, { class: "h-[14px] w-[14px] text-muted shrink-0" }),
278
- /* @__PURE__ */ jsx(
279
- "input",
280
- {
281
- ref: filterRef,
282
- type: "text",
283
- "aria-label": "Filter navigation",
284
- placeholder: filterPlaceholder,
285
- modelValue: query,
286
- "on:input": (event) => {
287
- query.value = event.target.value;
288
- save();
289
- },
290
- class: "bg-transparent text-small outline-none w-full text-fg placeholder:text-muted focus-visible:outline-2 focus-visible:outline-accent",
291
- style: "outline-offset:-4px"
292
- }
293
- )
294
- ] }) }),
295
- navigation ? /* @__PURE__ */ jsx(Show, { when: computed(() => broader.value !== null), children: () => /* @__PURE__ */ jsx("div", { class: "border-b border-muted px-hsp-md py-vsp-2xs text-small", "data-sidebar-scope-toolbar": true, children: /* @__PURE__ */ jsxs("div", { class: "flex items-center justify-between gap-hsp-xs", "data-sidebar-scope-actions": true, children: [
296
- /* @__PURE__ */ jsxs(
297
- "button",
298
- {
299
- type: "button",
300
- "data-sidebar-broaden": true,
301
- disabled: computed(() => broader.value === null),
302
- "on:click": () => {
303
- if (broader.value !== null) changeScope(broader.value);
304
- },
305
- style: "min-height:40px",
306
- class: "flex items-center gap-hsp-sm px-hsp-xs font-medium rounded text-fg hover:bg-surface focus-visible:bg-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent disabled:text-muted disabled:opacity-50",
307
- children: [
308
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2191" }),
309
- labels.broaden
310
- ]
311
- }
312
- ),
313
- /* @__PURE__ */ jsx(Show, { when: canRestore, children: () => /* @__PURE__ */ jsx(
314
- "button",
315
- {
316
- type: "button",
317
- "data-sidebar-restore": true,
318
- "on:click": () => changeScope(null),
319
- title: labels.restore,
320
- "aria-label": labels.restore,
321
- style: "width:40px;height:40px",
322
- class: "grid place-items-center shrink-0 rounded text-muted hover:text-fg focus-visible:text-fg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
323
- children: /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "aria-hidden": "true", children: [
324
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
325
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "6" }),
326
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "2" })
327
- ] })
328
- }
329
- ) })
330
- ] }) }) }) : null,
331
- /* @__PURE__ */ jsx("div", { "data-sidebar-tree-root": true, children: /* @__PURE__ */ jsx(
332
- Show,
333
- {
334
- when: computed(() => selectedNodes.value.length === 1 && selectedNodes.value[0]?.shape === "note-tray"),
335
- fallback: () => /* @__PURE__ */ jsx(NodeList, { nodes: filteredNodes, currentSlug: activeSlug, depth: 0, forceOpen: computed(() => !!query.value), scopeControls }),
336
- children: () => /* @__PURE__ */ jsx(Show, { when: computed(() => !!filteredNodes.value[0]), children: () => /* @__PURE__ */ jsx(
337
- TrayList,
268
+ fallback: () => /* @__PURE__ */ jsxs(
269
+ "nav",
270
+ {
271
+ ref: navRef,
272
+ tabindex: -1,
273
+ "data-sidebar-context": navigation?.id,
274
+ "data-sidebar-scope": computed(() => selected.value ?? ""),
275
+ children: [
276
+ rootMenuItems ? /* @__PURE__ */ jsxs(
277
+ "button",
338
278
  {
339
- tray: computed(() => filteredNodes.value[0]),
340
- itemCount: getNoteTrayItems(selectedNodes.value[0]).length,
341
- currentSlug: activeSlug,
342
- forceOpen: computed(() => !!query.value),
343
- locale,
344
- dateFormats,
345
- scopeControls
279
+ type: "button",
280
+ "on:click": () => {
281
+ showingRootMenu.value = true;
282
+ },
283
+ class: "lg:hidden flex w-full items-center gap-hsp-xs px-hsp-sm py-vsp-xs text-left text-small text-muted hover:text-fg border-b border-muted",
284
+ children: [
285
+ /* @__PURE__ */ jsx(ChevronLeft, { class: "h-icon-sm w-icon-sm shrink-0" }),
286
+ backToMenuLabel ?? "Back to main menu"
287
+ ]
346
288
  }
347
- ) })
348
- }
349
- ) }),
350
- footer
351
- ] }),
289
+ ) : null,
290
+ /* @__PURE__ */ jsx("div", { class: "px-hsp-sm py-vsp-xs border-b border-muted", "data-sidebar-filter-region": true, children: /* @__PURE__ */ jsxs("div", { class: "flex items-center gap-hsp-xs bg-surface rounded px-hsp-sm py-vsp-2xs", children: [
291
+ /* @__PURE__ */ jsx(Search, { class: "h-[14px] w-[14px] text-muted shrink-0" }),
292
+ /* @__PURE__ */ jsx(
293
+ "input",
294
+ {
295
+ ref: filterRef,
296
+ type: "text",
297
+ "aria-label": "Filter navigation",
298
+ placeholder: filterPlaceholder,
299
+ modelValue: query,
300
+ "on:input": (event) => {
301
+ query.value = event.target.value;
302
+ save();
303
+ },
304
+ class: "bg-transparent text-small outline-none w-full text-fg placeholder:text-muted focus-visible:outline-2 focus-visible:outline-accent",
305
+ style: "outline-offset:-4px"
306
+ }
307
+ )
308
+ ] }) }),
309
+ navigation ? /* @__PURE__ */ jsx(Show, { when: computed(() => broader.value !== null), children: () => /* @__PURE__ */ jsx("div", { class: "border-b border-muted px-hsp-md py-vsp-2xs text-small", "data-sidebar-scope-toolbar": true, children: /* @__PURE__ */ jsxs("div", { class: "flex items-center justify-between gap-hsp-xs", "data-sidebar-scope-actions": true, children: [
310
+ /* @__PURE__ */ jsxs(
311
+ "button",
312
+ {
313
+ type: "button",
314
+ "data-sidebar-broaden": true,
315
+ disabled: computed(() => broader.value === null),
316
+ "on:click": () => {
317
+ if (broader.value !== null) changeScope(broader.value);
318
+ },
319
+ style: "min-height:40px",
320
+ class: "flex items-center gap-hsp-sm px-hsp-xs font-medium rounded text-fg hover:bg-surface focus-visible:bg-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent disabled:text-muted disabled:opacity-50",
321
+ children: [
322
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2191" }),
323
+ labels.broaden
324
+ ]
325
+ }
326
+ ),
327
+ /* @__PURE__ */ jsx(Show, { when: canRestore, children: () => /* @__PURE__ */ jsx(
328
+ "button",
329
+ {
330
+ type: "button",
331
+ "data-sidebar-restore": true,
332
+ "on:click": () => changeScope(null),
333
+ title: labels.restore,
334
+ "aria-label": labels.restore,
335
+ style: "width:40px;height:40px",
336
+ class: "grid place-items-center shrink-0 rounded text-muted hover:text-fg focus-visible:text-fg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
337
+ children: /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "aria-hidden": "true", children: [
338
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
339
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "6" }),
340
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "2" })
341
+ ] })
342
+ }
343
+ ) })
344
+ ] }) }) }) : null,
345
+ /* @__PURE__ */ jsx("div", { "data-sidebar-tree-root": true, children: /* @__PURE__ */ jsx(
346
+ Show,
347
+ {
348
+ when: computed(() => selectedNodes.value.length === 1 && selectedNodes.value[0]?.shape === "note-tray"),
349
+ fallback: () => /* @__PURE__ */ jsx(NodeList, { nodes: filteredNodes, currentSlug: activeSlug, depth: 0, forceOpen: computed(() => !!query.value), scopeControls }),
350
+ children: () => /* @__PURE__ */ jsx(Show, { when: computed(() => !!filteredNodes.value[0]), children: () => /* @__PURE__ */ jsx(
351
+ TrayList,
352
+ {
353
+ tray: computed(() => filteredNodes.value[0]),
354
+ itemCount: getNoteTrayItems(selectedNodes.value[0]).length,
355
+ currentSlug: activeSlug,
356
+ forceOpen: computed(() => !!query.value),
357
+ locale,
358
+ dateFormats,
359
+ scopeControls
360
+ }
361
+ ) })
362
+ }
363
+ ) }),
364
+ footer
365
+ ]
366
+ }
367
+ ),
352
368
  children: () => /* @__PURE__ */ jsxs("nav", { children: [
353
369
  menu,
354
370
  footer
@@ -2,20 +2,22 @@ interface SidebarScrollPreserveOptions {
2
2
  document: Document;
3
3
  requestAnimationFrame: (callback: FrameRequestCallback) => number;
4
4
  cancelAnimationFrame: (handle: number) => void;
5
+ ResizeObserver?: typeof ResizeObserver;
5
6
  }
6
7
  /**
7
8
  * Install the scroll-preservation state machine on a document.
8
9
  *
9
- * This low-level entrypoint owns an explicit cleanup for focused tests and
10
- * non-singleton hosts. The sidebar's browser module uses the durable,
11
- * duplicate-safe `ensureSidebarScrollPreserve` wrapper below instead.
12
- */
13
- export declare function installSidebarScrollPreserve({ document, requestAnimationFrame, cancelAnimationFrame, }: SidebarScrollPreserveOptions): () => void;
14
- /**
15
- * Install exactly one module-lifetime controller for a browser document.
16
- * Repeated calls from component renders or duplicate boot paths are no-ops.
10
+ * The desktop aside is replaced when sidebar island props change: preparation
11
+ * drops `data-zfb-transition-persist`, so element identity is not enough for
12
+ * the same logical tree. A saved offset is applied only after the destination
13
+ * can hold it. Chromium clamps a write that happens while `scrollHeight` is
14
+ * still too small, and that clamped 0 would stick.
17
15
  */
16
+ export declare function installSidebarScrollPreserve({ document, requestAnimationFrame, cancelAnimationFrame, ResizeObserver: ResizeObserverImpl, }: SidebarScrollPreserveOptions): () => void;
17
+ /** Install exactly one module-lifetime controller for a browser document. */
18
18
  export declare function ensureSidebarScrollPreserve(options?: SidebarScrollPreserveOptions): void;
19
+ /** The sidebar island calls this after the destination tree has committed. */
20
+ export declare function notifySidebarScrollReady(root: Element): void;
19
21
  /** Test/HMR teardown for a controller installed through the singleton wrapper. */
20
22
  export declare function disposeSidebarScrollPreserve(document: Document): void;
21
23
  export {};