-
-
Notifications
You must be signed in to change notification settings - Fork 29
Mobile Friendly System Wiki
The twentyβfirst module and the last one in the waffle menu: seven pages β browse, search, database tables, scan management, and the file, function and table detail pages. Shipped in #1487β#1490, mobile.css v135 / mobile.js v57, LAYER 36.
Read MobileβFriendly first for the strategy and the one hard rule, and Techniques & Tricks for the catalogue this round draws on.
Every wiki_* table on the development install is empty β the codebase has never been scanned β so all seven pages draw "no scan data" and there is nothing anywhere to measure. The first probe run came back clean on every page, and it was worthless.
π An assertion that cannot fail is not passing, it is absent β Β§28's rule and Β§30's in a third form. Β§28's version is a document pinned to
100dvhso it can never be taller than the screen; Β§30's is a body that clips so it can never be wider. This one is a page with no rows, and it is the easiest of the three to walk past, because the page looks finished.
Every page takes its data from api/wiki/*.php, so the fix was to stub fetch in the harness and reβrun each page's own loaders against fixtures shaped like this codebase's worst realistic case β long file names, long function names, descriptions that are whole sentences. The real renderers do the rendering; only the transport is fake, and not one row is written to the database.
-
The fixtures have to match the real vocabulary.
table.phpgroups its references bytypeOrder = ['SELECT', 'INSERT', β¦]and my fixture used lowercase, so nothing matched and the reference lists silently never rendered. And a screenshot showed acssfile type with no badge β which turned out to be my fixture inventing a type: the scanner only ever emits PHP or JS ($IncludeExtensions = @("*.php", "*.js")) and the page styles exactly those two. Reporting that as a bug would have been reporting a fault in my own test data. - The loaders are async even against a stub. Tapping straight after calling one taps a tree that has not rendered, and the scenario reports "no leaf found" β which looks exactly like a leaf that cannot be tapped.
.wiki-container, .wiki-detail (Γ3), .wiki-tables, .wiki-scan and .wiki-search are all height: calc(100vh - 48px), so 36a is one rule for all seven pages. The System round's find the shared shells before estimating β except that here the shell is a shape rather than an include, which grepping the container class per page still finds.
Five of the seven could not be scrolled at all before β search and the fileβdetail page were the two that already could β because 100vh is the large viewport on iOS and LAYER 2 already makes <body> a 100dvh flex column; the shell only has to take what is left. This is the seventh module built that way.
.wiki-sidebar 280px fixed
.wiki-main 80px <- what is left of 360
.file-table 1110px <- clipped inside those 80
A table thirteen times wider than the pane holding it, and the page passed every containment check while doing it because .wiki-container and .wiki-main both clip (Β§30). The secondβnarrowest pane in the rollout after Workflow's 20px, and like that one it is arithmetic rather than a bug: a 280px fixed sidebar beside anything, on a 360px screen, leaves 80.
A stacked tree would spend ~200px of every screen on navigation you have finished with, which is exactly what Ed objected to on the contracts screen (#1375, "a contract gets the whole phone screen"). And a tree cannot become the chip strip CMDB's flat class list became, because the hierarchy is the information β api and api/tickets are different places only because the indentation says so.
So it goes where the Calendar sidebar went: a .mobile-sheet behind one button, with the real node relocated rather than cloned (a clone would give the page two #folderTrees and loadFolderTree() would render into whichever came first), moved lazily on first open, and moved back if the viewport goes wide again.
β Zero new translation keys. The button's label is read out of the sidebar's own .sidebar-title, which the page already prints translated; the sheet's Close is common.close.
The first version closed the sheet whenever a folder was tapped. Driving it showed why that is wrong, and it is not a small wrong:
selectFolder() expands the tapped folder's children as well as selecting it β that is the page's own behaviour, and it is why the tree opens collapsed. So closing on every tap would shut the sheet at the exact moment it had just revealed the next level down, and api/tickets would have been unreachable on a phone, however many times you tried. Every tap on api would filter the list and throw away the thing you were navigating towards.
π A parent is a step; a leaf is an arrival. Tapping a parent expands it and the sheet stays; tapping a leaf closes it.
renderTreeonly puts a chevron glyph in.tree-togglewhen a node has children, so the markup already says which is which and nothing has to be inferred.
includes/header.php carries a perβanalyst leftβpanel mode (system_wiki_sidebar_mode). Set to hover, it collapses the sidebar to a 16px hotβzone that expands on hover β and a phone has nothing to hover with. Measured with the mode forced on: position: absolute, min-width: 16px. This is the Contracts fault exactly, and the third module to ship the pattern.
!important, which the Contracts fix did not. Β§24: header.php emits its <style> inside <body>, so it loads after mobile.css however the head is ordered. Its rule is .wiki-container.sidebar-hover .wiki-sidebar:hover β (0,4,0) β and the module marker takes mine to (0,4,0) too, a tie the later sheet wins. The specificity trick that has worked all rollout is simply not available here.
.history-table w=320 rowH=170 every <th> measuring 0
Contained, and crushed into a 170px row β but the why is Β§15 in its most exact form yet. LAYER 15b's .history-table card feed is completely unscoped, written for the Assets history and custody trails, and this page happens to use the same class name. The moment it opted in it inherited half a card feed from a module it has nothing to do with: display: block on rows and cells, thead { display: none } β which is why every column measured zero β and no labels, no card, and seven values stacked one per line with four of them bare numbers.
π LAYER 35's headline was that a shared class means a fix is shared. This is the same coin's other face: it means a design is shared, and a design is a decision about content this module never made. The tell is a
<th>measuring 0.
β¬ 15b is left alone β it is correct for Assets, its own comment explains that it avoids column indices deliberately because Assets has two variants of the table, and reβscoping a shipped rule to fix a page it was never aimed at is the larger risk. LAYER 36g takes what 15b already did right and adds the card and the labels. And the hidden head is still in the DOM, which is all the Β§21 harvester needs β hiding a head is not the same as removing it.
Measured with fixtures, at 360Γ740:
| section | before | row height | answer |
|---|---|---|---|
| Functions | 824px | 83 | card feed β the description column is a sentence |
| Classes | 460px | 116 | card feed β a sentence wrapping inside 104px |
| Dependencies | 444px | 33 | left a table, just allowed to wrap |
| Dependents | 320px | 32 | left completely alone |
| Database Tables | 320px | 33 | left completely alone |
| Session Variables | 324px | 32 | left completely alone |
The best Β§11 example since LMS's fourβtablesβthreeβanswers. Dependencies is 84px over and the whole of that is one long file path, so it only needs permission to wrap β a threeβcolumn row reading require Β· includes/db.php Β· L6 is a good table row and a card would be worse.
The functions description cell carries an inline max-width:300px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap. An inline style beats any stylesheet rule that is not !important, and Β§14 says a flex item's automatic minimum size is its content width β so one nowrap cell would have pinned every card wider than the screen while the document stayed perfectly contained. The same thing as .log-datetime in Reporting, with no way to outβspecify it.
The first draft keyed the two feeds on .section:nth-of-type(1) and (2), which matched nothing: :nth-of-type counts among siblings of the same tag, and every one of these is a <div> sitting after .breadcrumb and .file-header, which are also divs. Measured: every cell still order: 0, six tables still tables, the layer with no effect at all β Β§9's symptom with a completely different cause.
β And :nth-child(3)/(4) would have been worse than wrong rather than merely wrong: the Classes section is rendered only when the file has classes, so any positional selector points at a different table depending on which file you opened.
π So it keys on the thing that decides the treatment: how many columns the table has.
:has(thead th:nth-child(5))is Functions, fourβbutβnotβfive is Classes, three is left alone β which is also exactly how Β§11's decision was made, so the selector now says the same thing the comment does. Robust to the conditional section, to reordering, and to a seventh section being added later.
table-layout: fixed on every .detail-table to make that path wrap made the four small tables three equal 112px columns β Type and Line given as much room as a file path. overflow-wrap: anywhere is enough on its own: it drops the cell's minβcontent width to one character, which is all the auto layout needed in order to be allowed to shrink it.
.tables-table w=634 rowH=35
cols Table Name 151 Β· Files 58 Β· Total Refs 61 Β·
SELECT 74 Β· INSERT 75 Β· UPDATE 80 Β· DELETE 75 Β· JOIN 61
tickets / 96 / 412 / 268 / 21 / 94 / 6 / 23 is Β§21's own case and needs all seven labelled β the most this list has ever taken, and right for the same reason LAYER 33b needed six of nine.
β But not seven identical lines, which is the spreadsheet effect Β§21 warns about. The five operation counts are a set you read together β that is the whole point of the page β so they stay on one wrapping line as labelled badges, and only Files and Total Refs take a line of their own. The colour on each badge already distinguishes them for anyone who can see it; the label is what makes that true for everyone else.
Search needed nothing for its results. All three panels render .result-item β a bordered block with a title, a meta line and a description β which is already a card, and already the form every other list in this rollout had to be converted into. Only the search bar and the three counting tabs needed anything.
| check | result |
|---|---|
| width containment, 7 pages @360Γ740 |
docScrollW = 360 on every one |
| reachability (Β§28) | a real scroller or a genuine fit on all seven; five had neither before |
| the folder sheet, driven | button 336Γ42 and a tap reaches it; sheet 360Γ740 on screen; the real sidebar moved in and #folderTree still unique; rows 360Γ50; a parent expands and the sheet stays (1 β 5 visible rows); a leaf closes it and the list retitles |
| the chevron | never closes the sheet |
| harvested labels | 10 / 28 / 12 cells stamped, counted not eyeballed |
| the hover rail forced on |
position: static, width: auto, min-width: 0 β the !important beats the inβbody <style>
|
| desktop control @1100Γ900 | every element's box identical with mobile.css disabled in place, all 7 pages |
and mobile.js at desktop width |
subβbar hidden, sheet hidden, sidebar back in the container, .wiki-container back to its own height: 852px; flex: 0 1 auto; overflow: hidden β because the desktop control cannot prove this: both its snapshots are taken after mobile.js has run, so an injected node is in each and diffs to nothing |
| nested CSS comments (Β§17) | clean, braces balanced |
| other pages | diff contains nothing but the two version numbers |
π The desktop control has a blind spot and it is worth naming. It proves
mobile.cssmoves nothing. It cannot prove the same ofmobile.js, which injects nodes β so anything that adds chrome needs its own desktop assertion, separately.
- 36a the six shells, in one rule
-
36b the hover rail, with
!importantβ Β§24, and the first time the specificity route was closed -
36c the folder tree as a sheet;
.wiki-mainfrom 80px to full width - 36d the stats bar twoβup, search box promoted above the figures
-
36e the file list as a card feed with
LinesandFunctionsharvested - 36f eight columns, seven labels, five of them kept on one line as a set
- 36g the scan history β and another module's feed already on it
-
36h six tables, three answers, one inline
nowrap, and a selector keyed on column count - 36i the breadcrumb, on all three detail pages
-
36j/36k a table's references and a function's signature:
overflow-wrap, nottable-layout: fixed - 36l search, which needed nothing but its chrome
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
- β³ πΌοΈ 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