Skip to content

Autodoc design system: 3-tier architecture, shared layout + typehints#18

Merged
tony merged 174 commits intomainfrom
autodoc-improvements
Apr 12, 2026
Merged

Autodoc design system: 3-tier architecture, shared layout + typehints#18
tony merged 174 commits intomainfrom
autodoc-improvements

Conversation

@tony
Copy link
Copy Markdown
Member

@tony tony commented Apr 12, 2026

Summary

gp-sphinx moves from a shared-config package to an integrated autodoc design system. Twelve packages now sit in three clear tiers with one badge palette, one layout pipeline, one typehint renderer, and one CSS vocabulary — so Python APIs, argparse CLIs, pytest fixtures, Sphinx config values, docutils directives, and FastMCP tools all render like they belong together.

  • Two new foundation packagessphinx-ux-autodoc-layout (card regions, parameter folding, managed signatures) and sphinx-autodoc-typehints-gp (single-package replacement for sphinx-autodoc-typehints + sphinx.ext.napoleon, resolves annotations statically at build time with no monkey-patching).
  • Three-tier architecture — shared infrastructure (sphinx-ux-badges, sphinx-ux-autodoc-layout, sphinx-autodoc-typehints-gp) at the bottom; six domain packages (-api-style, -argparse, -docutils, -fastmcp, -pytest-fixtures, -sphinx) in the middle; theme and coordinator (gp-sphinx, sphinx-gp-theme, sphinx-fonts) on top. Lower layers never depend on higher ones. The sidebar further splits into four reader-facing navigation buckets (Domain Packages, UX, Utils, Internal) — an orthogonal view of the same twelve packages.
  • argparse Sphinx domainsphinx-autodoc-argparse now ships a real Domain subclass with program / option / subcommand / positional ObjTypes, :argparse:* xref roles, and two auto-generated indices (argparse-programsindex, argparse-optionsindex). :option: / std:cmdoption keeps emitting for intersphinx compat; Framework :: Sphinx :: Domain classifier added.
  • Unified visual vocabulary — every class, directive, and CSS custom property is namespaced under a single gp-sphinx-* two-tier BEM root (gp-sphinx-badge, gp-sphinx-fastmcp__*, etc.). The opaque per-package prefixes (sab-, smf-, spf-, api-, gas-, gal-) are gone, along with the duplicate palettes that hid behind them.
  • Sphinx 8.1 floor + typed domains — workspace floor bumped to Sphinx 8.1 so every extension can use env.domains.<name>_domain typed accessors; eight t.cast / # type: ignore workarounds retired as a result.
  • Test harness rebuilt for speed — a shared SphinxScenario cache keyed by content-hash SHA-256 reuses identical builds across tests (full-suite runtime ~40 s → ~4.2 s for 916 tests per the workspace's own profiling note). Most tests now run against the docutils doctree directly; a syrupy-based snapshot layer locks in doctree structure, rendered HTML, and warning output with path + ANSI normalization built in.

Breaking changes (renames)

All old names redirect via docs/redirects.txt, but imports need updating downstream:

  • sphinx-argparse-neosphinx-autodoc-argparse
  • sphinx-gpthemesphinx-gp-theme
  • sphinx-autodoc-badgessphinx-ux-badges
  • sphinx-autodoc-layoutsphinx-ux-autodoc-layout
  • sphinx-typehints-gpsphinx-autodoc-typehints-gp
  • autosphinxconfig-index directive → autoconfigvalue-index
  • fastmcp-toolsummary directive → fastmcp-tool-summary
  • doc-pytest-plugin directive → auto-pytest-plugin

Test plan

Every commit on this branch passed the same gate before landing:

  • uv run ruff check . --fix --show-fixes
  • uv run ruff format .
  • uv run mypy
  • uv run py.test --reruns 0 -vvv — 1123 passed / 3 skipped
  • just build-docs

Worth eyes on from a reviewer:

  • Gallery page (docs/gallery.md) renders every badge variant and every autodoc component against the new CSS; confirm no visual regressions vs. the pre-branch build.
  • Rendered Safety-tier badge colours are byte-identical (only the custom-property names changed, not the hex values).
  • Deprecated pytest fixtures render visually muted both with and without sphinx-autodoc-api-style loaded (the muting rule is now self-contained in sphinx-autodoc-pytest-fixtures' own CSS as well).
  • pytest -m integration and pytest -m "not integration" both partition cleanly — every Sphinx-build test is marked.
  • CI smoke matrix is green for all twelve publishable packages, including the four newly-matrixed entries (sphinx-autodoc-fastmcp, sphinx-autodoc-typehints-gp, sphinx-ux-badges, sphinx-ux-autodoc-layout).
  • docs/architecture.md and docs/whats-new.md accurately describe the shipped state of the tier boundaries.

tony added 30 commits April 12, 2026 09:01
why: out/ was a repo-local symlink tree mirroring testpaths, used as a
workaround for an upstream pytest capture-teardown bug. Removing it
from tracked history (filter-repo) requires a gitignore so it cannot
be recommitted by accident if the validator mirror is recreated
locally.
what:
- Add out/ to .gitignore under a new "Repo-local pytest mirror" section
…arameter folding

why: Large autodoc entries (90+ kwargs) need semantic regions for
independent styling and progressive disclosure.  Parameters repeated 3x
due to autoclass_content="both" + autodoc_class_signature="separated".
what:
- Fix DEFAULT_AUTOCLASS_CONTENT from "both" to "class" (eliminates 3x params)
- Two custom nodes: gal_region (contiguous content wrappers) and gal_fold
  (<details>/<summary> for large field lists)
- doctree-resolved handler at priority 600 (after api-style at 500)
- Order-preserving contiguous chunking: narrative / fields / members
- Fold large field_list in desc_content, not desc_signature
- Descendant CSS selectors (survive <details> wrapping)
- JS hash-based auto-expand for <details> ancestors
- Tests for nodes, classification, wrapping, and folding
why: Sphinx's DocFieldTransformer collapses params into a single field
with a bullet_list of list_items. The fold logic counted field nodes
(always 1-3) instead of list_items (the actual param count), so the
fold never triggered.
what:
- Add _count_field_entries() that counts list_items inside collapsed
  bullet_list fields plus standalone field nodes
- Enable extension in docs/conf.py with gal_enabled=True
- Add gal_demo_api.py demo with LayoutDemo class (13 params)
- Add live demo section to sphinx-autodoc-layout.md
- Verify: docs build shows <details class="gal-fold"> wrapping params
why: Large signatures (13+ params) overwhelm the header. The user
wants collapsed: __init__(host, [...]) that expands to show each
param on its own indented line.
what:
- Add gal_sig_fold node wrapping desc_parameterlist in <details>
- Summary shows first param + [...]; open shows full list one-per-line
- CSS: font-size:0 hides commas, block em.sig-param for indentation
- Parens from desc_parameterlist visitor stay intact (no duplication)
- Fix _count_field_entries for Sphinx collapsed bullet_list fields
…m indent

why: Raw comma text nodes and flex centering broke the expanded gal-sig-fold
display; param lines used heavy 2rem indent.
what:
- Hide comma text nodes via font-size on open details; restore em/sig-paren
- Trailing commas via ::after; last param has none
- dt:has(open) align-items flex-start for api-style flex dt
- Use display block on open details; reduce param margin-left to 1rem
why: Preserve Sphinx autodoc semantics while exposing a stable, customizable DOM contract and letting long signatures expand onto a real second row.
what:
- rebuild managed Python autodoc entries into explicit api-* header, content, and footer components with custom inline signature disclosure
- add layout integration coverage for parameter regions, member nesting, badge and source placement, and hash-driven expansion behavior
- align pytest fixture warning paths with both caplog-based unit tests and Sphinx integration warning streams
why: We need focused snapshot tests for rendered autodoc HTML, and the user asked for the dependency to land in its own isolated commit.
what:
- Add Syrupy to the dev dependency group
- Refresh uv.lock for the new snapshot testing dependency
why: Expanded folded signatures needed cleaner formatting, explicit collapse affordances, and stronger regression coverage to keep the new API layout maintainable.
what:
- Rework folded signature output around Sphinx's native multiline parameter HTML with typed annotation recovery
- Add explicit collapse controls and CSS fixes for single-indent multiline rendering
- Cover the rendered API header with integration checks and Syrupy-backed HTML snapshots
why: Collapsed folded signatures were still pinned to the top of the padded
dt card because the centering state lived on the inner layout row instead of
the real desc_signature shell.
what:
- move managed signature expansion state onto desc_signature/dt and sync it in JS
- render managed dt tags directly so custom header attributes reach the HTML output
- retarget CSS, integration coverage, and snapshots to the dt-level header model
why: Integration tests were rebuilding the same synthetic Sphinx projects dozens of times and dominated the suite runtime.
what:
- add a typed test-only Sphinx scenario cache helper with shared-build and copied-source modes
- reuse cached scenario builds in layout and pytest-fixture integration tests
- add regression tests covering cache reuse, scenario key changes, and source-tree isolation
why: Keep local feedback focused on doctree and snapshot contracts while reserving full Sphinx builds for explicit end-to-end output checks.
what:
- split synthetic Sphinx scenario coverage into doctree-first and minimal E2E lanes
- add typed snapshot and scenario helpers plus fast local test commands and docs
- move non-builder scenario cache tests out of integration and fix remaining typing issues
why: Local test runs were dominated by pytest tmp_path retention and numbering overhead rather than real test work.
what:
- update just recipes to use fixed repo-local basetemps and no tmp retention
- document the optimized local runner strategy and known pytest capture bugs
- trim a few unnecessary tmp_path usages in snapshot, font, and scenario tests
why: Keep the Sphinx-heavy test lane focused on true builder-facing contracts
while documenting where runtime still goes in the current environment.
what:
- extract pure pytest-fixture helpers for generated directive text, fixture-index
  table selection, and doc-pytest-plugin section assembly
- replace heavier synthetic Sphinx assertions with narrower helper and doctree
  tests, and update snapshots to match the slimmer contract surface
- refresh the performance analysis note and local runner docs with current
  benchmark data, remaining E2E hotspots, and known pytest runner anomalies
why: The earlier analysis over-weighted deselected fast lanes and hid where the
real full-suite time still goes. We also still had a few synthetic Sphinx tests
rebuilding per test when module-scoped shared results would cover the same
contracts.
what:
- update notes/test-analysis.md with full-suite benchmarks, cProfile findings,
  remaining slow tests, and current runner anomalies
- remove redundant pytest ignore filters and document fast local loops as
  local-only diagnostics instead of coverage baselines
- share layout and pytest-fixture Sphinx scenarios via module-scoped cache roots
  and a layout conftest to reduce unnecessary synthetic harnessing
why: Keep the performance write-up grounded in full-suite coverage and trim one more unnecessary temp-dir hotspot.
what:
- refresh notes/test-analysis.md with full-suite benchmarks, cProfile findings, and runner anomaly details
- document the pytest tempdir and capture costs separately from real Sphinx builder time
- reuse a module-scoped temp root in tests/test_sphinx_scenarios.py to reduce needless tmp_path churn
why: Keep full coverage intact while cutting over-harnessed Sphinx tests and
recording where suite time is really going.
what:
- replace remaining directive/order/filter checks with pure helper or smaller doctree tests
- reuse shared layout and scenario builds more aggressively with session or module cache roots
- update test-analysis with full-suite timings, profile findings, and the external pytest runner repro
why: The raw full suite was still paying avoidable pytest temp-path overhead even after the Sphinx harness audit, and doctests were still creating fresh tmp paths without adding coverage.
what:
- switch doctest namespace injection to a session-scoped shared tmp path
- update test-analysis notes with the new full-suite benchmarks and profile data
- document that the remaining multi-second tests are mostly real builder-facing contracts
why: Reduce synthetic Sphinx setup cost without hiding coverage, and refresh the suite analysis around real full-suite behavior.
what:
- add shared session-scoped pytest-fixture scenario roots for doctree, html, and type-checking tests
- replace one heavy MyST autofixtures snapshot with a smaller contract-focused smoke
- update the test performance note with current benchmarks, slow-test causes, and runner bug analysis
why: Reduce full-suite runtime by removing unnecessary synthetic fixture surface
from smoke tests while preserving real emitted-output and xref coverage.
what:
- shrink pytest-fixture HTML, text, MyST, and fixture-index smoke scenarios to
  the minimum fixture set needed for each contract
- narrow scenario-cache smokes to a plain-page Sphinx scenario instead of
  paying for autodoc imports
- refresh notes/test-analysis.md with the new full-suite baselines, slow-test
  table, and updated profiling findings
why: One more repeated dummy-builder scenario was still paying for separate setup despite testing the same reduced fixture surface.
what:
- share one MyST dummy-builder result across the doc-pytest-plugin and autofixtures smokes
- keep the canonical RST doc-pytest-plugin snapshot on the reduced smoke fixture source
- refresh notes/test-analysis.md with current serial full-suite timings, slow tests, and profile data
why: Keep full builder-backed coverage while removing the last smoke-only
Sphinx work that was larger than the contracts under test.
what:
- reduce the pytest-fixture html and text smoke pages to the minimal emitted
  output surface they assert
- refresh notes/test-analysis.md with current full-suite benchmarks, profile
  findings, and runner-bug evidence
why: Reduce unnecessary Sphinx harness cost without hiding failures and keep the required validator running the real suite.
what:
- rewrite the TYPE_CHECKING alias metadata smoke as a typed helper-level registration test
- remove the unused shared type-checking Sphinx scenario fixture and refresh the runtime analysis note
- add the repo-local pytest and out-path mitigation for the upstream-style capture teardown bug
why: Make Python-domain autodoc output easier to customize while reducing validation cost without hiding failures.
what:
- add a typed api_slot contract and make sphinx-autodoc-layout own final header/body composition
- switch api-style and pytest-fixtures to structured badge/source slots and auto-load layout
- replace heavy layout header snapshots with doctree snapshots and refresh test/runtime analysis
…ackages

why: Centralize object-entry composition so badges, headers, and body regions
can be reused across the autodoc packages without raw HTML mutation or extra
full-build coverage.
what:
- add shared layout profiles and parse helpers for py, std, and rst entries
- move autodoc-sphinx and autodoc-docutils onto slot-based layout badges
- refactor FastMCP cards onto shared api regions and add lighter tests
why: Consolidate the remaining slot-injection duplication, validate a future
FastMCP desc migration safely, and keep the runtime analysis aligned with the
actual post-refactor suite.
what:
- add a shared layout slot helper and move producer packages onto it
- add a test-only FastMCP desc prototype plus lighter doctree and snapshot coverage
- refresh the autodoc/runtime analysis with second-wave benchmarks and profile data
why: FastMCP lacked rendered output demos and the autodoc package pages had drifted into inconsistent documentation patterns.
what:
- add a typed FastMCP docs demo module and wire FastMCP into the docs build
- standardize autodoc package pages around config, usage examples, and live demos
- add focused docs parity tests plus a cached FastMCP page render smoke
…ured section passthrough

why: Centralize the remaining duplicated badge construction and body-section building
across all autodoc producer packages so future changes land in one place.
what:
- add BadgeSpec dataclass + build_badge_from_spec/build_badge_group_from_specs to sphinx-autodoc-badges
- add _sections.py (ApiFactRow, build_api_facts_section, build_api_table_section) to sphinx-autodoc-layout
- add _STRUCTURED_SECTION_NAMES passthrough in _wrap_content_runs so pre-built api-facts/api-options nodes survive the transform
- migrate all producer _badges.py files onto BadgeSpec (docutils, fastmcp, pytest-fixtures, sphinx)
- add _normalize_directive_nodes / _normalize_role_nodes to autodoc-docutils using shared sections
- move confval type/default/registered-by facts into build_api_facts_section in autodoc-sphinx
- replace inline returns_para in fastmcp with build_api_facts_section
- add _wrap_fixture_field_lists in pytest-fixtures transforms to wrap field lists in api-facts/api-parameters
- update all integration tests to assert api-facts and api-options sections are present
- add test_wrap_preserves_prebuilt_fact_sections, test_build_badge_from_spec, test_build_badge_group_from_specs
- update fixture doctree snapshots for sab-badge-group class and api-facts wrapping
- update test-analysis.md with wave 3 suite status and benchmark data
…ucer-level doctree tests

why: rst:directive, rst:role, and confval entries had the correct api-container HTML
structure but no visual card styling; Furo only cards dl.py.* entries so non-Python domain
entries appeared flat. Producer behavior also lacked targeted unit tests.
what:
- add dl.api-container:not(.py) card rules to layout.css (border, border-radius, box-shadow,
  header background, hover, nested lighter treatment) — matches api_style.css dl.py values
- CSS lives in layout.css so any project using sphinx-autodoc-layout gets card styling
  regardless of whether sphinx-autodoc-api-style is also loaded
- add tests/ext/autodoc_docutils/test_doctree.py: 9 unit tests for _normalize_directive_nodes
  and _normalize_role_nodes verifying api-facts labels, fact values, option extraction,
  and cross-domain isolation — no Sphinx app required
- add tests/ext/autodoc_sphinx/test_doctree.py: 11 unit tests for _config_fact_rows
  verifying Type/Default/Registered-by rows, complex-default literal_block path,
  simple-default paragraph path, and ApiFactRow type conformance
- update notes/test-analysis.md with wave 4 diagnosis, fix, docs rebuild note, and benchmark
…ehavior

why: Three visual bugs introduced after the api-container containerization:
- dt.api-header had align-items:center without display:flex so centering was a no-op
- rst/confval badge type classes had no color definitions (transparent background)
- api-link permalink was always visible because Furo only hides direct dt children

what:
- add display:flex to dl.api-container > dt.api-header (both normal and expanded variants)
  so div.api-layout is vertically centered within the padded dt
- add visibility:hidden to .api-link and visibility:visible on dt:hover and :focus-visible
  to match Furo's headerlink hover behavior for the nested permalink
- create sphinx-autodoc-sphinx/_static/css/sphinx_autodoc_sphinx.css with amber tokens
  for sas-badge--type (config badge) and gray tokens for sas-badge--rebuild (outline badge)
- create sphinx-autodoc-docutils/_static/css/sphinx_autodoc_docutils.css with violet tokens
  for sadoc-badge--type (directive/role/option badges)
- register new CSS files in setup() for both packages; update doctest FakeApp
- update test_css.py assertions to expect display:flex in dt.api-header rules
tony added 28 commits April 12, 2026 14:01
…string

why: aa664ff renamed SAB.obj_type()'s returned class from sab-type-*
to gp-sphinx-badge--type-*, but this test's docstring kept the old
name. Assertions already check the new name, so the docstring was the
sole remaining stale reference in this file.
what:
- Replace "returns sab-type-* class" with
  "returns gp-sphinx-badge--type-* class" on one docstring line
- No behavioural change; docstring-only edit
why: AGENTS.md requires "every parameter and -> None must be typed"
on test functions. Four tests added in the doctree-test extraction
passed snapshot_doctree / snapshot_warnings unannotated, while the
adjacent test_snapshots.py and test_layout/ files already follow the
pattern `snapshot_doctree: t.Callable[..., None]`. The fixtures
declare that exact return type in tests/_snapshots.py, so the
annotation is a straight alignment with the fixture contract and
the house style.
what:
- Annotate snapshot_doctree / snapshot_warnings parameters as
  t.Callable[..., None] on 4 test functions:
  test_default_fixture_post_transform_snapshot,
  test_dependency_rendering_snapshot,
  test_warning_and_manual_option_snapshot,
  test_doc_pytest_plugin_rst_snapshot.
- No behavioural change; purely annotation work.
why: AGENTS.md's Test Level Hierarchy defines "Sphinx integration"
as any test that "must verify actual HTML output or Sphinx event
wiring". Every test in this file runs a real Sphinx build (dummy
builder for doctree-level assertions) via `build_shared_sphinx_result`,
which exercises full event wiring (directives register, transforms
run, domain data populates, warnings emit). None were marked, so
`pytest -m integration` and `-m "not integration"` selectors didn't
reflect the actual test shape.
what:
- Mark all 13 tests in test_sphinx_pytest_fixtures_doctree.py with
  @pytest.mark.integration: the 9 that call build_fixture_result
  inline (manual, dependency, warning/manual-option, autofixture,
  autofixtures_smoke, short_name, plugin RST, plugin MyST,
  autofixtures MyST, lint-level) plus the 4 that consume the
  module-scoped default_dummy_result / autofixtures_usage_result /
  myst_smoke_result fixtures.
- No behavioural change; markers only. `just test` still runs all
  944 tests; `pytest -m integration` now correctly includes these.
…ture muting

why: The `:deprecated:` option on the `py:fixture` directive appends
SAB.STATE_DEPRECATED ("gp-sphinx-badge--state-deprecated") to the
parent `desc` container so CSS can dim the entry. Before the aa664ff
rename the muting rule lived in pytest-fixtures' own CSS
(dl.py.fixture.spf-deprecated > dt). After the rename the
equivalent rule only exists in sphinx-autodoc-api-style's CSS at
the broader `dl.py.gp-sphinx-badge--state-deprecated > dt` scope.
That is fine inside the gp-sphinx bundle, but pytest-fixtures'
pyproject does not depend on sphinx-autodoc-api-style — users who
install pytest-fixtures standalone (or without the api-style
extension enabled) lose the visual signal silently.
what:
- Add a fixture-scoped rule to sphinx_autodoc_pytest_fixtures.css:
  `dl.py.fixture.gp-sphinx-badge--state-deprecated > dt { opacity: 0.7 }`.
  Co-exists harmlessly with api-style's broader rule (same opacity,
  narrower selector); only fires when pytest-fixtures' directive has
  flagged the container.
- Inline comment records the coupling with api-style and the
  standalone-install rationale.
- No change to extension code; the SAB.STATE_DEPRECATED application
  in _directives.py already drives this selector.
why: The aa664ff CSS namespace rename turned SAB.UNDERLINE into a
silent alias for SAB.UNDERLINE_SOLID — both constants now hold
"gp-sphinx-badge--underline-solid". Before the rename UNDERLINE
held the distinct "sab-badge--underline" class (plain underline),
but that CSS rule and its distinct value were dropped in the
rename. The aliased constant has zero call sites (confirmed via
`git grep 'SAB\.UNDERLINE\b'` — only UNDERLINE_SOLID,
UNDERLINE_DOTTED, and NO_UNDERLINE are referenced, including the
`sab_demo.py` documentation label at line 270). Keeping the alias
would silently mislead any future caller who picked it by name
expecting plain underline.
what:
- Remove the redundant `UNDERLINE = "gp-sphinx-badge--underline-solid"`
  line from the SAB container; the underline axis now exposes only
  the three distinct values NO_UNDERLINE, UNDERLINE_DOTTED,
  UNDERLINE_SOLID.
- No caller or CSS change required.
why: The per-package smoke job in tests.yml matrices across every
publishable workspace package so each wheel installs cleanly and
sphinx-build succeeds against its docs. The matrix was missing the
four packages added or renamed most recently — sphinx-autodoc-fastmcp,
sphinx-autodoc-typehints-gp, sphinx-ux-badges, and
sphinx-ux-autodoc-layout — so CI was silently skipping their install
and smoke verification on every push.
what:
- Add the four missing package slugs to the smoke matrix, completing
  coverage for every entry in packages/.
- No job logic change; smoke job reuses the existing install + docs
  build step sequence.
… srcs

why: AGENTS.md requires every function and method to have working
doctests (no # doctest: +SKIP, no code-block workarounds). That
rule was only being enforced for six of the eleven workspace
packages because testpaths omitted the others. Adding them turns
the pytest run into the authoritative doctest gate for the full
workspace — currently lifting the suite from 944 to 1099 collected
items with no failures.
what:
- Add packages/sphinx-autodoc-argparse/src, autodoc-docutils/src,
  autodoc-pytest-fixtures/src, autodoc-typehints-gp/src,
  ux-autodoc-layout/src, ux-badges/src to [tool.pytest.ini_options]
  testpaths.
- Reorder the list alphabetically within the sphinx-autodoc-* /
  sphinx-ux-* groups so future additions land in an obvious spot.
…e install args

why: smoke_sphinx_autodoc_typehints_gp passed
f"sphinx {dist_dir}/sphinx_autodoc_typehints_gp-{version}-py3-none-any.whl"
as a single positional to _install_into_venv. That helper is
variadic (*requirements) and forwards each arg as its own token
to uv pip install, so the concatenated string became one
requirement with a literal space in the name — uv would reject it
as a syntactically invalid requirement. Every other smoke_* in this
module already uses the _target_wheel_path() helper; this one was
an oversight.
what:
- Replace the f-string with two positional arguments: "sphinx" and
  _target_wheel_path(dist_dir, "sphinx-autodoc-typehints-gp").
- Matches the idiom used by the other smoke_* functions in the
  same file.
…loor

why: Record the breaking and feature-level shifts introduced by the
autodoc-improvements branch so downstream alpha-testers can
understand what changed before the next 0.0.1-series release cut.
Kept terse — the package is still pre-1.0 and entries describe the
net shipped result, not per-commit internals.
what:
- Add a new "Breaking changes" section covering the five package
  renames, the Sphinx 8.1 floor bump, and the gp-sphinx-* CSS
  namespace unification.
- Append three bullets to the existing "Features" section: the
  three-tier design system restructuring, the new
  sphinx-ux-autodoc-layout package, and the new
  sphinx-autodoc-typehints-gp package.
why: The aa664ff namespace-unification commit retired six opaque
per-package CSS prefixes (sab-, smf-, spf-, api-, gas-, gal-) in
favour of a single gp-sphinx-* two-tier BEM root. Writing the
convention into AGENTS.md prevents the next package from inventing
another prefix and pays forward the time spent on this refactor.
what:
- Add a new "CSS Standards" section to AGENTS.md between "Testing
  Strategy" and "Coding Standards" listing the two-tier BEM rule,
  the modifier axis pattern, the custom-property namespace mirror,
  and the chained-selector 0,3,0 specificity convention.
- Names Furo-owned variables as explicitly out of scope so the rule
  does not overreach.
…ests

why: The original wording ("Must verify actual HTML output or Sphinx
event wiring") left room to argue that buildername="dummy" tests
aren't really integration. During this branch's review, 13 such
tests were authored without @pytest.mark.integration because
"dummy" sounded lightweight enough to skip the marker. Tightening
the rule closes the loophole — if the test instantiates a Sphinx
app, it's integration, regardless of builder.
what:
- Rewrite the Sphinx-integration row of the Test Level Hierarchy
  table in AGENTS.md to name build_shared_sphinx_result /
  build_isolated_sphinx_result explicitly and call out the
  dummy-builder case so future authors don't have to re-litigate
  it.
why: The STATE_DEPRECATED fixture-muting regression on this branch
was a stealth cross-package CSS dep — sphinx-autodoc-pytest-fixtures
emitted gp-sphinx-badge--state-deprecated on fixture containers, but
the muting rule only existed in sphinx-autodoc-api-style's CSS.
Standalone installs of pytest-fixtures lost the visual signal
silently. Writing the rule down prevents the next cross-package
selector from slipping in unnoticed.
what:
- Add a "Package CSS self-containment" subsection under Code
  Architecture. States the principle (each package's CSS styles
  every class that package's Python emits) and draws the
  reuse-vs-dependence distinction so shared classes like
  gp-sphinx-badge stay legitimate while sibling-package CSS
  dependencies do not.
…project

why: twine check on the built wheel and sdist emitted
"long_description_content_type missing" and "long_description
missing" warnings — the only workspace package doing so. The
release.yml pipeline runs `twine check dist/*` after building every
publishable package; a CI step that escalates warnings to failures
(or a downstream tool that does) would block the next tag push.
Every sibling pyproject declares `readme = "README.md"` and
`authors = [...]`; this one was the outlier.
what:
- Add `readme = "README.md"` to [project] (README.md already exists
  alongside pyproject.toml, so the reference resolves without any
  file move).
- Add `authors = [{name = "Tony Narlock", email = "tony@git-pull.com"}]`
  to match the sibling pattern.
- Verified by rebuilding the package and re-running twine check;
  output now reads PASSED (previously PASSED with warnings).
why: pyproject.toml already declares `Typing :: Typed` in classifiers,
but the PEP 561 marker file was never created — so downstream mypy /
pyright runs skipped the package's inline annotations and treated
imported symbols as Any. The classifier became aspirational without
the file.  Every other workspace package ships py.typed alongside
its source; this was the outlier.
what:
- Add an empty py.typed alongside src/sphinx_autodoc_typehints_gp/
  __init__.py. hatchling's wheel.packages setting includes it
  automatically (verified by unzipping the rebuilt wheel and
  confirming sphinx_autodoc_typehints_gp/py.typed is present).
- No code or pyproject changes needed; the classifier is now
  truthful.
why: sphinx-autodoc-argparse, sphinx-fonts, and sphinx-gp-theme
carried `Development Status :: 4 - Beta` while the remaining nine
workspace packages were at `3 - Alpha`. The whole workspace
publishes lockstep at 0.0.1a7 — a pre-release alpha version — so
the three Beta outliers misrepresent their actual readiness to
downstream consumers browsing PyPI.  Since we release-gate all
twelve packages together, their classifier story should line up.
what:
- Change `Development Status :: 4 - Beta` → `3 - Alpha` in the
  three outlier pyproject.toml files.
- No functional or metadata-otherwise change; twine check still
  passes and the lockstep version gate is untouched.
…olding

why: The existing std:cmdoption wiring gives us `:option:` xrefs but
no argparse-specific namespace, no program-scoped option keys, no
per-domain indices, and no `:argparse:subcommand:` / `:argparse:positional:`
roles. Sphinx's Domain API exists for exactly this case — a
cohesive vocabulary for a class of documentation objects.  This
commit lays down the scaffolding: domain class, four ObjTypes
(program / option / subcommand / positional), four XRefRoles, two
auto-generated indices (programs, options), and parallel-safe env
lifecycle hooks.  Nothing is wired into setup() yet — deliberately,
so this change cannot alter any build and remains reviewable.
what:
- Add packages/sphinx-autodoc-argparse/src/sphinx_autodoc_argparse/
  domain.py with:
    * PROGRAM / OPTION / SUBCOMMAND / POSITIONAL module-level name
      constants and an OBJECT_TYPES tuple for iteration.
    * ArgparseProgramsIndex (alphabetical) and ArgparseOptionsIndex
      (grouped by program) Index subclasses.
    * ArgparseDomain with name="argparse", label="Argparse CLI",
      data_version=0, initial_data={programs, options, subcommands,
      positionals}, and typed property accessors for each table.
    * note_program / note_option / note_subcommand / note_positional
      helpers for renderer use.
    * clear_doc / merge_domaindata / resolve_xref / resolve_any_xref /
      get_objects implementing the parallel-safe env contract modelled
      on sphinx.domains.rst.ReSTDomain.
    * _lookup private helper supporting both "program name" whitespace-
      joined and bare-name target forms.
    * Module-level docstring + class/function-level doctests verifying
      name / label / data_version / object-type registration.
- No change to setup(), renderer.py, or any other file.  1103 tests
  pass (baseline 1099 + 4 new doctests from the scaffolding).
why: E1 added the ArgparseDomain scaffolding but did not register it.
Wire it now, before add_directive("argparse", ...), so every Sphinx
build instantiates the domain, makes the :argparse:program: /
:argparse:option: / :argparse:subcommand: / :argparse:positional:
roles discoverable, and exposes the two auto-generated index pages
(argparse-programsindex, argparse-optionsindex). No object is
registered yet, so xrefs resolve to None and indices render empty —
E3 adds the dual-emit hooks that actually populate the domain.
what:
- Import ArgparseDomain in the package __init__.
- Call app.add_domain(ArgparseDomain) at the top of the
  registration block in setup(), with a comment pointing to the
  dual-emit relationship with std:cmdoption.
- 1103 tests still pass — no behavioural change because the domain
  tables are empty at build time.
…ubcommands, positionals

why: Now that E1 defined ArgparseDomain and E2 registered it, the
renderer needs to populate it. Continue emitting to std:cmdoption
(so downstream :option: / intersphinx keeps working) AND record
every program, option, subcommand, and positional with the new
domain so :argparse:* xrefs resolve and the two indices render
real content.  Positionals and options are distinguished by name
prefix: names starting with "-" are options, anything else is a
positional (standard argparse convention).
what:
- Rename _register_option -> _register_argument and rewrite to
  dual-emit: std_domain.add_program_option (existing) + either
  argparse_domain.note_option or note_positional based on the
  leading-dash test.
- Add _register_program helper; call it from render() when a
  program name is present. Anchor slug is `argparse-<prog-slug>`
  to match the existing argparse_program HTML visitor convention.
- Add _register_subcommand helper; call it from render_subcommand()
  for every nested subcommand under a parent_prog.  Anchor slug is
  `argparse-<parent-prog-slug>-<subcmd>`.
- Update the in-renderer call site (render_argument) to use the
  renamed _register_argument.
- Add doctest examples for all three new helpers showing the no-op
  path when env is absent.
- 1105 tests pass (was 1103 + 2 new doctests). Snapshot .ambr files
  unchanged — the domain writes metadata only, not rendered HTML.
…dices, xref resolution

why: The ArgparseDomain scaffolding (E1), wiring (E2), and renderer
dual-emit (E3) landed without explicit test coverage.  Lock in the
contract now so future refactors get early signal when something
regresses — and demonstrate end-to-end resolution once a real
Sphinx build runs the full .. argparse:: pipeline.

As a side effect, the existing test_option_xrefs_resolve_without_warnings
filter in test_domain_integration.py had to be narrowed: the earlier
"option" substring match false-matched the `desc_optional` node-class
re-registration noise that Sphinx emits when another test runs a
Sphinx build earlier in the same process.  The new filter targets
actual xref-resolution failure strings.
what:
- Add tests/ext/autodoc_argparse/test_domain.py with 18 cases across
  three groups:
    * Unit tests (note_program / note_option / note_subcommand /
      note_positional, clear_doc, merge_domaindata including the
      "ignore entries outside docnames" path, get_objects emission).
    * Lookup tests (_lookup on program, whitespace-joined option
      target, bare option fallback, unknown objtype).
    * Index tests (ArgparseProgramsIndex alphabetisation,
      ArgparseOptionsIndex grouping by program, docnames filter).
    * Two @pytest.mark.integration tests that build a synthetic
      Sphinx project via the .. argparse:: directive and assert the
      domain's data dicts are populated and resolve_xref returns a
      real refnode.
- Narrow the warning filter in
  test_domain_integration.py::test_option_xrefs_resolve_without_warnings
  to "undefined label" / "unknown option" /
  "reference target not found", documenting the change inline so the
  choice of substrings is explicit.
- All existing tests pass; new count 1123 (+18).
…:: Domain

why: E1-E4 landed a real ArgparseDomain subclass with four ObjTypes,
four XRefRoles, two auto-generated indices, and parallel-safe env
handling.  The classifier is now truthful: PyPI users searching
for `Framework :: Sphinx :: Domain` will find a package that does
ship an actual domain, not just a domain-themed extension.
what:
- Add `Framework :: Sphinx :: Domain` to the classifier list in
  packages/sphinx-autodoc-argparse/pyproject.toml, between the
  bare `Framework :: Sphinx` and `Framework :: Sphinx :: Extension`
  rows.
- Rebuild the wheel + sdist and confirm twine check PASSES cleanly
  (same as before; the classifier is a known Trove entry).
…s tier

why: With E1-E5 landed, sphinx-autodoc-argparse now ships a real
Sphinx domain (ArgparseDomain) — programs, options, subcommands,
positionals, two auto-generated indices, full resolve_xref
coverage. Its home on the tier map is now tier 2 alongside the
other domain extensions, not tier 3 with the theme and fonts.
Update the tier narrative on both the architecture and packages
index pages so the sidebar ordering the reader encounters matches
the package's actual role.
what:
- docs/packages/index.md: rename "Autodoc domain packages" to
  "Domain packages", add sphinx-autodoc-argparse to its bullet
  (alphabetically ordered), and rename the tier-3 bullet from
  "Theme, fonts, and CLI" to "Theme and coordinator" with argparse
  removed.
- docs/architecture.md: add a sphinx-autodoc-argparse row to the
  Tier 2 table with its domain column noting the custom
  `argparse` domain and its ObjType vocabulary.  Clarify the
  sphinx-autodoc-pytest-fixtures row as "extends `py` domain".
  Rename Tier 3 heading to "Theme and coordinator" and drop the
  argparse row.  Update the closing sentence from "five domain
  packages" to "six".
… indices

why: The argparse domain is live (E1-E5) and sits in Tier 2 (E6),
but the package page has no user-facing documentation on how to
use the new xref roles or the two auto-generated indices.  Add a
"Cross-reference roles" section covering the full role vocabulary,
the target-syntax rules (whitespace-joined vs bare), and the
intersphinx-compat contract — so readers know `:argparse:option:`
is now the idiomatic form without losing `:option:` access for
intersphinx consumers.
what:
- Add a "Cross-reference roles" section to
  docs/packages/sphinx-autodoc-argparse.md after "Inline roles"
  with:
    * A four-row table listing :argparse:program: /
      :argparse:option: / :argparse:subcommand: /
      :argparse:positional: with concrete example targets.
    * A short paragraph on target-syntax rules (whitespace-join
      splits into a (program, name) tuple key; bare form resolves
      when there's one match).
    * "Auto-generated indices" subsection naming
      argparse-programsindex and argparse-optionsindex with
      :ref: link examples.
    * "Intersphinx compatibility" note clarifying that
      :option: / std:cmdoption continues to resolve and appear in
      objects.inv for external consumers.
…Fonts/CLI to UX

why: The Furo sidebar is driven by three hidden toctree blocks in
docs/index.md, not by the narrative prose in docs/packages/index.md
or docs/architecture.md.  The previous E6 commit updated the
narrative but not the sidebar, so readers still saw
sphinx-autodoc-argparse listed under "Theme, Fonts & CLI" in every
package page.  Bring the sidebar in line with the architectural
reclassification, and shorten the third-tier caption from
"Theme, Fonts & CLI" to the cleaner "UX" now that argparse has
moved out and only gp-sphinx / sphinx-gp-theme / sphinx-fonts
remain.
what:
- docs/index.md: insert packages/sphinx-autodoc-argparse in the
  "Domain Packages" toctree between api-style and docutils
  (alphabetical order within the tier).
- docs/index.md: rename the third toctree caption from
  "Theme, Fonts & CLI" to "UX"; remove
  packages/sphinx-autodoc-argparse from it.
- Verified by clean-building docs and inspecting the rendered
  sidebar headings (Shared Infrastructure / Domain Packages / UX).
why: "Shared Infrastructure" bundled three heterogenous packages
under one caption — sphinx-ux-badges and sphinx-ux-autodoc-layout
are visual/styling primitives that naturally belong alongside
sphinx-fonts, while sphinx-autodoc-typehints-gp is a type-rendering
utility that doesn't fit either.  The single generic caption made
it hard for a reader browsing the sidebar to reason about what each
package is for at a glance.  Split into four purpose-named
captions so the navigation matches the conceptual categories.
what:
- docs/index.md: replace the three hidden toctrees (Shared
  Infrastructure / Domain Packages / UX) with four captioned
  toctrees:
    * Domain Packages — the six autodoc-* extensions (unchanged).
    * UX — sphinx-fonts, sphinx-ux-autodoc-layout, sphinx-ux-badges
      (the three visual/styling primitives).
    * Utils — sphinx-autodoc-typehints-gp (type-rendering utility
      that replaces sphinx-autodoc-typehints + sphinx.ext.napoleon).
    * Internal — gp-sphinx (metapackage / coordinator) and
      sphinx-gp-theme (furo sub-theme), the two packages readers
      don't install on their own.
- Alphabetise entries within each toctree for predictability.
- No narrative (docs/architecture.md, docs/packages/index.md)
  changes in this commit; the architecture tiers describe
  dependency layers and remain three-tier, while the sidebar
  captions describe navigation buckets.
why: The README's "what you get" bullets advertised five domain
autodocumenters and listed their targets (Python API, pytest
fixtures, FastMCP tools, docutils directives, Sphinx config
values) — missing argparse CLIs, which is now a sixth domain
package with its own Sphinx Domain subclass and :argparse:*
xref roles.  Keeping the README aligned with the shipped reality
prevents a reader from skipping sphinx-autodoc-argparse when they
scan the bullet list to decide what the workspace covers.
what:
- Change "Five domain autodocumenters" -> "Six" on line 53 of
  README.md and insert "argparse CLIs" between "Python API" and
  "pytest fixtures" in the accompanying list.
- No other README change; the three-tier architecture heading
  below stays — sidebar navigation (Domain / UX / Utils /
  Internal) and dependency tiers (three) describe different axes.
why: The What's New page counted seven advancements and the
"Shared layout stack" section listed five domain packages.  The
argparse domain that landed in the recent seven-commit series (a
real Sphinx Domain subclass with :argparse:* roles and two
auto-generated indices) is a peer advancement that the page did
not yet describe.  Update the counts and introduce the new section
so readers arriving via the sidebar's "What's new" card see the
whole picture.
what:
- Opening line: "seven major advancements" -> "eight".
- "Shared layout stack" section: "The five domain packages" -> "The
  six domain packages"; add argparse to the {doc} list,
  alphabetically between api-style and docutils.
- Insert a new "## argparse Sphinx domain" section between
  "Shared layout stack" and "Three-tier package organization".
  Describes the Domain subclass, the four :argparse:* xref roles,
  the two auto-generated indices (argparse-programsindex,
  argparse-optionsindex), and the intersphinx compatibility
  contract with :option: / std:cmdoption.
why: The 0.0.1 (unreleased) Features section documented the two
new foundation packages (sphinx-ux-autodoc-layout,
sphinx-autodoc-typehints-gp) and the three-tier restructuring,
but not the argparse Sphinx domain added in the recent
E1-E7 commit series.  Without a changelog entry, downstream
alpha-testers reading CHANGES wouldn't know about the new
:argparse:* xref roles or the two auto-generated indices.
what:
- Append one bullet to the ### Features section of the
  0.0.1 (unreleased) entry describing the domain: ObjTypes,
  xref roles, indices, the new classifier, and the
  std:cmdoption compatibility guarantee.
why: The sidebar now uses four navigation buckets (Domain
Packages, UX, Utils, Internal) while the architecture page
continues to describe dependency layers in three tiers.  A reader
looking at both could reasonably wonder whether the discrepancy
is a bug.  Call out the two-axis framing explicitly so the
choice reads as intentional: sidebar = reader-facing grouping;
tier map = dependency ordering.
what:
- Append one sentence after the opening paragraph of
  docs/architecture.md clarifying that the four sidebar buckets
  are orthogonal to the three dependency tiers documented below.
- No other content change; the three-tier structure stays as-is.
@tony tony force-pushed the autodoc-improvements branch from a11867c to 693130e Compare April 12, 2026 19:01
@tony tony merged commit 1278de6 into main Apr 12, 2026
40 checks passed
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