Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ A diagram renderer, which draws flowcharts and diagrams from a plain-text descri

### Essentials / Advanced toggle

A two-option switch, provided by the [mkdocs-audience-toggle](#mkdocs-audience-toggle) plugin (configured under `plugins:` in `mkdocs.yml`), that lets a reader hide everything beyond a first-pass beginner curriculum. Content is opted into hiding by marking it `data-fcm-hide="essentials"`:
A two-option switch, provided by the [mkdocs-audience-toggle](#mkdocs-audience-toggle) plugin (configured under `plugins:` in `mkdocs.yml`), that lets a reader hide everything beyond a first-pass beginner curriculum. Content is opted into hiding by marking it `data-audience-hide="essentials"`:

- On a `##`/`###` heading inside a content page (e.g. functions.md's `## Decorators`), it hides that heading plus every sibling up to the next heading of the same or higher level, and removes the matching entry from the `toc.integrate` sidebar — so there's no dead nav link to something that's hidden.
- On a homepage card-grid row, it hides just that row. A whole homepage card hides too, once its first paragraph (the only one attr_list can attach the marker to) carries the marker — extra.css has a small `:has()` rule that extends that into hiding the entire `<li>`, since the plugin itself only hides the exact element marked.
Expand Down Expand Up @@ -177,10 +177,10 @@ plugins:
```

```markdown
## Decorators {: data-fcm-hide="essentials" }
## Decorators {: data-audience-hide="essentials" }
```

Material has no built-in way to tailor a page to different readers. The plugin hides a marked heading together with its whole section and its table of contents entry. It switches to the nearest mode that shows the content when a link points to something hidden. It also collapses to icons or moves to its own row on narrow screens. It includes its own Playwright and axe-core tests. See the [plugin's README](https://github.com/luka-sherman/mkdocs-audience-toggle) for all options. This site's setup is under `audience_toggle` in `mkdocs.yml`, with color overrides in `extra.css`'s `#fcm-toggle` rule.
Material has no built-in way to tailor a page to different readers. The plugin hides a marked heading together with its whole section and its table of contents entry. It switches to the nearest mode that shows the content when a link points to something hidden. It also collapses to icons or moves to its own row on narrow screens. It includes its own Playwright and axe-core tests. See the [plugin's README](https://github.com/luka-sherman/mkdocs-audience-toggle) for all options. This site's setup is under `audience_toggle` in `mkdocs.yml`, with color overrides in `extra.css`'s `#audience-toggle` rule.

### [mkdocs-cheatsheet](https://pypi.org/project/mkdocs-cheatsheet/)

Expand All @@ -205,6 +205,21 @@ plugins:

A section with its own cheatsheet page, like Libraries here, shows up on the homepage as title-and-description cards only. The optional header button replaces the logo with a "Cheatsheet" link home. It includes its own Playwright and axe-core tests. See the [plugin's README](https://github.com/luka-sherman/mkdocs-cheatsheet) for all options. This site's setup is under `cheatsheet` in `mkdocs.yml`, with the flag rules in STRUCTURE.md's "Cheatsheet flags" section.

### [mkdocs-light-dark-toggle](https://pypi.org/project/mkdocs-light-dark-toggle/)

An always-visible two-button light/dark switch, replacing Material's native single-knob palette toggle (whose knob is the only clickable spot, and whose current scheme is the only thing you can see). It started as this site's own light/dark JavaScript, and I rewrote it as a published plugin.

```bash
pip install mkdocs-light-dark-toggle
```

```yaml
plugins:
- light_dark_toggle
```

Needs zero configuration to work: it ships with built-in sun/moon icons, unlike `mkdocs-audience-toggle` above where mode icons have no universal meaning and must always be supplied. The native palette radios stay in the DOM, hidden — their own JavaScript still applies and persists the scheme via `localStorage`, the plugin only drives them. It includes its own Playwright and axe-core tests. See the [plugin's README](https://github.com/luka-sherman/mkdocs-light-dark-toggle) for all options. This site's setup is under `light_dark_toggle` in `mkdocs.yml`, with color overrides in `extra.css`'s `#light-dark-toggle` rule.

## Theme

### Custom CSS
Expand Down Expand Up @@ -327,3 +342,23 @@ The domain is set up via the [docs/CNAME](docs/CNAME) file, which MkDocs copies
## License

The content and code in this repo are not licensed for reuse — see [LICENSE](LICENSE).

## AI usage

I used [Claude Code](https://claude.com/claude-code) for:

- Restructuring and rewriting existing content
- Scaffolding first drafts of library pages based off the site's existing [structure](STRUCTURE.md)
- Implementing new features (e.g. the [Essentials/Advanced toggle](#essentials--advanced-toggle))
- Propagating a content or naming change everywhere it's referenced (headings, anchors, homepage keyword links)
- Diagnosing and fixing test failures (structure, accessibility) instead of just rerunning them
- Verifying a change with `mkdocs build` and `pytest` before calling it done

How I managed it:

- Scoped to low-judgment, high-volume work — architecture and correctness stayed manual
- Rewrote drafts in my own words rather than publishing them as-is, so I kept my own mental model of the content I'm teaching from
- Checked output against [STRUCTURE.md](STRUCTURE.md)
- Increased [test coverage](#testing) and [continuous integration](#continuous-integration) to protect its integrity
- Scoped prompts to one task at a time to keep context down
- After one prompt "fixed" content in bulk on its own, I switched to: report gaps, don't auto-fix content on your behalf
12 changes: 6 additions & 6 deletions STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,16 +169,16 @@ shows library pages as title-and-description cards only, and their links appear
- **Card text.** Each page's front matter sets `cheatsheet_description` (falling back to
`description`), plus `cheatsheet_title`, `cheatsheet_icon` or `cheatsheet_title_suffix` (the
built-in/third-party badge on library cards) where the defaults don't fit.
- **Essentials mode.** A flagged heading that carries `data-fcm-hide="essentials"` passes it to
- **Essentials mode.** A flagged heading that carries `data-audience-hide="essentials"` passes it to
its cheatsheet line or link, so the audience toggle hides both together. A whole card is
hidden with `cheatsheet_attrs: {data-fcm-hide: essentials}` in front matter.
hidden with `cheatsheet_attrs: {data-audience-hide: essentials}` in front matter.
- **Verify with a real build.** A renamed heading moves its flag with it, so the cheatsheet can't
go stale, but `mkdocs build` still prints a `WARNING` for any broken link elsewhere.
- **Marking content "advanced" for the Essentials/Advanced toggle** — the header's segmented
control is provided by the `mkdocs-audience-toggle` plugin (configured under `plugins:` in
`mkdocs.yml`) and hides content marked `data-fcm-hide="essentials"`. On a content page, append
`{ data-fcm-hide="essentials" }` to the heading line (e.g. `functions.md`'s
`## Decorators { data-fcm-hide="essentials" }`). This hides that heading, everything up to the
`mkdocs.yml`) and hides content marked `data-audience-hide="essentials"`. On a content page, append
`{ data-audience-hide="essentials" }` to the heading line (e.g. `functions.md`'s
`## Decorators { data-audience-hide="essentials" }`). This hides that heading, everything up to the
next heading of the same or higher level, and its sidebar entry. The cheatsheet picks the
marker up from the heading, so there's no second place to tag.
Which spots to mark, and what counts as advanced/niche vs. core, is a per-editor judgment call
Expand Down Expand Up @@ -211,7 +211,7 @@ Pick the existing type that matches the branch, don't invent new ones without a
| `??? info` | Defining a term/concept adjacent to the page but not the topic itself. |
| `??? failure` | The negative counterpart to a `success` branch — "this didn't work, here's what to do about it" (e.g. workspace.md's "download Python here" branch when `python --version` doesn't show 3.x.x). |
| `??? ai` | Opinion/meta content specifically about learning with or around AI (e.g. index.md's FAQ tabs on whether/how to use AI while learning) — not used for teaching content about Python itself. |
| `??? efficiency` | A runtime/space aside naming the cost behind a choice already shown in prose (e.g. list vs. set membership, `sort()` vs. `sorted()`) — usually a Big O difference, occasionally a constant-factor one (`.get()` vs. two hash lookups, vectorized NumPy vs. a Python loop) where it's still worth flagging but doesn't change the O(...) class. Wrap it in `<div data-fcm-hide="essentials" markdown="block">` on a page that participates in the Essentials/Advanced toggle (skip it on a page that doesn't, like the library reference pages), and close with a link to [style.md's "Efficiency"](docs/practices/style.md#efficiency) section. Formalizes a tradeoff the surrounding prose already states in plain language; doesn't introduce the tradeoff for the first time. |
| `??? efficiency` | A runtime/space aside naming the cost behind a choice already shown in prose (e.g. list vs. set membership, `sort()` vs. `sorted()`) — usually a Big O difference, occasionally a constant-factor one (`.get()` vs. two hash lookups, vectorized NumPy vs. a Python loop) where it's still worth flagging but doesn't change the O(...) class. Wrap it in `<div data-audience-hide="essentials" markdown="block">` on a page that participates in the Essentials/Advanced toggle (skip it on a page that doesn't, like the library reference pages), and close with a link to [style.md's "Efficiency"](docs/practices/style.md#efficiency) section. Formalizes a tradeoff the surrounding prose already states in plain language; doesn't introduce the tradeoff for the first time. |
| `!!! example` | An always-open side-by-side comparison the reader is meant to see without a click, not a branch — e.g. "how to loop each type," showing every collection type's loop pattern in one visible table. |

Default to collapsed (`???`), not always-open (`!!!`) — an always-open admonition competes with
Expand Down
15 changes: 0 additions & 15 deletions docs/javascripts/a11y_patches.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,8 @@
});
}

// pymdownx.tasklist (style.md's checklist) renders each `- [ ]` as a real
// <input type="checkbox" disabled> wrapped in its own <label>, but that
// label contains only the checkbox itself — the item's actual text sits
// outside it as a plain sibling — so the checkbox has no accessible name
// (axe: "Form elements must have labels"). It's also disabled and never
// toggles, so it's not a real control a screen reader user can act on;
// hiding it from the accessibility tree lets that text be read on its own
// instead of prefixed with a confusing, non-interactive "checkbox" role.
function hideDecorativeTaskListCheckboxes() {
document.querySelectorAll(".task-list-control > input[type=checkbox]").forEach((el) => {
el.setAttribute("aria-hidden", "true");
});
}

function initA11yPatches() {
makeScrollRegionsFocusable();
hideDecorativeTaskListCheckboxes();
}

// Material's navigation.instant swaps page content via JS without a full
Expand Down
43 changes: 43 additions & 0 deletions docs/javascripts/library_spans.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
(function () {
// Recomputes --library-span (the add-on-library boxes' grid span) once the
// mkdocs-audience-toggle plugin hides some of their cards in Essentials
// mode — the one bit of library-grid behavior no plugin owns, since it's
// specific to this site's own card layout.

// The cheatsheet plugin's --cards-N class sizes each library box for its full card count; hiding cards
// in Essentials mode leaves boxes too wide. Recompute the visible count
// into --library-span so extra.css can override --cards-N while active.
function updateLibrarySpans() {
document.querySelectorAll(".library-grid > .md-cheatsheet__group").forEach(function (box) {
const cards = box.querySelectorAll(".grid.cards > ul > li");
let visible = 0;
cards.forEach(function (li) {
if (getComputedStyle(li).display !== "none") visible++;
});
if (visible > 0) box.style.setProperty("--library-span", Math.min(visible, 4));
});
}

// The plugin hides content (and sets html[data-audience-mode]) synchronously
// from its own script; a MutationObserver callback always fires as a
// separate microtask after that synchronous work finishes, so this stays
// correctly ordered regardless of which script's DOMContentLoaded/
// document$ subscriber happens to run first.
function setUp() {
if (window.__librarySpanObserverBound) return;
window.__librarySpanObserverBound = true;
new MutationObserver(updateLibrarySpans).observe(document.documentElement, {
attributeFilter: ["data-audience-mode"],
});
updateLibrarySpans();
}

// navigation.instant swaps page content via JS without a full reload, so
// DOMContentLoaded only fires once. document$ is Material's own
// observable that emits on every page change, instant or not.
if (window.document$) {
window.document$.subscribe(setUp);
} else {
document.addEventListener("DOMContentLoaded", setUp);
}
})();
Loading
Loading