@assistkick/create 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/dist/bin/create.js +0 -0
  2. package/package.json +9 -7
  3. package/templates/assistkick-product-system/.env.example +1 -0
  4. package/templates/assistkick-product-system/local.db +0 -0
  5. package/templates/assistkick-product-system/package.json +4 -2
  6. package/templates/assistkick-product-system/packages/backend/package.json +2 -0
  7. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
  9. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
  10. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
  11. package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
  12. package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +61 -6
  13. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
  14. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
  15. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
  16. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
  17. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
  18. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +158 -0
  19. package/templates/assistkick-product-system/packages/backend/src/server.ts +60 -9
  20. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
  21. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
  22. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
  23. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
  24. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +43 -77
  25. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
  26. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
  27. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
  28. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
  29. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
  30. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
  31. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
  32. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
  33. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
  34. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
  35. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
  40. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +245 -0
  41. package/templates/assistkick-product-system/packages/frontend/package-lock.json +3455 -0
  42. package/templates/assistkick-product-system/packages/frontend/package.json +6 -0
  43. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +8 -0
  44. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +458 -18
  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 +20 -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 +187 -56
  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/IterationCommentModal.tsx +80 -0
  62. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +263 -167
  63. package/templates/assistkick-product-system/packages/frontend/src/components/LoginPage.tsx +14 -14
  64. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +54 -33
  65. package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +32 -49
  66. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +43 -48
  67. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
  68. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +20 -14
  69. package/templates/assistkick-product-system/packages/frontend/src/components/UsersView.tsx +52 -52
  70. package/templates/assistkick-product-system/packages/frontend/src/components/VideoGallery.tsx +313 -0
  71. package/templates/assistkick-product-system/packages/frontend/src/components/VideographyView.tsx +250 -0
  72. package/templates/assistkick-product-system/packages/frontend/src/components/WorkflowsView.tsx +474 -0
  73. package/templates/assistkick-product-system/packages/frontend/src/components/ds/AccentBorderList.tsx +53 -0
  74. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Button.tsx +87 -0
  75. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonGroup.tsx +29 -0
  76. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonShowcase.tsx +221 -0
  77. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CardGlass.tsx +141 -0
  78. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CompletionRing.tsx +30 -0
  79. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ContentCard.tsx +34 -0
  80. package/templates/assistkick-product-system/packages/frontend/src/components/ds/IconButton.tsx +74 -0
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +103 -87
  82. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +9 -188
  83. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Kbd.tsx +11 -0
  84. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KindBadge.tsx +21 -0
  85. package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +81 -37
  86. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SidePanelShowcase.tsx +370 -0
  87. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SideSheet.tsx +64 -0
  88. package/templates/assistkick-product-system/packages/frontend/src/components/ds/StatusDot.tsx +18 -0
  89. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCardPositionNode.tsx +36 -0
  90. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCycleCountNode.tsx +60 -0
  91. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/EndNode.tsx +42 -0
  92. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GenerateTTSNode.tsx +52 -0
  93. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +189 -0
  94. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +123 -0
  95. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RebuildBundleNode.tsx +20 -0
  96. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RenderVideoNode.tsx +72 -0
  97. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RunAgentNode.tsx +51 -0
  98. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/SetCardMetadataNode.tsx +53 -0
  99. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/StartNode.tsx +18 -0
  100. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/TransitionCardNode.tsx +59 -0
  101. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +341 -0
  102. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +643 -0
  103. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/autoLayout.ts +103 -0
  104. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/edgeColors.ts +35 -0
  105. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +246 -0
  106. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.test.ts +119 -0
  107. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +136 -0
  108. package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +13 -11
  109. package/templates/assistkick-product-system/packages/frontend/src/hooks/useAutoSave.ts +75 -0
  110. package/templates/assistkick-product-system/packages/frontend/src/hooks/useToast.tsx +16 -3
  111. package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
  112. package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
  113. package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
  114. package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
  115. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
  116. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +1 -1
  117. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +2 -2
  118. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
  119. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -2
  120. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
  121. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +6 -3
  123. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +4 -4
  124. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +275 -3535
  125. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
  128. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
  129. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
  130. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
  131. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
  132. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
  133. package/templates/assistkick-product-system/packages/frontend/vite.config.ts +5 -0
  134. package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
  135. package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
  136. package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
  137. package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
  138. package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
  139. package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
  140. package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
  141. package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
  142. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
  143. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
  144. package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
  145. package/templates/assistkick-product-system/packages/shared/db/migrations/0014_nifty_punisher.sql +15 -0
  146. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
  147. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
  148. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
  149. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
  150. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
  151. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
  152. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
  153. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
  154. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
  155. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0014_snapshot.json +1545 -0
  156. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +77 -0
  157. package/templates/assistkick-product-system/packages/shared/db/schema.ts +114 -0
  158. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
  159. package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
  160. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
  161. package/templates/assistkick-product-system/packages/shared/lib/graph.ts +5 -0
  162. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1999 -0
  163. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1437 -0
  164. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
  165. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
  166. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
  167. package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
  168. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
  169. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
  170. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
  171. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
  172. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
  173. package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
  174. package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
  175. package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
  176. package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
  177. package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
  178. package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
  179. package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
  180. package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
  181. package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
  182. package/templates/assistkick-product-system/packages/video/index.ts +4 -0
  183. package/templates/assistkick-product-system/packages/video/package.json +28 -0
  184. package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
  185. package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
  186. package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
  187. package/templates/assistkick-product-system/packages/video/style.css +1 -0
  188. package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
  189. package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
  190. package/templates/assistkick-product-system/tests/video_render_service.test.ts +181 -0
  191. package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
  192. package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
  193. package/templates/skills/assistkick-developer/SKILL.md +3 -0
  194. package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
  195. package/templates/skills/product-system/graph.json +1890 -0
  196. package/templates/skills/product-system/kanban.json +304 -0
  197. package/templates/skills/product-system/nodes/comp_001.md +56 -0
  198. package/templates/skills/product-system/nodes/comp_002.md +57 -0
  199. package/templates/skills/product-system/nodes/data_001.md +51 -0
  200. package/templates/skills/product-system/nodes/data_002.md +40 -0
  201. package/templates/skills/product-system/nodes/data_004.md +38 -0
  202. package/templates/skills/product-system/nodes/dec_001.md +34 -0
  203. package/templates/skills/product-system/nodes/dec_016.md +32 -0
  204. package/templates/skills/product-system/nodes/feat_008.md +30 -0
  205. package/templates/skills/video-composition-agent/SKILL.md +232 -0
  206. package/templates/skills/video-script-writer/SKILL.md +136 -0
@@ -1,4 +1,5 @@
1
1
  import React, { useState } from 'react';
2
+ import { Link, useNavigate } from 'react-router-dom';
2
3
  import { Search } from 'lucide-react';
3
4
 
4
5
  export interface NavItem {
@@ -6,6 +7,7 @@ export interface NavItem {
6
7
  label: string;
7
8
  icon?: React.ReactNode;
8
9
  description?: string;
10
+ href?: string;
9
11
  }
10
12
 
11
13
  export interface NavBarSidekickProps {
@@ -25,9 +27,11 @@ export interface NavBarSidekickProps {
25
27
  trailing?: React.ReactNode;
26
28
  }
27
29
 
28
- export function NavBarSidekick({
30
+ export const NavBarSidekick = ({
29
31
  items, activeId, onNavigate, brand, center, actions, trailing,
30
- }: NavBarSidekickProps) {
32
+ }: NavBarSidekickProps) => {
33
+ const navigate = useNavigate();
34
+
31
35
  // Fallback to internal state when no external activeId is provided (design system demo mode)
32
36
  const [internalActive, setInternalActive] = useState(items[0]?.id ?? '');
33
37
  const active = activeId ?? internalActive;
@@ -59,22 +63,39 @@ export function NavBarSidekick({
59
63
  <div className="flex items-center rounded-xl bg-surface p-1 border border-edge">
60
64
  {items.map((item) => {
61
65
  const isActive = active === item.id;
62
- return (
66
+ const classes = [
67
+ 'flex items-center gap-1.5 rounded-lg px-3.5 py-1.5',
68
+ 'text-[12px] font-semibold uppercase tracking-wider',
69
+ 'transition-all duration-150 cursor-pointer outline-none no-underline',
70
+ isActive
71
+ ? 'bg-accent text-surface shadow-sm'
72
+ : 'text-content-muted hover:text-content hover:bg-surface-raised',
73
+ ].join(' ');
74
+ const style = isActive ? { boxShadow: '0 2px 8px -2px var(--accent)' } : {};
75
+ const children = (
76
+ <>
77
+ {item.icon && <span className="flex items-center">{item.icon}</span>}
78
+ {item.label}
79
+ </>
80
+ );
81
+
82
+ return item.href ? (
83
+ <Link
84
+ key={item.id}
85
+ to={item.href}
86
+ className={classes}
87
+ style={style}
88
+ >
89
+ {children}
90
+ </Link>
91
+ ) : (
63
92
  <button
64
93
  key={item.id}
65
94
  onClick={() => handleSelect(item.id)}
66
- className={[
67
- 'flex items-center gap-1.5 rounded-lg px-3.5 py-1.5',
68
- 'text-[12px] font-semibold uppercase tracking-wider',
69
- 'transition-all duration-150 cursor-pointer outline-none',
70
- isActive
71
- ? 'bg-accent text-surface shadow-sm'
72
- : 'text-content-muted hover:text-content hover:bg-surface-raised',
73
- ].join(' ')}
74
- style={isActive ? { boxShadow: '0 2px 8px -2px var(--accent)' } : {}}
95
+ className={classes}
96
+ style={style}
75
97
  >
76
- {item.icon && <span className="flex items-center">{item.icon}</span>}
77
- {item.label}
98
+ {children}
78
99
  </button>
79
100
  );
80
101
  })}
@@ -121,7 +142,12 @@ export function NavBarSidekick({
121
142
  onKeyDown={(e) => {
122
143
  if (e.key === 'Escape') { setCmdOpen(false); setCmdQuery(''); }
123
144
  if (e.key === 'Enter' && filteredItems.length > 0) {
124
- handleSelect(filteredItems[0].id);
145
+ const first = filteredItems[0];
146
+ if (first.href) {
147
+ navigate(first.href);
148
+ } else {
149
+ handleSelect(first.id);
150
+ }
125
151
  setCmdOpen(false);
126
152
  setCmdQuery('');
127
153
  }
@@ -132,27 +158,45 @@ export function NavBarSidekick({
132
158
  {filteredItems.length === 0 ? (
133
159
  <div className="py-2 text-[12px] text-content-muted">No results</div>
134
160
  ) : (
135
- filteredItems.map((item) => (
136
- <button
137
- key={item.id}
138
- onClick={() => { handleSelect(item.id); setCmdOpen(false); setCmdQuery(''); }}
139
- className={[
140
- 'flex items-center gap-3 rounded-lg px-3 py-2 text-left',
141
- 'text-[13px] transition-colors duration-100 cursor-pointer outline-none',
142
- active === item.id
143
- ? 'bg-accent/10 text-accent'
144
- : 'text-content-secondary hover:bg-surface-raised hover:text-content',
145
- ].join(' ')}
146
- >
147
- {item.icon && <span className="flex items-center">{item.icon}</span>}
148
- <div>
149
- <div className="font-medium">{item.label}</div>
150
- {item.description && (
151
- <div className="text-[11px] text-content-muted">{item.description}</div>
152
- )}
153
- </div>
154
- </button>
155
- ))
161
+ filteredItems.map((item) => {
162
+ const cmdClasses = [
163
+ 'flex items-center gap-3 rounded-lg px-3 py-2 text-left no-underline',
164
+ 'text-[13px] transition-colors duration-100 cursor-pointer outline-none',
165
+ active === item.id
166
+ ? 'bg-accent/10 text-accent'
167
+ : 'text-content-secondary hover:bg-surface-raised hover:text-content',
168
+ ].join(' ');
169
+ const cmdChildren = (
170
+ <>
171
+ {item.icon && <span className="flex items-center">{item.icon}</span>}
172
+ <div>
173
+ <div className="font-medium">{item.label}</div>
174
+ {item.description && (
175
+ <div className="text-[11px] text-content-muted">{item.description}</div>
176
+ )}
177
+ </div>
178
+ </>
179
+ );
180
+
181
+ return item.href ? (
182
+ <Link
183
+ key={item.id}
184
+ to={item.href}
185
+ onClick={() => { setCmdOpen(false); setCmdQuery(''); }}
186
+ className={cmdClasses}
187
+ >
188
+ {cmdChildren}
189
+ </Link>
190
+ ) : (
191
+ <button
192
+ key={item.id}
193
+ onClick={() => { handleSelect(item.id); setCmdOpen(false); setCmdQuery(''); }}
194
+ className={cmdClasses}
195
+ >
196
+ {cmdChildren}
197
+ </button>
198
+ );
199
+ })
156
200
  )}
157
201
  </div>
158
202
  )}
@@ -160,4 +204,4 @@ export function NavBarSidekick({
160
204
  )}
161
205
  </nav>
162
206
  );
163
- }
207
+ };
@@ -0,0 +1,370 @@
1
+ import React from 'react';
2
+ import {
3
+ X, ArrowRight, ArrowLeft,
4
+ CheckCircle2, Circle, Loader2, FileText, FolderPlus,
5
+ Trash2, Lightbulb, GitBranch, Activity,
6
+ ListTodo, AlignLeft, ClipboardCheck, HelpCircle, CheckSquare, StickyNote,
7
+ } from 'lucide-react';
8
+ import { ContentCard } from './ContentCard';
9
+ import { AccentBorderList, AccentBorderItem } from './AccentBorderList';
10
+ import { CompletionRing } from './CompletionRing';
11
+ import { StatusDot } from './StatusDot';
12
+
13
+ /* ── Mock data ── */
14
+
15
+ const MOCK_NODE = {
16
+ id: 'feat_auth_login',
17
+ name: 'User Authentication & Session Management',
18
+ type: 'feature',
19
+ status: 'partially_defined',
20
+ completeness: 0.65,
21
+ };
22
+
23
+ const MOCK_TASKS = {
24
+ total: 5,
25
+ completed: 3,
26
+ items: [
27
+ { name: 'Define login flow', status: 'completed' },
28
+ { name: 'Password validation rules', status: 'completed' },
29
+ { name: 'Session management', status: 'completed' },
30
+ { name: 'OAuth integration', status: 'in_progress' },
31
+ { name: 'Rate limiting', status: 'pending' },
32
+ ],
33
+ };
34
+
35
+ const MOCK_DESCRIPTION = `The authentication module is responsible for handling all aspects of user identity verification, session lifecycle management, and secure credential storage across the platform. It serves as the foundational security layer that every other service depends on for verifying user identity and enforcing access controls.
36
+
37
+ The module supports two primary authentication flows: traditional email/password login with bcrypt-hashed credentials stored in the database, and federated OAuth 2.0 authentication via third-party identity providers including Google, GitHub, and Microsoft Azure AD. Both flows converge into a unified session management layer that issues short-lived JWT access tokens (15-minute TTL) paired with long-lived refresh tokens (30-day TTL) stored in httpOnly secure cookies.
38
+
39
+ Session tokens carry a minimal claim set — user ID, tenant ID, role bitmask, and token generation timestamp — to keep payload size under 1KB while still enabling most authorization decisions without a database round-trip. The refresh token rotation strategy uses a sliding window approach: each refresh request invalidates the previous token and issues a new one, with a grace period of 30 seconds to handle concurrent requests from multiple browser tabs.
40
+
41
+ Rate limiting is enforced at two levels: per-IP throttling on the login endpoint (10 attempts per minute with exponential backoff) and per-account lockout after 5 consecutive failed attempts within a 15-minute window. Account lockout triggers a notification email and requires either a time-based unlock (30 minutes) or manual unlock via the admin dashboard.`;
42
+
43
+ const MOCK_ACCEPTANCE_CRITERIA = [
44
+ {
45
+ title: 'Registration & password policy',
46
+ body: 'Users can register with email and password; passwords must meet complexity requirements (minimum 12 characters, at least one uppercase letter, one lowercase letter, one digit, and one special character). The system must reject commonly breached passwords by checking against a configurable blocklist of at least 10,000 entries.',
47
+ },
48
+ {
49
+ title: 'JWT token issuance',
50
+ body: 'Users can log in with valid credentials and receive a JWT access token and refresh token pair. The access token must expire within 15 minutes and the refresh token within 30 days. Both tokens must be cryptographically signed using RS256 with a 2048-bit RSA key pair that is rotated quarterly.',
51
+ },
52
+ {
53
+ title: 'OAuth provider support',
54
+ body: 'OAuth login flow must support Google, GitHub, and Microsoft Azure AD providers. The system must handle the full authorization code flow including PKCE challenge, token exchange, and user profile retrieval. If the OAuth email matches an existing account, the accounts must be automatically linked. If no account exists, a new one must be created with a randomly generated secure password.',
55
+ },
56
+ {
57
+ title: 'Rate limiting & lockout',
58
+ body: 'Failed login attempts must be rate-limited to 10 per IP per minute and 5 per account per 15-minute window. After exceeding the per-account limit, the account must be locked for 30 minutes and an email notification sent to the account holder. The lockout notification must include the IP address, geolocation (city/country), and user agent of the failed attempt.',
59
+ },
60
+ {
61
+ title: 'Token refresh rotation',
62
+ body: 'Token refresh must implement sliding window rotation: the old refresh token is invalidated and a new one issued. A 30-second grace period must exist to handle concurrent refresh requests from multiple tabs. Any attempt to reuse an already-rotated token must immediately invalidate all tokens for that user session and trigger a security alert.',
63
+ },
64
+ {
65
+ title: 'Audit logging',
66
+ body: 'All authentication events (login, logout, token refresh, failed attempt, lockout, unlock) must be logged to the audit trail with timestamp, IP address, user agent, and outcome. Audit logs must be retained for at least 90 days and be queryable by user ID, IP address, and event type.',
67
+ },
68
+ {
69
+ title: 'Performance & error handling',
70
+ body: 'The login page must render within 200ms on a 3G connection and complete the full authentication flow (including server round-trip) within 2 seconds on a broadband connection. Error messages must not reveal whether the email or password was incorrect — a generic "Invalid credentials" message must be used.',
71
+ },
72
+ ];
73
+
74
+ const MOCK_OPEN_QUESTIONS = [
75
+ {
76
+ question: 'Should we support biometric authentication (WebAuthn/FIDO2) as a second factor in the initial release, or defer it to a follow-up iteration? The specification mentions MFA but does not specify which second factors should be available beyond TOTP.',
77
+ context: 'Raised during architecture review on 2024-01-15. WebAuthn support would require additional database tables for credential storage and changes to the login flow UI.',
78
+ },
79
+ {
80
+ question: 'What should the behavior be when a user tries to OAuth-link an email that already belongs to a different account? Currently the spec says "accounts must be automatically linked" but this could be a security risk if the OAuth provider email is not verified.',
81
+ context: 'Security team flagged this during threat modeling. Some OAuth providers do not guarantee email verification. We need a policy decision on whether to require email verification before linking, or to prompt the user to confirm account merge.',
82
+ },
83
+ {
84
+ question: 'How should the system handle refresh token rotation when the client is offline and comes back online with an expired refresh token? The current 30-day TTL means long-idle sessions will require re-authentication, but the product team wants a "remember me" experience that lasts up to 90 days.',
85
+ context: 'Product management requested extended session persistence for mobile users. Need to balance security requirements (SOC2 compliance requires session limits) with user experience goals.',
86
+ },
87
+ ];
88
+
89
+ const MOCK_RESOLVED_QUESTIONS = [
90
+ {
91
+ question: 'Should we use symmetric (HS256) or asymmetric (RS256) signing for JWT tokens?',
92
+ resolution: 'Decided on RS256 with 2048-bit keys. This allows services to verify tokens using only the public key, without needing access to the signing secret. Key rotation is handled via JWKS endpoint that publishes the current and previous public keys. The private key is stored in AWS KMS and never leaves the HSM boundary.',
93
+ resolvedDate: '2024-01-10',
94
+ },
95
+ {
96
+ question: 'Where should refresh tokens be stored on the client side — localStorage, sessionStorage, or cookies?',
97
+ resolution: 'Refresh tokens will be stored in httpOnly secure cookies with SameSite=Strict attribute. This prevents XSS attacks from accessing the token (unlike localStorage) while still allowing automatic inclusion in refresh requests. The cookie is scoped to the /api/auth/refresh endpoint only, so it is not sent with every API request. Access tokens are stored in memory only and are not persisted across page reloads — the client performs a silent refresh on app initialization.',
98
+ resolvedDate: '2024-01-12',
99
+ },
100
+ {
101
+ question: 'Should we implement our own rate limiting or use a third-party service like Cloudflare Rate Limiting?',
102
+ resolution: 'We will implement application-level rate limiting using a Redis-backed sliding window counter. While Cloudflare provides IP-based rate limiting at the edge, we need per-account rate limiting that requires knowledge of the authenticated user, which is only available at the application layer. Redis was chosen over in-memory counters to ensure rate limits are consistent across all application instances in the cluster. The implementation will use the token bucket algorithm with configurable burst size.',
103
+ resolvedDate: '2024-01-18',
104
+ },
105
+ {
106
+ question: 'Do we need to support SAML 2.0 in addition to OAuth 2.0 for enterprise SSO?',
107
+ resolution: 'SAML 2.0 support is deferred to Phase 2. The current enterprise customer pipeline (3 accounts) all support OAuth 2.0/OIDC. We will revisit when we onboard customers that require SAML. The authentication architecture is designed to be protocol-agnostic at the session layer, so adding SAML later will not require changes to downstream services — only a new identity provider adapter.',
108
+ resolvedDate: '2024-02-01',
109
+ },
110
+ ];
111
+
112
+ const MOCK_NOTES = `## Implementation Notes
113
+
114
+ The auth module is structured as a standalone package within the monorepo that exposes a clean API surface: \`createSession()\`, \`validateToken()\`, \`refreshSession()\`, and \`revokeSession()\`. All internal implementation details (bcrypt rounds, JWT claims structure, Redis key format) are encapsulated and not leaked to consumers.
115
+
116
+ ### Dependencies & Integration Points
117
+
118
+ - **Database**: Uses a dedicated \`auth\` schema with tables for users, credentials, sessions, oauth_links, and audit_log. Migrations are managed via Drizzle ORM and versioned alongside the code.
119
+ - **Redis**: Shared Redis cluster for rate limiting counters, session blacklist (for immediate revocation), and JWKS cache. Keys are namespaced with \`auth:\` prefix.
120
+ - **Email**: Integrates with the notification service for sending lockout alerts, password reset links, and email verification tokens. Uses Resend as the delivery provider.
121
+ - **Monitoring**: Custom metrics exported to Prometheus — login success/failure rate, token refresh latency p99, active sessions gauge, rate limit hit counter.
122
+
123
+ ### Security Considerations
124
+
125
+ The bcrypt work factor is set to 12 rounds, which provides approximately 250ms hashing time on current hardware. This is intentionally slow to resist brute-force attacks but fast enough to not impact user experience. The work factor should be reviewed annually and increased as hardware improves.
126
+
127
+ All sensitive configuration (JWT private key ARN, Redis connection string, bcrypt work factor) is injected via environment variables and never committed to the repository. The CI/CD pipeline retrieves secrets from AWS Secrets Manager at deploy time.
128
+
129
+ ### Known Technical Debt
130
+
131
+ - The OAuth provider abstraction currently has provider-specific code paths in the callback handler. This should be refactored into a proper strategy pattern.
132
+ - The audit log table does not have an archival strategy. At current write volume (~50K events/day), the table will reach 18M rows by end of quarter. Need to implement partitioning or offload to a time-series database.
133
+ - Error codes are string constants scattered across multiple files. Should be consolidated into a single enum with documentation.`;
134
+
135
+ const MOCK_WORK_SUMMARIES = [
136
+ {
137
+ cycle: 3,
138
+ approach: 'Implemented JWT refresh token rotation with sliding window expiry.',
139
+ filesCreated: ['src/auth/refresh.ts', 'src/auth/middleware.ts'],
140
+ filesUpdated: ['src/routes/login.ts'],
141
+ filesDeleted: ['src/auth/legacy-session.ts'],
142
+ decisions: [
143
+ 'Use RS256 for JWT signing',
144
+ 'Store refresh tokens in httpOnly cookies',
145
+ ],
146
+ },
147
+ {
148
+ cycle: 2,
149
+ approach: 'Added OAuth provider abstraction layer.',
150
+ filesCreated: ['src/auth/oauth.ts'],
151
+ filesUpdated: ['src/routes/auth.ts', 'src/config.ts'],
152
+ filesDeleted: [],
153
+ decisions: ['Unified callback URL pattern for all providers'],
154
+ },
155
+ ];
156
+
157
+ const MOCK_EDGES = [
158
+ { neighborId: 'feat_user_profile', neighborName: 'User Profile', relation: 'depends_on', direction: 'outgoing' },
159
+ { neighborId: 'feat_api_gateway', neighborName: 'API Gateway', relation: 'consumed_by', direction: 'incoming' },
160
+ { neighborId: 'feat_rbac', neighborName: 'Role-Based Access', relation: 'relates_to', direction: 'outgoing' },
161
+ ];
162
+
163
+ /* ── Local helpers ── */
164
+
165
+ function TaskIcon({ status }: { status: string }) {
166
+ if (status === 'completed') return <CheckCircle2 size={14} className="text-accent shrink-0" />;
167
+ if (status === 'in_progress') return <Loader2 size={14} className="text-accent-secondary shrink-0 animate-spin" />;
168
+ return <Circle size={14} className="text-content-muted shrink-0" />;
169
+ }
170
+
171
+ /* ── Card Stack Panel ── */
172
+
173
+ function CardStackPanel() {
174
+ return (
175
+ <div className="flex w-[420px] flex-col gap-3 rounded-2xl border border-edge bg-panel p-4">
176
+ {/* Header */}
177
+ <div className="flex items-start justify-between">
178
+ <div className="flex-1 min-w-0">
179
+ <h3 className="text-[15px] font-semibold text-content truncate">{MOCK_NODE.name}</h3>
180
+ <span className="font-mono text-[11px] text-content-muted">{MOCK_NODE.id}</span>
181
+ </div>
182
+ <button className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg text-content-muted hover:bg-surface-raised hover:text-content transition-colors">
183
+ <X size={14} />
184
+ </button>
185
+ </div>
186
+
187
+ {/* Status card */}
188
+ <div className="flex items-center gap-3 rounded-xl border border-edge bg-surface-alt p-3">
189
+ <CompletionRing pct={MOCK_NODE.completeness} />
190
+ <div className="flex-1 min-w-0">
191
+ <div className="flex items-center gap-2">
192
+ <span className="rounded-md bg-surface-raised px-2 py-0.5 text-[11px] font-medium text-content-secondary capitalize">
193
+ {MOCK_NODE.type}
194
+ </span>
195
+ <StatusDot status={MOCK_NODE.status} />
196
+ <span className="text-[11px] text-content-secondary capitalize">
197
+ {MOCK_NODE.status.replace(/_/g, ' ')}
198
+ </span>
199
+ </div>
200
+ </div>
201
+ </div>
202
+
203
+ {/* Tasks */}
204
+ <ContentCard
205
+ icon={<ListTodo size={13} />}
206
+ label="Tasks"
207
+ badge={
208
+ <span className="rounded-full bg-accent/15 px-2 py-0.5 text-[10px] font-semibold text-accent">
209
+ {MOCK_TASKS.completed}/{MOCK_TASKS.total}
210
+ </span>
211
+ }
212
+ defaultOpen
213
+ >
214
+ <div className="space-y-1.5">
215
+ {MOCK_TASKS.items.map((t, i) => (
216
+ <div key={i} className="flex items-center gap-2">
217
+ <TaskIcon status={t.status} />
218
+ <span className={`text-[12px] ${t.status === 'completed' ? 'text-content-muted line-through' : 'text-content'}`}>
219
+ {t.name}
220
+ </span>
221
+ </div>
222
+ ))}
223
+ </div>
224
+ </ContentCard>
225
+
226
+ {/* Description */}
227
+ <ContentCard icon={<AlignLeft size={13} />} label="Description" defaultOpen>
228
+ <div className="text-[12px] leading-relaxed text-content-secondary whitespace-pre-line">
229
+ {MOCK_DESCRIPTION}
230
+ </div>
231
+ </ContentCard>
232
+
233
+ {/* Acceptance Criteria */}
234
+ <ContentCard
235
+ icon={<ClipboardCheck size={13} />}
236
+ label="Acceptance Criteria"
237
+ badge={<span className="text-[10px] text-content-muted">{MOCK_ACCEPTANCE_CRITERIA.length} criteria</span>}
238
+ >
239
+ <AccentBorderList>
240
+ {MOCK_ACCEPTANCE_CRITERIA.map((ac, i) => (
241
+ <AccentBorderItem key={i} index={i + 1}>
242
+ <h4 className="text-[12px] font-semibold text-content inline">{ac.title}</h4>
243
+ <p className="text-[12px] leading-[1.75] text-content-secondary mt-1">{ac.body}</p>
244
+ </AccentBorderItem>
245
+ ))}
246
+ </AccentBorderList>
247
+ </ContentCard>
248
+
249
+ {/* Open Questions */}
250
+ <ContentCard
251
+ icon={<HelpCircle size={13} />}
252
+ label="Open Questions"
253
+ badge={
254
+ <span className="rounded-full bg-accent-secondary/15 px-2 py-0.5 text-[10px] font-semibold text-accent-secondary">
255
+ {MOCK_OPEN_QUESTIONS.length}
256
+ </span>
257
+ }
258
+ >
259
+ <AccentBorderList>
260
+ {MOCK_OPEN_QUESTIONS.map((q, i) => (
261
+ <AccentBorderItem key={i} color="secondary" index={i + 1}>
262
+ <p className="text-[12px] leading-[1.75] text-content">{q.question}</p>
263
+ <p className="text-[11px] leading-[1.7] text-content-muted italic mt-1">{q.context}</p>
264
+ </AccentBorderItem>
265
+ ))}
266
+ </AccentBorderList>
267
+ </ContentCard>
268
+
269
+ {/* Resolved Questions */}
270
+ <ContentCard
271
+ icon={<CheckSquare size={13} />}
272
+ label="Resolved Questions"
273
+ badge={
274
+ <span className="rounded-full bg-accent/15 px-2 py-0.5 text-[10px] font-semibold text-accent">
275
+ {MOCK_RESOLVED_QUESTIONS.length}
276
+ </span>
277
+ }
278
+ >
279
+ <AccentBorderList>
280
+ {MOCK_RESOLVED_QUESTIONS.map((q, i) => (
281
+ <AccentBorderItem key={i} index={i + 1}>
282
+ <h4 className="text-[12px] font-semibold text-content">{q.question}</h4>
283
+ <p className="text-[12px] leading-[1.75] text-content-secondary mt-1">{q.resolution}</p>
284
+ <span className="inline-block mt-1 text-[10px] text-content-muted">Resolved {q.resolvedDate}</span>
285
+ </AccentBorderItem>
286
+ ))}
287
+ </AccentBorderList>
288
+ </ContentCard>
289
+
290
+ {/* Notes */}
291
+ <ContentCard icon={<StickyNote size={13} />} label="Notes">
292
+ <div className="text-[12px] leading-relaxed text-content-secondary whitespace-pre-line">
293
+ {MOCK_NOTES}
294
+ </div>
295
+ </ContentCard>
296
+
297
+ {/* Work Summary */}
298
+ <ContentCard
299
+ icon={<Activity size={13} />}
300
+ label="Work Summary"
301
+ badge={<span className="text-[10px] text-content-muted">{MOCK_WORK_SUMMARIES.length} cycles</span>}
302
+ >
303
+ <AccentBorderList>
304
+ {MOCK_WORK_SUMMARIES.map((ws, i) => (
305
+ <AccentBorderItem key={i} index={`Cycle ${ws.cycle}`}>
306
+ <p className="text-[12px] leading-[1.75] text-content-secondary mt-1 mb-2">{ws.approach}</p>
307
+ <div className="flex flex-wrap gap-1.5 mb-1.5">
308
+ {ws.filesCreated.map((f, fi) => (
309
+ <span key={fi} className="inline-flex items-center gap-1 rounded-md bg-accent/10 px-1.5 py-0.5 text-[10px] text-accent">
310
+ <FolderPlus size={10} />{f.split('/').pop()}
311
+ </span>
312
+ ))}
313
+ {ws.filesUpdated.map((f, fi) => (
314
+ <span key={fi} className="inline-flex items-center gap-1 rounded-md bg-accent-secondary/10 px-1.5 py-0.5 text-[10px] text-accent-secondary">
315
+ <FileText size={10} />{f.split('/').pop()}
316
+ </span>
317
+ ))}
318
+ {ws.filesDeleted.map((f, fi) => (
319
+ <span key={fi} className="inline-flex items-center gap-1 rounded-md bg-error/10 px-1.5 py-0.5 text-[10px] text-error">
320
+ <Trash2 size={10} />{f.split('/').pop()}
321
+ </span>
322
+ ))}
323
+ </div>
324
+ {ws.decisions.length > 0 && (
325
+ <div className="space-y-1">
326
+ {ws.decisions.map((d, di) => (
327
+ <div key={di} className="flex items-start gap-1.5 text-[10px] text-content-muted">
328
+ <Lightbulb size={10} className="mt-0.5 shrink-0 text-accent-secondary" />
329
+ {d}
330
+ </div>
331
+ ))}
332
+ </div>
333
+ )}
334
+ </AccentBorderItem>
335
+ ))}
336
+ </AccentBorderList>
337
+ </ContentCard>
338
+
339
+ {/* Relationships */}
340
+ <div className="rounded-xl border border-edge bg-surface-alt p-3">
341
+ <div className="flex items-center gap-2 mb-2">
342
+ <GitBranch size={13} className="text-content-muted" />
343
+ <span className="text-[12px] font-medium text-content">Relationships</span>
344
+ <span className="text-[10px] text-content-muted">{MOCK_EDGES.length}</span>
345
+ </div>
346
+ <div className="space-y-1.5">
347
+ {MOCK_EDGES.map((e, i) => (
348
+ <div key={i} className="flex items-center gap-2 rounded-lg px-2 py-1.5 hover:bg-surface-raised/50 transition-colors group">
349
+ {e.direction === 'outgoing'
350
+ ? <ArrowRight size={12} className="text-accent shrink-0" />
351
+ : <ArrowLeft size={12} className="text-accent-secondary shrink-0" />}
352
+ <span className="text-[11px] text-content-muted capitalize flex-shrink-0">
353
+ {e.relation.replace(/_/g, ' ')}
354
+ </span>
355
+ <span className="text-[12px] text-accent font-medium truncate group-hover:underline cursor-pointer">
356
+ {e.neighborName}
357
+ </span>
358
+ </div>
359
+ ))}
360
+ </div>
361
+ </div>
362
+ </div>
363
+ );
364
+ }
365
+
366
+ /* ── Export showcase ── */
367
+
368
+ export function SidePanelShowcase() {
369
+ return <CardStackPanel />;
370
+ }
@@ -0,0 +1,64 @@
1
+ import { useEffect, useRef, type ReactNode } from 'react';
2
+
3
+ interface Props {
4
+ isOpen: boolean;
5
+ onClose: () => void;
6
+ title: string;
7
+ children: ReactNode;
8
+ width?: number;
9
+ zIndex?: number;
10
+ }
11
+
12
+ export const SideSheet = ({ isOpen, onClose, title, children, width = 420, zIndex = 250 }: Props) => {
13
+ const sheetRef = useRef<HTMLDivElement>(null);
14
+
15
+ // Close on outside click — defer listener to skip the opening click
16
+ useEffect(() => {
17
+ if (!isOpen) return;
18
+ const handleClick = (e: MouseEvent) => {
19
+ if (sheetRef.current && !sheetRef.current.contains(e.target as Node)) {
20
+ onClose();
21
+ }
22
+ };
23
+ const frame = requestAnimationFrame(() => {
24
+ document.addEventListener('click', handleClick);
25
+ });
26
+ return () => {
27
+ cancelAnimationFrame(frame);
28
+ document.removeEventListener('click', handleClick);
29
+ };
30
+ }, [isOpen, onClose]);
31
+
32
+ // Close on Escape
33
+ useEffect(() => {
34
+ if (!isOpen) return;
35
+ const handleKey = (e: KeyboardEvent) => {
36
+ if (e.key === 'Escape') onClose();
37
+ };
38
+ document.addEventListener('keydown', handleKey);
39
+ return () => document.removeEventListener('keydown', handleKey);
40
+ }, [isOpen, onClose]);
41
+
42
+ return (
43
+ <div
44
+ ref={sheetRef}
45
+ className={`fixed top-[44px] right-0 h-[calc(100vh-44px)] flex flex-col overflow-hidden bg-panel border-l border-edge shadow-[-4px_0_16px_var(--panel-shadow)] transition-transform duration-250 ease-out ${
46
+ isOpen ? 'translate-x-0' : 'translate-x-full'
47
+ }`}
48
+ style={{ width, zIndex }}
49
+ >
50
+ <div className="flex items-center justify-between px-4 py-3 border-b border-edge min-h-[48px]">
51
+ <span className="text-[13px] font-semibold text-content truncate">{title}</span>
52
+ <button
53
+ className="shrink-0 px-2 py-1 text-[18px] text-content-secondary bg-transparent border-none rounded cursor-pointer hover:bg-tab-hover hover:text-content"
54
+ onClick={onClose}
55
+ >
56
+ &times;
57
+ </button>
58
+ </div>
59
+ <div className="flex-1 overflow-y-auto p-4 text-[13px] leading-relaxed text-content">
60
+ {children}
61
+ </div>
62
+ </div>
63
+ );
64
+ };
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+
3
+ const STATUS_COLORS: Record<string, string> = {
4
+ draft: 'bg-content-muted',
5
+ partially_defined: 'bg-accent-secondary',
6
+ defined: 'bg-accent',
7
+ in_progress: 'bg-accent-secondary',
8
+ };
9
+
10
+ export interface StatusDotProps {
11
+ status: string;
12
+ }
13
+
14
+ export function StatusDot({ status }: StatusDotProps) {
15
+ return (
16
+ <span className={`inline-block h-2 w-2 rounded-full ${STATUS_COLORS[status] || 'bg-content-muted'}`} />
17
+ );
18
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import { Handle, Position } from '@xyflow/react';
3
+ import type { NodeProps } from '@xyflow/react';
4
+ import { GitBranch } from 'lucide-react';
5
+ import { KANBAN_COLUMNS } from './workflow_types';
6
+
7
+ export function CheckCardPositionNode({ selected }: NodeProps) {
8
+ return (
9
+ <div className={`px-4 py-3 rounded-lg border bg-surface-raised text-content min-w-[180px] ${
10
+ selected ? 'border-accent shadow-[0_0_0_1px_var(--accent)]' : 'border-edge'
11
+ }`}>
12
+ <Handle type="target" position={Position.Top} className="!w-3 !h-3 !bg-accent !border-2 !border-surface" />
13
+ <div className="flex items-center gap-2 mb-2">
14
+ <GitBranch size={14} className="text-cyan-400" />
15
+ <span className="text-[12px] font-semibold">Check Card Position</span>
16
+ </div>
17
+ <div className="text-[10px] text-content-muted mb-1">Output per column:</div>
18
+ <div className="flex flex-wrap gap-1">
19
+ {KANBAN_COLUMNS.map((col, i) => (
20
+ <div key={col} className="relative">
21
+ <span className="px-1.5 py-0.5 rounded bg-surface text-[9px] text-content-muted border border-edge">
22
+ {col.replace('_', ' ')}
23
+ </span>
24
+ <Handle
25
+ type="source"
26
+ position={Position.Bottom}
27
+ id={col}
28
+ className="!w-2 !h-2 !bg-cyan-400 !border !border-surface"
29
+ style={{ left: `${((i + 0.5) / KANBAN_COLUMNS.length) * 100}%` }}
30
+ />
31
+ </div>
32
+ ))}
33
+ </div>
34
+ </div>
35
+ );
36
+ }