# Startup Optimization Specification v2

## Priority

**User experience first: Input bar must be visible and available immediately on launch.**

## Problem Statement

When the application starts, there is a 2-3 second delay before the input box appears. During this time, users see only the banner and have no way to interact with the application. This creates a poor first impression and makes the application feel unresponsive.

## User Experience Goal

Users should see the input box and be able to start typing within 100-200ms of launching the application, even if backend initialization is still in progress.

## Current Startup Sequence (Verified)

```python
# kollabor/application.py start() method (lines 471-534)

1. Display banner                            # Immediate
2. Initialize LLM core service               # ~500ms-1s
   - Initialize API communication
   - Initialize provider system
   - Register hooks
   - Start background MCP discovery (already async)
   - Initialize conversation
3. Check and display provider warnings       # ~100ms
4. Initialize all plugins (sequential)       # ~500ms-1s
   - Initialize each plugin
   - Register hooks for each plugin
5. Display ready message                     # Immediate
6. Mark startup as complete                  # Immediate
7. Start render loop                         # INPUT BOX APPEARS HERE ← DELAY
8. Start input handler                       # Enters raw mode
9. Initialize script widget refresh          # Background
10. Check for first-run wizard               # Conditional
```

**Total delay before input box: 2-3 seconds**

## Architectural Constraints (Verified by Code Review)

### Critical Dependencies

1. **Render loop and input handler must start together**
   - Input handler enters raw mode (input_loop_manager.py:146)
   - Render loop requires raw mode for cursor positioning
   - Starting one without the other causes terminal state corruption

2. **Render loop depends on input_handler existing**
   - render_active_area() checks `self.input_handler` for command_mode (terminal_renderer.py:331)
   - Cannot start render loop before input_handler is created

3. **Status widgets depend on initialized services**
   - WidgetContext requires llm_service, profile_manager, agent_manager (application.py:402-410)
   - Status widget rendering will fail if these services aren't ready
   - Widget refresh scheduler must be initialized (application.py:514)

4. **Message coordinator is the safe path for initialization feedback**
   - Designed to display messages without blocking input
   - Manages writing_messages flag correctly
   - Doesn't interfere with render loop state

### What Can Change Safely

1. **Render loop and input handler can start immediately after banner**
   - No technical blocker preventing early start
   - Input handler doesn't require LLM service to start
   - Raw mode entry is independent of initialization

2. **Initialization can happen in background**
   - LLM service initialization is async
   - Plugin initialization is async
   - Can run concurrently with render loop

3. **Messages can be displayed during initialization**
   - Message coordinator is designed for this
   - Doesn't block input
   - Provides user feedback

## Solution: Early Render Loop with Message Coordinator Feedback

### Architecture

```
Phase 1: Instant UI (0-100ms)
├─ Display banner
├─ Create and start render loop + input handler (TOGETHER)
└─ Input box appears immediately

Phase 2: Background Initialization (concurrent)
├─ Display "Initializing..." via message coordinator
├─ Run initialization tasks:
│  ├─ Initialize LLM core
│  ├─ Initialize plugins
│  └─ Update progress via message coordinator
└─ User can type during this phase (input queued in LLM service)

Phase 3: Ready
├─ Clear initialization messages
├─ Display ready message via message coordinator
└─ Mark startup complete
```

### Key Design Decisions

1. **Input is available immediately but LLM processing is deferred**
   - User can type right away
   - LLM service queues messages until ready
   - No complex input blocking logic needed

2. **Use message coordinator for all initialization feedback**
   - No new state flags (initializing, loading, etc.)
   - No special render_active_area() modifications
   - Follows existing patterns

3. **Status widgets show placeholder/disabled state until ready**
   - Widget rendering checks if services are available
   - Shows "Initializing..." or similar for unavailable data
   - No crashes from null service references

4. **Render loop and input handler start together**
   - Preserves terminal mode coordination
   - No race conditions
   - Maintains existing state machine

## Implementation Plan

### Phase 1: Minimal Risk Foundation (REQUIRED FIRST)

**Goal:** Profile current startup to identify actual bottlenecks.

```python
# Add timing to each startup step
import time

async def start(self, initial_message: str | None = None) -> None:
    times = {}

    t0 = time.time()
    await self._display_banner()
    times['banner'] = time.time() - t0

    t0 = time.time()
    await self._initialize_llm_core()
    times['llm_core'] = time.time() - t0

    # ... etc for each step

    logger.info(f"Startup timing: {times}")
```

**Deliverables:**
- Timing data for each startup step
- Identification of slowest components
- Baseline metrics for optimization

**Success Criteria:**
- Know exactly where the 2-3 seconds are spent
- Identify if any single step is >500ms

### Phase 2: Early Render Loop (CORE OPTIMIZATION)

**Goal:** Show input box immediately by starting render loop early.

#### Step 1: Refactor startup sequence

```python
async def start(self, initial_message: str | None = None) -> None:
    """Start with immediate input availability."""

    # Display banner (unchanged)
    await self._display_banner()

    # START RENDER LOOP + INPUT HANDLER IMMEDIATELY
    # This makes input box appear instantly
    self.running = True
    render_task = self.create_background_task(
        self._render_loop(), "render_loop"
    )
    input_task = self.create_background_task(
        self.input_handler.start(), "input_handler"
    )

    # Wait for input handler to enter raw mode
    await asyncio.sleep(0.1)

    # Display initialization message via coordinator
    self.renderer.message_coordinator.display_message_sequence([
        {
            "role": "assistant",
            "content": "Initializing Kollabor CLI...",
            "thinking": False
        }
    ])

    # Run initialization in background (unchanged)
    await self._initialize_llm_core()
    await self._display_provider_warning()
    await self._initialize_plugins()

    # Clear initialization message and show ready
    await self._display_ready_message()

    # Mark startup complete (unchanged)
    self._startup_complete = True

    # Continue with rest of startup...
    await self.script_widget_manager.initialize_refresh_scheduler(self.event_bus)
    await self._check_first_run_wizard()

    # Wait for completion
    await asyncio.gather(render_task, input_task)
```

#### Step 2: Handle widget context gracefully

```python
# In status widgets, check if services are available

class ProfileWidget(StatusWidget):
    def render(self, context: WidgetContext) -> str:
        if not context.profile_manager:
            return "⌘ initializing..."  # Placeholder

        # Normal rendering...
        active = context.profile_manager.get_active_profile()
        return f"⌘ {active.name} {active.model}"
```

#### Step 3: Queue user input during initialization

```python
# In LLMService, check if ready before processing

async def process_user_input(self, user_input: str) -> None:
    # Wait for initialization to complete
    if not self._initialized:
        # Display message via coordinator
        self.renderer.message_coordinator.display_message_sequence([
            {
                "role": "assistant",
                "content": "Still initializing, please wait...",
                "thinking": False
            }
        ])

        # Wait for initialization with timeout
        wait_time = 0
        while not self._initialized and wait_time < 30:
            await asyncio.sleep(0.1)
            wait_time += 0.1

        if not self._initialized:
            # Give up after 30 seconds
            self.renderer.message_coordinator.display_message_sequence([
                {
                    "role": "assistant",
                    "content": "Initialization failed. Please restart the application.",
                    "thinking": False
                }
            ])
            return

    # Process normally...
```

**Deliverables:**
- Input box appears within 100-200ms
- Initialization messages displayed via message coordinator
- User can type immediately (processing deferred until ready)

**Success Criteria:**
- No duplicate input boxes
- No terminal mode artifacts
- No crashes from uninitialized services
- User can start typing immediately

### Phase 3: Progressive Feedback (POLISH)

**Goal:** Provide detailed initialization progress.

```python
async def _initialize_with_progress(self) -> None:
    """Initialize with progress updates via message coordinator."""

    steps = [
        ("Initializing LLM kollabor...", self._initialize_llm_core),
        ("Checking provider configuration...", self._display_provider_warning),
        ("Loading plugins...", self._initialize_plugins),
    ]

    for message, task in steps:
        # Update progress message
        self.renderer.message_coordinator.display_message_sequence([
            {"role": "assistant", "content": message, "thinking": False}
        ])

        # Run task
        await task()

        # Clear progress message
        # (Message coordinator handles this automatically on next message)

    # Show final ready message
    await self._display_ready_message()
```

**Deliverables:**
- Step-by-step progress during initialization
- Clear visual feedback for each phase
- User understands what's happening

**Success Criteria:**
- Users see progress updates
- Perceived startup time feels shorter
- No confusion about what's happening

## What We're NOT Doing

### ❌ Parallel Plugin Initialization

**Reason:** High risk, complex dependency management

- Plugins may depend on each other (no dependency tracking exists)
- Hook registration order matters for priority
- Event bus concurrent access not guaranteed safe
- Would require plugin architecture refactor

**Alternative:** Profile first. If plugins are slow, optimize the slow ones individually.

### ❌ Early Render Loop Without Input Handler

**Reason:** Terminal mode mismatch

- Render loop requires raw mode
- Input handler enters raw mode
- Starting them separately causes corruption

**Alternative:** Start them together (as proposed).

### ❌ New State Flags (initializing, loading, etc.)

**Reason:** Violates message coordinator pattern

- Adds complexity to state machine
- Bypasses coordinator pattern
- Increases maintenance burden

**Alternative:** Use message coordinator for all feedback.

### ❌ Complex Input Queuing

**Reason:** OS already handles this

- Terminal buffers keystrokes naturally
- Input handler receives them when ready
- No custom queuing logic needed

**Alternative:** Just defer LLM processing until ready.

## Testing Requirements

### Unit Tests

1. **Startup timing tests**
   - Verify input box appears within 200ms
   - Verify render loop starts before initialization completes
   - Verify input handler starts with render loop

2. **Message coordinator integration**
   - Verify initialization messages display correctly
   - Verify messages clear when ready
   - Verify no interference with input box rendering

3. **Service availability checks**
   - Verify widgets handle null services gracefully
   - Verify LLM service defers processing when not ready
   - Verify no crashes during initialization

### Integration Tests

1. **User input during initialization**
   - Type immediately after launch
   - Verify input appears in box
   - Verify processing waits for initialization
   - Verify message displayed if user tries to submit early

2. **Initialization failure scenarios**
   - LLM service fails to initialize
   - Plugin initialization error
   - Timeout during initialization
   - Verify graceful error handling

3. **Terminal state management**
   - Verify no cursor artifacts
   - Verify no render corruption
   - Verify resize during initialization works
   - Verify Ctrl+C during initialization works

### Manual Testing Scenarios

1. **Fast initialization (< 1s)**
   - Local API, all plugins load quickly
   - Verify no flicker
   - Verify smooth transition to ready

2. **Slow initialization (3-5s)**
   - Slow network, provider checks timeout
   - Verify progress messages appear
   - Verify user can still type
   - Verify no blocking

3. **Very slow initialization (> 10s)**
   - Network timeout, plugin hangs
   - Verify timeout handling
   - Verify error message
   - Verify app remains responsive

4. **User behavior patterns**
   - User types immediately (before ready)
   - User pastes large text (before ready)
   - User hits Ctrl+C during initialization
   - User resizes terminal during initialization

## Success Metrics

### Primary Metrics

1. **Input box visible within 200ms**
   - Measured from banner display to input box render
   - 95th percentile < 200ms

2. **User can type immediately**
   - No blocked input
   - Keystrokes appear in input box
   - Measured: time to first keystroke echo < 300ms

3. **Zero initialization crashes**
   - No AttributeError from null services
   - No terminal corruption
   - No render artifacts

### Secondary Metrics

1. **Perceived startup time reduced**
   - User survey: "App feels responsive on launch"
   - Before: 2-3s wait time
   - After: 0.2s wait time (even if initialization is same)

2. **Actual startup time maintained or reduced**
   - Total time to ready should not increase
   - Goal: maintain or reduce by 20-30%

## Rollback Plan

### Immediate Rollback (If Critical Bug)

```python
# Feature flag in config
if self.config.get("kollabor.startup.early_render_loop", False):
    # New behavior: early render loop
    await self._start_with_early_render_loop()
else:
    # Old behavior: wait for initialization
    await self._start_traditional()
```

### Gradual Rollout

1. **Week 1:** Enable for developers only (config flag)
2. **Week 2:** Enable for 10% of users (if available via telemetry)
3. **Week 3:** Enable for 50% of users
4. **Week 4:** Enable for 100% of users
5. **Week 5:** Remove feature flag, make default

### Rollback Triggers

- More than 1% crash rate increase
- User reports of duplicate input boxes
- Terminal corruption reports
- Any regression in functionality

### Rollback Procedure

1. Set config default to `early_render_loop: false`
2. Document issue in `known_issues/`
3. Create regression test
4. Fix root cause before re-enabling

## Related Files

### Core Files
- `kollabor/application.py` - Startup sequence (lines 471-534)
- `kollabor/io/terminal_renderer.py` - Render loop
- `kollabor/io/message_coordinator.py` - Message display coordination
- `kollabor/io/input_handler.py` - Input handling

### Supporting Files
- `kollabor/llm/llm_service.py` - LLM processing queue
- `kollabor/io/status/widgets.py` - Status widget rendering
- `kollabor/config/manager.py` - Feature flag support

## Risk Assessment

### Low Risk ✅
- Starting render loop + input handler early (together)
- Using message coordinator for progress feedback
- Deferring LLM processing until ready
- Widget placeholder rendering

### Medium Risk ⚠️
- Timing coordination between render loop and initialization
- First-run wizard interaction during startup
- Modal system interaction during startup

### High Risk ❌ (NOT DOING)
- Parallel plugin initialization
- Starting render loop without input handler
- Adding new state flags
- Complex input queuing logic

## Implementation Checklist

### Pre-Implementation
- [ ] Profile current startup (Phase 1)
- [ ] Review with team
- [ ] Create feature flag in config
- [ ] Write tests for new behavior

### Implementation (Phase 2)
- [ ] Refactor startup sequence
- [ ] Add service availability checks to widgets
- [ ] Add LLM processing deferral
- [ ] Test manually with all scenarios
- [ ] Update documentation

### Post-Implementation
- [ ] Monitor crash rates
- [ ] Collect user feedback
- [ ] Measure startup metrics
- [ ] Document lessons learned

## Open Questions

1. **Should initialization progress be displayed in status area instead of message coordinator?**
   - Pro: Doesn't clutter message history
   - Con: Status area might not be initialized yet
   - Decision: Start with message coordinator, move to status area in Phase 3

2. **Should we show a spinner during initialization?**
   - Pro: Clear visual feedback
   - Con: Adds complexity
   - Decision: Start with text messages, add spinner in Phase 3

3. **How long should we wait before timing out initialization?**
   - Current: No timeout
   - Proposed: 30 seconds
   - Decision: Make configurable, default 30s

4. **Should first-run wizard wait for full initialization?**
   - Pro: Simpler logic
   - Con: Delays wizard
   - Decision: Wait for initialization before showing wizard

## Approval Required

This spec requires approval from:
- [ ] User (for UX approach)
- [ ] Code review (for architectural approach)
- [ ] Testing (for test coverage)

Once approved, proceed with Phase 1 (profiling) before implementing Phase 2.
