Commit Graph
686 Commits
Author SHA1 Message Date
KattandClaude Opus 5.5 a3316d2383 Links: wake for the first look at a link placed while idle
The link watch wakes the app every two seconds only while the last look
found links (068a934, #967). A file placed less than two seconds after a
look that found none asked for no more frames, so while the window sat
idle nothing stamped it; a change made meanwhile was taken for the file
as first seen and never acted on. The watch now remembers the active
document's uid and revision at each look and, when they differ on a
later frame, asks for one wake when the next look is due. There is
still no per-frame walk of the links, and no two-second wakes for a
document without links.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 04:10:33 +07:00
bflatastic cacc9e4f36 #1006: Select > All on Active Artboard selects on the active artboard
The menu item sends no artboard, so the engine fell back to the first one even with another
active in the Artboards panel.

The frontend fills in the active artboard (the view's, as the Artboards panel and navigator
show it) for select.allOnArtboard, as it already did for Paste in Place / in Front / in Back
(#693); the two now share one block, and the index is kept within the artboards. An explicit
artboard still wins; the engine is unchanged.

The test makes artboard 2 active, then 1, and invokes the menu item each time, then passes
artboard 1 explicitly with artboard 1 active.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	ROADMAP.md
2026-10-11 03:51:27 +07:00
bflatastic 148ef1870a #1004: a connected app's screenshot renders the artboard asked for
The desktop app's ui.render (MCP `screenshot` when connected) always rendered the first
artboard and ignored `artboard`, which the headless backend honours.

It now renders the 0-based `artboard` (the first without one, an error for one that doesn't
exist), as headless does. control-protocol.md and the control.rs list show the parameter.

The test drives ui.render through the control channel on a 64 x 64 and a 96 x 48 artboard:
no index and 0 give 64 x 64, 1 gives 96 x 48 (48 x 24 at scale 0.5), 2 is an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	ROADMAP.md
2026-10-11 03:51:19 +07:00
bflatastic eeb213e5a8 #1002: new art is refused when no layer is shown and unlocked
With every layer hidden or locked, the insertion layer fell back to the top layer anyway, so
drawing a shape, typing, pasting, making a graph, a flare, a grid, trim marks or a symbol put
the art on a hidden or locked layer: invisible, or unselectable by Select All. Illustrator
refuses to draw on a locked or hidden target layer (public forum answers, e.g.
https://community.adobe.com/t5/illustrator/can-t-draw-a-new-object-in-my-document/m-p/11107182).

DocState::target_parent is insertion_parent for new art, failing with "the target layer is
locked or hidden" when it would be a layer that isn't editable; those creation paths use it.
Another layer that is shown and unlocked still takes the art, as before. The message is in the
de, es, fr, it, ja, ru and uk catalogs. File > Place is left as it is (another branch reworks
it).

The test hides, then locks, the only layer: an ellipse, a polar grid, text and a paste each
fail with the message and leave the document unchanged; a new layer then takes a rectangle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	ROADMAP.md
2026-10-11 03:51:11 +07:00
bflatasticandClaude Opus 5.5 9e09d53fd9 Review #1013: the stepper test from #1015 compiles and hits the spinner
The headless test taken from #1015 set `RawInput::modifiers`, which egui
0.36 doesn't have, and clicked left of the spinner (the right-aligned
label's text ends before its box). Modifiers now come as
`ModifiersChanged` events, as the Appearance panel tests send them, and
the click is placed from the field's text. It also checks Ctrl/Cmd steps
a tenth. It fails with the spinner ignoring modifiers (15 pt for 24).
rustfmt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 02:14:00 +07:00
Nigel Ihza Firdausy 6d5fdc8a2a test(ui): Shift-click on the Stroke weight stepper steps 10 pt (#991)
The headless panel test of #1015, kept on top of #1013's fix (#1015's
own change to the spinner is superseded by it).
2026-10-11 02:06:41 +07:00
Nigel Ihza Firdausy 057c2ee2c1 fix(widgets): honor Shift on numeric spinner buttons (#991) 2026-10-11 02:06:20 +07:00
bflatasticandClaude Opus 5.5 5a9c6e2e06 Review #1012: whole numbers typed in command dialogs are integers
The PR kept a field's integer kind only while its value had been an
integer: typing 2.5 in Columns and then 3 sent 3.0, and the mosaic fell
back to 10 × 10 again. A whole number typed in any command dialog is now
an integer (`typed_number`) whatever the field held before; commands
that take any number read integers as well, and a fraction stays one.

Test: Object › Create Object Mosaic… opened from its menu item, 3 typed
over Columns and Enter makes a 3 × 10 mosaic (it made 10 × 10 before,
#1000).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 02:06:19 +07:00
Nigel Ihza Firdausy 130bacde12 fix(dialogs): preserve integral command parameters in numeric fields (#1000) 2026-10-11 01:59:54 +07:00
bflatasticandClaude Opus 5.5 59c890df9e Part of gap 2: Effect › Brush Strokes (Accented Edges, Angled Strokes, Crosshatch, Dark Strokes, Ink Outlines, Spatter, Sprayed Strokes, Sumi-e)
Eight raster effects on the pixel-filter pipeline
(vectorcraft_effects::pixel::brushstrokes), under a new Effect › Brush
Strokes submenu between Blur and Distort in the Raster Effects section:

- Accented Edges (edgeWidth pt 1..14, default 2; edgeBrightness 0..50,
  38; smoothness 1..15, 5): the edges of the softened object painted
  towards white chalk at high brightness or black ink at low brightness;
  Smoothness also leaves the faint edges out. The shape stays as it was.
- Angled Strokes (directionBalance 0..100, 50; strokeLength pt 3..50, 15;
  sharpness 0..10, 3): light areas in right diagonal strokes, dark areas
  in left diagonal ones; 0 makes every stroke a left diagonal, 100 every
  stroke a right diagonal. Sharpness brings detail and bristles back.
- Crosshatch (strokeLength pt 3..50, 9; sharpness 0..20, 6; strength
  1..3, 1): the detail kept under pencil hatching along both diagonals,
  Strength passes of it, the edges roughened.
- Dark Strokes (balance 0..10, 5; blackIntensity 0..10, 6;
  whiteIntensity 0..10, 2): short strokes towards black in the dark
  areas, long strokes towards white in the light areas.
- Ink Outlines (strokeLength pt 1..50, 4; darkIntensity 0..50, 20;
  lightIntensity 0..50, 10): black outlines over the edges, ink hatching
  in the shadows, shadows darkened and highlights lightened.
- Spatter (sprayRadius pt 0..25, 10; smoothness 1..15, 5): each pixel
  shifted by noise up to 0.4 x the radius; Smoothness makes the grains
  larger and smoother.
- Sprayed Strokes (strokeLength pt 0..20, 12; sprayRadius pt 0..25, 7;
  strokeDirection Right Diagonal, Horizontal, Left Diagonal or Vertical):
  strokes along the direction, scattered by up to 0.3 x the radius.
- Sumi-e (strokeWidth pt 3..15, 10; strokePressure 0..15, 2; contrast
  0..40, 16): wet strokes, ink bleeding from the darks along paper
  fibres, more ink with pressure and an S-curve contrast for rich blacks.
  It works in value only: hues and saturation stay, and so does the
  object's shape (no fringe in the transparency).

A stroke averages the colour along a segment through the pixel (16
samples at most, the art softened first when they lie more than a pixel
apart); strokes lie in rows around the object's centre, each row
starting at random and each stroke shifted along itself, with streaked
noise for the bristles. Everything is in document space, so the effects
look the same at any zoom or resolution, move with the object and draw
the same every time; outsets and reaches are exact. On a 2000 x 2000
raster in release the slowest settings take 0.3-2.0 s (Crosshatch at
50 pt with 3 passes the most).

Shared helpers instead of copies: edges::sobel (Glowing Edges now uses
it, unchanged), distort::displace and texture::Tones are pub(super).

Catalogue rows with parameter docs, menu entries, Sprayed Strokes'
direction dropdown, translations (de, es, fr, it, ja, ru, uk, zh-hans,
zh-hant; menu labels in cs and pt-br), tests (parameters and clamping,
outsets and reaches, each filter and option showing, chalk and ink on
the edges only, shadows and rich blacks, Sumi-e keeping the shape and
hues, hatching keeping the tone, strokes smearing along their direction,
Spatter's radius, resolution and move invariance, canvas, multithreaded,
PDF and SVG, the command sweep with junk parameters, the menu) and
ROADMAP.

Behaviour from public documentation only:
https://helpx.adobe.com/photoshop-elements/using/brush-stroke-filters.html
https://www.computerhope.com/jargon/p/photoshop-sprayed-strokes.htm

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 01:19:26 +07:00
bflatasticandClaude Opus 5.5 f443dde16f Review: German for Revolve, one row per catalog key, README
The Revolve dialog and gizmo strings in German (the catalog landed after
this branch was written), with Kreiseln and Aussehen umwandeln as the
catalog already names them. Intensity, which main's Texture filters
added meanwhile, is kept once per catalog. README's missing list says
what 3D still lacks beyond the initial Revolve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:04:55 +07:00
alpha-rock c36b6976e3 M9: add live Revolve with artboard rotation and lighting controls 2026-10-10 23:44:46 +07:00
bflatasticandClaude Opus 5.5 d0015a496b variables: Window › Variables opens the panel
As Window's other panel items do, and as the reference's Window menu has
it, Window › Variables is the panel itself instead of a submenu of
eighteen commands. The panel menu gains Make Text Dynamic, Make
Visibility Dynamic, Unbind Variable and Delete Variable beside the
bottom bar's buttons, which share one reading of what the selection
allows; every command stays in the command palette. The labels only
the submenu used leave the catalogs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:34:21 +07:00
bflatasticandClaude Opus 5.5 500e19680d variables: review fixes for the panel, dialogs and translations
- Dataset and variable names are the user's: the panel's Data Set dropdown and
  the dialogs' name dropdowns show them as they are instead of translating
  them; the dialog's Kind dropdown shows translated kind names.
- Choosing the empty option in a dialog dropdown picked the first name.
- The panel reads the art once per frame for every row's italics, the dialog
  no longer clones the document's variables every frame, and slices are
  matched instead of indexed.
- "No document" and "Define a variable first" go through tl!, and the dialog
  headings Variable Options / Edit Data Set, Text and Visibility have rows in
  every complete catalog; the unused "{name} · {kind} · {count}" row is gone.
- German rows for every Variables string (the catalog predates this branch).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:34:21 +07:00
Fahri Meral f2252b0d9c variables: highlighted row owns Delete, Options and Select Bound Object
Select Bound Object ran variable.unbind on the selection, so choosing it silently
removed the bindings; Delete Variable and Delete Data Set deleted every row. Now
the panel keeps a highlighted variable row (a click highlights and selects what
it drives, as the reference panel does): Select Bound Object selects what the
highlighted variable drives via select.set, Delete Variable deletes only it, and
Delete Data Set deletes only the current set. Variable Options and Rename Data
Set open their dialogs preselected with the highlighted variable / active set
(the double-click used to invoke variable.rename with a bare name, which only
errored). Unbind enables only over a bound selection; Make Text Dynamic only for
exactly one type object.

The highlight is per-document session state with its own command
(variable.highlight, no undo step, like layer.highlight): rename carries it,
delete clears it when stale. New engine tests (18) and panel tests cover the
reviewer's regression case, verified to fail on the old wiring.
2026-10-10 23:34:21 +07:00
Fahri Meral 06dbe5b1e8 variables: put the panel in the collapsed icon column
Opening the panel from the Window menu worked, but collapsing the dock did not
show it: `ICON_PANELS` only registers a panel (so `window.panel` finds it) while
`ICON_PANEL_GROUPS` is what the collapsed column actually draws, and `variables`
was in the first list only. Sixteen panels are registered without a group; for
the ones meant to live in the column, the entry in `ICON_PANELS` alone is not
enough.

`variables` gets a row of its own at the bottom of the column, where the
reference app keeps it. Both lists now say which is which, so the next panel
does not repeat this, and a test asserts a registered panel reaches the column
(it fails without the group entry) and that every grouped id names a registered
panel with an icon.

`floating.rs`'s icon test counted the column's icons, which only means
"one left" while the column is tall enough to show them all — past that, one
scrolling into view replaces the one that left. It runs on a window tall enough
for every icon now, and checks the count is the number of registered groups'
icons rather than a number that happens to pass.

Dragging a panel into the icon column still doesn't work, and is not something
this PR changes: tearing a panel off makes it float, for every panel. Docking it
back into the column would be a dock feature of its own.

cargo xtask ci: all 7 steps pass on Rust 1.99.
2026-10-10 23:34:21 +07:00
Fahri Meral 5a638b0a80 variables: the panel does the workflow itself, and rows can be captured
The engine side was called a solid base, but the panel could only list and
apply: variables and datasets were made through commands, which most people
won't find. The reference app's Variables workflow is now what the panel does,
and every button runs a `variable.*` / `dataset.*` command, so the panel and an
agent drive the document the same way.

New commands (all also in Window › Variables, the palette and over MCP):

- `variable.makeTextDynamic` / `variable.makeVisibilityDynamic`: define a
  variable of that kind over the selection, named after what it drives, and
  bind it — one step, as the panel's two buttons.
- `dataset.capture`: record what the bound objects hold right now as a new row
  ("Data Set 1"… ) and make it active. This, rather than typing values, is how
  rows are made: change the art, then capture.
- `dataset.update`: write what the art holds now into the active row. Without
  an active row it says so rather than picking one.
- `variable.rename` / `dataset.rename` (Variable Options…, Rename Data Set…):
  a name is how a variable is referred to, so renaming carries its bindings and
  its values in every row with it.
- `dataset.list` reports `matches` per row: whether the art still holds what
  that row says.

`Document::captured_values` is the one place that reads a bound object's value,
so capture, update and the drift check can't disagree about what the art says.

Panel: the current data set across the top as a dropdown with ◀ ▶; variable
rows with their kind's icon and the name of the object they drive ("Multiple
objects" when it is more than one), a click selecting it and a double click
opening Variable Options; a dataset row in italics once an edit has moved the
art away from what it says; Make Text/Visibility Dynamic, New, Unbind and Delete
below, Capture and Update on a second line; and a (≡) menu with the same
commands for the ones that act on a row rather than the selection.

Checked on Rust 1.99 (the current stable), which is what the review asked for:
cargo xtask ci, all 7 steps pass.
2026-10-10 23:34:21 +07:00
Fahri Meral 202894372a variables: fix binding lifetime, dataset editing and the missing UI to define
Review of #761 turned up three real defects and a feature that was visible
but not usable. All of it is fixed here, each defect with a regression test
that fails without the fix.

- A binding outlived the object it named. Deleting art left its entry in
  `variables.bindings` forever: it counted as a skip in every dataset
  application, `variable.list` counted it, and the saved file grew with every
  delete. `Document::prune_variable_bindings` now lets them go after each
  edit, as `prune_assets` does for Asset Export's.
- `Variables::prune` cleared `active_dataset` by comparing it against the
  *variable* names being deleted. A dataset named like a variable lost its
  active state when the variable went. The two are separate namespaces; the
  block is gone (dataset.delete already did it right).
- `variable.delete` reported the number of names asked for, not the number
  deleted, so deleting a name that was not there still said `deleted: 1`.

- `dataset.set` (new): a dataset's values could only be set when it was
  created, so changing one meant deleting and rebuilding the row — losing its
  place in the list and the active one. This replaces the row's values and
  reports what it dropped.
- Window › Variables, and dialogs for the "…" commands. Every command had an
  empty menu path and the command palette runs those with `{}`, so nothing
  could be defined from the UI at all. The commands now carry their menu
  path, the "…" ones open a Variables dialog (a name and a kind; a name and a
  field per variable, its kind's editor), and `dataset.set` opens with the
  row's own values.
- Locked art is neither bound nor rewritten by a dataset, and says so.
- `set_plain_text` told a node that existed but was not type that it was
  missing.
- The panel had Document Info's icon, so the two were indistinguishable in
  the collapsed column; it gets its own, plus New/Delete buttons, and its
  rows say what their count is.

Format round trips now cover variables (the testkit fixture defines both
kinds, binds them and applies a row), and ROADMAP/README no longer list the
feature as missing.

cargo xtask ci: all 7 steps pass.
2026-10-10 23:34:21 +07:00
Fahri Meral ba2069c057 variables: panel, window menu entry and translations 2026-10-10 23:32:59 +07:00
bflatasticandClaude Opus 5.5 8a0cd9dbf6 Part of gap 2: Effect › Distort (Diffuse Glow, Glass, Ocean Ripple)
Three raster effects on the pixel-filter pipeline
(vectorcraft_effects::pixel::distort), under a new Effect › Distort
submenu between Blur and Pixelate in the Raster Effects section (the
vector Distort & Transform stays its own submenu):

- Diffuse Glow (graininess 0..10, default 6; glowAmount 0..20, 10;
  clearAmount 0..20, 15): the highlights, softened over 2 points, glow
  white from the brightness Clear Amount leaves clear; white grain as
  dense as Graininess says is sprinkled over the object, thicker in the
  glow. Transparency stays as it was.
- Glass (distortion 0..20, 5; smoothness 1..15, 3; texture Blocks,
  Canvas, Frosted or Tiny Lens, default Frosted; scaling % 50..200, 100;
  invert): the object seen through a glass surface; each pixel takes the
  colour the surface's slope bends it to, sampled between pixels. Slopes
  are taken over a span Smoothness widens. The surfaces are made in code
  (Canvas is Texturizer's weave); Load Texture is left out.
- Ocean Ripple (rippleSize 1..15, 9; rippleMagnitude 0..20, 9): smooth
  noise ripples about Ripple Size + 2 points across shift the object by
  up to 0.03 x magnitude x their size.

The shifts are capped, so the effects' outsets and reaches are exact,
and every surface lies in document space around the object's centre:
the same at any zoom or resolution, moving with the object, identical
on every redraw.

Catalogue rows with parameter docs, menu entries, the Texture dropdown,
translations (and German's missing Canvas), tests (parameters and
clamping, outsets, each texture and Invert, the ripples' reach, the glow
and its grain, resolution and move invariance, canvas, multithreaded,
PDF and SVG, the command sweep with junk parameters, the menu) and
ROADMAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:23:36 +07:00
bflatasticandClaude Opus 5.5 3a48de555e Review: translate the tick mark lengths; ROADMAP
Short and Full Width, the Graph Type dialog's new Length choices, in the
complete catalogs (None was there). The Value Axis dropdown keeps the
one that landed with #971. ROADMAP Last updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:49:15 +07:00
IHAGI-c d7fb5ec727 Graph Type: tick mark length and count per axis, category tick marks, axis label prefix and suffix
Each axis now takes its own tick marks, and the value axes a prefix and
suffix for their numbers:
https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/change-graph-value-axes.html

- Value axes (the left one, or a bar graph's bottom one, and the right
  one): tick marks of no, short or full length, and a count per
  division; labels stay at the divisions.
- Category axis: the same, at the labels or, with Draw Tick Marks
  Between Labels, between them (halfway between labels when they sit
  at the plot's edges).
- Add Labels: prefix and suffix per value axis ($, %).
- The Graph Type dialog offers the lengths and value axis sides as
  choices, and counts it sends back as floats are read.

Own choices, as the page doesn't say: the lengths are none, short (the
4 pt ticks as before) and full (across the plot); category axes start
with no tick marks, as they had none before. Counts are capped at 20
per division and 10,000 category tick marks, affixes at one line of 64
characters. A full-length tick on the zero line or an axis line is left
out.

The legend moves past a bar graph's last value label when a suffix
runs it into the legend's gap. New fields are written only when set;
graphs with the default options draw the same art as before.
2026-10-10 22:38:35 +07:00
bflatasticandClaude Opus 5.5 bea68002e9 Review: Graph Type's Value Axis is a dropdown
The generic Graph Type dialog showed `valueAxis` as a text field. It now
lists On Left Side, On Right Side and On Both Sides, translated in the
complete catalogs, with a test that the choices match the command's
params and that OK moves the axis. ROADMAP Last updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:48:51 +07:00
bflatasticandClaude Opus 5.5 a644c818c9 Review: German for the features landed since the catalog was written
Pixelate, Texture and Video raster effects, Libraries groups, search,
import and export, Update Changed Links, System Title Bar, Scatter, Art
and Pattern Brush Options with Colorization, and the PSD messages, in
the catalog's own conventions. ROADMAP Last updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:28:15 +07:00
dwetscherandClaude Opus 5.5 194968bc27 German interface language (de)
Add the German catalog: every menu string, every `tl!` literal, the plural
forms and the status and error messages (3,818 rows), written from the meaning
of the English labels in the vocabulary German-speaking users of vector
illustration software know (Zeichenfläche, Farbfelder, Kontur, Fläche, Pfad,
Ankerpunkt, Ebene, Verlauf, Deckkraft). Conventions are in the header of
`de.tsv`.

- Register `de` ("Deutsch") in `i18n::LANGUAGES` as a complete language; every
  `de-*` locale (`de-DE`, `de-AT`, `de-CH` …) resolves to it.
- Tests: German joins the menu-label, menu-string, `tl!`-literal and message
  coverage checks; `german_reads_as_german` pins the core vocabulary, the
  plurals, a nested message, the joined "…format" style names and the theme
  context ("Erscheinungsbild" vs the Appearance panel's "Aussehen"); ä ö Ä Ö ß
  are checked in the bundled UI fonts. The "unsupported locale" cases use
  Dutch now that German is supported.
- README, ROADMAP and docs/development.md list German.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:28:15 +07:00
bflatasticandClaude Opus 5.5 6315086f96 Review: System Title Bar only where it applies, docs, title repaint
- Preferences hides System Title Bar on macOS and the web, where it did
  nothing (it fell through to the plain checkbox row), and lists it with
  the window options instead of under Language.
- The next-launch note reuses "Applies the next time VectorCraft starts."
  instead of a second wording in every catalog.
- A new window title asks for one more frame: AppKit lays the title bar
  out again for it, and the next frame centres the window buttons (#968).
- The menu bar drops the bold branch for the VectorCraft menu it no
  longer shows; theme is imported where the tests use it.
- Docs: Language is Preferences › User Interface › Language (VectorCraft
  › Language on macOS); the control protocol describes System Title Bar;
  ROADMAP Last updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:15:21 +07:00
rafi612 2e643b0e76 Make the menu bar more compact 2026-10-10 21:01:38 +07:00
rafi612 bd895a1de7 Remove the in-window VectorCraft menu (File first, Exit quits, Mac unchanged) 2026-10-10 21:01:38 +07:00
rafi612 12f8a77a00 User Interface › System Title Bar: native decorations on Windows/Linux, file in OS title 2026-10-10 21:01:38 +07:00
bflatasticandClaude Opus 5.5 258d61ced0 #968: centre the macOS window buttons on the app bar
AppKit places close, minimize and zoom in a standard title bar, shorter
than the 44-point app bar they sit over, so they rode high on it. They
are now moved to the middle of the bar, inset from its left as far as
from its top, with the title bar container made as tall as the bar so
they stay clickable. AppKit lays the title bar out again on resize and
when leaving full screen, so the placement runs every frame and sets a
frame only when it is out of place. The app bar keeps the room the
buttons take, measured, instead of a fixed 78 points.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:56:18 +07:00
bflatasticandClaude Opus 5.5 9216c47204 #962: Inner Glow's Source is a Center/Edge dropdown
The effect dialog showed Inner Glow's `source` as a text field, so the
only way to pick Center was to type the parameter value. It now lists
Center and Edge like the other fields that pick one of a few values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:32:47 +07:00
bflatasticandClaude Opus 5.5 068a934f6a Links: one watch for changed linked files
The link watch checks the active document's linked files every two
seconds and as the window comes back to the front, so the separate check
on focus that #925 added (missing_links::after_focus and its asked-files
set) goes: with both, a change was acted on twice. The watch also
walks the document's links only when a look is due, not every frame, and
wakes the app every two seconds only while the document has links. The
doc comments of status and links::groups are back on their functions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:19:27 +07:00
KattandClaude Opus 5.5 079580257a Links: Update Links applies while a document is open
Linked files another app changes while their document is open now follow
Preferences > File Handling > Update Links, as they do when it opens:
Automatically reads them again (an Update Links undo step), Ask When
Modified offers to (a question worded for files changed on disk while
working), Manually leaves them modified in the Links panel.

The desktop app stamps the active document's linked files (size +
modification time) on a worker thread every two seconds and when its
window comes back to the front; no look starts while a dialog is open.
Stamps are kept per document; a change is acted on once; a file still
being written is tried again. links.updateChanged reads changed files
again regardless of the preference (CLI, MCP).

New strings translated in all ten catalogs; ROADMAP updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:19:27 +07:00
bflatasticandClaude Opus 5.5 30ba774e6f Part of gap 2: Effect › Texture (Craquelure, Grain, Mosaic Tiles, Patchwork, Stained Glass, Texturizer)
Six raster effects on the pixel-filter pipeline
(vectorcraft_effects::pixel::texture), under a new Effect › Texture
submenu between Sharpen and Video in the Raster Effects section:

- Craquelure (crackSpacing pt 2..100, default 15; crackDepth 0..10, 6;
  crackBrightness 0..10, 9): the object on relief plaster that cracks into
  plates about Crack Spacing apart (a Voronoi network over jittered points)
  and along the quarter, half and three-quarter contours of its softened
  tones. Depth sets how dark and wide the cracks are and how high the
  plates' bevels and the tones stand (lit from the top left); Brightness
  how brightly the surface is lit.
- Grain (intensity 0..100, 40; contrast 0..100, 50 = unchanged; grainType
  Regular, Soft, Sprinkles, Clumped, Contrasty, Enlarged, Stippled,
  Horizontal, Vertical, Speckle; default Regular): coloured 1-point grains,
  smooth ones, clumps, 2-point grains, streaks along rows or columns, dark
  specks; Sprinkles and Stippled use the background colour, white.
- Mosaic Tiles (tileSize pt 2..100, 12; groutWidth 1..15, 3; lightenGrout
  0..10, 9): irregular tiles bevelled at their edges, with sunken grout half
  a point wide per step, lightened as Lighten Grout says.
- Patchwork (squareSize pt 0..10, 4; relief 0..25, 8): squares filled with
  the colour around their centres, raised to heights that follow the
  highlights and shadows, a little more or less at random.
- Stained Glass (cellSize pt 2..50, 10; borderThickness 1..20, 4;
  lightIntensity 0..10, 3): single-coloured panes leaded in the foreground
  colour (black, half a point per step), lit from behind at the object's
  centre and fading out towards its corners (PixelSpace gains `extent`).
- Texturizer (texture Brick, Burlap, Canvas, Sandstone, default Canvas;
  scaling % 50..200, 100; relief 0..50, 4; lightDirection Bottom, Bottom
  Left, Left, Top Left, Top, Top Right, Right, Bottom Right, default Top;
  invert): the object on a surface in relief. The four surfaces are height
  fields generated in code (bricks in stretcher bond, plain weaves coarse
  and fine, octaves of smooth noise); no image files. Load Texture is left
  out.

Every pattern lies in document space around the object's centre, so the
result is the same at any zoom or raster resolution, moves with the object
and is identical on every redraw (hash noise of cell indices); slopes are
taken in document units. Work per pixel is bounded (a 5 x 5 cell search,
four or five height samples, box blurs); parameters are clamped to their
ranges and finite-checked. The Pixelate helpers (Place, the cell search,
the softened colours) are shared, Mezzotint uses the shared smooth noise,
and the effect dialogs' dropdowns translate their labels in an "effect"
context where the plain word means something else (Soft, Horizontal and
Vertical are type settings in Japanese and Chinese).

Catalogue rows with parameter docs, outsets and reaches, menu entries,
dropdowns (Grain Type, Texture, Light Direction), translations, tests
(parameters and clamping, cracks and contours, ten distinct grains, tiles
and grout, squares in relief, leaded panes and the centre light, the
textures lit and inverted, the same at twice the resolution and when
moved, canvas, multithreaded, PDF and SVG, the command sweep with junk
parameters, the dialogs and the menu) and ROADMAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:31:47 +07:00
BFlat 32a688d68b Merge pull request #953 from crqch/fix-contributor-links
Fix contributor links
2026-10-10 18:50:11 +07:00
bflatasticandClaude Opus 5.5 22443d8711 Keep CJK UI fallback fonts to ui_fonts
The app already finds an installed font for CJK (and other) characters
the UI fonts lack, the first time a frame paints one (ui_fonts): reading
a system CJK font at every startup duplicated that, cost a font file's
read and memory for interfaces that never show CJK, and broke its test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 18:36:15 +07:00
Michał Kosior crqch a0cc4a4b94 Fix contributor links 2026-10-10 13:35:36 +02:00
seldoms f0349fb2f8 i18n: wrap hardcoded UI strings for Chinese localization
- Wrap remaining hardcoded UI labels and buttons with tr()/tl!() calls
- Add missing Simplified and Traditional Chinese translations
- Add entries to other language catalogs for consistency
2026-10-10 18:27:56 +07:00
bflatasticandClaude Opus 5.5 6a13088840 Fix #949: artboard names fit their artboard when zoomed out
Artboard names are drawn at a fixed screen size above each artboard, so
zoomed out, where artboards shrink on screen, the names ran past them
and over their neighbours. A name is now no wider than its artboard on
screen, ending in an ellipsis when longer, and an artboard narrower
than 24 px shows none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 18:24:50 +07:00
bflatasticandClaude Opus 5.5 2bc6c66c94 Part of gap 5: Scatter, Art and Pattern Brush Options
Double-clicking a Scatter, Art or Pattern brush in the Brushes panel (or the
panel menu's Brush Options...) now opens its options dialog, as Calligraphic
and Bristle brushes already did. OK runs brush.options and every stroke
painted with the brush redraws.

- Scatter Brush Options: Name; Size (10-1000 %), Spacing (10-10000 %),
  Scatter (-1000-1000 %) and Rotation (-180-180 deg), each Fixed (the first
  value), Random (between the two values) or Pressure (the first value at
  the lightest pen pressure, the second at the heaviest); Rotation relative
  to Page or Path. Scatter brushes now keep these modes (`modes`); brushes
  saved before keep their look (Random where the two values differ).
- Art Brush Options: Name; Width (1-1000 %); Scale Proportionately, Stretch
  to Fit Stroke Length or Stretch Between Guides with the guides' Start and
  End (% of the art length); the four Directions; Flip Along and Across.
- Pattern Brush Options: Name; Scale (1-10000 %), Spacing (0-10000 %); the
  five tiles (Outer Corner, Side, Inner Corner, Start, End) drawn with their
  art or None; Flip Along and Across; Stretch to fit, Add space to fit or
  Approximate path.
- All three: Colorization (None, Tints, Tints and Shades, Hue Shift with its
  Key Color picked from the art's colours, the most used one by default) and
  a live preview of a stroke painted with the edited brush.

Brush definitions from files and commands are now kept within these ranges
(Brush::sanitize, also for Calligraphic and Bristle values); unknown modes,
directions, fits or colorization methods are refused. The brush.options and
ui.brushOptions docs list the new params and fields. Tests: the brush model
(modes, pressure, sanitizing, art colours), engine params reaching the
brush and strokes redrawing, junk params in command_sweep, and headless
dialog tests for each dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:48:05 +07:00
bflatasticandClaude Opus 5.5 d57134a308 Fix #938: Live Corner widgets on every selected path
The corner widgets only showed when the selection was exactly one path.
Now every selected path that has them shows its widgets: live rectangles
and polygons with the Selection tool, any path with Direct Selection,
each with its own corners (all of a path selected whole, the
Direct-Selected ones otherwise). Dragging any widget rounds every shown
corner of every path in one step, Alt-click cycles their kind, and the
red limit outline covers them all; a double-click opens Corners for the
path it was on. Each widget is drawn in its own layer's colour.

object.setLiveShape takes items: [{id, corners?}], several objects each
with its own corners, which the drag sends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:19:52 +07:00
bflatasticandClaude Opus 5.5 d62ecfe33a Fix #926: the Libraries panel's search, groups and library export/import
Search: a search field at the top of the Libraries panel filters the
current library's colours, character and paragraph styles and graphics
by name, ignoring case; groups without a match are hidden, groups with
one open, and a search nothing matches says so ("No results").

Groups: items can be put in user-named groups, kept in the library file
(`groups: [{name, items: [{kind, item}]}]`, left out when there are
none, so files written before groups load as they are). New commands:
library.createGroup {library?, name?, items?}, library.renameGroup
{library?, group, name}, library.deleteGroup {library?, group} (its
items stay, ungrouped) and library.moveItem {library?, kind, item,
group?}. An item is in one group at most; deleting an item takes it out
of its group. The panel shows each group as a collapsible section with
its item count after the ungrouped items (rename and delete on its
context menu), each item's context menu has Move to Group (No Group,
the groups, New Group...), and a New Group button sits in the footer.
library.get lists the groups, library.list counts them. The generic
command dialog takes fixed params in `__params` for this.

Export / import: library.export {library?, path?} writes a library
(colours, styles, graphics with art and thumbnails, groups) as a
.vclibrary file, the JSON the library folder keeps (without a path:
{data}); library.import {path | data | dataBase64, name?} adds one as a
new library, made current, numbered on a name clash ("Brand (2)").
The panel menu has Import Library... and Export Library..., through the
app's file dialogs (the web downloads, and picks through File > Open,
which now lists Libraries files). Imported and folder files are made
safe: at most 10,000 items of a kind and 1,000 groups, names clipped and
made unique, sizes finite, thumbnails kept only when their PNG header
says at most 1024 px a side, group entries naming missing items or
unknown kinds dropped; a file over 512 MB or that isn't a library is an
error. Paths go through the engine's file helpers (automation roots).
The importer is fuzzed (import_fuzz: garbage and mutated libraries).

Also fixed: the third graphic added to a library got the second's id
(`graphic-2` twice).

Docs: the commands' params docs, docs/mcp.md, ROADMAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:17:01 +07:00
bflatasticandClaude Opus 5.5 c71af3079c Part of gap 2: Effect › Video (De-Interlace, NTSC Colors)
Adds the Video group to the raster effects, after Sharpen:

- De-Interlace…: Eliminate Odd or Even Fields, Create New Fields by
  Duplication or Interpolation. The fields are the rows of the document's
  raster grid (Document Raster Effects Settings › Resolution, carried to
  the filters as PixelSpace::line) counted from the top of the page, so
  the result is the same at any zoom and moves with the page. A line
  taken out copies the line above (the one below at the top), or takes
  the average of the two.
- NTSC Colors (no options, applied at once): colours a composite
  television signal can't carry, with luma plus chroma past 110 % of white
  or luma minus chroma below -20 % in YIQ, have their chroma cut to fit,
  keeping their luma. Saturated yellow, cyan and pure red lose some
  saturation; legal colours stay as they are.

Catalogue rows with parameter docs, dialog dropdowns, menu, translations,
tests (effects, canvas, command sweep junk, menu) and ROADMAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:05:56 +07:00
bflatasticandClaude Opus 5.5 0eb4ce5e98 Fix #918: PSD and PSB files open and place as their merged image
File › Place and File › Open read PSD and PSB files: the merged image
every such document carries after its layers, which is what a linked PSD
shows. A placed PSD links like any image, so the Links panel updates and
relinks it, and it is sized by its ResolutionInfo resource.

The reader is written from the public file format specification. It
reads Bitmap, Grayscale, Duotone, Indexed, RGB, CMYK (through the colour
settings' CMYK) and Lab, at 1, 8, 16 and 32 bits per channel (32-bit
values are linear), raw, PackBits or zipped with or without prediction.
A document with transparency stores its merged image on white with the
alpha in the first extra channel. It comes back with straight colours,
also for 16- and 32-bit files, which say so in their Lr16/Lr32 block.
Multichannel files are refused with a message. The file is untrusted:
every read is bounds-checked and the image's size is checked against
the decode budget before anything is allocated.

PSB is a read-only format of its own, so exports never write a PSD under
a .psb name. The desktop packaging still doesn't associate either
extension with VectorCraft (UNASSOCIATED_EXTS): they belong to raster
editors. Layers (import options) are left for later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:09:41 +07:00
bflatasticandClaude Opus 5.5 b3ed7df2a4 Fix #921: the Properties panel's Gradient section with Type and Draw
With the Gradient tool active, the Properties panel shows a Gradient
section: the type buttons (Linear, Radial, Freeform) and, for a
freeform gradient, Draw: Points or Lines as radio buttons. The Gradient
panel and the Properties panel share the type row and the draw mode
change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:50:23 +07:00
bflatasticandClaude Opus 5.5 2ef32bb1a2 Fix #924: the Tabs panel's Leader and Align On keep what is typed
Both fields read their text from the tab stop again every frame, so each
keystroke was lost before Enter or a click away could apply it: the
field seemed to clear itself while typing. They now use the panels' text
field, which keeps the text being typed until it is committed. The
leader keeps spaces (". " for spaced dots), up to eight characters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:49:49 +07:00
bflatasticandClaude Opus 5.5 11beb8fff1 Fix #925: linked files changed in another app update when the window comes back
Preferences › File Handling › Update Links only acted when a document
opened, so a linked image edited in another app while VectorCraft stayed
open kept its old pixels until Update Link in the Links panel, even with
Automatically chosen.

When the window comes back to the front, the active document's links
are now checked. With Automatically, changed files are read again; with
Ask When Modified, they are offered for update, once per change of a
file (keyed by path, size and modification time, since two saves can
share a time); with Manually, the Links panel shows them as modified.
Nothing is asked while a dialog is open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:49:08 +07:00
bflatasticandClaude Opus 5.5 9bad0a24ca Part of gap 2: Effect › Pixelate (Color Halftone, Crystallize, Mezzotint, Pointillize)
Four Photoshop-style raster effects on the pixel-filter pipeline
(vectorcraft_effects::pixel::pixelate), under a new Effect › Pixelate
submenu between Blur and Sharpen in the Raster Effects section:

- Color Halftone (maxRadius 4..127, default 8; channel1..channel4 screen
  angles -360..360, defaults 108/162/90/45): each colour channel is
  screened at its angle in square cells sized so a dot of Max. Radius
  fills one; each cell's dot covers the share of the cell that is the
  channel's coverage-weighted mean there (white stays white, black
  black). RGB documents screen R, G, B (channels 1 to 3); CMYK documents
  screen C, M, Y, K inks (channels 1 to 4), also when they are drawn ink
  plane by ink plane (the K plane uses channel 4).
- Crystallize (cellSize 3..300, default 10): every pixel takes the
  colour at the nearest of one jittered point per cell (exact Voronoi
  over a 5 x 5 neighbourhood, pruned by distance).
- Mezzotint (type: Fine/Medium/Grainy/Coarse Dots, Short/Medium/Long
  Lines, Short/Medium/Long Strokes; default Fine Dots; a dropdown in the
  dialog): each channel is turned fully on or off against its own random
  threshold pattern, so the object becomes pure colours.
- Pointillize (cellSize 3..300, default 5): one dot of random size per
  cell at a jittered point, coloured by the softened object there and as
  opaque as the object at its centre, stacked in random order over a
  white canvas where the object is.

Lengths are in points (Scale Strokes & Effects scales them) and every
pattern lies in document space around the object's centre, so the
result is the same at any zoom or raster resolution, moves with the
object, and is identical on every redraw (hash noise of the pattern's
cell indices, no state). Work per pixel is bounded whatever the
parameters, which are clamped to their ranges and finite-checked; the
halftone's cell grid is capped by the raster's size and a screen finer
than 3 pixels leaves the image as it is (its average). PixelSpace gains
`channels` so the renderer tells the filters what the channels hold.

Catalogue rows with parameter docs, outsets and reaches for cropping,
menu entries, dialog labels (Max. Radius, Channel 1-4, Cell Size), the
Mezzotint Type dropdown, translations, and tests: parameters and
clamping, tone and coverage, CMYK inks, polygon crystals, pure-colour
mezzotints with ten distinct patterns, dots on a white canvas, the same
result at twice the resolution and when moved, the canvas, PDF and SVG
through the engine, the command sweep with junk parameters, the dialogs
and the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:42:24 +07:00
Rick 934b65b42a M11: honor Move Artwork in artboard coordinate edits 2026-10-10 09:49:24 +02:00
bflatasticandClaude Opus 5.5 20e5a746bd Part of #885: the Transform effect's dialog takes Transform Each's controls
Effect › Distort & Transform › Transform… was a plain list of fields.
It now has Transform Each's layout: Scale and Move with sliders, Rotate
with an angle dial, then Copies, Reflect X and Y, the reference point and
Random, kept with the effect so editing it from the Appearance panel
shows them again.

- The effect scales, rotates and reflects about the chosen point of the
  9-point grid (`reference`, the centre by default).
- Random takes a share of each value, drawn from the object's id: each
  object varies its own way and keeps its result on every redraw; a
  group's members move together.
- Copies stop before one reaches past 1e9 pt, and the scale and move are
  capped, so large values can't overflow.
- Transform Each and the effect share the sections and options code; the
  angle dial is a new widget.
- A plain number field now takes the focus when it is a dialog's first
  field, so both dialogs open with Scale › Horizontal ready for typing.

Transform Objects, Transform Patterns and Scale Strokes & Effects are not
in the effect yet: the geometry effects reshape paths only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:32:10 +07:00