Commit Graph
3465 Commits
Author SHA1 Message Date
8da11395e9 M3: glass effects take variables, and their frost stays within 0–100 (#1886)
* M3: glass effects take variables, and their frost stays within 0–100 (#1885)

Glass's light angle, intensity, refraction, depth, dispersion, frost and
splay bind to number variables, on a layer (`variable.bind`,
`effects/<i>/<field>`, `radius` being the frost) and in an effect style
(`style.bind`), and the Design panel's glass fields take a variable like
the shadow and blur fields. A variable reads as the field shows it:
intensity, refraction, dispersion and splay in percent.

A glass's frost stays within 0–100, the reference tool's range: a larger
frost from an older file draws at 100, and the field, a bound variable and
scaling stop there.

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

* M3: a frost from an older file scales and shows as it draws (#1885)

Review follow-up: a frost saved past 100 draws at 100, so scaling a layer
starts from that (150 halved draws at 50, not 75) and the Design panel's
Frost field shows it. The revision row's binding path escapes its pipes, so
the table keeps three columns.

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

* M3: Dev Mode and .fig export show a frost from an older file as it draws (#1885)

A frost saved before the 0–100 range draws at 100, and the canvas, CSS and
scaling already read it that way, but Dev Mode's Glass row and the .fig
export still read the stored number, so they said 150 where the canvas
shows 100. Both now read the sanitized frost.

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

---------

Co-authored-by: Oleksii Sukholita <oleksii.sukholita@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 22:08:15 +01:00
64f254a823 Colour picker: dragging a gradient handle on the canvas keeps the picker and the handles up (#1852) (#1985)
* M1: dragging a gradient handle on the canvas keeps the colour picker open (#1852)

A press on one of the open gradient's handles or stops closed the picker: the canvas took the press
and began the drag, then the picker, drawn later in the same frame, saw a press outside its card and
closed, committing the drag's interaction at once. The handles vanished with the picker and the drag
went on without an undo step of its own. The handles are the picker's controls: while a gradient
handle or stop drags (`handles::gradient_dragging`, the press included) the picker doesn't close on
an outside press, as it already doesn't while the eyedropper samples. A press elsewhere on the
canvas still closes it. Docs: ui-parity.md.

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

* M1: a handle drag released off the canvas ends there (#1852)

Review: a gradient handle drag whose button went up over a panel, over the colour picker or outside
the window stayed stashed, its interaction uncommitted, until the pointer was back on the canvas,
and the next press there moved the handle to wherever it landed; with the picker now held open while
a gradient drag is stashed, it would also have stayed open meanwhile. `handles::end_released_drag`
commits and clears a stashed drag when the button is up, called from the canvas input's off-canvas
branch and when the pointer is over something drawn above the canvas. Test:
`a_gradient_drag_released_off_the_canvas_ends_there` (released over the sidebar and over the picker).

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

* M1: a handle drag ends on a release over a ruler, and when Escape or Undo closes its gesture (#1852)

The release check ran only where the pointer was not on the canvas, after the
comment pins and ruler guides had their turn; both return early, so a gradient
handle released over a ruler stayed dragged, uncommitted, until the next press.
It now runs first, wherever the pointer is, and the frame it ends a drag on is
the handle's, as the release on the handle itself was.

Escape (the picker closes and commits) or Undo can also close the gesture while
the button is still down. The stashed drag then kept setting the fill every
frame with no gesture open, one undo step a frame; with no gesture open the
drag is now over.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 22:07:52 +01:00
Trevor BrownandClaude Fable 5.1 4fcb95eeab M3: the Local variables window selects several variables with Shift and Cmd/Ctrl, and acts on them at once (#2002)
The window keeps a selection of variable ids: a click selects one row, Shift+click the range from the anchor through the rows shown, Cmd/Ctrl+click toggles a row, and Escape or a click on the empty space clears it. Selected rows use the sidebar's selected-row colour.

Right-clicking a row that is part of a selection of several offers Delete, Move to group and Scope for all of them, each sent as one command.batch so it is one undo step. Rename and value edits stay single-row.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 15:54:11 -05:00
Trevor BrownandClaude Fable 5.1 877d0c61dd M1: the boolean operation buttons carry original icons (#2003)
The Design panel's Boolean groups menu listed Union, Subtract, Intersect and Exclude as text only. Each entry now leads with its operation's icon, drawn in code from polygons and rectangles: two offset squares with the result filled at reduced alpha. The commands, enabled rules and layout are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 14:44:32 -05:00
Trevor BrownandClaude Fable 5.1 19e3728fa5 M2: Suggest auto layout warns about uneven cross offsets and refuses instances (#1823)
Bands that start at different offsets across the flow (a centred title above a card) are lined up at the smallest offset, which moves some of them, so the result now carries a warning for it. Instances and layers inside them are refused before anything changes, with the same error Convert to group gives; a main component still works.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 11:55:12 -05:00
Trevor BrownandClaude Fable 5.1 4c793faf51 M2: Suggest auto layout adds nested auto layout frames by rows and columns and keeps the placement (#1823)
layout.suggest used to run layout.add and made one row of everything. It now splits a frame's layers into rows or columns by the gaps between them, wraps each band of several layers in a hugging frame without fill and repeats inside it, so the design keeps its placement where the gaps are even. The frame keeps its size, and uneven gaps and layers that overlap along both axes come back as warnings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 11:06:33 -05:00
Trevor BrownandClaude Fable 5.1 a63f0d7937 Docs: name the Indic scripts the UI fallback covers (#2014)
From review: "the Indic scripts" promised more than the sample list holds. The localization doc, the ROADMAP and the families comment now say Devanagari, Gurmukhi, Gujarati, Tamil, Telugu, Kannada and Malayalam.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 10:41:00 -05:00
FredAppsandClaude Opus 5.5 01762dae33 fix(ui): About credits table columns can be dragged wider or narrower (#1969)
* fix(ui): About credits table columns can be dragged wider or narrower

The Contributors and Models tables already open fitted to the dialog, the
name column taking the spare width, and scroll sideways only when there is
not room for every column. Their columns were fixed, though: no edge could
be dragged. They are resizable now, down to 28 points (the flexible column
to its minimum). Widening a column grows the table, as in a spreadsheet,
and only then does it scroll sideways; cells clip instead of overlapping.

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

* About: refit credits tables when what they measure changes

The columns became resizable, so the table keeps its own widths. A longer
name, a changed cell or a resized window left the old widths in place and
clipped text. The table now goes back to its fit when a column measure or
the clip rect width changes, which scrolling never moves.

Adds tests that drag every column edge, change the fit and scroll a
widened table, as CodeRabbit asked for the drag.

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

* Tests: a fitted credits table fills the window exactly

The table ends on the window edge, which the layout rounds to whole points,
so the check allows that edge.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 15:48:40 +01:00
Trevor BrownandClaude Fable 5.1 2c92a16944 M10: UI script fallbacks leave Bengali and Thai to the CJK path; Hindi row and ROADMAP status (#2014)
From review: crate::cjk picks the Bengali and Thai faces after this list in its own order, so the two scripts come out of SCRIPT_SAMPLES and their families out of SCRIPT_FAMILIES; the Hindi row says the desktop UI takes an installed Devanagari face; the ROADMAP status line and Localization row carry the change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 09:43:47 -05:00
Trevor BrownandClaude Fable 5.1 facc2a2178 M10: the UI takes a system face for Ethiopic, the Indic scripts and the other scripts its fonts lack (#2014)
A String variable's Amharic value drew as boxes in the panels: `bidi::script_fallbacks` sampled Hebrew and Arabic only. It now samples one letter of each of 18 scripts against the system families that draw them on Windows, macOS and Linux; `theme::system_fallbacks` takes the first installed one per script, once. Test: `bidi::tests::other_scripts_take_an_installed_system_face`; localization doc and ROADMAP updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 08:50:45 -05:00
Helen Kwok 05db36303f figcraft-web: a Save of a host-opened document goes out under the host's file name
In page host mode a plain Save of a .figcraft the host opened used the document's internal name, so the host got a different file name with saveAs true instead of an update to the file it opened. Services gains host_file(); Save uses it for a .figcraft host file, Save As keeps the document name, and a missing, .json or .fig host file stays a Save As.
2026-11-05 17:51:41 +08:00
Carbocode 0b68405d85 M8: reuse application field lifecycle for selection opacity 2026-11-05 09:59:05 +01:00
Trevor BrownandClaude Fable 5.1 c16c3676c2 M4: text direction: CSS keeps Left beside a forced rtl, paragraph arrays read to the paragraph count, the empty last paragraph keeps its direction (#1830)
From review: `text-align: left` beside `direction: rtl` (Left is absolute on the canvas); the REST import reads the four parallel paragraph arrays up to the paragraph count, not the 256-entry list limit; `rtl_para` gets an entry for the position after the last char so an empty last paragraph keeps its forced direction; the localization status line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 03:46:28 -05:00
Trevor BrownandClaude Fable 5.1 cd5c9aeb09 M4: a paragraph's base direction can be forced: Text ▸ Text direction (#1830)
Direction came from the letters only; Add right-to-left text says controls override it per paragraph (Text > Text direction, Left to right or Right to left). `TextDirection` on `ParaList`, `Bidi::of` with a base level per paragraph, `text.direction` and the three menu commands (the edited paragraphs while editing), `node.set textDirection`, the Type settings control once a right-to-left script is in the text, inspect `textDirection` / `lineDirections`, the CSS `direction`, and the `.fig` lines' `sourceDirectionality` in and out.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 03:23:07 -05:00
ashokram-si f35af507ec M1, #1758: mouse buttons 4 and 5 pan the canvas like the middle button 2026-11-05 13:04:17 +05:30
JonRC 19af9c9c3b M6: test that a bad JPG quality is refused for every format (#2004)
As vdCompat, bleed and the SVG flags are: PNG, WebP, SVG and a format taken
from the path refuse quality 0 and write nothing, and a quality kept on a PNG
setting is used once the setting becomes a JPG.
2026-11-05 00:41:46 -03: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
SoldierPePe e32a5dc120 M10: a minimized mirrored window keeps its Expand button where the rail was
Minimize (#1835) puts an Expand button where the navigation bar was. In a mirrored window the rail is on the right, so the button goes to the top right there; the test pins both sides.
2026-11-05 03:08:19 +03:00
SoldierPePe 30fe964244 M10: a mirrored style picker sits beside the Design panel as drawn
In a small or narrow window the Design panel is drawn narrower than its
saved width, so the mirrored style picker read the saved width and could
open over the panel. It now places itself from the panel's drawn rect,
as the colour picker does; the left-to-right placement is unchanged.
2026-11-05 02:59:04 +03:00
SoldierPePe 5233c451de M10: a right-to-left UI language mirrors the window
With the UI in Arabic (or another right-to-left language), the icon rail
and the layers panel go on the right and the Design panel on the left.
The panel edges size the panels dragged the other way, with the same
width preferences. The Design panel's edge line, the colour picker, the
style picker and the interaction editor sit on its canvas side, and
egui's own chrome mirrors through Style::right_to_left. Rows inside the
panels, the toolbar and the tab bar keep their order for now.
2026-11-05 02:59:04 +03:00
fluid-chey 3a71a24176 Merge branch 'fix/live-reconnect' into fix/live-offline-merge 2026-11-04 14:22:52 -07:00
fluid-cheyandClaude Opus 5.5 7560e13e6f M9: leaving stops sending at its deadline, however much is left
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 14:22:49 -07:00
fluid-chey 6878ab1186 Merge branch 'fix/live-reconnect' into fix/live-offline-merge 2026-11-04 14:06:13 -07:00
fluid-cheyandClaude Opus 5.5 f49a8df47c M9: leaving a live session sends what's waiting at its pace
A queue past what a server takes at once would get the connection refused before
the end of it arrived. The desktop keeps sending at the pace for up to 10 s on its
own thread, and the browser sends what may go now. `collab.reconnect` also respects
a refusal of network access while it waits to reconnect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 14:06:11 -07:00
fluid-chey 61262192ab Merge branch 'fix/live-reconnect' into fix/live-offline-merge 2026-11-04 13:49:17 -07:00
fluid-cheyandClaude Opus 5.5 c25088c58a M9: count the pace of queued sends when they go, and say when leaving early
The pace was set when a message was queued, so messages held up by a stalled
connection left together afterwards and could trip the server's rate limit. One
queue now counts it as they go, for the desktop and the browser alike.

Leaving while changes are unconfirmed now says so: an undo waits for its edit, so
what was still on its way may not all reach the others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:49:15 -07:00
fluid-chey b4f301c008 Merge branch 'fix/live-reconnect' into fix/live-offline-merge 2026-11-04 13:21:39 -07:00
fluid-cheyandClaude Opus 5.5 0ca48147e5 M9: a reconnect starts its pace afresh in the browser, as on the desktop
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:21:38 -07:00
fluid-chey 33b3cfe74c Merge branch 'fix/live-reconnect' into fix/live-offline-merge
# Conflicts:
#	docs/collaboration-parity.md
2026-11-04 13:04:38 -07:00
fluid-cheyandClaude Opus 5.5 e87078a8ec M9: leaving a live session sends what was waiting its turn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:03:09 -07:00
fluid-cheyandClaude Opus 5.5 f566f9611d M9: a slow join in a busy room isn't dropped for other people's cursors
A client taking in a large welcome holds one queue slot for the whole transfer,
while everyone's presence fills the rest. Five people moving their cursors fill
1024 slots in about 7 s, and the joiner is dropped with the same result on every
try. Presence now stays out of a queue that is half full: it is latest-wins, and
the other half stays free for edits, which can't be left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:54:06 -07:00
fluid-chey 84cd5968eb Merge branch 'fix/live-reconnect' into fix/live-offline-merge 2026-11-04 12:49:13 -07:00
fluid-cheyandClaude Opus 5.5 436b893741 M9: pace queued sends without blocking, and address review comments
Queued sync messages wait in a list the session loop drains, so Leave, a lost
connection, presence and pings are never held up behind them. A text frame from
the host stays final. A session that ended stays on its tab after a Save as:
ending it there could turn a tab into a plain local file with no word about it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:49:11 -07:00
fluid-cheyandClaude Opus 5.5 04455c4899 M9: joining a live session again merges what you did while disconnected
Joining again after a session couldn't go on opened your offline changes in a
copy beside the file. They now go on top as one undoable edit, through the
three-way merge branches use. The rejoin asks to resume as the same client, so
layers made offline are yours to send. What can't go in (the same thing changed
both ways, or layers a new client can't make) leaves the server's version, and
your whole version opens beside it so nothing is lost. A server that no longer
knows you merges the same way instead of discarding unconfirmed work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:23:09 -07:00
fluid-cheyandClaude Opus 5.5 fccaa43101 M9: a joined live session keeps reconnecting and says when it's disconnected
A session you joined could end for reasons that pass, or after about 85 s of
retries, and its tab then looked like an unsaved local file. Work done there
reached no one, with only a 3 s toast to say so.

A live connection now reconnects until you leave, pausing a minute after each
12 attempts. Only refusals that are for good stop it, so "too many messages",
"busy" and the like retry. Messages queued while offline go out paced so a long
queue can't trip the server's rate limit. A session that ended stays on its tab:
the tab shows a red mark and a strip across the canvas offers Retry now or Try
again, Save a copy and Dismiss. Joining again uses the same tab, and what changed
offline opens beside it as an unsaved copy. A session that gets out of step
joins again by itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:17:25 -07:00
fluid-cheyandClaude Opus 5.5 ff862dcf7a M7: a click on a page opens it after a rename that changed nothing
The pages list ignored whether the rename field was still open, so a rename
cancelled with Escape or committed unchanged stayed set. Its idle field sat
over the row's name, caught the next click and started editing, and the page
didn't open. The row now ends the rename when the field lets go, as layer rows
do. Rename page from the row's menu now takes the keyboard, as a double click
does: it had relied on the idle field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:38:48 -07:00
Janiz-zzzandClaude Opus 5.5 bae4a4e81d i18n(fi): translate the 43 labels Finnish lacks
Text wrap style, auto layout resize shortcuts, Hide UI and Minimize the
interface, interface size commands, library analytics periods and
counts, Replace image, Extend collection and upload from computer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:40:58 +02:00
Markus Reimr 23792e8503 M10: translate German text-wrap controls and tooltips 2026-11-04 14:32:29 +01:00
Trevor BrownandClaude Fable 5.1 cacf73a094 M8: a reconnected loop is closed only where the segment makes it one continuous chain (#1951)
A loop whose segments are the chain between the two ends but are listed out of walking order has
no place for the new segment that makes it continuous; inserting it at the first gap would leave
the other gaps. Such a loop is left as it was (the walkers' bridging still draws it), as a loop
broken in two places is. Test: the chain A-B-C-D-E with its loop listed out of order, joined A to E.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 06:45:00 -05:00
Trevor BrownandClaude Fable 5.1 9ff2105d9b M8: a reconnected loop keeps its direction, so a hole stays a hole whichever end was clicked first (#1951)
Cause: `ops::connect` rebuilt the broken loop along the path from the second end to the first, so
connecting the ends in the other order reversed the loop's walk; a hole wound against its outline
then wound with it and the non-zero rule filled it.

Fix: the new segment goes into the loop at its gap, in the loop's own order. Test: a ring whose
hole loses a corner with Delete and is connected again, either end first (red before for one
order).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 06:03:00 -05:00
Trevor BrownandClaude Fable 5.1 4cd01f0a04 M4: a justify gap is reordered with the line's runs, so a nested embedding gets its gaps at its edges (#1970)
Cause: `visual_gaps` mapped a gap at a run boundary to the gap before the later run's first item,
which for a run nested inside another (a Chinese word in a Hebrew phrase in Chinese text, made
with embedding marks) is a gap inside the embedding: one boundary took it, another got none, and
the line came up a gap short of the box's edge.

Fix: each gap is reordered with the line as a glyph of no width at the lower of its two
neighbours' levels (rule N1 for a space between them), through the same `visual_order`: items and
gaps keep alternating and each gap lands once at the boundary it belongs to. Test row with explicit
embedding marks (red before: "char 8 moved 2 (want 1)").

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 06:01:39 -05:00
Trevor BrownandClaude Fable 5.1 de881846a8 M8: joining the ends of a path broken open closes its fill loop through the new segment (#1951)
Cause: deleting a point with Delete (not Shift+Delete) opens the path and leaves its region's
loop with a gap, filled closed by a straight line across it; joining the two ends again (the pen
onto the other end, Connect, Join) added the segment to the network but not to the loop, which
still spanned the gap. Unrounded, the straight line and the segment coincide; once the points are
rounded, the fill and the selection outline run straight across while the stroke, walked from the
network itself, follows the segment and its arcs (#1506's picture).

Fix: `ops::connect` splices the new segment into the loop whose segments are the chain between
the two ends, so the loop closes through it; a chain no loop fills still becomes a region of its
own. Tests: the chamfer made by Shift+Delete, by Delete then Connect, and drawn with the pen, each
rounded, with the fill tracing the stroke (engine); every loop order and segment direction of a
chamfered rectangle (doc).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 04:35:37 -05:00
Trevor BrownandClaude Fable 5.1 44f9eccb71 M4: a justify gap stays out of grapheme clusters and opens beside a right-to-left run (#1970)
Review: a combining mark shaped as a glyph of its own (U+20DD on an ideograph, say) got a gap
between it and its base; a gap after the last logical glyph of a right-to-left run opened after
that glyph's visual place, inside the word, instead of between the run and the ideograph after it.
A gap now needs the next item to start a grapheme cluster, and `visual_gaps` opens each logical
gap at its visual boundary: between logical neighbours where they are, between two runs where they
meet. Two more rows in the test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 04:33:28 -05:00
Trevor BrownandClaude Fable 5.1 95d71a6825 M4: justified Chinese, Japanese and Korean text spreads the room between its characters (#1970)
Cause: justified lines only widened their spaces, so a line of ideographs, written without any,
kept its natural width and its right edge never reached the box's.

Fix: the room on a line (but a paragraph's last) also goes into the gaps next to an ideograph,
kana, Hangul or full-width form wherever a line could break there (`breaks::justify_between`,
beside `break_between`): each is a word of its own, as for line breaking, and no gap opens before a
closing mark or after an opening one. Spaces widen as before; mixed text widens both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 03:59:46 -05:00
Trevor BrownandClaude Fable 5.1 31dcd0e5fe M6: a saved connector path going back into its object is routed instead (#1910)
Review: the first and last runs were checked for lying at right angles to their sides, not for
going out of the object, so a path whose first run went up through the start's box (or whose last
arrived from inside the end's) was kept and drawn through it. Both runs now have to go the way the
side faces; one along the side or back into the object leaves the connector to the router. Two more
cases in the routed-here test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 03:46:49 -05:00
Trevor BrownandClaude Fable 5.1 bda7ca910d M6: a FigJam elbow connector saved with its path keeps it on import (#1910)
The `.jam` importer routed every elbowed connector itself, so one drawn around a layer by hand in
FigJam came in routed through it. FigJam saves the path as `connectorControlPoints`: one per run
between the first and the last, each a point on its run (in the connector's parent's coordinates)
with the run's axis; the first run leaves the start's side and the last arrives at the end's. They
become the `ConnectorPath::Runs` a board connector reshaped by hand has, walked by
`figcraft_doc::route_along`, so the path is kept exactly. Points that don't make such a path (off
both axes, two runs the same way in a row, a first or last run sideways to its side, more runs than
a hand could make) leave the connector to the router as before, and only connectors routed here
count in the "own routing" note. `figcraft_doc::attach_points` and `MAX_RUNS` are exported for it.
Docs: file-format-parity.md, boards-parity.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 01:52:02 -05:00
Dylan M. Taylor deee3d8acf fix(format): bound the characters Sketch text runs write in all 2026-11-04 04:50:11 +00:00
Dylan M. Taylor 6f780211dd fix(collab): build no repair for a batch refused for lack of permission (#1980) 2026-11-03 23:49:39 -05:00
Dylan M. Taylor 6d1b5da863 fix: create test and benchmark temp directories fresh, never adopt an existing one (#1977) 2026-11-03 23:48:46 -05:00