Donner 0.5.1
Embeddable browser-grade SVG2 engine
Loading...
Searching...
No Matches
Design: v0.5 Release

Status: Shipped (2026-04-16) Updated: 2026-04-17

Summary

Release checklist and implementation plan for Donner v0.5, tagged v0.5.0 on 2026-04-16. This release bundled all work since v0.1: renderer abstraction, tiny-skia software backend, text rendering (6 phases), all 17 SVG filter primitives with SIMD performance, the in-tree editor, WASM editor, and .svgz support.

Animation (9 phases), composited rendering, and interactivity (6 phases) are scoped out of v0.5 and deferred to v1.0.

See the v0.5 Retrospective below for follow-up work to carry into the next release.

Goals

  • Ship a clean v0.5 release with all tests passing and CI green.
  • Reduce test thresholds to <100px pixel differences where possible.
  • Verify both CMake and Bazel builds for Skia and tiny-skia backends.
  • Update README and docs to reflect current capabilities.
  • Demonstrate new features (filters, text) with examples.
  • Harden with a full fuzzer run before release.

Non-Goals

  • Animation (`<animate>`, `<animateTransform>`, `<animateMotion>`, `<set>`) — deferred to v1.0.
  • Composited rendering and interactivity — deferred to v1.0.
  • New feature work beyond `<textPath>` (that's v1.0).
  • `<a>` and `<switch>` element support (v1.0).
  • 100% resvg test suite pass rate (known gaps are documented).
  • Upstream contributions (resvg harness integration is v1.0).
  • Deprecated SVG 1.1 features: `enable-background`, `BackgroundImage`/`BackgroundAlpha` filter inputs, SVG fonts, `<cursor>`, `<altGlyph>`, `<tref>`, CSS `clip` rect. These are removed in SVG 2 and will not be implemented. See [unsupported_svg1_features.md](../../docs/unsupported_svg1_features.md).

Next Steps

v0.5.0 was tagged and published on 2026-04-16. Remaining post-release work is tracked in the [v0.5 Retrospective](#v05-retrospective) below and carried forward into v1.0.

  • **Deferred to v1.0:** Full per-entity recomputation beyond the current style-only dirty path, CSS differential restyling, float feImage fragments (Phase 10), SubregionCropRect architecture, `ch` unit glyph measurement, bidirectional text, and all composited-rendering / spatial-index work.

Implementation Plan

Phase 1: Test Threshold Reduction

Root-cause and fix pixel differences to bring thresholds below 100px where possible.

  • e-feConvolveMatrix-01430348px diff Fixed: floating-point divisor precision issue (kernel sum ~-1e-16 instead of 0.0). Now 6584px diff (filter region boundary edges). Threshold set to 7000.
  • Threshold bump to 0.02 — Bumped kDefaultThreshold from 0.01 to 0.02. This eliminated many false positives from sub-pixel color-space rounding differences.
  • Audit all thresholds >100px — Reviewed every WithThreshold(...) override. Results with 0.02 threshold:
    • Removed 15 overrides (now 0 diffs): feConvolveMatrix-024, feDropShadow-003, feOffset-007, feSpotLight-007/008, feTile-001/002/004/005, filter-004/009/010/014/017/ 018/028/046/053/054.
    • Tightened 8 thresholds: feTurbulence-018 (9000→500), feTurbulence-019 (24000→1100), filter-019 (16400→4100), filter-027 (21000→20000), filter-026 (12000→11000), feMerge-003 (10500→7000), feImage-007/008 (6600→4500), feImage-009 (18200→13500), feImage-010 (18900→14000), feDropShadow-001 (400→300), feDropShadow-002 (200→150).
    • Remaining high thresholds: feGaussianBlur-012 (40000, rotated asymmetric blur), feImage-012/021/024 (34000–36000, complex fragment refs), feFlood-008 (18000, OBB transform), filter-011/026/027 (8000–20000, subregion/transform effects).
    • Root causes for remaining diffs (all architectural/fundamental, not reducible to <100px without major rework):
      • feImage fragment references (13 tests, 4K–36K px): 8-bit intermediate buffer for fragment rendering. Would need float rendering pipeline.
      • Complex transforms (6 tests, 2K–19K px): Coordinate rounding differences at sub-pixel level with skew/rotate transforms. Inherent to integer rasterization.
      • Filter subregion/boundary (4 tests, 4K–39K px): Convolution/blur boundary edge handling, rotated asymmetric blur (different algorithm than resvg).
      • linearRGB quantization (2 tests, 1K–2K px): sRGB↔linearRGB round-trip through uint8 storage between filter nodes. Would need float intermediate buffers.
      • Other (8 tests, 129–990 px): Turbulence noise precision, nested pattern AA, lighting transform, dropShadow blur boundary — all within 2x of default threshold.
  • Verify all tests pass — Both renderer_tests and resvg_test_suite green.

Phase 2: Fuzzer Run

Run every fuzzer for 10 minutes, debug and fix any crashes.

  • Enumerate all fuzzers — 21 fuzzer targets built with --config=asan-fuzzer:
    //donner/base/xml:xml_parser_fuzzer
    //donner/base/xml:xml_parser_structured_fuzzer
    //donner/base/encoding:decompress_fuzzer
    //donner/base/fonts:woff_parser_fuzzer
    //donner/base/parser:number_parser_fuzzer
    //donner/css/parser:anb_microsyntax_parser_fuzzer
    //donner/css/parser:color_parser_fuzzer
    //donner/css/parser:declaration_list_parser_fuzzer
    //donner/css/parser:selector_parser_fuzzer
    //donner/css/parser:stylesheet_parser_fuzzer
    //donner/svg/parser:path_parser_fuzzer
    //donner/svg/parser:svg_parser_fuzzer
    //donner/svg/parser:svg_parser_structured_fuzzer
    //donner/svg/parser:transform_parser_fuzzer
    //donner/svg/parser:list_parser_fuzzer
    //donner/svg/parser:clock_value_parser_fuzzer
    //donner/svg/parser:animate_value_fuzzer
    //donner/svg/parser:animate_motion_path_fuzzer
    //donner/svg/parser:animate_transform_value_fuzzer
    //donner/svg/parser:syncbase_ref_fuzzer
    //donner/svg/resources:url_loader_fuzzer
  • Run each fuzzer for 10 minutes — All 21 fuzzers ran with --max_total_time=600. Script: tools/run_all_fuzzers.sh (4 parallel, ~50 min total).
  • Triage crashes — 1 crash found:
    • path_parser_fuzzer: Assertion failure in Path::closePath() when consecutive z commands issued (e.g., M6 6 z z z). currentSegmentStartCommandIndex_ was kNPos on second close. Fixed by making consecutive closePath() a no-op when subpath is already closed.
  • Add regression tests — Crash input added to donner/svg/parser/tests/path_parser_corpus/. Unit test ConsecutiveClosePath added to PathParser_tests.cc.

Phase 3: Build Verification

Verify CMake and Bazel builds across both backends.

  • Fix compiler warnings — Zero warnings across both Bazel backends (tiny-skia and Skia). Fixed unused variable mapW in DisplacementMap.cpp.
  • CMake build (tiny-skia)gen_cmakelists.py + cmake -S . -B build && cmake --build build ✅. Linker warnings about duplicate libraries (cosmetic, not errors).
  • CMake build (Skia)cmake -DDONNER_RENDERER_BACKEND=skia ✅. Build succeeds. Linker warnings about duplicate libraries (cosmetic, not errors).
  • CMake build with tests-DDONNER_BUILD_TESTS=ON compiles ✅. 59/66 tests pass. 7 failures all from Bazel runfiles not available in CMake (tests needing data files: fonts, golden images, CSS test data, resvg SVGs). Not a code issue — CMake runfiles integration is a known limitation.
  • Extend build report — Deferred to Phase 11 (Release). Update tools/generate_build_report.py to differentiate between Skia and tiny-skia backends:
    • Report binary sizes for both backends.
    • Report build times for both backends.
    • Show feature matrix (text, filters, WOFF2) with size impact.
  • Regenerate build report — Deferred to Phase 11. python3 tools/generate_build_report.py --all --save

Phase 4: Documentation Updates

  • Branding update — Tagline applied across README.md, Doxygen mainpage (docs/introduction.md), Doxyfile (PROJECT_BRIEF), and MODULE.bazel.
  • README.md — Updated: new branding, full supported elements list (53 elements including all 17 filter primitives, text, animation), code examples use Renderer (not the old full-Skia renderer class), removed outdated limitations section, added tiny-skia backend mention.
  • docs/building.md — Added CMake configuration options table (5 options with defaults), Bazel configuration options table (4 configs), updated build time estimates for both backends, removed "experimental" label from CMake section.
  • Configuration reference — Integrated into building.md as tables (CMake and Bazel).

Phase 5: Examples

Create and refresh examples used during release prep.

  • Filter example — No dedicated example needed; svg_to_png handles SVGs with filters transparently (filters are applied automatically during rendering).
  • Animation exampleexamples/svg_animation.cc (programmatic: renders frames to PNG) and experimental/viewer/svg_animation_viewer.cc (ImGui: play/pause, timeline scrubber, speed control, frame stepping).
  • Text example — No dedicated example needed; svg_to_png handles SVGs with text transparently (text rendering is automatic).
  • Interactivity exampleexamples/svg_interactivity.cc (programmatic: hit testing, bounding boxes) and experimental/viewer/svg_interactivity_viewer.cc (ImGui: real-time hover tracking, click-to-select with bounds overlay, element inspector, cursor type, event log).
  • Update existing examplessvg_to_png, svg_tree_interaction, custom_css_parser, svg_viewer all build and work correctly.
  • Add CMake targets — All examples (including svg_animation and svg_interactivity) have CMake targets via gen_cmakelists.py.

Phase 7: CI Verification

  • Push to branch — 12 commits pushed to tiny-skia branch.
  • main.yml (Bazel) — Both ubuntu-24.04 and macos-15 green.
  • cmake.yml — Both ubuntu-24.04 and macos-15 green.
  • coverage.yml — Coverage report uploads successfully.
  • codeql.yml — No new security findings (only runs on main/PRs to main).
  • Fix any CI failures — Fixed 5 issues:
    1. Missing libfontconfig-dev/libfreetype-dev in Linux CI apt-get
    2. Skia fontconfig API change (SkFontScanner_Make_FreeType())
    3. e-feImage-005 Linux OOM (OS memory pressure on 7GB runner)
    4. Added 256MB defensive allocation cap in Pixmap::fromSize
    5. Added allocation guards to FloatPixmap, GaussianBlur, Morphology, DisplacementMap

Phase 8: Code Coverage

  • Run coverage report./tools/coverage.sh generates HTML report in coverage-report/. Fixed macOS coverage: added target_compatible_with to Skia/tiny-skia backend targets so --config=latest_llvm doesn't try to compile Skia's objc_library targets.
  • Verify ≥80% line coverage81.7% line coverage (20,973/25,669 lines), 84.0% function coverage (4,414/5,253), 73.4% branch coverage (8,054/10,966).
  • Coverage gaps acceptable — Above 80% threshold. Remaining gaps are in expected areas (renderer backends, error paths, binary tools).

Phase 9: Incremental Invalidation

Implement the v0.5 subset of computed-tree invalidation so unchanged documents can skip recomputation and DOM mutations carry precise dirty metadata. Detailed design in incremental_invalidation.md.

  • DirtyFlagsComponent — Per-entity dirty flags (Style, Layout, Transform, WorldTransform, Shape, Paint, Filter, RenderInstance, ShadowTree) with compound flags for common mutation patterns.
  • Mutation hookssetStyle, updateStyle, setClassName, trySetPresentationAttribute, tree mutations (appendChild, removeChild, insertBefore, replaceChild, remove) all set appropriate dirty flags with cascading propagation to descendants.
  • Fast pathinstantiateRenderTree() skips all recomputation when the render tree has been built, no entities are dirty, and no full rebuild is required. Repeated renders of unchanged documents are O(1) instead of O(n).
  • Selective per-entity recomputation — Partially implemented. StyleSystem can recompute entities marked with DirtyFlagsComponent::Style when a full style recompute is not required, but RenderingContext::ensureComputedComponents() still tears down shadow trees, clears computed render state, and reruns layout, text, shape, paint, and filter passes when any entity is dirty.
  • CSS differential restyling — When a stylesheet or class attribute changes, determine which selector matches changed and re-resolve only affected elements.

Phase 10: Filter Pipeline Float Precision

Rework the filter graph pipeline to use float intermediate buffers, reducing pixel diffs from architectural uint8 quantization.

  • Float inter-node buffers — Changed FilterGraph.cpp to store all intermediate results as FloatPixmap (float sRGB) between nodes instead of Pixmap (uint8 sRGB). Per-node sRGB↔linearRGB conversion now uses float↔float precision, eliminating lossy uint8 round-trips.
    • All named buffers, previousOutput, source, and paint inputs use FloatPixmap.
    • Convert to uint8 sRGB only at the final output stage via toPixmap().
    • All 17 filter primitives now use float overloads exclusively in the graph executor.
    • Float bilinear interpolation for feImage scaling (was uint8).
  • Float feImage fragment rendering — Render feImage fragment references to FloatPixmap instead of uint8 Pixmap. This addresses the 4K–36K px diffs across 13 feImage tests.
    • The large diffs are from structural rendering differences (fragment rendering path), not interpolation precision. Requires a float rendering path in the renderer itself.
  • Subregion CropRect architecture — Adopt Skia-style CropRect wrapping: run filter operations on expanded regions, then crop the output. This replaces the current mid-pipeline applySubregionClipping that zeros pixels before downstream nodes can read them.
    • For each node, expand the working region by the downstream kernel size.
    • Apply subregion clipping only to the final node's output.
    • Expected to fix filter-011 (7K), filter-019 (4K), filter-027 (19K).
  • Local-space blur for rotated elements — When an element has a rotation/skew transform and an asymmetric blur (σX ≠ σY), apply the blur in element-local coordinates before transforming to device space. This addresses feGaussianBlur-012 (39K px).
    • Detect asymmetric + non-identity transform combinations in the renderer.
    • Inverse-transform, blur, then re-apply transform.
  • Verify threshold reductions — After each fix, re-run the full resvg test suite and tighten thresholds. Target: all tests below 1000px diffs where possible.

Phase 11: &lt;textPath&gt; Implementation

Implement &lt;textPath&gt; element support for text rendered along arbitrary paths. Detailed design in text_rendering.md.

  • SVGTextPathElement class — Element class, ElementType enum, AllSVGElements, parser registration for href, startOffset, method, side, spacing attributes.
  • Path-based text layoutPath::pointAtArcLength() for path sampling, glyph repositioning in both TextLayout and TextShaper, per-glyph tangent rotation.
  • Renderer support — Per-glyph transforms in TinySkia backend.
  • Tests — 37 resvg e-textPath-* tests passing with thresholds. 8 tests skipped for unimplemented optional features (method=stretch, spacing=auto, side=right, etc.).

Phase 12: Text Properties & Test Coverage

Comprehensive text rendering improvements and test coverage expansion.

  • Per-character positioning (Phase 8) — x/y/dx/dy/rotate attribute lists with global indexing across tspan boundaries. e-tspan-011: 17K→1.2K pixels.
  • letter-spacing / word-spacing (Phase 9) — CSS properties parsed and applied in both layout engines. Non-zero letter-spacing tests: 16K→1-3.6K.
  • baseline-shift (Phase 10) — baseline/sub/super/length/percentage values per-span.
  • writing-mode (Phase 11) — Vertical text layout with HB_DIRECTION_TTB. CJK vertical tests at 1.8K-3.2K pixels. 18 tests passing.
  • alignment-baseline (Phase 12) — Per-span baseline override reusing DominantBaseline enum.
  • Gradient/pattern text fills — drawText() uses makeFillPaint()/makeStrokePaint() for gradient and pattern paint servers on text. a-fill-031: 22K→12K, a-stroke-007: 19K→12K.
  • Filter allocation guards — 256MB caps on FloatPixmap, GaussianBlur, Morphology, DisplacementMap allocations. Prevents std::bad_alloc on memory-constrained systems.
  • vw/vh/vmin/vmax viewport units — FontMetrics.viewportSize resolves viewport units against canvas size. e-rect-034/036: 137K→0 pixels.
  • em/ex/rem font-relative units — FontMetrics carries element computed font-size and root font-size. e-rect-022/023/031 pass with 0 diff.
  • Enabled 36 previously-skipped tests — 27 text-related, 4 mask/marker (Skia-only crash resolved), 2 rect viewport units, 3 rect font-relative units.
  • Threshold tightening — ~50 tests tightened to within 15% of actual diffs for better regression detection.
  • Mask-on-mask infrastructure — ResolvedMask.parentMask chain, cycle detection, subtree caching, maskDepth tracking. Rendering needs luminance multiplication API.
  • .clangd config — Fixed false positive entt.hpp errors in Claude Code and VS Code by refreshing compile_commands.json and adding .clangd config.
  • AGENTS.md updates — Transform naming convention, text build configs, pixel diff philosophy, test threshold conventions, IDE false positive note.

Phase 13: Mask-on-Mask Rendering

Correct mask luminance composition when a <mask> element has its own mask= attribute.

  • Chain resolution — resolveMask() resolves parent mask chain with cycle detection.
  • Subtree caching — instantiateOffscreenSubtree() handles already-traversed subtrees.
  • Render chain order — renderMask() renders innermost-first (matching view draw order). pushMask/popMask LIFO stack composes luminances correctly.
  • Per-entity parent mask shadow trees — Added ShadowBranchType::OffscreenParentMask. Phase 1 creates separate shadow trees on each masked entity for the parent mask, avoiding view iterator sharing conflicts. e-mask-026: 160K→~0 pixels.
  • FontManager integration — Stored in registry context after loadResources(). Used by ShapeSystem for ch unit "0" glyph measurement via stb_truetype.
  • Stroke em units — a-stroke-dasharray-005/a-stroke-dashoffset-004 were already working, just incorrectly skipped. Both pass with 1px diff.
  • Enabled 5 disabled test categories — a-font (44 passing), a-filter (17 passing), a-flood (7 passing), a-dominant-baseline (1), a-clip (2). Plus 6 individual re-enabled tests (color-interpolation-filters, fill-033, fill-opacity-004, shape-rendering-008, stroke-opacity-004). ~76 new passing tests.
  • e-mask-025 — Mutual recursion cycle detection works but rendering differs from reference.
  • e-mask-027 — Shadow entity mask resolution (separate from mask-on-mask).

Phase 14: Release

The release is cut in this exact order — the build report commit is the commit that gets tagged, so every other code change must already be on main before the build report lands:

  1. Every other release-blocking code change merges to main first.
  2. RELEASE_NOTES.md is updated on main with the final highlights for this version (this is a normal commit, not the tagged one).
  3. As the last commit, regenerate docs/build_report.md against a clean tree and commit it as a dedicated release commit (e.g. Release v0.5.0: regenerate build report). Nothing else goes in that commit.
  4. Tag v0.5.0 points at the build-report commit. Any code fix discovered after that point is a v0.5.1 concern, not a retroactive tag move.
  • Final pre-release validation — Local warning-clean builds and the full Bazel test matrix across default/Skia/text-full completed on 2026-04-16. Remaining work: final CI verification on the release commit. Doxygen warnings are waived for v0.5 and are not a release blocker.
  • Release notes draftedRELEASE_NOTES.md contains a v0.5.0 entry with highlights, breaking changes, included artifacts, and example usage.
  • Update release notes with final highlightsRELEASE_NOTES.md was refreshed on 2026-04-16 during the final docs pass (including the final coverage figures). This commit must land before the build-report commit.
  • Generate build report as the release commitb8bb66bf Release v0.5.0: regenerate build report regenerated docs/build_report.md on a clean tree with Skia/tiny-skia differentiation and was the sole content of the tagged commit.
  • Create release tag and publishv0.5.0 tagged on the build-report commit; GitHub release published 2026-04-16T20:05:42Z using the RELEASE_NOTES.md entry as the body.
  • Verify release artifacts — GitHub release shows v0.5.0 with donner-svg_darwin_arm64, donner-svg_linux_x86_64, and SLSA attestations attached.
  • Post-release follow-up — Update ProjectRoadmap.md to mark v0.5 as shipped and announce the release. Retrospective items captured below.

Release Checklist

Copied from release_checklist.md and filled in for v0.5.

Pre-Release: Code Quality

  • Warning-clean buildbazel build //donner/..., --config=text-full, and the legacy full-Skia configuration all completed warning-clean on 2026-04-16.
  • Doxygen warning gate waived — Doxygen warnings are waived for v0.5 and do not block the release. The final docs pass still covers doc-comment review and generated-site review.
  • Tests passbazel test //donner/... completed green on 2026-04-16 across:
    • Default (tiny-skia)
    • --config=text-full
    • legacy full-Skia configuration
  • Fuzzers run — All 21 fuzzers ran for 10 minutes and the discovered crash was fixed with regression coverage.
  • CMake build verified — Both Skia and tiny-skia CMake builds were validated during release prep.

Pre-Release: Documentation

  • Audit doc comments — Completed a final public-API doc-writer spot-check on 2026-04-16 (SVGParser, SVGDocument, generated landing pages). Remaining Doxygen warnings are part of the waived backlog and are concentrated in internal/editor surface area.
  • Update examples and code snippets — README/examples and release-prep examples were refreshed to cover current features.
  • Update Doxygen pages — Regenerated and reviewed the HTML output on 2026-04-16 after installing Graphviz locally and fixing the actionable broken refs / duplicate anchor. The remaining Doxygen warning backlog stays waived for v0.5.
  • Update markdown docs — Final sweep completed on 2026-04-16 across docs/*.md, docs/design_docs/*.md, and RELEASE_NOTES.md, including stale coverage/release metadata fixes.
  • Update README.md — README reflects the current v0.5 feature set.
  • Remove experimental gates on shipped features — No shipped feature still declares static constexpr bool IsExperimental = true.

Pre-Release: Release Notes

  • Write RELEASE_NOTES.md entryRELEASE_NOTES.md already includes a drafted v0.5.0 section.

Final Commit

The build report is the last commit that lands before the tag. It must happen after every other blocking fix and after the release-notes update, and it must be its own dedicated commit — the tagged commit is the build-report commit, nothing else.

  • All other blocking changes merged to main — every release-blocking code change and the final RELEASE_NOTES.md update are already on main before the build report commit is prepared.
  • Generate build report — Regenerate docs/build_report.md against a clean tree with Skia/tiny-skia backend differentiation. Commit it on main as the dedicated release commit (e.g. Release v0.5.0: regenerate build report). Nothing else goes in this commit.
  • CI green on the build-report commit — Release-commit CI went green before tagging.

Release

  • Create release tagv0.5.0 tagged on the build-report commit (b8bb66bf).
  • Push tag — Tag pushed to origin.
  • Verify release artifacts — Release page renders, linux/macos binaries and SLSA attestations are attached.

Post-Release

  • Update ProjectRoadmap.md — Mark v0.5 as shipped and update the design-doc table.
  • Announce — Post the release to the relevant channels.

Testing and Validation

  • Unit tests: bazel test //donner/... (both the default tiny-skia configuration and the legacy full-Skia configuration at the time).
  • Image comparison: renderer_tests and resvg_test_suite with reduced thresholds.
  • Fuzzing: All 21 fuzzers × 10 minutes, crashes fixed and regression-tested.
  • Build matrix: Bazel (Linux/macOS) × CMake (Linux/macOS) × {Skia, tiny-skia}.
  • CI: All GitHub Actions workflows green on the release commit.
  • Manual verification: Animated splash SVG renders in browser and via svg_to_png.

v0.5 Retrospective

Captured on 2026-04-17 after the v0.5.0 release. These are the issues that surfaced during the cut that should be fixed before the next release.

Release-process bugs (carry to v0.6)

  1. BCR publish failed. Publishing Donner to the Bazel Central Registry on the v0.5.0 tag did not succeed. Needs debugging against the process captured in 0018-bcr_release.md — identify the failure mode, fix the tooling, and re-run on v0.5.x or v0.6.
  2. Binary-size report in Doxygen is broken. The Doxygen-rendered build report (binary-size tables, feature matrix) is not displaying correctly — the treeview appears empty. Root-cause the generation/integration step and ensure the report renders in the hosted Doxygen site before the next tag.
  3. Design docs pollute the Doxygen sidebar. All 25+ design docs currently appear as top-level sidebar entries. They should be grouped under a single "Design Docs" section so the developer-facing sidebar stays navigable.
  4. Doxygen lazy-lists filter/element pages. The generated site enumerates every filter primitive (and other element categories) as individual sidebar entries. Condense these into a single "All Elements" / "Filter Primitives" index page so the sidebar isn't dominated by element enumeration.
  5. Too many public Bazel targets. The public-target surface grew significantly in v0.5 — especially inside //donner/editor/... — with many internal helpers visible to external consumers. Audit visibility across the tree and tighten to //donner/...-private wherever the target isn't a deliberate public API. The editor subtree is the priority; it leaked the most targets.

vNext features to finish (not shipped in v0.5)

These workstreams were never part of v0.5's feature surface — they were in-progress vNext work that sat alongside the v0.5 effort, with code landing on main but not enabled or exposed in the release. They should be the first feature work to finish before new v1.0 scope is picked up:

  1. Sandboxing — Scaffolding landed on main during v0.5 development (wire-format fuzzer, sandbox_diff CLI, hardened-child test fixes), but the broader editor sandbox story from 0023-editor_sandbox.md was not enabled for v0.5 users. Finish the remaining milestones.
  2. Composited rendering0025-composited_rendering.md is in draft and was not part of v0.5. Prerequisite for fluid editor dragging and the v1.0 interactivity story; needs to graduate from draft to shipped.
  3. Geode renderer on a real GPU0017-geode_renderer.md has Phase 0–2 complete and the resvg suite green on MSAA, but Geode is not enabled as a user-facing backend in v0.5 and has not been validated on physical GPU hardware (only CI / software adapters). Finish the real-GPU path before moving to new renderer work.

Next-release features (after the items above)

Once the retrospective bugs and open-feature close-outs are done, the next tranche of feature work to pick up:

  1. <a> element support — Treat <a> as a grouping element for rendering (Issue S18 in 0024-proposed_issues_2026q2.md). Small scope, 3 resvg tests unblocked.
  2. <switch> element + systemLanguage — Conditional processing per SVG2 §5.9 (Issue S4 in 0024). Medium scope, ~15 resvg tests unblocked.