@assistkick/create 1.6.0 → 1.8.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 (214) hide show
  1. package/package.json +2 -2
  2. package/templates/assistkick-product-system/.env.example +1 -0
  3. package/templates/assistkick-product-system/local.db +0 -0
  4. package/templates/assistkick-product-system/package.json +4 -2
  5. package/templates/assistkick-product-system/packages/backend/package.json +2 -0
  6. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
  7. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
  9. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
  10. package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
  11. package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +43 -4
  12. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
  13. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
  14. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
  15. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
  16. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
  17. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +154 -0
  18. package/templates/assistkick-product-system/packages/backend/src/server.ts +81 -9
  19. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
  20. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
  21. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
  22. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
  23. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +28 -78
  24. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
  25. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
  26. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
  27. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
  28. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
  29. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
  30. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
  31. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
  32. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
  33. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
  34. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
  35. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +222 -0
  40. package/templates/assistkick-product-system/packages/frontend/index.html +3 -0
  41. package/templates/assistkick-product-system/packages/frontend/package-lock.json +800 -11
  42. package/templates/assistkick-product-system/packages/frontend/package.json +11 -1
  43. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +24 -7
  44. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +456 -16
  45. package/templates/assistkick-product-system/packages/frontend/src/api/client_files.test.ts +172 -0
  46. package/templates/assistkick-product-system/packages/frontend/src/api/client_video.test.ts +238 -0
  47. package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +307 -0
  48. package/templates/assistkick-product-system/packages/frontend/src/components/CoherenceView.tsx +82 -66
  49. package/templates/assistkick-product-system/packages/frontend/src/components/CompositionPlaceholder.tsx +97 -0
  50. package/templates/assistkick-product-system/packages/frontend/src/components/DesignSystemView.tsx +383 -0
  51. package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +57 -0
  52. package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +313 -0
  53. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeContextMenu.tsx +61 -0
  54. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +73 -0
  55. package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +404 -0
  56. package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +193 -64
  57. package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +71 -73
  58. package/templates/assistkick-product-system/packages/frontend/src/components/GraphSettings.tsx +8 -8
  59. package/templates/assistkick-product-system/packages/frontend/src/components/GraphView.tsx +1 -1
  60. package/templates/assistkick-product-system/packages/frontend/src/components/InviteUserDialog.tsx +15 -11
  61. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +226 -291
  62. package/templates/assistkick-product-system/packages/frontend/src/components/LoginPage.tsx +14 -14
  63. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +54 -33
  64. package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +40 -66
  65. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +55 -115
  66. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
  67. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +155 -77
  68. package/templates/assistkick-product-system/packages/frontend/src/components/UsersView.tsx +52 -52
  69. package/templates/assistkick-product-system/packages/frontend/src/components/VideoGallery.tsx +313 -0
  70. package/templates/assistkick-product-system/packages/frontend/src/components/VideographyView.tsx +250 -0
  71. package/templates/assistkick-product-system/packages/frontend/src/components/WorkflowsView.tsx +474 -0
  72. package/templates/assistkick-product-system/packages/frontend/src/components/ds/AccentBorderList.tsx +53 -0
  73. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Button.tsx +87 -0
  74. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonGroup.tsx +29 -0
  75. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonShowcase.tsx +221 -0
  76. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CardGlass.tsx +141 -0
  77. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CompletionRing.tsx +30 -0
  78. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ContentCard.tsx +34 -0
  79. package/templates/assistkick-product-system/packages/frontend/src/components/ds/IconButton.tsx +74 -0
  80. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +270 -0
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +37 -0
  82. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Kbd.tsx +11 -0
  83. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KindBadge.tsx +21 -0
  84. package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +207 -0
  85. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SidePanelShowcase.tsx +370 -0
  86. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SideSheet.tsx +64 -0
  87. package/templates/assistkick-product-system/packages/frontend/src/components/ds/StatusDot.tsx +18 -0
  88. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCardPositionNode.tsx +36 -0
  89. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCycleCountNode.tsx +60 -0
  90. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/EndNode.tsx +42 -0
  91. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +189 -0
  92. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +123 -0
  93. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RunAgentNode.tsx +51 -0
  94. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/SetCardMetadataNode.tsx +53 -0
  95. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/StartNode.tsx +18 -0
  96. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/TransitionCardNode.tsx +59 -0
  97. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +335 -0
  98. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +634 -0
  99. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/autoLayout.ts +103 -0
  100. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/edgeColors.ts +35 -0
  101. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +208 -0
  102. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.test.ts +119 -0
  103. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +107 -0
  104. package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +13 -11
  105. package/templates/assistkick-product-system/packages/frontend/src/hooks/useAutoSave.ts +75 -0
  106. package/templates/assistkick-product-system/packages/frontend/src/hooks/useGraph.ts +6 -21
  107. package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +15 -80
  108. package/templates/assistkick-product-system/packages/frontend/src/hooks/useToast.tsx +16 -3
  109. package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
  110. package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
  111. package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
  112. package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
  113. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
  114. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +19 -0
  115. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +54 -0
  116. package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +6 -0
  117. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
  118. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +93 -0
  119. package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +30 -0
  120. package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +9 -0
  121. package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +6 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
  123. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
  124. package/templates/assistkick-product-system/packages/frontend/src/stores/useGitModalStore.ts +14 -0
  125. package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphStore.ts +36 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphUIStore.ts +25 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +90 -0
  128. package/templates/assistkick-product-system/packages/frontend/src/stores/useQaSheetStore.ts +27 -0
  129. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +76 -0
  130. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +336 -3632
  131. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
  132. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
  133. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
  134. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
  135. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
  136. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
  137. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
  138. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
  139. package/templates/assistkick-product-system/packages/frontend/vite.config.ts +7 -1
  140. package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
  141. package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
  142. package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
  143. package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
  144. package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
  145. package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
  146. package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
  147. package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
  148. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
  149. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
  150. package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
  151. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
  152. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
  153. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
  154. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
  155. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
  156. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
  157. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
  158. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
  159. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
  160. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
  161. package/templates/assistkick-product-system/packages/shared/db/schema.ts +113 -0
  162. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
  163. package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
  164. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
  165. package/templates/assistkick-product-system/packages/shared/lib/graph.ts +16 -5
  166. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1753 -0
  167. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1281 -0
  168. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
  169. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
  170. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
  171. package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
  172. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
  173. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
  174. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
  175. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
  176. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
  177. package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
  178. package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
  179. package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
  180. package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
  181. package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
  182. package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
  183. package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
  184. package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
  185. package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
  186. package/templates/assistkick-product-system/packages/video/index.ts +4 -0
  187. package/templates/assistkick-product-system/packages/video/package.json +28 -0
  188. package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
  189. package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
  190. package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
  191. package/templates/assistkick-product-system/packages/video/style.css +1 -0
  192. package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
  193. package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
  194. package/templates/assistkick-product-system/tests/video_render_service.test.ts +179 -0
  195. package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
  196. package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
  197. package/templates/skills/assistkick-bootstrap/SKILL.md +3 -3
  198. package/templates/skills/assistkick-code-reviewer/SKILL.md +2 -2
  199. package/templates/skills/assistkick-debugger/SKILL.md +2 -2
  200. package/templates/skills/assistkick-developer/SKILL.md +6 -3
  201. package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
  202. package/templates/skills/assistkick-interview/SKILL.md +2 -2
  203. package/templates/skills/product-system/graph.json +1890 -0
  204. package/templates/skills/product-system/kanban.json +304 -0
  205. package/templates/skills/product-system/nodes/comp_001.md +56 -0
  206. package/templates/skills/product-system/nodes/comp_002.md +57 -0
  207. package/templates/skills/product-system/nodes/data_001.md +51 -0
  208. package/templates/skills/product-system/nodes/data_002.md +40 -0
  209. package/templates/skills/product-system/nodes/data_004.md +38 -0
  210. package/templates/skills/product-system/nodes/dec_001.md +34 -0
  211. package/templates/skills/product-system/nodes/dec_016.md +32 -0
  212. package/templates/skills/product-system/nodes/feat_008.md +30 -0
  213. package/templates/skills/video-composition-agent/SKILL.md +232 -0
  214. package/templates/skills/video-script-writer/SKILL.md +136 -0
@@ -1,12 +1,7 @@
1
1
  import React, { useState, useEffect, useCallback } from 'react';
2
2
  import { apiClient } from '../api/client';
3
- import type { Project } from '../hooks/useProjects';
4
-
5
- interface GitRepoModalProps {
6
- project: Project;
7
- onClose: () => void;
8
- onProjectUpdated: () => void;
9
- }
3
+ import { useGitModalStore } from '../stores/useGitModalStore';
4
+ import { useProjectStore } from '../stores/useProjectStore';
10
5
 
11
6
  interface Installation {
12
7
  id: number;
@@ -22,32 +17,47 @@ interface Repo {
22
17
  cloneUrl: string;
23
18
  }
24
19
 
25
- type Tab = 'status' | 'github' | 'url' | 'init';
20
+ type Tab = 'status' | 'github' | 'ssh' | 'url' | 'init';
21
+
22
+ const tabCls = (active: boolean) =>
23
+ `bg-none border-none cursor-pointer font-mono text-xs px-3 py-2.5 border-b-2 ${
24
+ active ? 'text-accent border-b-accent' : 'text-content-secondary border-b-transparent hover:text-content'
25
+ }`;
26
+
27
+ const btnBase = 'inline-block px-4 py-2 border border-edge rounded bg-surface-raised text-content font-mono text-xs cursor-pointer mt-3 hover:bg-tab-hover disabled:opacity-50 disabled:cursor-not-allowed';
28
+ const btnPrimary = 'inline-block px-4 py-2 rounded font-mono text-xs cursor-pointer mt-3 bg-accent text-white border border-accent hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed';
29
+ const btnDanger = 'inline-block px-4 py-2 rounded font-mono text-xs cursor-pointer mt-4 bg-transparent border border-error text-error hover:bg-[rgba(255,107,107,0.1)] disabled:opacity-50 disabled:cursor-not-allowed';
26
30
 
27
- export function GitRepoModal({ project, onClose, onProjectUpdated }: GitRepoModalProps) {
31
+ export function GitRepoModal() {
32
+ const project = useGitModalStore((s) => s.project)!;
33
+ const onClose = useGitModalStore((s) => s.close);
34
+ const onProjectUpdated = useProjectStore((s) => s.refetchProjects);
28
35
  const [tab, setTab] = useState<Tab>('status');
29
36
  const [loading, setLoading] = useState(false);
30
37
  const [error, setError] = useState<string | null>(null);
31
38
  const [success, setSuccess] = useState<string | null>(null);
32
39
 
33
- // Status tab
34
40
  const [gitStatus, setGitStatus] = useState<any>(null);
35
41
 
36
- // GitHub tab
37
42
  const [installations, setInstallations] = useState<Installation[]>([]);
38
43
  const [selectedInstallation, setSelectedInstallation] = useState<string>('');
39
44
  const [repos, setRepos] = useState<Repo[]>([]);
40
45
  const [selectedRepo, setSelectedRepo] = useState<string>('');
41
46
  const [githubConfigured, setGithubConfigured] = useState(false);
42
47
 
43
- // URL tab
44
48
  const [cloneUrl, setCloneUrl] = useState('');
45
49
 
50
+ // SSH state
51
+ const [sshPublicKey, setSshPublicKey] = useState<string | null>(null);
52
+ const [sshCloneUrl, setSshCloneUrl] = useState('');
53
+ const [copied, setCopied] = useState(false);
54
+
46
55
  const loadStatus = useCallback(async () => {
47
56
  try {
48
57
  const data = await apiClient.getGitStatus(project.id);
49
58
  setGitStatus(data);
50
59
  setGithubConfigured(data.githubAppConfigured);
60
+ if (data.sshPublicKey) setSshPublicKey(data.sshPublicKey);
51
61
  } catch (err: any) {
52
62
  setError(err.message);
53
63
  }
@@ -165,6 +175,50 @@ export function GitRepoModal({ project, onClose, onProjectUpdated }: GitRepoModa
165
175
  try {
166
176
  await apiClient.disconnectGitRepo(project.id);
167
177
  setSuccess('Repository disconnected');
178
+ setSshPublicKey(null);
179
+ onProjectUpdated();
180
+ await loadStatus();
181
+ } catch (err: any) {
182
+ setError(err.message);
183
+ } finally {
184
+ setLoading(false);
185
+ }
186
+ };
187
+
188
+ const handleGenerateSshKey = async () => {
189
+ setLoading(true);
190
+ setError(null);
191
+ setCopied(false);
192
+ try {
193
+ const data = await apiClient.generateSshKey(project.id);
194
+ setSshPublicKey(data.publicKey);
195
+ setSuccess(sshPublicKey ? 'SSH keypair regenerated — old key is now invalid' : 'SSH keypair generated');
196
+ onProjectUpdated();
197
+ } catch (err: any) {
198
+ setError(err.message);
199
+ } finally {
200
+ setLoading(false);
201
+ }
202
+ };
203
+
204
+ const handleCopyPublicKey = async () => {
205
+ if (!sshPublicKey) return;
206
+ try {
207
+ await navigator.clipboard.writeText(sshPublicKey);
208
+ setCopied(true);
209
+ setTimeout(() => setCopied(false), 2000);
210
+ } catch {
211
+ setError('Failed to copy to clipboard');
212
+ }
213
+ };
214
+
215
+ const handleConnectSsh = async () => {
216
+ if (!sshCloneUrl.trim()) return;
217
+ setLoading(true);
218
+ setError(null);
219
+ try {
220
+ await apiClient.connectSshRepo(project.id, sshCloneUrl.trim());
221
+ setSuccess('Repository connected via SSH');
168
222
  onProjectUpdated();
169
223
  await loadStatus();
170
224
  } catch (err: any) {
@@ -175,98 +229,105 @@ export function GitRepoModal({ project, onClose, onProjectUpdated }: GitRepoModa
175
229
  };
176
230
 
177
231
  return (
178
- <div className="git-modal-overlay" onClick={onClose}>
179
- <div className="git-modal" onClick={e => e.stopPropagation()}>
180
- <div className="git-modal-header">
181
- <h3>Git Repository &mdash; {project.name}</h3>
182
- <button className="git-modal-close" onClick={onClose}>&times;</button>
232
+ <div className="fixed inset-0 bg-black/50 z-[1000] flex items-center justify-center" onClick={onClose}>
233
+ <div className="bg-surface-alt border border-edge rounded-lg w-[480px] max-w-[90vw] max-h-[80vh] overflow-y-auto shadow-[0_8px_32px_var(--panel-shadow)] font-mono text-[13px]" onClick={e => e.stopPropagation()}>
234
+ <div className="flex justify-between items-center px-5 pt-4 pb-3 border-b border-edge">
235
+ <h3 className="m-0 text-sm text-content font-semibold">Git Repository &mdash; {project.name}</h3>
236
+ <button className="bg-none border-none text-content-secondary cursor-pointer text-lg px-1 leading-none hover:text-content" onClick={onClose}>&times;</button>
183
237
  </div>
184
238
 
185
- {error && <div className="git-modal-error">{error}</div>}
186
- {success && <div className="git-modal-success">{success}</div>}
239
+ {error && <div className="px-5 py-2 text-error text-xs bg-[rgba(255,107,107,0.08)] border-b border-edge">{error}</div>}
240
+ {success && <div className="px-5 py-2 text-[#69db7c] text-xs bg-[rgba(105,219,124,0.08)] border-b border-edge">{success}</div>}
187
241
 
188
- <div className="git-modal-tabs">
189
- <button className={`git-modal-tab${tab === 'status' ? ' active' : ''}`} onClick={() => { setTab('status'); setError(null); setSuccess(null); }}>Status</button>
190
- <button className={`git-modal-tab${tab === 'github' ? ' active' : ''}`} onClick={() => { setTab('github'); setError(null); setSuccess(null); handleLoadInstallations(); }}>GitHub</button>
191
- <button className={`git-modal-tab${tab === 'url' ? ' active' : ''}`} onClick={() => { setTab('url'); setError(null); setSuccess(null); }}>Clone URL</button>
192
- <button className={`git-modal-tab${tab === 'init' ? ' active' : ''}`} onClick={() => { setTab('init'); setError(null); setSuccess(null); }}>Init</button>
242
+ <div className="flex border-b border-edge px-4">
243
+ <button className={tabCls(tab === 'status')} onClick={() => { setTab('status'); setError(null); setSuccess(null); }}>Status</button>
244
+ <button className={tabCls(tab === 'github')} onClick={() => { setTab('github'); setError(null); setSuccess(null); handleLoadInstallations(); }}>GitHub</button>
245
+ <button className={tabCls(tab === 'ssh')} onClick={() => { setTab('ssh'); setError(null); setSuccess(null); }}>SSH Key</button>
246
+ <button className={tabCls(tab === 'url')} onClick={() => { setTab('url'); setError(null); setSuccess(null); }}>Clone URL</button>
247
+ <button className={tabCls(tab === 'init')} onClick={() => { setTab('init'); setError(null); setSuccess(null); }}>Init</button>
193
248
  </div>
194
249
 
195
- <div className="git-modal-body">
250
+ <div className="px-5 py-4">
196
251
  {tab === 'status' && (
197
- <div className="git-modal-status">
252
+ <div>
198
253
  {gitStatus ? (
199
254
  <>
200
- <div className="git-status-row">
201
- <span className="git-status-label">Workspace:</span>
202
- <span className={`git-status-value ${gitStatus.hasWorkspace ? 'connected' : ''}`}>
255
+ <div className="flex justify-between py-1.5 border-b border-edge">
256
+ <span className="text-content-secondary text-xs">Workspace:</span>
257
+ <span className={`text-xs ${gitStatus.hasWorkspace ? 'text-[#69db7c]' : 'text-content'}`}>
203
258
  {gitStatus.hasWorkspace ? 'Configured' : 'Not configured'}
204
259
  </span>
205
260
  </div>
206
261
  {gitStatus.hasWorkspace && (
207
262
  <>
208
- <div className="git-status-row">
209
- <span className="git-status-label">Remote:</span>
210
- <span className={`git-status-value ${gitStatus.hasRemote ? 'connected' : ''}`}>
263
+ <div className="flex justify-between py-1.5 border-b border-edge">
264
+ <span className="text-content-secondary text-xs">Remote:</span>
265
+ <span className={`text-xs ${gitStatus.hasRemote ? 'text-[#69db7c]' : 'text-content'}`}>
211
266
  {gitStatus.hasRemote ? 'Connected' : 'Local only'}
212
267
  </span>
213
268
  </div>
214
- <div className="git-status-row">
215
- <span className="git-status-label">Branch:</span>
216
- <span className="git-status-value">{gitStatus.currentBranch || '-'}</span>
269
+ <div className="flex justify-between py-1.5 border-b border-edge">
270
+ <span className="text-content-secondary text-xs">Branch:</span>
271
+ <span className="text-xs text-content">{gitStatus.currentBranch || '-'}</span>
217
272
  </div>
218
273
  </>
219
274
  )}
220
275
  {project.githubRepoFullName && (
221
- <div className="git-status-row">
222
- <span className="git-status-label">GitHub Repo:</span>
223
- <span className="git-status-value">{project.githubRepoFullName}</span>
276
+ <div className="flex justify-between py-1.5 border-b border-edge">
277
+ <span className="text-content-secondary text-xs">GitHub Repo:</span>
278
+ <span className="text-xs text-content">{project.githubRepoFullName}</span>
224
279
  </div>
225
280
  )}
226
281
  {project.repoUrl && !project.githubRepoFullName && (
227
- <div className="git-status-row">
228
- <span className="git-status-label">Clone URL:</span>
229
- <span className="git-status-value git-status-url">{project.repoUrl}</span>
282
+ <div className="flex justify-between py-1.5 border-b border-edge">
283
+ <span className="text-content-secondary text-xs">Clone URL:</span>
284
+ <span className="text-xs text-content max-w-60 overflow-hidden text-ellipsis whitespace-nowrap">{project.repoUrl}</span>
230
285
  </div>
231
286
  )}
232
- <div className="git-status-row">
233
- <span className="git-status-label">GitHub App:</span>
234
- <span className={`git-status-value ${gitStatus.githubAppConfigured ? 'connected' : ''}`}>
287
+ <div className="flex justify-between py-1.5 border-b border-edge">
288
+ <span className="text-content-secondary text-xs">Auth Method:</span>
289
+ <span className="text-xs text-content">
290
+ {gitStatus.gitAuthMethod === 'ssh_key' ? 'SSH Key' : gitStatus.gitAuthMethod === 'github_app' ? 'GitHub App' : 'None'}
291
+ </span>
292
+ </div>
293
+ <div className="flex justify-between py-1.5 border-b border-edge">
294
+ <span className="text-content-secondary text-xs">GitHub App:</span>
295
+ <span className={`text-xs ${gitStatus.githubAppConfigured ? 'text-[#69db7c]' : 'text-content'}`}>
235
296
  {gitStatus.githubAppConfigured ? 'Configured' : 'Not configured'}
236
297
  </span>
237
298
  </div>
238
299
  {gitStatus.hasWorkspace && (
239
- <button className="git-modal-btn git-modal-btn-danger" onClick={handleDisconnect} disabled={loading}>
300
+ <button className={btnDanger} onClick={handleDisconnect} disabled={loading}>
240
301
  Disconnect
241
302
  </button>
242
303
  )}
243
304
  </>
244
305
  ) : (
245
- <div className="git-modal-loading">Loading...</div>
306
+ <div className="text-content-muted text-center py-5">Loading...</div>
246
307
  )}
247
308
  </div>
248
309
  )}
249
310
 
250
311
  {tab === 'github' && (
251
- <div className="git-modal-github">
312
+ <div>
252
313
  {!githubConfigured ? (
253
- <div className="git-modal-info">
314
+ <div className="text-content-secondary text-xs leading-relaxed mb-3">
254
315
  GitHub App not configured. Set GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY environment variables.
255
- <button className="git-modal-btn" onClick={handleTestConnection} disabled={loading}>
316
+ <button className={btnBase} onClick={handleTestConnection} disabled={loading}>
256
317
  Test Connection
257
318
  </button>
258
319
  </div>
259
320
  ) : (
260
321
  <>
261
322
  {installations.length === 0 ? (
262
- <div className="git-modal-info">
323
+ <div className="text-content-secondary text-xs leading-relaxed mb-3">
263
324
  No GitHub App installations found. Install the app on your GitHub organization or account.
264
325
  </div>
265
326
  ) : (
266
- <div className="git-modal-installations">
267
- <label className="git-modal-label">Installation:</label>
327
+ <div>
328
+ <label className="block text-content-secondary text-[11px] uppercase tracking-wider mb-1.5 mt-3">Installation:</label>
268
329
  <select
269
- className="git-modal-select"
330
+ className="w-full py-2 px-2.5 bg-surface-raised border border-edge rounded text-content font-mono text-xs"
270
331
  value={selectedInstallation}
271
332
  onChange={e => handleLoadRepos(e.target.value)}
272
333
  >
@@ -281,10 +342,10 @@ export function GitRepoModal({ project, onClose, onProjectUpdated }: GitRepoModa
281
342
  )}
282
343
 
283
344
  {repos.length > 0 && (
284
- <div className="git-modal-repos">
285
- <label className="git-modal-label">Repository:</label>
345
+ <div>
346
+ <label className="block text-content-secondary text-[11px] uppercase tracking-wider mb-1.5 mt-3">Repository:</label>
286
347
  <select
287
- className="git-modal-select"
348
+ className="w-full py-2 px-2.5 bg-surface-raised border border-edge rounded text-content font-mono text-xs"
288
349
  value={selectedRepo}
289
350
  onChange={e => setSelectedRepo(e.target.value)}
290
351
  >
@@ -296,7 +357,7 @@ export function GitRepoModal({ project, onClose, onProjectUpdated }: GitRepoModa
296
357
  ))}
297
358
  </select>
298
359
  <button
299
- className="git-modal-btn git-modal-btn-primary"
360
+ className={btnPrimary}
300
361
  onClick={handleConnectGitHub}
301
362
  disabled={loading || !selectedRepo}
302
363
  >
@@ -309,11 +370,79 @@ export function GitRepoModal({ project, onClose, onProjectUpdated }: GitRepoModa
309
370
  </div>
310
371
  )}
311
372
 
373
+ {tab === 'ssh' && (
374
+ <div>
375
+ <p className="text-content-secondary text-xs leading-relaxed mb-3">
376
+ Generate an SSH deploy key for this project. Add the public key to your GitHub repository
377
+ as a deploy key to enable SSH-based git operations.
378
+ </p>
379
+
380
+ {!sshPublicKey ? (
381
+ <button
382
+ className={btnPrimary}
383
+ onClick={handleGenerateSshKey}
384
+ disabled={loading}
385
+ >
386
+ {loading ? 'Generating...' : 'Generate SSH Key'}
387
+ </button>
388
+ ) : (
389
+ <>
390
+ <label className="block text-content-secondary text-[11px] uppercase tracking-wider mb-1.5 mt-3">Public key:</label>
391
+ <div className="relative">
392
+ <textarea
393
+ className="w-full py-2 px-2.5 bg-surface-raised border border-edge rounded text-content font-mono text-[11px] outline-none resize-none h-16"
394
+ readOnly
395
+ value={sshPublicKey}
396
+ />
397
+ <button
398
+ className="absolute top-1.5 right-1.5 px-2 py-1 bg-surface-raised border border-edge rounded text-[10px] text-content-secondary hover:text-content cursor-pointer"
399
+ onClick={handleCopyPublicKey}
400
+ >
401
+ {copied ? 'Copied!' : 'Copy'}
402
+ </button>
403
+ </div>
404
+
405
+ <div className="mt-3 p-2.5 bg-[rgba(255,200,50,0.08)] border border-[rgba(255,200,50,0.2)] rounded text-xs text-content-secondary leading-relaxed">
406
+ Add this key as a <strong className="text-content">deploy key</strong> in your GitHub repo settings.
407
+ Make sure to check <strong className="text-content">&quot;Allow write access&quot;</strong> so the system can push commits.
408
+ </div>
409
+
410
+ <div className="mt-4 border-t border-edge pt-4">
411
+ <label className="block text-content-secondary text-[11px] uppercase tracking-wider mb-1.5">SSH clone URL:</label>
412
+ <input
413
+ className="w-full py-2 px-2.5 bg-surface-raised border border-edge rounded text-content font-mono text-xs outline-none focus:border-accent"
414
+ type="text"
415
+ placeholder="git@github.com:org/repo.git"
416
+ value={sshCloneUrl}
417
+ onChange={e => setSshCloneUrl(e.target.value)}
418
+ onKeyDown={e => { if (e.key === 'Enter') handleConnectSsh(); }}
419
+ />
420
+ <button
421
+ className={btnPrimary}
422
+ onClick={handleConnectSsh}
423
+ disabled={loading || !sshCloneUrl.trim()}
424
+ >
425
+ {loading ? 'Cloning...' : 'Clone & Connect via SSH'}
426
+ </button>
427
+ </div>
428
+
429
+ <button
430
+ className={`${btnBase} mt-4`}
431
+ onClick={handleGenerateSshKey}
432
+ disabled={loading}
433
+ >
434
+ {loading ? 'Regenerating...' : 'Regenerate Key'}
435
+ </button>
436
+ </>
437
+ )}
438
+ </div>
439
+ )}
440
+
312
441
  {tab === 'url' && (
313
- <div className="git-modal-url">
314
- <label className="git-modal-label">Git clone URL:</label>
442
+ <div>
443
+ <label className="block text-content-secondary text-[11px] uppercase tracking-wider mb-1.5 mt-3">Git clone URL:</label>
315
444
  <input
316
- className="git-modal-input"
445
+ className="w-full py-2 px-2.5 bg-surface-raised border border-edge rounded text-content font-mono text-xs outline-none focus:border-accent"
317
446
  type="text"
318
447
  placeholder="https://github.com/org/repo.git"
319
448
  value={cloneUrl}
@@ -321,7 +450,7 @@ export function GitRepoModal({ project, onClose, onProjectUpdated }: GitRepoModa
321
450
  onKeyDown={e => { if (e.key === 'Enter') handleConnectUrl(); }}
322
451
  />
323
452
  <button
324
- className="git-modal-btn git-modal-btn-primary"
453
+ className={btnPrimary}
325
454
  onClick={handleConnectUrl}
326
455
  disabled={loading || !cloneUrl.trim()}
327
456
  >
@@ -331,13 +460,13 @@ export function GitRepoModal({ project, onClose, onProjectUpdated }: GitRepoModa
331
460
  )}
332
461
 
333
462
  {tab === 'init' && (
334
- <div className="git-modal-init">
335
- <p className="git-modal-info">
463
+ <div>
464
+ <p className="text-content-secondary text-xs leading-relaxed mb-3">
336
465
  Initialize a new empty local git repository for this project.
337
466
  You can connect it to a remote later.
338
467
  </p>
339
468
  <button
340
- className="git-modal-btn git-modal-btn-primary"
469
+ className={btnPrimary}
341
470
  onClick={handleInit}
342
471
  disabled={loading}
343
472
  >
@@ -35,86 +35,84 @@ export function GraphLegend({ visible, onTypeToggle, onSearchChange }: GraphLege
35
35
  if (!visible) return null;
36
36
 
37
37
  return (
38
- <div id="graph-legend">
39
- <div className="graph-legend">
40
- <div className="legend-search-container" id="search-container">
41
- <input
42
- type="text"
43
- className="search-input"
44
- placeholder="Search nodes..."
45
- onChange={(e) => onSearchChange(e.target.value)}
46
- />
38
+ <div className="fixed bottom-4 left-4 bg-surface-alt border border-edge rounded-md z-[150] max-h-[calc(100vh-116px)] overflow-y-auto min-w-[180px] shadow-[0_2px_8px_var(--panel-shadow)]">
39
+ <div className="relative flex flex-wrap items-center gap-1.5 px-2.5 py-2 border-b border-edge" id="search-container">
40
+ <input
41
+ type="text"
42
+ className="flex-1 min-w-0 h-7 px-2 bg-surface-raised border border-edge rounded text-content font-mono text-[11px] outline-none transition-[border-color] duration-150 focus:border-accent placeholder:text-content-muted"
43
+ placeholder="Search nodes..."
44
+ onChange={(e) => onSearchChange(e.target.value)}
45
+ />
46
+ </div>
47
+ <div className="flex items-center gap-1.5 px-3 py-2 text-content-secondary font-mono text-[11px] font-semibold uppercase tracking-wide"><span>Legend</span></div>
48
+ <div className="px-3 pb-2.5">
49
+ <div className="mt-2 first:mt-0">
50
+ <div className="text-[10px] text-content-muted uppercase tracking-wider mb-1">Node Types</div>
51
+ <div className="flex flex-col gap-[3px]">
52
+ {Object.entries(NODE_COLORS).map(([type, color]) => {
53
+ const shape = NODE_SHAPES[type] || 'circle';
54
+ const pathD = nodeShapePath(shape, 6);
55
+ const active = !hiddenTypes.has(type);
56
+ return (
57
+ <div
58
+ key={type}
59
+ className={`flex items-center gap-2 cursor-pointer transition-opacity duration-150 rounded px-1 py-0.5 -mx-1 -my-0.5 hover:bg-surface-raised ${active ? 'opacity-100' : 'opacity-40'}`}
60
+ onClick={() => handleTypeClick(type)}
61
+ title={`Toggle ${LEGEND_LABELS[type]}`}
62
+ >
63
+ <svg className="shrink-0" width="16" height="16" viewBox="-8 -8 16 16">
64
+ <path d={pathD} fill={color} />
65
+ </svg>
66
+ <span className="text-[11px] text-content-secondary whitespace-nowrap">{LEGEND_LABELS[type] || type}</span>
67
+ </div>
68
+ );
69
+ })}
70
+ </div>
47
71
  </div>
48
- <div className="legend-header"><span>Legend</span></div>
49
- <div className="legend-body">
50
- <div className="legend-section">
51
- <div className="legend-section-title">Node Types</div>
52
- <div className="legend-items">
53
- {Object.entries(NODE_COLORS).map(([type, color]) => {
54
- const shape = NODE_SHAPES[type] || 'circle';
55
- const pathD = nodeShapePath(shape, 6);
56
- const active = !hiddenTypes.has(type);
57
- return (
58
- <div
59
- key={type}
60
- className={`legend-item legend-type-toggle${active ? ' active' : ''}`}
61
- onClick={() => handleTypeClick(type)}
62
- title={`Toggle ${LEGEND_LABELS[type]}`}
63
- >
64
- <svg className="legend-node-svg" width="16" height="16" viewBox="-8 -8 16 16">
65
- <path d={pathD} fill={color} />
66
- </svg>
67
- <span className="legend-item-label">{LEGEND_LABELS[type] || type}</span>
68
- </div>
69
- );
70
- })}
71
- </div>
72
+ <div className="mt-2 first:mt-0">
73
+ <div className="text-[10px] text-content-muted uppercase tracking-wider mb-1">Node Size</div>
74
+ <div className="flex items-center gap-1.5">
75
+ <span className="w-2 h-2 rounded-full" style={{ background: 'var(--text-muted)' }} />
76
+ <span className="text-[10px] text-content-muted">{'\u2192'}</span>
77
+ <span className="w-[18px] h-[18px] rounded-full" style={{ background: 'var(--text-muted)' }} />
78
+ <span className="text-[11px] text-content-secondary whitespace-nowrap">more connections = larger</span>
72
79
  </div>
73
- <div className="legend-section">
74
- <div className="legend-section-title">Node Size</div>
75
- <div className="legend-size-explanation">
76
- <span className="legend-size-small" />
77
- <span className="legend-size-arrow">{'\u2192'}</span>
78
- <span className="legend-size-large" />
79
- <span className="legend-item-label">more connections = larger</span>
80
- </div>
80
+ </div>
81
+ </div>
82
+ <button className="flex items-center gap-1.5 w-full px-3 py-1.5 bg-transparent border-none border-t border-edge text-content-secondary font-mono text-[11px] font-semibold cursor-pointer uppercase tracking-wide hover:text-content" onClick={toggleEdges} title="Toggle edge relations">
83
+ <span className="text-[9px]" dangerouslySetInnerHTML={{ __html: edgesCollapsed ? '&#9654;' : '&#9660;' }} />
84
+ <span>Edge Relations</span>
85
+ </button>
86
+ <div className={edgesCollapsed ? 'hidden' : 'px-3 pb-2.5'}>
87
+ <div className="mt-2 first:mt-0">
88
+ <div className="flex flex-col gap-[3px]">
89
+ {Object.entries(EDGE_COLORS).map(([relation, color]) => (
90
+ <div key={relation} className="flex items-center gap-2">
91
+ <span className="w-5 h-[3px] rounded-sm shrink-0" style={{ background: color }} />
92
+ <span className="text-[11px] text-content-secondary whitespace-nowrap">{EDGE_LABELS[relation] || relation}</span>
93
+ </div>
94
+ ))}
81
95
  </div>
82
96
  </div>
83
- <button className="legend-edges-toggle" onClick={toggleEdges} title="Toggle edge relations">
84
- <span className="legend-toggle-icon" dangerouslySetInnerHTML={{ __html: edgesCollapsed ? '&#9654;' : '&#9660;' }} />
85
- <span>Edge Relations</span>
86
- </button>
87
- <div className={`legend-edges-body${edgesCollapsed ? ' collapsed' : ''}`}>
88
- <div className="legend-section">
89
- <div className="legend-items">
90
- {Object.entries(EDGE_COLORS).map(([relation, color]) => (
91
- <div key={relation} className="legend-item">
92
- <span className="legend-edge-swatch" style={{ background: color }} />
93
- <span className="legend-item-label">{EDGE_LABELS[relation] || relation}</span>
94
- </div>
95
- ))}
97
+ <div className="mt-2 first:mt-0">
98
+ <div className="text-[10px] text-content-muted uppercase tracking-wider mb-1">Node Border</div>
99
+ <div className="flex flex-col gap-[3px]">
100
+ <div className="flex items-center gap-2">
101
+ <span className="w-3 h-3 rounded-full shrink-0 !bg-surface-raised border-2 border-[#ffd43b]" />
102
+ <span className="text-[11px] text-content-secondary whitespace-nowrap">incomplete</span>
96
103
  </div>
97
- </div>
98
- <div className="legend-section">
99
- <div className="legend-section-title">Node Border</div>
100
- <div className="legend-items">
101
- <div className="legend-item">
102
- <span className="legend-node-swatch legend-border-yellow" />
103
- <span className="legend-item-label">incomplete</span>
104
- </div>
105
- <div className="legend-item">
106
- <span className="legend-node-swatch legend-border-red" />
107
- <span className="legend-item-label">has open questions</span>
108
- </div>
104
+ <div className="flex items-center gap-2">
105
+ <span className="w-3 h-3 rounded-full shrink-0 !bg-surface-raised border-2 border-[#ff6b6b]" />
106
+ <span className="text-[11px] text-content-secondary whitespace-nowrap">has open questions</span>
109
107
  </div>
110
108
  </div>
111
- <div className="legend-section">
112
- <div className="legend-section-title">Gap Indicators</div>
113
- <div className="legend-items">
114
- <div className="legend-item">
115
- <span className="legend-node-swatch legend-gap-pulse" />
116
- <span className="legend-item-label">{'\u26A0'} pulsing = open questions</span>
117
- </div>
109
+ </div>
110
+ <div className="mt-2 first:mt-0">
111
+ <div className="text-[10px] text-content-muted uppercase tracking-wider mb-1">Gap Indicators</div>
112
+ <div className="flex flex-col gap-[3px]">
113
+ <div className="flex items-center gap-2">
114
+ <span className="w-3 h-3 rounded-full shrink-0 legend-gap-pulse" />
115
+ <span className="text-[11px] text-content-secondary whitespace-nowrap">{'\u26A0'} pulsing = open questions</span>
118
116
  </div>
119
117
  </div>
120
118
  </div>
@@ -89,18 +89,18 @@ export function GraphSettings({ isOpen, onClose, graphRef }: GraphSettingsProps)
89
89
  ];
90
90
 
91
91
  return (
92
- <div className={`settings-sheet${isOpen ? ' open' : ''}`} ref={sheetRef}>
93
- <div className="settings-header">
94
- <span className="settings-title">Settings</span>
95
- <button className="settings-close" onClick={onClose}>&times;</button>
92
+ <div className={`fixed top-[84px] right-0 w-[280px] bg-panel border-l border-edge shadow-[-4px_0_16px_var(--panel-shadow)] transition-transform duration-[250ms] ease-out z-[250] flex flex-col ${isOpen ? 'translate-x-0' : 'translate-x-full'}`} ref={sheetRef}>
93
+ <div className="flex items-center justify-between px-4 py-3 border-b border-edge min-h-12">
94
+ <span className="text-[13px] font-semibold text-content uppercase tracking-wide">Settings</span>
95
+ <button className="bg-transparent border-none text-content-secondary text-lg cursor-pointer px-2 py-1 rounded hover:bg-tab-hover hover:text-content" onClick={onClose}>&times;</button>
96
96
  </div>
97
- <div className="settings-body">
97
+ <div className="px-4 py-3 flex flex-col">
98
98
  {toggleItems.map(({ key, label }) => (
99
- <label key={key} className="settings-row">
100
- <span className="settings-label">{label}</span>
99
+ <label key={key} className="flex items-center justify-between py-2.5 border-b border-edge cursor-pointer last:border-b-0">
100
+ <span className="text-xs text-content-secondary">{label}</span>
101
101
  <input
102
102
  type="checkbox"
103
- className="settings-toggle"
103
+ className="settings-toggle appearance-none w-9 h-5 bg-surface-raised border border-edge rounded-[10px] relative cursor-pointer transition-[background,border-color] duration-150 shrink-0 checked:bg-[rgba(77,171,247,0.2)] checked:border-accent"
104
104
  checked={settings[key]}
105
105
  onChange={() => handleToggle(key)}
106
106
  />
@@ -365,6 +365,6 @@ export const GraphView = forwardRef<GraphViewHandle, GraphViewProps>(
365
365
  return () => window.removeEventListener('resize', handleResize);
366
366
  }, [visible]);
367
367
 
368
- return <div id="graph-container" ref={containerRef} style={{ display: visible ? 'block' : 'none' }} />;
368
+ return <div className="w-full h-[calc(100vh-44px)] overflow-hidden" ref={containerRef} style={{ display: visible ? 'block' : 'none' }} />;
369
369
  }
370
370
  );