Architecture¶
vexy-dexypy is a six-stage pipeline. Stages 1 to 3 run once per page. Stages
4 to 6 run once per strategy. Stages pass typed objects from
vexy_dexypy.model to each other and write JSON sidecars, so a later stage can
run again without the earlier ones.
This page follows one build through the stages:
Stage 1: read¶
Input: a Source. Source.parse sets kind to url for http:// and
https:// input, and to file for anything else, and computes the slug
www-vexy-art-lines.
The reader (vexy_dexypy.readers.read) chooses a reader from the fetch mode:
- Local files:
StaticReader. offline:OfflineReader, which runssingle-fileormonolith.live(default):LiveReader, if Playwright is installed.- Otherwise: the registered reader with the highest
can_readscore. The static reader scores0.5, the dynamic reader0.3.
Output: a PageDoc with the captured HTML at out/www-vexy-art-lines/raw/index.html,
the asset folder, a content hash, and metadata (title, generator).
The page is then classified. classify lowercases the HTML and counts
fingerprint strings per page type:
| Page type | Fingerprints |
|---|---|
webflow |
data-wf-page, w-mod-js, data-w-id, webflow.js |
mkdocs-material |
mkdocs-material, md-content, data-md-component, md-typeset |
docusaurus |
__docusaurus, theme-doc-markdown |
framer |
data-framer-name, framer.app |
bubble |
data-bb-id, bubble-element |
One hit gives confidence 0.5; each extra hit adds 0.25, up to 1.0. The
highest confidence wins; no hits means generic. The result is written to
_meta/page.json.
Stage 2: analyze¶
paginate.analyze opens the HTML in Chromium at the stage size (1280×720 by
default) and measures every section, article, h1, h2, h3, img,
.card and [class*="hero"] element. plan_breaks walks the visible elements
from top to bottom:
- An element taller than the stage adds breaks every stage height inside it
(reason
overflow). - An element that starts more than one stage height (less 50 px) below the
current slide start adds a break at its top (reason
overflow). - An
h1,h2,sectionorarticlethat starts more than 40% of a stage height below the current slide start adds a break (reason is the tag name).
Breaks are sorted and duplicates within 1 px are removed. The slide count is the number of breaks plus one.
Without Playwright, a static fallback adds one evenly spaced advisory break per
<section> or <h2> and marks the plan {"mode": "static-fallback"}.
With --vision, a full-page screenshot is sent to the vision model, and its
y values are added as vision breaks. The merged breaks are de-duplicated
within 10% of the stage height. If the model is unreachable, the heuristic plan
is kept.
Output: a SlidePlan, written to _meta/slideplan.json.
Stage 3: normalize¶
pick_importer chooses the importer whose name matches the page type, or the
generic importer. Every importer first tries the in-browser preprocessor: it
injects the bundled vexy-dexyjs.js into the page and calls
window.VexyDexy.preprocess(config) with the framework name, stage size and
breaks. The returned HTML is a sequence of <section> slides.
If the browser step fails, each importer has a Python fallback:
webflow: keeps<section>elements that are not menu, nav, footer, mask, header or banner chrome, and tags each one light or dark from its background colour. With fewer than two sections, it removes chrome and distributes the page blocks into the planned slide count.mkdocs-material: removes the Material header, sidebars, navigation and footer, and splitsarticle.md-content__innerath1andh2.generic: extracts the article body withtrafilaturaif installed, otherwise stripsnav,header,footer,aside,scriptandstyle, then splits at headings.docusaurus: removes page chrome and splits.theme-doc-markdown.bubble: changesposition: absoluteinline styles toposition: relative, then hands the page to thegenericimporter.framer: hands the page to thegenericimporter.
Output: out/www-vexy-art-lines/normalized/normalized.html. Asset references
are rewritten from assets/ to ../raw/assets/ so they still resolve.
Stage 4: prepare¶
preexport.prepare creates out/www-vexy-art-lines/playwright/ and
out/www-vexy-art-lines/reveal/, writes _paged.css and _theme.css into
each, and builds the engine input for the strategy's chassis (paged, reveal,
impress or marp). See Engines.
Output: a RenderJob per strategy.
Stage 5: render¶
The orchestrator runs the two strategies concurrently. Each exporter writes
_combined.pdf, and a copy goes to _meta/cache/.
Local pages are never opened as file://. A short-lived HTTP server on
127.0.0.1 serves the folder two levels above the HTML file, so
../raw/assets/ links, web fonts, fetch() calls and ES modules behave as on
a real site. Navigation waits for the load event and then pauses 400 ms.
Stage 6: write¶
writers.write_deck splits _combined.pdf with pypdf into
01-slide.pdf, 02-slide.pdf, …, optionally converts them to SVG, and writes
index.html. The orchestrator then writes _meta/run-summary.json:
{
"source": "https://www.vexy.art/lines/",
"framework": "webflow",
"slides": 12,
"results": [
{"strategy": "playwright", "ok": true, "slides": 11, "error": null},
{"strategy": "reveal", "ok": true, "slides": 9, "error": null}
]
}
The numbers in this example are illustrative.
Error types¶
All errors derive from VexyDexError in vexy_dexypy.errors:
| Error | Raised when |
|---|---|
UsageError |
A flag or config value is invalid (exit code 1) |
ReadError |
Fetching, archiving or reading the source fails |
NormalizeError |
A normalizer fails |
ExportError |
An engine fails; isolated to its strategy |
WriteError |
Splitting or SVG conversion fails; isolated to its strategy |
VisionError |
The vision model is unavailable or returns unusable data |
Source layout¶
src/vexy_dexypy/
cli.py # Fire commands
settings.py # flags + TOML -> frozen Settings
model.py # Source, PageDoc, Break, SlidePlan, Strategy, RenderJob, DeckResult
orchestrator.py # stage sequencing and concurrency
classify.py # page-type fingerprints
paginate.py # slide-break planning
vision.py # optional vision refinement
dom.py # shared BeautifulSoup helpers
preexport.py # CSS and deck wrapping
writers.py # split, SVG, preview
_browser.py # loopback server, browser launch, vexy-dexyjs injection
readers/ importers/ exporters/
assets/ # vexy-dexyjs.js, reveal.js, impress.js
The full design is in the 24-chapter specification in the repository's
spec/ folder.