Skip to content

Mobile Friendly Service Status

Ed Mozley edited this page Aug 9, 2026 · 2 revisions

Mobile: Service Status

Service Status is the fifth module made mobile‑friendly, after Tickets, Assets, Calendar and Knowledge. Same hard rule: one @media (max-width: 768px) block, desktop byte‑identical.

🧰 Code‑level catalogue of the techniques: Mobile: Techniques & Tricks.

Why Service Status? "Is the VPN down?" is a question people ask you in corridors. It is also the smallest module brought along so far β€” and the first that needed no JavaScript at all.

Shipped in one round: #1003.


Scope

Page State
service-status/ β€” the board Done β€” two‑up service grid, incidents as a card feed, modal reflow (LAYER 18)
service-status/settings/ Done β€” LAYER 15e, plus the new .tab-content > table scroller it exposed
service-status/help.php Done β€” inherits 16h from the Calendar round

Zero lines of mobile.js. The board has no sidebar, no pane stack and nothing worth wrapping, so the shared shell β€” waffle drawer, views hamburger, company switcher β€” is the whole of its behaviour. The existing guard chain already falls through to that correctly, so the module was brought along by three <link> tags, one <body> attribute and a CSS layer.

πŸ”‘ Not every module needs a branch. Four in a row had one, which made a fifth feel inevitable. Check what the page actually does before writing one β€” this one only ever needed CSS.


What's been done

The board goes two‑up. The service grid was already repeat(auto-fill, minmax(200px, 1fr)), so it reflows on its own β€” but a 200px floor gives exactly one column at 360px, and a dozen services becomes a dozen full‑width cards you scroll past. That loses the thing a board is for. Dropping the floor to 150px fits two (2Γ—150 + 10 gap = 310, inside the 336 available) and roughly halves the scroll.

πŸ”‘ "It reflows" is not the same as "it works". A single‑column auto‑fit grid is technically responsive and practically a list. The question to ask is what the component is for: a status board exists to be scanned, so density matters more than card size.

The description line also loses its min-height: 16px. That reservation keeps desktop cards aligned; two‑up on a phone it is dead space in every card, and the name plus the badge is the point.

Incidents become a card feed. Four columns β€” title, status, affected services, updated β€” and every one of them self‑describes: a title, a coloured status badge, coloured service tags, a date. Reading order carries the meaning, so no labels are needed and nothing is lost.

.incident-table, .incident-table tbody,
.incident-table tr, .incident-table td { display: block !important; }
.incident-table thead { display: none !important; }

⚠️ !important is required here, and not for the usual reason. The page's own JS sets table.style.display = 'table' inline every time it renders rows, and an inline style beats an ordinary author rule. Without it the card feed works until the first refresh and then silently reverts. Grep a module for style.display before converting one of its tables.

Every cell's contents carry a class from the renderer (.incident-title, .incident-status, .incident-services-list, .incident-date), so the card is styled by name, never by cell position.

The incident modal gets the LAYER 3 sheet treatment plus its own bespoke chrome: Delete on its own full‑width row, and min-width: 0 on the affected‑service <select> so the select‑plus‑remove pair can shrink instead of overflowing.


The gap it exposed on a shipped page

LAYER 15e has always scrolled settings tables β€” but only .settings-section-body table. Half the settings pages in the product put a bare <table> straight inside the .tab-content: all three of Service Status', and three on the Assets settings page that has been live since #937.

Those had looked fine to the overflow check for exactly the reason the Calendar settings page did: .container carries overflow-y: auto, and per spec a non‑visible value on one axis computes the other to auto, so the page silently became the horizontal scroller. Contained, and unusable.

body[data-mobile-page="settings"] .tab-content > table:not(.lookup-table) {
    display: block; overflow-x: auto; overscroll-behavior-x: contain;
    white-space: nowrap; max-width: 100%;
}

A scroller and not a card feed, on purpose. These are 6‑ and 7‑column config tables whose columns include unlabelled booleans β€” Resolved?, Default?. Reading order can carry three or four self‑describing values; it cannot carry seven, so these keep their header row. That is the same judgement as the incident feed above, reached the other way.

⚠️ :not(.lookup-table) is load‑bearing. This selector is (0,2,2); 16i's card‑feed rules are (0,2,1), so on the Calendar's categories table β€” which is both β€” this one wins. It happens to be harmless (the conflicting declarations land on different elements, which was measured), but relying on that is a trap for whoever edits either rule next. The exclusion states the intent: a .lookup-table is a card feed, everything else scrolls.


Verification

Same harness as the previous rounds β€” the real authenticated pages driven headless, asserted not eyeballed, at 360px and 1400px.

  • The board's column count is measured by counting cards that share a top edge: 2 at 360px, 6 at 1400px.
  • The inline‑display:table override is asserted directly, because that is the one thing that would silently regress on the next render.
  • The desktop control asserts the inverse of every mobile claim: incidents still a real table, headers still shown, board still multi‑column.
  • The sweep includes the shipped Assets and Calendar settings pages, because the 15e change reaches both. Assets' tables now report scrollWidth 498 > clientWidth 272 β€” contained and scrollable where before they were contained and crushed.
  • A dedicated conflict harness checks the Calendar card feed survived the new higher‑specificity rule: rows still blocks, headers still dropped, cells still wrapping, actions still pinned.

⚠️ And the harness earned its keep twice on formatting. Both times I appended a paragraph to an existing CSS comment and closed it early, orphaning text outside the block β€” which makes the parser drop the rule that follows. Both times the brace‑balance and column‑0 checks passed happily and three assertions went red. A CSS comment edit is a code edit.

Still owed: a real device pass with Ed.


Known rough edges

  • The status board polls; there is no pull‑to‑refresh.
  • The settings config tables scroll sideways rather than reflow β€” deliberate (above), but a 7‑column table is still a 7‑column table on a phone.
  • aria-label sweep across the injected chrome is still owed, module‑wide.

Reference

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally