Commit Graph
727 Commits
Author SHA1 Message Date
Jindřich ŠaršonandClaude Opus 5.5 ad78ee94c3 M6: Sketch import: instances, artboards and Apple fonts like Sketch
Compared with Sketch's renders of a Sketch 64 file. Resized instances lay
out their layers the way Sketch does (measured: pins share the change in
proportion, a fixed size keeps its centre's share, and files before format
110 just scale); masters' backgrounds show in instances only when Sketch
includes them; swaps to a missing symbol keep the original; artboards
don't draw a style they carry. Apple's system fonts are found under the
names designs use (SF Pro, SF UI Text and Display, SF Compact), the
optical size follows the text size for named styles too, and imported
text keeps the platform's trak tracking as letter spacing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:27:20 +02:00
Jindřich ŠaršonandClaude Opus 5.5 5b9e18317d M6: Sketch import: match Sketch's own rendering
Compared artboard by artboard with Sketch's renders of a real Sketch 50
file (sketchtool, black-box). Shape groups whose children have no
operation are one compound path filled by the group's winding rule (read
from the layer), not a union: rings, map streets and icons keep their
holes. Filled open paths close, as Sketch fills them. Flips apply after
rotation. Text with a fixed line height sits where Sketch's line spacing
behaviour puts it. Edited ovals and rectangles keep their points. Bitmap
colour adjustments become image filters. scale 0 means unscaled. A local
test compares sample files with sketchtool's renders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:27:20 +02:00
Jindřich ŠaršonandClaude Opus 5.5 233c8e9708 M6: Sketch import: local corpus check and docs
FIGCRAFT_SKETCH_SAMPLES runs every .sketch file of a folder through
file.open (opens with linked instances and finite geometry, or is
refused as too old). File format parity gains a .sketch row; CLI, MCP,
roadmap and ROADMAP mention the import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:27:20 +02:00
vetlucasmartins 2118480e7a Dev Mode: React + Tailwind page code and Tailwind theme export
- inspect.code {language: "tailwind"}: a layer tree as a React component
  styled with Tailwind CSS v4 utilities, no stylesheet. Auto layout maps to
  flex utilities on the spacing scale; fixed-width auto layout frames, text
  and wide layers become fluid (w-full max-w-[…]); instances are inlined
  with their overrides, except those connected to code (codeConnect), which
  are their snippet with one import each; bound variables are theme
  utilities (bg-accent, rounded-card).
- mobile: <frame id> (same layer names): mobile-first code, desktop classes
  that differ behind lg:, resets for desktop defaults, padding compared per
  side; layers only on desktop are hidden below lg, those only on mobile are
  listed in the header.
- variable.export {format: "tailwind"}: @import "tailwindcss" and an @theme
  block (--color-*, --radius-*, --text-*, --spacing-*, --font-*), names
  unique per file and shared with page code.

Builds on the token roles of the CSS export px fix (#1008).
2026-10-21 20:03:46 +01:00
vetlucasmartins 196e3432dd data.fill and text.fit: fill copies of a frame from rows of data
- data.fill {id, rows, gap?, formats?, dir?, format?, scale?}: repeats a
  frame once per row in one undo step; each row key names layers in the
  copy (text, or {text?, fill?, visible?, fit?}). `formats` makes each row
  once per size (feed, story, square), the copy resized so its auto layout
  reflows. With `dir`, each copy is exported under a unique file name; a
  failed export is reported in `errors` and keeps the copies.
- text.fit {ids?, maxLines?, minSize?, orphans?}: the largest font size,
  down to a floor, at which fixed-width text stays within N lines and its
  box; avoids a one-word last line; `fits` comes from the final text.
- Plugins can't call data.fill: it runs other commands inside, past the
  plugin policy, like command.batch.
2026-10-21 19:54:47 +01:00
vetlucasmartins 40fd169bec variable.export css: number lengths in px
A radius exported as `--radius: 12` was invalid where CSS reads it as a
length (`border-radius: var(--radius)` ignores a bare number). Number
values now carry px unless the layers bound to them, their scopes, or the
variables aliasing them make them an opacity or a font weight, which stay
bare. The roles come from a small token_roles module.

Updates the variables_io_tests expectation for the radius (4 → 4px,
6.5 → 6.5px), and adds a test for a bound radius, a bound opacity and an
opacity alias's target.
2026-10-21 19:38:30 +01:00
KameronandClaude Opus 5.5 014098b6c9 fix(components): a boolean property's default is its bound layers' visibility on the main (#993)
component.addProperty {type: "boolean", default: false, layer} hid the
bound layer in new instances but left it showing in the main component,
so the main didn't show its own default.

On the main, a boolean property's value is now the visibility of the
layers bound to it:

- addProperty, bindProperty {field: "visible"} and editProperty {default}
  show or hide the main's bound layers to match the default (every
  variant of a set for editProperty). Without a default, addProperty
  takes the bound layer's current visibility instead of always `true`.
- Showing or hiding a bound layer on the main sets the default, and the
  property's other bound layers (in every variant) follow. Edits inside
  instances stay overrides.
- Existing instances keep their stored values; documents open as saved
  (no normalisation on load).

Fixes #993

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 11:27:46 -07:00
KameronandClaude Opus 5.5 afdc31b727 Let node.set replace a vector's path
node.set took no `path`, so redrawing a vector meant deleting and recreating it and
losing its id, place in the layer list, paints, styles, bindings and step references.

`path` now takes SVG path data for vector layers and replaces the geometry through
the same reader node.create uses (path_network, shared with create). width/height in
the same call scale it as create does. The path's own box goes at the layer's current
origin, so rotation and flips stay; with x/y in the same call it goes at x/y plus the
box's offset, which is where create puts it.

On several layers, layers that aren't vectors are skipped with a warning, as auto
layout keys already are (the skip logic is shared now). A single non-vector is
refused, and so is a vector inside an instance: there is no geometry override, so
the next sync would undo it. Data that doesn't parse, draws nothing or has
non-finite bounds is refused cleanly; create now refuses non-finite bounds too.

Tests cover keeping the layer, placement and scaling against create, rotation,
undo/redo, invalid data, non-vectors, instances, boolean operands, edit mode, and a
junk-path sweep. The engine-wide junk sweep has node.set path entries.

Fixes #1001

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 11:21:24 -07:00
KameronandClaude Opus 5.5 9315e5e014 command.batch: named steps ($ref) and slash paths in field
A batch step can name itself with "as" and later steps refer to it with
{"$ref": "name"}, alongside positional {"$step": n}. "field" now takes a
slash path into the result as well as a key, so {"$ref": "copy",
"field": "ids/0"} reaches the first id edit.duplicate returned (the
convention variable.bind uses for fills/0/color; a leading "/" as in
JSON Pointer also works, and a key holding "/" still matches first).

Resolution moves to crates/engine/src/batch.rs (BatchStep, BatchScope),
shared by command.batch, the CLI script runner, the control channel and
the MCP batch tool, which now passes "as" through. Duplicate names fail
before any step runs; unknown, same-step or later names, bad paths and
out-of-range indexes fail the batch with the step, name and full path.
$ref is reserved like $step; nested batches keep their own names.

Fixes #994.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 11:19:47 -07:00
KameronandClaude Opus 5.5 3146ad2b2f node.reparent: index is the layer's final position (#992)
`node.reparent {ids, parent, index}` read `index` against the parent's
children as they were before the move, so within the same parent moving a
layer up one place did nothing and asking for two places moved it one.
`index` now counts the parent's children after the move, the way
`node.reorder` already does, in the same parent or a new one; several
layers still land together in their stacking order.

The layers panel was the only caller passing an index other than 0. It
drops on a gap between rows counted before the move, so it now subtracts
the dragged layers below that gap and drags behave as before.

Documents `index` in the command's params and in docs/mcp.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 11:03:08 -07:00
midasdf 42bdfcce34 M4: CJK kinsoku completed to JIS X 4051
JIS X 4051's line-start (行頭禁則) and line-end (行末禁則) sets were mostly
there — closing punctuation and brackets, small kana, the prolonged sound
mark, iteration marks and opening brackets already never start or end a line —
but a few categories fell through:

- the halfwidth small kana ァ–ッ (U+FF67..U+FF6F) and the halfwidth voicing
  marks ゙゚ (U+FF9E/U+FF9F) were not line-start prohibited, so a wrapped line
  could begin with them;
- the spacing (semi-)voiced sound marks ゛゜ (U+309B/U+309C) and the kana
  iteration marks 〱〲〳〴〵 (U+3031..U+3035) likewise;
- the halfwidth parentheses ⦅⦆ were missing from the line-end/line-start
  sets.

Add them (ranges in `class`, the two parentheses in the OPEN and CLOSE
strings), and pin every JIS category in the behaviour suite: the existing 22
cases stay and 9 join them (halfwidth small kana and small tsu, halfwidth and
spacing voicing marks, kana and vertical kana iteration marks, both halfwidth
parentheses), plus a wrap case where a halfwidth small kana moves down with
its base (キャ) and a case showing a manual newline may still start a line with
a prohibited character (禁則 is for automatic wrapping only).
`break_opportunities` fails on the old sets at the first new halfwidth
small-kana case.

Latin wrapping, ideograph breaks and the rest of the behaviour suite are
unchanged, and the headline numbers stay as they are.

Verified: cargo test -p figcraft-text --lib 92 passed; cargo clippy
-p figcraft-text --all-targets -- -D warnings clean; rustfmt clean on the
touched files.

Docs: docs/target-app-parity.md text behaviour row and ROADMAP progress log,
revision history and header.
2026-10-22 01:37:55 +09:00
Nicek 12e0cdc147 feat: 为填充、描边和效果添加右键复制粘贴功能
实现了在属性行和分区标题上右键菜单,支持复制粘贴单个填充、描边和效果。新增了相关的命令、测试和国际化文本,完善了剪贴板数据结构以支持这些新功能。
2026-10-21 22:18:30 +08:00
AganzeFelicite d68a2c54c5 Merge remote-tracking branch 'upstream/main' into fix/adobe-fonts-macos-298
# Conflicts:
#	ROADMAP.md
#	crates/text/src/fontdb.rs
#	crates/text/src/fontdb_tests.rs
#	docs/roadmap.md
2026-10-21 05:36:34 +02:00
tylerjenningsw f80bdb634d docs: zoom previews for cold transitions (M8)
Refs #295
2026-10-20 16:38:27 -04:00
Mex df5ffec87b M0.9: allow literal dash-prefixed CLI filenames 2026-10-20 21:25:49 +02:00
Lucas cabe2e3cfb i18n(pl): native review, catch-up and terminology fixes
- Mark Polish catalog as reviewed and approved by native speaker (@lukasz-sklad)
- Replace "Lekki" with "Jasny" for Light mode
- Translate disambiguated context keys (Fill|sizing, Join|stroke, Light|glass, Open|comments)
- Catch up with 15 UI strings added in recent main commits
- Update docs/translating.md to list Polish as reviewed
2026-10-20 15:58:55 +02:00
RiceDee bcd5b6e632 #903: Combine as variants in the context menu and Design panel; Add variant on a lone component 2026-10-20 16:18:13 +11:00
xiaou61 beb87d8696 M1: describe the fallback the export actually takes (#465)
The warning still said fixed and content-sized tracks were written as equal
tracks, which the export no longer does. It now reports only the tracks whose
size the format can't hold, and only for the tracks an exported grid has.

Co-authored-by: xiaou61 <113765024+xiaou61@users.noreply.github.com>
2026-10-20 11:14:02 +08:00
xiaou61 ea2fa7c367 M1: say that the automation roots reach only the headless side (#835)
The CLI help and docs/mcp.md described the roots as confining whatever the run
touches, which reads as if they also applied to `mcp --attach`. They don't: an
attached server runs the commands inside the app, and the app confines them
from its own flags. VectorCraft documents the same split, and the missing
sentence is what made the difference confusing, as #835 reports.

Co-authored-by: xiaou61 <113765024+xiaou61@users.noreply.github.com>
2026-10-20 11:08:21 +08:00
xiaou61 414aa1b63c M1: write grid track sizes in .fig export (#465)
The import has read `gridColumnsSizing` / `gridRowsSizing` since #630, but the
export wrote only equal tracks and warned about it, so a grid with a fixed
column beside two weighted ones came back equal after a save and reload.

Write the sizing map next to the order map, keyed by the same track ids: fixed
px, fractional fill and content-sized tracks keep their sizes. A track the
format has no name for (negative or out-of-range size, or a document track the
frame doesn't list) keeps its id and reads back equal, with the existing
warning.

Co-authored-by: xiaou61 <113765024+xiaou61@users.noreply.github.com>
2026-10-20 11:04:13 +08:00
BFlat b5038726c4 Merge pull request #902 from storytold/fix/issue-892
M3: duplicating a main component makes an instance (#892)
2026-10-20 06:19:17 +07:00
BFlat 2bcd004071 Merge pull request #897 from devius/ka-header
i18n(ka): say how the Georgian catalog was made (AI-assisted, native-reviewed); still a draft
2026-10-20 06:11:27 +07:00
bflatastic f979878d9e Merge main into fix/issue-892
# Conflicts:
#	ROADMAP.md
2026-10-20 06:09:17 +07:00
bflatastic 64efe12e6f Merge main into fix/issue-890
# Conflicts:
#	ROADMAP.md
#	docs/ui-parity.md
2026-10-20 05:58:56 +07:00
bflatastic ab59877559 Merge main into #897 2026-10-20 05:47:17 +07:00
bflatastic 8a46478e81 Merge main into fix/issue-879
# Conflicts:
#	ROADMAP.md
2026-10-20 05:46:30 +07:00
bflatastic b82369201d M3: duplicating a main component makes an instance (#892)
Duplicate and Alt-drag copied a main component as a second main. Within
its file the copy is now an instance of it (components::copy_in_file,
shared by edit.duplicate and the Alt-drag copy), as Copy then Paste
already was. A variant duplicated in its set stays a new variant.
2026-10-20 05:44:46 +07:00
bflatastic e3b20b6540 Merge main into fix/issue-894
# Conflicts:
#	ROADMAP.md
2026-10-20 05:35:21 +07:00
Davit MatchakhelidzeandClaude Opus 5.5 e123a2efe7 i18n(ka): the draft status says the review is done and approval pending; the guide and the field doc say both kinds of draft
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-20 02:33:25 +04:00
Davit MatchakhelidzeandClaude Opus 5.5 bae2f057a1 i18n(ka): the catalog header and docs say how Georgian was made (AI-assisted, native-reviewed); still a draft
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-20 02:11:55 +04:00
bflatastic 05da6b2125 M1: node.get reports layer data (pluginData) (#879)
Layer data written with node.set {props: {pluginData}} was saved but no
command read it back. node.get (the command, the control method and MCP
get_node) and document.inspect now report pluginData as {key: value}
when a layer or page has any, so a tool keeping state in a file can
read it on its next run. Widget keys are included: the plugin rules
only stop plugins writing them, and widget.list already reports that
state.
2026-10-20 04:58:32 +07:00
bflatastic 5617c94167 M1: nudge while measuring; distances follow, handles hide (#894)
Alt+arrows were left out of the nudge keys, so with Alt held to measure
the arrows did nothing and the distances never changed. The arrows now
nudge with Alt held too (Shift: the big step); the hover measurement is
recomputed every frame, so the labels follow each nudge. While measuring
to another layer the selection's handles and size label hide; both
layers keep their outlines.
2026-10-20 04:58:31 +07:00
bflatastic 9ee0c7f572 M1: readable disabled and selected text (#890)
The dim text token (disabled buttons, placeholders, hints) was 2.1:1 on
the light theme's panels; disabled buttons also faded to 40% opacity,
and selected text was drawn in the dark green, 1.4:1 on the dark
theme's selection highlight. The dim text token is now at least 3:1
against panels, fields and selected rows in both themes while staying
dimmer than secondary text, a disabled button is grey with that text,
and selected text takes the theme's readable green. A test checks the
ratios with the colour contrast helper.
2026-10-20 04:57:20 +07:00
bflatastic ddde77db0a M9: release fonts: web build embeds craft-fonts, pin back to the Latin manifest (#885)
The browser has no system fonts, and since releases moved to the shared
workflow the web build was made without craft-fonts: no font list and no
text drawn. The Georgian change also re-pinned craft-fonts to its main,
which has no fonts/latin-manifest.txt, so the next release would fail
under CRAFT_FONTS_REQUIRED. Pin the Latin commit again until one commit
has both (craft-fonts#42); desktop Georgian uses an installed font.
2026-10-20 04:26:04 +07:00
bflatastic 40fe1a623e Merge fix/872 into #813 2026-10-20 03:26:53 +07:00
bflatastic 4b030f027a Merge main into #872 2026-10-20 03:20:30 +07:00
JimKnoxxandClaude Opus 5.5 8d5a29dfda M5: one presentation tab per page of a file
Main plays a presentation in a single "Present" tab. Now each page of a
file gets its own tab, named "file - page", and the name follows
renames. Play on a page that has a tab shows that tab as it was; from
another start frame it starts again. A tab keeps its own copy of the
prototype: it plays on after its file closes, and a saved file opened
again gets its tabs back. A tab whose file is closed or not in front
never reads or changes the file in front (device, responsive,
comments). Escape or the tab's X closes that tab and goes back to its
file, or Home.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:15:13 +02:00
BFlat 9d9cb9915a Merge pull request #812 from lukasz-sklad/feat/polish-translation
feat(i18n): Complete Polish translation (pl)
2026-10-20 03:05:06 +07:00
BFlat 1abb9083c7 Merge pull request #821 from attisvalentin/fix/rescan-and-load-fonts
M4: fonts installed while the app runs: text.rescanFonts, text.loadFont, text.refit
2026-10-20 03:04:29 +07:00
bflatastic 948c6fb8e6 Merge main into #813 (via #872)
# Conflicts:
#	docs/translating.md
2026-10-20 02:06:07 +07:00
bflatastic da1f0a0484 Merge #812 into #872
# Conflicts:
#	docs/translating.md
2026-10-20 02:05:46 +07:00
bflatastic baf93b7756 Merge #874 into #872
# Conflicts:
#	docs/mcp.md
2026-10-20 02:05:13 +07:00
bflatastic 9c2988d408 Merge main into #872
# Conflicts:
#	crates/text/build.rs
2026-10-20 02:04:57 +07:00
bflatastic c13c4301be Merge main into #812 2026-10-20 02:04:36 +07:00
bflatastic df99f364a7 Merge main into #821 2026-10-20 02:04:35 +07:00
bflatastic e8b7917002 Merge #874 into #818
# Conflicts:
#	crates/ui-egui/locales/es.toml
#	crates/ui-egui/locales/fr.toml
2026-10-20 01:43:53 +07:00
bflatastic 7b478052d1 Merge main into #818 2026-10-20 01:43:44 +07:00
bflatastic bc35f02ecb Merge main into #874 2026-10-20 01:43:43 +07:00
bflatastic 20e4028afa #818: interface sizes are relative to the system's display scale (100% is native, Auto goes; 75-200% in Preferences, pref.uiScale takes 0.75-3, "auto" reads as 1); interface_size moved above batch_rename_dialog's doc comment; tests and docs follow 2026-10-20 00:23:14 +07:00
bflatastic a9975ccc22 #818: drop the progress-doc edits (ROADMAP revision row, ui-parity rows); the maintainer adds them 2026-10-20 00:13:55 +07:00