Commit Graph
8 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 5 c6cebc5000 zoom and pan the lead photograph
The Shape control it got last commit did nothing, and the reason is the trap that
file already warns about: `contentFor` is a per-kind allowlist and everything it
does not name is dropped. `subject-cover` was not in it, so the panel wrote a
ratio, got a 200, and the API stored `{}`. Its own comment says that is "exactly
what silently threw `ratio` away when the admin first sent one".

Now it holds a ratio and a crop, and the block gets the same framing surface the
About Us hero has.

Two things it does NOT get, both deliberate:

  No Change and no Remove — `fixed` on MediaField. The picture is the subject's
  first carousel item and this block cannot store a different one. Framing is its
  decision; which photograph is not.

  No local knowledge of which picture that is. The site answers
  `/pages/:id/subject-lead`, because which template draws which section, which row
  is the example and which of its pictures leads are four facts that live in
  templates.ts — a second copy here is how the two drift when a fifth template
  arrives.

And it says out loud that the crop applies to every row the template draws. It is
framed against one photograph and used on all of them, which is the one
surprising thing about cropping a template rather than a page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 15:19:04 +01:00
pastilhas 4587ce2d39 Revert "frame a photograph on the page, at the size it is printed"
This reverts commit 812371b64a.
2026-09-03 20:47:23 +01:00
pastilhasandClaude Opus 5 812371b64a frame a photograph on the page, at the size it is printed
The panel frames a 21:9 header in a 200-pixel thumbnail while the real one is on screen six inches
away, in the layout it belongs to. Dragging the picture itself is the obvious control, and until now
it was also the expensive one: every new rectangle made the site cut a fresh ladder — nine widths, two
formats, 1.54s measured — with the preview waiting for it.

Both halves are gone. The site draws the rectangle in CSS in development (previous commit, other
repo), so a framing costs nothing to show; the drag here is inline styles on the picture, and the only
write is one PATCH when the hand stops.

Inline styles rather than the site's custom properties, because the site renders a FRAMED picture one
way and an untouched one another — and an untouched photograph is exactly the one somebody is about to
drag. Inline beats both, and beats the object-fit the unframed path leaves behind, which would
otherwise crop the picture a second time inside a box that is already the crop.

Attached to every shaped photograph rather than to the selected one. Selection would put itself in the
paint's dependencies, so choosing a block would repaint the whole page's chrome; this way onDocument
still depends on the sections alone. The cost is that a click could become a nudge, so nothing moves
until the pointer has travelled 3px — below that it is a click, it selects the block, and no crop is
written.

The panel's zoom buttons now reach the real picture too, through `onDraft`. Otherwise the thumbnail
and the page would disagree for the 600ms until the debounce lands, which reads as a broken control
rather than a delayed one.

crop.ts is new and is the reason this is safe: parseCrop had already been copied once, and a crop that
means one thing in the panel and another on the page is a bug you can only feel. MediaField now draws
its own preview through the same `rectToBox`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 20:36:03 +01:00
pastilhasandClaude Opus 5 a4c3cb3644 zoom steps by a flat 0.1x
It multiplied by 1.25, which compounds: the same button was a 0.25x step at fit and a 1.75x step at 7x,
so the first press did almost nothing and the last threw the picture across the frame.

Added rather than multiplied, so every press is the same amount of change — which is what somebody
nudging a crop into place is asking for, and it makes the number beside the buttons predictable, which
a factor never is.

Rounded to a tenth, because floating point turns 1 + 0.1 + 0.1 into 1.2000000000000002 and the label
shows it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 00:08:33 +01:00
pastilhasandClaude Opus 5 80a7d2683f zoom is a pair of buttons, and the wheel belongs to the page
The wheel was the obvious gesture and it cannot be made to behave. To zoom without also scrolling the
panel it has to call preventDefault, which means the page stops scrolling whenever the pointer happens
to be over a picture — and a panel of slots is mostly picture. Not calling it means both happen at
once. There is no third option.

So `+` and `−` on hover, top right, grouped and left of Change and Remove: they are the pair somebody
presses repeatedly, and a destructive Remove sitting between them would be pressed by accident
eventually.

Anchored to the centre of the current window rather than to a pointer, because a button has no pointer
position — and that is what somebody expects from a `+` anyway: the middle of what I am looking at
stays where it is.

The level is shown between them. A press that changes nothing at 1.0x or 8x is then explained rather
than read as a dead button.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 00:07:30 +01:00
pastilhasandClaude Opus 5 84336b8af5 a framing slot renders the original, not a rung
The preview asked `derived/` for a 640-wide rung and drew it about 1900 pixels wide — stretched three
times over, which reads as a poor photograph rather than as the wrong file. Raising the rung only moves
the problem: the box is most of a panel wide and can be zoomed on top of that, so any rung chosen in
advance is chosen wrong, and the widest one available is whatever the ladder happens to hold.

The original has every pixel there is. It is also, often, a HEIC that no browser can decode — which is
what av-club is for, and the platform already renders those for its own file browser through exactly
this path.

So there is a `/original/*` route: it resolves a path inside `originals/`, forwards to av-club's
`/v1/render` with a width, and streams back something a browser can draw. It decodes nothing itself.
That is the standing rule and the reason there is no image library in this plugin.

Not cached on disk, like the platform's own /render and for the same reason: a full-size render is
large, looked at once, and keyed on a width the panel chose. Caching it would fill a disk to make the
second look at one photograph faster.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 00:02:24 +01:00
pastilhasandClaude Opus 5 fe51c70f70 a media slot can be framed by dragging and zooming
A photograph is rarely the shape of the slot it goes in, so something decides which part survives. It
was a centred crop and no way to change it.

Dragging the picture slides the window; the wheel zooms, anchored to the pointer so the thing under the
cursor stays under it. Both write the site's own format — `x,y,w,h` in percentages of the ORIGINAL —
which is resolution-independent and survives the file being re-encoded.

Zooming in is the window getting smaller: w and h shrink together, keeping the slot's shape. Fit is the
floor, because you cannot show more of a photograph than there is, and at exactly fit the crop is
cleared rather than written as the centred value — a null crop and a crop that happens to be centred
should not be two states.

── The preview is not object-cover ──

It draws the rectangle: the picture scaled to `100/w` of the box and offset so the chosen corner lands
at the origin. object-cover shows the largest window of the slot's shape that fits and nothing else —
it can be panned and it cannot be zoomed, so a preview built on it would silently ignore half of what
this control produces.

── Also here ──

buildSite now hands the site an av-club URL and user. That is the first piece of publishing images
cropped rather than cropping them in the browser with CSS: the plugin knows the sidecar registry, the
site knows a URL. Inert until the site uses it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 23:57:33 +01:00
pastilhasandClaude Opus 5 9d3cd7702f a media slot is the picture, at the shape the page will show it
It was a read-only text box holding a path and a Browse button beside it. A path is not what anybody is
choosing — somebody picking the header photograph is choosing a PHOTOGRAPH, and reading
`ABOUT US/HEADER.jpg` to find out whether you picked the right one is doing the computer's job for it.

MediaField is the slot: empty, it is a dashed box at the page's own aspect ratio that you click or drop
onto; filled, it is the picture, with Change and Remove appearing over it on hover. The panel also stops
changing height as things are chosen, so a page with three slots starts to look like the page rather
than like a form.

── The aspect ratio is a prop and has to be ──

The header is 21:9, the product video 16:9, a gallery slot square. The component cannot know that and
must not guess: the SITE decides what shape a slot is, and a component that guessed would be a second
opinion to keep in step with the stylesheet. Both callers name the ratio their partial draws.

── Dropping ──

It accepts a drag from Officer's own file browser, which carries paths under
`application/x-officer-files` — the same payload that moves files between windows. So dragging a
photograph out of Media onto a slot is one gesture and no upload.

It does not accept a file from the desktop. That is an upload into originals/, a different operation
with its own failure modes, and the Media section already does it properly.

── Also ──

`thumbUrl` was copied into GalleriesPanel and RecordsPanel and was about to be copied a third time. It
is a module now: three copies of a URL shape is three places to be wrong about the rung when the ladder
changes, which it did four times in a day.

A file that has gone since it was chosen falls back to the empty state rather than a browser's
broken-image glyph, and says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 23:43:12 +01:00