FrameForge v2 (2.11.0) keeps its documents, grammar, schema, prose, and
Python code in sync — the Pydantic models are the source of truth and everything
else is generated from or checked against them.
FrameForge aims to be an agent-native visual-authoring substrate — one
structured, programmable foundation (SDK + MCP) for producing professional visual
assets across documents, decks, diagrams, books, and letters today, extending
toward UIs, vector graphics, logos, and design systems. See PURPOSE.md
for the full why and scope.
Status: FrameForge v2 is a proposed, not-yet-conformantly-implemented system. The prose and grammar are design targets to verify. The parts you can actually run — the models, the generated schema, the validator, and the codemod — are the parts to trust.
src/frameforge/ ← integration package: conform.py, cli.py, fontpack.py.
docs/schema/
frameforge-v2.schema.json ← GENERATED from the models (119 $defs). Do not hand-edit.
build_schema.py ← regenerates the schema; `--check` fails if it drifts.
docs/grammar/
frameforge-v2.ebnf ← the consolidated CORE grammar (base + P1–P4); styling deferred to the module.
frameforge-v2-style.ebnf ← the AUTHORITATIVE CSS style module (adopted verbatim at 2.2.0).
docs/spec/frameforge-v2-spec.md ← the normative prose (folds P1–P4 + the style module + cascade + corrections).
tooling/
validate.py ← structural (models) + static/geometric rules the schema can't express.
codemod.py ← migrates a document to HEAD (stroke split, size→sizing, gradient, aliases).
render_fixtures.py ← SVG render CLI driver (re-exports the Renderer; `--check-overflow` text-fit gate).
render_chromium.py ← optional Headless-Chromium SVG→PNG raster renderer (CSS-fidelity path).
render_fg_doc.py ← the matplotlib PROXY renderer, patched to HEAD (sanity check only).
pdf_to_frameforge_yml.py ← optional PyMuPDF PDF → fixed-layout FrameForge YAML extractor.
(HTML export lives in the package → `ff-render --to html`. It is driven by the SAME
builder as SVG through `painters/html.py` (`HtmlPainter`) and assembled by
`backends/html.py` (the DocumentRenderer port), so its object-type coverage equals
SVG's — semantic HTML shell, inline SVG artwork, contract- and golden-tested.)
gen_status.py ← GENERATES docs/FIXTURE-STATUS.md from the validator (`--check` gates drift).
gen_docs.py ← GENERATES the docs-site pages (reference/gallery/spec/grammar plus SDK docs).
gen_capability_manifest.py ← GENERATES docs/capability-manifest.json (core/SDK/MCP status per capability).
check_grammar_sync.py ← GATES grammar ⇄ models drift (core profile); `--strict` for full parity.
check_accessibility.py ← GATES page reading_order integrity; warns on missing image alt (a11y).
render_golden.py ← GATES b1/ oracle SVG output against a pinned hash lock (golden).
tests/fixtures/ ← the fixture corpus (declared versions span 2.0.0–2.4.x; top-level YAML gated by `make validate`, the b1/ oracle by test_head.py).
b1/ ← the 8 AUTHORITATIVE fixtures (the oracle the tests assert against).
conftest.py ← shared pytest bootstrap (sys.path + the frameforge shadow-module rule).
tests/
test_head.py ← assertions: authoritative fixtures validate, schema in sync, style surface, P3.
test_docs_in_sync.py ← doc drift gate: numbers, Layout paths, generated-doc policy, fixture status.
test_doc_examples.py ← validates every complete FrameForge example shown in the prose.
docs/ + mkdocs.yml ← the MkDocs site: `index.md` is hand-written, `sdk*.md` are committed generated snapshots, transient generated pages are ignored, and non-site sources (schema/spec/grammar, plus seed/ and decisions/) are `exclude_docs`.
docs/capability-manifest.json ← GENERATED machine-readable capability status {core, sdk, mcp} (ADR-0002).
docs/error-codes.md ← every validator finding code + SDK rule_id: meaning and fix (sync-tested).
docs/output-space.md ← what FrameForge can generate: the verified-today backends + the conceptual output space (anchor drift-gated by tests/test_output_space_doc.py).
docs/BRAND.md ← the brand guideline (proposal); §3 governs the generated logo.
docs/FIXTURE-STATUS.md ← GENERATED validator status for the delivered fixtures (gen_status.py).
docs/codebase-standards.md ← the elevated engineering bar, status-tagged (Enforced / Adopted / Target).
AGENTS.md ← programmatic CLI/tooling reference (make targets, tooling flags, workflows).
Dockerfile + docker/ ← the font-rich SDK/MCP runtime image (`make docker-build`).
CHANGELOG.md ← version, the breaking change + migration, conformance classes, rec. resolution.
pyproject.toml + uv.lock ← the real hatchling package since 2.5.0 (`[tool.uv] package = true`): `uv sync` installs it editable with the `ff-render`/`fg-font` console scripts; dep groups dev/render/browser/pdf/pdfout/metrics/mcp/vision.
Makefile + .github/workflows/ ← `make check` = the local gate; CI mirrors it (+ a docs build/deploy job).
The contract, the authoring SDK, and the renderer are separate distributions,
not directories in this tree: frameforge-api
(SOURCE OF TRUTH for the Pydantic document contract),
frameforge-sdk (authoring,
measurement, static validation) and
frameforge-render (the
renderer DDD boundary: domain, application, infrastructure). uv sync installs
them from their repositories at the commits uv.lock pins — a checkout beside
this one is not required, and is not what a build resolves.
- frameforge-viewer is the independent React display package extracted from this repository with its viewer history preserved. FrameForge publishes a versioned
frameforge-render-bundlemanifest plus measured SVG/PNG/PDF artifacts; the artifact viewer consumes those outputs without needing the viewing host’s fonts. Its direct-document mode remains an explicitly separate browser renderer. - frameforge-fonts owns the exact-face closure and shared metrics provider. SDK measurement/validation,
frameforge.conform, the renderer, and MCP can all consume one provider overfp_version: 1bytes.
The companions own their npm/Python dependencies, CI, releases, and upstream compatibility checks. They are siblings, not subdirectories of this codebase.
- Schema ⇄ models.
docs/schema/frameforge-v2.schema.jsonis produced byDocument.model_json_schema().build_schema.py --checkreturns non-zero if the committed file differs from a fresh build — so they cannot silently drift. - Validator ⇄ models.
validate.pyvalidates against the sameDocumentmodel, then layers the §3.3/§3.6/§9.6 rules. - Codemod ⇄ validator. The codemod's migrations are exactly the breaking/renamed forms the validator rejects; running it makes a legacy document pass.
- Grammar ⇄ models. The EBNF is a view of the models (the source). This is now
enforced, not trusted:
tooling/check_grammar_sync.py(thegrammar-checkgate) introspects the models and diffs the EBNF, failing CI on core-profile drift — a mismatched object/flowtypediscriminator or a divergent enum. Out-of-profile grammar (charts, the UML zoo, connectors) is reported as a non-blocking warning;--strictdemands full parity.
The project is managed with uv. uv sync once
creates .venv with the runtime deps and the dev group (see
pyproject.toml), and installs the frameforge package itself — putting the
ff-render and fg-font console scripts on PATH; prefix commands with uv run.
A fresh git clone is all it needs: the family packages resolve from their own
repositories at the commits uv.lock pins. To develop the engine against a
sibling checkout instead of the pinned commit, make dev-link reinstalls the
ones you have next to this tree as editable; a later uv sync undoes it.
uv sync # create/populate .venv
# schema is generated and in sync
uv run python docs/schema/build_schema.py --check
# validate the delivered tracked fixtures — 53/53 zero errors in docs/FIXTURE-STATUS.md
# text-fit diagnostics run by default; add --no-text-fit only for structure-only checks
make validate
# migrate a legacy v2 document to HEAD
uv run python tooling/codemod.py path/to/legacy.fg.json --in-place --bump
# the HEAD assertions (13/13 green)
uv run python tests/test_head.py
# or the full pytest suite (also run by `make check`)
uv run pytest
# SVG proxy renderer (dependency-free core) -> out/render/index.html
uv run python tooling/render_fixtures.py --all
# optional browser-fidelity raster renderer (install Playwright + Chromium first)
uv sync --group browser
uv run playwright install chromium
uv run python tooling/render_chromium.py tests/fixtures/effects.fg.yaml --out out/chromium
# optional PDF text/layout extractor (install PyMuPDF first)
uv sync --group pdf
uv run python tooling/pdf_to_frameforge_yml.py input.pdf output.frameforge.yml
# optional MCP server for AI feedback loops:
# Python SDK code -> generated FrameForge YAML -> validation + rendered SVG/PNG,
# plus the coordinate-aware measurement layer (see "Subsystems" below)
uv sync --group mcp
uv run --group mcp python -m frameforge_mcp
# or: make mcp
# optional local web UI over the same MCP feedback functions
make live # http://127.0.0.1:8789
# choose a port when needed: make live LIVE_PORT=8790
# the whole local gate — every gate on the Makefile `check` target
make check
# build & browse the generated documentation site (Material theme, live reload)
make docs-serveFor positioned text, use the shared authoring/layout metric contract rather than an empirical slack factor:
from frameforge_sdk import closure_metrics, fit_width, validate_static_rules
from frameforge.conform import render_pages_with_stats
family = ["Inter", "DejaVu Sans", "sans-serif"]
provider = closure_metrics(
"book.fp", strict=True,
generics={"sans-serif": "Inter"},
)
w = fit_width(
"Advanced SQL", font_family=family, font_size=13,
metrics_provider=provider)
layer.text([64, 64, w, 22], "Advanced SQL",
style={"font_family": family, "font_size": 13, "white_space": "pre"})
report = validate_static_rules(document, metrics_provider=provider)
svgs, stats = render_pages_with_stats(document, metrics_provider=provider)Install the local umbrella group with uv sync --group metrics; published
consumers install frameforge-sdk[metrics] plus frameforge-render[fonts].
The provider outranks real_metrics, and diagnostics report
metrics_mode: closure. Without a provider, the historical host-bound real
and deterministic estimate modes remain available. MCP render and fit_text
calls accept the closure path as font_closure and an optional
font_generics map.
See the closure migration guide and the
runnable examples/closure_metrics.py.
The matplotlib proxy renderer (tooling/render_fg_doc.py) needs the extra
render dependency group:
uv sync --group render # adds matplotlib + pillow- MCP measurement layer (
frameforge/mcp/+frameforge/vision/). Besides the author→render loop, the MCP server exposes a coordinate-aware raster→vector reconstruction toolset:measure_image(grids/rulers/regions/ landmarks/zoom crops),mark_points,overlay_images, a statefulworkspacepin board,construct_vectors,vectorize_image(region/outline/potrace/ layers tracing),score_reconstruction(numeric edge-match convergence), andmap_coordinates(homography/warp). The live tool registry is enumerated in docs/capability-manifest.json; it needs thevisiondependency group. Every runtime knob (FRAMEFORGE_*env vars — session/publish roots, transport budgets, render ceilings, Chromium flags, the VLM lane) is documented with its real default in the gated table at the frameforge-mcp README; container-only knobs live in docker/README.md. - Vector Construction Coach (
src/frameforge/coach/). A staged construction loop over the SDK — style-grammar checks, layer-order rules, a silhouette gate, SVG ingest/cleaning, and figure-proportion helpers. It coaches construction discipline; it is not a curve-drawing engine. Demos:coach_demo.pyand the othercoach_*clients in theframeforge-examplerepo. - Docker runtime (
Dockerfile+docker/). The font-rich canonical SDK/MCP runtime (thousands of font families baked in) for font-faithful raster verification:make docker-build/docker-mcp/docker-shell/docker-fonts; client wiring indocker/mcp.docker.json. - Live UI (
frameforge/live/). A local web view over the same MCP feedback functions for humans watching/driving a session:make live(http://127.0.0.1:8789, port viaLIVE_PORT). - Font determinism toolchain (
src/frameforge/fontpack.py+tooling/render_chromium.py; ADR-0004).fg-font—--listresolvable families,--check DOC(non-zero exit if a content font would substitute),--pack DOC --out P.fp(portable pack of the exact TTFs + sha256 manifest;--fetchprovisions missing families from the open Google Fonts corpus so packs build on thin hosts),--install P.fp(scoped fontconfig) — andrender_chromium.py --font-pack P.fp, which scopes fontconfig to the pack before Chromium launches so measure == render on any host. Installed as a console script since 2.5.0:uv run fg-font …(or themake font-*targets;tooling/fg_font.pyremains as a direct-run shim). Silent font substitution is banned: layout emits afont_substitutionwarning to diagnostics and stderr whenever a requested face is missing.
Programmatic entry points for all of the above — every make target and tooling
CLI with flags — are catalogued in AGENTS.md; the examples
cookbook lives in the sibling frameforge-example repo (docs/examples.md there).
At 2.2.0 the authoritative CSS style module is adopted verbatim (Style is the
~80-property bag; TextStyle/StrokeStyle are projections of it; fill/stroke are
Paint; gradient stops use position; class + css escape). 2.1.0 folded Patches
1–4, made the stroke single-form breaking, renamed size → sizing, generated the
schema from Pydantic, and added the validator + codemod. Full detail in CHANGELOG.md.
These predecessor documents are not included in this HEAD bundle — their content is folded into the artifacts above; they are listed only for historical context:
FrameForge-2.0.0-Specification.md— the spec reverse-engineered from the renderer, superseded bydocs/spec/frameforge-v2-spec.md.FrameForge-2.0.0-Specification-Complement.md— the reconciliation that produced the recommendations this release implements; its §8 actions are resolved inCHANGELOG.md.- The four standalone patch documents (P1–P4) are folded into the grammar, the spec, and the models here; they remain useful as rationale.
- This is a proposed format. No renderer is conformant; the proxy renderer uses DejaVu stand-in fonts and is a sanity check, not a fidelity guarantee.
- The grammar is consolidated and paren-balanced and is a view of the models — now
kept honest by the
grammar-checkgate (check_grammar_sync.py) for the core profile; the models remain the authority if the two ever disagree. - Font pinning enables deterministic layout only up to a stated rounding tolerance (a defined shaping model is also required) — not pixel-exact identity (§9.6).
- The current delivered top-level fixture status is generated in
FIXTURE-STATUS.md; at this snapshot 53/53 have zero errors. Advisory warnings, when present, are recorded there instead of summarized by hand here. - What FrameForge can — and deliberately will not — generate is mapped in docs/output-space.md: the backends wired today (whose entry points are drift-gated) plus the conceptual output space the IR admits.
- How FrameForge presents itself — name, voice, colour, type, and the logo — is the
docs/BRAND.md guideline (a proposal). The logo is generated: the
mark/wordmark source of truth is
frameforge_logo.pyin the siblingframeforge-examplerepo, which writes the masters out of tree (_tmp/brand/) — brand assets are non-core and are not tracked in this repository.