Commit Graph
588 Commits
Author SHA1 Message Date
BFlatandClaude Opus 5.5 2628b0dd3d Preferences merge with other tabs/windows and retry failed saves; web startup shows a Reload hint (#475, #476, #477) (#561)
* Preferences: saves merge with what another tab or window stored, and failed writes are retried (#475, #476)

A save now writes only the values this instance changed since it last
loaded or saved them over what storage holds (a three-way merge over the
JSON tree; arrays are leaves), so a second browser tab no longer reverts
the other's changes. The saved revision moves only when the write
succeeds: a failure is retried after 2 s, doubling to 30 s (sooner after a
newer change), with a status message on the first failure and a notice
while it keeps failing. Shared by the desktop and web builds.

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

* Web: a startup that never gets going shows what may have failed and a Reload link (#477)

When the .wasm fails to download or doesn't match the page, no Rust runs,
so the loading page itself (CSS only, no script) adds the hint after 20 s.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:07:19 +07:00
BFlatandClaude Opus 5.5 7eb3b2e072 Automation: non-object params rejected; headless sees finished jobs; serve stdio survives a bad line; MCP batch keeps results (#503, #504, #505, #506, #529) (#546)
* Command params that are not a JSON object are an error (#506)

`Session::dispatch` now rejects array, string, number and boolean params
with "invalid parameters for `<id>`: params must be a JSON object" instead
of running the command with every default; `null` still means no params.
The one check covers the control channel, `serve`, MCP (headless and
bridge) and the CLI, and batch steps fail per step. `photocraft-cli run
--new` prefixes its error with `--new`.

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

* Headless requests apply finished background jobs first (#503)

A `wait: false` job was applied to its document only by a later command
or `jobs.list`, so `doc.save`, export, `doc.inspect`, `doc.render` and
`session.list` (and their MCP tools) still read and wrote the document
without it. `Headless::sync_jobs` now runs at the top of every JSON-lines
request and before every headless MCP tool call.

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

* `serve` stdio keeps the session after a rejected request line (#505)

An over-long request line (or one that is not UTF-8) ended the stdio
session, losing every open document. It now gets one error reply, the
rest of that line is skipped without being dispatched, and the next
request is served. `read_bounded_line` reads bytes, so a cut inside a
multi-byte character is reported as too long instead of an I/O error.
TCP connections still close after an oversized line.

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

* MCP `command_batch` keeps its step results when the reply budget stops it (#504)

The batch budget charged each step's plain JSON size, but MCP sends the
batch reply as escaped text content, so a batch the budget had already
stopped could still exceed the tool-result ceiling and come back as a
bare "response exceeds" error. MCP (headless and bridge) now uses
`BatchReplyBudget::escaped`, which charges the escaped size, through the
new `Headless::batch_with_budget`; `serve` keeps the plain budget.

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

* docs: the 1 MiB request-line limit does not apply to MCP stdio (#529)

The book stated the 1 MiB request-line limit without naming a transport.
It applies to desktop and headless TCP and to the headless JSON-lines
stdio server; MCP over stdio reads its own lines and only its tool
results are size-checked. Say so in the automation security pages and in
the control protocol's transport limits.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 14:00:36 +07:00
BFlatandClaude Opus 5.5 8f0f95959d Control channel: menu/dialog requests wait for their job; right-click menus at the point; MCP ui_pointer button (#510, #511, #512, #514) (#548)
* ui.inspect descriptions point to ui.menu.list for the menu tree (#510)

The control-protocol doc, the control.rs method list and the MCP
`ui_inspect` description said `ui.inspect` returns the menu tree; it
never did. They now say the menu tree comes from `ui.menu.list` (over
MCP, through `control_call`).

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

* ui.menu.invoke and ui.dialog.confirm wait for the job they start (#511)

Since filters became background jobs, invoking a no-dialog filter from
the menu or confirming a filter dialog over the control channel replied
`{job, pending: true}` before the filter was applied, unlike v0.2.0 and
unlike `engine.execute`. All three now share one helper that runs the
command with script events off and, when it started a job, defers the
reply until the job's result lands; `"wait": false` keeps the immediate
`{job, pending}` reply. The now-unused `run_automation` is gone.

The new test drives Blur More through `ui.menu.invoke` and Gaussian Blur
through its dialog's OK, with and without `wait` (it failed before).

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

* ui.pointer right-click opens its menu at the pressed point (#512)

A `ui.pointer` right-click listed the layers at the requested document
point but drew the layer menu at the canvas centre, and the Brush Preset
picker opened there too. Both now open at the point's screen position,
where a mouse right-click on it opens them. `ViewXform::active` gives the
active canvas's document-to-screen mapping as last laid out (the
construction several tests repeat); the centre stays the fallback when
the point can't be mapped.

The ui.pointer doc now lists `button`, both menus, and that top-level
modifier flags are ignored when `modifiers` is given.

The new test checks two points against the mouse path and the picker
position (it failed before).

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

* MCP ui_pointer forwards button and rejects unknown arguments (#514)

Bridge clients couldn't right-click through `ui_pointer`: it forwarded
only `events` and `modifiers` and silently dropped anything else
(`button`, a typo, a top-level `space`). It now forwards an optional
`button`, and `PointerParams` denies unknown fields, so the schema says
`additionalProperties: false` and a stray argument is an error.

The new bridge test checks the forwarded params, the rejection of
`buton` and top-level `space`, and the tool schema (it failed before).

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:55:01 +07:00
BFlatandClaude Opus 5.5 e30b121969 History: tool and preset names, Frame from Layers as one step, undo/redo restore the targeted layers (#513, #528, #446, #496, #495) (#551)
* Elliptical Marquee selections record "Elliptical Marquee" in History (#513)

Both marquee tools commit through `select.rect`, which named every step
"Rectangular Marquee" whatever its `ellipse` param. The step is now named
after the tool that made it; the rectangular marquee keeps its name.

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

* Preset filters record their own names in History (#528)

Blur, Blur More, Sharpen, Sharpen More, Sharpen Edges and Despeckle run
a generic algorithm with fixed parameters and recorded its name
("Gaussian Blur", "Unsharp Mask", "Surface Blur"). The presets now live
in one table with their names, used for the history step and the job
progress text; Gaussian Blur, Unsharp Mask and Surface Blur keep theirs.

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

* Vertical type command no longer claims it renders horizontally (#446)

Vertical type has rendered in columns since #199, but the params text of
`type.orientation.vertical` still said it was "rendered horizontally for
now". It now documents the same `{"layer":id?}` as Horizontal.

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

* Frame from Layers is one history step (#496)

It grouped the layers through `layer.new.groupFromLayers` (its own
"Group Layers" step) and then recorded "Frame from Layers", so one undo
left the group behind. The grouping step is now folded into the frame
step with `purge_last`, as Transform Again on a copy does; if the group
can't be framed, the grouping is undone with nothing left to redo.

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

* Undo and redo restore the layers each history state targeted (#495)

History states held only the document, so after undo or redo the active
layer stayed whatever it was: redoing New Layer left the old layer
active (a Fill then painted it), and undoing Delete Layer left the
restored layer unselected. Each state now also keeps the active and
selected layers it had when it was created (when the document was
opened, or right after the step that made it), and undo and redo target
them again where they still exist. Selecting a layer is still not a
step and doesn't change any state's target, so selecting another layer
and then undoing or redoing goes back to that state's target, as the
reference app's Step Backward does.

The History panel lists redo labels from a new `redo_labels` accessor
instead of cloning and replaying the history every frame.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:54:10 +07:00
BFlatandClaude Opus 5.5 bd1322a5e4 Recovered documents keep their recovery data until saved or closed (#444) (#549)
Launch no longer deletes recovery entries right after loading them. A
recovered document adopts the entry it came from: its next autosave
replaces it in place (atomically) and saving or closing the document
removes it, so a second crash before the next autosave loses nothing.
New documents autosave under per-launch keys, so a document id reused
from an earlier launch never overwrites a surviving entry, and sidecars
whose key doesn't match their file name are ignored.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:53:48 +07:00
BFlatandClaude Opus 5.5 1df68e9a5d CLI batch: recorded-action steps, --format .ext, strict --quality, refuse --out = --in (#489, #490, #491, #492) (#545)
* CLI --quality refuses every value outside 1-100 (#491)

--quality was parsed as a u8 and clamped, so 0 and 101-255 were silently
accepted while 256 and above were rejected. Every value outside 1-100 is now
a bad --quality error, checked before any file is read or written.

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

* CLI batch: --format with a leading dot names outputs <stem>.<ext> (#490)

batch built output names from the raw --format value, so --format .jpg wrote
a..jpg. One leading dot is now dropped, as the save path and
file.automate.batch already do.

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

* CLI batch --actions takes recorded-action and droplet steps (#489)

batch read only {"command", "params"} objects, so the [id, params] pairs a
recorded action or droplet stores failed with 'missing "command"'. It now
uses the engine's action parser (automate_cmds::parse_action, split out of
parse_script), which takes [id, params], objects and bare ids, bare or
wrapped in {"steps": ...} or a droplet; {"actions": ...} still works.

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

* CLI batch refuses an --out folder that is the --in folder (#492)

Results are saved as <out>/<stem>.<ext>, so --out naming the input folder
silently replaced the originals. batch now compares the canonical folders
(relative spellings, trailing separators, symbolic links) and fails before
writing anything, naming the folder; the new --in-place flag opts in.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:21:07 +07:00
BFlatandClaude Opus 5.5 29c280a2c6 Docs: roadmap, scorecard and README after the 2026-10-07 user-report fixes (#538)
- Roadmap (updated 2026-10-07): the M11 row notes the stricter CLI,
  batch collision reports, per-step `wait` in MCP batches and safe
  headless saves; the UI / UX row lists the keyboard-only shortcuts
  (layer navigation, number keys, hardness, step and repeat), Alt-click
  sampling, double-click for Layer Style, New from Clipboard, the
  centred window and remembered Liquify settings.
- Scorecard: UI-217-1 is partial (Fill and Liquify settings persist);
  UI-217-2's note points at the new `native_options()`. docs/scorecard.md
  regenerated; docs/parity.md is unchanged (no new catalogue items).
- README: `<subcommand> --help` in the CLI snippet.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:57:23 +07:00
floflo777 8a5e89faa6 32-bit documents: Exposure works on the stored linear values (#400)
* 32-bit documents: Exposure works on the stored linear values

Their samples are linear already, so going through the 2.2 curve applied
the gamma twice. rgb32 and gray32 exposure.psd now match Photoshop within
0.5/255 (was 69/255 and 44/255).

* Raise the Photoshop oracle floor to 130
2026-10-07 11:49:29 +07:00
Alex GorevskiandCopilot a312c3d0c7 PSD export: select PSB from size estimate (#431)
Estimate encoded raster and retained-data size with checked arithmetic, and select PSB when the PSD limit is exceeded. Keep forced PSB choices and document the completed scorecard slice.

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

Copilot-Session: f77709e9-6475-4953-ab57-b78347b4499d
2026-10-07 11:41:08 +07:00
Carter Smith f373fdb52f Raise rust-version to 1.95, the minimum egui 0.36 needs (#471) 2026-10-07 11:32:23 +07:00
floflo777 9ae984c0ae Use the real Gaussian Blur id in the docs and MCP schema (#390)
filter.blur.gaussian isn't registered, so the documented CLI run and the
agent loop failed with "unknown command". Pin the schema examples with a test.
2026-10-07 10:34:03 +07:00
BFlatandClaude Opus 5.5 6119c00962 Layers panel: double-click beside a layer's name opens Layer Style (#350) (#452)
A double-click anywhere on a Layers row started a rename. Now only a
double-click on the name renames; the rest of the row follows the
layer's kind: an adjustment or fill thumbnail opens its Properties
(Layer Content Options), a Smart Object thumbnail opens its contents
(Edit Contents), and anywhere else opens Layer Style (Blending Options).
The Background still becomes a normal layer, mask thumbnails keep their
own clicks, and a command that's disabled for the layer does nothing.
docs/ui-design.md lists the row's double-click targets.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 08:37:47 +07:00
BFlatandClaude Opus 5.5 3443833532 Headless saves without path never flatten over the opened file (#416) (#447)
MCP `doc_save {}` and `serve` `doc.save {}` fell back to the document's
own path for any format, so an agent that saved its work replaced the
input PNG or JPEG with a flattened, re-encoded copy, and a `format`
override wrote PNG bytes under a `.psd` name. The desktop's File › Save
already writes back only to layered files.

The rule now lives in one place, `engine::file_cmds::saves_in_place`
(plus `extension`), shared by File › Save, the control channel's
`app.save` and the headless save. Without `path`, a save writes back
only to a PSD, PSB or .pcraft file in its own format; anything else is
an error naming `path` and the file is left unchanged. An explicit
`path`, even the opened file's own, still writes as before. The MCP
`doc_save` description and `path` doc, and docs/control-protocol.md,
state the rule.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 08:20:43 +07:00
BFlatandClaude Opus 5.5 55054b5fcc MCP command_batch and serve batch honour each step's wait (#415) (#439)
`command_batch` steps reuse `command_run`'s params, so its schema
advertised `wait`, but the tool mapped each step to `{command, params}`
and ran it to completion: `"wait": false` blocked on the filter and
started no job. Steps now carry `wait` (default true): headless batches
start the command through `command_start`, and bridge mode forwards it
to `engine.execute`, which already honours it. `serve`'s `batch` reads a
step's `wait` the same way. The schema doc and docs/control-protocol.md
describe the per-step `wait`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 07:59:57 +07:00
BFlatandClaude Opus 5.5 2620460267 ui.set: reject unknown fields; the theme error names all five themes (#412, #413) (#437)
`ui.set` skipped keys it doesn't read and replied with success, so the
`dark` field advertised by the MCP `ui_set` tool (and any typo) changed
nothing while reporting ok. It now checks every key against
`UI_SET_FIELDS` before applying any of them and names the accepted
fields in the error. The MCP tool description, its `fields` doc, the
control.rs method list and docs/control-protocol.md list the real fields.

An unknown theme listed only studio, studioLight and classic; the error
now comes from `ThemeKind::ALL` through the new `ThemeKind::id`, so it
names pro and proMedium too, and each listed name selects its theme.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 07:59:14 +07:00
Brandon Thomas fbcd0c238e Canvas scrollbars, and Preferences > Tools > Overscroll works (#300) (#367) (#367) 2026-10-06 18:22:23 -04:00
Brandon Thomas 0da7d4b0f5 Right-click on the canvas lists the layers under the pointer (#307) (#360) (#360) 2026-10-06 18:22:11 -04:00
Brandon Thomas c161eaec73 Fonts: use craft-fonts as an optional build input; move the Japanese fonts out (#353) (#353) 2026-10-06 18:21:46 -04:00
Brandon ThomasandClaude Opus 5.5 909efc0f6d Background jobs in the app: progress, Cancel, open-as-job (part of #210) (#373)
Re-lands #290, which was merged into its stacked base branch (jobs/engine-210) instead of main.
Same tree as the gated #290 commit 39152db.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 15:44:40 -04:00
才倾梦华V b3c44cf070 Preferences: add an Apply button (#325) (#325) 2026-10-06 11:29:06 -04:00
TiredIsa abd6f6e591 Localise the UI into Russian: a complete catalog and the missed tl! call sites (#298) (#298) 2026-10-06 11:28:52 -04:00
Mawfyy edc441a8d4 Add a Spanish catalog (menus, dialogs, panels) (#299) (#299) 2026-10-06 11:28:37 -04:00
Andrew Shen 321e986f7b i18n: add Traditional Chinese (zh-hant) UI catalog (#302) (#302) 2026-10-06 11:28:23 -04:00
Brandon Thomas dd2f566daa macOS CLI: verify the shipped CLI is Developer ID signed and notarized in the release workflow (#308) (#320) (#320) 2026-10-06 10:48:05 -04:00
Brandon Thomas eaf92dcd9d Smart Objects and Smart Filters survive PSD save and open (#288) (#288) 2026-10-06 07:46:37 -04:00
b5b5ce8394 i18n: add Simplified Chinese localization (#282)
* i18n: add Simplified Chinese translation catalog (#282)

* i18n: register the Simplified Chinese catalog (#282)

Adds the zh-hans row to LANGUAGES. It's marked partial (complete_menus: false): strings added
to main since the catalog was written fall back to English until translated.

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-06 07:43:52 -04:00
Brandon Thomas a96a621dee FreeBSD release tarball and README platform lines (#284) (#284) 2026-10-05 20:54:00 -04:00
Brandon Thomas 3423864502 Fast Select › Modify › Smooth and Feather (part of #211) (#280) (#280) 2026-10-05 19:59:11 -04:00
Brandon Thomas 7db46219ce Pencil tool, Edit › Fill… dialog and "Rasterize?" prompt (#213) (#279) (#279) 2026-10-05 19:48:03 -04:00
17d4cd0d78 Localise the UI: i18n foundation and a Japanese catalog (menus, dialogs, panels) (#169)
* Localise the UI: i18n foundation and a Japanese catalog

Adds a small i18n layer and a complete Japanese catalog for the menus, the
command palette, dialogs and panels. English stays the default and the source
language; command ids, the control protocol, the CLI and MCP are unchanged.

- i18n (crates/ui-egui/src/i18n): one TSV catalog per language, registered with
  one row in LANGUAGES. Lookups: tr/tl! (plain), tr_ctx (context), tr_id
  (command-id keyed, English fallback), trn (plurals), fmt ({name}
  placeholders). Locale tags resolve most-specific-first; macOS falls back to
  the preferred-languages list.
- New preference interface.language (auto | language code), a dropdown in
  Preferences > Interface. The engine stores a validated code and does not
  know the language list.
- Widgets (checkbox, slider_row, dropdown, buttons, tabs, icon tooltips)
  translate their labels; direct egui calls use tl!("...").
- Japanese catalog (about 2,000 entries) written from the meaning of the
  English labels; no string table was extracted from any other product.
- Tests: catalog well-formedness (duplicates, placeholders, ellipses, plural
  forms, command ids) and coverage of menus, tl! literals, blend modes and
  generated preference labels.

Status-bar messages and errors stay English. Per-language font fallback,
right-to-left layout and locale-aware number formats are not done yet.

Rebased onto main (maintainer): conflicts resolved keeping main's newer UI
(tab strips, platform shortcut labels, menu navigation, Properties layout)
with the tl!/tr wrapping. command_label/tip_label translate their label
before appending the shortcut, shortcut hints use {key} placeholders, and
strings main added since are wrapped and translated where unambiguous.
The macOS locale lookup runs /usr/bin/defaults by absolute path and
ignores failures.

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

* Menus widen to fit translated labels instead of wrapping them

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

* Workspace switcher keeps English keys so translated labels still select a workspace

The options' keys were translated too, so in Japanese the current workspace
showed as "—" and picking one never matched apply_workspace.

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

* Regenerate docs/scorecard.md for interface.language

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-05 19:40:10 -04: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 315be69dd5 Portable mode: keep settings beside the exe in the Windows portable zip (#228) (#267) (#267) 2026-10-05 18:15:29 -04:00
才倾梦华V 2172d085c6 Fix Windows MSI advertised shortcut icons (#238) (#238) 2026-10-05 18:13:51 -04:00
Brandon Thomas 47f4abfed4 Stylus pressure, tilt, rotation and eraser on macOS and X11 (#255) (#255) 2026-10-05 17:47:59 -04:00
Brandon Thomas 968baf639e Photoshop oracles via storytold/photocraft-corpus: pinned corpora, corpus feature, test-corpus, CI job (#190) (#190) 2026-10-05 17:47:33 -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 Thomas 8bac8926a3 PSD stroke effects: outlines of filled shapes, vector stroke above interior effects, joined subpaths (#233) (#233) 2026-10-05 11:48:28 -04:00
Brandon Thomas ae80df2eb4 Vertical type (tategaki): lay out, render, edit and round-trip (#199) (#246) (#246) 2026-10-05 11:48:14 -04:00
Brandon Thomas 34de1a5dfc Preferences: wire tooltips, Home screen, recent files; hide what nothing reads (#245) (#245) 2026-10-05 11:16:27 -04:00
Brandon Thomas 0f955e6c82 Scorecard: cargo xtask perf + scorecard, budgets, checklists, nightly perf (#221) (#239) (#239) 2026-10-05 10:53:02 -04:00
Brandon Thomas 7f3d9ea68a Packaging: Linux runtime deps + start-up library check (#201); wasm 26.3 -> 18.8 MiB (#198) (#237) (#237) 2026-10-05 10:52:20 -04:00
John HassandJohn Hass a502a227a9 Fix automatic UI scaling on 4K monitors (#225) (#225)
Co-authored-by: John Hass <john@basement-lab.hasshouse.com>
2026-10-05 09:58:45 -04:00
Brandon Thomas 4b53f620ef Tool feedback: selection-mode cursor badges, scrubby zoom, contrasting outlines, Shift lines (#188) (#188) 2026-10-05 08:01:52 -04:00
Brandon Thomas 574d0d3d53 PSD corpus: psd-tools set (pinned, verified), an oracle for every file, and the fixes it found (#195) (#195) 2026-10-05 08:01:39 -04:00
Brandon ThomasandClaude Opus 5.5 433b84fae7 Docs: where we stand and where we're going (roadmap, README, AGENTS.md) (#186)
roadmap.md: the honest parity assessment now breaks parity down by dimension with dated
measurements (PSD oracle 115/170, round trips 169/169, shortcut audit 0 failures, perf numbers,
RAW coverage, AI 0%, ~20 missing tools...), an overall estimate (feature surface ~60-70%,
pro-switchable ~25-35%), and a priority order for what's next.
README: the status note says plainly that PhotoCraft isn't yet a daily-driver Photoshop
replacement, what the biggest gaps are, and links the assessment.
AGENTS.md: agents read the assessment first when picking work and update it when numbers move.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 06:14:53 -04:00
Brandon ThomasandClaude Opus 5.5 20783fdc70 Roadmap: honest parity assessment (2026-10-05) (#182)
parity.md's 625/625 measures menu wiring, not behaviour. Record a candid assessment after
0.2.0 and the first user feedback (feature surface ~55-70%, pro-switchable ~20-35%, area by area),
how to make parity measurable (workflow acceptance tests, real-file corpus, visual QA, user
reports), mark parity-estimate.md's ~82% as an upper bound, and update the current focus.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 05:20:57 -04:00
Brandon ThomasandClaude Opus 5.5 231bf5fe81 Flatpak single-file bundles as release artifacts (#163)
* Flatpak single-file bundle as a release artifact (#140)

packaging/linux/flatpak/ai.storyteller.photocraft.bundle.yml repackages the
release's Linux tarball (no Rust build, no network) on the freedesktop 26.08
runtime; packaging/linux/flatpak-bundle.sh builds it with flatpak-builder and
flatpak build-bundle and smoke-tests it. release.yml gains a Flatpak job per
arch that feeds the draft release and SHA256SUMS. The Flathub manifest moves to
26.08 with the same documented finish-args (portals + Pictures/Documents), and
packaging-lint checks the two manifests agree. Font scanning also looks in
/run/host/fonts and /run/host/user-fonts so host fonts show up in the sandbox.

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

* Docs: installing the Flatpak bundle (#140)

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

* Flatpak job: install the SVG pixbuf loader for appstream compose

Without librsvg2-common (absent on the x86_64 runner image) the host's
appstreamcli compose fails with file-read-error on the scalable icon.

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

* release.yml: the Flatpak job runs without the release environment

It only repackages the Linux job's tarball and signs nothing, so it doesn't need (and
shouldn't hold) the signing secrets while running third-party tooling.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 04:15:21 -04:00
Brandon ThomasandClaude Opus 5.5 a6c5fa6732 Layers rows: name truncates before the fx/lock/link indicators; fx triangle; Collapse All Groups (#144, #143) (#167)
- Layers panel rows lay out their right-hand indicators first (lock, fx badge
  with an effects disclosure triangle, link, optional blend-mode label) and
  give the name the remaining width, cut with an ellipsis. The Studio kind
  sub-label and the Effects sub-rows truncate the same way; a narrow panel
  squeezes group indentation before the name. Rows leave room for the
  overlay scrollbar. Drawn rects are recorded for tests (layer_row_ui).
- New view-state command layer.setEffectsExpanded (DocState::fx_collapsed,
  no history step), driven by the fx triangle (Alt-click: all layers).
- Layers panel menu: Collapse All Groups (layer.setExpanded all=false).
  Group open/closed state is document data (PSD lsct, .pcraft).
- ui.set {dockWidth} sets the right dock width (250..520) for agents and
  snapshots.
- engine example designer_psd: ~140-layer designer PSD (4-deep groups, long,
  CJK/Greek/accented names, fx, clipping, masks, vector masks, locks, links,
  type/shape/fill/adjustment layers, hidden layers) for visual QA.
- Tests: name never intersects an indicator at 1x/1.5x/2x, 250/290/520 pt and
  in every theme; fx triangle hides/shows effect rows without selecting;
  Collapse All Groups from the panel menu, state survives PSD and .pcraft.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 03:38:47 -04:00
Brandon ThomasandClaude Opus 5.5 177620a58d Layers groups expand/collapse (#126); every displayed shortcut fires (#127) (#141)
* Layers panel: group disclosure triangles expand/collapse groups (#126)

Group rows get a disclosure triangle; click toggles the group, Alt-click sets
every group. Rows now list a group directly above its contents (the panel
used to reverse a pre-order walk, putting headers below their children) and
closed groups hide their contents (a kind filter still shows them).

The state is Group::expanded (PSD section divider open/closed folder,
.pcraft), toggled by the new layer.setExpanded command as a view change:
no history step, a clean document stays clean. document.inspect reports it.

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

* Shortcuts: every displayed shortcut fires, from wherever focus is (#127)

- The menus show the catalogue's Photoshop shortcuts, but only command and
  shell shortcuts were bound: Image Size (Cmd+Alt+I), Canvas Size
  (Cmd+Alt+C), Feather (Shift+F6), Layer > Quick Export / Export As,
  Window > Info (F8) and Actions (Alt+F9) showed a key that did nothing.
  The binding table (new shortcut_dispatch module) now merges the
  catalogue's shortcuts for live items, one owner per key.
- Any focused widget swallowed every shortcut (egui_wants_keyboard_input):
  after clicking into a value field (Layers Opacity/Fill, options bar
  Opacity/Flow/Smoothing), the Curves graph, or Tab, nothing fired until
  the canvas was clicked. Now a focused text field keeps typing and its
  editing keys (select all, clipboard, undo, navigation) while Cmd
  shortcuts and F-keys still fire, as in Photoshop; a focused non-text
  widget keeps only its navigation keys.
- A shortcut whose command is disabled says why on the status bar.
- Layers panel rows carry their layer name for accessibility (and tests).

Tests press every displayed menu / shell shortcut and every tool letter in
a whole-app kittest harness with a layered document (group, type layer,
selection) from four focus places, asserting each dispatches its item or
the item is greyed with a reason; plus real runs of the common ones.

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

* UI tests: scroll Layers rows into view under the fixed-height dock

The dock (#139) gives the Layers panel a fixed height that scrolls, so a
group's triangle or a row can sit below the fold. Tests now scroll the
node into view (accesskit ScrollIntoView) and click its real rect.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:28:57 -04:00
Brandon ThomasandClaude Opus 5.5 b4b1aab4ad Dock: fixed-height panel groups with splitters, collapse, reorder and a remembered layout (#139)
Panel group heights no longer follow their content (#88): switching between a
pixel and an adjustment layer used to grow Properties and squeeze Layers.

- New `dock` module: `DockLayout` (order, heights, collapsed) in `UiState::dock`
  (serde default, settable with `ui.set {dock}`). The last expanded group fills
  the column; content taller than its group scrolls inside it.
- Drag the gap between groups to resize (min heights, clamped to the window);
  double-click a tab or use the panel menu (hamburger, now live) to collapse,
  move a group up/down, or close it; drag a tab strip to reorder (blocked by
  Lock Workspace).
- Layout saved with New Workspace, reset by Reset Workspace (default heights,
  order and tabs), and remembered across launches in `prefs.panelLayout` while
  Remember Workspace Changes is on.
- #129: the icon rail toggled panel visibility, so a stray click made a panel
  vanish. It now shows / expands / collapses a docked group and never hides it.
  Window > <panel> and Window > Color/Gradients/... expand a collapsed group
  instead of hiding it, so the menu always brings a panel back.

Tests: kittests for stable rects across layer kinds (Pro and Studio), splitter
drag + UI-state round trip + prefs persistence, reset/new workspace, double-click
collapse and strip-drag reorder (and lock), tiny windows, rail/Window menu
recovery, and a click-around test over layers, canvas, toolbar and options bar.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:36:48 -04:00