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..e174e7c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,30 @@ 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.
+
+### 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,
diff --git a/README.md b/README.md
index 237e0a2..241a420 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
-
+
@@ -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/js/docs-shell.js b/assets/js/docs-shell.js
index 9d3a8e5..1c3ecda 100644
--- a/assets/js/docs-shell.js
+++ b/assets/js/docs-shell.js
@@ -134,21 +134,21 @@
/* --------------------------------------------------------- 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;
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');
@@ -159,14 +159,21 @@
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,
});
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/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/_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/assets/scss/td/_content.scss b/assets/scss/td/_content.scss
index 8ecdd30..994b0e5 100644
--- a/assets/scss/td/_content.scss
+++ b/assets/scss/td/_content.scss
@@ -194,6 +194,36 @@
margin-bottom: 0;
}
+ @media screen {
+ // Structural guards must not raise the cost of consumer overrides.
+ &: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(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;
+ word-break: normal;
+
+ a,
+ code {
+ overflow-wrap: inherit;
+ word-break: inherit;
+ }
+ }
+
+ > :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.
+ max-width: 40ch;
+ }
+ }
+ }
+
&--full {
width: 100%;
max-width: none;
@@ -205,6 +235,8 @@
&--static:not([tabindex]) {
max-width: none;
overflow: visible;
+ // A nested static viewport must not inherit an outer screen cell's rule.
+ overflow-wrap: anywhere;
}
}
}
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/assets/scss/td/shell/_toc.scss b/assets/scss/td/shell/_toc.scss
index dd23085..2b7186d 100644
--- a/assets/scss/td/shell/_toc.scss
+++ b/assets/scss/td/shell/_toc.scss
@@ -29,7 +29,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: var(--td-shell-toc-w);
z-index: 20;
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-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(
diff --git a/bin/check-shell.py b/bin/check-shell.py
index 53261c0..03389f0 100755
--- a/bin/check-shell.py
+++ b/bin/check-shell.py
@@ -284,6 +284,12 @@ def check_sources() -> list[str]:
"collapsed rail controls no longer align with the article topline", errors)
require(not re.search(r"\b(?:width|flex-basis)\s+var\(--td-motion-", toc_styles),
"responsive TOC widths must not animate and make the article rebound during resizing", 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/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/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
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.
+
+
+
+
+
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.
+
+
+
+
+
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.
+
+
+
+
+
Short ordinary table
+
+
+
+
+
+
City
+
Port
+
Purpose
+
+
+
+
+
Tokyo
+
8080
+
Graph API
+
+
+
Osaka
+
8686
+
Metadata
+
+
+
+
+
Explicit width with existing modifiers
+
+
+
+
Existing modifiers remain independent
+
+
+
Component
+
Read
+
Write
+
+
+
+
+
A long component identifier
+
Yes
+
No
+
+
+
+
+
+
+
25 - Unpublished download demo
Pinned release facts remain visible without exposing download actions.