Commit Graph
1249 Commits
Author SHA1 Message Date
bflatastic c9a2491ef7 Rotate View: a dial shows the angle while a drag rotates the view
Squashed from #2677 (its merge of main dropped).
2026-10-19 00:37:53 +07:00
bflatastic b529f356cc Tests: the dock-width test builds its UiState with the width, as clippy asks (#2953) 2026-10-19 00:28:48 +07:00
nrq6993-a11yandCursor 57ddb71d97 #1753: saved workspaces restore dock panel width
Persist the main dock column width in workspace snapshots and reapply it when switching layouts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 00:27:40 +07:00
bflatastic 048875cc4e fmt (#2952) 2026-10-19 00:27:40 +07:00
nrq6993-a11yandCursor 49f7f5d7d8 G1: busy cursor during background open, save and export
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 00:26:27 +07:00
bflatastic 278fba490a i18n(ka): the pdfInfo thumbnail page error (#2914) 2026-10-19 00:16:42 +07:00
bflatastic d807236bf2 i18n(zh-hant): the shared parameter-check errors, now that Traditional Chinese translates every message (#2930, #2784)
#2784 made zh-hant a language that translates every status and error message while #2930 added the shared parameter-check errors to the others; main's shared_param_check_errors_are_translated failed on zh-hant.
2026-10-19 00:15:35 +07:00
dotCooCoo ab01a1a927 Save for Web: each browser preview is written to its own folder
Preview in Browser wrote every page to one folder,
VectorCraft Save for Web in the system's temporary folder. A second
preview of a document with the same title, from the same app or from
another one running, overwrote the first preview's page, and any image
file of the same name (PNG-8 and PNG-24 both write .png), before the
browser read them. The same overwrite made
dialogs::tests_save_for_web::preview_in_browser_opens_the_page fail now
and then, when another Save for Web test in the same binary, or a test
run in another checkout on the machine, previewed Untitled-1 at the
same time.

Each preview now goes to a new folder inside that one, named after the
process id and a count of the process's previews. A name that an
earlier process with the same id left there is skipped for the next
count. The page keeps its name and its images folder. A preview first
removes the entries of VectorCraft Save for Web that were last changed
an hour ago or earlier, which also clears the files the single folder
held before.

Docs: docs/target-app-parity.md.
2026-10-19 00:03:53 +07:00
dotCooCoo 9fe0f03de2 i18n: number, object id and date parameter errors are translated
The errors of the parameter checks that many commands share had no
@msg row in any catalog: "`{key}` must be a number, not {v}" from
check_numbers (object.move, object.rotate, object.setBounds and the
other callers), "missing integer `{key}`" from index_param in
cmd/appearance.rs (appearance, effect and freeform gradient commands),
the three object id errors of checked_ids_param and checked_id, and
the Unix seconds error of date_param. A template
matches only the whole text, so the row for "`{key}` must be a
number" did not match the longer check_numbers message. These errors
showed in English in the status bar, and "no such object id {value}"
matched the general "no such object {0}" row and showed half in
English. The scan in complete_languages_translate_every_message does
not read messages built with bad(), so that test did not report them.

Each of the 6 messages now has a row in the 18 catalogs that translate
every status and error message and in Bulgarian, Danish, Indonesian and
Dutch, and "missing number `{key}`" has a row in Danish and Indonesian.
No English string changes.

Docs: docs/target-app-parity.md.
2026-10-19 00:03:48 +07:00
dotCooCoo 05264b2801 MCP: document.pdfInfo answers one page for an EPS, a .ps file or a PostScript .ai
document.pdfInfo refused PostScript (an EPS, a .ps file, or a .ai in
PostScript form), mostly with "this is a PostScript file ..., not a
PDF", although document.open and file.place read the same file
through the EPS reader as one page.

It now answers {pages: 1, needsPassword: false, pageInfo: [{width,
height, rotation: 0, boxes: {}}]}. The page is the file's bounding box
(%%HiResBoundingBox when it has one, a US Letter page without one),
read from its comments without running its program. thumbnail: 1 is a
PNG of what that page prints, read by the EPS reader without any
editing data the file carries. A program whose header is only %! is
read as PostScript, as clipboard.importEps reads it.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-19 00:03:45 +07:00
dotCooCoo 841fc0cd51 i18n: Traditional Chinese translates status-bar and error messages
Squashed from #2784 (its merge of main dropped).
2026-10-18 23:50:58 +07:00
MortenHusted d25289a3be Select: select.color picks objects by fill and stroke colour, no object needed
Scripts had to know an object id before they could select art by colour
(select.same.* and select.magicWand start from one). select.color
{fill?, stroke?} takes colours as paint.setFill does and selects the
objects whose fill and stroke read as them in #rrggbb, as
document.inspect reports colours, with Select > Same's selection rules.

The label is translated in each of the 23 catalogs, which
complete_languages_translate_every_menu_string requires.
2026-10-18 23:50:57 +07:00
dotCooCoo 09a6fbe470 Type: a click away from the text being edited ends editing without making new text
While a Type tool edited text, a click on blank canvas ended the
editing and also placed new point type at the click. Area Type and
Type on a Path did the same with a click on a path.

Such a click now only ends the editing. The typing session is
committed, empty point type that a click placed is discarded as before,
and the Type tool in use stays active; the next click makes new type.
The same applies when the editing began with a double-click from a
selection tool. A click on other type still edits it at once, and a
drag that draws an area-type frame still makes the frame.
2026-10-18 23:50:57 +07:00
dotCooCoo b30d80e056 Fields: arrow keys and the wheel step to the next whole unit, and Ctrl/Cmd steps to the next tenth
In number fields, Up/Down and a wheel notch added a whole step and kept
the fraction, so 5.1 mm stepped up to 6.1 mm. A plain step now goes to
the next whole unit in its direction (5.1 goes up to 6 and down to 5; 5
goes up to 6 and down to 4), and a Ctrl/Cmd step goes to the next tenth,
in the field's own unit. Repeated presses or notches of one size add or
take one step each, and presses and notches are applied in the order
they came. A Shift step still adds or takes ten and keeps the fraction.

The number fields that widgets.rs draws for the panels, the Control bar
and the dialogs (unit, percent, degree and count fields) all use this
step (widgets::step_value).
2026-10-18 23:50:57 +07:00
bflatastic 12d107cc56 fmt (#2928) 2026-10-18 23:50:57 +07:00
nrq6993-a11yandCursor 2025c357d9 Fix #2719: honor Display Print Size for status-bar 100% zoom
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 23:50:57 +07:00
bflatastic 88494319da fmt (#2911) 2026-10-18 23:50:57 +07:00
nrq6993-a11yandCursor bf19ada5e1 Fix #2646: keep undocked shared-dock panels opaque
Floating groups on the canvas now always get a panel-colour backdrop from layout geometry when egui has not registered the area yet, and panel bodies paint their own fill so empty regions do not show through.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 23:48:57 +07:00
Davit MatchakhelidzeandClaude Opus 5.5 4675a1633e i18n(ka): Letter and Word Spacing as in the other Craft apps; "weren't read" in the catalog's usual form
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-18 23:48:55 +07:00
ElPaule d99b04e594 i18n(de): add missing PSD error messages and effect Soft entry 2026-10-18 23:48:54 +07:00
bflatastic 5f37fccdce New Document: Recent and Saved cards keep the orientation they were made in, so one A3 card is chosen, not two (#2860)
The cards turned to the chosen orientation in every category, so a portrait and a landscape A3 in Recent looked the same and both read as chosen. Only the built-in presets' cards turn now.
2026-10-18 20:12:47 +07:00
bflatastic bb14031aa3 Canvas: selected point type's baseline marker runs under each line, centred and right-aligned type too (#2879)
The marker ran from the anchor to the right edge of the bounds, so centred type showed half a line and right-aligned type none, and rotated type a level line. It now draws each line's baseline in text space (TextObject::baselines, the lines a click selects the type on), turned with the type.
2026-10-18 19:02:52 +07:00
AJ Richardson 411f5f7680 prioritize bounding-box handles over mirror axis 2026-10-18 19:02:44 +07:00
AJ Richardson 63cebd392a Add interactive Mirror Repeat axis controls 2026-10-18 19:02:44 +07:00
bflatastic 5906f53ca5 Fonts: the timed look for activated fonts doesn't wake an idle app; the next frame reads it (#2884) 2026-10-18 19:02:44 +07:00
nrq6993-a11yandCursor 640c87b537 Fix #2576: poll for fonts font managers activate while the app is in front
Font services (NexusFont, Adobe Fonts) register faces with the OS without
touching font-folder mtimes; only re-asking the platform lister sees them.
Check every two seconds as well as on window focus, and test that path churn
is detected on the system font scan.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 18:46:03 +07:00
ham9zah 561db27fef G1: test that a group of clipped images shows only its masks and their bounds
A group of four clip groups, each a placed image larger than its
rounded-square mask: one Selection click selects the group, which
shows the four masks' outlines and no image rectangle, with a bounding
box and bounds equal to the union of the masks. The behaviour landed
in #2650 (part of #2422); this covers the grouped case.
2026-10-18 18:46:01 +07:00
Tomasz LewandowskiandClaude Opus 5.5 cc1a496947 i18n(pl): Artboard is "obszar kompozycji", Guides "linie pomocnicze"
Three new Polish rows broke the catalog's own vocabulary:

- "By Artboard or Item" and "Artboard or Item Name" (Export for Screens)
  read "obszar roboczy", which this catalog uses for Workspace; Artboard is
  "obszar kompozycji" everywhere else (72 rows).
- "Enable Guides for 9-Slice Scaling" read "prowadnice"; Guides are
  "linie pomocnicze" in the other 29 rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DukHNvUY45rjRnvtz91x33
2026-10-18 18:46:00 +07:00
bflatastic 37b90eb050 i18n: Georgian rows for the strings added since #2045 last merged main
Cancel Export, Clear History, Justification…, the .ait template and editing-data notes, the variable-library note, and the style undo templates.
2026-10-18 18:29:55 +07:00
Davit Matchakhelidze 7cf8dd4764 i18n: Georgian (ka) interface language
Squashed from #2045 (merge commits from main dropped).
2026-10-18 18:09:14 +07:00
bflatastic 1e3ab306d4 Tests: the dropdown keyboard test clears the texture updates it doesn't apply (#2824) 2026-10-18 13:26:54 +07:00
bflatastic 1b4c421bc4 .ai import: the variable-library note reads its counts after a colon and is translated in every complete language (#2842) 2026-10-18 13:26:54 +07:00
Louise FoxandClaude Opus 5.5 0842941f8d Dialogs: a plug-in's parameters scroll when they don't fit the window
A plug-in's dialog (Effect › Plug-ins, Object › Plug-ins) lays out each
parameter of its manifest as a row, and a manifest may declare up to 64.
With many of them the dialog ran off the top and the bottom of the
window, its heading and its OK and Cancel buttons off screen. A plug-in
of 64 number parameters got a dialog 2,329 px tall, its top at y = -755
in a 900 px window.

modal::scrolled_rows() puts rows in a vertical scroll area as tall as
the screen less the rest of the modal window and its edge gaps.
modal::show_at() measures that rest once it has drawn the window, for
the next frame, and asks for that frame at once if the rest changed.
The dialog then fits without waiting for input. schema_fields() puts the
parameter grid in it, for filters and effects alike. A dialog of a few
parameters is as tall as before.

a_plug_in_dialog_of_many_settings_fits_the_window opens an effect's and
a filter's dialog of 64 parameters in a 1280 × 900 window, and one of 4.
Before the change it failed with "effect: the dialog is at
[[457.0 -755.0] - [823.0 1574.0]] in the [1280.0 900.0] window".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
2026-10-18 12:41:25 +07:00
Louise FoxandClaude Opus 5.5 dbb6b470b3 Menus: leave installed plug-ins' names out of the strings to translate
menu_strings() lists the strings a complete language catalog has to
cover, and it leaves out names that are user data: fonts, recent files,
saved views and libraries. An installed plug-in's name stayed in.
Plug-ins are installed for the whole process, and a test that installs
one before complete_languages_translate_every_menu_string runs made that
test fail with the plug-in's name as an untranslated menu string.

lists_plugin() tells an Object › Plug-ins filter or an Effect ›
Plug-ins effect by its command and params. Those are part of the menu
item itself and stay right while another test installs or removes
plug-ins. menu_strings() uses it, and so does
menu_catalogs_translate_every_menu_label in place of its own copy of the
rule.

plugins_are_listed_in_the_object_and_effect_menus checks it. Before the
change it failed with "Desaturate… is a menu string to translate".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
2026-10-18 12:41:25 +07:00
MarekSpodBiedry df1bf3fe32 Close a dropdown list after an option is picked with the keyboard (#2789) 2026-10-18 12:41:25 +07:00
Fabian GrajalesandClaude Opus 5.5 ec410552cf Export: a progress dialog with Cancel for background exports
While an export runs in the background (Background Export), a dialog shows
what is being exported and how far it has got: artboard by artboard for a
raster export of several artboards, busy otherwise. Cancel (also Esc) stops
it and writes no file; Continue in Background leaves it to the status bar.
It opens after 0.4 s and never over another dialog.

Built on the shared vectorcraft_doc::cancel (#2546): fileio::progress::Progress
wraps its CancelToken and a progress sink, so a cancelled export stops
drawing and keeps no half-drawn pattern tile or effect raster.

- ui: background jobs carry their Progress; export_progress draws the
  dialog; UI commands file.cancelExport and file.exportInBackground.
- i18n: the strings in every language with complete menus, Danish, Dutch
  and Indonesian included; "{n} more exports waiting" is a plural (trn).
- docs: ui-parity, gaps (G12) and ROADMAP rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dt49z1ubK9x1bPq51APgz3
2026-10-18 12:40:20 +07:00
VictorandClaude Opus 5.5 a275496ef4 G4: Paragraph panel menu › Justification… (word and letter spacing, glyph scaling, auto leading, single word justification)
The Paragraph panel menu's Justification… was a disabled stub, and justified
lines stretched word spaces between fixed limits (80% to 150%).

Each paragraph now has Justification options: Word Spacing, Letter Spacing
and Glyph Scaling (Minimum, Desired, Maximum, in percent), Auto Leading and
Single Word Justification. Desired applies to every line; a justified line
fills its measure with word spacing first, then letter spacing, then glyph
scaling, each within its range, and past them its word spaces take the
rest. The Every-line Composer scores lines by that flexibility. Auto
Leading sets the leading of characters whose leading is Auto. A justified
line holding one word is spread between its letters (Full Justify) or
aligned left, centre or right.

The defaults are the documented ones (80/100/133, 0/0/0, 100/100/100, 120,
Full Justify), saved only when they differ. Because the default maximum
word spacing is 133%, justified paragraphs break differently than before;
the Every-line Composer's second pass is now unbounded for justified text
(it used to give up past four times the stretch and fall back to the
Single-line Composer, which the narrower default range would make common),
and lines that can't stretch at all are still refused.

text.setFormat takes wordSpacing, letterSpacing, glyphScaling ([min,
desired, max] or {min?, desired?, max?}), autoLeading and
singleWordJustification; Minimum ≤ Desired ≤ Maximum must hold or nothing
changes. Paragraph styles carry the options (New and Redefine capture
them), and Reset Panel resets them. Justification… opens a dialog
(ui.justification) with Preview; OK is disabled while a row is out of
order. Live SVG text places characters the options move, and a glyph that
Glyph Scaling scales is stretched to its width; PDF uses the glyphs'
transforms. Vertical type isn't glyph scaled; type on a path doesn't use
the options.

Docs: docs/mcp.md, docs/type-parity.md, docs/gaps.md.

Closes #2755.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:40:14 +07:00
dotCooCoo 1caa957531 History: the panel menu clears the history
The History panel menu had no Clear History, and VectorCraft had no
command that removes the undo and redo steps. In the reference app,
Clear History in the History panel menu removes the states after the
selected state, and it removes the states before it while their
changes remain in the document.

edit.clearHistory (Clear History) removes every step to undo and to
redo in the active document and returns how many it removed. The
document and the selection stay as they are, and clearing is not an
undo step. Typing or a drag still in progress becomes a step first, as
Undo makes it one, so its change stays in the document. The command is
disabled when there is no step to clear, and it waits while an input
method composes, as Undo does.

The History panel menu has Clear History after Step Forward. It runs
the command and is disabled when there is no step to clear.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-18 12:39:07 +07:00
dotCooCoo 3c0c112fbd Export for Screens: the dialog's example names the file the export writes
The Export for Screens dialog built its Example line from its fields as
typed, so it often showed a name the export does not write. It used the
artboard's name with its spaces (Artboard 1.png where the export writes
Artboard-1.png) and the first artboard whatever the range, kept a
prefix, a suffix or a device name such as CON as typed, used the scale
text as the row folder instead of the row's own folder or its size,
kept a vector row's @2x suffix, left out the folder that Create
Sub-folders adds, and read a counter set as text as 1.

The line now comes from the export's own naming. The naming part of
document.exportForScreens moves into a Naming type that the export
uses for every file, and fileio::screen_first_file returns the path of
the first file the export writes for the given params, without encoding
or writing anything. The dialog passes it the params it exports with,
without the destination, so the line shows before a folder is chosen
and is left out while the settings choose nothing to export. The Start
at field shows the counter the export uses when it was set as text.

Docs: docs/target-app-parity.md.
2026-10-18 12:37:29 +07:00
dotCooCoo 468d7b21e9 Save As: on Linux the file type picked in the save dialog decides the format
On Linux the save dialog is the XDG desktop portal's, shown through
rfd. rfd sends no `current_filter` and reads only the file name from
the portal's answer, and the GTK file chooser keeps the typed name
when another file type is picked. With "PDF-compatible .ai" picked,
`poster` typed for a new document was saved as a native
`poster.vectorcraft`, and so was the suggested
`Untitled-1.vectorcraft` left as it was.

When a save offers two or more file types (Save As, Save a Copy, Save
as Template and the first Save of a new document), the app now calls
the portal's SaveFile itself over D-Bus. The dialog starts on the
suggested file's type, and its answer names the type picked. A name
typed without an extension, or the suggested name left as it was,
gets that type's extension. A name typed with another save format's
extension, other than the suggested file's (for Save as Template, a
template format's), is saved in that format, and any other extension
gets the picked type's added. The dialog keeps the window as its
parent on X11 and Wayland, and rfd shows the dialog when the portal
can't. The same code runs on the BSDs; Windows, macOS and the web
build keep their dialogs.

Docs: docs/target-app-parity.md.
2026-10-18 12:36:23 +07:00
dotCooCoo db3092ae2e Patterns: Pattern Options refuses a name another swatch has
pattern.options refused a new name that another pattern had, but not
one that a color swatch, a gradient swatch, a second swatch of the
pattern or a color group had. The rename then gave the pattern's swatch
that name, so two swatches, or a swatch and a color group, had one
name. The rename also gave the new name to every swatch that had the
old one, a color swatch included, and art linked to that color swatch
kept a link to a name no swatch had. Make and Save a Copy numbered a
pattern's or a swatch's name, but not a color group's.

pattern.options now refuses a name that a swatch or a color group has,
with "a swatch or color group named `<name>` exists". It takes a name
that only the pattern's own swatches have, such as a name Swatch
Options gave the pattern's swatch, when none of them still has the old
name. A rename gives the new name only to the pattern's swatches that
had the old one. Make and Save a Copy number a color group's name as
they number a swatch's. The new message has a row in the 17 catalogs
that translate every message (COMPLETE_MESSAGES) and in bg, da, id and
nl.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-18 12:36:22 +07:00
dotCooCoo 0cf12a1611 Save as Template: aiVersion 7 is an error for a .ait, and the dialog shows no Version
file.saveAsTemplate with format ait and aiVersion: "7" wrote the
legacy PostScript .ai of version 7 under the .ait name: for a
one-artboard test document, a 7,556-byte file starting %!PS-Adobe-3.0.
The web's Save as Template dialog showed the .ai Version menu for
.ait, whose Legacy (version 7) choice set aiVersion 7.

aiVersion 7 for a .ait is now an error, "aiVersion 7 writes legacy .ai
files; a .ait template is always PDF-compatible", and nothing is
written. The 17 catalogs that translate every message have a row for
it. The dialog shows no Version row for .ait, and aiVersion "pdf"
still writes the PDF-compatible template.

Docs: docs/mcp.md, docs/target-app-parity.md, docs/file-format-parity.md
(two stale rows for .ai and .ait removed).
2026-10-18 12:35:18 +07:00
dotCooCoo d23030ce2b Export: a .ait export names file.saveAsTemplate, and the CLI's convert writes the template
document.export, document.serialize, document.exportSelection and
Export for Screens rows answered "PDF-compatible .ait template files
can be opened but not written" for a .ait, although
file.saveAsTemplate writes them. The CLI's convert and run --export
call document.export, so vectorcraft-cli convert in.pdf out.ait
failed with that error.

The error now reads "PDF-compatible .ait template files are written
by file.saveAsTemplate", with a row in the 17 catalogs that translate
status messages and in bg, da, id and nl. convert and run --export
write a .ait with file.saveAsTemplate, which writes every artboard
with the .ai options. convert passes --replace-lossy and its .ai flags
on, and refuses --artboard and --range for a .ait.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-18 12:34:41 +07:00
dotCooCoo 2643f57b1f Color: type, gradient, mesh and swatch commands store RGB input as color-managed CMYK in a CMYK document
In a CMYK document, paint.setFill stores an RGB color as CMYK through
the color settings, but the other commands that take a color stored it
as RGB: text.create, text.createInPath, text.setStyle and
text.setRangeStyle colors, a shapeBuilder.merge hex fill,
paint.editGradient stops, freeform gradient points, gradient mesh
points, the Symbol Stainer's color, and swatch.new {colors},
swatch.newGroup, swatch.editGroup and swatch.edit. The Mesh tool sent
the fill as a hex value, so a CMYK fill came in as RGB.

These commands now store such a color through paint::new_color, as
paint.setFill does, and take keepModel to keep RGB (shapeBuilder.merge
inside its fill object). A color the swatch
or gradient already has keeps its model, so stops sent back with a
moved offset keep their colors (the Gradient panel's ramp and the
Gradient tool send keepModel), and a stop that names a process
swatch takes the swatch's color as it is, in paint.setFill gradients
too. Where the app copies an existing color, it keeps the color's
model: the Color panel's sliders for gradient stops and freeform
points, a swatch put on a stop or a point (the stop popover, Alt-click
in the Swatches panel, a drop on the Gradient panel's ramp), the fill
the Mesh tool gives a new point or the Symbol Stainer applies, and the
colors the Color Guide, Color Themes and Recolor Artwork save as
swatches.

swatch.new {color, mode} converted RGB into CMYK before converting it
into mode, so the New Swatch dialog in RGB mode stored #00ff00 as
R101 G176 B62 in a CMYK document. With mode, the color is now
converted once.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-18 12:34:40 +07:00
dotCooCoo ef9e554dd2 i18n: the History panel translates the step names that contain a style's name
The History panel and Edit › Undo and Redo did not translate the steps
that apply or redefine a character or paragraph style, or redefine a
graphic style. The engine built their names with format! ("Apply Body",
"Redefine Body", "Redefine Graphic Style “S”"), and the panel looks a
name up as a whole, so no catalog row matched a name with a style's
name in it. A name cannot be matched against template rows instead: a
character style named Brush gives "Apply Brush", which is also the name
of the brush step.

The engine now names these steps with Session::edit_named, and each
such step keeps its template and the style's name
(HistoryEntry::named) through undo and redo. The History panel and
Edit › Undo and Redo show the template's row with the name in it
(panels::history::entry_name). The name stays as the document holds
it, and the Normal styles' names are shown in the interface language.
Every complete catalog has rows for Apply {name} and Redefine {name},
and Redefine Graphic Style “{name}” had one.
every_undo_label_is_translated also requires a row for each template,
and the only step name it allows to be built with format! is the
Liquify tools' "{} Tool".

Docs: docs/target-app-parity.md.
2026-10-18 12:34:39 +07:00
dotCooCoo 22ce516e2d Appearance: the inline effect editor shows 3D, Warp, Rasterize and channel choices as dropdowns
The Appearance panel's inline effect editor still showed eight choice
parameters as text fields holding the stored value: Revolve's edge,
Extrude & Bevel's bevel, every Warp style's orientation, Rasterize's
Color Model, Background and Anti-aliasing, and the channel of Levels
and Curves. Their dialogs built these lists in their own bodies, so
effect::choices did not hold them. Nothing checked a channel's value:
a typo was stored, and Levels or Curves drew it as RGB.

effect::own_choices now holds these lists, each label as its dialog
passes it to its dropdown or radio buttons. The dialogs read the lists
from it (Rasterize's Anti-aliasing from the AntiAlias list it is built
from), and the inline editor shows each one as a dropdown with the
dialog's translated labels; Rasterize's Color Model reads CMYK or RGB
by the document's mode. effect.apply and effect.setParams refuse a
Levels or Curves channel other than rgb, red, green or blue (r, g, b
and any letter case are read too) with "`channel` must be one of rgb,
red, green, blue", and the document and its undo history stay as they
were.

Docs: docs/mcp.md (effect params), docs/target-app-parity.md.
2026-10-18 12:34:36 +07:00
dotCooCoo 30e54dedcf Accessibility: color and channel sliders report their values to screen readers
The color and channel sliders gave screen readers a name but no value.
Each caller maps its own range onto the thumb's position, so a screen
reader announced the Color panel's G slider, Transform Each's Scale or
the Navigator's Zoom without the setting the field beside it shows.

color_slider_valued and channel_slider_valued take an optional
SliderValue: the number and text of the field beside the slider and
the slider's range. The accessibility node gets the number, the text
and the range, which extends to a number past the slider's end.
color_slider_named and channel_slider_named keep their arguments and
report no value. Every caller outside the tests uses the new variants:
the Color panel, Swatch Options, the Color Picker, the Navigator, the
Character panel's variable font axes and the dialog sliders.
form::slider_rail_named reports the number it edits, and the harmony
wheel's Brightness slider, which has no field, reports its brightness
in percent.

Docs: docs/target-app-parity.md.
2026-10-18 12:34:35 +07:00
dotCooCoo e461b8b818 Fields: Shift+Enter applies a pattern, layer, artboard or asset name and keeps the field active
Shift+Enter (Shift+Return) in the Pattern Options panel's Name field and
in the name fields that a double-click opens in the Layers, Artboards
and Asset Export panels ended the editing, as Enter does. These fields
draw their own text box and do not use the shared field widgets that
#2431 changed.

- Each of them now calls widgets::own_field_shift_enter, built on
  #2431's shift_enter_keeps_focus and after_shift_enter. Shift+Enter
  applies the name as Enter does and the field keeps the focus. In the
  next frame the field shows the name as applied, all selected, so
  typing replaces it.
- Enter, Escape and a click elsewhere work as before.

Docs: docs/target-app-parity.md.
2026-10-18 12:34:34 +07:00
dotCooCoo 496288a023 Direct Selection: a group selected whole shows its anchors as selected
With Direct Selection, the Pen or the anchor tools, a group, compound
path or clip group selected whole drew the anchors of its paths hollow,
the look of unselected anchors, while a path selected whole drew its
anchors filled. After two Group Selection clicks selected a path and
then its group, the group's hollow anchors covered the path's filled
ones.

With these tools, the selection overlay now draws the anchors of the
paths inside a group, compound path or clip group selected whole
filled, as it draws the anchors of a path selected whole. Direction
handles still show only on a path that is selected itself, and Direct
Selection drags only those.

Docs: docs/target-app-parity.md.
2026-10-18 12:34:33 +07:00
dotCooCoo dd5d421a9c Tabs: Repeat Tab measures the first stop from the left indent
Repeat Tab on a paragraph's first tab stop spaced the copies by the
stop's distance from the ruler's 0. The reference app measures that
distance from the paragraph's left indent. With a left indent of 20 pt
and one stop at 50 pt, the copies were 50 pt apart instead of 30.

Repeat Tab now spaces the copies of the first stop by its distance
from the left indent of the paragraph whose stops the Tabs panel
shows: the paragraph the caret is in while the Type tool edits text,
otherwise the first paragraph of the first selected type object. A
stop left of the indent repeats at its distance from the indent: a
stop at 5 pt in a paragraph indented 20 pt repeats every 15 pt. The
reference app's help page does not cover this case, so this follows
VectorCraft's own rule. The copies of a later stop are still spaced by
its distance from the stop before it, and the limits of 20 copies and
2,000 pt stay.

Docs: docs/target-app-parity.md.
2026-10-18 12:34:31 +07:00