@assistkick/create 1.9.0 → 1.11.0

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 (206) hide show
  1. package/dist/src/scaffolder.d.ts +12 -1
  2. package/dist/src/scaffolder.js +40 -3
  3. package/dist/src/scaffolder.js.map +1 -1
  4. package/package.json +1 -1
  5. package/templates/assistkick-product-system/package.json +1 -1
  6. package/templates/assistkick-product-system/packages/backend/package.json +1 -0
  7. package/templates/assistkick-product-system/packages/backend/src/mcp/permission_mcp_server.ts +196 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +31 -7
  9. package/templates/assistkick-product-system/packages/backend/src/routes/auth.ts +15 -12
  10. package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.test.ts +95 -0
  11. package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.ts +97 -0
  12. package/templates/assistkick-product-system/packages/backend/src/routes/chat_permission.ts +94 -0
  13. package/templates/assistkick-product-system/packages/backend/src/routes/chat_sessions.ts +189 -0
  14. package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.test.ts +131 -0
  15. package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.ts +94 -0
  16. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +12 -3
  17. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +2 -2
  18. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +391 -23
  19. package/templates/assistkick-product-system/packages/backend/src/routes/git_branches.test.ts +306 -0
  20. package/templates/assistkick-product-system/packages/backend/src/routes/git_connect.test.ts +133 -0
  21. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +66 -9
  22. package/templates/assistkick-product-system/packages/backend/src/routes/preview.ts +204 -0
  23. package/templates/assistkick-product-system/packages/backend/src/routes/projects.test.ts +205 -0
  24. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +37 -9
  25. package/templates/assistkick-product-system/packages/backend/src/routes/skills.test.ts +139 -0
  26. package/templates/assistkick-product-system/packages/backend/src/routes/skills.ts +95 -0
  27. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +5 -4
  28. package/templates/assistkick-product-system/packages/backend/src/routes/users.ts +4 -4
  29. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +8 -8
  30. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +5 -5
  31. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +6 -6
  32. package/templates/assistkick-product-system/packages/backend/src/server.ts +107 -27
  33. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +105 -203
  34. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +76 -266
  35. package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.test.ts +427 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.ts +345 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.test.ts +170 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.ts +106 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.test.ts +217 -0
  40. package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.ts +188 -0
  41. package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.test.ts +1243 -0
  42. package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.ts +894 -0
  43. package/templates/assistkick-product-system/packages/backend/src/services/coherence-review.ts +3 -3
  44. package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.test.ts +85 -0
  45. package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.ts +54 -0
  46. package/templates/assistkick-product-system/packages/backend/src/services/email_service.ts +13 -10
  47. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +11 -3
  48. package/templates/assistkick-product-system/packages/backend/src/services/invitation_service.ts +1 -1
  49. package/templates/assistkick-product-system/packages/backend/src/services/password_reset_service.ts +1 -1
  50. package/templates/assistkick-product-system/packages/backend/src/services/permission_service.test.ts +243 -0
  51. package/templates/assistkick-product-system/packages/backend/src/services/permission_service.ts +259 -0
  52. package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.test.ts +172 -0
  53. package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.ts +225 -0
  54. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +29 -0
  55. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +17 -0
  56. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +255 -0
  57. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +300 -25
  58. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +44 -0
  59. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +62 -7
  60. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +77 -6
  61. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +149 -14
  62. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +2 -1
  63. package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.test.ts +45 -0
  64. package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.ts +157 -0
  65. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +4 -3
  66. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +3 -3
  67. package/templates/assistkick-product-system/packages/frontend/package.json +5 -0
  68. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +2 -0
  69. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +336 -5
  70. package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +192 -12
  71. package/templates/assistkick-product-system/packages/frontend/src/components/AttachmentPreviewList.tsx +98 -0
  72. package/templates/assistkick-product-system/packages/frontend/src/components/AutocompleteDropdown.tsx +65 -0
  73. package/templates/assistkick-product-system/packages/frontend/src/components/ChatAttachButton.tsx +56 -0
  74. package/templates/assistkick-product-system/packages/frontend/src/components/ChatDropZone.tsx +80 -0
  75. package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageBubble.tsx +155 -0
  76. package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageContent.tsx +182 -0
  77. package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageInput.tsx +233 -0
  78. package/templates/assistkick-product-system/packages/frontend/src/components/ChatSessionSidebar.tsx +218 -0
  79. package/templates/assistkick-product-system/packages/frontend/src/components/ChatStopButton.tsx +32 -0
  80. package/templates/assistkick-product-system/packages/frontend/src/components/ChatTodoSidebar.tsx +113 -0
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ChatView.tsx +842 -0
  82. package/templates/assistkick-product-system/packages/frontend/src/components/CommitMessageModal.tsx +82 -0
  83. package/templates/assistkick-product-system/packages/frontend/src/components/DiagramOverlay.tsx +160 -0
  84. package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +5 -5
  85. package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +9 -10
  86. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +5 -5
  87. package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +112 -41
  88. package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +2 -2
  89. package/templates/assistkick-product-system/packages/frontend/src/components/HighlightedText.tsx +87 -0
  90. package/templates/assistkick-product-system/packages/frontend/src/components/ImageLightbox.tsx +192 -0
  91. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +2 -2
  92. package/templates/assistkick-product-system/packages/frontend/src/components/MentionPill.tsx +33 -0
  93. package/templates/assistkick-product-system/packages/frontend/src/components/MermaidBlock.tsx +148 -0
  94. package/templates/assistkick-product-system/packages/frontend/src/components/PermissionDialog.tsx +91 -0
  95. package/templates/assistkick-product-system/packages/frontend/src/components/PermissionModeSelector.tsx +229 -0
  96. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +249 -83
  97. package/templates/assistkick-product-system/packages/frontend/src/components/QueuedMessageBubble.tsx +38 -0
  98. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +212 -117
  99. package/templates/assistkick-product-system/packages/frontend/src/components/SystemPromptAccordion.tsx +48 -0
  100. package/templates/assistkick-product-system/packages/frontend/src/components/TaskIcon.tsx +11 -0
  101. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +25 -9
  102. package/templates/assistkick-product-system/packages/frontend/src/components/ToolDiffView.tsx +114 -0
  103. package/templates/assistkick-product-system/packages/frontend/src/components/ToolResultCard.tsx +87 -0
  104. package/templates/assistkick-product-system/packages/frontend/src/components/ToolUseCard.tsx +149 -0
  105. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +25 -8
  106. package/templates/assistkick-product-system/packages/frontend/src/components/UnifiedGitWidget.tsx +722 -0
  107. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +2 -0
  108. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +2 -1
  109. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/ProgrammableNode.tsx +178 -0
  110. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +3 -0
  111. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +103 -9
  112. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +26 -2
  113. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +42 -1
  114. package/templates/assistkick-product-system/packages/frontend/src/hooks/useDocumentTitle.ts +11 -0
  115. package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +1 -0
  116. package/templates/assistkick-product-system/packages/frontend/src/hooks/use_chat_stream.ts +826 -0
  117. package/templates/assistkick-product-system/packages/frontend/src/hooks/use_file_tree_cache.ts +69 -0
  118. package/templates/assistkick-product-system/packages/frontend/src/hooks/use_mention_autocomplete.ts +284 -0
  119. package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.test.ts +183 -0
  120. package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.ts +150 -0
  121. package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.test.ts +305 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.ts +113 -0
  123. package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.test.ts +157 -0
  124. package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.ts +95 -0
  125. package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.test.ts +65 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.ts +110 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/lib/message_queue.ts +66 -0
  128. package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.test.ts +124 -0
  129. package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.ts +112 -0
  130. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +2 -0
  131. package/templates/assistkick-product-system/packages/frontend/src/routes/ChatRoute.tsx +8 -0
  132. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +2 -0
  133. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +0 -4
  134. package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +2 -0
  135. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +2 -0
  136. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -0
  137. package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +2 -0
  138. package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +2 -0
  139. package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +2 -0
  140. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +2 -0
  141. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +2 -0
  142. package/templates/assistkick-product-system/packages/frontend/src/routes/accept_invitation.tsx +2 -0
  143. package/templates/assistkick-product-system/packages/frontend/src/routes/forgot_password.tsx +2 -0
  144. package/templates/assistkick-product-system/packages/frontend/src/routes/login.tsx +2 -0
  145. package/templates/assistkick-product-system/packages/frontend/src/routes/register.tsx +2 -0
  146. package/templates/assistkick-product-system/packages/frontend/src/routes/reset_password.tsx +2 -0
  147. package/templates/assistkick-product-system/packages/frontend/src/stores/useAttachmentStore.ts +66 -0
  148. package/templates/assistkick-product-system/packages/frontend/src/stores/useChatSessionStore.ts +107 -0
  149. package/templates/assistkick-product-system/packages/frontend/src/stores/useMessageQueueStore.ts +110 -0
  150. package/templates/assistkick-product-system/packages/frontend/src/stores/usePreviewStore.ts +78 -0
  151. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +7 -0
  152. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +6 -1
  153. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +30 -357
  154. package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.test.ts +115 -0
  155. package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.ts +91 -0
  156. package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.test.ts +30 -0
  157. package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.ts +3 -0
  158. package/templates/assistkick-product-system/packages/shared/db/migrations/0015_magenta_jazinda.sql +1 -0
  159. package/templates/assistkick-product-system/packages/shared/db/migrations/0016_giant_xorn.sql +1 -0
  160. package/templates/assistkick-product-system/packages/shared/db/migrations/0017_sloppy_mentor.sql +6 -0
  161. package/templates/assistkick-product-system/packages/shared/db/migrations/0018_vengeful_kabuki.sql +9 -0
  162. package/templates/assistkick-product-system/packages/shared/db/migrations/0019_careful_sentinels.sql +8 -0
  163. package/templates/assistkick-product-system/packages/shared/db/migrations/0020_clever_spot.sql +27 -0
  164. package/templates/assistkick-product-system/packages/shared/db/migrations/0021_graceful_hex.sql +1 -0
  165. package/templates/assistkick-product-system/packages/shared/db/migrations/0022_short_kingpin.sql +1 -0
  166. package/templates/assistkick-product-system/packages/shared/db/migrations/0023_ambiguous_sharon_carter.sql +1 -0
  167. package/templates/assistkick-product-system/packages/shared/db/migrations/0024_fat_unus.sql +1 -0
  168. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0015_snapshot.json +1552 -0
  169. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0016_snapshot.json +1560 -0
  170. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0017_snapshot.json +1598 -0
  171. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0018_snapshot.json +1657 -0
  172. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0019_snapshot.json +1709 -0
  173. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0020_snapshot.json +1733 -0
  174. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0021_snapshot.json +1740 -0
  175. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0022_snapshot.json +1755 -0
  176. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0023_snapshot.json +1762 -0
  177. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0024_snapshot.json +1769 -0
  178. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
  179. package/templates/assistkick-product-system/packages/shared/db/schema.ts +40 -1
  180. package/templates/assistkick-product-system/packages/shared/lib/claude-service.test.ts +236 -0
  181. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +46 -5
  182. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +65 -39
  183. package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.test.ts +173 -0
  184. package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.ts +213 -0
  185. package/templates/assistkick-product-system/packages/shared/lib/validator.test.ts +70 -0
  186. package/templates/assistkick-product-system/packages/shared/lib/validator.ts +17 -1
  187. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +803 -27
  188. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +502 -68
  189. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +4 -4
  190. package/templates/assistkick-product-system/packages/shared/package.json +2 -1
  191. package/templates/assistkick-product-system/packages/shared/test_fixtures/hanging_stream.mjs +46 -0
  192. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +44 -0
  193. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +7 -0
  194. package/templates/assistkick-product-system/packages/shared/tools/remove_node.ts +2 -1
  195. package/templates/assistkick-product-system/packages/shared/tools/resolve_question.ts +2 -1
  196. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -1
  197. package/templates/assistkick-product-system/tests/message_queue.test.ts +178 -0
  198. package/templates/assistkick-product-system/tests/message_queue_per_session.test.ts +143 -0
  199. package/templates/skills/assistkick-bootstrap/SKILL.md +26 -26
  200. package/templates/skills/assistkick-code-reviewer/SKILL.md +45 -46
  201. package/templates/skills/assistkick-db-explorer/SKILL.md +13 -13
  202. package/templates/skills/assistkick-debugger/SKILL.md +23 -23
  203. package/templates/skills/assistkick-developer/SKILL.md +59 -63
  204. package/templates/skills/assistkick-interview/SKILL.md +26 -26
  205. package/templates/skills/assistkick-video-composition-agent/SKILL.md +231 -0
  206. package/templates/skills/assistkick-video-script-writer/SKILL.md +136 -0
@@ -1,31 +1,39 @@
1
- import React from 'react';
2
1
  import { marked } from 'marked';
2
+ import {
3
+ ArrowRight, ArrowLeft, FileText, FolderPlus, Trash2, Lightbulb,
4
+ GitBranch, Activity, ListTodo, AlignLeft, ClipboardCheck,
5
+ HelpCircle, CheckSquare, StickyNote,
6
+ } from 'lucide-react';
3
7
  import { useSidePanelStore } from '../stores/useSidePanelStore';
4
8
  import { useGraphStore } from '../stores/useGraphStore';
5
9
  import { useGraphUIStore } from '../stores/useGraphUIStore';
6
- import { getTaskCssClass, getTaskIcon, getTaskIconClass, getTaskNameClass, shouldShowTaskList } from '../utils/task_status';
10
+ import { shouldShowTaskList } from '../utils/task_status';
7
11
  import { SideSheet } from './ds/SideSheet';
12
+ import { ContentCard } from './ds/ContentCard';
13
+ import { AccentBorderList, AccentBorderItem } from './ds/AccentBorderList';
14
+ import { CompletionRing } from './ds/CompletionRing';
15
+ import { StatusDot } from './ds/StatusDot';
16
+ import { TaskIcon } from './TaskIcon';
17
+
18
+ const renderMarkdownSection = (md: string): string => {
19
+ try {
20
+ return marked.parse(md) as string;
21
+ } catch {
22
+ return md;
23
+ }
24
+ };
8
25
 
9
26
  export function SidePanel() {
10
27
  const {
11
- isOpen, node, content, workSummaries, expandedSummaries,
12
- tasks, expandedTasks, close, toggleSummaries, toggleTasks,
28
+ isOpen, node, parsedContent, workSummaries,
29
+ tasks, close,
13
30
  } = useSidePanelStore();
14
31
  const graphData = useGraphStore((s) => s.graphData);
15
32
  const onEdgeClick = useGraphUIStore((s) => s.onEdgeClick);
16
33
 
17
- const renderMarkdown = (md: string) => {
18
- const stripped = md.replace(/^---[\s\S]*?---\n*/m, '');
19
- try {
20
- return marked.parse(stripped) as string;
21
- } catch {
22
- return stripped;
23
- }
24
- };
25
-
26
34
  const findEdges = (nodeId: string) => {
27
35
  if (!graphData) return [];
28
- const edges: any[] = [];
36
+ const edges: { neighborId: string; relation: string; direction: string }[] = [];
29
37
  graphData.edges.forEach((e: any) => {
30
38
  if (e.from === nodeId) edges.push({ neighborId: e.to, relation: e.relation, direction: 'outgoing' });
31
39
  else if (e.to === nodeId) edges.push({ neighborId: e.from, relation: e.relation, direction: 'incoming' });
@@ -37,132 +45,219 @@ export function SidePanel() {
37
45
 
38
46
  if (!node) return (
39
47
  <SideSheet isOpen={isOpen} onClose={close} title="" zIndex={200}>
40
- <div className="panel-body text-sm leading-relaxed" />
48
+ <div />
41
49
  </SideSheet>
42
50
  );
43
51
 
44
52
  const statusLabel = (node.status || 'draft').replace(/_/g, ' ');
45
- const completeness = Math.round((node.completeness || 0) * 100);
53
+ const completeness = node.completeness || 0;
46
54
  const typeLabel = node.type ? node.type.replace(/_/g, ' ') : '';
47
55
  const edges = findEdges(node.id);
56
+ const isFeature = node.type === 'feature' || node.id?.startsWith('feat_');
48
57
 
49
58
  return (
50
59
  <SideSheet isOpen={isOpen} onClose={close} title={title} zIndex={200}>
51
- <div className="panel-body text-sm leading-relaxed">
52
- <div className="flex items-center gap-2 mb-3 pb-2.5 border-b border-edge flex-wrap">
53
- <span className="text-[10px] text-content-muted uppercase tracking-wide bg-surface-raised px-2 py-0.5 rounded-sm">{typeLabel}</span>
54
- <span className={`text-[10px] px-2 py-0.5 rounded-sm font-semibold capitalize panel-status-${node.status || 'draft'}`}>{statusLabel}</span>
55
- <span className="text-[11px] text-content-secondary ml-auto">{completeness}% complete</span>
60
+ <div className="flex flex-col gap-3">
61
+ {/* Status card */}
62
+ <div className="flex items-center gap-3 rounded-xl border border-edge bg-surface-alt p-3">
63
+ <CompletionRing pct={completeness} />
64
+ <div className="flex-1 min-w-0">
65
+ <div className="flex items-center gap-2">
66
+ <span className="rounded-md bg-surface-raised px-2 py-0.5 text-[11px] font-medium text-content-secondary capitalize">
67
+ {typeLabel}
68
+ </span>
69
+ <StatusDot status={node.status || 'draft'} />
70
+ <span className="text-[11px] text-content-secondary capitalize">
71
+ {statusLabel}
72
+ </span>
73
+ </div>
74
+ </div>
56
75
  </div>
76
+
77
+ {/* Tasks */}
57
78
  {shouldShowTaskList(tasks) && (
58
- <div className="my-2 border border-edge rounded">
59
- <button
60
- className="bg-none border-none text-content font-mono text-xs cursor-pointer py-1.5 px-2 w-full text-left flex items-center gap-1.5 hover:text-content hover:bg-surface-raised"
61
- onClick={toggleTasks}
62
- >
63
- <span className={`inline-block text-[9px] transition-transform duration-150${expandedTasks ? ' rotate-90' : ''}`}>{'\u25B6'}</span>
64
- Tasks ({tasks!.completed}/{tasks!.total})
65
- </button>
66
- {expandedTasks && tasks!.items?.length > 0 && (
67
- <div className="px-2 pt-1 pb-1.5 flex flex-col gap-[3px] border-t border-edge">
68
- {tasks!.items.map((task: any, idx: number) => (
69
- <div key={idx} className={getTaskCssClass(task.status)}>
70
- <span className={`shrink-0 w-3 text-center text-[10px] ${getTaskIconClass(task.status)}`}>
71
- {getTaskIcon(task.status)}
72
- </span>
73
- <span className={getTaskNameClass(task.status)}>{task.name}</span>
74
- </div>
75
- ))}
76
- </div>
77
- )}
78
- </div>
79
+ <ContentCard
80
+ icon={<ListTodo size={13} />}
81
+ label="Tasks"
82
+ badge={
83
+ <span className="rounded-full bg-accent/15 px-2 py-0.5 text-[10px] font-semibold text-accent">
84
+ {tasks!.completed}/{tasks!.total}
85
+ </span>
86
+ }
87
+ defaultOpen
88
+ >
89
+ <div className="space-y-1.5">
90
+ {tasks!.items.map((task: any, idx: number) => (
91
+ <div key={idx} className="flex items-center gap-2">
92
+ <TaskIcon status={task.status} />
93
+ <span className={`text-[12px] ${task.status === 'completed' ? 'text-content-muted line-through' : 'text-content'}`}>
94
+ {task.name}
95
+ </span>
96
+ </div>
97
+ ))}
98
+ </div>
99
+ </ContentCard>
100
+ )}
101
+
102
+ {/* Description */}
103
+ {parsedContent?.description && (
104
+ <ContentCard icon={<AlignLeft size={13} />} label="Description" defaultOpen>
105
+ <div
106
+ className="text-[12px] leading-relaxed text-content-secondary [&_h1]:text-[15px] [&_h1]:mt-2 [&_h1]:mb-1.5 [&_h1]:text-content [&_h2]:text-[14px] [&_h2]:mt-2 [&_h2]:mb-1 [&_h2]:text-content [&_h3]:text-[13px] [&_h3]:mt-1.5 [&_h3]:mb-[3px] [&_h3]:text-content [&_p]:my-[3px] [&_ul]:pl-[18px] [&_ul]:my-[3px] [&_li]:my-px [&_code]:bg-surface-raised [&_code]:px-1 [&_code]:py-px [&_code]:rounded-[3px] [&_code]:text-[11px] [&_blockquote]:border-l-[3px] [&_blockquote]:border-edge [&_blockquote]:pl-2.5 [&_blockquote]:text-content-secondary [&_blockquote]:my-1.5 [&_strong]:text-content [&_pre]:bg-surface-raised [&_pre]:px-2.5 [&_pre]:py-2 [&_pre]:rounded [&_pre]:overflow-x-auto [&_pre]:text-[11px] [&_pre_code]:bg-transparent [&_pre_code]:p-0"
107
+ dangerouslySetInnerHTML={{ __html: renderMarkdownSection(parsedContent.description) }}
108
+ />
109
+ </ContentCard>
110
+ )}
111
+
112
+ {/* Acceptance Criteria */}
113
+ {parsedContent && parsedContent.acceptanceCriteria.length > 0 && (
114
+ <ContentCard
115
+ icon={<ClipboardCheck size={13} />}
116
+ label="Acceptance Criteria"
117
+ badge={<span className="text-[10px] text-content-muted">{parsedContent.acceptanceCriteria.length} criteria</span>}
118
+ >
119
+ <AccentBorderList>
120
+ {parsedContent.acceptanceCriteria.map((ac, i) => (
121
+ <AccentBorderItem key={i} color="accent" index={i + 1}>
122
+ <p className="text-[12px] leading-[1.75] text-content-secondary">{ac}</p>
123
+ </AccentBorderItem>
124
+ ))}
125
+ </AccentBorderList>
126
+ </ContentCard>
79
127
  )}
80
- <div dangerouslySetInnerHTML={{ __html: renderMarkdown(content) }} />
81
- {workSummaries.length > 0 && (
82
- <div className="mt-3 border-t border-edge pt-2">
83
- <button
84
- className="bg-none border-none text-content-secondary text-[13px] font-mono cursor-pointer py-1 w-full text-left hover:text-content"
85
- onClick={toggleSummaries}
86
- >
87
- {expandedSummaries ? '\u25BC' : '\u25B6'} Work Summary ({workSummaries.length} cycle{workSummaries.length !== 1 ? 's' : ''})
88
- </button>
89
- {expandedSummaries && (
90
- <div className="mt-2 text-xs text-content-secondary">
91
- {workSummaries.map((ws, idx) => (
92
- <div key={idx} className="mb-3 pb-2 border-b border-edge last:border-b-0 last:mb-0">
93
- <div className="font-semibold font-mono text-content mb-1">Cycle {ws.cycle}</div>
94
- {ws.filesCreated?.length > 0 && (
95
- <div>
96
- <span className="font-semibold text-content-secondary font-mono text-[11px] uppercase tracking-wider" style={{ color: '#4caf50' }}>Created:</span>
97
- <ul>
98
- {ws.filesCreated.map((f, fi) => (
99
- <li key={fi}>{f}</li>
100
- ))}
101
- </ul>
102
- </div>
103
- )}
104
- {ws.filesUpdated?.length > 0 && (
105
- <div>
106
- <span className="font-semibold text-content-secondary font-mono text-[11px] uppercase tracking-wider" style={{ color: '#ff9800' }}>Updated:</span>
107
- <ul>
108
- {ws.filesUpdated.map((f, fi) => (
109
- <li key={fi}>{f}</li>
110
- ))}
111
- </ul>
112
- </div>
113
- )}
114
- {ws.filesDeleted?.length > 0 && (
115
- <div>
116
- <span className="font-semibold text-content-secondary font-mono text-[11px] uppercase tracking-wider" style={{ color: '#f44336' }}>Deleted:</span>
117
- <ul>
118
- {ws.filesDeleted.map((f, fi) => (
119
- <li key={fi}>{f}</li>
120
- ))}
121
- </ul>
122
- </div>
123
- )}
124
- {ws.approach && (
125
- <div>
126
- <span className="font-semibold text-content-secondary font-mono text-[11px] uppercase tracking-wider">Approach:</span>
127
- <p>{ws.approach}</p>
128
- </div>
129
- )}
130
- {ws.decisions?.length > 0 && (
131
- <div>
132
- <span className="font-semibold text-content-secondary font-mono text-[11px] uppercase tracking-wider">Decisions:</span>
133
- <ul>
134
- {ws.decisions.map((d, di) => (
135
- <li key={di}>{d}</li>
136
- ))}
137
- </ul>
138
- </div>
139
- )}
128
+
129
+ {/* Open Questions */}
130
+ {parsedContent && parsedContent.openQuestions.length > 0 && (
131
+ <ContentCard
132
+ icon={<HelpCircle size={13} />}
133
+ label="Open Questions"
134
+ badge={
135
+ <span className="rounded-full bg-accent-secondary/15 px-2 py-0.5 text-[10px] font-semibold text-accent-secondary">
136
+ {parsedContent.openQuestions.length}
137
+ </span>
138
+ }
139
+ >
140
+ <AccentBorderList>
141
+ {parsedContent.openQuestions.map((q, i) => (
142
+ <AccentBorderItem key={i} color="secondary" index={i + 1}>
143
+ <p className="text-[12px] leading-[1.75] text-content">{q}</p>
144
+ </AccentBorderItem>
145
+ ))}
146
+ </AccentBorderList>
147
+ </ContentCard>
148
+ )}
149
+
150
+ {/* Resolved Questions */}
151
+ {parsedContent && parsedContent.resolvedQuestions.length > 0 && (
152
+ <ContentCard
153
+ icon={<CheckSquare size={13} />}
154
+ label="Resolved Questions"
155
+ badge={
156
+ <span className="rounded-full bg-accent/15 px-2 py-0.5 text-[10px] font-semibold text-accent">
157
+ {parsedContent.resolvedQuestions.length}
158
+ </span>
159
+ }
160
+ >
161
+ <AccentBorderList>
162
+ {parsedContent.resolvedQuestions.map((q, i) => (
163
+ <AccentBorderItem key={i} color="accent" index={i + 1}>
164
+ <p className="text-[12px] leading-[1.75] text-content">{q.question}</p>
165
+ {q.resolution && (
166
+ <p className="text-[12px] leading-[1.75] text-content-secondary mt-1">{q.resolution}</p>
167
+ )}
168
+ </AccentBorderItem>
169
+ ))}
170
+ </AccentBorderList>
171
+ </ContentCard>
172
+ )}
173
+
174
+ {/* Notes */}
175
+ {parsedContent?.notes && (
176
+ <ContentCard icon={<StickyNote size={13} />} label="Notes">
177
+ <div
178
+ className="text-[12px] leading-relaxed text-content-secondary [&_h1]:text-[15px] [&_h1]:mt-2 [&_h1]:mb-1.5 [&_h1]:text-content [&_h2]:text-[14px] [&_h2]:mt-2 [&_h2]:mb-1 [&_h2]:text-content [&_h3]:text-[13px] [&_h3]:mt-1.5 [&_h3]:mb-[3px] [&_h3]:text-content [&_p]:my-[3px] [&_ul]:pl-[18px] [&_ul]:my-[3px] [&_li]:my-px [&_code]:bg-surface-raised [&_code]:px-1 [&_code]:py-px [&_code]:rounded-[3px] [&_code]:text-[11px] [&_blockquote]:border-l-[3px] [&_blockquote]:border-edge [&_blockquote]:pl-2.5 [&_blockquote]:text-content-secondary [&_blockquote]:my-1.5 [&_strong]:text-content [&_pre]:bg-surface-raised [&_pre]:px-2.5 [&_pre]:py-2 [&_pre]:rounded [&_pre]:overflow-x-auto [&_pre]:text-[11px] [&_pre_code]:bg-transparent [&_pre_code]:p-0"
179
+ dangerouslySetInnerHTML={{ __html: renderMarkdownSection(parsedContent.notes) }}
180
+ />
181
+ </ContentCard>
182
+ )}
183
+
184
+ {/* Work Summary */}
185
+ {isFeature && workSummaries.length > 0 && (
186
+ <ContentCard
187
+ icon={<Activity size={13} />}
188
+ label="Work Summary"
189
+ badge={<span className="text-[10px] text-content-muted">{workSummaries.length} cycles</span>}
190
+ >
191
+ <AccentBorderList>
192
+ {workSummaries.map((ws, i) => (
193
+ <AccentBorderItem key={i} index={`Cycle ${ws.cycle}`}>
194
+ {ws.approach && (
195
+ <p className="text-[12px] leading-[1.75] text-content-secondary mt-1 mb-2">{ws.approach}</p>
196
+ )}
197
+ <div className="flex flex-wrap gap-1.5 mb-1.5">
198
+ {ws.filesCreated?.map((f, fi) => (
199
+ <span key={`c${fi}`} className="inline-flex items-center gap-1 rounded-md bg-accent/10 px-1.5 py-0.5 text-[10px] text-accent">
200
+ <FolderPlus size={10} />{f.split('/').pop()}
201
+ </span>
202
+ ))}
203
+ {ws.filesUpdated?.map((f, fi) => (
204
+ <span key={`u${fi}`} className="inline-flex items-center gap-1 rounded-md bg-accent-secondary/10 px-1.5 py-0.5 text-[10px] text-accent-secondary">
205
+ <FileText size={10} />{f.split('/').pop()}
206
+ </span>
207
+ ))}
208
+ {ws.filesDeleted?.map((f, fi) => (
209
+ <span key={`d${fi}`} className="inline-flex items-center gap-1 rounded-md bg-error/10 px-1.5 py-0.5 text-[10px] text-error">
210
+ <Trash2 size={10} />{f.split('/').pop()}
211
+ </span>
212
+ ))}
140
213
  </div>
141
- ))}
142
- </div>
143
- )}
144
- </div>
214
+ {ws.decisions?.length > 0 && (
215
+ <div className="space-y-1">
216
+ {ws.decisions.map((d, di) => (
217
+ <div key={di} className="flex items-start gap-1.5 text-[10px] text-content-muted">
218
+ <Lightbulb size={10} className="mt-0.5 shrink-0 text-accent-secondary" />
219
+ {d}
220
+ </div>
221
+ ))}
222
+ </div>
223
+ )}
224
+ </AccentBorderItem>
225
+ ))}
226
+ </AccentBorderList>
227
+ </ContentCard>
145
228
  )}
229
+
230
+ {/* Relationships */}
146
231
  {edges.length > 0 && (
147
- <div className="mt-4 pt-3 border-t border-edge">
148
- <h3 className="text-xs text-content mb-2 uppercase tracking-wide">Relationships</h3>
149
- <ul className="list-none p-0 m-0 flex flex-col gap-1">
232
+ <div className="rounded-xl border border-edge bg-surface-alt p-3">
233
+ <div className="flex items-center gap-2 mb-2">
234
+ <GitBranch size={13} className="text-content-muted" />
235
+ <span className="text-[12px] font-medium text-content">Relationships</span>
236
+ <span className="text-[10px] text-content-muted">{edges.length}</span>
237
+ </div>
238
+ <div className="space-y-1.5">
150
239
  {edges.map((edge, i) => {
151
240
  const neighborNode = graphData?.nodes.find((n: any) => n.id === edge.neighborId);
152
241
  const name = neighborNode ? neighborNode.name : edge.neighborId;
153
- const direction = edge.direction === 'outgoing' ? '\u2192' : '\u2190';
154
242
  return (
155
- <li key={i} className="flex items-center gap-1.5 px-1.5 py-1 rounded-sm transition-background duration-150 hover:bg-surface-raised">
156
- <span className="text-[11px] text-content-muted w-3.5 text-center shrink-0">{direction}</span>
157
- <span className="text-[10px] text-content-muted min-w-20 shrink-0">{edge.relation.replace(/_/g, ' ')}</span>
158
- <a className="text-xs text-accent no-underline cursor-pointer flex-1 overflow-hidden text-ellipsis whitespace-nowrap hover:underline" href="#" onClick={(e) => { e.preventDefault(); onEdgeClick?.(edge.neighborId); }}>
243
+ <div key={i} className="flex items-center gap-2 rounded-lg px-2 py-1.5 hover:bg-surface-raised/50 transition-colors group">
244
+ {edge.direction === 'outgoing'
245
+ ? <ArrowRight size={12} className="text-accent shrink-0" />
246
+ : <ArrowLeft size={12} className="text-accent-secondary shrink-0" />}
247
+ <span className="text-[11px] text-content-muted capitalize flex-shrink-0">
248
+ {edge.relation.replace(/_/g, ' ')}
249
+ </span>
250
+ <a
251
+ className="text-[12px] text-accent font-medium truncate group-hover:underline cursor-pointer"
252
+ href="#"
253
+ onClick={(e) => { e.preventDefault(); onEdgeClick?.(edge.neighborId); }}
254
+ >
159
255
  {name}
160
256
  </a>
161
- <span className="text-[10px] text-content-muted shrink-0">{edge.neighborId}</span>
162
- </li>
257
+ </div>
163
258
  );
164
259
  })}
165
- </ul>
260
+ </div>
166
261
  </div>
167
262
  )}
168
263
  </div>
@@ -0,0 +1,48 @@
1
+ /**
2
+ * SystemPromptAccordion — displays the composed system prompt as a collapsible
3
+ * accordion at the top of the chat message area.
4
+ *
5
+ * Collapsed by default with a 'System Prompt' label.
6
+ * Expanding reveals the full read-only system prompt text.
7
+ * Scrolls with messages (not pinned).
8
+ *
9
+ * Follows the design system: surface-alt background, edge borders,
10
+ * content-secondary text, monospace font. Consistent with ToolUseCard.
11
+ */
12
+
13
+ import { useState } from 'react';
14
+ import { ChevronDown, ChevronRight, Terminal } from 'lucide-react';
15
+
16
+ interface SystemPromptAccordionProps {
17
+ prompt: string;
18
+ }
19
+
20
+ export const SystemPromptAccordion = ({ prompt }: SystemPromptAccordionProps) => {
21
+ const [expanded, setExpanded] = useState(false);
22
+
23
+ return (
24
+ <div className="rounded-lg border border-edge bg-surface-alt overflow-hidden">
25
+ <button
26
+ type="button"
27
+ onClick={() => setExpanded(prev => !prev)}
28
+ className="flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-surface-raised/50 transition-colors cursor-pointer"
29
+ >
30
+ <Terminal size={13} className="text-content-muted shrink-0" />
31
+ <span className="text-[12px] font-mono text-content-secondary flex-1">
32
+ System Prompt
33
+ </span>
34
+ {expanded
35
+ ? <ChevronDown size={13} className="text-content-muted shrink-0" />
36
+ : <ChevronRight size={13} className="text-content-muted shrink-0" />}
37
+ </button>
38
+
39
+ {expanded && (
40
+ <div className="border-t border-edge px-3 py-2">
41
+ <pre className="text-[12px] font-mono text-content-secondary whitespace-pre-wrap break-words m-0 max-h-[400px] overflow-y-auto">
42
+ {prompt}
43
+ </pre>
44
+ </div>
45
+ )}
46
+ </div>
47
+ );
48
+ };
@@ -0,0 +1,11 @@
1
+ import { CheckCircle2, Circle, Loader2 } from 'lucide-react';
2
+
3
+ interface TaskIconProps {
4
+ status: string;
5
+ }
6
+
7
+ export const TaskIcon = ({ status }: TaskIconProps) => {
8
+ if (status === 'completed') return <CheckCircle2 size={14} className="text-accent shrink-0" />;
9
+ if (status === 'in_progress') return <Loader2 size={14} className="text-accent-secondary shrink-0 animate-spin" />;
10
+ return <Circle size={14} className="text-content-muted shrink-0" />;
11
+ };
@@ -16,7 +16,7 @@ import type { Project } from '../hooks/useProjects';
16
16
  import { useProjectStore } from '../stores/useProjectStore';
17
17
  import { Button } from './ds/Button';
18
18
  import { IconButton } from './ds/IconButton';
19
- import { X, Plus, RefreshCw } from 'lucide-react';
19
+ import { X, RefreshCw, Sparkles, TerminalSquare } from 'lucide-react';
20
20
 
21
21
  interface TerminalViewProps {
22
22
  visible: boolean;
@@ -29,6 +29,7 @@ interface SessionInfo {
29
29
  name: string;
30
30
  projectId: string;
31
31
  projectName: string;
32
+ sessionType: 'claude' | 'terminal';
32
33
  state: 'suspended' | 'running';
33
34
  createdAt: string;
34
35
  lastUsedAt: string;
@@ -194,7 +195,7 @@ export function TerminalView({ visible, projects }: TerminalViewProps) {
194
195
  connectToSession(sessionId);
195
196
  }, [connectToSession]);
196
197
 
197
- const handleCreateSession = useCallback(async () => {
198
+ const handleCreateSession = useCallback(async (sessionType: 'claude' | 'terminal') => {
198
199
  const projectId = selectedProjectId || projects[0]?.id;
199
200
  if (!projectId) return;
200
201
 
@@ -203,7 +204,7 @@ export function TerminalView({ visible, projects }: TerminalViewProps) {
203
204
 
204
205
  setCreating(true);
205
206
  try {
206
- const data = await apiClient.createTerminalSession(project.id, project.name);
207
+ const data = await apiClient.createTerminalSession(project.id, project.name, sessionType);
207
208
  const newSession: SessionInfo = data.session;
208
209
  const updated = await fetchSessions();
209
210
  const created = updated.find(s => s.id === newSession.id) || newSession;
@@ -324,6 +325,11 @@ export function TerminalView({ visible, projects }: TerminalViewProps) {
324
325
  {session.state === 'suspended' && (
325
326
  <span className="shrink-0 text-[10px] leading-none text-content-muted opacity-70" title="Suspended — will resume on connect">&#x23F8;</span>
326
327
  )}
328
+ {session.sessionType === 'terminal' ? (
329
+ <TerminalSquare size={11} strokeWidth={2} className="shrink-0 text-content-muted" title="Plain shell" />
330
+ ) : (
331
+ <Sparkles size={11} strokeWidth={2} className="shrink-0 text-accent" title="Claude session" />
332
+ )}
327
333
  {session.name}
328
334
  </div>
329
335
  <div className="text-[11px] text-content-muted font-mono whitespace-nowrap overflow-hidden text-ellipsis pr-5 mt-0.5">{session.projectName}</div>
@@ -340,17 +346,27 @@ export function TerminalView({ visible, projects }: TerminalViewProps) {
340
346
  ))}
341
347
  </div>
342
348
 
343
- {/* New session — uses the project selected in the navbar */}
344
- <div className="flex flex-col gap-1.5 p-2.5 border-t border-edge shrink-0">
349
+ {/* New session buttons — uses the project selected in the navbar */}
350
+ <div className="flex flex-row gap-1.5 p-2.5 border-t border-edge shrink-0">
345
351
  <Button
346
352
  variant="primary"
347
353
  size="sm"
348
- icon={!creating ? <Plus size={12} strokeWidth={2} /> : undefined}
349
- className="w-full"
350
- onClick={handleCreateSession}
354
+ icon={!creating ? <Sparkles size={12} strokeWidth={2} /> : undefined}
355
+ className="flex-1"
356
+ onClick={() => handleCreateSession('claude')}
357
+ disabled={creating || !selectedProjectId}
358
+ >
359
+ {creating ? '...' : 'Claude'}
360
+ </Button>
361
+ <Button
362
+ variant="secondary"
363
+ size="sm"
364
+ icon={!creating ? <TerminalSquare size={12} strokeWidth={2} /> : undefined}
365
+ className="flex-1"
366
+ onClick={() => handleCreateSession('terminal')}
351
367
  disabled={creating || !selectedProjectId}
352
368
  >
353
- {creating ? '...' : 'New'}
369
+ {creating ? '...' : 'Terminal'}
354
370
  </Button>
355
371
  </div>
356
372
  </div>
@@ -0,0 +1,114 @@
1
+ /**
2
+ * ToolDiffView — humanized side-by-side diff view for Edit and Write tool calls.
3
+ * Shows old_string vs new_string for Edit, or the full content for Write,
4
+ * rendered as syntax-highlighted, read-only code blocks.
5
+ */
6
+
7
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
8
+ import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
9
+
10
+ /** Map common file extensions to Prism language identifiers. */
11
+ const extToLang: Record<string, string> = {
12
+ ts: 'typescript', tsx: 'tsx', js: 'javascript', jsx: 'jsx',
13
+ py: 'python', rb: 'ruby', rs: 'rust', go: 'go',
14
+ java: 'java', kt: 'kotlin', cs: 'csharp', cpp: 'cpp', c: 'c',
15
+ css: 'css', scss: 'scss', html: 'html', vue: 'html',
16
+ json: 'json', yaml: 'yaml', yml: 'yaml', toml: 'toml',
17
+ md: 'markdown', sql: 'sql', sh: 'bash', bash: 'bash', zsh: 'bash',
18
+ xml: 'xml', svg: 'xml', graphql: 'graphql', prisma: 'prisma',
19
+ dockerfile: 'docker',
20
+ };
21
+
22
+ const inferLang = (filePath: string): string => {
23
+ const name = filePath.split('/').pop() ?? '';
24
+ if (name.toLowerCase() === 'dockerfile') return 'docker';
25
+ const ext = name.split('.').pop()?.toLowerCase() ?? '';
26
+ return extToLang[ext] ?? 'text';
27
+ };
28
+
29
+ /** Shared customStyle for the syntax highlighter to blend with our theme. */
30
+ const codeStyle: React.CSSProperties = {
31
+ margin: 0,
32
+ padding: '0.75rem',
33
+ fontSize: '12px',
34
+ lineHeight: '1.5',
35
+ background: 'transparent',
36
+ borderRadius: 0,
37
+ };
38
+
39
+ interface CodePanelProps {
40
+ label: string;
41
+ code: string;
42
+ language: string;
43
+ accent?: 'red' | 'green' | 'neutral';
44
+ }
45
+
46
+ const accentClasses: Record<string, string> = {
47
+ red: 'border-red-500/30 bg-red-500/5',
48
+ green: 'border-green-500/30 bg-green-500/5',
49
+ neutral: 'border-edge bg-surface-alt',
50
+ };
51
+
52
+ const labelClasses: Record<string, string> = {
53
+ red: 'text-red-400',
54
+ green: 'text-green-400',
55
+ neutral: 'text-content-muted',
56
+ };
57
+
58
+ const CodePanel = ({ label, code, language, accent = 'neutral' }: CodePanelProps) => (
59
+ <div className={`flex flex-col min-w-0 flex-1 border rounded-md overflow-hidden ${accentClasses[accent]}`}>
60
+ <div className={`text-[10px] font-mono uppercase tracking-wider px-3 py-1.5 border-b border-edge/50 ${labelClasses[accent]}`}>
61
+ {label}
62
+ </div>
63
+ <div className="overflow-auto max-h-[400px]">
64
+ <SyntaxHighlighter
65
+ language={language}
66
+ style={oneDark}
67
+ customStyle={codeStyle}
68
+ wrapLongLines
69
+ >
70
+ {code || '(empty)'}
71
+ </SyntaxHighlighter>
72
+ </div>
73
+ </div>
74
+ );
75
+
76
+ interface ToolDiffViewProps {
77
+ toolName: 'Edit' | 'Write';
78
+ input: Record<string, unknown>;
79
+ }
80
+
81
+ export const ToolDiffView = ({ toolName, input }: ToolDiffViewProps) => {
82
+ const filePath = (input.file_path as string) ?? '';
83
+ const language = inferLang(filePath);
84
+
85
+ if (toolName === 'Edit') {
86
+ const oldStr = (input.old_string as string) ?? '';
87
+ const newStr = (input.new_string as string) ?? '';
88
+
89
+ return (
90
+ <div className="flex flex-col gap-2">
91
+ <div className="text-[10px] font-mono text-content-muted truncate px-1">
92
+ {filePath}
93
+ {input.replace_all && <span className="ml-2 text-amber-400">(replace all)</span>}
94
+ </div>
95
+ <div className="flex gap-2 min-h-0">
96
+ <CodePanel label="Old" code={oldStr} language={language} accent="red" />
97
+ <CodePanel label="New" code={newStr} language={language} accent="green" />
98
+ </div>
99
+ </div>
100
+ );
101
+ }
102
+
103
+ // Write tool — single panel with content
104
+ const content = (input.content as string) ?? '';
105
+
106
+ return (
107
+ <div className="flex flex-col gap-2">
108
+ <div className="text-[10px] font-mono text-content-muted truncate px-1">
109
+ {filePath}
110
+ </div>
111
+ <CodePanel label="Content" code={content} language={language} />
112
+ </div>
113
+ );
114
+ };