Skip to content

Example style guide, oxfmt, and a code font that fits 80 columns - #101

Draft
tony wants to merge 92 commits into
mainfrom
example-style
Draft

tony wants to merge 92 commits into
mainfrom
example-style

Conversation

@tony

@tony tony commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Brings the shared libtmux example rules to the docs repository and holds code in the site's Markdown pages to 80 columns. API-page examples, the staged F#, Julia, Kotlin, Scala, Ruby and Lua guides, and file= quotes render from port revisions pinned before this work. Most fit once each port merges and its pinned revision moves. Three kinds stay wide: the 82- and 86-column setup lines docs' generators write (142 lines in four models), the ports' allowed lines, and compared doctest output.

  • Biome lived only in the theme's format script with no CI caller; this swaps it for oxfmt 0.70.0, the version the ts port pins.
  • An 80-column check now reads every Markdown page. Against main at 98c2031 it reports 333 lines over 80 in 81 files, up to 114 columns wide; the commits fit the pinned programs and console recipes and re-run them natively. Two kinds stay allowed: the two quoted PEP 508 requirements, and five lines the native Julia pages copy from libtmux-julia at a sha256-pinned revision. Those five go when libtmux-julia#7 merges and the pin moves.
  • In Firefox and WebKit a code block on main holds 78 columns at 1024 px, 79.6 at 1280 and 99 at 1440 (CI's Chrome measured the same 78 at 1024), so an 80-column line scrolls at laptop widths. The code font is now sized so 80 columns fit from 1024 px up, and a browser test checks it.
  • A drift gate fails when a port's copy of the shared rules or checker differs from this repository's.

What changed

Policy

  • WRITING.md gains the shared example rules as subsections inside ## Documented examples that run, before ### Examples across ports (the existing anchor stays), and drops the old 100-column comment limit the shared section replaces. "Code blocks" becomes a heading so the shared section's link resolves. The "In this repository" block covers oxfmt, how each kind of fence runs and stays held to the width, and the pinned-program rule.
  • AGENTS.md routes code examples to it and links the heading the shared section sits under, which the checker requires.

Tooling

  • Replace Biome with oxfmt (pnpm catalog, .oxfmtrc.json, root format and format:check), reformat the source (345 files, +21713/-9065, mechanical), list that commit in .git-blame-ignore-revs, then run format:check in scripts/test-all.sh and the medium loop.
  • scripts/check_example_width.py and .github/example-width.toml read every tracked **/*.md file, including README.md, WRITING.md and AGENTS.md (CLAUDE.md links to AGENTS.md). One [[exclude]] entry skips notes/research/**, dated notes with pasted tool output. Eight [[allow]] entries, each with a reason: the quoted PEP 508 requirement that pins libtmux to a commit in the two Python recipes, the deliberately wide Bad line in WRITING.md's port block, and five lines the native Julia pages copy from libtmux-julia at 6f62bd7 (84 to 92 columns). The medium and outer loops and scripts/test-all.sh run the self-test and then the check.
  • The checker reads Markdown fences, indented and <pre> blocks, doc-comment code and reST blocks, and measures the whole source line. Each failure names the file to fix, and a clean run prints how many files it read.
  • ci(examples) Compare shared rules across ports adds scripts/check_shared_examples.py and .github/workflows/shared-examples.yml, which compare each port's checker and WRITING.md with this repository's, weekly and on pushes and pull requests that change the shared files; its commit message lists what fails and what passes with a notice. PREVIOUS accepts the checker and shared section libtmux-powershell#1 merged until 2026-11-10, while libtmux-powershell#14 brings both to the current ones.
  • fix(site) Fit 80 code columns at laptop widths makes each prose code block an inline-size container and sets --ec-codeFontSize to clamp(0.8125rem, calc((100cqi - 4.0625rem) / (80 * 0.6)), 0.875rem), the homepage's container-sized pattern: 14 px wherever 80 columns already fit, never under 13 px.
  • site/test/code-block-width.outer.test.ts opens /en/concepts/queries/ at 1024 px and at 1280 px, where the contents column appears, loads the code font itself, and asserts that every code block holds at least 80 characters of it after the sidebar, margins and the copy button's gutter. A control runs the same assertion at 390 px and expects it to fail; another blocks the webfont and expects the test to refuse to measure. CI's test job assembles the site and runs it; the test launches Chrome, while the rest of the audit keeps the pinned Chromium.

Examples

  • Most page commits fit one to three ports' code on that page (Fit the Go send call, Fit Kotlin lines in 80), so each port's maintainers can review their own language; the rest fit shared YAML, CI or shell samples.
  • Programs and recipes pinned by sha256 or shellRecipe in site/test/fixtures were fitted in 80 and re-pinned, each with a native re-run on tmux 3.2a and 3.7c. The commit messages record the command, tool versions and source revision.
  • fix(fixtures[capture]) gives the Rust main.rs entry the path src/main.rs. The page tells readers to save it there, and without the path the native runner wrote main.rs at the project root and cargo exited 101.
  • Split the JVM run command continues the attach guides' 88-column Gradle line with \, one flag per line, without reordering a word, so the shell runs the same command; shellRecipe in attach-examples.json matches.
  • Wrap the Gradle line puts the Java workspace dependency inside implementation(...) so the coordinate stays whole at 69 columns. The page runs from a fresh directory on tmux 3.2a and 3.7c.

Evidence

  • Biome would rewrite all 13 theme files, which it never formatted. oxfmt 0.70.0 (source, schema, release) matches the ts port's pin; the Biome-only setup is at this commit.
  • Rejected oxfmt over Markdown: it rewrote fenced programs that site/test/complete-examples.test.ts pins by sha256, failing 5 of its 170 cases. site/public/** is ignored because those scripts are injected verbatim.
  • Width test: before the font rule it failed in CI at 78.1 columns (run); locally in Firefox 155 and WebKit 26.6 the narrowest block measured 78.1 to 78.2 at 1024 px and 79.6 at 1280, and with the rule 81.7 to 81.8 at both. Local Chrome under WSL draws about 2.5% wider and also reaches 81.7 to 81.8 with the rule. Playwright's bundled Chromium draws the font at 7.84 px per character where Firefox and WebKit draw 8.23 to 8.24, so it holds 80.2 columns at 1024 px without the rule and would pass; the test measures in Chrome.
  • Drift gate: rejected a submodule or package for the shared files: either adds a dependency in twelve repositories for two files each port already runs offline. The self-test needs no network and plants a checker mismatch, a text mismatch, a missing file, a pending port with both files, an unlisted adopter, and clean, unmarked, listed and not-fetched controls.
  • Rejected an advisory width report: nothing fails, so the backlog regrows.

How to verify

$ pnpm run format:check
$ python3 scripts/check_example_width.py --self-test
$ python3 scripts/check_example_width.py
$ python3 scripts/check_shared_examples.py --self-test

The comparison needs the network.

$ python3 scripts/check_shared_examples.py

The width test needs an assembled site; CI's test job builds one and runs it.

$ pnpm test:publication

Merge order

Merge this PR before the ports. PENDING lists every port except libtmux-powershell, each with the reason and the date it was listed; an entry older than 30 days fails, so from 2026-11-04 the drift job fails for any port still pending. libtmux-powershell#1 is merged, so the drift job compares powershell's files from its first run, and PREVIOUS accepts the checker and shared section it merged until 2026-11-10. Once a port's example-style PR merges, the next run of the drift job fails with "both files present; remove it from PENDING" until a follow-up change here removes that port.

Not in this PR

  • scripts/check-example-prose.py does not cover product pages, so the Swift and TypeScript workspace and MCP pages were re-run by hand with the commands in their commit messages.

@tony
tony force-pushed the example-style branch 2 times, most recently from f76db42 to 61f707c Compare October 4, 2026 14:27
@tony
tony deployed to docs-preview October 4, 2026 15:37 — with GitHub Actions Active
@tony
tony force-pushed the example-style branch 5 times, most recently from 27250d4 to 1709964 Compare October 4, 2026 18:18
tony added a commit to libtmux/libtmux-cxx that referenced this pull request Oct 4, 2026
why: Examples render in fixed-width boxes on libtmux.org. Measured there
in Chrome, Firefox and WebKit, a code box holds 78 columns at a 1024 px
viewport and 99 at 1440 px, so a line near the repository's 88-column
limit scrolls sideways on a laptop-width window; libtmux/docs#101 sizes
the code font so 80 fit from 1024 px up.

The nested config relies on clang-format 18 (CI installs
clang-format-18 in the pinned toolchain step), which reads the nearest
.clang-format and inherits the parent through InheritParentConfig:
https://github.com/llvm/llvm-project/blob/llvmorg-18.1.3/clang/docs/ClangFormatStyleOptions.rst#L178
ColumnLimit is documented at:
https://github.com/llvm/llvm-project/blob/llvmorg-18.1.3/clang/docs/ClangFormatStyleOptions.rst#L2981
The root config sets ColumnLimit 88 and the other style keys:
https://github.com/libtmux/libtmux-cxx/blob/81506a5e145af4fae8956d2f07310648d75c554d/.clang-format

Rejected editing the root .clang-format: library, tests and apps keep
88, and only code a reader sees needs the narrower limit.
Rejected // clang-format off around long lines: it hides the lines
from the formatter instead of fixing them.
Rejected a per-file --style={ColumnLimit: 80} flag in CI: the editor
and a local clang-format would disagree with CI.

what:
- Add examples/.clang-format inheriting the parent config with
  ColumnLimit 80
- Reformat every example with
  clang-format-18 -i --style=file on each examples/**/*.cpp and *.hpp;
  CI's clang-format check reads the nested file
- Re-sync the README regions with
  python3 tools/docs/check_readme.py --fix
tony added a commit to libtmux/libtmux-dotnet that referenced this pull request Oct 4, 2026
why: Nothing stopped an example from growing past the width readers see.
Code blocks on libtmux.org scroll sideways (overflow-x: auto) at 14 px
IBM Plex Mono, and measured in Chrome, Firefox and WebKit they hold 99
columns at 1440 px and 78 at 1024 px; libtmux/docs#101 sizes the code
font so 80 fit from 1024 px up. Run against origin/master, this checker
and config report 691 lines over 80 in 67 files, across Markdown pages,
examples, project files and doc comments; the previous commits fix all
but 5 MSBuild property lines, which stay allowed because MSBuild keeps
whitespace inside property text.

The same checker is used by every libtmux port, so the rule reads the
same in each. It reads .github/example-width.toml, checks fenced code
in Markdown, whole example sources and code inside doc comments, and
skips output, hidden lines and lines that are only a URL. Untagged
Markdown fences count as code, so output fences are tagged `text`;
MyST `{code-block}` tags are read; East Asian wide characters count as
two columns. The config is validated: an unknown key, a glob that
matches no tracked file, an exclusion that matches no checked file,
or an `[[exclude]]` or `[[allow]]` entry without a reason fails, as
does an allow entry that no longer matches a line. allow_ceiling must
equal the number of allow entries, so the list grows only through a
reviewed change to that number. The config records the digests of the
checker and of the shared WRITING.md text, so a local edit to either
fails until every port changes together. `--self-test` plants a wide
line and a stale entry and requires the checker to report both;
failures are also emitted as GitHub `::error` annotations.

Three rules keep the gate from going stale. Every tracked file under a
directory named examples (any case) must be selected by `markdown`,
`sources` or `doc_comments`, or matched by an `[[exclude]]` with a
reason, so a new example cannot slip past unchecked. AGENTS.md must
link WRITING.md at the last level-1 or level-2 heading above the shared
markers. The failure summary ends by saying to fix a wide line by
changing the code, not the line breaks, with the WRITING.md anchor.

Alternatives considered:
- An advisory report: nothing fails, so the count drifts back up.
- A one-time cleanup with no gate: undone by the next example edit.
- A Markdown linter's line-length rule: cannot tell output and hidden
  lines from code, or carry an allowlist with reasons.

what:
- Add eng/docs/check_example_width.py
- Add .github/example-width.toml over every *.md file, every C# and F#
  source and every csproj and fsproj under examples/, examples/api/
  NuGet.config, and doc comments under src/
- Exclude with a reason each: decision records, recorded benchmark
  runs, the generated F# reference, the fsdocs template, NuGet lock
  files under examples/ and the JSON index examples/api/manifest.json
- Allow seven lines with allow_ceiling = 7: the Bad example in
  WRITING.md's port block, and six MSBuild property elements in
  project files, whose text keeps any whitespace added to wrap it
- Record shared_digest and checker_digest from `--digest`
- Run `--self-test` and the check in the build job after F# formatting
- Document both commands in CONTRIBUTING.md beside the other gates
tony added a commit to libtmux/libtmux-go that referenced this pull request Oct 4, 2026
why: gofumpt never wraps a line, so example programs ran to 150 columns
and scrolled sideways in README code blocks. In Chrome, Firefox and
WebKit, libtmux.org shows a code block 78 columns wide at a 1024 px
viewport and 99 at 1440 px, so a 150-column line needs a horizontal
scrollbar at every common width; libtmux/docs#101 sizes the code font so
80 fit from 1024 px up.

golangci-lint v2.13.1 already bundles golines v0.15.0:

https://github.com/golangci/golangci-lint/blob/v2.13.1/go.mod#L69

with a max-len setting (default 100, tab-len 4):

https://github.com/golangci/golangci-lint/blob/v2.13.1/.golangci.reference.yml#L4790-L4796

golines wraps at that width and treats a tab as tab-len columns:

https://github.com/golangci/golines/blob/v0.15.0/README.md#L57-L60

`golangci-lint run` enforces enabled formatters like gofumpt, so the
width gate needs no new pin or CI step.

Alternatives considered:
- Run golines as a separate tool: a second pin that can drift from
  the linter's bundled version.
- Enable the lll linter: it reports a long line but cannot wrap it.
- Keep shorten-comments on: it would split the `// docs:<name>`
  marker lines the README generator reads whole, so it stays off.

The root module keeps gofumpt alone. Formatter exclusions apply to
every formatter at once, so they cannot scope golines to the
example_test.go files there. Those files get their own check in a
later commit.

what:
- Enable golines at max-len 80, tab-len 4 in examples/.golangci.yml
- Reformat the examples module with
  `golangci-lint fmt ./...` (v2.13.1), run in examples/
- Regenerate the README regions quoted from those programs with
  `go generate ./...` in tmux/
- Hand-written rewrites of the breaks that read badly follow, one
  commit per example
tony added a commit to libtmux/libtmux-go that referenced this pull request Oct 4, 2026
why: golines wraps Go source in the examples module and nothing else.
Three surfaces a reader sees stayed unchecked. On the tree right after
golines ran, this checker and config report 255 lines over 80 columns:
247 on those surfaces, and 8 inside the examples module that golines
cannot break:

- The Example functions in the tmux, mcp, and tmuxq modules, which
  render on pkg.go.dev. The root module cannot scope golines to
  example_test.go files: a formatter exclusion applies to every
  formatter at once.
  https://github.com/golangci/golangci-lint/blob/v2.13.1/.golangci.reference.yml#L4806-L4825
- Fenced code in README files, which golines never reads.
- Code in doc comments, such as the ScriptedTmux snippet.

In Chrome, Firefox and WebKit, libtmux.org shows a code block 78 columns
wide at a 1024 px viewport and 99 at 1440 px; libtmux/docs#101 sizes the
code font so 80 fit from 1024 px up. golines wraps at max-len with tabs
counted as tab-len columns, which is the same measure the checker uses:

https://github.com/golangci/golines/blob/v0.15.0/README.md#L57-L60

The checker is the one script every libtmux port uses, so the rule
reads the same in each. It ships with `--self-test`, which plants a
wide line and fails unless the checker reports it, then fails if an
allowed line is reported or a stale allow entry is not, so CI runs it
first. It also fails on a config that cannot work: an unknown key, an
exclude glob that matches no checked file, an allow entry without a
reason, or an allow_ceiling that differs from the number of allow
entries.
It fails on a tracked file under a directory named `examples` that
no pattern selects and no exclude names, so a new example file cannot
go unread, and on an AGENTS.md that lacks a link to the WRITING.md
heading above the shared section. Its failure summary points to the
fix: change the code, not the line breaks.
It reads an untagged Markdown fence as code, so output fences are
tagged `text`, counts East Asian wide characters as two columns, and
exempts only a line that is just a URL after an optional comment,
list, or quote marker.

Alternatives considered:
- The lll linter: it reads neither Markdown fences nor doc comments,
  and counts a tab as one column unless tab-width is set.
  https://github.com/golangci/golangci-lint/blob/v2.13.1/.golangci.reference.yml#L2211-L2218
- Enable golines in the root module: it would also rewrap library code
  that is not an example.
- Rewrap by hand and trust review: nothing stops the next example
  from scrolling sideways again.

what:
- Add internal/tools/check_example_width.py, run with Python 3
  and its standard library only
- Add .github/example-width.toml: every Markdown page, the files the
  check reads (example programs, tests, and the examples module's
  go.mod and .golangci.yml, which a reader copies), one exclude for
  examples/LICENSE, which is license text, the checker and shared-text
  digests, and four allowed lines, each with a reason and written with
  its raw leading tab: two `// docs:<name>` marker lines the README
  generator reads whole, one `// Output:` line a test compares, and the
  wide Bad line in the WRITING.md port block
- Set allow_ceiling to the number of allow entries
- Run the self-test and then the check as a step of the `generated`
  job in tests.yml
- Document the command in CONTRIBUTING.md beside the other gates
tony added a commit to libtmux/libtmux-julia that referenced this pull request Oct 4, 2026
why: Example programs render in fixed-width code boxes on libtmux.org
that scroll sideways past a width. Measured there in Chrome, Firefox and
WebKit, a code block shows 78 columns at a 1024 px viewport and 99 at
1440 px; libtmux/docs#101 sizes the code font so 80 fit from 1024 px up.
The format phase of dev/check-quality.jl used margin 92 for every file,
so the examples passed while running wider than the box.

JuliaFormatter 2.14.0 format_text reads no .JuliaFormatter.toml: only
the file-walking format() entry point looks one up, and
dev/check-quality.jl calls format_text. A config file would therefore
not change the gate, and the margin has to come from the check itself.

Sources, pinned to the v2.14.0 tag:
- format_text and its keyword options:
  https://github.com/domluna/JuliaFormatter.jl/blob/v2.14.0/src/packagedef.jl#L120-L165
- format() is the only caller that merges .JuliaFormatter.toml:
  https://github.com/domluna/JuliaFormatter.jl/blob/v2.14.0/src/packagedef.jl#L236-L252
- margin option, default 92:
  https://github.com/domluna/JuliaFormatter.jl/blob/v2.14.0/docs/src/formatting_options.md#L562-L566
- config file lookup and precedence:
  https://github.com/domluna/JuliaFormatter.jl/blob/v2.14.0/docs/src/config.md

Alternatives considered:
- .JuliaFormatter.toml in examples/: ignored by format_text.
- Margin 80 for all Julia files: reflows src/ and test/ code that
  readers never see in a code box.
- Width check only, no formatter margin: the formatter would rejoin
  any hand-broken line that fits 92, so the check and formatter would
  disagree.

what:
- Add format_margin in dev/check-quality.jl: margin 80 for any path
  under an examples/ directory, 92 for all other Julia files.
- Pass that margin to format_text in the format phase.
- Run JuliaFormatter 2.14.0 with DefaultStyle, indent 4, margin 80,
  format_docstrings=false and whitespace_in_kwargs=false over the five
  example programs that the margin changes. The doc pages read these
  files at build time, so no copy needs a sync.
- Verified with dev/check-quality.jl format (132 files pass).
tony added a commit to libtmux/libtmux-julia that referenced this pull request Oct 4, 2026
why: Code a reader sees on libtmux.org scrolls sideways past 78 columns
at a 1024 px viewport and 99 at 1440 px, measured in Chrome, Firefox and
WebKit on the rendered site; libtmux/docs#101 sizes the code font so 80
fit from 1024 px up. JuliaFormatter bounds only Julia source: it does
not format Markdown, and format_docstrings is off in
dev/check-quality.jl, so fences in READMEs, docs pages and docstrings
can grow wide while the format phase stays green. Run against
origin/master, this checker reports every line over 80 in those
surfaces, and the commits before this one fix them all.

Sources, pinned to the JuliaFormatter v2.14.0 tag:
- format_docstrings defaults to false, and format_text is the only
  entry point the format phase calls:
  https://github.com/domluna/JuliaFormatter.jl/blob/v2.14.0/docs/src/formatting_options.md#L378-L382
  https://github.com/domluna/JuliaFormatter.jl/blob/v2.14.0/src/packagedef.jl#L120-L165

Alternatives considered:
- Formatter margin alone: cannot see Markdown fences or docstrings.
- A generic Markdown line-length linter: it checks prose and tables,
  and cannot skip program output, hidden setup lines or a one-word
  URL line, so it flags text no author can wrap.
- A Julia-only check: the shared checker serves every libtmux port and
  needs only Python 3.11 and no Julia toolchain.

what:
- Add dev/check_example_width.py, the checker shared by every libtmux
  port, with a self-test that plants a wide line, an allowed line and
  a stale allow entry. It also fails on unknown config keys, globs
  that match no tracked file and allow entries without a reason; it
  exempts only URL-only lines, checks untagged Markdown fences as
  code, reads MyST {code-block} tags, counts East Asian wide
  characters as two columns, and compares copied formatter configs
  with the root. It prints GitHub ::error annotations and a --digest
  of itself and the shared WRITING.md section.
- Add .github/example-width.toml: every tracked Markdown file via
  **/*.md, example programs and the workspace YAML under examples/
  directories, and docstring fences. Every tracked file under an
  examples directory must be selected or excluded, and AGENTS.md must
  link WRITING.md. The one [[exclude]] covers docs/generated/**,
  generator output a reader never copies from. The one [[allow]] is the
  Bad line in WRITING.md's port block, counted by allow_ceiling.
  checker_digest and shared_digest record the output of python3
  dev/check_example_width.py --digest; the second covers the shared
  WRITING.md section, so a local edit to it fails the check.
- Run the self-test and the check in the matrix job of the Julia
  workflow, and document both commands in CONTRIBUTING.md.
- Verified with the self-test and the check on this tree.
tony added a commit to libtmux/libtmux-rs that referenced this pull request Oct 4, 2026
why: Code blocks on libtmux.org scroll sideways past a fixed width:
measured in Chrome, Firefox and WebKit, a block shows 78 columns at a
1024 px viewport and 99 at 1440 px (a phone scrolls anyway). The
repository-wide max_width = 100 therefore hid a horizontal scrollbar in
every example a reader opens at laptop width. libtmux/docs#101 sizes the
code font so 80 fit from 1024 px up, and 80 is the width the other
libtmux ports hold their examples to.

rustfmt reads the nearest rustfmt.toml walking up from each file, so a
config in each examples directory narrows only the examples while
src/ keeps 100. cargo fmt --all and editors both honour it, with no
extra flag to forget:
- max_width: https://github.com/rust-lang/rust/blob/1.97.1/src/tools/rustfmt/Configurations.md#L1937
- config lookup: https://github.com/rust-lang/rust/blob/1.97.1/src/tools/rustfmt/Configurations.md#L3
Verified with rustfmt 1.9.0-stable (rust 1.97.1).

Alternatives considered:
- A root max_width = 80: reformats all of src/ for a rendering limit
  that only examples have.
- A --config max_width=80 flag in the justfile: editors and a bare
  cargo fmt would disagree with CI.
- #[rustfmt::skip] per item: hand-wrapped code drifts and is never
  checked.

what:
- Add a rustfmt.toml with max_width = 80 to each crates/*/examples
  directory, copying the root settings otherwise
- Reformat the examples with cargo fmt --all
- End the match-arm println! calls in recover.rs with a semicolon,
  which the reformat turned into block-less arms and clippy's
  semicolon_if_nothing_returned rejects
tony added a commit to libtmux/libtmux-ruby that referenced this pull request Oct 4, 2026
why: Examples render in fixed-width boxes that scroll sideways, and 80
columns fits a libtmux.org code block in a laptop-width window once
libtmux/docs#101 sizes the code font to fit (78 columns at 1024 px in
Chrome without it). syntax_tree holds program source under examples/ to
80, but README and docs code blocks, and comments that no formatter
wraps, were unchecked. Before the formatter and example commits, a scan
found 170 lines over 80.

One checker serves every libtmux port, so the rule behaves the same
everywhere:
- scripts/check_example_width.py is stdlib-only Python with doctests
  and a --self-test that plants a wide line, an allowed line and a
  stale allow entry, so the gate can be shown to fail.
- An allow entry names the exact line and a reason; an entry that no
  longer matches, or has no reason, fails, so the list only shrinks.
- The config is validated: an unknown key or a glob that matches no
  tracked file fails.
- Only a line that is a URL is exempt. An untagged Markdown fence counts
  as code (tag output `text`); rustdoc `# ` lines and MyST
  `{code-block}` tags are read; East Asian wide characters count as two
  columns.
- A `[[formatter_copies]]` entry requires a copied formatter config to
  equal its root except lines naming its `key`.
- Findings emit GitHub `::error` annotations; `--digest` prints the
  checker and shared-text hashes.

Rejected:
- Extending each port's existing checker: thirteen implementations
  with mismatched coverage.
- An advisory report: no enforcement, and the backlog regrows.
- Ruby-only RuboCop Layout/LineLength: splits heredoc calls and cannot
  read Markdown fences.

Source, pinned: tomllib is in the standard library from Python 3.11
(https://docs.python.org/3.11/library/tomllib.html); ubuntu-24.04
runners ship Python 3.12.

what:
- Add scripts/check_example_width.py
- Add .github/example-width.toml: every Markdown file (`**/*.md`), the
  example programs and the workspace file under examples/ (`*.rb`,
  `*.yaml`), one allow entry for the Bad example in WRITING.md, and
  the checker and shared-text digests from --digest
- Exclude examples/manifest.json: its output strings are compared byte
  for byte by the example tests, so its lines cannot be rewrapped
- Run the self-test and the check in the style CI job
- Fail a tracked file under any directory named examples that is
  neither checked nor excluded, so a new example cannot go unread
- Document both commands in CONTRIBUTING
tony added a commit to libtmux/libtmux-swift that referenced this pull request Oct 4, 2026
why: Examples render in fixed-width boxes that scroll sideways. In
Chrome, Firefox and WebKit a code block on libtmux.org fits 78 columns
at a 1024 px viewport and 99 at 1440 px, so a line past 80 scrolls on a
laptop-sized window; libtmux/docs#101 sizes the code font so 80 fit from
1024 px up. The repository .swift-format allows 100:

https://github.com/libtmux/libtmux-swift/blob/196ffbb47b3339f6a567389329e3af3d87259030/.swift-format#L5

which is also swift-format's default:

https://github.com/swiftlang/swift-format/blob/swift-6.2.4-RELEASE/Documentation/Configuration.md?plain=1#L20-L25

swift-format reads the .swift-format nearest each file, starting in the
file's own directory and walking up:

https://github.com/swiftlang/swift-format/blob/swift-6.2.4-RELEASE/README.md?plain=1#L202-L207

so a copy under Examples/ narrows the examples and leaves Sources/,
Tests/ and Benchmarks/ at 100. The CI lint step already passes
Examples as a path, so it picks the new file up unchanged. The
toolchain is the 6.2.4 pin in .mise.toml:

https://github.com/libtmux/libtmux-swift/blob/196ffbb47b3339f6a567389329e3af3d87259030/.mise.toml#L2

Alternatives considered:
- lineLength 80 in the root file: reflows the library, its tests and
  benchmarks for a limit only the examples need.
- --configuration on every invocation: the file system is no longer
  searched, so a bare `swift format` in an editor or by hand disagrees
  with CI.
- Hand-wrapping without a config: the 100-column formatter joins the
  lines again and `lint --strict` fails.

what:
- Add Examples/.swift-format, the root file with lineLength 80
- Run `swift format format --in-place --recursive Examples` with the
  pinned 6.2.4 formatter: 29 files changed, 149 insertions and 66
  deletions
- Re-copy the README and DocC snippets that the reformat drifted from
  Examples/ so Scripts/check_examples.py still passes
- State the 80-column limit under Examples/ in CONTRIBUTING.md
tony added 30 commits October 12, 2026 03:42
why: libtmux.org code blocks scroll sideways at laptop widths. The git
remote and fetch lines of the setup recipe ran to 90 and 94, and the
AggregateError guard in workspace.ts to 88.

what:
- Continue the remote URL and the 40-character revision on lines of
  their own after a backslash; neither token can break, and each fits
- Put the thrown AggregateError in a braced block in workspace.ts
- Update the workspace.ts sha256 and the shellRecipe string in
  product-examples.json

The runner in scripts/check-example-prose.py does not cover product
pages, and WRITING.md names no script for them. Ran the displayed
recipe by hand: a fresh directory, the page's package.json and
workspace.ts saved from the page, then each console command in order
with bun 1.4.2 and the tmux under test first on PATH.

- tmux 3.2a: tmux -V "tmux 3.2a", every command exit 0, prints
  "built: 2 windows" and "editor: 2 panes"
- tmux 3.7c: tmux -V "tmux 3.7c", every command exit 0, same output
- Source revision 3fe1ca654b81b8cbf4a13b777a001a3298c87a6f
- complete-examples.test.ts: 170 passed
why: libtmux.org code blocks scroll sideways at laptop widths, and the
deadline line in capture.cpp ran to 85.

what:
- Name the clock once with a using alias and build the deadline and the
  loop test from it
- Update the capture.cpp sha256 in capture-examples.json; the shell
  recipe is unchanged

Native run, from the repository root:

    python3 scripts/check-example-prose.py \
        --example capture \
        --port cxx \
        --output-dir <new directory>

- tmux 3.2a first on PATH: tmux -V "tmux 3.2a", exit 0, passed
- tmux 3.7c first on PATH: tmux -V "tmux 3.7c", exit 0, passed
- Both runs build with Clang 18.1.3 and CMake 3.28.3 and print
  "libtmux capture ready"
- Source revision 393d4b0ad666f18a6581f1eb281741a75a7503f0
- complete-examples.test.ts: 170 passed
why: libtmux.org code blocks scroll sideways at laptop widths, and the
socket guard in connect.cpp ran to 91.

what:
- Put the thrown runtime_error in a braced block under the guard
- Update the connect.cpp sha256 in attach-examples.json; the shell
  recipe is unchanged

Native run, from the repository root:

    python3 scripts/check-example-prose.py \
        --example attach \
        --port cxx \
        --output-dir <new directory>

- tmux 3.2a first on PATH: tmux -V "tmux 3.2a", exit 0, passed
- tmux 3.7c first on PATH: tmux -V "tmux 3.7c", exit 0, passed
- Both runs build with Clang 18.1.3 and CMake 3.28.3, print work and
  leave has-session passing
- Source revision 393d4b0ad666f18a6581f1eb281741a75a7503f0
- complete-examples.test.ts: 170 passed
why: libtmux.org code blocks scroll sideways at laptop widths, and three
lines in capture.lua ran to 91, 83 and 85.

what:
- Break the runtime:connect table one field per line
- Name the quoted tmux prefix once in a local and build the command
  from it
- Name the wait-for argument list before passing it to server:command
- Update the capture.lua sha256 in capture-examples.json; the shell
  recipe is unchanged

Native run, from the repository root with Lua 5.5.1 and LuaRocks 3.13.0
on PATH:

    python3 scripts/check-example-prose.py \
        --example capture \
        --port lua \
        --output-dir <new directory>

- tmux 3.2a first on PATH: tmux -V "tmux 3.2a", exit 0, passed
- tmux 3.7c first on PATH: tmux -V "tmux 3.7c", exit 0, passed
- Both runs install luv 1.52.1-0, build the library and print
  "libtmux capture ready"
- Source revision 5baa3f9b830ebdbc76fb50b5b3d7a5ad3f76d443
- complete-examples.test.ts: 170 passed
why: libtmux.org code blocks scroll sideways at laptop widths, and three
lines in connect.lua ran to 82, 86 and 91.

what:
- Put the two assert calls one argument per line
- Break the runtime:connect table one field per line
- Update the connect.lua sha256 in attach-examples.json; the shell
  recipe is unchanged

Native run, from the repository root with Lua 5.5.1 and LuaRocks 3.13.0
on PATH:

    python3 scripts/check-example-prose.py \
        --example attach \
        --port lua \
        --output-dir <new directory>

- tmux 3.2a first on PATH: tmux -V "tmux 3.2a", exit 0, passed
- tmux 3.7c first on PATH: tmux -V "tmux 3.7c", exit 0, passed
- Both runs install luv 1.52.1-0, build the library, print work and
  leave has-session passing
- Source revision 5baa3f9b830ebdbc76fb50b5b3d7a5ad3f76d443
- complete-examples.test.ts: 170 passed
why: libtmux.org code blocks scroll sideways at laptop widths, and the
session lookup in connect.rb ran to 82.

what:
- Name the snapshot's sessions before searching them for "work"
- Update the connect.rb sha256 in attach-examples.json; the shell
  recipe is unchanged

Native run, from the repository root with Ruby 4.0.7 on PATH:

    python3 scripts/check-example-prose.py \
        --example attach \
        --port ruby \
        --output-dir <new directory>

- tmux 3.2a first on PATH: tmux -V "tmux 3.2a", exit 0, passed
- tmux 3.7c first on PATH: tmux -V "tmux 3.7c", exit 0, passed
- Both runs bundle install the library, print work and leave
  has-session passing
- Source revision 2599d45369515aaf2fd5793bfe71cdc20de641b6
- complete-examples.test.ts: 170 passed
why: Five lines of Capture.swift were 82, 86, 82, 82 and 89 wide.

what:
- Name the printf command as a local instead of an inline argument
- Break the timeout error, and the cleanup error, across lines
- Bind FileHandle.standardError to a local named stderr
- Update the Capture.swift sha256 in capture-examples.json

Native run: python3 scripts/check-example-prose.py --example capture
--port swift --page examples/capture-pane-output, Swift 6.2.4, library
revision 254f8b2b. Passed with tmux 3.2a and with tmux 3.7c (tmux -V
reports each), printing "libtmux capture ready".
why: Two lines of Connect.swift were 92 and 86 wide.

what:
- Bind the process environment to a local before the guard
- Break the failure message onto its own line
- Update the Connect.swift sha256 in attach-examples.json

Native run: python3 scripts/check-example-prose.py --example attach
--port swift --page guides/attaching-to-tmux, Swift 6.2.4, library
revision 254f8b2b. Passed with tmux 3.2a and with tmux 3.7c (tmux -V
reports each), printing "work".
why: The git remote add and fetch lines of the setup recipe were 91 and
92 wide.

what:
- Continue each of the two commands on a second line with "\", putting
  the URL and the 40-hex revision on lines of their own
- Update the shellRecipe in product-examples.json to match
- The program is unchanged, so its sha256 is unchanged

Native run: the displayed setup and "swift run --jobs 2 MCPExample" in
a fresh directory, via a by-hand runner (no script covers product
pages), Swift 6.2.4, library revision 254f8b2b. Passed with tmux 3.2a
and with tmux 3.7c (tmux -V reports each), printing "tools:
list_sessions" and "sessions: mcp-example".
why: The git remote add and fetch lines of the build recipe were 91 and
92 wide.

what:
- Continue each of the two commands on a second line with "\", putting
  the URL and the 40-hex revision on lines of their own
- Update the shellRecipe in product-examples.json to match
- The launcher run-mcp.sh is unchanged, so its sha256 is unchanged

Native run: the displayed setup, the libtmux-mcp build and
"sh run-mcp.sh" (stdin closed, so the launcher starts its private
server, serves, and cleans up on EOF) in a fresh directory, via a
by-hand runner (no script covers product pages), Swift 6.2.4, library
revision 254f8b2b. Exit 0 with tmux 3.2a and with tmux 3.7c (tmux -V
reports each); the launcher logged "serving socket path ... through
tmux".
why: Two lines of the program were 86 and 81 wide and the fetch recipe
had two lines of 91 and 92.

what:
- Break the default-command option and the WorkspaceBuilder.build call
  at the outermost level
- Continue the remote add and fetch commands with "\", putting the URL
  and the 40-hex revision on lines of their own
- Update the WorkspaceExample.swift sha256 and the shellRecipe in
  product-examples.json

Native run: the displayed setup and "swift run --jobs 2
WorkspaceExample" in a fresh directory, via a by-hand runner (no script
covers product pages), Swift 6.2.4, library revision 254f8b2b. Passed
with tmux 3.2a and with tmux 3.7c (tmux -V reports each), printing
"built: 2 windows" and "editor: 2 panes".
why: The Rust capture page tells readers to save the program as
src/main.rs, but its fixture entry gave only the name, so
scripts/check-example-prose.py wrote main.rs at the project root and
cargo stopped with "no targets specified in the manifest" (exit 101).
The native runner could not verify this page at all.

what:
- Give the main.rs entry a path of src/main.rs, the field the Swift
  entries already use for Sources/

Native runs with check-example-prose.py --example capture --port rs:
tmux 3.2a and 3.7c pass (cargo +1.97.1 run, exit 0, no missing output).
The same command without the path exits 101: cargo finds no target in
the manifest before tmux runs.
why: The Java workspace build file declared its dependency on one
81-column line. The coordinate is a single string, but Kotlin's call
can break after the opening parenthesis, which leaves the string
whole at 69 columns.

what:
- Put the libtmux-workspace coordinate on its own line inside
  implementation(...)

Ran the page as displayed in a fresh directory (git fetch of
libtmux-java 84222831, then ./libtmux-source/gradlew --no-daemon
--max-workers=2 -p . run) with tmux 3.2a and 3.7c first on PATH:
both print "built: 2 windows" and "editor: 2 panes", exit 0.
why: Five lines of the inspect-sessions program ran 81 to 84 columns:
the platform guard, the DOTNET_ROOT copy, the capabilities cast, the
result check and the cleanup failure.

what:
- Break the platform-guard throw and the cleanup failure after the
  opening parenthesis
- Read DOTNET_ROOT into a local before copying it into the environment
- Break the capabilities cast after `=` and the result check before `||`
- Update the Program.cs sha256 in product-examples.json

Native run: the page's recipe in a fresh directory (LibTmux.Mcp
0.0.0-alpha.20 as a local tool, then `dotnet run`), .NET SDK 10.0.400.
Passed with tmux 3.2a and with tmux 3.7c, printing "Session: docs-demo
(1 window)" and "Owned server stopped."
why: Seven lines of the run-command program ran 81 to 84 columns: the
platform guard, the DOTNET_ROOT copy, three exception messages, the
cleanup failure and the result check.

what:
- Break each long throw and the cleanup failure after the opening
  parenthesis
- Read DOTNET_ROOT into a local before copying it into the environment
- Break the result check before `||`, as the page's other checks do
- Update the Program.cs sha256 in product-examples.json

Native run: the page's recipe in a fresh directory (LibTmux.Mcp
0.0.0-alpha.20 as a local tool, then `dotnet run`), .NET SDK 10.0.400.
Passed with tmux 3.2a and with tmux 3.7c, printing "Command exit
status: 0" and "Captured marker: MCP command ready".
why: Three lines of the inspect-sessions program ran 81 to 83 columns
(tabs counted as 4): the cleanup deadline, the close-error join and the
session check.

what:
- Name the background context before the cleanup deadline
- Name the close error before joining it
- Name the created session's ID before comparing the listing to it
- Update the main.go sha256 in product-examples.json

Native run: the page's recipe in a fresh directory (libtmux-mcp
v0.0.1-alpha.12 installed into .tools, `go mod tidy`, `go run .`),
Go 1.26.5. Passed with tmux 3.2a and with tmux 3.7c, printing "demo: 1
window(s), 0 attached client(s)".
why: Five lines of the run-command program ran 81 to 113 columns (tabs
counted as 4): the cleanup deadline, the close-error join, the
unconfirmed-completion error, the pane check and the incomplete-output
error.

what:
- Name the background context before the cleanup deadline, and the
  close error before joining it
- Name the pane ID and the resolved panes before checking them
- Break the unconfirmed-completion message at a word boundary; the
  joined text is unchanged
- Put the incomplete-output format and its arguments on lines of their
  own
- Update the main.go sha256 in product-examples.json

Native run: the page's recipe in a fresh directory (libtmux-mcp
v0.0.1-alpha.12 installed into .tools, `go mod tidy`, `go run .`),
Go 1.26.5. Passed with tmux 3.2a and with tmux 3.7c, printing "exit
status: 7" and "command finished", as the page shows.
why: The line that adds async cleanup errors to the failure report in
the page-session-metadata program ran 86 columns.

what:
- Name the async cleanup errors before mapping them into the report
- Update the snapshot-sessions.rb sha256 in product-examples.json

Native run: the page's recipe in a fresh directory (libtmux-ruby at
9b1545562a, `bundle install` against its frozen lockfile, then
`ruby -W:no-experimental snapshot-sessions.rb`), Ruby 4.0.7. Passed
with tmux 3.2a and with tmux 3.7c, printing the page's expected output:
"Endpoint: example", "build", "editor".
why: Fourteen lines of the inspect-sessions page ran 81 to 110 columns:
three dependency lines in Cargo.toml, the list_sessions request and its
error, and the startup, inspection, stop and cleanup lines of main.rs.

what:
- Give the two git dependencies and tokio their own dependency tables
  in Cargo.toml, so each key sits on its own line
- Name the list_sessions request, the serialized error body, the
  startup result, the inspection future, the stop wait and the kept
  directory before using them
- Shorten the two daemon cleanup messages to "daemon stop
  verification", which names the wait they report on
- Format main.rs with rustfmt at max_width 80
- Update the Cargo.toml and main.rs sha256 in product-examples.json

Native run: the page's recipe in a fresh directory
(`cargo +1.97.1 generate-lockfile`, then
`cargo +1.97.1 run --locked --quiet`), libtmux-rs at a6fc2a6567.
Passed with tmux 3.2a and with tmux 3.7c, printing the page's expected
output: "session: build", "session: editor".
why: Seven lines of the Rust queries page ran 84 to 98 columns: the
cardinality program's query import, and the timeout and
retained-directory lines that each of the three programs repeats.

what:
- Name the demonstration future before the timeout, and the kept
  directory's display before the failure message, in each program
- Format the programs with rustfmt at max_width 80, which moves the
  query import's names onto their own line inside the braces
- Update the three program sha256 values in concept-examples.json

Native run: the page's recipe (libtmux-rs cloned at e9be0b6f6d as
libtmux-source, then `cargo +1.97.1 run --quiet --bin` matching,
cardinality and refresh), Rust 1.97.1. Passed with tmux 3.2a and with
tmux 3.7c, printing "active panes: 2", "cardinality: none, one,
multiple" and "fresh listing: renamed".
why: The Rust transports page's program ran its timeout line to 92
columns and its retained-directory message to 84.

what:
- Name the demonstration future before the timeout, and the kept
  directory's display before the failure message
- Update the subprocess.rs sha256 in concept-examples.json

Native run: the page's recipe (libtmux-rs cloned at e9be0b6f6d as
libtmux-source, then `cargo +1.97.1 run --quiet --bin subprocess`),
Rust 1.97.1. Passed with tmux 3.2a and with tmux 3.7c, printing
"sessions: work" and "missing session: tmux refused the command".
why: Four lines of the Rust batching guide ran 84 to 99 columns: the
libtmux dependency line in Cargo.toml, the incomplete-plan error, the
timeout and the retained-directory message.

what:
- Give libtmux its own dependency table in Cargo.toml, so the path,
  default-features and features keys sit on their own lines
- Name the plan's operations before the incomplete-plan error, the
  demonstration future before the timeout, and the kept directory's
  display before the failure message
- Update the Cargo.toml and batching.rs sha256 in concept-examples.json

Native run: the page's recipe (libtmux-rs cloned at e9be0b6f6d as
libtmux-source, then `cargo +1.97.1 run --quiet --bin batching`),
Rust 1.97.1. Passed with tmux 3.2a and with tmux 3.7c, printing
"marked: 1 dispatches; one, two" and "refused plan: failed, skipped".
why: Three lines of the Rust control-mode guide ran 84 to 107 columns:
the libtmux dependency line in Cargo.toml, the timeout and the
retained-directory message.

what:
- Give libtmux its own dependency table in Cargo.toml, so the path,
  default-features and features keys sit on their own lines
- Name the demonstration future before the timeout, and the kept
  directory's display before the failure message
- Update the Cargo.toml and control.rs sha256 in concept-examples.json

Native run: the page's recipe (libtmux-rs cloned at e9be0b6f6d as
libtmux-source, then `cargo +1.97.1 run --quiet --bin control`),
Rust 1.97.1. Passed with tmux 3.2a and with tmux 3.7c, printing
"attached control clients: 1" and "connection closed; server still
running".
why: The Kotlin and Scala attach guides end their `&&` chain with an
88-column Gradle line that scrolls sideways in a laptop-width code
block.

what:
- Continue the Gradle line with `\`, one flag per line, in both guides
- Match the shellRecipe entries in attach-examples.json, which the
  site test compares with each page's commands

The words and their order are unchanged, so the shell runs the same
command. Native run, exact displayed setup and program through
scripts/check-example-prose.py --example attach, JDK 26.0.2 on Linux:
- Kotlin, tmux 3.2a and 3.7c: exit 0, "work" printed
- Scala, tmux 3.2a and 3.7c: exit 0, "work" printed
why: 80 columns should fit a libtmux.org code block in a laptop-width
window, but a docs-page block held fewer. On a full local assembly,
/en/concepts/queries/ leaves 644 px for a code line at a 1024 px
viewport and 656 px at 1280, after the line's 14.5 px start padding and
the copy button's 48 px gutter. Firefox and WebKit draw 14 px IBM Plex
Mono at 8.23 to 8.24 px per character. That holds 78.1 to 78.2 columns
at 1024 (CI's Chrome: 78.1) and 79.6 at 1280, so an 80-column line
scrolled sideways. The homepage already sizes its code font from its
container to fit a column count.

what:
- Make each prose code block an inline-size container and set
  --ec-codeFontSize to
  clamp(0.8125rem, calc((100cqi - 4.0625rem) / (80 * 0.6)), 0.875rem):
  the default 14 px wherever 80 columns already fit, smaller only below
  that, never under the homepage's 13 px floor

Measured by injecting the rule into a mirror of the deployed page: 81.7
to 81.8 columns at 1024 and 1280 px in Chrome, Firefox and WebKit; 90 at
1366 px and 99 at 1440 px in Firefox and WebKit, unchanged at 14 px.

Rejected: narrowing the copy button's gutter (changes every frame for
a few pixels), and promising 80 columns only from 1366 px (a 1280 px
laptop window would still scroll).
why: WRITING.md held example comments to 100 columns and set no limit
for code. Before `fix(site) Fit 80 code columns at laptop widths`, a
libtmux.org code block held 78 columns at 1024 px in Firefox and WebKit;
with its font rule they fit 81.7 to 81.8 from 1024 px, and 99 at 1440.
Each port's example-style PR adds the same section.

what:
- Paste the shared section under "Documented examples that run", before
  "Examples across ports"; the existing anchor stays
- Drop the 100-column comment limit that the shared section replaces
- Make "Code blocks" a heading so the shared section's link resolves
- Mark the shared text with shared:examples comments so a checker can
  digest it
- Add "In this repository" in the shared four-bullet shape: the
  formatter and the width check command, what no formatter touches, the
  markers that decide whether a fence runs, and how compared output and
  pinned programs change, followed by a Bad/Good pair from a line the C#
  transports page needed to fix
why: The routing line named only prose, comments and commits, so it
did not send an agent editing a code example on a page to the rules for
its width and layout.

what:
- Add code examples to the WRITING.md trigger list and link the
  "Documented examples that run" section
why: oxfmt skips Markdown, so nothing held hand-written fences in the
site's pages to a width. In Firefox and WebKit, libtmux.org code blocks
held 78 columns at 1024 px before `fix(site) Fit 80 code columns at
laptop widths` and fit 81.7 to 81.8 after it, and the long lines the
check first found ran to 114 columns.

The shared checker is the same file in each port that adopts it. Every
pinned program and console recipe this branch can change now fits in 80
and was re-run natively. The native Julia pages copy libtmux-julia's
programs at the revision site/test/fixtures/program-examples.json pins
and check each copy by sha256, so their five wide lines stay until
libtmux-julia#7 fits the sources and the pin moves.

what:
- Add scripts/check_example_width.py with its --self-test, which
  plants a failure for every rule, plus clean controls
- Add .github/example-width.toml: 80 columns over every tracked *.md
  file, including README.md, WRITING.md and AGENTS.md (CLAUDE.md links
  to AGENTS.md). Every tracked file under an examples directory is
  Markdown, so the markdown glob selects all of them and no example file
  needs an exclude. The one [[exclude]] is notes/research, dated
  research notes with pasted tool output that no reader copies code from
- Allow eight lines, each with its reason: the quoted PEP 508
  requirement that pins libtmux to a commit, once in each of the two
  Python recipes; the deliberately wide Bad line in WRITING.md's port
  block; and five lines of the pinned Julia programs, output_stream.jl
  (84 columns), owned_capture.jl (88) and shared_windows.jl (86, 90, 92)
- Record checker_digest and shared_digest, the --digest of this checker
  and of WRITING.md's shared section, so a local edit to either fails
  the check
- Run the self-test and then the check in scripts/test-all.sh and the
  medium and outer loops
- Document the command in CONTRIBUTING.md beside the other gates
why: WRITING.md's shared example section and check_example_width.py are
copied into each port that adopts them, and the rule says to change them
in all of them together. Nothing enforced it, so a port could drift
without any job failing.

Alternatives considered: a submodule or package holding the shared files
would add a dependency to twelve repositories for a checker and a shared
section that every port runs offline, and a bot that opens sync PRs
still lets a stale copy merge before the bot runs.

what:
- Add scripts/check_shared_examples.py: for each port, fetch the checker
  and WRITING.md from the default branch on raw.githubusercontent.com,
  digest them like check_example_width.py --digest, and compare with
  this repository's files
- Fail a port without a file unless the PENDING list names it with a
  reason and the date it was listed, which passes with a notice; fail a
  pending port that has both files until it leaves the list, and an
  entry older than 30 days, so the list cannot go stale
- Fail a file that differs unless PREVIOUS accepts it
- List every port but libtmux-powershell in PENDING; PREVIOUS accepts
  powershell's merged checker and shared section until 2026-11-10
- Add NOT_FETCHED, empty while every port is public, for a port the
  script cannot read, such as a private repository whose raw files
  return 404 without a token; it prints a notice naming where that port
  is compared instead
- Add --self-test with a planted checker mismatch, a planted text
  mismatch, a missing file, a pending port with both files, a pending
  entry past 30 days, an entry for a port not in PORTS, a previous
  version inside and past its window, and clean,
  unmarked and not-fetched controls; it needs no network
- Fail when a public libtmux repository outside PORTS carries the
  shared markers (listed through GET /orgs/libtmux/repos); --self-test
  plants one with markers and one without
- Add .github/workflows/shared-examples.yml: self-test, then the
  comparison, weekly and on pushes to main and pull requests that change
  the shared files or the gate, passing GITHUB_TOKEN from github.token;
  a port that drifts on its own shows in the weekly run, not on
  unrelated pull requests
- End a failed run with what to change: a file that differs means the
  shared rules change in every port together; an out-of-date PORTS,
  PENDING or PREVIOUS entry gets the edit its error line names
- Import the checker without writing a __pycache__ beside it
- Document the command in CONTRIBUTING.md, marked network-only so the
  local loops skip it

Checked live against the real API with a token: no unlisted adopter was
reported and the run exited 0.
why: The width check holds fence source to 80 columns, but the reader
sees the block inside a sidebar, margins and the block's own padding.
Nothing measured that, so a CSS change could leave a block narrower
than the line the check passed and scroll it sideways with no failure.

what:
- Add site/test/code-block-width.outer.test.ts: serve the assembled tree
  on a loopback port, open /en/concepts/queries/ at 1024 px and at 1280
  px, where the contents column appears, and assert that every code
  block's scrolling element, less the line's padding, holds at least 80
  characters of the code font, measured from an 80-character run
- Pair it with a control that runs the same assertion at 390 px and
  expects it to fail, so a measurement that cannot fail is caught
- Load the code font itself before measuring and refuse to measure
  without it; a second control blocks *.woff2 and expects that refusal
- Skip when no assembled tree is present, like the other output suites;
  CI's test job assembles the site, so it runs there
- Name the narrowest block in a failure: the heading above it, any tab
  panel, list, aside, details or table around it, and its content and
  character widths
- Measure in Chrome unless LIBTMUX_DOCS_BROWSER_CHANNEL names another
  browser; the rest of the publication audit keeps the pinned Chromium
- Mention the suite in CONTRIBUTING.md beside the browser notes

In CI's Chrome, without the font rule the narrowest block held 78.1
columns at 1024 px and the check failed (libtmux/docs Actions run
37223904428); with it, the test passes and both controls see the failure
they expect. Playwright's bundled Chromium draws the code font at 7.84
px per character where Firefox and WebKit draw 8.23 to 8.24 px, so it
holds 80.2 columns at 1024 px without the rule and would pass either
way, which is why the test measures in Chrome. fonts.ready can settle
before a lazily used face starts loading, which is why the test loads
the code font itself.

This branch was successfully deployed

1 active deployment
docs-preview — df0d2ff0 Deployed Oct 12, 2026 by tony via publish-preview / publish #710
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant