Commit Graph
1608 Commits
Author SHA1 Message Date
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 b28edd3c7a M9: record the offline merge in the collaboration status
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:49:13 -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
Andreas Haraminac cd77b017bd M6: correct architecture guide for existing .fig support 2026-11-04 19:06:37 +00: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 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 5855c8705c docs: boards-parity's status line and revision history move with its .jam row (#1910)
Review: the row changed without the status line and a history row, which land together.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 02:39:24 -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
Trevor BrownandClaude Fable 5.1 7be7c054eb M12: a connector joined to a layer inside a moving section is no snap target; the docs say which connectors share a run (#1916)
Review: a section moving carries its stickies and their connectors, so those connectors and
their labels are no snap targets for the section either (an end counts as carried when its node
or any ancestor is moving). The parity rows, the END_GAP doc and the test's doc no longer say
connectors from one side run together: elbows share their first run along the normal, curves
leave tangent to it, straight ones fan out from the same point.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 21:46:34 -05:00
Trevor BrownandClaude Fable 5.1 2dc7bed2a0 M12: connectors attached to a side start 4 pt outside the border (#1916)
A connector end attached to a side was routed from the middle of that side, so the stroke's
centre line started on the border and half its width plus the round cap lay on the fill, and
several connectors leaving one side piled up into a knot sticking into the card. FigJam starts
the line a few pixels outside the border and connectors leaving one side run together along the
side's normal before they fan out (the issue's screenshots). `board_route::END_GAP` (4 pt): an end
attached to a side (`RouteEnd::Box`) starts that far outside it along the side's normal; an end
placed by hand on the object (`RouteEnd::On`) keeps its exact point; a free end is unchanged.
The pinned route coordinates in the engine, format and UI tests move with it; the overlay's
attach points stay on the border. Docs: the Connectors rows, status line and history of
boards-parity.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 21:08:19 -05:00
Trevor BrownandClaude Fable 5.1 5eb28b310a M12: the bar above the selection stays away while a quick-create handle is dragged (#1919)
Review: letting a handle go over the selection bar made the next shape behind it. The bar
hides while a "+" is dragged, as it does for any drag (board_connect::dragging), so there
is nothing to drop behind; the drop makes the next one where the bar was, and the bar is
back once typing in it ends. Test the_bar_stays_away_while_a_handle_is_dragged; the docs
rows say the toolbar and the bar.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 14:47:46 -05:00
Trevor BrownandClaude Fable 5.1 246a89733c M12: quick-create drag: stickies and shapes in sections, parent coordinates, the handle's side, no drop off the canvas (#1919)
Review: the drop target is the topmost sticky or shape under the pointer, sections and frames
looked into (board::sticky_or_shape_at, instead of the stamp's any-layer hit test on the page's
children); the drop point is placed in the parent's coordinates; the connector leaves by the
handle's side (as the preview draws it) and arrives by the facing side; a handle let go off the
canvas makes nothing; the parity rows on quick-create handles agree.

Tests: dropping_a_handle_on_a_shape_inside_a_frame_joins_it, letting_a_handle_go_over_a_panel_makes_nothing,
the_next_one_goes_where_it_is_dropped (frame case).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 12:44:59 -05:00
Trevor BrownandClaude Fable 5.1 d48aa59ec7 M12: drag a "+" handle to drop the next shape anywhere or join another object (#1919)
The quick-create handles only took a click. As the help says ("drag the preview to any location
on your board"), a handle can now be dragged: the preview follows the pointer and, let go on empty
board, the next sticky or shape is made there, centred on the pointer, joined by a connector whose
ends take the sides facing each other (board.addConnected gains at: [x, y]); let go on another
sticky or shape, a connector joins the two instead (board.connector), that object outlined while
over it. figcraft_engine::board::object_at exposes the hit test a stamp uses. The click is as
before.

Tests: board_connect_tests (two), board_connected_tests::the_next_one_goes_where_it_is_dropped.
Docs: boards-parity row, status line and revision history.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 11:52:41 -05:00
Trevor BrownandClaude Fable 5.1 4ad80fa7be M1: Layers panel width: measure every candidate as painted, cap painting, no drops past the list (#1904)
Review: the widest name is found by laying rows out in descending order of a bound on their width
until the bound falls under the widest found, with no cut at 48 rows; each name is laid out as it
is painted (one line, cut at NAME_ROOM); the painted room is capped at NAME_ROOM too; and a drag
over a wide row counts only inside the list's clip rect, so a layer released over the canvas drops
nowhere.

Tests: a_wide_name_among_many_long_narrow_ones_is_still_measured,
a_name_with_line_breaks_is_measured_as_painted, a_drag_released_over_the_canvas_drops_nowhere.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 09:57:59 -05:00
SHXJ 9770f1696e M10: Traditional Chinese (Taiwan) interface draft
Add a 2,647-row zh-Hant catalog (1,975 UI strings, 641 command labels, 31 menu wordings), locale mapping for zh-TW/zh-HK/zh-MO/zh-Hant, and a Noto Sans CJK TC-first fallback. The catalog is marked draft pending native review; the web picker stays hidden because craft-fonts currently ships only the Simplified CJK face.
2026-11-03 22:56:03 +08:00
Trevor BrownandClaude Fable 5.1 fe18f6d96d M6: .jam import: a connector end with an AUTO or CENTER magnet faces the other end (#1909)
An end whose magnet is AUTO has no fixed point: FigJam picks the side when it draws, and the file
keeps whatever relativePosition the end last had, so the importer sent every automatic end out of
the stale side (the left one in the reporter's file). AUTO and CENTER ends now attach to the side
facing the other end, as a board connector drawn here does, for straight, elbowed and curved
connectors alike; named magnets and ends without one are unchanged.

Test: connectors_with_an_automatic_side_leave_the_side_facing_the_target (the fixture schema gains
the ConnectorMagnet enum). Docs: file-format-parity .jam row, status line and revision history.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 09:48:51 -05:00
Trevor BrownandClaude Fable 5.1 be04efc4fb M10: embeddable script faces as document fallbacks; the Arabic UI with Arab embedded (#1902)
Review: Noto Sans Hebrew, Devanagari and Georgian join Arabic and Thai in FALLBACK_FAMILIES, so a
web build that embeds one draws that script in an Inter document; with Arab embedded the picker
offers the Arabic UI catalog, as cjk::can_show intends (the docs said otherwise); the docs
example lists one tag per script.

Test: every_embeddable_script_face_is_a_document_fallback.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 09:41:15 -05:00
Trevor BrownandClaude Fable 5.1 14431850ca M1: Layers panel as wide as its widest name, scrolling sideways (#1904)
The Layers panel cut a name that didn't fit with an ellipsis and had no horizontal scrollbar, so
an imported component set's variants all read "Size=small, Type=prim..." and a deeply nested name
lost its room to the indent. The row cache now also keeps the width the widest row needs (a few
names measured per rebuild, picked by a bound on their width), the list takes that width and
scrolls both ways, revealing a layer scrolls up or down only, and a name past 1200 pt still
elides (#1650).

Tests: layers_wide_names_tests. Docs: ui-parity Layers panel row, status line and revision
history.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 09:34:08 -05:00
Trevor BrownandClaude Fable 5.1 89d411d50f M1: Layers panel component rows in the theme's component tint, at AA contrast (#1903)
The Layers panel drew component, variant and instance names and icons in the canvas outline
purple whatever the theme (3.1:1 on the dark panel, 2.7:1 on the selected row), and the rows
inside a selected component in the plain selection's green. They take the theme's component
token (the dark theme's lilac; the light theme's a shade darker than the canvas purple, so it
reads at AA on a selected and a hovered row too), and a new component_row_child token paints the
rows inside a selected component in a tone of its row. The canvas keeps its purple.

Test: layers_component_rows_tests (both themes). Docs: ui-parity themes row, status line and
revision history.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 09:29:49 -05:00
Trevor BrownandClaude Fable 5.1 1354c6bb6f M10: FIGCRAFT_EMBED_SCRIPTS embeds other scripts' craft-fonts faces in a web build (#1902)
The browser has no system fonts and the text crate's build script keeps the web catalog to Inter,
Noto Sans Georgian, the wordmark and (with FIGCRAFT_EMBED_CJK) Noto Sans CJK, so craft-fonts'
Noto Sans Arabic never reached a web build and Persian or Arabic text drew as missing-glyph
boxes. FIGCRAFT_EMBED_SCRIPTS names script tags as the manifest lists them (Arab, Hebr, ...);
a web build keeps the faces of those scripts as well. The default build is unchanged.

Test: web_embeds_other_scripts_faces_only_when_asked (build script). Docs: localization-parity
(web fonts paragraph, Arabic row, status line, revision history), translating.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 09:23:44 -05:00
Trevor BrownandClaude Fable 5.1 bb7d568d49 M2: baseline alignment puts a layer without text on the line with its bottom (#1825)
In the baseline branch of the flow layout only an item with a text baseline got one; any other
layer stayed at the top of the line. A layer without a text baseline now uses its bottom edge, as
the help center's figure shows ("the bottoms of the icon and the word home are aligned on the red
line"): an icon next to a text sits on the text's baseline, a layer reaching below it moves the
text down, and a line with no text lines the layers' bottoms up (CSS does the same).

Tests: alignment_rules (three cases), baseline_alignment_horizontal, engine
baseline_alignment_puts_an_icons_bottom_on_the_text_baseline. Docs: auto-layout-parity row,
status line and revision history.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 09:13:19 -05:00
Carbocode 901bc6d4fc M8: compute layer selection roots once per visible row 2026-11-03 10:39:24 +01:00
Carbocode ee6ca15ccf M8: join and round layer hierarchy highlights 2026-11-03 10:38:54 +01:00
Hamza 58af1a6237 M4, #1747: a text layer's name follows the characters typed into it 2026-11-03 12:41:29 +05:00
Trevor BrownandClaude Opus 5.5 564dbcfe52 Merge upstream/main into fix/scrub-speed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:39:16 -05:00
Trevor BrownandClaude Opus 5.5 bc140f51cd Merge upstream/main into fix/text-mixed-fill-section
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:38:49 -05:00
Trevor BrownandClaude Opus 5.5 1496b8028c Merge upstream/main into fix/place-crosshair
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:38:48 -05:00
Trevor BrownandClaude Opus 5.5 1c26c03e2f Merge upstream/main into fix/field-alt-scrub
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:38:48 -05:00
Trevor BrownandClaude Opus 5.5 a3cadb66cf Docs: one vector edit row after the merge
The merge with main kept both versions of the row; they are combined, the Bend click sentence and the region fill sentences together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:12:47 -05:00
Trevor BrownandClaude Opus 5.5 a22d8b1f9e Merge upstream/main into feat/region-fills
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:12:17 -05:00
Trevor BrownandClaude Opus 5.5 b936b95fd9 Stack on #1857: merge fix/text-mixed-fill-section
Both redirect the Fill section's reads and writes: the selected regions of a vector come first (#1878), else a text's characters' shared fills (#1857), in the Fill section, the colour picker, the canvas handles, fill.copy, fill.set and fill.move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:12:08 -05:00
Trevor BrownandClaude Opus 5.5 4671402df5 Stack on #1854 and #1869: merge fix/control-arrows-pan
Pick select changes the same ui_key and key_event paths as the crosshair and the pan; stacked on them as the maintainers asked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:55:16 -05:00
Trevor BrownandClaude Opus 5.5 66a98b108b Merge upstream/main into fix/padding-all-sides
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:47:59 -05:00
Trevor BrownandClaude Opus 5.5 8c028c1ace Stack on #1855: merge fix/field-alt-scrub
Both change number_field_impl's click and drag handling; stacked so the combination is reviewed and tested together, as the maintainers asked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:47:39 -05:00
Trevor BrownandClaude Opus 5.5 e884e3373a Merge upstream/main into fix/control-arrows-pan
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:46:51 -05:00
Trevor BrownandClaude Opus 5.5 84a0693076 Fill section: the canvas crop and gradient handles follow the characters' shared fill
The handles read the paint list the Fill section and the colour picker show (character_fills), so with the characters' image already in Crop mode the crop handles show and edit it, not the layer's own fill at that index; writes still go through fill.set, which moves the characters' list to the layer first. No undo step on entering crop mode. Test canvas::handles::character_fill_tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:45:53 -05:00
0e86f3597a M4: wrap style on a text layer: Auto, Balance and Pretty decide where its lines break (#1827) (#1871)
* M4: wrap style on a text layer: Auto, Balance and Pretty decide where its lines break (#1827)

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

* Docs: no table pipe inside the wrap-style row's code span

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

* Wrap styles: no new emergency breaks, hanging punctuation in Pretty's width, wrap style as an override

Balance and Pretty reject a re-break with more mid-word breaks than the greedy lines; Pretty narrows a line by its text less its hanging marks; wrapStyle joins the textStyle override group; docs wording. Tests.

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-03 00:13:48 -05:00
a579ba2d3c M8: a click with the Bend tool curves a sharp point, its handles along the line between its neighbours (#1875) (#1877)
* M8: a click with the Bend tool curves a sharp point, its handles along the line between its neighbours (#1875)

A release without movement on a sharp point between two segments runs ops::auto_smooth instead of cancelling; a curved point still sharpens, a drag still sets the handles by hand, an endpoint is left alone. Tests; docs.

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

* Vector edit hit test: the nearer of a handle's end and a point wins

A short path's handles end within the point's hit tolerance; a click on the point is on the point, one on the handle's end still takes the handle. Test short_handles_leave_their_point_clickable.

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

* Vector edit hit test: the nearer of a handle's end and a point is judged on the page

Candidates stay within the tolerance as before; ranking by page distance keeps a non-uniform scale from favouring the wrong one. Test a_handle_and_a_point_are_compared_on_the_page.

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

* Vector edit hit test: a handle wins a tie with a point, as before

A handle snapped onto another point stays selectable there. Test a_handle_on_another_point_is_still_the_handles (fails with the strict comparison).

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-03 00:13:38 -05:00
Trevor BrownandClaude Opus 5.5 aa2fed07e2 Region fills: fill.pattern stays on the layer; the canvas handles and the picker's image paths edit the region's paint
fill.pattern sets the layer's fill as its spec says. The gradient and crop handles read the paint from the stack fill.set writes (the selected regions' while there are any); for a region's paint, Upload from computer and image presets write the paint like any edit, and a drop on the picker goes to the canvas (image.replace and image.adjust edit layer paints). Tests in regions_tests, canvas/handles/region_tests and colorpicker image tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 09:49:10 -05:00
Trevor BrownandClaude Opus 5.5 d2e7c49e57 Region fills: each region once, no override for a no-op, Copy fill reads the region, layer-only controls off region rows
Named regions are deduplicated; an inheriting region stays inheriting when an edit leaves its stack as it was, and its own stack is only replaced once the edit succeeds; fill.copy reads the selected regions' paint; the Fill section leaves the fill style, a variable bound to the layer's fill and the image crop off region rows. Tests in regions_tests and region_fill_rows_tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 09:14:55 -05:00
Trevor BrownandClaude Fable 5.1 da4a798323 M1: pick select: Tab while the keyboard selection box shows walks the layer tree with a pink outline, Space selects (#1836)
Tab or the chord again switches the box to a pick cursor; Tab/Shift+Tab move between sibling layers, Enter/Shift+Enter into and out of a layer, Space selects, Escape or a canvas press ends it; ui.key takes the same keys. Gesture and parity rows, catalog, test, docs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-02 08:13:54 -05:00
Trevor BrownandClaude Fable 5.1 2e392e99b1 M8: a fill per region: the Fill section and the fill commands edit the regions selected in vector edit mode (#1875)
fill.set/add/remove/move/paste on the edited layer go to the selected regions' own fill stacks (or those named by regions), the layer's fills untouched; the Fill section and the colour picker list them. Test regions_tests::the_fill_commands_edit_the_selected_regions; docs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-02 07:55:27 -05:00