@shepai/cli 1.46.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 (177) 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/create-feature.d.ts +2 -0
  34. package/dist/src/presentation/web/app/actions/create-feature.d.ts.map +1 -1
  35. package/dist/src/presentation/web/app/actions/create-feature.js +3 -1
  36. package/dist/src/presentation/web/app/actions/get-merge-review-data.d.ts +7 -0
  37. package/dist/src/presentation/web/app/actions/get-merge-review-data.d.ts.map +1 -0
  38. package/dist/src/presentation/web/app/actions/get-merge-review-data.js +49 -0
  39. package/dist/src/presentation/web/app/actions/get-workflow-defaults.d.ts +11 -0
  40. package/dist/src/presentation/web/app/actions/get-workflow-defaults.d.ts.map +1 -0
  41. package/dist/src/presentation/web/app/actions/get-workflow-defaults.js +15 -0
  42. package/dist/src/presentation/web/app/page.d.ts.map +1 -1
  43. package/dist/src/presentation/web/app/page.js +1 -0
  44. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts +5 -1
  45. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts.map +1 -1
  46. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js +25 -7
  47. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts +17 -0
  48. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts.map +1 -1
  49. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.js +54 -2
  50. package/dist/src/presentation/web/components/common/feature-node/feature-node.d.ts.map +1 -1
  51. package/dist/src/presentation/web/components/common/feature-node/feature-node.js +8 -1
  52. package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.d.ts +1 -0
  53. package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.d.ts.map +1 -1
  54. package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.js +22 -1
  55. package/dist/src/presentation/web/components/common/merge-review/index.d.ts +4 -0
  56. package/dist/src/presentation/web/components/common/merge-review/index.d.ts.map +1 -0
  57. package/dist/src/presentation/web/components/common/merge-review/index.js +2 -0
  58. package/dist/src/presentation/web/components/common/merge-review/merge-review-config.d.ts +75 -0
  59. package/dist/src/presentation/web/components/common/merge-review/merge-review-config.d.ts.map +1 -0
  60. package/dist/src/presentation/web/components/common/merge-review/merge-review-config.js +1 -0
  61. package/dist/src/presentation/web/components/common/merge-review/merge-review-drawer.d.ts +3 -0
  62. package/dist/src/presentation/web/components/common/merge-review/merge-review-drawer.d.ts.map +1 -0
  63. package/dist/src/presentation/web/components/common/merge-review/merge-review-drawer.js +7 -0
  64. package/dist/src/presentation/web/components/common/merge-review/merge-review.d.ts +3 -0
  65. package/dist/src/presentation/web/components/common/merge-review/merge-review.d.ts.map +1 -0
  66. package/dist/src/presentation/web/components/common/merge-review/merge-review.js +36 -0
  67. package/dist/src/presentation/web/components/common/merge-review/merge-review.stories.d.ts +32 -0
  68. package/dist/src/presentation/web/components/common/merge-review/merge-review.stories.d.ts.map +1 -0
  69. package/dist/src/presentation/web/components/common/merge-review/merge-review.stories.js +165 -0
  70. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  71. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +66 -2
  72. package/dist/src/presentation/web/components/features/control-center/control-center.stories.d.ts.map +1 -1
  73. package/dist/src/presentation/web/components/features/control-center/control-center.stories.js +15 -0
  74. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  75. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +37 -15
  76. package/dist/tsconfig.build.tsbuildinfo +1 -1
  77. package/package.json +1 -1
  78. package/web/.next/BUILD_ID +1 -1
  79. package/web/.next/build-manifest.json +2 -2
  80. package/web/.next/cache/.previewinfo +1 -1
  81. package/web/.next/cache/.rscinfo +1 -1
  82. package/web/.next/cache/.tsbuildinfo +1 -1
  83. package/web/.next/cache/config.json +3 -3
  84. package/web/.next/fallback-build-manifest.json +2 -2
  85. package/web/.next/prerender-manifest.json +3 -3
  86. package/web/.next/required-server-files.js +1 -1
  87. package/web/.next/required-server-files.json +1 -1
  88. package/web/.next/server/app/_global-error.html +2 -2
  89. package/web/.next/server/app/_global-error.rsc +1 -1
  90. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  91. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  92. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  93. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  94. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  95. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  96. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  97. package/web/.next/server/app/page/server-reference-manifest.json +52 -22
  98. package/web/.next/server/app/page.js +1 -1
  99. package/web/.next/server/app/page.js.nft.json +1 -1
  100. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  101. package/web/.next/server/app/version/page.js.nft.json +1 -1
  102. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  103. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +4 -4
  104. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
  105. package/web/.next/server/chunks/ssr/[root-of-the-server]__19d52794._.js +9 -0
  106. package/web/.next/server/chunks/ssr/[root-of-the-server]__19d52794._.js.map +1 -0
  107. package/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  108. package/web/.next/{standalone/src/presentation/web/.next/server/chunks/ssr/_3ac3601c._.js → server/chunks/ssr/_1fafa9db._.js} +2 -2
  109. package/web/.next/server/chunks/ssr/_1fafa9db._.js.map +1 -0
  110. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  111. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  112. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  113. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
  114. package/web/.next/server/pages/500.html +2 -2
  115. package/web/.next/server/server-reference-manifest.js +1 -1
  116. package/web/.next/server/server-reference-manifest.json +53 -23
  117. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  118. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  119. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  120. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  121. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  122. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  123. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  124. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  125. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  126. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  127. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  128. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  129. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  130. package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +52 -22
  131. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js +1 -1
  132. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
  133. package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
  134. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js.nft.json +1 -1
  135. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  136. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +4 -4
  137. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__19d52794._.js +9 -0
  138. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  139. package/web/.next/{server/chunks/ssr/_3ac3601c._.js → standalone/src/presentation/web/.next/server/chunks/ssr/_1fafa9db._.js} +2 -2
  140. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  141. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +1 -1
  142. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  143. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  144. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +53 -23
  145. package/web/.next/standalone/src/presentation/web/app/actions/create-feature.ts +5 -1
  146. package/web/.next/standalone/src/presentation/web/app/actions/get-merge-review-data.ts +64 -0
  147. package/web/.next/standalone/src/presentation/web/app/actions/get-workflow-defaults.ts +28 -0
  148. package/web/.next/standalone/src/presentation/web/app/page.tsx +1 -0
  149. package/web/.next/standalone/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.tsx +74 -1
  150. package/web/.next/standalone/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx +55 -5
  151. package/web/.next/standalone/src/presentation/web/components/common/feature-node/feature-node.stories.tsx +23 -1
  152. package/web/.next/standalone/src/presentation/web/components/common/feature-node/feature-node.tsx +6 -1
  153. package/web/.next/standalone/src/presentation/web/components/common/merge-review/index.ts +7 -0
  154. package/web/.next/standalone/src/presentation/web/components/common/merge-review/merge-review-config.ts +81 -0
  155. package/web/.next/standalone/src/presentation/web/components/common/merge-review/merge-review-drawer.tsx +34 -0
  156. package/web/.next/standalone/src/presentation/web/components/common/merge-review/merge-review.stories.tsx +240 -0
  157. package/web/.next/standalone/src/presentation/web/components/common/merge-review/merge-review.tsx +252 -0
  158. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +96 -1
  159. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center.stories.tsx +15 -0
  160. package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +47 -17
  161. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  162. package/web/.next/static/chunks/{9f8e71029fb04681.js → 0da7a9860fa6ae05.js} +2 -2
  163. package/web/.next/static/chunks/22f30ce91a3c79c1.js +1 -0
  164. package/web/.next/static/chunks/66378a6c055c4e67.js +10 -0
  165. package/web/.next/static/chunks/8668962d3e682263.css +2 -0
  166. package/web/.next/trace +1 -1
  167. package/web/.next/trace-build +1 -1
  168. package/web/.next/server/chunks/ssr/[root-of-the-server]__4e8ca89c._.js +0 -9
  169. package/web/.next/server/chunks/ssr/[root-of-the-server]__4e8ca89c._.js.map +0 -1
  170. package/web/.next/server/chunks/ssr/_3ac3601c._.js.map +0 -1
  171. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__4e8ca89c._.js +0 -9
  172. package/web/.next/static/chunks/83ff3b6bbee54bce.js +0 -1
  173. package/web/.next/static/chunks/b7b7abe79e05b229.css +0 -2
  174. package/web/.next/static/chunks/ecf44fa8a155c854.js +0 -10
  175. /package/web/.next/static/{nM-uXIiW9k_0Z_GM_4bI4 → 1kZFDMQ0i-Y8dy1KkHlzv}/_buildManifest.js +0 -0
  176. /package/web/.next/static/{nM-uXIiW9k_0Z_GM_4bI4 → 1kZFDMQ0i-Y8dy1KkHlzv}/_clientMiddlewareManifest.json +0 -0
  177. /package/web/.next/static/{nM-uXIiW9k_0Z_GM_4bI4 → 1kZFDMQ0i-Y8dy1KkHlzv}/_ssgManifest.js +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-review.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/merge-review/merge-review.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,gBAAgB,EAAoB,MAAM,uBAAuB,CAAC;AA0DhF,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAoB,EAAE,EAAE,gBAAgB,2CA4KhG"}
@@ -0,0 +1,36 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { CheckCircle2, ExternalLink, GitMerge, Loader2, XCircle, AlertTriangle, FileDiff, GitCommitHorizontal, GitBranch, ArrowRight, Layers, Send, } from 'lucide-react';
5
+ import { CiStatus } from '../../../../../../packages/core/src/domain/generated/output.js';
6
+ import { Badge } from '../../ui/badge.js';
7
+ import { Button } from '../../ui/button.js';
8
+ import { Input } from '../../ui/input.js';
9
+ function CiStatusBadge({ status }) {
10
+ switch (status) {
11
+ case CiStatus.Success:
12
+ return (_jsxs(Badge, { className: "border-transparent bg-green-50 text-green-700 hover:bg-green-50", children: [_jsx(CheckCircle2, { className: "mr-1 h-3.5 w-3.5" }), "Passing"] }));
13
+ case CiStatus.Pending:
14
+ return (_jsxs(Badge, { className: "border-transparent bg-yellow-50 text-yellow-700 hover:bg-yellow-50", children: [_jsx(Loader2, { className: "mr-1 h-3.5 w-3.5 animate-spin" }), "Pending"] }));
15
+ case CiStatus.Failure:
16
+ return (_jsxs(Badge, { className: "border-transparent bg-red-50 text-red-700 hover:bg-red-50", children: [_jsx(XCircle, { className: "mr-1 h-3.5 w-3.5" }), "Failing"] }));
17
+ }
18
+ }
19
+ function PhaseList({ phases }) {
20
+ return (_jsx("div", { className: "border-border rounded-lg border", children: _jsxs("div", { className: "px-4 py-3", children: [_jsxs("div", { className: "mb-3 flex items-center gap-2", children: [_jsx(Layers, { className: "text-muted-foreground h-4 w-4" }), _jsx("span", { className: "text-foreground text-xs font-semibold", children: "Implementation Phases" })] }), _jsx("ol", { className: "space-y-2", children: phases.map((phase, i) => (_jsxs("li", { className: "flex items-start gap-2.5", children: [_jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-emerald-100 text-[10px] font-bold text-emerald-700", children: i + 1 }), _jsxs("div", { className: "min-w-0", children: [_jsx("span", { className: "text-foreground text-xs font-medium", children: phase.name }), phase.description ? (_jsx("p", { className: "text-muted-foreground mt-0.5 text-[11px] leading-relaxed", children: phase.description })) : null] })] }, phase.id))) })] }) }));
21
+ }
22
+ export function MergeReview({ data, onApprove, onRefine, isProcessing = false }) {
23
+ const { pr, diffSummary, phases, branch, warning } = data;
24
+ const [chatInput, setChatInput] = useState('');
25
+ function handleSubmit(e) {
26
+ e.preventDefault();
27
+ const text = chatInput.trim();
28
+ if (!text)
29
+ return;
30
+ onRefine(text);
31
+ setChatInput('');
32
+ }
33
+ return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("div", { className: "flex-1 space-y-4 overflow-y-auto p-4", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "mt-1.5 h-2.5 w-2.5 shrink-0 rounded-full bg-emerald-500" }), _jsxs("div", { className: "flex-1", children: [_jsx("h2", { className: "text-foreground text-sm font-bold", children: "Merge Review" }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs leading-relaxed", children: pr
34
+ ? 'Review the pull request details and approve to merge.'
35
+ : 'Review the changes and approve to merge.' })] })] }), branch ? (_jsx("div", { className: "border-border rounded-lg border", children: _jsxs("div", { className: "flex items-center gap-2 px-4 py-3", children: [_jsx(GitBranch, { className: "text-muted-foreground h-4 w-4 shrink-0" }), _jsx(Badge, { variant: "secondary", className: "font-mono text-[11px]", children: branch.source }), _jsx(ArrowRight, { className: "text-muted-foreground h-3.5 w-3.5 shrink-0" }), _jsx(Badge, { variant: "outline", className: "font-mono text-[11px]", children: branch.target })] }) })) : null, pr ? (_jsx("div", { className: "border-border rounded-lg border", children: _jsxs("div", { className: "space-y-3 px-4 py-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("a", { href: pr.url, target: "_blank", rel: "noopener noreferrer", className: "text-primary flex items-center gap-1.5 text-sm font-semibold underline underline-offset-2", children: ["PR #", pr.number, _jsx(ExternalLink, { className: "h-3.5 w-3.5" })] }), _jsx(Badge, { variant: "outline", className: "text-xs", children: pr.status })] }), pr.ciStatus ? (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "CI Status" }), _jsx(CiStatusBadge, { status: pr.ciStatus })] })) : null, pr.commitHash ? (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Commit" }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(GitCommitHorizontal, { className: "text-muted-foreground h-3.5 w-3.5" }), _jsx("code", { className: "bg-muted text-foreground rounded-md px-1.5 py-0.5 font-mono text-[11px]", children: pr.commitHash.slice(0, 7) })] })] })) : null] }) })) : null, diffSummary ? (_jsx("div", { className: "border-border rounded-lg border", children: _jsxs("div", { className: "px-4 py-3", children: [_jsxs("div", { className: "mb-3 flex items-center gap-2", children: [_jsx(FileDiff, { className: "text-muted-foreground h-4 w-4" }), _jsx("span", { className: "text-foreground text-xs font-semibold", children: "Changes" })] }), _jsxs("div", { className: "grid grid-cols-4 gap-3", children: [_jsxs("div", { className: "text-center", children: [_jsx("div", { className: "text-foreground text-sm font-bold", children: diffSummary.filesChanged }), _jsx("div", { className: "text-muted-foreground text-[10px]", children: "files" })] }), _jsxs("div", { className: "text-center", children: [_jsxs("div", { className: "text-sm font-bold text-green-600", children: ["+", diffSummary.additions] }), _jsx("div", { className: "text-muted-foreground text-[10px]", children: "additions" })] }), _jsxs("div", { className: "text-center", children: [_jsxs("div", { className: "text-sm font-bold text-red-600", children: ["-", diffSummary.deletions] }), _jsx("div", { className: "text-muted-foreground text-[10px]", children: "deletions" })] }), _jsxs("div", { className: "text-center", children: [_jsx("div", { className: "text-foreground text-sm font-bold", children: diffSummary.commitCount }), _jsx("div", { className: "text-muted-foreground text-[10px]", children: "commits" })] })] })] }) })) : warning ? (_jsx("div", { className: "border-border rounded-lg border", children: _jsxs("div", { className: "flex items-center gap-2 px-4 py-3", children: [_jsx(AlertTriangle, { className: "text-muted-foreground h-4 w-4 shrink-0" }), _jsx("span", { className: "text-muted-foreground text-xs", children: warning })] }) })) : null, phases && phases.length > 0 ? _jsx(PhaseList, { phases: phases }) : null] }), _jsxs("div", { className: "border-border shrink-0 border-t", children: [_jsxs("form", { onSubmit: handleSubmit, className: "flex items-center gap-2 p-4 pb-2", children: [_jsx(Input, { type: "text", placeholder: "Ask AI to revise before merging...", "aria-label": "Ask AI to revise before merging", disabled: isProcessing, value: chatInput, onChange: (e) => setChatInput(e.target.value), className: "flex-1" }), _jsx(Button, { type: "submit", variant: "secondary", size: "icon", "aria-label": "Send", disabled: isProcessing, children: _jsx(Send, {}) })] }), _jsx("div", { className: "px-4 pt-2 pb-4", children: _jsxs(Button, { type: "button", className: "w-full", disabled: isProcessing, onClick: onApprove, "aria-label": "Approve Merge", children: [isProcessing ? (_jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" })) : (_jsx(GitMerge, { className: "mr-1.5 h-4 w-4" })), "Approve Merge"] }) })] })] }));
36
+ }
@@ -0,0 +1,32 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { MergeReview } from './merge-review.js';
3
+ import { MergeReviewDrawer } from './merge-review-drawer.js';
4
+ declare const meta: Meta<typeof MergeReview>;
5
+ export default meta;
6
+ type Story = StoryObj<typeof MergeReview>;
7
+ /** Default — all data present with CI success. */
8
+ export declare const Default: Story;
9
+ /** CI status is Pending — shows yellow badge with spinner. */
10
+ export declare const CIPending: Story;
11
+ /** CI status is Failure — shows red badge. */
12
+ export declare const CIFailure: Story;
13
+ /** Diff summary unavailable — shows warning message. */
14
+ export declare const NoDiffSummary: Story;
15
+ /** CI status undefined — CI section omitted entirely. */
16
+ export declare const NoCIStatus: Story;
17
+ /** Processing state — approve button disabled with spinner. */
18
+ export declare const Processing: Story;
19
+ /** No PR — merge without a pull request, shows branch direction and diff summary. */
20
+ export declare const NoPrWithDiff: Story;
21
+ /** No PR and no diff — bare merge approval with branch info. */
22
+ export declare const NoPrNoDiff: Story;
23
+ /** With implementation phases shown. */
24
+ export declare const WithPhases: Story;
25
+ type DrawerStory = StoryObj<typeof MergeReviewDrawer>;
26
+ /** Merge review drawer with full data. */
27
+ export declare const InDrawer: DrawerStory;
28
+ /** Drawer with delete button visible. */
29
+ export declare const WithDeleteButton: DrawerStory;
30
+ /** Drawer without PR — direct merge with diff summary and branch info. */
31
+ export declare const InDrawerNoPr: DrawerStory;
32
+ //# sourceMappingURL=merge-review.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-review.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/merge-review/merge-review.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAGvD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAsC1D,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,WAAW,CAclC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,WAAW,CAAC,CAAC;AAE1C,kDAAkD;AAClD,eAAO,MAAM,OAAO,EAAE,KAKrB,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,SAAS,EAAE,KAQvB,CAAC;AAEF,8CAA8C;AAC9C,eAAO,MAAM,SAAS,EAAE,KAQvB,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,aAAa,EAAE,KAS3B,CAAC;AAEF,yDAAyD;AACzD,eAAO,MAAM,UAAU,EAAE,KAQxB,CAAC;AAEF,+DAA+D;AAC/D,eAAO,MAAM,UAAU,EAAE,KAMxB,CAAC;AAEF,qFAAqF;AACrF,eAAO,MAAM,YAAY,EAAE,KAS1B,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,UAAU,EAAE,KAQxB,CAAC;AAEF,wCAAwC;AACxC,eAAO,MAAM,UAAU,EAAE,KAQxB,CAAC;AAIF,KAAK,WAAW,GAAG,QAAQ,CAAC,OAAO,iBAAiB,CAAC,CAAC;AA8BtD,0CAA0C;AAC1C,eAAO,MAAM,QAAQ,EAAE,WAYtB,CAAC;AAEF,yCAAyC;AACzC,eAAO,MAAM,gBAAgB,EAAE,WAa9B,CAAC;AAEF,0EAA0E;AAC1E,eAAO,MAAM,YAAY,EAAE,WAiB1B,CAAC"}
@@ -0,0 +1,165 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { fn } from '@storybook/test';
4
+ import { PrStatus, CiStatus } from '../../../../../../packages/core/src/domain/generated/output.js';
5
+ import { MergeReview } from './merge-review.js';
6
+ import { MergeReviewDrawer } from './merge-review-drawer.js';
7
+ const fullPr = {
8
+ url: 'https://github.com/shep-ai/cli/pull/42',
9
+ number: 42,
10
+ status: PrStatus.Open,
11
+ commitHash: 'a1b2c3d4e5f6789',
12
+ ciStatus: CiStatus.Success,
13
+ };
14
+ const samplePhases = [
15
+ { id: 'phase-1', name: 'Foundation & Config', description: 'Set up types and server action' },
16
+ { id: 'phase-2', name: 'Component Implementation', description: 'Build the merge review UI' },
17
+ { id: 'phase-3', name: 'Integration & Wiring', description: 'Connect to control center' },
18
+ ];
19
+ const sampleBranch = { source: 'feat/add-auth', target: 'main' };
20
+ const fullData = {
21
+ pr: fullPr,
22
+ branch: sampleBranch,
23
+ phases: samplePhases,
24
+ diffSummary: {
25
+ filesChanged: 12,
26
+ additions: 340,
27
+ deletions: 85,
28
+ commitCount: 5,
29
+ },
30
+ };
31
+ const defaultActions = {
32
+ onApprove: fn().mockName('onApprove'),
33
+ onRefine: fn().mockName('onRefine'),
34
+ };
35
+ /* ─── Standalone MergeReview ─── */
36
+ const meta = {
37
+ title: 'Common/MergeReview',
38
+ component: MergeReview,
39
+ tags: ['autodocs'],
40
+ parameters: {
41
+ layout: 'fullscreen',
42
+ },
43
+ decorators: [
44
+ (Story) => (_jsx("div", { style: { height: '600px', width: '400px', border: '1px solid var(--color-border)' }, children: _jsx(Story, {}) })),
45
+ ],
46
+ };
47
+ export default meta;
48
+ /** Default — all data present with CI success. */
49
+ export const Default = {
50
+ args: {
51
+ data: fullData,
52
+ ...defaultActions,
53
+ },
54
+ };
55
+ /** CI status is Pending — shows yellow badge with spinner. */
56
+ export const CIPending = {
57
+ args: {
58
+ data: {
59
+ ...fullData,
60
+ pr: { ...fullPr, ciStatus: CiStatus.Pending },
61
+ },
62
+ ...defaultActions,
63
+ },
64
+ };
65
+ /** CI status is Failure — shows red badge. */
66
+ export const CIFailure = {
67
+ args: {
68
+ data: {
69
+ ...fullData,
70
+ pr: { ...fullPr, ciStatus: CiStatus.Failure },
71
+ },
72
+ ...defaultActions,
73
+ },
74
+ };
75
+ /** Diff summary unavailable — shows warning message. */
76
+ export const NoDiffSummary = {
77
+ args: {
78
+ data: {
79
+ pr: fullPr,
80
+ branch: sampleBranch,
81
+ warning: 'Diff statistics unavailable — worktree may have been removed',
82
+ },
83
+ ...defaultActions,
84
+ },
85
+ };
86
+ /** CI status undefined — CI section omitted entirely. */
87
+ export const NoCIStatus = {
88
+ args: {
89
+ data: {
90
+ ...fullData,
91
+ pr: { ...fullPr, ciStatus: undefined },
92
+ },
93
+ ...defaultActions,
94
+ },
95
+ };
96
+ /** Processing state — approve button disabled with spinner. */
97
+ export const Processing = {
98
+ args: {
99
+ data: fullData,
100
+ ...defaultActions,
101
+ isProcessing: true,
102
+ },
103
+ };
104
+ /** No PR — merge without a pull request, shows branch direction and diff summary. */
105
+ export const NoPrWithDiff = {
106
+ args: {
107
+ data: {
108
+ branch: { source: 'feat/fix-login', target: 'main' },
109
+ phases: samplePhases,
110
+ diffSummary: fullData.diffSummary,
111
+ },
112
+ ...defaultActions,
113
+ },
114
+ };
115
+ /** No PR and no diff — bare merge approval with branch info. */
116
+ export const NoPrNoDiff = {
117
+ args: {
118
+ data: {
119
+ branch: { source: 'feat/fix-login', target: 'main' },
120
+ warning: 'No PR or diff data available',
121
+ },
122
+ ...defaultActions,
123
+ },
124
+ };
125
+ /** With implementation phases shown. */
126
+ export const WithPhases = {
127
+ args: {
128
+ data: {
129
+ ...fullData,
130
+ phases: samplePhases,
131
+ },
132
+ ...defaultActions,
133
+ },
134
+ };
135
+ const drawerMeta = {
136
+ title: 'Common/MergeReviewDrawer',
137
+ component: MergeReviewDrawer,
138
+ tags: ['autodocs'],
139
+ parameters: {
140
+ layout: 'fullscreen',
141
+ },
142
+ };
143
+ function DrawerTemplate(props) {
144
+ const [open, setOpen] = useState(true);
145
+ return (_jsxs("div", { style: { height: '100vh', background: '#f8fafc', padding: '2rem' }, children: [_jsx("button", { type: "button", onClick: () => setOpen(true), style: { padding: '8px 16px', border: '1px solid #ccc', borderRadius: '6px' }, children: "Open Drawer" }), _jsx(MergeReviewDrawer, { ...props, open: open, onClose: () => setOpen(false) })] }));
146
+ }
147
+ /** Merge review drawer with full data. */
148
+ export const InDrawer = {
149
+ ...drawerMeta,
150
+ render: () => (_jsx(DrawerTemplate, { featureName: "Add User Authentication", featureId: "FEAT-042", repositoryPath: "/home/user/projects/my-app", branch: "feat/add-auth", data: fullData, ...defaultActions })),
151
+ };
152
+ /** Drawer with delete button visible. */
153
+ export const WithDeleteButton = {
154
+ ...drawerMeta,
155
+ render: () => (_jsx(DrawerTemplate, { featureName: "Add User Authentication", featureId: "FEAT-042", repositoryPath: "/home/user/projects/my-app", branch: "feat/add-auth", data: fullData, ...defaultActions, onDelete: fn().mockName('onDelete') })),
156
+ };
157
+ /** Drawer without PR — direct merge with diff summary and branch info. */
158
+ export const InDrawerNoPr = {
159
+ ...drawerMeta,
160
+ render: () => (_jsx(DrawerTemplate, { featureName: "Fix Login Bug", featureId: "FEAT-099", repositoryPath: "/home/user/projects/my-app", branch: "feat/fix-login", data: {
161
+ branch: { source: 'feat/fix-login', target: 'main' },
162
+ phases: samplePhases,
163
+ diffSummary: fullData.diffSummary,
164
+ }, ...defaultActions, onDelete: fn().mockName('onDelete') })),
165
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"control-center-inner.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/control-center-inner.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAO1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAS5E,UAAU,uBAAuB;IAC/B,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,YAAY,EAAE,IAAI,EAAE,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,uBAAuB,2CA+PzF"}
1
+ {"version":3,"file":"control-center-inner.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/control-center-inner.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAW1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAU5E,UAAU,uBAAuB;IAC/B,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,YAAY,EAAE,IAAI,EAAE,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,uBAAuB,2CAyVzF"}
@@ -5,15 +5,27 @@ import { toast } from 'sonner';
5
5
  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
+ import { getWorkflowDefaults } from '../../../app/actions/get-workflow-defaults.js';
9
+ import { getMergeReviewData } from '../../../app/actions/get-merge-review-data.js';
8
10
  import { FeaturesCanvas } from '../../features/features-canvas/index.js';
9
11
  import { FeatureDrawer, FeatureCreateDrawer } from '../../common/index.js';
10
12
  import { PrdQuestionnaireDrawer } from '../../common/prd-questionnaire/index.js';
11
13
  import { TechDecisionsDrawer } from '../../common/tech-decisions-review/index.js';
14
+ import { MergeReviewDrawer } from '../../common/merge-review/index.js';
12
15
  import { NotificationPermissionBanner } from '../../common/notification-permission-banner/index.js';
13
16
  import { ControlCenterEmptyState } from './control-center-empty-state.js';
14
17
  import { useControlCenterState } from './use-control-center-state.js';
15
18
  export function ControlCenterInner({ initialNodes, initialEdges }) {
16
19
  const { nodes, edges, selectedNode, isCreateDrawerOpen, isDeleting, pendingRepositoryPath, onNodesChange, handleConnect, handleAddFeature, handleAddFeatureToRepo, handleAddFeatureToFeature, handleAddRepository, handleNodeClick, clearSelection, handleCreateFeatureSubmit, handleDeleteFeature, handleDeleteRepository, closeCreateDrawer, } = useControlCenterState(initialNodes, initialEdges);
20
+ // Workflow defaults for the create-feature drawer
21
+ const [workflowDefaults, setWorkflowDefaults] = useState();
22
+ useEffect(() => {
23
+ getWorkflowDefaults()
24
+ .then(setWorkflowDefaults)
25
+ .catch(() => {
26
+ // Settings unavailable — drawer falls back to all-false defaults
27
+ });
28
+ }, []);
17
29
  // PRD questionnaire drawer state
18
30
  const [prdSelections, setPrdSelections] = useState({});
19
31
  const [isPrdProcessing, setIsPrdProcessing] = useState(false);
@@ -22,8 +34,12 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
22
34
  // Tech decisions drawer state
23
35
  const [techDecisionsData, setTechDecisionsData] = useState(null);
24
36
  const [isLoadingTechDecisions, setIsLoadingTechDecisions] = useState(false);
37
+ // Merge review drawer state
38
+ const [mergeReviewData, setMergeReviewData] = useState(null);
39
+ const [isLoadingMergeReview, setIsLoadingMergeReview] = useState(false);
25
40
  const showPrdDrawer = selectedNode?.lifecycle === 'requirements' && selectedNode?.state === 'action-required';
26
41
  const showTechDecisionsDrawer = selectedNode?.lifecycle === 'implementation' && selectedNode?.state === 'action-required';
42
+ const showMergeReviewDrawer = selectedNode?.lifecycle === 'review' && selectedNode?.state === 'action-required';
27
43
  const handlePrdSelect = useCallback((questionId, optionId) => {
28
44
  setPrdSelections((prev) => ({ ...prev, [questionId]: optionId }));
29
45
  }, []);
@@ -65,6 +81,24 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
65
81
  toast.success('Plan approved — agent resuming');
66
82
  clearSelection();
67
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
+ }, []);
68
102
  // Fetch questionnaire data and reset selections when a different feature is selected
69
103
  const prdFeatureId = showPrdDrawer ? selectedNode?.featureId : null;
70
104
  useEffect(() => {
@@ -138,6 +172,36 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
138
172
  cancelled = true;
139
173
  };
140
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]);
141
205
  const hasRepositories = nodes.some((n) => n.type === 'repositoryNode');
142
206
  // Listen for global "open create drawer" events from the sidebar
143
207
  useEffect(() => {
@@ -146,7 +210,7 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
146
210
  return () => window.removeEventListener('shep:open-create-drawer', handler);
147
211
  }, [handleAddFeature]);
148
212
  if (!hasRepositories) {
149
- return (_jsxs(_Fragment, { children: [_jsx(NotificationPermissionBanner, {}), _jsx(ControlCenterEmptyState, { onRepositorySelect: handleAddRepository }), _jsx(FeatureCreateDrawer, { open: isCreateDrawerOpen, onClose: closeCreateDrawer, onSubmit: handleCreateFeatureSubmit, repositoryPath: pendingRepositoryPath })] }));
213
+ return (_jsxs(_Fragment, { children: [_jsx(NotificationPermissionBanner, {}), _jsx(ControlCenterEmptyState, { onRepositorySelect: handleAddRepository }), _jsx(FeatureCreateDrawer, { open: isCreateDrawerOpen, onClose: closeCreateDrawer, onSubmit: handleCreateFeatureSubmit, repositoryPath: pendingRepositoryPath, workflowDefaults: workflowDefaults })] }));
150
214
  }
151
- 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 })] }));
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 })] }));
152
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]);