Commit Graph
28 Commits
Author SHA1 Message Date
Tanish Manku 2b3d05636f Follow-ups to #5801/#5859: precheck docs name the group nesting limit; precheck test covers valid ids and null; Free Transform refusals translated; stranded-backup error tested (#6365) 2026-10-15 15:32:08 -07:00
Tanish Manku 8376363d8c Shortcuts without the UI: shell commands and tools in the engine, reserved keys, set files (#5801)
* Shortcuts without the UI: shell commands and tools in the engine, reserved keys, set files

A retro against "everything is drivable headless; the UI is a layer" found the shortcut
work leaning on the UI. Fixed:

- The shell's command table (UI_COMMANDS: View › Zoom In, the panels…) moves into the
  engine as data (shell_commands::SHELL_COMMANDS; menus::UI_COMMANDS is now an alias), with
  the tools (shell_commands::TOOLS: id, Photoshop name, default key; a test keeps it equal
  to the toolbar). edit.keyboardShortcuts binds, lists, imports, exports and checks them for
  conflicts with no UI: a headless .kys import takes tool keys, and an export is the whole set.
  Tools may share a key (Shift cycling); a command given a tool's key takes it from them.
- Reserved system keys are refused by the engine (the dialog uses the same check) and left
  out of imported sets.
- importKys / exportKys / summarize take {"path": …} to read or write files, like
  actions.save; the automation gate refuses those paths for MCP.

* Keyboard shortcuts: bindings commit only after their exports are written (a bad target changes nothing); docs: the command knows the tools

* Keyboard shortcuts: both export targets are resolved and checked before either file is written; a failed later write removes the earlier one

* docs: reserved keys per platform

* Keyboard shortcuts export: files are staged next to their targets and moved into place only when all are written; a failure leaves the user's files untouched

* Keyboard shortcut exports all or nothing: unique create_new staging and backups beside each target, restored on any failure; the same target twice refused; docs: Ctrl+Shift+Esc

* Shortcut exports: a failed backup move removes its placeholder; undo restores backups without deleting first and names any it couldn't; read-only targets refused up front
2026-10-15 15:26:09 -07:00
Sir StarchandBrandon Thomas cd046f4a70 Arrow-key nudges: a run of nudges is one History state, with Tools preferences (#2259) (#2764)
* Arrow-key nudges: a run of nudges is one History state, with Tools preferences (#2259)

Tapping or holding an arrow key nudged the selected layers one pixel per
History state, so one Undo took back one pixel. Nudges of the same thing
that follow each other now share one `coalesce` key (the engine already
folds edits with the same key into one step), so a run is one state and
one Undo takes it back.

- nudge_bundle.rs decides which run a nudge belongs to: the previous nudge
  was of the same thing (layers, selection outline), came within the
  pause, and left the document as it is now (no other edit, Undo/Redo,
  layer selection or save in between).
- move_mods.rs runs layer.translate and select.transformSelection through
  it; Alt-nudges (a copy each) never join a run. Selected pixels float
  until dropped, which is one state already.
- Free Transform keeps its own session history; its box nudges fold into
  one session step (transform_tool.rs).
- Tools preferences: Bundle Arrow-Key Nudges (default on; off gives a
  state per press as before) and Nudge Bundle Pause (ms) (default 1000,
  100..10000, hidden while bundling is off). Labels translated in all
  catalogues.

The two #1428 tests that asserted a state per press for the selection
outline now cover both settings.

* Regenerate docs/scorecard.md after merging main

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 15:32:39 -04:00
9788e5ee5e Shortcuts: a command can answer a second key (F2/F3/F4, Shift+F7, Cmd++) (#3665) (#3746)
* Shortcuts: a command can answer a second key (F2/F3/F4, Shift+F7, Cmd++) (#3665)

Photoshop's default set gives some commands a second key besides the one
its menu shows: F2/F3/F4 Cut/Copy/Paste beside Cmd+X/C/V, Shift+F7 Inverse
beside Cmd+Shift+I, Cmd++ beside Cmd+= for Zoom In, Shift+Backspace for
Fill. A command could hold only one key here, so these couldn't exist and
a .kys import kept only a row's first key (a set listing Copy as Cmd+C and
F3 could leave Cmd+C unbound).

- prefs::ALTERNATE_SHORTCUTS holds Photoshop's second keys;
  Preferences.alternate_shortcuts (alternateShortcuts) replaces a
  command's defaults, [] removes them. Old settings files load unchanged.
- edit.keyboardShortcuts takes `alternates`; reset, moving a key (taken
  from another command's main key or alternates) and the conflict list
  count every key, and a call's changes now apply atomically.
- .kys import keeps a row's other keys as the command's alternates.
- Dispatch matches every key (a user's main key wins over a default
  alternate); the UI-only SECONDARY table is folded in, so Fill's
  Shift+Backspace survives an override of its main key. Menus still show
  only the main key.
- Keyboard Shortcuts dialog shows each command's alternates, removable
  one by one, with Add Shortcut to add one; Use Default / Reset All
  restore them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Translate Add Shortcut into Hungarian; regenerate the scorecard

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Scorecard: regenerate for the new alternate_shortcuts preference

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: regenerate scorecard after merging main

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Regenerate docs/scorecard.md after merging main

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Regenerate docs/scorecard.md after merging main

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 15:16:43 -04:00
12a474d550 Interface: hide the Home screen's recent files (#4382) (#4387)
* Interface: a preference to hide the Home screen's recent files (#4382)

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

* i18n: drop the hu/tr 'Enable animations' lines, #4362 adds them

Both PRs added the same two lines; whichever merged second would leave a duplicate key (bundled_catalogs_are_consistent rejects it).

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

* docs: regenerate scorecard after merging main

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

* Merge main; fix i18n (duplicate ja row, missing sr row) and regenerate scorecard

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

* Drop the duplicate hu row

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:27:52 -04:00
Valentin KleinandClaude Opus 5.5 a7f7f1fd1e Edit › Menus: Menus, Keyboard Shortcuts, Preferences, Save and Exit can't be hidden (#5096) (#5183)
Hiding Edit › Menus (or Keyboard Shortcuts) left no way back into the
dialog, and Save, Save As, Exit and Preferences could disappear too.
edit.menus now refuses to hide them, the dialog greys their Visible box,
and the menus keep them even when a preferences file lists them.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:07:28 -04:00
Ivan · VanTan e12b7f7352 fix(prefs): preserve selected themes during legacy migration (#5179) 2026-10-14 01:58:31 -04:00
Sir Starch 2ec1c8c0aa Themes: default light theme to Pro Light to preserve the Pro layout (#5016) (#5029)
When appearance mode was toggled between dark and light with default
settings, PhotoCraft switched from Pro Medium (Pro layout) to Studio Light
(Studio layout), altering the UI and displaying a Fit button in the status
bar.

Now that the Photoshop Pro light brightness levels exist (#2661), default
the saved light theme to Pro Light so the Pro interface layout and status
bar are preserved when toggling appearance. Also map the "light" alias to
Pro Light and list the Pro options first under Light Theme preferences.

Fixes #5016.
2026-10-13 12:02:32 -04:00
5e0a6d404d Tools: Shift key switches tools by default, as in Photoshop (#3525) (#3528)
* Tools: Shift key switches tools by default, as in Photoshop (#3525)

Use Shift Key for Tool Switch is on by default in Photoshop: T keeps the
current tool, Shift+T cycles. Saved settings keep their stored value.

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

* Tests: the M-key marquee cycling test sends Shift+M, the new default (#3296)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 00:54:41 -04:00
b14037083d Brush and Pencil: hold ~ to erase, and Clear mode (#3439) (#3465)
* Brush and Pencil: hold ~ to erase

Fixes #3439. Holding ~ (the backtick key) with the Brush or Pencil makes
the strokes started while it is held erase, with that tool's own brush,
as in Photoshop; releasing it paints again and the tool never changes.
The key is a Tools › Temporary binding (tools.temporary.erase), so it
can be rebound or removed in Keyboard Shortcuts.

* Brush and Pencil: Clear painting mode, used by ~

Photoshop's Clear joins the Brush's and Pencil's Mode menus, after
Dissolve: chosen there, every stroke erases with that tool's brush until
another mode is picked. Holding ~ now puts the stroke in Clear mode too,
and the Mode menu shows Clear while the key is held. History names the
tool rather than the Eraser. Agents get it as "mode": "clear" on
paint.stroke and paint.pencil.

* Keep the Smoothing doc comment on smoothing_field

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-12 00:02:42 -04:00
a874b149f2 Themes: Photoshop's light-gray and light Pro brightness levels (#2661) (#2892)
Photoshop has four interface brightness levels; PhotoCraft had the two
darkest (Pro, Pro Medium Gray). Add the other two with the Pro layout:

- Pro (Light Gray), id proLightGray: #B8B8B8 panels.
- Pro (Light), id proLight: #F0F0F0 panels.

They are light themes: Preferences > Interface lists them under Light
Theme, Window > Theme has an entry for each, and the legacy
interface.theme preference selects and migrates them. ThemeKind::is_pro
replaces the hard-coded Pro | ProMedium checks so the dock keeps the Pro
tab layout on every Pro level. Labels are translated in every catalog.

Tests check the panel greys, ids and names, the darkest-to-lightest
order, and WCAG contrast of text, icons and accent text on every surface.


Claude-Session: https://claude.ai/code/session_01CVtZHu998NDhf4Z8QvS5pd

Co-authored-by: ngrafton2006 <ngrafton2006@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 08:09:05 -04:00
LuniiyaandBrandon Thomas e44d96e1d4 Favorite themes from the appearance button's right-click menu (#2445)
* feat(ui): favorite themes from the appearance button's context menu

Hovering a theme in Preferences > Interface shows a star; clicking it saves
the theme to interface.favoriteThemes (filled accent star once favorited).
Right-clicking the title-bar appearance button opens a menu with Appearance
Settings, then the favorite light themes and the favorite dark themes.

* Regenerate the scorecard for the favorite themes pref

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 03:09:47 -04:00
e59bb2909b feat(ui): allow disabling Studio canvas background dots (#2317)
* feat(prefs): persist an optional canvas dot pattern setting (#2264)

* feat(canvas): respect interface canvas dot preference (#2264)

* test(prefs): cover canvas dot toggle persistence and reset (#2264)

* Translate the Show canvas dots preference and regenerate the scorecard

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 02:15:06 -04:00
f8ea3d8423 Ruler units keep their own decimal places (#2434) (#2476)
* Ruler units keep their own decimal places (#2434)

Document sizes were shown with one decimal place whatever the unit, so a
1234 px side at 300 ppi read 4.1 in instead of 4.113 in, and millimetres,
centimetres and picas lost a place too.

`Unit::decimals` now follows Photoshop: whole pixels, one place for points
and percent, two for millimetres, centimetres and picas, three for inches.
`fmt_decimals` rounds half away from zero and drops trailing zeros, so a
value is never shown with more precision than it has: 0.7995 in reads 0.8,
never 0.799 or 0.800, and a whole number keeps no trailing .0. It guards the
zero-decimal case so 550 px cannot lose its zero.

The Info panel readout, Image Size, Canvas Size, New Document and the shape
dialog all take their precision from that one place, through the new
`widgets::value_field_prec` and `widgets::unit_decimals`.

Percent keeps its single place: it was not reported and reads better coarse.

* Pass the new decimals argument at the remaining value_field_in call (test)

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-10 14:34:49 -04:00
8d23c62272 Pen pressure curve: Preferences › Tools shapes how pen pressure reaches every brush (#2195)
* Pen pressure curve: Preferences › Tools shapes how pen pressure reaches every brush

Brush phase 6a (TOOL-213-11, #610). Every pen paints its pressure straight through, so artists
with a heavy or a light hand, or a tablet whose response feels off, had no way to adjust it.

- `tools.pressureCurve`: [input, output] points, linear by default, settable with `prefs.set`
  (validated: 2..=16 pairs within 0..1).
- `prefs::PressureCurve` reads it as a monotone cubic (Fritsch–Carlson), so a firmer press never
  gives less pressure. It sanitises the points itself (finite, clamped, sorted, one per input,
  capped, outputs made non-decreasing; flat beyond the ends), because a whole-section update or a
  hand-edited file can store anything.
- The stylus maps every pen sample's pressure through it before any brush sees it, so the live
  stroke, the commit and the journal agree. A mouse stays at full pressure.
- Preferences › Tools draws a curve editor under Use Tablet Pressure: drag a point, click to add
  one, right-click to remove one, with Linear, Soft and Hard presets. Translated in all 16 catalogs.

* Regenerate docs/scorecard.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-10 04:00:10 -04:00
265f10b91d Fix SVG selection and import detection (#2224)
* Fix SVG import detection and configurable group depth

* Fix malformed SVG error routing and formatting

* Refresh scorecard for SVG depth preference

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:58:08 -04:00
Max MurphyandBrandon Thomas 995c895c33 Notices: auto-hide after a delay, with Interface preferences (#2022) (#2100)
* Notices: auto-hide after a delay, with Interface preferences (#2022)

Non-blocking notices no longer stay on screen until closed: each card
hides itself after Interface › Notification › Auto Hide Notifications'
delay (new, default 6 s), and the timer pauses while the pointer rests
on the stack until it moves away. Two new Interface preferences,
notificationAutoHide and notificationDurationSeconds, are read by
notices.rs; errors still land in the status bar.

The decision lives in the pure notices::expire helper, unit-tested
without an egui clock; Notice.shown_at round-trips through the control
channel with serde defaults. Labels translated across the complete
catalogues. Regenerated docs/scorecard.md.

* Notices: repaint on expiry, and a fresh delay when a long hover ends (#2022)

Addresses review of the auto-hide change:

- When the last notice expires, `show` had already painted it and, with
  nothing left, requested no further frame, so the card lingered until
  unrelated input. It now requests a repaint in the frame that drops
  anything, clearing the stack at once.
- A stationary hover over a settled UI refreshed `shown_at` only on
  frames, so hovering longer than the delay then leaving expired the
  notice instantly. `show` now tracks the pointer leaving the stack and
  restarts the timers on that frame, so hovering never counts as elapsed
  time.

Regression tests: expiring a notice requests a frame to clear it;
leaving a long hover gives a fresh delay.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 21:38:03 -04:00
LuniiyaandBrandon Thomas 8c3b6f8e5f Appearance modes: sync with system, separate dark and light themes (#1981)
* Appearance modes: sync with system, separate dark and light themes

* Appearance button cycles Auto, Light, Dark

* Appearance: default stays Dark, follow the portal signal instead of polling, split out the Preferences resize

* Preferences: size both appearance cards for the five dark themes

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 21:34:49 -04:00
Gert Pellin a132e37ee1 Linux: display server preference (X11 / XWayland) so file drops work
On a Wayland session dropping a file on the window does nothing: winit
0.30 (eframe 0.36) has no Wayland drag and drop (#386). The only way
out was launching with `WAYLAND_DISPLAY= photocraft`.

Add Preferences › Performance › Linux display server (auto | x11,
applies at next launch), direction 2 in #386. With x11, main.rs asks
winit for its X11 backend through eframe's event_loop_builder hook
(EventLoopBuilderExtX11::with_x11), so the window runs through XWayland
and drops work. No environment is modified (no unsafe). It only applies
when an X server is reachable (DISPLAY set); otherwise winit's default
is kept, so a missing XWayland can't stop the app from starting. Other
platforms ignore it.

The Wayland drop notice now points at the preference instead of the
environment variable (all catalogs updated).

Tests: the startup reader (x11, auto, unknown value, wrong type, broken
JSON, missing file), the notice text, prefs_usage, label translations.
Verified on KDE Plasma 6 Wayland: with x11 the window is an X11 window
(XWayland) and dropping a NEF from Dolphin opens it.
2026-10-09 16:06:12 +07:00
UnemarsandClaude Opus 5.5 3439d327f7 Preferences › Performance › Low Resolution Previews (on by default) (#1247)
Live previews of large documents (filter dialogs, adjustment dialogs that fall back to the
filter preview, zoomed-out adjustment dialogs, an adjustment layer's sliders while they drag)
render on a nearest-neighbour reduced copy: a 16 MP photo previews at every 3rd pixel, drawn
3x blocky at 100 %. Turning the setting off renders them at full resolution.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 19:10:02 -04:00
TiredIsa 6197270230 Preferences: reset a single shortcut or menu colour override (#988) (#1064) 2026-10-08 16:39:16 -04:00
eb2fb2634f Add rendering modes and CPU fallback recovery (#434)
* Add rendering policy with legacy preference migration and safe adapter selection

* Add rendering mode preferences and GPU fallback recovery warning

* Complete CPU fallback recovery and validate rendering mode UI

* Simplify GPU recovery and reuse preference persistence

* Rendering modes: rebase onto the preferences merge/retry save path; drop docs screenshots

save_preferences now writes through write_prefs (merging other instances' changes) and
only marks the revision saved when the write succeeds, so a failed recovery choice is
still retried. Removes a leftover diff3 marker in es.tsv, the docs/images/rendering-*.png
screenshots and their ATTRIBUTION row (no binary assets in the repo), and regenerates
docs/scorecard.md for the new performance.renderingMode preference.

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 06:11:06 -04:00
HEOJUNFOandClaude Opus 5.5 533566c5ad prefs.reset resets a single colorSettings key (#445) (#467)
`prefs.get` and `prefs.set` route `colorSettings.*` to the colour state,
but `prefs.reset` only special-cased the whole section. A nested path
fell through to `Preferences::reset`, which has no colorSettings, so it
failed with "unknown preference". Take the default from
`ColorSettings::default()` and set it the way `prefs.set` does.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 10:34:30 +07:00
Brandon Thomas 352c849139 Shortcuts: rebindable fill keys, D/X and held temporary tools (#249) (#275) (#275) 2026-10-05 18:50:47 -04:00
Brandon Thomas 7c6a78b05a GPU: crash-safe startup and device-loss fallback (#243, #4) (#252) (#252) 2026-10-05 12:05:22 -04:00
Brandon ThomasandClaude Opus 5.5 7e7864afae Painting: right-click opens the brush picker (or erases), real Smoothing, eraser-on-Background tests (#86)
* Right-click opens the Brush Preset picker (or erases), eraser-on-Background tests, README comma

- #75: the right mouse button never paints with the tool. With a painting tool it opens the
  Brush Preset picker at the pointer (Photoshop). New preference Preferences › Tools ›
  Right-click with painting tools (tools.rightClickWithPaintingTools: brushPicker | erase,
  serde default brushPicker): with erase, a right drag with the Brush erases with the current
  brush (one paint.stroke, one undo step, pen pressure/tilt as usual). ui.pointer accepts
  "button": "secondary". New module ui-egui/src/paint_mouse.rs; picker body shared with the
  options-bar chip.
- #76: the engine already paints the background colour when erasing a transparency-locked
  layer (the Background); added tests at 8/16/32-bit in RGB/CMYK/Gray, with a selection, on a
  normal layer (erases to transparency), on a transparency-locked layer, and through the UI.
- paint.stroke/pencil refuse coordinates beyond ±1e6 (an f64::MAX point ran out of memory).
- #73: test that the canvas hands the live stroke over to the committed document with no
  frame lacking the stroke, refreshing only the stroke's rect.
- #69: README comma.

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

* Brush smoothing follows the options bar; the live stroke draws the catch-up tail (#73)

- The options-bar Smoothing % (pro and classic bars, Brush and Eraser) edits the session
  brush's smoothing, which the live stroke and the committed paint.stroke both use; the
  canvas no longer forces 0.3. The session brush starts at Photoshop's 10 %.
- With smoothing the brush lags the pointer and catches up when the stroke ends. LiveStroke
  now draws that catch-up tail on every step (StrokeRenderer::tail_preview renders only the
  tiles the tail touches; the previous step's tail is restored first), so the preview at the
  last move equals the commit and nothing pops in on release. At 0 % there is no lag.
- Path › Stroke Path ignores smoothing (it strokes the exact geometry).
- Tests: paint tail_preview = finished stroke; canvas: 0 % preview = commit, 50 % preview =
  commit with the end reaching the pointer, smoothing changes the stroke, commit equals
  paint.stroke with that smoothing, no stale tails after sharp turns.

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

* Smoothing is a per-tool option that brush presets keep

- Picking a brush preset (tools.setBrush / paint.* "preset", .abr import with select) keeps the
  current smoothing amount and mode instead of the preset's (BrushSettings::picked_over),
  as Photoshop's options bar does. An explicit smoothing alongside the preset still applies.
- The Brush and the Eraser each remember their smoothing (UiState smoothing_tool /
  tool_smoothing, synced on tool switch before the options bar, the canvas and each stroke);
  a tool seen for the first time starts at 10 %.
- Tests: engine presets keep 10 % / a user value with pulled-string mode; UI per-tool
  smoothing across Brush/Eraser/other tools and on the first stroke after a switch.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:31:17 -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 536a7346fb more algorithms, progress 2026-10-01 08:25:06 -04:00