@shepai/cli 1.47.0 → 1.48.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 (161) hide show
  1. package/apis/json-schema/ApprovalGateDefaults.yaml +26 -0
  2. package/apis/json-schema/Feature.yaml +3 -0
  3. package/apis/json-schema/Repository.yaml +16 -0
  4. package/apis/json-schema/Settings.yaml +5 -0
  5. package/apis/json-schema/WorkflowConfig.yaml +4 -20
  6. package/dist/packages/core/src/application/ports/output/services/git-pr-service.interface.d.ts +17 -0
  7. package/dist/packages/core/src/application/ports/output/services/git-pr-service.interface.d.ts.map +1 -1
  8. package/dist/packages/core/src/application/ports/output/services/worktree-service.interface.d.ts +11 -1
  9. package/dist/packages/core/src/application/ports/output/services/worktree-service.interface.d.ts.map +1 -1
  10. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.d.ts +3 -1
  11. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.d.ts.map +1 -1
  12. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.js +11 -4
  13. package/dist/packages/core/src/application/use-cases/features/get-plan-artifact.use-case.d.ts +19 -0
  14. package/dist/packages/core/src/application/use-cases/features/get-plan-artifact.use-case.d.ts.map +1 -0
  15. package/dist/packages/core/src/application/use-cases/features/get-plan-artifact.use-case.js +52 -0
  16. package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
  17. package/dist/packages/core/src/infrastructure/di/container.js +5 -0
  18. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-worker.d.ts.map +1 -1
  19. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-worker.js +2 -0
  20. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge.node.d.ts +2 -0
  21. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge.node.d.ts.map +1 -1
  22. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge.node.js +10 -3
  23. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/merge-prompts.d.ts.map +1 -1
  24. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/merge-prompts.js +15 -7
  25. package/dist/packages/core/src/infrastructure/services/git/git-pr.service.d.ts +2 -0
  26. package/dist/packages/core/src/infrastructure/services/git/git-pr.service.d.ts.map +1 -1
  27. package/dist/packages/core/src/infrastructure/services/git/git-pr.service.js +41 -0
  28. package/dist/packages/core/src/infrastructure/services/git/worktree.service.d.ts +2 -1
  29. package/dist/packages/core/src/infrastructure/services/git/worktree.service.d.ts.map +1 -1
  30. package/dist/packages/core/src/infrastructure/services/git/worktree.service.js +29 -4
  31. package/dist/src/presentation/cli/ui/messages.d.ts.map +1 -1
  32. package/dist/src/presentation/cli/ui/messages.js +1 -7
  33. package/dist/src/presentation/web/app/actions/get-merge-review-data.d.ts +7 -0
  34. package/dist/src/presentation/web/app/actions/get-merge-review-data.d.ts.map +1 -0
  35. package/dist/src/presentation/web/app/actions/get-merge-review-data.js +49 -0
  36. package/dist/src/presentation/web/app/page.d.ts.map +1 -1
  37. package/dist/src/presentation/web/app/page.js +1 -0
  38. package/dist/src/presentation/web/components/common/feature-node/feature-node.d.ts.map +1 -1
  39. package/dist/src/presentation/web/components/common/feature-node/feature-node.js +8 -1
  40. package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.d.ts +1 -0
  41. package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.d.ts.map +1 -1
  42. package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.js +22 -1
  43. package/dist/src/presentation/web/components/common/merge-review/index.d.ts +4 -0
  44. package/dist/src/presentation/web/components/common/merge-review/index.d.ts.map +1 -0
  45. package/dist/src/presentation/web/components/common/merge-review/index.js +2 -0
  46. package/dist/src/presentation/web/components/common/merge-review/merge-review-config.d.ts +75 -0
  47. package/dist/src/presentation/web/components/common/merge-review/merge-review-config.d.ts.map +1 -0
  48. package/dist/src/presentation/web/components/common/merge-review/merge-review-config.js +1 -0
  49. package/dist/src/presentation/web/components/common/merge-review/merge-review-drawer.d.ts +3 -0
  50. package/dist/src/presentation/web/components/common/merge-review/merge-review-drawer.d.ts.map +1 -0
  51. package/dist/src/presentation/web/components/common/merge-review/merge-review-drawer.js +7 -0
  52. package/dist/src/presentation/web/components/common/merge-review/merge-review.d.ts +3 -0
  53. package/dist/src/presentation/web/components/common/merge-review/merge-review.d.ts.map +1 -0
  54. package/dist/src/presentation/web/components/common/merge-review/merge-review.js +36 -0
  55. package/dist/src/presentation/web/components/common/merge-review/merge-review.stories.d.ts +32 -0
  56. package/dist/src/presentation/web/components/common/merge-review/merge-review.stories.d.ts.map +1 -0
  57. package/dist/src/presentation/web/components/common/merge-review/merge-review.stories.js +165 -0
  58. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  59. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +55 -1
  60. package/dist/src/presentation/web/components/features/control-center/control-center.stories.d.ts.map +1 -1
  61. package/dist/src/presentation/web/components/features/control-center/control-center.stories.js +15 -0
  62. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  63. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +37 -15
  64. package/dist/tsconfig.build.tsbuildinfo +1 -1
  65. package/package.json +1 -1
  66. package/web/.next/BUILD_ID +1 -1
  67. package/web/.next/build-manifest.json +2 -2
  68. package/web/.next/cache/.previewinfo +1 -1
  69. package/web/.next/cache/.rscinfo +1 -1
  70. package/web/.next/cache/.tsbuildinfo +1 -1
  71. package/web/.next/cache/config.json +3 -3
  72. package/web/.next/fallback-build-manifest.json +2 -2
  73. package/web/.next/prerender-manifest.json +3 -3
  74. package/web/.next/required-server-files.js +1 -1
  75. package/web/.next/required-server-files.json +1 -1
  76. package/web/.next/server/app/_global-error.html +2 -2
  77. package/web/.next/server/app/_global-error.rsc +1 -1
  78. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  79. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  80. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  81. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  82. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  83. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  84. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  85. package/web/.next/server/app/page/server-reference-manifest.json +39 -24
  86. package/web/.next/server/app/page.js +1 -1
  87. package/web/.next/server/app/page.js.nft.json +1 -1
  88. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  89. package/web/.next/server/app/version/page.js.nft.json +1 -1
  90. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  91. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +4 -4
  92. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
  93. package/web/.next/server/chunks/ssr/[root-of-the-server]__19d52794._.js +9 -0
  94. package/web/.next/server/chunks/ssr/[root-of-the-server]__19d52794._.js.map +1 -0
  95. package/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  96. package/web/.next/{standalone/src/presentation/web/.next/server/chunks/ssr/_3ac3601c._.js → server/chunks/ssr/_1fafa9db._.js} +2 -2
  97. package/web/.next/server/chunks/ssr/_1fafa9db._.js.map +1 -0
  98. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  99. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  100. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  101. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
  102. package/web/.next/server/pages/500.html +2 -2
  103. package/web/.next/server/server-reference-manifest.js +1 -1
  104. package/web/.next/server/server-reference-manifest.json +40 -25
  105. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  106. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  107. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  108. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  109. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  110. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  111. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  112. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  113. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  114. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  115. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  116. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  117. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  118. package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +39 -24
  119. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js +1 -1
  120. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
  121. package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
  122. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js.nft.json +1 -1
  123. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  124. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +4 -4
  125. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__19d52794._.js +9 -0
  126. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  127. package/web/.next/{server/chunks/ssr/_3ac3601c._.js → standalone/src/presentation/web/.next/server/chunks/ssr/_1fafa9db._.js} +2 -2
  128. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  129. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  130. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  131. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  132. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +40 -25
  133. package/web/.next/standalone/src/presentation/web/app/actions/get-merge-review-data.ts +64 -0
  134. package/web/.next/standalone/src/presentation/web/app/page.tsx +1 -0
  135. package/web/.next/standalone/src/presentation/web/components/common/feature-node/feature-node.stories.tsx +23 -1
  136. package/web/.next/standalone/src/presentation/web/components/common/feature-node/feature-node.tsx +6 -1
  137. package/web/.next/standalone/src/presentation/web/components/common/merge-review/index.ts +7 -0
  138. package/web/.next/standalone/src/presentation/web/components/common/merge-review/merge-review-config.ts +81 -0
  139. package/web/.next/standalone/src/presentation/web/components/common/merge-review/merge-review-drawer.tsx +34 -0
  140. package/web/.next/standalone/src/presentation/web/components/common/merge-review/merge-review.stories.tsx +240 -0
  141. package/web/.next/standalone/src/presentation/web/components/common/merge-review/merge-review.tsx +252 -0
  142. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +82 -1
  143. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center.stories.tsx +15 -0
  144. package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +47 -17
  145. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  146. package/web/.next/static/chunks/{9f8e71029fb04681.js → 0da7a9860fa6ae05.js} +2 -2
  147. package/web/.next/static/chunks/22f30ce91a3c79c1.js +1 -0
  148. package/web/.next/static/chunks/66378a6c055c4e67.js +10 -0
  149. package/web/.next/static/chunks/8668962d3e682263.css +2 -0
  150. package/web/.next/trace +1 -1
  151. package/web/.next/trace-build +1 -1
  152. package/web/.next/server/chunks/ssr/[root-of-the-server]__d6d75c22._.js +0 -9
  153. package/web/.next/server/chunks/ssr/[root-of-the-server]__d6d75c22._.js.map +0 -1
  154. package/web/.next/server/chunks/ssr/_3ac3601c._.js.map +0 -1
  155. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__d6d75c22._.js +0 -9
  156. package/web/.next/static/chunks/779c99975c85389e.js +0 -10
  157. package/web/.next/static/chunks/83ff3b6bbee54bce.js +0 -1
  158. package/web/.next/static/chunks/b7b7abe79e05b229.css +0 -2
  159. /package/web/.next/static/{wQ3vPV9I8JeacVNyp4HFg → 1kZFDMQ0i-Y8dy1KkHlzv}/_buildManifest.js +0 -0
  160. /package/web/.next/static/{wQ3vPV9I8JeacVNyp4HFg → 1kZFDMQ0i-Y8dy1KkHlzv}/_clientMiddlewareManifest.json +0 -0
  161. /package/web/.next/static/{wQ3vPV9I8JeacVNyp4HFg → 1kZFDMQ0i-Y8dy1KkHlzv}/_ssgManifest.js +0 -0
@@ -6,10 +6,12 @@ import { approveFeature } from '../../../app/actions/approve-feature.js';
6
6
  import { getFeatureArtifact } from '../../../app/actions/get-feature-artifact.js';
7
7
  import { getResearchArtifact } from '../../../app/actions/get-research-artifact.js';
8
8
  import { getWorkflowDefaults } from '../../../app/actions/get-workflow-defaults.js';
9
+ import { getMergeReviewData } from '../../../app/actions/get-merge-review-data.js';
9
10
  import { FeaturesCanvas } from '../../features/features-canvas/index.js';
10
11
  import { FeatureDrawer, FeatureCreateDrawer } from '../../common/index.js';
11
12
  import { PrdQuestionnaireDrawer } from '../../common/prd-questionnaire/index.js';
12
13
  import { TechDecisionsDrawer } from '../../common/tech-decisions-review/index.js';
14
+ import { MergeReviewDrawer } from '../../common/merge-review/index.js';
13
15
  import { NotificationPermissionBanner } from '../../common/notification-permission-banner/index.js';
14
16
  import { ControlCenterEmptyState } from './control-center-empty-state.js';
15
17
  import { useControlCenterState } from './use-control-center-state.js';
@@ -32,8 +34,12 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
32
34
  // Tech decisions drawer state
33
35
  const [techDecisionsData, setTechDecisionsData] = useState(null);
34
36
  const [isLoadingTechDecisions, setIsLoadingTechDecisions] = useState(false);
37
+ // Merge review drawer state
38
+ const [mergeReviewData, setMergeReviewData] = useState(null);
39
+ const [isLoadingMergeReview, setIsLoadingMergeReview] = useState(false);
35
40
  const showPrdDrawer = selectedNode?.lifecycle === 'requirements' && selectedNode?.state === 'action-required';
36
41
  const showTechDecisionsDrawer = selectedNode?.lifecycle === 'implementation' && selectedNode?.state === 'action-required';
42
+ const showMergeReviewDrawer = selectedNode?.lifecycle === 'review' && selectedNode?.state === 'action-required';
37
43
  const handlePrdSelect = useCallback((questionId, optionId) => {
38
44
  setPrdSelections((prev) => ({ ...prev, [questionId]: optionId }));
39
45
  }, []);
@@ -75,6 +81,24 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
75
81
  toast.success('Plan approved — agent resuming');
76
82
  clearSelection();
77
83
  }, [selectedNode?.featureId, clearSelection]);
84
+ const handleMergeApprove = useCallback(async () => {
85
+ const featureId = selectedNode?.featureId;
86
+ if (!featureId)
87
+ return;
88
+ const result = await approveFeature(featureId);
89
+ if (!result.approved) {
90
+ toast.error(result.error ?? 'Failed to approve merge');
91
+ return;
92
+ }
93
+ toast.success('Merge approved — agent resuming');
94
+ clearSelection();
95
+ }, [selectedNode?.featureId, clearSelection]);
96
+ const handleMergeRefine = useCallback(async (_text) => {
97
+ setIsLoadingMergeReview(true);
98
+ // TODO: Call API to refine merge
99
+ await new Promise((resolve) => setTimeout(resolve, 1500));
100
+ setIsLoadingMergeReview(false);
101
+ }, []);
78
102
  // Fetch questionnaire data and reset selections when a different feature is selected
79
103
  const prdFeatureId = showPrdDrawer ? selectedNode?.featureId : null;
80
104
  useEffect(() => {
@@ -148,6 +172,36 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
148
172
  cancelled = true;
149
173
  };
150
174
  }, [techDecisionsFeatureId]);
175
+ // Fetch merge review data when a feature is in review + action-required
176
+ const mergeFeatureId = showMergeReviewDrawer ? selectedNode?.featureId : null;
177
+ useEffect(() => {
178
+ setMergeReviewData(null);
179
+ if (!mergeFeatureId)
180
+ return;
181
+ let cancelled = false;
182
+ setIsLoadingMergeReview(true);
183
+ getMergeReviewData(mergeFeatureId)
184
+ .then((result) => {
185
+ if (cancelled)
186
+ return;
187
+ if ('error' in result) {
188
+ toast.error(result.error);
189
+ return;
190
+ }
191
+ setMergeReviewData(result);
192
+ })
193
+ .catch(() => {
194
+ if (!cancelled)
195
+ toast.error('Failed to load merge review data');
196
+ })
197
+ .finally(() => {
198
+ if (!cancelled)
199
+ setIsLoadingMergeReview(false);
200
+ });
201
+ return () => {
202
+ cancelled = true;
203
+ };
204
+ }, [mergeFeatureId]);
151
205
  const hasRepositories = nodes.some((n) => n.type === 'repositoryNode');
152
206
  // Listen for global "open create drawer" events from the sidebar
153
207
  useEffect(() => {
@@ -158,5 +212,5 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
158
212
  if (!hasRepositories) {
159
213
  return (_jsxs(_Fragment, { children: [_jsx(NotificationPermissionBanner, {}), _jsx(ControlCenterEmptyState, { onRepositorySelect: handleAddRepository }), _jsx(FeatureCreateDrawer, { open: isCreateDrawerOpen, onClose: closeCreateDrawer, onSubmit: handleCreateFeatureSubmit, repositoryPath: pendingRepositoryPath, workflowDefaults: workflowDefaults })] }));
160
214
  }
161
- return (_jsxs(_Fragment, { children: [_jsx(NotificationPermissionBanner, {}), _jsx(FeaturesCanvas, { nodes: nodes, edges: edges, onNodesChange: onNodesChange, onConnect: handleConnect, onAddFeature: handleAddFeature, onNodeAction: handleAddFeatureToFeature, onNodeClick: handleNodeClick, onPaneClick: clearSelection, onRepositoryAdd: handleAddFeatureToRepo, onRepositoryDelete: handleDeleteRepository, onRepositorySelect: handleAddRepository, emptyState: _jsx(ControlCenterEmptyState, { onRepositorySelect: handleAddRepository }) }), _jsx(FeatureDrawer, { selectedNode: showPrdDrawer || showTechDecisionsDrawer ? null : selectedNode, onClose: clearSelection, onDelete: handleDeleteFeature, isDeleting: isDeleting }), questionnaireData ? (_jsx(PrdQuestionnaireDrawer, { open: showPrdDrawer, onClose: clearSelection, featureName: selectedNode?.name ?? '', featureDescription: selectedNode?.description, featureId: selectedNode?.featureId, repositoryPath: selectedNode?.repositoryPath, branch: selectedNode?.branch, specPath: selectedNode?.specPath, data: questionnaireData, selections: prdSelections, onSelect: handlePrdSelect, onRefine: handlePrdRefine, onApprove: handlePrdApprove, onDelete: handleDeleteFeature, isDeleting: isDeleting, isProcessing: isPrdProcessing || isLoadingQuestionnaire })) : null, techDecisionsData ? (_jsx(TechDecisionsDrawer, { open: showTechDecisionsDrawer, onClose: clearSelection, featureName: selectedNode?.name ?? '', featureId: selectedNode?.featureId, repositoryPath: selectedNode?.repositoryPath, branch: selectedNode?.branch, specPath: selectedNode?.specPath, data: techDecisionsData, onRefine: handleTechDecisionsRefine, onApprove: handleTechDecisionsApprove, onDelete: handleDeleteFeature, isDeleting: isDeleting, isProcessing: isLoadingTechDecisions })) : null, _jsx(FeatureCreateDrawer, { open: isCreateDrawerOpen, onClose: closeCreateDrawer, onSubmit: handleCreateFeatureSubmit, repositoryPath: pendingRepositoryPath, workflowDefaults: workflowDefaults })] }));
215
+ return (_jsxs(_Fragment, { children: [_jsx(NotificationPermissionBanner, {}), _jsx(FeaturesCanvas, { nodes: nodes, edges: edges, onNodesChange: onNodesChange, onConnect: handleConnect, onAddFeature: handleAddFeature, onNodeAction: handleAddFeatureToFeature, onNodeClick: handleNodeClick, onPaneClick: clearSelection, onRepositoryAdd: handleAddFeatureToRepo, onRepositoryDelete: handleDeleteRepository, onRepositorySelect: handleAddRepository, emptyState: _jsx(ControlCenterEmptyState, { onRepositorySelect: handleAddRepository }) }), _jsx(FeatureDrawer, { selectedNode: showPrdDrawer || showTechDecisionsDrawer || showMergeReviewDrawer ? null : selectedNode, onClose: clearSelection, onDelete: handleDeleteFeature, isDeleting: isDeleting }), questionnaireData ? (_jsx(PrdQuestionnaireDrawer, { open: showPrdDrawer, onClose: clearSelection, featureName: selectedNode?.name ?? '', featureDescription: selectedNode?.description, featureId: selectedNode?.featureId, repositoryPath: selectedNode?.repositoryPath, branch: selectedNode?.branch, specPath: selectedNode?.specPath, data: questionnaireData, selections: prdSelections, onSelect: handlePrdSelect, onRefine: handlePrdRefine, onApprove: handlePrdApprove, onDelete: handleDeleteFeature, isDeleting: isDeleting, isProcessing: isPrdProcessing || isLoadingQuestionnaire })) : null, techDecisionsData ? (_jsx(TechDecisionsDrawer, { open: showTechDecisionsDrawer, onClose: clearSelection, featureName: selectedNode?.name ?? '', featureId: selectedNode?.featureId, repositoryPath: selectedNode?.repositoryPath, branch: selectedNode?.branch, specPath: selectedNode?.specPath, data: techDecisionsData, onRefine: handleTechDecisionsRefine, onApprove: handleTechDecisionsApprove, onDelete: handleDeleteFeature, isDeleting: isDeleting, isProcessing: isLoadingTechDecisions })) : null, mergeReviewData ? (_jsx(MergeReviewDrawer, { open: showMergeReviewDrawer, onClose: clearSelection, featureName: selectedNode?.name ?? '', featureId: selectedNode?.featureId, repositoryPath: selectedNode?.repositoryPath, branch: selectedNode?.branch, specPath: selectedNode?.specPath, data: mergeReviewData, onApprove: handleMergeApprove, onRefine: handleMergeRefine, onDelete: handleDeleteFeature, isDeleting: isDeleting, isProcessing: isLoadingMergeReview })) : null, _jsx(FeatureCreateDrawer, { open: isCreateDrawerOpen, onClose: closeCreateDrawer, onSubmit: handleCreateFeatureSubmit, repositoryPath: pendingRepositoryPath, workflowDefaults: workflowDefaults })] }));
162
216
  }
@@ -1 +1 @@
1
- {"version":3,"file":"control-center.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/control-center.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAQjD,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,aAAa,CAgBpC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAkEnC,eAAO,MAAM,KAAK,EAAE,KAKnB,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAQ1B,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAKzB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAS7B,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,EAAE,KAYzB,CAAC;AAwIF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,KAKzB,CAAC"}
1
+ {"version":3,"file":"control-center.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/control-center.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAQjD,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,aAAa,CAgBpC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAiFnC,eAAO,MAAM,KAAK,EAAE,KAKnB,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAQ1B,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAKzB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAS7B,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,EAAE,KAYzB,CAAC;AAwIF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,KAKzB,CAAC"}
@@ -60,6 +60,21 @@ const featureNodes = [
60
60
  branch: 'feat/api-gateway',
61
61
  },
62
62
  },
63
+ {
64
+ id: 'feat-4',
65
+ type: 'featureNode',
66
+ position: { x: 800, y: 345 },
67
+ data: {
68
+ name: 'Merge Review',
69
+ description: 'PR ready for merge approval',
70
+ featureId: '#f4',
71
+ lifecycle: 'review',
72
+ state: 'action-required',
73
+ progress: 90,
74
+ repositoryPath: '/home/user/my-repo',
75
+ branch: 'feat/merge-review',
76
+ },
77
+ },
63
78
  ];
64
79
  const repoNode = {
65
80
  id: 'repo-1',
@@ -1 +1 @@
1
- {"version":3,"file":"use-control-center-state.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/use-control-center-state.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAOhF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,YAAY,EAAE,eAAe,GAAG,IAAI,CAAC;IACrC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,aAAa,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC,EAAE,KAAK,IAAI,CAAC;IAC/D,aAAa,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;IAChD,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,eAAe,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACzE,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,sBAAsB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,yBAAyB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,EAAE,CAAC,SAAS,EAAE,eAAe,KAAK,IAAI,CAAC;IACnD,yBAAyB,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAChE,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,sBAAsB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,CACjB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,YAAY,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,KACpC,MAAM,CAAC;CACb;AAID,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,cAAc,EAAE,EAC9B,YAAY,EAAE,IAAI,EAAE,GACnB,kBAAkB,CAwepB"}
1
+ {"version":3,"file":"use-control-center-state.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/use-control-center-state.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAOhF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,YAAY,EAAE,eAAe,GAAG,IAAI,CAAC;IACrC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,aAAa,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC,EAAE,KAAK,IAAI,CAAC;IAC/D,aAAa,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;IAChD,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,eAAe,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACzE,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,sBAAsB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,yBAAyB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,EAAE,CAAC,SAAS,EAAE,eAAe,KAAK,IAAI,CAAC;IACnD,yBAAyB,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAChE,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,sBAAsB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,CACjB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,YAAY,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,KACpC,MAAM,CAAC;CACb;AAID,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,cAAc,EAAE,EAC9B,YAAY,EAAE,IAAI,EAAE,GACnB,kBAAkB,CAsgBpB"}
@@ -273,8 +273,17 @@ export function useControlCenterState(initialNodes, initialEdges) {
273
273
  return;
274
274
  }
275
275
  setSelectedNode(null);
276
- setNodes((prev) => prev.filter((n) => n.id !== featureId));
277
- setEdges((prev) => prev.filter((e) => e.source !== featureId && e.target !== featureId));
276
+ setNodes((currentNodes) => {
277
+ const remainingNodes = currentNodes.filter((n) => n.id !== featureId);
278
+ const remainingEdges = edges.filter((e) => e.source !== featureId && e.target !== featureId);
279
+ const result = layoutWithDagre(remainingNodes, remainingEdges, {
280
+ direction: 'LR',
281
+ ranksep: 200,
282
+ nodesep: 60,
283
+ });
284
+ setEdges(result.edges);
285
+ return result.nodes;
286
+ });
278
287
  toast.success('Feature deleted successfully');
279
288
  router.refresh();
280
289
  }
@@ -284,7 +293,7 @@ export function useControlCenterState(initialNodes, initialEdges) {
284
293
  finally {
285
294
  setIsDeleting(false);
286
295
  }
287
- }, [router]);
296
+ }, [router, edges]);
288
297
  const handleDeleteRepository = useCallback(async (repositoryId) => {
289
298
  const repoNodeId = `repo-${repositoryId}`;
290
299
  // Optimistic: remove node and its edges immediately
@@ -332,27 +341,31 @@ export function useControlCenterState(initialNodes, initialEdges) {
332
341
  .split(/[\\/]/)
333
342
  .pop() ?? path;
334
343
  // Optimistic UI: add node immediately
344
+ let savedAddRepoY = 0;
335
345
  setNodes((currentNodes) => {
336
346
  const repoNodes = currentNodes.filter((n) => n.type === 'repositoryNode');
337
347
  const addRepoNode = currentNodes.find((n) => n.type === 'addRepositoryNode');
338
- // Place in the repo column, below the last existing repo node
348
+ // Save addRepoNode's original Y for rollback on error
349
+ if (addRepoNode)
350
+ savedAddRepoY = addRepoNode.position.y;
351
+ // Place in the repo column, at the addRepoNode's current position
339
352
  const repoX = repoNodes[0]?.position.x ?? addRepoNode?.position.x ?? 50;
340
353
  const repoHeight = 50; // repositoryNode height
341
354
  const gap = 15; // match dagre nodesep
342
- const lastRepoBottomY = repoNodes.length > 0
343
- ? Math.max(...repoNodes.map((n) => n.position.y)) + repoHeight
344
- : addRepoNode
345
- ? addRepoNode.position.y
346
- : 0;
347
- const position = { x: repoX, y: lastRepoBottomY + gap };
355
+ const position = {
356
+ x: repoX,
357
+ y: addRepoNode ? addRepoNode.position.y : 0,
358
+ };
348
359
  const newNode = {
349
360
  id: tempId,
350
361
  type: 'repositoryNode',
351
362
  position,
352
363
  data: { name: repoName, repositoryPath: path, id: tempId },
353
364
  };
354
- // Move addRepo button below the new node
355
- const addRepoY = position.y + repoHeight + gap;
365
+ // Shift addRepo button down by exactly one slot
366
+ const addRepoY = addRepoNode
367
+ ? addRepoNode.position.y + repoHeight + gap
368
+ : position.y + repoHeight + gap;
356
369
  return currentNodes
357
370
  .map((n) => n.type === 'addRepositoryNode' ? { ...n, position: { ...n.position, y: addRepoY } } : n)
358
371
  .concat(newNode);
@@ -361,8 +374,12 @@ export function useControlCenterState(initialNodes, initialEdges) {
361
374
  addRepository({ path, name: repoName })
362
375
  .then((result) => {
363
376
  if (result.error) {
364
- // Rollback optimistic node
365
- setNodes((prev) => prev.filter((n) => n.id !== tempId));
377
+ // Rollback optimistic node and restore addRepoNode position
378
+ setNodes((prev) => prev
379
+ .filter((n) => n.id !== tempId)
380
+ .map((n) => n.type === 'addRepositoryNode'
381
+ ? { ...n, position: { ...n.position, y: savedAddRepoY } }
382
+ : n));
366
383
  toast.error(result.error);
367
384
  return;
368
385
  }
@@ -385,7 +402,12 @@ export function useControlCenterState(initialNodes, initialEdges) {
385
402
  router.refresh();
386
403
  })
387
404
  .catch(() => {
388
- setNodes((prev) => prev.filter((n) => n.id !== tempId));
405
+ // Rollback optimistic node and restore addRepoNode position
406
+ setNodes((prev) => prev
407
+ .filter((n) => n.id !== tempId)
408
+ .map((n) => n.type === 'addRepositoryNode'
409
+ ? { ...n, position: { ...n.position, y: savedAddRepoY } }
410
+ : n));
389
411
  toast.error('Failed to add repository');
390
412
  });
391
413
  }, [router]);