Mobile Friendly Self Service - edmozley/freeitsm GitHub Wiki
The portal is the public‑facing half of FreeITSM and the one surface most requesters only ever see on a phone. Part one shipped in #1207; part two in #1491–#1494, self-service.css v13.
Read Mobile‑Friendly first for the strategy and the one hard rule, and Techniques & Tricks for the catalogue this draws on.
It does not opt into mobile.css. Ed's call, and the reasoning still holds: the portal shares none of the analyst shell — no .header-nav, no waffle, no .main-container — so pulling in twelve and a half thousand lines of rules written against analyst markup would drag §15's hazard across a public surface, and .summary-cards, .search-box, .form-input and .modal-content already exist on both sides.
So: techniques from the wiki, stylesheet local. The mobile layer is a @media (max-width: 768px) block appended to assets/css/self-service.css, after the pre‑existing 900/640/500 blocks so it wins on document order.
mobile.css and had to be written again here — the guide's scroller (LAYER 16h) and its contents‑strip fix (#1464). It is still the right call; it is simply not a free one, and knowing which is which is the point of writing it down.
-
🔴 Ed overruled the nav, and he was right. It was first built as a scrolling chip row; he asked why it was not a ☰ drawer like every other module. The codebase settles it —
mobile.js'sinjectViewsHamburger()turns.header-navinto a drawer on every module, and.portal-navis structurally the same thing.🔑 The chip rows in this rollout are for FILTERS, not DESTINATIONS.
.filter-chips,.filter-tabsand.detail-tabsare all filters. Check which pattern a precedent is before copying it. -
🔴 The drawer was on screen, correctly coloured, 44px per link — and completely untappable.
.portal-headerisposition: sticky; z-index: 100, which creates a stacking context, so the nav'sz-index: 3000resolved inside it and the whole drawer painted at level 100 — beneath its own overlay at 2999. Every geometry assertion passed;elementFromPointon a link returned.ss-nav-overlay. Fixed withbody.ss-nav-open .portal-header { z-index: 3000 }, only while open.⚠️ A fixed‑position drawer inside a sticky, z‑indexed header is trapped in its parent's stacking context. AssertelementFromPoint, never the rect. -
⚠️ Two pre‑existing rules had never applied to anything — both of which look like the problem is handled:rule why it did nothing @640 .portal-nav a { padding: 6px 10px }padding cannot beat a min-width: 104pxfloor — the floor was the constraint@500 .ticket-table .col-optional { display: none }col-optionalappears nowhere in the markup -
⭐ The brand wordmark stays on mobile. The 640px block hid
.portal-brand spanoutright to buy nav room; with the nav in a drawer that room is not needed, and hiding it deletes the customer's identity on the device most requesters use. It truncates instead. Ed asked the question that found this.
help.php 7118px of content
2 UNREACHABLE: HTML 7118/740 | BODY.portal-app 7118/740
Ten of eleven sections unreachable, and no single rule to blame:
| file | rule | and it is correct |
|---|---|---|
self-service.css |
body.portal-app { height: 100vh; overflow: hidden } |
the app shell — on every other portal page a pane scrolls inside it |
help.css @900px |
.help-container { height: auto }, .help-main { overflow-y: visible }
|
hands the scroll outwards, which is right for a document that can take it |
| — | nothing gives the document permission |
This is §28, and the same three‑file interaction the Network Mapper round found on the analyst guides.
The first attempt made <body> a flex column and gave .help-container flex: 1 1 auto. Nothing changed, because .help-container is not a child of <body>: the portal header include wraps every page in a .portal-layout, which stayed display: block and grew to its content.
DIV.help-container flex: 1 1 auto height 7069px <- resolved
DIV.portal-layout display: block height 7069px <- against THIS
BODY.portal-help display: flex height 740px
🔑 A
flex: 1is measured against its own parent, so every element between the viewport‑height box and the scroller has to pass the height down. Measuring the container alone saidflex: 1 1 autoand looked correct. What found it was dumping the entire ancestor chain — display, height, min‑height, flex and overflow for each — which is now the first thing to print when a scroller will not take.
Scoped to a new body.portal-help class, because making <body> a flex column is not something any other portal page wants.
The page's own handler scrolls #helpMain — right on a desktop, where .help-main is the scroller; below 900px that element is overflow-y: visible and scrollTo on it throws nothing and does nothing. The highlight never moved off "1".
The portal cannot use mobile.js's #1464 fix, so the page's handler now resolves the scroller on every use rather than caching it, and listens on both candidates. Verified at both widths by intercepting the call rather than reading scrollTop — smooth scrolling never advances under --virtual-time-budget:
@360 -> help-container <- {top: 557, behavior: smooth}
@1100 -> help-main <- {top: 365, behavior: smooth}
a.ticket-link 90x17 display: inline min-height: 24px
min-height is inert on an inline box, and an <a> wrapping text is inline — so round one's rule computed to 24px and changed nothing. It matters more than the number suggests: the <tr> has no click handler, so those two anchors are the only way to open a ticket from the page a requester lands on.
The fix is the display type, not a bigger number.
Your requests and Recent tickets are both .ticket-table, but with three columns and five. The phone card rules key on column index, so the requests table was having its status cell styled as a ticket's subject: the approval pill given its own line with 8px of margin under it, the reference stranded, the date pushed alongside.
The renderer now stamps req-table and the index rules are scoped :not(.req-table) — the same answer as the two log tables in Reporting, and exactly what LAYER 15b's own comment warned about when it said nothing here may depend on a column INDEX.
display: block per cell, not inline-flex on the status cell, which is what the first attempt used: the base rule leaves a td display: inline, so an inline‑flex status cell and an inline date cell shared one line box, and once the date joined it the flex box was handed a narrower width and wrapped internally — pill on one line, reference on the next, date beside the reference. Three items, three places, none of them chosen.
-
The session key is
ss_user_id, notanalyst_id. Get thes:byte lengths exactly right or PHP discards the whole session and every page 302s, which looks like an auth failure. - 🔴
login.phpandregister.phpredirect when a session exists, so a cookie‑carrying harness measuresindex.phpand labels it "login" — round one had two of eight results be fiction that way. Run pre‑auth pages with no cookie, and print the resolvedlocation.pathnameon every result so the page can prove which page it is. -
register.phpstill 302s on this install because self‑registration is off; it cannot be measured without enabling it first. -
ticket.phpredirects totickets.php— the detail lives in the list page, so there is no separate detail page. - Kill transitions on every run, not only for screenshots. Headless Chrome never advances them, so a drawer reads as off‑screen and invisible after being opened.
⚠️ The shrink‑floor detector needs two exclusions, one per axis. Round one recorded the horizontal one:text-overflow: ellipsis+nowrapreports as a floor and is the feature (.tk-item-previewshowed "+602px" and was perfect). Part two added the vertical twin:-webkit-line-clamp: 3withoverflow: hiddenis a deliberate three‑line preview whose content is supposed to be taller than its box —.article-card-previewreported126/54six times on the dashboard and was working exactly as designed.
| check | result |
|---|---|
| width containment, 8 pages @360×740 |
docScrollW = 360 on every one |
| reachability (§28) | a real scroller or a genuine fit on all eight; the guide had neither |
| 320×640 | green on all four pages re‑run |
| anti‑zoom sweep | every visible field on every page ≥ 16px |
| the ☰ drawer | 266×740, z: 3000, header promoted only while open, all four destinations 242×44 and elementFromPoint reaches each
|
| the reading pane | list hidden, 44px back bar, thread scrolls, composer fields ≥16px |
| the contents strip | jumps to the right offset at both widths, active chip follows |
| the guide on desktop @1100 |
body back to display: block, .help-main the scroller again, handler resolving to it |
| every CSS change | inside the @media (max-width: 768px) block — verified by line number against the block's own bounds |
req-table / portal-help
|
referenced only inside that block; no styling at any other width |
⬜ Owed: self-service.nav.menu — the ☰ button's accessible name — exists in 9 of 25 locales. t() falls back to English, so the other sixteen announce "Menu" in English rather than the key, which is graceful rather than broken. It belongs in the i18n fan‑out, not in a guess at sixteen translations.
⬜ Not measured: register.php, which redirects while self‑registration is off.