tephpy getting started — design specification#

Estimated reading time: 32 minutes

Living document. This specification is maintained alongside the code, not archived behind it. The pages it describes cite it by section — start spec §3.3 and the like — so these sections are the reasoning behind what those pages say, and where the two ever diverge it is the specification that gets corrected. Read it as current.

  • Date: 2026-09-05 (originated; maintained since)

  • Status: living design specification

  • Citation prefix: start spec §… — named for docs/src/start/, the directory it governs, rather than for “getting started”, which would collide in prose with the ordinary English phrase and read as a description instead of a citation

  • Scope: the four on-ramp pages, their landing page, the gate changes that bring them under the guarantees the quadrants already carry, and the installation page’s pre-release admonition

  • Parent spec: 2026-07-22-tephpy-design.mdspec §8.6’s documentation layering, which names the four quadrants and says nothing about how a reader reaches them

  • Sibling specs: 2026-08-27-narrative-quadrants-design.mdnarrative spec §3.9’s landing-page shape, reused here rather than reinvented; 2026-08-25-scope-and-support-design.md§3.1’s non-goals statement, which §3.2 below summarises and must not fork

1. Purpose#

tephpy tells nobody how to install it. Not in the documentation, and not in the README.md, whose only heading is Non-Goals. Measured 2026-09-05: no pip install, conda install or pixi add string exists anywhere a reader can see — the only matches in the repository are inside docs/src/developer/plans/, which conf.py’s exclude_patterns keeps out of the build. Meanwhile .github/workflows/ci-wheels.yml carries publish-pypi and publish-testpypi jobs.

That is the gap this specification closes, and it is a stranger one than it sounds. Every page the narrative quadrants added — two tutorials, two explanation pages, nine how-tos — opens by assuming the reader has the package. tutorials/first-tephigram.rst says “the only thing you need is tephpy installed” and offers no way to do it. The documentation is complete from the second step onwards.

#66 asks for this, filed 2026-08-01 as one issue covering three separate things: an on-ramp, a contributor guide, and a split of the parent specification’s §3.2. Its survey of where tephpy stood is now stale in every row — the quadrants it calls index-only hold fourteen pages between them — and it files the on-ramp last, under “also worth having”. The measurement above is the reason this specification takes it first.

This document covers the on-ramp alone. The contributor guide and the §3.2 split remain #66’s, and §6 says so.

2. Decisions#

  1. A directory, not four top-level pages. geovista puts installation, overview, quick_start and next_steps beside its root index, and #66 compares against that. tephpy’s gates discover by directory name, so docs/src/start/ brings the section under each of them by adding one string, where four sibling files would need a hand-maintained list of four paths in three places — the shape narrative spec §3.9 was written to remove from the landing pages.

  2. The section gets a landing page in narrative spec §3.9’s shape. Not a fifth convention: the same introduction, two-column table and hidden ordered toctree, held by the same gate. It also buys the section one entry in the header rather than four (§3.8).

  3. The quick start shows working code and then gets out of the way. It is a shop window, not a lesson: roughly ten lines, one figure, and a handover to Your First Tephigram, which teaches the same diagram properly. A quick start that taught would be a second tutorial competing with the first.

  4. Its code is executed and its figure compared, like every other user page. This is the decision that costs something — three gates are scoped to the Diátaxis quadrants and have to widen (§3.6). The alternative was a page whose ten lines are the first thing a reader runs and the only user-facing code in the project that nothing checks.

  5. The installation page matches geovista’s page in structure, and adopts its icon vocabulary. Three sections — stable, latest, developer — each offering conda, pip, pixi and uv in a synced tab-set. sphinx-design 0.7.0 is already installed and provides :sync-group:, so the behaviour costs no dependency; the tool icons cost one, sphinx-iconify, and §7 records the question that remains open about it.

  6. The pre-release note is enforced rather than remembered. tephpy has no tags and reports 0.1.0.dev190, so “is this released?” is decidable offline from tephpy.__version__ alone. §3.7 makes the admonition’s presence a function of that rather than of somebody’s release checklist.

3. Architecture#

3.1 The section, and its landing page#

Five files in docs/src/start/:

page

what it is

index.rst

the section’s landing page — narrative spec §3.9’s shape

overview.rst

what tephpy is, and what it is not

installation.rst

how to get it, four ways, three states

quick-start.rst

ten lines and a diagram

next-steps.rst

where to go once it works

The landing page carries an introduction and the two-column table of the other four, and its toctree is hidden and ordered as the table is. It is exempt from the reading-time banner on reading spec §3.7’s rule, as the four quadrant landing pages are, and for the same reason: nobody reads an index through.

Naming the landing page’s own title Getting Started rather than Start is deliberate; the directory is short because it appears in every URL under it, and the title is explicit because it appears in the header.

Added 2026-09-06. Each of the four pages carries an :iconify: glyph before its title, from the mdi set and distinct from the four §3.3 already spends on that page’s own sections. This is local to this section and deliberately not a site rule: the theme renders a title’s icon in the Section Navigation sidebar as well, so one page having one and three not reads as an omission rather than as emphasis. Either every page in a section carries one or none does. Nothing gates it, because the cost of getting it wrong is a slightly ragged sidebar and the cost of a gate is a rule the rest of the documentation would then have to obey.

A name is checked against the Iconify API before it is written. A name that does not resolve renders blank, with no build errormdi:signpost and devicon:pixi were both reached for here and neither exists.

3.2 Overview#

docs/src/start/overview.rst. What tephpy draws, what it delegates, and what it declines.

The non-goals are summarised and linked, never restated. README.md carries the canonical list under scope spec §3.1, six entries each with somewhere to go instead, and a second copy in the documentation is a second thing to keep true — which is #193’s defect in a new place. This page says that tephpy delegates thermodynamics to MetPy and declines the skew-T, the hodograph and BUFR decoding, and links the README’s list for the rest.

It also does the one thing the README cannot: place tephpy against its neighbours for a reader who has met neither. MetPy owns the skew-T and the thermodynamic maths; tephi is the ancestor this package reimplements; tephpy draws the tephigram and reads it.

3.3 Installation#

docs/src/start/installation.rst. The page #66 should have led with.

Three states, four tools, one synced choice. The page has three sections — Stable, Latest and Developer — and each offers the same four tools in a tab-set carrying :sync-group: install, so a reader who picks pixi in the first section is still in pixi in the third. That synchronisation is the feature that makes the page usable and it is sphinx-design 0.7.0’s, already installed against a >=0.6.1 floor that already permits it.

section

what it installs

Stable

the latest release, from conda-forge or PyPI

Latest

the development version, from the main branch

Developer

a working clone, with the pixi environments this repository defines

The four tools are conda, pip, pixi and uv, in that order, with pixi carrying :selected: because it is what this repository develops with and what every pixi run in the documentation assumes. Each stable tab closes with a link to that tool’s own installation instructions, because a reader who has not got the tool cannot use the tab that assumes it.

The developer section is tephpy’s own, not geovista’s transposed: the clone, then pixi run tests, pixi run lint and pixi run docs, which are the three commands AGENTS.md names and the three a first contribution needs.

3.4 Quick start#

docs/src/start/quick-start.rst. The shop window of decision 3.

The arc is one block: a figure with the tephigram projection, a sample sounding drawn on it, and the diagram that results. It ends by handing over — Your First Tephigram takes the same diagram apart line by line — so the two pages stand in a stated relationship rather than competing.

Its python is executed by tests/test_docs_snippets.py and its figure compared against a docs/baseline/ image, which is what §3.6 widens the gates for. The page joins DOCUMENTED, PUBLISHES_FIGURES and check_docs_figures.py::PUBLISHES: three membership lists for one page, which narrative spec §4 already records the cost of.

3.5 Next steps#

docs/src/start/next-steps.rst. Where a reader goes once the quick start has worked.

The four quadrants and the gallery, each with the sentence that says who it is for — which is the same editorial job a landing-table row does, one level up. It is the only page in the section that is purely signposting, and it is a page rather than a paragraph on the quick start because a reader who arrives already installed should be able to reach it without reading the quick start first.

3.6 The gates, and the constant that stops being true#

Six constants in this repository name the Diátaxis quadrants. Four widen to include start; two do not, and the reason differs for each:

constant

widens

why

tests/test_docs_snippets.py::QUADRANTS

yes

the quick start’s code is executed

.github/scripts/check_docs_figures.py::QUADRANTS

yes

its figure is compared to a baseline

.github/scripts/check_glossary_links.py::QUADRANTS

yes

its prose names glossary terms

tests/test_docs_landing_pages.py::QUADRANTS

yes

the section’s landing page is narrative spec §3.9’s shape

docs/src/_ext/tephpy_topics_data.py::QUADRANTS

no

the on-ramp is the way in, not corpus to browse by topic; its pages declare no :tags:

tests/test_glossary_links.py

no

it reads the gate’s own tuple, so it follows the widening without an edit — though the rename touched it, because it names the constant

tests/test_docs_figures.py

no

the same, and this table missed it when first written: it reads gate.QUADRANTS too, so the rename touched two mirrors rather than one

Each of the four is named QUADRANTS and documented as the Diátaxis quadrants. A fifth name makes both the identifier and its comment false, so each is renamed and its comment rewritten in the same change. The four quadrants are not what those gates are about; what they are about is the pages written for users, which is a set the on-ramp joins.

No test asserts the length of any of these tuples, checked 2026-09-05, so widening them breaks no count. The gates assert instead that every directory they name exists, which is the right shape and needs no change.

3.7 The pre-release admonition, and the gate that retires it#

tephpy has cut no release. The installation page nevertheless documents installing the released package, because a page that described only the development install would have to be rewritten rather than edited at v0.1.0.

The page therefore opens with a note saying the commands do not work yet. That note is a claim about the world, and this project does not leave those to memory: a test reads tephpy.__version__ and asserts the note is present exactly while tephpy has never been released.

Extended 2026-09-08. README.md now carries the same claim — §3.9’s installation section shows pip install tephpy and says in a note that it does not work yet — so the gate reads a tuple of carriers rather than one path, parametrised so a failure names the file still to edit. Gating the page alone would have gone green on the release commit while the README went on saying tephpy had never been published, which is the failure this section exists to prevent, one surface over. The sentence itself is shared verbatim between the two: one wording to keep true rather than two that can drift into disagreeing about the same fact.

Corrected 2026-09-05 in review. This section first specified that test as “while the version carries .dev”, which answers a different question — whether this checkout sits exactly on a tag — and deadlocks on the real one. setuptools_scm reports 0.1.0.dev190 before the first tag, 0.1.0 on it, and 0.1.1.dev1 on the very next commit. A .dev test reads that third state as unreleased and demands the now-false note back, and there is no commit at which the note could be removed, because removing it happens on a .dev commit too. The signal is therefore a comparison against the first release, Version(tephpy.__version__) >= Version("0.1.0"): packaging sorts 0.1.0.dev190 below it and both 0.1.0 and 0.1.1.dev1 above. tests/test_docs_installation.py parametrises that sequence so the deadlock cannot return.

It is offline, which spec §8.5 requires — nothing queries PyPI to ask whether the package is there. It reads the version of the installed package, which in every environment that runs the suite is this checkout. packaging is already a member of the test extra, so the comparison adds no dependency.

Corrected 2026-09-14, preparing v0.1.0. The rule above is now one-sided: a released tephpy must not carry the note, and an unreleased one is not required to. The two-sided form deadlocked the release it was written for, one turn past the .dev deadlock corrected above. The signal changes when the repository is tagged; the note is removed when a file is edited; those are different commits over the same tree. So requiring the note while unreleased forbade removing it from the commit that gets tagged — and that commit is the one that is built, with README.md as the distribution’s long description (#315 preparation). Published 0.1.0 would have told every reader of its PyPI page and of the stable documentation that tephpy had never been released, with the correction landing in 0.1.1. A release candidate does not lift it either: packaging sorts 0.1.0rc1 below 0.1.0, so the rehearsal runs with the note still demanded.

What the earlier paragraph describes as intended — the tag is cut, the test fails, the note comes out — is therefore the defect, not the design. The direction that protects a reader is kept; the direction that deadlocked is dropped. Removing the note early is a deliberate edit in a reviewed pull request, and the two carriers are still held to agreeing with each other, which is what the second carrier was added for.

3.9 The README’s installation section#

Added 2026-09-08, closing §7’s open item.

README.md gains an Installation section between the opening paragraph and Non-Goals — what tephpy is, how to get it, then what it declines. It is the same reader as §3.3’s arriving by the GitHub or PyPI door, and before this the README sent them nowhere: its only heading was Non-Goals. scope spec §3.1 carries the README’s ordering as a whole, including the status note that follows Non-Goals.

The section shows one command, pip install tephpy, and links §3.3 for the rest. Reproducing that page’s four routes across three sections in Markdown, which has no tab directive, would be a second truth about installing that nothing holds to the first. What the README owes the reader is the shortest true path plus a door; the page owes them the choice.

The three tools named in that sentence — conda, pixi and uv — are each linked to their own documentation, as a convenience to a reader who does not already know what they are. The links go to the tools’ front doors rather than to their install instructions, which is where §3.3’s per-tab Consult the … links point: that page is helping a reader who has chosen a tool, and this sentence is met by one who has not. pixi.sh redirects to pixi.prefix.dev; it is written the short way because the README’s badge and §3.3 both already write it that way, and three spellings of one project’s home is one more than a reader benefits from.

The pre-release note follows the shape §3.7 gives the page, for the same reason: the headline command is the released one, so the whole edit at v0.1.0 is deleting the note, and everything left standing is already true. A section showing only the main install would instead need rewriting at the tag, by someone who has to remember to.

Two constraints the wording obeys rather than chooses:

  • The link is an absolute https://tephpy.readthedocs.io/en/latest/start/installation.html written as a Markdown reference, per docs-style’s Documentation Links — GitHub and PyPI resolve no Sphinx role. It names no fragment, so there is no derived anchor to break. check_documentation_links.py already reads README.md and now resolves this URL against the build.

  • The main install is written as a repository URL, which check_github_references.py passes because it names nothing by number; that gate forbids a hand-written issue or pull request URL. docs/src/start/installation.rst already carries the identical URL.

The admonition is a > [!WARNING], and the README’s two blocks differ deliberately. It was first written as a > [!NOTE] to match the status note, which was the wrong thing to match on: the two blocks do different work. The status note describes the project’s maturity, which a reader may act on or not. This one sits directly beneath a command and says that command does not work — a reader who skims it copies a line that fails. The alert level is chosen for what the reader loses by missing it, not for consistency with the nearest other block.

It is louder than the page’s own admonition of §3.7, which stays a .. note::. That is not a contradiction: the page shows the working routes a scroll below the note, in tabs the reader is already choosing between, while the README shows one command and that command is the broken one. The same fact deserves different volume where the cost of missing it differs.

> [!WARNING] renders as a GitHub alert and degrades on PyPI to a blockquote showing the literal [!WARNING], exactly as the status note’s [!NOTE] already does. The degradation is the same shape at either level, so it argues for neither.

4. Companion changes#

  • docs/src/index.rst — the root toctree gains start/index first, so the section leads the header and the previous/next chain starts at the on-ramp.

  • pyproject.tomlsphinx-iconify joins the docs feature’s pypi-dependencies, with a floor; it has no conda-forge package. And requirements/pypi-optional-docs.txt, which is what [project.optional-dependencies].docs is generated from: the pixi table and that file are two parallel lists, as playwright already demonstrates, and populating only the first leaves pip install ".[docs]" without an extension conf.py imports. Found in review rather than by this specification.

  • docs/src/conf.pysphinx_iconify joins extensions, and html_theme_options["header_links_before_dropdown"] is set to 6 (§3.8, as corrected).

  • tests/test_docs_readingtime.pystart/index.rst joins EXEMPT with its reason; the other four pages carry banners and need no entry, because the corpus is derived.

  • docs/src/developer/specs/index.rst — the prefix table gains a start spec §… row, and its toctree the matching entry. The two are separate hand-written lists on one page, and writing the row without the entry fails the build with document isn't included in any toctree — which is what happened while this specification was being written, and is the divergence narrative spec §3.9’s gate exists to prevent one directory over. §7 records it.

  • narrative spec §3.9 and docs/src/developer/docs-style.rst — both describe the landing shape as a quadrant landing page, which this section makes incomplete. Each gains a sentence saying the shape is not quadrant-only and citing start spec §3.1. The rule does not change; what changes is the set it is stated over.

  • docs/baseline/ — one baseline for the quick start’s figure, generated rather than hand-written.

  • README.md — an installation section was not part of this specification as first written; §7 recorded the question and §3.9 now answers it, in a later change.

5. Testing#

what lands

what holds it

the quick start’s python

tests/test_docs_snippets.py, once §3.6 widens its sweep — one script, on every supported Python

the quick start’s figure

check_docs_figures.py against its docs/baseline/ baseline within RMS 2

the landing page’s table against its toctree

tests/test_docs_landing_pages.py, once §3.6 widens its sweep

every :term: on the four pages

check_glossary_links.py, once §3.6 widens its sweep

the four read pages carrying a reading-time banner

tests/test_docs_readingtime.py, already derived over the whole tree

the pre-release note matching the version, on the page and in README.md alike

a new assertion, §3.7, parametrised over both carriers once §3.9 added the second

the README’s link into this section

check_documentation_links.py, which already reads README.md

every start spec §… citation

the pre-commit anchor check and check_rendered_citations.py

the prose

review, against docs-style’s Reviewing Claims

One new assertion, and four existing gates widened. That ratio is the point of decision 1: the section is held by machinery that already exists, and joins it by naming a directory.

6. Scope#

In scope. The five pages of §3.1§3.5, the quick start’s baseline, the gate changes and renames of §3.6, the admonition and its assertion of §3.7, the navigation change of §3.8, and the companion changes of §4. The README’s installation section of §3.9 joined them on 2026-09-08, in a change of its own — this specification is living, and §7’s open item was closed by answering it rather than by dropping it.

Out of scope. #66’s other two halves: the developer and contributor guide, and the split of the parent specification’s §3.2 into a plotting tour. Both are separately scoped and neither is a prerequisite of this one. The reference quadrant’s landing page, which narrative spec §7 held open — settled there on 2026-09-11, and refined to a grid of cards on 2026-09-15. Any change to the non-goals themselves, which are scope spec §3.1’s.

Tranches. Corrected 2026-09-05 from the implementation. The rename goes first and alone, which is what is provably behaviour-preserving: 2177 tests passed before it and 2177 after. The widening cannot precede the pages — tests/test_docs_landing_pages.py and tests/test_glossary_links.py each assert every section they name is a directory on disk, and widening either before docs/src/start/ exists fails with start is missing. So each widening lands with the pages it governs, and the section’s landing table grows a row as each page arrives rather than naming pages that do not yet exist.

7. Open items#

Tagged per docs spec §3.5.

  • Closed 2026-09-05sphinx-iconify does not embed its icons. Measured against a probe build and then against this project’s own: it emits <iconify-icon> elements — the built installation page carries 52 of them and no inline <svg> — and adds one script tag, https://code.iconify.design/iconify-icon/3.0.1/iconify-icon.min.js. Loaded in Chromium with every request captured, a page with two icons made three external calls: that script, and one api.iconify.design request per icon collection. So the installation page costs each reader a third-party script and per-collection data fetches, at read time. That is a departure from tooltip spec §3.3, which switched off tippy’s three network-reaching sources so this documentation neither builds nor reads over the network — made knowingly, for parity with the page #66 names as the bar. iconify_script_url is a configuration value defaulting to that CDN, so the script could be vendored into _static; the data fetches belong to the web component and the extension exposes no control over them. Revisit if the cost proves unwelcome.

  • Open, not blocking — whether the specification collection’s own index should be held by tests/test_docs_landing_pages.py. It carries a hand-written prefix table and a hand-written toctree over the same sixteen documents, and they can disagree: writing one without the other is a mistake made once already, during this specification. The build catches the direction that leaves a document unreachable and says nothing about a table row naming a document the toctree omits. Not taken here because the gate’s rows are :doc: links and the prefix table’s second column is one, which may or may not survive contact with the parser.

  • Closed 2026-09-08README.md gains an installation section pointing at this page. It was out of scope as first written because the README is scope spec §3.1’s surface and this specification should not have edited it in passing; taken up as its own change, it is specified in §3.9 and the note it carries is gated with the page’s by §3.7’s test.

8. References#