Repository navigation
Mobile Friendly Network Mapper
The seventeenth module, and the second canvas after Process Mapper. Three pages β the diagram list, the mapper and the guide. Shipped in #1464β#1468, mobile.css v129 / mobile.js v52, LAYER 31.
β οΈ This round shipped in two halves, and the second one overturns the first's scope. #1464β#1467 made the module readable and said plainly that authoring stayed on the desktop, for a reason worth keeping (below). Ed then asked for the drag, so #1468 added placing, moving and deleting a node with a finger β see Ed asked for the drag anyway. The reasoning that declined it is left standing rather than rewritten, because it is what shaped what got built: the objection was never "placing is impossible", it was "placing without a way back is a trap", and the answer was to supply the way back. Drawing connectors is still desktop-only.
Read MobileβFriendly first for the strategy and the one hard rule, and Techniques & Tricks for the catalogue this round draws on.
Run before touching anything, and it settled most of the round:
| Grep | Result |
|---|---|
a preβexisting @media in the module |
none β but see the guide below, where a shared stylesheet had one |
localStorage |
none |
:hover revealing a control (Β§26) |
one, and it is already correct β below |
| its own modal class | π΄ .nm-modal / .nm-modal-overlay, invisible to LAYER 3 β five of them |
the nm- prefix in any other module (Β§15) |
clean β no other module or shared stylesheet defines a single .nm-* class |
That last one exists because of what pm- did to Process Mapper: two modules independently picked the same threeβletter prefix and one module's entire layout mode ended up arranging the other's panel. It is now a standing check, and this time it came back clean. Everything is still scoped on [data-mobile-module="network-mapper"], which costs nothing and means the next module to reach for nm- cannot inherit this layer.
Β§22 splits drag into the kind that survives touch and the kind that does not:
network-mapper.js : 17 Γ mousedown / mousemove / mouseup
0 Γ touchstart / touchmove / pointerdown
So a node cannot be repositioned with a finger, and the connector drag β mousedown on an edge handle, mousemove, mouseup β has no touch path either. Between them that is the whole of authoring's geometry.
π Say what the round is for, out loud, before building it. The round makes a map readable: reach the list, open a diagram, pan and zoom it, tap a node and read its CMDB record, follow the link through, switch versions, present, export.
The class palette is HTML5 dragβandβdrop, not a handβrolled one:
`<div class="nm-palette-tile" draggable="true" β¦>` // + dragstart / dragover / dropwhich Β§22 says does work on a phone β iOS Safari has driven that API from a long press since iOS 11. Dropping a new node is therefore very probably possible here, and onCanvasDrop places it at the drop point and opens the object picker, so it would even land where you meant.
It is still not offered, and the reason is the asymmetry:
β You could place a node and then never move it again. A misβdrop on a 360px canvas would be permanent. The missing half is not the action, it is the recovery β and a control that half works is worse than one that is honestly not there.
That is a different argument from Process Mapper's ("the drag simply does not fire") reaching the same answer, and it is the one worth remembering: Β§22 tells you whether a drag fires, not whether the feature it belongs to is usable. Check what happens after the drag as well.
.nm-node:hover .nm-edge-handle,
.nm-node.selected .nm-edge-handle { opacity: 1; }.selected is in that selector list, and a tap selects a node β so the connector handles are revealed on touch. Not a Β§26 fault, and the first module in the rollout where the pattern arrived written correctly. (They still cannot be dragged; but they are visible rather than silently absent, which is precisely what Β§26 asks for.)
All three pages open with body { height: 100vh; overflow: hidden }. So docScrollW === innerWidth cannot fail on any of them, and per Β§30 an assertion that cannot fail is absent, not passing. Every number below is a box, not the document.
What the boxes said, on the real page at 360Γ740, before anything was written:
diagram.php .nm-palette x=0 w=240 67% of the screen
.nm-canvas x=240 w=119 π΄ the diagram
.nm-editor-actions w=1206 right=1242 882px off the edge
.nm-editor-title w=4 scrollW=87 the diagram's name
help.php .help-container h=8977, NO scroller at all
index.php .nm-toolbar .actions right=464
.nm-card w=320 inside a w=304 grid
.nm-page and .nm-editor are both height: calc(100vh - 60px) β the fifth and sixth container in the rollout built that way (LAYER 21, 27, 29, 30, and these two). Handed back to LAYER 2's 100dvh flex column with flex: 1 1 auto; min-height: 0, the min-height being Β§28 and not optional.
A fixed 240px column taking 67% of the viewport. The third module to hit Β§30's rule of thumb after the Forms approvals sidebar and CMDB browse: no pane holding content may be narrower than roughly 60% of the screen, and 119px is not a layout, it is a symptom.
It becomes a slideβin sheet with an injected button β LAYER 4's chrome, 29d's and 30b's. position: fixed takes it out of the flex row, so the canvas gets its width back with no rule of its own.
palette_title β "CMDB classes" β is directionβfree, stays, and is honestly what the panel now is: the classes in this diagram with their object counts. Zero new locale keys, and the sheet button reuses that same string.
β The canvas was already overflow: auto over a 3000Γ3000 spacer, so panning is native touch scrolling and needed no JavaScript β the same enabler Process Mapper had. Measured: scrollHeight 5000 in a clientHeight of 559, reaching 4441.
.nm-editor-actions holds fifteen controls and carries flex-shrink: 0, so the bar's other child absorbed the whole difference: .nm-editor-title-area at w=0, and the diagram's own name rendered and completely invisible. The standing lesson that flex-shrink: 0 is the commonest reason a row will not fit a phone, and that it fails by crushing its neighbour rather than by spilling itself β with .nm-canvas-wrap clipping, nothing could report it.
The bar stacks: title on its own line, actions beneath as a sideways strip (30c's answer, honest here because zoom, present, both exports and the version picker all work by tap).
And then the title was still 4px. The measurement that mattered:
.nm-back-btn w=101 flex 0/0/auto "β All diagrams"
.nm-editor-title w=4 flex 1/1/auto "My network" β grow AND min-width:0 already set
.nm-version-pill w=76
.nm-palette-btn w=131 flex 0/0/auto "β° CMDB classes"
101 + 76 + 131 + three 8px gaps = 332 of a 336px line.
π
flex-growdistributes POSITIVE free space. When the other items already fill the line there is none, and a grow factor is inert β which looks exactly like a rule that did not apply.min-width: 0had made the title shrinkable, and that was the whole of its effect: it shrank.
Fixed by wrapping the row and giving the title Β§23's middle basis, flex: 1 1 60% β auto puts the pill on a line by itself, 0 lets all four back on to be crushed again. Title now 227px.
LAYER 30f's shape: the 320px rightβhand column moves to the bottom, and the selected node is scrolled into the strip above it. A tap already opens it β browsers synthesise the compatibility mouse events after a tap, onNodeMouseDown fires, the node gets .selected, and the module's own translated panel appears. Nothing new built, no string invented.
π΄ But 58dvh copied straight from LAYER 30f left 71px of map. Process Mapper's bar is 48px; this one is a header (48) plus a stacked editor bar (141) plus a meta row (51) = 240px before the map gets a pixel. The selected node was in the strip β the assertion passed β and there was no room for anything around it, which is most of why you look at a network diagram.
π A dvh split only works when the two halves are the whole viewport. Copy one out of a module with a 48px bar into a module with a 240px one and it silently spends the difference on the half you cared about.
Three changes: the sheet to 55dvh; the meta row hidden while the sheet is open (the diagram's provenance is worth nothing while you read one node's record, and it comes straight back); and the canvas capped by margin-bottom: 55dvh rather than max-height, because a margin on a flex: 1 item in a 100dvh column leaves it exactly the rest β whatever the bar above has wrapped to, in any locale. A max-height is a second guess at the same number.
Result: 142px of map, up from 71, with the node and its neighbours and their connector labels all visible.
-
31b β the list toolbar, whose actions ended at x=464. Stacks; the search takes the width.
β οΈ Β§16: this row sets bothjustify-contentandalign-items, and both swap meaning when a row becomes a column. Restated explicitly rather than inherited β same for 31d's bar. -
31c β the card grid,
repeat(auto-fill, minmax(320px, 1fr))measured as a 304px grid holding 320px cards.auto-fillwill drop to one column but will not go below the minimum you gave it, and 320 is more than 360 has after gutters. Β§14's family seen from the grid side: a track minimum is a shrink floor by another name. -
31g β five
.nm-modalshells promoted to fullβscreen sheets. Fifth module to roll its own modal class (Problem Management, Change Management, Contracts, Process Mapper, this), and as always nothing in the measurements complains β a 95%βwide centred box is perfectly contained. Keyed on the shell, not the five ids. -
31h β the two dropdowns (
#versionsDropdown,#pageDropdown),position: absolute; right: 0; min-width: 320pxhung off buttons that 31d had just put in a sideways scroller. Bottom sheets, pure CSS β the page toggles them withstyle.display, which this does not touch, so there is nothing to keep in step. -
31i β the antiβzoom sweep, written as an exclusion (
input:not([type=checkbox])β¦) rather than a list of types, so a field added later is covered by default instead of forgotten.
help.php measured 8977px tall inside a clipping 740px body with no vertical scroller anywhere on it β seven of its eight sections unreachable, and every containment check clean. Β§28, and the cause is a third file styling the page β the LMS trap:
/* help.css, its own @media (max-width: 900px) */
.help-container { height: auto; } /* hand the scroll to the document⦠*/
.help-main { overflow-y: visible; }
/* inbox.css */
body { overflow: hidden; } /* β¦which cannot take it */The fix was three lines of optβin: LAYER 16h already gives .help-container the scroller role. Every other module's guide was fine because every other module's guide had opted in.
Each guide's own script does helpMain.scrollTo(β¦) for the jump and binds its scrollβspy to helpMain.addEventListener('scroll', β¦). Correct on a desktop, where .help-main is the scroller. Below 900px it is overflow-y: visible and the scroller is .help-container β and scrollTo on an element that does not scroll throws nothing and does nothing.
Measured identically on network-mapper, cmdb and lms, using Β§13's check rather than a scroll position:
tapped contents item 5
elementFromPoint(180,400) before: P after: P β the same node
=> π΄ inert
β Β§26's shape without the hover: a control that is present, looks live, and is connected to nothing. It reads as "this app doesn't do that" rather than as a bug, so in seventeen guides across sixteen shipped modules nobody has ever reported it.
Fixed once in mobile.js for all seventeen, gated on mq.matches, delegated. After:
scrollTo calls: #helpMain top=3458 | .help-container top=3533
.help-container scrollTop = 3533
elementFromPoint before: P -> after: DIV.help-card β
The first call is the page's own, still doing nothing; the second is this layer's, doing the work. The spy and the stripβfollow are the same fix β a highlight that never moves is the other half of the same broken control.
Β§30's addendum says distrust a new probe on its first red. Three of this round's four reds were the probe:
-
The palette sheet "did not open" β attribute flipped, panel still at
x=-295.--virtual-time-budgetmakessetTimeoutfire instantly on a virtual clock while CSS transitions run on the animation clock, so a 900ms wait was 0ms of transition. Withtransition: noneforced:x=0,visibility: visible,elementFromPointat its centre returning.nm-palette-body, 8 tiles, scrim closing it. -
The meta row "was clipped" in a screenshot. Yβpositions settled it:
span2 y=218againsty=199for the other two, row 51px tall β it wraps. I misread a 360px image. - The list page "was 460px wide and clipped" in a screenshot β search box and New diagram running off. That was Β§8's Windows clamp: the redirectβtoβtheβrealβpage route renders at ~500px and crops to the 360px window. With the scroller filter off (Β§18), zero elements exceeded 360px. Screenshot through a 360βpinned iframe, which is what Β§8 prescribes and what the working shots used.
Giving the card's action row flex: 1 1 100% made its button full width and 38px tall. The only action on that card is Delete, and the card's entire surface is the tap target for opening the diagram.
β Every number said it was an improvement: 22px β 38px is a tapβtarget floor being met, and a tapβtarget check would have passed it. Size is not the only property a control has β a
dangerbutton that grows also becomes easier to hit by accident, and on a card that is itself a button, that is the property that matters.
Thumbβsized in height, natural width, still pushed right by the row's own space-between. That is now four rounds in a row where the thing that mattered most was found by looking rather than measuring.
- All three pages contained at 360Γ740, measured by box rather than document (Β§30), with the scroller filter off as well as on.
- Each has a scroller that reaches its end (Β§28): list 747 of 747, canvas 4517 of 4517, guide 8480 of 8480.
-
The palette sheet driven open and shut β
x=0, 295px, visible,elementFromPointreturning the panel not the scrim, 8 tiles, hintdisplay: none, scrim tap closing it. -
A node tapped with a synthesised
mousedown, as a real tap produces: panelopen, 360 wide, top=333; the selected node moved y=662 β y=233, above the sheet and on screen; the detail body scrolls and reaches its end. -
The branding dialogue driven open: 360Γ740 sheet, grid stacked, inputs at 16px. The versions dropdown at
x=0 w=360 top=629β a bottom sheet. -
Desktop control at 1100Γ900, and it is byteβidentical to the preβchange baseline β
.nm-editor-title-area w=0,.nm-editor-actions w=1206 right=1242,.nm-palette w=240,.nm-canvas w=859,.nm-card 340Γ170, action button50Γ22. The injected palette button isdisplay: none. -
Process Mapper measured as a positive control at 360 and 1100 β unchanged at both, and
[class*="nm-"]returns 0 there. -
The help fix desktopβcontrolled on three modules: only the page's own
#helpMaincall fires, the mobile listener noβops, the page still scrolls. -
Β§17 caught a stray
*/introduced by editing a comment β braces balanced, and the rules after it were being dropped. The awk check is worth running after every comment edit, not just at the end. -
The Β§25 audit: the module's own diff is nine lines β three
<link>, three<body>markers, three<script>β and every one of the other 91 pages changed only its?v=. Zero desktop changes.
"on mobile version can you make it so you can drag something from the cmdb classes panel onto the diagram"
Which makes it the right thing to build. The objection above was not withdrawn, though, so what got built is all three halves, because the question that settles whether placement is a feature or a trap is what can a phone do about a mistake:
| gesture | ends by dispatching | |
|---|---|---|
| place | long-press a class tile, drag onto the map, release | a real drop DragEvent carrying a DataTransfer
|
| move | long-press a node, drag it |
mousedown on the node, then mousemove / mouseup
|
| delete | the node's own sheet gains a Delete | keydown { key: 'Delete' } |
π΄ The second and third are not extras.
deleteSelectedNode()is bound to Delete/Backspace and nothing else, and a phone has neither β so without them a node of the wrong class, or in the wrong place, was permanent. A feature whose undo needs a keyboard is a one-way door.
Each gesture ends by dispatching the event the module already listens for, so snap-to-grid, the model-coordinate maths (scroll offset, zoom, the half-icon centring), the read-only guard, the dirty flag, the connector cleanup and autosave all run exactly as they do under a mouse β including guards this layer would otherwise have had to duplicate and keep in step. Β§1's wrap, don't edit, applied to a feature rather than a layout.
deleteSelectedNode and openObjectPicker are not on window.NM, which turned out to be the useful constraint: reaching them through the module's own event handlers is better than exporting them would have been.
Β§22 says the palette's draggable="true" drag would probably work on touch. It is not what this uses, because the palette is a sheet covering the canvas β it has to get out of the way mid-gesture, and a native drag will not survive its source element sliding off screen. A hand-rolled touch drag can close the sheet and carry on. (touchmove/touchend are bound to document, not to the tile, for exactly that reason.)
β A side benefit worth banking: Β§22 says you cannot settle a drag headlessly, and that is true of the NATIVE one. A hand-rolled touch drag can be driven with synthetic TouchEvents, so all three gestures were exercised end to end before shipping. It is still not a device pass.
A short drag starting on the canvas is a pan; one starting on the palette is a scroll of the class list. Both had to stay exactly as they were. The gesture only becomes a drag after 320ms without the finger travelling more than 10px β the browser's own long-press-to-drag behaviour β and travelling first hands the gesture straight back. Verified: a 40px move before the timer left no ghost.
e.preventDefault() in a non-passive touchmove does double duty: it stops the canvas panning under the drag, and it suppresses the compatibility mouse events the browser would otherwise synthesise at touchend, which would arrive after ours and re-select.
A long press is also the system gesture for the selection callout and magnifier. Both elements already carried user-select: none, which is a different property and does not suppress the callout on its own β so -webkit-touch-callout: none is set on the tiles, the nodes and the ghost.
Driven end to end with synthetic TouchEvents on a throwaway diagram, because autosave is on and the dev database is real data:
place long press -> ghost present, sheet closed; ghost follows to (200,420);
elementFromPoint under the finger = the canvas; release -> object
picker OPEN with 6 objects; choosing one -> 1 node β
move long press -> .nm-touch-drag applied; drag -> left/top
180,160 -> 280,220, snapped to the 20px grid β
delete tap -> sheet open, Delete 73x42 at x=271 (thumb-sized,
NOT full width); click -> 0 nodes, sheet closed β
guard 40px of travel before the timer -> no ghost, still a scroll β
desktop 1100px: both injected buttons display:none, a long-press
drag produces no ghost, no picker, no node β
-
Drawing connectors. Still
mousedown/mousemoveon an edge handle, still desktop-only. The handles are visible on touch (they follow.selected), so this is a gesture that is missing rather than a control that is hidden. -
.nm-editor-actionsoverflows a 1100px desktop window (1206px of toolbar). Preβexisting, identical before and after this round, and not a mobile question β recorded here because the baseline measured it. - Present mode fits the whole diagram to the viewport and clips, so on a phone it is an honest but very small picture with no way to pan. Left alone; the normal canvas is the read path.
- MobileβFriendly Β· Techniques & Tricks
- Mobile: Process Mapper β the other canvas, and the round this one is modelled on
- Network Mapper β what the module does
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