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

.
refactor(http): enter the module context from the route, not the constructor
A controller is a shared service, so its constructor runs once per process while
the page it builds is answered many times. initModule() lived there, and with it
the default translation domain of the page: every request after the first one in a
process rendered with the domain of whichever controller was constructed last, so
pages came out half translated. AdminControllerContext added the first breadcrumb
of the panel from the same place, with the same result. Under FPM the process ends
with the request and none of this shows; RequestIsolationTest has been red on it.

The setup a page needs because of where it lives now belongs to the request cycle:

* every route a module declares is stamped with that module, taken from the path
of the file declaring it (RouteCollectorFactory -> RouteCollection::setModule()
-> Route::MODULE_ATTRIBUTE), and the matcher reports it apart from the action
parameters;
* the kernel enters ModuleContext for the matched route before anything renders,
so a guard rejecting the request answers in the right language too;
* AdminAreaContext (the panel domain and its first breadcrumb) is entered by the
access guards of the panel. The two routes declared outside them keep working:
the login screen needs only the domain of its own module, and the system check
enters the area itself.

ControllerContext and AdminControllerContext are deleted along with all 214 calls
to initModule(). Six news controllers built breadcrumbs in their constructors —
those moved into the actions. ControllerConstructorTest keeps the rule: a
controller constructor declares dependencies and calls none of them.

The .pot files carry no new messages; their source references moved with the code.
.
fix(http): keep the 404 status when the error template fails
A theme may override the error page, and a broken override turned every 404 into a
500. The render is now guarded: the failure is logged and the page degrades to plain
text, so the status a crawler and a browser act on survives.
.
chore: remove what the Plates migration left behind
The translation scanner no longer looks for .phtml files, the bbcode config is
deleted (nothing reads config('bbcode') and the CMS has no BBCode parser), and the
docblocks describing two engines serving pages side by side now describe one.
.
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