Commit Graph
424 Commits
Author SHA1 Message Date
BFlat 11b2ddfaf4 Merge pull request #1958 from MortenHusted/fix/import-variable-id-collisions
M3: importing a file keeps its own variables and styles when their ids are already taken
2026-11-07 10:03:52 +08:00
BFlat 7522c39ba5 Merge pull request #1760 from Silvertree2010/fix/1745-pen-tool-undo-cmd-z-bug
Make undo while drawing with the pen remove only the last anchor
2026-11-07 09:50:33 +08:00
BFlat 5762212ced Merge pull request #2006 from JonRC/fix/jpeg-quality-srgb-icc
M6: JPG exports take a quality and embed the sRGB profile (#2004)
2026-11-07 09:47:15 +08:00
JonRC a55a728c4c M6: JPG exports take a quality and embed the sRGB profile (#2004)
file.export always encoded JPG at quality 90, and an sRGB document's JPGs
carried no colour profile, so viewers could only assume sRGB. PNG exports
always get an sRGB chunk, and Display P3 JPGs already embedded their ICC
profile.

- ExportSetting gets a quality (1 to 100, default 90, saved only when it
  isn't 90), parsed with the other export options: file.export, export.add,
  export.update, file.exportLayers and MCP export take it.
- encode_export_with takes the quality; encode_export keeps quality 90.
  Every JPG now embeds the document's profile, sRGB included.
2026-11-05 00:09:19 -03:00
JonRC 5244b0075d fix(engine): an unknown stroke cap lists the caps node.set takes (#2005)
strokeCap, startCap and endCap answered only "unknown stroke cap". They now
name the key and every cap, as strokeAlign, strokeJoin and dashCap do.
2026-11-04 23:53:43 -03:00
MortenHustedandClaude Opus 5.5 024b7fe452 M3: importing a file keeps its own variables and styles when their ids are already taken
`file.import` of a FigCraft file kept the document's style, variable or
collection for every id both files used, and the pasted layers kept
those ids. Ids are numbered per file, so two files made apart share
them, and the imported layers were bound to the document's unrelated
variables, modes and styles.

`append_pages` now gives the incoming file new ids first (`Renames`):
- an incoming style, variable or collection is shared, as before, only
  when the document has the same under its id and everything it names
  is shared too (the same file imported again); a collection and its
  variables are shared only together; library copies keep their ids;
- every other id the document already uses gets a new one from its
  allocators, which now take ids to avoid (the incoming file's own);
- layers' style links, bindings, explicit modes and prototype actions,
  styles' bindings, variables' collections, aliases, calculations and
  extensions, and the renamed collections' mode ids follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:14:20 +01:00
1f0acd2bbd M4: Alt+Enter while editing text opens the link at the caret (text.openLink) (#1860)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-02 05:31:21 -05:00
a953a04e33 Shortcuts: Cmd/Ctrl+Shift+Alt+= / - / 0 make the interface larger, smaller and default-sized (#1846)
* Shortcuts: Cmd/Ctrl+Shift+Alt+= / - / 0 make the interface larger, smaller and default-sized

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Interface size keys: live while editing text too, and the new size draws on the next frame

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* A preference set during a frame repaints this window too, so the interface size applies on the next frame

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Control channel: a preference changed by a hidden window's command repaints every window

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-01 23:59:08 -05:00
NetterSilverandClaude Opus 5.5 0e6fcf9bf7 Make undo while drawing with the pen remove only the last anchor
Fixes #1745

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:03:45 +01:00
BFlat 12e9b409ed M12: a connector end on any part of an object with Cmd/Ctrl (#1733) 2026-10-31 10:27:56 +07:00
BFlat 6b28ef57dc M4: Dash cap setting for Dash and Custom strokes (#1728) 2026-10-31 09:41:39 +07:00
BFlat 1c5c9e04c3 M11: widgets draw lines and SVG pictures (plugin API 6) (#1727)
A widget description can have a line, 100 long and black and 1 thick unless told
otherwise, and an SVG that comes in as locked layers, scaled to fit a size and
within the widget's 256 layers. The Counter example uses both.
2026-10-31 08:13:08 +07:00
BFlat 3837b08a71 M8: a damaged image is refused with a reason instead of showing as an empty box (#1724)
* M8: a damaged image is refused with a reason instead of showing as an empty box (#1474)

* M8: replacing an image refuses a damaged file too
2026-10-31 07:22:44 +07:00
BFlat b462a48346 M12: a connector's path can be reshaped by dragging its path handles (#1710) (#1717) 2026-10-31 04:53:36 +07:00
BFlat fd8d49fc95 M11: a widget opens its plugin's panel (plugin API 5) (#1684)
* M11: a widget opens its plugin's panel (plugin API 5)

Gap 6: a widget callback's ui_show opens the plugin's panel and keeps the module loaded; the panel's callbacks get the widget's id and state and redraw it in the same undo step. Older plugins' ui_show from a widget still opens nothing. The Counter example sets its count from a panel.

* M11: open_panel takes its opener as one value
2026-10-31 03:24:32 +07:00
BFlat b377c70a8c M4: the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable (#1674)
* M4: the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable of what it picks

* M4: the Create from color window's collection picker is its own function
2026-10-31 02:09:08 +07:00
BFlat 9f4d1ce75b M12: board votes say who voted for what, and earlier votes' results are kept (#1666)
* M12: an ended vote's results say who voted for each object

* M12: a board keeps earlier votes' results, listed in Results and deleted one at a time

* M12: the vote Results popover is drawn by small helpers, and its stepper, rows and Clear are tested
2026-10-31 01:19:03 +07:00
Patrick Wozniak 2d23f95673 M9: Home ▸ Online shows the server's team list, and its files open by links without a secret (desktop and browser) 2026-10-30 17:10:12 +01:00
BFlat b3ca228a75 M5: skipping the Open link notice by command is saved at once (#1667)
proto.linkNotice from the Actions palette, the control channel or MCP changed the preference in memory only until the app next saved its preferences. It now sets Session::prefs_unsaved, which the app saves at the end of the frame like installed plugins and live-session tokens; the notice's own Don't show this again goes the same way. Gap 15.
2026-10-30 23:05:57 +07:00
BFlat 4f5a25cc82 M12: spreadsheet cells paste onto a board as a table (#1663) 2026-10-30 22:32:44 +07:00
BFlat 27920c5e26 M8: a drag moving many layers normalises only what sits around them, not each layer's bindings, sizing, text and instance checks (#1571) (#1656) 2026-10-30 20:53:03 +07:00
BFlat 01b07af55a M6: a pasted image the clipboard doesn't carry shows as missing and is named (#1648)
* M6: a pasted image the clipboard doesn't carry shows as missing and is named (#1646)

The placeholder an import stores for an image it lacks is now striped, not flat grey; the fill keeps the image's name; .fig and paste give a fixed, translated note per image; edit.pasteDesignLayers returns missingImages, the paste's message names those layers, and a paste with notes opens the import report.

* M6: the missing-image placeholder's stripes stay clean when scaled, and a .fig gives one note (#1646)
2026-10-30 19:34:24 +07:00
BFlat e4a7e89f39 M3: typed and stored variable, collection and mode ids find exactly that object (#1613) (#1635)
Builds on #1618. Variables::get and Variables::collection take an exact id before a name; stored mode ids go through Collection::mode_by_id (expressions, the prototype's mode switch); font family variables and the colour picker, prototype editor and panels read stored ids exactly. Ported from fix/stored-ids-exact.
2026-10-30 18:00:34 +07:00
BFlat f0abe51a84 M6: plugin widgets have hover styles (gap 6) (#1634) 2026-10-30 17:33:04 +07:00
BFlat 10c66678c3 Merge pull request #1641 from ChristopherLinnett/feat/1280-color-calculations
M3: colour variables can be calculated from others, faded or mixed per mode (#1280)
2026-10-30 16:16:23 +07:00
BFlat 99440fa9ff M8: a drag that only moves the selection shifts what was worked out at its start (#1571) (#1640)
The engine notes when a drag has only moved the selected layers by one offset (Interaction::moved_by); the canvas keeps the outlines, box and slots from the drag's start document and shifts them, the selection spacing and the Design panel's values (but the position fields) come from that document, and the Export section's values are kept. Dragging 648 layers: UI pass 2.10 to 1.44 ms a frame.
2026-10-30 16:13:55 +07:00
BFlat 34fce9bfcf M12: create the next board shape already connected (#1631)
* M12: create the next board shape already connected (#1597)

A selected sticky or shape shows a + beside each side; hovering one previews where the next goes, clicking makes one of the same kind, size and colour there, joined by a connector, and starts typing in it, in one undo step. board.addConnected {id?, side?, edit?} does it from commands.

* M12: the connected-shape handle and command in every full catalog (#1597)

* M12: Shift+Cmd/Ctrl+Enter on a board shape adds the next one already connected (#1597)
2026-10-30 15:59:13 +07:00
bflatastic 68450a6a0e Merge main into #1641 2026-10-30 15:57:31 +07:00
BFlat c1451fecd5 M4: an imported file finds an installed family under the other names its fonts give it (#1292) (#1628)
Fonts name a family by its typographic name and by a legacy name per weight; fonts without typographic names are installed under the legacy ones only. FontDb::resolve finds either from the other, and matches names across case, spaces, hyphens and underscores; every lookup (import, paste, REST import, rendering, missing fonts, the font report) uses it. A weight-number style goes by that weight, and .fig and REST imports wait for the installed fonts to be indexed.
2026-10-30 15:44:09 +07:00
Christopher Linnett bc76665c65 M3: colour variables can be calculated from others, faded or mixed per mode (#1280)
A colour variable's value in a mode can now be {"alpha": {"variable", "opacity"}} - that
variable's colour with its alpha replaced - or {"mix": {"from", "to", "t"}}, a per-channel blend
of two colour variables or literals over the stored values (in the document's colour profile),
alpha included. Calculations resolve lazily like aliases, so bound layers, mode switches,
instances and undo follow their sources; they compose, and a loop, or a calculation that reads
more than 32 variables in its collection's modes (other collections in their default modes), is
refused when it is set. Operands are stored by id and read by exact id, as stored references
are. Files keep them; REST, CSS, Penpot and .fig exports write the colour they show in each mode;
deleting a source leaves its readers on the colour they showed; library import and swap
re-point their operands. The Variables panel shows them with their formula and can swap one for
an alias or detach it to its colour.
2026-10-30 16:25:26 +08:00
bflatastic e5a5da9e62 Merge main into #1609 2026-10-30 14:35:19 +07:00
bflatastic 991ea891b5 Merge main into #1609 2026-10-30 13:56:15 +07:00
bflatastic 966f823551 Merge main into #1420 2026-10-30 13:52:53 +07:00
BFlat c47177498d M8: every file a command reads by path is a regular file within a cap (#1114) (#1625) 2026-10-30 13:48:26 +07:00
bflatastic cf129e42c9 Merge main into #1420 2026-10-30 13:21:51 +07:00
BFlat 7b886404c3 M8: Opening a file contacts no team library server it names until you check it (#1621)
* M8: opening a file contacts no team library server it names until you check it (#1097)

* M8: translate the team library check strings (#1097)

* M8: the Assets panel library test checks what allows a library, without its own fake link (Sonar duplication and coverage)
2026-10-30 13:03:31 +07:00
BFlat f1eaf3fcb4 M8: cap the people a client keeps track of and the deleted ids a session remembers (#1622)
* M8: cap the people a client keeps track of and the deleted ids a session remembers (#1121)

A hostile host or server could grow a client's presence, seen-peers and who-is-here lists without bound, and a session's deleted-id set only grew. Clients keep at most MAX_PEERS (4,096) others, leaving newcomers out with a warning; max_people is at most that; a session remembers at most 262,144 deleted ids, the lowest forgotten first.

* M8: a max_people above what clients track is lowered with a warning, not refused (#1121)

Refusing it would stop a server whose existing config says more from starting after an upgrade. Config::people() lowers max_people to MAX_PEERS for the core's limits, and the startup warnings name the configured and the used value.

* M8: the live welcome's handling in its own function (Sonar S3776)
2026-10-30 12:33:29 +07:00
BFlat b2885452fb M8: doc.list gives AI agents and plugins file names, not paths (#1117) (#1620) 2026-10-30 12:14:34 +07:00
BFlat 24cf643254 M8: a batch keeps one undo snapshot, not one per step (#1099) (#1610) 2026-10-30 11:22:06 +07:00
Mert Dede 5c3aef840a Merge remote-tracking branch 'origin/main' into feat/996-inspect-fields
# Conflicts:
#	docs/roadmap.md
2026-10-30 07:07:54 +03:00
9573b8b2d2 M8: a grouped command whose closure panics leaves the document as before (#1085)
A panic in a Session::grouped closure outside any command skipped the group rollback: the
documents stayed half edited, the history limit unbounded, live documents held and the group
depth raised. checkpointed now catches the unwind, runs the same rollback as a failed group and
resumes the panic, so the caller's guard still reports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
2026-10-30 05:54:31 +03:00
BFlat 38a4feccc9 M8: hover hit-tests once per pointer move, and a moved auto layout frame keeps its layout (#1571) (#1601)
Every pointer move draws about three frames, and the canvas hit-tested the hover on each: a hover where nothing changed since the last (place, modifiers, zoom, file, page, revision, selection, tool and modes) is no longer sent to the tools again. Dragging an auto layout frame laid its contents out again on every move: a frame whose only change is its transform now keeps its own layout (its parent's still runs). ui.inspect reports frameTimes. Measured on a synthetic 1,900-layer file: hover UI pass 0.91 to 0.49 ms a frame and CPU 33% to 22% of a core; normalise while dragging 648 layers 3.8 to 2.1 ms a move.
2026-10-30 09:28:34 +07:00
BFlat bc11197c21 M1: Enter or a double-click with the pen edits the vector with the Move tool (#1430) (#1590)
A double-click with the pen drew on: its second press added a point on the last one, and a double-click meant to edit a layer drew a stray vector on it. A press on the last anchor now adds nothing; a double-click ends the path (or, when it only started one, edits the layer under it) and Enter ends the path, both going on in vector edit mode with the Move tool. In edit mode the pen's Enter and double-click take the Move tool, the double-click taking back what its first press began. Escape still keeps the pen (#760).
2026-10-30 08:41:01 +07:00
BFlat 8ee8ba6ce7 M6: FigJam .jam files open, as boards (#1342) (#1586)
* M6: FigJam .jam files open like .fig files (#1342)

A .jam is the same archive as a .fig (its canvas header reads fig-jam.), which the .fig reader already took; File > Open and its filter, drag and drop, file.import and the command line now take .jam too, and the home screen shows its thumbnail. FigJam layers come in as the .fig reader makes them (stickies and shapes with text as frames with their generated layers, other FigJam layers as frames or vectors with a note, unknown types left out with a note). Tests use a synthetic fig-jam. canvas and a .jam made from our own export.

* M12: a FigJam .jam file opens as a board (#1342)

The .fig reader marks a canvas with FigJam's fig-jam. header as FileKind::Board, so a .jam opens with the board toolbar, the dot grid and the board panels and saves as a board; a .fig stays a design file, and importing a .jam into an open file leaves its kind alone. Its objects stay ordinary layers (frames and vectors with the existing import notes). Builds on the .jam opening branch (#1586).
2026-10-30 08:03:56 +07:00
BFlat e7ae0f4426 M9: share links can comment: a comment access level beside edit and view (#1525) (#1576)
Someone who joins with a comment link (or its join code) sees the file live and adds, replies to, reacts to and resolves comment threads, but can't change the design: their app refuses other edits and the server accepts a comment link's batch only when every change in it touches comment threads. Sharing online makes a comment link and code beside the edit and view ones; the Share popover offers Can edit, Can comment and Can view, and the commenter's toolbar shows a Can comment badge with Move, Hand and Comment.

Backward compatible: clients announce the comments word; one that doesn't is welcomed with a comment link as a viewer and gets comment links listed as view links, a level this build doesn't know reads as view, and an older server that can't make a comment link leaves the share without one.
2026-10-30 07:29:40 +07:00
BFlat cc3052de5e M8: cap the input of boolean operations (#1104) (#1581)
Each sweep takes at most 250 000 segments and 2 million possible crossings (pairs of segments whose bounds overlap, counted in O(n log n)), and a fold at most 4 million segments swept over its steps; past them the operation is refused with PathOpsError::TooComplex before it runs. A boolean group past the caps draws without its result, with a warning once, and the refusal is cached; vector.flatten refuses it instead of dropping it.
2026-10-30 06:03:35 +07:00
Mert DedeandClaude Opus 5.5 42e1a39d72 Merge main into #1420
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 01:47:34 +03:00
BFlat 7d76b0c673 M3: component properties keep their order on rename; Make slot asks for the name (#1536, #1537) (#1565)
Properties were listed by name, so a rename moved one. Components now keep prop_order (the names as they were made; a rename keeps the place) and every listing follows it. Make slot opens a name field under the frame's Slot row; Enter makes the slot, Escape cancels, a taken name keeps the field open.
2026-10-30 04:49:00 +07:00
BFlat 132abcc672 M8: show in the app where an attached AI agent is working (#1416) (#1556)
Session::execute_as records the agent's presence: its name, the file, and the innermost layers its last edit changed or the selection it set, for 8 seconds after its last command. The app shows it as an avatar in the people row and an outline with a name tag on the canvas, in its own colour. Display only: no undo step, never saved, never synced to a live session.
2026-10-30 02:03:27 +07:00
bflatastic 4ec382ea86 Merge main into #1547 2026-10-30 00:32:08 +07:00