Repository navigation
Mobile Friendly Domains
Scope: all seven pages β the register, the table view, the dashboard, registrar accounts, a domain's page (and its Edit dialog), settings, and the guide. Layers:
mobile.cssLAYERS 41aβ41h, andmobile.jsLAYER 41 (the register's filter sheet) plus twoFEEDSentries. Parent: MobileβFriendly Β· Techniques: Β§3, Β§4, Β§10, Β§11, Β§21, Β§23, Β§28, Β§31
Domains arrived after the rollout had finished, so it was built with mobile.css linked on every page, the link correctly after each page's own CSS (Β§9), markers on five of seven pages, and a @media (max-width: 900px) block of its own that stacks the register's sidebar above the list. Nothing spilled sideways on any page. That is a good starting position, and also why the measurement run alone would have called it done.
Measured at 360Γ740 through an explicitβwidth iframe (Β§8, Β§30), then looked at:
| Page | Found | Answer |
|---|---|---|
| Register | Search, ten views and six filters stacked above the list β a screen and a half before the first domain. Six stats one per row. A tenβcolumn table cut off after Status. | Sidebar β a Β§4 sheet behind a Filters bar; stats twoβup; the table β a card feed |
| Dashboard | Seven stats one per row | Twoβup |
| A domain | Six tabs wrapping into three ragged rows; "All checks" 14px past its card | One swipeable tab row; card headings wrap |
| Accounts | Seven columns clipped after two | A card feed |
| Table view | β | Already answered by the shared .dt-* layer (LAYER 15) |
| Settings | β | Already answered by the shared settings layer (tabs swipe, config tables scroll sideways) |
| Guide | β | Already answered by the shared guide layer |
| Every page | Every field 13px | 16px moduleβwide (Β§3) |
Three pages (dashboard, accounts, a domain) scroll inside a wrapper that was written inline:
<div style="height:calc(100vh - 62px);overflow-y:auto;background:var(--app-bg,#f5f5f5)">
<div style="padding:20px 26px 40px">Two problems on a phone: the header is not 62px, and 100vh runs under Safari's address bar, hiding the bottom of the page. And an inline style can only be beaten with !important.
Β§9's third option β give the markup a class and change nothing else β taken one step further: the inline styles moved into domains.css as .dom-shell / .dom-shell-pad with the same values, so the phone layer has a normal selector:
body[data-mobile-module="domains"] .dom-shell {
flex: 1 1 auto; height: auto; min-height: 0; /* Β§28: min-height or nothing scrolls */
overflow-y: auto; overflow-x: hidden;
}Moving a style out of the markup is a desktop change, so it is proven rather than argued: at 1100px the shell computes height 738 (= innerHeight β 62), overflow-y: auto, padding: 20px 26px 40px, and the desktop control below returns identical.
mobile.js moves the real .dom-sidebar into a fullβscreen sheet opened from a bottom Filters bar, and moves it home when the viewport leaves mobile β so every listener domains-register.js attached keeps working, and the desktop never builds the sheet or the bar. The bar shows the current view beside the word ("Filter Β· Needs attention"), harvested from the view's own button, so the list's state is visible without opening anything and no string was invented.
The sheet closes on a view (an arrival β what you chose is behind it) and stays open on the search box and the dropβdowns, because you often set several. That distinction is the System Wiki round's lesson.
The first run: tap a view, the list changed β and the sheet stayed open on top of it.
sheetBody.addEventListener('click', function (e) {
var btn = e.target.closest('#domViews button'); // null!
if (btn) setOpen(false);
});The page's own click handler reβrenders #domViews to move the highlight. Bubbling runs after it, so by then the tapped button has been replaced and is no longer inside #domViews β closest() walks up a detached node and finds nothing. The fix is one argument: listen in the capture phase, which runs before the page's handler.
sheetBody.addEventListener('click', handler, true);π A delegated listener that asks "where was this tapped?" must ask before the page redraws the answer. This is Β§31's test again β change the choice and assert the screen changed β and only driving it found it: every measurement of the open sheet passed.
Ten known columns, read one domain at a time: a feed (Β§11). Status, name and grade speak for themselves; the expiry, renewal, registrar, certificate and owner get their headings back (Β§21), harvested from the hidden <thead>:
{ table: 'body[data-mobile-page="domains-register"] #domTable', columns: [3, 4, 5, 7, 8, 9] }The protection icons (lock, registry lock, DNSSEC) are SVG with no text, so the harvester rightly declines to label them; they lead the facts line as a set of badges instead.
The feed took four screenshots to get right, and every one of them passed every measurement.
- Every fact on its own line β sevenβline cards that read like a spreadsheet. The facts now flow as one wrapping line, and the checkbox is absolutely positioned in the corner with the card indented past it, so no cell needs its own offset.
-
Β§23 met in the data β with
flex: 1 1 60%on the name, a short name let the status climb onto the first line and a long one did not. Name and grade now fill the line exactly, so the status always starts the second:#domTable tbody td:nth-child(2) { flex: 1 1 calc(100% - 44px); } /* + 28px grade + 12px gap */
-
A long status ("Letting lapse") wrapped the expiry, and the icons rode up beside it on the right. A forced line break fixed it β a zeroβheight, fullβwidth flex item placed by
order:#domTable tbody tr::after { content: ""; order: 5; flex: 0 0 100%; height: 0; } #domTable tbody td:nth-child(8) { order: 6; } /* the icons start the facts line */
β A flex row has no "new line here" β but a fullβwidth item of zero height is one. Put it at the
orderwhere the line must break and everything after it starts at the left, whatever the content before it did. It is the deterministic answer when Β§23's middle basis is not enough because two lines depend on the data.
The six tabs (#domTabs, the shared .tabs/.tab classes) become one swipeable row β the same idiom as the settings tabs and .detail-tabs. Card headings that carry a link ("All checks", "See all") now wrap instead of poking past the card. The Edit dialog is a .modal-content, so LAYER 3 already makes it a fullβscreen sheet; measured anyway: 28 fields all inside, the three date fields already reset by LAYER 3 and still fitting when forced to the 210px an iPhone needs (Β§20), the customer picker's results inside the sheet, Save on screen.
Refresh, Check now, Edit and Delete sat in a row under the hero and scrolled away as soon as you read down the page, and "β All domains" was a small grey text link. On a phone all five now live in a footer pinned to the bottom - Back first as a chevron square, then the four actions as icons, Edit in the module's green and Delete in red, set apart on the right.
It is the Checklists editor's footer, done the same way: the real link and buttons move (each is bound by id in domains-view.js, so a copy would do nothing), each one's own label becomes its aria-label (Refresh and Check keep the icon they already carry), the footer's height is measured into --dom-vbar-h and reserved, and above 768px every node goes back to its exact place. The row they left, and the link's wrapper, are hidden so no empty strip remains.
busy() rewrites a button while it works - it saves the button's contents, shows Workingβ¦, then restores what it saved. Because the save happens at click time, what comes back is the icon; the four action buttons share the width (62px) rather than being 52px squares, so Workingβ¦ fits.
confirm.js adds its active class inside a requestAnimationFrame, and animation frames do not run in a headless iframe, so the overlay sat at visibility: hidden and the bar's Edit button was under the finger. Adding the class the frame would add showed the overlay (z-index 99998) on top. Disabling transitions was not enough, because the class is never added at all - a third kind of Β§30 harness lie, next to transitions and behavior: 'smooth'.
At 360Γ740, on every page: nothing off either edge (left < 0 included); the page's scroller found, scrolled to the end, and what is under the finger changed (Β§13, Β§28); every field 16px (the one exception is a colour picker, excluded by design); no script errors. On the register: the sheet opens, holds the real sidebar, closes on a view, the list changes, the bar's label follows, and a dropβdown keeps it open; 45 labelled cells.
.modal found the real one, .dom-shell, scrolling to the end.
Desktop control at 1100px on all seven pages β fingerprint every element's box, disable mobile.css in place, fingerprint again: identical, 5,695 boxes. Plus the Β§30 blind spot: the Filters bar and sheet are never built at desktop width, the sidebar is in its home, and no data-mobile-label is stamped.
-
The dashboard's grade chart clips its last legend entry ("Not checked yet") at 360px. The legend is drawn by Chart.js on a canvas, so CSS cannot reach it; it needs the chart's own legend position to change at phone width, which is a
domains.jschange, not a layer. - A device pass. Everything here is headless; iOS gesture handling and the real address bar are not.
See also: Domains Β· MobileβFriendly Β· Techniques Β· Mobile: Checklists (the same filterβsheet pattern)
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- π§ͺ Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- Theming & Dark Mode
- ποΈ Recent β getting back to what you were doing
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π Mobile: Forms
- β³ π Mobile: Contracts
- β³ π Mobile: Domains
- β³ π Mobile: People
- β³ π Mobile: Projects
- β³ π Mobile: LMS
- β³ πΊοΈ Mobile: CMDB
- β³ πΊοΈ Mobile: Network Mapper
- β³ π§ Mobile: Process Mapper
- β³ βοΈ Mobile: Workflow
- β³ π₯οΈ Mobile: System
- β³ π Mobile: Reporting
- β³ π Mobile: System Wiki
- β³ π Mobile: Self-Service Portal
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- β³ π‘οΈ CSRF protection (S4) β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- π CardDAV contact sync
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π REST API: Domains
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ π·οΈ REST API: Cost centres
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π·οΈ Ticket categories
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
-
β³
βοΈ Telegram channel - β³ β CSAT company scope and filters β Developer Guide
- β³ π₯ Microsoft Teams channel
- β³ π¨οΈ Mattermost channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ β Record previews
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ π¨ Telling the analyst a ticket is theirs
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ π Confidential tickets
- β³ π₯ Portal managers
- β³ π Who has seen a ticket
- β³ π Reading long tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π½ Just my tickets, or no closed ones
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
- π Projects
-
Assets
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π·οΈ QR asset labels
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ π Linking equipment to tickets
- β³ βοΈ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- β³ π¨ The form designer β Developer Guide
- β³ π Layout & the grid β Developer Guide
- β³ ποΈ Collections β grouping submissions
- β³ π Submissions as PDFs
- β³ β‘ What happens next β a form's own actions
- β³ π οΈ Sections & conditional logic β Developer Guide
- β³ π οΈ Lookup fields β Developer Guide
- β³ π‘οΈ Catalogue request approvals
- People
- Domains
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ π’ One board across every company
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)
- What this is
-
π Bugs resolved
- β³ πΌοΈ Logo and courses broke on Apache with PHP-FPM
- β³ π’ Chat tickets ignored your ticket numbering
- β³ π Dates shown as a dash, or in server time
- β³ π Assets β Users showed people from other companies
- β³ π Restricted analysts could read other modules' data
- β³ πΌοΈ Replies with a picture in the thread failed to send
- β³ π Reply attachments never reached the customer
- β³ π οΈ Outbound email attachments β Developer Guide
- β³ π A global SSO provider was missing from the portal
- β³ π Behind a proxy, the SSO redirect said http
- β³ βοΈ The portal tagline moved when you saved it
- β³ π¨ The portal settings screen forgot what you saved
- β³ π‘οΈ The approvals inbox said "Error" and nothing else
- β³ π A table's answers were missing from the PDF
- β³ β A single-select column let you tick every option
- β³ π The portal ignored a form's field widths
- β³ π The tasks board stopped taking clicks
- β³ ποΈ #121 The index list is out of date after upgrading
- β³ π #133 The calendar subscription was empty
- β³ π #131 Tasks always reopened on the board
- β³ π₯ #129 Every page returned HTTP 500 after upgrading
- β³ π³ #127 A PHP warning above the System page
- β³ π #126 Notes stamped with the server's clock
- β³ π Storing every date in UTC
- β³ πͺ The portal was down for everyone signed in
- β³ βοΈ #120 Workflow notes could never be written
- β³ βοΈ #123 Three errors when running Database Verification
- β³ π #122 The description box was a stub in the corner
- β³ π£ Demo data deleted real accounts
- β³ π #117 Sign-in redirected to the wrong address
- β³ π¨ #108 The priority dot was invisible
- β³ β±οΈ #116 Time logged from the right-click menu
- β³ π #114 API keys refused by our own guard
- β³ ποΈ #110 Assigning a task told nobody
- β³ πͺ #107 Signed out while still working
- β³ π #103 "Share with Requester" reached nobody
- β³ π #102 Search found nothing for hyphens
- β³ πͺ #101 Source code editor opened behind
- β³ βοΈ #88 Subtasks could not be ticked off
- β³ π» #84 Asset deep link selected nothing
- β³ π« #79 A new ticket arrived with no status
- β³ π§ #79 A ticket from email did not say so
- β³ π #78 Bell opened to nothing
- β³ π¬ #77 Mail only collected from Inbox
- β³ π #74 The default password could not be changed
- β³ π¦ #70 Renaming an impact level
- β³ π€ #67 App-only mailboxes could not send
- β³ π #45 Verify only ever worked for Microsoft
- β³ π #45 IMAP reported as not authenticated
- β³ βοΈ An email template stopped escaping itself
- β³ π The portal dashboard showed the wrong time
- β³ π’ The folder said 99 and the list showed 96