Author ui/legal/{privacy,eula}_ru.md, reworked from the source documents: the seller INN is unified (290210610742), contacts unified to the Telegram bot + email + postal address, the EULA governing-law clause leads with Russian law + jurisdiction as residence tiers, the single-binding-language clause is dropped, data collection is scoped to voluntary/support provision, and "Компания" is no longer shout-cased.
Serve both as static pages through the render sidecar, reusing a shared renderLegalHtml generalised from renderOfferHtml: new GET /privacy/ and GET /eula/ (301 from the slashless form), the markdown baked into the image, no backend fetch. Route them at the edge via the @legal caddy matcher with a CI probe asserting each page's canonical URL + the seller INN, so a missing route cannot silently fall through to the landing. Add the two links to the landing footer.
Docs baked in: ARCHITECTURE (renderer legal pages + edge routing), FUNCTIONAL (+_ru) footer, renderer/README routes. Tests: renderer legal.test.mjs, ui renderLegalHtml unit, landing footer e2e.
Robokassa moderation requires the public offer to list every digital good with
its price. Move /offer/ off the static landing container to the render sidecar:
it splices the live catalog price list (§4.4) into the owner-edited
ui/legal/offer_ru.md and renders it with the shared ui/src/lib/offer.ts — one
renderer, no drift, always matching the current catalog with no redeploy.
- backend: /api/v1/internal/offer/pricing (internal, off the edge allow-list)
projects the active catalog into two markdown tables — chip packs priced per
rail (roubles / VK votes / Telegram Stars) and chip-priced values — through
payments.Money so no float reaches the page. Cached in memory: warmed at boot,
marked stale on every catalog mutation, so a served render issues no query.
- renderer: GET /offer/ fetches the tables and substitutes them at the
<#pricing_template#> marker, then renders; offer_ru.md is baked into the image
and marked is bundled from ui. GET /offer -> 301. Only /offer/ is edge-exposed.
- caddy: route /offer/ to the sidecar; drop the now-dead landing /offer/
handlers and the vite emit-offer plugin.
- offer: fill §4.3 (the chip-payment wording) and drop the in-page back link.
- landing footer: a feedback link (the offer's Telegram contact) beside the
offer link.
- docs (ARCHITECTURE, FUNCTIONAL +_ru, renderer README), CI /offer/ probe,
unit + integration + node tests.
The offer is the legal document a purchase accepts, needed before real-money
intake goes live. Convert the source PDF to an editable ui/legal/offer_ru.md and
render it to a standalone static dist/offer/index.html at build (a vite
emit-offer plugin using marked); the landing container serves it at /offer/,
with a bare /offer redirecting in. Add a small centered "Публичная оферта"
footer link on the landing (ru/en) and a CI probe asserting /offer/ serves the
page rather than silently falling through to the landing shell.