Skip to content

📝 docs: reorganise the docs and add the missing tutorials - #909

Merged
nstarman merged 31 commits into
GalacticDynamics:mainfrom
nstarman:claude/diataxis-unxt-docs-d0b47c
Aug 25, 2026
Merged

📝 docs: reorganise the docs and add the missing tutorials#909
nstarman merged 31 commits into
GalacticDynamics:mainfrom
nstarman:claude/diataxis-unxt-docs-d0b47c

Conversation

@nstarman

@nstarman nstarman commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Reorganises the documentation — the core docs/ tree and all seven unxts.* package doc sets — into tutorials, how-to guides, reference and explanation, and adds the tutorials that were missing. 17 commits, each independently reviewable.

Why

Everything practitioner-facing lived either on index.md or in a guides/ bucket that mixed instruction, description and rationale in one page. guides/quantity.md was reference, how-to and explanation at once; index.md was a landing page, an install guide, a quickstart tour and an ecosystem page fused together. There was no tutorial anywhere — every entry path assumed the reader already knew why they wanted unitful arrays.

The package doc sets had the same problem in the same place. In unxts.hypothesis and unxts.interop.xarray the landing page had grown longer than the guide it linked to and duplicated it section by section. In unxts.api the rationale for the whole design was inside an HTML comment.

Core docs/ — 22 pages → 37, nothing deleted

index.md              router
tutorials/            first-quantity, mars-lander, dimensional-analysis
how-to/               13 guides
reference/            quantity, units, unitsystems, dimensions,
                      configuration, dataclassish, glossary, api/
explanation/          sharp-bits, why-quantity-is-non-parametric,
                      equality-and-equivalence, api-conventions
about/                contributing, citation + ecosystem

The rationale for the non-parametric default previously existed in migration.md, a guides/quantity.md admonition, README.md and unxts.parametric/docs/index.md — four copies of one argument. It now has one home.

Package doc sets — 27 pages → 38

Each kept a flat tree, with the page's kind carried by its title and a grouped index, rather than growing four subfolders: they are 2–6 pages each and already sit one level down in the nav.

Tutorials

There were none. There are now twelve — five core, one per package — each executed end to end, with every output block real output from that run, and each running in the test suite.

Where Tutorial The lesson
core Your first calculation with units Build a projectile calculator, then jit, differentiate and vectorise it. The gradient comes back in secondsm / (m/s) — without being told.
core Land on Mars in your own units Build a mission unit system, fly a descent in it, then reproduce the Mars Climate Orbiter's lbf·s/N·s mistake and hit the ground at 139 m/s.
core Check a formula before you run it Dimensional analysis with no numbers: catch a wrong formula, then derive the pendulum period's form from (length/acceleration)**0.5 being a time.
core Write a function others can call Designing a unitful interface: a bare-float version returns 0.0 for a caller working in solar masses, and even with quantities a wrong-dimension argument is not rejected — it returns an 'unknown' dimension that only errors later, somewhere else.
core Carry units through a whole analysis A rotation curve through xarrayunxtmatplotlib: the computed unit km2 kg kpc / m3 looks like nonsense until dimension_of says mass, and the plot labels itself $\mathrm{M_\odot}$.
unxts.parametric Let the type system catch a unit mistake Q["length"](1, "s") is silently accepted; PQ["length"](1, "s") refuses.
unxts.hypothesis Write your first property test State a property, then see what the silence covered.
unxts.linalg Build a heterogeneous metric The determinant's unit falls out as the product of the diagonal.
unxts.interop.xarray Turn unit labels into real units xarray adds km to hours without complaint and drops the label; quantify, and it raises.
unxts.interop.matplotlib Plot data that knows its units Axis labels write themselves, and data in minutes lands correctly on an axis already in hours.
unxts.interop.gala Compute in galactic units usys["velocity"] composes kpc / Myr from base units nobody supplied.
unxts.api Teach unxt about your own type A generic function accepts both a Quantity and your class, unchanged.

register_ufunc and is_unit_convertible are public but appeared only in generated autodoc; both are now in the reference.

Bugs found and fixed along the way

Where Was Now
guides/type-checking.md "UNXT_ENABLE_RUNTIME_TYPECHECKING=None disables checking, and is the default" "False" is the default and disables; "None" installs the hook with no typechecker (setup_package.py:19). Two pages also contradicted each other on this.
CONTRIBUTING.md github.com/unxt/unxt, and an Astropy dev-workflow link on docs.unxt.org GalacticDynamics/unxt, docs.astropy.org
CONTRIBUTING.md uv run --group test nox -s test uv run nox -s test — the session declares its own group
reference/quantity.md (inherited from the old guide) StaticQuantity "accepts Python scalars and NumPy arrays only, rejecting JAX arrays" a concrete JAX array is accepted and materialised back to NumPy; only a traced value is rejected
unxts.interop.gala/docs/guide.md a round trip "yields an equivalent unit system" it does not — back == gu.galactic is False. gala registers a preferred unit for the derived velocity dimension (km/s); an unxt system holds only base units and composes the rest, so the preference is dropped and cannot be reconstructed.

Also updated for upstream changes

Quantity.__array__ now refuses rather than stripping units (#892) — a core sharp bit that had no home in the docs. QuantityMatrix gained unit_of/ustrip and scalar-unit uconvert (#880, #902). UnxtConverter.unit_format replaced the axisinfo_kw dict (#862). None shipped with documentation; all three are now described.

AGENTS.md and skills/unxt/SKILL.md pointed at doc paths that moved, and are fixed.

Redirects

sphinxext-rediraffe was already a docs dependency but commented out of conf.py. Now enabled, with 22 old URLs mapped; every stub and every target was checked against the built HTML.

Verification

  • nox -s docs succeeds. sphinx-build -n --keep-going208 warnings, zero structural: 206 pre-existing py: autodoc nitpicks from docstrings this PR never touches, 2 sphinx-tippy network flakes.
  • pytest docs/1140 examples, all passing. The exact CI invocation (pytest README.md docs src/ tests/ --benchmark-disable -n logical --dist=loadfile) → 4271 passed, 0 failed. Whole project suite: 3845 passed, 0 failed.
  • Core examples went 600 → 493 at the reorganisation, then up to 1026 with the new pages. The initial drop was deduplication, checked by diffing the API tokens exercised in executed blocks before and after rather than assumed; 88 of 107 came from index.md's quickstart re-testing what the reference pages test.

Known gaps

  • No error reference. unxt has ~108 raise sites, 46 with custom messages, but most of what users actually hit surfaces as astropy's UnitConversionError. The messages people do paste into a search engine are already documented on the pages that provoke them, so the gap is findability rather than absence — a hand-written page keyed by message text would be a large artifact with exactly the drift problem noted below. Not attempted here; worth a decision rather than a default.
  • Tutorials are per-topic, not per-audience. There is no "you are an astronomer / you are an ML engineer" entry path. Probably right at this size, but worth revisiting if the tutorial count grows.

Closed since this PR was opened:

  • Nothing teaches how to design a unit-aware function for others to call. Now tutorials/design-a-unitful-api.md.
  • unxts.hypothesis/strategies.md is long and ungrouped. Now an orienting table plus two groups. (The "736 words" figure in the original list was stale; it is 1505.)
  • unxts.api/api.md hand-maintains "Example Implementations" lists that will drift. The page now points at f.methods for the authoritative set. The lists stay — they are labelled examples and carry prose a runtime query cannot.
  • Derived units of a DynamicalSimUSysFlag system cannot be printed. Fixed upstream in 🐛 fix(unitsystems): derived units of scaled systems can be printed #910; usys["velocity"] now returns Unit("2.07387 m / s") and the how-to's warning is gone.

A note on the CI failure this branch hit

Four jobs failed on unxts.interop.matplotlib/guide.md with ConversionError: Failed to convert value(s) to axis units, and the rest passed. The cause was
shared global state rather than anything in the guide: it and the package index
drew with a bare plt.plot(x, y), which targets matplotlib's current axes.
Sybil runs every doc file in one process, so whichever file ran first could leave
a figure current — and when that figure's x-axis carried kpc, from the
ecosystem tutorial added here, plotting deg onto it raised. Under
-n logical --dist=loadfile the file-to-worker assignment varies with the
runner's core count, which is why it failed on some jobs and not others.

Fixed at the root: those pages now create their own fig, ax, so no page depends
on what ran before it, and the pages that make figures close them.

Copilot AI lite review requested due to automatic review settings August 24, 2026 13:56
@nstarman nstarman changed the title 📝 docs: restructure the docs into Diátaxis modes, and write the missing tutorials 📝 docs: restructure the core and package docs into Diátaxis modes Aug 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request restructures unxt’s documentation (core docs/ plus the unxts.* package doc sets) to follow the Diátaxis information architecture, adds new tutorial content, and updates the docs build tooling + redirects so existing URLs continue to work.

Changes:

  • Reorganized core docs into tutorials/, how-to/, reference/, explanation/ (nav “Discussion”), plus about/, including a new end-to-end beginner tutorial.
  • Restructured each unxts.* package doc set to be mode-labeled while remaining a flat tree, reducing duplicated content and clarifying entry pages.
  • Updated docs infrastructure: enabled sphinxext-rediraffe redirects, moved generated API docs under reference/api/, and adjusted nox + .gitignore for the relocated jupytext notebook.

Reviewed changes

Copilot reviewed 74 out of 81 changed files in this pull request and generated no comments.

Show a summary per file
File Description
README.md Updates documentation deep links to new Diátaxis locations.
packages/unxts.parametric/docs/type-checking.md Retitles/reframes runtime dimension-checking how-to and repoints core links.
packages/unxts.parametric/docs/sharp-bits.md Retitles and repoints sharp-bits link into core discussion docs.
packages/unxts.parametric/docs/quantity.md Expands ParametricQuantity reference and integrates former “dimensions” content.
packages/unxts.parametric/docs/index.md Refactors landing page into mode-grouped router; removes duplicated material.
packages/unxts.parametric/docs/dimensions.md Removes page (content merged into quantity.md).
packages/unxts.parametric/docs/configuration.md Updates core configuration link to new reference location.
packages/unxts.linalg/docs/units-matrix.md Retitles to object-centric page name (UnitsMatrix).
packages/unxts.linalg/docs/tutorial-metric.md Reworks tutorial into a true end-to-end lesson flow.
packages/unxts.linalg/docs/sharp-bits.md Retitles/expands restrictions discussion and adds inv heterogeneity example.
packages/unxts.linalg/docs/quantity-matrix.md Retitles to object-centric page name (QuantityMatrix).
packages/unxts.linalg/docs/linear-algebra.md Retitles and clarifies operations reference page.
packages/unxts.linalg/docs/index.md Reorders toctree and converts landing page to mode-grouped router.
packages/unxts.interop.xarray/docs/xarray-guide.md Retitles as a how-to; moves API + limitations into dedicated pages.
packages/unxts.interop.xarray/docs/sharp-bits.md New “sharp bits” discussion page for xarray integration limitations.
packages/unxts.interop.xarray/docs/index.md Refactors landing page into mode-grouped router with clearer at-a-glance example.
packages/unxts.interop.xarray/docs/api.md New API reference page documenting accessor + lower-level functions.
packages/unxts.interop.matplotlib/docs/index.md Refactors landing page into mode-grouped router with updated headings.
packages/unxts.interop.matplotlib/docs/guide.md Retitles guide as a how-to and normalizes “See also”/API link text.
packages/unxts.interop.matplotlib/docs/api.md Normalizes API page title.
packages/unxts.interop.gala/docs/index.md Refactors landing page into mode-grouped router and updates deep links.
packages/unxts.interop.gala/docs/guide.md Retitles guide as a how-to; updates “See also” links.
packages/unxts.interop.gala/docs/api.md Normalizes API page title and updates deep links.
packages/unxts.hypothesis/docs/testing-guide.md Retitles as a how-to and expands “See also” navigation.
packages/unxts.hypothesis/docs/strategies.md New strategies reference page extracted from index content.
packages/unxts.hypothesis/docs/recipes.md New how-to page for composing hypothesis strategies.
packages/unxts.api/docs/why-abstract-dispatch.md New discussion page explaining the abstract dispatch split.
packages/unxts.api/docs/index.md Refactors landing page into mode-grouped router; adds high-level “at a glance”.
packages/unxts.api/docs/extending.md Retitles and improves structure for how to extend via dispatch.
packages/unxts.api/docs/api.md Normalizes API page title and adds intro framing.
noxfile.py Updates docs build (jupytext path) and API doc generation output directory.
docs/tutorials/index.md New tutorials section landing page.
docs/tutorials/first-quantity.md New beginner tutorial with executed, end-to-end unitful JAX workflow.
docs/reference/unitsystems.md New unit systems reference page (replacing mixed-mode guide content).
docs/reference/units.md New units reference page (replacing mixed-mode guide content).
docs/reference/quantity.md New quantity reference page consolidating class/method surface.
docs/reference/index.md New reference section landing page and routing.
docs/reference/glossary.md Moves glossary under reference and fixes intra-doc links.
docs/reference/dimensions.md Updates parametric-quantity link target and “See also”/API role usage.
docs/reference/dataclassish.md Retitles/reframes dataclassish behavior as reference; adds see-also links.
docs/reference/configuration.md New configuration reference (options, defaults, and file keys).
docs/reference/api/unitsystems.md Moves API stub under reference/api/.
docs/reference/api/units.md Moves API stub under reference/api/.
docs/reference/api/quantity.md Moves API stub under reference/api/ and keeps exclude-members list.
docs/reference/api/index.md New API index under reference, with submodule toctree.
docs/reference/api/experimental.md Moves API stub under reference/api/.
docs/reference/api/dims.md Moves API stub under reference/api/.
docs/interop/astropy.md Removes legacy interop page (content moved to new how-to).
docs/how-to/work-in-natural-units.md Retitles as how-to, adds pointer to new unit-systems reference.
docs/how-to/use-jax-functions.md New how-to consolidating quax/quaxed/JAX transform usage patterns.
docs/how-to/optimize-performance.md Retitles/moves performance notebook source to how-to section.
docs/how-to/migrate-to-v2.md Retitles as how-to and links to new discussion rationale page.
docs/how-to/interoperate-with-astropy.md New how-to replacing old interop page and documenting jit pitfalls.
docs/how-to/install.md New installation how-to.
docs/how-to/index.md New how-to section landing page with curated guide grouping.
docs/how-to/define-a-unit-system.md New how-to for defining custom unit system subclasses.
docs/how-to/convert-units.md New how-to for uconvert/ustrip/uconvert_value.
docs/how-to/control-display.md New how-to for display configuration usage patterns.
docs/how-to/check-types-at-runtime.md New how-to clarifying runtime type-checking defaults and usage.
docs/guides/units_and_systems.md Removes mixed-mode legacy guide (replaced by reference/how-to pages).
docs/guides/type-checking.md Removes mixed-mode legacy guide (replaced by how-to).
docs/guides/configuration.md Removes mixed-mode legacy guide (replaced by reference/how-to).
docs/explanation/why-quantity-is-non-parametric.md New discussion page consolidating rationale behind v2 default.
docs/explanation/sharp-bits.md New discussion page for inherent JAX/units sharp edges.
docs/explanation/index.md New discussion section landing page.
docs/explanation/equality-and-equivalence.md New discussion page distinguishing == vs equivalent.
docs/explanation/api-conventions.md Retitles and reframes conventions as discussion; adds see-also links.
docs/dev.md Removes legacy developer page (merged into about/contributing).
docs/contributing.md Removes legacy contributing page (replaced by include-based about page).
docs/conf.py Enables rediraffe, adds redirect map, and updates exclude patterns/source suffixes.
docs/about/index.md New about section landing page (citation + ecosystem + navigation).
docs/about/contributing.md New about page including root CONTRIBUTING plus docs-build + Diátaxis guidance.
CONTRIBUTING.md Fixes repo URL + dev workflow link + updates test command; adds Diátaxis pointer.
audit/summary.md Adds audit summary of restructure decisions, verification, and remaining gaps.
audit/package-migration-map.md Adds package-doc-set migration map and rationale for flat structure.
audit/migration-map.md Adds core-doc migration plan/map (as documentation/audit artifact).
.gitignore Updates ignored jupytext-generated notebook path to new location.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@nstarman nstarman changed the title 📝 docs: restructure the core and package docs into Diátaxis modes 📝 docs: restructure the docs into Diátaxis modes and write the missing tutorials Aug 24, 2026
@nstarman nstarman added this to the v2.1.0 milestone Aug 24, 2026
@nstarman
nstarman force-pushed the claude/diataxis-unxt-docs-d0b47c branch from 163c980 to d639e40 Compare August 24, 2026 14:32
@github-actions github-actions Bot added 📝 Add / update documentation Add or update documentation. 🙈 Add / update .gitignore Add or update a .gitignore file. 🔧 Add / update configuration Add or update configuration files. 🧩 unxts-hypothesis Issues/PRs affecting the unxts.hypothesis namespace package 🧩 unxts-interop-gala Issues/PRs affecting the unxts.interop.gala namespace package 🧩 unxts-interop-matplotlib Issues/PRs affecting the unxts.interop.matplotlib namespace package 🧩 unxts-interop-xarray Issues/PRs affecting the unxts.interop.xarray namespace package 🧩 unxts-api Issues/PRs affecting the unxts.api namespace package 🧩 unxts-parametric Issues/PRs affecting the unxts.parametric namespace package 🧩 unxts-linalg Issues/PRs affecting the unxts.linalg namespace package labels Aug 24, 2026
@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.80%. Comparing base (62da772) to head (3ed9ff2).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #909      +/-   ##
==========================================
- Coverage   99.80%   99.80%   -0.01%     
==========================================
  Files          84       82       -2     
  Lines        4019     4013       -6     
  Branches      314      314              
==========================================
- Hits         4011     4005       -6     
  Misses          4        4              
  Partials        4        4              

☔ View full report in Codecov by Harness.
📢 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.

@nstarman
nstarman force-pushed the claude/diataxis-unxt-docs-d0b47c branch from 8f34674 to ef957fd Compare August 24, 2026 15:28
@nstarman nstarman changed the title 📝 docs: restructure the docs into Diátaxis modes and write the missing tutorials 📝 docs: reorganise the docs and add the missing tutorials Aug 24, 2026
nstarman and others added 10 commits August 24, 2026 11:59
Everything practitioner-facing lived either on `index.md` or in a `guides/`
bucket that mixed instruction, description and rationale in the same page:
`guides/quantity.md` was reference, how-to and explanation at once, and
`index.md` was a landing page, an install guide, a quickstart tour and an
ecosystem page fused together. There was no tutorial anywhere.

22 pages become 35; nothing is deleted.

- `index.md` becomes a router. Its JAX/JIT/autodiff sections become
  `how-to/use-jax-functions.md`, install becomes `how-to/install.md`, and
  citation and ecosystem move to `about/index.md`. Its Quantity, dimension and
  unit tours duplicated the guides, so they go rather than move.
- `tutorials/first-quantity.md` is new: build a projectile calculator, then jit,
  differentiate and vectorise it.
- The rationale for the non-parametric default existed in `migration.md`, a
  `guides/quantity.md` admonition and `README.md`; it now has one home in
  `explanation/why-quantity-is-non-parametric.md`.
- `guides/sharp-bits.md` was half fixes and half reasons. The fixes move to the
  how-tos that own them; the reasons stay in `explanation/sharp-bits.md`.
- `guides/natural-units.md` is a how-to, not the tutorial it called itself.
- `docs/api/` moves inside `reference/`.
- `docs/contributing.md` was byte-identical to root `CONTRIBUTING.md`; it is now
  a MyST `{include}` of it, with `docs/dev.md` folded in as its build section.

Three factual errors found and fixed:

- `UNXT_ENABLE_RUNTIME_TYPECHECKING`: the old page said `None` disables checking
  and is the default. `setup_package.py` says `"False"` is both; `"None"`
  installs the hook with no typechecker. Two pages also contradicted each other.
- `CONTRIBUTING.md` pointed at `github.com/unxt/unxt` (correct:
  `GalacticDynamics/unxt`) and at an Astropy dev-workflow link on
  `docs.unxt.org`, which does not exist.
- `uv run --group test nox -s test` — the session declares its own `test` group.

Things outside `docs/` that name doc paths move with them: `noxfile.py` (the
jupytext source, and `sphinx-apidoc -o reference/api/`, which would otherwise
recreate the dissolved directory), `docs/conf.py` `exclude_patterns`,
`.gitignore`, `README.md`, and the `packages/unxts.parametric/docs/*`
cross-references into the core pages.

`sphinxext-rediraffe` was already a docs dependency but commented out of
`conf.py`. Enabled, with all 21 old URLs mapped and every stub verified against
the built tree.

`pytest docs/` goes 600 to 493 examples, all passing; the drop is the `index.md`
quickstart that duplicated the reference pages, confirmed by diffing the API
calls exercised before and after. `nox -s docs` succeeds and `sphinx-build -n`
warnings drop 226 to 219.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…g page

`index.md` was doing five jobs: landing page, install guide, quickstart, a full
explanation of why the default `Quantity` is non-parametric, and a class
comparison table. The explanation and the table also existed, in nearly the same
words, in the core docs.

- `index.md` becomes a router: what it is, install, one at-a-glance example, a
  short "should you use it?", pages grouped by kind, public API. The
  non-parametric rationale and the comparison table link to the core docs rather
  than restating them.
- `dimensions.md` was 63 words — a section, not a page. Merged into
  `quantity.md` and redirected.
- Titles now say what each page is for: `quantity.md` is `ParametricQuantity`,
  `type-checking.md` is "How to check dimensions at runtime", `sharp-bits.md` is
  "The parametric sharp bits".

Examples 52 to 50; the two lost are duplicate imports from the merged page's
setup block, which `quantity.md` already had.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`index.md` was 821 words and held the entire per-strategy reference — every
parameter of `named_dimensions`, `derived_units`, `units`, `quantities`,
`unitsystems`, `angles` and `wrap_to` — plus an "Advanced Usage" section of
composition recipes, plus a quickstart, plus install, plus licence and
contributing boilerplate. It was longer than the guide it linked to, and its
testing patterns were a second copy of `testing-guide.md`'s.

- The per-strategy reference moves to `strategies.md`.
- "Advanced Usage" becomes `recipes.md`, "How to combine strategies".
- `index.md` becomes a router with one runnable at-a-glance property.
- `testing-guide.md` becomes "How to write property-based tests".

Examples 49 to 50.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…guide

`index.md` was doing five jobs — landing page, install, a rationale for the
abstract-dispatch design, a how-to on registering implementations, a "for package
authors" section, and licence boilerplate. Two of those duplicated
`extending.md`, and the design rationale was sitting in a commented-out block.

- `index.md` becomes a router, with the abstract functions in one table.
- `why-abstract-dispatch.md` is new: why the API is a separate distribution, why
  dispatch rather than an ABC (the types being extended usually cannot be
  subclassed: astropy, gala, xarray), and what the approach costs — call-time
  errors instead of definition-time ones, a contract that lives in prose, and
  registration by import side effect.
- `extending.md` becomes "How to extend unxt with your own types", absorbing the
  duplicated registration walkthrough.
- `api.md` gains a sentence saying what a reader will find in it.

Examples 45 to 42; the three are duplicate registration snippets now living once
in `extending.md`, verified by diffing the dispatch calls exercised.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… pages

`tutorial-metric.md` linked out mid-lesson and had a step that fails on purpose.
A deliberate failure is fine in a how-to and wrong in a tutorial: the learner
cannot tell an intended failure from their own mistake.

Rewritten as "Build a heterogeneous metric" — construct a polar-coordinate metric
whose radial entry is `m2` and angular entry is `m2 / rad2`, inspect two elements
with different units, take the diagonal, take the determinant (whose unit comes
out as the product `m4 / rad2`), then convert the whole matrix to km. Every step
succeeds and prints; all output produced by running it.

The `inv` refusal moves to `sharp-bits.md`, which already explained the
uniform-unit requirement but only demonstrated it for `det`.

Reference pages now name the thing: `QuantityMatrix`, `UnitsMatrix`,
"Linear-algebra operations". Examples 111 to 117.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both guides were titled "... Interoperability Guide", which names the subject
without saying whether the page instructs. Retitled to "How to convert between
gala and unxt unit systems" and "How to plot quantities with matplotlib", and
both index pages now list their contents by kind.

Also fixes three stale deep links: the `AbstractUnitSystem` reference in the gala
docs still pointed at `/en/latest/api/unitsystems/`, which moved to
`/en/latest/reference/api/unitsystems.html`. These are external URLs, so neither
the link checker nor the Sphinx build sees them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`index.md` was not a landing page — it was a second, shorter copy of
`xarray-guide.md`, repeating its DataArray and Dataset sections and adding an
"Advanced Usage" section whose most interesting example was commented out.

- `xarray-guide.md` becomes "How to use unxt with xarray", keeping the tasks.
- `api.md` is new, holding the lower-level functions that were buried at the
  bottom of the guide plus a signature table for the `.unxt` accessor. The
  accessor's parameters were previously documented only by example; the table is
  taken from `accessors.py`, including the `format` argument the old docs left
  commented out.
- `sharp-bits.md` is new, holding the two limitations — dimension coordinates
  cannot hold quantities, and which operations drop units. Both are consequences
  of how xarray works rather than choices this package made.
- `index.md` becomes a router with one quantify/dequantify round trip.

Examples 33 to 27; the six removed were the index's duplicate copies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four structural warnings from `sphinx-build -n`:

- `unxts.interop.xarray/sharp-bits.md` jumped H1 to H3, because the sections it
  inherited were `###` under a `##` that no longer exists.
- `xarray-guide.md` linked to an anchor that moved to `sharp-bits.md`.
- `unxts.parametric/index.md`'s `](quantity)` was ambiguous against the
  `unxt.quantity` module.
- `reference/dimensions.md` used a `path#anchor` form MyST does not resolve.

Fixing these turned up a rule worth recording: the package docs are built through
the `docs/packages/<name>` symlinks, so a MyST link written with an explicit `.md`
suffix resolves against the real file location outside the Sphinx source tree and
fails. Extensionless links work; `./quantity` disambiguates against a same-named
Python object without reintroducing the suffix.

Build now has zero structural warnings: 208 total, of which 206 are pre-existing
autodoc nitpicks and 2 are sphinx-tippy failing to reach Wikipedia. 811 doctest
examples passing, 22 redirects, none broken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The package had reference, how-to and discussion pages but no lesson, so the only
way in was an API tour that assumed the reader already knew why they would want a
dimension in a type.

"Let the type system catch a unit mistake" shows the default `Quantity` silently
accepting `Q["length"](1.0, "s")`, then `PQ["length"]` refusing the same call,
then a dimension being inferred, then two lengths multiplying into a class
parametrized by `area`, and finally two `@dispatch` implementations of one
function selected by the argument's dimension.

The one step that raises is the point of the page: the guard firing is the
product working. That differs from the `inv` refusal removed from the linalg
tutorial, which was a limitation interrupting a lesson about something else.

All output produced by running it. Examples 50 to 69.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Property-based testing is a new skill for most people arriving here, not just a
new API, so this package had the strongest case for a lesson and did not have one.

"Write your first property test" states a round-trip property, runs it, then
shows what the silence covered — 25 generated arrays, then several units of
length, then quantities of every physical dimension in the catalogue — before
pointing the technique at a function the reader writes.

Two notes on its construction. A version that planted a unit bug for Hypothesis
to find was discarded: unconstrained, the falsifying example was `0/0`, teaching
division by zero rather than unit confusion; constrained, it produced
`FlakyFailure`. A lesson that depends on a failure arriving in a particular shape
is not reliable. Bug-hunting belongs in the how-to.

Every test carries `@settings(deadline=None, max_examples=25)` explicitly rather
than relying on the repo conftest's global deadline-free profile. That profile
makes JAX-heavy property tests pass in this repo's suite; a reader running the
page in their own project has none and would hit `DeadlineExceeded`.
`max_examples=25` also takes the page from 72s to 25s.

Examples 50 to 74.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ies, api.md drift

**tutorials/design-a-unitful-api.md** is new, and is the lesson the other
tutorials were missing: every one of them has the reader writing a script, not a
function other people call.

It starts from the version everyone writes first - bare floats with the units in
the docstring - and shows a caller who works in solar masses getting `0.0` back:
no error, right type, plausible magnitude, wrong by fifteen orders. Switching to
quantities fixes that, and Earth and the Sun both come out right (11.19 and
617.39 km/s) in units the function never mentions.

Then the part worth the page: units do **not** validate your arguments. Passing a
time where the mass goes computes happily, because the square root of anything is
computable - it just returns a result whose dimension is `'unknown'`, and the
error surfaces later, wherever the caller tries to convert. That is a bug report
about the wrong line. So the lesson adds an `is_unit_convertible` guard at the
boundary, an explicit `AllowValue` policy for callers who pass bare numbers, and
finishes on why the function should return what the algebra gives rather than
converting on the caller's behalf.

**strategies.md** is grouped. It was seven flat headings over 1505 words (the
"736 words" in the earlier gap list was a stale figure from the old index
section). Now an orienting table of what each strategy generates, then two
groups - dimensions and units; quantities, angles and unit systems. The table
deliberately carries no anchor links: hand-written slugs rot silently, and the
headings are immediately below it.

**api.md** gets a non-rotting source of truth. Its ten "Example Implementations"
lists are hand-written and lag the code, so the page now opens by pointing at
`f.methods`, which `plum` maintains. Writing the example surfaced the reason it
is worth having: the abstract functions carry no implementations until a provider
is imported, so the snippet needs an explicit `import unxt` - which is the same
registration-by-import-side-effect point `why-abstract-dispatch.md` makes.

The ten lists stay. They are labelled examples rather than a complete list, and
they carry prose a runtime query cannot; deleting them to avoid drift would trade
real content for a risk the new note already covers.

1140 examples passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 88 out of 95 changed files in this pull request and generated 2 comments.

Comment thread docs/how-to/register-a-quax-rule.md Outdated
Comment thread docs/reference/units.md Outdated
Both from Copilot's review of GalacticDynamics#909, both correct.

`register-a-quax-rule.md` listed the three registration modules without their
`src/` and `packages/.../src/...` prefixes, so none of the three paths resolved
from the repository root. The whole point of that table is "go read these", which
an unfindable path defeats. Paths corrected and the column relabelled to say what
they are relative to.

Swept the rest of the docs for the same mistake while there - every `src/`,
`packages/`, `tests/` or `docs/` path cited in a page now resolves; that check is
0 unresolvable across the tree.

`reference/units.md` had "# str have no units" in a doctest comment; now
"strings have no units". Inherited from the old `guides/units_and_systems.md`
rather than introduced here, but it moved with the page so it is fair to fix.

1140 examples passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Small edits only, no restructuring and no changed claims. The pages read
uniformly, in a way that is easier to measure than to describe, so this was
driven by counting repetitions rather than by taste.

What was actually repeating:

- "Here is the part worth slowing down for" opened a section in three different
  tutorials, word for word. Now zero.
- "That is the whole point / whole cycle" closed three sections. Now one.
- Four of the five core tutorials opened with the identical sentence stem "In
  this tutorial we will". Diataxis recommends that phrasing, so the fix is
  variety rather than removal: several pages now lead with the hook that was
  sitting in their second paragraph and let the stem fall mid-sentence, and six
  package tutorials keep it unchanged.
- `docs/index.md` ran 28 em-dashes per thousand words. Now 3.

Two openers improved by promotion rather than rewriting: `ecosystem-pipeline`
led with a summary and buried "three libraries that know nothing about each
other, and the units survive all of it" underneath it, and `tutorial-metric`
buried "a single-unit Quantity cannot represent such an object at all". Both now
open on the interesting sentence.

One measurement was a false alarm and is left alone: `tutorials/index.md` scores
28 em-dashes per thousand words, but every one separates a link from its
description in a list. That is list punctuation, not voice.

All 1140 examples still pass; only prose was touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 88 out of 95 changed files in this pull request and generated 1 comment.

Comment thread packages/unxts.api/docs/tutorial-your-own-type.md
Review raised that `@dispatch def unit_of(...)` only extends an existing plum
function when `unit_of` is already bound in the module namespace, and that the
tutorial's setup block imports only `dispatch` and `unxt as u`, so the
registration would create a new unrelated function.

That is not what happens, and the tutorial now shows why rather than asserting
it. `plum.Dispatcher._get_function` files a module-level function under its bare
`__name__` in one namespace on the dispatcher, with no module qualification, and
`plum.dispatch` is a single shared instance that `unxts.api` also uses. So any
`@dispatch def unit_of` anywhere joins the same `Function` object - no import
required.

Checked in a real module rather than a doctest, since a doctest namespace is not
a user's file: `unit_of is u.unit_of` is True, `u.unit_of(r)` returns the
registered result, a differently-named function stays separate, and unxt's own
implementations keep working.

The page now demonstrates the identity with `unit_of is u.unit_of`, states the
mechanism, and adds the caveat that falls out of it: because the bare name is the
whole key, a `@dispatch def unit_of` intended as a private helper would join
unxt's function as well.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 88 out of 95 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

docs/reference/dataclassish.md:5

  • The rewritten lead sentence (line 3) now states that every unxt type is an Equinox module / Python dataclass that works with dataclassish. The following sentence (line 5) — carried over from the previous version of this page — repeats the same fact almost verbatim, so the two are now redundant. Consider dropping the duplicated sentence.

nstarman and others added 2 commits August 24, 2026 23:43
The docs job checked one thing: `myst.xref_missing`, so internal navigation is
gated. Three other ways the docs rot silently were not.

**Cited repository paths.** Prose that points a reader at a file is not a link,
so nothing validated it - and review found three such paths in
`register-a-quax-rule.md` that had never resolved. `scripts/check_doc_paths.py`
fails when a backtick-quoted `src/`, `packages/`, `tests/`, `docs/` or `scripts/`
path does not exist. Verified it fails: breaking one path exits 1 and names the
page.

**Redirects.** 22 old URLs are mapped through rediraffe and nothing stopped the
next page move from breaking them. `rediraffecheckdiff` diffs the doc tree
against `main` and sets a failing status for a deleted page with no redirect, so
the map cannot fall behind the tree. Needs `fetch-depth: 0` on the checkout.

**External links.** `linkcheck` was available via nox and never run in CI. It
found real rot on its first run: `quaxed.readthedocs.io` 404s (five pages), and
JAX moved its docs to `docs.jax.dev` (four pages). Both fixed here, which is what
makes the gate viable - it now reports zero broken links.

`linkcheck_ignore` skips `unxt.readthedocs.io`: a page added by a PR cannot exist
on Read the Docs until that PR merges, so self-links 404 by construction
pre-merge. Anchor checks are skipped for two hosts that generate fragments
client-side. Neither exemption weakens the gate - internal navigation is covered
by the existing `myst.xref_missing` check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Verified the gate by removing one entry from the redirect map: it exits 1 with
"(broken) docs/guides/quantity.md was deleted but is not redirected!". Two things
that testing turned up, both now recorded next to the setting:

- It fails on *deletions* of pages the base branch had. A pure rename is
  reported separately by git as R and only produces a hint, so the gate does not
  catch that case.
- `origin/main` is correct in CI, where `origin` is this repository, but running
  the builder from a fork clone diffs against the fork's own `main`. Mine is
  hundreds of commits stale, so it reported two deletions upstream never had.
  Point it at `upstream/main` locally rather than believing the output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nstarman
nstarman requested a review from a team as a code owner August 25, 2026 03:46
@github-actions github-actions Bot added 👷 Add / update CI build system Add or update CI build system. 💚 Fix CI build Fix CI Build. labels Aug 25, 2026
nstarman and others added 2 commits August 24, 2026 23:56
Reading this page line by line, and running its examples rather than trusting
that CI passing meant they worked, turned up six that do not.

They pass CI because none of them is ever *called*. Every block defines a
`@given` function; Sybil executes the block, so the definition succeeds and the
assertions inside never run. The page has been green while containing code that
errors on first use.

- `test_matrix_multiplication_shape` called `q1.ustrip("")` on quantities of
  arbitrary unit, with the comment "Make dimensionless for matrix
  multiplication". `ustrip("")` converts *to* dimensionless; it raises
  `UnitConversionError` on anything with a real unit. Now generates
  dimensionless quantities.
- `test_quantity_equality_reflexive` did `assert q1 == q1`. `==` is element-wise
  and returns an array, so this is `ValueError: truth value ... ambiguous` for
  any non-scalar. It passed only because `st.from_type(u.Q)` happens to yield
  scalars - a latent break, not a working example. Now reduces with `jnp.all`.
- Four dtype examples asked strategies for `jnp.float64`. JAX disables 64-bit
  types unless `jax_enable_x64` is set, so Hypothesis generates values the
  float32 array cannot hold and the test errors with `InvalidArgument` rather
  than failing a property. The section now says so, and shows the opt-in.

Two mistakes of my own while fixing it, both caught by running things:

- The replacement dtypes have to be ones the strategy accepts. My first attempt
  used `jnp.bfloat16`, which it rejects ("not recognised in jax.numpy"). The
  supported set is float32, complex64 and int32.
- The x64 opt-in snippet is a `python` block, so Sybil ran it - enabling 64-bit
  types for the whole doctest process and breaking 118 examples across unrelated
  pages. It is now marked `skip`, which is precisely the process-global hazard
  the note beside it warns about.

Every fixed example was executed as a real property before being written back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…oken call

Second half of the line-by-line read. Unlike the hypothesis guide, these blocks
do execute - but only their module-level statements. Anything inside a function
body is never run, and that is where the bug was.

- **"The three steps" was wrong and redundant.** Step 1 told the reader to
  `from unxts.api import unit_of, ...` before registering. No import is needed:
  `plum.dispatch` is one shared dispatcher that files module-level functions
  under their bare `__name__`, so `@dispatch def unit_of` joins unxt's function
  wherever it is written. Steps 2 and 3 then showed the same function twice.
  Replaced with what registration actually does, carrying a live
  `assert unit_of is u.unit_of` so the claim is checked rather than asserted,
  plus the two consequences that follow: the name is the whole key (a private
  helper called `unit_of` joins unxt's function too), and the annotations are
  the contract.

- **`SpecialQuantity` called `u.ustrip(to_unit, obj.value, obj.unit)`.** `ustrip`
  takes (unit, quantity); the three-argument form raises
  `plum.NotFoundLookupError`. It sat inside a function body, so it never ran.
  Both branches now build a `Quantity` first, and the block exercises them so a
  regression would fail the suite.

Checked and deliberately left alone: the `object` fallback example. It looked
dangerous - registering a catch-all on unxt's global `unit_of` - but `object`
loses to every more specific implementation, unxt already returns `None` for
unregistered types, and the behaviour is unchanged before and after. Redundant,
not harmful.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the 🐛 Fix a bug Fix a bug. label Aug 25, 2026
@nstarman
nstarman requested a balanced review from Copilot August 25, 2026 04:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 90 out of 97 changed files in this pull request and generated 1 comment.

Comment thread scripts/check_doc_paths.py Outdated
…atches

Review pointed out that `DOC_GLOBS` scanned `docs/`, `packages/*/docs/` and root
`*.md` only, missing the skill markdown - which cites repository paths in exactly
the same way and rots exactly the same way. It named two live instances, and both
are real:

- `.github/skills/code-review/SKILL.md` cited `docs/guides/perf.md` (now
  `docs/how-to/optimize-performance.md`)
- and `docs/conventions.md` (now `docs/explanation/api-conventions.md`)

Those are files this restructure moved, so the drift is mine. My own sweep missed
them because it had the same blind spot as the guard.

Globs now cover `skills/**` and `.github/**`; the check goes from 7 cited paths
to 9. Verified it would have caught the reported rot: restoring the old
`docs/guides/perf.md` reference exits 1 and names the file.

`.claude/plans` and `.claude/specs` stay excluded, now with the reason written
down rather than implied. They carry ~27 stale paths between them, and correctly
so: they are dated records of past work that name paths as they were at the time.
Updating them would falsify the record.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 91 out of 98 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

docs/how-to/check-types-at-runtime.md:108

  • The link text "Dimension annotations for type checking" is stale. The target page was retitled to "How to check dimensions at runtime" and its section is now "Annotate with a dimensioned type" (see packages/unxts.parametric/docs/type-checking.md:1,10), so this link text no longer matches any heading on the linked page. Consider updating the link text to match the page's current title.
Annotations on the default `Quantity` constrain dtype and shape but **not** dimension: `Quantity["length"]` is `Quantity`, and the subscript does nothing. To have an argument's dimension checked, use `ParametricQuantity` from the separate [`unxts.parametric`](../packages/unxts.parametric/index) package, which encodes the dimension in its type — see [Dimension annotations for type checking](../packages/unxts.parametric/type-checking).

@nstarman
nstarman merged commit 6e4159a into GalacticDynamics:main Aug 25, 2026
56 checks passed
@nstarman
nstarman deleted the claude/diataxis-unxt-docs-d0b47c branch August 25, 2026 06:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

👷 Add / update CI build system Add or update CI build system. 🔧 Add / update configuration Add or update configuration files. 📝 Add / update documentation Add or update documentation. 🙈 Add / update .gitignore Add or update a .gitignore file. 🐛 Fix a bug Fix a bug. 💚 Fix CI build Fix CI Build. 🧩 unxts-api Issues/PRs affecting the unxts.api namespace package 🧩 unxts-hypothesis Issues/PRs affecting the unxts.hypothesis namespace package 🧩 unxts-interop-gala Issues/PRs affecting the unxts.interop.gala namespace package 🧩 unxts-interop-matplotlib Issues/PRs affecting the unxts.interop.matplotlib namespace package 🧩 unxts-interop-xarray Issues/PRs affecting the unxts.interop.xarray namespace package 🧩 unxts-linalg Issues/PRs affecting the unxts.linalg namespace package 🧩 unxts-parametric Issues/PRs affecting the unxts.parametric namespace package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants