@vybestack/llxprt-ui 0.7.0-nightly.251211.5750c518a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/PLAN-messages.md +681 -0
  2. package/PLAN.md +47 -0
  3. package/README.md +25 -0
  4. package/bun.lock +1024 -0
  5. package/dev-docs/ARCHITECTURE.md +178 -0
  6. package/dev-docs/CODE_ORGANIZATION.md +232 -0
  7. package/dev-docs/STANDARDS.md +235 -0
  8. package/dev-docs/UI_DESIGN.md +425 -0
  9. package/eslint.config.cjs +194 -0
  10. package/images/nui.png +0 -0
  11. package/llxprt.png +0 -0
  12. package/llxprt.svg +128 -0
  13. package/package.json +66 -0
  14. package/scripts/check-limits.ts +177 -0
  15. package/scripts/start.js +71 -0
  16. package/src/app.tsx +599 -0
  17. package/src/bootstrap.tsx +23 -0
  18. package/src/commands/AuthCommand.tsx +80 -0
  19. package/src/commands/ModelCommand.tsx +102 -0
  20. package/src/commands/ProviderCommand.tsx +103 -0
  21. package/src/commands/ThemeCommand.tsx +71 -0
  22. package/src/features/chat/history.ts +178 -0
  23. package/src/features/chat/index.ts +3 -0
  24. package/src/features/chat/persistentHistory.ts +102 -0
  25. package/src/features/chat/responder.ts +217 -0
  26. package/src/features/completion/completions.ts +161 -0
  27. package/src/features/completion/index.ts +3 -0
  28. package/src/features/completion/slash.test.ts +82 -0
  29. package/src/features/completion/slash.ts +248 -0
  30. package/src/features/completion/suggestions.test.ts +51 -0
  31. package/src/features/completion/suggestions.ts +112 -0
  32. package/src/features/config/configSession.test.ts +189 -0
  33. package/src/features/config/configSession.ts +179 -0
  34. package/src/features/config/index.ts +4 -0
  35. package/src/features/config/llxprtAdapter.integration.test.ts +202 -0
  36. package/src/features/config/llxprtAdapter.test.ts +139 -0
  37. package/src/features/config/llxprtAdapter.ts +257 -0
  38. package/src/features/config/llxprtCommands.test.ts +40 -0
  39. package/src/features/config/llxprtCommands.ts +35 -0
  40. package/src/features/config/llxprtConfig.test.ts +261 -0
  41. package/src/features/config/llxprtConfig.ts +418 -0
  42. package/src/features/theme/index.ts +2 -0
  43. package/src/features/theme/theme.test.ts +51 -0
  44. package/src/features/theme/theme.ts +105 -0
  45. package/src/features/theme/themeManager.ts +84 -0
  46. package/src/hooks/useAppCommands.ts +129 -0
  47. package/src/hooks/useApprovalKeyboard.ts +156 -0
  48. package/src/hooks/useChatStore.test.ts +112 -0
  49. package/src/hooks/useChatStore.ts +252 -0
  50. package/src/hooks/useInputManager.ts +99 -0
  51. package/src/hooks/useKeyboardHandlers.ts +130 -0
  52. package/src/hooks/useListNavigation.test.ts +166 -0
  53. package/src/hooks/useListNavigation.ts +62 -0
  54. package/src/hooks/usePersistentHistory.ts +94 -0
  55. package/src/hooks/useScrollManagement.ts +107 -0
  56. package/src/hooks/useSelectionClipboard.ts +48 -0
  57. package/src/hooks/useSessionManager.test.ts +85 -0
  58. package/src/hooks/useSessionManager.ts +101 -0
  59. package/src/hooks/useStreamingLifecycle.ts +71 -0
  60. package/src/hooks/useStreamingResponder.ts +401 -0
  61. package/src/hooks/useSuggestionSetup.ts +23 -0
  62. package/src/hooks/useToolApproval.test.ts +140 -0
  63. package/src/hooks/useToolApproval.ts +264 -0
  64. package/src/hooks/useToolScheduler.ts +432 -0
  65. package/src/index.ts +3 -0
  66. package/src/jsx.d.ts +11 -0
  67. package/src/lib/clipboard.ts +18 -0
  68. package/src/lib/logger.ts +107 -0
  69. package/src/lib/random.ts +5 -0
  70. package/src/main.tsx +13 -0
  71. package/src/test/mockTheme.ts +51 -0
  72. package/src/types/events.ts +87 -0
  73. package/src/types.ts +13 -0
  74. package/src/ui/components/ChatLayout.tsx +694 -0
  75. package/src/ui/components/CommandComponents.tsx +74 -0
  76. package/src/ui/components/DiffViewer.tsx +306 -0
  77. package/src/ui/components/FilterInput.test.ts +69 -0
  78. package/src/ui/components/FilterInput.tsx +62 -0
  79. package/src/ui/components/HeaderBar.tsx +137 -0
  80. package/src/ui/components/RadioSelect.test.ts +140 -0
  81. package/src/ui/components/RadioSelect.tsx +88 -0
  82. package/src/ui/components/SelectableList.test.ts +83 -0
  83. package/src/ui/components/SelectableList.tsx +35 -0
  84. package/src/ui/components/StatusBar.tsx +45 -0
  85. package/src/ui/components/SuggestionPanel.tsx +102 -0
  86. package/src/ui/components/messages/ModelMessage.tsx +14 -0
  87. package/src/ui/components/messages/SystemMessage.tsx +29 -0
  88. package/src/ui/components/messages/ThinkingMessage.tsx +14 -0
  89. package/src/ui/components/messages/UserMessage.tsx +26 -0
  90. package/src/ui/components/messages/index.ts +15 -0
  91. package/src/ui/components/messages/renderMessage.test.ts +49 -0
  92. package/src/ui/components/messages/renderMessage.tsx +43 -0
  93. package/src/ui/components/messages/types.test.ts +24 -0
  94. package/src/ui/components/messages/types.ts +36 -0
  95. package/src/ui/modals/AuthModal.tsx +106 -0
  96. package/src/ui/modals/ModalShell.tsx +60 -0
  97. package/src/ui/modals/SearchSelectModal.tsx +236 -0
  98. package/src/ui/modals/ThemeModal.tsx +204 -0
  99. package/src/ui/modals/ToolApprovalModal.test.ts +206 -0
  100. package/src/ui/modals/ToolApprovalModal.tsx +282 -0
  101. package/src/ui/modals/index.ts +20 -0
  102. package/src/ui/modals/modals.test.ts +26 -0
  103. package/src/ui/modals/types.ts +19 -0
  104. package/src/uicontext/Command.tsx +102 -0
  105. package/src/uicontext/Dialog.tsx +65 -0
  106. package/src/uicontext/index.ts +2 -0
  107. package/themes/ansi-light.json +59 -0
  108. package/themes/ansi.json +59 -0
  109. package/themes/atom-one-dark.json +59 -0
  110. package/themes/ayu-light.json +59 -0
  111. package/themes/ayu.json +59 -0
  112. package/themes/default-light.json +59 -0
  113. package/themes/default.json +59 -0
  114. package/themes/dracula.json +59 -0
  115. package/themes/github-dark.json +59 -0
  116. package/themes/github-light.json +59 -0
  117. package/themes/googlecode.json +59 -0
  118. package/themes/green-screen.json +59 -0
  119. package/themes/no-color.json +59 -0
  120. package/themes/shades-of-purple.json +59 -0
  121. package/themes/xcode.json +59 -0
  122. package/tsconfig.json +28 -0
  123. package/vitest.config.ts +10 -0
@@ -0,0 +1,681 @@
1
+ # Message Rendering System Implementation Plan
2
+
3
+ ## Overview
4
+
5
+ This plan defines a clean, modular message component architecture for the nui TUI application. The goal is to replace the current `[user]`/`[responder]` text prefixes with proper visual decorations using opentui's box border system.
6
+
7
+ ## Current State Analysis
8
+
9
+ ### Existing Code Structure
10
+
11
+ **`src/ui/components/ChatLayout.tsx`**:
12
+
13
+ - `renderChatLine(line: ChatLine, theme: ThemeDefinition)` - renders messages with `[role]` prefix
14
+ - `renderToolBlock(block: ToolBlock, theme: ThemeDefinition)` - renders tool output in bordered boxes
15
+ - `roleColor(role: Role, theme: ThemeDefinition)` - maps roles to theme colors
16
+
17
+ **`src/hooks/useChatStore.ts`**:
18
+
19
+ - Defines `Role = "user" | "responder" | "thinking"`
20
+ - Defines `ChatLine` and `ToolBlock` interfaces
21
+ - Exports types used throughout the app
22
+
23
+ **`src/features/theme/theme.ts`**:
24
+
25
+ - `ThemeColors` interface defines all theme color properties
26
+ - Currently has `text.user`, `text.responder`, `text.thinking` for message colors
27
+
28
+ ### Identified Issues
29
+
30
+ 1. Messages use text prefixes (`[user]`, `[responder]`) that get copied during text selection
31
+ 2. No visual distinction between message types beyond color
32
+ 3. No "system" message type for notifications like "Loaded profile: chutes"
33
+ 4. No spacing/grouping between messages from different roles
34
+
35
+ ---
36
+
37
+ ## Target Architecture
38
+
39
+ ### Component Hierarchy
40
+
41
+ ```
42
+ src/ui/components/messages/
43
+ index.ts # Public exports
44
+ types.ts # Message types and interfaces
45
+ MessageContainer.tsx # Groups consecutive messages from same role
46
+ BaseMessage.tsx # Shared message rendering logic (internal)
47
+ UserMessage.tsx # User input with left border
48
+ SystemMessage.tsx # System notifications with distinct border
49
+ ModelMessage.tsx # AI responses without decoration
50
+ ThinkingMessage.tsx # Thinking/reasoning with subtle styling
51
+ renderMessage.ts # Factory function for rendering any message type
52
+ ```
53
+
54
+ ### Data Model Changes
55
+
56
+ **Current Role type:**
57
+
58
+ ```typescript
59
+ type Role = 'user' | 'responder' | 'thinking';
60
+ ```
61
+
62
+ **New MessageRole type:**
63
+
64
+ ```typescript
65
+ type MessageRole = 'user' | 'model' | 'system' | 'thinking';
66
+ ```
67
+
68
+ Note: `responder` is renamed to `model` for semantic clarity. A migration function will handle backward compatibility.
69
+
70
+ **New ChatLine interface:**
71
+
72
+ ```typescript
73
+ interface ChatLine {
74
+ id: string;
75
+ kind: 'line';
76
+ role: MessageRole;
77
+ text: string;
78
+ }
79
+ ```
80
+
81
+ ### Theme Schema Additions
82
+
83
+ Add to `ThemeColors` interface in `src/features/theme/theme.ts`:
84
+
85
+ ```typescript
86
+ interface ThemeColors {
87
+ // ... existing properties ...
88
+
89
+ readonly message: {
90
+ readonly userBorder: string; // Left border for user messages
91
+ readonly systemBorder: string; // Left border for system messages
92
+ readonly systemText: string; // Text color for system messages
93
+ readonly groupSpacing: number; // Vertical gap between message groups (optional, default 1)
94
+ };
95
+ }
96
+ ```
97
+
98
+ ### Component Interfaces
99
+
100
+ **MessageProps (base interface):**
101
+
102
+ ```typescript
103
+ interface MessageProps {
104
+ readonly id: string;
105
+ readonly text: string;
106
+ readonly theme: ThemeDefinition;
107
+ }
108
+ ```
109
+
110
+ **UserMessageProps:**
111
+
112
+ ```typescript
113
+ interface UserMessageProps extends MessageProps {
114
+ // No additional props needed
115
+ }
116
+ ```
117
+
118
+ **SystemMessageProps:**
119
+
120
+ ```typescript
121
+ interface SystemMessageProps extends MessageProps {
122
+ // No additional props needed
123
+ }
124
+ ```
125
+
126
+ **ModelMessageProps:**
127
+
128
+ ```typescript
129
+ interface ModelMessageProps extends MessageProps {
130
+ // No additional props needed
131
+ }
132
+ ```
133
+
134
+ **ThinkingMessageProps:**
135
+
136
+ ```typescript
137
+ interface ThinkingMessageProps extends MessageProps {
138
+ // No additional props needed
139
+ }
140
+ ```
141
+
142
+ ### Visual Design
143
+
144
+ **UserMessage:**
145
+
146
+ ```tsx
147
+ <box
148
+ border={['left']}
149
+ borderColor={theme.colors.message.userBorder}
150
+ customBorderChars={{
151
+ ...EmptyBorder,
152
+ vertical: '┃',
153
+ bottomLeft: '╹',
154
+ topLeft: '╻',
155
+ }}
156
+ style={{ paddingLeft: 1 }}
157
+ >
158
+ <text fg={theme.colors.text.user}>{text}</text>
159
+ </box>
160
+ ```
161
+
162
+ **SystemMessage:**
163
+
164
+ ```tsx
165
+ <box
166
+ border={['left']}
167
+ borderColor={theme.colors.message.systemBorder}
168
+ customBorderChars={{
169
+ ...EmptyBorder,
170
+ vertical: '│',
171
+ bottomLeft: '╵',
172
+ topLeft: '╷',
173
+ }}
174
+ style={{ paddingLeft: 1 }}
175
+ >
176
+ <text fg={theme.colors.message.systemText}>{text}</text>
177
+ </box>
178
+ ```
179
+
180
+ **ModelMessage:**
181
+
182
+ ```tsx
183
+ <text fg={theme.colors.text.responder}>{text}</text>
184
+ ```
185
+
186
+ **ThinkingMessage:**
187
+
188
+ ```tsx
189
+ <text fg={theme.colors.text.thinking} style={{ fontStyle: 'italic' }}>
190
+ {text}
191
+ </text>
192
+ ```
193
+
194
+ ### Empty Border Constant
195
+
196
+ Define a constant for custom border characters:
197
+
198
+ ```typescript
199
+ export const EmptyBorder = {
200
+ top: ' ',
201
+ bottom: ' ',
202
+ left: ' ',
203
+ right: ' ',
204
+ topLeft: ' ',
205
+ topRight: ' ',
206
+ bottomLeft: ' ',
207
+ bottomRight: ' ',
208
+ horizontal: ' ',
209
+ vertical: ' ',
210
+ };
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Test Specification (Test-First Approach)
216
+
217
+ ### 1. Theme Tests (`src/features/theme/theme.test.ts`)
218
+
219
+ **Create new file** with the following tests:
220
+
221
+ ```typescript
222
+ describe('ThemeColors message properties', () => {
223
+ it('should require message.userBorder property', () => {
224
+ // Verify type requires the property
225
+ // Verify existing themes fail validation without it
226
+ });
227
+
228
+ it('should require message.systemBorder property', () => {
229
+ // Verify type requires the property
230
+ });
231
+
232
+ it('should require message.systemText property', () => {
233
+ // Verify type requires the property
234
+ });
235
+
236
+ it('should accept optional message.groupSpacing property', () => {
237
+ // Verify property is optional
238
+ // Verify defaults to expected value when not present
239
+ });
240
+ });
241
+
242
+ describe('loadThemes', () => {
243
+ it('should load themes with message color properties', () => {
244
+ // Verify loaded themes have message properties
245
+ });
246
+ });
247
+ ```
248
+
249
+ ### 2. Message Type Tests (`src/ui/components/messages/types.test.ts`)
250
+
251
+ **Create new file:**
252
+
253
+ ```typescript
254
+ describe('MessageRole type', () => {
255
+ it('should include user role', () => {
256
+ const role: MessageRole = 'user';
257
+ expect(role).toBe('user');
258
+ });
259
+
260
+ it('should include model role', () => {
261
+ const role: MessageRole = 'model';
262
+ expect(role).toBe('model');
263
+ });
264
+
265
+ it('should include system role', () => {
266
+ const role: MessageRole = 'system';
267
+ expect(role).toBe('system');
268
+ });
269
+
270
+ it('should include thinking role', () => {
271
+ const role: MessageRole = 'thinking';
272
+ expect(role).toBe('thinking');
273
+ });
274
+ });
275
+
276
+ describe('migrateRole', () => {
277
+ it('should convert responder to model', () => {
278
+ expect(migrateRole('responder')).toBe('model');
279
+ });
280
+
281
+ it('should pass through user unchanged', () => {
282
+ expect(migrateRole('user')).toBe('user');
283
+ });
284
+
285
+ it('should pass through thinking unchanged', () => {
286
+ expect(migrateRole('thinking')).toBe('thinking');
287
+ });
288
+
289
+ it('should pass through system unchanged', () => {
290
+ expect(migrateRole('system')).toBe('system');
291
+ });
292
+
293
+ it('should pass through model unchanged', () => {
294
+ expect(migrateRole('model')).toBe('model');
295
+ });
296
+ });
297
+ ```
298
+
299
+ ### 3. Message Rendering Tests (`src/ui/components/messages/renderMessage.test.ts`)
300
+
301
+ **Create new file:**
302
+
303
+ ```typescript
304
+ import { describe, it, expect } from 'vitest';
305
+ import { getMessageRenderer, MessageRole } from './types';
306
+
307
+ describe('getMessageRenderer', () => {
308
+ const mockTheme = createMockTheme(); // Helper function
309
+
310
+ it('should return UserMessage renderer for user role', () => {
311
+ const renderer = getMessageRenderer('user');
312
+ expect(renderer.displayName || renderer.name).toBe('UserMessage');
313
+ });
314
+
315
+ it('should return ModelMessage renderer for model role', () => {
316
+ const renderer = getMessageRenderer('model');
317
+ expect(renderer.displayName || renderer.name).toBe('ModelMessage');
318
+ });
319
+
320
+ it('should return SystemMessage renderer for system role', () => {
321
+ const renderer = getMessageRenderer('system');
322
+ expect(renderer.displayName || renderer.name).toBe('SystemMessage');
323
+ });
324
+
325
+ it('should return ThinkingMessage renderer for thinking role', () => {
326
+ const renderer = getMessageRenderer('thinking');
327
+ expect(renderer.displayName || renderer.name).toBe('ThinkingMessage');
328
+ });
329
+ });
330
+
331
+ describe('roleColor', () => {
332
+ const mockTheme = createMockTheme();
333
+
334
+ it('should return user text color for user role', () => {
335
+ expect(roleColor('user', mockTheme)).toBe(mockTheme.colors.text.user);
336
+ });
337
+
338
+ it('should return responder text color for model role', () => {
339
+ expect(roleColor('model', mockTheme)).toBe(mockTheme.colors.text.responder);
340
+ });
341
+
342
+ it('should return systemText color for system role', () => {
343
+ expect(roleColor('system', mockTheme)).toBe(
344
+ mockTheme.colors.message.systemText,
345
+ );
346
+ });
347
+
348
+ it('should return thinking text color for thinking role', () => {
349
+ expect(roleColor('thinking', mockTheme)).toBe(
350
+ mockTheme.colors.text.thinking,
351
+ );
352
+ });
353
+ });
354
+ ```
355
+
356
+ ### 4. Integration Tests (`src/ui/components/messages/integration.test.ts`)
357
+
358
+ **Create new file:**
359
+
360
+ ```typescript
361
+ describe('Message rendering integration', () => {
362
+ it('should render user message with border decoration', () => {
363
+ // Test that UserMessage produces a box with left border
364
+ });
365
+
366
+ it('should render system message with distinct border style', () => {
367
+ // Test that SystemMessage produces a box with left border
368
+ // Verify border style differs from user message
369
+ });
370
+
371
+ it('should render model message without border decoration', () => {
372
+ // Test that ModelMessage produces plain text without box wrapper
373
+ });
374
+
375
+ it('should use theme colors for all message types', () => {
376
+ // Verify each message type uses appropriate theme color
377
+ });
378
+ });
379
+ ```
380
+
381
+ ### 5. ChatStore Migration Tests (`src/hooks/useChatStore.test.ts`)
382
+
383
+ **Create new file:**
384
+
385
+ ```typescript
386
+ describe('useChatStore role migration', () => {
387
+ it('should accept system role in appendLines', () => {
388
+ // Verify appendLines works with system role
389
+ });
390
+
391
+ it('should accept model role in appendLines', () => {
392
+ // Verify appendLines works with model role
393
+ });
394
+
395
+ it('should store lines with correct role', () => {
396
+ // Verify stored lines have expected role
397
+ });
398
+ });
399
+ ```
400
+
401
+ ### Test Helper: Mock Theme
402
+
403
+ Create a shared test utility:
404
+
405
+ ```typescript
406
+ // src/test/mockTheme.ts
407
+ export function createMockTheme(): ThemeDefinition {
408
+ return {
409
+ slug: 'test',
410
+ name: 'Test Theme',
411
+ kind: 'dark',
412
+ colors: {
413
+ background: '#000000',
414
+ panel: {
415
+ bg: '#111111',
416
+ border: '#333333',
417
+ },
418
+ text: {
419
+ primary: '#ffffff',
420
+ muted: '#888888',
421
+ user: '#00ff00',
422
+ responder: '#0088ff',
423
+ thinking: '#ff8800',
424
+ tool: '#ff00ff',
425
+ },
426
+ input: {
427
+ fg: '#ffffff',
428
+ bg: '#000000',
429
+ border: '#333333',
430
+ placeholder: '#666666',
431
+ },
432
+ status: {
433
+ fg: '#ffffff',
434
+ },
435
+ accent: {
436
+ primary: '#00ffff',
437
+ },
438
+ diff: {
439
+ addedBg: '#003300',
440
+ addedFg: '#00ff00',
441
+ removedBg: '#330000',
442
+ removedFg: '#ff0000',
443
+ },
444
+ selection: {
445
+ fg: '#000000',
446
+ bg: '#ffffff',
447
+ },
448
+ message: {
449
+ userBorder: '#00ff00',
450
+ systemBorder: '#ffff00',
451
+ systemText: '#ffff00',
452
+ },
453
+ },
454
+ };
455
+ }
456
+ ```
457
+
458
+ ---
459
+
460
+ ## Implementation Order
461
+
462
+ ### Phase 1: Foundation (No Breaking Changes)
463
+
464
+ 1. **Add test helper file**: `src/test/mockTheme.ts`
465
+ 2. **Write theme tests**: `src/features/theme/theme.test.ts`
466
+ 3. **Update ThemeColors interface**: Add `message` property group
467
+ 4. **Update all theme JSON files**: Add `message` colors to each theme
468
+
469
+ ### Phase 2: Message Types Module
470
+
471
+ 5. **Write types tests**: `src/ui/components/messages/types.test.ts`
472
+ 6. **Create types module**: `src/ui/components/messages/types.ts`
473
+ - Define `MessageRole` type
474
+ - Define `migrateRole()` function
475
+ - Define component interfaces
476
+
477
+ ### Phase 3: Message Components
478
+
479
+ 7. **Write renderMessage tests**: `src/ui/components/messages/renderMessage.test.ts`
480
+ 8. **Create EmptyBorder constant**: In types.ts or separate constants file
481
+ 9. **Create BaseMessage**: Internal shared logic
482
+ 10. **Create UserMessage**: User input with left border
483
+ 11. **Create SystemMessage**: System notifications with distinct border
484
+ 12. **Create ModelMessage**: Plain text for AI responses
485
+ 13. **Create ThinkingMessage**: Subtle styling for reasoning
486
+ 14. **Create renderMessage factory**: Function to select appropriate component
487
+ 15. **Create index.ts**: Public exports
488
+
489
+ ### Phase 4: Integration
490
+
491
+ 16. **Write useChatStore tests**: `src/hooks/useChatStore.test.ts`
492
+ 17. **Update useChatStore**: Accept new roles, use `migrateRole()` for backward compatibility
493
+ 18. **Update ChatLayout**: Replace `renderChatLine` with new message components
494
+ 19. **Remove old code**: Delete `[role]` prefix rendering
495
+ 20. **Write integration tests**: `src/ui/components/messages/integration.test.ts`
496
+
497
+ ### Phase 5: Polish
498
+
499
+ 21. **Add message grouping**: Optional spacing between role changes
500
+ 22. **Verify all themes**: Ensure all theme files have valid message colors
501
+ 23. **Manual testing**: Visual verification in terminal
502
+
503
+ ---
504
+
505
+ ## File Changes Summary
506
+
507
+ ### New Files
508
+
509
+ | File | Purpose |
510
+ | -------------------------------------------------- | ------------------------------- |
511
+ | `src/test/mockTheme.ts` | Shared mock theme for tests |
512
+ | `src/features/theme/theme.test.ts` | Theme validation tests |
513
+ | `src/ui/components/messages/index.ts` | Public exports |
514
+ | `src/ui/components/messages/types.ts` | Types and interfaces |
515
+ | `src/ui/components/messages/types.test.ts` | Type tests |
516
+ | `src/ui/components/messages/constants.ts` | EmptyBorder and other constants |
517
+ | `src/ui/components/messages/BaseMessage.tsx` | Shared message logic |
518
+ | `src/ui/components/messages/UserMessage.tsx` | User message component |
519
+ | `src/ui/components/messages/SystemMessage.tsx` | System message component |
520
+ | `src/ui/components/messages/ModelMessage.tsx` | Model message component |
521
+ | `src/ui/components/messages/ThinkingMessage.tsx` | Thinking message component |
522
+ | `src/ui/components/messages/renderMessage.ts` | Factory function |
523
+ | `src/ui/components/messages/renderMessage.test.ts` | Factory tests |
524
+ | `src/ui/components/messages/integration.test.ts` | Integration tests |
525
+ | `src/hooks/useChatStore.test.ts` | Chat store tests |
526
+
527
+ ### Modified Files
528
+
529
+ | File | Changes |
530
+ | ---------------------------------- | ----------------------------------------- |
531
+ | `src/features/theme/theme.ts` | Add `message` to ThemeColors interface |
532
+ | `themes/*.json` (15 files) | Add `message` color properties |
533
+ | `src/hooks/useChatStore.ts` | Update Role type, add system role support |
534
+ | `src/ui/components/ChatLayout.tsx` | Replace renderChatLine, remove prefix |
535
+
536
+ ---
537
+
538
+ ## Theme Color Recommendations
539
+
540
+ For each theme, add `message` properties. Example for dracula theme:
541
+
542
+ ```json
543
+ {
544
+ "message": {
545
+ "userBorder": "#8be9fd",
546
+ "systemBorder": "#f1fa8c",
547
+ "systemText": "#f1fa8c"
548
+ }
549
+ }
550
+ ```
551
+
552
+ **Color Guidelines:**
553
+
554
+ - `userBorder`: Match or complement `text.user` color
555
+ - `systemBorder`: Use `accent.warning` or a distinct notification color
556
+ - `systemText`: Same as `systemBorder` for consistency
557
+
558
+ ---
559
+
560
+ ## Risks and Edge Cases
561
+
562
+ ### Risk 1: OpenTUI Border Support
563
+
564
+ **Risk:** The `border={["left"]}` syntax and `customBorderChars` may behave differently than expected in opentui/react vs opentui/solid.
565
+
566
+ **Mitigation:**
567
+
568
+ - Test border rendering early in Phase 3
569
+ - Have fallback to simpler border style if custom chars don't work
570
+ - Consider using standard `borderStyle: "single"` with only left border if needed
571
+
572
+ ### Risk 2: Backward Compatibility
573
+
574
+ **Risk:** Existing code uses `"responder"` role which needs migration.
575
+
576
+ **Mitigation:**
577
+
578
+ - `migrateRole()` function handles conversion transparently
579
+ - Keep `"responder"` as valid input that gets converted to `"model"`
580
+ - Run migration at data boundary (useChatStore)
581
+
582
+ ### Risk 3: Theme File Updates
583
+
584
+ **Risk:** Missing `message` properties in theme files could cause runtime errors.
585
+
586
+ **Mitigation:**
587
+
588
+ - Add optional defaults in theme loading code
589
+ - Validate all theme files in CI
590
+ - Provide clear error messages for missing properties
591
+
592
+ ### Risk 4: Text Selection
593
+
594
+ **Risk:** Box borders might still interfere with text selection in some terminals.
595
+
596
+ **Mitigation:**
597
+
598
+ - Test in multiple terminals (iTerm2, Terminal.app, Windows Terminal)
599
+ - Document known limitations
600
+ - Border characters are separate from content, should not be selected
601
+
602
+ ### Edge Case: Empty Messages
603
+
604
+ Handle messages with empty or whitespace-only text:
605
+
606
+ - Render minimal height (1 line)
607
+ - Still show border decoration for user/system
608
+ - Don't collapse message entirely
609
+
610
+ ### Edge Case: Multi-line Messages
611
+
612
+ Messages can span multiple lines. Ensure:
613
+
614
+ - Border extends full height of message
615
+ - Text wraps properly within border
616
+ - No visual artifacts at line breaks
617
+
618
+ ### Edge Case: Very Long Messages
619
+
620
+ For messages exceeding viewport:
621
+
622
+ - Let scrollbox handle scrolling (already implemented)
623
+ - Border should extend through all lines
624
+ - Consider lazy rendering for performance (future optimization)
625
+
626
+ ---
627
+
628
+ ## Success Criteria
629
+
630
+ 1. All tests pass (existing + new)
631
+ 2. User messages have visible left border that doesn't appear in clipboard on select
632
+ 3. System messages have distinct visual style from user messages
633
+ 4. Model messages render as plain text without decoration
634
+ 5. No `[user]` or `[responder]` text prefixes visible
635
+ 6. All 15 theme files include valid message colors
636
+ 7. Backward compatibility: existing `"responder"` role still works
637
+
638
+ ---
639
+
640
+ ## Example Usage After Implementation
641
+
642
+ ```typescript
643
+ // In ChatLayout or similar
644
+ import { renderMessage, migrateRole } from "./messages";
645
+
646
+ function ScrollbackView(props: ScrollbackProps): JSX.Element {
647
+ return (
648
+ <scrollbox ...>
649
+ <box flexDirection="column" style={{ gap: 0, width: "100%" }}>
650
+ {props.lines.map((entry) =>
651
+ entry.kind === "line"
652
+ ? renderMessage(migrateRole(entry.role), entry.id, entry.text, props.theme)
653
+ : renderToolBlock(entry, props.theme)
654
+ )}
655
+ </box>
656
+ </scrollbox>
657
+ );
658
+ }
659
+ ```
660
+
661
+ ---
662
+
663
+ ## Appendix: Theme File Locations
664
+
665
+ All theme files to update:
666
+
667
+ 1. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/ansi-light.json`
668
+ 2. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/ansi.json`
669
+ 3. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/atom-one-dark.json`
670
+ 4. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/ayu-light.json`
671
+ 5. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/ayu.json`
672
+ 6. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/default-light.json`
673
+ 7. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/default.json`
674
+ 8. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/dracula.json`
675
+ 9. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/github-dark.json`
676
+ 10. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/github-light.json`
677
+ 11. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/googlecode.json`
678
+ 12. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/green-screen.json`
679
+ 13. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/no-color.json`
680
+ 14. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/shades-of-purple.json`
681
+ 15. `/Users/acoliver/projects/llxprt-code-branches/newui/nui/themes/xcode.json`