Write

switch (msg.action) {
    case "upload-excel":
        msg._mode = "upload";
        return [msg, null, null, null];
    case "refresh-work-orders":
        msg._mode = "select";
        msg.topic = "SELECT * FROM work_orders ORDER BY created_at DESC;";
        return [null, msg, null, null];
    case "start-work-order": {
        // PHASE 3: Check for existing progress before loading
        msg._mode = "check-progress";
        const order = msg.payload || {};
        if (!order.id) {
            node.error("No work order id supplied for start", msg);
            return [null, null, null, null];
        }

        // Store order temporarily in flow context for resume/restart handlers
        flow.set("pendingWorkOrder", order);

        // Query database for existing progress
        msg.topic = "SELECT cycle_count, good_parts, progress_percent FROM work_orders WHERE work_order_id = ?";
        msg.payload = [order.id];

        return [null, null, msg, null];
    }
    case "resume-work-order": {
        // PHASE 3: Resume existing work order from database state
        msg._mode = "resume";
        const order = msg.payload || {};
        const existingCycles = Number(order.cycle_count) || 0;
        const existingGoodParts = Number(order.good_parts) || 0;

        if (!order.id) {
            node.error("No work order id supplied for resume", msg);
            return [null, null, null, null];
        }

        // Set status to RUNNING
        msg.topic = "UPDATE work_orders SET status = CASE WHEN work_order_id = ? THEN 'RUNNING' ELSE 'PENDING' END, updated_at = CASE WHEN work_order_id = ? THEN NOW() ELSE updated_at END WHERE status <> 'DONE'";
        msg.payload = [order.id, order.id];

        // Initialize global state with existing values from database
        global.set("activeWorkOrder", order);
        global.set("cycleCount", existingCycles);
        flow.set("lastMachineState", 0);
        global.set("scrapPromptIssuedFor", null);

        node.warn(`[RESUME] Loaded WO ${order.id} with ${existingCycles} cycles, ${existingGoodParts} good parts`);

        return [null, null, msg, null];
    }
    case "restart-work-order": {
        // PHASE 3: Restart work order from zero (reset database)
        msg._mode = "restart";
        const order = msg.payload || {};

        if (!order.id) {
            node.error("No work order id supplied for restart", msg);
            return [null, null, null, null];
        }

        // Reset database values to 0 and set status to RUNNING
        msg.topic = "UPDATE work_orders SET status = CASE WHEN work_order_id = ? THEN 'RUNNING' ELSE 'PENDING' END, cycle_count = 0, good_parts = 0, progress_percent = 0, updated_at = CASE WHEN work_order_id = ? THEN NOW() ELSE updated_at END WHERE status <> 'DONE'";
        msg.payload = [order.id, order.id];

        // Initialize global state to zero
        global.set("activeWorkOrder", order);
        global.set("cycleCount", 0);
        flow.set("lastMachineState", 0);
        global.set("scrapPromptIssuedFor", null);

        node.warn(`[RESTART] Reset WO ${order.id} to zero`);

        return [null, null, msg, null];
    }
    case "complete-work-order": {
        msg._mode = "complete";
        const order = msg.payload || {};
        if (!order.id) {
            node.error("No work order id supplied for complete", msg);
            return [null, null, null, null];
        }
        msg.completeOrder = order;
        // SQL with bound parameter for safety
        msg.topic = "UPDATE work_orders SET status = 'DONE', updated_at = NOW() WHERE work_order_id = ?";
        msg.payload = [order.id];

        // Clear ALL state on completion
        global.set("activeWorkOrder", null);
        global.set("trackingEnabled", false);
        global.set("productionStarted", false);
        global.set("kpiStartupMode", false);
        global.set("operatingTime", 0);
        global.set("lastCycleTime", null);
        global.set("cycleCount", 0);
        flow.set("lastMachineState", 0);
        global.set("scrapPromptIssuedFor", null);

        node.warn('[COMPLETE] Cleared all state flags');
        return [null, null, null, msg];
    }
    case "get-current-state": {
        // Return current state for UI sync on tab switch
        const activeOrder = global.get("activeWorkOrder") || null;
        const trackingEnabled = global.get("trackingEnabled") || false;
        const productionStarted = global.get("productionStarted") || false;
        const kpis = global.get("currentKPIs") || { oee: 0, availability: 0, performance: 0, quality: 0 };

        msg._mode = "current-state";
        msg.payload = {
            activeWorkOrder: activeOrder,
            trackingEnabled: trackingEnabled,
            productionStarted: productionStarted,
            kpis: kpis
        };

        return [null, msg, null, null];
    }
    case "restore-session": {
        // Query DB for any RUNNING work order on startup
        msg._mode = "restore-query";
        msg.topic = "SELECT * FROM work_orders WHERE status = 'RUNNING' LIMIT 1";
        msg.payload = [];
        node.warn('[RESTORE] Checking for running work order on startup');
        return [null, msg, null, null];
    }
    case "scrap-entry": {
        const { id, scrap } = msg.payload || {};
        const scrapNum = Number(scrap) || 0;

        if (!id) {
            node.error("No work order id supplied for scrap entry", msg);
            return [null, null, null, null];
        }

        const activeOrder = global.get("activeWorkOrder");
        if (activeOrder && activeOrder.id === id) {
            activeOrder.scrap = (Number(activeOrder.scrap) || 0) + scrapNum;
            global.set("activeWorkOrder", activeOrder);
        }

        global.set("scrapPromptIssuedFor", null);

        msg._mode = "scrap-update";
        msg.scrapEntry = { id, scrap: scrapNum };
        // SQL with bound parameters for safety
        msg.topic = "UPDATE work_orders SET scrap_parts = scrap_parts + ?, updated_at = NOW() WHERE work_order_id = ?";
        msg.payload = [scrapNum, id];

        return [null, null, msg, null];
    }
    case "scrap-skip": {
        const { id, remindAgain } = msg.payload || {};

        if (!id) {
            node.error("No work order id supplied for scrap skip", msg);
            return [null, null, null, null];
        }

        if (remindAgain) {
            global.set("scrapPromptIssuedFor", null);
        }

        msg._mode = "scrap-skipped";
        return [null, null, null, null];
    }
    case "start": {
        // START with KPI timestamp init - FIXED
        const now = Date.now();

        global.set("trackingEnabled", true);
        global.set("productionStarted", true);
        global.set("kpiStartupMode", true);
        global.set("kpiBuffer", []);
        global.set("lastKPIRecordTime", now - 60000);
        global.set("productionStartTime", now);
        global.set("lastMachineCycleTime", now);
        global.set("lastCycleTime", now);
        global.set("operatingTime", 0);

        node.warn('[START] Initialized: trackingEnabled=true, productionStarted=true, kpiStartupMode=true, operatingTime=0');

        const activeOrder = global.get("activeWorkOrder") || {};
        msg._mode = "production-state";

        msg.payload = msg.payload || {};

        msg.trackingEnabled = true;
        msg.productionStarted = true;
        msg.machineOnline = true;

        msg.payload.trackingEnabled = true;
        msg.payload.productionStarted = true;
        msg.payload.machineOnline = true;

        return [null, msg, null, null];
    }
    case "stop": {
        global.set("trackingEnabled", false);
        global.set("productionStarted", false);
        node.warn('[STOP] Set trackingEnabled=false, productionStarted=false');

        // Send UI update so button state reflects change
        msg._mode = "production-state";
        msg.payload = msg.payload || {};
        msg.trackingEnabled = false;
        msg.productionStarted = false;
        msg.machineOnline = true;
        msg.payload.trackingEnabled = false;
        msg.payload.productionStarted = false;
        msg.payload.machineOnline = true;

        return [null, msg, null, null];
    }
    case "start-tracking": {
        const activeOrder = global.get('activeOrder') || {};

        if (!activeOrder.id) {
            node.warn('[START] Cannot start tracking: No active order loaded.');
            return [null, { topic: "alert", payload: "Error: No active work order loaded." }, null, null];
        }

        const now = Date.now();
        global.set("trackingEnabled", true);
        global.set("kpiBuffer", []);
        global.set("lastKPIRecordTime", now - 60000);
        global.set("lastMachineCycleTime", now);
        global.set("lastCycleTime", now);
        global.set("operatingTime", 0.001);
        node.warn('[START] Cleared kpiBuffer for fresh production run');

        // FIX: Use work_order_id consistently
        const dbMsg = {
            topic: `UPDATE work_orders SET production_start_time = ${now}, is_tracking = 1 WHERE work_order_id = '${activeOrder.id}'`,
            payload: []
        };

        const stateMsg = {
            topic: "machineStatus",
            payload: msg.payload || {}
        };

        stateMsg.payload.trackingEnabled = true;
        stateMsg.payload.productionStarted = true;
        stateMsg.payload.machineOnline = true;

        return [dbMsg, stateMsg, null, null];
    }
}


File created successfully at: /tmp/work_order_buttons_phase3.js

switch (msg.action) {
    case "upload-excel":
        msg._mode = "upload";
        return [msg, null, null, null];
    case "refresh-work-orders":
        msg._mode = "select";
        msg.topic = "SELECT * FROM work_orders ORDER BY created_at DESC;";
        return [null, msg, null, null];
    case "start-work-order": {
        // PHASE 3: Check for existing progress before loading
        msg._mode = "check-progress";
        const order = msg.payload || {};
        if (!order.id) {
            node.error("No work order id supplied for start", msg);
            return [null, null, null, null];
        }

        // Store order temporarily in flow context for resume/restart handlers
        flow.set("pendingWorkOrder", order);

        // Query database for existing progress
        msg.topic = "SELECT cycle_count, good_parts, progress_percent FROM work_orders WHERE work_order_id = ?";
        msg.payload = [order.id];

        return [null, null, msg, null];
    }
    case "resume-work-order": {
        // PHASE 3: Resume existing work order from database state
        msg._mode = "resume";
        const order = msg.payload || {};
        const existingCycles = Number(order.cycle_count) || 0;
        const existingGoodParts = Number(order.good_parts) || 0;

        if (!order.id) {
            node.error("No work order id supplied for resume", msg);
            return [null, null, null, null];
        }

        // Set status to RUNNING
        msg.topic = "UPDATE work_orders SET status = CASE WHEN work_order_id = ? THEN 'RUNNING' ELSE 'PENDING' END, updated_at = CASE WHEN work_order_id = ? THEN NOW() ELSE updated_at END WHERE status <> 'DONE'";
        msg.payload = [order.id, order.id];

        // Initialize global state with existing values from database
        global.set("activeWorkOrder", order);
        global.set("cycleCount", existingCycles);
        flow.set("lastMachineState", 0);
        global.set("scrapPromptIssuedFor", null);

        node.warn(`[RESUME] Loaded WO ${order.id} with ${existingCycles} cycles, ${existingGoodParts} good parts`);

        return [null, null, msg, null];
    }
    case "restart-work-order": {
        // PHASE 3: Restart work order from zero (reset database)
        msg._mode = "restart";
        const order = msg.payload || {};

        if (!order.id) {
            node.error("No work order id supplied for restart", msg);
            return [null, null, null, null];
        }

        // Reset database values to 0 and set status to RUNNING
        msg.topic = "UPDATE work_orders SET status = CASE WHEN work_order_id = ? THEN 'RUNNING' ELSE 'PENDING' END, cycle_count = 0, good_parts = 0, progress_percent = 0, updated_at = CASE WHEN work_order_id = ? THEN NOW() ELSE updated_at END WHERE status <> 'DONE'";
        msg.payload = [order.id, order.id];

        // Initialize global state to zero
        global.set("activeWorkOrder", order);
        global.set("cycleCount", 0);
        flow.set("lastMachineState", 0);
        global.set("scrapPromptIssuedFor", null);

        node.warn(`[RESTART] Reset WO ${order.id} to zero`);

        return [null, null, msg, null];
    }
    case "complete-work-order": {
        msg._mode = "complete";
        const order = msg.payload || {};
        if (!order.id) {
            node.error("No work order id supplied for complete", msg);
            return [null, null, null, null];
        }
        msg.completeOrder = order;
        // SQL with bound parameter for safety
        msg.topic = "UPDATE work_orders SET status = 'DONE', updated_at = NOW() WHERE work_order_id = ?";
        msg.payload = [order.id];

        // Clear ALL state on completion
        global.set("activeWorkOrder", null);
        global.set("trackingEnabled", false);
        global.set("productionStarted", false);
        global.set("kpiStartupMode", false);
        global.set("operatingTime", 0);
        global.set("lastCycleTime", null);
        global.set("cycleCount", 0);
        flow.set("lastMachineState", 0);
        global.set("scrapPromptIssuedFor", null);

        node.warn('[COMPLETE] Cleared all state flags');
        return [null, null, null, msg];
    }
    case "get-current-state": {
        // Return current state for UI sync on tab switch
        const activeOrder = global.get("activeWorkOrder") || null;
        const trackingEnabled = global.get("trackingEnabled") || false;
        const productionStarted = global.get("productionStarted") || false;
        const kpis = global.get("currentKPIs") || { oee: 0, availability: 0, performance: 0, quality: 0 };

        msg._mode = "current-state";
        msg.payload = {
            activeWorkOrder: activeOrder,
            trackingEnabled: trackingEnabled,
            productionStarted: productionStarted,
            kpis: kpis
        };

        return [null, msg, null, null];
    }
    case "restore-session": {
        // Query DB for any RUNNING work order on startup
        msg._mode = "restore-query";
        msg.topic = "SELECT * FROM work_orders WHERE status = 'RUNNING' LIMIT 1";
        msg.payload = [];
        node.warn('[RESTORE] Checking for running work order on startup');
        return [null, msg, null, null];
    }
    case "scrap-entry": {
        const { id, scrap } = msg.payload || {};
        const scrapNum = Number(scrap) || 0;

        if (!id) {
            node.error("No work order id supplied for scrap entry", msg);
            return [null, null, null, null];
        }

        const activeOrder = global.get("activeWorkOrder");
        if (activeOrder && activeOrder.id === id) {
            activeOrder.scrap = (Number(activeOrder.scrap) || 0) + scrapNum;
            global.set("activeWorkOrder", activeOrder);
        }

        global.set("scrapPromptIssuedFor", null);

        msg._mode = "scrap-update";
        msg.scrapEntry = { id, scrap: scrapNum };
        // SQL with bound parameters for safety
        msg.topic = "UPDATE work_orders SET scrap_parts = scrap_parts + ?, updated_at = NOW() WHERE work_order_id = ?";
        msg.payload = [scrapNum, id];

        return [null, null, msg, null];
    }
    case "scrap-skip": {
        const { id, remindAgain } = msg.payload || {};

        if (!id) {
            node.error("No work order id supplied for scrap skip", msg);
            return [null, null, null, null];
        }

        if (remindAgain) {
            global.set("scrapPromptIssuedFor", null);
        }

        msg._mode = "scrap-skipped";
        return [null, null, null, null];
    }
    case "start": {
        // START with KPI timestamp init - FIXED
        const now = Date.now();

        global.set("trackingEnabled", true);
        global.set("productionStarted", true);
        global.set("kpiStartupMode", true);
        global.set("kpiBuffer", []);
        global.set("lastKPIRecordTime", now - 60000);
        global.set("productionStartTime", now);
        global.set("lastMachineCycleTime", now);
        global.set("lastCycleTime", now);
        global.set("operatingTime", 0);

        node.warn('[START] Initialized: trackingEnabled=true, productionStarted=true, kpiStartupMode=true, operatingTime=0');

        const activeOrder = global.get("activeWorkOrder") || {};
        msg._mode = "production-state";

        msg.payload = msg.payload || {};

        msg.trackingEnabled = true;
        msg.productionStarted = true;
        msg.machineOnline = true;

        msg.payload.trackingEnabled = true;
        msg.payload.productionStarted = true;
        msg.payload.machineOnline = true;

        return [null, msg, null, null];
    }
    case "stop": {
        global.set("trackingEnabled", false);
        global.set("productionStarted", false);
        node.warn('[STOP] Set trackingEnabled=false, productionStarted=false');

        // Send UI update so button state reflects change
        msg._mode = "production-state";
        msg.payload = msg.payload || {};
        msg.trackingEnabled = false;
        msg.productionStarted = false;
        msg.machineOnline = true;
        msg.payload.trackingEnabled = false;
        msg.payload.productionStarted = false;
        msg.payload.machineOnline = true;

        return [null, msg, null, null];
    }
    case "start-tracking": {
        const activeOrder = global.get('activeOrder') || {};

        if (!activeOrder.id) {
            node.warn('[START] Cannot start tracking: No active order loaded.');
            return [null, { topic: "alert", payload: "Error: No active work order loaded." }, null, null];
        }

        const now = Date.now();
        global.set("trackingEnabled", true);
        global.set("kpiBuffer", []);
        global.set("lastKPIRecordTime", now - 60000);
        global.set("lastMachineCycleTime", now);
        global.set("lastCycleTime", now);
        global.set("operatingTime", 0.001);
        node.warn('[START] Cleared kpiBuffer for fresh production run');

        // FIX: Use work_order_id consistently
        const dbMsg = {
            topic: `UPDATE work_orders SET production_start_time = ${now}, is_tracking = 1 WHERE work_order_id = '${activeOrder.id}'`,
            payload: []
        };

        const stateMsg = {
            topic: "machineStatus",
            payload: msg.payload || {}
        };

        stateMsg.payload.trackingEnabled = true;
        stateMsg.payload.productionStarted = true;
        stateMsg.payload.machineOnline = true;

        return [dbMsg, stateMsg, null, null];
    }
}


Now I need to find a function node that processes the database results from the progress check query. Let me search for nodes that handle database responses:


Bash

cat /home/mdares/projects/Plastico/flows.json | jq '.[] | select(.type == "function" and (.name | contains("Process") or contains("Handler") or contains("DB"))) | {id, name}' 2>/dev/null | head -20

Find processing/handler functions


{
  "id": "a1b2c3d4e5f6a7b8",
  "name": "Mold Presets Handler"
}
{
  "id": "b2c3d4e5f6a7b8c9",
  "name": "Process DB Results"
}
{
  "id": "alert_process_function",
  "name": "Process Alert for DB"
}


Let me check "Process DB Results" function:


cat /home/mdares/projects/Plastico/flows.json | jq '.[] | select(.name == "Process DB Results") | .func' -r | head -50

Read Process DB Results function


// Replace function in "Process DB Results" node

const originalTopic = msg._originalTopic || '';
const dbResults = Array.isArray(msg.payload) ? msg.payload : [];

if (!originalTopic) {
    return null;
}

// IMPORTANT: Clear socketid to prevent loops back to sender
delete msg._socketid;
delete msg.socketid;

// Manufacturers query → list for first dropdown
if (originalTopic === 'getManufacturers') {
    const manufacturers = dbResults
        .map(row => row.manufacturer)
        .filter((mfg, index, arr) => mfg && arr.indexOf(mfg) === index)
        .sort();

    msg.topic = 'manufacturersList';
    msg.payload = manufacturers;

    node.status({ fill: 'green', shape: 'dot', text: `${manufacturers.length} manufacturers` });
    return msg;
}

// Preset lists (legacy load/search)
if (originalTopic === 'loadMoldPresets' || originalTopic === 'searchMoldPresets') {
    const presets = dbResults.map(row => ({
        mold_name: row.mold_name || '',
        manufacturer: row.manufacturer || '',
        theoretical_cavities: Number(row.theoretical_cavities) || 0,
        functional_cavities: Number(row.functional_cavities) || 0
    }));

    msg.topic = 'moldPresetsList';
    msg.payload = presets;

    node.status({ fill: 'green', shape: 'dot', text: `${presets.length} presets found` });
    return msg;
}

// Molds for selected manufacturer
if (originalTopic === 'getMoldsByManufacturer') {
    const presets = dbResults.map(row => ({
        mold_name: row.mold_name || '',
        manufacturer: row.manufacturer || '',
        theoretical_cavities: Number(row.theoretical_cavities) || 0,
        functional_cavities: Number(row.functional_cavities) || 0


This function processes different database queries. I need to add a handler for the "check-progress" mode. Let me read the full function:


cat /home/mdares/projects/Plastico/flows.json | jq '.[] | select(.name == "Process DB Results") | .func' -r > /tmp/process_db_results.js && wc -l /tmp/process_db_results.js

Extract full Process DB Results function


73 /tmp/process_db_results.js


     1→// Replace function in "Process DB Results" node
     2→
     3→const originalTopic = msg._originalTopic || '';
     4→const dbResults = Array.isArray(msg.payload) ? msg.payload : [];
     5→
     6→if (!originalTopic) {
     7→    return null;
     8→}
     9→
    10→// IMPORTANT: Clear socketid to prevent loops back to sender
    11→delete msg._socketid;
    12→delete msg.socketid;
    13→
    14→// Manufacturers query → list for first dropdown
    15→if (originalTopic === 'getManufacturers') {
    16→    const manufacturers = dbResults
    17→        .map(row => row.manufacturer)
    18→        .filter((mfg, index, arr) => mfg && arr.indexOf(mfg) === index)
    19→        .sort();
    20→
    21→    msg.topic = 'manufacturersList';
    22→    msg.payload = manufacturers;
    23→
    24→    node.status({ fill: 'green', shape: 'dot', text: `${manufacturers.length} manufacturers` });
    25→    return msg;
    26→}
    27→
    28→// Preset lists (legacy load/search)
    29→if (originalTopic === 'loadMoldPresets' || originalTopic === 'searchMoldPresets') {
    30→    const presets = dbResults.map(row => ({
    31→        mold_name: row.mold_name || '',
    32→        manufacturer: row.manufacturer || '',
    33→        theoretical_cavities: Number(row.theoretical_cavities) || 0,
    34→        functional_cavities: Number(row.functional_cavities) || 0
    35→    }));
    36→
    37→    msg.topic = 'moldPresetsList';
    38→    msg.payload = presets;
    39→
    40→    node.status({ fill: 'green', shape: 'dot', text: `${presets.length} presets found` });
    41→    return msg;
    42→}
    43→
    44→// Molds for selected manufacturer
    45→if (originalTopic === 'getMoldsByManufacturer') {
    46→    const presets = dbResults.map(row => ({
    47→        mold_name: row.mold_name || '',
    48→        manufacturer: row.manufacturer || '',
    49→        theoretical_cavities: Number(row.theoretical_cavities) || 0,
    50→        functional_cavities: Number(row.functional_cavities) || 0
    51→    }));
    52→
    53→    msg.topic = 'moldPresetsList';
    54→    msg.payload = presets;
    55→
    56→    node.status({ fill: 'blue', shape: 'dot', text: `${presets.length} molds for manufacturer` });
    57→    return msg;
    58→}
    59→
    60→// Result of inserting a new mold
    61→if (originalTopic === 'addMoldPreset') {
    62→    msg.topic = 'addMoldResult';
    63→    msg.payload = {
    64→        success: true,
    65→        result: msg.payload
    66→    };
    67→
    68→    node.status({ fill: 'green', shape: 'dot', text: 'Mold added' });
    69→    return msg;
    70→}
    71→
    72→node.status({ fill: 'yellow', shape: 'ring', text: 'Unknown topic: ' + originalTopic });
    73→return null;
    74→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

// Replace function in "Process DB Results" node

const originalTopic = msg._originalTopic || '';
const dbResults = Array.isArray(msg.payload) ? msg.payload : [];

if (!originalTopic) {
    return null;
}

// IMPORTANT: Clear socketid to prevent loops back to sender
delete msg._socketid;
delete msg.socketid;

// Manufacturers query → list for first dropdown
if (originalTopic === 'getManufacturers') {
    const manufacturers = dbResults
        .map(row => row.manufacturer)
        .filter((mfg, index, arr) => mfg && arr.indexOf(mfg) === index)
        .sort();

    msg.topic = 'manufacturersList';
    msg.payload = manufacturers;

    node.status({ fill: 'green', shape: 'dot', text: `${manufacturers.length} manufacturers` });
    return msg;
}

// Preset lists (legacy load/search)
if (originalTopic === 'loadMoldPresets' || originalTopic === 'searchMoldPresets') {
    const presets = dbResults.map(row => ({
        mold_name: row.mold_name || '',
        manufacturer: row.manufacturer || '',
        theoretical_cavities: Number(row.theoretical_cavities) || 0,
        functional_cavities: Number(row.functional_cavities) || 0
    }));

    msg.topic = 'moldPresetsList';
    msg.payload = presets;

    node.status({ fill: 'green', shape: 'dot', text: `${presets.length} presets found` });
    return msg;
}

// Molds for selected manufacturer
if (originalTopic === 'getMoldsByManufacturer') {
    const presets = dbResults.map(row => ({
        mold_name: row.mold_name || '',
        manufacturer: row.manufacturer || '',
        theoretical_cavities: Number(row.theoretical_cavities) || 0,
        functional_cavities: Number(row.functional_cavities) || 0
    }));

    msg.topic = 'moldPresetsList';
    msg.payload = presets;

    node.status({ fill: 'blue', shape: 'dot', text: `${presets.length} molds for manufacturer` });
    return msg;
}

// Result of inserting a new mold
if (originalTopic === 'addMoldPreset') {
    msg.topic = 'addMoldResult';
    msg.payload = {
        success: true,
        result: msg.payload
    };

    node.status({ fill: 'green', shape: 'dot', text: 'Mold added' });
    return msg;
}

node.status({ fill: 'yellow', shape: 'ring', text: 'Unknown topic: ' + originalTopic });
return null;


This function handles different database query results based on `_originalTopic`. I need to check how the message flow works. Let me look for nodes that set `_originalTopic`:


2

Found 0 total occurrences across 0 files.


Let me look at the wiring from Work Order buttons to understand the flow better:


cat /home/mdares/projects/Plastico/flows.json | jq '.[] | select(.name == "Work Order buttons") | {outputs, wires}' 2>/dev/null

Check Work Order buttons outputs


The 3rd output goes to mariaDB (f6ad294bc02618c9). Now I need to create a new function node to process the check-progress query results and send the resume prompt to UI. Let me trace through the architecture and create a comprehensive Phase 3 implementation plan:

**Phase 3 requires:**
1. ✅ Modified "start-work-order" action to query DB first
2. ✅ New "resume-work-order" and "restart-work-order" actions
3. ⏳ New function node to process progress check results
4. ⏳ UI template changes to show resume/restart prompt

Let me check if I should use an existing approach by looking at how other DB results are handled. Let me see what node f8f4ca6de445c0c2 is (seems to be a special handler for output 3):


cat /home/mdares/projects/Plastico/flows.json | jq '.[] | select(.id == "f8f4ca6de445c0c2") | {id, name, type}' 2>/dev/null

Check what handles output 3 from WO buttons


It's just a debug node. The primary handling is through mariaDB. I need to understand how results come back from the DB. Let me find what's wired FROM the mariaDB node:


cat /home/mdares/projects/Plastico/flows.json | jq '.[] | select(.id == "f6ad294bc02618c9") | .wires' 2>/dev/null

See where mariaDB outputs go


cat /home/mdares/projects/Plastico/flows.json | jq '.[] | select(.id == "578c92e75bf0f266") | {id, name, type}' 2>/dev/null

Check node after mariaDB


Let me read the Refresh Trigger function to understand how it routes messages:


cat /home/mdares/projects/Plastico/flows.json | jq '.[] | select(.name == "Refresh Trigger") | .func' -r

Read Refresh Trigger function


if (msg._mode === "start" || msg._mode === "complete") {
    // Preserve original message for Back to UI (output 2)
    const originalMsg = {...msg};
    // Create select message for refreshing WO table (output 1)
    msg._mode = "select";
    msg.topic = "SELECT * FROM work_orders ORDER BY updated_at DESC;";
    return [msg, originalMsg];
}
if (msg._mode === "cycle" || msg._mode === "production-state") {
    return [null, msg];
}
if (msg._mode === "scrap-prompt") {
    return [null, msg];
}
if (msg._mode === "restore-query") {
    // Pass restore query results to Back to UI
    return [null, msg];
}
if (msg._mode === "current-state") {
    // Pass current state to Back to UI
    return [null, msg];
}
if (msg._mode === "scrap-complete") {
    // Preserve original message for Back to UI (output 2)
    const originalMsg = {...msg};
    // Create select message for refreshing WO table (output 1)
    msg._mode = "select";
    msg.topic = "SELECT * FROM work_orders ORDER BY updated_at DESC;";
    return [msg, originalMsg];
}
return [null, msg];


Perfect! This function routes different message modes. I need to add handling for "check-progress", "resume", and "restart" modes. Let me check what's on output 2:


cat /home/mdares/projects/Plastico/flows.json | jq '.[] | select(.name == "Refresh Trigger") | .wires' 2>/dev/null

Check Refresh Trigger outputs


cat /home/mdares/projects/Plastico/flows.json | jq '.[] | select(.id == "f2bab26e27e2023d") | {id, name, type}' 2>/dev/null

Check node on output 2


Excellent! So the flow is:
1. Work Order buttons → mariaDB
2. mariaDB → Refresh Trigger
3. Refresh Trigger → (output 1) mariaDB again OR (output 2) Back to UI

Let me read the "Back to UI" function:


cat /home/mdares/projects/Plastico/flows.json | jq '.[] | select(.name == "Back to UI") | .func' -r > /tmp/back_to_ui.js && head -80 /tmp/back_to_ui.js

Read Back to UI function


const mode = msg._mode || '';
const started = msg.startOrder || null;
const completed = msg.completeOrder || null;

delete msg._mode;
delete msg.startOrder;
delete msg.completeOrder;
delete msg.action;
delete msg.filename;

// ========================================================
// MODE: UPLOAD
// ========================================================
if (mode === "upload") {
    msg.topic = "uploadStatus";
    msg.payload = { message: "✅ Work orders uploaded successfully." };
    return [msg, null, null, null];
}

// ========================================================
// MODE: SELECT (Load Work Orders)
// ========================================================
if (mode === "select") {
    const rawRows = Array.isArray(msg.payload) ? msg.payload : [];
    msg.topic = "workOrdersList";
    msg.payload = rawRows.map(row => ({
        id: row.work_order_id ?? row.id ?? "",
        sku: row.sku ?? "",
        target: Number(row.target_qty ?? row.target ?? 0),
        good: Number(row.good_parts ?? row.good ?? 0),
        scrap: Number(row.scrap_count ?? row.scrap ?? 0),
        progressPercent: Number(row.progress_percent ?? row.progress ?? 0),
        status: (row.status ?? "PENDING").toUpperCase(),
        lastUpdateIso: row.updated_at ?? row.last_update ?? null,
        cycleTime: Number(row.cycle_time ?? row.theoretical_cycle_time ?? 0)
    }));
    return [msg, null, null, null];
}

// ========================================================
// MODE: START WORK ORDER
// ========================================================
if (mode === "start") {
    const order = started || {};
    const kpis = msg.kpis || global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: order.id || "",
            sku: order.sku || "",
            target: Number(order.target) || 0,
            good: Number(order.good) || 0,
            scrap: Number(order.scrap) || 0,
            cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0),
            progressPercent: Number(order.progressPercent) || 0,
            lastUpdateIso: order.lastUpdateIso || null,
            kpis: kpis
        }
    };

    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: COMPLETE WORK ORDER
// ========================================================
if (mode === "complete") {
    const homeMsg = { topic: "activeWorkOrder", payload: null };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: CYCLE UPDATE DURING PRODUCTION
// ========================================================
if (mode === "cycle") {
    const cycle = msg.cycle || {};


Now I understand the architecture! I need to:
1. ✅ Modify "Work Order buttons" to handle start-work-order / resume / restart
2. Modify "Refresh Trigger" to handle check-progress mode
3. Modify "Back to UI" to send resume prompt when needed

Let me create the modified versions. First, let me update the todo with more specific subtasks:


TodoWrite

Review Phase 2 progress and understand DB Guard setup

Reviewing Phase 2 progress and DB Guard setup

Add 5-second throttling to work_orders persistence

Adding 5-second throttling to persistence

Verify Phase 2 wiring and test persistence

Update Work Order buttons with resume/restart actions

Modify Refresh Trigger to handle check-progress mode

Modify Back to UI to send resume prompt

Phase 4: Fix Complete button to persist final counts

Phase 5: Update session restore to set RUNNING status

Phase 6: Load work order data from database (not session)

Phase 7: Add tab switch state refresh (optional)

Phase 8: Testing and validation


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Review Phase 2 progress and understand DB Guard setup

Reviewing Phase 2 progress and DB Guard setup

Add 5-second throttling to work_orders persistence

Adding 5-second throttling to persistence

Verify Phase 2 wiring and test persistence

Phase 3: Implement Resume/Restart prompt on Load

Phase 4: Fix Complete button to persist final counts

Phase 5: Update session restore to set RUNNING status

Phase 6: Load work order data from database (not session)

Phase 7: Add tab switch state refresh (optional)

Phase 8: Testing and validation

Update Work Order buttons with resume/restart actions

Modify Refresh Trigger to handle check-progress mode

Modify Back to UI to send resume prompt

Phase 8: Testing and validation


Now let me update the flows.json. First, backup and then update Work Order buttons:


Bash

cd /home/mdares/projects/Plastico && cp flows.json flows.json.backup_phase3_$(date +%Y%m%d_%H%M%S)

Backup flows.json for Phase 3


Shell cwd was reset to /home/mdares/Desktop


cd /home/mdares/projects/Plastico && FUNC_CODE=$(cat /tmp/work_order_buttons_phase3.js) && jq --arg func "$FUNC_CODE" '(.[] | select(.name == "Work Order buttons") | .func) = $func' flows.json > /tmp/flows_phase3_step1.json && mv /tmp/flows_phase3_step1.json flows.json

Update Work Order buttons function


Now let me create the updated Refresh Trigger function:


Write

if (msg._mode === "start" || msg._mode === "complete") {
    // Preserve original message for Back to UI (output 2)
    const originalMsg = {...msg};
    // Create select message for refreshing WO table (output 1)
    msg._mode = "select";
    msg.topic = "SELECT * FROM work_orders ORDER BY updated_at DESC;";
    return [msg, originalMsg];
}
if (msg._mode === "resume" || msg._mode === "restart") {
    // PHASE 3: After resume/restart, refresh WO list and notify UI
    const originalMsg = {...msg};
    // Create select message for refreshing WO table (output 1)
    msg._mode = "select";
    msg.topic = "SELECT * FROM work_orders ORDER BY updated_at DESC;";
    return [msg, originalMsg];
}
if (msg._mode === "check-progress") {
    // PHASE 3: Pass progress check results to Back to UI for processing
    return [null, msg];
}
if (msg._mode === "cycle" || msg._mode === "production-state") {
    return [null, msg];
}
if (msg._mode === "scrap-prompt") {
    return [null, msg];
}
if (msg._mode === "restore-query") {
    // Pass restore query results to Back to UI
    return [null, msg];
}
if (msg._mode === "current-state") {
    // Pass current state to Back to UI
    return [null, msg];
}
if (msg._mode === "scrap-complete") {
    // Preserve original message for Back to UI (output 2)
    const originalMsg = {...msg};
    // Create select message for refreshing WO table (output 1)
    msg._mode = "select";
    msg.topic = "SELECT * FROM work_orders ORDER BY updated_at DESC;";
    return [msg, originalMsg];
}
return [null, msg];


File created successfully at: /tmp/refresh_trigger_phase3.js

if (msg._mode === "start" || msg._mode === "complete") {
    // Preserve original message for Back to UI (output 2)
    const originalMsg = {...msg};
    // Create select message for refreshing WO table (output 1)
    msg._mode = "select";
    msg.topic = "SELECT * FROM work_orders ORDER BY updated_at DESC;";
    return [msg, originalMsg];
}
if (msg._mode === "resume" || msg._mode === "restart") {
    // PHASE 3: After resume/restart, refresh WO list and notify UI
    const originalMsg = {...msg};
    // Create select message for refreshing WO table (output 1)
    msg._mode = "select";
    msg.topic = "SELECT * FROM work_orders ORDER BY updated_at DESC;";
    return [msg, originalMsg];
}
if (msg._mode === "check-progress") {
    // PHASE 3: Pass progress check results to Back to UI for processing
    return [null, msg];
}
if (msg._mode === "cycle" || msg._mode === "production-state") {
    return [null, msg];
}
if (msg._mode === "scrap-prompt") {
    return [null, msg];
}
if (msg._mode === "restore-query") {
    // Pass restore query results to Back to UI
    return [null, msg];
}
if (msg._mode === "current-state") {
    // Pass current state to Back to UI
    return [null, msg];
}
if (msg._mode === "scrap-complete") {
    // Preserve original message for Back to UI (output 2)
    const originalMsg = {...msg};
    // Create select message for refreshing WO table (output 1)
    msg._mode = "select";
    msg.topic = "SELECT * FROM work_orders ORDER BY updated_at DESC;";
    return [msg, originalMsg];
}
return [null, msg];


Now let me read the full "Back to UI" function to add the check-progress handler:


Bash

cat /home/mdares/projects/Plastico/flows.json | jq '.[] | select(.name == "Back to UI") | .func' -r > /tmp/back_to_ui.js && wc -l /tmp/back_to_ui.js

Extract full Back to UI function


267 /tmp/back_to_ui.js


Let me read just the first 100 lines to understand the structure, then I'll add the check-progress handler at the beginning:


     1→const mode = msg._mode || '';
     2→const started = msg.startOrder || null;
     3→const completed = msg.completeOrder || null;
     4→
     5→delete msg._mode;
     6→delete msg.startOrder;
     7→delete msg.completeOrder;
     8→delete msg.action;
     9→delete msg.filename;
    10→
    11→// ========================================================
    12→// MODE: UPLOAD
    13→// ========================================================
    14→if (mode === "upload") {
    15→    msg.topic = "uploadStatus";
    16→    msg.payload = { message: "✅ Work orders uploaded successfully." };
    17→    return [msg, null, null, null];
    18→}
    19→
    20→// ========================================================
    21→// MODE: SELECT (Load Work Orders)
    22→// ========================================================
    23→if (mode === "select") {
    24→    const rawRows = Array.isArray(msg.payload) ? msg.payload : [];
    25→    msg.topic = "workOrdersList";
    26→    msg.payload = rawRows.map(row => ({
    27→        id: row.work_order_id ?? row.id ?? "",
    28→        sku: row.sku ?? "",
    29→        target: Number(row.target_qty ?? row.target ?? 0),
    30→        good: Number(row.good_parts ?? row.good ?? 0),
    31→        scrap: Number(row.scrap_count ?? row.scrap ?? 0),
    32→        progressPercent: Number(row.progress_percent ?? row.progress ?? 0),
    33→        status: (row.status ?? "PENDING").toUpperCase(),
    34→        lastUpdateIso: row.updated_at ?? row.last_update ?? null,
    35→        cycleTime: Number(row.cycle_time ?? row.theoretical_cycle_time ?? 0)
    36→    }));
    37→    return [msg, null, null, null];
    38→}
    39→
    40→// ========================================================
    41→// MODE: START WORK ORDER
    42→// ========================================================
    43→if (mode === "start") {
    44→    const order = started || {};
    45→    const kpis = msg.kpis || global.get("currentKPIs") || {
    46→        oee: 0, availability: 0, performance: 0, quality: 0
    47→    };
    48→
    49→    const homeMsg = {
    50→        topic: "activeWorkOrder",
    51→        payload: {
    52→            id: order.id || "",
    53→            sku: order.sku || "",
    54→            target: Number(order.target) || 0,
    55→            good: Number(order.good) || 0,
    56→            scrap: Number(order.scrap) || 0,
    57→            cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0),
    58→            progressPercent: Number(order.progressPercent) || 0,
    59→            lastUpdateIso: order.lastUpdateIso || null,
    60→            kpis: kpis
    61→        }
    62→    };
    63→
    64→    return [null, homeMsg, null, null];
    65→}
    66→
    67→// ========================================================
    68→// MODE: COMPLETE WORK ORDER
    69→// ========================================================
    70→if (mode === "complete") {
    71→    const homeMsg = { topic: "activeWorkOrder", payload: null };
    72→    return [null, homeMsg, null, null];
    73→}
    74→
    75→// ========================================================
    76→// MODE: CYCLE UPDATE DURING PRODUCTION
    77→// ========================================================
    78→if (mode === "cycle") {
    79→    const cycle = msg.cycle || {};
    80→
    81→    const workOrderMsg = {
    82→        topic: "workOrderCycle",
    83→        payload: {
    84→            id: cycle.id || "",
    85→            sku: cycle.sku || "",
    86→            target: Number(cycle.target) || 0,
    87→            good: Number(cycle.good) || 0,
    88→            scrap: Number(cycle.scrap) || 0,
    89→            progressPercent: Number(cycle.progressPercent) || 0,
    90→            lastUpdateIso: cycle.lastUpdateIso || new Date().toISOString(),
    91→            status: cycle.progressPercent >= 100 ? "DONE" : "RUNNING"
    92→        }
    93→    };
    94→
    95→    const kpis = msg.kpis || global.get("currentKPIs") || {
    96→        oee: 0, availability: 0, performance: 0, quality: 0
    97→    };
    98→
    99→    const homeMsg = {
   100→        topic: "activeWorkOrder",

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

const mode = msg._mode || '';
const started = msg.startOrder || null;
const completed = msg.completeOrder || null;

delete msg._mode;
delete msg.startOrder;
delete msg.completeOrder;
delete msg.action;
delete msg.filename;

// ========================================================
// MODE: UPLOAD
// ========================================================
if (mode === "upload") {
    msg.topic = "uploadStatus";
    msg.payload = { message: "✅ Work orders uploaded successfully." };
    return [msg, null, null, null];
}

// ========================================================
// MODE: SELECT (Load Work Orders)
// ========================================================
if (mode === "select") {
    const rawRows = Array.isArray(msg.payload) ? msg.payload : [];
    msg.topic = "workOrdersList";
    msg.payload = rawRows.map(row => ({
        id: row.work_order_id ?? row.id ?? "",
        sku: row.sku ?? "",
        target: Number(row.target_qty ?? row.target ?? 0),
        good: Number(row.good_parts ?? row.good ?? 0),
        scrap: Number(row.scrap_count ?? row.scrap ?? 0),
        progressPercent: Number(row.progress_percent ?? row.progress ?? 0),
        status: (row.status ?? "PENDING").toUpperCase(),
        lastUpdateIso: row.updated_at ?? row.last_update ?? null,
        cycleTime: Number(row.cycle_time ?? row.theoretical_cycle_time ?? 0)
    }));
    return [msg, null, null, null];
}

// ========================================================
// MODE: START WORK ORDER
// ========================================================
if (mode === "start") {
    const order = started || {};
    const kpis = msg.kpis || global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: order.id || "",
            sku: order.sku || "",
            target: Number(order.target) || 0,
            good: Number(order.good) || 0,
            scrap: Number(order.scrap) || 0,
            cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0),
            progressPercent: Number(order.progressPercent) || 0,
            lastUpdateIso: order.lastUpdateIso || null,
            kpis: kpis
        }
    };

    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: COMPLETE WORK ORDER
// ========================================================
if (mode === "complete") {
    const homeMsg = { topic: "activeWorkOrder", payload: null };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: CYCLE UPDATE DURING PRODUCTION
// ========================================================
if (mode === "cycle") {
    const cycle = msg.cycle || {};

    const workOrderMsg = {
        topic: "workOrderCycle",
        payload: {
            id: cycle.id || "",
            sku: cycle.sku || "",
            target: Number(cycle.target) || 0,
            good: Number(cycle.good) || 0,
            scrap: Number(cycle.scrap) || 0,
            progressPercent: Number(cycle.progressPercent) || 0,
            lastUpdateIso: cycle.lastUpdateIso || new Date().toISOString(),
            status: cycle.progressPercent >= 100 ? "DONE" : "RUNNING"
        }
    };

    const kpis = msg.kpis || global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",


I'll create a modified version by inserting the check-progress handler. Let me read the full file and modify it:


     1→const mode = msg._mode || '';
     2→const started = msg.startOrder || null;
     3→const completed = msg.completeOrder || null;
     4→
     5→delete msg._mode;
     6→delete msg.startOrder;
     7→delete msg.completeOrder;
     8→delete msg.action;
     9→delete msg.filename;
    10→
    11→// ========================================================
    12→// MODE: UPLOAD
    13→// ========================================================
    14→if (mode === "upload") {
    15→    msg.topic = "uploadStatus";
    16→    msg.payload = { message: "✅ Work orders uploaded successfully." };
    17→    return [msg, null, null, null];
    18→}
    19→
    20→// ========================================================
    21→// MODE: SELECT (Load Work Orders)
    22→// ========================================================
    23→if (mode === "select") {
    24→    const rawRows = Array.isArray(msg.payload) ? msg.payload : [];
    25→    msg.topic = "workOrdersList";
    26→    msg.payload = rawRows.map(row => ({
    27→        id: row.work_order_id ?? row.id ?? "",
    28→        sku: row.sku ?? "",
    29→        target: Number(row.target_qty ?? row.target ?? 0),
    30→        good: Number(row.good_parts ?? row.good ?? 0),
    31→        scrap: Number(row.scrap_count ?? row.scrap ?? 0),
    32→        progressPercent: Number(row.progress_percent ?? row.progress ?? 0),
    33→        status: (row.status ?? "PENDING").toUpperCase(),
    34→        lastUpdateIso: row.updated_at ?? row.last_update ?? null,
    35→        cycleTime: Number(row.cycle_time ?? row.theoretical_cycle_time ?? 0)
    36→    }));
    37→    return [msg, null, null, null];
    38→}
    39→
    40→// ========================================================
    41→// MODE: START WORK ORDER
    42→// ========================================================
    43→if (mode === "start") {
    44→    const order = started || {};
    45→    const kpis = msg.kpis || global.get("currentKPIs") || {
    46→        oee: 0, availability: 0, performance: 0, quality: 0
    47→    };
    48→
    49→    const homeMsg = {
    50→        topic: "activeWorkOrder",
    51→        payload: {
    52→            id: order.id || "",
    53→            sku: order.sku || "",
    54→            target: Number(order.target) || 0,
    55→            good: Number(order.good) || 0,
    56→            scrap: Number(order.scrap) || 0,
    57→            cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0),
    58→            progressPercent: Number(order.progressPercent) || 0,
    59→            lastUpdateIso: order.lastUpdateIso || null,
    60→            kpis: kpis
    61→        }
    62→    };
    63→
    64→    return [null, homeMsg, null, null];
    65→}
    66→
    67→// ========================================================
    68→// MODE: COMPLETE WORK ORDER
    69→// ========================================================
    70→if (mode === "complete") {
    71→    const homeMsg = { topic: "activeWorkOrder", payload: null };
    72→    return [null, homeMsg, null, null];
    73→}
    74→
    75→// ========================================================
    76→// MODE: CYCLE UPDATE DURING PRODUCTION
    77→// ========================================================
    78→if (mode === "cycle") {
    79→    const cycle = msg.cycle || {};
    80→
    81→    const workOrderMsg = {
    82→        topic: "workOrderCycle",
    83→        payload: {
    84→            id: cycle.id || "",
    85→            sku: cycle.sku || "",
    86→            target: Number(cycle.target) || 0,
    87→            good: Number(cycle.good) || 0,
    88→            scrap: Number(cycle.scrap) || 0,
    89→            progressPercent: Number(cycle.progressPercent) || 0,
    90→            lastUpdateIso: cycle.lastUpdateIso || new Date().toISOString(),
    91→            status: cycle.progressPercent >= 100 ? "DONE" : "RUNNING"
    92→        }
    93→    };
    94→
    95→    const kpis = msg.kpis || global.get("currentKPIs") || {
    96→        oee: 0, availability: 0, performance: 0, quality: 0
    97→    };
    98→
    99→    const homeMsg = {
   100→        topic: "activeWorkOrder",
   101→        payload: {
   102→            id: cycle.id || "",
   103→            sku: cycle.sku || "",
   104→            target: Number(cycle.target) || 0,
   105→            good: Number(cycle.good) || 0,
   106→            scrap: Number(cycle.scrap) || 0,
   107→            cycleTime: Number(cycle.cycleTime) || 0,
   108→            progressPercent: Number(cycle.progressPercent) || 0,
   109→            lastUpdateIso: cycle.lastUpdateIso || new Date().toISOString(),
   110→            kpis: kpis
   111→        }
   112→    };
   113→
   114→    return [workOrderMsg, homeMsg, null, null];
   115→}
   116→
   117→// ========================================================
   118→// MODE: MACHINE PRODUCTION STATE
   119→// ========================================================
   120→if (mode === "production-state") {
   121→    const homeMsg = {
   122→        topic: "machineStatus",
   123→        payload: {
   124→            machineOnline: msg.machineOnline ?? true,
   125→            productionStarted: !!msg.productionStarted,
   126→            trackingEnabled: msg.payload?.trackingEnabled ?? msg.trackingEnabled ?? false
   127→        }
   128→    };
   129→    return [null, homeMsg, null, null];
   130→}
   131→
   132→// ========================================================
   133→// MODE: CURRENT STATE (for tab switch sync)
   134→// ========================================================
   135→if (mode === "current-state") {
   136→    const state = msg.payload || {};
   137→    const homeMsg = {
   138→        topic: "currentState",
   139→        payload: {
   140→            activeWorkOrder: state.activeWorkOrder,
   141→            trackingEnabled: state.trackingEnabled,
   142→            productionStarted: state.productionStarted,
   143→            kpis: state.kpis
   144→        }
   145→    };
   146→    return [null, homeMsg, null, null];
   147→}
   148→
   149→// ========================================================
   150→// MODE: RESTORE QUERY (startup state recovery)
   151→// ========================================================
   152→if (mode === "restore-query") {
   153→    const rows = Array.isArray(msg.payload) ? msg.payload : [];
   154→
   155→    if (rows.length > 0) {
   156→        const row = rows[0];
   157→        const restoredOrder = {
   158→            id: row.work_order_id || row.id || "",
   159→            sku: row.sku || "",
   160→            target: Number(row.target_qty || row.target || 0),
   161→            good: Number(row.good_parts || row.good || 0),
   162→            scrap: Number(row.scrap_parts || row.scrap || 0),
   163→            progressPercent: Number(row.progress_percent || 0),
   164→            cycleTime: Number(row.cycle_time || 0),
   165→            lastUpdateIso: row.updated_at || null
   166→        };
   167→
   168→        // Restore global state
   169→        global.set("activeWorkOrder", restoredOrder);
   170→        global.set("cycleCount", Number(row.cycle_count) || 0);
   171→        // Don't auto-start tracking - user must click START
   172→        global.set("trackingEnabled", false);
   173→        global.set("productionStarted", false);
   174→
   175→        node.warn('[RESTORE] Restored work order: ' + restoredOrder.id + ' with ' + global.get("cycleCount") + ' cycles');
   176→
   177→        const homeMsg = {
   178→            topic: "activeWorkOrder",
   179→            payload: restoredOrder
   180→        };
   181→        return [null, homeMsg, null, null];
   182→    } else {
   183→        node.warn('[RESTORE] No running work order found');
   184→    }
   185→    return [null, null, null, null];
   186→}
   187→
   188→// ========================================================
   189→// MODE: SCRAP PROMPT
   190→// ========================================================
   191→if (mode === "scrap-prompt") {
   192→    const prompt = msg.scrapPrompt || {};
   193→
   194→    const homeMsg = { topic: "scrapPrompt", payload: prompt };
   195→    const tabMsg = { ui_control: { tab: "Home" } };
   196→
   197→    // output1: nothing
   198→    // output2: home template
   199→    // output3: tab navigation
   200→    // output4: graphs template (unused here)
   201→    return [null, homeMsg, tabMsg, null];
   202→}
   203→
   204→// ========================================================
   205→// MODE: SCRAP UPDATE
   206→// ========================================================
   207→if (mode === "scrap-update") {
   208→    const activeOrder = global.get("activeWorkOrder") || {};
   209→    const kpis = msg.kpis || global.get("currentKPIs") || {
   210→        oee: 0, availability: 0, performance: 0, quality: 0
   211→    };
   212→
   213→    const homeMsg = {
   214→        topic: "activeWorkOrder",
   215→        payload: {
   216→            id: activeOrder.id || "",
   217→            sku: activeOrder.sku || "",
   218→            target: Number(activeOrder.target) || 0,
   219→            good: Number(activeOrder.good) || 0,
   220→            scrap: Number(activeOrder.scrap) || 0,
   221→            cycleTime: Number(activeOrder.cycleTime) || 0,
   222→            progressPercent: Number(activeOrder.progressPercent) || 0,
   223→            lastUpdateIso: activeOrder.lastUpdateIso || new Date().toISOString(),
   224→            kpis: kpis
   225→        }
   226→    };
   227→
   228→    return [null, homeMsg, null, null];
   229→}
   230→
   231→// ========================================================
   232→// MODE: SCRAP COMPLETE
   233→// ========================================================
   234→if (mode === "scrap-complete") {
   235→    const homeMsg = { topic: "activeWorkOrder", payload: null };
   236→    return [null, homeMsg, null, null];
   237→}
   238→
   239→// ========================================================
   240→// MODE: CHARTS → SEND REAL DATA TO GRAPH TEMPLATE
   241→// ========================================================
   242→//if (mode === "charts") {
   243→
   244→//    const realOEE = msg.realOEE || global.get("realOEE") || [];
   245→//    const realAvailability = msg.realAvailability || global.get("realAvailability") || [];
   246→//    const realPerformance = msg.realPerformance || global.get("realPerformance") || [];
   247→//    const realQuality = msg.realQuality || global.get("realQuality") || [];
   248→
   249→//    const chartsMsg = {
   250→//        topic: "chartsData",
   251→//        payload: {
   252→//            oee: realOEE,
   253→//            availability: realAvailability,
   254→//            performance: realPerformance,
   255→//            quality: realQuality
   256→//        }
   257→//    };
   258→
   259→    // Send ONLY to output #4
   260→//    return [null, null, null, chartsMsg];
   261→//}
   262→
   263→// ========================================================
   264→// DEFAULT
   265→// ========================================================
   266→return [null, null, null, null];
   267→
   268→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

const mode = msg._mode || '';
const started = msg.startOrder || null;
const completed = msg.completeOrder || null;

delete msg._mode;
delete msg.startOrder;
delete msg.completeOrder;
delete msg.action;
delete msg.filename;

// ========================================================
// MODE: UPLOAD
// ========================================================
if (mode === "upload") {
    msg.topic = "uploadStatus";
    msg.payload = { message: "✅ Work orders uploaded successfully." };
    return [msg, null, null, null];
}

// ========================================================
// MODE: SELECT (Load Work Orders)
// ========================================================
if (mode === "select") {
    const rawRows = Array.isArray(msg.payload) ? msg.payload : [];
    msg.topic = "workOrdersList";
    msg.payload = rawRows.map(row => ({
        id: row.work_order_id ?? row.id ?? "",
        sku: row.sku ?? "",
        target: Number(row.target_qty ?? row.target ?? 0),
        good: Number(row.good_parts ?? row.good ?? 0),
        scrap: Number(row.scrap_count ?? row.scrap ?? 0),
        progressPercent: Number(row.progress_percent ?? row.progress ?? 0),
        status: (row.status ?? "PENDING").toUpperCase(),
        lastUpdateIso: row.updated_at ?? row.last_update ?? null,
        cycleTime: Number(row.cycle_time ?? row.theoretical_cycle_time ?? 0)
    }));
    return [msg, null, null, null];
}

// ========================================================
// MODE: START WORK ORDER
// ========================================================
if (mode === "start") {
    const order = started || {};
    const kpis = msg.kpis || global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: order.id || "",
            sku: order.sku || "",
            target: Number(order.target) || 0,
            good: Number(order.good) || 0,
            scrap: Number(order.scrap) || 0,
            cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0),
            progressPercent: Number(order.progressPercent) || 0,
            lastUpdateIso: order.lastUpdateIso || null,
            kpis: kpis
        }
    };

    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: COMPLETE WORK ORDER
// ========================================================
if (mode === "complete") {
    const homeMsg = { topic: "activeWorkOrder", payload: null };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: CYCLE UPDATE DURING PRODUCTION
// ========================================================
if (mode === "cycle") {
    const cycle = msg.cycle || {};

    const workOrderMsg = {
        topic: "workOrderCycle",
        payload: {
            id: cycle.id || "",
            sku: cycle.sku || "",
            target: Number(cycle.target) || 0,
            good: Number(cycle.good) || 0,
            scrap: Number(cycle.scrap) || 0,
            progressPercent: Number(cycle.progressPercent) || 0,
            lastUpdateIso: cycle.lastUpdateIso || new Date().toISOString(),
            status: cycle.progressPercent >= 100 ? "DONE" : "RUNNING"
        }
    };

    const kpis = msg.kpis || global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: cycle.id || "",
            sku: cycle.sku || "",
            target: Number(cycle.target) || 0,
            good: Number(cycle.good) || 0,
            scrap: Number(cycle.scrap) || 0,
            cycleTime: Number(cycle.cycleTime) || 0,
            progressPercent: Number(cycle.progressPercent) || 0,
            lastUpdateIso: cycle.lastUpdateIso || new Date().toISOString(),
            kpis: kpis
        }
    };

    return [workOrderMsg, homeMsg, null, null];
}

// ========================================================
// MODE: MACHINE PRODUCTION STATE
// ========================================================
if (mode === "production-state") {
    const homeMsg = {
        topic: "machineStatus",
        payload: {
            machineOnline: msg.machineOnline ?? true,
            productionStarted: !!msg.productionStarted,
            trackingEnabled: msg.payload?.trackingEnabled ?? msg.trackingEnabled ?? false
        }
    };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: CURRENT STATE (for tab switch sync)
// ========================================================
if (mode === "current-state") {
    const state = msg.payload || {};
    const homeMsg = {
        topic: "currentState",
        payload: {
            activeWorkOrder: state.activeWorkOrder,
            trackingEnabled: state.trackingEnabled,
            productionStarted: state.productionStarted,
            kpis: state.kpis
        }
    };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: RESTORE QUERY (startup state recovery)
// ========================================================
if (mode === "restore-query") {
    const rows = Array.isArray(msg.payload) ? msg.payload : [];

    if (rows.length > 0) {
        const row = rows[0];
        const restoredOrder = {
            id: row.work_order_id || row.id || "",
            sku: row.sku || "",
            target: Number(row.target_qty || row.target || 0),
            good: Number(row.good_parts || row.good || 0),
            scrap: Number(row.scrap_parts || row.scrap || 0),
            progressPercent: Number(row.progress_percent || 0),
            cycleTime: Number(row.cycle_time || 0),
            lastUpdateIso: row.updated_at || null
        };

        // Restore global state
        global.set("activeWorkOrder", restoredOrder);
        global.set("cycleCount", Number(row.cycle_count) || 0);
        // Don't auto-start tracking - user must click START
        global.set("trackingEnabled", false);
        global.set("productionStarted", false);

        node.warn('[RESTORE] Restored work order: ' + restoredOrder.id + ' with ' + global.get("cycleCount") + ' cycles');

        const homeMsg = {
            topic: "activeWorkOrder",
            payload: restoredOrder
        };
        return [null, homeMsg, null, null];
    } else {
        node.warn('[RESTORE] No running work order found');
    }
    return [null, null, null, null];
}

// ========================================================
// MODE: SCRAP PROMPT
// ========================================================
if (mode === "scrap-prompt") {
    const prompt = msg.scrapPrompt || {};

    const homeMsg = { topic: "scrapPrompt", payload: prompt };
    const tabMsg = { ui_control: { tab: "Home" } };

    // output1: nothing
    // output2: home template
    // output3: tab navigation
    // output4: graphs template (unused here)
    return [null, homeMsg, tabMsg, null];
}

// ========================================================
// MODE: SCRAP UPDATE
// ========================================================
if (mode === "scrap-update") {
    const activeOrder = global.get("activeWorkOrder") || {};
    const kpis = msg.kpis || global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: activeOrder.id || "",
            sku: activeOrder.sku || "",
            target: Number(activeOrder.target) || 0,
            good: Number(activeOrder.good) || 0,
            scrap: Number(activeOrder.scrap) || 0,
            cycleTime: Number(activeOrder.cycleTime) || 0,
            progressPercent: Number(activeOrder.progressPercent) || 0,
            lastUpdateIso: activeOrder.lastUpdateIso || new Date().toISOString(),
            kpis: kpis
        }
    };

    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: SCRAP COMPLETE
// ========================================================
if (mode === "scrap-complete") {
    const homeMsg = { topic: "activeWorkOrder", payload: null };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: CHARTS → SEND REAL DATA TO GRAPH TEMPLATE
// ========================================================
//if (mode === "charts") {

//    const realOEE = msg.realOEE || global.get("realOEE") || [];
//    const realAvailability = msg.realAvailability || global.get("realAvailability") || [];
//    const realPerformance = msg.realPerformance || global.get("realPerformance") || [];
//    const realQuality = msg.realQuality || global.get("realQuality") || [];

//    const chartsMsg = {
//        topic: "chartsData",
//        payload: {
//            oee: realOEE,
//            availability: realAvailability,
//            performance: realPerformance,
//            quality: realQuality
//        }
//    };

    // Send ONLY to output #4
//    return [null, null, null, chartsMsg];
//}

// ========================================================
// DEFAULT
// ========================================================
return [null, null, null, null];


Now I'll create the modified version with the check-progress, resume, and restart handlers added:


Write

const mode = msg._mode || '';
const started = msg.startOrder || null;
const completed = msg.completeOrder || null;

delete msg._mode;
delete msg.startOrder;
delete msg.completeOrder;
delete msg.action;
delete msg.filename;

// ========================================================
// MODE: CHECK PROGRESS (PHASE 3)
// ========================================================
if (mode === "check-progress") {
    const rows = Array.isArray(msg.payload) ? msg.payload : [];

    if (rows.length > 0) {
        const row = rows[0];
        const cycleCount = Number(row.cycle_count) || 0;
        const goodParts = Number(row.good_parts) || 0;

        // Retrieve pending work order from flow context
        const pendingOrder = flow.get("pendingWorkOrder");

        if (!pendingOrder) {
            node.error("No pending work order found in check-progress");
            return [null, null, null, null];
        }

        // If there's existing progress, send resume prompt to UI
        if (cycleCount > 0 || goodParts > 0) {
            msg.topic = "resumePrompt";
            msg.payload = {
                id: pendingOrder.id,
                sku: pendingOrder.sku || "",
                target: Number(pendingOrder.target) || 0,
                cycleCount: cycleCount,
                goodParts: goodParts,
                progressPercent: Number(row.progress_percent) || 0
            };

            node.warn(`[RESUME PROMPT] WO ${pendingOrder.id} has ${goodParts}/${pendingOrder.target} parts (${cycleCount} cycles)`);
            return [null, msg, null, null];
        }
    }

    // No progress found - proceed with normal start (automatically call resume with 0 values)
    const pendingOrder = flow.get("pendingWorkOrder");

    if (pendingOrder) {
        // Auto-start with zero values
        msg.topic = "UPDATE work_orders SET status = CASE WHEN work_order_id = ? THEN 'RUNNING' ELSE 'PENDING' END, updated_at = CASE WHEN work_order_id = ? THEN NOW() ELSE updated_at END WHERE status <> 'DONE'";
        msg.payload = [pendingOrder.id, pendingOrder.id];

        global.set("activeWorkOrder", pendingOrder);
        global.set("cycleCount", 0);
        flow.set("lastMachineState", 0);
        global.set("scrapPromptIssuedFor", null);

        node.warn(`[AUTO-START] WO ${pendingOrder.id} has no existing progress, starting fresh`);

        // Clear pending order
        flow.set("pendingWorkOrder", null);
    }

    return [null, null, null, null];
}

// ========================================================
// MODE: RESUME WORK ORDER (PHASE 3)
// ========================================================
if (mode === "resume") {
    const order = global.get("activeWorkOrder") || {};
    const cycleCount = Number(global.get("cycleCount")) || 0;
    const kpis = global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: order.id || "",
            sku: order.sku || "",
            target: Number(order.target) || 0,
            good: Number(order.good) || 0,
            scrap: Number(order.scrap) || 0,
            cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0),
            progressPercent: Number(order.progressPercent) || 0,
            lastUpdateIso: order.lastUpdateIso || null,
            kpis: kpis
        }
    };

    // Clear pending order
    flow.set("pendingWorkOrder", null);

    node.warn(`[RESUME] Loaded WO ${order.id} with ${cycleCount} cycles in UI`);

    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: RESTART WORK ORDER (PHASE 3)
// ========================================================
if (mode === "restart") {
    const order = global.get("activeWorkOrder") || {};
    const kpis = global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: order.id || "",
            sku: order.sku || "",
            target: Number(order.target) || 0,
            good: 0,
            scrap: 0,
            cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0),
            progressPercent: 0,
            lastUpdateIso: null,
            kpis: kpis
        }
    };

    // Clear pending order
    flow.set("pendingWorkOrder", null);

    node.warn(`[RESTART] Reset WO ${order.id} to zero in UI`);

    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: UPLOAD
// ========================================================
if (mode === "upload") {
    msg.topic = "uploadStatus";
    msg.payload = { message: "✅ Work orders uploaded successfully." };
    return [msg, null, null, null];
}

// ========================================================
// MODE: SELECT (Load Work Orders)
// ========================================================
if (mode === "select") {
    const rawRows = Array.isArray(msg.payload) ? msg.payload : [];
    msg.topic = "workOrdersList";
    msg.payload = rawRows.map(row => ({
        id: row.work_order_id ?? row.id ?? "",
        sku: row.sku ?? "",
        target: Number(row.target_qty ?? row.target ?? 0),
        good: Number(row.good_parts ?? row.good ?? 0),
        scrap: Number(row.scrap_count ?? row.scrap ?? 0),
        progressPercent: Number(row.progress_percent ?? row.progress ?? 0),
        status: (row.status ?? "PENDING").toUpperCase(),
        lastUpdateIso: row.updated_at ?? row.last_update ?? null,
        cycleTime: Number(row.cycle_time ?? row.theoretical_cycle_time ?? 0)
    }));
    return [msg, null, null, null];
}

// ========================================================
// MODE: START WORK ORDER
// ========================================================
if (mode === "start") {
    const order = started || {};
    const kpis = msg.kpis || global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: order.id || "",
            sku: order.sku || "",
            target: Number(order.target) || 0,
            good: Number(order.good) || 0,
            scrap: Number(order.scrap) || 0,
            cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0),
            progressPercent: Number(order.progressPercent) || 0,
            lastUpdateIso: order.lastUpdateIso || null,
            kpis: kpis
        }
    };

    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: COMPLETE WORK ORDER
// ========================================================
if (mode === "complete") {
    const homeMsg = { topic: "activeWorkOrder", payload: null };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: CYCLE UPDATE DURING PRODUCTION
// ========================================================
if (mode === "cycle") {
    const cycle = msg.cycle || {};

    const workOrderMsg = {
        topic: "workOrderCycle",
        payload: {
            id: cycle.id || "",
            sku: cycle.sku || "",
            target: Number(cycle.target) || 0,
            good: Number(cycle.good) || 0,
            scrap: Number(cycle.scrap) || 0,
            progressPercent: Number(cycle.progressPercent) || 0,
            lastUpdateIso: cycle.lastUpdateIso || new Date().toISOString(),
            status: cycle.progressPercent >= 100 ? "DONE" : "RUNNING"
        }
    };

    const kpis = msg.kpis || global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: cycle.id || "",
            sku: cycle.sku || "",
            target: Number(cycle.target) || 0,
            good: Number(cycle.good) || 0,
            scrap: Number(cycle.scrap) || 0,
            cycleTime: Number(cycle.cycleTime) || 0,
            progressPercent: Number(cycle.progressPercent) || 0,
            lastUpdateIso: cycle.lastUpdateIso || new Date().toISOString(),
            kpis: kpis
        }
    };

    return [workOrderMsg, homeMsg, null, null];
}

// ========================================================
// MODE: MACHINE PRODUCTION STATE
// ========================================================
if (mode === "production-state") {
    const homeMsg = {
        topic: "machineStatus",
        payload: {
            machineOnline: msg.machineOnline ?? true,
            productionStarted: !!msg.productionStarted,
            trackingEnabled: msg.payload?.trackingEnabled ?? msg.trackingEnabled ?? false
        }
    };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: CURRENT STATE (for tab switch sync)
// ========================================================
if (mode === "current-state") {
    const state = msg.payload || {};
    const homeMsg = {
        topic: "currentState",
        payload: {
            activeWorkOrder: state.activeWorkOrder,
            trackingEnabled: state.trackingEnabled,
            productionStarted: state.productionStarted,
            kpis: state.kpis
        }
    };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: RESTORE QUERY (startup state recovery)
// ========================================================
if (mode === "restore-query") {
    const rows = Array.isArray(msg.payload) ? msg.payload : [];

    if (rows.length > 0) {
        const row = rows[0];
        const restoredOrder = {
            id: row.work_order_id || row.id || "",
            sku: row.sku || "",
            target: Number(row.target_qty || row.target || 0),
            good: Number(row.good_parts || row.good || 0),
            scrap: Number(row.scrap_parts || row.scrap || 0),
            progressPercent: Number(row.progress_percent || 0),
            cycleTime: Number(row.cycle_time || 0),
            lastUpdateIso: row.updated_at || null
        };

        // Restore global state
        global.set("activeWorkOrder", restoredOrder);
        global.set("cycleCount", Number(row.cycle_count) || 0);
        // Don't auto-start tracking - user must click START
        global.set("trackingEnabled", false);
        global.set("productionStarted", false);

        node.warn('[RESTORE] Restored work order: ' + restoredOrder.id + ' with ' + global.get("cycleCount") + ' cycles');

        const homeMsg = {
            topic: "activeWorkOrder",
            payload: restoredOrder
        };
        return [null, homeMsg, null, null];
    } else {
        node.warn('[RESTORE] No running work order found');
    }
    return [null, null, null, null];
}

// ========================================================
// MODE: SCRAP PROMPT
// ========================================================
if (mode === "scrap-prompt") {
    const prompt = msg.scrapPrompt || {};

    const homeMsg = { topic: "scrapPrompt", payload: prompt };
    const tabMsg = { ui_control: { tab: "Home" } };

    // output1: nothing
    // output2: home template
    // output3: tab navigation
    // output4: graphs template (unused here)
    return [null, homeMsg, tabMsg, null];
}

// ========================================================
// MODE: SCRAP UPDATE
// ========================================================
if (mode === "scrap-update") {
    const activeOrder = global.get("activeWorkOrder") || {};
    const kpis = msg.kpis || global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: activeOrder.id || "",
            sku: activeOrder.sku || "",
            target: Number(activeOrder.target) || 0,
            good: Number(activeOrder.good) || 0,
            scrap: Number(activeOrder.scrap) || 0,
            cycleTime: Number(activeOrder.cycleTime) || 0,
            progressPercent: Number(activeOrder.progressPercent) || 0,
            lastUpdateIso: activeOrder.lastUpdateIso || new Date().toISOString(),
            kpis: kpis
        }
    };

    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: SCRAP COMPLETE
// ========================================================
if (mode === "scrap-complete") {
    const homeMsg = { topic: "activeWorkOrder", payload: null };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: CHARTS → SEND REAL DATA TO GRAPH TEMPLATE
// ========================================================
//if (mode === "charts") {

//    const realOEE = msg.realOEE || global.get("realOEE") || [];
//    const realAvailability = msg.realAvailability || global.get("realAvailability") || [];
//    const realPerformance = msg.realPerformance || global.get("realPerformance") || [];
//    const realQuality = msg.realQuality || global.get("realQuality") || [];

//    const chartsMsg = {
//        topic: "chartsData",
//        payload: {
//            oee: realOEE,
//            availability: realAvailability,
//            performance: realPerformance,
//            quality: realQuality
//        }
//    };

    // Send ONLY to output #4
//    return [null, null, null, chartsMsg];
//}

// ========================================================
// DEFAULT
// ========================================================
return [null, null, null, null];


File created successfully at: /tmp/back_to_ui_phase3.js

const mode = msg._mode || '';
const started = msg.startOrder || null;
const completed = msg.completeOrder || null;

delete msg._mode;
delete msg.startOrder;
delete msg.completeOrder;
delete msg.action;
delete msg.filename;

// ========================================================
// MODE: CHECK PROGRESS (PHASE 3)
// ========================================================
if (mode === "check-progress") {
    const rows = Array.isArray(msg.payload) ? msg.payload : [];

    if (rows.length > 0) {
        const row = rows[0];
        const cycleCount = Number(row.cycle_count) || 0;
        const goodParts = Number(row.good_parts) || 0;

        // Retrieve pending work order from flow context
        const pendingOrder = flow.get("pendingWorkOrder");

        if (!pendingOrder) {
            node.error("No pending work order found in check-progress");
            return [null, null, null, null];
        }

        // If there's existing progress, send resume prompt to UI
        if (cycleCount > 0 || goodParts > 0) {
            msg.topic = "resumePrompt";
            msg.payload = {
                id: pendingOrder.id,
                sku: pendingOrder.sku || "",
                target: Number(pendingOrder.target) || 0,
                cycleCount: cycleCount,
                goodParts: goodParts,
                progressPercent: Number(row.progress_percent) || 0
            };

            node.warn(`[RESUME PROMPT] WO ${pendingOrder.id} has ${goodParts}/${pendingOrder.target} parts (${cycleCount} cycles)`);
            return [null, msg, null, null];
        }
    }

    // No progress found - proceed with normal start (automatically call resume with 0 values)
    const pendingOrder = flow.get("pendingWorkOrder");

    if (pendingOrder) {
        // Auto-start with zero values
        msg.topic = "UPDATE work_orders SET status = CASE WHEN work_order_id = ? THEN 'RUNNING' ELSE 'PENDING' END, updated_at = CASE WHEN work_order_id = ? THEN NOW() ELSE updated_at END WHERE status <> 'DONE'";
        msg.payload = [pendingOrder.id, pendingOrder.id];

        global.set("activeWorkOrder", pendingOrder);
        global.set("cycleCount", 0);
        flow.set("lastMachineState", 0);
        global.set("scrapPromptIssuedFor", null);

        node.warn(`[AUTO-START] WO ${pendingOrder.id} has no existing progress, starting fresh`);

        // Clear pending order
        flow.set("pendingWorkOrder", null);
    }

    return [null, null, null, null];
}

// ========================================================
// MODE: RESUME WORK ORDER (PHASE 3)
// ========================================================
if (mode === "resume") {
    const order = global.get("activeWorkOrder") || {};
    const cycleCount = Number(global.get("cycleCount")) || 0;
    const kpis = global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: order.id || "",
            sku: order.sku || "",
            target: Number(order.target) || 0,
            good: Number(order.good) || 0,
            scrap: Number(order.scrap) || 0,
            cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0),
            progressPercent: Number(order.progressPercent) || 0,
            lastUpdateIso: order.lastUpdateIso || null,
            kpis: kpis
        }
    };

    // Clear pending order
    flow.set("pendingWorkOrder", null);

    node.warn(`[RESUME] Loaded WO ${order.id} with ${cycleCount} cycles in UI`);

    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: RESTART WORK ORDER (PHASE 3)
// ========================================================
if (mode === "restart") {
    const order = global.get("activeWorkOrder") || {};
    const kpis = global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: order.id || "",
            sku: order.sku || "",
            target: Number(order.target) || 0,
            good: 0,
            scrap: 0,
            cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0),
            progressPercent: 0,
            lastUpdateIso: null,
            kpis: kpis
        }
    };

    // Clear pending order
    flow.set("pendingWorkOrder", null);

    node.warn(`[RESTART] Reset WO ${order.id} to zero in UI`);

    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: UPLOAD
// ========================================================
if (mode === "upload") {
    msg.topic = "uploadStatus";
    msg.payload = { message: "✅ Work orders uploaded successfully." };
    return [msg, null, null, null];
}

// ========================================================
// MODE: SELECT (Load Work Orders)
// ========================================================
if (mode === "select") {
    const rawRows = Array.isArray(msg.payload) ? msg.payload : [];
    msg.topic = "workOrdersList";
    msg.payload = rawRows.map(row => ({
        id: row.work_order_id ?? row.id ?? "",
        sku: row.sku ?? "",
        target: Number(row.target_qty ?? row.target ?? 0),
        good: Number(row.good_parts ?? row.good ?? 0),
        scrap: Number(row.scrap_count ?? row.scrap ?? 0),
        progressPercent: Number(row.progress_percent ?? row.progress ?? 0),
        status: (row.status ?? "PENDING").toUpperCase(),
        lastUpdateIso: row.updated_at ?? row.last_update ?? null,
        cycleTime: Number(row.cycle_time ?? row.theoretical_cycle_time ?? 0)
    }));
    return [msg, null, null, null];
}

// ========================================================
// MODE: START WORK ORDER
// ========================================================
if (mode === "start") {
    const order = started || {};
    const kpis = msg.kpis || global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: order.id || "",
            sku: order.sku || "",
            target: Number(order.target) || 0,
            good: Number(order.good) || 0,
            scrap: Number(order.scrap) || 0,
            cycleTime: Number(order.cycleTime || order.theoreticalCycleTime || 0),
            progressPercent: Number(order.progressPercent) || 0,
            lastUpdateIso: order.lastUpdateIso || null,
            kpis: kpis
        }
    };

    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: COMPLETE WORK ORDER
// ========================================================
if (mode === "complete") {
    const homeMsg = { topic: "activeWorkOrder", payload: null };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: CYCLE UPDATE DURING PRODUCTION
// ========================================================
if (mode === "cycle") {
    const cycle = msg.cycle || {};

    const workOrderMsg = {
        topic: "workOrderCycle",
        payload: {
            id: cycle.id || "",
            sku: cycle.sku || "",
            target: Number(cycle.target) || 0,
            good: Number(cycle.good) || 0,
            scrap: Number(cycle.scrap) || 0,
            progressPercent: Number(cycle.progressPercent) || 0,
            lastUpdateIso: cycle.lastUpdateIso || new Date().toISOString(),
            status: cycle.progressPercent >= 100 ? "DONE" : "RUNNING"
        }
    };

    const kpis = msg.kpis || global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: cycle.id || "",
            sku: cycle.sku || "",
            target: Number(cycle.target) || 0,
            good: Number(cycle.good) || 0,
            scrap: Number(cycle.scrap) || 0,
            cycleTime: Number(cycle.cycleTime) || 0,
            progressPercent: Number(cycle.progressPercent) || 0,
            lastUpdateIso: cycle.lastUpdateIso || new Date().toISOString(),
            kpis: kpis
        }
    };

    return [workOrderMsg, homeMsg, null, null];
}

// ========================================================
// MODE: MACHINE PRODUCTION STATE
// ========================================================
if (mode === "production-state") {
    const homeMsg = {
        topic: "machineStatus",
        payload: {
            machineOnline: msg.machineOnline ?? true,
            productionStarted: !!msg.productionStarted,
            trackingEnabled: msg.payload?.trackingEnabled ?? msg.trackingEnabled ?? false
        }
    };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: CURRENT STATE (for tab switch sync)
// ========================================================
if (mode === "current-state") {
    const state = msg.payload || {};
    const homeMsg = {
        topic: "currentState",
        payload: {
            activeWorkOrder: state.activeWorkOrder,
            trackingEnabled: state.trackingEnabled,
            productionStarted: state.productionStarted,
            kpis: state.kpis
        }
    };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: RESTORE QUERY (startup state recovery)
// ========================================================
if (mode === "restore-query") {
    const rows = Array.isArray(msg.payload) ? msg.payload : [];

    if (rows.length > 0) {
        const row = rows[0];
        const restoredOrder = {
            id: row.work_order_id || row.id || "",
            sku: row.sku || "",
            target: Number(row.target_qty || row.target || 0),
            good: Number(row.good_parts || row.good || 0),
            scrap: Number(row.scrap_parts || row.scrap || 0),
            progressPercent: Number(row.progress_percent || 0),
            cycleTime: Number(row.cycle_time || 0),
            lastUpdateIso: row.updated_at || null
        };

        // Restore global state
        global.set("activeWorkOrder", restoredOrder);
        global.set("cycleCount", Number(row.cycle_count) || 0);
        // Don't auto-start tracking - user must click START
        global.set("trackingEnabled", false);
        global.set("productionStarted", false);

        node.warn('[RESTORE] Restored work order: ' + restoredOrder.id + ' with ' + global.get("cycleCount") + ' cycles');

        const homeMsg = {
            topic: "activeWorkOrder",
            payload: restoredOrder
        };
        return [null, homeMsg, null, null];
    } else {
        node.warn('[RESTORE] No running work order found');
    }
    return [null, null, null, null];
}

// ========================================================
// MODE: SCRAP PROMPT
// ========================================================
if (mode === "scrap-prompt") {
    const prompt = msg.scrapPrompt || {};

    const homeMsg = { topic: "scrapPrompt", payload: prompt };
    const tabMsg = { ui_control: { tab: "Home" } };

    // output1: nothing
    // output2: home template
    // output3: tab navigation
    // output4: graphs template (unused here)
    return [null, homeMsg, tabMsg, null];
}

// ========================================================
// MODE: SCRAP UPDATE
// ========================================================
if (mode === "scrap-update") {
    const activeOrder = global.get("activeWorkOrder") || {};
    const kpis = msg.kpis || global.get("currentKPIs") || {
        oee: 0, availability: 0, performance: 0, quality: 0
    };

    const homeMsg = {
        topic: "activeWorkOrder",
        payload: {
            id: activeOrder.id || "",
            sku: activeOrder.sku || "",
            target: Number(activeOrder.target) || 0,
            good: Number(activeOrder.good) || 0,
            scrap: Number(activeOrder.scrap) || 0,
            cycleTime: Number(activeOrder.cycleTime) || 0,
            progressPercent: Number(activeOrder.progressPercent) || 0,
            lastUpdateIso: activeOrder.lastUpdateIso || new Date().toISOString(),
            kpis: kpis
        }
    };

    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: SCRAP COMPLETE
// ========================================================
if (mode === "scrap-complete") {
    const homeMsg = { topic: "activeWorkOrder", payload: null };
    return [null, homeMsg, null, null];
}

// ========================================================
// MODE: CHARTS → SEND REAL DATA TO GRAPH TEMPLATE
// ========================================================
//if (mode === "charts") {

//    const realOEE = msg.realOEE || global.get("realOEE") || [];
//    const realAvailability = msg.realAvailability || global.get("realAvailability") || [];
//    const realPerformance = msg.realPerformance || global.get("realPerformance") || [];
//    const realQuality = msg.realQuality || global.get("realQuality") || [];

//    const chartsMsg = {
//        topic: "chartsData",
//        payload: {
//            oee: realOEE,
//            availability: realAvailability,
//            performance: realPerformance,
//            quality: realQuality
//        }
//    };

    // Send ONLY to output #4
//    return [null, null, null, chartsMsg];
//}

// ========================================================
// DEFAULT
// ========================================================
return [null, null, null, null];


Now let me update both functions in flows.json:


TodoWrite

Review Phase 2 progress and understand DB Guard setup

Reviewing Phase 2 progress and DB Guard setup

Add 5-second throttling to work_orders persistence

Adding 5-second throttling to persistence

Verify Phase 2 wiring and test persistence

Update Work Order buttons with resume/restart actions

Modify Refresh Trigger to handle check-progress mode

Modify Back to UI to send resume prompt

Phase 4: Fix Complete button to persist final counts

Phase 5: Update session restore to set RUNNING status

Phase 6: Load work order data from database (not session)

Phase 7: Add tab switch state refresh (optional)

Phase 8: Testing and validation


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Review Phase 2 progress and understand DB Guard setup

Add 5-second throttling to work_orders persistence

Verify Phase 2 wiring and test persistence

Modify Back to UI to send resume prompt

Phase 4: Fix Complete button to persist final counts

Phase 5: Update session restore to set RUNNING status

Phase 6: Load work order data from database (not session)

Phase 7: Add tab switch state refresh (optional)

Phase 8: Testing and validation

Update Work Order buttons with resume/restart actions

Modify Refresh Trigger to handle check-progress mode

Modify Back to UI to send resume prompt

Phase 8: Testing and validation


Bash

cd /home/mdares/projects/Plastico && FUNC_CODE=$(cat /tmp/refresh_trigger_phase3.js) && jq --arg func "$FUNC_CODE" '(.[] | select(.name == "Refresh Trigger") | .func) = $func' flows.json > /tmp/flows_phase3_step2.json && mv /tmp/flows_phase3_step2.json flows.json

Update Refresh Trigger function


cd /home/mdares/projects/Plastico && FUNC_CODE=$(cat /tmp/back_to_ui_phase3.js) && jq --arg func "$FUNC_CODE" '(.[] | select(.name == "Back to UI") | .func) = $func' flows.json > /tmp/flows_phase3_step3.json && mv /tmp/flows_phase3_step3.json flows.json

Update Back to UI function


Now verify the JSON is still valid:


jq '.' /home/mdares/projects/Plastico/flows.json > /dev/null && echo "✓ JSON is valid" || echo "✗ JSON is INVALID"

Validate JSON syntax


✓ JSON is valid


Perfect! Now let me update the todo and create a Phase 3 summary. Note that Phase 3 still needs the UI template changes to display the resume/restart dialog, but the backend logic is complete: