From ecd02a4b0ef7c3ed8315d12eb56a66eefaadd2b0 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Tue, 3 Feb 2026 11:44:46 -0500 Subject: [PATCH] docs(08): create inline keyboard infrastructure plans Phase 8: Inline Keyboard Infrastructure - 3 plans in 3 waves (sequential dependency) - Plan 01: Container list keyboard and submenu navigation - Plan 02: Action execution and confirmation flow - Plan 03: Progress feedback and completion messages Covers KEY-01 through KEY-05 requirements. Ready for execution. Co-Authored-By: Claude Opus 4.5 --- .planning/ROADMAP.md | 11 +- .../08-01-PLAN.md | 383 ++++++++++++++++++ .../08-02-PLAN.md | 317 +++++++++++++++ .../08-03-PLAN.md | 304 ++++++++++++++ 4 files changed, 1013 insertions(+), 2 deletions(-) create mode 100644 .planning/phases/08-inline-keyboard-infrastructure/08-01-PLAN.md create mode 100644 .planning/phases/08-inline-keyboard-infrastructure/08-02-PLAN.md create mode 100644 .planning/phases/08-inline-keyboard-infrastructure/08-03-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index a210697..829a3e0 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -63,11 +63,18 @@ Plans: **Requirements:** KEY-01, KEY-02, KEY-03, KEY-04, KEY-05 +**Plans:** 3 plans + +Plans: +- [ ] 08-01-PLAN.md — Container list keyboard and submenu navigation +- [ ] 08-02-PLAN.md — Action execution and confirmation flow +- [ ] 08-03-PLAN.md — Progress feedback and completion messages + **Success Criteria:** 1. Status command returns a message with inline buttons showing available actions per container 2. Tapping an action button (start/stop/restart) executes that action on the target container -3. Dangerous actions (stop, restart, update) show a confirmation prompt before executing -4. During operation execution, the message updates to show progress (e.g., "Stopping plex...") +3. Dangerous actions (stop, update) show a confirmation prompt before executing +4. During operation execution, the message updates to show progress (e.g., "Updating plex...") 5. After action completes, buttons are removed and final status is shown in the message --- diff --git a/.planning/phases/08-inline-keyboard-infrastructure/08-01-PLAN.md b/.planning/phases/08-inline-keyboard-infrastructure/08-01-PLAN.md new file mode 100644 index 0000000..8c22b4f --- /dev/null +++ b/.planning/phases/08-inline-keyboard-infrastructure/08-01-PLAN.md @@ -0,0 +1,383 @@ +--- +phase: 08-inline-keyboard-infrastructure +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: [n8n-workflow.json] +autonomous: true + +must_haves: + truths: + - "User sees container list with tappable buttons when typing /status" + - "Tapping a container name shows submenu with status details and action buttons" + - "Pagination works for container lists longer than 6 containers" + - "Direct access (/status plex) shows that container's submenu directly" + artifacts: + - path: "n8n-workflow.json" + provides: "Container list keyboard and submenu nodes" + contains: "Build Container List Keyboard" + - path: "n8n-workflow.json" + provides: "Submenu keyboard builder" + contains: "Build Container Submenu" + key_links: + - from: "Keyword Router (status)" + to: "Build Container List Keyboard" + via: "workflow connection" + pattern: "inline_keyboard" + - from: "Route Callback" + to: "Build Container Submenu" + via: "select: callback routing" + pattern: "select:" +--- + + +Build the container list inline keyboard and container submenu for Phase 8 Inline Keyboard Infrastructure. + +Purpose: Enable users to interact with containers via tappable buttons. The `/status` command shows a paginated container list with buttons. Tapping a container shows a submenu with status details and action buttons. + +Output: Updated n8n-workflow.json with container list keyboard and submenu nodes wired to existing status flow. + + + +@/home/luc/.claude/get-shit-done/workflows/execute-plan.md +@/home/luc/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/08-inline-keyboard-infrastructure/08-CONTEXT.md +@.planning/phases/08-inline-keyboard-infrastructure/08-RESEARCH.md +@n8n-workflow.json + + + + + + Task 1: Add Container List Inline Keyboard + n8n-workflow.json + +Modify the status command flow to return an inline keyboard instead of text. + +1. Create a Code node "Build Container List Keyboard" that: + - Takes container list from "Get Containers" node + - Strips `linuxserver-` prefix from names for display + - Groups by state (running containers first) + - Builds paginated keyboard (6 containers per page) + - Uses callback_data format: `select:{containerName}` for container buttons + - Uses `list:{page}` for pagination (e.g., `list:0`, `list:1`) + - Each row: one container button with "name — Running/Stopped" text + - Navigation row at bottom: "Previous" / "Next" buttons if needed + - Returns: `{ chatId, text, reply_markup: { inline_keyboard: [...] } }` + +2. Create an HTTP Request node "Send Container List" that: + - Method: POST + - URL: `https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/sendMessage` + - Body (JSON): + ```json + { + "chat_id": "={{ $json.chatId }}", + "text": "={{ $json.text }}", + "parse_mode": "HTML", + "reply_markup": {{ JSON.stringify($json.reply_markup) }} + } + ``` + +3. Wire the flow: + - "Get Containers" (status branch) -> "Build Container List Keyboard" -> "Send Container List" + - Remove/bypass the old text-only status response for this flow + +4. Handle `/status {name}` direct access: + - In "Build Container List Keyboard", check if input has a container name filter + - If single container requested, output should route to submenu builder instead + - Add output pin for "single container" case + +Code template for keyboard builder: +```javascript +const containers = $input.all().map(item => item.json); +const chatId = $('IF User Authenticated').item.json.message.chat.id; +const messageText = $('IF User Authenticated').item.json.message.text || ''; + +// Check for direct container access: "/status plex" or "status plex" +const match = messageText.match(/status\s+(\S+)/i); +const filterName = match ? match[1].toLowerCase() : null; + +// If single container requested, find it and route to submenu +if (filterName) { + const container = containers.find(c => { + const name = c.Names[0].replace(/^\//, '').replace(/^linuxserver-/, '').toLowerCase(); + return name === filterName || name.includes(filterName); + }); + + if (container) { + return { + json: { + singleContainer: true, + containerName: container.Names[0].replace(/^\//, '').replace(/^linuxserver-/, ''), + container: container, + chatId: chatId + } + }; + } +} + +// Build paginated list +const page = 0; // Default to first page +const perPage = 6; +const start = page * perPage; + +// Sort: running first, then by name +const sorted = containers.sort((a, b) => { + if (a.State === 'running' && b.State !== 'running') return -1; + if (a.State !== 'running' && b.State === 'running') return 1; + const nameA = a.Names[0].replace(/^\//, '').replace(/^linuxserver-/, ''); + const nameB = b.Names[0].replace(/^\//, '').replace(/^linuxserver-/, ''); + return nameA.localeCompare(nameB); +}); + +const pageContainers = sorted.slice(start, start + perPage); +const totalPages = Math.ceil(sorted.length / perPage); + +const keyboard = []; + +// Container buttons +pageContainers.forEach(container => { + const name = container.Names[0].replace(/^\//, '').replace(/^linuxserver-/, ''); + const state = container.State === 'running' ? 'Running' : 'Stopped'; + const icon = container.State === 'running' ? 'đŸŸĸ' : 'âšĒ'; + keyboard.push([{ + text: `${icon} ${name} — ${state}`, + callback_data: `select:${name}` + }]); +}); + +// Navigation row +const navRow = []; +if (page > 0) { + navRow.push({ text: 'â—€ī¸ Previous', callback_data: `list:${page - 1}` }); +} +if (page < totalPages - 1) { + navRow.push({ text: 'Next â–ļī¸', callback_data: `list:${page + 1}` }); +} +if (navRow.length > 0) keyboard.push(navRow); + +return { + json: { + singleContainer: false, + chatId: chatId, + text: `Containers (${start + 1}-${Math.min(start + perPage, sorted.length)} of ${sorted.length})\n\nTap a container to manage it:`, + reply_markup: { inline_keyboard: keyboard } + } +}; +``` + + + 1. Load workflow in n8n UI + 2. Send "/status" to bot + 3. Verify: Response is inline keyboard (not text) + 4. Verify: Containers shown with status icons + 5. Verify: Tapping button shows callback in n8n logs (even if not handled yet) + + + - /status returns inline keyboard with container list + - Each container is a tappable button with name and state + - Running containers shown first with green icon + - Pagination navigation appears when >6 containers + + + + + Task 2: Add Container Submenu with Action Buttons + n8n-workflow.json + +Add routing and nodes to handle `select:{name}` callbacks and display container submenu. + +1. Update "Parse Callback Data" code node to recognize `select:` prefix: + - If callback_data starts with `select:`, extract container name + - Set `isSelect: true` and `containerName: extractedName` + - Pass through to routing + +2. Add new output to "Route Callback" switch node: + - New rule: `isSelect === true` -> output "select" + - Position it before the fallback output + +3. Create Code node "Prepare Container Fetch" that: + - Takes parsed callback data with containerName + - Outputs container name and callback context for Docker API call + - Preserves queryId, chatId, messageId for response + +4. Create HTTP Request node "Get Single Container": + - Method: GET + - URL: `http://docker-socket-proxy:2375/containers/json?all=true&filters={"name":["{{ $json.containerName }}"]}` + - Returns container details + +5. Create Code node "Build Container Submenu" that: + - Takes container details and callback context + - Builds action keyboard based on container state: + - If running: [Stop] [Restart] row, [Logs] [Update] row + - If stopped: [Start] row, [Logs] [Update] row + - Uses callback_data format: `action:{action}:{containerName}` (e.g., `action:stop:plex`) + - Adds "Back to List" button: `list:0` + - Builds text with container status details + - Returns structure for editMessageText + +6. Create HTTP Request node "Send Container Submenu" that: + - Method: POST + - URL: `https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText` + - Body: + ```json + { + "chat_id": "={{ $json.chatId }}", + "message_id": {{ $json.messageId }}, + "text": "={{ $json.text }}", + "parse_mode": "HTML", + "reply_markup": {{ JSON.stringify($json.reply_markup) }} + } + ``` + +7. Create HTTP Request node "Answer Select Callback" (place BEFORE submenu fetch): + - Method: POST + - URL: `https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery` + - Body: `{ "callback_query_id": "={{ $json.queryId }}" }` + - CRITICAL: Must answer callback FIRST to prevent Telegram loading indicator + +8. Wire the flow: + - Route Callback (select output) -> Answer Select Callback -> Prepare Container Fetch -> Get Single Container -> Build Container Submenu -> Send Container Submenu + +Code template for Build Container Submenu: +```javascript +const container = $input.all()[0].json[0]; // First container from filtered list +const { queryId, chatId, messageId, containerName } = $('Prepare Container Fetch').item.json; + +const keyboard = []; + +// Action row 1: state-dependent +if (container.State === 'running') { + keyboard.push([ + { text: 'âšī¸ Stop', callback_data: `action:stop:${containerName}` }, + { text: '🔄 Restart', callback_data: `action:restart:${containerName}` } + ]); +} else { + keyboard.push([ + { text: 'â–ļī¸ Start', callback_data: `action:start:${containerName}` } + ]); +} + +// Action row 2: always available +keyboard.push([ + { text: '📋 Logs', callback_data: `action:logs:${containerName}` }, + { text: 'âŦ†ī¸ Update', callback_data: `action:update:${containerName}` } +]); + +// Navigation row +keyboard.push([ + { text: 'â—€ī¸ Back to List', callback_data: 'list:0' } +]); + +// Build status text +const stateIcon = container.State === 'running' ? 'đŸŸĸ' : 'âšĒ'; +const status = container.Status || container.State; +const image = container.Image.split(':')[0].split('/').pop(); // Get image name without registry/tag + +return { + json: { + chatId: chatId, + messageId: messageId, + text: `${stateIcon} ${containerName}\n\n` + + `State: ${container.State}\n` + + `Status: ${status}\n` + + `Image: ${image}`, + reply_markup: { inline_keyboard: keyboard } + } +}; +``` + + + 1. Send "/status" to bot + 2. Tap a container button + 3. Verify: Message edits in-place to show container details + 4. Verify: Action buttons match container state (Stop/Restart for running, Start for stopped) + 5. Verify: "Back to List" button present + 6. Tap "Back to List" + 7. Verify: Returns to container list + + + - Tapping container in list shows submenu with details and action buttons + - Submenu shows container state, status, image + - Action buttons match container state (Start vs Stop/Restart) + - "Back to List" returns to container list + - All transitions are message edits (no new messages) + + + + + Task 3: Handle List Pagination Callbacks + n8n-workflow.json + +Add routing to handle `list:{page}` callbacks for pagination navigation. + +1. Update "Parse Callback Data" to recognize `list:` prefix: + - If callback_data starts with `list:`, extract page number + - Set `isList: true` and `page: extractedPage` + +2. Add new output to "Route Callback": + - New rule: `isList === true` -> output "list" + +3. Create Code node "Build Paginated List" (or reuse container list logic): + - Similar to Task 1 keyboard builder but: + - Uses page number from callback + - Uses chatId/messageId from callback (for edit, not send) + - Returns structure for editMessageText + +4. Create HTTP Request "Answer List Callback": + - answerCallbackQuery to prevent loading indicator + +5. Create HTTP Request "Edit Container List": + - editMessageText with updated page + +6. Wire flow: + - Route Callback (list output) -> Answer List Callback -> Get Containers -> Build Paginated List -> Edit Container List + +Note: May need to reuse "Get Containers" node or create parallel path. + + + 1. Have more than 6 containers (or temporarily set perPage to 3 for testing) + 2. Send "/status" + 3. Tap "Next" button + 4. Verify: Message edits to show next page of containers + 5. Tap "Previous" button + 6. Verify: Returns to previous page + 7. Verify: No loading indicator hangs (callback answered) + + + - Pagination buttons navigate between pages + - Message edits in-place (no new messages) + - Callback answered immediately (no loading indicator) + - Page numbers correct in header text + + + + + + +After completing all tasks: +1. `/status` shows inline keyboard with container list +2. Tapping container shows submenu with action buttons +3. "Back to List" returns to container list +4. Pagination works (if >6 containers) +5. `/status plex` shows that container's submenu directly +6. All transitions are message edits, not new messages +7. No hanging loading indicators (callbacks answered) + + + +- KEY-01 requirement partially met: container list with inline buttons works +- Navigation flow complete: List -> Submenu -> List +- Foundation ready for action execution (Plan 02) + + + +After completion, create `.planning/phases/08-inline-keyboard-infrastructure/08-01-SUMMARY.md` + diff --git a/.planning/phases/08-inline-keyboard-infrastructure/08-02-PLAN.md b/.planning/phases/08-inline-keyboard-infrastructure/08-02-PLAN.md new file mode 100644 index 0000000..38decf8 --- /dev/null +++ b/.planning/phases/08-inline-keyboard-infrastructure/08-02-PLAN.md @@ -0,0 +1,317 @@ +--- +phase: 08-inline-keyboard-infrastructure +plan: 02 +type: execute +wave: 2 +depends_on: [08-01] +files_modified: [n8n-workflow.json] +autonomous: true + +must_haves: + truths: + - "Tapping Start button starts a stopped container" + - "Tapping Stop button shows confirmation dialog" + - "Tapping Update button shows confirmation dialog" + - "Tapping Restart button executes restart immediately" + - "Confirming Stop/Update executes the action" + - "Cancelling returns to container submenu" + - "Confirmation expires after 30 seconds" + artifacts: + - path: "n8n-workflow.json" + provides: "Action execution routing and confirmation flow" + contains: "Route Action Type" + - path: "n8n-workflow.json" + provides: "Confirmation keyboard builder" + contains: "Build Confirmation" + key_links: + - from: "Route Callback" + to: "Route Action Type" + via: "action: callback routing" + pattern: "action:" + - from: "Route Action Type" + to: "existing container ops" + via: "start/stop/restart/update wiring" + pattern: "containers/.*/start" +--- + + +Wire action buttons to container operations and add confirmation flow for dangerous actions. + +Purpose: When users tap action buttons in the container submenu, the corresponding action executes. Stop and Update require confirmation (per user decision). Start and Restart execute immediately. + +Output: Updated n8n-workflow.json with action routing, confirmation flow, and wiring to existing container operations. + + + +@/home/luc/.claude/get-shit-done/workflows/execute-plan.md +@/home/luc/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/08-inline-keyboard-infrastructure/08-CONTEXT.md +@.planning/phases/08-inline-keyboard-infrastructure/08-RESEARCH.md +@.planning/phases/08-inline-keyboard-infrastructure/08-01-SUMMARY.md +@n8n-workflow.json + + + + + + Task 1: Route Action Callbacks to Container Operations + n8n-workflow.json + +Add routing to handle `action:{type}:{name}` callbacks and wire to existing container operations. + +1. Update "Parse Callback Data" to recognize `action:` prefix: + ```javascript + // Add to existing parsing logic + if (data.startsWith('action:')) { + const parts = data.split(':'); + return { + json: { + isAction: true, + actionType: parts[1], // start, stop, restart, update, logs + containerName: parts[2], + queryId: callbackQuery.id, + chatId: callbackQuery.message.chat.id, + messageId: callbackQuery.message.message_id + } + }; + } + ``` + +2. Add "isAction" output to "Route Callback" switch node: + - Rule: `isAction === true` -> output "action" + - This catches all action callbacks before routing by type + +3. Create Switch node "Route Action Type": + - Input: from Route Callback "action" output + - Outputs: + - "start": `actionType === 'start'` + - "restart": `actionType === 'restart'` + - "logs": `actionType === 'logs'` + - "stop": `actionType === 'stop'` (needs confirmation) + - "update": `actionType === 'update'` (needs confirmation) + +4. For immediate actions (start, restart, logs), wire to existing container operation nodes: + + **Start flow:** + - Create Code node "Prepare Start Action": + ```javascript + const { queryId, chatId, messageId, containerName } = $json; + return { + json: { + queryId, + chatId, + messageId, + containerName, + // Format for existing Start Container node + container: containerName + } + }; + ``` + - Answer callback query immediately + - Wire to existing "Start Container" HTTP Request node + - After start completes, show success message (handled in Plan 03) + + **Restart flow:** + - Similar to start, wire to existing "Restart Container" node + + **Logs flow:** + - Wire to existing "Get Logs" flow + - Logs may need special handling (send as new message, not edit) + +5. For dangerous actions (stop, update), route to confirmation builder (Task 2) + +6. Wire flows - ensuring callback is answered FIRST: + - Route Action Type (start) -> Answer Start Callback -> Prepare Start Action -> Start Container -> (completion handling in Plan 03) + - Route Action Type (restart) -> Answer Restart Callback -> Prepare Restart Action -> Restart Container -> (completion handling) + - Route Action Type (logs) -> Answer Logs Callback -> existing logs flow + - Route Action Type (stop) -> Build Stop Confirmation (Task 2) + - Route Action Type (update) -> Build Update Confirmation (Task 2) + + + 1. From container submenu, tap "Start" on a stopped container + 2. Verify: Container starts (check n8n execution or docker ps) + 3. Tap "Restart" on a running container + 4. Verify: Container restarts + 5. Tap "Logs" + 6. Verify: Logs returned (may be separate message for now) + 7. Tap "Stop" on running container + 8. Verify: Shows confirmation (not executed yet - Task 2) + + + - Start button starts containers immediately + - Restart button restarts containers immediately + - Logs button triggers log retrieval + - Stop/Update route to confirmation flow + - All callbacks answered (no loading indicator) + + + + + Task 2: Add Confirmation Flow for Dangerous Actions + n8n-workflow.json + +Add confirmation dialog for Stop and Update actions with 30-second timeout. + +1. Create Code node "Build Stop Confirmation": + ```javascript + const { queryId, chatId, messageId, containerName } = $json; + const timestamp = Math.floor(Date.now() / 1000); // Unix seconds + + const keyboard = { + inline_keyboard: [ + [ + { text: '✅ Yes, Stop', callback_data: `confirm:stop:${containerName}:${timestamp}` }, + { text: '❌ Cancel', callback_data: `cancel:${containerName}` } + ] + ] + }; + + return { + json: { + queryId, + chatId, + messageId, + text: `âš ī¸ Stop ${containerName}?\n\nThis will stop the container immediately.\n\nExpires in 30 seconds`, + reply_markup: keyboard + } + }; + ``` + +2. Create Code node "Build Update Confirmation": + ```javascript + const { queryId, chatId, messageId, containerName } = $json; + const timestamp = Math.floor(Date.now() / 1000); + + const keyboard = { + inline_keyboard: [ + [ + { text: '✅ Yes, Update', callback_data: `confirm:update:${containerName}:${timestamp}` }, + { text: '❌ Cancel', callback_data: `cancel:${containerName}` } + ] + ] + }; + + return { + json: { + queryId, + chatId, + messageId, + text: `âŦ†ī¸ Update ${containerName}?\n\nThis will pull the latest image and recreate the container.\n\nExpires in 30 seconds`, + reply_markup: keyboard + } + }; + ``` + +3. Create HTTP Request nodes to answer callback and show confirmation: + - "Answer Stop Callback" -> answerCallbackQuery + - "Show Stop Confirmation" -> editMessageText with confirmation keyboard + - Same pattern for Update + +4. Wire confirmation display: + - Route Action Type (stop) -> Answer Stop Callback -> Build Stop Confirmation -> Show Stop Confirmation + - Route Action Type (update) -> Answer Update Callback -> Build Update Confirmation -> Show Update Confirmation + +5. Update "Parse Callback Data" to handle `confirm:` and `cancel:` callbacks: + ```javascript + // confirm:stop:plex:1738595200 + if (data.startsWith('confirm:')) { + const parts = data.split(':'); + const timestamp = parseInt(parts[3]); + const currentTime = Math.floor(Date.now() / 1000); + const expired = (currentTime - timestamp) > 30; + + return { + json: { + isConfirm: true, + expired: expired, + actionType: parts[1], // stop or update + containerName: parts[2], + queryId: callbackQuery.id, + chatId: callbackQuery.message.chat.id, + messageId: callbackQuery.message.message_id + } + }; + } + + // cancel:plex + if (data.startsWith('cancel:')) { + const containerName = data.split(':')[1]; + return { + json: { + isCancel: true, + containerName: containerName, + queryId: callbackQuery.id, + chatId: callbackQuery.message.chat.id, + messageId: callbackQuery.message.message_id + } + }; + } + ``` + +6. Add "isConfirm" output to "Route Callback": + - Rule: `isConfirm === true && !expired` -> output "confirm" + - The existing "expired" output handles expired confirmations + +7. Create Switch node "Route Confirmed Action": + - Input: from Route Callback "confirm" output + - Outputs: "stop", "update" based on actionType + +8. Wire confirmed actions to actual operations: + - Route Confirmed Action (stop) -> Answer Confirm Callback -> Stop Container -> (completion in Plan 03) + - Route Confirmed Action (update) -> Answer Confirm Callback -> existing Update flow -> (completion) + +9. Handle cancel callback: + - isCancel should route back to container submenu + - Reuse/extend existing cancel handling + - On cancel: fetch container details again -> show submenu + + + 1. Tap "Stop" on a running container + 2. Verify: Confirmation dialog appears with Yes/Cancel buttons + 3. Wait 35 seconds, then tap "Yes" + 4. Verify: Shows "Confirmation expired" message + 5. Tap "Stop" again, immediately tap "Yes" + 6. Verify: Container stops + 7. Start a container, tap "Stop", tap "Cancel" + 8. Verify: Returns to container submenu (not list) + 9. Test same flow for "Update" button + + + - Stop shows confirmation dialog + - Update shows confirmation dialog + - Confirmation includes 30-second timeout warning + - Tapping "Yes" within 30s executes action + - Tapping "Yes" after 30s shows expired message + - Tapping "Cancel" returns to container submenu + + + + + + +After completing all tasks: +1. Start button works immediately (no confirmation) +2. Restart button works immediately (no confirmation) +3. Stop button shows confirmation, then executes on confirm +4. Update button shows confirmation, then executes on confirm +5. Cancel returns to container submenu +6. Expired confirmations are rejected with message +7. Logs button retrieves container logs + + + +- KEY-02 requirement met: Action buttons perform operations +- KEY-03 requirement met: Dangerous actions show confirmation +- Actions wire correctly to existing container operation nodes +- Confirmation timeout enforced at 30 seconds + + + +After completion, create `.planning/phases/08-inline-keyboard-infrastructure/08-02-SUMMARY.md` + diff --git a/.planning/phases/08-inline-keyboard-infrastructure/08-03-PLAN.md b/.planning/phases/08-inline-keyboard-infrastructure/08-03-PLAN.md new file mode 100644 index 0000000..cb55125 --- /dev/null +++ b/.planning/phases/08-inline-keyboard-infrastructure/08-03-PLAN.md @@ -0,0 +1,304 @@ +--- +phase: 08-inline-keyboard-infrastructure +plan: 03 +type: execute +wave: 3 +depends_on: [08-02] +files_modified: [n8n-workflow.json] +autonomous: false + +must_haves: + truths: + - "After action completes, message shows result with 'Back to menu' button" + - "Buttons are removed from completed action messages" + - "Update operations show progress message during execution" + - "Full keyboard flow works end-to-end" + artifacts: + - path: "n8n-workflow.json" + provides: "Completion message handlers" + contains: "Show Action Result" + - path: "n8n-workflow.json" + provides: "Progress feedback for updates" + contains: "Show Update Progress" + key_links: + - from: "container operation nodes" + to: "Show Action Result" + via: "completion flow" + pattern: "editMessageText" + - from: "Update Container flow" + to: "Show Update Progress" + via: "progress message" + pattern: "Updating" +--- + + +Add progress feedback during operations and completion messages after actions finish. + +Purpose: Users see visual feedback during operations ("Updating plex...") and final results ("plex updated") with a button to return to the menu. This completes the inline keyboard UX. + +Output: Updated n8n-workflow.json with progress and completion handlers, plus full end-to-end verification. + + + +@/home/luc/.claude/get-shit-done/workflows/execute-plan.md +@/home/luc/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/08-inline-keyboard-infrastructure/08-CONTEXT.md +@.planning/phases/08-inline-keyboard-infrastructure/08-RESEARCH.md +@.planning/phases/08-inline-keyboard-infrastructure/08-01-SUMMARY.md +@.planning/phases/08-inline-keyboard-infrastructure/08-02-SUMMARY.md +@n8n-workflow.json + + + + + + Task 1: Add Completion Messages for Quick Actions + n8n-workflow.json + +Add completion handlers that show results and "Back to menu" button after actions. + +Per user decision: Quick actions (start, stop, restart) show final result only, not progress. + +1. Create Code node "Build Action Success" that: + - Takes action result and context (chatId, messageId, containerName, actionType) + - Builds completion message based on action type + - Includes "Back to menu" button + - Removes action buttons (keyboard has only navigation) + + ```javascript + const { chatId, messageId, containerName, actionType } = $json; + + // Build success message based on action + const messages = { + start: `â–ļī¸ ${containerName} started`, + stop: `âšī¸ ${containerName} stopped`, + restart: `🔄 ${containerName} restarted` + }; + + const text = messages[actionType] || `Action completed on ${containerName}`; + + const keyboard = { + inline_keyboard: [ + [{ text: 'â—€ī¸ Back to Containers', callback_data: 'list:0' }] + ] + }; + + return { + json: { + chatId, + messageId, + text, + reply_markup: keyboard + } + }; + ``` + +2. Create HTTP Request node "Show Action Result": + - Method: POST + - URL: editMessageText endpoint + - Body: chat_id, message_id, text, parse_mode, reply_markup + +3. Wire completion after each action: + - Start Container -> Build Action Success (with actionType='start') -> Show Action Result + - Stop Container -> Build Action Success (with actionType='stop') -> Show Action Result + - Restart Container -> Build Action Success (with actionType='restart') -> Show Action Result + +4. Handle action failures: + - Create Code node "Build Action Error": + ```javascript + const { chatId, messageId, containerName, actionType, error } = $json; + + const text = `❌ Failed to ${actionType} ${containerName}\n\n${error || 'Unknown error'}`; + + const keyboard = { + inline_keyboard: [ + [{ text: '🔄 Try Again', callback_data: `action:${actionType}:${containerName}` }], + [{ text: 'â—€ī¸ Back to Containers', callback_data: 'list:0' }] + ] + }; + + return { json: { chatId, messageId, text, reply_markup: keyboard } }; + ``` + - Wire error outputs from container operations to error handler + + + 1. Start a stopped container via button + 2. Verify: After start completes, message shows "plex started" with "Back to Containers" button + 3. Stop a running container (confirm when prompted) + 4. Verify: Shows "plex stopped" with back button + 5. Restart a container + 6. Verify: Shows "plex restarted" with back button + 7. Tap "Back to Containers" + 8. Verify: Returns to container list + + + - Start shows completion message with back button + - Stop shows completion message with back button + - Restart shows completion message with back button + - Errors show retry and back buttons + - Action buttons removed after completion (only back button remains) + + + + + Task 2: Add Progress Feedback for Update Operations + n8n-workflow.json + +Add progress message for update operations (longer running than start/stop/restart). + +Per user decision: Updates show progress (simple status, not detailed steps). + +1. Create Code node "Build Update Progress" that shows in-progress state: + ```javascript + const { chatId, messageId, containerName } = $json; + + return { + json: { + chatId, + messageId, + text: `âŦ†ī¸ Updating ${containerName}...\n\nPulling latest image and recreating container.\nThis may take a few minutes.`, + reply_markup: { inline_keyboard: [] } // Remove buttons during update + } + }; + ``` + +2. Create HTTP Request "Show Update Progress": + - editMessageText with progress message + - Removes all buttons during operation + +3. Wire update flow: + - Route Confirmed Action (update) -> Answer Callback -> Build Update Progress -> Show Update Progress -> existing Update Container flow + +4. Create Code node "Build Update Success": + ```javascript + const { chatId, messageId, containerName } = $json; + + const keyboard = { + inline_keyboard: [ + [{ text: 'â—€ī¸ Back to Containers', callback_data: 'list:0' }] + ] + }; + + return { + json: { + chatId, + messageId, + text: `✅ ${containerName} updated successfully`, + reply_markup: keyboard + } + }; + ``` + +5. Wire update completion: + - After Update Container completes -> Build Update Success -> Show Action Result + +6. Handle update errors: + - Wire error path to Build Action Error with appropriate context + + + 1. Start update on a container (confirm when prompted) + 2. Verify: Message immediately changes to "Updating plex..." with no buttons + 3. Wait for update to complete + 4. Verify: Message changes to "plex updated successfully" with back button + 5. If update fails, verify error message appears with retry option + + + - Update shows progress message during execution + - Buttons removed during update (prevents duplicate actions) + - Success message shown after update completes + - Error message with retry button if update fails + + + + + Task 3: Full End-to-End Verification + + Complete inline keyboard infrastructure: + - Container list with tappable buttons + - Container submenu with action buttons + - Confirmation dialogs for dangerous actions + - Progress feedback for updates + - Completion messages with navigation + + + **Flow 1: Basic Navigation** + 1. Send "/status" to bot + 2. Verify: Inline keyboard appears with container list + 3. Tap a container name + 4. Verify: Message edits to show container details and action buttons + 5. Tap "Back to List" + 6. Verify: Returns to container list + + **Flow 2: Start Container** + 1. From container list, tap a STOPPED container + 2. Tap "Start" button + 3. Verify: Container starts, message shows "started" with back button + 4. Tap "Back to Containers" + 5. Verify: Returns to list, container now shows as Running + + **Flow 3: Stop Container (with confirmation)** + 1. Tap a RUNNING container + 2. Tap "Stop" button + 3. Verify: Confirmation dialog appears "Stop container? Yes / No" + 4. Tap "Cancel" + 5. Verify: Returns to container submenu (not list) + 6. Tap "Stop" again + 7. Tap "Yes, Stop" + 8. Verify: Container stops, message shows "stopped" with back button + + **Flow 4: Update Container (with progress)** + 1. Tap a container + 2. Tap "Update" button + 3. Verify: Confirmation dialog appears + 4. Tap "Yes, Update" + 5. Verify: Message shows "Updating..." with no buttons + 6. Wait for completion + 7. Verify: Message shows "updated successfully" with back button + + **Flow 5: Confirmation Timeout** + 1. Tap a container, tap "Stop" + 2. Wait 35 seconds + 3. Tap "Yes, Stop" + 4. Verify: Shows "Confirmation expired" message + + **Flow 6: Direct Access** + 1. Send "/status plex" (or another container name) + 2. Verify: Jumps directly to that container's submenu + + **Flow 7: Pagination (if applicable)** + 1. If you have >6 containers, verify pagination buttons work + 2. If not, verify no pagination buttons appear + + Type "approved" to mark Phase 8 complete, or describe any issues found + + + + + +After completing all tasks: +1. All KEY-01 through KEY-05 requirements met +2. Full navigation flow works (list -> submenu -> action -> result -> list) +3. Confirmations work with timeout +4. Progress shown for updates +5. Buttons removed after action completion +6. No hanging loading indicators anywhere +7. Direct access (/status name) works + + + +- KEY-01: Status command shows container list with inline action buttons +- KEY-02: Tapping action button performs start/stop/restart on container +- KEY-03: Dangerous actions (stop, update) show confirmation dialog +- KEY-04: Progress shown via message edit during operations +- KEY-05: Buttons removed after action completes (only back button remains) + + + +After completion, create `.planning/phases/08-inline-keyboard-infrastructure/08-03-SUMMARY.md` +