Commit Graph
3345 Commits
Author SHA1 Message Date
Marco Arnulfo 7608639021 Type: a colour picked while typing keeps what was typed (fixes #1950)
The Color panel and the Color Picker preview a colour in an interaction.
While typing, that interaction joined the open typing session and
previewed from the document before the typing, so new type came back as
its placeholder text. Typing in progress is now kept as its own step
before another interaction begins, as Undo already does.
2026-10-16 02:56:35 +07:00
Gert Pellin e6170de1a2 Transform commands: a string for an optional number is an error (#1952)
object.setBounds read x, y, width and height with f64_or, which falls back
to the current value for anything that isn't a JSON number, so
{"width": "60 mm"} returned ok and changed nothing. object.move,
object.nudge, object.rotate, object.scale (sy) and object.shear did the
same with their optional numbers. A value that is neither a number nor
null is now a BadParams error naming the key, as a missing required
number already is.
2026-10-16 02:56:35 +07:00
MarekSpodBiedry a0f8a88e82 Paint: picking another pattern swatch keeps the pattern transform (#1970) 2026-10-16 02:56:35 +07:00
Ayberk A. 6d898558fd i18n: the notes for an EPS or .ai whose layers weren't read are translated (#2035)
The importer wraps its reason in "the file's layers weren't read ({why}):
it comes in as its page, in one layer" (and, for an .ai, "... from its
editing data ({why}): they come from its PDF part"). The reasons had @msg
rows, the wrappers none, so after "Opened with 1 note(s):" the whole note
stayed English. The two templates are now in every language that
translates messages (and pt-br, which has the reasons), and the reason
inside is translated through them. These notes are pushed as warnings, so
the message scan doesn't see them: a test checks them in each language.

"<Clip Group>" already has a French entry on main; layer names such as
"Layer 1" are stored names and stay as they are.
2026-10-16 02:56:35 +07:00
Ayberk A. 5ff34782cb color.loadProfile: say that a loaded profile lasts for the run (#1955)
The profile is registered in the process's colour registry only, so the
next `vectorcraft-cli run` (or the next launch of the app) doesn't know it
and edit.colorSettings falls back to the built-in profile. The command's
description now says so, and that the profile has to be loaded again in
each run that names it. Keeping profiles across runs would need a place in
the preferences to remember them, which is left for the maintainers.
2026-10-16 02:56:35 +07:00
Ayberk A. 07c6b938bc Shortcuts: + selects the Add Anchor Point tool (#2142)
The tool's default key is "+", and `shortcuts::parse` split a bare "+" as a
separator with nothing on either side: the key part came out empty, so the
shortcut didn't parse, `tool_for_key` found no tool for a typed "+" and the
key did nothing. "-" (Delete Anchor Point) has no separator to trip on, which
is why it worked. A bare "+" is now the key itself, the "=" key, as "Cmd++"
already was, so "+" typed with Shift on a US layout, by a layout's own "+"
key or on the numpad selects the tool.
2026-10-16 02:56:35 +07:00
dotCooCoo cb9522f7cd Type: Cmd/Ctrl+Alt+Q resets tracking to 0 and kerning to Auto
The reference app resets tracking and kerning to 0 with Cmd/Ctrl+Alt+Q.
VectorCraft had no command for it.

type.resetTracking (Reset Tracking and Kerning, default Cmd+Alt+Q) sets
tracking to 0 and kerning to Auto, which the Character panel shows as 0,
in the text the Type tool has selected, or else in all the text of
every selected type object. At a caret it resets only the kerning of
the character before it, where Alt+Left/Right kerns. It is one undo
step. The range logic of type.step moves into a shared type_ranges.

When a call gives ids, type.step and type.resetTracking change all the
text of those type objects even while the Type tool edits text, as
paint.setFill does. Before, type.step changed the Type tool's selected
text instead.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 02:56:35 +07:00
dotCooCoo 0155eaf468 3D and Materials: Inflate builds its edge index once
inflate built an edge index (Edges) of the shape's rings, and
Membrane::solve, which inflate calls first, built a second index of the
same rings. Membrane::solve now takes the index that inflate builds.

In a release build, one Edges::new takes 0.08 ms of the 27 ms that
inflate takes on a ring of 2,000 points, and 2.0 ms of 219 ms on a ring
of 1,000 spikes whose 2,000 edges fill 693,816 bucket entries. The
facets do not change: for nine shapes, three views and 96 combinations
of the Inflate settings, the Debug text of all 2,592 results is byte
for byte the same on main and with the change.
2026-10-16 02:56:34 +07:00
dotCooCoo 5a83bcfafa Export: the export commands' params and the MCP export tool list the .ai format
document.export, document.serialize and document.exportSelection write
a PDF-compatible .ai (format: "ai"), and document.formats lists ai as
writable, but none of their params texts named it. The params text of
document.exportSelection named only png, jpg, webp, svg, svgz and pdf,
although the command writes all 20 writable formats. The description
of the MCP export tool and its row in docs/mcp.md named every format
of the tool's format enum except ai, and the range text and the row
said that only PDF writes several artboards.

The three params texts now list every format their command writes,
and document.export's range and useArtboards texts name .ai beside
PDF. The MCP export tool's description names ai, and its artboard and
range texts say that .ai, like PDF, writes one page per artboard. The
range text and the row also say that SVG, SVGZ and EPS write one file
per chosen artboard, and that rasters, DXF, EMF and WMF do so with
options.useArtboards: true.

Docs: docs/mcp.md (the export tool).
2026-10-16 02:56:34 +07:00
dotCooCoo 66dbe40ea6 Export for Screens: the params text and the format error list every format a row writes
The document.exportForScreens params text listed png, png8, jpg, webp,
gif, svg, svgz and pdf for a row's format, and its settings keys left
out svgz. A row of another format VectorCraft writes, such as eps, was
refused with "Export for Screens writes png, png8, jpg, webp, gif, svg,
svgz or pdf". Rows of tiff, bmp, tga and psd are written too, and the
Export for Screens dialog offers TIFF rows.

The params text, its settings keys and the error now list the 12
formats a row writes: png, png8, jpg, webp, gif, tiff, bmp, tga, psd,
svg, svgz and pdf. Which formats a row accepts does not change.
2026-10-16 02:56:34 +07:00
Marco Arnulfo 53c59f06de PDF: an untitled document saved to a file exports titled after it (fixes #2166) 2026-10-16 02:56:34 +07:00
dotCooCoo 513685a221 Docs: the Print section describes printing through the system's print service
The Print section of docs/mcp.md said that File → Print writes the job
as a print-ready PDF and that there is no printer driver. The desktop
app sends the PDF to the system's print service, the web build opens
the browser's print dialog on it, and the PDF File entry in the
Printer list saves it. The section now says so, points to Printing,
and marks the `file.print` reply that follows as the headless one.
2026-10-16 02:56:34 +07:00
dotCooCoo 4a1323ad07 i18n: Russian Isolate and Sublayer rows use Cyrillic letters only
The Russian catalog's Isolate row began with a Latin "A" (U+0041),
"Aизолировать", and the Create New Sublayer and New Sublayer… rows
spelled "подслой" with a Latin "c" (U+0063). The rows now read
"Изолировать", "Создать новый подслой" and "Новый подслой…".

A new test fails when a word in any catalog's translation holds letters
from more than one of the Latin, Cyrillic and Greek scripts.
2026-10-16 02:56:34 +07:00
Justin Willhite 8da47f11f8 PDF export: a CMYK page without blending through is an isolated group (#2156) 2026-10-16 02:56:34 +07:00
bflatastic 18f8a1bdcf PDF import: a damaged array is refused as damaged, not as functions built on one another 2026-10-16 02:56:34 +07:00
Claude 95d27fe390 PDF.R1: never panic on a damaged array or dictionary in an imported PDF
hayro-syntax's Dict::entries still unwraps each value it reads, so a
dictionary cut short (or with a stray token) aborts the import. The
craft-libs ArrayIter now stops at the first unreadable item instead of
panicking, which also means a hostile item after a self-referencing
function or colour space is silently dropped from nests_without_end.

- import_color: `readable(array)` checks every item parses; `items()`
  yields nothing for an unreadable array; `entries()` reads a dictionary
  key by key through get_raw, skipping values that don't parse.
- pages/editing/import_scan: use the helpers instead of raw_iter() and
  Dict::entries().
- nests_without_end refuses a file with an unreadable array.
- Tests: two refusal cases in pdf tests and an .ai cut inside an array
  or dictionary in engine/tests/import_fuzz.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcHisW1vt6pkVPYbzL6Cpg
2026-10-16 02:56:34 +07:00
bflatastic ff7f47119d i18n(bg): rows for the strings this batch adds (Darkness, Isolate Selected Object, Keep selected, Sample Color from Screen, Tritanopia, Panels Except Tools and Control) 2026-10-16 02:56:32 +07:00
BFlat 01c66e51b7 Merge pull request #2016 from EricKuck/fix/ai-pattern-phase
AI import: a pattern fill tiles from its tile's lower left, wherever the definition drew the tile
2026-10-16 02:38:08 +07:00
BFlat 23a0a5f857 Merge pull request #2015 from EricKuck/fix/ai-non-native-art-orientation
AI import: non-native art stays upright unless its placement mirrors it
2026-10-16 02:37:52 +07:00
BFlat 651e2e1a57 Merge pull request #2014 from EricKuck/fix/export-art-over-artboard-groups
SVG export: a chosen artboard's file leaves out the art of other artboards inside a shared group
2026-10-16 02:37:38 +07:00
BFlat 5a5bb73aa9 Merge pull request #2062 from dotCooCoo/fix/page-knockout-group-backdrop
Render: a knockout group drawn over its backdrop counts an element's partial coverage once
2026-10-16 02:37:22 +07:00
BFlat 88e950a0d7 Merge pull request #2073 from dotCooCoo/feat/outline-object-nested-type
Effects: type in a group with a geometry effect keeps its transparency, mask, effects and visibility
2026-10-16 02:37:05 +07:00
BFlat 99bdcc6633 Merge pull request #2232 from MarekSpodBiedry/issue-2086
Menus: keep scroll bar clear of shortcuts and stop above the status bar (#2086)
2026-10-16 02:36:44 +07:00
BFlat ef53890c28 Merge pull request #2109 from dotCooCoo/fix/area-type-rows-columns-overflow
Type: area type opens and lays out with at most 100 rows and 100 columns
2026-10-16 02:36:23 +07:00
BFlat 011a1a0314 Merge pull request #2108 from dotCooCoo/fix/issue-1770-rosette-spurs
Fix #1770: Shape Builder merges leave no spur or split outline when shared edges differ by rounding
2026-10-16 02:36:05 +07:00
BFlat 17c512f5c9 Merge pull request #2055 from dotCooCoo/fix/copied-artboard-free-id
Artboards: copied artboards take a free id and keep their own guides
2026-10-16 02:35:47 +07:00
BFlat ec602d7e27 Merge pull request #2114 from dotCooCoo/fix/moved-rasters-test-fonts-steady
Render tests: two moved raster tests no longer run alongside the tests that add fonts
2026-10-16 02:35:30 +07:00
BFlat bb5f16c101 Merge pull request #2118 from dotCooCoo/fix/alt-drag-tests-created-stamp
Tests: the Alt-drag tests no longer fail when their documents are made in different seconds
2026-10-16 02:35:13 +07:00
BFlat b8041d3745 Merge pull request #2235 from Messhias/fix/2094-create-outlines-shortcut
Fix #2094: Cmd+Shift+O Create Outlines while typing
2026-10-16 02:34:55 +07:00
BFlat 0205a3332f Merge pull request #2227 from Messhias/fix/1910-reflect-axis
fix(G11): Reflect X/Y flip the correct axes (#1910)
2026-10-16 02:34:42 +07:00
BFlat a7425f27c1 Merge pull request #2176 from Silvertree2010/fix/2078-when-i-click-pixels-the-unit-of
Make New Document preset cards follow the chosen units
2026-10-16 02:34:25 +07:00
BFlat fb803dbdae Merge pull request #2158 from Silvertree2010/fix/2084-the-orientation-selection-is-wro
Fix New Document orientation on category tabs and preset cards
2026-10-16 02:34:08 +07:00
BFlat 81420b87c8 Merge pull request #2204 from Silvertree2010/fix/h1429-svg-export-writes-control-charac
Make SVG export of type with control characters well-formed
2026-10-16 02:33:51 +07:00
BFlat 57b5931c83 Merge pull request #2022 from Silvertree2010/fix/h797-negative-picas-points-value-2p6
Apply the sign of a picas+points value to the whole value
2026-10-16 02:33:32 +07:00
BFlat ce02a42f35 Merge pull request #2069 from Silvertree2010/fix/h839-field-arithmetic-rejects-followe
Evaluate * and / left to right in field arithmetic
2026-10-16 02:33:15 +07:00
BFlat 4b60fcead7 Merge pull request #2209 from Silvertree2010/fix/h1425-field-arithmetic-rejects-a-negat
Accept a negative operand after an operator in field arithmetic
2026-10-16 02:32:59 +07:00
dotCooCoo 3bef84aaa6 Type: type drawn as glyph outlines keeps its underline, stroke weight, gradients and patterns
Type is drawn as its glyph outlines under a geometry effect, in EMF and
WMF export, in DXF export when it is written as outlines, through
Flatten Transparency with Convert All Text to Outlines, and inside an
envelope. Those outlines differed from the type the canvas draws in
four ways. They had no underline or strikethrough bars. A character
stroke on scaled type kept its unscaled weight. An unplaced gradient was
fitted to the glyphs it painted instead of the layout box. A run's
pattern tiles stayed in document space instead of scaling and turning
with the type. A group's or layer's own fills and strokes also painted
the glyphs of the type in it without the bars.

The outlines now include each run's underline and strikethrough bars,
as the canvas has drawn them since #850. Each run's path is made in
text space with its unplaced gradients fixed to the layout box, then
placed by the type's transform, which also scales its stroke weight
and dashes and transforms its pattern tiles. The type's own fills and
strokes have their unplaced gradients fixed to the bounding box of the
layout box in the document, where the canvas fits them.

Geometry effects that move or scale the outlines move and scale the
type's gradients with them: the type's own effects, also inside an
envelope and after Flatten Transparency has converted the type to
outlines, and the effects of a group, symbol instance or envelope that
holds the type. outline_text marks its group with a new node field,
type_outlines, which is saved with the document. reshape_leaves maps
the placed gradients in each marked group through the map that takes
the group's bounds before the effects onto its bounds after them. A
scale out of proportion stretches a radial gradient into an ellipse
and turns the isolines of a diagonal linear gradient, as it does on
type scaled by its own transform. Placed gradients outside those
groups stay where they are.
2026-10-16 02:26:15 +07:00
dotCooCoo 98d1d738a2 Export Selection: the crop takes in what each object's effects draw
Export Selection, Asset Export, the Assets tab of Export for Screens
and the rasterized art of CSS Properties cropped each object to its
visual bounds, which leave out live effects. A path with an 8 pt drop
shadow was exported with the shadow cut off at the path's edge, and
a group's own shadow was cut off the same way.

The crop is now the bounds the renderer measures for what it draws
(`vectorcraft_render::effect_bounds`): `painted_bounds` without its
room for brush art. A brushed path is cropped to its visual bounds as
before, and a Shaper group leaves out its transparent source art as
its visual bounds do. `assets.list` reports the same crop. The
renderer measures a shadow's or glow's reach on every side of the
object, so the image also has transparent room on the side away from
an offset shadow.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 02:26:14 +07:00
dotCooCoo 1c30cbf88c Print: Print and Print Preview warn when type prints in the fallback font
PDF, EPS, EMF/WMF and raster exports, and SVG exports that outline or
embed fonts, warn when type is set in a font family that isn't
available, but file.print and print.preview gave no such warning. A job
in a missing family printed in Source Sans 3 with no warning, and the
Print dialog's Summary showed none.

Both commands now add the exports' warning for the type that prints:
visible type that fits its frame or path, on the layers that Print
Layers picks, where what it paints (its effects applied) lands on the
paper of an artboard or tile that prints, inside its clipping paths.
The type in the symbols, inline graphics, patterns, scatter brushes,
opacity masks and placed documents of the art that prints counts too.
file.print reads the fonts before raster effects and Print as Bitmap
turn type into images, for PDF and PostScript output.

Docs: docs/mcp.md (Print), docs/target-app-parity.md (Fonts).
2026-10-16 02:26:13 +07:00
dotCooCoo d069f0fe98 Variables: selecting a data set sets the objects inside a group that it shows
dataset.select applied a row's values in the order the variables were
defined, and it skips an object inside a hidden group. When a row
showed a group and set an object inside it, and the object's variable
was defined first, the object was skipped while the group was still
hidden: one selection applied the row in part, and selecting the row
again applied the rest. A text variable on type inside the group was
skipped the same way, and a row that hid a group skipped the objects
inside it whose variables were defined after the group's.

dataset.select, dataset.next and dataset.prev now apply the visibility
values that show objects first, outermost object first, then the other
values in definition order, then the visibility values that hide
objects, innermost object first. Of two variables of the same kind on
one object, the one defined last still wins.

Docs: docs/mcp.md.
2026-10-16 02:26:11 +07:00
dotCooCoo 01b3599227 Variables: binding errors name the command and say when a layer is hidden
Binding art in a hidden group or layer failed with "node N is locked",
even when nothing was locked. The error now says whether the object is
locked, is in a locked group or layer, or is in a hidden group or layer.

Make Text Dynamic and Make Visibility Dynamic reported their errors under
`variable.makeDynamic`, which is not a command id. They now name
`variable.makeTextDynamic` or `variable.makeVisibilityDynamic`.
2026-10-16 02:26:09 +07:00
dotCooCoo 00b02c1bdb Variables: Update Data Set writes only the variables the active data set holds
A data set can hold values for only some variables: `dataset.new` and
`dataset.set` store only the values they are given, and selecting the
data set leaves the other variables alone. Update Data Set replaced the
active data set's values with the values the art reports for every
variable. After one Update, a data set that set only a visibility
variable also held the current text of the bound type, and selecting it
again wrote that text back. Update also dropped a variable's value when
its objects were locked or inside a hidden or locked layer or group,
because such objects report no value.

`dataset.update` now writes the art's current value for each variable
the active data set holds, and keeps the data set's value when the art
reports none. A variable that the data set does not hold stays out of
it, including one defined after the data set was captured;
`dataset.update {all: true}` also adds the other variables that the art
reports a value for. The reply adds `left`, the variables it did not
write. The Variables panel's Update Data Set button and menu item run
it without `all`.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 02:26:08 +07:00
dotCooCoo ff33e0556f Pen: the new-path asterisk leaves the pointer once a path is started
The Pen's pointer showed the new-path asterisk before the first anchor
and also while a path was being drawn, where a click adds the path's
next anchor. A new Cursor::PenDrawing, the pen nib without a badge, now
shows there. The close, convert and join badges still show over the
first anchor, the last anchor and another open path's end. The asterisk
shows only where a click starts a new path.

The Continue, Add and Delete badges now also show after another path
was selected while the Pen was still drawing (for example with a
Cmd-click). A click there already continued the path or added or
deleted an anchor, but the pointer showed the asterisk.
2026-10-16 02:26:06 +07:00
dotCooCoo 35f43d631f View: Proof Setup simulates tritanopia
View › Proof Setup had two color blindness items, protanopia and
deuteranopia. A third item, Color blindness – Tritanopia-type, proofs
the canvas with a tritanopia simulation, and the Separations Preview
panel's Proof Setup list offers it too. `view.proofSetup` takes
`target: "tritanopia"`.

The simulation follows Brettel, Viénot and Mollon (1997): a color
keeps its L and M cone responses, and its S response is replaced so
that it lies on one of two half-planes. Both contain the neutral
axis; one passes through the 485 nm color and the other through the
660 nm color. The neutral axis runs to the display's white, so grays
keep their value. The matrices are computed at
compile time from the published inputs cited in the code. The
protanopia and deuteranopia matrices are unchanged.
2026-10-16 02:26:04 +07:00
dotCooCoo bbad808a02 Transform: Alt+Enter applies a value to a copy of the selection
In the Transform panel, the Properties panel's Transform section and
the Control bar, Alt+Enter (Option+Return) in X, Y, W, H, Rotate or
Shear ended editing as Enter does, and the selection itself moved,
resized, turned or sheared. Now the field's command gets `copy: true`
when the Enter that committed it was pressed with Alt. The copy takes
the value, the original stays where it was, and the copy is selected,
in one undo step. A press elsewhere with Alt held applies the value
without a copy. With anchor points selected with Direct Selection,
Alt+Enter applies the value to them, as Enter does.

- widgets::enter_modifiers returns the modifiers of the frame's Enter
  press.
- transform::alt_enter_copies gives the `copy` param of the three
  surfaces' commands.

Docs: docs/target-app-parity.md.
2026-10-16 02:26:01 +07:00
dotCooCoo d68e14a393 Pencil and Paintbrush: with Keep selected off, a drawn path is not left selected
A Pencil or Paintbrush stroke always left the path it drew or continued
selected. The reference app's Pencil and Paintbrush options have Keep
selected for this, and VectorCraft's Tool Options had no such row.

The Pencil and Paintbrush Tool Options now have a Keep selected
checkbox, on by default, which keeps today's behavior. When it is off,
a new path is not selected and the selection stays as it was, and a
continued path is removed from the selection while the rest stays. The
stroke is still one undo step. The option persists like the tools'
other options, and path.freehand and brush.freehand take keepSelected.

Docs: docs/target-app-parity.md, docs/ui-parity.md, docs/control-protocol.md.
2026-10-16 02:26:00 +07:00
dotCooCoo 4d63227223 Swatches: Ctrl/Cmd+Shift-click on New Swatch makes a global process color
Ctrl/Cmd+Shift-clicking New Swatch in the Swatches panel opened New
Swatch for a spot color, as a Ctrl/Cmd-click does. It now opens the
dialog for a global process color, and with Alt also held it saves one
at once. `ui.newSwatch` takes `global`.

An Alt+Ctrl/Cmd-click with a gradient or pattern as the active paint
failed with "spot, mode and group apply to solid colours only". It now
saves a gradient or pattern swatch, as a Ctrl/Cmd-click does through
the dialog.
2026-10-16 02:25:59 +07:00
dotCooCoo 720ce5ed73 Effects: Drop Shadow has Darkness, the object's own colors with black added
The reference app's Drop Shadow has a Color and a Darkness, the
percentage of black added to the object's own colors. Drop Shadow had
only a Color, so every shadow was one color. Choosing one or the other,
and the defaults, are VectorCraft's own.

Drop Shadow now takes `shade` ("color" or "darkness", default "color")
and `darkness` (%, default 100). Its dialog and the Appearance panel's
inline editor show Color and Darkness as radio buttons. With Darkness
d, a CMYK document keeps the object's cyan, magenta and yellow and
raises its black from K to K + d × (1 − K), and an RGB document
multiplies each channel by 1 − d, so in RGB 0% gives the object's
color and 100% gives black. A shadow saved without these keys draws as
before. SVG export writes a Darkness shadow with feComponentTransfer
and SVG import reads it back; CSS export leaves it out.

Docs: docs/target-app-parity.md.
2026-10-16 02:25:58 +07:00
dotCooCoo b5a0fee1ea Window: Shift+Tab shows and hides the panels but leaves the toolbar and the Control bar
Shift+Tab ran Tab's command and hid the toolbar and the Control bar
along with the panels. egui matches a chord without Shift when Shift
is also held.

A new UI command, window.panels (default Shift+Tab), shows and hides
the dock and the floating panels and leaves the toolbar and the
Control bar as they are. The shortcut table tries chords with more
modifiers first, so Tab's chord no longer takes Shift+Tab. Hiding the
panels with Tab or Shift+Tab also closes a panel popped out of the
icon column, which stayed on screen at the window's right edge.

Docs: docs/target-app-parity.md.
2026-10-16 02:25:56 +07:00
dotCooCoo 103c6ebdfb Draw Inside: a double-click outside the object returns to Draw Normal
In Draw Inside, a double-click with the Selection, Direct Selection or
Group Selection tool outside the object being drawn into now sets the
drawing mode to Draw Normal (view.drawMode {mode: "normal"}), as
Shift+D does. The object's geometric bounds, grown by the selection
tolerance, count as inside, and a double-click after the object was
deleted also returns to Draw Normal. The tool then takes the
double-click as before: with the Selection tool, a double-click on the
empty canvas still exits isolation mode, and one on another object
still isolates that object. Other tools leave Draw Inside on.

Docs: docs/target-app-parity.md.
2026-10-16 02:25:55 +07:00