Editing the documentation
The site is built with Astro Starlight and lives in docs/.
It is published to GitHub Pages on every push to main and on every release.
docs/├── astro.config.mjs # sections (topics), redirects, theme, languages├── screenshots/ # large screenshots (examples/screenshots.py)├── scripts/│ ├── api_docs_en.py # English API descriptions│ ├── api_docs_es.py # Spanish API descriptions│ ├── gen_api.py # generates reference/*.md in both languages│ ├── library_data.py # the component library: one entry per component│ ├── gen_library.py # generates library/*.mdx in both languages│ ├── shading_nodes_data.py # node catalog texts and examples (en/es)│ ├── gen_shading_nodes.py # generates shading/nodes/*.mdx in both languages│ ├── anim_webp.py # records animated WebP captures (shared library + CLI)│ ├── library_anim.py # what each component's animated preview does│ ├── library_shots.py # generates public/library/*.webp (animated previews)│ ├── component_shots.py # generates src/assets/components/*.png│ ├── example_shots.py # generates example and tutorial screenshots│ └── sync-changelog.mjs # CHANGELOG.md → What's new page (both languages) + stable version└── src/content/docs/ # the pages (Markdown / MDX) ├── guides/ ... # English (default language, site root) └── es/ # Spanish translations, same pathsPreview
Section titled “Preview”cd docsnpm installnpm run dev # http://localhost:4321/pyzed/Languages
Section titled “Languages”The documentation is multilingual. English is the default language and is served at the site root; each translation lives in a folder named after its locale:
| Language | Files | URL |
|---|---|---|
| English | src/content/docs/guides/canvas.md |
/pyzed/guides/canvas/ |
| Spanish | src/content/docs/es/guides/canvas.md |
/pyzed/es/guides/canvas/ |
- A translation uses the same path as the English page, under
es/. - When you add a page, add it in both languages. If a translation is missing, Starlight shows the English page with a notice instead of a 404.
- Internal links include the locale:
/pyzed/guides/...in English pages and/pyzed/es/guides/...in Spanish ones. - Relative paths to images and examples have one more
../in the Spanish files, since they are one folder deeper.
Sections and navigation
Section titled “Sections and navigation”The top-level sections (Learn, Library, Designer, Shading, Theming, Distribute, Showcase, Integrations, AI and
Reference) are the topics list in astro.config.mjs, using the
starlight-sidebar-topics plugin. Each one is a link in the header
navbar (src/components/Header.astro) and has its own sidebar; on phones they appear at the top of the menu. A page
belongs to the section whose sidebar lists it, usually through the folder it lives in:
| Section | Folders |
|---|---|
| Learn | getting-started/, tutorials/{basic,intermediate,advanced}/, guides/, why/, examples, faq |
| Library | library/, blocks/ (and the older components/) |
| Designer | editor/, download |
| Shading, Theming, Distribute | shading/, theming/, packaging/ |
| Showcase | showcase/, games/ |
| Integrations, AI | integrations/, ai/ |
| Reference | reference/, changelog, contributing/ |
- The overview of a section gets a hero when its frontmatter has
section: { eyebrow, icon, lead }(src/components/PageTitle.astro). - Link to pages with
<NavCards>(src/components/NavCards.astro); a card withouthrefshows as “coming soon”. - Show screenshots and short recordings with
<Media>(src/components/Media.astro): an image fromsrc/assets(optionally with alightversion) or an mp4/webm loop with aposter, plus a caption. - Write terminal commands with
<Commands uv={`...`} pip={`...`} python={`...`} />(src/components/Commands.astro, in an.mdxpage). It draws theuv,pipandpythontabs (synced across the site) and splits each into macOS/Linux and Windows PowerShell blocks when they differ. Write{py},{venv},{activate},{vpy}and{env NAME=value}where the systems differ; the component fills inpython3/py,source .venv/bin/activate/.venv\Scripts\Activate.ps1and so on. Commands that only exist on one system go in<Tabs syncKey="os">instead. - Theme swatches come from
src/components/theme-tokens.json(written bydocs/scripts/gen_theme_tokens.py), and the theming screenshots fromdocs/scripts/theming_shots.py. - Moving a page:
git mvit in both languages, add the old path toMOVEDinastro.config.mjsso the old URL redirects, and update the links to it (git grepthe old path, includingdocs/scriptsandpython/).
Diagrams
Section titled “Diagrams”Write diagrams as mermaid code blocks; the astro-mermaid integration renders them on the page
and follows the light/dark theme:
```mermaidflowchart LR view["App.view()"] --> tree[Widget tree] --> render[render.rs] --> iced[Iced]```Regenerating the reference and images
Section titled “Regenerating the reference and images”These need Pyzed installed (uv sync, or maturin develop --release). For a new component, follow
Documenting a component.
python docs/scripts/gen_api.py # API reference, both languagespython docs/scripts/gen_api.py --lang en # English only (or --lang es)python docs/scripts/gen_api.py --check # reports public names without documentationpython docs/scripts/gen_shading_nodes.py # shader node catalog, both languagespython docs/scripts/gen_shading_nodes.py --check # fails if the pages are stale or an example breakspython docs/scripts/library_shots.py # animated previews of the component librarypython docs/scripts/component_shots.py # component illustrationspython docs/scripts/example_shots.py # example and tutorial screenshotspython examples/screenshots.py # large screenshotsWhen you add a public function, describe its parameters in docs/scripts/api_docs_en.py and
docs/scripts/api_docs_es.py, then regenerate the reference. CI runs gen_api.py --check.
Animated captures
Section titled “Animated captures”docs/scripts/anim_webp.py records a Pyzed view as a looping animated WebP, with a scripted pointer,
keyboard and clock. Every component of the library is recorded with it
(library_shots.py), and any other page can use it for its own clips: animations, audio
widgets, a form being filled in.
It uses the same off-screen renderer as pz.screenshot (no window, display or GPU), through
pyzed._core.record: the pointer’s position is passed to every frame, so hover and pressed
states and tooltips show up; events (press, release, wheel, keys, typed text) reach the widgets
between frames; and time moves by exactly one frame per frame (animations, spinners, carets and
presences follow it), so a clip comes out the same on every machine, however long a frame takes.
import pyzed as pzfrom pyzed import uifrom anim_webp import record_webp, hover, press, type_text, drag, wait, leave
class Login(pz.App): def __init__(self): self.email = ""
def view(self): return pz.column([ ui.input(self.email, lambda v: setattr(self, "email", v), placeholder="Email"), ui.button("Sign in", lambda: None, variant="solid"), ], spacing=8, width=280)
record_webp( Login(), [press("input"), type_text("ada@example.com"), hover("Sign in"), press(), wait(600), leave()], out="docs/public/media/login.webp", size=(360, 160), # canvas, in logical pixels (recorded at 2x) theme="One Light", crop="content", # trim to what changes, plus a margin poster="docs/public/media/login.still.webp",)record_webp takes an App (instance or class) or a function that returns the widget. Steps:
| Step | What it does |
|---|---|
wait(ms) |
time passes (animations, spinners and carets move) |
move(target, ms), hover(target, ms, hold) |
the pointer glides to target (and stays) |
press(target=None, hold), click |
a left click, held for hold ms |
drag(start, end, ms) |
press, glide, release (sliders, splitters, color fields) |
type_text(text, cps) |
types into the focused field |
key(name, mods="") |
one key: "Enter", "Tab", "Down", "a" with "ctrl"… |
scroll(lines, target=None) |
the wheel over target |
leave(ms) |
the pointer leaves (hover states end) |
call(fn), set_state(**attrs) |
change the app’s state between frames |
theme(name) |
switch the app’s theme |
A target is a point (x, y), a text shown in the view ("Sign in": its center), "input" /
"input:1" (the first / second text field), "center" (the middle of what is drawn), "@0.25,0.5"
(a point relative to what is drawn), (target, dx, dy) to aim beside one, or fn(ctx) -> (x, y).
Widgets drawn without text (switches, sliders, dropdown items) are aimed at with points.
Encoding: identical consecutive frames are merged into one longer frame, the clip loops forever,
and frames are lossy (quality=80, method=6) or lossless, whichever is smaller (WebP’s
allow_mixed), which keeps text sharp. Aim for 150 KB or less per clip and never more than
400 KB; shorten the clip, crop it or lower fps (20 by default) if it grows. Views can read
clip_time() (seconds since the clip started) to move things that would otherwise stand still.
The lower-level pieces are there too: record_frames() (the frames, as RGBA), images(),
crop_box(), encode_webp(), fit_webp() (lowers the quality until the file fits a size; record.py
encodes its anim=True clips with it), info(path) (frames, duration and size of a WebP). From the command
line:
python docs/scripts/anim_webp.py out.webp --code 'ui.button("Save", None, variant="solid")' \ --steps 'hover("Save"); press(); wait(500)' --size 320x120 --crop --theme "One Light"On a page, serve the files as they are, from docs/public/: Astro’s image pipeline would turn
an animated WebP into a still picture. src/components/AnimatedImage.astro writes the markup: the
<img> with its width, height, alt, loading="lazy" and decoding="async", and the still frame
for readers who ask for reduced motion:
<picture> <source srcset="/pyzed/media/login.still.webp" media="(prefers-reduced-motion: reduce)" /> <img src="/pyzed/media/login.webp" width="360" height="160" alt="Typing an email and signing in" loading="lazy" decoding="async" /></picture><Media clip="..."> uses it for the clips in public/media/, and the library’s
src/components/library/Clip.astro for the ones in public/library/ (sizes from
public/library/manifest.json). What each library preview does is in docs/scripts/library_anim.py;
library_shots.py --probe <slug> prints the texts and fields a script can aim at.
What’s new and versions
Section titled “What’s new and versions”flowchart LR
C["Commits (gitmoji)"] --> PR["Pull request"] --> CI["CI: lint, tests, wheels"]
PR --> MAIN["main"]
MAIN --> CL["CHANGELOG.md"]
T["tag vX.Y.Z"] --> R["GitHub Release + PyPI"]
CL --> S["sync-changelog.mjs"] --> WEB["What's new + version badge"]
MAIN --> D["docs.yml → GitHub Pages"]
docs/scripts/sync-changelog.mjs generates the What’s new page in both
languages from CHANGELOG.md (repository root) on every build. Each version is a
## [X.Y.Z] - YYYY-MM-DD heading; versions with a suffix (1.0.0-rc.1, 0.2.0b1) are marked as
pre-releases. The most recent one without a suffix is shown as the latest stable release in the
header, the home page and the What’s new page.