|
| 1 | +# Implementation Slice: Hook Event Display |
| 2 | + |
| 3 | +> **Status:** PROPOSED |
| 4 | +> **Date:** 2026-03-21 |
| 5 | +> **Priority:** P0 (First testable user experience) |
| 6 | +
|
| 7 | +## Executive Summary |
| 8 | + |
| 9 | +This implementation slice delivers **one complete end-to-end flow**: Claude Code session events flow from hooks to mobile app and render as OpenCode-style tool cards. This is the highest-value slice because: |
| 10 | + |
| 11 | +1. **Primary integration path** - Hooks are the documented way to observe Claude Code sessions |
| 12 | +2. **Minimal dependencies** - No Agent SDK API key required for observation mode |
| 13 | +3. **Immediate verification** - User can see tool_use/tool_result events appear in real-time |
| 14 | +4. **Architecture-preserving** - Agent-agnostic event types with Claude-specific adapters |
| 15 | + |
| 16 | +--- |
| 17 | + |
| 18 | +## Scope Definition |
| 19 | + |
| 20 | +### In Scope |
| 21 | + |
| 22 | +| Component | Scope | Lines Changed (Est.) | |
| 23 | +|-----------|-------|---------------------| |
| 24 | +| **Mobile: Event State** | Riverpod providers for incoming `claude_event` messages | ~200 | |
| 25 | +| **Mobile: Tool Card Widget** | OpenCode-style card for tool_use/tool_result | ~300 | |
| 26 | +| **Mobile: Event Timeline** | Scrolling list of events with timestamps | ~150 | |
| 27 | +| **Bridge: Protocol Verification** | End-to-end test for hook → websocket flow | ~100 | |
| 28 | +| **Docs: Setup Guide** | How to configure Claude Code hooks for ReCursor | ~100 | |
| 29 | + |
| 30 | +**Total estimated:** ~850 lines across 8-10 files |
| 31 | + |
| 32 | +### Explicitly Deferred (Future Slices) |
| 33 | + |
| 34 | +| Deferred Item | Rationale | |
| 35 | +|---------------|-----------| |
| 36 | +| Agent SDK chat interface | Requires API key, separate user flow | |
| 37 | +| Approval workflow | Requires session state management | |
| 38 | +| Git diff rendering | Depends on tool result parsing | |
| 39 | +| Settings/configuration UI | Not needed for first flow | |
| 40 | + |
| 41 | +--- |
| 42 | + |
| 43 | +## Architecture Context |
| 44 | + |
| 45 | +``` |
| 46 | +┌─────────────────────────────────────────────────────────────────┐ |
| 47 | +│ IMPLEMENTATION SLICE │ |
| 48 | +├─────────────────────────────────────────────────────────────────┤ |
| 49 | +│ │ |
| 50 | +│ Claude Code ──POST──► Bridge ──WebSocket──► Mobile ──Display │ |
| 51 | +│ │ │ │ │ |
| 52 | +│ Hooks.json /hooks/event claude_event ToolCard │ |
| 53 | +│ │ │ │ |
| 54 | +│ (Existing) (New Provider) (New Widget)│ |
| 55 | +│ │ |
| 56 | +└─────────────────────────────────────────────────────────────────┘ |
| 57 | +``` |
| 58 | + |
| 59 | +**What already exists (preserve):** |
| 60 | +- Bridge: `/hooks/event` endpoint (packages/bridge/src/hooks/receiver.ts) |
| 61 | +- Bridge: EventQueue with broadcast (packages/bridge/src/hooks/event_queue.ts) |
| 62 | +- Bridge: Protocol mapper for hook events to BridgeMessage (packages/bridge/src/hooks/protocol_mapper.ts) |
| 63 | +- Mobile: WebSocket service receiving messages (apps/mobile/lib/core/network/websocket_service.dart) |
| 64 | +- Mobile: BridgeMessage types including `claudeEvent` (apps/mobile/lib/core/network/websocket_messages.dart) |
| 65 | + |
| 66 | +**What's missing:** |
| 67 | +- Mobile: State management to transform incoming `claude_event` messages into UI-usable models |
| 68 | +- Mobile: Tool card widget rendering (OpenCode-style) |
| 69 | +- Mobile: Timeline/scrollable list to display events |
| 70 | + |
| 71 | +--- |
| 72 | + |
| 73 | +## Implementation Plan |
| 74 | + |
| 75 | +### Phase 1: Mobile Event State (Est. 200 lines, 2-3 files) |
| 76 | + |
| 77 | +**Goal:** Transform incoming `claude_event` WebSocket messages into typed, observable state. |
| 78 | + |
| 79 | +#### Files to Create/Modify |
| 80 | + |
| 81 | +| File | Action | Description | |
| 82 | +|------|--------|-------------| |
| 83 | +| `apps/mobile/lib/features/session/domain/models/event_models.dart` | CREATE | Dart models for HookEvent types | |
| 84 | +| `apps/mobile/lib/features/session/domain/providers/event_provider.dart` | CREATE | Riverpod provider to collect events from WebSocket stream | |
| 85 | +| `apps/mobile/lib/features/session/domain/providers/` | MODIFY | Wire event provider into WebSocket message listener | |
| 86 | + |
| 87 | +#### Task Breakdown |
| 88 | + |
| 89 | +```markdown |
| 90 | +### Phase 1: Mobile Event State |
| 91 | + |
| 92 | +- [ ] 1. Create `apps/mobile/lib/features/session/domain/models/event_models.dart` [S, Risk: L] |
| 93 | + - [ ] 1.1. Define `HookEvent` sealed class with event types [XS] |
| 94 | + - [ ] 1.2. Define `ToolUseEvent` subclass with tool, params, risk_level [XS] |
| 95 | + - [ ] 1.3. Define `ToolResultEvent` subclass with tool, result, duration [XS] |
| 96 | + - [ ] 1.4. Define `SessionStartEvent`, `SessionEndEvent`, `MessageEvent` subclasses [XS] |
| 97 | + - [ ] 1.5. Add `fromJson` factory for parsing BridgeMessage payloads [S] |
| 98 | + |
| 99 | +- [ ] 2. Create `apps/mobile/lib/features/session/domain/providers/event_provider.dart` [S, Risk: L] |
| 100 | + - [ ] 2.1. Define `EventNotifier` extending `AsyncNotifier<List<HookEvent>>` [XS] |
| 101 | + - [ ] 2.2. Implement `addEvent(HookEvent event)` method [XS] |
| 102 | + - [ ] 2.3. Implement `getEventsForSession(String sessionId)` filter [XS] |
| 103 | + - [ ] 2.4. Expose `eventsProvider` for widget consumption [XS] |
| 104 | + |
| 105 | +- [ ] 3. Wire event provider to WebSocket stream [XS, Risk: L] |
| 106 | + - [ ] 3.1. Locate WebSocket message listener in websocket_service.dart [XS] |
| 107 | + - [ ] 3.2. Add event provider reference injection [XS] |
| 108 | + - [ ] 3.3. Route `claude_event` messages to event provider [XS] |
| 109 | +``` |
| 110 | + |
| 111 | +### Phase 2: Tool Card Widget (Est. 300 lines, 3-4 files) |
| 112 | + |
| 113 | +**Goal:** Render tool_use and tool_result events as OpenCode-style cards. |
| 114 | + |
| 115 | +#### Files to Create/Modify |
| 116 | + |
| 117 | +| File | Action | Description | |
| 118 | +|------|--------|-------------| |
| 119 | +| `apps/mobile/lib/features/session/presentation/widgets/tool_card.dart` | CREATE | StatefulWidget for tool_use/tool_result display | |
| 120 | +| `apps/mobile/lib/features/session/presentation/widgets/tool_icon.dart` | CREATE | Icon mapping by tool type (Read, Edit, Bash, etc.) | |
| 121 | +| `apps/mobile/lib/features/session/presentation/widgets/status_indicator.dart` | CREATE | Visual status (pending, running, completed, error) | |
| 122 | + |
| 123 | +#### Task Breakdown |
| 124 | + |
| 125 | +```markdown |
| 126 | +### Phase 2: Tool Card Widget |
| 127 | + |
| 128 | +- [ ] 4. Create `apps/mobile/lib/features/session/presentation/widgets/tool_icon.dart` [XS, Risk: L] |
| 129 | + - [ ] 4.1. Define `ToolIcon` widget with `toolName` parameter [XS] |
| 130 | + - [ ] 4.2. Map tool names to icons (Edit→edit, Read→description, Bash→terminal) [XS] |
| 131 | + - [ ] 4.3. Use Material icons with theme colors [XS] |
| 132 | + |
| 133 | +- [ ] 5. Create `apps/mobile/lib/features/session/presentation/widgets/status_indicator.dart` [XS, Risk: L] |
| 134 | + - [ ] 5.1. Define `StatusIndicator` widget with `ToolStatus` enum [XS] |
| 135 | + - [ ] 5.2. Map statuses to colors (pending→grey, running→blue, completed→green, error→red) [XS] |
| 136 | + - [ ] 5.3. Add animated progress for 'running' status [XS] |
| 137 | + |
| 138 | +- [ ] 6. Create `apps/mobile/lib/features/session/presentation/widgets/tool_card.dart` [M, Risk: L] |
| 139 | + - [ ] 6.1. Define `ToolCard` widget accepting `HookEvent` [XS] |
| 140 | + - [ ] 6.2. Add collapsible header with ToolIcon, tool name, status [S] |
| 141 | + - [ ] 6.3. Implement expandable body showing input params (truncated) [S] |
| 142 | + - [ ] 6.4. Add tool result section for tool_result events [S] |
| 143 | + - [ ] 6.5. Support dark/light theme via Theme.of(context) [XS] |
| 144 | + - [ ] 6.6. Add timestamp and duration display [XS] |
| 145 | +``` |
| 146 | + |
| 147 | +### Phase 3: Event Timeline (Est. 150 lines, 2 files) |
| 148 | + |
| 149 | +**Goal:** Display scrolling list of events from current session. |
| 150 | + |
| 151 | +#### Files to Create/Modify |
| 152 | + |
| 153 | +| File | Action | Description | |
| 154 | +|------|--------|-------------| |
| 155 | +| `apps/mobile/lib/features/session/presentation/widgets/event_timeline.dart` | CREATE | ListView of ToolCards with auto-scroll | |
| 156 | +| `apps/mobile/lib/features/session/presentation/screens/session_screen.dart` | CREATE | Main session view placeholder | |
| 157 | + |
| 158 | +#### Task Breakdown |
| 159 | + |
| 160 | +```markdown |
| 161 | +### Phase 3: Event Timeline |
| 162 | + |
| 163 | +- [ ] 7. Create `apps/mobile/lib/features/session/presentation/widgets/event_timeline.dart` [S, Risk: L] |
| 164 | + - [ ] 7.1. Define `EventTimeline` widget consuming `eventsProvider` [XS] |
| 165 | + - [ ] 7.2. Build ListView.builder with ToolCard items [XS] |
| 166 | + - [ ] 7.3. Implement auto-scroll to newest event with ScrollController [XS] |
| 167 | + - [ ] 7.4. Add empty state message for no events [XS] |
| 168 | + - [ ] 7.5. Use `ref.watch` to reactively update on new events [XS] |
| 169 | + |
| 170 | +- [ ] 8. Create `apps/mobile/lib/features/session/presentation/screens/session_screen.dart` [S, Risk: L] |
| 171 | + - [ ] 8.1. Define `SessionScreen` with AppBar and EventTimeline body [XS] |
| 172 | + - [ ] 8.2. Add session ID display in AppBar title [XS] |
| 173 | + - [ ] 8.3. Wire to app navigation after health verification passes [S] |
| 174 | +``` |
| 175 | + |
| 176 | +### Phase 4: End-to-End Verification (Est. 100 lines, 2-3 files) |
| 177 | + |
| 178 | +**Goal:** Verify bridge correctly transforms and forwards hook events. |
| 179 | + |
| 180 | +#### Files to Create/Modify |
| 181 | + |
| 182 | +| File | Action | Description | |
| 183 | +|------|--------|-------------| |
| 184 | +| `packages/bridge/tests/hooks/event_flow.test.ts` | CREATE | Integration test for hook → websocket | |
| 185 | +| `apps/mobile/test/features/session/event_flow_test.dart` | CREATE | Widget test for event rendering | |
| 186 | + |
| 187 | +#### Task Breakdown |
| 188 | + |
| 189 | +```markdown |
| 190 | +### Phase 4: End-to-End Verification |
| 191 | + |
| 192 | +- [ ] 9. Create `packages/bridge/tests/hooks/event_flow.test.ts` [S, Risk: M] |
| 193 | + - [ ] 9.1. Mock WebSocket server ConnectionManager [S] |
| 194 | + - [ ] 9.2. POST sample hook event to `/hooks/event` [XS] |
| 195 | + - [ ] 9.3. Verify event queued in EventQueue [XS] |
| 196 | + - [ ] 9.4. Verify broadcast called with BridgeMessage [XS] |
| 197 | + |
| 198 | +- [ ] 10. Create `apps/mobile/test/features/session/event_flow_test.dart` [S, Risk: M] |
| 199 | + - [ ] 10.1. Create mock WebSocket service injecting events [S] |
| 200 | + - [ ] 10.2. Render EventTimeline widget [XS] |
| 201 | + - [ ] 10.3. Inject sample claude_event message [XS] |
| 202 | + - [ ] 10.4. Verify ToolCard appears with correct data [S] |
| 203 | + |
| 204 | +- [ ] 11. Run all tests and verify 100% pass [XS, Risk: L] |
| 205 | + - [ ] 11.1. Run `npm test` in packages/bridge [XS] |
| 206 | + - [ ] 11.2. Run `flutter test` in apps/mobile [XS] |
| 207 | + - [ ] 11.3. Fix any failing tests [Variable] |
| 208 | +``` |
| 209 | + |
| 210 | +### Phase 5: Documentation (Est. 100 lines, 1 file) |
| 211 | + |
| 212 | +**Goal:** Clear setup instructions for users to configure Claude Code hooks. |
| 213 | + |
| 214 | +#### Files to Create/Modify |
| 215 | + |
| 216 | +| File | Action | Description | |
| 217 | +|------|--------|-------------| |
| 218 | +| `docs/setup/claude-code-hooks-setup.md` | CREATE | Step-by-step hook configuration guide | |
| 219 | + |
| 220 | +#### Task Breakdown |
| 221 | + |
| 222 | +```markdown |
| 223 | +### Phase 5: Documentation |
| 224 | + |
| 225 | +- [ ] 12. Create `docs/setup/claude-code-hooks-setup.md` [S, Risk: L] |
| 226 | + - [ ] 12.1. Document hooks.json location (~/.claude/hooks/hooks.json) [XS] |
| 227 | + - [ ] 12.2. Provide curl command template for bridge URL [XS] |
| 228 | + - [ ] 12.3. List supported event types and their payloads [S] |
| 229 | + - [ ] 12.4. Add troubleshooting section for common issues [S] |
| 230 | + - [ ] 12.5. Include example hooks.json for ReCursor [S] |
| 231 | +``` |
| 232 | + |
| 233 | +--- |
| 234 | + |
| 235 | +## Existing Modifications to Preserve |
| 236 | + |
| 237 | +The following files have uncommitted changes that **must not be modified** by this slice: |
| 238 | + |
| 239 | +| File | Change Type | Description | |
| 240 | +|------|-------------|-------------| |
| 241 | +| `apps/mobile/lib/core/network/websocket_messages.dart` | Modified | Added `ConnectionPurpose` enum and purpose field - **DO NOT TOUCH** | |
| 242 | +| `apps/mobile/lib/core/network/websocket_service.dart` | Modified | Added purpose parameter to connect() - **DO NOT TOUCH** | |
| 243 | +| `packages/bridge/src/types.ts` | Modified | Added `ConnectionPurpose` type - **DO NOT TOUCH** | |
| 244 | +| `packages/bridge/src/websocket/connection_manager.ts` | Modified | Added purpose tracking to MobileClient - **DO NOT TOUCH** | |
| 245 | +| `packages/bridge/src/websocket/connection_mode.ts` | Modified | Connection mode detection logic - **DO NOT TOUCH** | |
| 246 | +| `packages/bridge/src/websocket/message_handler.ts` | Modified | Purpose handling in auth - **DO NOT TOUCH** | |
| 247 | + |
| 248 | +**Strategy:** All new code goes in `features/session/` directory, not in `core/network/`. |
| 249 | + |
| 250 | +--- |
| 251 | + |
| 252 | +## Testing Strategy |
| 253 | + |
| 254 | +### Unit Tests |
| 255 | + |
| 256 | +- `event_models_test.dart` - JSON parsing for all event types |
| 257 | +- `event_provider_test.dart` - State management logic |
| 258 | +- `tool_card_test.dart` - Widget rendering with various statuses |
| 259 | + |
| 260 | +### Integration Tests |
| 261 | + |
| 262 | +- Bridge: POST hook event → WebSocket broadcast |
| 263 | +- Mobile: WebSocket receive → State update → Widget render |
| 264 | + |
| 265 | +### Manual Verification |
| 266 | + |
| 267 | +1. Start bridge server: `npm run dev` in `packages/bridge` |
| 268 | +2. Start mobile app: `flutter run` in `apps/mobile` |
| 269 | +3. Connect mobile to bridge (use stored credentials or manual entry) |
| 270 | +4. POST test hook event to `http://localhost:3000/hooks/event` |
| 271 | +5. Verify tool card appears in mobile app within 2 seconds |
| 272 | + |
| 273 | +--- |
| 274 | + |
| 275 | +## Success Criteria |
| 276 | + |
| 277 | +| Criterion | Verification Method | |
| 278 | +|-----------|---------------------| |
| 279 | +| Events appear in mobile app | Screenshot of EventTimeline with tool cards | |
| 280 | +| End-to-end test passes | `npm test` + `flutter test` output | |
| 281 | +| No regressions in existing tests | All existing tests pass | |
| 282 | +| Documentation complete | User can configure hooks without help | |
| 283 | +| Architecture preserved | No modifications to websocket_messages.dart or websocket_service.dart | |
| 284 | + |
| 285 | +--- |
| 286 | + |
| 287 | +## Risk Assessment |
| 288 | + |
| 289 | +| Risk | Likelihood | Impact | Mitigation | |
| 290 | +|------|-------------|--------|------------| |
| 291 | +| Breaking existing connection flow | Low | High | All new code in `features/session/`, not `core/network/` | |
| 292 | +| Hook event format changes upstream | Low | Medium | Document Claude Code version tested against | |
| 293 | +| Performance with many events | Low | Low | EventQueue already handles 1000 event limit | |
| 294 | +| Mobile state race conditions | Medium | Medium | Use Riverpod's built-in async handling | |
| 295 | + |
| 296 | +--- |
| 297 | + |
| 298 | +## Effort Estimate |
| 299 | + |
| 300 | +| Phase | Effort | Risk | Dependencies | |
| 301 | +|-------|--------|------|--------------| |
| 302 | +| Phase 1: Event State | M (4h) | L | None | |
| 303 | +| Phase 2: Tool Cards | M (6h) | L | Phase 1 | |
| 304 | +| Phase 3: Timeline | S (2h) | L | Phase 2 | |
| 305 | +| Phase 4: Verification | S (3h) | M | Phases 1-3 | |
| 306 | +| Phase 5: Documentation | S (1h) | L | None | |
| 307 | +| **Total** | **M (16h / ~2 days)** | **L** | - | |
| 308 | + |
| 309 | +--- |
| 310 | + |
| 311 | +## Files Summary |
| 312 | + |
| 313 | +**New files to create (10):** |
| 314 | +``` |
| 315 | +apps/mobile/lib/features/session/ |
| 316 | +├── domain/ |
| 317 | +│ ├── models/ |
| 318 | +│ │ └── event_models.dart # Phase 1 |
| 319 | +│ └── providers/ |
| 320 | +│ └── event_provider.dart # Phase 1 |
| 321 | +└── presentation/ |
| 322 | + ├── screens/ |
| 323 | + │ └── session_screen.dart # Phase 3 |
| 324 | + └── widgets/ |
| 325 | + ├── event_timeline.dart # Phase 3 |
| 326 | + ├── status_indicator.dart # Phase 2 |
| 327 | + ├── tool_card.dart # Phase 2 |
| 328 | + └── tool_icon.dart # Phase 2 |
| 329 | +
|
| 330 | +packages/bridge/tests/hooks/ |
| 331 | +└── event_flow.test.ts # Phase 4 |
| 332 | +
|
| 333 | +apps/mobile/test/features/session/ |
| 334 | +└── event_flow_test.dart # Phase 4 |
| 335 | +
|
| 336 | +docs/setup/ |
| 337 | +└── claude-code-hooks-setup.md # Phase 5 |
| 338 | +``` |
| 339 | + |
| 340 | +**Files to modify (1):** |
| 341 | +``` |
| 342 | +apps/mobile/lib/features/startup/domain/bridge_startup_controller.dart |
| 343 | +# Wire session screen after health verification (Phase 3) |
| 344 | +``` |
| 345 | + |
| 346 | +**Existing modified files to preserve (not touch):** |
| 347 | +- See "Existing Modifications to Preserve" section above |
| 348 | + |
| 349 | +--- |
| 350 | + |
| 351 | +## Acceptance Checklist |
| 352 | + |
| 353 | +Before marking complete: |
| 354 | + |
| 355 | +- [ ] All new files compile without errors |
| 356 | +- [ ] All unit tests pass (`flutter test`, `npm test`) |
| 357 | +- [ ] Manual test: POST hook event appears in mobile within 2 seconds |
| 358 | +- [ ] Documentation allows user to configure hooks independently |
| 359 | +- [ ] No modifications to existing `websocket_service.dart` or `websocket_messages.dart` |
| 360 | +- [ ] Architecture pattern matches `docs/architecture/overview.md` (agent-agnostic) |
| 361 | + |
| 362 | +--- |
| 363 | + |
| 364 | +*Last updated: 2026-03-21* |
0 commit comments