POST /api/contact posted { to, subject, body, replyTo } to ${mailApiUrl}/send,
which the mail service (libreshop/mail src/app.py) does not have, so every
message failed with a 500. It now posts { to_email, subject, message } to
POST /v1/send/message, still to paperwork@muellerprints.de, with the same
subject and text as before. The service sets no Reply-To; the sender's
address stays in the text. A line break in the subject is replaced by a
space, so it cannot start another mail header.
mailApiUrl was read from runtimeConfig without being declared there, so
NUXT_MAIL_API_URL could not set it and the fallback http://mail:2222 was
always used. It is declared now, with that default; mp's compose.yml sets
no NUXT_MAIL_API_URL.
contactMail and mailSendUrl (server/utils/contactMail.ts) are pure and tested.
Refs libretech/mp#71
The order routes (get, put, add-product, remove-product, checkout, capture)
call forwardToCms, which throws a CMS error on as
createError({ statusCode, statusMessage, data: { message, errors?, missing? } }),
built by the pure shopErrorFromCms (server/utils/cmsError.ts).
Before, the FetchError was thrown on as it was: the browser got the CMS's
status, but Nitro treated it as unhandled, answered "Server Error" without
data and logged every CMS 4xx as [unhandled]. Now the browser also gets the
CMS's message, the errors of a rejected update and the fields a checkout
misses, and no other field. A status that is the shop's own fault (401, 403,
...) is answered 500, a CMS that does not answer 503; 5xx are logged without
the query and the order uuid.
npm test runs tests/unit with Node's type stripping and no dependencies, as
in libreshop/cms. nuxt.config keeps tests/ out of the app's type check.
Refs libretech/mp#71
Recovered work in progress that sat uncommitted since 2026-05: a
SelectionBox renders as a plain div instead of a NuxtLink when locked,
so a variant with only one choice reads as decided rather than
clickable, and it reports aria-checked in that state. useProductContent
gained the content lookups the details and index pages now use, the
cookie banner says Schließen instead of Akzeptieren, the Über uns
header entry is gone, and ProductCard's hover shadow no longer relies
on a short-circuit that could yield a non-array.
Source moved verbatim from mp/shop/ on 2026-04-29; mp was the first
concrete adapter consuming the libreshop toolkit. Builds and publishes
git.librete.ch/libreshop/shop on every main / v* push via the standard
.gitea/workflows/build.yml shared across libreshop components.