Commit Graph
350 Commits
Author SHA1 Message Date
Simon LeblancandClaude Opus 5.5 e22d22a4ce Order the French code and docs alphabetically by language code
French now sits between Spanish and Italian (es, fr, it, ru): the
plural rule, the LANGUAGES registry entry (so the Language menu lists
Français before Italiano), the locale tests, the fr() helper and
french_reads_as_french. README, ROADMAP and docs/development.md list
the languages in the same order.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-09 01:18:25 +02:00
Simon LeblancandClaude Opus 5.5 9ec940222b Add French (fr) interface translation
Add fr.tsv with the full interface coverage: every menu string, every
tl! literal, the plurals and the status/error messages (@msg).
Registered as complete_menus with the French plural rule (0 and 1 are
singular); every fr-* locale (fr-FR, fr-BE, fr-CA, fr-CH …) resolves
to it. Keeps the product, workspace and perspective preset names in
English like the other complete catalogs.

Tests: french_reads_as_french; fr added to KEEPS_MENU_NAMES and
COMPLETE_MESSAGES; the Latin-script glyph test covers French letters
and the no-break space; locale tests use de-DE as the unsupported case.

Clean-room: written from the meaning of the English labels in the
vocabulary French-speaking users of vector illustration software know
(Plan de travail, Nuancier, Contour, Fond, Tracé, Calque). No other
application's localisation was copied.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-09 01:18:14 +02:00
bflatastic f365dc1c74 Merge #627: M7: inline symbols in text 2026-10-09 06:02:59 +07:00
bflatastic f3b90fb93f Merge #623: Part of #579: vectorcraft-cli lists the fonts font services load too
# Conflicts:
#	ROADMAP.md
2026-10-09 05:48:40 +07:00
haven b843fe8af3 M7: inline symbols in text
Document symbols placed inline in a run of type (beyond Illustrator; like
InDesign inline anchored objects): TextRun.inline holds the symbol, scale and
shift; the run is one U+FFFC laid out as a glyph sized to the art, centred on
the cap height. text.insertInline and Type > Insert Inline Symbol; editing
keeps the graphic whole; canvas, SVG (<use>) and PDF draw the art; Create
Outlines turns it into a symbol instance.
2026-10-08 15:45:34 -07:00
bflatasticandClaude Opus 5.5 aa5e8f46e9 Part of #579: vectorcraft-cli lists the fonts font services load too
The DirectWrite font lister (#594) was installed in the desktop app only:
`vectorcraft-cli` (convert, run, MCP) didn't see fonts that font services
such as Adobe Fonts load on Windows, so its exports set such type in a
fallback font.

- The CLI shares the app's `system_fonts.rs` and installs it first thing
  on Windows (not the win7 build). The text crate stays free of unsafe
  code; the CLI denies unsafe code instead of forbidding it, so the
  shared module's own allowance applies.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 05:43:13 +07:00
bflatastic 22008b4e68 Merge #619: Fix #598: the Free Transform widget
# Conflicts:
#	ROADMAP.md
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
2026-10-09 05:36:53 +07:00
bflatasticandClaude Opus 5.5 2497713779 Fix #598: the Free Transform widget
While the Free Transform tool (E) is active, a small box at the canvas's
top left holds Constrain over the tool's three modes: Free Transform,
Perspective Distort and Free Distort. Before, the modes were reachable
only by modifier keys or `tool.setOption`.

- The buttons set the tool's `constrain` and `mode` options through
  `tool.setOption`, as agents do. Constrain acts as Shift held and is
  greyed in the distort modes, where it does nothing. The modifier keys
  keep working while dragging.
- Theme tokens; Constrain shows the link icon, Free Transform the tool's
  own icon, the distort modes are drawn in code. Tooltips translated
  (cs, es, it, ja, pt-br, ru, zh-hans, zh-hant).
- `ui.inspect` reports where it shows (`freeTransformWidget`).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 05:31:43 +07:00
bflatastic 8d5b52965c Merge #611: M7: per-paragraph paragraph attributes 2026-10-09 05:13:59 +07:00
bflatastic 33f38cef08 Merge #610: Fix Shaper recognition and add editable Shaper groups
# Conflicts:
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
2026-10-09 05:13:59 +07:00
haven f7761bf5bc M7: per-paragraph paragraph attributes
Each paragraph has its own alignment, indents, spacing, hyphenation, tab
stops and paragraph style (TextObject.paras; para stays paragraph 0's, so
old files and readers work). Layout, editing (split/merge), threads, the
Paragraph/Tabs/Paragraph Styles panels, ranged text.setStyle/setFormat/
tabs/paraStyle.apply, SVG export/import and the fuzzers follow.
2026-10-08 15:07:13 -07:00
David Whiting 7e82dbc05b M2.9: add editable Shaper groups and shape orientation steps 2026-10-08 22:52:23 +01:00
bflatastic 611deeda46 Merge #606: M7: area type auto size and shrink text to fit 2026-10-09 04:52:14 +07:00
haven 674aef95b0 M7: area type auto size and shrink text to fit
Area Type Options gain a fit: none, autoHeight (Illustrator's Auto Size:
the frame's height follows the text after every edit, resolved in
refresh_bounds inside the edit so it shares the edit's undo step) and
shrinkText {minPercent} (beyond Illustrator: overflowing text scales its
sizes, leading and baseline shifts down at layout time by the largest
factor, in 0.1 pt steps of the first run, that fits). The Auto Size New
Area Type preference now applies. text.areaOptions and document.inspect
report overflow and the shrink factor; the Area Type Options dialog
offers fit and first baseline as dropdowns.
2026-10-08 14:48:25 -07:00
bflatastic ef99ab8d1c Merge #603: Part of #372: .ai files write layers and sublayers as PDF layers, hidden ones kept
# Conflicts:
#	ROADMAP.md
2026-10-09 04:46:09 +07:00
bflatasticandClaude Opus 5.5 50bb36ebc2 Part of #372: .ai files write layers and sublayers as PDF layers, hidden ones kept
A `.ai` file's PDF part is what apps that don't read VectorCraft's native
document open. It had no PDF layers: hidden layers and sublayers were left
out of it and everything else was flat page content.

- `.ai` files (Save As, exports, the CLI) write PDF layers unless
  `createLayers` is false (a PDF standard still decides for itself).
- createLayers writes sublayers too, as optional content groups listed
  under their layer's in `/Order`, hidden ones off with their art.
- A layer's form gets a box round the whole page instead of its art's
  bounds, which readers made a clipping group of, gathering a layer's art
  and its sublayers' in one group.
- PDF import: art inside a group, clip or form that dissolves into its
  parent keeps the layer it was drawn in, so sublayers nested in their
  layer's content import as sublayers.
- Save PDF: "Create PDF layers from layers and sublayers" (ja, es, it,
  pt-br, ru, zh-hans, zh-hant).

Hidden objects (not layers) still aren't in the PDF part.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 04:40:30 +07:00
haven d2bb95daab M7: area type vertical alignment
Area Type Options > Align: Top / Center / Bottom / Justify (AreaOptions.verticalAlign,
LayoutOptions.vertical_align). Each row/column cell is aligned on its own; rectangular
cells shift their lines, other frames and text wrap reflow until settled without
losing text. Justify shares the leftover space equally between line gaps. Area Type Options
shows Align and First Baseline as dropdowns (the parameter dialog's choices table).
2026-10-08 14:28:02 -07:00
bflatastic 030d407f61 Merge #594: Fix #579: fonts activated by font services such as Adobe Fonts are listed on Windows
# Conflicts:
#	ROADMAP.md
2026-10-09 04:18:54 +07:00
bflatasticandClaude Opus 5.5 5854346053 Fix #579: fonts activated by font services such as Adobe Fonts are listed on Windows
Font services load their fonts with GDI at run time, from files in their
own folders, without installing them: neither the font folders nor the
registry list them, so the font scan never saw them. DirectWrite's system
font collection holds them since Windows 10 1607.

- vectorcraft-text: `set_platform_font_files` lets the app name font files
  the platform knows; each scan and each check for installed fonts asks
  again and reads those outside the font folders. A file named by itself
  is read whatever its extension (font services keep fonts in files
  without one).
- The desktop app lists the local files of DirectWrite's system font
  collection on Windows (`system_fonts.rs`, through the windows crate
  already in the build), read in place.
- The check for fonts installed while the app was in the background runs
  on a thread: DirectWrite's check for updates takes about 50 ms.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 04:16:14 +07:00
haven d2aad22dac M7: Every-line Composer for ragged text
Illustrator's Every-line Composer balances the breaks of the whole
paragraph for left/centre/right aligned text too, not only justified
text. VectorCraft only composed justified area text; ragged text was
always broken greedily.

- Model: ParaStyle.composer (vectorcraft_doc::Composer, re-exported by
  vectorcraft-text), Every-line by default like Illustrator, saved only
  when Single-line. layout() reads it from the object;
  LayoutOptions.composer is now an Option override for tests.
- Composer: the justified gate is gone. Ragged lines never shrink (a
  line that doesn't fit ends the node), badness is 100*r^3 with r = the
  line's leftover space over a rag zone of 1/6 of the width (finite for
  lines without spaces); the last line is free unless shorter than 1/3
  of the width (measured in zones of 1/3), so a lone short word on the
  last line pulls a word down. Hyphens cost 25 in ragged text (50 in
  justified). Knuth-Plass fitness classes with adjacency demerits
  (justified 10000, ragged 1000). Lines past the last width change
  share one node key, active nodes are capped (4096) and paragraphs
  over 200k glyphs break single-line, so work stays bounded; ragged
  tries a tolerance of one rag zone, then any fitting breaks, then
  falls back to greedy.
- text.setFormat {composer: "singleLine"|"everyLine"}; the Paragraph
  panel menu's composer items work with a check mark (Reset Panel goes
  back to Every-line).
- Tests: ragged Every-line moves "or" down in "Destroy all creatures
  with toughness X or less." where greedy leaves "less." alone; the rag
  of long paragraphs is no worse than greedy (sum of squared leftover
  and of neighbour differences); Single-line is greedy; the 10k-char
  layout budget covers ragged text; command test with undo; native
  format round trip (rich_session's text is now Single-line).
  No existing test needed changing: the justified tests and
  hyphenated_layout_adds_hyphens (ragged, now Every-line) still pass.

Auto alignment (start of the paragraph direction) is ragged; a wrapped
line's Mojikumi flush opening bracket gives its half em back to the
composer, as in the line loop; tests cover Auto in both directions.

Layout of 10k chars, release: justified Every-line 5.6 ms, ragged
Every-line 5.8 ms, ragged Single-line 4.8 ms.
2026-10-08 14:03:33 -07:00
bflatastic 04827da657 Merge #581: Add Russian (ru) interface translation 2026-10-09 03:48:19 +07:00
MaksPV 89b10cce52 Add Russian (ru) interface translation
Add ru.tsv with the full interface coverage: every menu string, every
tl! literal, the plurals (one/few/many) and the status/error messages
(@msg). Registered as complete_menus with the Russian plural rule;
every ru-* locale resolves to it. Keeps the product, workspace and
perspective preset names in English like the other complete catalogs.

Tests: russian_reads_as_russian, russian_plurals_have_three_forms,
russian_glyphs_are_available_without_system_fonts; ru added to
KEEPS_MENU_NAMES and COMPLETE_MESSAGES.

Clean-room: written from the meaning of the English labels in the
vocabulary Russian-speaking users of vector illustration software know
(Слой, Образцы, Обработка контуров, Монтажная область, Обводка,
Заливка), matching the Russian catalog of the sibling app PhotoCraft.
No other application's localisation was copied.
2026-10-08 23:44:45 +03:00
bflatastic c7094320b3 Merge #580: Fix #522: SVG round trips keep hidden objects and data-* attributes
# Conflicts:
#	ROADMAP.md
2026-10-09 03:41:22 +07:00
bflatasticandClaude Opus 5.5 2a35c4a82f Fix #522: SVG round trips keep hidden objects and data-* attributes
An SVG used as an animation rig lost its hidden alternates and their
rotation pivots: the exporter left hidden objects out (only hidden layers
could be kept), and the importer dropped data-* attributes.

- SVG export: hiddenLayers (on by default for document.save, as before)
  now keeps hidden objects too, each written display="none" on its own
  element (path, image, text or group). Exports still leave them out
  unless asked; the CLI's convert asks, as a conversion keeps the
  document.
- An object's own data: ObjectAttributes.data, the data-* attributes in
  order (ours, data-name and data-vc-*, aside). The importer keeps them on
  the objects and layers they belong to (at most 64 per element), the
  exporter writes them back, the native format saves them (missing in
  older files), and object.setProps {data: {key: value|null}} sets or
  removes them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 03:36:08 +07:00
bflatasticandClaude Opus 5.5 1e2cb082ba Fix #526: the exports and the CLI write .ai as Save As does
file.saveAs and document.save wrote a PDF-compatible .ai, but
document.export (and with it the CLI's run --export x.ai and convert
in.svg out.ai, and MCP's export) refused it as a format that can only be
opened, and --help didn't list it as writable.

The .ai encoder is now shared: save.rs's ai_params and encode_ai (a PDF
of every artboard with the PDF options, carrying the native document,
blank pages with pdfCompatible: false) serve Save As and the exports
alike. The .ai format is writable, so document.formats, --help and the
MCP export tool list it; an export leaves the document's own path alone.
Export As and Export for Screens keep leaving .ai to Save As.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 03:18:07 +07:00
haven 5e7af3f83c Placed documents: Place's Link tooltip, Break Link in the Links panel 2026-10-08 12:20:59 -07:00
bflatasticandClaude Opus 5.5 b832599a47 Fix #550: an SVG of chosen artboards holds only their art
Exporting one artboard of many to SVG wrote the whole document's art into
that artboard's file, the other artboards' art outside its viewBox: an
editor opening it showed every artboard's art. A file for an artboard
named in the export (Use Artboards with a range or All, Export for
Screens) now holds the objects over that artboard only, at any depth of
layers, as DXF's crop leaves them; objects reaching into it stay whole
and a clipping layer keeps all of its objects. An SVG with no artboard
named (Save As) still holds the whole document.

Part of #546: Export As with a range writes the chosen artboard alone in
SVG, DXF, PDF and PNG, each file with that artboard's art (tested from
the dialogs).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 01:38:38 +07:00
bflatasticandClaude Opus 5.5 ac043129c8 Fix #545: Windows renders through DX12 before Vulkan
Windows lists each GPU under both backends, and Vulkan came first. On an
Intel GPU its driver made the whole window flicker black while the
pointer moved; the same machine was fine with WGPU_BACKEND=dx12 or gl.
adapter_order now ranks a GPU's DX12 adapter before its Vulkan one, after
the power preference, so the GPU chosen doesn't change, only how it is
driven. Vulkan stays the fallback (a failed DX12 start restarts on it)
and WGPU_BACKEND=vulkan still picks it. Elsewhere there is no DX12
adapter, so nothing changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 00:56:49 +07:00
bflatastic 971b0bb95e Merge #529: Fix #506: drawing tools snap to Smart Guides as the Selection tool does
# Conflicts:
#	ROADMAP.md
2026-10-09 00:44:46 +07:00
bflatastic 7248c6aeec Merge #538: Fix #511: Live Corners on any path's corners (stars, polygons, pen paths)
# Conflicts:
#	ROADMAP.md
2026-10-09 00:44:29 +07:00
bflatastic 7a270c8deb Merge #527: Type (#432): new type in a Japanese interface starts top-to-top with em box centre alignment
# Conflicts:
#	ROADMAP.md
2026-10-09 00:44:10 +07:00
bflatasticandClaude Opus 5.5 ab65474044 macOS: the menus live in the system menu bar, settings in the VectorCraft menu
On macOS the menus are in the system menu bar, laid out as a Mac app's (Illustrator on the Mac as
the reference), and the app settings sit in the VectorCraft menu: About, Settings ▸ (one item per
Preferences page, General… ⌘K), Language ▸, Appearance ▸ (UI brightness), Services, Hide
VectorCraft ⌃⌘H (⌘H stays View › Hide Edges), Hide Others ⌥⌘H, Show All and Quit ⌘Q (the app's own
quit, so unsaved documents are asked about); they leave Edit and Help. Window gets Minimize ⌃⌘M,
Zoom and Bring All to Front; Help is the system's Help menu. Windows, Linux and the web keep the
in-window menu bar unchanged; --in-window-menus or VECTORCRAFT_IN_WINDOW_MENUS keeps it on macOS
too, and so does a menu bar AppKit refuses to build.

One menu model: the in-window menus and the native bar draw the same rows (menus::entry), and the
platform-free native_menu module lays them out, decides when to update (a cheap state hash; labels,
enabled and checked in place, a rebuild only when the structure changes) and hands key equivalents
back to egui as the key presses they were, so shortcuts, user overrides and focused text fields
behave as in the window and nothing fires twice. The muda backend (pinned =0.21.1, macOS only, no
unsafe) is apps/vectorcraft/src/mac_menu.rs, mirroring PhotoCraft's. ui.inspect reports
nativeMenuBar. Translations for the new labels in ja, es, zh-hans, zh-hant, cs and pt-br.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 23:45:31 +07:00
bflatasticandClaude Opus 5.5 4eac7b505f Fix #506: drawing tools snap to Smart Guides as the Selection tool does
Every drawing tool now asks one snapper (guides::DrawSnap) for the points it
places: the Pen's and Curvature tool's anchors, the Pencil's ends, the shape
tools' start and dragged corner or radius (Rectangle, Rounded Rectangle,
Ellipse, Polygon, Star, Line Segment, Arc, Spiral, the grids), the Type tool's
click and frame and a new artboard's corners. Points land on other objects'
anchors, centres and paths ("anchor", "center", "path") or line up with their
anchors, edges and centres ("align"); hovering shows it before the press, the
Pen's guides end its rubber band, and the Pen lands on the path being drawn
too. Shift keeps a point to its 45-degree step (from Constrain Angle) or a
box's diagonal and slides it along that way into line with a target.
Preferences > Smart Guides > Construction Guides and Angles now act: a drawn
point near a guide through the point it leaves from lands on it.

Before, the Pen snapped only its press and showed nothing, and the shape tools
re-gathered targets on every drag step from the document showing their own
preview, so the dragged corner stuck to where the last step left it.

Performance: targets are gathered once per document state (keyed on the
document's uid and revision, now in ToolContext), from the art in the window,
and sorted, so the anchors, centres and lines near the pointer are found by
bisection. Leaving objects out of the targets no longer looks up each node's
ancestry (quadratic in the document's size): one pruned walk does it, which
also speeds up the Selection tool's moves and resizes.

The direction handles' HandleSnap (#494) is now a DrawSnap holding its own
targets (Targets::for_handle) for the drag: one cache, one lookup.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 23:20:27 +07:00
bflatasticandClaude Opus 5.5 1d14f337db Fix #511: Live Corners on any path's corners (stars, polygons, pen paths)
Corner widgets only ever showed on live rectangles: CornerWidgets::of bailed
out unless the selection's live shape was LiveShape::Rectangle, and
object.setLiveShape only edited rectangle radii. A star is a plain path, so it
had no widgets with either selection tool.

- geom::corners: the corners of any path (anchors without handles between two
  straight sides) and the path with them cut round, inverted round or
  chamfered, each corner a circle tangent to both sides, its radius no larger
  than takes the cut halfway along its shorter side. Rectangles now cut their
  corners with it (bit-identical anchors and layout).
- doc::corners::LiveCorners/set_corners: one model for a live rectangle's,
  a live polygon's (new radii/kinds) and any other path's corners (new
  LiveShape::Path keeping the uncut outline, plain again once nothing is cut);
  radii scale with transforms (Scale Corners) and stay circular.
- object.setLiveShape takes anchor indices for `corners` on any path; polygons
  keep a shared radius as their sides change.
- Widgets: Direct Selection on any path, Selection on live rectangles and
  polygons; placed on each corner's bisector, the drag follows it, the red
  limit outline marks corners at their own limit; the angle preference and
  View > Hide Corner Widget apply. Corners dialog and the Properties and
  Transform panels' Corner Radius (now on polygons too) work on them.
- Tests for the star, the polygon and a pen path in geom, doc, tools, engine
  and ui-egui; docs and ROADMAP.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 23:05:41 +07:00
callmeBallochandClaude Opus 5.5 7c7ae55625 i18n: Italian interface (it)
Adds an Italian catalog (crates/ui-egui/src/i18n/it.tsv, 3,549 rows in
es.tsv's order) covering every menu string, every tl! literal, the
plurals and every status/error message, at the same scope as Spanish.
Standard Italian in sentence case, written clean-room from the meaning
of the English labels; the product, workspace, perspective preset and
language names stay in English as in es and ja.

Registered as "Italiano" with the one/other plural rule and
complete_menus; every it-* locale resolves to it. Tests: it joins
KEEPS_MENU_NAMES and COMPLETE_MESSAGES, italian_reads_as_italian checks
vocabulary and plurals, tags_map_to_languages the locale tags, and the
bundled-font glyph test covers Italian accents and guillemets. README,
ROADMAP and docs/development.md list the language.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011iayep93edeimiAE8oSfaT
2026-10-08 23:04:31 +07:00
bflatasticandClaude Opus 5.5 e63606826a Fix #508: PDF/.ai import keeps type strokes, wrapped lines and layer order
- Type left unfinished when a layer's marked content ends, and a clip that closes in a later layer,
  stay in their own layer (no stray "Page N" layer, no art moved to the next layer).
- Top-level layers stack as the pages mark them, so a layer whose art starts on a later page keeps its
  place; layers marked with no art come in empty.
- A stroke written as each glyph's outline stroked, right after (or before) the type, becomes the type
  object's stroke above (or below) its characters: the type stays one editable object.
- Spaces read from gaps wider than a space get their own tracking where even tracking would put the
  glyphs after them more than a tenth of an em off (barcode digit groups); lines are measured to their
  last visible glyph, so a wrapped line's trailing space no longer spreads into the tracking.
- Lines wrapped in a frame (each wrapped line ending in a space, one left edge, font, size and leading,
  with an optional paragraph spacing) come back as one area type object, left aligned or justified,
  only where laying it out breaks the lines exactly where the file does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 22:37:37 +07:00
bflatasticandClaude Opus 5.5 71371b9d58 Linux clipboard probe: look only while Paste depends on it, stop at once (review of #510)
- The worker reads the clipboard only while nothing is copied in the app
  and a document is open (as the in-line check did before), and idles on
  a channel otherwise; it sends a verdict and wakes the UI only when the
  answer changes.
- The in-line check (Windows, macOS, web) keeps its short-circuit: it
  doesn't open the system clipboard after an in-app copy or without a
  document, as before the PR.
- A dead worker hands the check back to the UI thread instead of
  freezing the last verdict.
- Dropping the app wakes the worker and waits (250 ms at most) until it
  has let go of its clipboard handle: on X11 the last arboard handle
  alive hands what the app copied to the clipboard manager, and a
  detached worker holding it would lose that handover at exit.
- The factory stays in `Services` until the first frame takes it (no
  extra app fields); main.rs installs it with `cfg!(target_os = "linux")`.
- Tests trimmed from five to three that share tests_sysclip's helpers:
  the frame never waits on a slow clipboard and only the probe thread
  reads it (nothing read without a document), the probe rests after an
  in-app copy and lets go of its handle when the app drops, and a dead
  probe falls back to the in-line check. The in-line case is already
  covered by tests_clipboard.
- ROADMAP (Last updated, Platforms, Shipped so far) and
  docs/development.md (Linux: clipboard owners that never answer).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 22:36:44 +07:00
imachusandClaude Opus 5.5 fba610284c Type (#432): new type in a Japanese interface starts top-to-top with em box centre alignment
As decided in #432 (option 2): while the interface is in Japanese, new type starts with em box
top-to-top leading and em box centre character alignment; other languages keep the Roman
baseline, so Latin area type doesn't move.

- The UI tells the engine the language it is drawn in each frame (`Session::ui_language`); without
  a UI, an explicit `interfaceLanguage` preference counts (`Session::japanese_interface`).
- `text.create` and `text.createInPath` (the Type tools' clicks and drags) apply it, and take
  `leadingModel` / `charAlign` to choose; the values used are journaled, so a replay does the same
  in any language.
- `paraStyle.new` / `charStyle.new` made from nothing (no `attrs`, no text selected) carry them.
- Imported text (PDF, SVG, DXF…) keeps the Roman baseline. Saved documents keep what they say.
- docs/mcp.md (New type in a Japanese interface) and the ROADMAP note it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 00:08:03 +09:00
bflatasticandClaude Opus 5.5 aefd961bbd Fix #494: Pen + Cmd edits the path being drawn; Pen + Alt reshapes segments; handle drags fixed and fast
Reported: with the Pen there was no way to edit curves in place as Ctrl does in Illustrator on
Windows (Cmd on the Mac), segments couldn't be grabbed, and the curve-editing tools felt slow and
sometimes didn't take a handle.

- Cmd/Ctrl held at a press with any tool but a selection tool now borrows the selection tool in the
  engine (Session::pointer), not in the canvas: the selection tool chosen last, or Direct Selection
  for the Pen, Curvature and anchor tools until one is chosen. The tool lent to isn't deactivated,
  so the Pen goes on drawing its path after the release while that path stays selected; it no
  longer draws on another open path selected meanwhile. Agents reach it with pointer_gesture
  mods.cmd. With Cmd held the pointer is the borrowed tool's. Choosing a tool or switching
  documents mid-gesture gives the tool back first.
- Pen + Alt over a segment of a selected path reshapes it, as the Anchor Point tool does.
- The handles shown are the handles that drag (one helper for Direct Selection, the Anchor Point
  tool and Pen + Alt): Direct Selection now drags the handles of a path selected as a whole (as
  the Pen leaves it, so Cmd + drag of a handle while drawing works), the Anchor Point tool no
  longer grabs handles that Show handles when multiple anchors are selected hides, the nearest
  handle wins, and a handle shorter than the tolerance leaves its anchor to be picked. The anchor
  tools pick within Selection & Anchor Display > Tolerance instead of a fixed 4 px.
- path.reshapeSegment keeps a smooth anchor at either end smooth (its other handle turns with the
  moved one) instead of kinking it.
- A dragged handle gathered its smart guide targets from the whole document on every pointer move
  (21 ms per move on 20,000 paths): HandleSnap now gathers them once per drag (1 ms).
  vectorcraft-cli perf measures handle and segment drags on a 5,000-anchor path.
- catalog::is_selection_tool and catalog::edits_anchors replace the UI's own lists.
- Tests in tools, engine and MCP (headless pointer_gesture); docs/mcp.md, control-protocol.md,
  ROADMAP.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 22:00:13 +07:00
bflatasticandClaude Opus 5.5 d12d5195da Fix #502: no startup crash on dual-GPU Linux desktops under Wayland
0.5.0 asked wgpu for the low-power adapter everywhere (#306). On a Linux
desktop whose KDE Wayland compositor runs on an NVIDIA GPU, wgpu sorted the
Ryzen's integrated RADV GPU first: it reports that it can present to the
window, but KWin can't import the buffers it allocates ("importing the
supplied dmabufs failed"), which is a fatal Wayland protocol error. The next
surface configuration then got ERROR_SURFACE_LOST_KHR and egui-wgpu panicked
("The surface isn't supported by this adapter", winit.rs:129).

- Preferences > Performance > Graphics Processor gains Automatic, the new
  default: power saving on Windows and macOS as #306 wanted, and on Linux the
  system's order, where Mesa's device-select layer puts the GPU the desktop
  runs on first. Values are now automatic / lowPower / highPerformance; 0.5.0
  saved powerSaving for everyone, which reads as automatic.
- The app picks the window's adapter itself (apps/vectorcraft/src/gpu.rs,
  eframe's native_adapter_selector): only adapters that present to the
  surface, WGPU_ADAPTER_NAME first, hardware before software, native backends
  before OpenGL, then the power preference. Every adapter and the pick are
  logged.
- run_native runs under the engine guard. When the graphics fail during the
  first frames (a wgpu error, or a panic in wgpu, egui-wgpu or naga), the app
  starts again without that adapter (exec on Unix, so an AppImage stays
  mounted; a new process on Windows), passing the failed adapters in
  VECTORCRAFT_GPU_SKIP, and says so in the status bar. When none is left it
  logs why and exits with an error, never a panic.
- docs/development.md, README and ROADMAP: the new default, the restart and
  the escape hatches (WGPU_POWER_PREF, WGPU_ADAPTER_NAME, WGPU_BACKEND,
  MESA_VK_DEVICE_SELECT, DRI_PRIME).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 21:59:55 +07:00
bflatasticandClaude Opus 5.5 ea1993c848 Fix #495: panels, panel groups and the Tools panel undock into floating panels
Nothing could be undocked: the dock's tabs only switched panels, the icon
column only popped its panels out next to it, and only tool flyouts
(#437) could float.

- A dock tab, a panel icon or a popped-out panel's title dragged out of
  the dock floats that panel inside the app window; the strip right of
  the dock's tabs floats the whole Properties | Layers | Libraries group.
- A floating group moves by its title bar (or the strip right of its
  tabs); a tab dragged out of it floats on its own; dropped on another
  group's title bar or tabs it stacks with that group; dropped on the
  dock, which lights up under the pointer, its panels go back where they
  live in the dock (tabbed group or icon column). Its x does the same.
- The Tools panel floats by its title bar or grip and docks again on the
  window's left edge, which lights up the same way.
- window.panel.float {panel, x?, y?, onto?, group?} and window.panel.dock
  {panel, group?} (panel "tools" for the Tools panel) for the control
  channel and MCP; every panel menu ends with Float Panel / Dock Panel;
  window.panel on a floating panel shows its tab.
- Floating groups and the Tools panel's position are kept with the
  preferences and in user workspaces (built-ins dock everything),
  sanitized on load, and clamped into a smaller window when drawn.
- One move state in egui temp memory drives every drag, so a tab torn off
  keeps following the pointer; per frame the work is a lookup per panel.
- Icon buttons can be drawn for a caller's response (paint_icon_button),
  so the column's icons keep their ids when one above them floats.
- Translations (ja, es, zh-hans, zh-hant; cs and pt-br for the commands),
  es @msg rows, docs/control-protocol.md and ROADMAP.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 21:14:31 +07:00
73bf92b5a9 Release: Flatpak bundles, AppImage zsync updates, README Downloads (#500)
* Release: Flatpak x86_64/aarch64 bundles, AppImage zsync updates, README Downloads

- New `flatpak` matrix job (ubuntu-24.04 and ubuntu-24.04-arm) repackages each Linux tarball
  into vectorcraft-<ver>-linux-<arch>.flatpak via packaging/linux/flatpak-bundle.sh and the
  new ai.storyteller.vectorcraft.bundle.yml; packaging-lint keeps its runtime and finish-args
  identical to the from-source Flathub manifest.
- AppImages embed gh-releases-zsync update information and ship a .zsync beside them; the
  Linux job checks both.
- Jobs that sign nothing (version, linux, flatpak, freebsd, web) leave the `release`
  environment, so a branch workflow_dispatch is a dry run; the draft-release job still needs
  the signed macOS/Windows jobs.
- README: a Downloads section listing every build; docs/releasing.md updated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Release: read AppImage update info without extract-and-run

With APPIMAGE_EXTRACT_AND_RUN=1 the runtime forwards --appimage-updateinformation to the app,
which then tries to open a window and fails on the headless runner.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-08 10:00:34 -04:00
bflatasticandClaude Opus 5.5 d0a99faf1e macOS DMG: generate DS_Store and TIFF from a script, attribute the assets (review of #493)
- packaging/macos/dmg/generate.py writes background.tiff and DS_Store on any OS (resvg with
  the repo's fonts; ds_store and mac_alias, as dmgbuild uses). Both are byte-reproducible and
  carry nothing from the machine that made them.
- DS_Store: same window, icon size, positions and background alias as before, without the
  bookmark (pBBk/pBB0) and the alias's disk-image record, which held the author's boot volume
  UUID and name, its creation date and temporary paths.
- background.tiff: the same pixels as before (checked), now opaque RGB with Deflate (1.8 MB to
  1.0 MB) and LittleCMS's built-in sRGB profile instead of an HP profile with no open licence;
  no file names or software tags.
- background.svg: drop an unused pattern and the xlink namespace (renders the same).
- ASSETS.md rows for the three files; README, docs/releasing.md (volume name) and ROADMAP.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 20:36:43 +07:00
bflatasticandClaude Opus 5.5 d11daabdc7 Contextual Task Bar: drag it by its handle, pin and reset its position
The grip at the bar's left end now drags the bar anywhere on the canvas
(grab cursor), which always holds it whole, also when the window shrinks.
Unpinned, the bar keeps that offset as it follows the selection (in the
document it was moved in); the "…" button opens a More Options menu with
Hide Bar, Pin Bar Position (checked while pinned: the bar stays where it
is across selection changes and the handle still moves it; unpinned it
follows the selection again from there), Reset Bar Position (unpinned,
back under the selection) and Show Properties Panel, as in Illustrator.
As there, the position and the pin last for the session only.

- UI commands window.taskBar.pin {pinned?} and window.taskBar.reset, so
  the control channel and MCP reach them; ui.inspect reports taskBar
  {pinned, rect}.
- One opt_bool helper reads the {key?: bool} params of the toggling window
  commands (toolbarColumns, floatTools, collapseDock, taskBar.pin).
- New strings in ja, es, zh-hans, zh-hant, pt-br and cs; the dropped
  "Hide Contextual Task Bar" tooltip and per-key es messages removed.
- Headless egui test: dragging the grip moves the bar, the offset follows
  the selection, pin holds it across selection changes and a shrinking
  window, unpin follows from there, reset restores.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 20:23:15 +07:00
wstockandClaude Fable 5.1 948f696f52 Part of #394: Auto-Collapse Iconic Panels
Preferences › User Interface › Auto-Collapse Iconic Panels was stored
and never read: a panel popped out of the icon column stayed until its
» or its icon. On, a press away from the flyout now puts it away. A
press on the icon column is left to the icons (they swap or close the
flyout on release, so acting on the press would reopen the one being
closed), and a press in a popup the flyout opened (a panel menu, a
dropdown) keeps it. Off by default, as in Illustrator.

Developed with AI assistance (Claude Code); reviewed and tested locally.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-08 20:22:14 +07:00
bflatasticandClaude Opus 5.5 077e4f34ec Numbers Without Units Are Points: picas fields, on by default (review of #486)
The option tells points from picas, where a bare `12` could be either: the
reference app dims it unless a unit is Picas, and it starts on. A number typed
with no unit into a field in picas is now read in points with it on (the
default again, as before #486), and in picas with it off; fields in any other
unit read it in their unit either way, as there. The Preferences dialog dims
the checkbox unless one of the Units is Picas.

The fields learn the preference when it changes, through
`prefs_dialog::apply_runtime` as Scrub Numeric Fields does, rather than from a
write into the egui context every frame. Tests for picas and other fields, the
wiring and the dimming; docs/mcp.md and ROADMAP say so.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 20:22:11 +07:00
wstockandClaude Fable 5.1 b1aac0cc1b Part of #394: Numbers Without Units Are Points
Preferences › Units › Numbers Without Units Are Points was stored and
never read. On, a number typed with no unit into any length field
(`num_field`, `opt_field`, and so the spin fields built on them) is read
in points whatever the field's unit; a typed unit (`12 mm`, `1in`)
still wins, and the field keeps showing its value in its own unit. The
app sets the frame's rule from the preference in `logic`, the fields
read it through the egui context (`widgets::typed_unit`), so none of
the 45 callers change.

The default flips from true to false, which is Illustrator's default
and the behaviour the fields had while the option was unread.

Developed with AI assistance (Claude Code); reviewed and tested locally.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-08 20:22:10 +07:00
wstockandClaude Fable 5.1 f1935596ef Part of #394: Show The Home Screen When No Documents Are Open
Preferences › General › Show The Home Screen When No Documents Are Open
was stored and never read: the Home screen always replaced the canvas
while no document was open. Off, the window is now empty while no
document is open (as when Illustrator's option is off), and the Home
button (`app.home`) still shows the screen; closing the last document
returns to the empty window. `menus::home_showing` is the one place
that decides, for the canvas and the Home button's lit state.

Developed with AI assistance (Claude Code); reviewed and tested locally.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-08 20:21:42 +07:00
bflatasticandClaude Opus 5.5 517b166722 Fix #485: the mouse wheel steps the focused numeric field
The wheel did nothing over numeric fields. As in Illustrator, once a numeric
field has the focus, turning the wheel over it steps its value like Up/Down:
a notch a step, Shift ten, Ctrl/Cmd a tenth, applied at once with the new value
selected.

- widgets::step_value (was step_with_arrows) reads the arrows and the wheel
  through one path for num_field, plain/mixed fields and the spin fields built
  on them (panels, the Control bar, dialogs, the Font Size and Leading combo
  boxes), and now opt_field (Stroke dash/gap) too. Trackpad and fine-wheel
  points add up to notches; the rest carries to the next turn.
- While the pointer is over the focused field the panel under it doesn't
  scroll; an unfocused field, or the pointer anywhere else, leaves the wheel to
  the panel and the canvas as before.
- The dialogs' and preferences' egui drag values become the same recessed
  number fields (widgets::range_field: clamped to their range, rounded to their
  precision), so they type, step and scroll like every other numeric field.
- Headless tests for the wheel (units, modifiers, points, focus and pointer),
  and for the panel scrolling or not; docs/control-protocol.md and ROADMAP.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 20:21:39 +07:00
bflatasticandClaude Opus 5.5 377f236687 Fix #483: Shift-drag marquees toggle the selection; the Lasso's Alt keeps the rest
A Shift-drag marquee only ever added to the selection, so there was no way to
take some of the selected objects out with it. As in Illustrator, Shift-drag
now toggles what the marquee reaches: the selected ones are deselected, the
others selected, the rest of the selection stays.

- Selection tool: the Shift marquee runs select.toggle, which now also takes
  {ids} (each one toggles).
- Direct Selection and Group Selection: the Shift marquee toggles the anchors
  inside it (select.anchorsMany mode "toggle"); a path left with none leaves
  the selection, one with all of them is selected whole again.
- select.anchorsMany takes mode "set"|"add"|"toggle"|"subtract" (add: true is
  still mode add). Adding anchors of a path selected whole keeps it whole, and
  anchor references outside the path are ignored.
- Lasso: Shift adds and Alt subtracts through the same command, so an Alt lasso
  no longer drops selected type, groups and other non-path objects.
- Tests at the tool, engine (Session::pointer) and MCP (pointer_gesture) levels,
  command sweep cases for the new modes; docs/mcp.md and ROADMAP.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 20:21:36 +07:00