Skip to content

Preset editor as a flow: forks, triggers, appearance - #34

Merged
thousandflowers merged 26 commits into
mainfrom
feat/appearance-translucent-sheets
Sep 7, 2026
Merged

thousandflowers merged 26 commits into
mainfrom
feat/appearance-translucent-sheets

Conversation

@thousandflowers

Copy link
Copy Markdown
Owner

What changes

  • Appearance: Settings › Appearance (Follow the Mac / Light / Dark); every sheet is translucent (thinMaterial, macOS 13.3+). AppSettings decodes tolerantly, so a new key no longer wipes saved settings.
  • Preset editor takes the whole window, Shortcuts-style: fixed input block, a canvas of blocks with a flow line, a searchable block library (click or drag), per-block drop spots, drag-to-reorder, confirm-on-discard, ⌘S to save.
  • Input: every kind Forge reads, plus a custom list of exact formats (popover); RulePreset.inputFormats, honoured by the Convert sheet and the folder watcher. Old Documents presets keep taking data files, models, subtitles and fonts.
  • Questions say where the answer comes from — asked each time, or read from the file name (_10MB, _1920px, _q80).
  • Logic blocks: If (name, folder, kind, extension, size, sides; two arms), Split into copies (N independent arms, outputs named after the arm, per-arm formats), Join (arms carry on together), Merge (copies become one PDF). Forks are resolved per file in the coordinator before any processor runs; nested forks work.
  • Triggers: a preset runs when a file is renamed with its word in a watched folder (foto_web.jpg); an Only if condition gates the whole preset.
  • UI pass: hit areas, editable surfaces, hover/press states, motion, muted→system colours, arms that never overlap, canvas that scrolls sideways.
  • Editor split into seven files; dead code removed; hand-rolled locks → OSAllocatedUnfairLock.

Test plan

  • swift test — 376 tests, 0 failures (new: settings, input formats, name tokens, logic resolution, split/merge/join end-to-end, triggers, conditions, library)
  • Scripts/build_app.sh and a real launch after every step; screens captured
  • Reviewer: New Preset → If → nested If in Otherwise → Split with 3 copies → Join → Save; run it on a folder of images
  • Reviewer: rename a file to x_web.jpg in a watched folder with a preset whose word is web

🤖 Generated with Claude Code

https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK

thousandflowers and others added 26 commits September 7, 2026 00:49
Settings gains an Appearance picker (Follow the Mac / Light / Dark), applied
through preferredColorScheme on the window; the default keeps following the
system. Every sheet (Convert options, Preset editor, Confirmation, Redaction
review, Add folder) uses the thin material so the window shows through, on
macOS 13.3 and later.

AppSettings decodes with a tolerant initialiser: a settings file saved before
the appearance key existed no longer fails to decode and reset everything.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
Name, description and category sit in a static card: they lived in a grouped
Form inside a fixed frame, which is a list, and a list scrolls under the
pointer. "Asks for" and "Comes out as" are no longer sections above the steps;
the format, each question and the naming template are blocks in the same list
as the operations, added from the same menu and removed with the same button.
The Add-a-step menu is centred under the last block.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
The editor is no longer a sheet. It covers the window: name and description
across the top, the chain of blocks stacked down the middle, and a searchable
library of blocks on the right. The first block, the files that come in, is
fixed and cannot be removed; everything else is added from the library with a
click, dragged into order, and removed with its own button.

Formats are kept apart from the steps so the steps reorder by index. The
Presets toolbar steps aside while the editor is up.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
A List spent the first click selecting the row, so the block was never
added. The library is a ScrollView of plain buttons with a plus on each row.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
…rop library

The input block offers every kind Forge reads - images, videos, audio,
documents, data files, 3D models, subtitles, fonts - and a custom preset
picks the exact formats it answers to from a popover of everything the
processors open. The choice is saved as inputFormats and honoured by the
Convert sheet and the folder watcher.

A line with an arrow runs from each block to the next, so the canvas reads
as the flow it is. The library has a real search field, one colour per
group, tiles with a + each, and every tile drags onto the canvas; a step
dropped between two blocks lands there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
A question can be asked once per batch or read from the file's own name
(holiday_10MB.jpg, holiday_1920px.jpg, holiday_q80.jpg), with a default for
files that say nothing. Name tokens now carry width and quality as well as
size and privacy, and the Convert sheet only asks the questions that are
asked. Group colours are muted, icons are smaller, and the formats popover
stays put: its anchor no longer changes width with the count.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
A List only took drops on rows it already had, wanted a click to select a
row before its fields took typing, and drew its own drag previews. Every
block is now a drop spot: a library block lands there, a step dragged from
elsewhere moves there, and the end zone takes the rest. A library tile is a
button, so clicking it adds the block; onDrag on the same view had been
swallowing the click.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
…fonts

Those kinds sat on the Documents shelf until they had shelves of their own;
a preset or a watched folder saved back then must keep taking the files it
always took.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
…cked

Save is on whenever the preset does something and, when it is off, the
reason sits beside it. Changing the kind removes the steps and formats that
no longer apply, on the canvas where it can be seen. A question starts with
the key the docs use (maxsize, width, quality); an empty or duplicated key
gets a red edge and holds Save.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
…, one editor per request

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
A chain can now fork. An If decides per file on its longest side, width,
height, size or extension and runs one of two chains. A Split ends the
main line and opens an arm per copy, each its own chain with its own steps
and, if it wants, its own format; the outputs are named after the arms.
A Join brings the arms back to the main line, where what follows runs on
every copy. A Merge turns the copies into one PDF, the copies never
touching the destination.

The coordinator resolves every fork for the file in hand before anything
runs, so the processors keep seeing flat chains. The editor draws the
fork as independent arms side by side, each with its own end, and the If
as a block with two paths that rejoin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
…le and gate on a test

Every fork - if or split - is now a node on the main line with an
independent arm per path, side by side, each its own chain. An if's two
arms are its two answers.

The input block says when the preset runs: dropped on Forge, landed in a
watched folder, or a file renamed with the preset's word (foto_web.jpg),
which beats the folder's own preset and is dropped from the output's
name. An 'only if' test gates the whole preset; a file that fails it is
left alone and history says so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
…er the fork

A condition can now test the file's name (is, is not, contains, starts
with, ends with), its folder, its kind, or pass everything, besides size,
sides and extension; each subject offers only the comparisons it can
make. The arms of a fork sit centred under the node.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
Every remove button has a 24pt hit area and a spoken label. Fields that
looked like labels - the preset name and description, the names of a
split's copies - have a surface that says they take typing, plainer under
the pointer and ringed when focused. The next-node slot under a fork is a
menu of what can go there, not only a drop target. Blocks arrive, leave
and move with motion. A fork whose arms hold steps asks before it goes.
Counts keep their width, prose wraps instead of truncating, library tiles
lift under the pointer, ⌘F reaches the search, ⌘S is named on Save.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
Nothing under the arms unless the user put a join or a merge after the
fork: no bar back to one line, no shared slot. A main line that ends in a
fork has no end zone of its own; the shared output blocks sit before the
fork. A step dropped after any fork gets a join in front of it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
The arms rendered their steps as plain blocks, so a nested If lost its two
paths. One renderer now serves every depth: fork with arms, join node, or
block; an arm that ends in a fork has no end zone of its own; a step
dropped after a nested fork gets its join. Arms flex to the width they are
given, so a fork inside one has room.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
…sideways

An arm is 420pt wide with plain steps and as wide as its own fork with a
nested one; the arms hug their content under the node instead of sharing
whatever width the parent had, so a fork inside an arm no longer spills
over its neighbour. The canvas scrolls sideways when the tree is wider
than the window and stays centred when it is not.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
The editor is seven files instead of one: the view, its actions and their
tree, the block library, the controls every block shares (card, chip,
remove button, icon, condition editor, format chips, editable surface,
step list), the action row, the question block, the formats popover;
OutputFormat is a model. Four hand-rolled lock boxes are
OSAllocatedUnfairLock. Dead code goes: toOperations(), isFormat, isLogic,
isText, mainLineOpen, isThrottled, updateAvailable, loadPreset. A format
step no longer poses as a resize: it has no library kind, so changing the
preset's kind leaves it alone - it used to be pruned from an audio
preset's arms.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjRgz3rt1BHJxPDYMawAAK
@thousandflowers
thousandflowers merged commit 25b4472 into main Sep 7, 2026
1 check passed
@thousandflowers
thousandflowers deleted the feat/appearance-translucent-sheets branch September 7, 2026 01:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant