From 9ea2192bd14c89fe2594b4b4cede01432806af11 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 01:48:38 +0800 Subject: [PATCH 01/18] fix(markmap): follow article text color in screen media --- assets/scss/td/_content-components.scss | 10 ++++++++++ bin/check-namespace.py | 9 ++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/assets/scss/td/_content-components.scss b/assets/scss/td/_content-components.scss index f39982a..d4637af 100644 --- a/assets/scss/td/_content-components.scss +++ b/assets/scss/td/_content-components.scss @@ -233,6 +233,16 @@ --term-color-7: #5d7189; } +// Markmap declares its text token on both the wrapper and the generated SVG. +// Override both inside article content, with enough specificity to survive +// the vendor stylesheet injected after ours. The body token follows live +// mode/preset changes; keep the vendor's dark ink when printing on white. +@media screen { + .td-content .markmap { + --markmap-text-color: var(--bs-body-color); + } +} + // Markmap highlights fenced code inside its nodes with highlight.js, whose // vendored `default.min.css` is a light-only theme with no dark counterpart -- // unlike Swagger UI and DocSearch, which carry their own and only need the diff --git a/bin/check-namespace.py b/bin/check-namespace.py index 835ae62..48491f5 100755 --- a/bin/check-namespace.py +++ b/bin/check-namespace.py @@ -104,12 +104,15 @@ } # Custom properties a third-party runtime reads from its own stylesheet: the -# theme sets them to theme its player, so they cannot carry a --td- prefix. +# theme sets them to theme its integrations, so they cannot carry a --td- prefix. # asciinema-player.css reads --term-* (palette, font, metrics); renaming them # leaves the player on its built-in black-on-white defaults inside an OINK -# terminal surface, which is how the 0.5 sweep broke it. +# terminal surface, which is how the 0.5 sweep broke it. Markmap reads its +# --markmap-text-color token on the SVG; prefixing it leaves dark text on the +# dark article canvas. THIRD_PARTY_PROPERTY = re.compile( - r"^--term-(color-(foreground|background|\d+)|font-family|line-height|cols|rows)$" + r"^--(?:term-(color-(foreground|background|\d+)|font-family|line-height|cols|rows)" + r"|markmap-text-color)$" ) BEM_CLASS = re.compile(r"^[a-z][a-z0-9-]*(?:__|--)[a-z0-9_-]+$") PLAYER_THEME_RE = re.compile( From 4e40bd880ea6e2988b9ed2046655abd7588ea8a9 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 01:48:56 +0800 Subject: [PATCH 02/18] fix: keep fixed TOC rail above the visible footer --- assets/js/docs-shell.js | 6 +- assets/scss/td/shell/_toc.scss | 3 +- bin/check-shell.py | 6 ++ tests/js/docs-shell-footer.test.js | 156 +++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 tests/js/docs-shell-footer.test.js diff --git a/assets/js/docs-shell.js b/assets/js/docs-shell.js index ad62722..96a5497 100644 --- a/assets/js/docs-shell.js +++ b/assets/js/docs-shell.js @@ -134,10 +134,12 @@ /* --------------------------------------------------------- footerOffset */ - // Shorten the fixed sidebar as the footer enters the viewport. + // Shorten fixed rails as the footer enters the viewport. function initFooterOffset() { var footer = document.querySelector('[data-td-shell-footer]'); - var panel = document.querySelector('.td-shell-sidebar__panel'); + var panel = document.querySelector( + '.td-shell-sidebar__panel, .td-shell-toc__panel', + ); if (!footer || !panel) return; var frame = 0; diff --git a/assets/scss/td/shell/_toc.scss b/assets/scss/td/shell/_toc.scss index 921fd0d..4a5d426 100644 --- a/assets/scss/td/shell/_toc.scss +++ b/assets/scss/td/shell/_toc.scss @@ -23,7 +23,8 @@ @include media-breakpoint-up(xl) { position: fixed; inset-block-start: var(--td-shell-nav-h); - inset-block-end: 0; + // Share the sidebar's available height when the footer enters view. + inset-block-end: var(--td-shell-footer-offset, 0px); inset-inline-end: 0; width: 286px; z-index: 20; diff --git a/bin/check-shell.py b/bin/check-shell.py index cfe1c17..e762c38 100755 --- a/bin/check-shell.py +++ b/bin/check-shell.py @@ -282,6 +282,12 @@ def check_sources() -> list[str]: alignment = "var(--td-shell-nav-h) + var(--td-shell-content-top, 2.5rem)" require(alignment in layout_styles and alignment in toc_styles, "collapsed rail controls no longer align with the article topline", errors) + fixed_toc = scss_block(toc_styles, "\n.td-shell-toc__panel {") + flow_toc = scss_block(toc_styles, " .td-shell-layout--toc-flow .td-shell-toc__panel {") + require("inset-block-end: var(--td-shell-footer-offset, 0px)" in fixed_toc + and "position: sticky" in flow_toc + and "inset-block-end: auto" in flow_toc, + "fixed TOC must avoid the visible footer while flow keeps its sticky boundary", errors) require("@media (min-width: 768px) and (hover: hover) and (pointer: fine)" in navbar_styles, "drawer-width viewports no longer disable auto-hide", errors) require("@media (max-width: 767.98px)" in navbar_styles diff --git a/tests/js/docs-shell-footer.test.js b/tests/js/docs-shell-footer.test.js new file mode 100644 index 0000000..1ef1723 --- /dev/null +++ b/tests/js/docs-shell-footer.test.js @@ -0,0 +1,156 @@ +'use strict'; + +// The footer is shared by two independently optional rails. Exercise the +// real initializer, including event coalescing and footer-height changes; +// rendered rail geometry belongs to the documentation site's browser suite. +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const test = require('node:test'); +const vm = require('node:vm'); + +const SOURCE = fs.readFileSync( + path.join(__dirname, '..', '..', 'assets/js/docs-shell.js'), + 'utf8', +); + +function build({ footer = true, sidebar = true, toc = true, visual = false } = {}) { + const properties = new Map(); + const attrs = new Map(); + const listeners = new Map(); + const viewportListeners = new Map(); + const frames = new Map(); + const measurements = { top: 1100, reads: 0 }; + const footerElement = { + getBoundingClientRect() { + measurements.reads += 1; + return { top: measurements.top }; + }, + }; + let resize; + let frameID = 0; + const html = { + style: { + setProperty(name, value) { properties.set(name, value); }, + }, + hasAttribute(name) { return attrs.has(name); }, + getAttribute(name) { return attrs.get(name) || null; }, + setAttribute(name, value) { attrs.set(name, value); }, + removeAttribute(name) { attrs.delete(name); }, + }; + const document = { + documentElement: html, + querySelector(selector) { + if (selector === '[data-td-shell-footer]') return footer ? footerElement : null; + // Return only the rail-presence query used by footer-offset. Other + // initializers should take their ordinary missing-component exits. + if (selector === '.td-shell-sidebar__panel') return sidebar ? {} : null; + if (selector === '.td-shell-sidebar__panel, .td-shell-toc__panel') { + return sidebar || toc ? {} : null; + } + return null; + }, + querySelectorAll() { return []; }, + getElementById() { return null; }, + addEventListener() {}, + }; + const window = { + innerHeight: 1000, + addEventListener(name, callback) { listeners.set(name, callback); }, + requestAnimationFrame(callback) { + frames.set(++frameID, callback); + return frameID; + }, + matchMedia() { return { matches: false, addEventListener() {} }; }, + ResizeObserver: class {}, + }; + if (visual) { + window.visualViewport = { + height: 800, + addEventListener(name, callback) { viewportListeners.set(name, callback); }, + }; + } + const context = { + window, + document, + ResizeObserver: class { + constructor(callback) { resize = callback; } + observe(target) { assert.equal(target, footerElement); } + }, + }; + vm.runInNewContext(SOURCE, context); + const flush = () => { + for (let guard = 0; frames.size && guard < 20; guard += 1) { + const pending = [...frames.values()]; + frames.clear(); + pending.forEach((callback) => callback()); + } + }; + flush(); + return { + window, measurements, listeners, viewportListeners, flush, + resize() { resize(); }, + offset() { return properties.get('--td-shell-footer-offset'); }, + }; +} + +for (const rails of [ + { sidebar: true, toc: true }, + { sidebar: true, toc: false }, + { sidebar: false, toc: true }, +]) { + test(`footer tracks visible height with ${JSON.stringify(rails)}`, () => { + const env = build(rails); + assert.equal(env.offset(), '0px'); + env.measurements.top = 670; + env.listeners.get('scroll')(); + env.flush(); + assert.equal(env.offset(), '331px'); + env.measurements.top = 1200; + env.listeners.get('scroll')(); + env.flush(); + assert.equal(env.offset(), '0px'); + }); +} + +test('scroll bursts share one measurement and a resized footer is observed', () => { + const env = build(); + const reads = env.measurements.reads; + env.measurements.top = 700; + for (let i = 0; i < 10; i += 1) env.listeners.get('scroll')(); + assert.equal(env.measurements.reads, reads); + env.flush(); + assert.equal(env.measurements.reads, reads + 1); + assert.equal(env.offset(), '301px'); + + env.measurements.top = 950; + env.resize(); + env.flush(); + assert.equal(env.offset(), '51px'); + + env.window.innerHeight = 900; + env.listeners.get('resize')(); + env.flush(); + assert.equal(env.offset(), '0px'); +}); + +test('the visual viewport owns offset when browser chrome changes its height', () => { + const env = build({ sidebar: false, visual: true }); + env.measurements.top = 700; + env.viewportListeners.get('scroll')(); + env.flush(); + assert.equal(env.offset(), '101px'); + env.window.visualViewport.height = 750; + env.viewportListeners.get('resize')(); + env.flush(); + assert.equal(env.offset(), '51px'); +}); + +for (const absent of [{ footer: false }, { sidebar: false, toc: false }]) { + test(`no footer measurements without a consumer: ${JSON.stringify(absent)}`, () => { + const env = build(absent); + assert.equal(env.offset(), undefined); + assert.equal(env.measurements.reads, 0); + assert.equal(env.listeners.has('scroll'), false); + }); +} From db7a5cb5bfc26176900ff89ca4483e81330b60ac Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 01:49:31 +0800 Subject: [PATCH 03/18] fix: preserve readable widths in technical tables --- assets/scss/td/_content.scss | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/assets/scss/td/_content.scss b/assets/scss/td/_content.scss index e98cbd0..e68989b 100644 --- a/assets/scss/td/_content.scss +++ b/assets/scss/td/_content.scss @@ -206,6 +206,34 @@ margin-bottom: 0; } + @media screen { + &:not(.td-table-scroll--static) > table { + // The viewport, not the table, owns the horizontal width constraint. + max-width: none; + + th, + td { + // Unlike anywhere, break-word keeps technical tokens in the + // intrinsic minimum width, with an escape hatch for very long ones. + overflow-wrap: break-word; + word-break: normal; + + a, + code { + overflow-wrap: inherit; + word-break: inherit; + } + } + + td { + // Bound prose's preferred measure instead of making a long + // description one enormous line. Auto layout may distribute extra + // space; matrix headers keep their explicit nowrap behavior. + max-width: 40ch; + } + } + } + &--full { width: 100%; max-width: none; From 5c299ad77e11ee05fbec19378699f236af5289d6 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 15:52:58 +0800 Subject: [PATCH 04/18] test(markmap): guard screen-only text token contract --- tests/js/markmap-theme-source.test.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/js/markmap-theme-source.test.js diff --git a/tests/js/markmap-theme-source.test.js b/tests/js/markmap-theme-source.test.js new file mode 100644 index 0000000..82974d7 --- /dev/null +++ b/tests/js/markmap-theme-source.test.js @@ -0,0 +1,23 @@ +'use strict'; + +// Source contract only; rendered contrast and interactions belong to the +// sibling documentation site's Markmap browser suite. +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const test = require('node:test'); + +const source = fs.readFileSync( + path.join(__dirname, '../../assets/scss/td/_content-components.scss'), 'utf8', +).replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, ''); + +test('Markmap maps the article token on screen with wrapper/SVG specificity', () => { + assert.match(source, + /@media\s+screen\s*\{\s*\.td-content\s+\.markmap\s*\{\s*--markmap-text-color:\s*var\(--bs-body-color\);\s*\}\s*\}/, + 'keep the mapping screen-only and applicable to both wrapper and SVG'); +}); + +test('Markmap has no competing text-token assignment or important override', () => { + const declarations = source.match(/--markmap-text-color\s*:[^;]+;/g) || []; + assert.deepEqual(declarations, ['--markmap-text-color: var(--bs-body-color);']); +}); From 16f5a2a522ec501d2edce7cebf61bc72eb9c0910 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 15:54:58 +0800 Subject: [PATCH 05/18] fix(tables): fit ordinary prose and isolate explicit wide tables --- assets/scss/td/_content.scss | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/assets/scss/td/_content.scss b/assets/scss/td/_content.scss index e68989b..fe60864 100644 --- a/assets/scss/td/_content.scss +++ b/assets/scss/td/_content.scss @@ -208,11 +208,17 @@ @media screen { &:not(.td-table-scroll--static) > table { - // The viewport, not the table, owns the horizontal width constraint. + // Ordinary prose should fit before scrolling. Tables whose technical + // columns need a wider reading measure opt in through a native class; + // do not guess that intent from a column's name, count, or language. + width: auto; max-width: none; - th, - td { + &.td-table--wide { + width: max-content; + } + + > :is(thead, tbody, tfoot) > tr > :is(th, td) { // Unlike anywhere, break-word keeps technical tokens in the // intrinsic minimum width, with an escape hatch for very long ones. overflow-wrap: break-word; @@ -225,7 +231,7 @@ } } - td { + > :is(thead, tbody, tfoot) > tr > td { // Bound prose's preferred measure instead of making a long // description one enormous line. Auto layout may distribute extra // space; matrix headers keep their explicit nowrap behavior. @@ -245,6 +251,8 @@ &--static { max-width: none; overflow: visible; + // A nested static viewport must not inherit an outer screen cell's rule. + overflow-wrap: anywhere; } } } From acad0229b1745e5ee6f66237d392b2d2bc3e3023 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 15:55:35 +0800 Subject: [PATCH 06/18] test(tables): guard fitting wide-mode and nested static boundaries --- tests/js/table-layout-source.test.js | 55 ++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/js/table-layout-source.test.js diff --git a/tests/js/table-layout-source.test.js b/tests/js/table-layout-source.test.js new file mode 100644 index 0000000..618185c --- /dev/null +++ b/tests/js/table-layout-source.test.js @@ -0,0 +1,55 @@ +'use strict'; + +// Deterministic source guard, not browser layout acceptance. The actual +// prose/technical geometry is checked in the sibling documentation site. +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const test = require('node:test'); + +const root = path.join(__dirname, '../..'); +const source = fs.readFileSync(path.join(root, 'assets/scss/td/_content.scss'), 'utf8') + .replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, ''); + +function blockAfter(text, marker) { + const start = text.indexOf(marker); + assert.notEqual(start, -1, 'missing owning selector: ' + marker); + const open = text.indexOf('{', start); + let depth = 1; + for (let end = open + 1; end < text.length; end++) { + if (text[end] === '{') depth++; + if (text[end] === '}' && --depth === 0) return text.slice(open + 1, end); + } + assert.fail('unclosed source block: ' + marker); +} + +const viewport = blockAfter(source, '.td-table-scroll {'); +const screen = blockAfter(viewport, '@media screen'); +const policy = blockAfter(screen, '&:not(.td-table-scroll--static) > table'); + +test('ordinary screen tables prefer fitting; wide mode is explicit', () => { + const top = policy.slice(0, policy.indexOf('&.td-table--wide')); + assert.match(top, /\bwidth:\s*auto;/); + assert.match(top, /\bmax-width:\s*none;/); + assert.doesNotMatch(top, /\bwidth:\s*max-content;/); + assert.match(blockAfter(policy, '&.td-table--wide'), /\bwidth:\s*max-content;/); +}); + +test('both layout policies preserve wrapping and the scrolling viewport', () => { + assert.match(policy, /overflow-wrap:\s*break-word;/); + assert.match(policy, /word-break:\s*normal;/); + assert.match(policy, /max-width:\s*40ch;/); + assert.match(viewport, /overflow-x:\s*auto;/); + assert.match(blockAfter(viewport, '> table {'), /min-width:\s*100%;/); +}); + +test('wide mode is assigned only inside the non-static screen policy', () => { + assert.equal((source.match(/&\.td-table--wide\s*\{/g) || []).length, 1); + assert.equal((policy.match(/&\.td-table--wide\s*\{/g) || []).length, 1); +}); + +test('cell sizing does not leak into nested static tables', () => { + assert.ok(policy.includes('> :is(thead, tbody, tfoot) > tr > :is(th, td)')); + assert.ok(policy.includes('> :is(thead, tbody, tfoot) > tr > td')); + assert.match(blockAfter(viewport, '&--static'), /overflow-wrap:\s*anywhere;/); +}); From 7121e6a46ef9e15d22c8aa214269143eeb63a423 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 15:57:07 +0800 Subject: [PATCH 07/18] test(toc): cover initial restoration observer fallback and mixed events --- tests/js/docs-shell-footer.test.js | 39 ++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/tests/js/docs-shell-footer.test.js b/tests/js/docs-shell-footer.test.js index 1ef1723..8c95c1c 100644 --- a/tests/js/docs-shell-footer.test.js +++ b/tests/js/docs-shell-footer.test.js @@ -14,13 +14,13 @@ const SOURCE = fs.readFileSync( 'utf8', ); -function build({ footer = true, sidebar = true, toc = true, visual = false } = {}) { +function build({ footer = true, sidebar = true, toc = true, visual = false, top = 1100, observer = true } = {}) { const properties = new Map(); const attrs = new Map(); const listeners = new Map(); const viewportListeners = new Map(); const frames = new Map(); - const measurements = { top: 1100, reads: 0 }; + const measurements = { top, reads: 0 }; const footerElement = { getBoundingClientRect() { measurements.reads += 1; @@ -64,6 +64,7 @@ function build({ footer = true, sidebar = true, toc = true, visual = false } = { matchMedia() { return { matches: false, addEventListener() {} }; }, ResizeObserver: class {}, }; + if (!observer) delete window.ResizeObserver; if (visual) { window.visualViewport = { height: 800, @@ -154,3 +155,37 @@ for (const absent of [{ footer: false }, { sidebar: false, toc: false }]) { assert.equal(env.listeners.has('scroll'), false); }); } + +// Scroll restoration may place the footer in view before the first event. +test('TOC-only restored page receives its initial footer boundary', () => { + const env = build({ sidebar: false, top: 670 }); + assert.equal(env.offset(), '331px'); + assert.equal(env.measurements.reads, 1); +}); + +test('scroll and resize still update the TOC without ResizeObserver', () => { + const env = build({ sidebar: false, observer: false }); + env.measurements.top = 700; + env.listeners.get('scroll')(); + env.flush(); + assert.equal(env.offset(), '301px'); + env.window.innerHeight = 900; + env.listeners.get('resize')(); + env.flush(); + assert.equal(env.offset(), '201px'); +}); + +test('mixed observer, window and visual viewport events share one frame', () => { + const env = build({ sidebar: false, visual: true }); + const reads = env.measurements.reads; + env.measurements.top = 700; + env.listeners.get('scroll')(); + env.listeners.get('resize')(); + env.viewportListeners.get('scroll')(); + env.viewportListeners.get('resize')(); + env.resize(); + assert.equal(env.measurements.reads, reads); + env.flush(); + assert.equal(env.measurements.reads, reads + 1); + assert.equal(env.offset(), '101px'); +}); From 34fab16792c8ba06fb540e0fe9de2972e43b8b31 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 15:58:39 +0800 Subject: [PATCH 08/18] test(tables): add fitting prose and explicit technical-width inputs --- .../content/fixtures/table-layout-policy.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/site/content/fixtures/table-layout-policy.md diff --git a/tests/site/content/fixtures/table-layout-policy.md b/tests/site/content/fixtures/table-layout-policy.md new file mode 100644 index 0000000..dd08a34 --- /dev/null +++ b/tests/site/content/fixtures/table-layout-policy.md @@ -0,0 +1,36 @@ +--- +title: Table layout policy +type: docs +--- + +## Ordinary prose + +| Owner | Project | State | When | Notes | +| --- | --- | --- | --- | --- | +| Alice handles planning and coordination | Tokyo release | Ready for review | After final review and approval | A short explanation of the current delivery status. | +| Bob handles design and delivery | Osaka patch | Waiting on tests | Before the next release window | Another short explanation of the expected next step. | +{#ordinary-prose-table} + +## Explicit technical width + +| Variable | Required | Default | Configuration key | Description | +| --- | --- | --- | --- | --- | +| `HG_PD_GRPC_HOST` | Yes | None | `grpc.host` | Advertised gRPC hostname/IP; use container hostnames within a container network. | +| `HG_PD_INITIAL_STORE_COUNT` | No | `1` | `pd.initial-store-count` | Minimum Store count needed for cluster availability. | +| `HG_PD_ACTUATOR_EXPOSURE` | No | `health,metrics,prometheus` | `management.endpoints.web.exposure.include` | Public Actuator endpoint allowlist; `*` is forbidden. | +{#wide-technical-table .td-table--wide} + +## Short ordinary table + +| City | Port | Purpose | +| --- | --- | --- | +| Tokyo | 8080 | Graph API | +| Osaka | 8686 | Metadata | +{#short-ordinary-table} + +## Explicit width with existing modifiers + +| Component | Read | Write | +| --- | --- | --- | +| A long component identifier | Yes | No | +{#wide-matrix-table .matrix .full-width .td-table--wide caption="Existing modifiers remain independent"} From 714ff126bde1163e502718e30369069f2faee507 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 15:59:43 +0800 Subject: [PATCH 09/18] test(tables): mirror native layout-policy inputs in Chinese --- .../fixtures/table-layout-policy.zh.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/site/content/fixtures/table-layout-policy.zh.md diff --git a/tests/site/content/fixtures/table-layout-policy.zh.md b/tests/site/content/fixtures/table-layout-policy.zh.md new file mode 100644 index 0000000..cabdd5e --- /dev/null +++ b/tests/site/content/fixtures/table-layout-policy.zh.md @@ -0,0 +1,36 @@ +--- +title: 表格布局策略 +type: docs +--- + +## 普通正文 + +| 负责人 | 项目 | 状态 | 时间 | 说明 | +| --- | --- | --- | --- | --- | +| Alice 负责规划与协调 | 东京版本 | 等待评审 | 最终评审通过后 | 关于当前交付状态的简短说明。 | +| Bob 负责设计与交付 | 大阪补丁 | 等待测试 | 下次发布之前 | 关于后续步骤的简短说明。 | +{#ordinary-prose-table} + +## 显式技术宽表 + +| 变量 | 必填 | 默认值 | 对应配置项 | 描述 | +| --- | --- | --- | --- | --- | +| `HG_PD_GRPC_HOST` | 是 | 无 | `grpc.host` | 对外宣告的 gRPC 主机名或 IP 地址;容器网络内部应使用容器主机名。 | +| `HG_PD_INITIAL_STORE_COUNT` | 否 | `1` | `pd.initial-store-count` | 集群可用时所需的最少存储节点数量。 | +| `HG_PD_ACTUATOR_EXPOSURE` | 否 | `health,metrics,prometheus` | `management.endpoints.web.exposure.include` | 对外开放的监控端点白名单,不允许使用通配符。 | +{#wide-technical-table .td-table--wide} + +## 短普通表格 + +| 城市 | 端口 | 用途 | +| --- | --- | --- | +| 东京 | 8080 | 图接口 | +| 大阪 | 8686 | 元数据 | +{#short-ordinary-table} + +## 显式宽度与已有修饰符 + +| 组件 | 读取 | 写入 | +| --- | --- | --- | +| 长组件标识符 | 是 | 否 | +{#wide-matrix-table .matrix .full-width .td-table--wide caption="已有修饰符保持独立"} From d20f908d231b906b863bcf4cdb1b6681a4b49735 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 16:30:10 +0800 Subject: [PATCH 10/18] fix(tables): preserve consumer overrides with zero-weight guards --- assets/scss/td/_content.scss | 9 +++++---- tests/js/table-layout-source.test.js | 14 +++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/assets/scss/td/_content.scss b/assets/scss/td/_content.scss index fe60864..90a2fcf 100644 --- a/assets/scss/td/_content.scss +++ b/assets/scss/td/_content.scss @@ -207,18 +207,19 @@ } @media screen { - &:not(.td-table-scroll--static) > table { + // Structural guards must not raise the cost of consumer overrides. + &:where(:not(.td-table-scroll--static)) > table { // Ordinary prose should fit before scrolling. Tables whose technical // columns need a wider reading measure opt in through a native class; // do not guess that intent from a column's name, count, or language. width: auto; max-width: none; - &.td-table--wide { + &:where(.td-table--wide) { width: max-content; } - > :is(thead, tbody, tfoot) > tr > :is(th, td) { + > :where(thead, tbody, tfoot) > :where(tr) > :is(th, td) { // Unlike anywhere, break-word keeps technical tokens in the // intrinsic minimum width, with an escape hatch for very long ones. overflow-wrap: break-word; @@ -231,7 +232,7 @@ } } - > :is(thead, tbody, tfoot) > tr > td { + > :where(thead, tbody, tfoot) > :where(tr) > td { // Bound prose's preferred measure instead of making a long // description one enormous line. Auto layout may distribute extra // space; matrix headers keep their explicit nowrap behavior. diff --git a/tests/js/table-layout-source.test.js b/tests/js/table-layout-source.test.js index 618185c..07be387 100644 --- a/tests/js/table-layout-source.test.js +++ b/tests/js/table-layout-source.test.js @@ -25,14 +25,14 @@ function blockAfter(text, marker) { const viewport = blockAfter(source, '.td-table-scroll {'); const screen = blockAfter(viewport, '@media screen'); -const policy = blockAfter(screen, '&:not(.td-table-scroll--static) > table'); +const policy = blockAfter(screen, '&:where(:not(.td-table-scroll--static)) > table'); test('ordinary screen tables prefer fitting; wide mode is explicit', () => { - const top = policy.slice(0, policy.indexOf('&.td-table--wide')); + const top = policy.slice(0, policy.indexOf('&:where(.td-table--wide)')); assert.match(top, /\bwidth:\s*auto;/); assert.match(top, /\bmax-width:\s*none;/); assert.doesNotMatch(top, /\bwidth:\s*max-content;/); - assert.match(blockAfter(policy, '&.td-table--wide'), /\bwidth:\s*max-content;/); + assert.match(blockAfter(policy, '&:where(.td-table--wide)'), /\bwidth:\s*max-content;/); }); test('both layout policies preserve wrapping and the scrolling viewport', () => { @@ -44,12 +44,12 @@ test('both layout policies preserve wrapping and the scrolling viewport', () => }); test('wide mode is assigned only inside the non-static screen policy', () => { - assert.equal((source.match(/&\.td-table--wide\s*\{/g) || []).length, 1); - assert.equal((policy.match(/&\.td-table--wide\s*\{/g) || []).length, 1); + assert.equal((source.match(/&:where\(\.td-table--wide\)\s*\{/g) || []).length, 1); + assert.equal((policy.match(/&:where\(\.td-table--wide\)\s*\{/g) || []).length, 1); }); test('cell sizing does not leak into nested static tables', () => { - assert.ok(policy.includes('> :is(thead, tbody, tfoot) > tr > :is(th, td)')); - assert.ok(policy.includes('> :is(thead, tbody, tfoot) > tr > td')); + assert.ok(policy.includes('> :where(thead, tbody, tfoot) > :where(tr) > :is(th, td)')); + assert.ok(policy.includes('> :where(thead, tbody, tfoot) > :where(tr) > td')); assert.match(blockAfter(viewport, '&--static'), /overflow-wrap:\s*anywhere;/); }); From 8773bd1ee57a426de7cb485f4413a3395618b208 Mon Sep 17 00:00:00 2001 From: imbajin Date: Wed, 14 Oct 2026 14:21:08 +0800 Subject: [PATCH 11/18] test(tables): tighten width guard and verify technical columns --- .github/workflows/ci.yml | 1 + bin/check-table-layout-browser.py | 146 ++++++++++++++++++ tests/js/table-layout-source.test.js | 22 ++- .../content/fixtures/table-layout-policy.md | 8 + .../fixtures/table-layout-policy.zh.md | 8 + 5 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 bin/check-table-layout-browser.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6484076..53ee8e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,6 +111,7 @@ jobs: pdf="$RUNNER_TEMP/oink-book.pdf" hugo --source tests/site --themesDir "$GITHUB_WORKSPACE/.." \ --destination "$public" --minify --printPathWarnings --panicOnWarning + python3 bin/check-table-layout-browser.py --public "$public" --chrome "$OINK_CHROME" python3 bin/book-epub.py \ --manifest "$public/book/book.json" \ --public "$public" \ diff --git a/bin/check-table-layout-browser.py b/bin/check-table-layout-browser.py new file mode 100644 index 0000000..b049c0a --- /dev/null +++ b/bin/check-table-layout-browser.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +"""Verify native table geometry in the built bilingual Hugo fixture (no JS deps).""" +import argparse +import json +from functools import partial +from html.parser import HTMLParser +from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +import subprocess +import tempfile +from threading import Thread + + +PROBE = r'''''' + + +class Results(HTMLParser): + def __init__(self): + super().__init__() + self.record = False + self.value = '' + + def handle_starttag(self, tag, attrs): + if tag == 'output' and ('id', 'oink-table-probe') in attrs: + self.record = True + + def handle_data(self, data): + if self.record: + self.value += data + + def handle_endtag(self, tag): + if tag == 'output': + self.record = False + + +class FixtureHandler(SimpleHTTPRequestHandler): + def do_GET(self): + if self.path.split('?')[0].rstrip('/') in ( + '/fixtures/table-layout-policy', '/zh/fixtures/table-layout-policy' + ): + page = Path(self.translate_path(self.path)) / 'index.html' + if not page.is_file(): + self.send_error(404) + return + html = page.read_text(encoding='utf-8') + if '' not in html: + self.send_error(500, 'missing closing body') + return + encoded = html.replace('', PROBE + '', 1).encode('utf-8') + self.send_response(200) + self.send_header('Content-Type', 'text/html; charset=utf-8') + self.send_header('Content-Length', str(len(encoded))) + self.end_headers() + self.wfile.write(encoded) + else: + super().do_GET() + + def log_message(self, *_): + pass + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--public', required=True, type=Path) + parser.add_argument('--chrome', required=True) + args = parser.parse_args() + with ThreadingHTTPServer(('127.0.0.1', 0), partial( + FixtureHandler, directory=str(args.public.resolve()))) as server: + thread = Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + for language in ('', '/zh'): + for width in (1440, 390): + with tempfile.TemporaryDirectory(prefix='oink-table-chrome-') as profile: + url = (f'http://127.0.0.1:{server.server_port}{language}' + '/fixtures/table-layout-policy/') + cmd = [args.chrome, '--headless', '--dump-dom', + '--disable-background-networking', '--disable-extensions', + '--no-first-run', '--no-default-browser-check', + '--host-resolver-rules=MAP * ~NOTFOUND, EXCLUDE 127.0.0.1', + '--virtual-time-budget=3000', f'--user-data-dir={profile}', + f'--window-size={width},1000', url] + run = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + result = Results() + result.feed(run.stdout) + if run.returncode != 0 or not result.value: + raise RuntimeError(f'Chrome did not evaluate {url} ({width}px): ' + f'{run.returncode}; {run.stderr[-400:]}') + report = json.loads(result.value) + if report['problems']: + raise AssertionError(f'{language or "en"}/{width}px: {report}') + print(f'{language or "en"}/{width}px: passed, ' + f'key {report["column"]:.1f}px, ' + f'description {report["description"]:.1f}px') + finally: + server.shutdown() + thread.join(timeout=5) + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/tests/js/table-layout-source.test.js b/tests/js/table-layout-source.test.js index 07be387..b82bbc7 100644 --- a/tests/js/table-layout-source.test.js +++ b/tests/js/table-layout-source.test.js @@ -1,7 +1,7 @@ 'use strict'; -// Deterministic source guard, not browser layout acceptance. The actual -// prose/technical geometry is checked in the sibling documentation site. +// Deterministic source guard; native browser geometry is checked by +// bin/check-table-layout-browser.py against the built fixtures. const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); @@ -26,13 +26,25 @@ function blockAfter(text, marker) { const viewport = blockAfter(source, '.td-table-scroll {'); const screen = blockAfter(viewport, '@media screen'); const policy = blockAfter(screen, '&:where(:not(.td-table-scroll--static)) > table'); +const widthIs = value => new RegExp(`(?:^|[;{])\\s*width:\\s*${value};`, 'm'); test('ordinary screen tables prefer fitting; wide mode is explicit', () => { const top = policy.slice(0, policy.indexOf('&:where(.td-table--wide)')); - assert.match(top, /\bwidth:\s*auto;/); + assert.match(top, widthIs('auto')); assert.match(top, /\bmax-width:\s*none;/); - assert.doesNotMatch(top, /\bwidth:\s*max-content;/); - assert.match(blockAfter(policy, '&:where(.td-table--wide)'), /\bwidth:\s*max-content;/); + assert.doesNotMatch(top, widthIs('max-content')); + assert.match(blockAfter(policy, '&:where(.td-table--wide)'), widthIs('max-content')); +}); + +test('width declarations cannot be spoofed by min-/max-width', () => { + const top = policy.slice(0, policy.indexOf('&:where(.td-table--wide)')); + const brokenTop = top.replace(/(^\s*)width:\s*auto;/m, '$1min-width: auto;'); + assert.notEqual(brokenTop, top); + assert.doesNotMatch(brokenTop, widthIs('auto')); + const wide = blockAfter(policy, '&:where(.td-table--wide)'); + const brokenWide = wide.replace(/(^\s*)width:\s*max-content;/m, '$1max-width: max-content;'); + assert.notEqual(brokenWide, wide); + assert.doesNotMatch(brokenWide, widthIs('max-content')); }); test('both layout policies preserve wrapping and the scrolling viewport', () => { diff --git a/tests/site/content/fixtures/table-layout-policy.md b/tests/site/content/fixtures/table-layout-policy.md index dd08a34..c252124 100644 --- a/tests/site/content/fixtures/table-layout-policy.md +++ b/tests/site/content/fixtures/table-layout-policy.md @@ -20,6 +20,14 @@ type: docs | `HG_PD_ACTUATOR_EXPOSURE` | No | `health,metrics,prometheus` | `management.endpoints.web.exposure.include` | Public Actuator endpoint allowlist; `*` is forbidden. | {#wide-technical-table .td-table--wide} +## Three-column configuration identifiers + +| config option | default value | description | +| --- | --- | --- | +| rocksdb.data_path | `rocksdb-data/data` | RocksDB data directory; must not be empty. | +| rocksdb.data_disks | `[]` | Assign separate disks using STORE/TABLE: /path/disk. Supported keys include g/vertex, g/edge_out, g/edge_in, g/vertex_label_index, g/edge_label_index, g/range_int_index, g/range_float_index, g/range_long_index, g/range_double_index, g/secondary_index, g/search_index, g/shard_index, g/unique_index, and g/olap. Do not reuse rocksdb.data_path. | +{#configuration-keys-table .td-table--wide} + ## Short ordinary table | City | Port | Purpose | diff --git a/tests/site/content/fixtures/table-layout-policy.zh.md b/tests/site/content/fixtures/table-layout-policy.zh.md index cabdd5e..c49e9bb 100644 --- a/tests/site/content/fixtures/table-layout-policy.zh.md +++ b/tests/site/content/fixtures/table-layout-policy.zh.md @@ -20,6 +20,14 @@ type: docs | `HG_PD_ACTUATOR_EXPOSURE` | 否 | `health,metrics,prometheus` | `management.endpoints.web.exposure.include` | 对外开放的监控端点白名单,不允许使用通配符。 | {#wide-technical-table .td-table--wide} +## 三列配置标识符 + +| 配置项 | 默认值 | 说明 | +| --- | --- | --- | +| rocksdb.data_path | `rocksdb-data/data` | RocksDB 数据目录,不能为空。 | +| rocksdb.data_disks | `[]` | 使用 STORE/TABLE: /path/disk 指定不同表的数据目录。支持的键包括 g/vertex、g/edge_out、g/edge_in、g/vertex_label_index、g/edge_label_index、g/range_int_index、g/range_float_index、g/range_long_index、g/range_double_index、g/secondary_index、g/search_index、g/shard_index、g/unique_index 和 g/olap。不能复用 rocksdb.data_path。 | +{#configuration-keys-table .td-table--wide} + ## 短普通表格 | 城市 | 端口 | 用途 | From 548e36d001212b4ebda1f05b654dae1d99c259b2 Mon Sep 17 00:00:00 2001 From: imbajin Date: Fri, 16 Oct 2026 16:27:35 +0800 Subject: [PATCH 12/18] fix(toc): refresh footer offset after tab changes --- assets/js/docs-shell.js | 1 + tests/js/docs-shell-footer.test.js | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/assets/js/docs-shell.js b/assets/js/docs-shell.js index 67f9e35..c3417f5 100644 --- a/assets/js/docs-shell.js +++ b/assets/js/docs-shell.js @@ -161,6 +161,7 @@ window.addEventListener('scroll', schedule, { passive: true }); window.addEventListener('resize', schedule); + document.addEventListener('td:tab-shown', schedule); if (window.visualViewport) { window.visualViewport.addEventListener('scroll', schedule, { passive: true, diff --git a/tests/js/docs-shell-footer.test.js b/tests/js/docs-shell-footer.test.js index 8c95c1c..7f6a23e 100644 --- a/tests/js/docs-shell-footer.test.js +++ b/tests/js/docs-shell-footer.test.js @@ -18,6 +18,7 @@ function build({ footer = true, sidebar = true, toc = true, visual = false, top const properties = new Map(); const attrs = new Map(); const listeners = new Map(); + const documentListeners = new Map(); const viewportListeners = new Map(); const frames = new Map(); const measurements = { top, reads: 0 }; @@ -52,7 +53,11 @@ function build({ footer = true, sidebar = true, toc = true, visual = false, top }, querySelectorAll() { return []; }, getElementById() { return null; }, - addEventListener() {}, + addEventListener(name, callback) { documentListeners.set(name, callback); }, + dispatchEvent(event) { + const callback = documentListeners.get(event.type); + if (callback) callback(event); + }, }; const window = { innerHeight: 1000, @@ -89,7 +94,7 @@ function build({ footer = true, sidebar = true, toc = true, visual = false, top }; flush(); return { - window, measurements, listeners, viewportListeners, flush, + window, document, measurements, listeners, viewportListeners, flush, resize() { resize(); }, offset() { return properties.get('--td-shell-footer-offset'); }, }; @@ -112,6 +117,23 @@ for (const rails of [ env.flush(); assert.equal(env.offset(), '0px'); }); + + test(`tab reflow refreshes the footer boundary without scrolling with ${JSON.stringify(rails)}`, () => { + const env = build({ ...rails, top: 670 }); + assert.equal(env.offset(), '331px'); + const reads = env.measurements.reads; + + env.measurements.top = 1200; + env.document.dispatchEvent({ type: 'td:tab-shown' }); + assert.equal(env.measurements.reads, reads); + env.flush(); + assert.equal(env.offset(), '0px'); + + env.measurements.top = 670; + env.document.dispatchEvent({ type: 'td:tab-shown' }); + env.flush(); + assert.equal(env.offset(), '331px'); + }); } test('scroll bursts share one measurement and a resized footer is observed', () => { @@ -183,6 +205,7 @@ test('mixed observer, window and visual viewport events share one frame', () => env.listeners.get('resize')(); env.viewportListeners.get('scroll')(); env.viewportListeners.get('resize')(); + env.document.dispatchEvent({ type: 'td:tab-shown' }); env.resize(); assert.equal(env.measurements.reads, reads); env.flush(); From 56da1aaf418d9c1e72d78ae6c2335011423bad5e Mon Sep 17 00:00:00 2001 From: imbajin Date: Sat, 17 Oct 2026 11:40:19 +0800 Subject: [PATCH 13/18] fix(toc): use layout viewport for footer clearance --- assets/js/docs-shell.js | 6 ++---- tests/js/docs-shell-footer.test.js | 30 ++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/assets/js/docs-shell.js b/assets/js/docs-shell.js index c3417f5..e9740f3 100644 --- a/assets/js/docs-shell.js +++ b/assets/js/docs-shell.js @@ -145,12 +145,10 @@ function update() { frame = 0; - var viewportHeight = window.visualViewport - ? window.visualViewport.height - : window.innerHeight; + // Fixed rails and footer bounds both use layout-viewport coordinates. var offset = Math.max( 0, - viewportHeight - footer.getBoundingClientRect().top, + window.innerHeight - footer.getBoundingClientRect().top, ); if (offset > 0) offset += 1; html.style.setProperty('--td-shell-footer-offset', offset + 'px'); diff --git a/tests/js/docs-shell-footer.test.js b/tests/js/docs-shell-footer.test.js index 7f6a23e..5a3c27b 100644 --- a/tests/js/docs-shell-footer.test.js +++ b/tests/js/docs-shell-footer.test.js @@ -73,6 +73,7 @@ function build({ footer = true, sidebar = true, toc = true, visual = false, top if (visual) { window.visualViewport = { height: 800, + offsetTop: 0, addEventListener(name, callback) { viewportListeners.set(name, callback); }, }; } @@ -157,16 +158,29 @@ test('scroll bursts share one measurement and a resized footer is observed', () assert.equal(env.offset(), '0px'); }); -test('the visual viewport owns offset when browser chrome changes its height', () => { - const env = build({ sidebar: false, visual: true }); - env.measurements.top = 700; +test('fixed rails use layout coordinates during pinch zoom and visual panning', () => { + const env = build({ sidebar: false, visual: true, top: 650 }); + assert.equal(env.offset(), '351px'); + + env.window.visualViewport.height = 500; + env.viewportListeners.get('resize')(); + env.flush(); + assert.equal(env.offset(), '351px'); + + env.window.visualViewport.offsetTop = 300; env.viewportListeners.get('scroll')(); env.flush(); - assert.equal(env.offset(), '101px'); - env.window.visualViewport.height = 750; - env.viewportListeners.get('resize')(); + assert.equal(env.offset(), '351px'); + + env.window.innerHeight = 900; + env.listeners.get('resize')(); env.flush(); - assert.equal(env.offset(), '51px'); + assert.equal(env.offset(), '251px'); + + env.measurements.top = 1200; + env.listeners.get('scroll')(); + env.flush(); + assert.equal(env.offset(), '0px'); }); for (const absent of [{ footer: false }, { sidebar: false, toc: false }]) { @@ -210,5 +224,5 @@ test('mixed observer, window and visual viewport events share one frame', () => assert.equal(env.measurements.reads, reads); env.flush(); assert.equal(env.measurements.reads, reads + 1); - assert.equal(env.offset(), '101px'); + assert.equal(env.offset(), '301px'); }); From 8372a676991e59486124c9c6bd4a66d408631734 Mon Sep 17 00:00:00 2001 From: Feng Ruohang Date: Sat, 17 Oct 2026 22:34:12 +0800 Subject: [PATCH 14/18] build: advance Hugo to 0.168 with standalone Dart Sass --- .github/workflows/ci.yml | 24 +++++++++++++++++++++++- AGENTS.md | 4 +++- CHANGELOG.md | 14 ++++++++++++++ README.md | 14 ++++++++------ assets/scss/td/_chroma.scss | 4 +++- assets/scss/td/_release.scss | 2 +- hugo.yaml | 2 +- layouts/_partials/head-css.html | 12 ++++++++---- layouts/_partials/scripts.html | 2 +- tests/goldens/html/home-landing-zh.html | 2 +- tests/goldens/html/home-landing.html | 2 +- tests/goldens/rss/fixtures.xml | 8 ++++---- theme.toml | 2 +- 13 files changed, 69 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a362524..1461c28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ permissions: env: GO_VERSION: 1.27.0 - HUGO_VERSION: 0.166.0 + HUGO_VERSION: 0.168.0 GOTOOLCHAIN: local jobs: @@ -51,6 +51,17 @@ jobs: hugo-version: ${{ env.HUGO_VERSION }} extended: true + - name: Install Dart Sass 1.105.2 + shell: bash + run: | + sass_archive="$RUNNER_TEMP/dart-sass-1.105.2-linux-x64.tar.gz" + curl --fail --location --silent --show-error \ + --output "$sass_archive" \ + https://github.com/sass/dart-sass/releases/download/1.105.2/dart-sass-1.105.2-linux-x64.tar.gz + echo "a530ee08d5792c80bdb9d9614b45fb58219c890cb42a280a80775f0af9c29582 $sass_archive" | sha256sum --check + tar --extract --gzip --file "$sass_archive" --directory "$RUNNER_TEMP" + echo "$RUNNER_TEMP/dart-sass" >> "$GITHUB_PATH" + - name: Install PDF inspection tools run: | sudo apt-get update @@ -192,6 +203,17 @@ jobs: hugo-version: ${{ env.HUGO_VERSION }} extended: true + - name: Install Dart Sass 1.105.2 + shell: bash + run: | + sass_archive="$RUNNER_TEMP/dart-sass-1.105.2-linux-x64.tar.gz" + curl --fail --location --silent --show-error \ + --output "$sass_archive" \ + https://github.com/sass/dart-sass/releases/download/1.105.2/dart-sass-1.105.2-linux-x64.tar.gz + echo "a530ee08d5792c80bdb9d9614b45fb58219c890cb42a280a80775f0af9c29582 $sass_archive" | sha256sum --check + tar --extract --gzip --file "$sass_archive" --directory "$RUNNER_TEMP" + echo "$RUNNER_TEMP/dart-sass" >> "$GITHUB_PATH" + - name: Check translation key parity run: python3 bin/check-i18n.py diff --git a/AGENTS.md b/AGENTS.md index 8d428d8..565038c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -94,13 +94,15 @@ make -C ../oink.pgsty.com dev ## Theme contracts -- As of 2026-10-15, Hugo Extended 0.166.0 is the single development, CI, and +- As of 2026-10-17, Hugo Extended 0.168.0 is the single development, CI, and release-test baseline and the current minimum. Advance it with stable Hugo releases; update CI pins, module/theme requirements, checkers, and current bilingual documentation together. Do not run older-Hugo compatibility matrices, adapt code for old versions, or add compatibility shims unless the user explicitly requests it. Historical release/research records are not current support requirements. +- SCSS builds use standalone Dart Sass 1.105.2 on `PATH`; CI pins the same + distribution. Preserve the project-variable import hooks and strict warnings. - The module declares Go 1.27.0; CI pins Go 1.27.0. Record the actual local tool versions when reporting validation. - Run checkers with `--panicOnWarning` when they build Hugo output. Ordinary diff --git a/CHANGELOG.md b/CHANGELOG.md index ab35f6c..c53e3d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to OINK are documented here. The project follows ## [Unreleased] +### Changed + +- Advance the minimum and sole development, CI, and release-test baseline to + Hugo Extended 0.168.0. +- Refresh homepage and RSS output goldens for Hugo's generator-tag placement + and automatic summaries that extend to preserve balanced HTML containers. + +- Compile main and ReDoc styles with standalone Dart Sass 1.105.2, preserving + project-variable hooks and the separate Font Awesome stylesheet. Install + Dart Sass on `PATH` before building. The four known Bootstrap/import-scope + language deprecations are scoped out; other warnings remain publication-fatal. +- Load the dark Chroma palette with Sass modules and correct the adjacent + download-channel selector for Dart Sass. + ## [1.3.0] — 2026-10-15 Resource-backed Expo albums with shared navigation, an original-image viewer, diff --git a/README.md b/README.md index 237e0a2..241a420 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Website Release CI - Hugo Extended 0.166.0 or newer + Hugo Extended 0.168.0 or newer Apache 2.0

@@ -41,7 +41,7 @@ print and RSS for publication, semantic Markdown for reuse, and optional ## Why OINK - **One deterministic toolchain.** Resolve the Hugo Module once, then build and - preview with one `hugo` binary. + preview with Hugo and the standalone Dart Sass compiler. - **Complete publishing surfaces.** Docs, Blog, Book, Expo image albums, OpenAPI, releases, downloads, and data-driven landing pages share one navigation and visual system. @@ -63,8 +63,8 @@ print and RSS for publication, semantic Markdown for reuse, and optional machine-readable outputs are tested as separate contracts. OINK 1.3.0 adds the Expo image-album shell and improves navigation, Landing, -and aggregate Print output. It uses Hugo Extended 0.166.0 as its minimum and -single development, CI, and release-test baseline. See the +and aggregate Print output. Unreleased development now requires Hugo Extended +0.168.0 and Dart Sass 1.105.2; v1.3.0 retains its original toolchain. See the [changelog](CHANGELOG.md) for the complete release notes. ## Start a site @@ -82,7 +82,8 @@ repository's internal fixtures. · [Setup guide](https://oink.pgsty.com/docs/start/) For development and validation, use Git, Go 1.27 or newer, and Hugo Extended -0.166.0. The site does not require Node.js. To evaluate the neutral template locally: +0.168.0 with [Dart Sass 1.105.2](https://github.com/sass/dart-sass/releases/tag/1.105.2) +on `PATH` (the standalone distribution, not the npm package). The site does not require Node.js. To evaluate the neutral template locally: ```sh git clone https://github.com/pgsty/oink-starter.git @@ -211,7 +212,8 @@ assuming drop-in compatibility. | Dependency | Policy | | --- | --- | -| Hugo | **Extended 0.166.0 or newer**; development, CI, and release tests use 0.166.0 | +| Hugo | **Extended 0.168.0 or newer**; development, CI, and release tests use 0.168.0 | +| Dart Sass | Standalone 1.105.2 on `PATH`; no Node.js or npm dependency | | Go | 1.27 or newer for Hugo Module resolution; not needed when using an offline archive or submodule | | Node.js | Not required to build or run an OINK site | | Locales | Native OINK interface text for all 31 locale names shipped by Docsy, plus generic `zh`; every bundle owns the same 218-message schema, including CLDR plural forms, with no English placeholder blocks | diff --git a/assets/scss/td/_chroma.scss b/assets/scss/td/_chroma.scss index bde0a52..2d92b40 100644 --- a/assets/scss/td/_chroma.scss +++ b/assets/scss/td/_chroma.scss @@ -1,3 +1,5 @@ +@use "sass:meta"; + // Class-based syntax highlighting (Chroma). // // Requires `markup.highlight.noClasses: false`, which the theme sets in its @@ -144,7 +146,7 @@ @if $enable-dark-mode { [data-bs-theme='dark'] { - @import 'chroma/dark'; + @include meta.load-css('chroma/dark'); .chroma { --td-code-hl-bg: color-mix( diff --git a/assets/scss/td/_release.scss b/assets/scss/td/_release.scss index b37e11f..485e98b 100644 --- a/assets/scss/td/_release.scss +++ b/assets/scss/td/_release.scss @@ -230,7 +230,7 @@ padding-block: 0.5rem 2rem; scroll-margin-block-start: calc(var(--td-scroll-padding-top, 0px) + 1rem); - + & { + & + & { padding-block-start: 2rem; border-block-start: 1px solid var(--bs-border-color); } diff --git a/hugo.yaml b/hugo.yaml index be99ebc..4bc2822 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -299,7 +299,7 @@ mediaTypes: module: hugoVersion: extended: true - min: 0.166.0 + min: 0.168.0 mounts: - source: assets target: assets diff --git a/layouts/_partials/head-css.html b/layouts/_partials/head-css.html index e4bfa91..ec2f077 100644 --- a/layouts/_partials/head-css.html +++ b/layouts/_partials/head-css.html @@ -1,8 +1,12 @@ {{ $scssMain := "scss/main.scss" -}} -{{ $scss := resources.Get $scssMain - | toCSS (dict "enableSourceMap" (not hugo.IsProduction)) -}} -{{/* Font Awesome 7's source SCSS requires Dart Sass, while Hugo Extended - uses LibSass. Concatenate its official compiled CSS into one stable vendor +{{/* Bootstrap 5.3 and the public project-variable hooks still share Sass's + legacy import scope. Silence only their known language deprecations; + Sass @warn, invalid selectors, and Hugo diagnostics remain visible. */ -}} +{{ $scss := resources.Get $scssMain | toCSS (dict + "transpiler" "dartsass" + "silenceDeprecations" (slice "import" "global-builtin" "color-functions" "if-function") + "enableSourceMap" (not hugo.IsProduction)) -}} +{{/* Concatenate Font Awesome's official compiled CSS into one stable vendor resource. Keep it separate from consumer SCSS so an ordinary theme or site style edit does not invalidate the icon distribution. The target remains under scss/ so Font Awesome's ../webfonts URLs keep their public shape. */ -}} diff --git a/layouts/_partials/scripts.html b/layouts/_partials/scripts.html index 6d17e66..8058260 100644 --- a/layouts/_partials/scripts.html +++ b/layouts/_partials/scripts.html @@ -261,7 +261,7 @@ {{ end -}} {{ if and $interactiveOutput (.Page.Store.Get "hasRedoc") -}} - {{ $redocCSS := resources.Get "scss/redoc.scss" | toCSS (dict "enableSourceMap" (not hugo.IsProduction)) -}} + {{ $redocCSS := resources.Get "scss/redoc.scss" | toCSS (dict "transpiler" "dartsass" "enableSourceMap" (not hugo.IsProduction)) -}} {{ if hugo.IsProduction }}{{ $redocCSS = $redocCSS | minify | fingerprint }}{{ end -}} diff --git a/tests/goldens/html/home-landing-zh.html b/tests/goldens/html/home-landing-zh.html index 7c89054..74a103c 100644 --- a/tests/goldens/html/home-landing-zh.html +++ b/tests/goldens/html/home-landing-zh.html @@ -1,7 +1,6 @@ - @@ -75,6 +74,7 @@ } catch (_) {} })(); + 跳转到主要内容 diff --git a/tests/goldens/html/home-landing.html b/tests/goldens/html/home-landing.html index ea0dc67..f4912f8 100644 --- a/tests/goldens/html/home-landing.html +++ b/tests/goldens/html/home-landing.html @@ -1,7 +1,6 @@ - @@ -75,6 +74,7 @@ } catch (_) {} })(); + Skip to content diff --git a/tests/goldens/rss/fixtures.xml b/tests/goldens/rss/fixtures.xml index 534381c..9de206d 100644 --- a/tests/goldens/rss/fixtures.xml +++ b/tests/goldens/rss/fixtures.xml @@ -13,7 +13,7 @@ https://example.org/fixtures/callouts/ Mon, 01 Jan 0001 00:00:00 +0000 https://example.org/fixtures/callouts/ -<h2 id="canonical-types">Canonical types </h2> <div class="td-callout td-callout--note" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-circle-info" aria-hidden="true"></i><span class="td-callout__label">Note</span></div> <div class="td-callout__body"> <p>Five GitHub types render natively on GitHub. This one is a note.</p> </div> </div><div class="td-callout td-callout--tip" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-lightbulb" aria-hidden="true"></i><span class="td-callout__label">Tip with a title</span></div> <div class="td-callout__body"> <p>Titles are inline Markdown: <code>code</code>, <em>emphasis</em>, <a href="https://example.org/docs/">links</a>.</p> </div> </div><div class="td-callout td-callout--important" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-circle-exclamation" aria-hidden="true"></i><span class="td-callout__label">Important</span></div> <div class="td-callout__body"> <p>Important information.</p> </div> </div><div class="td-callout td-callout--warning" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-triangle-exclamation" aria-hidden="true"></i><span class="td-callout__label">Warning</span></div> <div class="td-callout__body"> <p>Warning: this operation is destructive.</p> </div> </div><div class="td-callout td-callout--caution" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-circle-xmark" aria-hidden="true"></i><span class="td-callout__label">Caution</span></div> <div class="td-callout__body"> <p>Caution: data loss possible.</p> </div> </div><div class="td-callout td-callout--success" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-circle-check" aria-hidden="true"></i><span class="td-callout__label">Success</span></div> <div class="td-callout__body"> <p>Everything worked.</p> +<h2 id="canonical-types">Canonical types </h2> <div class="td-callout td-callout--note" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-circle-info" aria-hidden="true"></i><span class="td-callout__label">Note</span></div> <div class="td-callout__body"> <p>Five GitHub types render natively on GitHub. This one is a note.</p> </div> </div><div class="td-callout td-callout--tip" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-lightbulb" aria-hidden="true"></i><span class="td-callout__label">Tip with a title</span></div> <div class="td-callout__body"> <p>Titles are inline Markdown: <code>code</code>, <em>emphasis</em>, <a href="https://example.org/docs/">links</a>.</p> </div> </div><div class="td-callout td-callout--important" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-circle-exclamation" aria-hidden="true"></i><span class="td-callout__label">Important</span></div> <div class="td-callout__body"> <p>Important information.</p> </div> </div><div class="td-callout td-callout--warning" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-triangle-exclamation" aria-hidden="true"></i><span class="td-callout__label">Warning</span></div> <div class="td-callout__body"> <p>Warning: this operation is destructive.</p> </div> </div><div class="td-callout td-callout--caution" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-circle-xmark" aria-hidden="true"></i><span class="td-callout__label">Caution</span></div> <div class="td-callout__body"> <p>Caution: data loss possible.</p> </div> </div><div class="td-callout td-callout--success" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-circle-check" aria-hidden="true"></i><span class="td-callout__label">Success</span></div> <div class="td-callout__body"> <p>Everything worked.</p> </div> </div> Typography fixture @@ -27,14 +27,14 @@ https://example.org/fixtures/code-blocks/ Mon, 01 Jan 0001 00:00:00 +0000 https://example.org/fixtures/code-blocks/ -<h2 id="titled-numbered-and-highlighted">Titled, numbered, and highlighted </h2> <div class="td-code td-code--titled" id="td-code-344b5b47-fence-0" data-td-code data-td-code-auto-id data-td-language="yaml" data-td-line-count="5" role="group" aria-labelledby="td-code-344b5b47-fence-0-title"> <div class="td-code__header"> <span class="td-code__filename" id="td-code-344b5b47-fence-0-title" title="config/very-long-example-name-for-responsive-layout.yml">config/very-long-example-name-for-responsive-layout.yml</span> </div> <div class="td-code__viewport" id="td-code-344b5b47-fence-0-viewport" data-td-code-viewport><div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="hl"><span class="lnt">6 </span></span><span class="lnt">7 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">params</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">offline_search</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">ui</span><span class="p">:</span><span class="w"> </span></span></span><span class="line hl"><span class="cl"><span class="w"> </span><span class="nt">sidebar_menu_foldable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">sidebar_menu_compact</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></td></tr></table> </div> </div></div> </div> <h2 id="escaped-generic-attributes">Escaped generic attributes </h2> <div class="td-code td-code--untitled code-fixture" id="generic-code" data-td-code data-td-language="text" data-td-line-count="1" data-note="a &#34;quoted&#34; &amp; value"> <div class="td-code__viewport" id="generic-code-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">safe attributes</span></span></code></pre></div></div> </div> <h2 id="wrapped-and-collapsed">Wrapped and collapsed </h2> <div class="td-code td-code--untitled td-code--wrap" id="wrapped-example" data-td-code data-td-language="text" data-td-line-count="7" role="group" aria-label="Wrapped configuration example"> <div class="td-code__viewport" id="wrapped-example-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">alpha = one </span></span><span class="line"><span class="cl">beta = two </span></span><span class="line"><span class="cl">gamma = this-is-a-deliberately-long-unbroken-value-that-tests-responsive-wrapping-without-changing-the-source </span></span><span class="line"><span class="cl">delta = four </span></span><span class="line"><span class="cl">epsilon = five </span></span><span class="line"><span class="cl">zeta = six </span></span><span class="line"><span class="cl">eta = seven</span></span></code></pre></div></div> </div> <h2 id="console-commands">Console commands </h2> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-3" data-td-code data-td-code-auto-id data-td-language="console" data-td-line-count="9"> <div class="td-code__viewport" id="td-code-344b5b47-fence-3-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">printf</span> <span class="s1">&#39;hello\n&#39;</span> </span></span><span class="line"><span class="cl"><span class="go">hello </span></span></span><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">printf</span> <span class="s1">&#39;world\n&#39;</span> </span></span><span class="line"><span class="cl"><span class="go">world </span></span></span><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">printf</span> <span class="s1">&#39;%s\n&#39;</span> <span class="se">\ </span></span></span><span class="line"><span class="cl"><span class="gp">&gt;</span> first <span class="se">\ </span></span></span><span class="line"><span class="cl"><span class="gp">&gt;</span> second </span></span><span class="line"><span class="cl"><span class="go">first </span></span></span><span class="line"><span class="cl"><span class="go">second </span></span></span></code></pre></div></div> </div> <h2 id="shell-language-label">Shell language label </h2> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-4" data-td-code data-td-code-auto-id data-td-language="sh" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-4-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;displayed as Bash&#34;</span></span></span></code></pre></div></div> </div> <h2 id="package-managers-adjacent-fences-synchronized-group">Package managers (adjacent fences, synchronized group) </h2> <div class="td-tab-block td-tab-block--code" data-td-tab="npm" data-td-tab-group="package-manager" data-td-tab-value="npm" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>npm</div> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-5" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-5-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install @example/client</span></span></code></pre></div></div> </div> </div> <div class="td-tab-block td-tab-block--code" data-td-tab="pnpm" data-td-tab-value="pnpm" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>pnpm</div> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-6" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-6-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pnpm add @example/client</span></span></code></pre></div></div> </div> </div> <div class="td-tab-block td-tab-block--code" data-td-tab="yarn" data-td-tab-value="yarn" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>yarn</div> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-7" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-7-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add @example/client</span></span></code></pre></div></div> </div> </div> <h2 id="synchronized-peer">Synchronized peer </h2> <div class="td-tab-block td-tab-block--code" data-td-tab="npm" data-td-tab-group="package-manager" data-td-tab-value="npm" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>npm</div> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-8" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-8-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install --global @example/tool</span></span></code></pre></div></div> </div> </div> <div class="td-tab-block td-tab-block--code" data-td-tab="pnpm" data-td-tab-value="pnpm" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>pnpm</div> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-9" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-9-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pnpm add --global @example/tool</span></span></code></pre></div></div> </div> </div> <h2 id="local-tabs-without-a-group-no-hash--persistence">Local tabs without a group (no hash / persistence) </h2> <div class="td-tab-block td-tab-block--code" data-td-tab="A **literal** [label]" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>A **literal** [label]</div> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-10" data-td-code data-td-code-auto-id data-td-language="text" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-10-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">punctuation stays literal</span></span></code></pre></div></div> </div> </div> <div class="td-tab-block td-tab-block--code" data-td-tab="YAML" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>YAML</div> <div class="td-code td-code--titled" id="td-code-344b5b47-fence-11" data-td-code data-td-code-auto-id data-td-language="yaml" data-td-line-count="1" role="group" aria-labelledby="td-code-344b5b47-fence-11-title"> <div class="td-code__header"> <span class="td-code__filename" id="td-code-344b5b47-fence-11-title" title="config.yaml">config.yaml</span> </div> <div class="td-code__viewport" id="td-code-344b5b47-fence-11-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">title and tab coexist</span></span></span></code></pre></div></div> </div> </div> <h2 id="full-form--tabs--with-markdown-bodies">Full form: <code>{{&lt; tabs &gt;}}</code> with Markdown bodies </h2> <div class="td-tabs td-tabs--shortcode" id="td-tabs-344b5b47-0"> <div class="td-tabs__panel" id="setting-env" data-td-tabs-value="env"> <div class="td-tabs__panel-title" aria-hidden="true">Environment Variable</div> <div class="td-tabs__panel-body"><h6 id="envvar-queue-dir"><code>MINIO_LOGGER_WEBHOOK_QUEUE_DIR</code> </h6> <p>Set the queue directory through the environment.</p> +<h2 id="titled-numbered-and-highlighted">Titled, numbered, and highlighted </h2> <div class="td-code td-code--titled" id="td-code-344b5b47-fence-0" data-td-code data-td-code-auto-id data-td-language="yaml" data-td-line-count="5" role="group" aria-labelledby="td-code-344b5b47-fence-0-title"> <div class="td-code__header"> <span class="td-code__filename" id="td-code-344b5b47-fence-0-title" title="config/very-long-example-name-for-responsive-layout.yml">config/very-long-example-name-for-responsive-layout.yml</span> </div> <div class="td-code__viewport" id="td-code-344b5b47-fence-0-viewport" data-td-code-viewport><div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">3 </span><span class="lnt">4 </span><span class="lnt">5 </span><span class="hl"><span class="lnt">6 </span></span><span class="lnt">7 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">params</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">offline_search</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">ui</span><span class="p">:</span><span class="w"> </span></span></span><span class="line hl"><span class="cl"><span class="w"> </span><span class="nt">sidebar_menu_foldable</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">sidebar_menu_compact</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></td></tr></table> </div> </div></div> </div> <h2 id="escaped-generic-attributes">Escaped generic attributes </h2> <div class="td-code td-code--untitled code-fixture" id="generic-code" data-td-code data-td-language="text" data-td-line-count="1" data-note="a &#34;quoted&#34; &amp; value"> <div class="td-code__viewport" id="generic-code-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">safe attributes</span></span></code></pre></div></div> </div> <h2 id="wrapped-and-collapsed">Wrapped and collapsed </h2> <div class="td-code td-code--untitled td-code--wrap" id="wrapped-example" data-td-code data-td-language="text" data-td-line-count="7" role="group" aria-label="Wrapped configuration example"> <div class="td-code__viewport" id="wrapped-example-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">alpha = one </span></span><span class="line"><span class="cl">beta = two </span></span><span class="line"><span class="cl">gamma = this-is-a-deliberately-long-unbroken-value-that-tests-responsive-wrapping-without-changing-the-source </span></span><span class="line"><span class="cl">delta = four </span></span><span class="line"><span class="cl">epsilon = five </span></span><span class="line"><span class="cl">zeta = six </span></span><span class="line"><span class="cl">eta = seven</span></span></code></pre></div></div> </div> <h2 id="console-commands">Console commands </h2> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-3" data-td-code data-td-code-auto-id data-td-language="console" data-td-line-count="9"> <div class="td-code__viewport" id="td-code-344b5b47-fence-3-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">printf</span> <span class="s1">&#39;hello\n&#39;</span> </span></span><span class="line"><span class="cl"><span class="go">hello </span></span></span><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">printf</span> <span class="s1">&#39;world\n&#39;</span> </span></span><span class="line"><span class="cl"><span class="go">world </span></span></span><span class="line"><span class="cl"><span class="gp">$</span> <span class="nb">printf</span> <span class="s1">&#39;%s\n&#39;</span> <span class="se">\ </span></span></span><span class="line"><span class="cl"><span class="gp">&gt;</span> first <span class="se">\ </span></span></span><span class="line"><span class="cl"><span class="gp">&gt;</span> second </span></span><span class="line"><span class="cl"><span class="go">first </span></span></span><span class="line"><span class="cl"><span class="go">second </span></span></span></code></pre></div></div> </div> <h2 id="shell-language-label">Shell language label </h2> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-4" data-td-code data-td-code-auto-id data-td-language="sh" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-4-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;displayed as Bash&#34;</span></span></span></code></pre></div></div> </div> <h2 id="package-managers-adjacent-fences-synchronized-group">Package managers (adjacent fences, synchronized group) </h2> <div class="td-tab-block td-tab-block--code" data-td-tab="npm" data-td-tab-group="package-manager" data-td-tab-value="npm" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>npm</div> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-5" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-5-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install @example/client</span></span></code></pre></div></div> </div> </div> <div class="td-tab-block td-tab-block--code" data-td-tab="pnpm" data-td-tab-value="pnpm" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>pnpm</div> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-6" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-6-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pnpm add @example/client</span></span></code></pre></div></div> </div> </div> <div class="td-tab-block td-tab-block--code" data-td-tab="yarn" data-td-tab-value="yarn" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>yarn</div> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-7" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-7-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add @example/client</span></span></code></pre></div></div> </div> </div> <h2 id="synchronized-peer">Synchronized peer </h2> <div class="td-tab-block td-tab-block--code" data-td-tab="npm" data-td-tab-group="package-manager" data-td-tab-value="npm" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>npm</div> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-8" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-8-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install --global @example/tool</span></span></code></pre></div></div> </div> </div> <div class="td-tab-block td-tab-block--code" data-td-tab="pnpm" data-td-tab-value="pnpm" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>pnpm</div> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-9" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-9-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pnpm add --global @example/tool</span></span></code></pre></div></div> </div> </div> <h2 id="local-tabs-without-a-group-no-hash--persistence">Local tabs without a group (no hash / persistence) </h2> <div class="td-tab-block td-tab-block--code" data-td-tab="A **literal** [label]" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>A **literal** [label]</div> <div class="td-code td-code--untitled" id="td-code-344b5b47-fence-10" data-td-code data-td-code-auto-id data-td-language="text" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-fence-10-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">punctuation stays literal</span></span></code></pre></div></div> </div> </div> <div class="td-tab-block td-tab-block--code" data-td-tab="YAML" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>YAML</div> <div class="td-code td-code--titled" id="td-code-344b5b47-fence-11" data-td-code data-td-code-auto-id data-td-language="yaml" data-td-line-count="1" role="group" aria-labelledby="td-code-344b5b47-fence-11-title"> <div class="td-code__header"> <span class="td-code__filename" id="td-code-344b5b47-fence-11-title" title="config.yaml">config.yaml</span> </div> <div class="td-code__viewport" id="td-code-344b5b47-fence-11-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">title and tab coexist</span></span></span></code></pre></div></div> </div> </div> <h2 id="full-form--tabs--with-markdown-bodies">Full form: <code>{{&lt; tabs &gt;}}</code> with Markdown bodies </h2> <div class="td-tabs td-tabs--shortcode" id="td-tabs-344b5b47-0"> <div class="td-tabs__panel" id="setting-env" data-td-tabs-value="env"> <div class="td-tabs__panel-title" aria-hidden="true">Environment Variable</div> <div class="td-tabs__panel-body"><h6 id="envvar-queue-dir"><code>MINIO_LOGGER_WEBHOOK_QUEUE_DIR</code> </h6> <p>Set the queue directory through the environment.</p> <div class="td-code td-code--untitled" id="td-code-344b5b47-tabs0-env-fence-0" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-344b5b47-tabs0-env-fence-0-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">MINIO_LOGGER_WEBHOOK_QUEUE_DIR</span><span class="o">=</span>/var/lib/minio/queue</span></span></code></pre></div></div> </div> </div> </div> <div class="td-tabs__panel" id="setting-conf" data-td-tabs-value="conf" data-td-tabs-active> <div class="td-tabs__panel-title" aria-hidden="true">Configuration Setting</div> <div class="td-tabs__panel-body"><h6 id="conf-queue-dir"><code>logger_webhook queue_dir</code> </h6> <p>Set it with <code>mc admin config set</code>.</p> <div class="td-callout td-callout--tip" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-lightbulb" aria-hidden="true"></i><span class="td-callout__label">Tip</span></div> <div class="td-callout__body"> <p>Callouts and other blocks work inside tabs.</p> </div> </div></div> </div> </div> Steps https://example.org/fixtures/steps/ Mon, 01 Jan 0001 00:00:00 +0000 https://example.org/fixtures/steps/ -<h2 id="native-form-ordered-list">Native form: ordered list </h2> <ol class="steps"> <li> <p>Install the dependencies</p> <p>A step can hold any block: paragraphs, fences, callouts, nested lists.</p> <div class="td-tab-block td-tab-block--code" data-td-tab="Homebrew" data-td-tab-group="install" data-td-tab-value="brew" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>Homebrew</div> <div class="td-code td-code--untitled" id="td-code-e534f852-fence-0" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-e534f852-fence-0-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brew install pigsty</span></span></code></pre></div></div> </div> </div> <div class="td-tab-block td-tab-block--code" data-td-tab="APT" data-td-tab-value="apt" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>APT</div> <div class="td-code td-code--untitled" id="td-code-e534f852-fence-1" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-e534f852-fence-1-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt install pigsty</span></span></code></pre></div></div> </div> </div> </li> <li> <h3 id="init-workspace">Initialise the workspace </h3> <p>Headings inside a step enter the table of contents.</p> +<h2 id="native-form-ordered-list">Native form: ordered list </h2> <ol class="steps"> <li> <p>Install the dependencies</p> <p>A step can hold any block: paragraphs, fences, callouts, nested lists.</p> <div class="td-tab-block td-tab-block--code" data-td-tab="Homebrew" data-td-tab-group="install" data-td-tab-value="brew" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>Homebrew</div> <div class="td-code td-code--untitled" id="td-code-e534f852-fence-0" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-e534f852-fence-0-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brew install pigsty</span></span></code></pre></div></div> </div> </div> <div class="td-tab-block td-tab-block--code" data-td-tab="APT" data-td-tab-value="apt" data-td-tab-kind="code"> <div class="td-tab-block__title" data-td-tab-title>APT</div> <div class="td-code td-code--untitled" id="td-code-e534f852-fence-1" data-td-code data-td-code-auto-id data-td-language="bash" data-td-line-count="1"> <div class="td-code__viewport" id="td-code-e534f852-fence-1-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt install pigsty</span></span></code></pre></div></div> </div> </div> </li> <li> <h3 id="init-workspace">Initialise the workspace </h3> <p>Headings inside a step enter the table of contents.</p> <div class="td-callout td-callout--tip" role="note"> <div class="td-callout__title"><i class="td-callout__icon fa-solid fa-lightbulb" aria-hidden="true"></i><span class="td-callout__label">Tip</span></div> <div class="td-callout__body"> <p>Callouts work inside steps.</p> </div> </div></li> <li> <p>Verify the installation</p> <div class="td-code td-code--untitled" id="td-code-e534f852-fence-2" data-td-code data-td-code-auto-id data-td-language="console" data-td-line-count="2"> <div class="td-code__viewport" id="td-code-e534f852-fence-2-viewport" data-td-code-viewport><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> pig --version </span></span><span class="line"><span class="cl"><span class="go">pig 1.7.0 </span></span></span></code></pre></div></div> </div> </li> </ol> Math passthrough @@ -48,7 +48,7 @@ https://example.org/fixtures/content-primitives/ Tue, 18 Aug 2026 00:00:00 +0000 https://example.org/fixtures/content-primitives/ -<p>Status <span class="td-badge td-badge--warning">Beta</span> remains inline with prose.</p> <p>Linked release <a class="td-badge td-badge--info" href="https://example.org/release/">v0.3</a> and a filled state <span class="td-badge td-badge--danger">Deprecated</span>.</p> <p>Escaped text <span class="td-badge td-badge--success">R&amp;D &lt;Preview&gt;</span> and long text <span class="td-badge td-badge--neutral">A-deliberately-long-unbroken-status-value-for-responsive-layout</span>.</p> <p>CJK <span class="td-badge td-badge--info">实验功能</span> and RTL text <span class="td-badge td-badge--warning">ميزة تجريبية</span>.</p> <p>Open search with <span class="td-kbd-sequence"><kbd>Ctrl</kbd><span class="td-kbd-sequence__separator" aria-hidden="true">+</span><span class="visually-hidden"> with </span><kbd>K</kbd></span> or the command palette with <span class="td-kbd-sequence"><kbd>⌘</kbd><span class="td-kbd-sequence__separator" aria-hidden="true">+</span><span class="visually-hidden"> with </span><kbd>Shift</kbd><span class="td-kbd-sequence__separator" aria-hidden="true">+</span><span class="visually-hidden"> with </span><kbd>P</kbd></span>.</p> <p>Escaped keys remain readable: <span class="td-kbd-sequence"><kbd>[</kbd><span class="td-kbd-sequence__separator" aria-hidden="true">+</span><span class="visually-hidden"> with </span><kbd>A&#43;B</kbd><span class="td-kbd-sequence__separator" aria-hidden="true">+</span><span class="visually-hidden"> with </span><kbd>&gt;</kbd></span>.</p> <h2 id="fields">Fields </h2> <div class="td-fields"> <p class="td-fields__label" id="td-fields-b3143e1ab8e100c7db85584f152eee8b-10-label">Configuration fields</p> <dl class="td-fields__list" aria-labelledby="td-fields-b3143e1ab8e100c7db85584f152eee8b-10-label"> <div class="td-field"> <dt class="td-field__term"><code class="td-field__name">offline_search</code> <span class="visually-hidden">, </span><span class="td-field__type"><span class="td-field__meta-value"><code>boolean</code></span></span> <span class="visually-hidden">, </span><span class="td-field__required">required</span> <span class="visually-hidden">, </span><span class="td-field__default"><span class="td-field__meta-label">default</span><span class="td-field__meta-value"><code>true</code></span></span> </dt> <dd class="td-field__description"><p>Enables the local search index and links to the <a href="https://example.org/docs/">documentation</a>.</p> +<p>Status <span class="td-badge td-badge--warning">Beta</span> remains inline with prose.</p> <p>Linked release <a class="td-badge td-badge--info" href="https://example.org/release/">v0.3</a> and a filled state <span class="td-badge td-badge--danger">Deprecated</span>.</p> <p>Escaped text <span class="td-badge td-badge--success">R&amp;D &lt;Preview&gt;</span> and long text <span class="td-badge td-badge--neutral">A-deliberately-long-unbroken-status-value-for-responsive-layout</span>.</p> <p>CJK <span class="td-badge td-badge--info">实验功能</span> and RTL text <span class="td-badge td-badge--warning">ميزة تجريبية</span>.</p> <p>Open search with <span class="td-kbd-sequence"><kbd>Ctrl</kbd><span class="td-kbd-sequence__separator" aria-hidden="true">+</span><span class="visually-hidden"> with </span><kbd>K</kbd></span> or the command palette with <span class="td-kbd-sequence"><kbd>⌘</kbd><span class="td-kbd-sequence__separator" aria-hidden="true">+</span><span class="visually-hidden"> with </span><kbd>Shift</kbd><span class="td-kbd-sequence__separator" aria-hidden="true">+</span><span class="visually-hidden"> with </span><kbd>P</kbd></span>.</p> <p>Escaped keys remain readable: <span class="td-kbd-sequence"><kbd>[</kbd><span class="td-kbd-sequence__separator" aria-hidden="true">+</span><span class="visually-hidden"> with </span><kbd>A&#43;B</kbd><span class="td-kbd-sequence__separator" aria-hidden="true">+</span><span class="visually-hidden"> with </span><kbd>&gt;</kbd></span>.</p> <h2 id="fields">Fields </h2> <div class="td-fields"> <p class="td-fields__label" id="td-fields-b3143e1ab8e100c7db85584f152eee8b-10-label">Configuration fields</p> <dl class="td-fields__list" aria-labelledby="td-fields-b3143e1ab8e100c7db85584f152eee8b-10-label"> <div class="td-field"> <dt class="td-field__term"><code class="td-field__name">offline_search</code> <span class="visually-hidden">, </span><span class="td-field__type"><span class="td-field__meta-value"><code>boolean</code></span></span> <span class="visually-hidden">, </span><span class="td-field__required">required</span> <span class="visually-hidden">, </span><span class="td-field__default"><span class="td-field__meta-label">default</span><span class="td-field__meta-value"><code>true</code></span></span> </dt> <dd class="td-field__description"><p>Enables the local search index and links to the <a href="https://example.org/docs/">documentation</a>.</p> </dd> </div> <div class="td-field"> <dt class="td-field__term"><code class="td-field__name">offline_search_max_results</code> <span class="visually-hidden">, </span><span class="td-field__type"><span class="td-field__meta-value"><code>integer</code></span></span> <span class="visually-hidden">, </span><span class="td-field__default"><span class="td-field__meta-label">default</span><span class="td-field__meta-value"><code>10</code></span></span> </dt> <dd class="td-field__description"><p>Maximum number of visible results.</p> <p>A second paragraph preserves multiline description Markdown and <code>inline code</code>.</p> </dd> </div> <div class="td-field"> <dt class="td-field__term"><code class="td-field__name">explicitFalse</code> <span class="visually-hidden">, </span><span class="td-field__type"><span class="td-field__meta-value"><code>boolean</code></span></span> <span class="visually-hidden">, </span><span class="td-field__default"><span class="td-field__meta-label">default</span><span class="td-field__meta-value"><code>false</code></span></span> </dt> <dd class="td-field__description"><p>Proves that an explicit false value is not treated as an absent default.</p> </dd> </div> <div class="td-field"> <dt class="td-field__term"><code class="td-field__name">zeroLimit</code> <span class="visually-hidden">, </span><span class="td-field__type"><span class="td-field__meta-value"><code>integer</code></span></span> <span class="visually-hidden">, </span><span class="td-field__default"><span class="td-field__meta-label">default</span><span class="td-field__meta-value"><code>0</code></span></span> </dt> <dd class="td-field__description"><p>Proves that an explicit zero value remains visible.</p> </dd> </div> <div class="td-field"> <dt class="td-field__term"><code class="td-field__name">emptyPrefix</code> <span class="visually-hidden">, </span><span class="td-field__type"><span class="td-field__meta-value"><code>string</code></span></span> <span class="visually-hidden">, </span><span class="td-field__default"><span class="td-field__meta-label">default</span><span class="td-field__meta-value"><code>&#34;&#34;</code></span></span> </dt> <dd class="td-field__description"><p>Proves that an explicit empty string remains distinct from no default.</p> </dd> </div> <div class="td-field"> <dt class="td-field__term"><code class="td-field__name">resolverOutput</code> <span class="visually-hidden">, </span><span class="td-field__type"><span class="td-field__meta-value"><code>Array&lt;string&gt; | map[string]any</code></span></span> </dt> <dd class="td-field__description"><p>Exercises angle brackets, brackets, and a union in a type value.</p> </dd> </div> <div class="td-field"> <dt class="td-field__term"><code class="td-field__name">aDeliberatelyLongUnbrokenConfigurationFieldNameForResponsiveLayout</code> <span class="visually-hidden">, </span><span class="td-field__type"><span class="td-field__meta-value"><code>namespace.ReallyLongGenericType&lt;FirstArgument,SecondArgument&gt;</code></span></span> </dt> <dd class="td-field__description"><p>A long field name and type must stay within the component at narrow widths.</p> </dd> </div> <div class="td-field"> <dt class="td-field__term"><code class="td-field__name">搜索模式</code> <span class="visually-hidden">, </span><span class="td-field__type"><span class="td-field__meta-value"><code>字符串</code></span></span> <span class="visually-hidden">, </span><span class="td-field__required">required</span> <span class="visually-hidden">, </span><span class="td-field__default"><span class="td-field__meta-label">default</span><span class="td-field__meta-value"><code>本地</code></span></span> </dt> <dd class="td-field__description"><p>控制搜索是在浏览器本地执行,还是交给远程服务。</p> </dd> </div> <div class="td-field"> <dt class="td-field__term"><code class="td-field__name">وضع_البحث</code> <span class="visually-hidden">, </span><span class="td-field__type"><span class="td-field__meta-value"><code>سلسلة</code></span></span> <span class="visually-hidden">, </span><span class="td-field__default"><span class="td-field__meta-label">default</span><span class="td-field__meta-value"><code>محلي</code></span></span> </dt> <dd class="td-field__description"><p>يختبر النص من اليمين إلى اليسار داخل تعريف الحقل.</p> </dd> </div> </dl> </div> Cards, FileTree, Gallery diff --git a/theme.toml b/theme.toml index 9dfb8cf..31fa6b3 100644 --- a/theme.toml +++ b/theme.toml @@ -7,7 +7,7 @@ homepage = "https://github.com/pgsty/oink" demosite = "https://oink.pgsty.com/" tags = ["documentation", "multilingual", "local-first", "responsive", "docs", "book", "blog", "expo", "gallery"] features = ["offline search", "command palette", "dark mode", "Book publishing", "Expo image albums", "diagrams", "API documentation", "agent-friendly outputs"] -min_version = "0.166.0" +min_version = "0.168.0" [author] name = "PGSTY contributors" From c42fe62bb600521352d50a39d82aae87832de806 Mon Sep 17 00:00:00 2001 From: Feng Ruohang Date: Sat, 17 Oct 2026 22:34:51 +0800 Subject: [PATCH 15/18] fix: refresh footer clearance after content reflow --- assets/js/docs-shell.js | 10 ++++++++-- tests/js/docs-shell-footer.test.js | 27 ++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/assets/js/docs-shell.js b/assets/js/docs-shell.js index e9740f3..1c3ecda 100644 --- a/assets/js/docs-shell.js +++ b/assets/js/docs-shell.js @@ -166,8 +166,14 @@ }); window.visualViewport.addEventListener('resize', schedule); } - if ('ResizeObserver' in window) - new ResizeObserver(schedule).observe(footer); + if ('ResizeObserver' in window) { + var observer = new ResizeObserver(schedule); + observer.observe(footer); + // Tabs, blog views and loaded media move the footer without resizing it. + // Observe normal-flow content; the fixed rails never size this element. + var main = document.getElementById('td-main-content'); + if (main) observer.observe(main); + } update(); } diff --git a/tests/js/docs-shell-footer.test.js b/tests/js/docs-shell-footer.test.js index 5a3c27b..95fb0c7 100644 --- a/tests/js/docs-shell-footer.test.js +++ b/tests/js/docs-shell-footer.test.js @@ -28,6 +28,8 @@ function build({ footer = true, sidebar = true, toc = true, visual = false, top return { top: measurements.top }; }, }; + const mainElement = {}; + const observed = new Set(); let resize; let frameID = 0; const html = { @@ -52,7 +54,7 @@ function build({ footer = true, sidebar = true, toc = true, visual = false, top return null; }, querySelectorAll() { return []; }, - getElementById() { return null; }, + getElementById(id) { return id === 'td-main-content' ? mainElement : null; }, addEventListener(name, callback) { documentListeners.set(name, callback); }, dispatchEvent(event) { const callback = documentListeners.get(event.type); @@ -82,7 +84,7 @@ function build({ footer = true, sidebar = true, toc = true, visual = false, top document, ResizeObserver: class { constructor(callback) { resize = callback; } - observe(target) { assert.equal(target, footerElement); } + observe(target) { observed.add(target); } }, }; vm.runInNewContext(SOURCE, context); @@ -96,7 +98,8 @@ function build({ footer = true, sidebar = true, toc = true, visual = false, top flush(); return { window, document, measurements, listeners, viewportListeners, flush, - resize() { resize(); }, + resize() { assert.ok(observed.has(footerElement)); resize(); }, + reflow() { assert.ok(observed.has(mainElement)); resize(); }, offset() { return properties.get('--td-shell-footer-offset'); }, }; } @@ -226,3 +229,21 @@ test('mixed observer, window and visual viewport events share one frame', () => assert.equal(env.measurements.reads, reads + 1); assert.equal(env.offset(), '301px'); }); + +// Content can move the footer even when its own dimensions remain unchanged. +test('main-content reflow clears and restores the offset without a scroll or tab event', () => { + const env = build({ sidebar: false, top: 670 }); + const reads = env.measurements.reads; + assert.equal(env.offset(), '331px'); + env.measurements.top = 3100; + env.reflow(); + env.reflow(); + assert.equal(env.measurements.reads, reads); + env.flush(); + assert.equal(env.measurements.reads, reads + 1); + assert.equal(env.offset(), '0px'); + env.measurements.top = 900; + env.reflow(); + env.flush(); + assert.equal(env.offset(), '101px'); +}); From 7f2c03beeca3e39170b62be670a39742cfefbd93 Mon Sep 17 00:00:00 2001 From: Feng Ruohang Date: Sat, 17 Oct 2026 22:36:46 +0800 Subject: [PATCH 16/18] fix: scope technical table layout to explicit opt-in --- .github/workflows/ci.yml | 1 - assets/scss/td/_content.scss | 13 +- bin/check-content-primitives.py | 12 ++ bin/check-table-layout-browser.py | 146 ----------------- tests/goldens/html/authors-index.html | 7 + .../html/fixtures-backlinks-alpha.html | 7 + tests/goldens/html/fixtures-callouts.html | 7 + tests/goldens/html/fixtures-cards.html | 7 + tests/goldens/html/fixtures-code-blocks.html | 7 + .../html/fixtures-content-primitives.html | 7 + tests/goldens/html/fixtures-data-fences.html | 7 + tests/goldens/html/fixtures-gallery.html | 7 + tests/goldens/html/fixtures-guides.html | 7 + tests/goldens/html/fixtures-lists.html | 7 + tests/goldens/html/fixtures-media-embeds.html | 7 + .../html/fixtures-media-primitives.html | 7 + tests/goldens/html/fixtures-share.html | 7 + tests/goldens/html/fixtures-steps.html | 7 + tests/goldens/html/fixtures-tables.html | 7 + tests/goldens/html/tags-index.html | 7 + tests/goldens/navjson/navigation.json | 7 + tests/goldens/print/fixtures-section.html | 150 +++++++++++++++++- tests/goldens/rss/fixtures.xml | 7 + tests/js/table-layout-source.test.js | 67 -------- 24 files changed, 290 insertions(+), 225 deletions(-) delete mode 100644 bin/check-table-layout-browser.py delete mode 100644 tests/js/table-layout-source.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73fed62..1461c28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,7 +122,6 @@ jobs: pdf="$RUNNER_TEMP/oink-book.pdf" hugo --source tests/site --themesDir "$GITHUB_WORKSPACE/.." \ --destination "$public" --minify --printPathWarnings --panicOnWarning - python3 bin/check-table-layout-browser.py --public "$public" --chrome "$OINK_CHROME" python3 bin/book-epub.py \ --manifest "$public/book/book.json" \ --public "$public" \ diff --git a/assets/scss/td/_content.scss b/assets/scss/td/_content.scss index fa69e18..994b0e5 100644 --- a/assets/scss/td/_content.scss +++ b/assets/scss/td/_content.scss @@ -196,17 +196,12 @@ @media screen { // Structural guards must not raise the cost of consumer overrides. - &:where(:not(.td-table-scroll--static)) > table { - // Ordinary prose should fit before scrolling. Tables whose technical - // columns need a wider reading measure opt in through a native class; - // do not guess that intent from a column's name, count, or language. - width: auto; + &:where(:not(.td-table-scroll--static)) > table:where(.td-table--wide) { + // Preserve ordinary tables. Authors opt technical identifiers into a + // wider measure; the wrapper still contains any horizontal overflow. + width: max-content; max-width: none; - &:where(.td-table--wide) { - width: max-content; - } - > :where(thead, tbody, tfoot) > :where(tr) > :is(th, td) { // Unlike anywhere, break-word keeps technical tokens in the // intrinsic minimum width, with an escape hatch for very long ones. diff --git a/bin/check-content-primitives.py b/bin/check-content-primitives.py index 5c14e93..48f6640 100755 --- a/bin/check-content-primitives.py +++ b/bin/check-content-primitives.py @@ -44,6 +44,18 @@ def check_outputs(public: Path) -> list[str]: require(all(item.path.is_file() for item in chunks), "lists runtime chunk is missing", errors) lists_bundle = combined_source(public, lists) + # The explicit width marker survives HTML rendering; Markdown preserves + # authored GFM and its attribute lines. Actual cell sizes and scroll behavior are tested in the site. + for prefix in ("", "zh/"): + technical = (public / f"{prefix}fixtures/table-layout-policy/index.html").read_text() + technical_md = (public / f"{prefix}fixtures/table-layout-policy/index.md").read_text() + require('id="wide-technical-table"' in technical and 'class="td-table--wide"' in technical, + f"{prefix} technical table lost its explicit width marker", errors) + require('class="matrix full-width td-table--wide td-table--matrix"' in technical, + f"{prefix} wide marker displaced existing table modifiers", errors) + require("HG_PD_GRPC_HOST" in technical_md and "{#wide-technical-table .td-table--wide}" in technical_md and "Beta', diff --git a/bin/check-table-layout-browser.py b/bin/check-table-layout-browser.py deleted file mode 100644 index b049c0a..0000000 --- a/bin/check-table-layout-browser.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python3 -"""Verify native table geometry in the built bilingual Hugo fixture (no JS deps).""" -import argparse -import json -from functools import partial -from html.parser import HTMLParser -from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer -from pathlib import Path -import subprocess -import tempfile -from threading import Thread - - -PROBE = r'''''' - - -class Results(HTMLParser): - def __init__(self): - super().__init__() - self.record = False - self.value = '' - - def handle_starttag(self, tag, attrs): - if tag == 'output' and ('id', 'oink-table-probe') in attrs: - self.record = True - - def handle_data(self, data): - if self.record: - self.value += data - - def handle_endtag(self, tag): - if tag == 'output': - self.record = False - - -class FixtureHandler(SimpleHTTPRequestHandler): - def do_GET(self): - if self.path.split('?')[0].rstrip('/') in ( - '/fixtures/table-layout-policy', '/zh/fixtures/table-layout-policy' - ): - page = Path(self.translate_path(self.path)) / 'index.html' - if not page.is_file(): - self.send_error(404) - return - html = page.read_text(encoding='utf-8') - if '' not in html: - self.send_error(500, 'missing closing body') - return - encoded = html.replace('', PROBE + '', 1).encode('utf-8') - self.send_response(200) - self.send_header('Content-Type', 'text/html; charset=utf-8') - self.send_header('Content-Length', str(len(encoded))) - self.end_headers() - self.wfile.write(encoded) - else: - super().do_GET() - - def log_message(self, *_): - pass - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--public', required=True, type=Path) - parser.add_argument('--chrome', required=True) - args = parser.parse_args() - with ThreadingHTTPServer(('127.0.0.1', 0), partial( - FixtureHandler, directory=str(args.public.resolve()))) as server: - thread = Thread(target=server.serve_forever, daemon=True) - thread.start() - try: - for language in ('', '/zh'): - for width in (1440, 390): - with tempfile.TemporaryDirectory(prefix='oink-table-chrome-') as profile: - url = (f'http://127.0.0.1:{server.server_port}{language}' - '/fixtures/table-layout-policy/') - cmd = [args.chrome, '--headless', '--dump-dom', - '--disable-background-networking', '--disable-extensions', - '--no-first-run', '--no-default-browser-check', - '--host-resolver-rules=MAP * ~NOTFOUND, EXCLUDE 127.0.0.1', - '--virtual-time-budget=3000', f'--user-data-dir={profile}', - f'--window-size={width},1000', url] - run = subprocess.run(cmd, capture_output=True, text=True, timeout=30) - result = Results() - result.feed(run.stdout) - if run.returncode != 0 or not result.value: - raise RuntimeError(f'Chrome did not evaluate {url} ({width}px): ' - f'{run.returncode}; {run.stderr[-400:]}') - report = json.loads(result.value) - if report['problems']: - raise AssertionError(f'{language or "en"}/{width}px: {report}') - print(f'{language or "en"}/{width}px: passed, ' - f'key {report["column"]:.1f}px, ' - f'description {report["description"]:.1f}px') - finally: - server.shutdown() - thread.join(timeout=5) - return 0 - - -if __name__ == '__main__': - raise SystemExit(main()) diff --git a/tests/goldens/html/authors-index.html b/tests/goldens/html/authors-index.html index 30ffb7f..5e281ae 100644 --- a/tests/goldens/html/authors-index.html +++ b/tests/goldens/html/authors-index.html @@ -1026,6 +1026,13 @@ id="m-fixturesrelease-assets-base">Release assets with an explicit base
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • -24: Unpublished download demo +24: Table layout policy +
  • +
  • +25: Unpublished download demo
  • @@ -1551,7 +1554,150 @@

    23 - Release assets with an explici

    -

    24 - Unpublished download demo

    +

    24 - Table layout policy

    +

    Ordinary prose +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    OwnerProjectStateWhenNotes
    Alice handles planning and coordinationTokyo releaseReady for reviewAfter final review and approvalA short explanation of the current delivery status.
    Bob handles design and deliveryOsaka patchWaiting on testsBefore the next release windowAnother short explanation of the expected next step.
    +
    +

    Explicit technical width +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    VariableRequiredDefaultConfiguration keyDescription
    HG_PD_GRPC_HOSTYesNonegrpc.hostAdvertised gRPC hostname/IP; use container hostnames within a container network.
    HG_PD_INITIAL_STORE_COUNTNo1pd.initial-store-countMinimum Store count needed for cluster availability.
    HG_PD_ACTUATOR_EXPOSURENohealth,metrics,prometheusmanagement.endpoints.web.exposure.includePublic Actuator endpoint allowlist; * is forbidden.
    +
    +

    Three-column configuration identifiers +

    +
    + + + + + + + + + + + + + + + + + + + + +
    config optiondefault valuedescription
    rocksdb.data_pathrocksdb-data/dataRocksDB data directory; must not be empty.
    rocksdb.data_disks[]Assign separate disks using STORE/TABLE: /path/disk. Supported keys include g/vertex, g/edge_out, g/edge_in, g/vertex_label_index, g/edge_label_index, g/range_int_index, g/range_float_index, g/range_long_index, g/range_double_index, g/secondary_index, g/search_index, g/shard_index, g/unique_index, and g/olap. Do not reuse rocksdb.data_path.
    +
    +

    Short ordinary table +

    +
    + + + + + + + + + + + + + + + + + + + + +
    CityPortPurpose
    Tokyo8080Graph API
    Osaka8686Metadata
    +
    +

    Explicit width with existing modifiers +

    +
    + + + + + + + + + + + + + + + + +
    Existing modifiers remain independent
    ComponentReadWrite
    A long component identifierYesNo
    +
    +
    +
    +

    25 - Unpublished download demo

    Pinned release facts remain visible without exposing download actions.
    diff --git a/tests/goldens/rss/fixtures.xml b/tests/goldens/rss/fixtures.xml index 9de206d..946d0f6 100644 --- a/tests/goldens/rss/fixtures.xml +++ b/tests/goldens/rss/fixtures.xml @@ -149,6 +149,13 @@ | File | Checksum | | --- | --- | | [oink\-linux\-amd64\.tar\.gz](https://downloads.example.org/releases/stable/oink-linux-amd64.tar.gz) | SHA-256 · `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa` | | [OINK manual \(final\)\.zip](https://downloads.example.org/releases/stable/OINK%20manual%20%28final%29.zip) | SHA-256 · `bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb` | +Table layout policy +https://example.org/fixtures/table-layout-policy/ +Mon, 01 Jan 0001 00:00:00 +0000 +https://example.org/fixtures/table-layout-policy/ +<h2 id="ordinary-prose">Ordinary prose </h2> <div class="td-table-scroll td-table-scroll--static"> <table id="ordinary-prose-table"> <thead> <tr> <th scope="col">Owner</th> <th scope="col">Project</th> <th scope="col">State</th> <th scope="col">When</th> <th scope="col">Notes</th> </tr> </thead> <tbody> <tr> <td>Alice handles planning and coordination</td> <td>Tokyo release</td> <td>Ready for review</td> <td>After final review and approval</td> <td>A short explanation of the current delivery status.</td> </tr> <tr> <td>Bob handles design and delivery</td> <td>Osaka patch</td> <td>Waiting on tests</td> <td>Before the next release window</td> <td>Another short explanation of the expected next step.</td> </tr> </tbody> </table> </div> <h2 id="explicit-technical-width">Explicit technical width </h2> <div class="td-table-scroll td-table-scroll--static"> <table id="wide-technical-table" class="td-table--wide"> <thead> <tr> <th scope="col">Variable</th> <th scope="col">Required</th> <th scope="col">Default</th> <th scope="col">Configuration key</th> <th scope="col">Description</th> </tr> </thead> <tbody> <tr> <td><code>HG_PD_GRPC_HOST</code></td> <td>Yes</td> <td>None</td> <td><code>grpc.host</code></td> <td>Advertised gRPC hostname/IP; use container hostnames within a container network.</td> </tr> <tr> <td><code>HG_PD_INITIAL_STORE_COUNT</code></td> <td>No</td> <td><code>1</code></td> <td><code>pd.initial-store-count</code></td> <td>Minimum Store count needed for cluster availability.</td> </tr> <tr> <td><code>HG_PD_ACTUATOR_EXPOSURE</code></td> <td>No</td> <td><code>health,metrics,prometheus</code></td> <td><code>management.endpoints.web.exposure.include</code></td> <td>Public Actuator endpoint allowlist; <code>*</code> is forbidden.</td> </tr> </tbody> </table> </div> <h2 id="three-column-configuration-identifiers">Three-column configuration identifiers </h2> <div class="td-table-scroll td-table-scroll--static"> <table id="configuration-keys-table" class="td-table--wide"> <thead> <tr> <th scope="col">config option</th> <th scope="col">default value</th> <th scope="col">description</th> </tr> </thead> <tbody> <tr> <td>rocksdb.data_path</td> <td><code>rocksdb-data/data</code></td> <td>RocksDB data directory; must not be empty.</td> </tr> <tr> <td>rocksdb.data_disks</td> <td><code>[]</code></td> <td>Assign separate disks using STORE/TABLE: /path/disk. Supported keys include g/vertex, g/edge_out, g/edge_in, g/vertex_label_index, g/edge_label_index, g/range_int_index, g/range_float_index, g/range_long_index, g/range_double_index, g/secondary_index, g/search_index, g/shard_index, g/unique_index, and g/olap. Do not reuse rocksdb.data_path.</td> </tr> </tbody> </table> </div> <h2 id="short-ordinary-table">Short ordinary table </h2> <div class="td-table-scroll td-table-scroll--static"> <table id="short-ordinary-table"> <thead> <tr> <th scope="col">City</th> <th scope="col">Port</th> <th scope="col">Purpose</th> </tr> </thead> <tbody> <tr> <td>Tokyo</td> <td>8080</td> <td>Graph API</td> </tr> <tr> <td>Osaka</td> <td>8686</td> <td>Metadata</td> </tr> </tbody> </table> </div> <h2 id="explicit-width-with-existing-modifiers">Explicit width with existing modifiers </h2> <div class="td-table-scroll td-table-scroll--full td-table-scroll--matrix td-table-scroll--static"> <table id="wide-matrix-table" class="matrix full-width td-table--wide td-table--matrix"> <caption class="td-table__caption">Existing modifiers remain independent</caption> <thead> <tr> <th scope="col">Component</th> <th scope="col">Read</th> <th scope="col">Write</th> </tr> </thead> <tbody> <tr> <th scope="row">A long component identifier</th> <td>Yes</td> <td>No</td> </tr> </tbody> </table> </div> + + Unpublished download demo https://example.org/fixtures/download-pending/ Mon, 01 Jan 0001 00:00:00 +0000 diff --git a/tests/js/table-layout-source.test.js b/tests/js/table-layout-source.test.js deleted file mode 100644 index b82bbc7..0000000 --- a/tests/js/table-layout-source.test.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict'; - -// Deterministic source guard; native browser geometry is checked by -// bin/check-table-layout-browser.py against the built fixtures. -const assert = require('node:assert/strict'); -const fs = require('node:fs'); -const path = require('node:path'); -const test = require('node:test'); - -const root = path.join(__dirname, '../..'); -const source = fs.readFileSync(path.join(root, 'assets/scss/td/_content.scss'), 'utf8') - .replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, ''); - -function blockAfter(text, marker) { - const start = text.indexOf(marker); - assert.notEqual(start, -1, 'missing owning selector: ' + marker); - const open = text.indexOf('{', start); - let depth = 1; - for (let end = open + 1; end < text.length; end++) { - if (text[end] === '{') depth++; - if (text[end] === '}' && --depth === 0) return text.slice(open + 1, end); - } - assert.fail('unclosed source block: ' + marker); -} - -const viewport = blockAfter(source, '.td-table-scroll {'); -const screen = blockAfter(viewport, '@media screen'); -const policy = blockAfter(screen, '&:where(:not(.td-table-scroll--static)) > table'); -const widthIs = value => new RegExp(`(?:^|[;{])\\s*width:\\s*${value};`, 'm'); - -test('ordinary screen tables prefer fitting; wide mode is explicit', () => { - const top = policy.slice(0, policy.indexOf('&:where(.td-table--wide)')); - assert.match(top, widthIs('auto')); - assert.match(top, /\bmax-width:\s*none;/); - assert.doesNotMatch(top, widthIs('max-content')); - assert.match(blockAfter(policy, '&:where(.td-table--wide)'), widthIs('max-content')); -}); - -test('width declarations cannot be spoofed by min-/max-width', () => { - const top = policy.slice(0, policy.indexOf('&:where(.td-table--wide)')); - const brokenTop = top.replace(/(^\s*)width:\s*auto;/m, '$1min-width: auto;'); - assert.notEqual(brokenTop, top); - assert.doesNotMatch(brokenTop, widthIs('auto')); - const wide = blockAfter(policy, '&:where(.td-table--wide)'); - const brokenWide = wide.replace(/(^\s*)width:\s*max-content;/m, '$1max-width: max-content;'); - assert.notEqual(brokenWide, wide); - assert.doesNotMatch(brokenWide, widthIs('max-content')); -}); - -test('both layout policies preserve wrapping and the scrolling viewport', () => { - assert.match(policy, /overflow-wrap:\s*break-word;/); - assert.match(policy, /word-break:\s*normal;/); - assert.match(policy, /max-width:\s*40ch;/); - assert.match(viewport, /overflow-x:\s*auto;/); - assert.match(blockAfter(viewport, '> table {'), /min-width:\s*100%;/); -}); - -test('wide mode is assigned only inside the non-static screen policy', () => { - assert.equal((source.match(/&:where\(\.td-table--wide\)\s*\{/g) || []).length, 1); - assert.equal((policy.match(/&:where\(\.td-table--wide\)\s*\{/g) || []).length, 1); -}); - -test('cell sizing does not leak into nested static tables', () => { - assert.ok(policy.includes('> :where(thead, tbody, tfoot) > :where(tr) > :is(th, td)')); - assert.ok(policy.includes('> :where(thead, tbody, tfoot) > :where(tr) > td')); - assert.match(blockAfter(viewport, '&--static'), /overflow-wrap:\s*anywhere;/); -}); From ea1d80a4854c47a790b026838f931d66564a38e6 Mon Sep 17 00:00:00 2001 From: Feng Ruohang Date: Sat, 17 Oct 2026 22:48:50 +0800 Subject: [PATCH 17/18] docs: record the completed readability repairs --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c53e3d7..e174e7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,16 @@ All notable changes to OINK are documented here. The project follows - Load the dark Chroma palette with Sass modules and correct the adjacent download-channel selector for Dart Sass. +### Fixed + +- Keep Markmap node text readable across light/dark modes and visual presets, + while retaining dark text for print. +- Keep the fixed right rail above the footer, including pages without a + sidebar and content reflows from tabs, blog view changes, and loaded media. +- Add `.td-table--wide` for technical tables that need intact identifiers and + bounded descriptions inside a horizontal scroll region. Ordinary tables, + field lists, and static outputs retain their existing layout. + ## [1.3.0] — 2026-10-15 Resource-backed Expo albums with shared navigation, an original-image viewer, From bf4596ec55a57ec58b0a0eea47590e8e4d1e348e Mon Sep 17 00:00:00 2001 From: Feng Ruohang Date: Sat, 17 Oct 2026 22:56:25 +0800 Subject: [PATCH 18/18] test: keep technical table fixture anchors unique --- tests/goldens/print/fixtures-section.html | 2 +- tests/goldens/rss/fixtures.xml | 2 +- tests/site/content/fixtures/table-layout-policy.md | 2 +- tests/site/content/fixtures/table-layout-policy.zh.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/goldens/print/fixtures-section.html b/tests/goldens/print/fixtures-section.html index 85531e9..c4cf4e2 100644 --- a/tests/goldens/print/fixtures-section.html +++ b/tests/goldens/print/fixtures-section.html @@ -1652,7 +1652,7 @@

    Short ordinary table

    - +
    diff --git a/tests/goldens/rss/fixtures.xml b/tests/goldens/rss/fixtures.xml index 946d0f6..344bf59 100644 --- a/tests/goldens/rss/fixtures.xml +++ b/tests/goldens/rss/fixtures.xml @@ -153,7 +153,7 @@ https://example.org/fixtures/table-layout-policy/Mon, 01 Jan 0001 00:00:00 +0000https://example.org/fixtures/table-layout-policy/ -<h2 id="ordinary-prose">Ordinary prose </h2> <div class="td-table-scroll td-table-scroll--static"> <table id="ordinary-prose-table"> <thead> <tr> <th scope="col">Owner</th> <th scope="col">Project</th> <th scope="col">State</th> <th scope="col">When</th> <th scope="col">Notes</th> </tr> </thead> <tbody> <tr> <td>Alice handles planning and coordination</td> <td>Tokyo release</td> <td>Ready for review</td> <td>After final review and approval</td> <td>A short explanation of the current delivery status.</td> </tr> <tr> <td>Bob handles design and delivery</td> <td>Osaka patch</td> <td>Waiting on tests</td> <td>Before the next release window</td> <td>Another short explanation of the expected next step.</td> </tr> </tbody> </table> </div> <h2 id="explicit-technical-width">Explicit technical width </h2> <div class="td-table-scroll td-table-scroll--static"> <table id="wide-technical-table" class="td-table--wide"> <thead> <tr> <th scope="col">Variable</th> <th scope="col">Required</th> <th scope="col">Default</th> <th scope="col">Configuration key</th> <th scope="col">Description</th> </tr> </thead> <tbody> <tr> <td><code>HG_PD_GRPC_HOST</code></td> <td>Yes</td> <td>None</td> <td><code>grpc.host</code></td> <td>Advertised gRPC hostname/IP; use container hostnames within a container network.</td> </tr> <tr> <td><code>HG_PD_INITIAL_STORE_COUNT</code></td> <td>No</td> <td><code>1</code></td> <td><code>pd.initial-store-count</code></td> <td>Minimum Store count needed for cluster availability.</td> </tr> <tr> <td><code>HG_PD_ACTUATOR_EXPOSURE</code></td> <td>No</td> <td><code>health,metrics,prometheus</code></td> <td><code>management.endpoints.web.exposure.include</code></td> <td>Public Actuator endpoint allowlist; <code>*</code> is forbidden.</td> </tr> </tbody> </table> </div> <h2 id="three-column-configuration-identifiers">Three-column configuration identifiers </h2> <div class="td-table-scroll td-table-scroll--static"> <table id="configuration-keys-table" class="td-table--wide"> <thead> <tr> <th scope="col">config option</th> <th scope="col">default value</th> <th scope="col">description</th> </tr> </thead> <tbody> <tr> <td>rocksdb.data_path</td> <td><code>rocksdb-data/data</code></td> <td>RocksDB data directory; must not be empty.</td> </tr> <tr> <td>rocksdb.data_disks</td> <td><code>[]</code></td> <td>Assign separate disks using STORE/TABLE: /path/disk. Supported keys include g/vertex, g/edge_out, g/edge_in, g/vertex_label_index, g/edge_label_index, g/range_int_index, g/range_float_index, g/range_long_index, g/range_double_index, g/secondary_index, g/search_index, g/shard_index, g/unique_index, and g/olap. Do not reuse rocksdb.data_path.</td> </tr> </tbody> </table> </div> <h2 id="short-ordinary-table">Short ordinary table </h2> <div class="td-table-scroll td-table-scroll--static"> <table id="short-ordinary-table"> <thead> <tr> <th scope="col">City</th> <th scope="col">Port</th> <th scope="col">Purpose</th> </tr> </thead> <tbody> <tr> <td>Tokyo</td> <td>8080</td> <td>Graph API</td> </tr> <tr> <td>Osaka</td> <td>8686</td> <td>Metadata</td> </tr> </tbody> </table> </div> <h2 id="explicit-width-with-existing-modifiers">Explicit width with existing modifiers </h2> <div class="td-table-scroll td-table-scroll--full td-table-scroll--matrix td-table-scroll--static"> <table id="wide-matrix-table" class="matrix full-width td-table--wide td-table--matrix"> <caption class="td-table__caption">Existing modifiers remain independent</caption> <thead> <tr> <th scope="col">Component</th> <th scope="col">Read</th> <th scope="col">Write</th> </tr> </thead> <tbody> <tr> <th scope="row">A long component identifier</th> <td>Yes</td> <td>No</td> </tr> </tbody> </table> </div> +<h2 id="ordinary-prose">Ordinary prose </h2> <div class="td-table-scroll td-table-scroll--static"> <table id="ordinary-prose-table"> <thead> <tr> <th scope="col">Owner</th> <th scope="col">Project</th> <th scope="col">State</th> <th scope="col">When</th> <th scope="col">Notes</th> </tr> </thead> <tbody> <tr> <td>Alice handles planning and coordination</td> <td>Tokyo release</td> <td>Ready for review</td> <td>After final review and approval</td> <td>A short explanation of the current delivery status.</td> </tr> <tr> <td>Bob handles design and delivery</td> <td>Osaka patch</td> <td>Waiting on tests</td> <td>Before the next release window</td> <td>Another short explanation of the expected next step.</td> </tr> </tbody> </table> </div> <h2 id="explicit-technical-width">Explicit technical width </h2> <div class="td-table-scroll td-table-scroll--static"> <table id="wide-technical-table" class="td-table--wide"> <thead> <tr> <th scope="col">Variable</th> <th scope="col">Required</th> <th scope="col">Default</th> <th scope="col">Configuration key</th> <th scope="col">Description</th> </tr> </thead> <tbody> <tr> <td><code>HG_PD_GRPC_HOST</code></td> <td>Yes</td> <td>None</td> <td><code>grpc.host</code></td> <td>Advertised gRPC hostname/IP; use container hostnames within a container network.</td> </tr> <tr> <td><code>HG_PD_INITIAL_STORE_COUNT</code></td> <td>No</td> <td><code>1</code></td> <td><code>pd.initial-store-count</code></td> <td>Minimum Store count needed for cluster availability.</td> </tr> <tr> <td><code>HG_PD_ACTUATOR_EXPOSURE</code></td> <td>No</td> <td><code>health,metrics,prometheus</code></td> <td><code>management.endpoints.web.exposure.include</code></td> <td>Public Actuator endpoint allowlist; <code>*</code> is forbidden.</td> </tr> </tbody> </table> </div> <h2 id="three-column-configuration-identifiers">Three-column configuration identifiers </h2> <div class="td-table-scroll td-table-scroll--static"> <table id="configuration-keys-table" class="td-table--wide"> <thead> <tr> <th scope="col">config option</th> <th scope="col">default value</th> <th scope="col">description</th> </tr> </thead> <tbody> <tr> <td>rocksdb.data_path</td> <td><code>rocksdb-data/data</code></td> <td>RocksDB data directory; must not be empty.</td> </tr> <tr> <td>rocksdb.data_disks</td> <td><code>[]</code></td> <td>Assign separate disks using STORE/TABLE: /path/disk. Supported keys include g/vertex, g/edge_out, g/edge_in, g/vertex_label_index, g/edge_label_index, g/range_int_index, g/range_float_index, g/range_long_index, g/range_double_index, g/secondary_index, g/search_index, g/shard_index, g/unique_index, and g/olap. Do not reuse rocksdb.data_path.</td> </tr> </tbody> </table> </div> <h2 id="short-ordinary-table">Short ordinary table </h2> <div class="td-table-scroll td-table-scroll--static"> <table id="short-prose-table"> <thead> <tr> <th scope="col">City</th> <th scope="col">Port</th> <th scope="col">Purpose</th> </tr> </thead> <tbody> <tr> <td>Tokyo</td> <td>8080</td> <td>Graph API</td> </tr> <tr> <td>Osaka</td> <td>8686</td> <td>Metadata</td> </tr> </tbody> </table> </div> <h2 id="explicit-width-with-existing-modifiers">Explicit width with existing modifiers </h2> <div class="td-table-scroll td-table-scroll--full td-table-scroll--matrix td-table-scroll--static"> <table id="wide-matrix-table" class="matrix full-width td-table--wide td-table--matrix"> <caption class="td-table__caption">Existing modifiers remain independent</caption> <thead> <tr> <th scope="col">Component</th> <th scope="col">Read</th> <th scope="col">Write</th> </tr> </thead> <tbody> <tr> <th scope="row">A long component identifier</th> <td>Yes</td> <td>No</td> </tr> </tbody> </table> </div> Unpublished download demo diff --git a/tests/site/content/fixtures/table-layout-policy.md b/tests/site/content/fixtures/table-layout-policy.md index c252124..81789e1 100644 --- a/tests/site/content/fixtures/table-layout-policy.md +++ b/tests/site/content/fixtures/table-layout-policy.md @@ -34,7 +34,7 @@ type: docs | --- | --- | --- | | Tokyo | 8080 | Graph API | | Osaka | 8686 | Metadata | -{#short-ordinary-table} +{#short-prose-table} ## Explicit width with existing modifiers diff --git a/tests/site/content/fixtures/table-layout-policy.zh.md b/tests/site/content/fixtures/table-layout-policy.zh.md index c49e9bb..e847e35 100644 --- a/tests/site/content/fixtures/table-layout-policy.zh.md +++ b/tests/site/content/fixtures/table-layout-policy.zh.md @@ -34,7 +34,7 @@ type: docs | --- | --- | --- | | 东京 | 8080 | 图接口 | | 大阪 | 8686 | 元数据 | -{#short-ordinary-table} +{#short-prose-table} ## 显式宽度与已有修饰符
    City