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

.
refactor(profile): move the account and settings pages to Twig
The rest of the module: the personal page, the three settings pages, the profile
form, the password pages and the e-mail confirmation. No template of the profile
is left on Plates.

ViewResponse can carry cookies now. Changing one's own password re-issues the
persistent login cookie, and that was the one action that needed a Response of
its own; the cookies travel with the view instead, and the normalizer sets them.

The seven role radios of the profile form are one loop over a list, and the
avatar catalogue link points at /help/avatars/ instead of the legacy /help/?act=
URL it was redirected from.
.
refactor(profile): move the profile pages to Twig
The pages of a user — the profile, the activity, the karma, the bans, the IP
history and the image forms. The account and settings pages stay on Plates for
now.

Escaping moves out of the model and into the templates:

- the user agent is stored as the client sent it and escaped where it is
printed, so the five Plates templates that print it do that themselves now;
- the admin notes lose their SpecialChars cast for the same reason;
- "about" and the website are sanitized HTML, so they are Twig\Markup, and null
when empty.

display_place was still typed string while the place formatter had already
started returning Markup, so reading it raised a TypeError — every profile page
was broken. It is typed markup now.
.
refactor(news): move the public pages to Twig
The two search pages were the same file with a different form, so they share one
template and the form is a component; the article row of a list is another.

text_safe and preview_text_safe hand over Twig\Markup — they are what the
purifier and the media embedder produced — and null when there is nothing, so a
template can still ask whether there is a lead. The home page prints the lead
without a raw filter now.

The tags accessor no longer escapes the tags it returns: the value goes to a
template, which escapes on output, and the one admin form that prints it into an
input does so itself.
.
refactor(album): move the album pages to Twig
Thirteen templates become twelve plus four components: the photo card and the
vote buttons were copied into three pages each, and the grid around them into
two.

The description of a photo is escaped and given its smiley images by the
presenter, so it arrives as Twig\Markup.

The comments page still hands back a string — the legacy Comments class prints a
whole page of its own — while the error pages it may answer with are views.
.
refactor(collections): move the public pages to Twig
ItemContentFormatter returns Twig\Markup: it purifies the item text on output,
which is exactly the contract that lets a template print it as it is. It already
answered null for empty content, so nothing had to be invented for the "no text"
case.

The ten admin templates stay on Plates.
.
refactor(help): move the help pages to Twig
Eight templates become six: the smiley catalogue and the avatar catalogue were
the same file twice, and the footer of a list is a component now.

Escaping moves to the output here too — the catalogues ran htmlspecialchars() and
htmlentities() over the names they passed to the view, which under autoescape
would print the entities themselves.

The picture of a personal smiley is the rendered image tag, so the use case hands
it over as Twig\Markup. The interface of the renderer keeps returning a string:
its other callers are still on Plates and will each decide when they move.
.
refactor(guestbook): move the guestbook pages to Twig
The text of an entry and the reply to it come out of the formatter as
Twig\Markup — they have been through the purifier, the media embedder and the
smilies. A missing reply is null rather than empty markup, so the template can
still tell there is none.

The name of an entry was printed unescaped, including into the argument of an
inline onclick handler; it is escaped now, for JavaScript in that one place.

The editor partial received an already escaped value and printed it raw into an
attribute. It takes the raw value now and escapes once, on output.

The edit and reply pages rendered the editor a second time inside their scripts
block, which emitted a second component with the same id; only the one in the
form is left.
.
refactor(community): move the community pages to Twig
The public result page comes with them: `@theme/pages/result.twig`, named
explicitly by the callers that answer "for registered users only". The condition
that pulled the admin sidebar into it is gone — an admin copy of the page will be
its own file.

The list, the row and the links under a list become components, and the
byte-identical administration template is dropped: users, birthdays and the staff
list are one page with different data.

Two things the old markup hid:

- the row printed a delete link from an attribute nobody sets, so the block never
rendered; it is removed, and with it the "Delete" string of this domain;
- the karma of a row came from an alias the karma query adds, and reading it off
a row loaded any other way would call it as a method; it is computed from the
two real columns instead.

Links between the pages were relative (./search/, ../), which resolved to a
different page depending on the trailing slash of the current URL; they are
absolute now.
.
refactor(contacts): move the contact page to Twig
The first form to move, so two components of the theme come with it: the
validation messages of a field and the consent checkbox. Both print each message
as text and put the line breaks in the template, instead of joining messages with
a tag into one unescaped string.

The sanitized title of a consent is Twig\Markup now, which is what lets the
checkbox print it as it is under autoescape.

The phone of the tel: link is normalized by the DTO rather than by a preg_replace
in the middle of the markup.

The admin pages of the module stay on Plates: templates are dispatched one by
one, so the two halves of a module move independently.
.
docs(agents): add the Twig migration recipe
What the five migrated modules settled: the layout of a migrated module, the
review every output goes through, and the four traps that cost a page each —
the __call() of an Eloquent model, the truthiness of Markup, a layout variable
shadowing the data of the page, and string literals the escaper leaves alone.