-
-
Notifications
You must be signed in to change notification settings - Fork 27
Mobile Friendly Self Service
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.
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: 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
-
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
- β³ π’ 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