From 850a507684b620c25595b2d365b8590b6864d3ac Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Wed, 4 Feb 2026 10:33:46 -0500 Subject: [PATCH] fix(09-04): hide Back to List button for text command batches - Track fromKeyboard in batch state (true for callbacks, false for text) - Pass fromKeyboard through entire batch loop chain - Build Batch Summary only includes Back to List when fromKeyboard=true - Send Batch Summary uses conditional reply_markup from input Co-Authored-By: Claude Opus 4.5 --- n8n-workflow.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/n8n-workflow.json b/n8n-workflow.json index 9eee475..9b115b2 100644 --- a/n8n-workflow.json +++ b/n8n-workflow.json @@ -4639,7 +4639,7 @@ }, { "parameters": { - "jsCode": "// Initialize batch state for execution\n// Input comes from Route Batch Action OR batch callbacks (bexec, bstop confirmed)\nconst data = $json;\n\n// Handle different input sources\nlet containers, action, chatId, messageId;\n\nif (data.allMatched) {\n // From Route Batch Action (direct batch command)\n containers = data.allMatched;\n action = data.action;\n chatId = data.chatId;\n messageId = data.messageId || null;\n} else if (data.containerNames) {\n // From batch callback (bexec or bstop:confirm)\n // Need to resolve names to container objects\n containers = data.containerNames.map(name => ({ Name: name, Id: null }));\n action = data.batchAction || 'stop';\n chatId = data.chatId;\n messageId = data.messageId;\n} else {\n throw new Error('Invalid batch state input');\n}\n\nreturn {\n json: {\n containers: containers,\n action: action,\n totalCount: containers.length,\n successCount: 0,\n failureCount: 0,\n warningCount: 0,\n results: [],\n chatId: chatId,\n messageId: messageId,\n currentIndex: 0,\n progressMessageId: null\n }\n};" + "jsCode": "// Initialize batch state for execution\n// Input comes from Route Batch Action OR batch callbacks (bexec, bstop confirmed)\nconst data = $json;\n\n// Handle different input sources\nlet containers, action, chatId, messageId, fromKeyboard;\n\nif (data.allMatched) {\n // From Route Batch Action (direct batch command) - text flow\n containers = data.allMatched;\n action = data.action;\n chatId = data.chatId;\n messageId = data.messageId || null;\n fromKeyboard = false;\n} else if (data.containerNames) {\n // From batch callback (bexec or bstop:confirm) - keyboard flow\n // Need to resolve names to container objects\n containers = data.containerNames.map(name => ({ Name: name, Id: null }));\n action = data.batchAction || 'stop';\n chatId = data.chatId;\n messageId = data.messageId;\n fromKeyboard = data.fromKeyboard !== false; // Default true for callbacks\n} else {\n throw new Error('Invalid batch state input');\n}\n\nreturn {\n json: {\n containers: containers,\n action: action,\n totalCount: containers.length,\n successCount: 0,\n failureCount: 0,\n warningCount: 0,\n results: [],\n chatId: chatId,\n messageId: messageId,\n currentIndex: 0,\n progressMessageId: null,\n fromKeyboard: fromKeyboard\n }\n};" }, "id": "code-init-batch-state", "name": "Initialize Batch State", @@ -4670,7 +4670,7 @@ }, { "parameters": { - "jsCode": "// Store progress message ID and prepare first iteration\nlet batchState;\ntry {\n batchState = $('Initialize Batch State').item.json;\n} catch (e) {\n throw new Error('Failed to get Initialize Batch State: ' + e.message);\n}\n\nif (!batchState || !batchState.containers || !Array.isArray(batchState.containers)) {\n throw new Error('Invalid batch state');\n}\n\nconst response = $json;\nconst progressMessageId = response.result?.message_id || null;\n\n// Return single item with all data for manual loop\n// We'll process containers[currentIndex] and increment\nreturn {\n json: {\n containers: batchState.containers,\n currentIndex: 0,\n container: batchState.containers[0],\n action: batchState.action,\n totalCount: batchState.totalCount,\n chatId: batchState.chatId,\n progressMessageId: progressMessageId,\n successCount: 0,\n failureCount: 0,\n warningCount: 0,\n results: []\n }\n};" + "jsCode": "// Store progress message ID and prepare first iteration\nlet batchState;\ntry {\n batchState = $('Initialize Batch State').item.json;\n} catch (e) {\n throw new Error('Failed to get Initialize Batch State: ' + e.message);\n}\n\nif (!batchState || !batchState.containers || !Array.isArray(batchState.containers)) {\n throw new Error('Invalid batch state');\n}\n\nconst response = $json;\nconst progressMessageId = response.result?.message_id || null;\n\n// Return single item with all data for manual loop\n// We'll process containers[currentIndex] and increment\nreturn {\n json: {\n containers: batchState.containers,\n currentIndex: 0,\n container: batchState.containers[0],\n action: batchState.action,\n totalCount: batchState.totalCount,\n chatId: batchState.chatId,\n progressMessageId: progressMessageId,\n successCount: 0,\n failureCount: 0,\n warningCount: 0,\n results: [],\n fromKeyboard: batchState.fromKeyboard || false\n }\n};" }, "id": "code-prepare-batch-loop", "name": "Prepare Batch Loop", @@ -5030,7 +5030,7 @@ }, { "parameters": { - "jsCode": "// Build batch summary message with failure emphasis\n// Input: final batch state with results array and counters\nconst data = $json;\n\nconst action = data.action;\nconst results = data.results || [];\nconst successCount = data.successCount || 0;\nconst failureCount = data.failureCount || 0;\nconst warningCount = data.warningCount || 0;\nconst totalCount = data.totalCount || results.length;\nconst chatId = data.chatId;\nconst progressMessageId = data.progressMessageId;\n\n// Build summary text - failures emphasized first\nlet summaryText = `Batch ${action} Complete\\n\\n`;\n\n// Show failures first and prominently\nconst failures = results.filter(r => r.status === 'error');\nif (failures.length > 0) {\n summaryText += `\\u274c Failed (${failures.length}):\\n`;\n for (const f of failures) {\n summaryText += `\\u2022 ${f.name}: ${f.reason || 'Unknown error'}\\n`;\n }\n summaryText += '\\n';\n}\n\n// Show warnings summary (not individual) per context discretion\nconst warnings = results.filter(r => r.status === 'warning');\nif (warnings.length > 0) {\n // If 3 or fewer warnings, show details; otherwise just count\n if (warnings.length <= 3) {\n summaryText += `\\u26a0\\ufe0f Warnings (${warnings.length}):\\n`;\n for (const w of warnings) {\n summaryText += `\\u2022 ${w.name}: ${w.reason}\\n`;\n }\n summaryText += '\\n';\n } else {\n summaryText += `\\u26a0\\ufe0f Warnings: ${warnings.length} (containers already in desired state)\\n\\n`;\n }\n}\n\n// Show success count\nif (successCount > 0) {\n summaryText += `\\u2705 Successful: ${successCount}/${totalCount}\\n`;\n} else if (failureCount === 0 && warningCount > 0) {\n // All warnings, no success/failures\n summaryText += `No changes needed for ${totalCount} container(s)\\n`;\n}\n\nreturn {\n json: {\n chatId: chatId,\n messageId: progressMessageId,\n text: summaryText,\n hasFailures: failureCount > 0\n }\n};" + "jsCode": "// Build batch summary message with failure emphasis\n// Input: final batch state with results array and counters\nconst data = $json;\n\nconst action = data.action;\nconst results = data.results || [];\nconst successCount = data.successCount || 0;\nconst failureCount = data.failureCount || 0;\nconst warningCount = data.warningCount || 0;\nconst totalCount = data.totalCount || results.length;\nconst chatId = data.chatId;\nconst progressMessageId = data.progressMessageId;\nconst fromKeyboard = data.fromKeyboard || false;\n\n// Build summary text - failures emphasized first\nlet summaryText = `Batch ${action} Complete\\n\\n`;\n\n// Show failures first and prominently\nconst failures = results.filter(r => r.status === 'error');\nif (failures.length > 0) {\n summaryText += `\\u274c Failed (${failures.length}):\\n`;\n for (const f of failures) {\n summaryText += `\\u2022 ${f.name}: ${f.reason || 'Unknown error'}\\n`;\n }\n summaryText += '\\n';\n}\n\n// Show warnings summary (not individual) per context discretion\nconst warnings = results.filter(r => r.status === 'warning');\nif (warnings.length > 0) {\n // If 3 or fewer warnings, show details; otherwise just count\n if (warnings.length <= 3) {\n summaryText += `\\u26a0\\ufe0f Warnings (${warnings.length}):\\n`;\n for (const w of warnings) {\n summaryText += `\\u2022 ${w.name}: ${w.reason}\\n`;\n }\n summaryText += '\\n';\n } else {\n summaryText += `\\u26a0\\ufe0f Warnings: ${warnings.length} (containers already in desired state)\\n\\n`;\n }\n}\n\n// Show success count\nif (successCount > 0) {\n summaryText += `\\u2705 Successful: ${successCount}/${totalCount}\\n`;\n} else if (failureCount === 0 && warningCount > 0) {\n // All warnings, no success/failures\n summaryText += `No changes needed for ${totalCount} container(s)\\n`;\n}\n\n// Only include Back to List button if user came from inline keyboard\nconst replyMarkup = fromKeyboard ? { inline_keyboard: [[{ text: 'Back to List', callback_data: 'list:0' }]] } : null;\n\nreturn {\n json: {\n chatId: chatId,\n messageId: progressMessageId,\n text: summaryText,\n hasFailures: failureCount > 0,\n reply_markup: replyMarkup\n }\n};" }, "id": "code-build-batch-summary", "name": "Build Batch Summary", @@ -5047,7 +5047,7 @@ "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/editMessageText", "sendBody": true, "specifyBody": "json", - "jsonBody": "={{ JSON.stringify({ chat_id: $json.chatId, message_id: $json.messageId, text: $json.text, parse_mode: 'HTML', reply_markup: { inline_keyboard: [[{ text: 'Back to List', callback_data: 'list:0' }]] } }) }}", + "jsonBody": "={{ JSON.stringify({ chat_id: $json.chatId, message_id: $json.messageId, text: $json.text, parse_mode: 'HTML', ...$json.reply_markup ? { reply_markup: $json.reply_markup } : {} }) }}", "options": {} }, "id": "http-send-batch-summary",