You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: packages/backend.ai-docs-toolkit/ARCHITECTURE.md
+125Lines changed: 125 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -534,3 +534,128 @@ The same lookup also drives `<link rel="alternate" hreflang="…">` in the `<hea
534
534
- **Air-gapped** — the language picker script is inline; no CDN, no fetch, no eval.
535
535
- **JS budget** — the picker script is a few hundred bytes; the per-page switcher adds zero JS (links are plain `<a>`).
536
536
- **PDF non-regression** — the cover page still receives the multi-line title via `titleMultiline`; the `<title>` element remains single-line via the existing `.trim().replace(/\n/g, " ")` safety net.
When the input is the legacy flat form, `navigationGroups[lang]` contains a single group with `category: ""` (anonymous). The web sidebar renders an anonymous group as a flat list (no `<details>` drawer) so a config that hasn't migrated to grouped form looks identical to F1's sidebar — no breadcrumb middle segment, no group headers.
570
+
571
+
The two forms cannot be **mixed within a single language** (e.g. `en: [grouped, grouped, flat]`). The loader inspects only the first entry to decide the form; mismatched siblings are dropped with a warning. Each language can independently choose its form, however — `en: grouped, th: flat` is fine while a translator works through categorization.
572
+
573
+
Soft-fail policy on grouped-form structural problems:
574
+
575
+
- Group with empty `category:` → dropped with warning, items skipped.
576
+
- Group with no valid `items` → dropped with warning.
577
+
- Item missing `title` or `path` → dropped with warning, group keeps remaining items.
578
+
579
+
The build never crashes on these — matches F5's diagnostics-sink philosophy.
580
+
581
+
### Sidebar render (CSS-only collapsible groups)
582
+
583
+
The web sidebar renders one `<details class="doc-sidebar-group">` per category, each containing a `<ul class="doc-sidebar-nav doc-sidebar-nav--grouped">` of nav items. No JavaScript is involved — `<details>` natively handles the open/close state. The active page's group is `<details open>` on first load (computed at build time by checking which group contains the current item's path); other groups are collapsed by default.
584
+
585
+
The pre-F3 inline H2 sub-list under the active sidebar item is gone; that data lives in the right-rail TOC now.
586
+
587
+
### Right-rail "On this page" TOC
588
+
589
+
Every chapter page renders an `<aside class="doc-toc">` in the third grid column on desktop. The list shows H2 + H3 headings only (H4+ are excluded so the rail stays short on long chapters). The aside is sticky-positioned with `position: sticky; top: 0; height: 100vh; overflow-y: auto`.
590
+
591
+
**Scroll-spy**: `templates/assets/toc-scrollspy.js` (~1 KB) uses a single `IntersectionObserver` with `rootMargin: "-25% 0px -75% 0px"` (a 25%–75% spy band of the viewport). The link whose target heading is currently in the band gets `.is-active`. When no heading is intersecting (between two sections), the script falls back to picking the last heading whose top is above the spy line. On TOC link click, the active state syncs immediately so it doesn't lag behind the smooth-scroll animation.
592
+
593
+
The script ships only when `templates/assets/toc-scrollspy.js` exists — `website-generator.ts`'s asset pipeline registers it the same way as F4's optional `code-copy.js`. Pages with no H2 headings render an empty `<aside data-empty="true">`; CSS hides the heading and the script is a cheap no-op (no targets to observe).
594
+
595
+
### Breadcrumbs
596
+
597
+
A `<nav class="breadcrumb">` block appears between the page-header-bar (language switcher) and the chapter content. Format:
598
+
599
+
```
600
+
Home › Category › Chapter Title
601
+
```
602
+
603
+
- "Home"links to the per-language `index.html` landing page.
604
+
- "Category"is the F3 group containing the page (rendered as plain text — no per-category landing exists).
605
+
- "Chapter Title"is the current page (plain text, marked `aria-current="page"`).
606
+
607
+
When the page belongs to the synthetic anonymous group (legacy flat config), the middle segment is dropped — `Home › Chapter Title`. Separators (`›`, U+203A) are CSS pseudo-elements, so the structural HTML stays semantic (`<ol>` of `<li>` segments).
608
+
609
+
The breadcrumb is intentionally **not** placed inside the `<header class="page-header-bar">` flex container. The header is reserved for site-level controls (lang switcher, F6's incoming version selector). Keeping the breadcrumb as a separate block above the chapter avoids tight coupling with F6's layout.
- **≤1100px**: drop the third column. `.doc-toc` is hidden via `display: none`. Headings remain anchor-reachable from inline `#` links — the TOC content is recoverable, just not always visible. (Moving the rail inline would require runtime JS to relocate the `<aside>`; out of scope for F3.)
624
+
- **≤768px**: collapse to a single column. Sidebar becomes a top strip (`max-height: 40vh`), main content flows below.
Category labels themselves are localized **inline in `book.config.yaml`** — each language declares its own `category:` strings. There's no per-category lookup table; the localized label for the breadcrumb is whatever the matching nav-group's `category:` field says.
637
+
638
+
### PDF pipeline non-regression
639
+
640
+
The PDF pipeline reads `bookConfig.navigation[lang]` (the flat list). Since the flat list is constructed by walking `navigationGroups` in order and concatenating items, the PDF chapter ordering follows the grouped sequence. For the WebUI docs this means the legacy `Vfolder/Sessions All/.../Cluster Session` interleave is replaced by a clean `Getting Started → Workloads → Storage & Data → Administration → Reference` order — a small, deliberate ordering change documented in the F3 PR. The PDF pipeline itself never sees the categories, so the cover page, TOC entries, and chapter outline structure are unaffected.
* Normalize a per-language navigation value into both the flat list (for
166
+
* PDF / slug-map consumers) and the grouped list (for the F3 web sidebar).
167
+
*
168
+
* Soft-fails on per-entry problems: structurally invalid entries are
169
+
* dropped with a console warning but never crash the build. This matches
170
+
* F5's diagnostics-sink philosophy — the build keeps going so authors see
171
+
* the maximum amount of feedback per run.
172
+
*/
173
+
functionnormalizePerLangNavigation(
174
+
lang: string,
175
+
raw: RawNavigation|undefined,
176
+
): {flat: NavItem[];groups: NavGroup[]}{
177
+
if(!raw||!Array.isArray(raw)||raw.length===0){
178
+
return{flat: [],groups: []};
179
+
}
180
+
181
+
// Decide form by inspecting the first entry. The two forms cannot be
182
+
// mixed — entries that don't match the chosen form are dropped.
183
+
constgrouped=isGroupedEntry(raw[0]);
184
+
185
+
if(grouped){
186
+
constgroups: NavGroup[]=[];
187
+
constflat: NavItem[]=[];
188
+
for(constentryofraw){
189
+
if(!isGroupedEntry(entry)){
190
+
console.warn(
191
+
`[book.config.yaml] navigation.${lang}: ignoring entry with no "category" inside a grouped list (mixed forms are not allowed): ${JSON.stringify(entry)}`,
0 commit comments