Skip to content

Mobile Friendly Change Management

Ed Mozley edited this page Aug 25, 2026 · 8 revisions

Mobile: Change Management

Change Management is the eighth module made mobile‑friendly. Same hard rule: one @media (max-width: 768px) block, desktop byte‑identical.

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

Shipped as #1184, commit 9d8a16d6. LAYER 23.


Scope β€” six pages, three of them free

Page State
change-management/ β€” list, detail, editor Done β€” sidebar β†’ header strip, cards stacked, pane hiding (LAYER 23)
change-management/approvals.php Done β€” sidebar becomes a single scrolling filter row
change-management/table.php Done by opting in β€” inherits .dt-wrap from the Assets round
change-management/calendar.php Done by opting in β€” inherits the Calendar layer
change-management/settings/ Done by opting in β€” the shared settings rules, plus the body marker
change-management/help.php Done by opting in β€” inherits 16h

πŸ”‘ Half the module cost nothing. That is the return on building earlier layers around shared class names (.dt-page, .calendar-grid, .container) rather than page ids β€” a new module that reuses a component inherits its mobile treatment for free.


The widest starting point of the rollout

viewport = 360   docScrollW = 1286   ← on ALL SIX pages
index.php      sidebar 280  β†’  list pane   80px
approvals.php  sidebar 260  β†’  list pane  100px

1286px on a 360px screen, and an 80px column of content β€” the narrowest yet recorded here. The 1286 was the shared header, so opting in fixed it outright; the 80px was not touched by that at all.


LAYER 23

It mirrors LAYER 22 closely, because the module is built the same way: a fixed sidebar beside a flexible pane, plus a list/detail swap the module already does in its own JS.

⚠️ grid-column: 1 / -1 is not enough when the DOM order fights you

The sidebar holds three sections: Search, Status filters, New change β€” in that order. Making the filters span both columns should have left the two buttons sharing the top line. It didn't, because the filters sit between them in the DOM, so the grid placed Search on row 1, filters on row 2, and New change on row 3.

.changes-sidebar .sidebar-section:has(.status-filter-list) {
    grid-column: 1 / -1;
    order: 1;              /* ← move the filters LAST */
}

order moves the filters after both buttons, which puts the two actions side by side and reads better anyway: do the thing, then narrow what you are looking at.

The approvals sidebar has no wrapper to convert

Its filters are direct children of the sidebar, with no list element around them, so there is nothing to turn into a strip. The sidebar itself becomes the scrolling row, with the "Filter" heading riding along at the start rather than being hidden β€” it costs one word of scroll and it is the only thing naming what the chips are.

One wrapper covers three panes

var showView = window.showView;
window.showView = function (view) {
    var out = showView.apply(this, arguments);
    setPane(view === 'detail' || view === 'editor' ? view : 'list');
    return out;
};

A cleaner hook than Problem Management's two wrappers: showView('list' | 'detail' | 'editor') is a single synchronous function, so there is no promise to wait on and no failure path to guard.


⚠️ The bug the measurements could not see

Every containment check passed β€” docScrollW === innerWidth, zero uncontained elements β€” while the change list looked like this: priority and status pills marching off the right‑hand edge, and the assignee colliding with the date.

.change-card         { display: flex; align-items: center; gap: 16px; }  /* ref | title+meta | badges */
.change-card-badges  { flex-shrink: 0; }                                  /* ← four badges that will not give way */

Four badges that refuse to shrink make the card wider than the screen whatever the panes do, and because an ancestor scrolls, the document never widens. The page was contained; the content was off the edge of the box it sat in.

.change-card        { flex-direction: column; align-items: stretch; gap: 6px; }
.change-card-title  { white-space: normal; overflow: visible; }   /* two lines beat an ellipsis here */
.change-card-meta   { flex-wrap: wrap; gap: 2px 12px; }
.change-card-badges { flex-wrap: wrap; flex-shrink: 1; }

πŸ”‘ flex-shrink: 0 is the single most common cause of a row that will not fit a phone. It is correct on desktop β€” it stops badges being squashed into illegibility β€” and it is exactly wrong at 360px, where something has to give. Grep a module's CSS for it before wondering why a row overflows.


⚠️ …and a false positive in my own probe

The first sweep reported table.php as having a table overflowing its parent: 938 > 326. It wasn't a bug. The parent is the scroller β€” .dt-wrap with overflow-x: auto β€” so scrollWidth > clientWidth is precisely what a working scroller looks like.

// A table is only WRONG if it is wider than its box AND no ancestor scrolls.
let scrolled = false;
for (let p = t; p; p = p.parentElement) {
    const ox = getComputedStyle(p).overflowX;
    if (ox === 'auto' || ox === 'scroll') { scrolled = true; break; }
}

Re‑checked against asset-management/table.php as a known‑good control: identical readings. When a probe reports a fault on shipped code that has been through a device pass, suspect the probe first.


Verification

Check Result
All six pages at 360px βœ… contained, zero uncontained, no unscrolled table spills
Pane cycle: list β†’ detail β†’ editor β†’ back βœ… sidebar grid β†’ none β†’ none β†’ grid
Desktop @ 1200px and 1400px body block/row, sidebar 280px, cards still flex-direction: row
data-cm-pane on desktop βœ… never appears, at any point in the cycle
mobile.js parse check βœ… with a negative control
Looked at, not only measured βœ… β€” and it is the only thing that caught the card bug

Known rough edges

  • Pre‑existing, not introduced here: at a 1200px desktop window this module's shared header needs 1282px, so the page scrolls sideways until roughly 1290. It is fine at 1400. That is a desktop header issue β€” this module carries more nav items than the others β€” and is deliberately left alone.

Reference

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally