[{"data":1,"prerenderedAt":4644},["ShallowReactive",2],{"blog-\u002Fblog\u002F2026\u002F07\u002Fdigital-work-instruction":3,"blog-all-for-related":4643},{"id":4,"title":5,"authors":6,"body":8,"cta":4620,"date":4624,"description":4625,"extension":4626,"image":4627,"lastUpdated":4628,"meta":4629,"navigation":981,"path":4634,"seo":4635,"sitemap":4636,"stem":4637,"subtitle":4638,"tags":4639,"tldr":4641,"video":4628,"__hash__":4642},"blog\u002Fblog\u002F2026\u002F07\u002Fdigital-work-instruction.md","Building Digital Work Instructions Dashboard for the Shop Floor",[7],"sumit-shinde",{"type":9,"value":10,"toc":4598},"minimark",[11,15,18,28,36,39,49,58,63,66,129,139,143,146,171,174,177,199,203,206,214,353,360,364,385,478,487,490,494,497,504,525,534,537,540,562,577,581,584,608,614,721,735,767,781,793,797,806,824,1200,1206,1215,1219,1222,1233,1239,1297,1300,1379,1383,1386,1391,1427,1431,1460,1699,1737,1750,1759,1763,1766,1788,2032,2083,2093,2097,2100,2115,2631,2636,2653,2734,2754,2763,2773,2782,2803,2812,2816,2819,2839,2843,2854,2865,2886,2898,3082,3091,3098,3494,3502,3521,3524,3528,3535,3568,3763,3797,3800,3804,3810,3819,3895,3904,3913,3922,4321,4356,4365,4368,4372,4378,4415,4555,4565,4568,4571,4575,4588,4595],[12,13,14],"p",{},"Operators need to know what to build, how to build it, and what to do when something breaks. Digital work instructions put that on a screen, guided steps, a checklist, buttons to finish or flag a defect, and retire the paper binder.",[12,16,17],{},"But shared stations raise a question paper never had: who's using it? Without a way to tell operators apart, the work order, the current step, and the record of what happened all blur together. Authentication draws the line, the operator's identity becomes the thread the app hangs on.",[12,19,20,25],{},[21,22],"img",{"alt":23,"src":24},"Digital work instructions dashboard showing operator work orders, production status","\u002Fblog\u002F2026\u002F07\u002Fimages\u002Fdigital-work-instruction-dashboard.png",[26,27,23],"em",{},[12,29,30,34],{},[21,31],{"alt":32,"src":33},"Operator interface displaying guided work instructions with step completion and defect reporting options","\u002Fblog\u002F2026\u002F07\u002Fimages\u002Fdigital-work-instruction-2.png",[26,35,32],{},[12,37,38],{},"In this article, we'll build a digital work instructions app in FlowFuse: an operator interface with work orders, step-by-step assembly guidance, defect reporting, and traceability via authentication.",[40,41,42],"blockquote",{},[12,43,44,48],{},[45,46,47],"strong",{},"Note:"," This demo is deliberately configured with a preset demo user so anyone can try it, even without being on the team where it's deployed. The version you build following this article uses FlowFuse User Authentication, which limits dashboard access to members of the same team.",[12,50,51,52,57],{},"You can interact with the live demo here: ",[53,54,56],"a",{"href":55},"https:\u002F\u002Fexpensive-pied-flycatcher-5052.flowfuse.cloud\u002Fdashboard\u002Fhome","Try the Digital Work Instruction Dashboard Demo",".",[59,60,62],"h2",{"id":61},"what-youll-need","What You'll Need",[12,64,65],{},"Before you start building, get these ready:",[67,68,69,83,89,123],"ul",{},[70,71,72,75,76,82],"li",{},[45,73,74],{},"A FlowFuse account."," ",[53,77,81],{"href":78,"rel":79},"https:\u002F\u002Fapp.flowfuse.com\u002Faccount\u002Fcreate",[80],"nofollow","Sign up"," for FlowFuse Cloud, or use a self-hosted instance.",[70,84,85,88],{},[45,86,87],{},"A FlowFuse instance up and running."," If you don't have one yet, create a new instance from your FlowFuse Platform.",[70,90,91,94,95,99,100,103,104,103,107,103,110,103,113,103,116,103,119,122],{},[45,92,93],{},"FlowFuse Dashboard installed."," This tutorial uses ",[96,97,98],"code",{},"@flowfuse\u002Fnode-red-dashboard"," nodes (",[96,101,102],{},"ui-template",", ",[96,105,106],{},"ui-table",[96,108,109],{},"ui-form",[96,111,112],{},"ui-button",[96,114,115],{},"ui-control",[96,117,118],{},"ui-event",[96,120,121],{},"ui-text",") to build the operator interface. Install it from the Palette Manager if it isn't already in your instance.",[70,124,125,128],{},[45,126,127],{},"The FlowFuse Dashboard user addon."," This is what attaches the logged-in user to every dashboard message. We'll install it in the first section.",[40,130,131],{},[12,132,133,134,138],{},"Note: The Multi-User addon is available to Teams and Enterprise Self-Hosted customers. If you're on a different tier, ",[53,135,137],{"href":136},"\u002Fcontact-us\u002F","contact us"," for the configuration to get started.",[59,140,142],{"id":141},"how-the-application-works","How the Application Works",[12,144,145],{},"Before we build anything, let's walk through what the app does and how the pieces fit together. There are three pages, and one idea holding them together.",[147,148,149,159,165],"ol",{},[70,150,151,154,155,158],{},[45,152,153],{},"Home."," After logging in, the application uses the operator's username to retrieve only the work orders assigned to them from the ERP or MES. It displays their station, production summary, the highest-priority work order, and the remaining queue. Selecting ",[45,156,157],{},"Start Work Order"," opens the instructions.",[70,160,161,164],{},[45,162,163],{},"Instructions."," Operators follow step-by-step instructions with images, checklists, and target cycle times. They can progress through each step, complete the operation, or report an issue at any time.",[70,166,167,170],{},[45,168,169],{},"Report Issue."," Operators can quickly log defects by selecting the issue type, severity, affected part, and description, with the issue automatically linked to the current work order.",[12,172,173],{},"The logged-in username acts as the application's lookup key. It retrieves the operator's assigned work orders and stores their active work order and current step, allowing them to resume exactly where they left off, even on shared production stations.",[12,175,176],{},"There are two different questions the app has to answer, and it's worth keeping them separate from the start:",[67,178,179,189],{},[70,180,181,184,185,188],{},[45,182,183],{},"\"Where is this operator's data stored?\""," — under a persistent global key named after their username, e.g. ",[96,186,187],{},"global.get('operator1', 'persistent')",". This is a durable, shared store, and it's meant to be: it's what lets progress survive reloads and reconnects.",[70,190,191,194,195,198],{},[45,192,193],{},"\"Who is acting right now, in this message?\""," — this must be read fresh from the message that triggered the flow (",[96,196,197],{},"msg._client.user","), not from a single shared \"current user\" variable. A shared variable has only one value at a time; the moment a second operator's browser connects, it overwrites the first operator's identity for the whole running flow. We'll come back to this the moment it becomes relevant, but it's the one rule that makes every \"per operator\" claim in this tutorial actually true when two people are logged in at once.",[59,200,202],{"id":201},"importing-the-simulated-flow","Importing the Simulated Flow",[12,204,205],{},"Instead of connecting to a real ERP or MES, we'll use a simple simulated backend that serves sample work orders through an HTTP API.",[147,207,208],{},[70,209,210,211,57],{},"Import the following flow into FlowFuse and click ",[45,212,213],{},"Deploy",[12,215,216,217,352],{},"{% renderFlow 300 %}\n",[218,219,220,221,224,225,227,228,230,231,233,234,236,237,240,241,243,244,246,247,249,250,252,253,236,255,257,258,260,261,246,263,265,266,268,269,271,272,274,275,277,278,281,282,314,315,318,319,322,323,326,327,330,331,333,334,337,338,341,342,344,345,348,349,351],"span",{},"{\"id\":\"83cd1b1c4cc8e310\",\"type\":\"group\",\"z\":\"3012f9c2796b5b41\",\"style\":{\"stroke\":\"#b2b3bd\",\"stroke-opacity\":\"1\",\"fill\":\"#f2f3fb\",\"fill-opacity\":\"0.5\",\"label\":true,\"label-position\":\"nw\",\"color\":\"#32333b\"},\"nodes\":",[218,222,223],{},"\"5a2c1cb7932accb9\",\"f2334a5d527a684a\",\"f65db363fdde0eba\",\"cdda81f6a2701b7c\",\"8554ad9dfdb36f54\",\"6dd109a105da5dd5\",\"439862e5c86675cd\",\"9d853fad37785bec\",\"3506d88cd094d44c\",\"wo_stats_http_in\",\"wo_stats_func\",\"wo_stats_response\",\"6d1644eabb9a5bf5\",\"62760b81dea503c2\"",",\"x\":44,\"y\":59,\"w\":652,\"h\":482},{\"id\":\"5a2c1cb7932accb9\",\"type\":\"http in\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"name\":\"GET Work Orders\",\"url\":\"\u002Fworkorders\",\"method\":\"get\",\"upload\":false,\"skipBodyParsing\":false,\"swaggerDoc\":\"\",\"x\":160,\"y\":300,\"wires\":[[\"f2334a5d527a684a\"]]},{\"id\":\"f2334a5d527a684a\",\"type\":\"function\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"name\":\"Generate Work Orders\",\"func\":\"const stationId = msg.req.query.stationId;\\nconst username = msg.req.query.username;\\n\\nconst workOrders = global.get(\"workOrders\", \"persistent\") || ",[218,226],{},";\\n\\nconst result = workOrders.filter(w =>\\n    w.stationId === stationId &&\\n    w.assignedTo === username &&\\n    w.status !== \"defect\" &&\\n    w.status !== \"completed\"\\n);\\n\\nmsg.payload = result;\\n\\nreturn msg;\",\"outputs\":1,\"timeout\":\"\",\"noerr\":0,\"initialize\":\"\",\"finalize\":\"\",\"libs\":",[218,229],{},",\"x\":420,\"y\":300,\"wires\":[[\"f65db363fdde0eba\"]]},{\"id\":\"f65db363fdde0eba\",\"type\":\"http response\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"name\":\"\",\"statusCode\":\"\",\"headers\":{\"Content-Type\":\"application\u002Fjson\"},\"x\":610,\"y\":300,\"wires\":",[218,232],{},"},{\"id\":\"cdda81f6a2701b7c\",\"type\":\"http in\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"name\":\"POST Mark Defect\",\"url\":\"\u002Fworkorders\u002Fdefect\",\"method\":\"post\",\"upload\":false,\"skipBodyParsing\":false,\"swaggerDoc\":\"\",\"x\":160,\"y\":400,\"wires\":[[\"8554ad9dfdb36f54\"]]},{\"id\":\"8554ad9dfdb36f54\",\"type\":\"function\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"name\":\"Mark Defect\",\"func\":\"const { workOrderId, issueType, severity, affectedPart, description } = msg.payload || {};\\n\\nif (!workOrderId || !issueType || !severity || !affectedPart) {\\n    msg.statusCode = 400;\\n    msg.payload = { error: 'workOrderId, issueType, severity, and affectedPart are required' };\\n    return msg;\\n}\\n\\nconst workOrders = global.get('workOrders', 'persistent') || ",[218,235],{},";\\nconst idx = workOrders.findIndex(w => w.workOrderId === workOrderId);\\n\\nif (idx === -1) {\\n    msg.statusCode = 404;\\n    msg.payload = { error: 'Work order not found' };\\n    return msg;\\n}\\n\\nworkOrders",[218,238,239],{},"idx",".status = 'defect';\\nworkOrders",[218,242,239],{},".defect = {\\n    issueType,\\n    severity,\\n    affectedPart,\\n    description: description || null,\\n    reportedAt: new Date().toISOString()\\n};\\n\\nglobal.set('workOrders', workOrders, 'persistent');\\n\\nmsg.statusCode = 200;\\nmsg.payload = { success: true, workOrder: workOrders",[218,245,239],{}," };\\nreturn msg;\",\"outputs\":1,\"timeout\":\"\",\"noerr\":0,\"initialize\":\"\",\"finalize\":\"\",\"libs\":",[218,248],{},",\"x\":390,\"y\":400,\"wires\":[[\"6dd109a105da5dd5\"]]},{\"id\":\"6dd109a105da5dd5\",\"type\":\"http response\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"name\":\"\",\"statusCode\":\"\",\"headers\":{\"Content-Type\":\"application\u002Fjson\"},\"x\":610,\"y\":400,\"wires\":",[218,251],{},"},{\"id\":\"439862e5c86675cd\",\"type\":\"http in\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"name\":\"POST Complete Order\",\"url\":\"\u002Fworkorders\u002Fcomplete\",\"method\":\"post\",\"upload\":false,\"skipBodyParsing\":false,\"swaggerDoc\":\"\",\"x\":170,\"y\":500,\"wires\":[[\"9d853fad37785bec\"]]},{\"id\":\"9d853fad37785bec\",\"type\":\"function\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"name\":\"Complete Work Order\",\"func\":\"const { workOrderId } = msg.payload || {};\\n\\nif (!workOrderId) {\\n    msg.statusCode = 400;\\n    msg.payload = { error: 'workOrderId is required' };\\n    return msg;\\n}\\n\\nconst workOrders = global.get('workOrders', 'persistent') || ",[218,254],{},[218,256,239],{},".status = 'completed';\\nworkOrders",[218,259,239],{},".completedAt = new Date().toISOString();\\n\\nglobal.set('workOrders', workOrders, 'persistent');\\n\\nmsg.statusCode = 200;\\nmsg.payload = { success: true, workOrder: workOrders",[218,262,239],{},[218,264],{},",\"x\":410,\"y\":500,\"wires\":[[\"3506d88cd094d44c\"]]},{\"id\":\"3506d88cd094d44c\",\"type\":\"http response\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"name\":\"\",\"statusCode\":\"\",\"headers\":{\"Content-Type\":\"application\u002Fjson\"},\"x\":620,\"y\":500,\"wires\":",[218,267],{},"},{\"id\":\"wo_stats_http_in\",\"type\":\"http in\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"name\":\"GET Work Order Stats\",\"url\":\"\u002Fworkorders\u002Fstats\",\"method\":\"get\",\"upload\":false,\"skipBodyParsing\":false,\"swaggerDoc\":\"\",\"x\":180,\"y\":220,\"wires\":[[\"wo_stats_func\"]]},{\"id\":\"wo_stats_func\",\"type\":\"function\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"name\":\"Compute Stats\",\"func\":\"const workOrders = global.get('workOrders', 'persistent') || ",[218,270],{},";\\nnode.warn(msg.req.query);\\nconst stationId = msg.req.query.stationId;\\nconst username = msg.req.query.username;\\n\\n\u002F\u002F Filter only by station\\nconst stationWorkOrders = stationId\\n    ? workOrders.filter(w => w.stationId === stationId)\\n    : workOrders;\\n\\nconst total = stationWorkOrders.length;\\nconst completed = stationWorkOrders.filter(w => w.status === \"completed\").length;\\nconst defected = stationWorkOrders.filter(w => w.status === \"defect\").length;\\n\\n\u002F\u002F Filter by user within the station\\nconst assignedToMe = username\\n    ? stationWorkOrders.filter(w => w.assignedTo === username).length\\n    : 0;\\n\\nmsg.statusCode = 200;\\nmsg.payload = {\\n    total,\\n    completed,\\n    defected,\\n    assignedToMe\\n};\\n\\nreturn msg;\",\"outputs\":1,\"timeout\":\"\",\"noerr\":0,\"initialize\":\"\",\"finalize\":\"\",\"libs\":",[218,273],{},",\"x\":400,\"y\":220,\"wires\":[[\"wo_stats_response\"]]},{\"id\":\"wo_stats_response\",\"type\":\"http response\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"name\":\"\",\"statusCode\":\"\",\"headers\":{\"Content-Type\":\"application\u002Fjson\"},\"x\":610,\"y\":220,\"wires\":",[218,276],{},"},{\"id\":\"6d1644eabb9a5bf5\",\"type\":\"function\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"name\":\"Generate Mock Work Orders\",\"func\":\"const user = global.get(\"user\", \"persistent\");\\nconst currentUser = user?.username || \"operator1\";\\n\\n\u002F\u002F Pool of 5 operators (logged-in user + 4 others)\\nconst operators = ",[218,279,280],{},"\\n    currentUser,\\n    \"operator2\",\\n    \"operator3\",\\n    \"operator4\",\\n    \"operator5\"\\n",";\\n\\nconst vehicles = ",[218,283,284,285,288,289,292,293,288,296,299,300,288,303,306,307,288,310,313],{},"\\n    {\\n        model: \"Sedan X\",\\n        variants: ",[218,286,287],{},"\"Standard\", \"Premium\", \"Sport\"",",\\n        colors: ",[218,290,291],{},"\"White\", \"Black\", \"Red\"","\\n    },\\n    {\\n        model: \"SUV Y\",\\n        variants: ",[218,294,295],{},"\"Base\", \"Premium\", \"Sport\"",[218,297,298],{},"\"Grey\", \"White\", \"Black\"","\\n    },\\n    {\\n        model: \"Truck Z\",\\n        variants: ",[218,301,302],{},"\"Standard\", \"Heavy Duty\"",[218,304,305],{},"\"Blue\", \"Green\", \"White\"","\\n    },\\n    {\\n        model: \"Hatchback W\",\\n        variants: ",[218,308,309],{},"\"Base\", \"Sport\"",[218,311,312],{},"\"Silver\", \"Yellow\", \"Red\"","\\n    }\\n",";\\n\\nconst operations = ",[218,316,317],{},"\\n    {\\n        operationId: \"OP_WHEEL_FL\",\\n        name: \"Install Front Left Wheel\",\\n        instructionSetId: \"INS001\",\\n        estimatedCycleTime: 45\\n    },\\n    {\\n        operationId: \"OP_WHEEL_FR\",\\n        name: \"Install Front Right Wheel\",\\n        instructionSetId: \"INS002\",\\n        estimatedCycleTime: 45\\n    }\\n",";\\n\\nconst priorities = ",[218,320,321],{},"\"Critical\", \"High\", \"Normal\", \"Low\"",";\\nconst statuses = ",[218,324,325],{},"\"Ready\", \"Waiting\", \"Queued\"",";\\n\\nfunction random(arr) {\\n    return arr",[218,328,329],{},"Math.floor(Math.random() * arr.length)",";\\n}\\n\\n\u002F\u002F Read existing work orders\\nconst existingWorkOrders = global.get(\"workOrders\", \"persistent\") || ",[218,332],{},";\\n\\n\u002F\u002F Start IDs after the existing ones\\nlet nextId = 10245 + existingWorkOrders.length;\\n\\n\u002F\u002F Generate 20 new work orders\\nfor (let i = 0; i \u003C 20; i++) {\\n    const vehicle = random(vehicles);\\n    const operation = random(operations);\\n\\n    existingWorkOrders.push({\\n        workOrderId: ",[96,335,336],{},"WO-${nextId++}",",\\n        stationId: \"ST12\",\\n        status: random(statuses),\\n        priority: random(priorities),\\n        assignedTo: random(operators),\\n        vehicle: {\\n            vin: ",[96,339,340],{},"MH01AB${1234567890 + existingWorkOrders.length}",",\\n            model: vehicle.model,\\n            variant: random(vehicle.variants),\\n            color: random(vehicle.colors)\\n        },\\n        operation: {\\n            operationId: operation.operationId,\\n            name: operation.name,\\n            estimatedCycleTime: operation.estimatedCycleTime,\\n            instructionSetId: operation.instructionSetId\\n        }\\n    });\\n}\\n\\n\u002F\u002F Save updated list\\nglobal.set(\"workOrders\", existingWorkOrders, \"persistent\");\\n\\nmsg.payload = existingWorkOrders;\\nreturn msg;\",\"outputs\":1,\"timeout\":0,\"noerr\":0,\"initialize\":\"\",\"finalize\":\"\",\"libs\":",[218,343],{},",\"x\":440,\"y\":100,\"wires\":[[]]},{\"id\":\"62760b81dea503c2\",\"type\":\"ui-event\",\"z\":\"3012f9c2796b5b41\",\"g\":\"83cd1b1c4cc8e310\",\"ui\":\"37b66bc5b4d6d3d3\",\"name\":\"\",\"x\":160,\"y\":100,\"wires\":[[\"6d1644eabb9a5bf5\"]]},{\"id\":\"37b66bc5b4d6d3d3\",\"type\":\"ui-base\",\"name\":\"My Dashboard\",\"path\":\"\u002Fdashboard\",\"appIcon\":\"\",\"includeClientData\":true,\"acceptsClientConfig\":",[218,346,347],{},"\"ui-notification\",\"ui-control\",\"ui-template\",\"ui-form\",\"ui-table\",\"ui-text-input\",\"ui-number-input\",\"ui-file-input\",\"ui-button\",\"ui-button-group\",\"ui-dropdown\",\"ui-radio-group\",\"ui-slider\",\"ui-text\",\"ui-switch\",\"ui-chart\",\"ui-audio\",\"ui-markdown\",\"ui-progress\",\"ui-gauge\"",",\"showPathInSidebar\":false,\"headerContent\":\"page\",\"navigationStyle\":\"default\",\"titleBarStyle\":\"default\",\"showReconnectNotification\":true,\"notificationDisplayTime\":1,\"showDisconnectNotification\":true,\"allowInstall\":false},{\"id\":\"4b583d9b76bf5cf2\",\"type\":\"global-config\",\"env\":",[218,350],{},",\"modules\":{\"@flowfuse\u002Fnode-red-dashboard\":\"1.30.2\"}}","\n{% endrenderFlow 300 %}",[12,354,355,356,359],{},"The flow initializes a set of sample work orders and exposes REST APIs that the Digital Work Instructions application uses throughout this tutorial. The ",[96,357,358],{},"GET \u002Fworkorders"," endpoint returns the active work orders, while the other endpoints simulate completing a work order, reporting defects, and retrieving production statistics. This lets you build and test the application without a live ERP or MES. Later, you can replace these endpoints with calls to your production system while keeping the rest of the application unchanged.",[59,361,363],{"id":362},"setting-up-the-dashboard-layout","Setting Up the Dashboard Layout",[12,365,366,367,372,373,376,377,380,381,384],{},"The application consists of three pages. Most widgets are placed inside named groups, while a few are scoped differently. See the ",[53,368,371],{"href":369,"rel":370},"https:\u002F\u002Fdashboard.flowfuse.com\u002Fgetting-started",[80],"Dashboard layout docs"," if you're new to how pages, groups, and bases relate. The app bar greeting is ",[45,374,375],{},"UI-scoped",", and the work instruction widget is ",[45,378,379],{},"page-scoped",", allowing it to fill the entire ",[45,382,383],{},"Instructions"," page without requiring a group. Before adding any widgets, create the dashboard structure by setting up the pages and groups.",[147,386,387,432,435,467],{},[70,388,389,390,393,394],{},"Create three ",[45,391,392],{},"ui-page"," nodes. The Dashboard automatically creates a base dashboard the first time you add a Dashboard node to the canvas.",[67,395,396,410,421],{},[70,397,398,401,402,405,406,409],{},[45,399,400],{},"Home"," (path: ",[96,403,404],{},"\u002Fhome",") – The operator's landing page after login. Set the layout to ",[45,407,408],{},"Notebook"," to match the demo application.",[70,411,412,401,414,417,418,57],{},[45,413,383],{},[96,415,416],{},"\u002Finstructions",") – Displays the step-by-step work instructions. Set the layout to ",[45,419,420],{},"Grid",[70,422,423,401,426,429,430,57],{},[45,424,425],{},"Report Issue",[96,427,428],{},"\u002Freport-issue",") – Contains the defect reporting form. Set the layout to ",[45,431,420],{},[70,433,434],{},"Configure the theme for each page as desired.",[70,436,437,438,440,441,444,445],{},"On the ",[45,439,400],{}," page, create four ",[45,442,443],{},"ui-group"," nodes:",[67,446,447,452,457,462],{},[70,448,449],{},[45,450,451],{},"Current Station",[70,453,454],{},[45,455,456],{},"Stats",[70,458,459],{},[45,460,461],{},"Current Work Order",[70,463,464],{},[45,465,466],{},"Up Next",[70,468,437,469,471,472,474,475,477],{},[45,470,425],{}," page, create a single ",[45,473,443],{}," named ",[45,476,425],{},". This group will contain the issue reporting form and its action buttons.",[12,479,480,481,483,484,486],{},"The ",[45,482,383],{}," page does not require a ",[45,485,443],{}," because the work instruction widget is page-scoped and automatically occupies the full page.",[12,488,489],{},"With this layout in place, each widget added in the following sections can be assigned to the appropriate group, or directly to the page when required.",[59,491,493],{"id":492},"enabling-flowfuse-user-authentication","Enabling FlowFuse User Authentication",[12,495,496],{},"Everything starts with a login. Without authentication, the dashboard cannot identify who is using it, so every visitor sees the same experience.",[40,498,499],{},[12,500,501,503],{},[45,502,47],{}," FlowFuse User Authentication grants dashboard access to members of the same team as the instance. Operators you want to sign in must belong to that team, otherwise they won't be able to reach the dashboard. If you need people outside the team to use the application, you'll need to add them to the team.",[147,505,506,512,519],{},[70,507,508,509,57],{},"Open your FlowFuse instance ",[45,510,511],{},"Settings",[70,513,514,515,518],{},"Select the ",[45,516,517],{},"Security"," tab.",[70,520,521,522,57],{},"Enable ",[45,523,524],{},"FlowFuse User Authentication",[12,526,527,531],{},[21,528],{"alt":529,"src":530},"Screenshot: the Security tab in instance settings with FlowFuse User Authentication enabled","https:\u002F\u002Fflowfuse.com\u002Fimg\u002Fdisplaying-logged-in-user-flowfuse-instance-setting.png",[26,532,533],{},"Enable FlowFuse User Authentication in the instance Security settings. You'll also create a Personal Access Token here for authenticating API requests later in the tutorial.",[12,535,536],{},"The first time someone opens the dashboard, they will be prompted to sign in using their FlowFuse username and password. Once authenticated, the dashboard can personalize the experience for each operator throughout the rest of this tutorial.",[12,538,539],{},"Because this tutorial also interacts with the FlowFuse API, you'll need to create a Personal Access Token:",[147,541,543,552,555],{"start":542},4,[70,544,545,546,548,549,57],{},"In the same ",[45,547,517],{}," tab, click ",[45,550,551],{},"Add Token",[70,553,554],{},"Enter a name for the token and choose an expiration date.",[70,556,557,558,561],{},"Click ",[45,559,560],{},"Create",", then copy the generated token and save it somewhere secure. You will need it later in the tutorial.",[40,563,564],{},[12,565,566,569,570,573,574,576],{},[45,567,568],{},"Security note:"," Every ",[96,571,572],{},"http request"," node in this tutorial authenticates with this token, and for the sake of a self-contained tutorial we reference it as a plain Bearer token on each node. Don't leave it hardcoded that way in a real deployment. Store it as an environment variable on your FlowFuse instance and reference that variable from each ",[96,575,572],{}," node's auth config instead, so the raw token never sits in exported flow JSON or version control.",[59,578,580],{"id":579},"installing-the-user-addon","Installing the User Addon",[12,582,583],{},"Authentication identifies the user, but your flows also need access to that information. The FlowFuse User Addon attaches the authenticated user's details to every Dashboard message.",[147,585,586,592,598,604],{},[70,587,588,589,57],{},"Open ",[45,590,591],{},"Manage Palette",[70,593,594,595,518],{},"Go to the ",[45,596,597],{},"Install",[70,599,600,601,57],{},"Search for ",[96,602,603],{},"@flowfuse\u002Fnode-red-dashboard-2-user-addon",[70,605,557,606,57],{},[45,607,597],{},[12,609,610,611,613],{},"After installation, every Dashboard message includes a ",[96,612,197],{}," object:",[615,616,621],"pre",{"className":617,"code":618,"language":619,"meta":620,"style":620},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"userId\": \"\",\n  \"username\": \"\",\n  \"email\": \"\",\n  \"name\": \"\",\n  \"image\": \"\"\n}\n","json","",[96,622,623,631,653,669,684,700,715],{"__ignoreMap":620},[218,624,627],{"class":625,"line":626},"line",1,[218,628,630],{"class":629},"sMK4o","{\n",[218,632,634,637,641,644,647,650],{"class":625,"line":633},2,[218,635,636],{"class":629},"  \"",[218,638,640],{"class":639},"spNyl","userId",[218,642,643],{"class":629},"\"",[218,645,646],{"class":629},":",[218,648,649],{"class":629}," \"\"",[218,651,652],{"class":629},",\n",[218,654,656,658,661,663,665,667],{"class":625,"line":655},3,[218,657,636],{"class":629},[218,659,660],{"class":639},"username",[218,662,643],{"class":629},[218,664,646],{"class":629},[218,666,649],{"class":629},[218,668,652],{"class":629},[218,670,671,673,676,678,680,682],{"class":625,"line":542},[218,672,636],{"class":629},[218,674,675],{"class":639},"email",[218,677,643],{"class":629},[218,679,646],{"class":629},[218,681,649],{"class":629},[218,683,652],{"class":629},[218,685,687,689,692,694,696,698],{"class":625,"line":686},5,[218,688,636],{"class":629},[218,690,691],{"class":639},"name",[218,693,643],{"class":629},[218,695,646],{"class":629},[218,697,649],{"class":629},[218,699,652],{"class":629},[218,701,703,705,708,710,712],{"class":625,"line":702},6,[218,704,636],{"class":629},[218,706,707],{"class":639},"image",[218,709,643],{"class":629},[218,711,646],{"class":629},[218,713,714],{"class":629}," \"\"\n",[218,716,718],{"class":625,"line":717},7,[218,719,720],{"class":629},"}\n",[12,722,723,724,726,727,730,731,734],{},"The same information is available in a ",[45,725,102],{}," using ",[96,728,729],{},"setup.socketio.auth.user"," (or ",[96,732,733],{},"this.setup.socketio.auth.user"," in the script).",[147,736,737,748],{},[70,738,739,740,743,744,747],{},"In the ",[45,741,742],{},"FF Auth"," sidebar tab, ensure ",[45,745,746],{},"Include Client Data"," is enabled.",[70,749,750,751,754,755,103,757,103,759,103,761,763,764,766],{},"Also enable ",[45,752,753],{},"Accept Client Data"," for ",[45,756,102],{},[45,758,115],{},[45,760,118],{},[45,762,109],{},", and ",[45,765,106],{}," so each browser session is handled independently and operators don't affect each other's dashboard.",[12,768,769,773],{},[21,770],{"alt":771,"src":772},"Screenshot: FF Auth settings showing Include Client Data and Accept Client Data enabled for Dashboard nodes","\u002Fblog\u002F2026\u002F07\u002Fimages\u002Fclient-data-tab-dashboard.png",[26,774,521,775,777,778,780],{},[45,776,746],{}," and ",[45,779,753],{}," to make Dashboard interactions user-specific.",[12,782,783,784,786,787,789,790,792],{},"This is the setting that matters most in this tutorial, and it's worth being explicit about why. With ",[45,785,753],{}," enabled, every message a widget sends carries ",[96,788,197],{}," for the session that sent it. That's a per-session identity attached directly to the message, as opposed to a single shared variable that every session would otherwise read and overwrite. We'll use ",[96,791,197],{}," as the source of truth for \"who is acting right now\" in every function node from here on, rather than a global variable.",[59,794,796],{"id":795},"greeting-the-logged-in-operator","Greeting the Logged-In Operator",[12,798,799,800,805],{},"The first, simplest payoff: show the operator their name and avatar in the app bar, so it's obvious who's signed in at this station. This uses Vue's ",[53,801,804],{"href":802,"rel":803},"https:\u002F\u002Fdashboard.flowfuse.com\u002Fnodes\u002Fwidgets\u002Fui-template.html#teleports",[80],"Teleport"," to render into the header's action area from a single widget that lives on every page, so you don't have to add it per page.",[147,807,808,814,821],{},[70,809,810,811,813],{},"Add a ",[96,812,102],{}," node and name it \"App Bar User Info\".",[70,815,816,817,820],{},"Set its type to ",[45,818,819],{},"Widget (UI-Scoped)"," and select your \"My Dashboard\" ui-base. UI-scoped widgets render on every page automatically, so one widget covers the whole app, no group needed.",[70,822,823],{},"Paste in the snippet below:",[615,825,829],{"className":826,"code":827,"language":828,"meta":620,"style":620},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Ctemplate>\n    \u003C!-- Teleport into #app-bar-actions, the action bar's right-hand corner -->\n    \u003CTeleport v-if=\"loaded\" to=\"#app-bar-actions\">\n        \u003Cdiv class=\"user-info\">\n            \u003Cimg :src=\"setup.socketio.auth.user.image\" \u002F>\n            \u003Cspan>Hi, {{ setup.socketio.auth.user.name }}\u003C\u002Fspan>\n        \u003C\u002Fdiv>\n    \u003C\u002FTeleport>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nexport default {\n    data() {\n        return { loaded: false };\n    },\n    mounted() {\n        \u002F\u002F Wait for mount so #app-bar-actions exists before teleporting into it.\n        this.loaded = true;\n    }\n}\n\u003C\u002Fscript>\n\n\u003Cstyle>\n.user-info { display: flex; align-items: center; gap: 8px; }\n.user-info img { width: 24px; height: 24px; }\n\u003C\u002Fstyle>\n","html",[96,830,831,843,849,884,906,928,948,957,967,976,983,993,1006,1017,1038,1044,1054,1060,1077,1083,1088,1097,1102,1112,1158,1191],{"__ignoreMap":620},[218,832,833,836,840],{"class":625,"line":626},[218,834,835],{"class":629},"\u003C",[218,837,839],{"class":838},"swJcz","template",[218,841,842],{"class":629},">\n",[218,844,845],{"class":625,"line":633},[218,846,848],{"class":847},"sHwdD","    \u003C!-- Teleport into #app-bar-actions, the action bar's right-hand corner -->\n",[218,850,851,854,856,859,862,864,868,870,873,875,877,880,882],{"class":625,"line":655},[218,852,853],{"class":629},"    \u003C",[218,855,804],{"class":838},[218,857,858],{"class":639}," v-if",[218,860,861],{"class":629},"=",[218,863,643],{"class":629},[218,865,867],{"class":866},"sfazB","loaded",[218,869,643],{"class":629},[218,871,872],{"class":639}," to",[218,874,861],{"class":629},[218,876,643],{"class":629},[218,878,879],{"class":866},"#app-bar-actions",[218,881,643],{"class":629},[218,883,842],{"class":629},[218,885,886,889,892,895,897,899,902,904],{"class":625,"line":542},[218,887,888],{"class":629},"        \u003C",[218,890,891],{"class":838},"div",[218,893,894],{"class":639}," class",[218,896,861],{"class":629},[218,898,643],{"class":629},[218,900,901],{"class":866},"user-info",[218,903,643],{"class":629},[218,905,842],{"class":629},[218,907,908,911,913,916,918,920,923,925],{"class":625,"line":686},[218,909,910],{"class":629},"            \u003C",[218,912,21],{"class":838},[218,914,915],{"class":639}," :src",[218,917,861],{"class":629},[218,919,643],{"class":629},[218,921,922],{"class":866},"setup.socketio.auth.user.image",[218,924,643],{"class":629},[218,926,927],{"class":629}," \u002F>\n",[218,929,930,932,934,937,941,944,946],{"class":625,"line":702},[218,931,910],{"class":629},[218,933,218],{"class":838},[218,935,936],{"class":629},">",[218,938,940],{"class":939},"sTEyZ","Hi, {{ setup.socketio.auth.user.name }}",[218,942,943],{"class":629},"\u003C\u002F",[218,945,218],{"class":838},[218,947,842],{"class":629},[218,949,950,953,955],{"class":625,"line":717},[218,951,952],{"class":629},"        \u003C\u002F",[218,954,891],{"class":838},[218,956,842],{"class":629},[218,958,960,963,965],{"class":625,"line":959},8,[218,961,962],{"class":629},"    \u003C\u002F",[218,964,804],{"class":838},[218,966,842],{"class":629},[218,968,970,972,974],{"class":625,"line":969},9,[218,971,943],{"class":629},[218,973,839],{"class":838},[218,975,842],{"class":629},[218,977,979],{"class":625,"line":978},10,[218,980,982],{"emptyLinePlaceholder":981},true,"\n",[218,984,986,988,991],{"class":625,"line":985},11,[218,987,835],{"class":629},[218,989,990],{"class":838},"script",[218,992,842],{"class":629},[218,994,996,1000,1003],{"class":625,"line":995},12,[218,997,999],{"class":998},"s7zQu","export",[218,1001,1002],{"class":998}," default",[218,1004,1005],{"class":629}," {\n",[218,1007,1009,1012,1015],{"class":625,"line":1008},13,[218,1010,1011],{"class":838},"    data",[218,1013,1014],{"class":629},"()",[218,1016,1005],{"class":629},[218,1018,1020,1023,1026,1029,1031,1035],{"class":625,"line":1019},14,[218,1021,1022],{"class":998},"        return",[218,1024,1025],{"class":629}," {",[218,1027,1028],{"class":838}," loaded",[218,1030,646],{"class":629},[218,1032,1034],{"class":1033},"sfNiH"," false",[218,1036,1037],{"class":629}," };\n",[218,1039,1041],{"class":625,"line":1040},15,[218,1042,1043],{"class":629},"    },\n",[218,1045,1047,1050,1052],{"class":625,"line":1046},16,[218,1048,1049],{"class":838},"    mounted",[218,1051,1014],{"class":629},[218,1053,1005],{"class":629},[218,1055,1057],{"class":625,"line":1056},17,[218,1058,1059],{"class":847},"        \u002F\u002F Wait for mount so #app-bar-actions exists before teleporting into it.\n",[218,1061,1063,1066,1068,1071,1074],{"class":625,"line":1062},18,[218,1064,1065],{"class":629},"        this.",[218,1067,867],{"class":939},[218,1069,1070],{"class":629}," =",[218,1072,1073],{"class":1033}," true",[218,1075,1076],{"class":629},";\n",[218,1078,1080],{"class":625,"line":1079},19,[218,1081,1082],{"class":629},"    }\n",[218,1084,1086],{"class":625,"line":1085},20,[218,1087,720],{"class":629},[218,1089,1091,1093,1095],{"class":625,"line":1090},21,[218,1092,943],{"class":629},[218,1094,990],{"class":838},[218,1096,842],{"class":629},[218,1098,1100],{"class":625,"line":1099},22,[218,1101,982],{"emptyLinePlaceholder":981},[218,1103,1105,1107,1110],{"class":625,"line":1104},23,[218,1106,835],{"class":629},[218,1108,1109],{"class":838},"style",[218,1111,842],{"class":629},[218,1113,1115,1117,1120,1122,1126,1128,1131,1134,1137,1139,1142,1144,1147,1149,1153,1155],{"class":625,"line":1114},24,[218,1116,57],{"class":629},[218,1118,901],{"class":1119},"sBMFI",[218,1121,1025],{"class":629},[218,1123,1125],{"class":1124},"sqsOY"," display",[218,1127,646],{"class":629},[218,1129,1130],{"class":939}," flex",[218,1132,1133],{"class":629},";",[218,1135,1136],{"class":1124}," align-items",[218,1138,646],{"class":629},[218,1140,1141],{"class":939}," center",[218,1143,1133],{"class":629},[218,1145,1146],{"class":1124}," gap",[218,1148,646],{"class":629},[218,1150,1152],{"class":1151},"sbssI"," 8px",[218,1154,1133],{"class":629},[218,1156,1157],{"class":629}," }\n",[218,1159,1161,1163,1165,1168,1170,1173,1175,1178,1180,1183,1185,1187,1189],{"class":625,"line":1160},25,[218,1162,57],{"class":629},[218,1164,901],{"class":1119},[218,1166,1167],{"class":1119}," img",[218,1169,1025],{"class":629},[218,1171,1172],{"class":1124}," width",[218,1174,646],{"class":629},[218,1176,1177],{"class":1151}," 24px",[218,1179,1133],{"class":629},[218,1181,1182],{"class":1124}," height",[218,1184,646],{"class":629},[218,1186,1177],{"class":1151},[218,1188,1133],{"class":629},[218,1190,1157],{"class":629},[218,1192,1194,1196,1198],{"class":625,"line":1193},26,[218,1195,943],{"class":629},[218,1197,1109],{"class":838},[218,1199,842],{"class":629},[12,1201,1202,1203,1205],{},"Deploy and open the dashboard. The signed-in operator's name and avatar appear in the top-right corner. You don't redeploy when a different operator logs in, the addon fetches each user's data at runtime, so everyone sees their own. This widget reads identity straight off the browser's own socket session (",[96,1204,729],{},"), so it's already per-operator by construction, no shared state involved.",[12,1207,1208,75,1212],{},[21,1209],{"alt":1210,"src":1211},"Screenshot: the dashboard app bar showing the logged-in operator's avatar and greeting","\u002Fblog\u002F2026\u002F07\u002Fimages\u002Flogged-in-user-app-bar.png",[26,1213,1214],{},"The signed-in operator greeted by name and avatar in the app bar, rendered from a single UI-scoped widget.",[59,1216,1218],{"id":1217},"seeding-the-operator-into-context-on-login","Seeding the Operator into Context on Login",[12,1220,1221],{},"Greeting the operator is the visible half. The other half is making their identity available to every function node, not just the widgets.",[12,1223,1224,1225,1228,1229,1232],{},"You might reach for a single shared global here, ",[96,1226,1227],{},"global.set('user', msg._client.user, 'persistent')"," on connect, then read it everywhere. ",[45,1230,1231],{},"Don't."," Global context is one store for the whole flow, not one per session, so if two operators are connected at once, whichever connects last overwrites the identity for everyone, and function nodes start attributing the wrong operator's actions to the wrong person.",[12,1234,1235,1236,1238],{},"Instead, read ",[96,1237,197],{}," off each message for \"who is acting right now,\" and use the username only as a durable storage key for saved state. A shared global is still handy as a bootstrap fallback for the very first tick of a page, before any client-tagged message has round-tripped.",[147,1240,1241,1246,1294],{},[70,1242,810,1243,1245],{},[96,1244,118],{}," node named \"Client connected\" and select the \"My Dashboard\" ui-base. It fires when a browser session connects.",[70,1247,810,1248,1251,1252,1255,1256],{},[96,1249,1250],{},"change"," node named \"Seed globals\" with these ",[96,1253,1254],{},"set"," rules, in order:\n",[67,1257,1258,1271,1280,1289],{},[70,1259,1260,1263,1264,1266,1267,1270],{},[96,1261,1262],{},"user"," (global, persistent) → ",[96,1265,197],{},". ",[45,1268,1269],{},"Bootstrap fallback only",", not the source of truth elsewhere.",[70,1272,1273,1276,1277,57],{},[96,1274,1275],{},"StationContext.stationName"," (global) → your station's name, e.g. ",[96,1278,1279],{},"Wheel Assembly Station 12",[70,1281,1282,1285,1286,57],{},[96,1283,1284],{},"StationContext.stationId"," (global, persistent) → this station's ID, e.g. ",[96,1287,1288],{},"ST12",[70,1290,1291,1293],{},[96,1292,383],{}," (global, persistent) → the instruction set (steps, images, target times, checklists), cached so the Instructions page loads instantly.",[70,1295,1296],{},"Wire \"Client connected\" into \"Seed globals\".",[12,1298,1299],{},"From here on, every function node that needs the current operator uses this pattern, per-session identity first, bootstrap global only as a fallback:",[615,1301,1305],{"className":1302,"code":1303,"language":1304,"meta":620,"style":620},"language-javascript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const username = msg._client?.user?.username\n    || global.get('user', 'persistent')?.username;\n","javascript",[96,1306,1307,1335],{"__ignoreMap":620},[218,1308,1309,1312,1315,1317,1320,1322,1325,1328,1330,1332],{"class":625,"line":626},[218,1310,1311],{"class":639},"const",[218,1313,1314],{"class":939}," username ",[218,1316,861],{"class":629},[218,1318,1319],{"class":939}," msg",[218,1321,57],{"class":629},[218,1323,1324],{"class":939},"_client",[218,1326,1327],{"class":629},"?.",[218,1329,1262],{"class":939},[218,1331,1327],{"class":629},[218,1333,1334],{"class":939},"username\n",[218,1336,1337,1340,1343,1345,1349,1352,1355,1357,1359,1362,1365,1368,1370,1373,1375,1377],{"class":625,"line":633},[218,1338,1339],{"class":629},"    ||",[218,1341,1342],{"class":939}," global",[218,1344,57],{"class":629},[218,1346,1348],{"class":1347},"s2Zo4","get",[218,1350,1351],{"class":939},"(",[218,1353,1354],{"class":629},"'",[218,1356,1262],{"class":866},[218,1358,1354],{"class":629},[218,1360,1361],{"class":629},",",[218,1363,1364],{"class":629}," '",[218,1366,1367],{"class":866},"persistent",[218,1369,1354],{"class":629},[218,1371,1372],{"class":939},")",[218,1374,1327],{"class":629},[218,1376,660],{"class":939},[218,1378,1076],{"class":629},[59,1380,1382],{"id":1381},"showing-the-station-and-stats-on-the-home-page","Showing the Station and Stats on the Home Page",[12,1384,1385],{},"The Home page shows the current station and a quick summary of work order statistics. Both update whenever the page opens.",[1387,1388,1390],"h3",{"id":1389},"display-the-station-name","Display the Station Name",[147,1392,1393,1414],{},[70,1394,1395,1396,1399,1400,1402,1403,1406,1407,1410,1411,57],{},"From the ",[45,1397,1398],{},"Client connected"," event, add a ",[45,1401,1250],{}," node named ",[45,1404,1405],{},"Read station name"," and set ",[96,1408,1409],{},"msg.payload"," to ",[96,1412,1413],{},"global.StationContext.stationName",[70,1415,1416,1417,1402,1419,1421,1422,1424,1425,57],{},"Connect it to a ",[45,1418,121],{},[45,1420,451],{}," in the ",[45,1423,451],{}," group. Set the value to ",[96,1426,1409],{},[1387,1428,1430],{"id":1429},"display-the-stats","Display the Stats",[147,1432,1433,1444],{},[70,1434,810,1435,1402,1437,1440,1441,1443],{},[45,1436,115],{},[45,1438,1439],{},"Page changed",", set its event to ",[45,1442,1250],{},", and select your dashboard.",[70,1445,810,1446,1402,1449,1452,1453],{},[45,1447,1448],{},"function",[45,1450,1451],{},"Build \u002Fstats request URL"," and paste in the following code. ",[45,1454,1455,1456,1459],{},"Replace ",[96,1457,1458],{},"https:\u002F\u002Fyour-instance.flowfuse.cloud"," with your own FlowFuse instance URL.",[615,1461,1463],{"className":1302,"code":1462,"language":1304,"meta":620,"style":620},"const StationContext = global.get('StationContext', 'persistent');\nconst username = msg._client?.user?.username\n    || global.get('user', 'persistent')?.username;\n\nconst params = new URLSearchParams();\nparams.set('stationId', StationContext.stationId);\nif (username) params.set('username', username);\n\nmsg.url = `https:\u002F\u002Fyour-instance.flowfuse.cloud\u002Fworkorders\u002Fstats?${params}`;\nmsg.method = \"GET\";\nreturn msg;\n",[96,1464,1465,1501,1523,1557,1561,1580,1610,1637,1641,1669,1690],{"__ignoreMap":620},[218,1466,1467,1469,1472,1474,1476,1478,1480,1482,1484,1487,1489,1491,1493,1495,1497,1499],{"class":625,"line":626},[218,1468,1311],{"class":639},[218,1470,1471],{"class":939}," StationContext ",[218,1473,861],{"class":629},[218,1475,1342],{"class":939},[218,1477,57],{"class":629},[218,1479,1348],{"class":1347},[218,1481,1351],{"class":939},[218,1483,1354],{"class":629},[218,1485,1486],{"class":866},"StationContext",[218,1488,1354],{"class":629},[218,1490,1361],{"class":629},[218,1492,1364],{"class":629},[218,1494,1367],{"class":866},[218,1496,1354],{"class":629},[218,1498,1372],{"class":939},[218,1500,1076],{"class":629},[218,1502,1503,1505,1507,1509,1511,1513,1515,1517,1519,1521],{"class":625,"line":633},[218,1504,1311],{"class":639},[218,1506,1314],{"class":939},[218,1508,861],{"class":629},[218,1510,1319],{"class":939},[218,1512,57],{"class":629},[218,1514,1324],{"class":939},[218,1516,1327],{"class":629},[218,1518,1262],{"class":939},[218,1520,1327],{"class":629},[218,1522,1334],{"class":939},[218,1524,1525,1527,1529,1531,1533,1535,1537,1539,1541,1543,1545,1547,1549,1551,1553,1555],{"class":625,"line":655},[218,1526,1339],{"class":629},[218,1528,1342],{"class":939},[218,1530,57],{"class":629},[218,1532,1348],{"class":1347},[218,1534,1351],{"class":939},[218,1536,1354],{"class":629},[218,1538,1262],{"class":866},[218,1540,1354],{"class":629},[218,1542,1361],{"class":629},[218,1544,1364],{"class":629},[218,1546,1367],{"class":866},[218,1548,1354],{"class":629},[218,1550,1372],{"class":939},[218,1552,1327],{"class":629},[218,1554,660],{"class":939},[218,1556,1076],{"class":629},[218,1558,1559],{"class":625,"line":542},[218,1560,982],{"emptyLinePlaceholder":981},[218,1562,1563,1565,1568,1570,1573,1576,1578],{"class":625,"line":686},[218,1564,1311],{"class":639},[218,1566,1567],{"class":939}," params ",[218,1569,861],{"class":629},[218,1571,1572],{"class":629}," new",[218,1574,1575],{"class":1347}," URLSearchParams",[218,1577,1014],{"class":939},[218,1579,1076],{"class":629},[218,1581,1582,1585,1587,1589,1591,1593,1596,1598,1600,1603,1605,1608],{"class":625,"line":702},[218,1583,1584],{"class":939},"params",[218,1586,57],{"class":629},[218,1588,1254],{"class":1347},[218,1590,1351],{"class":939},[218,1592,1354],{"class":629},[218,1594,1595],{"class":866},"stationId",[218,1597,1354],{"class":629},[218,1599,1361],{"class":629},[218,1601,1602],{"class":939}," StationContext",[218,1604,57],{"class":629},[218,1606,1607],{"class":939},"stationId)",[218,1609,1076],{"class":629},[218,1611,1612,1615,1618,1620,1622,1624,1626,1628,1630,1632,1635],{"class":625,"line":717},[218,1613,1614],{"class":998},"if",[218,1616,1617],{"class":939}," (username) params",[218,1619,57],{"class":629},[218,1621,1254],{"class":1347},[218,1623,1351],{"class":939},[218,1625,1354],{"class":629},[218,1627,660],{"class":866},[218,1629,1354],{"class":629},[218,1631,1361],{"class":629},[218,1633,1634],{"class":939}," username)",[218,1636,1076],{"class":629},[218,1638,1639],{"class":625,"line":959},[218,1640,982],{"emptyLinePlaceholder":981},[218,1642,1643,1646,1648,1651,1653,1656,1659,1662,1664,1667],{"class":625,"line":969},[218,1644,1645],{"class":939},"msg",[218,1647,57],{"class":629},[218,1649,1650],{"class":939},"url ",[218,1652,861],{"class":629},[218,1654,1655],{"class":629}," `",[218,1657,1658],{"class":866},"https:\u002F\u002Fyour-instance.flowfuse.cloud\u002Fworkorders\u002Fstats?",[218,1660,1661],{"class":629},"${",[218,1663,1584],{"class":939},[218,1665,1666],{"class":629},"}`",[218,1668,1076],{"class":629},[218,1670,1671,1673,1675,1678,1680,1683,1686,1688],{"class":625,"line":978},[218,1672,1645],{"class":939},[218,1674,57],{"class":629},[218,1676,1677],{"class":939},"method ",[218,1679,861],{"class":629},[218,1681,1682],{"class":629}," \"",[218,1684,1685],{"class":866},"GET",[218,1687,643],{"class":629},[218,1689,1076],{"class":629},[218,1691,1692,1695,1697],{"class":625,"line":985},[218,1693,1694],{"class":998},"return",[218,1696,1319],{"class":939},[218,1698,1076],{"class":629},[147,1700,1701,1718,1721,1731],{"start":655},[70,1702,1703,1704,1706,1707,777,1710,1713,1714,1717],{},"Add an ",[45,1705,572],{}," node that uses ",[96,1708,1709],{},"msg.method",[96,1711,1712],{},"msg.url",", returns a parsed JSON object, and uses ",[45,1715,1716],{},"Bearer"," authentication.",[70,1719,1720],{},"Configure the bearer token using the access token you created earlier. For production, store it as an environment variable instead of hardcoding it in the flow.",[70,1722,810,1723,474,1725,1421,1728,1730],{},[45,1724,102],{},[45,1726,1727],{},"Stats Cards",[45,1729,456],{}," group and paste in the template below.",[70,1732,1733,1734,57],{},"Wire the nodes: ",[45,1735,1736],{},"Page changed → Build \u002Fstats request URL → HTTP Request → Stats Cards",[12,1738,1739,1740,1743,1744,1746,1747,1749],{},"The request includes the logged-in username, so the ",[45,1741,1742],{},"Assigned to me"," count is personalized for each operator, and because ",[96,1745,115],{}," is client-data-aware, ",[96,1748,197],{}," reflects whichever operator's browser triggered the page change.",[12,1751,1752,1756],{},[21,1753],{"alt":1754,"src":1755},"Placeholder: Stats Cards widget rendered on the Home page","\u002Fblog\u002F2026\u002F07\u002Fimages\u002Fstats-and-station.png",[26,1757,1758],{},"The Home page displays station statistics, including total, assigned to me, completed, and defected work orders.",[59,1760,1762],{"id":1761},"loading-the-operators-work-orders","Loading the Operator's Work Orders",[12,1764,1765],{},"Now for the main part of the Home page: fetching and displaying the operator's work orders. Because the request includes the logged-in username, each operator only sees the work orders assigned to them at the current station.",[147,1767,1768,1778],{},[70,1769,810,1770,1402,1772,1774,1775,1777],{},[45,1771,115],{},[45,1773,1439],{},", select your dashboard, and set the event to ",[45,1776,1250],{},". It fires whenever the operator navigates to the Home page, triggering the work order request.",[70,1779,810,1780,1402,1782,1785,1786,1459],{},[45,1781,1448],{},[45,1783,1784],{},"Build \u002Fworkorders request URL",", paste in the code below, and replace ",[96,1787,1458],{},[615,1789,1792],{"className":1302,"code":1790,"language":1304,"meta":1791,"style":620},"const StationContext = global.get('StationContext', 'persistent');\nconst username = msg._client?.user?.username\n    || global.get('user', 'persistent')?.username;\n\nconst stationId = StationContext?.stationId;\n\nconst params = new URLSearchParams();\nparams.set('stationId', stationId);\nif (username) params.set('username', username);\n\nmsg.url = `https:\u002F\u002Fyour-instance.flowfuse.cloud\u002Fworkorders?${params.toString()}`;\nmsg.method = 'GET';\nreturn msg;\n","id=\"8m19sv\"",[96,1793,1794,1828,1850,1884,1888,1905,1909,1925,1948,1972,1976,2006,2024],{"__ignoreMap":620},[218,1795,1796,1798,1800,1802,1804,1806,1808,1810,1812,1814,1816,1818,1820,1822,1824,1826],{"class":625,"line":626},[218,1797,1311],{"class":639},[218,1799,1471],{"class":939},[218,1801,861],{"class":629},[218,1803,1342],{"class":939},[218,1805,57],{"class":629},[218,1807,1348],{"class":1347},[218,1809,1351],{"class":939},[218,1811,1354],{"class":629},[218,1813,1486],{"class":866},[218,1815,1354],{"class":629},[218,1817,1361],{"class":629},[218,1819,1364],{"class":629},[218,1821,1367],{"class":866},[218,1823,1354],{"class":629},[218,1825,1372],{"class":939},[218,1827,1076],{"class":629},[218,1829,1830,1832,1834,1836,1838,1840,1842,1844,1846,1848],{"class":625,"line":633},[218,1831,1311],{"class":639},[218,1833,1314],{"class":939},[218,1835,861],{"class":629},[218,1837,1319],{"class":939},[218,1839,57],{"class":629},[218,1841,1324],{"class":939},[218,1843,1327],{"class":629},[218,1845,1262],{"class":939},[218,1847,1327],{"class":629},[218,1849,1334],{"class":939},[218,1851,1852,1854,1856,1858,1860,1862,1864,1866,1868,1870,1872,1874,1876,1878,1880,1882],{"class":625,"line":655},[218,1853,1339],{"class":629},[218,1855,1342],{"class":939},[218,1857,57],{"class":629},[218,1859,1348],{"class":1347},[218,1861,1351],{"class":939},[218,1863,1354],{"class":629},[218,1865,1262],{"class":866},[218,1867,1354],{"class":629},[218,1869,1361],{"class":629},[218,1871,1364],{"class":629},[218,1873,1367],{"class":866},[218,1875,1354],{"class":629},[218,1877,1372],{"class":939},[218,1879,1327],{"class":629},[218,1881,660],{"class":939},[218,1883,1076],{"class":629},[218,1885,1886],{"class":625,"line":542},[218,1887,982],{"emptyLinePlaceholder":981},[218,1889,1890,1892,1895,1897,1899,1901,1903],{"class":625,"line":686},[218,1891,1311],{"class":639},[218,1893,1894],{"class":939}," stationId ",[218,1896,861],{"class":629},[218,1898,1602],{"class":939},[218,1900,1327],{"class":629},[218,1902,1595],{"class":939},[218,1904,1076],{"class":629},[218,1906,1907],{"class":625,"line":702},[218,1908,982],{"emptyLinePlaceholder":981},[218,1910,1911,1913,1915,1917,1919,1921,1923],{"class":625,"line":717},[218,1912,1311],{"class":639},[218,1914,1567],{"class":939},[218,1916,861],{"class":629},[218,1918,1572],{"class":629},[218,1920,1575],{"class":1347},[218,1922,1014],{"class":939},[218,1924,1076],{"class":629},[218,1926,1927,1929,1931,1933,1935,1937,1939,1941,1943,1946],{"class":625,"line":959},[218,1928,1584],{"class":939},[218,1930,57],{"class":629},[218,1932,1254],{"class":1347},[218,1934,1351],{"class":939},[218,1936,1354],{"class":629},[218,1938,1595],{"class":866},[218,1940,1354],{"class":629},[218,1942,1361],{"class":629},[218,1944,1945],{"class":939}," stationId)",[218,1947,1076],{"class":629},[218,1949,1950,1952,1954,1956,1958,1960,1962,1964,1966,1968,1970],{"class":625,"line":969},[218,1951,1614],{"class":998},[218,1953,1617],{"class":939},[218,1955,57],{"class":629},[218,1957,1254],{"class":1347},[218,1959,1351],{"class":939},[218,1961,1354],{"class":629},[218,1963,660],{"class":866},[218,1965,1354],{"class":629},[218,1967,1361],{"class":629},[218,1969,1634],{"class":939},[218,1971,1076],{"class":629},[218,1973,1974],{"class":625,"line":978},[218,1975,982],{"emptyLinePlaceholder":981},[218,1977,1978,1980,1982,1984,1986,1988,1991,1993,1995,1997,2000,2002,2004],{"class":625,"line":985},[218,1979,1645],{"class":939},[218,1981,57],{"class":629},[218,1983,1650],{"class":939},[218,1985,861],{"class":629},[218,1987,1655],{"class":629},[218,1989,1990],{"class":866},"https:\u002F\u002Fyour-instance.flowfuse.cloud\u002Fworkorders?",[218,1992,1661],{"class":629},[218,1994,1584],{"class":939},[218,1996,57],{"class":629},[218,1998,1999],{"class":1347},"toString",[218,2001,1014],{"class":939},[218,2003,1666],{"class":629},[218,2005,1076],{"class":629},[218,2007,2008,2010,2012,2014,2016,2018,2020,2022],{"class":625,"line":995},[218,2009,1645],{"class":939},[218,2011,57],{"class":629},[218,2013,1677],{"class":939},[218,2015,861],{"class":629},[218,2017,1364],{"class":629},[218,2019,1685],{"class":866},[218,2021,1354],{"class":629},[218,2023,1076],{"class":629},[218,2025,2026,2028,2030],{"class":625,"line":1008},[218,2027,1694],{"class":998},[218,2029,1319],{"class":939},[218,2031,1076],{"class":629},[147,2033,2034,2066],{"start":655},[70,2035,1703,2036,2038,2039,1410,2042,2047,2048,2051,2052,2054,2055,2058,2059,2062,2063,2065],{},[45,2037,572],{}," node, set the ",[45,2040,2041],{},"Method",[45,2043,2044,2045],{},"Use ",[96,2046,1709],{},", leave the ",[45,2049,2050],{},"URL"," field blank so it uses ",[96,2053,1712],{},", set the ",[45,2056,2057],{},"Return"," type to a ",[45,2060,2061],{},"parsed JSON object",", and configure ",[45,2064,1716],{}," authentication. Use the access token you created earlier as the bearer token. For production deployments, store the token as an environment variable and reference it here instead of hardcoding it in the flow.",[70,2067,2068,2069,2071,2072,1402,2075,2078,2079,2082],{},"Connect the ",[45,2070,572],{}," node to a ",[45,2073,2074],{},"link out",[45,2076,2077],{},"Work Orders Out",". The widgets that display the work orders will connect to this node using their own ",[45,2080,2081],{},"link in"," nodes, allowing the same API response to be reused without stretching wires across the canvas.",[12,2084,2085,2086,2088,2089,2092],{},"The username in the request is what makes the dashboard personal. Two operators can open the same page at the same station, yet each receives only the work orders assigned to them, because each one's ",[96,2087,115],{}," event carries their own ",[96,2090,2091],{},"_client.user",", not a shared one.",[59,2094,2096],{"id":2095},"splitting-the-current-order-from-the-queue","Splitting the Current Order From the Queue",[12,2098,2099],{},"The Home page shows one work order front and centre and the rest as a queue. A function splits the list, sorting by priority so the most urgent job is the one the operator sees first. This function also records which work order the operator is about to start, that's the record the Instructions page, the Complete Operation flow, and the Report Issue flow will all read back later, so it has to be attributed to the right operator from the start.",[147,2101,2102,2107],{},[70,2103,810,2104,2106],{},[96,2105,2081],{}," node named \"link in 6\" and point it at the \"work orders out\" link.",[70,2108,810,2109,2111,2112,646],{},[96,2110,1448],{}," node named \"split current vs queue (by priority)\" with ",[45,2113,2114],{},"2 outputs",[615,2116,2118],{"className":1302,"code":2117,"language":1304,"meta":620,"style":620},"const orders = Array.isArray(msg.payload) ? msg.payload : [];\n\n\u002F\u002F Sort by priority (Critical\u002FUrgent -> High -> Medium -> Normal -> Low)\nconst priorityOrder = { critical: 0, urgent: 0, high: 1, medium: 2, normal: 3, low: 4 };\nconst sorted = [...orders].sort(\n    (a, b) => (priorityOrder[(a.priority || '').toLowerCase()] ?? 5)\n            - (priorityOrder[(b.priority || '').toLowerCase()] ?? 5)\n);\n\nconst currentWorkOrder = sorted[0] || null;\n\n\u002F\u002F Record which work order the current operator is about to see\u002Fstart.\n\u002F\u002F Identity comes from the triggering message's client data, not a shared global,\n\u002F\u002F so this is always attributed to the operator whose browser fetched this list.\nconst username = msg._client?.user?.username\n    || global.get('user', 'persistent')?.username;\n\nif (username && currentWorkOrder) {\n    const userState = global.get(username, 'persistent') || {};\n    userState.workOrderId = currentWorkOrder.workOrderId;\n    global.set(username, userState, 'persistent');\n}\n\nreturn [\n    { payload: currentWorkOrder },  \u002F\u002F output 1: current order (for card)\n    { payload: sorted.slice(1) }    \u002F\u002F output 2: remaining orders (queue)\n];\n",[96,2119,2120,2162,2166,2171,2241,2267,2318,2348,2354,2358,2381,2385,2390,2395,2400,2422,2456,2460,2475,2511,2532,2561,2565,2569,2576,2594,2623],{"__ignoreMap":620},[218,2121,2122,2124,2127,2129,2132,2134,2137,2140,2142,2145,2148,2150,2152,2155,2157,2160],{"class":625,"line":626},[218,2123,1311],{"class":639},[218,2125,2126],{"class":939}," orders ",[218,2128,861],{"class":629},[218,2130,2131],{"class":939}," Array",[218,2133,57],{"class":629},[218,2135,2136],{"class":1347},"isArray",[218,2138,2139],{"class":939},"(msg",[218,2141,57],{"class":629},[218,2143,2144],{"class":939},"payload) ",[218,2146,2147],{"class":629},"?",[218,2149,1319],{"class":939},[218,2151,57],{"class":629},[218,2153,2154],{"class":939},"payload ",[218,2156,646],{"class":629},[218,2158,2159],{"class":939}," []",[218,2161,1076],{"class":629},[218,2163,2164],{"class":625,"line":633},[218,2165,982],{"emptyLinePlaceholder":981},[218,2167,2168],{"class":625,"line":655},[218,2169,2170],{"class":847},"\u002F\u002F Sort by priority (Critical\u002FUrgent -> High -> Medium -> Normal -> Low)\n",[218,2172,2173,2175,2178,2180,2182,2185,2187,2190,2192,2195,2197,2199,2201,2204,2206,2209,2211,2214,2216,2219,2221,2224,2226,2229,2231,2234,2236,2239],{"class":625,"line":542},[218,2174,1311],{"class":639},[218,2176,2177],{"class":939}," priorityOrder ",[218,2179,861],{"class":629},[218,2181,1025],{"class":629},[218,2183,2184],{"class":838}," critical",[218,2186,646],{"class":629},[218,2188,2189],{"class":1151}," 0",[218,2191,1361],{"class":629},[218,2193,2194],{"class":838}," urgent",[218,2196,646],{"class":629},[218,2198,2189],{"class":1151},[218,2200,1361],{"class":629},[218,2202,2203],{"class":838}," high",[218,2205,646],{"class":629},[218,2207,2208],{"class":1151}," 1",[218,2210,1361],{"class":629},[218,2212,2213],{"class":838}," medium",[218,2215,646],{"class":629},[218,2217,2218],{"class":1151}," 2",[218,2220,1361],{"class":629},[218,2222,2223],{"class":838}," normal",[218,2225,646],{"class":629},[218,2227,2228],{"class":1151}," 3",[218,2230,1361],{"class":629},[218,2232,2233],{"class":838}," low",[218,2235,646],{"class":629},[218,2237,2238],{"class":1151}," 4",[218,2240,1037],{"class":629},[218,2242,2243,2245,2248,2250,2253,2256,2259,2261,2264],{"class":625,"line":686},[218,2244,1311],{"class":639},[218,2246,2247],{"class":939}," sorted ",[218,2249,861],{"class":629},[218,2251,2252],{"class":939}," [",[218,2254,2255],{"class":629},"...",[218,2257,2258],{"class":939},"orders]",[218,2260,57],{"class":629},[218,2262,2263],{"class":1347},"sort",[218,2265,2266],{"class":939},"(\n",[218,2268,2269,2272,2275,2277,2280,2282,2285,2288,2290,2293,2296,2299,2301,2303,2306,2309,2312,2315],{"class":625,"line":702},[218,2270,2271],{"class":629},"    (",[218,2273,53],{"class":2274},"sHdIc",[218,2276,1361],{"class":629},[218,2278,2279],{"class":2274}," b",[218,2281,1372],{"class":629},[218,2283,2284],{"class":639}," =>",[218,2286,2287],{"class":939}," (priorityOrder[(a",[218,2289,57],{"class":629},[218,2291,2292],{"class":939},"priority ",[218,2294,2295],{"class":629},"||",[218,2297,2298],{"class":629}," ''",[218,2300,1372],{"class":939},[218,2302,57],{"class":629},[218,2304,2305],{"class":1347},"toLowerCase",[218,2307,2308],{"class":939},"()] ",[218,2310,2311],{"class":629},"??",[218,2313,2314],{"class":1151}," 5",[218,2316,2317],{"class":939},")\n",[218,2319,2320,2323,2326,2328,2330,2332,2334,2336,2338,2340,2342,2344,2346],{"class":625,"line":717},[218,2321,2322],{"class":629},"            -",[218,2324,2325],{"class":939}," (priorityOrder[(b",[218,2327,57],{"class":629},[218,2329,2292],{"class":939},[218,2331,2295],{"class":629},[218,2333,2298],{"class":629},[218,2335,1372],{"class":939},[218,2337,57],{"class":629},[218,2339,2305],{"class":1347},[218,2341,2308],{"class":939},[218,2343,2311],{"class":629},[218,2345,2314],{"class":1151},[218,2347,2317],{"class":939},[218,2349,2350,2352],{"class":625,"line":959},[218,2351,1372],{"class":939},[218,2353,1076],{"class":629},[218,2355,2356],{"class":625,"line":969},[218,2357,982],{"emptyLinePlaceholder":981},[218,2359,2360,2362,2365,2367,2370,2373,2376,2378],{"class":625,"line":978},[218,2361,1311],{"class":639},[218,2363,2364],{"class":939}," currentWorkOrder ",[218,2366,861],{"class":629},[218,2368,2369],{"class":939}," sorted[",[218,2371,2372],{"class":1151},"0",[218,2374,2375],{"class":939},"] ",[218,2377,2295],{"class":629},[218,2379,2380],{"class":629}," null;\n",[218,2382,2383],{"class":625,"line":985},[218,2384,982],{"emptyLinePlaceholder":981},[218,2386,2387],{"class":625,"line":995},[218,2388,2389],{"class":847},"\u002F\u002F Record which work order the current operator is about to see\u002Fstart.\n",[218,2391,2392],{"class":625,"line":1008},[218,2393,2394],{"class":847},"\u002F\u002F Identity comes from the triggering message's client data, not a shared global,\n",[218,2396,2397],{"class":625,"line":1019},[218,2398,2399],{"class":847},"\u002F\u002F so this is always attributed to the operator whose browser fetched this list.\n",[218,2401,2402,2404,2406,2408,2410,2412,2414,2416,2418,2420],{"class":625,"line":1040},[218,2403,1311],{"class":639},[218,2405,1314],{"class":939},[218,2407,861],{"class":629},[218,2409,1319],{"class":939},[218,2411,57],{"class":629},[218,2413,1324],{"class":939},[218,2415,1327],{"class":629},[218,2417,1262],{"class":939},[218,2419,1327],{"class":629},[218,2421,1334],{"class":939},[218,2423,2424,2426,2428,2430,2432,2434,2436,2438,2440,2442,2444,2446,2448,2450,2452,2454],{"class":625,"line":1046},[218,2425,1339],{"class":629},[218,2427,1342],{"class":939},[218,2429,57],{"class":629},[218,2431,1348],{"class":1347},[218,2433,1351],{"class":939},[218,2435,1354],{"class":629},[218,2437,1262],{"class":866},[218,2439,1354],{"class":629},[218,2441,1361],{"class":629},[218,2443,1364],{"class":629},[218,2445,1367],{"class":866},[218,2447,1354],{"class":629},[218,2449,1372],{"class":939},[218,2451,1327],{"class":629},[218,2453,660],{"class":939},[218,2455,1076],{"class":629},[218,2457,2458],{"class":625,"line":1056},[218,2459,982],{"emptyLinePlaceholder":981},[218,2461,2462,2464,2467,2470,2473],{"class":625,"line":1062},[218,2463,1614],{"class":998},[218,2465,2466],{"class":939}," (username ",[218,2468,2469],{"class":629},"&&",[218,2471,2472],{"class":939}," currentWorkOrder) ",[218,2474,630],{"class":629},[218,2476,2477,2480,2483,2485,2487,2489,2491,2493,2495,2497,2499,2501,2503,2506,2508],{"class":625,"line":1079},[218,2478,2479],{"class":639},"    const",[218,2481,2482],{"class":939}," userState",[218,2484,1070],{"class":629},[218,2486,1342],{"class":939},[218,2488,57],{"class":629},[218,2490,1348],{"class":1347},[218,2492,1351],{"class":838},[218,2494,660],{"class":939},[218,2496,1361],{"class":629},[218,2498,1364],{"class":629},[218,2500,1367],{"class":866},[218,2502,1354],{"class":629},[218,2504,2505],{"class":838},") ",[218,2507,2295],{"class":629},[218,2509,2510],{"class":629}," {};\n",[218,2512,2513,2516,2518,2521,2523,2526,2528,2530],{"class":625,"line":1085},[218,2514,2515],{"class":939},"    userState",[218,2517,57],{"class":629},[218,2519,2520],{"class":939},"workOrderId",[218,2522,1070],{"class":629},[218,2524,2525],{"class":939}," currentWorkOrder",[218,2527,57],{"class":629},[218,2529,2520],{"class":939},[218,2531,1076],{"class":629},[218,2533,2534,2537,2539,2541,2543,2545,2547,2549,2551,2553,2555,2557,2559],{"class":625,"line":1090},[218,2535,2536],{"class":939},"    global",[218,2538,57],{"class":629},[218,2540,1254],{"class":1347},[218,2542,1351],{"class":838},[218,2544,660],{"class":939},[218,2546,1361],{"class":629},[218,2548,2482],{"class":939},[218,2550,1361],{"class":629},[218,2552,1364],{"class":629},[218,2554,1367],{"class":866},[218,2556,1354],{"class":629},[218,2558,1372],{"class":838},[218,2560,1076],{"class":629},[218,2562,2563],{"class":625,"line":1099},[218,2564,720],{"class":629},[218,2566,2567],{"class":625,"line":1104},[218,2568,982],{"emptyLinePlaceholder":981},[218,2570,2571,2573],{"class":625,"line":1114},[218,2572,1694],{"class":998},[218,2574,2575],{"class":939}," [\n",[218,2577,2578,2581,2584,2586,2588,2591],{"class":625,"line":1160},[218,2579,2580],{"class":629},"    {",[218,2582,2583],{"class":838}," payload",[218,2585,646],{"class":629},[218,2587,2364],{"class":939},[218,2589,2590],{"class":629},"},",[218,2592,2593],{"class":847},"  \u002F\u002F output 1: current order (for card)\n",[218,2595,2596,2598,2600,2602,2605,2607,2610,2612,2615,2617,2620],{"class":625,"line":1193},[218,2597,2580],{"class":629},[218,2599,2583],{"class":838},[218,2601,646],{"class":629},[218,2603,2604],{"class":939}," sorted",[218,2606,57],{"class":629},[218,2608,2609],{"class":1347},"slice",[218,2611,1351],{"class":939},[218,2613,2614],{"class":1151},"1",[218,2616,2505],{"class":939},[218,2618,2619],{"class":629},"}",[218,2621,2622],{"class":847},"    \u002F\u002F output 2: remaining orders (queue)\n",[218,2624,2626,2629],{"class":625,"line":2625},27,[218,2627,2628],{"class":939},"]",[218,2630,1076],{"class":629},[147,2632,2633],{"start":655},[70,2634,2635],{},"Import the Current Work Order Card ui-template below and assign it to the Current Work Order group. The complete component is provided below, so there's no need to recreate it.",[40,2637,2638],{},[12,2639,2640,2643,2644,2649,2650,2652],{},[45,2641,2642],{},"Tip:"," Whenever you need a custom Dashboard widget, you don't have to write the Vue code yourself. Use ",[53,2645,2648],{"href":2646,"rel":2647},"https:\u002F\u002Fflowfuse.com\u002Fdocs\u002Fuser\u002Fexpert\u002Fnode-red-embedded-ai\u002F#css-and-html-generation-for-flowfuse-dashboard",[80],"FlowFuse Expert"," and describe the widget in plain English and it will generate the ",[96,2651,102],{}," for you.",[12,2654,2655,2656,2733],{},"{% renderFlow 300 %}\n{% raw %}\n",[218,2657,2658,2659,2723,2724,2723,2727,2730,2731,351],{},"{\"id\":\"eb41d1ba8e4bfb01\",\"type\":\"ui-template\",\"z\":\"c50b1b501a74c081\",\"g\":\"3e9b1176168ca254\",\"group\":\"\",\"page\":\"\",\"ui\":\"\",\"name\":\"Current Work Order Card\",\"order\":1,\"width\":0,\"height\":0,\"head\":\"\",\"format\":\"",[839,2660,2661,2662,2663,2667,2668,2671,2672,2675,2676,2677,2680,2681,2684,2685,2688,2689,2692,2693,2696,2697,2698,2701,2702,2705,2706,2709,2710,2713,2714,2717,2718,2719,2722],{},"\\n  \u003Cdiv class=\"wi-root\">\\n    ","\\n    \u003Cdiv class=\"wi-topbar\">\\n      \u003Cdiv class=\"wi-topbar-left\">\\n        \u003Cspan class=\"wi-wo-label\">WORK ORDER\\n        \u003Cspan class=\"wi-wo-id\">",[2664,2665],"binding",{"value":2666},"msg?.payload?.workOrderId ?? '—'","\\n      \\n      \u003Cdiv class=\"wi-topbar-right\">\\n        \u003Cv-chip size=\"large\" variant=\"flat\" label ",[2669,2670],"color",{},"=\"priorityColor\" class=\"wi-chip\">\\n          \u003Cv-icon start icon=\"mdi-flag\">\\n          ",[2664,2673],{"value":2674},"(msg?.payload?.priority ?? 'Normal').toUpperCase()"," PRIORITY\\n        \\n      \\n    \\n\\n    \u003Cv-row no-gutters class=\"wi-body\">\\n      ","\\n      \u003Cv-col cols=\"12\" md=\"5\" class=\"wi-col\">\\n        \u003Cv-card flat class=\"wi-card wi-card-station\">\\n          \u003Cdiv class=\"wi-card-head\">\\n            \u003Cv-icon icon=\"mdi-map-marker-radius\" size=\"22\">\\n            ",[218,2678,2679],{},"STATION","\\n          \\n          \u003Cdiv class=\"wi-station-id\">",[2664,2682],{"value":2683},"msg?.payload?.stationId ?? '—'","\\n\\n          \u003Cv-divider class=\"wi-divider\">\\n\\n          \u003Cdiv class=\"wi-card-head\">\\n            \u003Cv-icon icon=\"mdi-clipboard-text-outline\" size=\"22\">\\n            ",[218,2686,2687],{},"OPERATION","\\n          \\n          \u003Cdiv class=\"wi-operation-name\">\\n            ",[2664,2690],{"value":2691},"msg?.payload?.operation?.name ?? 'Awaiting operation'","\\n          \\n\\n          \u003Cdiv class=\"wi-cycle\">\\n            \u003Cv-icon icon=\"mdi-timer-outline\" size=\"26\">\\n            \u003Cdiv class=\"wi-cycle-text\">\\n              \u003Cspan class=\"wi-cycle-label\">ESTIMATED CYCLE TIME\\n              \u003Cspan class=\"wi-cycle-value\">\\n                ",[2664,2694],{"value":2695},"msg?.payload?.operation?.estimatedCycleTime ?? '—'","\\n              \\n            \\n          \\n        \\n      \\n\\n      ","\\n      \u003Cv-col cols=\"12\" md=\"7\" class=\"wi-col\">\\n        \u003Cv-card flat class=\"wi-card wi-card-vehicle\">\\n          \u003Cdiv class=\"wi-card-head\">\\n            \u003Cv-icon icon=\"mdi-car-side\" size=\"22\">\\n            ",[218,2699,2700],{},"VEHICLE","\\n          \\n\\n          \u003Cdiv class=\"wi-vehicle-model\">\\n            ",[2664,2703],{"value":2704},"msg?.payload?.vehicle?.model ?? 'Unknown model'","\\n          \\n          \u003Cdiv class=\"wi-vehicle-variant\">\\n            ",[2664,2707],{"value":2708},"msg?.payload?.vehicle?.variant ?? ''","\\n          \\n\\n          \u003Cv-row no-gutters class=\"wi-spec-grid\">\\n            \u003Cv-col cols=\"12\" sm=\"7\">\\n              \u003Cdiv class=\"wi-spec\">\\n                \u003Cspan class=\"wi-spec-label\">VIN\\n                \u003Cspan class=\"wi-spec-value wi-vin\">\\n                  ",[2664,2711],{"value":2712},"msg?.payload?.vehicle?.vin ?? '—'","\\n                \\n              \\n            \\n            \u003Cv-col cols=\"12\" sm=\"5\">\\n              \u003Cdiv class=\"wi-spec\">\\n                \u003Cspan class=\"wi-spec-label\">COLOR\\n                \u003Cspan class=\"wi-spec-value\">\\n                  \u003Cv-icon\\n                    icon=\"mdi-circle\"\\n                    size=\"18\"\\n                    class=\"wi-color-dot\"\\n                  >\\n                  ",[2664,2715],{"value":2716},"msg?.payload?.vehicle?.color ?? '—'","\\n                \\n              \\n            \\n          \\n        \\n      \\n    \\n\\n    ","\\n    \u003Cv-btn block size=\"x-large\" color=\"green-darken-1\" class=\"wi-start-btn\" ",[2720,2721],"disabled",{},"=\"!msg?.payload?.workOrderId\"\\n      @click=\"send({ payload: { action: 'start', workOrderId: msg?.payload?.workOrderId, stationId: msg?.payload?.stationId } })\">\\n      \u003Cv-icon start icon=\"mdi-play-circle\" size=\"34\">\\n      START WORK ORDER\\n    \\n  \\n","\\n\\n",[990,2725,2726],{},"\\n  export default {\\n  computed: {\\n    priorityColor () {\\n      const p = (this.msg?.payload?.priority ?? '').toLowerCase()\\n      if (p === 'high' || p === 'urgent' || p === 'critical') return 'red-darken-1'\\n      if (p === 'medium') return 'amber-darken-2'\\n      return 'blue-grey-darken-1'\\n    }\\n  }\\n}\\n",[1109,2728,2729],{},"\\n  .wi-root {\\n    font-family: 'Roboto', system-ui, sans-serif;\\n    color: #1f2933;\\n    padding: 4px;\\n  }\\n\\n  .wi-topbar {\\n    display: flex;\\n    align-items: center;\\n    justify-content: space-between;\\n    flex-wrap: wrap;\\n    gap: 12px;\\n    background: #ffffff;\\n    border: 1px solid #e2e8f0;\\n    border-left: 6px solid #1565c0;\\n    border-radius: 10px;\\n    padding: 14px 20px;\\n    margin-bottom: 14px;\\n  }\\n\\n  .wi-topbar-left {\\n    display: flex;\\n    flex-direction: column;\\n    line-height: 1.1;\\n  }\\n\\n  .wi-wo-label {\\n    font-size: 12px;\\n    letter-spacing: 2px;\\n    color: #64748b;\\n    font-weight: 600;\\n  }\\n\\n  .wi-wo-id {\\n    font-size: 30px;\\n    font-weight: 700;\\n    color: #0f172a;\\n    font-variant-numeric: tabular-nums;\\n  }\\n\\n  .wi-topbar-right {\\n    display: flex;\\n    gap: 10px;\\n    flex-wrap: wrap;\\n  }\\n\\n  .wi-chip {\\n    font-weight: 700 !important;\\n    letter-spacing: 0.5px;\\n  }\\n\\n  .wi-body {\\n    gap: 0;\\n  }\\n\\n  .wi-col {\\n    padding: 6px;\\n  }\\n\\n  .wi-card {\\n    background: #ffffff !important;\\n    border: 1px solid #e2e8f0 !important;\\n    border-radius: 10px !important;\\n    padding: 20px 22px;\\n    height: 100%;\\n  }\\n\\n  .wi-card-vehicle {\\n    border-left: 6px solid #00897b !important;\\n  }\\n\\n  .wi-card-station {\\n    border-left: 6px solid #5e35b1 !important;\\n  }\\n\\n  .wi-card-head {\\n    display: flex;\\n    align-items: center;\\n    gap: 8px;\\n    font-size: 13px;\\n    letter-spacing: 2px;\\n    font-weight: 700;\\n    color: #64748b;\\n    margin-bottom: 6px;\\n  }\\n\\n  .wi-station-id {\\n    font-size: 44px;\\n    font-weight: 700;\\n    color: #4527a0;\\n    line-height: 1;\\n    margin-bottom: 6px;\\n  }\\n\\n  .wi-divider {\\n    margin: 16px 0;\\n    border-color: #edf2f7 !important;\\n  }\\n\\n  .wi-operation-name {\\n    font-size: 24px;\\n    font-weight: 600;\\n    color: #0f172a;\\n    margin-bottom: 18px;\\n  }\\n\\n  .wi-cycle {\\n    display: flex;\\n    align-items: center;\\n    gap: 12px;\\n    background: #f1f5f9;\\n    border-radius: 8px;\\n    padding: 12px 16px;\\n  }\\n\\n  .wi-cycle-text {\\n    display: flex;\\n    flex-direction: column;\\n    line-height: 1.2;\\n  }\\n\\n  .wi-cycle-label {\\n    font-size: 11px;\\n    letter-spacing: 1.5px;\\n    font-weight: 600;\\n    color: #64748b;\\n  }\\n\\n  .wi-cycle-value {\\n    font-size: 22px;\\n    font-weight: 700;\\n    color: #0f172a;\\n    font-variant-numeric: tabular-nums;\\n  }\\n\\n  .wi-vehicle-model {\\n    font-size: 34px;\\n    font-weight: 700;\\n    color: #0f172a;\\n    line-height: 1.05;\\n  }\\n\\n  .wi-vehicle-variant {\\n    font-size: 18px;\\n    font-weight: 500;\\n    color: #00695c;\\n    margin-bottom: 20px;\\n  }\\n\\n  .wi-spec-grid {\\n    gap: 0;\\n  }\\n\\n  .wi-spec {\\n    display: flex;\\n    flex-direction: column;\\n    padding: 10px 0;\\n  }\\n\\n  .wi-spec-label {\\n    font-size: 11px;\\n    letter-spacing: 1.5px;\\n    font-weight: 600;\\n    color: #64748b;\\n    margin-bottom: 2px;\\n  }\\n\\n  .wi-spec-value {\\n    font-size: 20px;\\n    font-weight: 600;\\n    color: #0f172a;\\n    display: flex;\\n    align-items: center;\\n    gap: 6px;\\n  }\\n\\n  .wi-vin {\\n    font-family: 'Roboto Mono', monospace;\\n    font-size: 18px;\\n    letter-spacing: 1px;\\n  }\\n\\n  .wi-color-dot {\\n    color: #cbd5e1;\\n  }\\n\\n  .wi-start-btn {\\n    margin-top: 16px;\\n    height: 84px !important;\\n    font-size: 24px !important;\\n    font-weight: 700 !important;\\n    letter-spacing: 1px;\\n    border-radius: 12px !important;\\n  }\\n","\",\"storeOutMessages\":true,\"passthru\":false,\"resendOnRefresh\":true,\"templateScope\":\"local\",\"className\":\"\",\"x\":570,\"y\":620,\"wires\":[[\"455c7536a9adcfe6\"]]},{\"id\":\"77ecf88a48f8c4f0\",\"type\":\"global-config\",\"env\":",[218,2732],{},"\n{% endraw %}\n{% endrenderFlow 300 %}",[147,2735,2736],{"start":542},[70,2737,2738,2739,2742,2743,1421,2745,2747,2748,2750,2751,57],{},"Wire ",[45,2740,2741],{},"output 1"," to the \"Current Work Order Card\" ",[96,2744,102],{},[45,2746,461],{}," group. It shows the work order ID, station, operation and estimated cycle time, the vehicle (model, variant, VIN, colour), a priority chip, and a ",[45,2749,157],{}," button. On click, the button sends ",[96,2752,2753],{},"{ action: 'start', workOrderId, stationId }",[12,2755,2756,75,2760],{},[21,2757],{"alt":2758,"src":2759},"Placeholder: Current Work Order Card rendered on the Home page","\u002Fblog\u002F2026\u002F07\u002Fimages\u002Fcurrent-work-order.png",[26,2761,2762],{},"The Current Work Order card: work order ID, priority chip, station, operation, vehicle details, and the Start button.",[147,2764,2765],{"start":686},[70,2766,810,2767,2769,2770,2772],{},[96,2768,106],{}," node and assign it to the ",[45,2771,466],{}," group. Turn off auto-columns and add two text columns as shown below: Work Order (workOrderId) and Priority (priority). Wire output 2 of the split function into it.",[12,2774,2775,75,2779],{},[21,2776],{"alt":2777,"src":2778},"Placeholder: Up Next table listing queued work orders","\u002Fblog\u002F2026\u002F07\u002Fimages\u002Fup-next.png",[26,2780,2781],{},"The Up Next table showing the remaining queued work orders with their priority.",[147,2783,2784],{"start":702},[70,2785,2786,2787,2789,2790,2793,2794,2796,2797,2799,2800,2802],{},"From the card, wire a ",[96,2788,1250],{}," node named \"go to Instructions\" that sets ",[96,2791,2792],{},"payload"," (msg) to ",[96,2795,383],{},", then into a ",[96,2798,115],{}," node with its event set to ",[45,2801,1250],{}," to switch the page.",[12,2804,2805,2806,2808,2809,2811],{},"When the operator taps ",[45,2807,157],{},", the card sends them to the Instructions page. Because the split function already saved this operator's ",[96,2810,2520],{}," under their own username, the Instructions page, Complete Operation, and Report Issue flows can all look it back up correctly, even if another operator is doing the exact same thing on another screen at the same moment.",[59,2813,2815],{"id":2814},"loading-the-cached-instruction-set","Loading the Cached Instruction Set",[12,2817,2818],{},"The Instructions page needs the steps to show. Because the instruction set was cached in context on login, the page can paint it instantly without another round trip.",[147,2820,2821,2826,2836],{},[70,2822,810,2823,2825],{},[96,2824,118],{}," node named \"Page opened\" for the Instructions page.",[70,2827,810,2828,2830,2831,2833,2834,57],{},[96,2829,1250],{}," node named \"Load cached instruction set\". Set ",[96,2832,2792],{}," (msg) to the persistent global ",[96,2835,383],{},[70,2837,2838],{},"Wire \"Page opened\" into it, and its output into the \"Work Instruction Widget\" you build next.",[59,2840,2842],{"id":2841},"remembering-each-operators-step","Remembering Each Operator's Step",[12,2844,2845,2846,2849,2850,2853],{},"This is something paper can't do and shared screens get wrong. If an operator completes the first two steps of a five-step wheel installation and returns later, they should resume at ",[45,2847,2848],{},"step 3",", not start again at ",[45,2851,2852],{},"step 1",". We achieve this by saving their progress against their username, the one they were identified as when the step was saved, not whichever username happens to be sitting in a shared global at read time.",[12,2855,2856,2857,2860,2861,2864],{},"The instruction widget emits two housekeeping actions: ",[96,2858,2859],{},"save_step"," whenever the operator moves between steps, and ",[96,2862,2863],{},"load_step"," when the page opens and needs to know where to resume. One function node handles both.",[147,2866,2867],{},[70,2868,2869,2870,75,2873,2875,2876,2878,2879,2882,2883,57],{},"Import the ",[45,2871,2872],{},"Work Instruction Widget",[96,2874,102],{}," below, assign it to the ",[45,2877,383],{}," page, and set its scope to ",[45,2880,2881],{},"Page",". The widget displays one instruction at a time with its image, target cycle time, and checklist. Operators can't move to the next step until every checklist item is completed, and the final step changes the button to ",[45,2884,2885],{},"Complete Operation",[40,2887,2888],{},[12,2889,2890,2643,2892,2649,2896,2652],{},[45,2891,2642],{},[53,2893,2648],{"href":2894,"rel":2895},"https:\u002F\u002Fflowfuse.com\u002Fdocs\u002Fuser\u002Fexpert\u002Fnode-red-embedded-ai\u002F?utm_source=chatgpt.com#css-and-html-generation-for-flowfuse-dashboard",[80],[96,2897,102],{},[12,2899,2655,2900,2733],{},[218,2901,2902,2903,2723,3011,3014,3015,3017,3018,3021,3022,3024,3025,3027,3028,3030,3031,3033,3034,3024,3036,3038,3039,3041,3042,3044,3045,3047,3048,3051,3052,3055,3056,3059,3060,3063,3064,3067,3068,3071,3072,3075,3076,3079,3080,351],{},"{\"id\":\"d3492b9665d71f27\",\"type\":\"ui-template\",\"z\":\"c50b1b501a74c081\",\"g\":\"96f12d35d68b430e\",\"group\":\"\",\"page\":\"\",\"ui\":\"\",\"name\":\"Work Instruction Widget\",\"order\":1,\"width\":0,\"height\":0,\"head\":\"\",\"format\":\"",[839,2904,2661,2905,2906,2909,2910,2913,2914,2917,2918,2921,2922,2923,2926,2927,2930,2931,2934,2935,2938,2939,2940,2941,2944,2945,2948,2949,2950,2953,2954,2957,2958,2961,2962,2965,2966,2968,2969,2972,2973,2976,2977,2980,2981,2983,2984,2986,2987,2992,2993,2994,2996,2997,2999,3000,3002,3003,3006,3007,3010],{},"\\n    \u003Cdiv class=\"wi-topbar\">\\n      \u003Cdiv class=\"wi-topbar-left\">\\n        \u003Cspan class=\"wi-op-label\">OPERATION\\n        \u003Cspan class=\"wi-op-name\">",[2664,2907],{"value":2908},"payload?.operation ?? 'Work Instruction'","\\n        \u003Cspan class=\"wi-op-sub\">\\n          ",[2664,2911],{"value":2912},"payload?.instructionSetId ?? '—'","  ·  v",[2664,2915],{"value":2916},"payload?.version ?? '—'","\\n        \\n      \\n      \u003Cdiv class=\"wi-topbar-right\">\\n        \u003Cdiv class=\"wi-timer\">\\n          \u003Cv-icon icon=\"mdi-timer-outline\" size=\"26\">\\n          \u003Cdiv class=\"wi-timer-text\">\\n            \u003Cspan class=\"wi-timer-label\">TARGET TIME\\n            \u003Cspan class=\"wi-timer-value\">",[2664,2919],{"value":2920},"currentStep?.estimatedTime ?? '—'","s\\n          \\n        \\n      \\n    \\n\\n    ","\\n    \u003Cv-card flat class=\"wi-card wi-stepper-card\">\\n      \u003Cdiv class=\"wi-stepper-head\">\\n        \u003Cspan class=\"wi-step-count\">\\n          STEP ",[2664,2924],{"value":2925},"(currentIndex + 1)"," OF ",[2664,2928],{"value":2929},"steps.length","\\n        \\n        \u003Cspan class=\"wi-step-percent\">",[2664,2932],{"value":2933},"progressPercent","% COMPLETE\\n      \\n      \u003Cv-progress-linear ",[2936,2937],"model-value",{},"=\"progressPercent\" color=\"green-darken-1\" height=\"8\" rounded>\\n    \\n\\n    ","\\n    \u003Cv-card flat class=\"wi-card wi-card-instruction\">\\n      \u003Cdiv class=\"wi-instruction-flex\">\\n        ","\\n        \u003Cdiv class=\"wi-img-col\">\\n          \u003Cdiv class=\"wi-image-wrap\">\\n            \u003Cimg\\n              ",[2942,2943],"src",{},"=\"stepImage\"\\n              ",[2946,2947],"alt",{},"=\"currentStep?.title\"\\n              class=\"wi-image\"\\n              @error=\"onImageError\"\\n            \u002F>\\n          \\n        \\n\\n        ","\\n        \u003Cdiv class=\"wi-text-col\">\\n          \u003Cdiv class=\"wi-card-head\">\\n            \u003Cv-icon icon=\"mdi-clipboard-text-outline\" size=\"20\">\\n            ",[218,2951,2952],{},"INSTRUCTION","\\n          \\n          \u003Cdiv class=\"wi-step-title\">",[2664,2955],{"value":2956},"currentStep?.title ?? '—'","\\n          \u003Cp class=\"wi-step-instruction\">",[2664,2959],{"value":2960},"currentStep?.instruction ?? ''","\\n\\n          \u003Cdiv class=\"wi-card-head wi-checklist-head\">\\n            \u003Cv-icon icon=\"mdi-format-list-checks\" size=\"20\">\\n            ",[218,2963,2964],{},"CHECKLIST","\\n          \\n          \u003Cdiv class=\"wi-checklist\">\\n            \u003Clabel\\n              v-for=\"(item, i) in (currentStep?.checklist ?? ",[218,2967],{},")\"\\n              ",[2970,2971],"key",{},"=\"i\"\\n              class=\"wi-check-row\"\\n              ",[2974,2975],"class",{},"=\"{ 'wi-check-row-done': checkedState",[218,2978,2979],{},"i"," }\"\\n            >\\n              \u003Cv-checkbox-btn\\n                ",[2936,2982],{},"=\"checkedState",[218,2985,2979],{},"\"\\n                color=\"green-darken-1\"\\n                density=\"comfortable\"\\n                @update:model-value=\"toggleCheck(i)\"\\n              >\\n              ",[218,2988,2989],{},[2664,2990],{"value":2991},"item","\\n            \\n          \\n        \\n      \\n    \\n\\n    ","\\n    \u003Cv-row no-gutters class=\"wi-controls\">\\n      \u003Cv-col cols=\"12\" sm=\"3\" class=\"wi-ctrl-col\">\\n        \u003Cv-btn block size=\"x-large\" variant=\"outlined\" color=\"blue-grey-darken-1\" class=\"wi-nav-btn\"\\n          ",[2720,2995],{},"=\"currentIndex === 0\" @click=\"prevStep\">\\n          \u003Cv-icon start icon=\"mdi-chevron-left\" size=\"30\">\\n          PREVIOUS\\n        \\n      \\n\\n      \u003Cv-col cols=\"12\" sm=\"3\" class=\"wi-ctrl-col\">\\n        \u003Cv-btn block size=\"x-large\" variant=\"flat\" color=\"red-darken-1\" class=\"wi-nav-btn\" @click=\"reportDefect\">\\n          \u003Cv-icon start icon=\"mdi-alert-octagon-outline\" size=\"28\">\\n          REPORT ISSUE\\n        \\n      \\n\\n      \u003Cv-col cols=\"12\" sm=\"6\" class=\"wi-ctrl-col\">\\n        \u003Cv-btn block size=\"x-large\" variant=\"flat\" ",[2669,2998],{},"=\"isLastStep ? 'green-darken-2' : 'green-darken-1'\"\\n          class=\"wi-nav-btn wi-next-btn\" ",[2720,3001],{},"=\"!allChecked\" @click=\"nextStep\">\\n          \u003Cv-icon start ",[3004,3005],"icon",{},"=\"isLastStep ? 'mdi-check-circle' : 'mdi-chevron-right'\" size=\"30\">\\n          ",[2664,3008],{"value":3009},"isLastStep ? 'COMPLETE OPERATION' : 'NEXT STEP'","\\n        \\n      \\n    \\n\\n    \u003Cdiv v-if=\"!allChecked\" class=\"wi-hint\">\\n      \u003Cv-icon icon=\"mdi-information-outline\" size=\"18\">\\n      Complete all checklist items to continue.\\n    \\n  \\n",[990,3012,3013],{},"\\n  export default {\\n  data () {\\n    return {\\n      currentIndex: 0,\\n      checked: {},\\n      assetBase: '\u002Fassets\u002F',\\n      placeholder: 'https:\u002F\u002Fplacehold.co\u002F800x600\u002Feef2f7\u002F475569?text=Step+Reference',\\n","      instruction: null\\n    }\\n  },\\n  computed: {\\n    payload () {\\n      return this.instruction ?? {}\\n    },\\n    steps () {\\n      return this.instruction?.steps ?? ",[218,3016],{},"\\n    },\\n    currentStep () {\\n      return this.steps",[218,3019,3020],{},"this.currentIndex"," ?? null\\n    },\\n    isLastStep () {\\n      return this.currentIndex === this.steps.length - 1\\n    },\\n    progressPercent () {\\n      if (!this.steps.length) return 0\\n      return Math.round((this.currentIndex \u002F this.steps.length) * 100)\\n    },\\n    checkedState () {\\n      return this.checked",[218,3023,3020],{}," ?? ",[218,3026],{},"\\n    },\\n    allChecked () {\\n      const list = this.currentStep?.checklist ?? ",[218,3029],{},"\\n      if (!list.length) return true\\n      const state = this.checkedState\\n      return list.every((_, i) => state",[218,3032,2979],{},")\\n    },\\n    stepImage () {\\n      return this.resolveImage(this.currentStep?.image)\\n    }\\n  },\\n  methods: {\\n    resolveImage (img) {\\n      if (!img) return this.placeholder\\n      if (\u002F^https?:\\\u002F\\\u002F\u002F.test(img) || img.startsWith('\u002F')) return img\\n      return this.assetBase + img\\n    },\\n    preloadImages () {\\n      for (const step of this.steps) {\\n        const url = this.resolveImage(step?.image)\\n        if (url && url !== this.placeholder) {\\n          const im = new Image()\\n          im.src = url\\n        }\\n      }\\n    },\\n    saveStep () {\\n      this.send({\\n        payload: {\\n          action: 'save_step',\\n          stepIndex: this.currentIndex,\\n          stepName: this.currentStep?.title ?? null,\\n          stepId: this.currentStep?.stepId ?? null\\n        }\\n      })\\n    },\\n    toggleCheck (i) {\\n      const arr = (this.checked",[218,3035,3020],{},[218,3037],{},").slice()\\n      arr",[218,3040,2979],{}," = !arr",[218,3043,2979],{},"\\n      this.checked = { ...this.checked, ",[218,3046,3020],{},": arr }\\n    },\\n    onImageError (e) {\\n      if (e?.target && e.target.src !== this.placeholder) {\\n        e.target.src = this.placeholder\\n      }\\n    },\\n    goToStep (i) {\\n      const max = this.steps.length - 1\\n      this.currentIndex = Math.min(Math.max(i, 0), Math.max(max, 0))\\n      this.saveStep()\\n    },\\n    nextStep () {\\n      if (this.isLastStep) {\\n        this.send({ payload: { action: 'complete_operation', stepIndex: this.currentIndex } })\\n        return\\n      }\\n      this.goToStep(this.currentIndex + 1)\\n    },\\n    prevStep () {\\n      if (this.currentIndex === 0) return\\n      this.goToStep(this.currentIndex - 1)\\n    },\\n    reportDefect () {\\n      this.send({\\n        payload: {\\n          action: 'Report Issue',\\n        }\\n      })\\n    }\\n  },\\n  watch: {\\n    msg: {\\n      immediate: true,\\n      handler (m) {\\n        const pl = m?.payload\\n        if (!pl) return\\n        if (Array.isArray(pl.steps)) {\\n          \u002F\u002F Instruction-set data: cache it so later messages can't wipe it.\\n          this.instruction = pl\\n          \u002F\u002F Preload every step image so navigation is instant.\\n          this.$nextTick(() => this.preloadImages())\\n        } else if (typeof pl.stepIndex === 'number') {\\n          \u002F\u002F Resume reply from the tracker: jump to the saved step.\\n          this.currentIndex = Math.min(\\n            Math.max(pl.stepIndex, 0),\\n            Math.max(this.steps.length - 1, 0)\\n          )\\n        }\\n      }\\n    }\\n  },\\n  mounted () {\\n    \u002F\u002F Ask the tracker which step this work order was left on.\\n    this.send({ payload: { action: 'load_step' } })\\n  }\\n}\\n\\n\\n",[1109,3049,3050],{},"\\n  .wi-root {\\n    font-family: 'Roboto', system-ui, sans-serif;\\n    color: #1f2933;\\n    padding: 2px;\\n  }\\n\\n  \u002F* Header \u002F\\n  .wi-topbar {\\n    display: flex;\\n    align-items: center;\\n    justify-content: space-between;\\n    flex-wrap: wrap;\\n    gap: 12px;\\n    background: #ffffff;\\n    border: 1px solid #e2e8f0;\\n    border-left: 6px solid #1565c0;\\n    border-radius: 10px;\\n    padding: 10px 16px;\\n    margin-bottom: 10px;\\n  }\\n\\n  .wi-topbar-left {\\n    display: flex;\\n    flex-direction: column;\\n    line-height: 1.15;\\n  }\\n\\n  .wi-op-label {\\n    font-size: 11px;\\n    letter-spacing: 2px;\\n    color: #64748b;\\n    font-weight: 600;\\n  }\\n\\n  .wi-op-name {\\n    font-size: 21px;\\n    font-weight: 700;\\n    color: #0f172a;\\n  }\\n\\n  .wi-op-sub {\\n    font-size: 12px;\\n    color: #94a3b8;\\n    font-weight: 500;\\n    margin-top: 2px;\\n  }\\n\\n  .wi-timer {\\n    display: flex;\\n    align-items: center;\\n    gap: 10px;\\n    padding: 8px 14px;\\n    border-radius: 10px;\\n    background: #f1f5f9;\\n    color: #0f172a;\\n  }\\n\\n  .wi-timer-text {\\n    display: flex;\\n    flex-direction: column;\\n    line-height: 1.1;\\n  }\\n\\n  .wi-timer-label {\\n    font-size: 10px;\\n    letter-spacing: 1.5px;\\n    font-weight: 600;\\n    opacity: 0.75;\\n  }\\n\\n  .wi-timer-value {\\n    font-size: 20px;\\n    font-weight: 700;\\n    font-variant-numeric: tabular-nums;\\n  }\\n\\n  \u002F"," Cards ",[26,3053,3054],{},"\u002F\\n  .wi-card {\\n    background: #ffffff !important;\\n    border: 1px solid #e2e8f0 !important;\\n    border-radius: 10px !important;\\n    padding: 14px 18px !important;\\n    height: 100%;\\n    box-sizing: border-box;\\n  }\\n\\n  .wi-card-head {\\n    display: flex;\\n    align-items: center;\\n    gap: 8px;\\n    font-size: 13px;\\n    letter-spacing: 2px;\\n    font-weight: 700;\\n    color: #64748b;\\n    margin-bottom: 8px;\\n  }\\n\\n  \u002F"," Stepper (compact) ",[26,3057,3058],{},"\u002F\\n  .wi-stepper-card {\\n    margin-bottom: 10px;\\n    padding: 10px 16px;\\n  }\\n\\n  .wi-stepper-head {\\n    display: flex;\\n    justify-content: space-between;\\n    align-items: baseline;\\n    margin-bottom: 8px;\\n  }\\n\\n  .wi-step-count {\\n    font-size: 14px;\\n    font-weight: 700;\\n    letter-spacing: 1px;\\n    color: #0f172a;\\n  }\\n\\n  .wi-step-percent {\\n    font-size: 12px;\\n    font-weight: 600;\\n    letter-spacing: 1px;\\n    color: #64748b;\\n  }\\n\\n  \u002F"," Instruction card ",[26,3061,3062],{},"\u002F\\n  .wi-card-instruction {\\n    border-left: 5px solid #5e35b1 !important;\\n    padding: 16px 20px !important;\\n    box-sizing: border-box;\\n  }\\n\\n  \u002F"," Desktop: keep the instruction card a consistent size across every step,\\n     so navigating between steps does not make the layout jump. The card\\n     still grows taller when the reference image needs more room. ",[26,3065,3066],{},"\u002F\\n  @media (min-width: 701px) {\\n    .wi-card-instruction {\\n      min-height: 360px;\\n    }\\n  }\\n\\n  .wi-step-title {\\n    font-size: 19px;\\n    font-weight: 700;\\n    color: #0f172a;\\n    margin-bottom: 6px;\\n    line-height: 1.15;\\n  }\\n\\n  .wi-step-instruction {\\n    font-size: 15px;\\n    line-height: 1.55;\\n    color: #334155;\\n    margin: 0 0 16px;\\n  }\\n\\n  .wi-checklist-head {\\n    margin-top: 6px;\\n    padding-top: 12px;\\n    border-top: 1px solid #edf2f7;\\n  }\\n\\n  .wi-checklist {\\n    display: flex;\\n    flex-direction: column;\\n    gap: 4px;\\n  }\\n\\n  .wi-check-row {\\n    display: flex;\\n    align-items: center;\\n    justify-content: flex-start;\\n    gap: 10px;\\n    padding: 6px 4px;\\n    border-radius: 8px;\\n    font-size: 15px;\\n    font-weight: 500;\\n    color: #1f2933;\\n    cursor: pointer;\\n  }\\n\\n  .wi-check-row>span {\\n    flex: 1;\\n    line-height: 1.35;\\n  }\\n\\n  .wi-check-row .v-selection-control {\\n    flex: none;\\n    min-width: 0;\\n  }\\n\\n  .wi-check-row-done>span {\\n    color: #94a3b8;\\n  }\\n\\n  \u002F"," Instruction two-column layout (plain flexbox, no Vuetify grid) ",[26,3069,3070],{},"\u002F\\n  .wi-instruction-flex {\\n    display: flex;\\n    gap: 20px;\\n    align-items: stretch;\\n    width: 100%;\\n    height: 100%;\\n  }\\n\\n  .wi-img-col {\\n    flex: 0 0 40%;\\n    display: flex;\\n    min-width: 0;\\n  }\\n\\n  .wi-text-col {\\n    flex: 1 1 60%;\\n    padding-top: 4px;\\n    min-width: 0;\\n  }\\n\\n  \u002F"," Reference image (left) ",[26,3073,3074],{},"\u002F\\n  .wi-image-wrap {\\n    border-radius: 10px;\\n    overflow: hidden;\\n    background: #f1f5f9;\\n    border: 1px solid #e2e8f0;\\n    width: 100%;\\n    height: 100%;\\n    min-height: 210px;\\n    display: flex;\\n    align-items: center;\\n    justify-content: center;\\n  }\\n\\n  \u002F"," Desktop: image box tracks a fixed aspect ratio based on its column\\n     width, so the card height scales with the image. The text column\\n     stretches to match via align-items: stretch on the flex container. ",[26,3077,3078],{},"\u002F\\n  @media (min-width: 701px) {\\n    .wi-image-wrap {\\n      aspect-ratio: 4 \u002F 3;\\n      height: auto;\\n      align-self: flex-start;\\n    }\\n  }\\n\\n  .wi-image {\\n    width: 100%;\\n    height: 100%;\\n    object-fit: contain;\\n  }\\n\\n  @media (max-width: 700px) {\\n    .wi-instruction-flex {\\n      flex-direction: column;\\n    }\\n\\n    .wi-img-col {\\n      flex: none;\\n    }\\n\\n    .wi-image-wrap {\\n      min-height: 150px;\\n    }\\n  }\\n\\n  \u002F"," Controls *\u002F\\n  .wi-controls {\\n    margin-top: 12px;\\n    gap: 0;\\n  }\\n\\n  .wi-ctrl-col {\\n    padding: 6px 8px;\\n  }\\n\\n  .wi-nav-btn {\\n    height: 52px !important;\\n    font-size: 15px !important;\\n    font-weight: 700 !important;\\n    letter-spacing: 0.5px;\\n    border-radius: 12px !important;\\n  }\\n\\n  .wi-nav-btn .v-btn__content {\\n    display: flex;\\n    align-items: center;\\n    justify-content: center;\\n  }\\n\\n  .wi-next-btn {\\n    font-size: 17px !important;\\n  }\\n\\n  .wi-hint {\\n    display: flex;\\n    align-items: center;\\n    justify-content: center;\\n    gap: 6px;\\n    margin-top: 8px;\\n    font-size: 14px;\\n    font-weight: 500;\\n    color: #94a3b8;\\n  }\\n\",\"storeOutMessages\":true,\"passthru\":true,\"resendOnRefresh\":true,\"templateScope\":\"widget:page\",\"className\":\"\",\"x\":670,\"y\":780,\"wires\":[[\"c67ea1f6c0580816\",\"effed3f3a008aef7\"]]},{\"id\":\"e51561215326fafd\",\"type\":\"global-config\",\"env\":",[218,3081],{},[12,3083,3084,75,3088],{},[21,3085],{"alt":3086,"src":3087},"Placeholder: Work Instruction Widget showing a step with image, checklist, and navigation controls","\u002Fblog\u002F2026\u002F07\u002Fimages\u002Finstructions.png",[26,3089,3090],{},"The Instructions page: step image, checklist, target time, and Previous \u002F Report Issue \u002F Next controls.",[147,3092,3093],{"start":633},[70,3094,810,3095,3097],{},[96,3096,1448],{}," node named \"Track step (save\u002Fload per user)\":",[615,3099,3101],{"className":1302,"code":3100,"language":1304,"meta":620,"style":620},"\u002F\u002F Remembers the current step per user for the active work order.\n\u002F\u002F Handles two actions from the widget:\n\u002F\u002F   save_step - persist the step the operator is on\n\u002F\u002F   load_step - reply with the saved step so the widget can resume\nconst p = msg.payload || {};\n\n\u002F\u002F Identity comes from this message's own client data first. This is what\n\u002F\u002F keeps two operators using the widget at the same time from reading or\n\u002F\u002F writing each other's progress.\nconst username = msg._client?.user?.username\n    || global.get('user', 'persistent')?.username;\nif (!username) return null;\n\n\u002F\u002F Read the user's saved state\nconst userState = global.get(username, \"persistent\") || {\n    workOrderId: null,\n    stepIndex: 0\n};\n\nswitch (p.action) {\n    case \"save_step\":\n        userState.stepIndex = p.stepIndex ?? 0;\n        global.set(username, userState, \"persistent\");\n        return null;\n\n    case \"load_step\":\n        msg.payload = {\n            workOrderId: userState.workOrderId,\n            stepIndex: userState.stepIndex ?? 0\n        };\n        return msg;\n\n    default:\n        return null;\n}\n",[96,3102,3103,3108,3113,3118,3123,3142,3146,3151,3156,3161,3183,3217,3234,3238,3243,3275,3285,3295,3300,3304,3319,3333,3358,3385,3391,3395,3407,3420,3436,3454,3460,3469,3474,3482,3489],{"__ignoreMap":620},[218,3104,3105],{"class":625,"line":626},[218,3106,3107],{"class":847},"\u002F\u002F Remembers the current step per user for the active work order.\n",[218,3109,3110],{"class":625,"line":633},[218,3111,3112],{"class":847},"\u002F\u002F Handles two actions from the widget:\n",[218,3114,3115],{"class":625,"line":655},[218,3116,3117],{"class":847},"\u002F\u002F   save_step - persist the step the operator is on\n",[218,3119,3120],{"class":625,"line":542},[218,3121,3122],{"class":847},"\u002F\u002F   load_step - reply with the saved step so the widget can resume\n",[218,3124,3125,3127,3130,3132,3134,3136,3138,3140],{"class":625,"line":686},[218,3126,1311],{"class":639},[218,3128,3129],{"class":939}," p ",[218,3131,861],{"class":629},[218,3133,1319],{"class":939},[218,3135,57],{"class":629},[218,3137,2154],{"class":939},[218,3139,2295],{"class":629},[218,3141,2510],{"class":629},[218,3143,3144],{"class":625,"line":702},[218,3145,982],{"emptyLinePlaceholder":981},[218,3147,3148],{"class":625,"line":717},[218,3149,3150],{"class":847},"\u002F\u002F Identity comes from this message's own client data first. This is what\n",[218,3152,3153],{"class":625,"line":959},[218,3154,3155],{"class":847},"\u002F\u002F keeps two operators using the widget at the same time from reading or\n",[218,3157,3158],{"class":625,"line":969},[218,3159,3160],{"class":847},"\u002F\u002F writing each other's progress.\n",[218,3162,3163,3165,3167,3169,3171,3173,3175,3177,3179,3181],{"class":625,"line":978},[218,3164,1311],{"class":639},[218,3166,1314],{"class":939},[218,3168,861],{"class":629},[218,3170,1319],{"class":939},[218,3172,57],{"class":629},[218,3174,1324],{"class":939},[218,3176,1327],{"class":629},[218,3178,1262],{"class":939},[218,3180,1327],{"class":629},[218,3182,1334],{"class":939},[218,3184,3185,3187,3189,3191,3193,3195,3197,3199,3201,3203,3205,3207,3209,3211,3213,3215],{"class":625,"line":985},[218,3186,1339],{"class":629},[218,3188,1342],{"class":939},[218,3190,57],{"class":629},[218,3192,1348],{"class":1347},[218,3194,1351],{"class":939},[218,3196,1354],{"class":629},[218,3198,1262],{"class":866},[218,3200,1354],{"class":629},[218,3202,1361],{"class":629},[218,3204,1364],{"class":629},[218,3206,1367],{"class":866},[218,3208,1354],{"class":629},[218,3210,1372],{"class":939},[218,3212,1327],{"class":629},[218,3214,660],{"class":939},[218,3216,1076],{"class":629},[218,3218,3219,3221,3224,3227,3230,3232],{"class":625,"line":995},[218,3220,1614],{"class":998},[218,3222,3223],{"class":939}," (",[218,3225,3226],{"class":629},"!",[218,3228,3229],{"class":939},"username) ",[218,3231,1694],{"class":998},[218,3233,2380],{"class":629},[218,3235,3236],{"class":625,"line":1008},[218,3237,982],{"emptyLinePlaceholder":981},[218,3239,3240],{"class":625,"line":1019},[218,3241,3242],{"class":847},"\u002F\u002F Read the user's saved state\n",[218,3244,3245,3247,3250,3252,3254,3256,3258,3261,3263,3265,3267,3269,3271,3273],{"class":625,"line":1040},[218,3246,1311],{"class":639},[218,3248,3249],{"class":939}," userState ",[218,3251,861],{"class":629},[218,3253,1342],{"class":939},[218,3255,57],{"class":629},[218,3257,1348],{"class":1347},[218,3259,3260],{"class":939},"(username",[218,3262,1361],{"class":629},[218,3264,1682],{"class":629},[218,3266,1367],{"class":866},[218,3268,643],{"class":629},[218,3270,2505],{"class":939},[218,3272,2295],{"class":629},[218,3274,1005],{"class":629},[218,3276,3277,3280,3282],{"class":625,"line":1046},[218,3278,3279],{"class":838},"    workOrderId",[218,3281,646],{"class":629},[218,3283,3284],{"class":629}," null,\n",[218,3286,3287,3290,3292],{"class":625,"line":1056},[218,3288,3289],{"class":838},"    stepIndex",[218,3291,646],{"class":629},[218,3293,3294],{"class":1151}," 0\n",[218,3296,3297],{"class":625,"line":1062},[218,3298,3299],{"class":629},"};\n",[218,3301,3302],{"class":625,"line":1079},[218,3303,982],{"emptyLinePlaceholder":981},[218,3305,3306,3309,3312,3314,3317],{"class":625,"line":1085},[218,3307,3308],{"class":998},"switch",[218,3310,3311],{"class":939}," (p",[218,3313,57],{"class":629},[218,3315,3316],{"class":939},"action) ",[218,3318,630],{"class":629},[218,3320,3321,3324,3326,3328,3330],{"class":625,"line":1090},[218,3322,3323],{"class":998},"    case",[218,3325,1682],{"class":629},[218,3327,2859],{"class":866},[218,3329,643],{"class":629},[218,3331,3332],{"class":629},":\n",[218,3334,3335,3338,3340,3343,3345,3348,3350,3352,3354,3356],{"class":625,"line":1099},[218,3336,3337],{"class":939},"        userState",[218,3339,57],{"class":629},[218,3341,3342],{"class":939},"stepIndex ",[218,3344,861],{"class":629},[218,3346,3347],{"class":939}," p",[218,3349,57],{"class":629},[218,3351,3342],{"class":939},[218,3353,2311],{"class":629},[218,3355,2189],{"class":1151},[218,3357,1076],{"class":629},[218,3359,3360,3363,3365,3367,3369,3371,3373,3375,3377,3379,3381,3383],{"class":625,"line":1104},[218,3361,3362],{"class":939},"        global",[218,3364,57],{"class":629},[218,3366,1254],{"class":1347},[218,3368,3260],{"class":939},[218,3370,1361],{"class":629},[218,3372,2482],{"class":939},[218,3374,1361],{"class":629},[218,3376,1682],{"class":629},[218,3378,1367],{"class":866},[218,3380,643],{"class":629},[218,3382,1372],{"class":939},[218,3384,1076],{"class":629},[218,3386,3387,3389],{"class":625,"line":1114},[218,3388,1022],{"class":998},[218,3390,2380],{"class":629},[218,3392,3393],{"class":625,"line":1160},[218,3394,982],{"emptyLinePlaceholder":981},[218,3396,3397,3399,3401,3403,3405],{"class":625,"line":1193},[218,3398,3323],{"class":998},[218,3400,1682],{"class":629},[218,3402,2863],{"class":866},[218,3404,643],{"class":629},[218,3406,3332],{"class":629},[218,3408,3409,3412,3414,3416,3418],{"class":625,"line":2625},[218,3410,3411],{"class":939},"        msg",[218,3413,57],{"class":629},[218,3415,2154],{"class":939},[218,3417,861],{"class":629},[218,3419,1005],{"class":629},[218,3421,3423,3426,3428,3430,3432,3434],{"class":625,"line":3422},28,[218,3424,3425],{"class":838},"            workOrderId",[218,3427,646],{"class":629},[218,3429,2482],{"class":939},[218,3431,57],{"class":629},[218,3433,2520],{"class":939},[218,3435,652],{"class":629},[218,3437,3439,3442,3444,3446,3448,3450,3452],{"class":625,"line":3438},29,[218,3440,3441],{"class":838},"            stepIndex",[218,3443,646],{"class":629},[218,3445,2482],{"class":939},[218,3447,57],{"class":629},[218,3449,3342],{"class":939},[218,3451,2311],{"class":629},[218,3453,3294],{"class":1151},[218,3455,3457],{"class":625,"line":3456},30,[218,3458,3459],{"class":629},"        };\n",[218,3461,3463,3465,3467],{"class":625,"line":3462},31,[218,3464,1022],{"class":998},[218,3466,1319],{"class":939},[218,3468,1076],{"class":629},[218,3470,3472],{"class":625,"line":3471},32,[218,3473,982],{"emptyLinePlaceholder":981},[218,3475,3477,3480],{"class":625,"line":3476},33,[218,3478,3479],{"class":998},"    default",[218,3481,3332],{"class":629},[218,3483,3485,3487],{"class":625,"line":3484},34,[218,3486,1022],{"class":998},[218,3488,2380],{"class":629},[218,3490,3492],{"class":625,"line":3491},35,[218,3493,720],{"class":629},[147,3495,3496],{"start":655},[70,3497,3498,3499,3501],{},"Wire the \"Work Instruction Widget\" output into this node, and wire the node's output back into the widget so the ",[96,3500,2863],{}," reply can jump it to the saved step.",[12,3503,3504,3505,777,3508,3511,3512,3514,3515,3517,3518,3520],{},"Look at the store key: ",[96,3506,3507],{},"global.get(username, ...)",[96,3509,3510],{},"global.set(username, ...)",". The operator's own username is the storage key, so two operators at the same station never overwrite each other, their progress lives under different keys, and because that key is now sourced from ",[96,3513,197],{}," rather than a shared global, it's always the username of whoever's browser actually sent this particular ",[96,3516,2859],{}," or ",[96,3519,2863],{}," message.",[12,3522,3523],{},"Deploy, walk halfway through an operation, then reload the page. It reopens where you left off, because the step is filed under your name.",[59,3525,3527],{"id":3526},"completing-the-operation-as-the-logged-in-user","Completing the Operation as the Logged-In User",[12,3529,3530,3531,3534],{},"When the operator completes the final instruction, the widget sends ",[96,3532,3533],{},"{ action: \"complete_operation\" }",". The flow looks up the operator's stored work order and builds the payload that will be sent to the API.",[147,3536,3537,3545,3560],{},[70,3538,810,3539,3541,3542,3544],{},[45,3540,2081],{}," node and connect it to the ",[45,3543,2872],{}," actions link.",[70,3546,810,3547,1402,3549,3552,3553,3556,3557,57],{},[45,3548,3308],{},[45,3550,3551],{},"Route by Action"," and route messages where ",[96,3554,3555],{},"msg.payload.action"," equals ",[96,3558,3559],{},"complete_operation",[70,3561,810,3562,1402,3564,3567],{},[45,3563,1448],{},[45,3565,3566],{},"Build Complete Payload"," and paste in the following code.",[615,3569,3571],{"className":1302,"code":3570,"language":1304,"meta":620,"style":620},"const username = msg._client?.user?.username\n    || global.get('user', 'persistent')?.username;\n\nif (!username) {\n    node.warn('No logged-in user found — cannot complete operation');\n    return null;\n}\n\nconst stored = global.get(username, 'persistent') || {};\nconst workOrderId = stored.workOrderId || null;\n\nmsg.payload = { workOrderId };\nreturn msg;\n",[96,3572,3573,3595,3629,3633,3645,3668,3675,3679,3683,3714,3735,3739,3755],{"__ignoreMap":620},[218,3574,3575,3577,3579,3581,3583,3585,3587,3589,3591,3593],{"class":625,"line":626},[218,3576,1311],{"class":639},[218,3578,1314],{"class":939},[218,3580,861],{"class":629},[218,3582,1319],{"class":939},[218,3584,57],{"class":629},[218,3586,1324],{"class":939},[218,3588,1327],{"class":629},[218,3590,1262],{"class":939},[218,3592,1327],{"class":629},[218,3594,1334],{"class":939},[218,3596,3597,3599,3601,3603,3605,3607,3609,3611,3613,3615,3617,3619,3621,3623,3625,3627],{"class":625,"line":633},[218,3598,1339],{"class":629},[218,3600,1342],{"class":939},[218,3602,57],{"class":629},[218,3604,1348],{"class":1347},[218,3606,1351],{"class":939},[218,3608,1354],{"class":629},[218,3610,1262],{"class":866},[218,3612,1354],{"class":629},[218,3614,1361],{"class":629},[218,3616,1364],{"class":629},[218,3618,1367],{"class":866},[218,3620,1354],{"class":629},[218,3622,1372],{"class":939},[218,3624,1327],{"class":629},[218,3626,660],{"class":939},[218,3628,1076],{"class":629},[218,3630,3631],{"class":625,"line":655},[218,3632,982],{"emptyLinePlaceholder":981},[218,3634,3635,3637,3639,3641,3643],{"class":625,"line":542},[218,3636,1614],{"class":998},[218,3638,3223],{"class":939},[218,3640,3226],{"class":629},[218,3642,3229],{"class":939},[218,3644,630],{"class":629},[218,3646,3647,3650,3652,3655,3657,3659,3662,3664,3666],{"class":625,"line":686},[218,3648,3649],{"class":939},"    node",[218,3651,57],{"class":629},[218,3653,3654],{"class":1347},"warn",[218,3656,1351],{"class":838},[218,3658,1354],{"class":629},[218,3660,3661],{"class":866},"No logged-in user found — cannot complete operation",[218,3663,1354],{"class":629},[218,3665,1372],{"class":838},[218,3667,1076],{"class":629},[218,3669,3670,3673],{"class":625,"line":702},[218,3671,3672],{"class":998},"    return",[218,3674,2380],{"class":629},[218,3676,3677],{"class":625,"line":717},[218,3678,720],{"class":629},[218,3680,3681],{"class":625,"line":959},[218,3682,982],{"emptyLinePlaceholder":981},[218,3684,3685,3687,3690,3692,3694,3696,3698,3700,3702,3704,3706,3708,3710,3712],{"class":625,"line":969},[218,3686,1311],{"class":639},[218,3688,3689],{"class":939}," stored ",[218,3691,861],{"class":629},[218,3693,1342],{"class":939},[218,3695,57],{"class":629},[218,3697,1348],{"class":1347},[218,3699,3260],{"class":939},[218,3701,1361],{"class":629},[218,3703,1364],{"class":629},[218,3705,1367],{"class":866},[218,3707,1354],{"class":629},[218,3709,2505],{"class":939},[218,3711,2295],{"class":629},[218,3713,2510],{"class":629},[218,3715,3716,3718,3721,3723,3726,3728,3731,3733],{"class":625,"line":978},[218,3717,1311],{"class":639},[218,3719,3720],{"class":939}," workOrderId ",[218,3722,861],{"class":629},[218,3724,3725],{"class":939}," stored",[218,3727,57],{"class":629},[218,3729,3730],{"class":939},"workOrderId ",[218,3732,2295],{"class":629},[218,3734,2380],{"class":629},[218,3736,3737],{"class":625,"line":985},[218,3738,982],{"emptyLinePlaceholder":981},[218,3740,3741,3743,3745,3747,3749,3751,3753],{"class":625,"line":995},[218,3742,1645],{"class":939},[218,3744,57],{"class":629},[218,3746,2154],{"class":939},[218,3748,861],{"class":629},[218,3750,1025],{"class":629},[218,3752,3720],{"class":939},[218,3754,3299],{"class":629},[218,3756,3757,3759,3761],{"class":625,"line":1008},[218,3758,1694],{"class":998},[218,3760,1319],{"class":939},[218,3762,1076],{"class":629},[147,3764,3765,3784],{"start":542},[70,3766,1703,3767,2038,3769,1410,3771,3774,3775,3777,3778,3781,3782,1459],{},[45,3768,572],{},[45,3770,2041],{},[45,3772,3773],{},"POST",", configure ",[45,3776,1716],{}," authentication using your access token, and set the URL to ",[96,3779,3780],{},"https:\u002F\u002Fyour-instance.flowfuse.cloud\u002Fworkorders\u002Fcomplete",". Replace ",[96,3783,1458],{},[70,3785,810,3786,3788,3789,1410,3791,3793,3794,3796],{},[45,3787,1250],{}," node that sets ",[96,3790,1409],{},[96,3792,400],{},", then connect it to a ",[45,3795,2074],{}," node. This will be used in the next step to reset the operator's state and navigate the operator back to the Home page.",[12,3798,3799],{},"Because the payload is built from the logged-in operator's stored state, keyed off that same operator's per-message identity, the API always completes the correct work order, even when multiple operators are using the dashboard at the same time.",[59,3801,3803],{"id":3802},"reporting-a-defect-as-the-logged-in-operator","Reporting a Defect as the Logged-In Operator",[12,3805,3806,3807,3809],{},"If an operator encounters a defect, they can report it from the ",[45,3808,2872],{},". The report is automatically linked to the work order they're currently performing, so there's no need to enter a work order ID manually.",[12,3811,480,3812,3814,3815,3818],{},[45,3813,425],{}," button sends ",[96,3816,3817],{},"{ action: \"Report Issue\" }",". Use that action to open the report form.",[147,3820,3821,3831,3841,3855],{},[70,3822,810,3823,1402,3825,3828,3829,3544],{},[45,3824,2081],{},[45,3826,3827],{},"Widget Action In"," and connect it to the ",[45,3830,2872],{},[70,3832,810,3833,1402,3835,3552,3837,3556,3839,57],{},[45,3834,3308],{},[45,3836,3551],{},[96,3838,3555],{},[96,3840,425],{},[70,3842,810,3843,3788,3845,1410,3847,3793,3849,3851,3852,3854],{},[45,3844,1250],{},[96,3846,1409],{},[96,3848,425],{},[45,3850,115],{}," node to navigate to the ",[45,3853,425],{}," page.",[70,3856,810,3857,1402,3859,3862,3863,3865,3866,3891,3894],{},[45,3858,109],{},[45,3860,3861],{},"Report Issue Form"," to the ",[45,3864,425],{}," group with four required fields:",[67,3867,3868,3874,3879,3885],{},[70,3869,3870,3873],{},[45,3871,3872],{},"Issue Type"," (dropdown)",[70,3875,3876,3873],{},[45,3877,3878],{},"Severity",[70,3880,3881,3884],{},[45,3882,3883],{},"Affected Part"," (text)",[70,3886,3887,3890],{},[45,3888,3889],{},"Description"," (multiline)",[3892,3893],"br",{},"Populate the dropdown fields with the issue types and severity levels used in your application.",[12,3896,3897,3901],{},[21,3898],{"alt":3899,"src":3900},"Screenshot: Report Issue form fields showing the Issue Type, Severity, Affected Part, and Description inputs","\u002Fblog\u002F2026\u002F07\u002Fimages\u002Fform-elements.png",[26,3902,3903],{},"The Report Issue form collects the defect details before submitting them against the current work order.",[12,3905,3906,3910],{},[21,3907],{"alt":3908,"src":3909},"Screenshot: Configuring the dropdown options for the Issue Type and Severity fields in the ui-form node","\u002Fblog\u002F2026\u002F07\u002Fimages\u002Fform-dropdown.png",[26,3911,3912],{},"Configure the Issue Type and Severity dropdowns with the options used in your application.",[147,3914,3915],{"start":686},[70,3916,810,3917,1402,3919,3567],{},[45,3918,1448],{},[45,3920,3921],{},"Prepare Defect Payload",[615,3923,3926],{"className":1302,"code":3924,"language":1304,"meta":3925,"style":620},"const p = msg.payload || {};\n\nconst username = msg._client?.user?.username\n    || global.get('user', 'persistent')?.username;\n\nif (!username) {\n    node.warn('No logged-in user found — cannot report defect');\n    return null;\n}\n\nconst stored = global.get(username, 'persistent') || {};\nconst workOrderId = stored.workOrderId || null;\n\nconst issueType = p.type;\nconst severity = p.severity;\nconst affectedPart = p['affected-part'];\nconst description = p.description || null;\n\nif (!workOrderId || !issueType || !severity || !affectedPart) {\n    node.warn('Missing required defect fields — defect not sent');\n    return null;\n}\n\nmsg.payload = {\n    workOrderId,\n    issueType,\n    severity,\n    affectedPart,\n    description\n};\n\nreturn msg;\n","id=\"vbv4m6\"",[96,3927,3928,3946,3950,3972,4006,4010,4022,4043,4049,4053,4057,4087,4105,4109,4127,4145,4168,4188,4192,4226,4247,4253,4257,4261,4273,4279,4286,4293,4300,4305,4309,4313],{"__ignoreMap":620},[218,3929,3930,3932,3934,3936,3938,3940,3942,3944],{"class":625,"line":626},[218,3931,1311],{"class":639},[218,3933,3129],{"class":939},[218,3935,861],{"class":629},[218,3937,1319],{"class":939},[218,3939,57],{"class":629},[218,3941,2154],{"class":939},[218,3943,2295],{"class":629},[218,3945,2510],{"class":629},[218,3947,3948],{"class":625,"line":633},[218,3949,982],{"emptyLinePlaceholder":981},[218,3951,3952,3954,3956,3958,3960,3962,3964,3966,3968,3970],{"class":625,"line":655},[218,3953,1311],{"class":639},[218,3955,1314],{"class":939},[218,3957,861],{"class":629},[218,3959,1319],{"class":939},[218,3961,57],{"class":629},[218,3963,1324],{"class":939},[218,3965,1327],{"class":629},[218,3967,1262],{"class":939},[218,3969,1327],{"class":629},[218,3971,1334],{"class":939},[218,3973,3974,3976,3978,3980,3982,3984,3986,3988,3990,3992,3994,3996,3998,4000,4002,4004],{"class":625,"line":542},[218,3975,1339],{"class":629},[218,3977,1342],{"class":939},[218,3979,57],{"class":629},[218,3981,1348],{"class":1347},[218,3983,1351],{"class":939},[218,3985,1354],{"class":629},[218,3987,1262],{"class":866},[218,3989,1354],{"class":629},[218,3991,1361],{"class":629},[218,3993,1364],{"class":629},[218,3995,1367],{"class":866},[218,3997,1354],{"class":629},[218,3999,1372],{"class":939},[218,4001,1327],{"class":629},[218,4003,660],{"class":939},[218,4005,1076],{"class":629},[218,4007,4008],{"class":625,"line":686},[218,4009,982],{"emptyLinePlaceholder":981},[218,4011,4012,4014,4016,4018,4020],{"class":625,"line":702},[218,4013,1614],{"class":998},[218,4015,3223],{"class":939},[218,4017,3226],{"class":629},[218,4019,3229],{"class":939},[218,4021,630],{"class":629},[218,4023,4024,4026,4028,4030,4032,4034,4037,4039,4041],{"class":625,"line":717},[218,4025,3649],{"class":939},[218,4027,57],{"class":629},[218,4029,3654],{"class":1347},[218,4031,1351],{"class":838},[218,4033,1354],{"class":629},[218,4035,4036],{"class":866},"No logged-in user found — cannot report defect",[218,4038,1354],{"class":629},[218,4040,1372],{"class":838},[218,4042,1076],{"class":629},[218,4044,4045,4047],{"class":625,"line":959},[218,4046,3672],{"class":998},[218,4048,2380],{"class":629},[218,4050,4051],{"class":625,"line":969},[218,4052,720],{"class":629},[218,4054,4055],{"class":625,"line":978},[218,4056,982],{"emptyLinePlaceholder":981},[218,4058,4059,4061,4063,4065,4067,4069,4071,4073,4075,4077,4079,4081,4083,4085],{"class":625,"line":985},[218,4060,1311],{"class":639},[218,4062,3689],{"class":939},[218,4064,861],{"class":629},[218,4066,1342],{"class":939},[218,4068,57],{"class":629},[218,4070,1348],{"class":1347},[218,4072,3260],{"class":939},[218,4074,1361],{"class":629},[218,4076,1364],{"class":629},[218,4078,1367],{"class":866},[218,4080,1354],{"class":629},[218,4082,2505],{"class":939},[218,4084,2295],{"class":629},[218,4086,2510],{"class":629},[218,4088,4089,4091,4093,4095,4097,4099,4101,4103],{"class":625,"line":995},[218,4090,1311],{"class":639},[218,4092,3720],{"class":939},[218,4094,861],{"class":629},[218,4096,3725],{"class":939},[218,4098,57],{"class":629},[218,4100,3730],{"class":939},[218,4102,2295],{"class":629},[218,4104,2380],{"class":629},[218,4106,4107],{"class":625,"line":1008},[218,4108,982],{"emptyLinePlaceholder":981},[218,4110,4111,4113,4116,4118,4120,4122,4125],{"class":625,"line":1019},[218,4112,1311],{"class":639},[218,4114,4115],{"class":939}," issueType ",[218,4117,861],{"class":629},[218,4119,3347],{"class":939},[218,4121,57],{"class":629},[218,4123,4124],{"class":939},"type",[218,4126,1076],{"class":629},[218,4128,4129,4131,4134,4136,4138,4140,4143],{"class":625,"line":1040},[218,4130,1311],{"class":639},[218,4132,4133],{"class":939}," severity ",[218,4135,861],{"class":629},[218,4137,3347],{"class":939},[218,4139,57],{"class":629},[218,4141,4142],{"class":939},"severity",[218,4144,1076],{"class":629},[218,4146,4147,4149,4152,4154,4157,4159,4162,4164,4166],{"class":625,"line":1046},[218,4148,1311],{"class":639},[218,4150,4151],{"class":939}," affectedPart ",[218,4153,861],{"class":629},[218,4155,4156],{"class":939}," p[",[218,4158,1354],{"class":629},[218,4160,4161],{"class":866},"affected-part",[218,4163,1354],{"class":629},[218,4165,2628],{"class":939},[218,4167,1076],{"class":629},[218,4169,4170,4172,4175,4177,4179,4181,4184,4186],{"class":625,"line":1056},[218,4171,1311],{"class":639},[218,4173,4174],{"class":939}," description ",[218,4176,861],{"class":629},[218,4178,3347],{"class":939},[218,4180,57],{"class":629},[218,4182,4183],{"class":939},"description ",[218,4185,2295],{"class":629},[218,4187,2380],{"class":629},[218,4189,4190],{"class":625,"line":1062},[218,4191,982],{"emptyLinePlaceholder":981},[218,4193,4194,4196,4198,4200,4202,4204,4207,4210,4212,4214,4217,4219,4221,4224],{"class":625,"line":1079},[218,4195,1614],{"class":998},[218,4197,3223],{"class":939},[218,4199,3226],{"class":629},[218,4201,3730],{"class":939},[218,4203,2295],{"class":629},[218,4205,4206],{"class":629}," !",[218,4208,4209],{"class":939},"issueType ",[218,4211,2295],{"class":629},[218,4213,4206],{"class":629},[218,4215,4216],{"class":939},"severity ",[218,4218,2295],{"class":629},[218,4220,4206],{"class":629},[218,4222,4223],{"class":939},"affectedPart) ",[218,4225,630],{"class":629},[218,4227,4228,4230,4232,4234,4236,4238,4241,4243,4245],{"class":625,"line":1085},[218,4229,3649],{"class":939},[218,4231,57],{"class":629},[218,4233,3654],{"class":1347},[218,4235,1351],{"class":838},[218,4237,1354],{"class":629},[218,4239,4240],{"class":866},"Missing required defect fields — defect not sent",[218,4242,1354],{"class":629},[218,4244,1372],{"class":838},[218,4246,1076],{"class":629},[218,4248,4249,4251],{"class":625,"line":1090},[218,4250,3672],{"class":998},[218,4252,2380],{"class":629},[218,4254,4255],{"class":625,"line":1099},[218,4256,720],{"class":629},[218,4258,4259],{"class":625,"line":1104},[218,4260,982],{"emptyLinePlaceholder":981},[218,4262,4263,4265,4267,4269,4271],{"class":625,"line":1114},[218,4264,1645],{"class":939},[218,4266,57],{"class":629},[218,4268,2154],{"class":939},[218,4270,861],{"class":629},[218,4272,1005],{"class":629},[218,4274,4275,4277],{"class":625,"line":1160},[218,4276,3279],{"class":939},[218,4278,652],{"class":629},[218,4280,4281,4284],{"class":625,"line":1193},[218,4282,4283],{"class":939},"    issueType",[218,4285,652],{"class":629},[218,4287,4288,4291],{"class":625,"line":2625},[218,4289,4290],{"class":939},"    severity",[218,4292,652],{"class":629},[218,4294,4295,4298],{"class":625,"line":3422},[218,4296,4297],{"class":939},"    affectedPart",[218,4299,652],{"class":629},[218,4301,4302],{"class":625,"line":3438},[218,4303,4304],{"class":939},"    description\n",[218,4306,4307],{"class":625,"line":3456},[218,4308,3299],{"class":629},[218,4310,4311],{"class":625,"line":3462},[218,4312,982],{"emptyLinePlaceholder":981},[218,4314,4315,4317,4319],{"class":625,"line":3471},[218,4316,1694],{"class":998},[218,4318,1319],{"class":939},[218,4320,1076],{"class":629},[147,4322,4323,4342],{"start":702},[70,4324,1703,4325,2038,4327,1410,4329,3774,4331,3777,4333,3781,4336,4338,4339,4341],{},[45,4326,572],{},[45,4328,2041],{},[45,4330,3773],{},[45,4332,1716],{},[96,4334,4335],{},"https:\u002F\u002Fyour-instance.flowfuse.cloud\u002Fworkorders\u002Fdefect",[96,4337,1458],{}," with your own FlowFuse instance URL, then connect the request to a ",[45,4340,2074],{}," node so the flow continues to the reset step.",[70,4343,810,4344,474,4346,4349,4350,4352,4353,4355],{},[45,4345,112],{},[45,4347,4348],{},"Back to Work Instructions"," to the same group and connect it to a ",[45,4351,115],{}," node that navigates back to the ",[45,4354,383],{}," page. Since the operator's progress is stored against their username, they return to the same instruction they left.",[12,4357,4358,4362],{},[21,4359],{"alt":4360,"src":4361},"Screenshot: Completed Report Issue page showing the defect reporting form and Back to Work Instructions button","\u002Fblog\u002F2026\u002F07\u002Fimages\u002Freport-issue.png",[26,4363,4364],{},"The completed Report Issue page where operators can submit a defect or return to the work instructions.",[12,4366,4367],{},"The work order ID is retrieved from the logged-in operator's stored state, ensuring every defect report is associated with the correct work order, and the lookup key for that state again comes from this message's own client data, not a value that could have been overwritten by someone else's login in the meantime.",[59,4369,4371],{"id":4370},"clearing-state-when-work-ends","Clearing State When Work Ends",[12,4373,4374,4375,4377],{},"When an operation is completed or a defect is reported, clear the operator's saved progress so the next work order starts from ",[45,4376,2852],{}," instead of resuming a finished one.",[147,4379,4380,4396,4408],{},[70,4381,810,4382,1402,4384,3828,4387,4389,4390,777,4392,4395],{},[45,4383,2081],{},[45,4385,4386],{},"Complete \u002F Defect Result In",[45,4388,2074],{}," nodes from the ",[45,4391,2885],{},[45,4393,4394],{},"Report Defect"," flows.",[70,4397,810,4398,4400,4401,3556,4404,4407],{},[45,4399,3308],{}," node that checks ",[96,4402,4403],{},"msg.statusCode",[96,4405,4406],{},"200",", so the state is only cleared after a successful API response.",[70,4409,810,4410,1402,4412,3567],{},[45,4411,1448],{},[45,4413,4414],{},"Clear User Step State",[615,4416,4419],{"className":1302,"code":4417,"language":1304,"meta":4418,"style":620},"const username = msg._client?.user?.username\n    || global.get('user', 'persistent')?.username;\n\nif (!username) return null;\n\nglobal.set(username, {}, 'persistent');\n\nmsg.payload = \"Home\";\nreturn msg;\n","id=\"6ej1v5\"",[96,4420,4421,4443,4477,4481,4495,4499,4525,4529,4547],{"__ignoreMap":620},[218,4422,4423,4425,4427,4429,4431,4433,4435,4437,4439,4441],{"class":625,"line":626},[218,4424,1311],{"class":639},[218,4426,1314],{"class":939},[218,4428,861],{"class":629},[218,4430,1319],{"class":939},[218,4432,57],{"class":629},[218,4434,1324],{"class":939},[218,4436,1327],{"class":629},[218,4438,1262],{"class":939},[218,4440,1327],{"class":629},[218,4442,1334],{"class":939},[218,4444,4445,4447,4449,4451,4453,4455,4457,4459,4461,4463,4465,4467,4469,4471,4473,4475],{"class":625,"line":633},[218,4446,1339],{"class":629},[218,4448,1342],{"class":939},[218,4450,57],{"class":629},[218,4452,1348],{"class":1347},[218,4454,1351],{"class":939},[218,4456,1354],{"class":629},[218,4458,1262],{"class":866},[218,4460,1354],{"class":629},[218,4462,1361],{"class":629},[218,4464,1364],{"class":629},[218,4466,1367],{"class":866},[218,4468,1354],{"class":629},[218,4470,1372],{"class":939},[218,4472,1327],{"class":629},[218,4474,660],{"class":939},[218,4476,1076],{"class":629},[218,4478,4479],{"class":625,"line":655},[218,4480,982],{"emptyLinePlaceholder":981},[218,4482,4483,4485,4487,4489,4491,4493],{"class":625,"line":542},[218,4484,1614],{"class":998},[218,4486,3223],{"class":939},[218,4488,3226],{"class":629},[218,4490,3229],{"class":939},[218,4492,1694],{"class":998},[218,4494,2380],{"class":629},[218,4496,4497],{"class":625,"line":686},[218,4498,982],{"emptyLinePlaceholder":981},[218,4500,4501,4504,4506,4508,4510,4512,4515,4517,4519,4521,4523],{"class":625,"line":702},[218,4502,4503],{"class":939},"global",[218,4505,57],{"class":629},[218,4507,1254],{"class":1347},[218,4509,3260],{"class":939},[218,4511,1361],{"class":629},[218,4513,4514],{"class":629}," {},",[218,4516,1364],{"class":629},[218,4518,1367],{"class":866},[218,4520,1354],{"class":629},[218,4522,1372],{"class":939},[218,4524,1076],{"class":629},[218,4526,4527],{"class":625,"line":717},[218,4528,982],{"emptyLinePlaceholder":981},[218,4530,4531,4533,4535,4537,4539,4541,4543,4545],{"class":625,"line":959},[218,4532,1645],{"class":939},[218,4534,57],{"class":629},[218,4536,2154],{"class":939},[218,4538,861],{"class":629},[218,4540,1682],{"class":629},[218,4542,400],{"class":866},[218,4544,643],{"class":629},[218,4546,1076],{"class":629},[218,4548,4549,4551,4553],{"class":625,"line":969},[218,4550,1694],{"class":998},[218,4552,1319],{"class":939},[218,4554,1076],{"class":629},[147,4556,4557],{"start":542},[70,4558,4559,4560,4562,4563,3854],{},"Connect the function node to a ",[45,4561,115],{}," node to navigate the operator back to the ",[45,4564,400],{},[12,4566,4567],{},"Because the state is stored and cleared by username, and that username is read from the message that carried the successful API response rather than a shared global, resetting one operator's progress never affects anyone else, even if a second operator has logged in on another screen in between.",[12,4569,4570],{},"Deploy the flow and open the dashboard. After signing in, operators see only the work orders assigned to them. If they leave and return, they resume from the same instruction. Once they complete the operation or report a defect, their progress is cleared and they're returned to the Home page, ready for the next work order.",[59,4572,4574],{"id":4573},"where-to-go-next","Where to Go Next",[12,4576,4577,4578,103,4581,763,4584,4587],{},"You've built a personalized digital work instructions application that knows who the operator is, what work they're assigned, and where they left off, correctly, even when several operators are on the dashboard at once. Replacing the simulator with a real ERP or MES is simply a matter of pointing the HTTP Request nodes to your production ",[96,4579,4580],{},"\u002Fworkorders",[96,4582,4583],{},"\u002Fworkorders\u002Fcomplete",[96,4585,4586],{},"\u002Fworkorders\u002Fdefect"," endpoints.",[12,4589,4590,4591,57],{},"From there, you can extend the application with production history, quality tracking, role-based experiences, and OEE dashboards, all powered by the same operator identity. To see how FlowFuse fits into modern automotive manufacturing, explore the ",[53,4592,4594],{"href":4593},"\u002Findustries\u002Fautomotive\u002F","Automotive solutions page",[1109,4596,4597],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sqsOY, html code.shiki .sqsOY{--shiki-light:#8796B0;--shiki-default:#B2CCD6;--shiki-dark:#B2CCD6}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}",{"title":620,"searchDepth":542,"depth":542,"links":4599},[4600,4601,4602,4603,4604,4605,4606,4607,4608,4612,4613,4614,4615,4616,4617,4618,4619],{"id":61,"depth":633,"text":62},{"id":141,"depth":633,"text":142},{"id":201,"depth":633,"text":202},{"id":362,"depth":633,"text":363},{"id":492,"depth":633,"text":493},{"id":579,"depth":633,"text":580},{"id":795,"depth":633,"text":796},{"id":1217,"depth":633,"text":1218},{"id":1381,"depth":633,"text":1382,"children":4609},[4610,4611],{"id":1389,"depth":655,"text":1390},{"id":1429,"depth":655,"text":1430},{"id":1761,"depth":633,"text":1762},{"id":2095,"depth":633,"text":2096},{"id":2814,"depth":633,"text":2815},{"id":2841,"depth":633,"text":2842},{"id":3526,"depth":633,"text":3527},{"id":3802,"depth":633,"text":3803},{"id":4370,"depth":633,"text":4371},{"id":4573,"depth":633,"text":4574},{"type":4621,"title":4622,"description":4623},"contact","Want to connect this to your real ERP or MES?","Talk to our team about integrating digital work instructions with your production systems, adding full traceability, and rolling this out across your shop floor.","2026-07-24","Learn how to build digital work instructions with FlowFuse. Create personalized operator apps with work orders, guided steps, defect reporting, and traceability.","md","\u002Fblog\u002F2026\u002F07\u002Fimages\u002Fdigital-instructions-dashboard-flowfuse.png",null,{"excerpt":4630},{"type":9,"value":4631},[4632],[12,4633,14],{},"\u002Fblog\u002F2026\u002F07\u002Fdigital-work-instruction",{"title":5,"description":4625},{"loc":4634},"blog\u002F2026\u002F07\u002Fdigital-work-instruction","Give every operator their own work instructions, progress, and production history.",[4640],"flowfuse","Build a FlowFuse digital work instructions application that authenticates each operator, loads only their assigned work orders, guides them step by step with per-operator progress tracking, and lets them report defects, all tied to their logged-in identity.","BMhXTnjvq8TbVSWvPmGU_k1818p2jRvjR52eNl0ScHk",[],1785528703260]