diff --git a/CHANGELOG.md b/CHANGELOG.md
index e174e7c..74fc14d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,13 +5,17 @@ All notable changes to OINK are documented here. The project follows
## [Unreleased]
+## [1.3.1] — 2026-10-17
+
+Readability repairs and an opt-in FileTree comment layout, with a refreshed
+Hugo and Sass toolchain.
+
### 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
@@ -29,6 +33,25 @@ All notable changes to OINK are documented here. The project follows
bounded descriptions inside a horizontal scroll region. Ordinary tables,
field lists, and static outputs retain their existing layout.
+- Add `wrap=true` to FileTree fences for complete long comments, including
+ unbroken identifiers. The default `wrap=false` preserves compact rows;
+ filenames, folding, divider controls, mobile stacking, and Print behavior
+ retain their existing contracts. Invalid values warn and fall back to false.
+
+### Contributors
+
+Thanks to [@imbajin](https://github.com/imbajin) for reporting and contributing
+these readability repairs, together with fixtures, tests, and bilingual docs:
+
+- [#48](https://github.com/pgsty/oink/pull/48): Markmap text colors across appearances.
+- [#50](https://github.com/pgsty/oink/pull/50): fixed TOC clearance above the footer.
+- [#52](https://github.com/pgsty/oink/pull/52): complete FileTree comments, integrated
+ behind the optional `wrap` parameter.
+- [#54](https://github.com/pgsty/oink/pull/54): readable technical table widths,
+ integrated through the explicit `.td-table--wide` modifier.
+- [Documentation #11](https://github.com/pgsty/oink.pgsty.com/pull/11): companion
+ design contracts and regression coverage.
+
## [1.3.0] — 2026-10-15
Resource-backed Expo albums with shared navigation, an original-image viewer,
@@ -2291,7 +2314,8 @@ fail the build with the new name rather than being silently ignored.
responsive shell rails, improved footer/hero/blog layouts, and accessibility
repairs.
-[Unreleased]: https://github.com/pgsty/oink/compare/v1.3.0...HEAD
+[Unreleased]: https://github.com/pgsty/oink/compare/v1.3.1...HEAD
+[1.3.1]: https://github.com/pgsty/oink/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/pgsty/oink/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/pgsty/oink/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/pgsty/oink/compare/v1.0.0...v1.1.0
diff --git a/README.md b/README.md
index 241a420..9291ff6 100644
--- a/README.md
+++ b/README.md
@@ -62,10 +62,10 @@ print and RSS for publication, semantic Markdown for reuse, and optional
JavaScript; interaction, accessibility, responsive behavior, print, and
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. 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.
+OINK 1.3.1 improves Markmap contrast, footer clearance, technical table widths,
+and optional FileTree comment wrapping. It requires Hugo Extended 0.168.0 and
+standalone Dart Sass 1.105.2. See the [changelog](CHANGELOG.md) for the fixes,
+upgrade requirements, and contributor credits.
## Start a site
diff --git a/assets/scss/td/_filetree.scss b/assets/scss/td/_filetree.scss
index 54364cd..23322db 100644
--- a/assets/scss/td/_filetree.scss
+++ b/assets/scss/td/_filetree.scss
@@ -295,6 +295,24 @@
text-overflow: ellipsis;
}
+// Authors can trade compact rows for complete comments; names keep ellipsis.
+.td-filetree--wrap {
+ .td-filetree__row {
+ block-size: auto;
+ min-block-size: var(--td-filetree-row);
+ }
+
+ .td-filetree__comment {
+ white-space: normal;
+ }
+
+ .td-filetree__comment-text {
+ overflow: visible;
+ text-overflow: clip;
+ overflow-wrap: anywhere;
+ }
+}
+
// Narrow viewports: the comment drops under the name instead of truncating.
@include media-breakpoint-down(sm) {
.td-filetree__row {
diff --git a/bin/check-content-primitives.py b/bin/check-content-primitives.py
index 48f6640..d8684d8 100755
--- a/bin/check-content-primitives.py
+++ b/bin/check-content-primitives.py
@@ -10,7 +10,7 @@
from __future__ import annotations
import argparse
-from html import unescape
+from html import escape, unescape
from pathlib import Path
import re
import tempfile
@@ -169,6 +169,34 @@ def check_outputs(public: Path) -> list[str]:
require(any(">Site tree
" in tree and "", errors)
+ # Keep full descriptions in the actual row text in every output, including
+ # CJK and unbroken tokens. Layout/resize checks live in the sibling site's
+ # FileTree browser suite; text presence alone cannot detect CSS clipping.
+ for language, suffix in (("", ""), ("zh/", ".zh")):
+ source = (FIXTURE / f"content/fixtures/filetree-comments{suffix}.md").read_text()
+ comments = re.findall(r" # (.+)$", source, re.MULTILINE)
+ comment_page = (public / language / "fixtures/filetree-comments/index.html").read_text()
+ comment_markdown = (public / language / "fixtures/filetree-comments/index.md").read_text()
+ comment_print = (public / language / "_print/fixtures/index.html").read_text()
+ rendered = re.findall(r'', comment_page)
+ require(len(comments) == 8 and rendered == [escape(text) for text in comments],
+ f"{language}FileTree long comments are missing from row text", errors)
+ require(comment_page.count('data-td-filetree-divider') == 4,
+ f"{language}FileTree long-comment fixtures lost their native dividers", errors)
+ require(comment_page.count('class="td-filetree td-filetree--wrap"') == 2,
+ f"{language}FileTree wrap must opt in only the two wrap=true trees", errors)
+ require(comment_page.count('class="td-filetree"') == 2,
+ f"{language}FileTree missing/false wrap must retain compact markup", errors)
+ require(comment_markdown.count('wrap=true}') == 2 and comment_markdown.count('wrap=false}') == 1,
+ f"{language}FileTree Markdown lost explicit wrap values", errors)
+ require("td-filetree--wrap" not in comment_print,
+ f"{language}FileTree screen wrapping must not change static Print markup", errors)
+ for text in comments:
+ require(text in comment_markdown,
+ f"{language}FileTree Markdown lost long comment {text}", errors)
+ require(f'' in comment_print,
+ f"{language}FileTree Print lost long comment {text}", errors)
+
# --- Native list forms: cards / gallery + filetree fences (lists fixture) --
for marker in (
'',
@@ -726,6 +754,8 @@ def check_table_family(hugo: str) -> list[str]:
("filetree-empty", "```filetree\n\n```\n", "requires tree entries"),
("filetree-unknown-attr", "```filetree {label=\"x\"}\n- a\n```\n", "unknown attribute"),
("filetree-empty-title", "```filetree {title=\"\"}\n- a\n```\n", "title must not be empty"),
+ ("filetree-wrap-bad", "```filetree {wrap=yes}\n- a # comment\n```\n", "wrap must be true or false"),
+ ("filetree-wrap-empty", "```filetree {wrap=\"\"}\n- a # comment\n```\n", "wrap must be true or false"),
("filetree-bad-dedent", "```filetree\n- a/\n - b\n - c\n```\n", "dedents to an indentation level that was never opened"),
("filetree-unknown-key", "```filetree\n- a {label=x}\n```\n", "unknown attribute \"label\""),
("filetree-malformed-attrs", "```filetree\n- a {icon}\n```\n", "malformed attributes"),
@@ -773,6 +803,10 @@ def check_invalid_cases(hugo: str) -> list[str]:
case_output = "\n".join(line for line in output.splitlines() if f"content/docs/{name}.md:" in line)
require(expected in case_output, f"invalid case {name} did not report {expected!r} at its position: {case_output or output.strip()}", errors)
require((temp_path / f"public/docs/{name}/index.html").is_file(), f"invalid case {name} lost its safe output", errors)
+ for name in ("filetree-wrap-bad", "filetree-wrap-empty"):
+ rendered = (temp_path / f"public/docs/{name}/index.html").read_text()
+ require("td-filetree--wrap" not in rendered,
+ f"{name} must fall back to compact comments", errors)
param_output = "\n".join(line for line in output.splitlines() if "content/docs/param-map.md:" in line)
require("only scalar values" in param_output, f"param map did not report the scalar rule at its position: {param_output or output.strip()}", errors)
param_page = temp_path / "public/docs/param-map/index.html"
diff --git a/layouts/_markup/render-codeblock-filetree.html b/layouts/_markup/render-codeblock-filetree.html
index 00722e6..ccbfade 100644
--- a/layouts/_markup/render-codeblock-filetree.html
+++ b/layouts/_markup/render-codeblock-filetree.html
@@ -1,11 +1,22 @@
{{- $page := .PageInner | default .Page -}}
{{- $format := lower ($page.Store.Get "tdOutputFormat" | default "html") -}}
{{- $policy := partial "content/attributes.html" (dict
- "attributes" .Attributes "allowed" (slice "title" "tab" "group" "value")
+ "attributes" .Attributes "allowed" (slice "title" "wrap" "tab" "group" "value")
"position" .Position "kind" "filetree"
) -}}
{{- $title := strings.TrimSpace (index $policy.consumed "title" | default "") -}}
{{- if and (isset $policy.consumed "title") (not $title) }}{{ warnf "filetree: title must not be empty at %s; dropping it" .Position }}{{ end -}}
+{{- $wrap := false -}}
+{{- $wrapSet := isset $policy.consumed "wrap" -}}
+{{- if $wrapSet -}}
+ {{- $wrapValue := index $policy.consumed "wrap" -}}
+ {{- if in (slice "true" "false") $wrapValue -}}
+ {{- $wrap = eq $wrapValue "true" -}}
+ {{- else -}}
+ {{- warnf "filetree: wrap must be true or false; got %q at %s; keeping compact comments" $wrapValue .Position -}}
+ {{- $wrapSet = false -}}
+ {{- end -}}
+{{- end -}}
{{- $tab := strings.TrimSpace (index $policy.consumed "tab" | default "") -}}
{{- if and (isset $policy.consumed "tab") (not $tab) }}{{ warnf "filetree: tab label must not be empty at %s; ignoring tab" .Position }}{{ end -}}
{{- $group := index $policy.consumed "group" | default "" -}}
@@ -20,8 +31,11 @@
{{- if eq $format "markdown" -}}
{{- /* re-emit the fence so the Markdown output stays valid source */ -}}
+ {{- $attrs := slice -}}
+ {{- with $title }}{{ $attrs = $attrs | append (printf `title=%q` .) }}{{ end -}}
+ {{- if $wrapSet }}{{ $attrs = $attrs | append (printf "wrap=%t" $wrap) }}{{ end -}}
{{- $attrText := "" -}}
- {{- with $title }}{{ $attrText = printf ` {title=%q}` . }}{{ end -}}
+ {{- with $attrs }}{{ $attrText = printf " {%s}" (delimit . " ") }}{{ end -}}
{{- $html = printf "```filetree%s\n%s```\n" $attrText (strings.TrimRight "\n" $inner | printf "%s\n") -}}
{{- else if eq $format "rss" -}}
{{- $html = printf `
%s
` (htmlEscape (strings.TrimRight "\n" $inner)) -}}
@@ -29,6 +43,7 @@
{{- $id := printf "td-filetree-%s-f%d" (md5 $page.RelPermalink) .Ordinal -}}
{{- $classes := slice "td-filetree" -}}
{{- if $static }}{{ $classes = $classes | append "td-filetree--static" }}{{ end -}}
+ {{- if and $wrap (not $static) }}{{ $classes = $classes | append "td-filetree--wrap" }}{{ end -}}
{{- if not $parsed.hasComments }}{{ $classes = $classes | append "td-filetree--plain" }}{{ end -}}
{{- with $policy.class }}{{ $classes = $classes | append . }}{{ end -}}
{{- $count := len $nodes -}}
diff --git a/tests/goldens/html/authors-index.html b/tests/goldens/html/authors-index.html
index 5e281ae..8db3624 100644
--- a/tests/goldens/html/authors-index.html
+++ b/tests/goldens/html/authors-index.html
@@ -1005,6 +1005,13 @@
id="m-fixturesdownload-demo">Download data demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -717,9 +724,9 @@
Gallery
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
21 - Gallery
+
21 - File tree comments
+
Storage directory comments
- wrapped/
- a-deliberately-long-runbook-filename-for-a-failover-drill.md
- graph/
+
+
+
22 - Gallery
Regression fixtures for a static-first responsive image Gallery.

Local page resource with intrinsic dimensions.

A deliberately long caption that must wrap inside its own card without widening the page or covering the neighboring image.

واجهة إعدادات عربية طويلة لاختبار الالتفاف والاتجاه التلقائي

远程图片保持静态 URL,构建过程不会下载它。
-
22 - Gallery with Zoom disabled
+
23 - Gallery with Zoom disabled
Gallery remains complete when page-level Image Zoom is disabled.
-
23 - Release assets with an explicit base
+
24 - Release assets with an explicit base
Asset tables can remain useful outside release pages without inventing version facts.
@@ -1554,7 +1561,7 @@
23 - Release assets with an explici
-
24 - Table layout policy
+
25 - Table layout policy
Ordinary prose