Conversation
Doxygen emits XML only; Breathe feeds it to Sphinx so the C++ reference renders through the same theme as the other libtmux ports and no Doxygen-generated HTML is published. The public headers wrap every declaration in an ABI-versioned inline namespace via LIBTMUX_NAMESPACE_BEGIN. Without expanding that macro through PREDEFINED, Doxygen sees an unbalanced brace and silently drops every declaration in the file, leaving an empty reference that still builds. docs/ holds internal engineering material, so conf.py excludes all of it except this reference.
Sphinx's own api/ subdirectory nested inside the deployed api/ prefix, giving /cxx/<version>/api/api/<header>/. Hoist the header pages so the toctree lives in index.rst directly.
Furo's own search page needs a JS index this build doesn't serve at this path; the shell already indexes every port at /search/. Overriding the search.html template needs an explicit templates_path, which this conf.py never set (unlike the Python port's gp_sphinx default).
Closes notes/status.md's "Python and C++ are unskinned islands" glitch: without this file Furo has no --lt-* mapping and paints its own stock blue. Imports https://libtmux.org/_shell/tokens.css and maps the shared ~25 semantic tokens onto Furo's --color-* contract. The html_static_path, html_css_files and html_js_files entries wiring it (plus shell.js) into conf.py landed in 30d0038 alongside an unrelated search-page fix from concurrent work in this worktree.
…hable A var() on an undefined custom property with no fallback resolves to the guaranteed-invalid value, so every remapped --color-* would compute to unset until libtmux.org resolves (notes/status.md: never deployed) -- worse than the stock-Furo glitch this adapter exists to close. Fall back to Furo's own stock color per property instead, so an unreachable tokens.css degrades to unmodified Furo rather than a broken page.
Pagefind logs a missing html[lang] warning when indexing the assembled site.
An absolute https://libtmux.org/_shell/ URL resolves nowhere until the site is deployed, so the injected chrome was invisible in every local preview and the design-token bridge could not be checked at all. A root-relative path resolves on both the deployed origin and a local preview server.
scripts/build-site.sh runs doxygen into ./xml/ on every assembly, so the tree is dirty after any local docs build. It is derived output — Breathe consumes it and nothing else reads it — and it is about 30 MB, which is a poor accident to commit.
Every briefdescription in the generated XML was empty — 995 of 995 — because the headers document themselves with plain `//`. Doxygen reads `///`, `//!`, `/**` and `/*!`, and treats everything else as an ordinary source comment. There were zero of the former in `include/`. The prose was already there. 1,105 comments across 31 headers, written deliberately, and the toolchain threw all of it away: anything consuming the XML — Breathe, Sphinx, or a custom generator — got structure with no descriptions, so the published C++ reference had no text against any symbol. 913 comment lines in 260 blocks are converted here, each one directly above the declaration it describes. Nothing else changes: the diff is comments only, and file-level blocks are left as `//` because they document a file rather than the next declaration. With `JAVADOC_AUTOBRIEF`, the XML now carries 261 brief descriptions where it carried none. Two comments needed escaping once Doxygen started parsing them: `<ostream>` read as an HTML tag, and `\t` and `\ooo` inside a sentence about escaping read as commands.
Without it a multi-line /// block becomes the detailed description and the brief stays empty, which is what a listing shows. The headers document themselves in prose paragraphs, so their first sentence is the summary a reader wants: 261 briefs reach the XML with this on and none without. Pairs with docs/doxygen-comment-style on the library side, which converts the // comments Doxygen was ignoring entirely.
why: The reference generator stripped a bare `//`, so every `///` block converted in bab15c1 rendered with a stray slash on each line and lost its paragraph breaks: `///` separators became `/` rather than blank. That is 239 of the page's blocks, and it is what fails the lint gate. what: - Strip `///` and `//!` alongside `//` through one `_uncomment` helper, used by both the declaration and the file-overview readers - Convert the parser fixture to `///` and add coverage for a bare `///` separator, a `//!` block, and a plain `//` block that still reads - Regenerate docs/api.md, whose entries render byte-identically again
why: Doxygen treats a `'` that is not between two word characters as a quote delimiter even inside a code span, so ``''`` swallowed text and desynchronised every backtick after it in the paragraph. Two lines below, `\t` and `\ooo` then sat outside any span and were read as commands. bab15c1 answered the warning by doubling the backslashes, which silenced it but printed `\\t` in both the XML and docs/api.md. Rejected escaping the backslashes: it treats the symptom, and the text a reader sees is wrong in both renderers. A double-backtick span is also the CommonMark-correct form for content beginning with a space, so docs/api.md renders exactly as before. what: - Write the `''` and ` #';${}` spans with double backticks - Restore single backslashes in the `vis` escape list - Regenerate docs/api.md; doxygen over include/ now warns 0 times about comments
why: 23 blocks across 7 headers still documented a declaration with a plain `//`, which Doxygen reads as an ordinary source comment. Their prose reached docs/api.md but not the XML, so the published reference had text against 261 of 284 documented symbols and silence against the rest — the same defect bab15c1 set out to close, on the remainder. what: - Write the 43 remaining declaration comment lines as `///` - Covers CommandRuntime's destructor, every Capability enumerator, Chain::send_text and Chain::command, four FailureKind values, Connection::set_pane_output, TargetError::separator_in_name, and the two testing skip macros - Comments only: docs/api.md regenerates byte-identically, and the XML carries 284 briefs where it carried 261
why: The `''` span this branch fixed had been warning for as long as the Doxyfile existed, on a build that exited zero, so the reference shipped with the text after it swallowed and nobody saw. A warning here always means prose did not survive the parse; there is no benign case. Rejected setting WARN_AS_ERROR only in CI: the site build and a local run would then disagree with the gate, and the first anyone heard of a warning would be a red lane. One Doxyfile, one behaviour everywhere. what: - Set WARN_AS_ERROR so `doxygen Doxyfile` fails on a warning - Set BUILTIN_STL_SUPPORT, without which entities.hpp's four std::hash specialisations warn and lose their comments - Install doxygen in the lint job and run it there, printing the version so a drift in the runner's package shows in the log - Update the brief count in the JAVADOC_AUTOBRIEF note to 285
why: `--check` reported the fixture's golden file as out of date and named no command, because none existed — the only way to rewrite it was to import the module and call the private renderer by hand. That is the first thing anyone touching the parser hits. what: - Add --write-fixture, sharing one _render_fixture with the check so the two cannot render differently - Name that command in the error message - Make --include and --output optional, and reject them by hand when a page is what was asked for - Document it, and `doxygen Doxyfile`, in CONTRIBUTING
why: The policy still told a contributor to write plain `//` above a declaration, on the reasoning that api_index.py harvests it. Doxygen harvests it too now, and drops a `//` block entirely — so following the rule as written puts the prose in docs/api.md and nowhere on libtmux.org. The whole branch contradicts it. what: - Ask for `///` on a declaration, and say why a file-level block is still `//` - Keep the no-tags rule, whose reason is unchanged: both generators render the block as prose - Name the two Markdown traps the gate now catches, so a contributor meets them in the policy rather than in a red lane - Write both examples the way the rule asks
why: d7056e3 added uv.lock as a byproduct of running uv while wiring up Doxygen and Sphinx. It locks only what pyproject.toml already declared — jsonschema, referencing, tomlkit — and nothing from the docs toolchain, so it is unrelated to the change this branch makes. Nothing reads it: CI runs `uv run --no-project`, and mcp_swap.py is a PEP 723 script whose dependencies travel in its own header. what: - Remove uv.lock, restoring the tree master already has
why: The JAVADOC_AUTOBRIEF note carried the number of briefs the setting produces, which moved from 261 to 285 the moment a header gained a comment. WRITING.md's upkeep gate names exactly this shape: a comment hand-syncing a value the code owns is false the first time it moves. what: - State that the briefs are empty without the setting, not how many are full with it
why: Nothing catches the defect this branch exists to fix. A `//` block above a declaration is not a Doxygen warning — with WARN_IF_UNDOCUMENTED off and HIDE_UNDOC_MEMBERS on it leaves the member undocumented and drops it silently, which is how 995 of 995 briefs came to be empty. The prose still reaches docs/api.md, so the page and the site disagree and both look fine. WARN_AS_ERROR cannot see it. what: - Have --check report every declaration block carrying no `///` or `//!` line, by file and line, over the headers the page covers - Collect them where they are already found, in _prose_above, behind an optional argument so nothing else changes shape - Cover it with the fixture's own plain-comment declaration
why: The published reference is a Sphinx build — build-site.sh runs sphinx-build over docs/ with Breathe against the Doxygen XML — and it was emitting 77 warnings nobody read. 45 were `inline constexpr` field handles in entities.hpp whose lambda initialiser Sphinx's C++ domain cannot parse: each reached the page as unparsed text, unstyled and without the anchor that makes it linkable. Doxygen warns about none of it, so neither existing gate could see it. Rejected keeping the initialisers and living with the warnings: a declaration that does not parse has no anchor, so nothing can link to it and the index does not carry it. The values are still in docs/api.md, whose renderer keeps a signature with its initializer. what: - Set MAX_INITIALIZER_LINES so declarations reach Sphinx without the initialiser Sphinx cannot read - Suppress duplicate_declaration.cpp, which is what one page per header is: every page re-declares the namespace its header opens - Run the same sphinx-build in the lint job with -W
why: The index called it an umbrella header that "only re-exports others, so they have no reference page of their own". It declares LIBTMUX_REQUIRES_TMUX and LIBTMUX_SKIP_TMUX_DEFECT, the two macros a test author reaches for first, and both are in the Doxygen XML with a brief. docs/api-testing.md has carried them all along, so the site was the only reference missing them. what: - Add docs/testing-capabilities.rst, path-qualified like the sibling capabilities.hpp page so Breathe can tell the two apart - List it in the toctree and drop it from the umbrella section, which now names the one header that is one
libtmux.org links /en/cxx/latest/ from the port switcher and the sidebar of every page, and nothing has ever published there — the URL 403s today. This is what publishes it. The tree is built by libtmux/docs's assembly rather than by anything in this repository: build-site.sh renders the shared concepts, guides and examples with this port's code fences, reading this checkout for the example sources those pages quote. So the job checks out both repositories and points LIBTMUX_DOCS_CHECKOUT_CXX at this one. Without that override the build resolves a developer's home directory, finds nothing, and fails on every inlined example. No port toolchain is needed on the runner. --skip-refs builds the prose tree only; this port's API reference is generated in libtmux/docs's own root pass and published at /en/reference/cxx/, not here. Both references to libtmux/docs pin one full-length commit SHA rather than a tag, with the release name in a trailing comment: this repository runs that repository's build script under a role that writes the live bucket, and a tag can be repointed. They must be bumped together. Temporary and deliberately conservative: no port has published through this workflow yet, so it publishes trunk at cxx/latest with is-default false — reachable and linkable, out of the search index, and the bare port root still falls through to the landing page until the shape is proven.
This repository's lint job runs actionlint, and actionlint 1.7.12 does not know the `queue` concurrency key — it fails the workflow on it. `queue` is in GitHub's published schema and libtmux/docs uses it with successful runs, so this is the linter lagging rather than an error. But a lint that fails is a lint that blocks, and `cancel-in-progress: false` gets the behaviour that matters here: a second push waits rather than cancelling the publish in flight.
That tree 403s on libtmux.org today — nothing has ever published under a port version prefix. This lets the branch publish without merging. Also points pnpm setup at the docs checkout's package.json (this repo has none of its own) and pins the manifest jq fix. Drop docs-site from the trigger in the change that merges to master.
why: The headers gained a second renderer, and the reference a reader is pointed at gained the prose it never carried. Both decide whether someone reading the published surface sees anything against a symbol, which is what an entry here is for. what: - Record the Doxygen and Breathe reference under Documentation - Record that declaration comments reach it, and why they did not - Record the `<ostream>` the Markdown reference was dropping
The C++ reference showed 85 types with a name, a member list and no sentence saying what they were. This writes the missing prose to the rules in WRITING.md: `///` above the declaration, no Doxygen tags, and what the reader cannot see from the signature. The recurring subject is snapshot semantics, because it is the design and it surprises: an entity reads its own fields without touching tmux, so every value it reports is the listing's moment rather than now, and equality is the server incarnation together with the id so a handle kept across a restart does not silently become a handle to whatever reused the number. `expected` and `unexpected_t` are documented on both arms of their `#if`; Doxygen resolves the preprocessor and would otherwise see only the branch without the comment.
"Exchange positions with another window, keeping both ids." sat above next_layout's comment block, which meant Doxygen read it as the first line of next_layout's brief. The published C++ reference described Window::next_layout as swapping two windows. It belongs to swap_with, which had no comment of its own, and is moved back there.
why: Port builds still use the previous docs shell. what: - Pin the docs checkout and deploy workflow to the published refresh.
Mark public declaration comments for Doxygen and add reference pages for error and wait headers. Regenerate the API indexes so the documentation gate and rendered reference stay current.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Publishes this repository's headers as the C++ port's API reference on libtmux.org, skins the output to match the shared site shell, and gates the two ways that reference can silently go blank.
Reference generation
Doxyfileanddocs/conf.pyrun Doxygen and render its XML through Breathe, one page per header for the 31 headers underinclude/. The pages sit directly underdocs/rather than in a nestedapi/subdirectory, which was nesting inside the deployedapi/prefix and giving/cxx/<version>/api/api/<header>/.Doxygen reads the comments now
Every
briefdescriptionin the generated XML was empty — 995 of 995 — because the headers document themselves with plain//, which Doxygen treats as an ordinary source comment rather than a documentation block. It reads///,//!,/**and/*!, andinclude/had none of them.956 comment lines across 27 headers become
///, each one directly above the declaration it describes. The diff is comments only. File-level blocks stay//because they document a file rather than the next declaration. WithJAVADOC_AUTOBRIEFthe XML carries 285 brief descriptions where it carried none..github/WRITING.mdasked for the opposite — "write plain//prose above the declaration" — on the reasoning thattools/docs/api_index.pyharvests it. It still does; Doxygen does not. The policy now asks for///and says why a file-level block is the exception.One comment was breaking the parse, and it was not the one that warned
Doxygen reported
Found unknown command '\t'inoptions.hpp, two lines below the real defect. The cause is a code span three lines earlier: Doxygen treats a'that is not between two word characters as a quote delimiter even inside a code span, so''swallowed the text after it and desynchronised every backtick in the paragraph — which left\tand\ooooutside any span, where they read as commands.Both spans are written with double backticks now, which Doxygen parses and which is also the CommonMark-correct form for a span whose content begins with a space.
docs/api.mdrenders exactly as it did.45 declarations were reaching the site as raw text
The published C++ pages are a Sphinx build:
build-site.shruns Doxygen into a scratch directory, thensphinx-buildover thisdocs/with Breathe against that XML, and copies the result into the assembled site. That build was emitting 77 warnings nobody was reading.45 of them are the
inline constexprfield handles inentities.hpp. Sphinx's C++ domain cannot parse a lambda inside an initialiser, so each one arrived on the page as literal text — unstyled, and without the_CPPv4anchor that makes a declaration linkable or indexable.MAX_INITIALIZER_LINESnow keeps the initialiser out of the XML, and the declarations render properly;docs/api.md, whose renderer keeps a signature with its initializer, still carries the values.The other 32 are the namespace each page re-declares, since Sphinx holds one global C++ scope across a build and this reference is one page per header. That is what a per-header reference is, so
conf.pysuppressesduplicate_declaration.cpp.Three gates, because the failure modes are different
Markup that did not parse is a Doxygen warning, and
WARN_AS_ERRORin theDoxyfilenow makes it fail — in a localdoxygen Doxyfile, in thelintjob, and in the site build, which run the same file.BUILTIN_STL_SUPPORTclears the fourstd::hashwarnings that would otherwise hide a real one.A comment Doxygen never sees produces no warning at all: with
WARN_IF_UNDOCUMENTEDoff andHIDE_UNDOC_MEMBERSon, a//block above a declaration leaves the member undocumented and drops it. That is the original 995-of-995 defect, and it passes the warning gate.api_index.py --checknow reports every such block by file and line, since it already walks exactly those declarations.A declaration Sphinx cannot parse is invisible to both of those — the XML is well-formed and the comment is there; only the render degrades. The
lintjob now runs the samesphinx-buildwith-W.Theming
docs/_static/libtmux-org.cssimports the shell'stokens.cssand maps its semantic tokens onto Furo's--color-*contract. Eachvar()carries Furo's own stock value as its fallback: avar()on an undefined custom property with no fallback resolves to the guaranteed-invalid value, so an unreachabletokens.csswould unset every remapped colour rather than degrade to unmodified Furo.html_js_filesloads the shared shell from a root-relative/_shell/shell.js. An absolutehttps://libtmux.org/_shell/URL resolves nowhere until the site is deployed, which left the injected chrome invisible in every local preview and the token bridge unverifiable.Search
docs/_templates/search.htmlredirects Furo's search page to the shell's Pagefind index at/search/, which covers every port. Furo's own page needs a client-side index this build does not emit at that path. Overriding the template needs an explicittemplates_path, which thisconf.pynever set. The stub carrieshtml[lang]so Pagefind stops warning about it while indexing the assembled site.Smaller things
.gitignorecovers thexml/tree the site build regenerates on every assembly — roughly 30 MB of derived output that Breathe consumes and nothing else reads.include/libtmux/testing/capabilities.hppwas listed on the index as an umbrella header with no page of its own. It declaresLIBTMUX_REQUIRES_TMUXandLIBTMUX_SKIP_TMUX_DEFECT, whichdocs/api-testing.mdhas carried all along; the site reference was the only one missing them, and now has them.api_index.py --checkreported its parser fixture as stale and named no command to fix it, because none existed.--write-fixtureis that command, sharing one renderer with the check so the two cannot disagree.