Mobile Friendly Problem Management - edmozley/freeitsm GitHub Wiki
Problem Management is the seventh module made mobile‑friendly, after Tickets, Assets, Calendar, Knowledge, Service Status and Watchtower. Same hard rule: one @media (max-width: 768px) block, desktop byte‑identical.
🧰 Code‑level catalogue of the techniques: Mobile: Techniques & Tricks.
Shipped as #1181, commit 854e3532. LAYER 22.
| Page | State |
|---|---|
problem-management/ — list, detail and modals |
Done — sidebar → header strip, filters → chip row, pane hiding, modal sheets (LAYER 22) |
problem-management/settings/ |
Done — inherits the shared settings layers |
problem-management/help.php |
Done — inherits 16h from the Calendar round |
viewport = 360 docScrollW = 872 CONTAINED = false
sidebar = 250px .pm-main = 110px
Two separate problems, and only one of them was the obvious one.
872px wide is the iOS reflow‑to‑desktop trap, and — exactly as with Watchtower — the cause was the shared header row rather than anything in this module. Opting in fixed it outright.
The 110px list did not move. A 250px fixed sidebar beside a flexible pane leaves the pane whatever is left, and on a 360px screen that is a third of the width for the actual content. The page was contained, scrollable, and useless.
🔑 This is the clearest demonstration yet that
docScrollW === innerWidthis the floor, not the finish line. Every automated check passed on a screen showing a 110px column of problems.
It holds three things: Search, New problem, and the status filters. Stacked as‑is that is ~250px of a 640px screen before the first problem appears.
.pm-sidebar {
display: grid;
grid-template-columns: 1fr 1fr; /* the two buttons share a line */
gap: 8px;
border-right: none;
border-bottom: 1px solid var(--border, #e5e7eb);
}
.pm-sidebar h3,
.pm-sidebar #pmStatusFilters { grid-column: 1 / -1; } /* these span both */A two‑column grid with selective spanning is a tidy way to say "these two side by side, everything else full width" without adding a wrapper element to the markup.
#pmStatusFilters {
flex-direction: row;
flex-wrap: nowrap;
overflow-x: auto;
scrollbar-width: none;
}
#pmStatusFilters::-webkit-scrollbar { display: none; }
.pm-filter { flex: 0 0 auto; white-space: nowrap; }Same scrollbar‑hiding idiom as .detail-tabs, .sw-filter-tabs and the settings .tabs — the bar is redundant on touch and eats a row of height on a narrow desktop window.
Why not a
<select>? Because each chip carries its count, and the counts are half the value of the control — "are there any open ones at all?" is answered without opening anything. A dropdown hides exactly the information you came for.
.pm-modal-content { width: 100%; max-width: none !important; margin: 0; border-radius: 0; min-height: 100%; }LAYER 3 promotes every .modal-content to a full‑screen sheet — but this module has its own .pm-modal-content, so none of that reached it. Three dialogs (link incidents, link change, detect problems) stayed as 92%‑width centred boxes with a 40px margin.
🔑 Check the module's modal class before assuming LAYER 3 covers it. A module that rolled its own is invisible to a rule keyed on the shared name, and there is nothing in the measurements to tell you — a centred box on a phone is contained.
Three modules in a row shipped with no mobile.js at all, which makes a fourth feel like failure. It is not — the test is what the page actually does.
This module was already master‑detail: pmOpenDetail() hides #pmListView and shows #pmDetailView in the same pane. Nothing needed restructuring. The only fault was that the sidebar stayed on screen while you read a problem, spending ~140px of a 640px screen on controls for the list you had just left.
var openDetail = window.pmOpenDetail;
window.pmOpenDetail = function () {
var out = openDetail.apply(this, arguments);
Promise.resolve(out).then(function () {
var dv = document.getElementById('pmDetailView');
if (dv && dv.style.display !== 'none') setPane('detail');
});
return out;
};Both functions are top‑level declarations, so they are properties of the global object and can be wrapped from mobile.js with no edit to problem-management.js — see Techniques §1.
Two details worth copying:
-
Mark the pane only after the promise resolves.
pmOpenDetailis async and bails on a failed fetch. Marking up front would leave the sidebar hidden behind an error toast, with the list underneath and no way back to it. -
The state is an attribute, the hiding is CSS.
body[data-pm-pane="detail"] .pm-sidebar { display: none }sits inside the@mediablock, and the wrapper refuses to set the attribute unlessmq.matches— so desktop never sees it even if the JS runs.
| Check | Result |
|---|---|
| List / detail / modal, each at 360px | ✅ contained, zero uncontained elements |
| Pane cycle: list → detail → back | ✅ sidebar grid → none → grid
|
| Help and Settings at 360px | ✅ contained, zero uncontained |
| Desktop control @ 1100 / 1200 / 1400px | sidebar 250px throughout, body not flex |
data-pm-pane on desktop |
✅ never appears, at any point in the same cycle |
mobile.js parse check |
✅ PARSED_OK — with a negative control
|
That last row matters. A parse harness that cannot fail proves nothing, so the same page was loaded with a deliberately broken script alongside: it reported Uncaught SyntaxError, which is what makes the clean result meaningful.
Driving all three states — not just the page as it loads — is the reason the modal fault was found at all. A page that renders one view at a time is one‑view verified.
- The AI "Detect problems" suggestions list ticket numbers as plain text. They come from a model rather than the database, so linking them needs a server‑side number→id resolve that would also quietly validate the model's output — see discussion #91. Not a mobile issue, but it shows up here.
-
The detail view's field grid is two columns even at 360px (
PRIORITY/ASSIGNED TO). It reads fine with short values; a long assignee name would be tight.
Ed found the History panel's What column running off the right-hand edge. Measuring the other two tables on the same page found the same fault in a quieter and worse form.
| Table | Measured at 360px | |
|---|---|---|
| History | 345px in a 328px section | spills — the half a person notices |
| Linked incidents | 294px, "fits" | subject cell 82 × 134px — seven lines for one sentence |
| Fix (linked change) | same shape | same |
🔑 Overflow gets reported; crushing does not. The two that squeezed passed every automated check — contained, nothing uncontained, no overflow — while being less readable than the one that visibly broke.
All three became card feeds, by the rule now written up as Techniques §11: every one of them has a column holding prose (What, Subject, Title), and a sentence can be neither scrolled sideways nor squeezed into 82px.
History now reads the way the module's own notes already do — a quiet when · who line, then the change itself:
15/07/2026, 01:17:50 · Administrator
changed linked_incident to "LUN-779-88063"
Two details worth copying:
-
The meta cells are
display: inline, not a flex row. Flex blockifies its items, so a flex column cannot put two cells on one line. Plaininlineon two adjacent cells does. -
:has(.pm-when)scopes the history-only rules. Three tables share the.pm-tableclass; that one class appears only on audit rows, so the markup needed no change at all to tell them apart.
<td colspan> — the first and last child of its row — so any rule keyed on either position swallows it. Asserted explicitly: "No change linked yet." still renders after the change.
Desktop control: display: table, table-header-group, table-row, table-cell, header row visible, four columns, at 1100px and 1400px.
- Mobile‑Friendly — the overview and the rollout state
- Mobile: Techniques & Tricks — the code‑level catalogue
- Problem Management — the module itself