-
-
Notifications
You must be signed in to change notification settings - Fork 29
Mobile Friendly Workflow
The eighteenth module, and the third canvas β but the first one that is fully usable on a phone, which is a fact about the module rather than about how much harder anyone tried. Seven pages: the rules list, the editor, the execution log, settings and three guides. Shipped in #1469β#1471, mobile.css v131 / mobile.js v54, LAYER 32 β plus LAYER 33 for system/webhooks/, the page this round missed because it sits in another directory and the module nav links to it anyway.
Read MobileβFriendly first for the strategy and the one hard rule, and Techniques & Tricks for the catalogue this round draws on.
| Grep | Result |
|---|---|
a preβexisting @media
|
π΄ one, at 760px β benign, but it leaves a band. Below |
localStorage |
none |
:hover revealing a control (Β§26) |
none |
| its own modal class | π΄ two β .wf-ai-modal, .wf-var-modal, both invisible to LAYER 3 |
the wf- prefix in other modules (Β§15) |
π΄ shared β wf-diagnosis, wf-count, wf-err, wf-item, wf-name also appear in system/webhooks/ and watchtower/
|
That last one is the Process Mapper pm- collision again, and this time the prefix really is shared. None of those five classes is styled by workflow.css or touched here, but that is luck rather than design β so every rule in the layer is scoped on [data-mobile-module="workflow"], which makes the question moot.
Β§22 gives the same answer as it did for both mappers:
workflow-editor.js : 6 Γ mousedown / mousemove / mouseup
0 Γ touchstart / touchmove / pointerdown
What is different is what the drag is for:
const COL_X = 220; // x at which auto-layout stacks nodes
addConditionNode(COL_X, lastY + ROW_GAP, β¦)Nodes are added by toolbar buttons and autoβstacked in a single column. They are configured in the detail panel. Deleting one is a real button (WFE.deleteSelected()), not a keypress. So the whole of authoring already works by tap, and dragging a box is decoration β rearranging a layout the module generated for you.
π Β§22 tells you whether a drag fires. It does not tell you what breaks if it never does. Ask what the drag is for before scoping a canvas. Network Mapper needed three new gestures because placing and deleting a node were behind a mouse and a Delete key. This module needed none, because neither is.
Three canvases, three different answers, all from the same grep.
Every page sets overflow: hidden on <body> or .wf-layout, so docScrollW === innerWidth cannot fail. Measured by box at 360Γ740, before anything:
editor .wf-detail w=340 β of a 360px screen
.wf-canvas w=20 β the workflow diagram
.wf-toolbar-left w=381, right=393
list 6-col table 637px in a 312px parent; three cells crushed to
78Γ194, 82Γ194, 78Γ194 (Β§11's signature)
executions 6-col table 615px, two crushed; a 538px-tall filter stack;
the tally squeezed to 252px
20px of canvas. CMDB browse and the Forms approvals sidebar both left 100px; Network Mapper 119px. And unlike either mapper's panel this one is not a drawer β .wf-detail is width: 340px; min-width: 340px, present and open at all times, because it is an inspector.
before opt-in .container w=360, clipping a 637px table with its own overflow
after opt-in .container w=705 β the table's width, not the screen's
LAYER 2 makes <body> a flex column, so .container became a flex item β and a flex item's automatic minimum size is its content width (Β§14). The 637px table set the floor, the container grew to meet it, and the page went with it.
β The fix already existed and could not reach it. LAYER 16h pins exactly this β width/max-width/min-width plus flex: 1 1 auto β keyed on body[data-mobile-page="settings"], with a comment describing an identical 776px measurement. A list page is not a settings page, so it got none of it.
π The standing note is "a rule that already covers this is worth measuring". This is its other half: a rule that covers the same fault on a different page type. Grep finds neither, because the selector you would grep for is the one that is missing.
Both mappers put their panel across the bottom, and this module wants the same shape for a stronger reason: the panel is where the work happens, so it should be the larger half β and you still need to see which node you are editing (Ed's point when he asked for the Process Mapper sheet).
β But not in dvh, and that is last round's lesson used rather than repeated. LAYER 31f copied a 58dvh sheet out of Process Mapper and left 71px of map, because the two modules have different amounts of chrome above them. Here the toolbar wraps, so the chrome is not even a fixed number.
.wf-main is already the box holding exactly these two panes and nothing else:
[data-mobile-module="workflow"] .wf-main { flex-direction: column; min-height: 0; }
[data-mobile-module="workflow"] .wf-canvas-wrap{ flex: 0 0 34%; min-height: 0; }
[data-mobile-module="workflow"] .wf-detail { flex: 1 1 auto; width: auto; min-width: 0; }π Split the box that holds the two panes, not the viewport. A percentage of
.wf-mainneeds no arithmetic, cannot drift when the bar above wraps, and is right in every locale and at every height. Reach fordvhonly when the two halves really are the whole screen.
width: auto and min-width: 0, because the 340px is declared twice.
The map pane is ~200px and a condition node is 160px tall, so a node further down a chain lands below the fold. The canvas is its own scroller and the module never scrolls it on selection.
#wfDetailTitle is the single funnel: every path into the panel β tapping a node, tapping bare canvas, + Condition, + Action, applying an AI proposal β ends in showBody(title, id), which sets that element's text. One MutationObserver covers all five and cannot get out of step with call sites this file does not own. It reveals only when a node is selected, so a bareβcanvas tap (which also changes the title, to the workflow's own settings) does not yank the map about.
Not needing JS is not a virtue in itself. The rest of this module genuinely needed none; this one thing the page cannot express in CSS, so it got wrapped.
-
32b β the toolbar, 681px of controls. Wraps to two rows rather than becoming one long strip, and the split is already meaningful: the left group builds the workflow, the right group runs and saves it. A single strip would put Save behind a swipe.
β οΈ Β§16: the row sets bothjustify-contentandalign-items, and both change meaning once it wraps. -
32d β
.wf-ai-modaland.wf-var-modalpromoted to fullβscreen sheets. Sixth module to roll its own modal class, and as always nothing in the measurements complains.β οΈ The templates dialogue is not one of them β it uses the shared.modal-content, so LAYER 3 already has it, and needs its!importantto beat the inlinestyle="max-width: 900px"the page writes. - 32e / 32f β two card feeds. The list settles itself under Β§11: the name cell carries the workflow's description, and if any column holds a sentence, it is a card feed. For the log, Β§11's followβup question β can you compare down a column at 360px? β answers no at two and a half of six columns, and the comparison is already served better on the same page by the tally and the status filter.
- Β§21 labels on the trigger slug, the dates and the duration; not on "48 runs", which says what it is, nor on the status pill. Verified stamped, and zero on empty cells β the "never run" workflow has a blank Last run, so the harvester's blank guard is doing real work here.
- 32f β the filter stack at 538px tall, five dropdowns, two dates and a search each on their own row, so the log began below the fold. Two up: 287px. The tally became an aligned threeβcolumn grid (Β§10) rather than a ragged wrap.
-
32g β Β§20, the sixth time. Two
input[type="date"]filters. The reset is not optional and cannot be cleared headlessly: iOS spells the month out where Chrome on Windows draws digits, so a desktop measurement understates it. -
32h β the module's own
@media (max-width: 760px), restated inside the 768px block.β οΈ Restated, not edited: widening the module's own breakpoint from 760 to 768 would change what a 764px screen renders β a tablet band, and therefore a desktop change made as a side effect of mobile work (Β§25). This module's file is untouched.
The last four rounds were each saved by a screenshot. This one was the reverse: every red the probes raised was the probe.
-
"The guides' tables and code blocks run 200px past the edge." They sit inside
.help-tableand<pre>, both of which declareoverflow-x: autodeliberately. Β§18's distinction exactly: a wrapper that scrolls by design versus one that scrolls because onlyoverflow-ywas declared. I nearly wrote a rule for a working scroller β the Software round's mistake. - "The settings Messageβformats table has crushed cells β 70Γ68, 79Γ68, 78Γ68." The table is a 1307px scroller inside a 296px box, working exactly as the shared settings rule intends; its cells are 607px and 473px wide, and the three flagged ones are narrow because their content is short ("slack", "Builtβin", "Copy"). All 68px tall because a real table row shares one height β Β§19, and my heuristic had no sharedβheight test in it.
-
"The condition node's text overlaps itself." The
<em>is a child of.wf-node-title, not a sibling; my overlap detector compared all descendants pairwise without excluding ancestor/descendant pairs. The desktop control at 1100px reported the identical "overlap".
π A crushedβcell probe needs Β§19's sharedβheight test, and an overlap probe needs a containment test. Both of mine reported a fault on code that was correct, and (2) was on a rule this rollout wrote deliberately. Β§30's addendum keeps being right: a probe earns trust by agreeing with a knownβgood control first.
- All seven pages contained at 360Γ740, measured by box (Β§30), with the Β§18 scroller filter off as well as on.
- Every page has a scroller that reaches its end (Β§28): list 368/368, editor canvas 413/413 and inspector 1310/1310, log 4539/4539, and the three guides 12133, 10441 and 7951 β all of them to the last pixel.
-
The editor driven: tapping a node moved the panel from "Workflow" to "Condition" with the node selected and fully inside the map pane;
+ Actiontook 3 nodes to 4 and pointed the panel at the new one. Nothing was written β this editor has no autosave,markDirty()only sets a flag. - Both settings tabs driven β "a page that renders one tab at a time is 1/N verified". Tab 2 is where probe alarm (2) came from.
- Β§21 labels checked on the page: stamped on the right columns, zero on empty cells, and the feed signature confirmed by cell heights (60, 16, 16, 16, 24, 38 β a table row would share one).
- Desktop control at 1100Γ900, byteβidentical to the preβchange baseline: editor canvas 760 / inspector 340, toolbar unwrapped at 44px, both tables real tables with every cell sharing one row height (89 and 66).
-
The Β§25 audit: the module's diff is seven pages Γ three lines of optβin, every other page changed only its
?v=, andworkflow.css,workflow-editor.jsandworkflow-executions.jsare untouched.
- Dragging a node to rearrange it still needs a mouse. Deliberate, and explained at the top β it rearranges a generated layout.
- π΄ Four strings in the editor are hardcoded English and show that way in all 24 languages: the detail panel's own title (
showBody('Workflow', β¦)) and the three save states (Unsaved changes, Savingβ¦, Saved). The module callswindow.t()28 times elsewhere, so this is four that were missed rather than a module without i18n β but the keys do not exist, so fixing it needs new locale entries across 24 files. Found here, not fixed here: it is a bug at every width, not a mobile one, and it belongs in its own round. -
contracts/help.phpscrolls sideways by 13px with a 15px scrollbar (Β§12's tell). Noticed while checking whether the guide finding generalised; preβexisting, unrelated to this module, recorded rather than chased.
Ed, straight after the round shipped:
"on the webhooks screen there's a panel at the bottom with content 'when', 'Workflow', 'format' etc spilling off to the right so we might need to rearrange this table"
β A gap in this round, not a separate discovery. Workflow's own module nav links straight to it β
// workflow/includes/header.php
<a href="<?php echo BASE_URL; ?>system/webhooks/" β¦> <?php echo t('workflow.nav.webhooks'); ?>β so opting seven Workflow pages in and stopping at the directory boundary left a phone user one tap from a screen that had never been touched. The page lives in system/ and renders the System header, so no grep of workflow/ would ever have found it.
π Scope a round by the module's own navigation, not by its directory. Walk the nav bar the module actually renders and check every destination, including the ones that belong to somebody else.
The delivery log is nine columns β When Β· Workflow Β· Format Β· URL Β· Status Β· Attempts Β· Last code Β· Next retry Β· Actions β measured at 556px wide, right edge at 600 on a 360px screen.
β And Ed named the headings, not data, which is the tell: the log is empty on his install, so what was spilling is the nine-column <thead> on its own. A table with no rows can still be 556px wide.
.wh-container { height: calc(100vh - 48px); overflow-y: auto; }Β§12: declaring one axis computes the other to auto, so the container was a horizontal scroller nobody asked for. It absorbed the table, and the containment sweep β which deliberately excludes anything with a scrolling ancestor β reported the page clean.
Β§18 says a suppressed finding is either working as intended or the bug is hidden, and the probe cannot tell you which. This was the second, in its worst form: the whole page dragged sideways instead of the table scrolling in its own box, and nothing on screen says that has happened. overflow-x: hidden on the container is the loadβbearing half of the fix β without it, the next oversized table hides the same way.
-
33a the shell β LAYER 2, plus that
overflow-x: hidden, plus card padding (22px 24px was 48px of a 360px screen, on five cards). - 33b the log as a card feed, with Β§21 labels on six of nine β When, Format, URL, Attempts, Last code and Next retry are every one a bare value. Consistency decided it as much as Β§11 did: this is the delivery log, one tap from the execution log (32f), which is already a feed.
-
33c the payload dialogue's
<pre>blocks contained β.modal-contentmeans LAYER 3 already makes it a sheet, but not what is inside it.
table.wh, and the first draft keyed on #deliveries β an id this page does not have. It would have been a whole layer with precisely zero measured effect, which Β§9 teaches you to blame on load order and which looks identical to a cache that did not bust. Read the markup for the selector. It also matters that .wh excludes the two table.mini summaries, which stay tables.
- 556px β 304px at 360, and the container now clips rather than absorbing.
-
The feed driven with fabricated rows through the page's own
renderRows()β no database writes β giving 12 labels across two cards, all six per row correct, zero on empty cells, and the feed signature in the cell heights (16, 20, 16, 16, 19, 16, 16, 16, 38). -
Desktop at 1100:
display: table, every cell 49px β one shared row height β and[data-mobile-label]finds NOTHING, which is Β§21's own strongest control. -
The two
.minisummaries measured with real data, because they are serverβrendered and simply absent when the log is empty: two throwaway rows inserted (the table had 0 rows, so nothing of Ed's was at risk), measured at 304px with every cell 29px, then deleted by the exact ids captured on insert. They stay tables and they fit. -
The Β§25 audit: three lines of optβin, every other page only its
?v=.
- MobileβFriendly Β· Techniques & Tricks
- Mobile: Network Mapper Β· Mobile: Process Mapper β the other two canvases, and the other two scope answers
- Workflows β 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: 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