Skip to content

Curated autodoc rendering: defaults, xrefs, and responsive layout#36

Merged
tony merged 44 commits intomainfrom
improved-defaults-reprs
May 11, 2026
Merged

Curated autodoc rendering: defaults, xrefs, and responsive layout#36
tony merged 44 commits intomainfrom
improved-defaults-reprs

Conversation

@tony
Copy link
Copy Markdown
Member

@tony tony commented May 9, 2026

Summary

  • Add curated default-value rendering for autodoc. autodoc_preserve_defaults=True is now the workspace default, and a new listener fills the synthetic-__init__ gap so dataclass parameters render as =[] instead of =<factory> and sentinel constants render as their source name (scope=DEFAULT_OPTION_SCOPE rather than <...object at 0x...>). Long :value: text on module-level constants collapses with a truncation marker so multi-KB defaults no longer dominate API pages.
  • Add cross-reference transforms. Identifier defaults inside parameter signatures become live :py:class:-shaped xrefs to their documented class, and type expressions in autodoc field lists route through the same xref pipeline as the signature — links resolve consistently and nested types render with chip styling matching the rest of the page.
  • Add responsive autodoc layout. The dual-variant header emits desktop and mobile subtrees under one <dt>; below 52rem the header row stacks instead of squeezing, with the type badge pinned beside the signature and the source link right-anchored in the toolbar. Below 30rem long signatures scroll horizontally instead of wrapping mid-identifier, and the permalink reveals on tap for touch users.
  • Add make_workspace_linkcode_resolve() — a drop-in linkcode_resolve factory for uv/pnpm monorepos. One registration covers every package under packages/ by computing GitHub URLs relative to a repo_root, always pointing at one configurable branch (default main) since workspace packages carry independent versions while the docs site tracks live tip.
  • Adopt cascade-layer hygiene across the autodoc stack. A new gp-sphinx cascade layer makes workspace overrides win over Furo declaratively; parameter and field-list rows share a unified metadata-sized typography band; code chips sit flush against generic-type brackets; root-level scaling follows a clamp() ramp instead of stepping at a single breakpoint.
  • Fix sphinx-gp-theme TOC font-size override. The override was inert because gp-furo-tokens emits the default on body, not :root — moving the override to body restores the configured larger size.
  • Fix sphinx-vite-builder CI hint. The recipe printed by PnpmMissingError (and matching README/AGENTS samples) used to include cache: pnpm, which fails on consumer CI when the consumer repo has no root pnpm-lock.yaml. The hint now omits that line with a note explaining when it is safe to add back.

Changes by area

gp-sphinx

  • config.py: new make_workspace_linkcode_resolve() factory; merge-config now wires autodoc_preserve_defaults.
  • defaults.py: new DEFAULT_AUTODOC_PRESERVE_DEFAULTS = True.

sphinx-autodoc-typehints-gp

Five new private modules implement the default-value pipeline:

  • _param_defaults.py — autodoc listener that fills synthetic __init__ defaults (dataclass / attrs / NamedTuple) by walking the real class body, bypassing the upstream listener's inspect.getsource() limitation.
  • _data_defaults.py:value: truncation for long module-level constants.
  • _default_xref_transform.py — post-transform that AST-parses default-value text and replaces identifier nodes with pending_xref(reftype='class') wrapped to match an inline :py:class: role's HTML shape.
  • _field_xref_transform.py — same canonicalization for type expressions inside autodoc field lists.
  • _resolvers.py — shared resolver catalog consumed by both default-value pipelines; private API (no public add_default_resolver until external demand surfaces).

sphinx-ux-autodoc-layout

  • _transforms.py: dual-variant header emission. _parse_signature_inputs partitions the original desc_signature children into signature row / badges / source link / parameters; _build_signature_column produces one fresh, independently-parented subtree per variant.
  • _static/css/layout.css: container-query-driven @media rules at 52rem and 30rem breakpoints.

Theme / tokens / cross-package CSS

  • gp-furo-theme: declares a gp-sphinx cascade layer after Furo's components layer; scaffold uses a clamp() ramp.
  • gp-furo-tokens: four --gp-sphinx-type-* role aliases over Furo's font-size scale.
  • sphinx-autodoc-api-style, sphinx-autodoc-pytest-fixtures, sphinx-ux-badges: field-list and icon rules relabelled as standalone fallbacks and re-layered into gp-sphinx. Single authoritative grid lives in sphinx-ux-autodoc-layout.

Bug fixes

  • sphinx-gp-theme/.../custom.css: TOC font-size override moved from :root to body.
  • sphinx-vite-builder/_internal/vite.py + README + AGENTS: drop cache: pnpm from the PnpmMissingError hint and matching CI recipes.

Design decisions

  • Workspace defaults, not opt-in. autodoc_preserve_defaults ships flipped on for every consumer rather than as a named option, because the off-state produces unreadable repr text universally. Consumers can still override per-project.
  • Dual variants over JS measurement. Both desktop and mobile header subtrees ship in the DOM; CSS container queries pick one. No layout-shift on hydrate; no JS dependency for the responsive cut-over.
  • linkcode_resolve factory points at one branch, not version tags. Workspace packages carry independent version strings but the docs site tracks live tip. A per-package version tag would require dispatching by __name__ and miss the live behaviour readers expect when following a "Source" link.
  • Resolver catalog stays private. add_default_resolver is not exported; the catalog inside _resolvers.py ships seeded but downstream registration waits for evidence of external demand.

Test plan

  • uv run ruff check . — clean
  • uv run ruff format --check . — no diff
  • uv run mypy — passes across all packages
  • uv run pytest -q — full suite passes, including new unit + integration coverage for _param_defaults, _data_defaults, _default_xref_transform, _field_xref_transform, and make_workspace_linkcode_resolve
  • rm -rf docs/_build && just build-docs — builds without new warnings or errors
  • Dual-variant doctree snapshot tests cover confval, rst:directive, mcp:tool, and large-signature shapes
  • HTML integration tests assert presence of gp-sphinx-api-layout--desktop and gp-sphinx-api-layout--mobile siblings under each managed header
  • Smoke against libtmux / libvcs / libtmux-mcp after a dev release tag propagates — those consumers pin gp-sphinx and sibling packages by version; the rendering win surfaces only once the workspace tag and sibling pins are updated downstream

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 9, 2026

Codecov Report

❌ Patch coverage is 96.07407% with 53 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.86%. Comparing base (4e5eb3e) to head (6dd623e).

Files with missing lines Patch % Lines
...nx_autodoc_typehints_gp/_default_xref_transform.py 88.05% 16 Missing ⚠️
...src/sphinx_autodoc_typehints_gp/_param_defaults.py 80.82% 14 Missing ⚠️
.../src/sphinx_autodoc_typehints_gp/_data_defaults.py 87.17% 5 Missing ⚠️
...hinx_autodoc_typehints_gp/_field_xref_transform.py 95.65% 4 Missing ⚠️
packages/gp-sphinx/src/gp_sphinx/config.py 92.30% 3 Missing ⚠️
...odoc-layout/src/sphinx_ux_autodoc_layout/_cards.py 94.00% 3 Missing ⚠️
tests/ext/typehints_gp/test_param_defaults.py 96.96% 3 Missing ⚠️
docs/_ext/api_demo_typehints_gp.py 92.30% 2 Missing ⚠️
tests/test_config.py 95.91% 2 Missing ⚠️
...layout/src/sphinx_ux_autodoc_layout/_transforms.py 99.15% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #36      +/-   ##
==========================================
+ Coverage   91.57%   91.86%   +0.28%     
==========================================
  Files         205      219      +14     
  Lines       16814    18035    +1221     
==========================================
+ Hits        15398    16567    +1169     
- Misses       1416     1468      +52     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tony
Copy link
Copy Markdown
Member Author

tony commented May 10, 2026

Code review

Found 1 issue:

  1. test_unsupported_default_falls_back_to_plain_text is @pytest.mark.integration but calls build_shared_sphinx_result(...) inline in the test function body rather than via a module-scoped fixture, triggering a fresh Sphinx build per invocation. CLAUDE.md says "No function-scoped Sphinx build fixtures — always module- or session-scoped". The three sibling integration tests in the same file (test_default_value_class_renders_as_xref_link, test_data_attribute_default_links_to_documented_constant, test_cross_module_default_resolves_via_refspecific) all use module-scoped @pytest.fixture(scope="module") wrappers; this one is the outlier.

@pytest.mark.integration
def test_unsupported_default_falls_back_to_plain_text(
tmp_path_factory: pytest.TempPathFactory,
) -> None:
"""Unparseable defaults (lambdas) leave the span as plain text."""
module_source = textwrap.dedent(
"""\
from __future__ import annotations
def has_lambda_default(callback=lambda: 1) -> None:
\"\"\"Function with a lambda default.\"\"\"
"""
)
cache_root = tmp_path_factory.mktemp("default-xref-lambda-html")
scenario = SphinxScenario(
files=(
ScenarioFile("lambda_demo.py", module_source),
ScenarioFile(
"conf.py",
_CONF_PY.replace("__SCENARIO_SRCDIR__", SCENARIO_SRCDIR_TOKEN),
substitute_srcdir=True,
),
ScenarioFile(
"index.rst",
textwrap.dedent(
"""\
Demo
====
.. autofunction:: lambda_demo.has_lambda_default
"""
),
),
),
)
result = build_shared_sphinx_result(
cache_root,
scenario,
purge_modules=("lambda_demo",),
)
html = read_output(result, "index.html")

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

tony added a commit that referenced this pull request May 10, 2026
why: PR #36 review caught a function-scoped Sphinx build inside
`test_unsupported_default_falls_back_to_plain_text` — CLAUDE.md
requires every integration test's Sphinx build to live in a module-
or session-scoped fixture so the build cost is shared across runs.
The three sibling tests in the file already follow the rule; this
one was the outlier because it was the last test added and used a
one-off scenario.

what:
- Hoist the lambda fixture project's module source into a
  module-level `_LAMBDA_MODULE_SOURCE` constant alongside the
  existing `_DATA_ATTRIBUTE_MODULE_SOURCE` and `_CROSS_MODULE_*`
  constants.
- Add `lambda_default_html_result` as a
  `@pytest.fixture(scope="module")` wrapping
  `build_shared_sphinx_result`, mirroring the existing three
  fixtures in shape (parameter list, scenario construction,
  `purge_modules=("lambda_demo",)` argument).
- Convert `test_unsupported_default_falls_back_to_plain_text` to
  consume the fixture parameter and call `read_output` directly;
  the two assertions are unchanged so the test still pins the
  plain-text-fallback contract for unparseable lambda defaults.
tony added a commit that referenced this pull request May 10, 2026
why: PR #36 review caught a function-scoped Sphinx build inside
`test_unsupported_default_falls_back_to_plain_text` — CLAUDE.md
requires every integration test's Sphinx build to live in a module-
or session-scoped fixture so the build cost is shared across runs.
The three sibling tests in the file already follow the rule; this
one was the outlier because it was the last test added and used a
one-off scenario.

what:
- Hoist the lambda fixture project's module source into a
  module-level `_LAMBDA_MODULE_SOURCE` constant alongside the
  existing `_DATA_ATTRIBUTE_MODULE_SOURCE` and `_CROSS_MODULE_*`
  constants.
- Add `lambda_default_html_result` as a
  `@pytest.fixture(scope="module")` wrapping
  `build_shared_sphinx_result`, mirroring the existing three
  fixtures in shape (parameter list, scenario construction,
  `purge_modules=("lambda_demo",)` argument).
- Convert `test_unsupported_default_falls_back_to_plain_text` to
  consume the fixture parameter and call `read_output` directly;
  the two assertions are unchanged so the test still pins the
  plain-text-fallback contract for unparseable lambda defaults.
@tony tony force-pushed the improved-defaults-reprs branch from 49ed000 to d08129a Compare May 10, 2026 11:42
@tony
Copy link
Copy Markdown
Member Author

tony commented May 10, 2026

Code review

Found 1 issue:

  1. _wrap_prefix_in_paragraph lacks an Examples doctest block (CLAUDE.md says "All functions and methods MUST have working doctests"). Sibling helpers in the same file (_role_class_for_field_name, _enclosing_field_name, _normalize_xref_contnode, _is_em_dash_separator, _is_prose_field) all have doctests; this one was missed. Same omission shape as the F6 batch addressed earlier on this branch.

def _wrap_prefix_in_paragraph(
paragraph: nodes.paragraph,
*,
field_name: str = "",
) -> bool:
"""Wrap the prefix children of *paragraph* in a monospace inline.
The prefix is the run of children before the first em-dash text
separator. If no separator exists (e.g. ``:rtype:`` /
``:raises:`` rows whose entire content is a single identifier),
wraps the full child list.
Skipped for prose-style fields (``Returns`` / ``Yields`` /
``Notes`` / ``Examples`` etc.) where the body is free-form
description text — wrapping those paragraphs would re-style
ordinary body copy and clash with embedded inline ``<code>``
spans like ``:any:`None```.
Returns ``True`` if a wrapper was added, ``False`` if the
paragraph was already wrapped, lives inside a prose field, or
otherwise had no eligible content.
"""

CLAUDE.md rule: https://github.com/git-pull/gp-sphinx/blob/d08129a03d466a5246e51ebbfaabad2519c09c0f/CLAUDE.md#L520-L526

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

tony added a commit to tmux-python/libtmux that referenced this pull request May 10, 2026
…ources

why: gp-sphinx PR git-pull/gp-sphinx#36 ships curated parameter and
data-attribute default rendering (source-text reprs, dataclass
factory resolution, long-value truncation, and `:py:class:`-styled
cross-reference links inside default values). Pinning the
gp-sphinx-family deps to that branch via `[tool.uv.sources]` lets
this repo's docs preview the user-visible win before the workspace
release bump propagates the changes via PyPI. After the audit,
libtmux's `<libtmux.constants._DefaultOptionScope object>` defaults
across `Pane._show_option`, `Window._show_option`,
`Session._show_option`, hook dataclasses, etc. drop from 171 ugly
sig-params to 0; `scope=` renders as `DEFAULT_OPTION_SCOPE` linked
to the documented constant.

what:
- Add `[tool.uv.sources]` overrides for gp-sphinx,
  sphinx-autodoc-typehints-gp, sphinx-autodoc-api-style, and
  sphinx-autodoc-pytest-fixtures, all pointing at the
  `improved-defaults-reprs` branch with the appropriate
  monorepo subdirectory.
- Regenerate `uv.lock`; uv resolves all transitive workspace
  siblings (sphinx-fonts, sphinx-gp-opengraph,
  sphinx-ux-autodoc-layout, etc.) from the same commit.
- Revert this commit when gp-sphinx>=0.0.1a18 lands and the
  per-package version pins move forward in the usual workspace
  bump.
tony added a commit that referenced this pull request May 10, 2026
why: PR #36 review caught a function-scoped Sphinx build inside
`test_unsupported_default_falls_back_to_plain_text` — CLAUDE.md
requires every integration test's Sphinx build to live in a module-
or session-scoped fixture so the build cost is shared across runs.
The three sibling tests in the file already follow the rule; this
one was the outlier because it was the last test added and used a
one-off scenario.

what:
- Hoist the lambda fixture project's module source into a
  module-level `_LAMBDA_MODULE_SOURCE` constant alongside the
  existing `_DATA_ATTRIBUTE_MODULE_SOURCE` and `_CROSS_MODULE_*`
  constants.
- Add `lambda_default_html_result` as a
  `@pytest.fixture(scope="module")` wrapping
  `build_shared_sphinx_result`, mirroring the existing three
  fixtures in shape (parameter list, scenario construction,
  `purge_modules=("lambda_demo",)` argument).
- Convert `test_unsupported_default_falls_back_to_plain_text` to
  consume the fixture parameter and call `read_output` directly;
  the two assertions are unchanged so the test still pins the
  plain-text-fallback contract for unparseable lambda defaults.
@tony tony force-pushed the improved-defaults-reprs branch from 2d22ead to 3f669e1 Compare May 10, 2026 13:32
tony added a commit to tmux-python/libtmux that referenced this pull request May 10, 2026
…ources

why: gp-sphinx PR git-pull/gp-sphinx#36 ships curated parameter and
data-attribute default rendering (source-text reprs, dataclass
factory resolution, long-value truncation, and `:py:class:`-styled
cross-reference links inside default values). Pinning the
gp-sphinx-family deps to that branch via `[tool.uv.sources]` lets
this repo's docs preview the user-visible win before the workspace
release bump propagates the changes via PyPI. After the audit,
libtmux's `<libtmux.constants._DefaultOptionScope object>` defaults
across `Pane._show_option`, `Window._show_option`,
`Session._show_option`, hook dataclasses, etc. drop from 171 ugly
sig-params to 0; `scope=` renders as `DEFAULT_OPTION_SCOPE` linked
to the documented constant.

what:
- Add `[tool.uv.sources]` overrides for gp-sphinx,
  sphinx-autodoc-typehints-gp, sphinx-autodoc-api-style, and
  sphinx-autodoc-pytest-fixtures, all pointing at the
  `improved-defaults-reprs` branch with the appropriate
  monorepo subdirectory.
- Regenerate `uv.lock`; uv resolves all transitive workspace
  siblings (sphinx-fonts, sphinx-gp-opengraph,
  sphinx-ux-autodoc-layout, etc.) from the same commit.
- Revert this commit when gp-sphinx>=0.0.1a18 lands and the
  per-package version pins move forward in the usual workspace
  bump.
@tony tony force-pushed the improved-defaults-reprs branch from 627c7e8 to 25efab5 Compare May 10, 2026 18:08
tony added a commit to tmux-python/libtmux-mcp that referenced this pull request May 10, 2026
…ources

why: gp-sphinx PR git-pull/gp-sphinx#36 ships curated parameter and
data-attribute default rendering. Pinning the gp-sphinx-family
deps to that branch via `[tool.uv.sources]` lets this repo's docs
preview the user-visible win before the workspace release bump
propagates the changes via PyPI.
`RetryMiddleware.__init__(retry_exceptions=(<class 'libtmux.exc.
LibTmuxException'>,))` now renders as
`retry_exceptions=(libtmux_exc.LibTmuxException,)` with
`LibTmuxException` linked to its documented exception class in the
same `<a class="reference internal"><code class="xref py
py-class">…</code></a>` shape that inline `:py:class:` roles
produce in body prose.

what:
- Add `[tool.uv.sources]` overrides for gp-sphinx,
  sphinx-autodoc-typehints-gp, sphinx-autodoc-api-style, and
  sphinx-autodoc-fastmcp, all pointing at the
  `improved-defaults-reprs` branch with the appropriate
  monorepo subdirectory.
- Regenerate `uv.lock`; uv resolves all transitive workspace
  siblings from the same commit (8c3a1418).
- Revert this commit when gp-sphinx>=0.0.1a18 lands and the
  per-package version pins move forward in the usual workspace
  bump.
tony added a commit that referenced this pull request May 10, 2026
why: PR #36 review caught a function-scoped Sphinx build inside
`test_unsupported_default_falls_back_to_plain_text` — CLAUDE.md
requires every integration test's Sphinx build to live in a module-
or session-scoped fixture so the build cost is shared across runs.
The three sibling tests in the file already follow the rule; this
one was the outlier because it was the last test added and used a
one-off scenario.

what:
- Hoist the lambda fixture project's module source into a
  module-level `_LAMBDA_MODULE_SOURCE` constant alongside the
  existing `_DATA_ATTRIBUTE_MODULE_SOURCE` and `_CROSS_MODULE_*`
  constants.
- Add `lambda_default_html_result` as a
  `@pytest.fixture(scope="module")` wrapping
  `build_shared_sphinx_result`, mirroring the existing three
  fixtures in shape (parameter list, scenario construction,
  `purge_modules=("lambda_demo",)` argument).
- Convert `test_unsupported_default_falls_back_to_plain_text` to
  consume the fixture parameter and call `read_output` directly;
  the two assertions are unchanged so the test still pins the
  plain-text-fallback contract for unparseable lambda defaults.
@tony tony force-pushed the improved-defaults-reprs branch from 42ced5b to b8fdc67 Compare May 10, 2026 19:37
tony added a commit to tmux-python/libtmux-mcp that referenced this pull request May 10, 2026
…ources

why: gp-sphinx PR git-pull/gp-sphinx#36 ships curated parameter and
data-attribute default rendering. Pinning the gp-sphinx-family
deps to that branch via `[tool.uv.sources]` lets this repo's docs
preview the user-visible win before the workspace release bump
propagates the changes via PyPI.
`RetryMiddleware.__init__(retry_exceptions=(<class 'libtmux.exc.
LibTmuxException'>,))` now renders as
`retry_exceptions=(libtmux_exc.LibTmuxException,)` with
`LibTmuxException` linked to its documented exception class in the
same `<a class="reference internal"><code class="xref py
py-class">…</code></a>` shape that inline `:py:class:` roles
produce in body prose.

what:
- Add `[tool.uv.sources]` overrides for gp-sphinx,
  sphinx-autodoc-typehints-gp, sphinx-autodoc-api-style, and
  sphinx-autodoc-fastmcp, all pointing at the
  `improved-defaults-reprs` branch with the appropriate
  monorepo subdirectory.
- Regenerate `uv.lock`; uv resolves all transitive workspace
  siblings from the same commit (8c3a1418).
- Revert this commit when gp-sphinx>=0.0.1a18 lands and the
  per-package version pins move forward in the usual workspace
  bump.
@tony tony force-pushed the improved-defaults-reprs branch from a0d3576 to 2ebd33b Compare May 10, 2026 22:58
tony added a commit that referenced this pull request May 10, 2026
why: PR #36 review caught a function-scoped Sphinx build inside
`test_unsupported_default_falls_back_to_plain_text` — CLAUDE.md
requires every integration test's Sphinx build to live in a module-
or session-scoped fixture so the build cost is shared across runs.
The three sibling tests in the file already follow the rule; this
one was the outlier because it was the last test added and used a
one-off scenario.

what:
- Hoist the lambda fixture project's module source into a
  module-level `_LAMBDA_MODULE_SOURCE` constant alongside the
  existing `_DATA_ATTRIBUTE_MODULE_SOURCE` and `_CROSS_MODULE_*`
  constants.
- Add `lambda_default_html_result` as a
  `@pytest.fixture(scope="module")` wrapping
  `build_shared_sphinx_result`, mirroring the existing three
  fixtures in shape (parameter list, scenario construction,
  `purge_modules=("lambda_demo",)` argument).
- Convert `test_unsupported_default_falls_back_to_plain_text` to
  consume the fixture parameter and call `read_output` directly;
  the two assertions are unchanged so the test still pins the
  plain-text-fallback contract for unparseable lambda defaults.
tony added a commit to tmux-python/libtmux-mcp that referenced this pull request May 11, 2026
…ources

why: gp-sphinx PR git-pull/gp-sphinx#36 ships curated parameter and
data-attribute default rendering. Pinning the gp-sphinx-family
deps to that branch via `[tool.uv.sources]` lets this repo's docs
preview the user-visible win before the workspace release bump
propagates the changes via PyPI.
`RetryMiddleware.__init__(retry_exceptions=(<class 'libtmux.exc.
LibTmuxException'>,))` now renders as
`retry_exceptions=(libtmux_exc.LibTmuxException,)` with
`LibTmuxException` linked to its documented exception class in the
same `<a class="reference internal"><code class="xref py
py-class">…</code></a>` shape that inline `:py:class:` roles
produce in body prose.

what:
- Add `[tool.uv.sources]` overrides for gp-sphinx,
  sphinx-autodoc-typehints-gp, sphinx-autodoc-api-style, and
  sphinx-autodoc-fastmcp, all pointing at the
  `improved-defaults-reprs` branch with the appropriate
  monorepo subdirectory.
- Regenerate `uv.lock`; uv resolves all transitive workspace
  siblings from the same commit (8c3a1418).
- Revert this commit when gp-sphinx>=0.0.1a18 lands and the
  per-package version pins move forward in the usual workspace
  bump.
tony added a commit to tmux-python/libtmux that referenced this pull request May 11, 2026
…ources

why: gp-sphinx PR git-pull/gp-sphinx#36 ships curated parameter and
data-attribute default rendering (source-text reprs, dataclass
factory resolution, long-value truncation, and `:py:class:`-styled
cross-reference links inside default values). Pinning the
gp-sphinx-family deps to that branch via `[tool.uv.sources]` lets
this repo's docs preview the user-visible win before the workspace
release bump propagates the changes via PyPI. After the audit,
libtmux's `<libtmux.constants._DefaultOptionScope object>` defaults
across `Pane._show_option`, `Window._show_option`,
`Session._show_option`, hook dataclasses, etc. drop from 171 ugly
sig-params to 0; `scope=` renders as `DEFAULT_OPTION_SCOPE` linked
to the documented constant.

what:
- Add `[tool.uv.sources]` overrides for gp-sphinx,
  sphinx-autodoc-typehints-gp, sphinx-autodoc-api-style, and
  sphinx-autodoc-pytest-fixtures, all pointing at the
  `improved-defaults-reprs` branch with the appropriate
  monorepo subdirectory.
- Regenerate `uv.lock`; uv resolves all transitive workspace
  siblings (sphinx-fonts, sphinx-gp-opengraph,
  sphinx-ux-autodoc-layout, etc.) from the same commit.
- Revert this commit when gp-sphinx>=0.0.1a18 lands and the
  per-package version pins move forward in the usual workspace
  bump.
@tony tony force-pushed the improved-defaults-reprs branch from bae0d5b to 80aa08c Compare May 11, 2026 01:16
tony added a commit that referenced this pull request May 11, 2026
why: PR #36 review caught a function-scoped Sphinx build inside
`test_unsupported_default_falls_back_to_plain_text` — CLAUDE.md
requires every integration test's Sphinx build to live in a module-
or session-scoped fixture so the build cost is shared across runs.
The three sibling tests in the file already follow the rule; this
one was the outlier because it was the last test added and used a
one-off scenario.

what:
- Hoist the lambda fixture project's module source into a
  module-level `_LAMBDA_MODULE_SOURCE` constant alongside the
  existing `_DATA_ATTRIBUTE_MODULE_SOURCE` and `_CROSS_MODULE_*`
  constants.
- Add `lambda_default_html_result` as a
  `@pytest.fixture(scope="module")` wrapping
  `build_shared_sphinx_result`, mirroring the existing three
  fixtures in shape (parameter list, scenario construction,
  `purge_modules=("lambda_demo",)` argument).
- Convert `test_unsupported_default_falls_back_to_plain_text` to
  consume the fixture parameter and call `read_output` directly;
  the two assertions are unchanged so the test still pins the
  plain-text-fallback contract for unparseable lambda defaults.
@tony tony force-pushed the improved-defaults-reprs branch 2 times, most recently from 9418f46 to 6025465 Compare May 11, 2026 01:37
tony added 4 commits May 10, 2026 20:42
why: The improved-defaults-reprs branch needs empirical evidence
about which parameter and data-attribute defaults render badly
across workspace consumers before any framework lands. An earlier
audit using `class="default_value"` as the match pattern silently
missed every truly ugly case — when a default's repr contains `<`,
`ast.parse` of the arglist fails inside `_parse_arglist` and Sphinx
falls back to `_pseudo_parse_arglist`, which emits the whole
`name=value` as one `desc_sig_name` text run with no `default_value`
span at all. The corrected probe matches `<em class="sig-param">…
</em>` instead.

what:
- Add packages/sphinx-autodoc-typehints-gp/scripts/audit_defaults.py
  with classify_param() recognising factory_sentinel,
  instance_sentinel, missing_sentinel, other, and long_data_value
  buckets. Doctests cover each bucket; pathlib-only IO; ruff/mypy
  clean.
- Add notes/defaults-discovery-d1.md recording the aggregate
  inventory across libtmux/vcspull/gp-libs/gp-sphinx, the per-tree
  breakdown, sample defaults per ugliness class, and the resolver-
  catalog implications (DataclassFactoryRepr, SentinelInstanceRepr,
  BoundMethodRepr, TruncateLongRepr).
- Document the architectural consequence: a docutils post-transform
  on default_value spans cannot reach the cases that need fixing,
  because those cases produce no default_value spans. Only an
  upstream string-level fix via autodoc-before-process-signature +
  DefaultValue shim applies.
why: Across libtmux, libvcs, vcspull, gp-libs, and gp-sphinx,
parameter defaults that hold non-primitive Python objects render as
`<libtmux.constants._DefaultOptionScope object>` and similar
unreadable repr text. Sphinx already ships a fix —
`autodoc_preserve_defaults=True` invokes `update_default_value` which
parses each function's source via AST and substitutes a `DefaultValue`
shim whose `__repr__` returns the literal source text. The flag is
off by default upstream and `gp-sphinx.defaults` never overrode it,
so the workspace shipped with the ugly repr behavior. D2 evidence
(notes/defaults-discovery-d2.md): the flip clears 81/171 (47%) of
libtmux's ugly parameter defaults and is a prerequisite for the
forthcoming Stage C cross-reference work that depends on having a
parseable arglist.

what:
- Add `DEFAULT_AUTODOC_PRESERVE_DEFAULTS: bool = True` in
  `gp_sphinx.defaults` with a docstring documenting the limitation
  on synthetic dataclass / attrs / NamedTuple `__init__` (covered
  by D3 / Stage B2 follow-on).
- Wire `autodoc_preserve_defaults` into the conf dict produced by
  `merge_sphinx_config()`.
- Add `notes/defaults-discovery-d2.md` with reproducible
  before/after counts: libtmux's `_show_option(scope=...)` renders
  `DEFAULT_OPTION_SCOPE` instead of `<libtmux.constants._Default
  OptionScope object>`; libvcs's `DEFAULT_RULES` line under the
  combined preserve+no-value flags shrinks from 688 chars to 45.
- Document why `autodoc_default_options['no-value']=True` was
  rejected as a workspace default (suppresses useful short values
  like `'/'`, `'HEAD'`, `OptionScope.Pane`); per-attribute opt-in
  via `:no-value:` directive option remains the right tool for
  individual long values until D4's curated resolver lands.
why: After defaulting `autodoc_preserve_defaults=True` workspace-wide
in the previous commit, the only remaining cluster of ugly
parameter defaults is dataclass synthetic `__init__` — Sphinx's own
`update_default_value` bails out at
`_dynamic/_preserve_defaults.py:107-110` because synthetic init has
no source code for `inspect.getsource()`. The empirical inventory
in notes/defaults-discovery-d1.md has libtmux's 90 `<factory>`
occurrences in this bucket. This commit fills the gap so workspace
consumers see clean source-text rendering for `field(default_factory=…)`.

what:
- Add `_param_defaults.py` with `ResolveContext`, `Resolver`
  Protocol, `DataclassFactoryRepr` resolver, and
  `update_synthetic_defvalues` listener. The listener walks
  `dataclasses.fields(parent)`, runs the resolver chain on each
  field's `default_factory`, and replaces matching
  `Parameter.default` with `sphinx.util.inspect.DefaultValue` shims
  so all downstream stringifiers emit the chosen text verbatim.
- `DataclassFactoryRepr` covers stdlib container constructors
  (list/dict/set/frozenset/tuple → `[]`, `{}`, `set()`, etc.) and
  named callable types (`Foo` → `Foo()`). Lambdas and unrecognised
  factories defer (return `None`) — Sphinx's stock `<factory>`
  rendering remains for those.
- Connect the listener via `app.connect("autodoc-before-process-
  signature", update_synthetic_defvalues)` in extension.setup().
- Add config flag `gp_typehints_curate_param_defaults` (default
  `True`) as a kill-switch for downstream debugging.
- Tests: 18 unit tests (Style A NamedTuple parametrization for
  each factory shape; mock-app for listener semantics) plus 2
  integration tests via `tests/_sphinx_scenarios.py`. Verify the
  rendered HTML's `default_value` spans contain the resolver-chosen
  text and that `<factory>` no longer appears.
- gp-sphinx own docs audit: 1 ugly factory_sentinel → 0 after this
  change.
why: For module-level constants like libvcs's DEFAULT_RULES (688
chars) and class data like GitURL.rule_map (5 738 chars in the
original audit), Sphinx emits a `:value: <objrepr>` line that
explodes the signature block. The built-in `:no-value:` baseline
(D2) suppresses every value indiscriminately — including useful
short ones like `'/'` and `OptionScope.Pane` — so it can't ship as
a workspace default. This commit adds the curated alternative: a
resolver chain that truncates only long values and leaves short
ones untouched.

what:
- Add `_data_defaults.py` with `TruncateLongRepr` resolver,
  `_curate_value_line` helper, and `GpDataDocumenter` /
  `GpAttributeDocumenter` subclasses. The subclasses override
  `add_line` to route `:value:` lines through the curator;
  everything else passes through. Reuses `ResolveContext` and
  `_run_chain` from D3's `_param_defaults` module so the resolver
  catalog grows uniformly across Site A and Site B.
- Register the documenters via `app.add_autodocumenter(...,
  override=True)` and add the kill-switch config flag
  `gp_typehints_curate_data_defaults` (default `True`).
- Tests: 9 unit tests parametrised in Style A NamedTuple form
  (TruncateLongRepr threshold edges, kill-switch, kind-filtering,
  pass-through) plus 2 integration tests via
  `tests/_sphinx_scenarios.py` that build fixture projects with a
  short and a long module constant and assert the HTML contains
  the truncated marker only on the long one.
- Resolver scope is intentionally conservative for the prototype
  (truncation only). Richer resolvers (list-of-dataclasses
  summary, compiled-regex repr) are deferred to D5 where the
  shared catalog factoring decision lands.
@tony tony force-pushed the improved-defaults-reprs branch from cdd6a58 to 6dd623e Compare May 11, 2026 02:00
tony added a commit to cihai/cihai that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18.dev0 ships the curated autodoc rendering
work from PR git-pull/gp-sphinx#36 — default-value source text,
identifier cross-references in parameter signatures, canonical
field-list xrefs, responsive autodoc layout, and a workspace-wide
linkcode_resolve factory.

what:
- bump every gp-sphinx workspace pin in pyproject.toml + uv.lock
- add [tool.uv].prerelease = "allow" so uv resolves the *.dev0 pins
tony added a commit to cihai/cihai-cli that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18.dev0 ships the curated autodoc rendering
work from PR git-pull/gp-sphinx#36 — default-value source text,
identifier cross-references in parameter signatures, canonical
field-list xrefs, responsive autodoc layout, and a workspace-wide
linkcode_resolve factory.

what:
- bump every gp-sphinx workspace pin in pyproject.toml + uv.lock
- add [tool.uv].prerelease = "allow" so uv resolves the *.dev0 pins
tony added a commit to tony/django-docutils that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18.dev0 ships the curated autodoc rendering
work from PR git-pull/gp-sphinx#36 — default-value source text,
identifier cross-references in parameter signatures, canonical
field-list xrefs, responsive autodoc layout, and a workspace-wide
linkcode_resolve factory.

what:
- bump every gp-sphinx workspace pin in pyproject.toml + uv.lock
- add [tool.uv].prerelease = "allow" so uv resolves the *.dev0 pins
tony added a commit to tony/django-slugify-processor that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18.dev0 ships the curated autodoc rendering
work from PR git-pull/gp-sphinx#36 — default-value source text,
identifier cross-references in parameter signatures, canonical
field-list xrefs, responsive autodoc layout, and a workspace-wide
linkcode_resolve factory.

what:
- bump every gp-sphinx workspace pin in pyproject.toml + uv.lock
- add [tool.uv].prerelease = "allow" so uv resolves the *.dev0 pins
tony added a commit to vcs-python/g that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18.dev0 ships the curated autodoc rendering
work from PR git-pull/gp-sphinx#36 — default-value source text,
identifier cross-references in parameter signatures, canonical
field-list xrefs, responsive autodoc layout, and a workspace-wide
linkcode_resolve factory.

what:
- bump every gp-sphinx workspace pin in pyproject.toml + uv.lock
- add [tool.uv].prerelease = "allow" so uv resolves the *.dev0 pins
tony added a commit to git-pull/gp-libs that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18.dev0 ships the curated autodoc rendering
work from PR git-pull/gp-sphinx#36 — default-value source text,
identifier cross-references in parameter signatures, canonical
field-list xrefs, responsive autodoc layout, and a workspace-wide
linkcode_resolve factory.

what:
- bump every gp-sphinx workspace pin in pyproject.toml + uv.lock
- add [tool.uv].prerelease = "allow" so uv resolves the *.dev0 pins
tony added a commit to tmux-python/libtmux that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18.dev0 ships the curated autodoc rendering
work from PR git-pull/gp-sphinx#36 — default-value source text,
identifier cross-references in parameter signatures, canonical
field-list xrefs, responsive autodoc layout, and a workspace-wide
linkcode_resolve factory.

what:
- bump every gp-sphinx workspace pin in pyproject.toml + uv.lock
- add [tool.uv].prerelease = "allow" so uv resolves the *.dev0 pins
tony added a commit to vcs-python/libvcs that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18.dev0 ships the curated autodoc rendering
work from PR git-pull/gp-sphinx#36 — default-value source text,
identifier cross-references in parameter signatures, canonical
field-list xrefs, responsive autodoc layout, and a workspace-wide
linkcode_resolve factory.

what:
- bump every gp-sphinx workspace pin in pyproject.toml + uv.lock
- add [tool.uv].prerelease = "allow" so uv resolves the *.dev0 pins
tony added a commit to tmux-python/tmuxp that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18.dev0 ships the curated autodoc rendering
work from PR git-pull/gp-sphinx#36 — default-value source text,
identifier cross-references in parameter signatures, canonical
field-list xrefs, responsive autodoc layout, and a workspace-wide
linkcode_resolve factory.

what:
- bump every gp-sphinx workspace pin in pyproject.toml + uv.lock
- add [tool.uv].prerelease = "allow" so uv resolves the *.dev0 pins
tony added a commit to cihai/unihan-db that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18.dev0 ships the curated autodoc rendering
work from PR git-pull/gp-sphinx#36 — default-value source text,
identifier cross-references in parameter signatures, canonical
field-list xrefs, responsive autodoc layout, and a workspace-wide
linkcode_resolve factory.

what:
- bump every gp-sphinx workspace pin in pyproject.toml + uv.lock
- add [tool.uv].prerelease = "allow" so uv resolves the *.dev0 pins
tony added a commit to cihai/unihan-etl that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18.dev0 ships the curated autodoc rendering
work from PR git-pull/gp-sphinx#36 — default-value source text,
identifier cross-references in parameter signatures, canonical
field-list xrefs, responsive autodoc layout, and a workspace-wide
linkcode_resolve factory.

what:
- bump every gp-sphinx workspace pin in pyproject.toml + uv.lock
- add [tool.uv].prerelease = "allow" so uv resolves the *.dev0 pins
tony added a commit to vcs-python/vcspull that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18.dev0 ships the curated autodoc rendering
work from PR git-pull/gp-sphinx#36 — default-value source text,
identifier cross-references in parameter signatures, canonical
field-list xrefs, responsive autodoc layout, and a workspace-wide
linkcode_resolve factory.

what:
- bump every gp-sphinx workspace pin in pyproject.toml + uv.lock
- add [tool.uv].prerelease = "allow" so uv resolves the *.dev0 pins
tony added a commit to tmux-python/libtmux-mcp that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18.dev0 ships the curated autodoc rendering
work from PR git-pull/gp-sphinx#36 — default-value source text,
identifier cross-references in parameter signatures, canonical
field-list xrefs, responsive autodoc layout, and a workspace-wide
linkcode_resolve factory.

what:
- bump every gp-sphinx workspace pin in pyproject.toml + uv.lock
- add [tool.uv].prerelease = "allow" so uv resolves the *.dev0 pins
@tony tony merged commit 424b622 into main May 11, 2026
64 checks passed
@tony tony deleted the improved-defaults-reprs branch May 11, 2026 02:19
tony added a commit to cihai/cihai that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18 is now live on PyPI with the curated
autodoc rendering work from PR git-pull/gp-sphinx#36 (merged to
main). The dev0 scaffolding can come out.

what:
- bump every gp-sphinx workspace pin from 0.0.1a18.dev0 to 0.0.1a18
- remove [tool.uv].prerelease = "allow" — no longer needed now
  that the dev marker is gone
tony added a commit to cihai/cihai-cli that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18 is now live on PyPI with the curated
autodoc rendering work from PR git-pull/gp-sphinx#36 (merged to
main). The dev0 scaffolding can come out.

what:
- bump every gp-sphinx workspace pin from 0.0.1a18.dev0 to 0.0.1a18
- remove [tool.uv].prerelease = "allow" — no longer needed now
  that the dev marker is gone
tony added a commit to tony/django-docutils that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18 is now live on PyPI with the curated
autodoc rendering work from PR git-pull/gp-sphinx#36 (merged to
main). The dev0 scaffolding can come out.

what:
- bump every gp-sphinx workspace pin from 0.0.1a18.dev0 to 0.0.1a18
- remove [tool.uv].prerelease = "allow" — no longer needed now
  that the dev marker is gone
tony added a commit to tony/django-slugify-processor that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18 is now live on PyPI with the curated
autodoc rendering work from PR git-pull/gp-sphinx#36 (merged to
main). The dev0 scaffolding can come out.

what:
- bump every gp-sphinx workspace pin from 0.0.1a18.dev0 to 0.0.1a18
- remove [tool.uv].prerelease = "allow" — no longer needed now
  that the dev marker is gone
tony added a commit to vcs-python/g that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18 is now live on PyPI with the curated
autodoc rendering work from PR git-pull/gp-sphinx#36 (merged to
main). The dev0 scaffolding can come out.

what:
- bump every gp-sphinx workspace pin from 0.0.1a18.dev0 to 0.0.1a18
- remove [tool.uv].prerelease = "allow" — no longer needed now
  that the dev marker is gone
tony added a commit to git-pull/gp-libs that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18 is now live on PyPI with the curated
autodoc rendering work from PR git-pull/gp-sphinx#36 (merged to
main). The dev0 scaffolding can come out.

what:
- bump every gp-sphinx workspace pin from 0.0.1a18.dev0 to 0.0.1a18
- remove [tool.uv].prerelease = "allow" — no longer needed now
  that the dev marker is gone
tony added a commit to tmux-python/libtmux that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18 is now live on PyPI with the curated
autodoc rendering work from PR git-pull/gp-sphinx#36 (merged to
main). The dev0 scaffolding can come out.

what:
- bump every gp-sphinx workspace pin from 0.0.1a18.dev0 to 0.0.1a18
- remove [tool.uv].prerelease = "allow" — no longer needed now
  that the dev marker is gone
tony added a commit to vcs-python/libvcs that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18 is now live on PyPI with the curated
autodoc rendering work from PR git-pull/gp-sphinx#36 (merged to
main). The dev0 scaffolding can come out.

what:
- bump every gp-sphinx workspace pin from 0.0.1a18.dev0 to 0.0.1a18
- remove [tool.uv].prerelease = "allow" — no longer needed now
  that the dev marker is gone
tony added a commit to tmux-python/tmuxp that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18 is now live on PyPI with the curated
autodoc rendering work from PR git-pull/gp-sphinx#36 (merged to
main). The dev0 scaffolding can come out.

what:
- bump every gp-sphinx workspace pin from 0.0.1a18.dev0 to 0.0.1a18
- remove [tool.uv].prerelease = "allow" — no longer needed now
  that the dev marker is gone
tony added a commit to cihai/unihan-db that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18 is now live on PyPI with the curated
autodoc rendering work from PR git-pull/gp-sphinx#36 (merged to
main). The dev0 scaffolding can come out.

what:
- bump every gp-sphinx workspace pin from 0.0.1a18.dev0 to 0.0.1a18
- remove [tool.uv].prerelease = "allow" — no longer needed now
  that the dev marker is gone
tony added a commit to cihai/unihan-etl that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18 is now live on PyPI with the curated
autodoc rendering work from PR git-pull/gp-sphinx#36 (merged to
main). The dev0 scaffolding can come out.

what:
- bump every gp-sphinx workspace pin from 0.0.1a18.dev0 to 0.0.1a18
- remove [tool.uv].prerelease = "allow" — no longer needed now
  that the dev marker is gone
tony added a commit to vcs-python/vcspull that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18 is now live on PyPI with the curated
autodoc rendering work from PR git-pull/gp-sphinx#36 (merged to
main). The dev0 scaffolding can come out.

what:
- bump every gp-sphinx workspace pin from 0.0.1a18.dev0 to 0.0.1a18
- remove [tool.uv].prerelease = "allow" — no longer needed now
  that the dev marker is gone
tony added a commit to tmux-python/libtmux-mcp that referenced this pull request May 11, 2026
why: gp-sphinx 0.0.1a18 is now live on PyPI with the curated
autodoc rendering work from PR git-pull/gp-sphinx#36 (merged to
main). The dev0 scaffolding can come out.

what:
- bump every gp-sphinx workspace pin from 0.0.1a18.dev0 to 0.0.1a18
- remove [tool.uv].prerelease = "allow" — no longer needed now
  that the dev marker is gone
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.

2 participants