Two unrelated things the bakery hit on the same afternoon.
THE FAVICON was not missing, it was unusable. The head pointed rel=icon at the 1000x1000 logo
PNGs, so a browser fetched 71 KB to paint 16 square pixels, and the mark is a vine branch drawn in
hairlines -- strokes thinner than one pixel at that size -- which arrives as a grey smudge. Replaced
with a real icon set built from one leaf of that branch, filled rather than stroked, because at 16px
a silhouette survives and an outline does not. A midrib was drawn first and cut the leaf into two
pale slivers at tab size, so it went; the tilt, the two points and the stem carry the shape. The SVG
answers prefers-color-scheme itself, which a .ico cannot, so the dark tab strip gets sage on
bakery-900 instead of a glowing cream tile. The .ico is listed first on purpose: a browser takes the
last format it understands, so reversing the two would hand Chrome the bitmap.
PHOTO UPLOADS failed on anything over 1 MB, which is every photo a phone takes. The cause was an
absence: nothing configured spring.servlet.multipart, so Boot's 1 MB default applied and the
container rejected the file with FileSizeLimitExceededException before it reached
ProductPhotoService -- the class whose entire job is turning "whatever came off a phone" into a
resized webp. The pipeline could never run on the input it was written for. Now 15 MB a file and
60 MB a request, the latter because the file input is `multiple`.
The failure was also ugly, and that is fixed separately: parsed eagerly, an over-sized part throws
from inside Tomcat's parameter parsing where no @ExceptionHandler can reach it, so the request died
as a 500 and then died again forwarding to /error, because that forward re-parsed the same too-large
request (the paired "Exception Processing [ErrorPage...]" lines in the log). resolve-lazily moves the
throw into argument binding, where AdminController now catches it and returns the same `problem`
flash the domain's other refusals use. max-swallow-size lets the body be discarded so the browser
receives that redirect rather than a connection reset.
The multipart numbers are asserted rather than trusted, because a default that was never set is
exactly the kind of thing that comes back silently.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>