Skip to content

feat(ui)!: add a visual template editor - #128

Merged
odevine merged 29 commits into
mainfrom
feat/template-editor
Oct 15, 2026
Merged

odevine merged 29 commits into
mainfrom
feat/template-editor

Conversation

@odevine

@odevine odevine commented Oct 15, 2026

Copy link
Copy Markdown
Owner

Summary

The ui gains an Editor mode for building a template visually. A person starts from a plain
card or from a copy of an installed template, drags and edits layers, text boxes, art windows
and symbols on a rendered test card, brings in their own PNG layers and fonts, and finishes
with a template the app can use or a .mimic file to share. Every edit is written to the
template's own files, so a template made here is the same kind that a template author keeps.

The Templates library also gains Import template and a remove button for each version the
app keeps, which round out managing templates from inside the app.

What is in it

The editor lists a template's layers, selects and moves them on the card with snapping and
undo, and edits each item's fields with levers and, for anything else, in a JSON pane that
reports problems by line. Images and fonts are brought in through the studio service, which
checks them. A text box set on a single line can be anchored on its baseline, the top of its
capitals or their middle, which keeps a shrinking title centered with the mana cost. The card's
view zooms and pans, layers can be hidden or forced on in the preview, and a text box shows the
room it has left and what is in its way. Test cards are real cards from presets or a search,
and can be saved, and a coverage strip draws the template for each. A template's id is
owner.name, and its owner can be set from its details.

Use in mimic makes a template available in Single, List and Run, Export writes a .mimic file
after showing its license and credits, and Save image writes the card's picture as shown or
drawn again at the template's own resolution. The editor reads a template again when its files
change outside the app.

In the library, Import template installs a .mimic file beside the downloaded ones, asks before it
replaces a different copy of an installed version, and reopens the template in use if that was the
one replaced. Remove deletes a version from the cache, says whether it can come back, and switches
away from a version in use. A cached version that the catalog does not list is now shown with the
catalog's versions.

Compatibility

A template that uses the cap-top or cap-center anchors needs engine 0.19.5, and an edit that sets
one names that release in the template's minEngine. The ui pins engine v0.19.5.

The folders local-fonts and assets are no longer described in the documentation. The code that would
read them in a checkout is unchanged.

Testing

Go tests run with the race detector, Node tests cover the editor's pure modules, and page tests
drive the real frontend against a controlled bridge in Chromium and WebKit. Tests against the
test server cover drags, saving, export, import and remove, and the launch check opens the editor,
edits a template and undoes it. The accessibility scenes include the editor's panels and dialogs.

Not checked here: the page in a real desktop window. That covers the native file and confirm
dialogs, the Save image menu and the icons in the library rows. The visual baselines are
regenerated with the Update visual baselines workflow.

Release-As: 2.0.0

🤖 Generated with Claude Code

odevine and others added 29 commits October 13, 2026 20:18
The Editor mode keeps the templates a person is building as drafts, each a
template's source folder under the config folder, and renders a test card
through one as it is on disk. A draft is never installed, so changing one
cannot change an installed template.

New template starts from the engine's starter at 72 to 1200 dpi, and Edit a
copy, beside each installed version in the Templates library, starts from that
template. A copy keeps the original's license and credits and adds a credit
that names the original and its version. The test card comes from nine presets
that differ in colors, types and text length, or from a Scryfall search, and
is kept with the draft. A draft that does not load lists its problems.

Pipeline.RenderThrough renders with a template the caller names, and
OpenSource opens an installed version for copying. The editor features not
built yet are gated on #115. The engine is pinned to v0.19.2, which has the
starter, the copy and the direct open of a draft.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Clicking the test card in the editor selects what is drawn at that point, and
clicking the same place again reaches what lies beneath it. The layers list
selects too and shows what the card does not draw. Dragging moves the
selection with snapping to the card and to the other items, the handles of a
text box, art window or symbol resize it, the arrow keys nudge it, and Raise
and Lower change a layer's place in the stack. Undo and redo take changes back,
and a run of arrow keys is one change.

Each change is an operation that names an element and gives its numbers in the
finished image's pixels. The app turns them into the layout's own, including
its rotation, changes the numbers in the layout file with its key order and
layout kept, and returns the operation that undoes it. It refuses a change that
would leave a template that loads unable to load, and a layout that extends
another is shown and not changed.

Studio.Describe lists what the layout draws for the test card and where, and
Studio.Apply makes the changes and saves them.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The editor outlined the rectangle a text box has in its layout file, which for a
box set on its baseline starts at the first baseline, so the outline sat below
the text it belonged to. A described item now says when its place is where its
text is drawn, and the preview outlines that place. Such a box can still be
moved, but it has no resize handles, since its rectangle only sets where the
baseline starts.

The engine is pinned to v0.19.3, which reports that place.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Under the selection in the editor, the settings of a layer, text box, art
window or symbol are fields that read and write the layout file: place and size,
what a box draws and when, its font role, size, color, alignment, spacing and
shadow, a layer's position, opacity, blend and slot, and an art window's layer
and turn. Layout holds the canvas, and Template details the name, description,
author, license and credits, and a copy keeps its original's license and credits
out of reach of the fields. A value is checked as it is typed, a change is made
when typing stops, and an empty optional field takes its setting out.

Edit JSON opens the template's files under the preview. Each mistake in the JSON
is shown with its line and column and each mistake in what it means with its
path. Text that is JSON and leaves the template loading is saved as typed, text
that does not is held back, and a template that does not load can be fixed
there.

Changes are set, unset and replace operations that go through the same check as
the others: the draft is loaded as it would be with the change, and the engine's
own rules refuse a bad value with their reason. Studio.Source, CheckFile and
Vocabulary feed the editor, and Apply reports whether the draft loads and what
is found in it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A box that snapped to the middle of a canvas with an odd width was moved by
half a pixel, and the fractional number could not be read by the edit that
saves the move, so the drop failed with a binding error. The snapped move is
rounded to whole pixels.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The editor's frontend modules, their tests and the service's main file were
named for the studio, which is not what the feature is called. The files are
renamed and the one comment that used the word now says editor.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…up with

A click on the card selects the topmost item under the pointer, the one the
preview outlines on hover, and no longer keeps the selected item or cycles
through the items beneath it. A press on the stage around the card lets go of
the selection. While a box is dragged, the guide it snaps to says which of the
box's edges is held and which edges lie on that line, and the items on the line
are outlined.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…and delete its layers

A layer shows a cell for each material its part reads, with the image that
draws it, the colors a pair is blended from, the any image that covers it or
nothing, and an image is replaced, added or removed from the cell. A PNG is
checked as it comes in, and one that is larger than the canvas, a different
size than the part's others or has no transparent pixels is taken with a
warning. Fonts are added from a file with their hash, names and license, and
each text role chooses a font of the template's or one of the engine's. A
template that gets its first font or role names the engine release that reads
them.

A text box, an art window or a layer for a part, new or existing, is added from
a dialog, and a layer for a new part and its first image are one step of undo.
Any element can be copied or deleted, and undo restores the layout file as it
was. The files an undo needs are held in memory, and one that was let go is said
to be gone.

An item can be locked, so the card does not pick, hover, move or resize it, and
an item can be marked as one a drag snaps to. A drag snaps to the card's own
lines and to marked items only, and the guide names the edge held and outlines
what lies on the line. Both marks are kept with the draft and are not part of
the template.

Studio.Assets, Thumbs, ImportImage, ImportFont and SetMarks are new, and the
edit operations gain put, drop, add, delete and duplicate. Apply also removes
files, and the draft forgets an image that changed so the next render reads it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Each row of the layers list has a grip to drag a layer by, an icon for what the
item is, a padlock that locks it on the card, a grid that makes it a line for a
drag to snap to, and a menu with Duplicate and Delete. A layer is also moved with
Alt and the arrow keys, and with Move up and Move down in its menu. The
Selection panel no longer has Raise, Lower, the two checkboxes or the Duplicate
and Delete buttons that these replace. The list is no longer a listbox, since its
rows now hold buttons, and the selected row is marked with aria-current.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The canvas size, resolution and turn belong to the whole template and not to the
selection, so they are in a Canvas section of the left column under the template
picker and not among the right column's sections. It starts collapsed, opens with
the same arrow the other sections have, and keeps whether it is open with the rest
of the layout.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…in proportion

The six presets are real printings, Mirran Spy, Consuming Aberration, Eerie Ultimatum,
Norin the Wary, Sensei's Divining Top and Flooded Strand, named by set and collector
number and looked up when one is chosen. The test card holds its properties as a
disclosure and sits in a section that, with the search, collapses and takes the height
its reader gives it. The canvas keeps its width and height in proportion until the
padlock is opened.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The card's view has camera controls pinned to its corner, a Select tool and a Pan tool
with zoom out and in, the zoom as a percent and a button that fits the card back in the
middle. The wheel zooms about the pointer, plus and minus step the zoom, and 0, 1 and 2 are
fit, 100% and 200%. Pan, or Space held over the view, or the middle button, moves the card
about the view by any distance without selecting or moving what is on it, and the
outlines, handles and guides go with the card.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Use in mimic builds the draft and keeps it among the installed templates as
user.<name> at the version draft, so Single, List and Run can draw with it.
Using it again replaces the bundle and reopens the template in use, and
deleting a draft takes its template back out. Export shows the license and
credits, then writes a .mimic through the save dialog, and Show folder opens
the draft's folder.

A card can be saved as a test card with the changes made to its fields, up to
24, and is offered after the built-in presets with no lookup. The coverage
strip draws every test card small and without art through the draft and says
which could not be drawn and why.

Release-As: 2.0.0

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Use in mimic and Export are no longer gated, so the test that expected them
to be locked now checks that none of the finishing buttons carry a gate.

The layout test that clicks the card's corner to pick the background now
clicks the top corner. The Coverage row under the card's view takes a little
height, which left the bottom corner under the activity strip.

The export dialog's problem list drops role="alert" from the list itself.
It overrode the list's semantics and failed the accessibility scan of the
editor-export scene.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…eps around a layer

About moves from the right column to the left, under Template and above Canvas,
and starts collapsed. Search Scryfall moves above Test card and stays collapsed.
The right column keeps Layers, Selection and Problems. The right column's saved
layout is read as the defaults once, and the left column's is kept under a new
key so the old order is not read as the new one.

Canvas has a single Size group with the width and height in px, the resolution
in dpi and the rotation, whose choices are written in degrees. Each unit is
inside its field, at the right. The ratio bracket takes the padlock's color
while the ratio is locked.

Selection shows the name and type on one line, puts a note about a locked or
undrawn item beneath it, and lays X and Y out in one column with Width and
Height in the other. An image's Remove button sits in the image's top right
corner. Every text button is now as tall as the primary one, and a dropdown is
as tall as a text field. Several tooltips and button labels use plainer
wording, Legendary Creature is now Legendary, and Insert Symbol is the name of
the symbol palette's button in every form that has it.

A text box has a Flow group for the layer it steps around, the box it stays
clear of and the gap it keeps. When a box that steps around a layer is
selected, the part of its box that layer covers is hatched on the card, and the
note beneath the name says whether the text steps around the layer, has the
whole box because the card does not draw the layer, or is not reached by it. An
emptied flow is taken out of the layout file along with its last setting.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…view, show the room text has, and copy or save the card's picture

A text box set on its baseline has the line its text sits on drawn across its width, with
an end to drag at each side. Dragging an end stretches the box along the line through a new
stretch operation, which moves one side of a text box in the finished image whatever the
layout's turn and undoes as a resize. Such a box moves and snaps by its baseline, and the
selection reads as the line, with X, Width and Baseline.

Each row of the layers list has an eye that leaves the item out of the preview, or for a
layer the card does not draw, draws it whatever its condition says, which describes the layout
again so the layer has a place on the card. It changes only the preview, and a coverage strip
ignores it. The render and describe requests carry the names, which the engine reads.

A selected text box shows the room it has in the editor's three colors: purple for the room its
text takes, a blue ghost for the room it has left, ending where the card ends it, and red for
what is in its way. The layer it steps around, the box it stays clear of and the layer it moves for
are outlined in red, and where the room it has as the layout gives it meets one, the space it
cannot use, is striped. The same is shown the other way round when such a layer or box is
selected. The marks stay on the card while a box is dragged and follow it, and the names of
them are set in a caption beneath the outline, where no text is. A box on its baseline that the
card moved or narrowed also has the line it ends up on drawn over its own, and a box with an area
has the box it was left tinted. Under the selection's name are notes for what the card did to a
text box, which override of it is in force and how its text fitted.

The view's controls gain an eye that shows and hides these overlays, kept with the layout settings
and on the O key, and Copy image and Save image, which put the picture of the card that is shown on
the clipboard as a PNG or write it to a file. The engine is pinned at 0.19.4 for the fields these
read.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Vertical choice of a text box offers cap-top and cap-center beside baseline. A box set
on either has its line drawn and dragged like a baseline's, and the engine's new Anchor
field on a described item names it, so the line's caption and the selection's third number
read cap top or cap center and a box without an anchor still reads baseline.

An edit that sets a cap anchor names engine 0.19.5, the first release that reads one, in
the template's minEngine, raising an older one, and undoing the edit puts back what was
there. A new template starts with its title and mana boxes on cap-center, so the first edit
of its layout names the release too. The engine is pinned at 0.19.5.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…es changed outside the app

A PNG dropped on the card or on the layers list adds it to the template. A dialog names the file,
offers a name for the part made from it, and asks what the image follows, either nothing, so it
draws on every card, or one of the frame's color slots. It then makes the part with the image as
its first and a layer for it on top of the stack, as one step of undo. Several files dropped are
asked about one after another, and closing the dialog drops the rest. A file that is not a PNG is
said and starts nothing, and a drop on another mode or on a template that cannot be changed does
nothing. The card and the layers list are outlined while a file is dragged over them.

A render of the test card can be asked to draw at the resolution the layout is authored at and not
the preview's. A button in the view's controls does so and asks where to put the picture, leaving
the preview and the card shown as they are. The copy image button and the code behind it are
removed, since Save image writes the same picture to a file.

A draft has a fingerprint of its template files, made from each file's name, size and time, that
does not count the app's own notes in .studio.json. Opening a draft and applying an edit return
it, and Studio.Stamp gives it on its own. When the window comes back to the front, the editor
compares the fingerprint with the one it last wrote or read, and reads the template again if they
differ, which draws the card again and starts its undo history over. Text in the JSON editor that
was not applied is asked about first, and a no leaves it and the files as they are.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… one menu and drop the file drop

Import template, beside New template in the Templates library, installs a .mimic file chosen
from anywhere on the computer. The app checks that it can read the file, that the template's id
looks like owner.name and its version like 1.2.3, and that its manifest loads, then copies it
into the cache the downloaded templates are read from, where it is listed with that version as
installed and not made the active template. Importing a file that is installed already says so,
and a different copy of an installed version asks before it takes that one's place, reopening the
template in use when that is the one replaced.

Save image is one button. When the preview is drawn smaller than the template is authored, it
opens a menu of As shown, which saves the preview at once, and At full size, which draws the card
again at the layout's own resolution and then asks where to put it. Otherwise it saves at once.
The separate full size button is removed.

The dialog for images dropped on the card and the layers list is removed, along with the bridge
call that took a path it had been given. A change to a draft's files made outside the app is still
read again when the window comes back.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Each version in the Templates library that the app keeps, whether it was downloaded, imported or
made available from the editor, has a remove button. It asks first, and says whether the version
can come back: one the catalog lists can be downloaded again, a draft is still in the editor, and
an imported version can return only from its file. Removing deletes the bundle from the cache and
the template's folder with its last one. A version in use is switched away from first, to the
newest other version of the same template, or else to the template the app starts with, or to
none when that is not installed either. A developer folder is not the app's to delete and has no
button, and nothing can be removed while a run is in progress.

A version in the cache that the catalog does not list, such as one imported for a template the
catalog carries, is now listed beside the catalog's versions, so it can be selected and removed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s documentation

The real card fonts that local-fonts held are embedded in the engine, and the m15 source and loose
build that assets held are kept in mimic-templates, which carries the current one. Neither folder is
needed to build, test or run the project now that a template ships its own fonts and the editor makes
and opens templates, so the documentation stops describing them.

The directory tables in the README and CLAUDE.md lose the local-fonts row, the fonts section of the
README names only the -fonts flag as the way to point the app at another folder, the ui README no
longer says a checkout's local-fonts folder can be the active one, and the release notes for the
throughput benchmark say to install mimic.m15 from the Templates tab and not as a loose folder under
assets. The ignore rules for both folders go, along with the assets path in the ui job's change filter,
and local-fonts/README.md is deleted, which was the only tracked file in either.

The code that would still read a local-fonts or assets folder in a checkout is unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A template's id is owner.name and started as user.<name>, which could be changed only by writing
the id in the JSON. The template's details now have an Owner field, the first half of the id, that
rewrites the id with the name after it kept. It takes lowercase letters, digits and hyphens, and a
button beside it fills it in from the author's name. Templates shared as .mimic files can then carry
ids of their own and not all collide as user.<name> when one is imported.

Changing the owner takes back the copy that Use in mimic made under the old id, unless a run is in
progress, and the choices kept for a template, such as its features and the faces assigned to it,
belong to its id and start over under the new one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… app's controls

The visual baselines are drawn by the runner's own browser and fonts, and were last made before
the editor existed. The editor's fourteen scenes now have a baseline in each theme, which
covers its empty and populated states, the selection and the levers, the JSON pane with a problem,
the images grid, the fonts, the coverage strip, the baseline box, and the new template, add and
export dialogs.

The other scenes change where the page does: text buttons and fields share one height across the
app, and the templates library has its import and per-version edit and remove controls in aligned
columns.

Made with the Update visual baselines workflow on the head of the template editor branch.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… is being typed, and say where the launch check's nudge got to

The Linux launch check timed out in the editor flow, waiting for an arrow key to move the title.
The editor ignores keys while focus is in a text control, and some webviews leave the field of a
mode that was left, such as Single's search box, as the active element after it is hidden. A
field that is not drawn is now not taken for one a person is typing in, so the editor's shortcuts
work in that state.

When that step of the launch check times out it now says what holds the focus, how many dialogs are
open, which mode shows, whether undo is on, where the title's rectangle is in the file and what
the toasts say, so a failure in a window with no display to look at can be told apart.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@odevine
odevine merged commit 8f78fc1 into main Oct 15, 2026
6 of 7 checks passed
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