Commit Graph
820 Commits
Author SHA1 Message Date
bflatastic c334aa7e66 README: new screenshots of the current interface, with 3D Revolve and Dimension tool showcases
Seven app screenshots retaken at 2240x1400 (UI at 1.6x), two of them new: a 3D still life with the Revolve dialog (examples/still-life.vectorcraft) and a bracket drawing with linked dimensions (examples/bracket-drawing.vectorcraft). The still life also joins Made in VectorCraft. The older examples are saved again in the current native format; they render the same.
2026-10-16 09:53:12 +07:00
Fabio William Conceição b04e3ac366 G12: Collect for Export from Layers panel and layer ids
Layers ≡ and row menus collect art via assets.add; a layer id expands to the art under it (including sublayers) so MCP and the panel match Object › Collect for Export.
2026-10-16 06:16:36 +07:00
Kamil KwiatkowskiandClaude Opus 5.5 a14942de9c Effects: Round Corners also rounds corners where a curve meets a line
A corner whose anchor carries a handle (where a curve meets a line, or two
curves meet) was left sharp. It now gets Live Corners' round cut
(geom::corners, #1144), reaching `radius` along each side and trimming a
curved side. Corners between handle-less anchors are rounded exactly as
before, and a closed path still starts where its first corner's rounding
starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 06:16:35 +07:00
dotCooCoo 67a278b053 Commands: ids leave out an object listed with a group that holds it, as the selection does
Commands that read the selection through edit::selected_roots leave
out an object whose group is also selected (edit::roots_of). Fourteen
places that read `ids` kept an object listed together with its group.
Given ids that list an object and a group that holds it, Make Repeat
failed with "no such object", New Symbol failed or stored the object
twice in the symbol, and object.transform moved it twice (20 pt for a
10 pt move).

These places now pass explicit ids through edit::outermost, which
drops an id whose ancestor is also listed, with the filter that
roots_of runs on the selection (now edit::without_inner). Nothing else
is dropped or replaced: unlike roots_of, it keeps layers and
compound-path members. A list without nesting gives the same result as
before. Commands that take both ids and the selection as listed, such
as paint.setFill without item, are unchanged.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 06:16:35 +07:00
dotCooCoo c586cecf75 Effects: Glass loads its texture from an image file
Effect › Distort › Glass… offered only its four built-in textures. The
reference app's Texture menu also has Load Texture, which takes an
image file as the glass surface.

The Glass dialog now has a Load Texture… button. It reads a PNG, JPEG,
GIF, WebP, TIFF, BMP, PSD or PSB file within the pixel allowance the
renderer draws images in, and the Texture menu gains Loaded Texture.
The effect stores the image's gray levels in `textureImage` as a
grayscale PNG in base64, so the document saves the texture with the
effect, and copies of the effect carry it. effect.apply and
effect.setParams take `textureImage` as {path}, {name, dataBase64} or
the stored string; any other value is an error.

The texture lies one image pixel per point at Scaling 100%, centered on
the object and repeated. Each pixel's luma is the surface's height,
from 0 for black to 1 pt for white, and a transparent pixel counts as
middle gray. A Glass without `textureImage` draws and saves as before.

Docs: docs/mcp.md (Glass textures), docs/effects-parity.md,
docs/gaps.md, docs/target-app-parity.md.
2026-10-16 06:16:35 +07:00
dotCooCoo 7e9d0be723 Fields: unit fields take % in arithmetic, as in 3cm*50% and 50pt+25%
Unit fields and length strings read a percentage operand as a share of
a value. After * or /, the percentage scales the term before it:
3cm*50% is 1.5 cm and 3cm/50% is 6 cm. After + or -, it adds or takes
away that share of everything before it: 50pt+25% is 62.5 pt and
100pt+20pt-50% is 60 pt. Before this change these expressions read as
nothing, and the field kept its old value.

The parser now splits a sum at its last + or -, so a percentage applies
to the whole sum before it. Text without % gives the same values as
before, apart from float rounding. A percentage on its own (50%) still
reads as nothing, and percent and degree fields still read % as their
suffix.

Command length strings use the same parser: file.new's width and
height, the distance params and the length preferences.

Docs: docs/mcp.md, docs/ui-parity.md, docs/target-app-parity.md.
2026-10-16 06:16:35 +07:00
dotCooCoo da43c662e0 Export: type copied by a Transform effect that only moves it stays type
Exports bake live effects into plain art, and a Transform effect on
type or on its group or layer turned the type into glyph outlines, even
when the effect only moved it or made moved copies. A PDF reader could
not select or search that text.

The export bake now keeps such type as type, moved into each place the
effect puts its outlines. It does this for Transform effects without
scale, rotation, reflection or Random, for type whose characters are in
solid colors without underline or strikethrough, and that has no fills
or strokes of its own, inline graphics or perspective, and when the
copies do not overlap. Type under such an effect on its group or layer
also needs no transparency or effects of its own, and the group or
layer needs no fill, stroke or Pathfinder effect of its own. The export
still outlines other type under geometry effects, and the canvas and
Expand Appearance still draw outlines.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 06:16:35 +07:00
dotCooCoo 3b327d9512 EPS import: CMYK images keep their ink amounts
The PostScript interpreter that reads EPS and PostScript files turned
the samples of a CMYK image into display RGB and stored an RGBA PNG,
so the image lost its ink amounts. The DCTDecode filter decoded a CMYK
JPEG to RGB, so it lost its ink amounts too. The PDF importer keeps
the ink amounts of 8- and 16-bit CMYK images (in a CMYK TIFF, or the
CMYK JPEG itself).

A CMYK image (image or colorimage in DeviceCMYK, CIEBasedDEFG or a
four-component ICCBased space) now keeps its samples in a CMYK TIFF
(ImageBlob::cmyk_tiff), read through its Decode array, at every bit
depth the interpreter reads. A CMYK JPEG gives its samples as stored,
and the image's Decode array reads them: [1 0 1 0 1 0 1 0] undoes the
inverted samples of Adobe's CMYK JPEGs. The pixels that a MaskColor
or an ImageType 3 mask hides become a grayscale opacity mask over the
image, as in PDF import. A mask that hides nothing adds no mask. Gray,
RGB, indexed and spot color images and image masks stay RGBA PNGs.

Docs: docs/file-format-parity.md, docs/target-app-parity.md.
2026-10-16 06:16:35 +07:00
dotCooCoo ee5b0938aa Windows: after a graphics driver crash, the next start leaves that adapter out
When a graphics driver crashed the process while the window started,
as Intel's Direct3D 12 driver (igd12umd64.dll) did on an Intel UHD
Graphics 605 (#2243), the next start picked the same adapter again. The
restart without a failing adapter and the first-frame watch run inside
the process, so the crash ended them along with it.

Each start now writes graphics-start.json in the log folder before the
graphics start, adds the adapter once one is picked, and clears both
when the first frame reaches the screen or the app ends without
crashing. A start that finds an earlier start still recorded there
leaves that adapter out, as a restart does, logs it and shows a note in
the status bar. When the crash came while the adapters were being
listed (wgpu's Direct3D 12 backend creates a device on each GPU to list
it), the next starts list OpenGL's adapters alone, except on macOS. The
adapters stay left out until the app version or the Graphics Processor
preference changes, a start shows its first frame on one, or no adapter
is left. WGPU_BACKEND and WGPU_ADAPTER_NAME choose regardless of the
record.

Docs: docs/development.md, docs/target-app-parity.md.
2026-10-16 06:16:34 +07:00
dotCooCoo 261b64251d PDF/.ai import: PDF and .ai files open with the File Info they carry
A PDF, PDF-compatible .ai or .ait opened from its pages had an empty
File Info. Nothing read the file's document information
dictionary or its XMP packet, so file.info reported no author,
description or keywords, even for a PDF that VectorCraft exported.

vectorcraft_pdf::file_info reads both. The author, description,
keywords, copyright status, notice and URL and the created and modified
dates come from the XMP packet (dc:creator, dc:description, dc:subject
or pdf:Keywords, xmpRights:Marked, dc:rights, xmpRights:WebStatement,
xmp:CreateDate, xmp:ModifyDate), else from the dictionary's Author,
Subject, Keywords, CreationDate and ModDate. document.open,
file.newFromTemplate and file.revert put each value through file.info's
checks and leave out a value that file.info refuses. A packet that is
not well-formed XML leaves the dictionary's values. File Info does not
set the title, and a file restored from the document it carries keeps
that document's File Info. vectorcraft-pdf depends on roxmltree 0.21,
which usvg already brings into Cargo.lock.

Docs: docs/mcp.md (File Info), docs/file-format-parity.md,
docs/target-app-parity.md.
2026-10-16 06:16:34 +07:00
dotCooCoo d1089270ba Fonts: a removed font that type used leaves the list at the next font scan
When type had used a font, removing the font while the app ran left
it in the font menus, and the type kept drawing in it until the app
restarted. The font list is the loaded faces plus the scanned catalog,
and a scan replaced the catalog but kept every loaded face.

A scan now drops each loaded face whose file it did not catalog, with
the face's cached outlines. A family with no face left leaves the font
lists, and type set in it lays out in the fallback font and is
reported missing, as when a document opens without the font. When the
font is installed again, the next scan lists it and the type draws in
it again. Bundled fonts and fonts added as bytes have no file and
stay.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 06:16:34 +07:00
bflatastic 080d90ed7f Docs: the landings of 2026-10-15/16 in the ROADMAP log, languages in the ROADMAP, README and localization parity 2026-10-16 05:56:27 +07:00
Janiz-zzzandClaude Opus 5.5 280de29c86 Add Finnish interface language
Adds a Finnish (Suomi, `fi`) catalog with 4,770 entries, written in
sentence case from the English labels with ordinary Finnish
vector-illustration vocabulary. It covers menus, `tl!` literals and the
668 `@msg` status and error messages: `fi` is in KEEPS_MENU_NAMES and
COMPLETE_MESSAGES. Like the other complete catalogs, it leaves the
product, workspace and perspective preset names in English.

Plurals are one/other with the partitive singular after any number but
1 (1 taso, 2 tasoa). Any fi-* locale selects Finnish; sv-FI does not.

Tests: `cargo test -p vectorcraft-ui-egui --lib i18n` passes (47),
including the new finnish_reads_as_finnish and the bundled-font glyph
check.

Clean-room, AI-assisted (Claude Code) translation; a native speaker
checked it in the running app, but it has not been proofread line by
line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 05:44:50 +07:00
Patrik LermonandClaude Opus 5.5 a91129e422 Swedish interface translation (sv)
Adds Swedish (Svenska) as an interface language: 4770 strings, written from the English source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 05:27:45 +07:00
tydude001andClaude Opus 5.5 e12a670c40 packaging: RPM glibc floor names libm's GLIBC_2.35, so RHEL 9 refuses it
#1290 (from #1275) declared the glibc floor as `libc.so.6(GLIBC_2.35)(64bit)`. That doesn't stop
RHEL 9: its current glibc, `2.34-283.el9_8`, provides `libc.so.6(GLIBC_2.35)` through backports. On
Rocky Linux 9, dnf upgrades glibc to that build, installs VectorCraft 0.10.0, and `vectorcraft-cli`
then fails with ``/lib64/libm.so.6: version `GLIBC_2.35' not found``. The binaries' only GLIBC_2.35
symbols are `hypot` and `hypotf`, from libm, and RHEL 9 doesn't provide `libm.so.6(GLIBC_2.35)`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 05:26:32 +07:00
bflatastic f7ab3277e0 Docs: one row per area and milestone after the stacked doc edits (#1928) 2026-10-16 04:55:02 +07:00
Fabio William ConceiçãoandCursor 4614821372 docs: note pt-br completion in target-app-parity status
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 04:55:02 +07:00
Fabio William Conceição 671136fa66 G16: complete Brazilian Portuguese (pt-br) interface
Finish the partial pt-br catalog so menus, tl! literals and status/error
messages stay translated under the completeness tests (#1914).
2026-10-16 04:55:02 +07:00
bflatastic 2ce3411493 Docs: one Variables and one Data sets entry in the MCP guide (#1943) 2026-10-16 04:55:01 +07:00
Fabio William ConceiçãoandCursor 7ca2763587 G6: document dataset commands and command.batch in MCP guide
Spell out dataset.list/capture/update/delete/rename and distinguish the MCP
command_batch tool from the engine command.batch Actions play.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 04:55:01 +07:00
bflatastic 08dde8783e Actions: Batch… stays in the Actions panel menu, as the reference app has it (#1938) 2026-10-16 04:55:01 +07:00
Fabio William ConceiçãoandCursor 0fe70314b8 G6: action.batch for File › Automate › Batch
Play an action on every file in a folder or every data set, with destination
and Override Open/Save/Export. Actions panel and File › Automate › Batch…
open the dialog; new strings in pt-br.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 04:50:07 +07:00
Fabio William ConceiçãoandCursor dc390aba37 G6: graph variables (CSV data merge)
VariableKind::Graph binds a graph root; dataset rows supply CSV as graph.setData.
Make Graph Dynamic, Variables dialog, capture/apply, and pt-br for the new strings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 04:50:07 +07:00
Fabio William Conceição 5ef090a0c2 G6: dataset CSV import/export with PT-BR UI labels
Load and save variable datasets as CSV for data-merge pipelines, and
translate the new Variables panel commands in Brazilian Portuguese.

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	docs/target-app-parity.md
2026-10-16 04:50:07 +07:00
Fabio William Conceição 41c8bd2a2d G6: image variables swap linked files and keep bounds
Add VariableKind::Image with path values on datasets, makeImageDynamic,
panel/dialog UI, and MCP docs so data merge can drive placed images.
2026-10-16 04:45:06 +07:00
Fabio William ConceiçãoandCursor 80090e7623 G5: Symbol Options Registration 9-point grid
Store the registration point with the symbol and place instances so that point lands on (x, y). Symbol Options exposes the same 9-point control as Transform.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 04:45:03 +07:00
sanyamsingh001andCodex 848d902354 G1: Add Warp Options controls for live effects
Expose existing Warp styles, orientation and percentage controls through the shared live effect dialog while preserving preview history and effect targets. Keep ordinary Enter owned by focused non-searchable combo controls.

Co-authored-by: Codex <noreply@openai.com>
2026-10-16 04:45:03 +07:00
Fabio William Conceição ed6d474042 G11: Asset Export seam regression and supersampled thumbs
assets.export of abutting fills must leave no ghost edge (#983 / #1037); panel thumbnails use the same Art Optimized supersample. Progress docs mark the gap closed.
2026-10-16 04:45:01 +07:00
sanyamsingh001andCodex 74573d6bad G1: Cancel existing-guide drags with Escape
Co-authored-by: Codex <noreply@openai.com>
2026-10-16 04:44:59 +07:00
sanyamsingh001andCodex 67f9d134ed G11: Preserve gaps when deleting open-path anchors
Keep surviving drawable runs separate when Delete or Backspace removes an open-path anchor. Preserve appearance, selection and one-step Undo.

Co-authored-by: Codex <noreply@openai.com>
2026-10-16 04:44:58 +07:00
sanyamsingh001andCodex a1b3c4804f G1: Keep the displayed artboard selected after switching documents
Co-authored-by: Codex <noreply@openai.com>
2026-10-16 04:44:56 +07:00
Gert Pellin 3ae6cf28e4 G13: Intertwine tool, Object › Intertwine › Edit, menu and translations (#1763)
Make and Edit choose the Intertwine tool: hovering highlights the overlap
area under the pointer, a click swaps which object is on top there, and a
path drawn around overlaps (Shift: a rectangle) arranges every overlap
inside it; Esc or Enter goes back to the tool chosen before. The overlap
faces move to vectorcraft-tools, shared by the tool and the command.

Clicks inside a raised area pick the member drawn on top there, the
Object menu gets the Intertwine submenu, and the new labels and the
predicate message are translated in the complete catalogs. docs/gaps.md
records what landed under G13.
2026-10-16 04:44:53 +07:00
Gert Pellin ba254a7481 MCP command_batch: draw_path and draw_shape run as steps (#2113)
A step whose id is draw_path or draw_shape runs that tool with its params,
checked as tools/call checks arguments; its result is the tool's, so later
steps can use "$N.id". The reply lists the ids the drawing steps made
under created. The unknown-argument check is shared with tools/call.
2026-10-16 04:44:52 +07:00
Gert Pellin 49fcfbd145 MCP draw_path / draw_shape: dash, cap, join, opacity, name and effects (#2112)
The drawing tools style the object they draw in the same call: dash, cap
and join go to the stroke.set that strokeWidth already sends, opacity and
name to object.setProps, and effects are applied in order as apply_effect
does. The options are checked (effect ids against effect.list) before
anything is drawn, so a bad one leaves no half-styled object behind.
2026-10-16 04:44:52 +07:00
Gert Pellin 736abd811d paint.setFill / setStroke: span "selection" places one gradient across the targets (#2127)
A gradient given to several objects fits each one's own bounds. With
span: "selection", a linear or radial gradient without its own vector is
placed once on the union of the targets' bounds, at its angle and aspect,
and every target gets that vector in document coordinates, so the ramp
runs on across them. A solid colour, a swatch, a freeform gradient or an
explicit start/end with it is refused before anything changes; the next
object drawn still fits the gradient to itself.
2026-10-16 04:44:50 +07:00
Marco Arnulfo 32dbc4b641 PDF export: the output intent shares the stream of the profile that tags the colours (fixes #1954) 2026-10-16 04:44:31 +07:00
bflatastic 2843687ef4 Text: leave out the bottom-handle Auto Size double-click until its behaviour is settled (#1947) 2026-10-16 04:44:31 +07:00
Marco Arnulfo a69371656b Text: Ctrl+Shift+C aligns centre off the Mac, a double-click on area type's bottom handle turns on Auto Size (fixes #1940) 2026-10-16 04:44:29 +07:00
Lucas LerouxandClaude Opus 5.5 55fadb6afe Text: a bundled-fonts feature (on by default) lets embedders that bring their own fonts leave ours out
The nine OFL faces in `assets/fonts` are compiled into every build of
vectorcraft-text: 3.4 MB of the binary, about 1.6 MB of a gzipped wasm.
An embedder that loads every font at run time with `FontDb::add_font`
(a web app serving its own font catalogue, say) ships them twice and
can't turn them off.

- vectorcraft-text: `bundled-fonts`, a default feature, holds the
  `include_bytes!` list. Off, a database starts without them (craft-fonts
  and system fonts are unaffected) and text has no glyphs until a font
  is added, as when the bundled fonts fail to parse.
- vectorcraft-engine: a `bundled-fonts` default feature forwards to it,
  so the apps and any embedder using the engine keep the fonts unless
  they build it with `default-features = false`.
- The workspace declares vectorcraft-text without default features, so
  no library crate turns the fonts back on. The tests of cad, metafile
  and tools, which lay type out, ask for the feature in their
  dev-dependencies; the other crates' tests get it through testkit.

`cargo test --workspace` builds with the fonts, as before. A new
integration test, `without_bundled_fonts`, covers the configuration
without them (`cargo test -p vectorcraft-text --no-default-features
--test without_bundled_fonts`): the database starts without them, text
in no font lays out with no glyphs, and a font added later is used and
is the fallback for families the database lacks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 04:19:05 +07:00
IanHollow 83f40f135a Text: make every supplied font family available to documents
Assisted-by: GPT-6
Signed-off-by: IanHollow <72767437+IanHollow@users.noreply.github.com>
2026-10-16 04:19:05 +07:00
YukiHiko 8b5e9e4927 G6: expose vertical writing in MCP add_text 2026-10-16 04:19:05 +07:00
YukiHiko 3a7d422046 G4: keep fullwidth punctuation upright without vertical alternates 2026-10-16 04:19:05 +07:00
ham9zah ad3531d464 Curvature: each three points follow the circle through them, curved ends (#1236, in part)
The Curvature tool built its curve as a uniform Catmull-Rom: both handles of a
smooth point a sixth of the chord between its neighbours, the ends of an open
path without handles. Uneven spacing overshot or looped, a J or U from three
points bent sharply at the middle with straight legs, and the end segments
looked flat.

- `curvature_spline` (draw2.rs) makes each stretch of three points follow the
  circle through them: a smooth point's tangent is that circle's
  (`smooth_handles`), each handle `chord / (3 cos²(α/2))` long, α between
  handle and chord capped at 90° (`arc_handle`). The ends of an open path
  mirror their neighbour's tangent in their chord (`end_handle`). A turn
  tighter than 30° (`TIGHT_TURN`) shortens its handles, so a narrow V comes to
  a point. Degenerate points become corners. `path.curvature` and the rubber
  band use it.
- `catmull_rom` is unchanged for the Pencil and Paintbrush fitting.
- `bend_last` (from: smooth), `curvature_move` on a smooth point, the point
  `curvature_insert` adds on a straight segment, and `path.convertAnchor` to
  smooth without x/y (`curvature_smooth`) use the same rule.
- `curvature_extend` gives the new end its end handle unless Alt-clicked
  (`corner` param on `path.curvatureEdit`).
- Neighbours of a point dragged on an existing path keep their tangents (a
  Pen-drawn shape isn't reshaped); the path being drawn re-curves throughout.
2026-10-16 04:19:05 +07:00
el-j 4913418d1c Type tool: Escape returns to the previous selection tool and preserves isolation mode
When entering text edit mode via double-click from a selection tool
(Selection or Direct Selection), pressing Escape now returns to the specific
tool used before double-clicking (rather than always falling back to
Selection).

Additionally, when editing text while in isolation mode (such as when drilling
into nested groups with the Selection tool), pressing Escape exits text editing
while keeping isolation mode active; a second Escape press is required to exit
isolation mode.

- Record the previous tool in TypeTool on double-click from canvas.
- Return to prev_tool on ToolKey::Escape, consuming the key in shortcuts when claimed.
- Add unit test in vectorcraft-tools verifying Escape returns to prev_tool.
- Add integration tests in vectorcraft-ui-egui verifying Escape returns to Direct
  Selection and that isolation mode persists until a second Escape.
- Update docs/target-app-parity.md and ROADMAP.md.
2026-10-16 04:19:05 +07:00
el-j 28daaf473c Canvas: double-clicking type with the direct selection tool puts the caret there
With the Direct Selection tool ("white arrow", shortcut 'a'), double-clicking
a text element (either standalone or nested inside a group) now switches to
the Type tool and immediately positions the caret at the clicked character,
matching the behavior previously introduced for the Selection tool in #284.

- In DirectSelectionTool::pointer, detect double-click hits on NodeKind::Text
  when !self.group and emit Action::SwitchTool("type").
- Group Selection (group: true) retains its existing group cycling behavior.
- Add unit test in vectorcraft-tools verifying tool switch on both standalone
  and grouped text.
- Add headless egui integration test in vectorcraft-ui-egui verifying tool switch,
  caret positioning, and inline editing without triggering shortcuts.
- Update docs/target-app-parity.md and ROADMAP.md.
2026-10-16 04:19:05 +07:00
Ayberk A. a86ff7f177 Document Info: Objects gives the paths' total length (#1958)
Document Info › Objects counts the paths but didn't say how long they are,
which sign and print work reads there (an LED strip along the letters, a
cut line). `document.info` now adds up the length of every path it counts,
the pieces of compound paths included and guides left out, and returns it
as `pathLength` (pt); the Objects section shows it as Path Length in the
document's units, right after the Paths count, so Selection Only gives the
selected paths' length. The label is translated in the catalogs that
cover the menus; pt-br only takes strings the UI itself shows.
2026-10-16 04:08:12 +07:00
andeecollardandClaude Opus 5.5 75b91e9ef3 Docs: leave the status lines to main
The 'Last updated · Change' status lines in docs/roadmap.md, docs/target-app-parity.md and docs/image-trace-quality.md change with almost every merge to main, so this PR's edits to them conflicted each time main moved. They are reverted to main's text; this PR's evidence bullets, history rows and table entries stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tBcdwNhptqtmX32JCGFzB
2026-10-16 04:07:51 +07:00
andeecollard 4714567240 M12: retain the interior of collapsed closed trace outlines 2026-10-16 04:07:50 +07:00
andeecollardandClaude Opus 5.5 477ff16357 Docs: leave the status lines to main
The 'Last updated · Change' status lines in docs/roadmap.md, docs/target-app-parity.md and docs/image-trace-quality.md change with almost every merge to main, so this PR's edits to them conflicted each time main moved. They are reverted to main's text; this PR's evidence bullets, history rows and table entries stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tBcdwNhptqtmX32JCGFzB
2026-10-16 04:07:45 +07:00
andeecollard c7aac7c250 M12: preserve tiny traced components and holes 2026-10-16 04:07:45 +07:00