📝 docs: reorganise the docs and add the missing tutorials - #909
Conversation
There was a problem hiding this comment.
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”), plusabout/, 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-redirafferedirects, moved generated API docs underreference/api/, and adjustednox+.gitignorefor 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.
163c980 to
d639e40
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
8f34674 to
ef957fd
Compare
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>
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>
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>
There was a problem hiding this comment.
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
unxttype is an Equinox module / Python dataclass that works withdataclassish. 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.
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>
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>
…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>
There was a problem hiding this comment.
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).
Reorganises the documentation — the core
docs/tree and all sevenunxts.*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.mdor in aguides/bucket that mixed instruction, description and rationale in one page.guides/quantity.mdwas reference, how-to and explanation at once;index.mdwas 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.hypothesisandunxts.interop.xarraythe landing page had grown longer than the guide it linked to and duplicated it section by section. Inunxts.apithe rationale for the whole design was inside an HTML comment.Core
docs/— 22 pages → 37, nothing deletedThe rationale for the non-parametric default previously existed in
migration.md, aguides/quantity.mdadmonition,README.mdandunxts.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.
m / (m/s)— without being told.(length/acceleration)**0.5being a time.0.0for 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.xarray→unxt→matplotlib: the computed unitkm2 kg kpc / m3looks like nonsense untildimension_ofsaysmass, and the plot labels itself$\mathrm{M_\odot}$.unxts.parametricQ["length"](1, "s")is silently accepted;PQ["length"](1, "s")refuses.unxts.hypothesisunxts.linalgunxts.interop.xarrayxarrayadds km to hours without complaint and drops the label; quantify, and it raises.unxts.interop.matplotlibunxts.interop.galausys["velocity"]composeskpc / Myrfrom base units nobody supplied.unxts.apiQuantityand your class, unchanged.register_ufuncandis_unit_convertibleare public but appeared only in generated autodoc; both are now in the reference.Bugs found and fixed along the way
guides/type-checking.mdUNXT_ENABLE_RUNTIME_TYPECHECKING=Nonedisables 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.mdgithub.com/unxt/unxt, and an Astropy dev-workflow link ondocs.unxt.orgGalacticDynamics/unxt,docs.astropy.orgCONTRIBUTING.mduv run --group test nox -s testuv run nox -s test— the session declares its own groupreference/quantity.md(inherited from the old guide)StaticQuantity"accepts Python scalars and NumPy arrays only, rejecting JAX arrays"unxts.interop.gala/docs/guide.mdback == gu.galacticisFalse.galaregisters a preferred unit for the derived velocity dimension (km/s); anunxtsystem 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.QuantityMatrixgainedunit_of/ustripand scalar-unituconvert(#880, #902).UnxtConverter.unit_formatreplaced theaxisinfo_kwdict (#862). None shipped with documentation; all three are now described.AGENTS.mdandskills/unxt/SKILL.mdpointed at doc paths that moved, and are fixed.Redirects
sphinxext-rediraffewas already a docs dependency but commented out ofconf.py. Now enabled, with 22 old URLs mapped; every stub and every target was checked against the built HTML.Verification
nox -s docssucceeds.sphinx-build -n --keep-going→ 208 warnings, zero structural: 206 pre-existingpy:autodoc nitpicks from docstrings this PR never touches, 2sphinx-tippynetwork 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.index.md's quickstart re-testing what the reference pages test.Known gaps
unxthas ~108 raise sites, 46 with custom messages, but most of what users actually hit surfaces asastropy'sUnitConversionError. 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.Closed since this PR was opened:
Nothing teaches how to design a unit-aware function for others to call.Nowtutorials/design-a-unitful-api.md.Now an orienting table plus two groups. (The "736 words" figure in the original list was stale; it is 1505.)unxts.hypothesis/strategies.mdis long and ungrouped.The page now points atunxts.api/api.mdhand-maintains "Example Implementations" lists that will drift.f.methodsfor the authoritative set. The lists stay — they are labelled examples and carry prose a runtime query cannot.Derived units of aFixed upstream in 🐛 fix(unitsystems): derived units of scaled systems can be printed #910;DynamicalSimUSysFlagsystem cannot be printed.usys["velocity"]now returnsUnit("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.mdwithConversionError: Failed to convert value(s) to axis units, and the rest passed. The cause wasshared 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 theecosystem tutorial added here, plotting
degonto it raised. Under-n logical --dist=loadfilethe file-to-worker assignment varies with therunner'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 dependson what ran before it, and the pages that make figures close them.