Commit Graph
1968 Commits
Author SHA1 Message Date
Barzabel 15ae2e9115 M1: memory budgets for decoded images and a file's images (#203, #189) 2026-10-14 20:36:47 +03:00
Patrick Wozniak 639c4d7db0 Home: test the file row layout on Open files
Recent files are cards since #249, so the row test now opens two
documents with synthetic paths and checks the Open files list: the row
that fits shows name and path in full; the long one shows the name cut at
its end, then the path cut at its start, at least 12 px apart, and both in
full on hover. Without the row fix the name shows as "Quar…" under the
path.
2026-10-14 19:05:12 +02:00
Patrick Wozniak f1e0a758d1 Home: file rows no longer draw the path over the name
The Open files and Recent files rows painted the detail (the file's path)
right-aligned at its full width and only then elided the name into
whatever was left, with a floor of 40 px. A path wider than the row ran
straight over the name, and the name itself shrank to "Mae…".

The row is now laid out name first: the name keeps its full width when
both fit, otherwise at least 60% of the row; the detail gets the room
left after a 16 px gap and is cut at its start ("…/02-technik/KNX/
file.figcraft"), which keeps the file and its nearest folders. A detail
with less than 48 px of room is left out. Rows of Recents' own lists show
name and path in full on hover when either was cut.

The new widgets::elide_start_font mirrors elide_font, keeping the end.
2026-10-14 18:59:47 +02:00
Rob CoenenandClaude Opus 5.5 9404226235 M4: vector edit mode keeps the canvas still under the pointer
With no points picked, the Design panel's hint ("Select points to edit
their position, radius and mirroring") didn't wrap and made the canvas
49 points wider, so entering vector edit mode moved the view by about 25
points and picking a region moved it back. The jump came in the middle
of the click that picked the region: with the smallest pointer movement
the release landed elsewhere on the page and the region was dragged by
that distance (about 47 units at 70%). Both edit mode hints now wrap.
Regression test: the canvas rectangle stays the same entering edit mode,
picking a region and switching to the width tool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:35:06 -04:00
Rob CoenenandClaude Opus 5.5 d5fee489f5 M7: recent files notice files deleted while the home screen is open
The recent files' dates were only read again when the home screen came
back into view, so a file deleted or moved in the meantime kept its card
until then. They are now also read again every 2 seconds while in view,
and at once when the window gets the focus back; a file that is gone
shows "File not found" without leaving the home screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:07:53 -04:00
tylerjenningsw 82d84d86b1 docs: zoom preview latency boundary and measurements (M8)
Refs #295
2026-10-14 11:59:24 -04:00
tylerjenningsw 72390d7b51 fix(canvas): preview in the browser and judge previews by coverage (M8)
Review of the zoom previews found three problems:

- The browser worker never previewed: wasm builds timed every render
  as 0 ms, below the 12 ms threshold. Renders are now timed with
  web-time's monotonic clock on every target (already in the tree via
  the engine), and the inline worker is built everywhere so a test runs
  it with the production threshold: preview one frame, refine the next,
  a newer request skips the old refinement.
- A preview was refused whenever the texture on screen was refined at
  the same zoom, even when that texture showed another part of the page
  (back at 100% about another point after 800%: 1/64 of the view). It
  now has to show the whole view asked for, not just its zoom.
- The latencies read the frame's start time before the inline render
  and before the texture was set. They now run on the same clock from
  the request to the image handed to egui as the canvas texture.

Refs #295
2026-10-14 11:57:58 -04:00
Barzabel 698e249fbf M1: drop shadows under a background blur (#255)
A layer with a background blur (or glass) drew its blurred backdrop into
its own pixels before casting its drop shadows from them, so a translucent
fill cast a shadow as dark as an opaque one, and with "show behind
transparent areas" the shadow was cast from the backdrop's pixels. The
backdrop was also read before the shadows were drawn, so a shadow showing
behind was never seen through the blur.

When a layer has both, it now casts its shadows from what it paints (with
the layer blur, noise and texture it will get), draws them, and only then
reads the backdrop, which goes under its content; the content still sees
the first backdrop under it while it's drawn. Layers without both draw as
before.
2026-10-14 18:45:15 +03:00
Marco Arnulfo 1aeb5abb74 M1: a click on the toolbar no longer reaches the canvas under it (#307) 2026-10-14 17:43:32 +02:00
AganzeFelicite f9b7aa03ca M0.9: answer control requests while the window is hidden
A minimised or covered main window draws no frames, and control requests
were only answered inside a frame, so every control-channel and attached
MCP call (save_file among them) timed out after 60 s while the app was out
of sight. eframe still calls the app's logic then whenever a repaint is
requested, which the control server already does for each request.

The desktop app's logic now answers, while the window is hidden, the
requests that need no frame: engine.execute, engine.commands,
document.inspect, node.get, window.list and ui.render. Screenshots, input
and the interface's own state still wait for the window, and their
timeout now says that the window has to be on screen.

Closes #269
2026-10-14 17:27:01 +02:00
tylerjenningsw 3405a07d33 Contributors: add tylerjenningsw's names 2026-10-14 11:24:31 -04:00
Barzabel 4f321fb68d M1: creating a variable collection no longer closes the app (#224)
Creating a collection (and adding a mode or a variable, double-clicking
a row, or Rename in its menu) asked egui to focus the new name's field in
the same frame, but that field is only drawn from the next frame on. For
one frame the AccessKit update named a focused node that wasn't in the
tree, and the platform adapter aborts on that, outside the app's frame
guard; Windows keeps AccessKit on through UI Automation far more often,
hence the silent close there. A rename now leaves a flag and its field
takes the focus in the frame it's drawn, through widgets::rename_field,
as the layer rows do.
2026-10-14 18:21:08 +03:00
tylerjenningsw d72e5e0516 docs: zoom previews in the roadmap, gaps and control protocol (M8)
Refs #295
2026-10-14 11:15:11 -04:00
Marco Arnulfo ed0e7a9b2d M1: leaving Design ends the pen path and goes back to Move (fixes #307) 2026-10-14 17:11:20 +02:00
tylerjenningsw 4c23cb71c0 perf(canvas): render coarse zoom previews before refinement (M8)
A new zoom can't reuse the retained canvas image, and the previous one,
re-projected, covers little of the view after a zoom out (1/64 of it
from 800% to 100%), so the page background showed until the full render
finished.

When renders from scratch have been slow (a frame or more), the worker
first renders the whole requested view with a quarter of the pixels
each way, in a Retained of its own so pans and edits still build on the
full image, then refines it unless a newer request is already waiting
(that one gets its own preview). The web build renders the preview and
the refined image in consecutive frames. Pixel preview gets none.

Finished images carry the file, page and whether they are coarse: the
canvas never shows an image of another file or page, and a late preview
never replaces a refined image already at the zoom asked for. ui.inspect
reports the canvas quality and the latency from request to the frame
showing the preview and the refined image.

Refs #295
2026-10-14 11:08:55 -04:00
midasmatterhorn f1f7da1f40 engine: refuse page.move for node ids that are not pages 2026-10-14 18:01:41 +03:00
tylerjenningsw a3a273cfd3 fix(pages): inherit the current page background when adding pages (M1)
page.add gave every added page the interface theme's colour. Figma's
documentation says added pages inherit the current page's background;
the theme only decides a new file's page. Read the active page's colour
before creating the new page, in the command, so the app, CLI, control
channel and MCP agree.

Refs #295
2026-10-14 10:58:17 -04:00
fede9234 3917c26802 Text: colour-glyph comments say only what the fonts are documented to carry
"Placeholder rectangles" was a claim about Apple Color Emoji's glyf
outlines that no source I can show supports: the font's sbix chapter
documents that most emoji are bitmaps while the flags are drawn from
outlines, and it is Noto Color Emoji that has no outlines at all. The
comments, the ROADMAP row and the parity row now say that instead.

No behaviour change: comments and docs only.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-10-14 16:55:07 +02:00
AganzeFelicite 00ac44f6ca M1: save files automatically
Preferences > Save files automatically (pref.autoSave, off by default;
hidden in the browser, which has no files to save to). With it on, each
open file that has a path is saved 3 s after it changes, never during a
gesture, a live text preview or while a version is shown. A failed save
says why in a toast and tries again a minute later.

Version history checkpoints (version.autosave, every 30 minutes) only ran
for files with unsaved changes, so a file that saves itself would stop
getting them; with auto-save on, edits made since the file was opened
count.

Closes #296
2026-10-14 16:48:56 +02:00
fede9234 de5ba60bc4 Text: colour-glyph comments cite the right source and size
The placement formula is Skia's own fontations port
(src/ports/fontations/src/bitmap.rs) reading the sbix spec's
lower-left-corner rule; the earlier comment named CoreText, which that
source does not mention. Apple Color Emoji measures ~180 MB on current
macOS (the 190 MB figure is a hosted copy of the file), so the comments
say ~180 MB.

No behaviour change: comments only.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-10-14 16:44:20 +02:00
Barzabel 8b8825ce68 M1: say why when the window can't open (#261)
Release builds on Windows have no console, so an eframe::run_native error
(no usable graphics adapter, a device the driver refuses) went to an unseen
standard error and the app quit silently. The error is now logged and shown
in a message box with a hint: unset WGPU_BACKEND when it is set, otherwise
update the driver or try another graphics API (OpenGL on Windows), and
where to report it. With --control the box is skipped so agents aren't
blocked.
2026-10-14 17:37:59 +03:00
fede9234 54ba84eddc Text: colour emoji draw from their own colour tables (#268)
Colour emoji came out as the placeholder rectangles their outlines carry
(Apple Color Emoji) or as .notdef when the face has no outline at all:
nothing read sbix, CBDT/EBDT or COLR. A glyph now asks its face for
colour ink before its outline: an embedded bitmap strike decoded to
premultiplied RGBA and placed by the strike's bearings the way Skia (and
CoreText) places it, scaled to the size the run asks for, or a COLR v0
glyph's layers filled from the font's palette.

Faces that carry only colour tables are indexed again: Apple Color Emoji
is no longer skipped for being 190 MB (the scan still reads headers and
name tables only), and "Segoe UI Emoji" and "Noto Color Emoji" join
"Apple Color Emoji" as colour fallbacks, read only for a text that
nothing else covers. The png crate decodes the strikes; skrifa keeps
shaping, metrics and outlines.

Left for later: COLR v1 paint graphs, alpha-only strikes and the SVG and
PDF exports (text is outlines there).

Tests: 8 cases in the text crate (placement, PNG and raw-BGRA decoding,
scaling, COLR layers and bitmaps of the machine's own colour font) and a
render case gated on that font; the font-free ones run everywhere.
Verified on Windows with Segoe UI Emoji (COLR) and with Noto Color Emoji
(CBDT) read from a downloaded file; the sbix path runs through the same
code and needs a macOS run to be seen.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-10-14 16:19:46 +02:00
MarekSpodBiedry a8a689d682 fix(server): keep each team library version in its own file (#206) 2026-10-14 16:19:01 +02:00
MarekSpodBiedry 6eeac4fa04 Keep the redo steps when a failed command batch rolls back (#196) 2026-10-14 16:14:35 +02:00
MarekSpodBiedry 8b961b980a Recovery: wait for the write in flight before an emergency save and before removing copies (#202) 2026-10-14 16:12:40 +02:00
NetterSilverandClaude Haiku 5.5 4193a82dfb Count a recovery revision as copied only after its write succeeds
Fixes #186

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 16:08:47 +02:00
Brandon ThomasandClaude Opus 5.5 833f0d2c27 Release 0.6.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.6.0
2026-10-14 09:05:59 -04:00
fede9234 3709e7320c fix(desktop): Windows' first preferred language picks the UI language (#291)
Windows' preferred-language list is also the input-language list: a language
added for a keyboard sits in it, and taking the first entry with a catalog
turned the interface German on a Polish system (a German keyboard) while the
system and the other apps stayed Polish. Only the first preferred language now
decides; when it has no catalog the interface reads English. macOS'
AppleLanguages and gettext's `LANGUAGE` still take the first language with a
catalog. Fixes #291.

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-10-14 14:54:01 +02:00
Marco Arnulfo e92792e3e8 M1: a press on a corner handle resizes instead of picking the corner-radius handle (fixes #292)
The corner-radius handle sits 10 px in from each corner and answers within 6 px, so a press on the inner half of the 8 px corner square picked it: the drag rounded the corners or, dragged outward, did nothing. Inside the drawn square the corner now resizes both sides (in ratio with Shift or the constrain-proportions link); further in the radius handle still answers.
2026-10-14 14:36:39 +02:00
Marco Arnulfo 310cb40af9 M1: open .figcraft files from Finder, Open With and the Dock on macOS (fixes #289) 2026-10-14 13:58:37 +02:00
haranishi d1ac66a803 M9: 日本語のテンプレートと編集操作ラベルを補完 2026-10-14 20:43:30 +09:00
notdezzi 661144021e fix(format): exclusive unique staging files for atomic save/export
Saving and exporting wrote a truncating std::fs::write to a deterministic
staging name (<path>.tmp-figcraft, <file>.figcraft.tmp) before renaming, so an
unrelated file or a planted symlink at that name was clobbered or written
through, and separate writers of one destination shared a staging file they
could corrupt. Both paths now go through figcraft_format::write_file_atomic: a
uniquely named sibling staging file is created exclusively (create_new) and
written through its own handle, renamed over the destination, and removed again
on any failure, so only the staging file a write created is ever touched. Tests
cover pre-existing staging files and symlinks, concurrent writers, and
injected rename and permission failures.
2026-10-14 13:11:28 +02:00
RealtaNuaandClaude Opus 5.5 73b2845ba4 fix(engine): Alt-drag copies snap to their original; Duplicate repeats the step
- A copy Alt-dragged off a layer snaps to that layer too: its edges,
  centres and equal spacing (Targets::add_box with the originals' box).
  Before, the originals were left out of the snap targets, so a copy
  couldn't line up with or space evenly from the layer it came from.
- Duplicate (Cmd+D) repeats the last duplicate's page offset while its
  copies are what it duplicates: Alt-drag a copy, or Duplicate and move
  the copy, then each Cmd+D steps out the next one as far again. The
  step is measured when Duplicate runs. It lasts as long as the
  selection (undo reuses ids), copies placed by an auto layout set no
  step, and explicit dx/dy win.
- measure_tests: an Alt-dragged copy between its original and another
  layer now snaps to equal spacing (45/45 instead of 50/40).

Tests: duplicate_spacing_tests (snap to the original, equal spacing,
repeat after moving a copy and after an Alt-drag, only for the last
copies, undo then a new layer, rotated frame, auto layout).

Refs #256

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 18:03:48 +07:00
notdezzi 10dc38495f fix(engine): roll back failed commands during interactions too
A command that fails part-way (node.set applies geometry keys before
values like visible, which can fail validation) mutated the document
even when an interaction was under way, because the rollback in
Session::execute was gated on interaction.is_none(); drop the gate so
the command-entry snapshot is restored during interactions as well,
which is the interaction's last valid preview and leaves the gesture
itself intact.
2026-10-14 12:54:02 +02:00
notdezzi efdd5fc645 fix(format): import explicit variable modes on frames and instances
A collection a saved file references by its asset key (the form library
collections keep) instead of a guid resolved to nothing, so every
explicitVariableModes entry was dropped silently and bound layers rendered
in the collection's default mode (#267). The .fig import now resolves such
references against the file's own nodes (the collection node carries the
same key) for mode entries and the variables' collection link, and reports
modes of collections it can't resolve.
2026-10-14 12:40:17 +02:00
Justin John 61def74383 M0: finish canvas drags released outside 2026-10-14 16:04:34 +05:30
Marco Arnulfo ce366ea325 M6: .fig import: a resized nested instance keeps the non-geometric derived data; resize check with a tolerance (#266) 2026-10-14 12:14:45 +02:00
Marco Arnulfo d8556e8f17 M6: .fig import: size overrides keep the constraint placement, nested instances resized again drop their old derived layout (#266) 2026-10-14 12:12:46 +02:00
Marco Arnulfo 01eb35df25 M6: .fig import lays out the layers of resized instances by their constraints (fixes #266) 2026-10-14 12:00:02 +02:00
RealtaNuaandClaude Opus 5.5 b8e5b8e133 fix(pen): show the bezier handles while drawing
The Pen showed only its anchors while a path was drawn; handles appeared
only in vector edit mode afterwards. Now:

- The point being dragged shows its in and out handles, and the point
  before it its out handle (the handles shaping the segments at the pen),
  drawn like vector edit mode's (one shared canvas helper).
- Moving on to the next point, the rubber band is the segment a click
  would make: it leaves the last anchor along its out handle instead of
  going straight.
- A press no longer leaves the previous straight rubber band on screen
  over the segment being dragged.

Overlay gains pen_handles and rubber_handle (shown in ui.inspect).

Tests: pen_overlay_tests (handles during and after a drag, none and a
straight band for corner points, cleared on finish); canvas_fixes_tests
(handle lines and dots painted mid-drag, curved band after release).

Refs #256

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 16:58:51 +07:00
RealtaNuaandClaude Opus 5.5 9c0b441119 fix(ui): disable alignment when the selection can't be aligned
One layer directly on the page has nothing to align to (one layer aligns
to its parent frame), so the six Alignment buttons looked active and did
nothing. They are now disabled there, with a hover explaining why.

- cmd/arrange.rs: align_target (unchanged rule) and pub can_align.
- right.rs: the align rows in add_enabled_ui + on_disabled_hover_text.
- The hover text in every catalog.

Tests: a_layer_alone_on_the_page_has_nothing_to_align_to (engine),
the_align_buttons_are_off_for_a_layer_alone_on_the_page (AccessKit).

Refs #256

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 16:53:33 +07:00
RealtaNuaandClaude Opus 5.5 3f57a4b76a fix(ui): gradient type icons that show their gradient
The colour picker's paint type row borrowed unrelated icons for the
gradient kinds (auto layout's bars, a circle, the rotate arrow, the
instance diamond). Each kind now has its own icon: the Solid swatch as
an outline with that kind's fade inside (linear ramp, radial, a conic
sweep from the top, diamond), drawn with a new per-vertex-alpha mesh
helper.

Test: gradient_icons_fade (each paints a mesh from opaque to clear).

Refs #256

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 16:52:00 +07:00
Marco Arnulfo bd482ea6dd fix(ui): scroll a long pages list in its own area so the layers stay in view (fixes #258) 2026-10-14 11:15:32 +02:00
Marco Arnulfo 64a54ca408 M7: double-clicking a page in the Pages list no longer closes the app (fixes #264) 2026-10-14 11:11:00 +02:00
Yaman DevrimandClaude Opus 5.5 5f3e1e1e6c M9: Turkish catalog reviewed by a native speaker
Drops the draft status from tr.toml (the Preferences window no longer
calls it a draft) and marks Turkish reviewed in ROADMAP.md,
docs/localization-parity.md and docs/translating.md. Turkish stays out of
COMPLETE, so new strings don't have to be translated into it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:47:57 +03:00
ahmed-226 dde193cbf4 M7: a slice's export preview shows the page content under it
A slice paints nothing itself, so thumbnail() rendered an empty pixmap of the slice's own box: expanding Preview for a slice showed only the transparency checkerboard while exporting the same slice wrote the artwork correctly.

Give the preview the export's slice rule (drawn_layers + export_area in cmd/file.rs): draw every top-level layer of the slice's page over its world bounds. Regression tests cover a slice over artwork, a slice with nothing under it, and a plain layer.

Signed-off-by: Ahmed Mahmoud <ahmedmhmouad41@gmail.com>
2026-10-14 10:37:23 +03:00
VanTan 4964910cc9 M7: follow system theme from the first frame 2026-10-14 10:34:14 +03:00
Yaman DevrimandClaude Opus 5.5 06850b8900 M9: Turkish draft catalog
crates/ui-egui/locales/tr.toml translates every UI string (1,577 keys),
command label (557) and menu wording (31), machine-drafted and marked
"draft — needs native review". Buttons and menu items use bare
imperatives (Kaydet, Sil), instructions the polite -in/-ın (seçin,
tıklayın); the percent sign goes before the number (%50). Turkish keeps
a noun singular after a count, so plural entries give only `other`.

Preferences ▸ Language lists Türkçe; System maps tr and tr-TR to it.
The catalog passes the i18n check even when listed as complete.
docs/translating.md, docs/localization-parity.md and ROADMAP.md list
the new draft. Adds my own contributors/people.toml entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:34:10 +03:00
VanTan 7a0bd980c6 M9: add Russian interface translation 2026-10-14 09:33:58 +03:00
Piettro CastroandClaude Opus 5.5 f3cd0509cb fix(engine): read the documented gradient shorthand on every route (#252)
`{"type":"linearGradient","stops":["#hex",…]}` was only expanded by the MCP
tools, so `figcraft-cli run` and the control channel's `engine.execute`
refused it ("`fills` must be paints or colours"). The expansion now lives in
the engine (`params::normalize_paint`, used by `paint_of`), so node.set,
node.create props, fill.add/set, strokes and paint styles all take it; MCP
calls the engine's copy instead of its own.

Tests: engine (every paint command, MCP-equal defaults, junk still refused),
control channel `engine.execute`, and `figcraft-cli run` end to end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:02:45 -05:00