Skip to content

Let packages supply a mark look (ainotate.looks entry point) - #5

Closed
testy-cool wants to merge 1 commit into
dontpayfull:mainfrom
testy-cool:look-hook
Closed

testy-cool wants to merge 1 commit into
dontpayfull:mainfrom
testy-cool:look-hook

Conversation

@testy-cool

Copy link
Copy Markdown
Contributor

Option 1 from #3: an ainotate.looks entry point, so a package can supply a mark look. The default look stays the only one built in; no font and no neat code here.

What a look package can do

Your four checks:

  • Default pixel-identical. tests/test_golden.py is unmodified. It skips on Linux (needs Helvetica Neue), so we also compared renders of all 12 mark types, light and dark, against main: 52 comparisons, zero differing pixels. Please run the golden tests on your Mac.
  • A failing look falls back to the default with a warning. Covers: not installed, import error, constructor raising, a bad attribute or return value, and raising mid-draw. The image is redrawn from the original spec, so a broken look can't half-draw or change redaction coverage. The warning reaches CLI output, MCP warnings and result.warnings:
    look 'gray' could not be loaded (ModuleNotFoundError: No module named 'gray_look'), so the default look was used
    
  • Agents can pick a look. spec.md has a "Looks" paragraph; the MCP annotate, preview and shoot tools document their look argument.
  • Every file under 500 lines. Largest touched: cli.py and mcp_server.py at 485, render.py at 426. The look plumbing lives in a new looks.py (191).

Picking a look: "look" in a spec, --look, or the MCP look argument; otherwise AINOTATE_LOOK, then look in the config file, then default.

Tests: 617 passed (tests/test_privacy.py::test_dom_fixture fails on Linux on main too). The neat package's 36 tests pass against this branch.

🤖 Generated with Claude Code

People want marks that look different from AInotate's bold default, for
example a calm hand-drawn style. Building each style into AInotate would
add fonts and drawing code to every install. A plug-in point keeps AInotate
small: a look lives in its own package and is used only where installed.

render() now draws every mark through a Look object. The built-in look,
the only one shipped, is `ainotate.looks.Look` (also importable from
ainotate.render). It holds exactly the drawing calls render() made before:
colors, label font and size, labels and text notes, outlines, step badges,
arrows, highlights, the spotlight dim and corner, the redaction fill, the
keycap font, the magnifier line width and the drop shadow. A package
registers a subclass under the entry point group "ainotate.looks" and
overrides what it changes. A look can change a redaction's color, never
its area.

The look is chosen by spec "look" (also --look on annotate and shoot, and
the `look` argument of the MCP annotate, preview and shoot tools), then the
env AINOTATE_LOOK, then `look` in config.toml, then "default", the same
order as the output folder. A look never breaks a render: when it is not
installed, cannot be imported, raises while being constructed or raises
while drawing, the default look is used and the render gets a warning
that names the look and the error, or lists the installed looks. The
warning reaches users like every render warning: result.warnings, the
CLI's WARN lines and the MCP result's warnings. A failure while drawing
redraws the whole image with the default look, so a half-drawn mix never
ships.

A look can never change a redaction or the caller's spec. An installed
look gets its own copies of the page, marks, rects and points, and its
own copy of the page image in every hook that is handed it, so nothing
it changes reaches the render, the redo or the spec. Every value render()
reads from it (attributes such as shadow and stroke, and the results of
color, redact_fill, label_size and keys_font) is read and checked inside
the same guard, so an attribute that raises or a value Pillow cannot
draw ("not-a-color"), or a number that is not finite, is a failure of the
look, with the same fallback; a color name such as "info" is passed on as
the color it names. As a last line, when an installed look is in use and
anything else raises, even AInotate's own code on a value the look gave
it, the image is redrawn from the spec with the default look and a
warning. An error the default look hits as well is AInotate's or the
spec's, and is raised as before.

The spec reference, the renderer notes served as ainotate://spec, the MCP
fallback summary and a description on the MCP `look` parameter say how to
pick a look.

Testing: the default look is pixel-identical. A test pins the sha256 of
a render of every mark type, light and dark, from before this change
(Pillow 12.3.0 / FreeType 2.14.3; other versions skip the pin). The
golden label-position test is unchanged but needs macOS Helvetica and
skips on Linux; run with DejaVu Sans Bold instead, its three specs give
the same label boxes and identical images on origin/main and this branch.
New tests register a fake look and check that it draws, that the flag,
env and config pick it, each fallback with its exact warning, the CLI
and MCP output, the MCP schema text, that looks which change their
geometry, spec data or page image leave redactions and the spec intact,
that a raising attribute, bad hook results, an infinite shadow and a
label size no frame can hold fall back, that a color name works, and that
an error the default look also hits is still raised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
visul added a commit that referenced this pull request Oct 10, 2026
#6)

Rebases testy-cool's #5 onto main: the look hook from #4 is replaced by this version,
which also recovers when a look fails mid-draw. Default renders stay pixel-identical
(per-platform pins kept from #4; golden tests pass on macOS).

Co-authored-by: testy-cool <202153476+testy-cool@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@visul visul closed this in #6 Oct 10, 2026
@visul

visul commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Thanks! #4 had already shipped the first version of the look hook in 0.1.8, so this branch conflicted with main. I rebased it in #6 (merged, with you as co-author): your looks.py replaces the #4 plumbing, and the per-platform pixel pins from #4 are kept so the golden check also runs on macOS. It goes out with the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants