/ app / spec / 02_input_interactions.md
02_input_interactions.md
  1  # Kamaji TUI - Input Handling & User Interactions Specification
  2  
  3  **Version:** 1.0
  4  **Last Updated:** 2025-11-01
  5  **Status:** Complete Documentation
  6  
  7  ---
  8  
  9  ## Table of Contents
 10  
 11  1. [Overview](#overview)
 12  2. [Input Architecture](#input-architecture)
 13  3. [Priority Hierarchy](#priority-hierarchy)
 14  4. [Keyboard Interactions](#keyboard-interactions)
 15  5. [Mouse Interactions](#mouse-interactions)
 16  6. [Mode-Specific Behaviors](#mode-specific-behaviors)
 17  7. [Text Input & Processing](#text-input--processing)
 18  8. [Autocomplete System](#autocomplete-system)
 19  9. [State Transitions](#state-transitions)
 20  10. [Command Processing](#command-processing)
 21  11. [Agent Mentions & Switching](#agent-mentions--switching)
 22  12. [Input Validation](#input-validation)
 23  
 24  ---
 25  
 26  ## Overview
 27  
 28  The Kamaji TUI implements a layered input handling system with clear priority hierarchies and mode-based routing. All keyboard and mouse input flows through the `InputHandler` component, which dispatches events based on the current UI state.
 29  
 30  ### Core Components
 31  
 32  - **InputHandler** (`input.go`): Central dispatcher for all input events
 33  - **IntegratedTUIModel.Update()** (`integrated.go`): Main event loop handler
 34  - **CommandPalette** (`palette.go`): Command search and execution overlay
 35  - **PermissionDialog** (`permissions.go`): Tool permission request overlay
 36  - **AgentSelector** (`agent_selector.go`): Agent selection menu
 37  - **AutocompleteState** (`autocomplete.go`): @ mention autocomplete system
 38  
 39  ---
 40  
 41  ## Input Architecture
 42  
 43  ### Event Flow
 44  
 45  ```
 46  tea.Msg
 47 48  IntegratedTUIModel.Update()
 49 50      ├─ tea.KeyMsg → InputHandler.HandleKeyMsg()
 51      ├─ tea.MouseMsg → InputHandler.HandleMouseMsg()
 52      ├─ tea.WindowSizeMsg → [resize handling]
 53      ├─ responseMsg → [LLM response handling]
 54      ├─ streamStartMsg → [streaming start]
 55      ├─ streamChunkMsg → [streaming chunk]
 56      ├─ PermissionResponseMsg → [permission handling]
 57      └─ [other custom messages]
 58  ```
 59  
 60  ### Input Handler Routing
 61  
 62  `HandleKeyMsg()` routes inputs based on priority:
 63  
 64  1. **Permission Dialog** (highest priority)
 65  2. **Agent Menu** (navigation overlay)
 66  3. **Command Palette** (command search)
 67  4. **Global Shortcuts** (Ctrl+P, Ctrl+A, Ctrl+S, Ctrl+T, Ctrl+M)
 68  5. **Main Input** (chat interface)
 69  
 70  ---
 71  
 72  ## Priority Hierarchy
 73  
 74  ### Input Priority Levels (Highest to Lowest)
 75  
 76  1. **Permission Dialog Visible**
 77     - Captures ALL input when visible
 78     - Keys: left, right, h, l, tab, enter, a, s, d, esc
 79     - Blocks all other input processing
 80  
 81  2. **Agent Menu Open** (`m.showAgentMenu == true`)
 82     - Captures: up, down, k, j, enter, esc
 83     - Prevents main input and global shortcuts
 84  
 85  3. **Command Palette Open** (`m.commandPalette.IsVisible()`)
 86     - Captures: esc, ctrl+c, q, up, down, k, j, enter, backspace, printable chars
 87     - Prevents main input (but NOT permission dialog)
 88  
 89  4. **Global Shortcuts** (when palette closed and not loading)
 90     - Ctrl+P, Ctrl+A, Ctrl+S, Ctrl+T, Ctrl+M
 91     - Active in main mode only
 92  
 93  5. **Main Input**
 94     - Tab (autocomplete), Ctrl+C (quit), Enter (send), regular typing
 95     - Only active when no overlays are open
 96  
 97  ---
 98  
 99  ## Keyboard Interactions
100  
101  ### Global Hotkeys (Main Mode Only)
102  
103  | Key | Action | Condition | File | Line |
104  |-----|--------|-----------|------|------|
105  | `Ctrl+P` | Open command palette | `!m.commandPalette.IsVisible() && !m.loading` | `input.go` | 33 |
106  | `Ctrl+A` | Open agent menu | `!m.commandPalette.IsVisible() && !m.loading` | `input.go` | 38 |
107  | `Ctrl+T` | Show tools list | `!m.commandPalette.IsVisible() && !m.loading` | `input.go` | 45 |
108  | `Ctrl+M` | Show MCP info | `!m.commandPalette.IsVisible() && !m.loading` | `input.go` | 51 |
109  | `Ctrl+S` | Toggle sidebar visibility | `!m.commandPalette.IsVisible() && !m.loading` | `input.go` | 54-68 |
110  | `Ctrl+C` | Quit application | Main input mode | `input.go` | 183-184 |
111  
112  **Note:** Ctrl+A appears twice in code (lines 38 and 48) - second instance is unreachable due to first check.
113  
114  ### Command Palette Mode
115  
116  | Key | Action | Effect | File | Line |
117  |-----|--------|--------|------|------|
118  | `esc` | Close palette | Hides palette, returns to main | `input.go` | 131-133 |
119  | `ctrl+c` | Close palette | Same as esc | `input.go` | 131-133 |
120  | `q` | Close palette | Same as esc | `input.go` | 131-133 |
121  | `up` | Move selection up | Cycles through filtered commands | `input.go` | 134-137 |
122  | `k` | Move selection up | Vim-style navigation | `input.go` | 134-137 |
123  | `down` | Move selection down | Cycles through filtered commands | `input.go` | 138-141 |
124  | `j` | Move selection down | Vim-style navigation | `input.go` | 138-141 |
125  | `enter` | Execute command | Executes selected command, closes palette | `input.go` | 140-146 |
126  | `backspace` | Remove character from search | Updates filter | `input.go` | 147-150 |
127  | `[printable]` | Add to search query | Filters commands (chars `' '` to `'~'`) | `input.go` | 152-154 |
128  
129  **Palette Navigation Logic:**
130  - `MoveUp()`: `selected = (selected - 1 + len(filtered)) % len(filtered)` (wraps around)
131  - `MoveDown()`: `selected = (selected + 1) % len(filtered)` (wraps around)
132  
133  ### Agent Menu Mode
134  
135  | Key | Action | Effect | File | Line |
136  |-----|--------|--------|------|------|
137  | `up` | Move selection up | Selects previous agent in list | `input.go` | 96-98 |
138  | `k` | Move selection up | Vim-style navigation | `input.go` | 96-98 |
139  | `down` | Move selection down | Selects next agent in list | `input.go` | 99-101 |
140  | `j` | Move selection down | Vim-style navigation | `input.go` | 99-101 |
141  | `enter` | Select agent | Activates agent, shows system message, closes menu | `input.go` | 102-120 |
142  | `esc` | Close menu | Returns to main input | `input.go` | 121-123 |
143  
144  **Agent Selection Logic:**
145  - Scrolling viewport implemented in `AgentSelector` with `scrollOffset`
146  - Visible cards calculated based on terminal height: `visibleCards = (height - 5) / 9`
147  - Auto-scrolls when selection moves outside visible area
148  - Resets to top (`selectedIndex = 0`, `scrollOffset = 0`) when opened
149  
150  ### Permission Dialog Mode
151  
152  | Key | Action | Effect | File | Line |
153  |-----|--------|--------|------|------|
154  | `left` | Move selection left | Previous option (wraps) | `permissions.go` | 99-100 |
155  | `h` | Move selection left | Vim-style | `permissions.go` | 99-100 |
156  | `right` | Move selection right | Next option (wraps) | `permissions.go` | 101-102 |
157  | `l` | Move selection right | Vim-style | `permissions.go` | 101-102 |
158  | `tab` | Move selection right | Same as right | `permissions.go` | 101-102 |
159  | `enter` | Confirm selection | Executes selected option, closes dialog | `permissions.go` | 103-104 |
160  | `a` / `A` | Allow | Directly selects "Allow" | `permissions.go` | 105-106 |
161  | `s` / `S` | Allow for session | Directly selects "Allow for Session" | `permissions.go` | 107-108 |
162  | `d` / `D` | Deny | Directly selects "Deny" | `permissions.go` | 109-110 |
163  | `esc` | Deny | Same as deny | `permissions.go` | 109-110 |
164  
165  **Options:**
166  - 0: Allow (permanently)
167  - 1: Allow for Session (until app closes)
168  - 2: Deny
169  
170  ### Main Input Mode
171  
172  | Key | Action | Effect | File | Line |
173  |-----|--------|--------|------|------|
174  | `Tab` | Autocomplete agent | Completes `@agent` mentions | `input.go` | 162-180 |
175  | `Ctrl+C` | Quit application | Exits TUI | `input.go` | 183-184 |
176  | `Enter` | Send message | Processes input, sends to LLM | `input.go` | 185-204 |
177  | `[any]` | Type character | Updates textarea | `input.go` | 207-209 |
178  
179  **Textarea Keybindings:**
180  The textarea component has its own internal keybindings (from charmbracelet/bubbles):
181  - Arrow keys: Navigate cursor
182  - Home/End: Beginning/end of line
183  - Backspace/Delete: Character deletion
184  - Ctrl+K: Delete to end of line
185  - Ctrl+U: Delete entire line
186  
187  ---
188  
189  ## Mouse Interactions
190  
191  ### Mouse Event Handling
192  
193  | Event | Action | Effect | File | Line |
194  |-------|--------|--------|------|------|
195  | `MouseWheelUp` | Scroll viewport up | Scrolls chat history up 3 lines | `input.go` | 83-85 |
196  | `MouseWheelDown` | Scroll viewport down | Scrolls chat history down 3 lines | `input.go` | 86-88 |
197  
198  **Note:** No click interactions implemented. Only scroll wheel is handled.
199  
200  ---
201  
202  ## Mode-Specific Behaviors
203  
204  ### 1. Permission Dialog Mode
205  
206  **Entry:** When `m.permissionDialog.Show(request)` is called
207  **Exit:** After user responds (Allow/Session/Deny)
208  **Priority:** HIGHEST - blocks all other input
209  
210  **State:**
211  - `visible`: boolean flag
212  - `selectedOption`: 0-2 (Allow/Session/Deny)
213  - `request`: PermissionRequest object
214  
215  **Behavior:**
216  - Captures all keyboard input
217  - Horizontal navigation (left/right/h/l/tab)
218  - Direct hotkey selection (a/s/d)
219  - Enter confirms, esc denies
220  - Sends `PermissionResponseMsg` on exit
221  
222  ### 2. Agent Menu Mode
223  
224  **Entry:** `Ctrl+A` pressed in main mode
225  **Exit:** Agent selected (Enter) or cancelled (Esc)
226  **State Flag:** `m.showAgentMenu`
227  
228  **State:**
229  - `selectedIndex`: Currently highlighted agent
230  - `scrollOffset`: First visible agent index
231  - `visibleCards`: Number of agents fitting on screen
232  
233  **Behavior:**
234  - Vertical navigation (up/down/j/k)
235  - Auto-scrolling with viewport management
236  - Resets to top on open (`agentSelector.Reset()`)
237  - On selection:
238    - Sets `m.selectedAgent`
239    - Adds system message with agent info
240    - Updates viewport and scrolls to bottom
241    - Sets `m.showAgentMenu = false`
242  
243  ### 3. Command Palette Mode
244  
245  **Entry:** `Ctrl+P` pressed in main mode
246  **Exit:** Command executed (Enter) or cancelled (Esc/Ctrl+C/q)
247  **State Method:** `m.commandPalette.IsVisible()`
248  
249  **State:**
250  - `query`: Search string
251  - `filtered`: Commands matching query
252  - `selected`: Index in filtered list
253  - `visible`: Display flag
254  
255  **Behavior:**
256  - Real-time search filtering
257  - Vertical navigation (up/down/j/k)
258  - Backspace removes from query
259  - Printable chars add to query
260  - Filter updates trigger `selected = 0` reset
261  - Enter executes `handlePaletteCommand(cmd.ID)`
262  
263  **Commands Available:**
264  | ID | Name | Category | Description |
265  |----|------|----------|-------------|
266  | `clear` | Sweep Clean | core | Clear messages |
267  | `tools` | Inspect Tools | tools | Show tools list |
268  | `agents` | Spirit Agents | tools | Show agents info |
269  | `mcp` | MCP Servers | tools | Show MCP status |
270  | `help` | Ancient Scrolls | settings | Show help text |
271  | `consciousness` | Consciousness Status | consciousness | View consciousness metrics |
272  | `thoughts` | Recent Thoughts | consciousness | Show thought stream |
273  | `personality` | Personality Profile | consciousness | View trait evolution |
274  | `memory` | Memory Crystals | tools | View message count |
275  | `quit` | Return Upstairs | settings | Exit app |
276  | `provider:*` | Provider switch | provider | Switch LLM provider |
277  
278  ### 4. Main Input Mode
279  
280  **Entry:** Default mode when no overlays active
281  **Exit:** When overlay opened
282  **Condition:** `!m.commandPalette.IsVisible() && !m.showAgentMenu && !m.permissionDialog.IsVisible()`
283  
284  **State:**
285  - `m.textarea`: Bubble tea textarea component
286  - `m.loading`: Prevents input when waiting for response
287  - `m.messages`: Message history
288  
289  **Behavior:**
290  - Tab autocomplete for `@agent` mentions
291  - Enter sends message (if non-empty)
292  - Ctrl+C quits
293  - All other keys update textarea
294  - Message sending triggers:
295    - `handleAgentMention()` - checks for `@agent`
296    - Adds user message to history
297    - Clears textarea
298    - Updates viewport to bottom
299    - Sets `m.loading = true`
300    - Starts bottom animation
301    - Returns `sendRequest(input)` command
302  
303  ---
304  
305  ## Text Input & Processing
306  
307  ### Input Field Behavior
308  
309  **Component:** `github.com/charmbracelet/bubbles/textarea`
310  
311  **Placeholder Text:**
312  `"Speak to the furnace spirit... (type @ for agents)"`
313  
314  **Display Logic:**
315  - When empty: Shows colored flame animation
316  - When typing: Shows textarea with text
317  - `@ mentions`: Shows autocomplete dropdown
318  
319  **Message Validation:**
320  ```go
321  input := strings.TrimSpace(m.textarea.Value())
322  if input == "" {
323      return m, nil  // Reject empty messages
324  }
325  ```
326  
327  **Processing Flow:**
328  1. User types in textarea
329  2. Tab triggers autocomplete (if `@` present)
330  3. Enter sends message:
331     - Trim whitespace
332     - Check for empty (reject if empty)
333     - Process `@agent` mentions
334     - Reset textarea
335     - Add to message history
336     - Update viewport
337     - Set loading state
338     - Send LLM request
339  
340  ### Message Sending
341  
342  **Trigger:** Enter key in main mode
343  **File:** `input.go` lines 185-204
344  **File:** `integrated.go` lines 700-733
345  
346  **Steps:**
347  1. Trim input: `strings.TrimSpace(m.textarea.Value())`
348  2. Validate non-empty
349  3. Check for agent mentions: `handleAgentMention(m, input)`
350  4. Reset textarea: `m.textarea.Reset()`
351  5. Append user message with timestamp
352  6. Update viewport: `m.viewport.SetContent(m.renderMessages())`
353  7. Scroll to bottom: `m.viewport.GotoBottom()`
354  8. Set loading: `m.loading = true`
355  9. Start animation: `m.bottomAnimation.Start()`
356  10. Return send command: `m.sendRequest(input)`
357  
358  **LLM Request:**
359  - Builds system context from selected agent
360  - Adds tool descriptions
361  - Combines: `systemContext + toolContext + "User: " + input`
362  - Attempts streaming: `m.llm.CallStream(ctx, fullPrompt)`
363  - Falls back to regular call if streaming fails
364  - Returns `streamStartMsg` or `responseMsg`
365  
366  ---
367  
368  ## Autocomplete System
369  
370  ### @ Mention Autocomplete
371  
372  **Component:** `AutocompleteState` (`autocomplete.go`)
373  **Trigger:** Typing `@` followed by characters
374  **Completion Key:** `Tab`
375  
376  ### Autocomplete Logic
377  
378  **Activation:**
379  ```go
380  if strings.HasPrefix(word, "@") {
381      prefix := strings.ToLower(word[1:])
382      allAgents := []string{"Kamaji", "Hayao", "Chihiro", "Moe", "Wayne"}
383  
384      // Find first matching agent
385      for _, agent := range allAgents {
386          if prefix == "" || strings.HasPrefix(strings.ToLower(agent), prefix) {
387              words[i] = "@" + agent
388              m.textarea.SetValue(strings.Join(words, " ") + " ")
389              return m, nil
390          }
391      }
392  }
393  ```
394  
395  **Available Agents:**
396  - Kamaji (default)
397  - Hayao
398  - Chihiro
399  - Moe
400  - Wayne
401  
402  **Matching:**
403  - Case-insensitive prefix matching
404  - Empty prefix matches first agent (Kamaji)
405  - Partial typing filters: `@m` → Moe, `@mo` → Moe, `@h` → Hayao
406  
407  **Display:**
408  1. **Inline Completion:** Grey text showing remainder of match
409     ```
410     @mo[e]  ← grey "e"
411     ```
412  
413  2. **Dropdown:** List of all matches (if >1)
414     ```
415       @Kamaji
416       @Hayao
417       @Chihiro
418       @Moe     ← highlighted
419       @Wayne
420     ```
421  
422  **Completion Behavior:**
423  - Tab replaces word with full agent name
424  - Adds trailing space after completion
425  - Sets textarea value directly
426  - Example: `"Tell @m"` + Tab → `"Tell @Moe "`
427  
428  ### AutocompleteState Methods
429  
430  | Method | Purpose | File | Line |
431  |--------|---------|------|------|
432  | `Update(text, agents)` | Updates matches based on current text | `autocomplete.go` | 22-51 |
433  | `GetInlineCompletion()` | Returns grey completion text | `autocomplete.go` | 54-66 |
434  | `GetDropdown()` | Returns dropdown list view | `autocomplete.go` | 69-84 |
435  | `Complete(originalText)` | Returns text with completion applied | `autocomplete.go` | 87-100 |
436  | `MoveUp()` | Previous match in dropdown | `autocomplete.go` | 103-110 |
437  | `MoveDown()` | Next match in dropdown | `autocomplete.go` | 113-120 |
438  
439  **Note:** Dropdown navigation (MoveUp/MoveDown) exists but is NOT connected to keyboard input in main code.
440  
441  ---
442  
443  ## State Transitions
444  
445  ### State Diagram
446  
447  ```
448  ┌──────────────┐
449  │  Main Input  │ ◄─────────────────────────────┐
450  └───┬──────────┘                                │
451      │                                           │
452      │ Ctrl+P                                    │ esc/enter
453      ├─────────────►┌──────────────────┐         │
454      │              │ Command Palette  ├─────────┤
455      │              └──────────────────┘         │
456      │                                           │
457      │ Ctrl+A                                    │
458      ├─────────────►┌──────────────────┐         │
459      │              │   Agent Menu     ├─────────┤
460      │              └──────────────────┘         │
461      │                                           │
462      │ Tool Request                              │
463      └─────────────►┌──────────────────┐         │
464                     │ Permission Dialog├─────────┘
465                     └──────────────────┘
466                      (highest priority)
467  ```
468  
469  ### State Variables
470  
471  | Variable | Type | Purpose | File |
472  |----------|------|---------|------|
473  | `m.showAgentMenu` | bool | Agent menu visible | `integrated.go` |
474  | `m.commandPalette.visible` | bool | Palette visible | `palette.go` |
475  | `m.permissionDialog.visible` | bool | Dialog visible | `permissions.go` |
476  | `m.loading` | bool | Waiting for LLM | `integrated.go` |
477  | `m.streaming` | bool | LLM streaming active | `integrated.go` |
478  | `m.sidebarVisible` | bool | Sidebar shown | `integrated.go` |
479  
480  ### Loading State
481  
482  **Purpose:** Prevent input during LLM processing
483  **Set:** On message send
484  **Cleared:** On response complete, stream complete, or error
485  
486  **Effect:**
487  - Disables global shortcuts (Ctrl+P, Ctrl+A, etc.)
488  - Shows thinking spinner in status bar
489  - Shows bottom loading animation
490  - Main input still accepts typing (not blocked)
491  
492  ---
493  
494  ## Command Processing
495  
496  ### Command Palette Commands
497  
498  **Handler:** `handlePaletteCommand(commandID string)` (`integrated.go` line 295)
499  
500  #### Core Commands
501  
502  | ID | Action | Implementation |
503  |----|--------|----------------|
504  | `clear` | Clear messages | `m.messages = []Message{}`, shows welcome |
505  | `tools` | Show tools | Calls `handleDirectAction("tools")` |
506  | `agents` | Show agents | Calls `handleDirectAction("agents")` |
507  | `mcp` | Show MCP | Calls `handleDirectAction("mcp")` |
508  | `help` | Show help | Appends multi-line help text to messages |
509  | `quit` | Exit | Returns `tea.Quit` |
510  
511  #### Consciousness Commands
512  
513  | ID | Action | Implementation |
514  |----|--------|----------------|
515  | `consciousness` | Status | Gets `m.consciousness.GetStatus()`, shows in message |
516  | `thoughts` | Recent thoughts | Gets `GetRecentThoughts(10)`, shows in message |
517  | `personality` | Personality | Gets `GetPersonalityDescription()`, shows in message |
518  | `memory` | Memory info | Shows message count: `len(m.messages)` |
519  
520  #### Provider Commands
521  
522  **Pattern:** `provider:*`
523  
524  | ID | Provider | Implementation |
525  |----|----------|----------------|
526  | `provider:q` | Amazon Q | Calls `switchProvider("q")` |
527  | `provider:ollama` | Ollama | Calls `switchProvider("ollama")` |
528  | `provider:openai` | OpenAI | Calls `switchProvider("openai")` |
529  | `provider:anthropic` | Anthropic | Calls `switchProvider("anthropic")` |
530  
531  **Switch Logic:**
532  ```go
533  if strings.HasPrefix(commandID, "provider:") {
534      provider := strings.TrimPrefix(commandID, "provider:")
535      return m.switchProvider(provider)
536  }
537  ```
538  
539  ### Direct Actions (Hotkeys)
540  
541  **Handler:** `handleDirectAction(action string)` (`integrated.go` line 564)
542  
543  **Actions:**
544  
545  1. **"tools"** - Shows formatted tools list:
546     - File Operations (5)
547     - Editing (2)
548     - Shell (1)
549     - Search & Discovery (4)
550     - Web (2)
551     - Enhanced Listing (2)
552     - Git (4)
553     - Total: 20 tools
554  
555  2. **"agents"** - Shows agent status:
556     - Primary agent info (provider, model)
557     - Specialized agents list
558     - Agent features
559     - All agents ready message
560  
561  3. **"mcp"** - Shows MCP status:
562     - Status: Not yet implemented
563     - Coming soon list
564     - Planned features
565  
566  **All actions:** Append system message and update viewport.
567  
568  ---
569  
570  ## Agent Mentions & Switching
571  
572  ### Mention Detection
573  
574  **Handler:** `handleAgentMention(m, input)` (`input.go` line 214)
575  **Trigger:** Before sending message
576  **Pattern:** `@agentname` anywhere in input
577  
578  **Implementation:**
579  ```go
580  words := strings.Fields(input)
581  for _, word := range words {
582      if strings.HasPrefix(word, "@") {
583          agentName := strings.ToLower(word[1:])
584          switch agentName {
585          case "moe":
586              m.switchToAgent("Moe")
587          case "kamaji":
588              m.switchToAgent("Kamaji")
589          case "hayao":
590              m.switchToAgent("Hayao")
591          case "chihiro":
592              m.switchToAgent("Chihiro")
593          case "wayne":
594              m.switchToAgent("Wayne")
595          }
596      }
597  }
598  ```
599  
600  **Behavior:**
601  - Case-insensitive matching
602  - Processes ALL mentions in input
603  - Last mention wins if multiple
604  - Silent switch (no feedback to user)
605  - Only processes hardcoded list
606  
607  ### Agent Switching
608  
609  **Method:** `switchToAgent(name string)`
610  **Effect:**
611  - Looks up agent in `m.availableAgents`
612  - Sets `m.selectedAgent`
613  - Updates system prompt for LLM
614  - Next message uses new agent's personality
615  
616  **Agent Context:**
617  Built in `getAgentSystemContext()` (`integrated.go` line 761):
618  - Agent name and personality name
619  - Traits list
620  - Tone and approach
621  - Specialties
622  - Capabilities summary
623  - Special instructions per agent (Prodigy, Kamaji, Moe)
624  
625  ---
626  
627  ## Input Validation
628  
629  ### Message Validation
630  
631  **Location:** `input.go` line 186-189
632  
633  ```go
634  input := strings.TrimSpace(m.textarea.Value())
635  if input == "" {
636      return m, nil  // Reject and do nothing
637  }
638  ```
639  
640  **Rules:**
641  - Trim leading/trailing whitespace
642  - Reject if empty after trim
643  - No length limits enforced
644  - No character filtering
645  
646  ### Search Query Validation (Palette)
647  
648  **Location:** `input.go` line 152
649  
650  ```go
651  if len(msg.String()) == 1 && msg.String() >= " " && msg.String() <= "~" {
652      m.commandPalette.AddToQuery(msg.String())
653  }
654  ```
655  
656  **Rules:**
657  - Only single printable ASCII characters
658  - Range: space (0x20) to tilde (0x7E)
659  - Rejects control chars, unicode, multi-byte
660  
661  ### Command ID Validation
662  
663  **Location:** `integrated.go` line 430-434
664  
665  ```go
666  if strings.HasPrefix(commandID, "provider:") {
667      provider := strings.TrimPrefix(commandID, "provider:")
668      return m.switchProvider(provider)
669  }
670  ```
671  
672  **Pattern Matching:**
673  - Provider commands: `provider:*`
674  - Other commands: direct ID match
675  - No input sanitization on IDs
676  
677  ---
678  
679  ## Additional Technical Details
680  
681  ### Viewport Scrolling
682  
683  **Component:** `github.com/charmbracelet/bubbles/viewport`
684  
685  **Auto-scroll Triggers:**
686  - Message received: `m.viewport.GotoBottom()`
687  - User message sent: `m.viewport.GotoBottom()`
688  - Command executed: `m.viewport.GotoBottom()`
689  
690  **Manual Scroll:**
691  - Mouse wheel up: `viewport.LineUp(3)`
692  - Mouse wheel down: `viewport.LineDown(3)`
693  - No keyboard scroll controls
694  
695  ### Sidebar Scroll (Panel)
696  
697  **Component:** `SidebarPanel` (`panel.go`)
698  
699  **Scroll State:**
700  - `scrollY`: Current scroll position
701  - `maxScroll`: Maximum scroll allowed
702  
703  **Methods:**
704  - `ScrollUp()`: Decrements `scrollY` if > 0
705  - `ScrollDown()`: Increments `scrollY` if < `maxScroll`
706  
707  **Note:** Scroll methods exist but are NOT connected to input handlers (no keybindings).
708  
709  ### Window Resize Handling
710  
711  **Event:** `tea.WindowSizeMsg`
712  **Handler:** `integrated.go` line 112-143
713  
714  **Updates:**
715  - `m.width`, `m.height`
716  - Sidebar width (adaptive: 20-35 based on terminal width)
717  - Main content width
718  - Sidebar size: `m.sidebar.SetSize()`
719  - Bottom animation width
720  - Permission dialog size
721  - Viewport dimensions
722  - Textarea width
723  
724  **Sidebar Width Logic:**
725  ```go
726  if msg.Width >= 90 { m.sidebarWidth = 35 }
727  else if msg.Width >= 70 { m.sidebarWidth = 30 }
728  else if msg.Width >= 55 { m.sidebarWidth = 25 }
729  else { m.sidebarWidth = 20 }
730  ```
731  
732  ### Animation Handling
733  
734  **Tick Messages:**
735  - `tickMsg`: Thinking spinner (80ms interval)
736  - `bottomAnimTickMsg`: Bottom loading animation
737  - `flameTickMsg`: Flame animation
738  
739  **None affect input handling** - animations are visual only.
740  
741  ---
742  
743  ## Summary of All Input Bindings
744  
745  ### Complete Key Binding Table
746  
747  | Mode | Key(s) | Action | Priority |
748  |------|--------|--------|----------|
749  | **ANY** | (varies) | Permission dialog | 1 (HIGHEST) |
750  | **Agent Menu** | up, k | Previous agent | 2 |
751  | **Agent Menu** | down, j | Next agent | 2 |
752  | **Agent Menu** | enter | Select agent | 2 |
753  | **Agent Menu** | esc | Cancel | 2 |
754  | **Palette** | esc, ctrl+c, q | Close palette | 3 |
755  | **Palette** | up, k | Previous command | 3 |
756  | **Palette** | down, j | Next command | 3 |
757  | **Palette** | enter | Execute command | 3 |
758  | **Palette** | backspace | Delete search char | 3 |
759  | **Palette** | [printable] | Add to search | 3 |
760  | **Permission** | left, h | Previous option | 1 |
761  | **Permission** | right, l, tab | Next option | 1 |
762  | **Permission** | enter | Confirm selection | 1 |
763  | **Permission** | a, A | Allow directly | 1 |
764  | **Permission** | s, S | Allow for session | 1 |
765  | **Permission** | d, D, esc | Deny | 1 |
766  | **Main** | ctrl+p | Open palette | 4 |
767  | **Main** | ctrl+a | Open agent menu | 4 |
768  | **Main** | ctrl+t | Show tools | 4 |
769  | **Main** | ctrl+m | Show MCP | 4 |
770  | **Main** | ctrl+s | Toggle sidebar | 4 |
771  | **Main** | tab | Autocomplete @agent | 5 |
772  | **Main** | ctrl+c | Quit | 5 |
773  | **Main** | enter | Send message | 5 |
774  | **Main** | (any) | Type in textarea | 5 |
775  | **Viewport** | Mouse Wheel Up | Scroll up 3 lines | N/A |
776  | **Viewport** | Mouse Wheel Down | Scroll down 3 lines | N/A |
777  
778  ---
779  
780  ## File Reference Index
781  
782  | File | Purpose | Key Components |
783  |------|---------|----------------|
784  | `input.go` | Central input dispatcher | `InputHandler`, `HandleKeyMsg`, `HandleMouseMsg` |
785  | `integrated.go` | Main event loop | `Update()`, message handlers, command execution |
786  | `palette.go` | Command search | `CommandPalette`, command list, filtering |
787  | `permissions.go` | Permission requests | `PermissionDialog`, option selection |
788  | `agent_selector.go` | Agent menu | `AgentSelector`, card rendering, scrolling |
789  | `autocomplete.go` | @ mention completion | `AutocompleteState`, matching logic |
790  | `panel.go` | Sidebar panel | `SidebarPanel` (scroll methods unused) |
791  
792  ---
793  
794  ## Known Limitations & Notes
795  
796  1. **Duplicate Ctrl+A Binding:** Lines 38 and 48 in `input.go` both check for Ctrl+A. Second check is unreachable.
797  
798  2. **Autocomplete Dropdown Navigation:** `MoveUp()`/`MoveDown()` exist but are not wired to keyboard input. Only Tab completion works.
799  
800  3. **Sidebar Scroll:** `ScrollUp()`/`ScrollDown()` methods exist but have no keybindings.
801  
802  4. **Loading State Input:** Global shortcuts disabled during loading, but textarea still accepts typing.
803  
804  5. **Agent List Hardcoded:** Available agents for @ completion are hardcoded, not dynamic from `m.availableAgents`.
805  
806  6. **No Click Support:** Mouse handling only supports scroll wheel, no click events.
807  
808  7. **No Multi-line Input:** Textarea is single-height, no Shift+Enter for newlines.
809  
810  8. **Command Palette Filter:** Case-insensitive search across name, description, and ID.
811  
812  9. **Permission Dialog:** Always appears as overlay, no queue system for multiple requests.
813  
814  10. **Viewport Auto-scroll:** No way to disable auto-scroll to bottom on new messages.
815  
816  ---
817  
818  **End of Specification**