История коммитов

.
fix(i18n): drop the tags translations carry and their msgids do not
With autoescape a tag that exists only in a translation reaches the page as
text. Four strings had one, in eight languages: the file integrity hint (pl,
vi), the antiflood message of the mail module (pl, uz) and the unconfirmed
email notice (ar, kk, ky, ro).

The <br> is replaced with a sentence break, so the text reads the same as its
msgid. The dictionaries are regenerated.
.
fix(media): stop wrapping every text in html/body
ImagesPopup parses a fragment of a page, and libxml wraps a fragment in
html/body while parsing. Dumping the whole document put that wrapper into every
text that went through the provider — visible on the home page, in the news and
under every comment.

Two more defects found while fixing it:

- the preview link is assembled by hand, and neither the source nor the alt text
was escaped: a quote in the alt ended the attribute and let the rest of it
into the markup
- an empty text raised a ValueError from DOMDocument, which is not an Exception
and was not caught by the guard around the parser
.
chore(theme): remove the assets of the deleted admin theme
public/themes/admin was the static half of themes/admin, which is gone: no
template, no config and no server rule points at it, and its sprite is a subset
of the one the default theme ships.
.
build(vite): take the entry points from the theme manifests
The list of inputs repeated what themes/<name>/theme.php already declares, so a
new theme had to be added in two places and the build config was the one people
forgot. It is derived from the manifests now, and a manifest naming a file that
does not exist fails the build instead of producing a bundle nobody asked for.

CHANGELOG gains the breaking change for theme authors: what Plates constructs
map to in Twig, where the templates of a theme live now, and the manifest.
.
docs(theme): show the rest of what a theme can override
The example theme explained how to replace a template of a module and stopped
there. It now also lists the layouts, components, system pages, the admin markup
and the emails, and how a theme declares its own build entry point.

Includes the documentation submodule, updated for Twig.
.
docs(agents): rewrite the view rules for Twig
Plates is gone, so the guides that described it are replaced rather than
patched.

- twig-migration.md becomes templates.md: where a template lives, the three
environments, how a page is written, components, the traps, verification
- escaping.md is about autoescape now: Markup for markup by contract, |raw as a
signal, the contexts autoescape does not cover, and no escaping baked into
data
- localization.md and its checklist gain the case a migration kept turning up:
a tag that exists only in the translation, which autoescape prints as text
- new-module.md, pagination.md, architecture.md, the security checklist and
AGENTS.md lose their Plates references; the project structure section matches
the public/ docroot
.
chore(view): remove Plates
Nothing renders through it since the installer moved, so the engine, its
factory, its four extensions, the Plates renderer, the delegating renderer, the
bridge extension and the mobicms/render dependency are gone.

- FilePresenter takes AssetResolver and the news comments take AssetRuntime,
the services that replaced the Plates extensions they used
- BaseController is deleted: nothing extended it
- the tests of the removed classes go with them, and the bridge case leaves
TwigEnvironmentTest
.
refactor(install): move the installer to Twig
The installer gets the environment the plan reserved for it: it runs before
there is a site, so its templates see no visitor, no csrf token and no modules,
only the theme it ships with.

- the five steps and the layout become @install/*.twig; the namespace comes
from InstallTemplatePaths, so a theme can override any of them
- the steps pass their data explicitly instead of pushing it into a shared
engine with addData()
- the repeated field markup of steps 3 and 4 became a macro, and the field
errors go through the component the rest of the site uses

Nothing renders Plates any more; the package stays until the classes around it
are removed.
.
refactor(view): move the error pages and the pagination to Twig, drop Plates from HTTP
The last templates Plates still served become @theme/pages/errors/{403,404}.twig
and @theme/components/pagination.twig, so nothing in the HTTP layer names a
Plates template any more.

- ExceptionResponseFactory, Pagination and PaginationFactory take
RendererInterface; pageNotFound() and PageNotFoundException default to the
Twig page
- RendererInterface is TwigRenderer now: the Plates engine, its renderer, the
bridge extension and the delegating renderer leave the container, and
ControllerContext stops registering template folders
- AdminControllerContext loses the four counter queries it ran on every request
of the panel for a sidebar that is rendered by AdminRuntime now
- themes/admin is deleted whole and themes/default/templates/system with it;
the installer keeps its own engine, so the package stays for now
.
refactor(mail): give the emails their own Twig environment
The six email templates move to @theme/emails/*.twig and are rendered by an
environment of their own: a message has no request behind it, so nothing of the
visitor, the csrf token or the build assets is offered there, and every address
it prints is absolute.

- MailRenderer takes the locale as an argument and puts the translator of the
process back afterwards, so a batch of messages in different languages leaves
nothing behind; EmailSender no longer mutates a shared engine
- a row queued before this change still names its Plates template and is mapped
to the Twig one of the same name
- twig:lint, twig:compile and i18n:scan pick the environment a template belongs
to, otherwise the mail functions read as unknown to them