Add SVG to the property panel export formats (PNG/JPEG/WEBP/SVG).
Selected nodes export as a tightly cropped SVG subtree preserving
ancestor rotation/flip/clip state; with no selection the full page
is serialized. Desktop writes through the save dialog, web reuses
the blob download path.
Serialization matches canvas rendering: tight-bounds path fitting
with non-scaling strokes, user-space gradient endpoints, implicit
strokes for open point paths, intrinsic-size image tiling, rounded
image clipping, per-line text without duplicate ids, and
collision-free defs ids.
The browser-text raster cache keyed on RGBA, so identical glyphs in N
colors were N cache entries — text-heavy pages blew the 512 cap and
re-rasterized + re-uploaded GPU textures every frame while panning.
Text now rasters once as a white alpha mask (color and alpha out of
the key) and tints at draw via a bounded, LRU ColorFilter cache;
eviction is LRU instead of FIFO. Color-emoji segments keep the legacy
baked-color path since they ignore fillStyle. Verified visually:
before/after screenshots differ only in sub-24/255 antialiasing-edge
quantization (0.45% of pixels, max delta 14).
Every undoable gesture deep-cloned the whole document plus all
component prototypes, and the 100-entry history held full copies —
peak memory ~100x document size. Snapshots now share unchanged
top-level nodes and components via Arc, diffed against a
transition-specific anchor (previous snapshot / popped destination /
pre-clear redo head) with id+deep-equality gating; restores and batch
rollback materialize by clone, and the instance-override snapshot
repair goes through copy-on-write so no shared history state is ever
mutated. Commit cost drops from an allocation-heavy full clone to an
allocation-free equality walk plus changed-subtree clones; memory to
one document plus deltas. Pinned by a random-sequence property suite
against a full-clone reference history.
Decoded node changes were held twice (cloned into by_key, cloned again
into TreeNodes), every symbol subtree was deep-cloned per component
instance, and every embedded blob was eagerly base64-encoded whether
referenced or not. Materialization now moves out of by_key via a
deduplicated latest-wins adjacency (repeated GUID records collapse to
one edge), symbol prototypes are shared via Rc, and blob encoding is
lazy and memoized so unreferenced blobs are never encoded.
Each selected node cost three full-tree walks (flex-flow check,
ancestor-dedup, find) per nudge/drag frame — near-quadratic on
select-all. One recursive walk now threads parent-flex and
ancestor-in-set context down; equivalence with the old algorithm is
pinned by a 10-case reference-implementation test suite.
Preview now presents ONE framed root inside a fixed device frame —
Phone 390x844 / Desktop 1440x900, inferred from the root width
(<=500 -> Phone) with a floating 3-segment switcher (Phone / Desktop /
Canvas, i18n'd across all 15 locales) overriding inference until exit.
Content keeps authored size (no reflow, no content scaling); overflow
scrolls vertically inside the frame, clamped to the pinned-nav top
bound. A bottom nav (semantics.role=nav bottom-anchored, or the
flush/full-width/<=120px heuristic) pins to the frame bottom in a
second clipped paint pass and the content viewport shrinks past it.
One DeviceFrame struct owns every transform: paint, the screen->scene
inverses (strip + letterbox dead zones resolve at surface-resolution
time), and the per-gesture presentation capture, so drags never flip
surfaces mid-gesture. Device mode fails closed — no wheel / pan /
pinch / modifier-zoom ever reaches the editor viewport (pinch gets its
own host entry, split from the line-wheel path at the desktop call
sites). Reconcile now reports { repaint, switched } so warning-only
passes stop recentering; screen switches re-infer, reset scroll and
rebuild the frame.
Preview scenes now take paint from the promoted document but GEOMETRY
from the unpromoted layout tree via the design canvas's exact layout
pass — honoring preserve_authored_geometry for Figma imports — so
preview positions match design mode by construction (was: full taffy
re-solve, elements shifted 100+ px on preserve docs, 2 px per promoted
hug widget). Hit-testing maps taps through the deepest painted node's
scene/runtime rect pair with a per-gesture anchor (pointer capture),
so drags never remap through neighbours mid-gesture. Input dispatch
split to preview/input.rs for the 800-line cap.
Pairs with the following device-frame commit (preview/mod.rs already
declares the present module it introduces).
The revision-bump regression test pushed chat_session_launch.rs past the
800-line ceiling (834); the inline tests module moves to
chat_session_launch_tests.rs (mirrors chat_session_launch_selection_tests.rs),
bringing the file to 756 lines.
The layer-panel row cache keys on document_revision, which made revision
correctness load-bearing: host paths that mutate content through raw
active_children_mut() without mark_document_changed() left the panel
painting stale rows (and is_dirty save-tracking wrong) — most visibly
the starter-frame clear before a design run. Adds the bump at the four
raw sites (starter clear, image-search apply, both pick-fill handlers);
all other raw-mutation sites audited as already bumping via history.
Every painted frame eagerly built and cloned a full accesskit TreeUpdate
(including a whole-tree layer walk) even with no assistive technology
attached, and the image-search session re-walked the node tree on every
RedrawRequested. The a11y build now happens inside update_if_active with
a wake-on-activation event so AT still converges to a current tree, and
the image scan is gated on (document revision, active page) with a full
session reset at every document-replacement seam (open, import, MCP
replace) so revision-0 aliasing cannot serve stale state.
The chat transcript re-laid-out every message twice per paint and again
on every mouse move; the layer panel re-walked the whole tree per frame;
the icon, markdown, and font panels re-filtered per frame while open.
Each now resolves from an owner-scoped cache: the transcript builds once
per frame/event (value-hash key, last-resolver-owns slots, forced owner
rotation at every session mutation), layer rows key on document revision
plus page with rotation at every document replacement, and panel filters
recompute only when their query/content/generation inputs change,
returning shared Rc results.
The viewer deep-cloned the whole LayoutScene every dirty frame and kept
an unconditional rAF loop alive forever. The scene is now shared via Rc,
and a DirtyState machine (wrap-safe generation counters, single
mark-and-arm funnel, bounded failure breaker, pending payload slots,
cancel-on-detach) schedules frames on demand so an idle tab sleeps.
resolve_refs_for_canvas cloned the whole document twice per scene
rebuild; the lookup copy existed only as a borrow-checker workaround.
Lookups now borrow the original document, which stays read-only for the
entire resolve pass while resolved nodes accumulate separately.
Every primitive draw allocated and deleted a CK.Paint per call per frame,
and the imported-family text path re-ran getGlyphIDs coverage scans on
every draw and measure. Long-lived fill/stroke paints are now mutated per
call, and coverage segments + per-family fonts are cached with bounded
eviction, invalidated when the imported-font registry changes.
The hover dashed outline painted at page level from the unrotated
doc-space bounds, so a rotated frame (and any element inside one)
showed an axis-aligned outline detached from its rendered geometry.
Accumulate the root-to-node flip/rotation chain during the scene
paint walk and replay it around the dashed rect so the outline lands
exactly where the node renders. Hit-testing already handled ancestor
rotation, so trigger area and visual now agree.
`with_decorations(false)` strips the native frame entirely on Windows,
so DWM painted the borderless window as a flat rectangle with sharp
corners instead of the Windows 11 look. Opt back into
`CornerPreference::Round` and a hairline border matching the dark
chrome via casement's `WindowAttributesExtWindows`.
Introduce the ZSeven-W product family (zode, agent-rs, jian, noema)
with a new Ecosystem section across all 15 locales, and reframe jian
from 'widget/render/event toolkit' to 'GPU-Skia UI framework' in the
Tech Stack table, Ecosystem table, and Project Structure tree.
Chrome text rendered blurry on Retina: all chrome/canvas text goes
through the offscreen 2D-canvas raster path (drawBrowserText), which
rasterized glyphs at CSS (1x) resolution. Those bitmaps are then drawn
onto the CanvasKit surface while a scale(dpr, dpr) transform is active,
magnifying them dpr-x — half-resolution on 2x displays.
Supersample the offscreen text canvas by the device pixel ratio and
composite it back at 1/dpr so glyphs land at native device resolution.
Thread the real dpr from Rust (CanvasKitBackend::new + resize_for_display)
into the bridge via a new setDpr method; cache key includes dpr and
stale bitmaps are dropped on dpr change.