Repository navigation
Process Mapper Developer Guide
What it does for the analyst: Process Mapper Β· on a phone: Mobile-Friendly: Process Mapper
A process is a flowchart: steps (boxes of a configurable type), connectors (arrows), and three things that are only there to organise the picture - lanes (horizontal bands), groups (rectangles behind steps) and notes (sticky notes). The editor is one hand-written JavaScript module (PM) on a single page: a sidebar list of processes on the left, the canvas in the middle, a detail panel on the right. It keeps the open process in memory and saves by sending all of it; the server deletes every row and inserts them again, so every save gives everything a new id. There is no versioning and no REST API.
The user page already describes every feature, including the right-click menu and the clipboard, in detail. This page is about how the code is put together.
| File | What it is |
|---|---|
process-mapper/index.php |
The whole editor page. Reads process_step_types and renders the toolbar buttons and the right-click Create new / Change to items from the active types; exports window.SHAPE_SIZES, window.STEP_TYPES (active and inactive) and window.STEP_TYPES_BY_SLUG
|
process-mapper/includes/shapes.php |
The twelve shapes and the default size of a new step of each - the one list both pages use |
process-mapper/settings/index.php |
Settings: the Step types tab and the Left panel tab |
process-mapper/settings/manifest.php |
Which capability guards each settings tab |
process-mapper/help.php |
The in-app guide |
assets/js/process-mapper.js |
The editor. const PM = (() => {...})(), started on DOMContentLoaded
|
assets/css/process-mapper.css |
All the shape geometry, as [data-shape="..."] rules |
api/process-mapper/*.php |
Eight endpoints (below) |
Seven tables, no tenant_id anywhere and no foreign keys - only indexes on process_id, lane_id and group_id (database/freeitsm.sql, includes/db_verify_indexes.php).
| Table | Columns |
|---|---|
processes |
id, title, description, created_by, created_datetime, updated_datetime, is_demo
|
process_steps |
id, process_id, type (the step type's slug, free text), label, description, url, x, y, width, height, color, color2, lane_id, group_id, is_demo
|
process_connectors |
id, process_id, from_step_id, to_step_id, label, is_demo
|
process_lanes |
id, process_id, label, color, color2, display_order, height
|
process_groups |
id, process_id, label, color, color2, x, y, width, height
|
process_annotations |
id, process_id, text, x, y, width, height, color (default #fff59d), color2
|
process_step_types |
id, name, slug (unique), shape, color, display_order, is_active, is_builtin
|
color2 everywhere means "gradient second stop"; NULL is a flat fill (fillStyle() builds the linear-gradient(135deg, ...)).
A lane has no y. Its top is worked out from the lanes above it: recomputeLaneBandTops() sorts by display_order and adds up heights into a cached _bandTop. A group has no lane_id - which lane it is in is worked out from its vertical centre whenever it matters.
get.php?id=N returns data = the processes row plus five arrays, each SELECT * from its table:
{
"success": true,
"data": {
"id": 4, "title": "Incident triage", "description": null, "created_by": 3,
"created_datetime": "...", "updated_datetime": "...", "is_demo": 0,
"steps": [{ "id": 51, "process_id": 4, "type": "decision", "label": "P1?", "description": "",
"url": null, "x": 300, "y": 40, "width": 150, "height": 150,
"color": "#f59e0b", "color2": null, "lane_id": 7, "group_id": null, "is_demo": 0 }],
"connectors": [{ "id": 90, "process_id": 4, "from_step_id": 50, "to_step_id": 51, "label": "", "is_demo": 0 }],
"groups": [],
"lanes": [{ "id": 7, "process_id": 4, "label": "Service desk", "color": "#f5f7fa",
"color2": null, "display_order": 0, "height": 180 }],
"annotations": []
}
}openProcess() maps these into the module arrays steps, connectors, groups, lanes and annotations. Two renames to know: a connector becomes {id, fromId, toId, label} in the browser and goes back as from_step_id / to_step_id; every object also carries its element in el.
Unsaved things get a negative tempId from nextTempId--, and anything that points at them (a connector end, a step's lane_id or group_id) holds that negative number until the save. The lookups all accept either: getStep(id), getGroup(), getLane(), getAnnotation(), and the groupRef() / laneRef() / annotationRef() helpers return id if there is one, else tempId. Selection is the same values: selectedStepIds (a Set), selectedConnectorId, selectedGroupId, selectedLaneId, selectedAnnotationId.
HTML for shapes, SVG for lines. Not a canvas element.
-
#pmCanvas(.pm-canvas,tabindex="0") is the scroll container and holds everything.#connectorsSvginside it holds the connectors;addArrowheadDef()adds the marker. -
renderAll()removes and rebuilds in back-to-front order: lanes (renderLanes()), groups (createGroupEl()), steps (createStepEl()), notes (createAnnotationEl()), thenrenderConnectors(). - A step is a
<div class="pm-step" data-type="<slug>" data-shape="<shape>">.shapeForSlug()looks the slug up inSTEP_TYPES_BY_SLUG; the CSS draws the shape. Thecloudshape also needs the<clipPath id="pmShapeCloud">SVG block inindex.php. The step gets four.pm-edge-handledots and, if it has aurl, an<a class="pm-step-url-badge">. -
renderConnectors()redraws every connector as a straight line between the facing sides of the two step boxes (calcConnectorPoints(), the larger of the two gaps wins), with a wide hit path and an optional label. Connectors store no geometry, so they follow whatever moved. - There is no zoom. Coordinates are canvas pixels: mouse position minus the canvas's screen position plus its scroll.
The palette is data, not code:
-
process-mapper/index.phpqueriesprocess_step_typesat page load. A type added in Settings appears in the editor only after a reload, because the toolbar and context-menu markup is rendered by PHP. -
stepDims(slug)gives a new step its size fromSHAPE_SIZES[shape]and its colour from the type.addStep()andcreateConnectedStep()both use it. -
changeStepType()swaps the slug, takes the new type's colour and clears the gradient. -
A step whose slug no longer matches a type falls back to
FALLBACK_SHAPE('rounded') andFALLBACK_COLOR. (The comment indelete_step_type.phpsays "plain rectangle" - the code says rounded.)
| Endpoint | Guard | Does |
|---|---|---|
get_step_types.php |
module access | Every type, ordered |
save_step_type.php |
Cap::PROCESS_MAPPER_STEP_TYPES |
Create or update. Shape must be one of the twelve, colour #rrggbb. On create the slug is made from the name and made unique with -2, -3...; the slug never changes after that
|
delete_step_type.php |
Cap::PROCESS_MAPPER_STEP_TYPES |
Refuses is_builtin = 1 (the four seeded: process, decision, start, document) |
reorder_step_types.php |
Cap::PROCESS_MAPPER_STEP_TYPES |
{order: [ids]} rewrites display_order in steps of 10 |
The capability and its umbrella Cap::PROCESS_MAPPER_MANAGE are declared in includes/capabilities.php and mapped to the tab in settings/manifest.php. Drawing processes needs plain module access only. See Roles - Developer Guide.
One set of document listeners. bindCanvasEvents() registers a single mousemove and mouseup on the document. onDocMouseMove() and onDocMouseUp() look at which drag state is set and hand over: annotationDragging, then laneDragging, then groupDragging, then dragging (steps), then connectDrag, then rubberBand. Adding a new draggable thing means adding a branch to both.
| Gesture | Code |
|---|---|
| Select / move steps |
onStepMouseDown() - Ctrl / Cmd toggles membership of selectedStepIds; otherwise it selects and starts dragging with the start position of every selected step, so the group moves together by the snapped delta. The detail panel opens in onDocMouseUp() only when dragging.moved is false |
| After a step move |
reassignStepLanes() and reassignStepGroups() re-derive lane_id / group_id from each step's centre |
| Rubber band |
onCanvasMouseDown() starts it only on the bare canvas, the empty-state element or a lane body. On mouseup, steps whose centre is inside are selected; a box under 5px is a click and clears the selection |
| Draw a connector |
startConnectDrag() from an edge handle; onDocMouseUp() finds the target with findStepAt() and calls addConnector()
|
| Click to connect |
startClickConnect() sets clickConnectFromId; the next step mousedown runs completeClickConnect(), a canvas mousedown or Escape runs cancelClickConnect()
|
| Rename |
onStepDblClick() lays an input.pm-rename-input over the step; connector labels use editConnectorLabel()
|
| Groups |
groupAtPoint() returns the smallest group containing a point. onGroupMouseDown() / onGroupDocMouseMove() / onGroupDocMouseUp() move or resize, carrying the steps whose group_id matches |
| Lanes |
onLaneHeaderMouseDown() reorders, onLaneDividerMouseDown() resizes. reorderLaneTo() snapshots each step's (and group's) offset inside its lane before the move and re-applies it after; resizeLaneTo() (minimum 80px) shifts everything whose centre is below the lane. autoAssignAllStepLanes() tags every step first so steps drawn before the lanes existed come along too |
| Notes |
addAnnotation(), onAnnotationMouseDown() and its two document handlers. Notes take no part in lanes, groups or connectors |
| Right-click menu |
onStepContextMenu() stores ctxTargetStep and ctxRightClickPos; showContextMenu() enables / hides items; bindContextMenu() wires them. Clipboard: cutStep(), copyStepForPaste(), pasteStep(), duplicateStep(), all through cloneStepData() and placeClonedStep(); formatting: copyFormat() / applyFormat()
|
| Delete |
deleteSelected() - whichever of note, lane, group, connector or steps is selected, in that order. Deleting a group or lane leaves its steps in place with the id cleared; deleting steps drops their connectors |
Keyboard. onCanvasKeyDown() is on the canvas element, not the document, so these only work while the canvas has focus - onStepMouseDown() and onCanvasMouseDown() focus it for that reason: Delete / Backspace, arrow keys (nudge by GRID, 20px), Ctrl+S, Ctrl+A. The sidebar shortcuts (/, Alt+Shift+O, arrows, Enter, Delete on a row) are document-level, in bindProcessListKeys().
There is no undo. Nothing records history.
addConnector() refuses a second connector between the same two steps in either direction. (Network Mapper only refuses the same direction.)
markDirty() -> setStatus('unsaved') -> scheduleAutosave() (2s debounce, only if autosave is on)
save() -> POST save.php (everything) -> openProcess(id, preserveDetail = true) -> re-select
-
save()sends{id, title, steps, connectors, groups, lanes, annotations}. Each item carriesid(or null) andtempId. -
api/process-mapper/save.phpruns in one transaction: update or insert theprocessesrow, delete every connector, step, group, lane and note of the process, then insert lanes first (building$laneIdMap), groups ($groupIdMap), steps (translatinglane_id/group_idthrough those maps, building$idMap), connectors (through$idMap) and notes. Each map is keyed byid ?? tempId. -
A connector whose end is not in
$idMapis dropped without an error, and so is alane_idorgroup_idthat maps to nothing. - Because every id is new,
save()first remembers what was selected by position - a step byx, y; a group or note byx, y, width, height; a lane bydisplay_order- and finds it again after the reload. Two steps at exactly the same position would re-select the first. -
scheduleAutosave()waits while any drag is in progress (dragging,groupDragging,laneDragging,connectDrag,rubberBand), because the reload destroys the dragged elements. - Autosave on / off is the user preference
process_mapper_autosave; the sidebar mode isprocess_mapper_sidebar_mode. -
A new process is created by
submitNewProcess()postingsave.phpwith noidand empty arrays. -
get.phpandsave.phpwrap the groups, lanes and notes tables intry / catch, so an install that has not run DB Verify since those shipped still loads and saves the rest.
The open process's id is in the address bar: syncUrl() uses history.pushState only when the URL actually changes, openInitialFromUrl() opens ?id= once the list has loaded, and onPopState() follows back / forward.
| Endpoint | Does |
|---|---|
list.php |
Every process, newest first, with created_by_name, step_count and lane_count for the sidebar hover card |
get.php?id= |
One process (above) |
save.php |
Create or full replace (above) |
delete.php |
Deletes the process, its connectors and its steps |
All check the session and requireModuleAccessJson('process-mapper'). The write endpoints also call csrfRequireWriteMethod(), on top of the central check in CSRF protection.
-
Documents on a process:
toggleProcessDocs()mounts the shared documents panel withparentType: 'process'- Attached documents - Developer Guide. -
Demo data:
autoLayoutProcessMapper()inapi/system/import_demo_data.phplays out demo steps left to right, because the demo JSON has no coordinates - Demo data - Developer Guide. -
Export:
captureCanvas()(html2canvas, vendored),exportToPdf()(jsPDF),exportToMermaid(). The user page covers what each produces.
- π΄ TRAP (in the code):
e.code, note.key, for Alt+Shift+O - on a Mac the key types Γ. InbindProcessListKeys(). - π΄ TRAP (in the code): look for
.fitsm-confirm-overlay.active-confirm.jsleaves its overlay in the page after the first confirm, so its presence means nothing. IndialogOpen(). - π΄ TRAP (in the code):
esc()must escape quotes.textContenttheninnerHTMLescapes< > &but not"or'; the two extra replaces make it safe inside an attribute (a connector label invalue="", the sidebar'saria-label). See Code traps. - π΄ Every id changes on every save. Never hold a step, lane or group id across a save.
β οΈ delete.phpleaves rows behind. It deletes connectors, steps and the process, but not the process'sprocess_lanes,process_groupsorprocess_annotations- and with no foreign keys nothing cascades.β οΈ processes.descriptionis never written by the editor.save.phponly setstitle; the sidebar hover card shows a description if one is there.β οΈ The Unsaved changes confirm inopenProcess()is hard-coded English, not a translation key.- Processes have no company; anyone with module access sees every process.
- Process Mapper Β· Mobile-Friendly: Process Mapper
- Network Mapper - Developer Guide - the sister canvas, which copied this one's connector geometry and arrowhead pattern
- Roles - Developer Guide Β· Database verification - Developer Guide
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
-
Architecture
- β³ π§Ή Root folder tidy (URL routing)
- β³ π Database Integrity (FKs & constraints)
- β³ π οΈ Database Verification β Developer Guide
- β³ π§© Service Layer (UI + API share code)
- β³ πͺ€ Code traps (the TRAP: comments)
- β³ π Commenting for the Engineering wiki
- β³ π οΈ Engineering wiki scanner - Developer Guide
- β³ π Service-Layer progress
- π€ Contributing (read before a pull request)
- π§ͺ Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- π± Currencies
- π§ The sidebar (optional module rail)
- π§© Preference templates
- π Display order (modules, Watchtower cards) β Developer Guide
- 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: Files
- β³ π 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
- β³ π Proposal: custom fields, inventory import and change evidence (#194)
- β³ π οΈ Custom fields, evidence + inventory - review (#195-#197)
- β³ π οΈ Long forms in a task's Work view - review (#198)
- β³ π« 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: Analysts
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π οΈ Inbox.js map β Developer Guide
- β³ π οΈ Tickets settings β Developer Guide
- β³ π Ticket workspace - design (PR #206)
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π§Ύ Custom ticket fields
- β³ π·οΈ Ticket categories
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ π Attachment size limits and large emails
- β³ 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
- β³ π Case reports - a ticket and its work as one PDF
- β³ π 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
- β³ π Leases
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π§© Asset type layouts
- β³ π Inventory freshness
- β³ π Connected assets
- β³ π Asset review
- β³ π·οΈ 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
- ποΈ Files
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- π‘ Zabbix (monitoring)
- π¬ Microsoft Teams alerts
- π 3CX (phone system)
- 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
- β³ π³ The Docker image was missing PHP extensions
- β³ π A failed DNS lookup read as a missing record
- β³ π A link could change data
- β³ π Access outlived its removal
- β³ π Knowledge articles could run scripts
- β³ π Knowledge writes ignored permissions
- β³ π Company boundaries (Oct 2026)
- β³ π Smaller gaps (Oct 2026)
- β³ π Escaped text could leave its attribute
- β³ π Task descriptions were not cleaned
- β³ π Task documents ignored company scope
- β³ π Repeating tasks ignored company scope
- β³ π¬ #183 Mailbox checks overlapped
- β³ π #188 Transfer lock shown off when unknown
- β³ β #184 Subtask names and Overdue
- β³ π§© #186 Teams: the buttons did nothing
- β³ π Text in a change could run script
- β³ π AI settings changed without the AI permission
- β³ πͺ #178 Deleting an analyst failed
- β³ π« A team with no departments showed 0 tickets
- β³ π Projects: adding a phase did nothing on Save
- β³ π Leavers could still sign in
- β³ πΌοΈ 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