@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
@@ -0,0 +1,75 @@
1
+ import { useRef, useCallback, useEffect, useState } from 'react';
2
+ import { AutoSaveService } from '../utils/auto_save_service';
3
+ import { apiClient } from '../api/client';
4
+ import { useToast } from './useToast';
5
+ import { getFileName } from '../utils/file_utils';
6
+
7
+ interface UseAutoSaveOptions {
8
+ projectId: string | null;
9
+ }
10
+
11
+ export const useAutoSave = ({ projectId }: UseAutoSaveOptions) => {
12
+ const { showToast } = useToast();
13
+ const [dirtyPaths, setDirtyPaths] = useState<Set<string>>(new Set());
14
+ const serviceRef = useRef<AutoSaveService | null>(null);
15
+ const projectIdRef = useRef(projectId);
16
+ projectIdRef.current = projectId;
17
+
18
+ // Keep showToast ref stable to avoid recreating service
19
+ const showToastRef = useRef(showToast);
20
+ showToastRef.current = showToast;
21
+
22
+ const getService = useCallback(() => {
23
+ if (!serviceRef.current) {
24
+ serviceRef.current = new AutoSaveService({
25
+ save: async (path: string, content: string) => {
26
+ const pid = projectIdRef.current;
27
+ if (!pid) throw new Error('No project selected');
28
+ await apiClient.saveFileContent(pid, path, content);
29
+ },
30
+ onDirtyChange: (path: string, dirty: boolean) => {
31
+ setDirtyPaths((prev) => {
32
+ const next = new Set(prev);
33
+ if (dirty) {
34
+ next.add(path);
35
+ } else {
36
+ next.delete(path);
37
+ }
38
+ return next;
39
+ });
40
+ },
41
+ onSaveError: (path: string, error: string) => {
42
+ const fileName = getFileName(path);
43
+ showToastRef.current(`Failed to save ${fileName}: ${error}`, 'error');
44
+ },
45
+ });
46
+ }
47
+ return serviceRef.current;
48
+ }, []);
49
+
50
+ // Dispose on unmount
51
+ useEffect(() => {
52
+ return () => {
53
+ serviceRef.current?.dispose();
54
+ serviceRef.current = null;
55
+ };
56
+ }, []);
57
+
58
+ const handleChange = useCallback((path: string, content: string) => {
59
+ getService().handleChange(path, content);
60
+ }, [getService]);
61
+
62
+ const saveNow = useCallback((path: string) => {
63
+ getService().saveNow(path);
64
+ }, [getService]);
65
+
66
+ const saveAndDiscard = useCallback((path: string) => {
67
+ getService().saveAndDiscard(path);
68
+ }, [getService]);
69
+
70
+ const isDirty = useCallback((path: string): boolean => {
71
+ return dirtyPaths.has(path);
72
+ }, [dirtyPaths]);
73
+
74
+ return { handleChange, saveNow, saveAndDiscard, isDirty, dirtyPaths };
75
+ };
@@ -1,27 +1,12 @@
1
- import { useState, useEffect, useCallback } from 'react';
2
- import { apiClient } from '../api/client';
1
+ import { useEffect } from 'react';
2
+ import { useGraphStore } from '../stores/useGraphStore';
3
3
 
4
4
  export function useGraph(projectId?: string | null) {
5
- const [graphData, setGraphData] = useState<any>(null);
6
- const [error, setError] = useState<string | null>(null);
7
- const [loading, setLoading] = useState(true);
8
-
9
- const fetchGraph = useCallback(async () => {
10
- try {
11
- setLoading(true);
12
- const data = await apiClient.fetchGraph(projectId ?? undefined);
13
- setGraphData(data);
14
- setError(null);
15
- } catch (err: any) {
16
- setError(err.message);
17
- } finally {
18
- setLoading(false);
19
- }
20
- }, [projectId]);
5
+ const { graphData, error, loading, fetchGraph } = useGraphStore();
21
6
 
22
7
  useEffect(() => {
23
- fetchGraph();
24
- }, [fetchGraph]);
8
+ fetchGraph(projectId);
9
+ }, [projectId, fetchGraph]);
25
10
 
26
- return { graphData, error, loading, refetch: fetchGraph };
11
+ return { graphData, error, loading, refetch: () => fetchGraph(projectId) };
27
12
  }
@@ -1,90 +1,25 @@
1
- import { useState, useEffect, useCallback } from 'react';
2
- import { apiClient } from '../api/client';
1
+ import { useEffect } from 'react';
2
+ import { useProjectStore } from '../stores/useProjectStore';
3
3
 
4
- const STORAGE_KEY = 'selectedProjectId';
5
-
6
- export interface Project {
7
- id: string;
8
- name: string;
9
- isDefault: number;
10
- archivedAt: string | null;
11
- repoUrl: string | null;
12
- githubInstallationId: string | null;
13
- githubRepoFullName: string | null;
14
- baseBranch: string | null;
15
- createdAt: string;
16
- updatedAt: string;
17
- }
4
+ export type { Project } from '../stores/useProjectStore';
18
5
 
19
6
  export function useProjects() {
20
- const [projects, setProjects] = useState<Project[]>([]);
21
- const [selectedProjectId, setSelectedProjectId] = useState<string | null>(
22
- () => localStorage.getItem(STORAGE_KEY),
23
- );
24
- const [loading, setLoading] = useState(true);
25
-
26
- const fetchProjects = useCallback(async () => {
27
- try {
28
- const data = await apiClient.fetchProjects();
29
- setProjects(data.projects);
30
- return data.projects as Project[];
31
- } catch {
32
- setProjects([]);
33
- return [] as Project[];
34
- } finally {
35
- setLoading(false);
36
- }
37
- }, []);
7
+ const store = useProjectStore();
38
8
 
39
9
  useEffect(() => {
40
- fetchProjects().then((fetched) => {
41
- const stored = localStorage.getItem(STORAGE_KEY);
42
- if (stored && fetched.some((p: Project) => p.id === stored)) {
43
- setSelectedProjectId(stored);
44
- } else if (fetched.length > 0) {
45
- const defaultProject = fetched.find((p: Project) => p.isDefault === 1);
46
- const fallback = defaultProject ? defaultProject.id : fetched[0].id;
47
- setSelectedProjectId(fallback);
48
- localStorage.setItem(STORAGE_KEY, fallback);
49
- }
50
- });
51
- }, [fetchProjects]);
52
-
53
- const selectProject = useCallback((id: string) => {
54
- setSelectedProjectId(id);
55
- localStorage.setItem(STORAGE_KEY, id);
56
- }, []);
57
-
58
- const createProject = useCallback(async (name: string) => {
59
- const data = await apiClient.createProject(name);
60
- await fetchProjects();
61
- selectProject(data.project.id);
62
- return data.project;
63
- }, [fetchProjects, selectProject]);
64
-
65
- const renameProject = useCallback(async (id: string, name: string) => {
66
- await apiClient.renameProject(id, name);
67
- await fetchProjects();
68
- }, [fetchProjects]);
69
-
70
- const archiveProject = useCallback(async (id: string) => {
71
- await apiClient.archiveProject(id);
72
- const updated = await fetchProjects();
73
- if (selectedProjectId === id && updated.length > 0) {
74
- const defaultProject = updated.find((p: Project) => p.isDefault === 1);
75
- const fallback = defaultProject ? defaultProject.id : updated[0].id;
76
- selectProject(fallback);
10
+ if (store.loading) {
11
+ store.init();
77
12
  }
78
- }, [fetchProjects, selectProject, selectedProjectId]);
13
+ }, []);
79
14
 
80
15
  return {
81
- projects,
82
- selectedProjectId,
83
- loading,
84
- selectProject,
85
- createProject,
86
- renameProject,
87
- archiveProject,
88
- refetchProjects: fetchProjects,
16
+ projects: store.projects,
17
+ selectedProjectId: store.selectedProjectId,
18
+ loading: store.loading,
19
+ selectProject: store.selectProject,
20
+ createProject: store.createProject,
21
+ renameProject: store.renameProject,
22
+ archiveProject: store.archiveProject,
23
+ refetchProjects: store.refetchProjects,
89
24
  };
90
25
  }
@@ -41,11 +41,24 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
41
41
  <ToastContext.Provider value={{ showToast }}>
42
42
  {children}
43
43
  {createPortal(
44
- <div className="toast-container">
44
+ <div className="fixed bottom-5 right-5 z-[10000] flex flex-col-reverse gap-2 pointer-events-none">
45
45
  {toasts.map(toast => (
46
- <div key={toast.id} className={`kanban-toast kanban-toast-${toast.type}`}>
46
+ <div
47
+ key={toast.id}
48
+ className={[
49
+ 'relative max-w-[480px] pointer-events-auto bg-surface-alt border border-edge rounded-lg pr-9 pl-4 py-3 shadow-[0_4px_16px_rgba(0,0,0,0.3)] font-mono text-xs text-content animate-[toast-slide-in_0.2s_ease-out]',
50
+ toast.type === 'error' && 'border-error border-l-4 text-error bg-gradient-to-br from-surface-alt to-[rgba(255,107,107,0.08)]',
51
+ toast.type === 'success' && 'border-[#4caf50] border-l-4 text-[#4caf50] bg-gradient-to-br from-surface-alt to-[rgba(76,175,80,0.08)]',
52
+ toast.type === 'info' && 'border-accent border-l-4',
53
+ ].filter(Boolean).join(' ')}
54
+ >
47
55
  {toast.message}
48
- <button className="kanban-toast-close" onClick={() => dismiss(toast.id)}>&times;</button>
56
+ <button
57
+ className="absolute top-2 right-2 bg-transparent border-none text-content-secondary text-base cursor-pointer px-1 leading-none hover:text-content"
58
+ onClick={() => dismiss(toast.id)}
59
+ >
60
+ &times;
61
+ </button>
49
62
  </div>
50
63
  ))}
51
64
  </div>,
@@ -13,6 +13,9 @@ interface AcceptInvitationPageProps {
13
13
  onSuccess: () => void;
14
14
  }
15
15
 
16
+ const inputBase = 'w-full h-9 px-2.5 bg-surface-raised border border-edge rounded text-content font-mono text-[13px] outline-none transition-[border-color] duration-150 focus:border-accent placeholder:text-content-muted';
17
+ const inputError = 'border-error focus:border-error';
18
+
16
19
  export function AcceptInvitationPage({ onSuccess }: AcceptInvitationPageProps) {
17
20
  const { theme, toggleTheme } = useTheme();
18
21
  const [searchParams] = useSearchParams();
@@ -73,10 +76,10 @@ export function AcceptInvitationPage({ onSuccess }: AcceptInvitationPageProps) {
73
76
 
74
77
  if (validating) {
75
78
  return (
76
- <div className="auth-page">
77
- <div className="auth-card">
78
- <div className="auth-header">
79
- <h1 className="auth-title">Validating Invitation...</h1>
79
+ <div className="flex items-center justify-center min-h-screen bg-surface p-4">
80
+ <div className="w-full max-w-[380px] bg-surface-alt border border-edge rounded-md px-7 pt-8 pb-6">
81
+ <div className="mb-6">
82
+ <h1 className="font-mono text-base font-semibold text-content mb-1">Validating Invitation...</h1>
80
83
  </div>
81
84
  </div>
82
85
  </div>
@@ -85,14 +88,14 @@ export function AcceptInvitationPage({ onSuccess }: AcceptInvitationPageProps) {
85
88
 
86
89
  if (!token || !email || !valid) {
87
90
  return (
88
- <div className="auth-page">
89
- <div className="auth-card">
90
- <div className="auth-header">
91
- <h1 className="auth-title">Invalid Invitation</h1>
91
+ <div className="flex items-center justify-center min-h-screen bg-surface p-4">
92
+ <div className="w-full max-w-[380px] bg-surface-alt border border-edge rounded-md px-7 pt-8 pb-6">
93
+ <div className="mb-6">
94
+ <h1 className="font-mono text-base font-semibold text-content mb-1">Invalid Invitation</h1>
92
95
  </div>
93
- <div className="auth-success-message">
96
+ <div className="text-center py-4 text-content font-mono text-[13px] leading-relaxed">
94
97
  <p>This invitation link is invalid or has expired.</p>
95
- <Link to="/login" className="auth-back-link">Go to login</Link>
98
+ <Link to="/login" className="inline-block mt-3 text-accent font-mono text-[13px] no-underline hover:underline">Go to login</Link>
96
99
  </div>
97
100
  </div>
98
101
  </div>
@@ -100,21 +103,21 @@ export function AcceptInvitationPage({ onSuccess }: AcceptInvitationPageProps) {
100
103
  }
101
104
 
102
105
  return (
103
- <div className="auth-page">
104
- <div className="auth-card">
105
- <div className="auth-header">
106
- <h1 className="auth-title">Accept Invitation</h1>
107
- <p className="auth-subtitle">Set your password for {email}</p>
106
+ <div className="flex items-center justify-center min-h-screen bg-surface p-4">
107
+ <div className="w-full max-w-[380px] bg-surface-alt border border-edge rounded-md px-7 pt-8 pb-6">
108
+ <div className="mb-6">
109
+ <h1 className="font-mono text-base font-semibold text-content mb-1">Accept Invitation</h1>
110
+ <p className="font-mono text-xs text-content-muted">Set your password for {email}</p>
108
111
  </div>
109
112
 
110
- <form className="auth-form" onSubmit={handleSubmit} noValidate>
111
- {error && <div className="auth-error-banner">{error}</div>}
113
+ <form className="flex flex-col gap-4" onSubmit={handleSubmit} noValidate>
114
+ {error && <div className="font-mono text-xs text-error bg-[rgba(255,107,107,0.08)] border border-[rgba(255,107,107,0.2)] rounded px-3 py-2">{error}</div>}
112
115
 
113
- <div className="auth-field">
114
- <label className="auth-label" htmlFor="invite-password">Password</label>
116
+ <div className="flex flex-col gap-1">
117
+ <label className="font-mono text-[11px] font-semibold text-content-secondary uppercase tracking-wider" htmlFor="invite-password">Password</label>
115
118
  <input
116
119
  id="invite-password"
117
- className={`auth-input${fieldError ? ' auth-input-error' : ''}`}
120
+ className={`${inputBase}${fieldError ? ` ${inputError}` : ''}`}
118
121
  type="password"
119
122
  value={password}
120
123
  onChange={e => setPassword(e.target.value)}
@@ -125,11 +128,11 @@ export function AcceptInvitationPage({ onSuccess }: AcceptInvitationPageProps) {
125
128
  />
126
129
  </div>
127
130
 
128
- <div className="auth-field">
129
- <label className="auth-label" htmlFor="invite-confirm-password">Confirm Password</label>
131
+ <div className="flex flex-col gap-1">
132
+ <label className="font-mono text-[11px] font-semibold text-content-secondary uppercase tracking-wider" htmlFor="invite-confirm-password">Confirm Password</label>
130
133
  <input
131
134
  id="invite-confirm-password"
132
- className={`auth-input${fieldError ? ' auth-input-error' : ''}`}
135
+ className={`${inputBase}${fieldError ? ` ${inputError}` : ''}`}
133
136
  type="password"
134
137
  value={confirmPassword}
135
138
  onChange={e => setConfirmPassword(e.target.value)}
@@ -137,11 +140,11 @@ export function AcceptInvitationPage({ onSuccess }: AcceptInvitationPageProps) {
137
140
  autoComplete="new-password"
138
141
  disabled={submitting}
139
142
  />
140
- {fieldError && <span className="auth-field-error">{fieldError}</span>}
143
+ {fieldError && <span className="font-mono text-[11px] text-error">{fieldError}</span>}
141
144
  </div>
142
145
 
143
146
  <button
144
- className="auth-submit"
147
+ className="w-full h-9 mt-1 bg-transparent border border-accent rounded text-accent font-mono text-[13px] cursor-pointer transition-[background,color] duration-150 hover:enabled:bg-accent hover:enabled:text-white disabled:opacity-60 disabled:cursor-not-allowed"
145
148
  type="submit"
146
149
  disabled={submitting}
147
150
  >
@@ -149,12 +152,12 @@ export function AcceptInvitationPage({ onSuccess }: AcceptInvitationPageProps) {
149
152
  </button>
150
153
  </form>
151
154
 
152
- <p className="auth-login-link">
155
+ <p className="font-mono text-xs text-content-secondary mt-4 text-center">
153
156
  <Link to="/login">Already have an account? Sign in</Link>
154
157
  </p>
155
158
 
156
159
  <button
157
- className="auth-theme-toggle"
160
+ className="block mx-auto mt-4 px-2.5 py-1 bg-transparent border border-edge rounded text-content-muted font-mono text-[11px] cursor-pointer transition-[border-color,color] duration-150 hover:border-content-secondary hover:text-content-secondary"
158
161
  onClick={toggleTheme}
159
162
  type="button"
160
163
  title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}
@@ -9,6 +9,9 @@ import { useTheme } from '../hooks/useTheme';
9
9
  import { apiClient } from '../api/client';
10
10
  import { validateEmail } from '../utils/auth_validation';
11
11
 
12
+ const inputBase = 'w-full h-9 px-2.5 bg-surface-raised border border-edge rounded text-content font-mono text-[13px] outline-none transition-[border-color] duration-150 focus:border-accent placeholder:text-content-muted';
13
+ const inputError = 'border-error focus:border-error';
14
+
12
15
  export function ForgotPasswordPage() {
13
16
  const { theme, toggleTheme } = useTheme();
14
17
  const [email, setEmail] = useState('');
@@ -38,29 +41,29 @@ export function ForgotPasswordPage() {
38
41
  }, [email]);
39
42
 
40
43
  return (
41
- <div className="auth-page">
42
- <div className="auth-card">
43
- <div className="auth-header">
44
- <h1 className="auth-title">Reset Password</h1>
45
- <p className="auth-subtitle">Enter your email to receive a reset link</p>
44
+ <div className="flex items-center justify-center min-h-screen bg-surface p-4">
45
+ <div className="w-full max-w-[380px] bg-surface-alt border border-edge rounded-md px-7 pt-8 pb-6">
46
+ <div className="mb-6">
47
+ <h1 className="font-mono text-base font-semibold text-content mb-1">Reset Password</h1>
48
+ <p className="font-mono text-xs text-content-muted">Enter your email to receive a reset link</p>
46
49
  </div>
47
50
 
48
51
  {sent ? (
49
- <div className="auth-success-message">
52
+ <div className="text-center py-4 text-content font-mono text-[13px] leading-relaxed">
50
53
  <p>If an account with that email exists, a reset link has been sent.</p>
51
54
  <p>Check your inbox and follow the link to reset your password.</p>
52
- <Link to="/login" className="auth-back-link">Back to login</Link>
55
+ <Link to="/login" className="inline-block mt-3 text-accent font-mono text-[13px] no-underline hover:underline">Back to login</Link>
53
56
  </div>
54
57
  ) : (
55
58
  <>
56
- <form className="auth-form" onSubmit={handleSubmit} noValidate>
57
- {error && <div className="auth-error-banner">{error}</div>}
59
+ <form className="flex flex-col gap-4" onSubmit={handleSubmit} noValidate>
60
+ {error && <div className="font-mono text-xs text-error bg-[rgba(255,107,107,0.08)] border border-[rgba(255,107,107,0.2)] rounded px-3 py-2">{error}</div>}
58
61
 
59
- <div className="auth-field">
60
- <label className="auth-label" htmlFor="forgot-email">Email</label>
62
+ <div className="flex flex-col gap-1">
63
+ <label className="font-mono text-[11px] font-semibold text-content-secondary uppercase tracking-wider" htmlFor="forgot-email">Email</label>
61
64
  <input
62
65
  id="forgot-email"
63
- className={`auth-input${error ? ' auth-input-error' : ''}`}
66
+ className={`${inputBase}${error ? ` ${inputError}` : ''}`}
64
67
  type="email"
65
68
  value={email}
66
69
  onChange={e => setEmail(e.target.value)}
@@ -72,7 +75,7 @@ export function ForgotPasswordPage() {
72
75
  </div>
73
76
 
74
77
  <button
75
- className="auth-submit"
78
+ className="w-full h-9 mt-1 bg-transparent border border-accent rounded text-accent font-mono text-[13px] cursor-pointer transition-[background,color] duration-150 hover:enabled:bg-accent hover:enabled:text-white disabled:opacity-60 disabled:cursor-not-allowed"
76
79
  type="submit"
77
80
  disabled={submitting}
78
81
  >
@@ -80,14 +83,14 @@ export function ForgotPasswordPage() {
80
83
  </button>
81
84
  </form>
82
85
 
83
- <p className="auth-login-link">
86
+ <p className="font-mono text-xs text-content-secondary mt-4 text-center">
84
87
  Remember your password? <Link to="/login">Sign in</Link>
85
88
  </p>
86
89
  </>
87
90
  )}
88
91
 
89
92
  <button
90
- className="auth-theme-toggle"
93
+ className="block mx-auto mt-4 px-2.5 py-1 bg-transparent border border-edge rounded text-content-muted font-mono text-[11px] cursor-pointer transition-[border-color,color] duration-150 hover:border-content-secondary hover:text-content-secondary"
91
94
  onClick={toggleTheme}
92
95
  type="button"
93
96
  title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}
@@ -21,6 +21,9 @@ interface FormErrors {
21
21
  general?: string;
22
22
  }
23
23
 
24
+ const inputBase = 'w-full h-9 px-2.5 bg-surface-raised border border-edge rounded text-content font-mono text-[13px] outline-none transition-[border-color] duration-150 focus:border-accent placeholder:text-content-muted';
25
+ const inputError = 'border-error focus:border-error';
26
+
24
27
  export function RegisterPage({ onSuccess }: RegisterPageProps) {
25
28
  const { theme, toggleTheme } = useTheme();
26
29
  const [email, setEmail] = useState('');
@@ -67,23 +70,23 @@ export function RegisterPage({ onSuccess }: RegisterPageProps) {
67
70
  }, [errors.password]);
68
71
 
69
72
  return (
70
- <div className="auth-page">
71
- <div className="auth-card">
72
- <div className="auth-header">
73
- <h1 className="auth-title">Create Admin Account</h1>
74
- <p className="auth-subtitle">Set up the first user for this system</p>
73
+ <div className="flex items-center justify-center min-h-screen bg-surface p-4">
74
+ <div className="w-full max-w-[380px] bg-surface-alt border border-edge rounded-md px-7 pt-8 pb-6">
75
+ <div className="mb-6">
76
+ <h1 className="font-mono text-base font-semibold text-content mb-1">Create Admin Account</h1>
77
+ <p className="font-mono text-xs text-content-muted">Set up the first user for this system</p>
75
78
  </div>
76
79
 
77
- <form className="auth-form" onSubmit={handleSubmit} noValidate>
80
+ <form className="flex flex-col gap-4" onSubmit={handleSubmit} noValidate>
78
81
  {errors.general && (
79
- <div className="auth-error-banner">{errors.general}</div>
82
+ <div className="font-mono text-xs text-error bg-[rgba(255,107,107,0.08)] border border-[rgba(255,107,107,0.2)] rounded px-3 py-2">{errors.general}</div>
80
83
  )}
81
84
 
82
- <div className="auth-field">
83
- <label className="auth-label" htmlFor="register-email">Email</label>
85
+ <div className="flex flex-col gap-1">
86
+ <label className="font-mono text-[11px] font-semibold text-content-secondary uppercase tracking-wider" htmlFor="register-email">Email</label>
84
87
  <input
85
88
  id="register-email"
86
- className={`auth-input${errors.email ? ' auth-input-error' : ''}`}
89
+ className={`${inputBase}${errors.email ? ` ${inputError}` : ''}`}
87
90
  type="email"
88
91
  value={email}
89
92
  onChange={handleEmailChange}
@@ -92,14 +95,14 @@ export function RegisterPage({ onSuccess }: RegisterPageProps) {
92
95
  autoFocus
93
96
  disabled={submitting}
94
97
  />
95
- {errors.email && <span className="auth-field-error">{errors.email}</span>}
98
+ {errors.email && <span className="font-mono text-[11px] text-error">{errors.email}</span>}
96
99
  </div>
97
100
 
98
- <div className="auth-field">
99
- <label className="auth-label" htmlFor="register-password">Password</label>
101
+ <div className="flex flex-col gap-1">
102
+ <label className="font-mono text-[11px] font-semibold text-content-secondary uppercase tracking-wider" htmlFor="register-password">Password</label>
100
103
  <input
101
104
  id="register-password"
102
- className={`auth-input${errors.password ? ' auth-input-error' : ''}`}
105
+ className={`${inputBase}${errors.password ? ` ${inputError}` : ''}`}
103
106
  type="password"
104
107
  value={password}
105
108
  onChange={handlePasswordChange}
@@ -107,11 +110,11 @@ export function RegisterPage({ onSuccess }: RegisterPageProps) {
107
110
  autoComplete="new-password"
108
111
  disabled={submitting}
109
112
  />
110
- {errors.password && <span className="auth-field-error">{errors.password}</span>}
113
+ {errors.password && <span className="font-mono text-[11px] text-error">{errors.password}</span>}
111
114
  </div>
112
115
 
113
116
  <button
114
- className="auth-submit"
117
+ className="w-full h-9 mt-1 bg-transparent border border-accent rounded text-accent font-mono text-[13px] cursor-pointer transition-[background,color] duration-150 hover:enabled:bg-accent hover:enabled:text-white disabled:opacity-60 disabled:cursor-not-allowed"
115
118
  type="submit"
116
119
  disabled={submitting}
117
120
  >
@@ -119,12 +122,12 @@ export function RegisterPage({ onSuccess }: RegisterPageProps) {
119
122
  </button>
120
123
  </form>
121
124
 
122
- <p className="auth-login-link">
125
+ <p className="font-mono text-xs text-content-secondary mt-4 text-center">
123
126
  Already have an account? <Link to="/login">Sign in</Link>
124
127
  </p>
125
128
 
126
129
  <button
127
- className="auth-theme-toggle"
130
+ className="block mx-auto mt-4 px-2.5 py-1 bg-transparent border border-edge rounded text-content-muted font-mono text-[11px] cursor-pointer transition-[border-color,color] duration-150 hover:border-content-secondary hover:text-content-secondary"
128
131
  onClick={toggleTheme}
129
132
  type="button"
130
133
  title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}