diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index d0a1b93..30e40b3 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -20,7 +20,7 @@ Modularize the workflow for maintainability, add "update all" functionality, fix **Requirements:** MOD-01, MOD-02, DEBT-03 -**Plans:** 6 plans +**Plans:** 7 plans Plans: - [x] 10-01-PLAN.md — Orphan node cleanup (removed 2 orphan nodes) @@ -29,6 +29,7 @@ Plans: - [x] 10-04-PLAN.md — Integration verification and user checkpoint - [x] 10-05-PLAN.md — Complete modularization (batch operations, logs sub-workflow) - [x] 10-06-PLAN.md — Remediation: fix routing gaps, wire logs, cleanup Python scripts +- [ ] 10-07-PLAN.md — UAT gap closure: race conditions, data chains, fuzzy matching, error handling **Success Criteria:** 1. ✓ Workflow split into logical sub-workflows (update, actions, logs) @@ -154,7 +155,7 @@ Plans: | 7 | Socket Security | v1.1 | Complete | | 8 | Inline Keyboard Infrastructure | v1.1 | Complete | | 9 | Batch Operations | v1.1 | Complete | -| 10 | Workflow Modularization | v1.2 | Complete | +| 10 | Workflow Modularization | v1.2 | UAT Gaps | | 10.1 | Aggressive Workflow Modularization | v1.2 | Pending (INSERTED) | | 10.2 | Better Logging & Log Management | v1.2 | Pending (INSERTED) | | 11 | Update All & Callback Limits | v1.2 | Pending | @@ -164,4 +165,4 @@ Plans: **v1.2 Coverage:** 12+ requirements mapped across 7 phases --- -*Updated: 2026-02-04 after Phase 10 planning* +*Updated: 2026-02-04 after Phase 10 UAT gap planning* diff --git a/.planning/phases/10-workflow-modularization/10-07-PLAN.md b/.planning/phases/10-workflow-modularization/10-07-PLAN.md new file mode 100644 index 0000000..9147e48 --- /dev/null +++ b/.planning/phases/10-workflow-modularization/10-07-PLAN.md @@ -0,0 +1,345 @@ +--- +phase: 10-workflow-modularization +plan: 07 +type: remediation +wave: 6 +depends_on: [10-06] +files_modified: [n8n-workflow.json] +autonomous: true +gap_closure: true + +must_haves: + truths: + - "Single text update sends only one result message (no race condition)" + - "Batch update executes via Container Update sub-workflow" + - "Batch actions execute via Container Actions sub-workflow" + - "Container logs text command works with fuzzy matching" + - "Refresh Logs button handles 'message not modified' gracefully" + artifacts: + - path: "n8n-workflow.json" + provides: "Fixed main workflow with all UAT gaps closed" + contains: "$('Build Progress Message').item.json" + key_links: + - from: "Prepare Text Update Input" + to: "Execute Text Update" + via: "Sequential connection (not parallel with Send Text Update Started)" + - from: "Prepare Batch Update Input" + to: "Execute Batch Update" + via: "Uses $('Build Progress Message').item.json.container" + - from: "Prepare Batch Action Input" + to: "Execute Batch Action Sub-workflow" + via: "Uses $('Build Progress Message').item.json" +--- + + +Close 5 UAT gaps discovered during user testing of Phase 10 modularization. + +Purpose: UAT revealed race conditions, broken data chains, missing fuzzy matching, and unhandled Telegram API errors that prevent the modularized workflow from functioning correctly. + +Output: Fully functional modularized workflow with all user-reported issues fixed. + + + +@/home/luc/.claude/get-shit-done/workflows/execute-plan.md +@/home/luc/.claude/get-shit-done/templates/summary.md + + + +@.planning/phases/10-workflow-modularization/10-06-SUMMARY.md +@n8n-workflow.json +@n8n-container-logs.json + + + + + + Task 1: Fix text update race condition (remove parallel message) + n8n-workflow.json + +**Problem:** User reported two messages sent for single text update - "already up to date" AND "Updating..." in race condition. Root cause: `Prepare Text Update Input` connects in PARALLEL to both `Send Text Update Started` AND `Execute Text Update`. The sub-workflow also sends a result message. + +**Current connection (lines 6616-6629):** +```json +"Prepare Text Update Input": { + "main": [ + [ + { "node": "Send Text Update Started", ... }, + { "node": "Execute Text Update", ... } + ] + ] +} +``` +Both nodes are in the same array = parallel execution. + +**Fix:** Remove `Send Text Update Started` from the connection array. The sub-workflow handles all messaging, so the "Updating..." message is redundant. + +**Steps:** +1. Find the "Prepare Text Update Input" entry in the `connections` object +2. Remove the object `{ "node": "Send Text Update Started", "type": "main", "index": 0 }` from the array +3. Keep only `{ "node": "Execute Text Update", "type": "main", "index": 0 }` + +**Result connection:** +```json +"Prepare Text Update Input": { + "main": [ + [ + { "node": "Execute Text Update", "type": "main", "index": 0 } + ] + ] +} +``` + + +- Grep for "Prepare Text Update Input" in connections shows only Execute Text Update +- No reference to Send Text Update Started in parallel + + Text update sends only one message (from sub-workflow) + + + + Task 2: Fix batch update data chain (use Build Progress Message reference) + n8n-workflow.json + +**Problem:** User reported "Cannot read properties of undefined (reading 'id')" in Prepare Batch Update Input. Root cause: The node uses `$json.container` but receives Telegram API response from `Edit Progress Message`, not the batch data. + +**Current code (node id: caeae5d6-f9ec-4aa3-83d3-198b6b55be65, lines 4688-4699):** +```javascript +const data = $json; +const container = data.container; +``` + +**Data flow:** +- `Build Progress Message` outputs: `{ container: {...}, chatId, progressMessageId, action, ... }` +- `Edit Progress Message` sends Telegram API call, outputs Telegram response (no container data) +- `Prepare Batch Update Input` receives Telegram response (wrong data!) + +**Fix:** Change to reference `Build Progress Message` directly instead of relying on `$json`: + +```javascript +// Prepare input for Container Update sub-workflow +const data = $('Build Progress Message').item.json; +const container = data.container; + +// Extract container info +const containerId = container.id || container.Id || ''; +const containerName = container.name || container.Name || ''; + +return { + json: { + containerId: containerId, + containerName: containerName, + chatId: data.chatId, + messageId: data.progressMessageId || 0, + responseMode: "inline" + } +}; +``` + +**Steps:** +1. Find node with id "caeae5d6-f9ec-4aa3-83d3-198b6b55be65" (Prepare Batch Update Input) +2. Update the `jsCode` parameter to use `$('Build Progress Message').item.json` instead of `$json` +3. Keep all other logic the same + + +- Grep for "Prepare Batch Update Input" shows `$('Build Progress Message').item.json` +- No `const data = $json` in that node + + Batch update correctly reads container data from Build Progress Message + + + + Task 3: Fix batch action data chain (use Build Progress Message reference) + n8n-workflow.json + +**Problem:** Same as Task 2 - "Cannot read properties of undefined (reading 'id')" in Prepare Batch Action Input. Uses `$json.container` but receives Telegram response. + +**Current code (node id: 958f19ef-249b-42ca-8a29-ecb91548f1dd, lines 4732-4743):** +```javascript +const data = $json; +const container = data.container; +const action = data.action; +``` + +**Fix:** Change to reference `Build Progress Message` directly: + +```javascript +// Prepare input for Container Actions sub-workflow +const data = $('Build Progress Message').item.json; +const container = data.container; +const action = data.action; + +// Extract container info +const containerId = container.id || container.Id || ''; +const containerName = container.name || container.Name || ''; + +return { + json: { + containerId: containerId, + containerName: containerName, + action: action, + chatId: data.chatId, + messageId: data.progressMessageId || 0, + responseMode: "inline" + } +}; +``` + +**Steps:** +1. Find node with id "958f19ef-249b-42ca-8a29-ecb91548f1dd" (Prepare Batch Action Input) +2. Update the `jsCode` parameter to use `$('Build Progress Message').item.json` instead of `$json` +3. Keep all other logic the same + + +- Grep for "Prepare Batch Action Input" shows `$('Build Progress Message').item.json` +- No `const data = $json` in that node + + Batch action correctly reads container data from Build Progress Message + + + + Task 4: Add fuzzy matching to logs text command and fix Send Logs Response chatId + n8n-workflow.json + +**Problem:** Two issues with logs text command: +1. Fuzzy matching not working - exact name required (unlike update/start/stop) +2. Send Logs Response uses `$json.chatId` but sub-workflow returns `{success, message, containerName}` without chatId + +**Current flow:** +``` +Parse Logs Command -> Prepare Text Logs Input -> Execute Text Logs -> Send Logs Response +``` + +**Issue 1 - No fuzzy matching:** The logs flow passes `containerQuery` directly to sub-workflow which does exact match only. Other commands (update/start/stop) have Docker query + fuzzy matching nodes BEFORE calling sub-workflow. + +**Issue 2 - Missing chatId:** Send Logs Response (node id: telegram-send-logs) uses: +```json +"chatId": "={{ $json.chatId }}" +``` +But `$json` is the sub-workflow output which doesn't include chatId. + +**Fix for Send Logs Response:** Change chatId to reference the input node that HAS chatId: +```json +"chatId": "={{ $('Prepare Text Logs Input').item.json.chatId }}" +``` + +**Note on fuzzy matching:** Adding full fuzzy matching infrastructure (Docker query + Match Container node + Check Match Count switch) would require significant changes. For now, the sub-workflow already does name lookup via Docker API. The issue is it requires EXACT normalized name. + +**Alternative simpler fix:** Update the logs sub-workflow's "Find Container" node to use `.includes()` instead of exact `===` match. This provides fuzzy matching without restructuring main workflow. + +**Steps:** +1. In n8n-workflow.json, find node id "telegram-send-logs" (Send Logs Response) +2. Change `chatId` from `={{ $json.chatId }}` to `={{ $('Prepare Text Logs Input').item.json.chatId }}` + +3. In n8n-container-logs.json, find "Find Container" node (id: 52dd705b-dd3b-4fdc-8484-276845857ad0) +4. Change the filter logic from exact match to includes: + - FROM: `normalizeName(c.Names[0]) === containerName` + - TO: `normalizeName(c.Names[0]).includes(containerName)` +5. Add handling for multiple matches (throw error suggesting which containers match) + + +- Send Logs Response uses `$('Prepare Text Logs Input').item.json.chatId` +- n8n-container-logs.json Find Container uses `.includes()` for matching + + Logs command works with fuzzy matching and Send Logs Response has valid chatId + + + + Task 5: Handle "message not modified" error in logs refresh + n8n-workflow.json + +**Problem:** User reported "message is not modified" error when refreshing logs that haven't changed. Telegram API rejects editMessageText when content is identical. + +**Current flow:** +``` +Prepare Inline Logs Input -> Execute Inline Logs -> Format Inline Logs Result -> Send Logs Result +``` + +**Send Logs Result (node id: http-send-logs-result):** Uses httpRequest to call editMessageText API. + +**Fix options:** +A. Add timestamp to force content difference (changes UX - shows timestamp) +B. Add error handling node after Send Logs Result to catch this specific error +C. Use Telegram node with "Continue On Fail" and handle error in next node + +**Recommended: Option A - Add timestamp to logs display** + +This is the cleanest solution because: +- Logs refresh SHOULD show when they were fetched +- Always succeeds (no error handling needed) +- Better UX - user knows logs are fresh + +**Implementation in Format Inline Logs Result (node id: b1800598-1ff6-4da3-8506-4e4e8127f902):** + +Update the jsCode to add timestamp to the message: + +```javascript +// Format logs result for inline keyboard display +const result = $json; +const data = $('Prepare Inline Logs Input').item.json; + +const containerName = result.containerName; + +// Add timestamp to prevent "message not modified" error on refresh +const timestamp = new Date().toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false +}); + +// Build inline keyboard +const keyboard = [ + [ + { text: '\ud83d\udd04 Refresh Logs', callback_data: `action:logs:${containerName}` }, + { text: '\u2b06\ufe0f Update', callback_data: `action:update:${containerName}` } + ], + [ + { text: '\u25c0\ufe0f Back to List', callback_data: 'list:0' } + ] +]; + +// Append timestamp to message +const messageWithTimestamp = result.message + `\n\nUpdated: ${timestamp}`; + +return { + json: { + chatId: data.chatId, + messageId: data.messageId, + text: messageWithTimestamp, + reply_markup: { inline_keyboard: keyboard } + } +}; +``` + +**Steps:** +1. Find node with id "b1800598-1ff6-4da3-8506-4e4e8127f902" (Format Inline Logs Result) +2. Update jsCode to add timestamp generation +3. Append timestamp to the message text before returning + + +- Format Inline Logs Result includes timestamp generation code +- Output text includes "Updated: HH:MM:SS" suffix + + Logs refresh always succeeds (timestamp ensures content is different) + + + + + +1. `grep -A5 '"Prepare Text Update Input"' n8n-workflow.json` shows only Execute Text Update connection +2. `grep 'Build Progress Message' n8n-workflow.json | grep -c 'Prepare Batch'` returns 2 (update + action) +3. `grep 'Prepare Text Logs Input' n8n-workflow.json` shows chatId reference in Send Logs Response +4. `grep 'includes' n8n-container-logs.json` shows fuzzy matching in Find Container +5. `grep 'timestamp' n8n-workflow.json` shows timestamp in Format Inline Logs Result + + + +- Text update sends single message (no race condition) +- Batch update and batch actions execute without "undefined" errors +- Logs command works with partial container names +- Logs refresh never fails with "message not modified" error + + + +After completion, create `.planning/phases/10-workflow-modularization/10-07-SUMMARY.md` +