Commit Graph
35 Commits
Author SHA1 Message Date
e13a984042 Integration: refuse non-array stop lists, keep CMYK/Lab stops, stroke width takes the gradient (#7203)
- parse_fill took a present non-array "stops" / "opacityStops" / "midpoints"
  as absent: {"gradient":{"stops":"red"}} silently became black to white. It is
  an error now (CodeRabbit's finding); the hostile-values test also checks that a
  rejected shape.create leaves the document and history alone.
- The swatches sent every gradient stop as sRGB RGBA, so in a CMYK or Lab
  document ticking Reverse or changing the angle turned every stop into an RGB
  colour (written back to PSD as RGBC). Stops now go out as stored Colors, which
  shape.create / shape.edit accept, finite with alpha in 0..1.
- Options bar, strokeless shape selected, stroke gradient chosen: the width
  field and the stroke type added a solid background-colour stroke. They take
  the stroke gradient now, as the Fill checkbox and the next shape do.

Co-authored-by: Denis <179751626+deniskhud@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:37:56 +01:00
096ad5b194 Integration: merge main, resolve conflicts (#7203)
Main moved the shape options bar and paint swatch out of vector_ui.rs into
shape_options.rs and shape_paint_ui.rs (#5452). Carry the PR's gradient paint
over: PaintChange::Gradient, paint_param and the swatch's Gradient tab live in
shape_paint_ui.rs, the tool gradients and set_tool_paint in shape_options.rs,
and shape_gradient_ui.rs is its own module. Both sides' new tests are kept.

Co-authored-by: Denis <179751626+deniskhud@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:26:26 +01:00
aiongg 26dbd7953b Merge PR #5834: Shapes: rounded corners on live triangles, polygons and stars (TOOL-214-12)
docs/scorecard.md: ours (regenerated at combine).
docs/ui-parity.md: kept main's header and added the PR's change entry once.
2026-10-22 15:37:29 +00:00
aiongg 7654482fd1 Merge PR #3330: Moving a shape keeps its rendering: Artboards to Files matches the reference app
crates/engine/src/layer_multi_cmds.rs: took the PR's call to shift_shape; main's added check that the moved path (plus stroke) stays inside the canvas moved into shift_shape (vector_cmds.rs) so both behaviours hold.
2026-10-22 15:37:29 +00:00
bazmarth 4573037531 Merge remote-tracking branch 'origin/main' into feat/polygon-corner-radius
# Conflicts:
#	docs/scorecard.md
#	docs/ui-parity.md
#	scorecard/tools.toml
2026-10-21 16:49:42 +00:00
David Mann 0b39361076 Merge main into feat/tool-214-12-shape-feather 2026-10-20 17:12:29 +02:00
deniskhudandClaude Opus 5.5 ce1d7576cf Shape gradient paint: cap gradient stop lists; keep the selected stop's colour row
shape.create / shape.edit refuse more than 1024 colour stops, opacity stops or
midpoints, as gradient.fill.stop does (an error, not a cut).

The Gradient tab's selected stop was an index that could outlive the stops (a preset
with fewer stops, another shape): the Color and Location rows vanished. An invalid
selection falls back to the first colour stop, a preset or Recent pick starts there,
and double-clicking a colour stop opens its colour, as in Photoshop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:35:06 +02:00
deniskhudandClaude Opus 5.5 af08ca8df8 Shape gradient paint: cargo fmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:32:05 +02:00
deniskhudandClaude Opus 5.5 89305c1d17 Shape tools: gradient Fill and Stroke from the swatches, with an editable stops strip
The shape Fill / Stroke swatches (options bar and Properties) offer No Color, Solid and
Gradient, as Photoshop's popover does, for the next shape and the selected one. The
Gradient tab (shape_gradient_ui.rs) has a Recent row, the preset library, a stops strip
(click to add a colour or opacity stop, drag to move, drag off to delete, midpoints) with
the selected stop's colour, opacity, location and Delete, and style, angle, scale,
Reverse and Align with layer. Stop edits reuse the Gradient Editor's apply_stop.

shape.create / shape.edit take and report every gradient field (opacityStops,
midpoints, offset, dither, align), so editing an imported gradient keeps the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 23:13:04 +02:00
Nigel Ihza Firdausy 35b4b30f2d style(path): keep regression cases consistently spaced 2026-10-15 15:00:44 +07:00
Nigel Ihza Firdausy caceb01dda test(path): cover invalid Make Selection params and open-path modes 2026-10-15 14:59:18 +07:00
David MannandClaude Opus 5.5 6bb45c8d5b Shapes: Density and Feather on shape layers (#5846)
A shape layer's path is its vector mask in Photoshop (Properties > Masks),
with its own vector-mask density and feather. PhotoCraft's ShapeLayer had
neither, so soft shapes could not be made, and stroked soft shapes from PSDs
lost both on import.

- doc: ShapeLayer gains `density` (0..1, default 1) and `feather` (px,
  default 0).
- vector: the shape renderer applies them to the fill as a soft vector mask
  (fill shows beyond the path at 1 - density; the feather is the vector
  mask's Gaussian), so the shape's own pixels, the GPU (which samples them)
  and the stroke/fill split all follow. The stroke stays crisp. The feather
  blur moves from compose::masks to vector::feather and both share it and the
  feathered area, so a shape and an equal vector mask render the same pixels.
- compose: soft shapes' bounds reach their fill, their layer effects follow
  the soft alpha, and the split cache key includes density and feather.
- engine: `layer.vectorMask.edit` on a shape layer sets density/feather in one
  history step (path/enabled/linked/invert are refused there); density is now
  validated (finite 0..100) instead of clamped. `layer.vectorMask.info`
  reports a shape's values.
- ui: Properties shows the Vector Mask Density and Feather sliders for a
  selected shape layer in both themes (live drag, one undo per gesture).
- .pcraft: manifest and convert carry both with serde defaults (older files
  load at 100 % and 0 px).
- PSD: export writes them as the vector density/feather mask parameters;
  import reads them into the shape (re-rendered, as the stored pixels lack the
  fill beyond the path). Soft unstroked solid/gradient shapes now import as
  shapes instead of fill layers with a vector mask; pattern-filled ones stay
  fill layers.

Photoshop's 10 px feather on a 100 px black square, row 100, x = 30..72:
ours is within 1 level of every measured value (8, 16 and 32 bits);
density 50 % shows 127/128 beyond the path and 0 inside.

Scorecard: TOOL-214-12 done.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 03:07:46 +02:00
bazmarthandClaude Opus 5.5 8744820a27 Shapes: rounded corners on live triangles, polygons and stars (TOOL-214-12)
As Photoshop's rounded triangles and polygons, a live polygon (the Triangle
tool draws a 3-sided one, stars included) now has one corner radius:
- doc: LiveShape::Polygon gains corner_radius (serde default, so older
  .pcraft files load sharp);
- vector: shapes::fillet and shapes::round_corners round every vertex with a
  circular arc tangent to both edges, each capped so neighbouring arcs meet
  at most halfway along an edge;
- engine: shape.create / shape.edit take "cornerRadius"; transforms scale it
  like a rectangle's radii;
- ui: the Properties panel shows a Corner radius field for polygons, and the
  on-canvas corner widgets now cover every selected rectangle, triangle,
  polygon and star (Path Selection, the Rectangle/Triangle/Polygon tools,
  Move with Show Transform Controls). Dragging any widget gives every
  selected shape the same radius; Alt still changes one corner of a
  rectangle. The widgets take the unsnapped press: snapping used to pull a
  shape tool's press off the widget, so the tool drew a new shape instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:07:03 +00:00
Valentin KleinandClaude Opus 5.5 706638b284 Tests: bad-layer-id checks use an id that can't exist (#3343) (#3351)
* Tests: bad-layer-id checks use an id that can't exist (#3343)

Layer ids come from one process-wide counter (shared with document and
smart-object ids, and bumped by ensure_ids_above on file loads). A full
`cargo test` run passes ids like 9999, so a layer a test just created can
get the "bad" id, the command succeeds and the assertion fails at random
(seen on CI in type_extra_cmds::tests::vertical_orientation_renders_columns_inside_the_canvas).

Use u64::MAX, which the counter never reaches, for every "no such layer"
sentinel in the engine and ui-egui tests. Test-only change.

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

* Tests: use u64::MAX for bad-layer-id sentinels added on main since (#3343)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:59:43 -04:00
7ad75b5db2 Layer masks: Reveal Selection drops the selection, as Photoshop does (#5251)
A mask made from the selection takes the selection with it in Photoshop: after
Layer > Layer Mask > Reveal Selection (the Layers panel's mask button with a
selection) the marching ants are gone. PhotoCraft kept the selection, so the
usual next step, Invert (Cmd/Ctrl+I) on the new mask, inverted only the part
inside the old selection and left the whole mask black, instead of flipping
the mask. Hide Selection (the Alt-click variant) already dropped it.

Reveal Selection now clears the selection in the same history step as the
mask: one undo brings the selection back and removes the mask.

Tests: adjust_cmds::tests::reveal_selection_drops_the_selection_so_invert_flips_the_whole_mask
(8/16/32-bit). Tests that deselected right after Reveal Selection no longer
need to (Deselect is disabled without a selection); layer_mask_depth and the
automation agent tasks now expect the selection to be gone.

Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:59:27 -04:00
Fabian Ghenof 898f4a789a Merge remote-tracking branch 'upstream/main' into fix/move-shape-keeps-rendering
# Conflicts:
#	crates/engine/src/vector_cmds/tests.rs
2026-10-13 09:34:07 +03:00
6b6a9234d1 feat(vector): add shape from selection with simplify tolerance (#214) (#3954)
* feat(vector): add shape from selection with simplify tolerance (#214)

- Add tolerance and simplifyTolerance parameter (clamped 0.5..10.0 px) to select.convertToShape.
- Wire select.convertToShape in filter_dialog::has_dialog to prompt for tolerance.
- Add tests for knot count tolerance scaling and selection requirement.

* rustfmt

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:28:50 -04:00
David MannandClaude Opus 5.5 72c8f58e11 Shapes: a rectangle with a negative size extends from its origin like the other shapes (#4203) (#4204)
rounded_rect took the absolute width/height without moving the origin, so
shape.create {"kind":"rect","rect":[50,10,-20,30]} covered x 50..70 while an
ellipse or polygon with the same rect covered 30..50. Normalise (x, w) and
(y, h) for negative sizes instead. The Shape tool drag already normalises its
rect, so interactive drawing is unchanged.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 21:17:38 -04:00
David MannandClaude Opus 5.5 e4f15eecdf Vector masks: validate feather like pixel masks (#4201) (#4202)
layer.vectorMask.edit stored `feather` as f.max(0.0) as f32, so 1e39 became
inf (hard edge, null in info, .pcraft save refused) and any huge value was
accepted. It now takes the same finite 0..=1000 px range and error as
layer.layerMask.edit, checked before the edit so a bad value leaves the mask
and history untouched. The params doc says so.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 21:17:14 -04:00
Fabian Ghenof 7d9d1384cb Merge remote-tracking branch 'upstream/main' into fix/move-shape-keeps-rendering
# Conflicts:
#	crates/engine/src/vector_cmds/tests.rs
2026-10-12 07:24:18 +03:00
piero-83 23417a60b6 fix(engine): create new layer masks at the document depth (#3359)
* fix(engine): use document depth for new pixel masks

* test(engine): check mask depth and selection coverage

* test(engine): preserve mask edits across native and PSD round trips
2026-10-11 22:30:26 -04:00
Valentin KleinandClaude Opus 5.5 55c1847f89 Paths: Stroke Path with any brush-based tool and Simulate Pressure (#2978) (#3039)
* Paths: Stroke Path with any brush-based tool and Simulate Pressure (#2978)

path.stroke now takes every brush-based tool of Photoshop's Stroke Path
dialog: besides the Brush, Pencil and Eraser it renders itself, the
Background Eraser, Clone and Pattern Stamp, Healing and Spot Healing
Brush, History Brush, Blur, Sharpen, Smudge, Dodge, Burn, Sponge, Color
Replacement and Mixer Brush run their paint.* command along each
subpath, in one history step, with only path.stroke journalled.

simulatePressure resamples each subpath and ramps the pressure from 0 at
its start to full at the middle and back to 0 at its end, so a brush
whose size follows pen pressure tapers at both ends.

The Stroke Path dialog (Pen/Path Selection right-click menu, the Paths
panel row menu, Alt-click on the panel's Stroke button) asks for the
tool and Simulate Pressure, as in Photoshop. Scorecard TOOL-214-11 done.

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

* Automation: classify path.stroke's params as a document vector path (#2978)

The new Stroke Path params doc says the tools run "along the path", which
registry_filesystem_path_params_are_classified reads as a possible host
file path. path.stroke strokes a document path and forwards only to
paint.* commands that take no files, so it joins the vector-path list.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:18:39 -04:00
Valentin KleinandClaude Opus 5.5 62e0335116 Transform Path: Distort, Perspective and Warp apply to paths (#3027) (#3032)
* Transform: Distort, Perspective and Warp keep shape layers live (#3009)

Edit › Transform › Distort / Perspective and Warp on a shape layer let the
user edit the box or mesh and then refused at commit ("needs a rasterized
shape"). Photoshop maps the shape's path and keeps it a shape.

- photocraft-vector: new `map` module. `map_points` sends every anchor and
  control point through a map (Distort / Perspective: exact for straight
  segments, exact end tangents for curves). `map_path` follows a curved map
  (Warp), halving each segment until the mapped control points follow the
  mapped curve within a tolerance (at most 64 pieces a segment, bounded in
  total); straight segments whose image is straight stay straight.
- engine: `transform_layer` and `warp_layer` map a shape's path instead of
  refusing; a live rectangle/ellipse/polygon/line becomes a plain path. A
  linked vector mask now follows Distort / Perspective / Warp too. A map that
  sends a point to infinity is an error, not a corrupt path.

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

* Transform Path: Distort, Perspective and Warp apply to paths (#3027)

path.transform accepted only an affine matrix, so the Free Transform
Path commit refused any box pulled out of a parallelogram ("Distort and
Perspective can't be applied to a path"), and Warp was not offered on a
path at all. path.transform now also takes a `quad` (with an optional
`rect` frame, default the path's control bounds) and a warp (`style`,
`mesh` or `warp`, as edit.transform.warp), validated (finite numbers,
magnitude at most 1e6, non-degenerate quad) and mapped with the
vector::map helpers from #3025. A parallelogram quad stays affine, so a
live shape stays live. The UI commit sends the quad or warp instead of
refusing, and Transform Path offers Warp; each is one undo step.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:43:13 -04:00
David MannandClaude Opus 5.5 66c0b59a23 Layers: Combine Shapes merges the selected shape layers (#2400) (#2906)
Layer › Combine Shapes › Unite / Subtract Front Shape / Intersect Shape
Areas / Exclude Overlapping Shapes only changed the operations between the
components of the active layer. With two single-shape layers selected (the
report's case) the active layer had one component, so nothing happened.

With two or more shape layers selected and no `layer`/`subpath` param, the
commands now merge them into the top-most layer in one history step: the
bottom layer's components keep their operations and every component of the
layers above takes the chosen one, in stacking order, then the other layers
are removed. Merge Shape Components merges them the same way (each layer
combined) and bakes the result. Selected non-shape layers are left alone,
locked layers refuse the merge, and one selected layer behaves as before.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 12:05:57 -04:00
8a9f4722d2 fix(shapes): preserve transformed outline on unchanged live geometry edits (#3197)
* fix(shapes): preserve transformed outline on unchanged live geometry edit

A nonuniform scale stores an anisotropic outline but live rounded rectangles only store scalar corner radii. Avoid regenerating the path when all live values are unchanged, preserving transformed corners and PSD vector source. Cover no-op edits, real radius edits and undo at all depths.

* cargo fmt

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 05:23:55 -04:00
Fabian GhenofandClaude Opus 5.5 c17cc118f5 Moving a shape keeps its rendering: Artboards to Files matches Photoshop
translate_vectors re-rendered a shape layer on every move. For a shape from a
PSD that replaced Photoshop's anti-aliasing with PhotoCraft's, so Artboards to
Files (which moves each board to the origin), the Move tool, nudges, layer
comps and moving artboards changed how imported shapes look.

A whole-pixel move now shifts the rendered pixels, through one shift_shape
helper shared with the Move tool's live preview (layer_multi_cmds::
shift_shown), which already did this. The render is cut at the canvas, so a
shape reaching past it still renders again, as does a fractional move; the
path moves either way, and fit_canvas still re-renders shapes when an
artboard move grows the canvas (#2389).

Measured on a Steam store asset template with #3218 (the same fix for type):
the "How to use" artboard has no pixel off by more than 2 levels from
Photoshop's own composite; before, both its shapes differed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:11:48 +03:00
ab15e4ea96 Curved shapes no longer report bounds 1 px too wide (#2537) (#2641)
* Drop f32 round-off coverage in the vector rasterizer (#2537)

Each row's cell accumulator is prefix-summed in f32. A left boundary adds
coverage and the right boundary subtracts it, but for curved or off-grid
edges the two don't cancel exactly, leaving ~1e-7 coverage in the pixel
just past the shape. Shape layers wrote the fill colour there (alpha
rounding to 0 at 8/16-bit, ~1e-7 at 32-bit), so content bounds and
shape.info reported curved shapes one pixel too wide.

Snap coverage below 1e-6 to 0 when writing a row. That is under half a
16-bit step, so 8- and 16-bit coverage values are unchanged; only the
residue pixels go away. Selections, vector masks and strokes share the
rasterizer and get the same fix.

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

* Rasterizer: NaN coverage becomes 0, not full (#2537)

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

---------

Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:41:51 -04:00
Kaustubh TripathiandClaude Opus 5.5 099aa57db5 fix(vector): Fill Path stays inside the active selection (#2387) (#2410)
Fill Path painted the whole work path even when a selection excluded
part of it. The path's coverage (after anti-aliasing and feather) is now
multiplied by the selection mask, so rectangular, elliptical and
feathered selections all limit the fill. With no selection the whole
path fills, as before.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:12:04 -04:00
ae96037672 Expose native shape stroke controls with staged previews (#2218)
* Expose native shape stroke controls with staged previews

* Document shape stroke defaults in MCP control schema

* Regenerate docs/scorecard.md

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

---------

Co-authored-by: optimalbytee <optimalbytee@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 06:42:52 -04:00
kayrockscreenprintingandClaude Opus 5.5 d4884d33fa Rasterize Vector Mask keeps a feathered edge (#1807)
Layer > Rasterize > Vector Mask multiplied the hard vector mask into the pixel mask, dropping
the vector mask's feather (and the pixel mask's feather or density). When any of those shape
the edge, the pixel mask now becomes exactly the combined mask the compositor showed, with
feather and density baked in. Tested at 8, 16 and 32 bits against the composite. Fixes #992.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:28:36 -04:00
Alex GorevskiandCopilot 51a0816f99 Bound paint stroke work (#678, #679) (#751)
* Bound paint stroke work

Cap airbrush catch-up for long timestamp gaps and enforce safe brush dimensions before rendering, with a lower-level renderer guard and regression coverage.

Fixes #678

Fixes #679

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e77cd34a-ce3e-4981-996f-4d37d49df90d

* fix: align paint brush limits and airbrush catch-up

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e85c024b-ace5-4415-9208-b8d4e4684505

---------

Copilot-Session: e77cd34a-ce3e-4981-996f-4d37d49df90d
Copilot-Session: e85c024b-ace5-4415-9208-b8d4e4684505
2026-10-07 17:12:49 -04:00
Alex GorevskiandCopilot 8302f57190 Fix vector pixel bounds overflow (#747)
* Fix vector pixel bounds overflow

Saturate exclusive pixel bounds at i32::MAX and cover extreme and ordinary paths with regression tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e77cd34a-ce3e-4981-996f-4d37d49df90d

* Cover extreme vector path fill commands

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e85c024b-ace5-4415-9208-b8d4e4684505

---------

Copilot-Session: e77cd34a-ce3e-4981-996f-4d37d49df90d
Copilot-Session: e85c024b-ace5-4415-9208-b8d4e4684505
2026-10-07 17:10:21 -04:00
b5eed81a25 Start context-menu parity across canvas, paths, and document tabs (#556)
* Add blend modes to Gradient tool

* Establish context menu parity contract and first workflows

* Align Select menu actions and style selection context rows

* Add Pen canvas Make Selection context action

* Complete Pen path context menu and symmetry actions

* Select menu: no extra top-level Feather (keep Select > Modify > Feather only, for 1:1 menu parity)

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

* Regenerate docs/scorecard.md and docs/parity.md

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

* Regenerate docs/scorecard.md and docs/parity.md

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 07:45:26 -04:00
Brandon ThomasandClaude Opus 5.5 f7eb6ea9e7 Format: adopt the craft-family rustfmt.toml and format the workspace
CI's `cargo fmt --all -- --check` has failed on every run because the
repo had no rustfmt config and was never formatted. Use the same settings
as EffectCraft/FilmCraft (max_width 160, use_small_heuristics Max), which
also keeps the churn small. Formatting only; no code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:31:17 -04:00
Brandon Thomas ae92ac20bf continued wip 2026-09-30 09:37:18 -04:00