Files
Lucas Berger ecd02a4b0e 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 <noreply@anthropic.com>
2026-02-03 11:44:46 -05:00

318 lines
11 KiB
Markdown

---
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"
---
<objective>
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.
</objective>
<execution_context>
@/home/luc/.claude/get-shit-done/workflows/execute-plan.md
@/home/luc/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Route Action Callbacks to Container Operations</name>
<files>n8n-workflow.json</files>
<action>
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)
</action>
<verify>
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)
</verify>
<done>
- 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)
</done>
</task>
<task type="auto">
<name>Task 2: Add Confirmation Flow for Dangerous Actions</name>
<files>n8n-workflow.json</files>
<action>
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: `⚠️ <b>Stop ${containerName}?</b>\n\nThis will stop the container immediately.\n\n<i>Expires in 30 seconds</i>`,
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: `⬆️ <b>Update ${containerName}?</b>\n\nThis will pull the latest image and recreate the container.\n\n<i>Expires in 30 seconds</i>`,
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
</action>
<verify>
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
</verify>
<done>
- 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
</done>
</task>
</tasks>
<verification>
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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/08-inline-keyboard-infrastructure/08-02-SUMMARY.md`
</output>