Skip to content

Mobile Friendly System

Ed Mozley edited this page Sep 5, 2026 · 1 revision

Mobile‑Friendly: System

The nineteenth module and by some way the largest round of the rollout: 53 pages against Contracts' 22. The landing page, twenty-odd administration screens, the fourteen debug tools, the System guide and its nineteen topic pages. Shipped in #1472, mobile.css v132 / mobile.js v55, LAYER 34.

Read Mobile‑Friendly first for the strategy and the one hard rule, and Techniques & Tricks for the catalogue this round draws on.

Ed's framing was exactly right: "the landing page looks pretty decent (although needs the usual work) but then each card needs some work β€” each of them present their own challenges!" There is no single System layout; there are twenty of them.


⭐ The file count is not the page count

Two greps before writing anything β€” the container class per page, and which include renders it β€” turned a 53-page round into 34 file edits:

shell pages how
.main-container 18 the landing, the debug-tools index and all fourteen debug tools (one renderer, debug-tools/includes/tool-page.php), encryption, modules
.syshelp-container 20 the System guide plus 19 topic pages, all through help/_top.php
.settings-shell 5 analysts, calendar-sync, preferences, roles, teams
bespoke 10 one shape each

πŸ”‘ Find the shared shells before estimating the work. Fourteen debug tools are five-line stubs behind one renderer; nineteen help topics are fragments behind one _top.php. Grepping the container class rather than the file list also said in advance which faults would be systemic rather than per-page β€” which is what made the round tractable.

⚠️ _top.php opens <body> but _bottom.php closes it, so the stylesheet and the body markers go in one file and the script tag in the other. A partial pair is one page, split across two files.


πŸ”΄ 34a β€” The mobile layer turned off a scroller the pages had built themselves

Measured immediately after opting in, at 360Γ—740:

/system/              πŸ”΄ NO SCROLLER, content 5008px
/system/debug-tools/  πŸ”΄ NO SCROLLER, content 3686px
/system/encryption/   πŸ”΄ NO SCROLLER, content 1092px

The landing page of the module, with five thousand pixels of cards and no way to reach any of it past the first screen. And every one of those pages had already done the right thing:

encryption   .main-container { flex: 1; overflow-y: auto; }
the landing  .system-landing { flex: 1; overflow-y: auto; }
debug tools  .debug-landing  { flex: 1; overflow-y: auto; }

LAYER 2 gives .main-container overflow: hidden, because in the ticket inbox that container holds the absolutely-positioned pane stack and must not scroll. Both rules are (0,1,0), and mobile.css loads last by design (Β§9).

πŸ”‘ Β§9's load-order rule cuts both ways. Putting mobile.css last is what lets it win the ties it needs to win; it also means it wins ties it should lose. A page that already solved something for itself is not protected by having solved it first.

This is LAYER 15f's finding a second time β€” ".servers-container is ALSO .main-container… putting the scroll back is the first rule here for that reason" β€” and it will recur, because .main-container is the app's most reused shell name. Scoping on the module marker takes the fix to (0,2,0).


⭐ Four pages fixed by naming them correctly

The five .settings-shell pages were given data-mobile-page="settings" and data-mobile-shell="own", mirroring tickets/settings.

The second marker is not optional. Each of those pages declares .settings-shell { display: flex; flex-direction: column; height: 100vh } and builds its scroll region one level down. mobile.css carries an unscoped .settings-shell { height: 100dvh }, so opting them in without data-mobile-shell="own" would have left a 100dvh shell inside a 100dvh flex <body> that also holds a 48px header β€” clipped by exactly the header's height, on five pages.

The payoff was immediate: their tables came out already scrolling sideways with their headers intact, because data-mobile-page="settings" is what the entire shared settings layer keys on.

πŸ”‘ Opting a page in has three parts and a settings page has four β€” the standing note. This round is the case where getting the fourth right did most of the work.


The tables split two ways, and the split is the interesting part

Config tables scroll sideways, keeping their headers β€” API keys (8 columns, 813px), SSO providers (7, 769px), companies, saved searches. That is Ed's own #1004 rule: every table on every settings screen behaves the same way, because they are a set, and one screen behaving unlike its neighbours costs more than the individual screen gains. white-space: nowrap is the half people drop β€” without it the browser wraps every column to one word per line, which passes the containment check and is unreadable.

Documentation tables become stacked blocks. Twenty help pages share .syshelp-table, and the API reference adds three more of the same shape β€” two or three columns whose last is a sentence:

HTTP Β· Code Β· Meaning          rows measured 81px, 113px, 129px tall
Parameter Β· Description Β· Value

Β§11 is unambiguous: if any column contains a sentence, it is a card feed; there is no second consideration. You cannot scroll prose sideways β€” you would scroll right to finish a sentence and left again to start the next row.

πŸ”‘ One module, two right answers, and the deciding question is not the column count β€” it is whether a human reads the cell or scans it. Config tables are scanned; reference tables are read.

Module access is the third answer: three columns of which two hold lists of names. Lists are prose by Β§11's test, and the question that page answers is "who can get into Contracts?" β€” one module at a time. A feed.


πŸ”΄ Two faults on Branding that no containment check could see

A 1fr track is a shrink floor

.design-controls  w=1282
.design-grid      w=264   BUT  grid-template-columns: 1282px

A bare 1fr track has an automatic minimum of min-content, so it cannot shrink below its contents β€” Β§14's min-width: auto seen from the grid side.

⭐ And the page already knew. Its desktop rule is grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr) β€” minmax(0, …) is the guard β€” and its own @media (max-width: 900px) collapses that to a bare 1fr, dropping the guard along with the second column.

πŸ”‘ When a page stacks its own grid at a breakpoint, check whether it kept the minmax(0, …). Going from two columns to one is the moment the track stops being bounded by its sibling and starts being bounded by its content β€” precisely when the guard begins to matter, and precisely when it tends to get dropped.

Six inputs at 55 pixels

.slot-grid { grid-template-columns: 80px 1fr 1fr 1fr } β€” a row label plus LEFT Β· CENTRE Β· RIGHT β€” measured 80px 54.65 54.67 54.67. The six boxes you type a company name into were 55px each. The page is perfectly contained and every check passes: this is contained but crushed, which only the question "is anything here still laid out in more than one column at phone width?" finds.

⭐ It is the same feature as the Network Mapper branding dialogue (.nm-brand-grid, LAYER 31g) β€” page header/footer slots, three across, in two modules. Two implementations of one idea, now behaving the same.


⭐ The desktop control, done properly for the first time

Every previous round compared measurements before and after by eye. This one toggles the stylesheet in place and diffs a layout fingerprint of every element:

const withCss = fingerprint(doc);                       // left,top,width,height Γ—400
[...doc.querySelectorAll('link')].forEach(l => {
  if (/mobile\.css/.test(l.href)) l.disabled = true;    // switch the layer off
});
const without = fingerprint(doc);
withCss === without      // ← the whole claim, in one comparison

Result on six System pages at 1100Γ—900: identical with and without mobile.css, every element, every box.

πŸ”‘ This is the strongest form of the one hard rule available, and it is three lines. It also cleared a false alarm the page-level sweep had raised on /system/api/ at desktop width β€” proving the flag was the probe rather than a regression, which comparing numbers by eye could not have done. Worth making routine.


⚠️ §17 fired, and it was worth having

A quoted CSS snippet inside the layer comment carried its own inline comment:

    .main-container { flex: 1; overflow-y: auto; }   /* encryption */

The inner */ closed the outer comment, and the rules after it were being eaten β€” which is why encryption still measured unfixed after the fix was written. Braces balanced throughout, because the eaten text contained none. Β§17 warns that a heavily commented stylesheet makes this likelier, and that quoting a rule inside a comment is exactly when it happens. Run the awk check after every comment edit, not once at the end.


How it was verified

  • All 53 pages swept at 360Γ—740 β€” every one contained, every one with a scroller that reaches its end, with the Β§18 filter reported both on and off so an absorbed overflow is visible rather than suppressed.
  • Both settings tabs driven, plus a representative debug tool (d013) and help topic β€” a page that renders one tab at a time is 1/N verified.
  • The analysts table checked cell by cell: display: block, scrollWidth 1047 in clientWidth 304, scrolls to 743 of 743, cells at natural widths all sharing one 61px row height β€” Β§19's healthy signature, not a crush. The probe's crushed-cell heuristic flagged it and was wrong.
  • Desktop proven by stylesheet toggle on six pages (above).
  • The Β§25 audit: 36 files, +138 / βˆ’36, every line an opt-in line; every other page in the product changed only its ?v=. mobile.js untouched β€” this is a CSS-only round.

⬜ Known and not fixed

  • The waffle button is 34Γ—34 on every module, under a 40px tap target. App-wide and pre-existing, not a System fault β€” recorded here because this round measured it. The drawer itself opens correctly on every System page (23 links, elementFromPoint returning the panel), and on pages that have never been in the rollout at all, because it is entirely self-contained in includes/waffle-menu.php.
  • api/docs.php renders tall rows at desktop width too. Pre-existing, unchanged by this round.

Related

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally