<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Phase 5 — CSS Foundation and Viewport — Arc Plan</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 760px;
margin: 1.5em auto; padding: 0 1em; line-height: 1.5; color: #1a1a1a; }
h1 { font-size: 1.4em; }
h2 { font-size: 1.1em; margin-top: 1.4em; }
ol { margin: 0.3em 0; } li { margin: 0.25em 0; }
ol.items { list-style: none; padding-left: 0; }
ol.items.numbered { list-style: decimal; padding-left: 2em; }
.check { color: #1a7f37; font-weight: bold; }
.next { color: #b35900; font-weight: bold; }
.review { color: #0969da; font-weight: bold; }
.nope { color: #82071e; font-weight: bold; }
.q { color: #8250df; font-weight: bold; }
code { background: #f2f2f2; padding: 0 0.25em; border-radius: 3px; }
em { color: #555; }
table { border-collapse: collapse; margin: 0.6em 0; }
td, th { border: 1px solid #ddd; padding: 0.2em 0.6em; text-align: left; }
</style>
</head>
<body>
<h1>Phase 5 — CSS Foundation and Viewport</h1>
<p><strong>Arc Post-Mortem Summary.</strong>
This arc rebuilt the stylesheets' foundation and made pages answer the
width they are given. It named the repeated colors, lengths, and control
heights as custom properties (I), said lengths in rem and clamp rather than
inches (II), and used logical direction properties so right-to-left falls
out for free (III). It moved the phone-versus-desktop split from a
user-agent guess into width-based media queries and added a short-height
media query so a phone in landscape gets the compact layout too (IV), and
added a built-in light/dark mode following the visitor's preference along
with reduced-motion and focus-visible accessibility handling (V). It closed
the interface fixes handed over from Phase 4.5 — the resource
toolbar's uneven control heights, its layout on a phone, and the Add Search
dialog (VI). Alongside the planned work it fixed a run of live-site issues:
a large-folder listing timeout, a wiki title, several mail-server
out-of-memory paths, a served-static-folder whole-file read, and the
static-folder directory-index links, and it added a memory-fatal log that
names the request the always-on server dies on. Two follow-ups were settled
as not worth doing: an automated accessibility checker beside the existing
one, and a visitor-facing theme picker.</p>
<p><em>Arc started 2026-07-17, after Phase 4.5. Sections are Roman numerals
and steps within them are lettered; each step's title says what it does,
followed by a sentence on what was proposed and, once worked, a few on what
was done and anything learned. Marker legend:
<span class="next">▶</span> being worked on,
<span class="review">⚑</span> implemented and waiting to be reviewed,
<span class="check">✓</span> done,
<span class="check">✓</span><span class="nope">✗</span>
closed by deciding not to do it, <span class="q">?</span> open decision,
unmarked = planned.</em></p>
<h2>What this arc is for</h2>
<p>Yioop decides how it looks in two places at once. The server reads the
user agent and branches on <code>$_SERVER["MOBILE"]</code> in 101 places
across 45 files, sending different markup to a phone; the stylesheets then
branch again on a <code>.mobile</code> class in 290 places. A page therefore
cannot answer the width it is actually given, only the width the server
guessed from a name. The viewport meta tag that would let a phone say how
wide it is is emitted only when the server has already decided the reader is
on one (<code>WebLayout.php:100</code>), because emitting it always was tried
and reverted: the layout is sized in inches and points and runs off the side
of a phone when the tag is honoured.</p>
<p>So the units come first, then the layout can flex, then the tag can be
told the truth, and then the branches can go. That order is why item 25 was
moved out of Phase 0 and why it sits at the end here rather than the start.
The measurements below were taken on 2026-07-17 at the start of the arc;
the master to-do's figures predate several arcs and read lower.</p>
<table>
<tr><th>What</th><th>search.css</th><th>all css</th></tr>
<tr><td>lengths in <code>in</code></td><td>309</td><td>340</td></tr>
<tr><td>lengths in <code>pt</code></td><td>138</td><td>169</td></tr>
<tr><td>lengths in <code>px</code></td><td>1171</td><td>1736</td></tr>
<tr><td>lengths in <code>rem</code> or <code>em</code></td><td>32</td>
<td>33</td></tr>
<tr><td><code>.html-ltr</code> / <code>.html-rtl</code> selectors</td>
<td>110</td><td>124</td></tr>
<tr><td>logical properties already used</td><td>—</td><td>18</td></tr>
<tr><td><code>.mobile</code> selectors</td><td>—</td><td>290</td></tr>
<tr><td>custom properties defined</td><td>0</td><td>0</td></tr>
<tr><td>distinct hex colors</td><td>—</td><td>111</td></tr>
</table>
<h2>I. Name the things the stylesheets repeat</h2>
<p><em>Master to-do item 26. Foundational: items 9, 17, 18 consume these,
and sections II and IV below cannot be done tidily without them.</em></p>
<ol class="items">
<li><span class="check">✓</span> A. <strong>Gather the colors into
custom properties.</strong> Name the palette on <code>:root</code> and
replace the literals. Four parts:
<ol>
<li><span class="check">✓</span> The four a site sets for itself
were written into every page's head
along with the selectors using them. Those rules now live in
search.css and a page sends only the values, cutting its style block
from 1418 bytes to 247.</li>
<li><span class="check">✓</span> The grays became a ladder from
<code>--gray-0</code> to
<code>--gray-100</code>, numbered by lightness, replacing 132
literals in search.css.</li>
<li><span class="check">✓</span> The named colors followed: 175
uses of <code>black</code>,
<code>white</code>, <code>gray</code> and their kin in search.css,
and 48 more with 139 hex literals across mail.css, messages.css,
frise.css and editor.css, which read the same ladder.</li>
<li><span class="check">✓</span><span class="nope">✗</span>
The hued colors stay literals: 43 of them over 60 uses, nearly all
one-offs, and they want naming as roles — error, link, success
— which is a design decision rather than a rounding.</li>
</ol>
A rung was kept for every neutral used three or more times, so almost
nothing moved: the largest shift anywhere is 7 of 255, and tinted grays
such as the <code>#e9e9ed</code> button face stayed literals rather than
being flattened, which a pixel comparison caught a first attempt
doing.</li>
<li><span class="check">✓</span> B. <strong>Name the spacing and
the type sizes.</strong> So that section II has something to convert
the inches and points <em>into</em> rather than converting each one by
hand. Two parts, and only one of them was worth doing:
<ol>
<li><span class="check">✓</span> The type sizes are named:
<code>--font-size-1</code> through
<code>--font-size-7</code>, smallest first, with
<code>--font-size-base</code> naming the 12pt that body text is set
in and that a browser would have chosen anyway. 160 declarations
across the five stylesheets now read them, leaving 19 rare sizes as
literals. Six pages render identically.</li>
<li><span class="check">✓</span><span class="nope">✗</span>
The spacing is not named, because there is no scale there to name.
Margins and paddings use 1, 2, 3, 4, 5, 6, 8, 10 and 12 pixels, and
the odd steps are not strays: 3px is used 53 times and 5px 69. A
<code>--space-1: 1px</code> would rename a number to a number, and
snapping the odd steps onto an even ladder would move 122 pieces of
layout for tidiness alone.</li>
</ol>
Naming the type sizes is what pays: saying type in a unit a screen
understands rather than a printer is now a change to seven lines in
<code>:root</code> instead of 160 declarations, which is the whole
reason this section comes before section II.</li>
<li><span class="check">✓</span> C. <strong>Name the control
height the toolbars share.</strong> The 32-pixel height of an icon
button was written out eight times in search.css and is why a row of
controls that ought to line up does not. <code>--control-height</code>
now names it: how tall a thing you can press or type into stands, an
icon button, a select, a search field. The eight are the icon button
and its container, the button group, the filter field on a phone once
it has focus, and four of the git issue controls; the one 32px left is
a glyph's size, which is not a control. Six pages render identically,
and section VI.A can now say what it means when it makes the resource
toolbar one row of one height.</li>
</ol>
<h2>II. Say lengths the way a screen understands them</h2>
<p><em>Master to-do item 29. A phone cannot honour an inch; this is what
made the viewport meta tag unusable.</em></p>
<ol class="items">
<li><span class="check">✓</span> A. <strong>Convert the inches and
points to <code>rem</code>, <code>em</code>, <code>%</code>,
<code>ch</code> and <code>vw</code>.</strong> Every inch across the
stylesheets is now <code>rem</code>, at six rem to the inch, which is
an inch's ninety-six pixels over a rem's sixteen: 309 in search.css and
31 more in the editor, frise, mail and messages sheets. That ratio
keeps the rendered size exactly as it was at the default type size
while letting it grow when a reader enlarges their text, which is the
point of the change; six pages render to the pixel. The points went
with the type scale in I.B, save the scale's own defining values in
<code>:root</code>, which stay points because that is where the sizes
are set. The wide page-content widths are in <code>rem</code> like the
rest; whether some should instead be a share of the viewport is II.C's
question, left open there rather than pre-empted here.</li>
<li><span class="check">✓</span> B. <strong>Let the sizes that
should breathe use <code>clamp()</code>.</strong> Two things now grow
with a wide screen and hold still on a normal one, bounded so neither
runs away:
<ol>
<li><span class="check">✓</span> The seven type sizes. Each is a <code>clamp()</code> of its present
size as a floor, a middle that grows once the viewport passes an
onset of 1100px, and a ceiling a few points above. Body text is 16px
at 1100px and below, 17.7px at 1440px, and 20px from 1920px up.</li>
<li><span class="check">✓</span> The page-content columns — body, search, and activity. Each
keeps its designed width up to the onset and may grow toward the
viewport beyond it, capped: the body column is 57rem to 1100px, about
67rem at 1440px, and 72rem from 1920px up.</li>
</ol>
Two named tokens carry the shape, <code>--fluid-onset</code> (the
1100px width below which nothing changes) and <code>--fluid-rate</code>
(how fast a size grows past it). At and below the onset every value
equals what it was, so a normal window is untouched; the growth is on
wider screens. This is the first Phase 5 change that alters rendering
by design rather than holding it identical, so the static renderer here
cannot check it — <code>wkhtmltoimage</code> does not resolve
<code>vw</code> against a set width — and the sizes were verified
by arithmetic across viewports rather than by pixel diff. It wants a
browser's eye at a few widths.</li>
<li><span class="check">✓</span> C. <strong>Decide what to do
about the fixed pixel lengths.</strong> A pixel is a screen unit and is
not wrong the way an inch is, so the rule Chris settled was to keep
what draws a line and convert what holds space:
<ol>
<li><span class="check">✓</span> Borders, border widths, border radii, and box shadows stay in
<code>px</code>: they draw the interface's lines and edges, which
should not thicken when a reader enlarges their text.</li>
<li><span class="check">✓</span> Padding, margin, gap, width, height, and the positioning offsets
of three pixels or more become <code>rem</code> at sixteen pixels to
the rem, so the space around and between things grows with the
reader's type. 799 lengths in search.css moved this way, every one
dividing cleanly since sixteen is a power of two, so the size is
identical at the default and six pages render to the pixel.</li>
<li><span class="check">✓</span> The one- and two-pixel lengths stay <code>px</code>: they are
hairline nudges that align an edge, not space that should scale, and
rounding them into rem would move them by a rem's rounding rather
than hold the line they were placed on.</li>
</ol>
</li>
</ol>
<h2>III. Say directions the way a document understands them</h2>
<p><em>Master to-do item 27. Independent of I and II, and can be worked in
parallel or dropped without affecting them.</em></p>
<ol class="items">
<li><span class="check">✓</span> A. <strong>Replace the paired
<code>.html-ltr</code>/<code>.html-rtl</code> rules with logical
properties.</strong> Across the stylesheets the mirror pairs are now
single rules: where left-to-right said <code>float: left</code> and
right-to-left said <code>float: right</code>, one rule says
<code>float: inline-start</code>, and the same for
<code>margin-inline-start</code>, <code>padding-inline-end</code>,
<code>inset-inline-start</code>, <code>text-align: start</code> and the
rest. 43 pairs collapsed in search.css and six more in mail.css and
messages.css, the last two a directional border radius named with the
logical corner properties (<code>border-end-end-radius</code> and its
siblings); editor.css and frise.css had none.
<ol>
<li><span class="check">✓</span> Each merged rule keeps both the <code>.html-ltr</code> and
<code>.html-rtl</code> selectors in its list rather than dropping the
prefix. Dropping it lowers the rule's specificity by a class and lets
other rules win that did not before, which broke the layout when
first tried; keeping both holds the specificity exactly while the
logical property does the direction.</li>
<li><span class="check">✓</span> Eight rules in search.css stay a scoped pair on purpose: three are
not mirrors (a pagination cell with a thicker border and a centered
label in one direction only; a small feed body inset differently each
way; a side advert at a different top offset each way), and the
machine-details arrow is a right-to-left glyph, content a logical
property does not cover.</li>
<li><span class="check">✓</span> The renderer here cannot check this: its old engine ignores logical
properties outright, so a pixel diff is meaningless. Correctness was
checked by resolving every logical property back to its physical
value for a left-to-right reading and confirming each rule sets
exactly what it set before, and by resolving the corner radii for
both directions; right-to-left follows from the symmetric mapping and
wants a browser with a right-to-left locale to confirm.</li>
</ol>
</li>
</ol>
<h2>IV. Let a page answer the width it is given</h2>
<p><em>Master to-do items 23 and 25. This is the point of the arc; I and II
exist so that this can be done without the layout running off the side.</em>
</p>
<ol class="items">
<li><span class="check">✓</span> A. <strong>Move the
<code>$_SERVER["MOBILE"]</code> branches into media queries.</strong>
108 branch sites across 49 PHP files (the earlier 101/45 count was
close). File by file, and each one is a decision about markup as much
as CSS: a branch that emits different <em>content</em> is not the same
as one that emits a different <em>arrangement</em>, and only the second
can become a media query. Too large for one patch, so in substeps:
<ol>
<li><span class="check">✓</span> <strong>Triage all 108
sites</strong> into content-diff (stays a PHP branch),
arrangement-diff (becomes a media query), and logic-diff (neither
— a real behavior choice); record the verdict per site so the
conversion patches have a settled list. The full table is in
<a href="IVA-mobile-branch-triage.html">IVA-mobile-branch-triage.html</a>:
of the 108, after moving the small-data column-hides to CSS per
Chris's filesize rule, 52 are arrangement and become media queries
in the next part, 36 are content and 18 are logic and stay PHP, and
2 are the viewport meta that IV.B removes. Done.</li>
<li><span class="check">✓</span> <strong>Convert the
arrangement-diff sites to media queries.</strong> Done in one pass
against the settled triage. The conversion reuses the codebase's
<code>@media (max-width: 600px)</code> block with four utilities:
<code>hide-narrow</code> (a wide-only column or cell, always emitted
and hidden below 600px, with the spacer-row <code>colspan</code>
made the constant desktop count), <code>show-narrow</code> (a
phone-only <code><br></code> or clear div, hidden by default
and shown below 600px), <code>center-wide</code> (centered only
above 600px), and a <code>configure-lang-shift</code> class for the
one language-label offset. The column-hides across Manageclassifiers,
Managelocales, Crawlstatus, Manageusers, Managegroups,
Manageadvertisements, Searchsources, and Wiki resources became CSS,
as did the clear divs, footer and result <code><br></code>s,
the center swaps, and the configure nudge. Doing the work moved a
set of sites out of arrangement: a branch that emits different
markup — a <code><br></code> versus a
<code></td><td></code> cell break, or a
<code></tr><tr></code> row-break in the classifier and
locale edit forms — cannot become a media query without first
making the table one shape, so those stay PHP, and so does the
pagination gap-versus-full-list. The triage records the final split:
35 arrangement (converted), 45 content, 25 logic, 2 viewport, 1
body-class. Done.</li>
<li><span class="check">✓</span> <strong>Resolve the mixed
sites</strong> — the ones that expressed a phone layout as
different markup, a <code><br></code> or a
<code></tr><tr></code> row-break where desktop used
table cells. Each is split into the part that stays PHP and the part
that becomes CSS by emitting one markup shape — the desktop
cells — and adding a <code>stack-narrow</code> class whose
cells become <code>display: block</code> below 600px, so the row
stacks on a phone as the row-break used to. It covers the search form
(<code>SearchformHelper</code>, its <code>$item_sep</code> now a
constant cell break), the classifier and locale edit forms
(<code>EditclassifierElement</code>, <code>EditlocalesElement</code>),
and the group-users cell (<code>ManagegroupsElement</code>); the
submit-row and status spacers are always emitted and hidden narrow.
No <code>MOBILE</code> arrangement branch remains. Done.</li>
</ol></li>
<li><span class="check">✓</span> B. <strong>Emit the viewport meta
tag always.</strong> The head emitted
<code><meta name="viewport" content="width=device-width"></code>
only when the request was sniffed as mobile, so a narrow screen that was
not sniffed kept a desktop-width viewport and never triggered the new
media queries. The gate is removed at <code>WebLayout.php:100</code> and
at the upgrade page's own head in <code>VersionFunctions.php</code>, so
the viewport is always declared and the width, not the user agent,
decides the layout. With A's media queries in place this is safe; the
<code>.mobile</code> class rules still coexist until C retires them.
Implemented, waiting on review.</li>
<li><span class="check">✓</span> C. <strong>Retire what is left of the <code>.mobile</code>
class.</strong> 290 selectors depend on it. Whatever survives A and B
is either a real content decision or a rule that should have been a
media query. In substeps:
<ol>
<li><span class="check">✓</span> <strong>Inventory the
<code>.mobile</code> selectors that remain after A and B</strong>
and sort each into remove, fold into a media query, or keep as a
real content marker. The full list is in
<a href="IVC-mobile-selector-inventory.html">IVC-mobile-selector-inventory.html</a>:
246 rules across the four stylesheets (search.css, mail.css,
frise.css, messages.css). 243 fold into the existing
<code>@media (max-width: 600px)</code> block with the
<code>.mobile</code> prefix dropped — the media query already
means the narrow screen the class stood for — and 3 are
removals where a non-mobile sibling in the same rule already
applies. None are genuine content markers to keep. Implemented,
waiting on review.</li>
<li><span class="check">✓</span> <strong>Retire them by that
inventory.</strong> Each <code>.mobile X</code> rule in
search.css, mail.css, and messages.css was folded into that file's
<code>@media (max-width: 600px)</code> block as <code>X</code>
(mail.css and messages.css had no such block, so one was made), and
the 3 redundant rules lost their <code>.mobile</code> selector. The
layout's inline <code>body.mobile</code> and
<code>.mobile .top-landing-spacer</code> styles became media
queries too, and the <code><body></code> no longer carries the
<code>mobile</code> class. A narrow-width render with the old class
and the new media query matched. The content and logic branches keep
reading the boolean <code>$data['MOBILE']</code>, which is a separate
value set earlier and untouched. Implemented, waiting on
review.</li>
</ol></li>
</ol>
<h2>V. Themes and readers</h2>
<p><em>Master to-do items 28 and 30. Both consume section I.</em></p>
<ol class="items">
<li><span class="check">✓</span> A. <strong>Built-in light and dark modes.</strong>
<code>color-scheme: light dark</code>, <code>light-dark()</code>,
<code>prefers-color-scheme</code>. Nothing in the stylesheets mentions
any of them today. This is not a theme in the
<code>getThemeNames()</code> sense — those are site-specific CSS
files that override Yioop's built-in styles (the V.D picker). A mode is
the built-in styles themselves carrying a second value for each named
color: both modes live in search.css, and the named colors from I.A get
a light value and a dark value through <code>light-dark()</code> so the
one stylesheet answers either. The toggle is got differently from a
theme file — the browser's <code>prefers-color-scheme</code> with
<code>color-scheme</code> on the root, not a linked override sheet. Needs
I.A first, since a mode is a second set of values for named colors and
there is nothing to name them against yet. A per-site switch gates it:
a checkbox in Appearance, stored as a new domain profile field beside
the existing color fields (<code>SystemComponent</code>'s
<code>APPEARANCE_DOMAIN_FIELDS</code>, written by
<code>ProfileModel::updateProfile</code>, so no schema change), lets a
site turn light/dark on or off globally; off leaves the site in its
single built-in appearance as today. The field is given its default
through <code>nsconddefine</code> in Config after Profile.php loads, the
way the other profile-backed appearance fields are, so under the
persistent WebSite server the toggle takes effect from
<code>p()</code>'s cache on save rather than needing a restart on an
install whose profile predated the field. Implemented, waiting on
review.</li>
<li><span class="check">✓</span> B. <strong>Accessibility-aware
media features.</strong> A <code>prefers-reduced-motion: reduce</code>
block in search.css now cuts the animation and the transitions this
file sets to instant, the way mail.css already did for its one
spinner, so a visitor who asks the system for less motion is not given
the sliding panels or the spin. The <code>aria-label</code> hover
tooltips became <code>:focus-visible</code> as well as <code>:hover</code>
so a keyboard visitor gets the tooltip on tab focus while a mouse click
no longer flashes it. WCAG 2.2 AA contrast was checked with the ratio
formula on the landing and an admin page in both light and dark: every
text node meets its 4.5:1 (or 3:1 for large text) threshold. Left for
later, since AA already holds and each carries regression risk out of
proportion to the gain here: <code>prefers-contrast</code> and gating
the many <code>:hover</code> rules behind
<code>@media (hover: hover)</code>. Implemented, waiting on review.</li>
<li><span class="check">✓</span><span class="nope">✗</span>
C. <strong>Add a checker to sit alongside the WAVE check.</strong>
axe-core or Lighthouse. Settled as not worth doing: both re-report what
the existing WAVE check and the manual WCAG 2.2 AA pass in B already
cover, so a third checker would add maintenance without finding more.</li>
<li><span class="check">✓</span><span class="nope">✗</span>
D. <strong>Let a visitor pick the site theme.</strong> Distinct from the
light and dark modes of A: those are the built-in styles in two shades,
while these named themes are whole CSS files that override the built-in
styles. The site already
owns a list of named themes — each a CSS file under the theme
folder, listed by <code>ProfileModel::getThemeNames()</code> and today
chosen per domain by an admin. This gives that choice to the visitor, in
the two places a visitor already changes how the site looks and reads:
<ol>
<li>A theme control in the landing page's hamburger menu, in the same
Settings group as the language control
(<code>SearchmenuElement</code>, beside the <code>language</code>
element it already renders), and a matching one in a signed-in
user's account settings next to the language row
(<code>ManageaccountElement</code>, after the
<code>options_element_language_label</code> row). Both offer the
same <code><select></code> of <code>getThemeNames()</code>.
Proposed.</li>
<li>Persist the pick the way the neighboring settings already persist:
in <code>$_SESSION['THEME']</code> for a visitor who is not signed
in, following the <code>SAFE_SEARCH</code> and
<code>OPEN_IN_TABS</code> pattern in
<code>SearchController</code>, and for a signed-in user in the
per-user session blob the <code>USER_SESSION</code> table already
holds (<code>UserModel::getUserSession</code> /
<code>setUserSession</code>), where locale-style preferences already
live, so it follows them between devices with no schema change.
Proposed.</li>
<li>Apply it through the seam the layout already has:
<code>WebLayout</code> reads <code>$data['PAGE_THEME_NAME']</code>
to choose which theme stylesheet to link, falling back to the
domain's <code>AUXILIARY_CSS_NAME</code>. Feeding the visitor's pick
into <code>PAGE_THEME_NAME</code> (account value when signed in, else
the session value, else the domain default) makes it the visitor's
theme without a new emission path, and leaves a wiki page's own
theme, which sets the same field, untouched. Proposed.</li>
</ol>
</li>
</ol>
<h2>VI. The interface fixes Phase 4.5 handed over</h2>
<p><em>These were tried in Phase 4.5 on 2026-07-17 and put back: fixing them
there meant adding the <code>.mobile</code> branches item 23 removes and the
<code>in</code> units item 29 removes. They are small, they are the reason
this arc's order is what it is, and they are worth doing as soon as I and II
land, since they are also the proof that I and II were done right.</em></p>
<ol class="items">
<li><span class="check">✓</span> A. <strong>Make the resource
toolbar one row of one height.</strong> The toolbar's controls came out
at different heights and on different lines, and the sort and overflow
dropdowns were pushed down by a fixed nudge to fake the alignment. Every
control now takes the one named control height and centers on the middle
line, so the row is a single clean height. The row stays end-aligned as
it was; only the buttons were fixed. Implemented, waiting on review.</li>
<li><span class="check">✓</span> B. <strong>Make the phone layout
trigger in landscape, not only portrait.</strong> The compact layout was
gated on <code>max-width: 600px</code> alone, which a phone held sideways
never hit: it is wide (around 850px) but short, so it fell to the desktop
layout and the desktop min-width ran off the side and clipped the page.
The gate is now <code>(max-width: 600px), (max-height: 480px)</code>, so
a short landscape phone gets the compact layout while a landscape tablet,
which stays well above 480px tall, keeps the desktop one. Measured, an
844×390 view went from overflowing at 1056px to fitting its 844px,
and portrait, a landscape tablet, and the desktop are unchanged. The
portrait toolbar can still fall to a second row when a long folder name
and every control will not fit the width together; holding it to one row
would mean shrinking the controls, so the wrap is left as it was.</li>
<li><span class="check">✓</span> C. <strong>Give the Add Search
dialog the shape of a dialog.</strong> It was built in
<code>wiki.js</code> with a heavy sixteen-pixel green border, a fixed
width fighting left and right insets, and a fixed height, so it looked
unlike any other dialog and was cut off on a phone. It now takes the
same shape as the standard light-content dialog: a rounded box with the
sidebar-color border, centered, sized to fit its content and held within
the viewport so it no longer runs off a phone. A poor line break in the
related Word Filter Preferences title — the bracketed Factory
Settings link splitting across two lines — is fixed in the same
pass by keeping the bracketed link together on one line. Waiting on
review.</li>
</ol>
<h2>Order and dependencies</h2>
<p>I before II (there is nothing to convert lengths <em>into</em> until the
scale is named), I and II before IV (the tag is only safe once the layout
can flex), I before V (a theme is a second set of values for named colors),
and I and II before VI. III stands alone and can go at any point. IV.B is
the last thing in the arc.</p>
<h2>Other work accomplished</h2>
<ul>
<li><span class="check">✓</span> <strong>A static HTML folder's
directory index links to, and serves, its files by their static
path.</strong> A page whose resources are served as a static HTML folder
listed each file with a wiki resource link that opened it in the viewer's
frame, and the matching static path returned Not Found. Two things were
wrong: the listing built resource links regardless of the static-folder
setting, and the static path was never url-decoded, so a file named with
spaces was looked for under its "+"-encoded name and missed. Now every
entry in a static-folder listing links to its own static path under the
page, and the served path decodes each segment the way the resource
server already does, so following a link fetches the file directly. The
decode splits an encoded slash back out before the "." and ".." checks,
so it cannot be used to step outside the page's folder. Waiting on
review.</li>
<li><span class="check">✓</span> <strong>A served static-folder file
no longer reads the whole file into memory, and the server names the
request that runs it out of memory.</strong> When an editor viewed a
page's served static HTML folder, the file was read whole and then
copied again to append an edit button, so a large served file could
exhaust the always-on server; it is now streamed in blocks with the
button emitted after, the way the same method already served the file
for a reader. Separately, the server now records the request it is
handling and, if it dies of a memory-limit fatal mid-request —
which the after-the-request memory log cannot catch because it never
runs — writes one "[website-memory-fatal]" line naming that
request and the peak memory, using a small reserve freed inside the
handler so there is room to write it. This was added to catch a live
out-of-memory whose cause the existing logs did not show. Waiting on
review.</li>
<li><span class="check">✓</span> <strong>The Appearance screen has
a help link.</strong> The Appearance activity now shows a "?" help button
at the top of the screen, floated to the end so the first control sits
beside it rather than below an empty row, that opens a help page
explaining the site name, colors, logos, landing page, theme, custom
stylesheet, and per-domain settings. No heading was added, since the
activity name already appears above. The light/dark checkbox is relabeled
from "Offer Light/Dark Mode" to "Light/Dark Mode Ready". The help page
text is delivered as a file to paste in through the wiki, since the help
pages are generated and not edited in place. Waiting on review.</li>
<li><span class="check">✓</span> <strong>The site lays itself out
by the width of the window, not by guessing the device.</strong>
Retiring the old phone-versus-desktop switch left a few narrow-screen
pages broken: the logo, the mail and message panes, the sign-in links,
and the search page all misbehaved on a small window. Each now responds
to the window width instead, and was checked in a real browser at a
phone width so nothing runs off the edge. Waiting on review.</li>
<li><span class="check">✓</span> <strong>A folder with a thousand
files lists again, and a request that runs too long ends on a proper
page.</strong> Listing a large folder of videos had become so slow it
timed the page out, because the site was reading every file's contents
just to name its type. It now names the type the quick way as it used
to, and a request that does run past its time limit ends on a short
"timed out" page instead of a bare error.</li>
<li><span class="check">✓</span> <strong>A wiki page shown without
signing in now shows its own title.</strong> Public wiki pages, including
a site's landing page, had their title mangled into "Site Name - Title"
instead of just the page's own title. They now show the title exactly as
the page names it, the same as when you are signed in, and a page with
no title of its own falls back to a sensible "Site Name - Page Name".</li>
<li><span class="check">✓</span> <strong>The mail server turns away
an oversized login instead of crashing.</strong> A sign-in that claimed
to be far larger than any real password could make the mail server eat
memory until it fell over. It now refuses an over-long login before
reading it, and keeps a hard ceiling on incoming data so no future path
can be flooded the same way.</li>
<li><span class="check">✓</span> <strong>The account bar and the
group bar are laid out separately.</strong> A change that lined up the
account bar's home button had knocked the busier group bar out of line.
Each bar now has its own layout, so both sit correctly.</li>
<li><span class="check">✓</span> <strong>The home button in the top
bar lines up with the activity name beside it.</strong> The little house
icon sat lower than the words next to it. The bar now centers everything
on one line, so the house, the dash, and the name all sit level.</li>
<li><span class="check">✓</span> <strong>The mail server no longer
runs out of memory sending a large mailbox to a slow reader.</strong>
Fetching a whole large mailbox to a client that accepted it slowly could
pile the entire mailbox into memory and crash every connection. The
server now waits for the slow reader to catch up, so memory stays small
and a slow send shows up in the log instead of as a crash.</li>
<li><span class="check">✓</span> <strong>A group's name lines up with
the counts beside it.</strong> On the account and groups pages a group's
name sat a few pixels below the post and thread counts next to it. It is
no longer pushed down, so the name and the counts now sit on the same
line.</li>
<li><span class="check">✓</span> <strong>Editing a group is now
called Group Settings and shows a gear.</strong> The pencil next to a
group opened a screen called "Edit Group", though it really holds the
group's settings. It is now called Group Settings throughout and shows a
gear icon, and every translation kept its wording.</li>
<li><span class="check">✓</span> <strong>Saving a domain's stylesheet
keeps you on the stylesheet editor.</strong> Saving while editing a
domain's theme used to drop you back to the main Appearance form, so
every further edit meant reopening the theme. The save now keeps you on
the editor, the way the site's own theme save already did.</li>
</ul>
</body>
</html>