@shepai/cli 1.48.0 → 1.49.1

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 (175) hide show
  1. package/apis/json-schema/RejectionFeedbackEntry.yaml +4 -1
  2. package/dist/packages/core/src/application/use-cases/agents/reject-agent-run.use-case.d.ts.map +1 -1
  3. package/dist/packages/core/src/application/use-cases/agents/reject-agent-run.use-case.js +3 -0
  4. package/dist/packages/core/src/domain/generated/output.d.ts +5 -1
  5. package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
  6. package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
  7. package/dist/packages/core/src/infrastructure/di/container.js +3 -0
  8. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.js +1 -1
  9. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge.node.d.ts.map +1 -1
  10. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge.node.js +65 -36
  11. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/merge-prompts.d.ts.map +1 -1
  12. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/merge-prompts.js +34 -1
  13. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/plan.prompt.d.ts.map +1 -1
  14. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/plan.prompt.js +30 -0
  15. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/requirements.prompt.d.ts.map +1 -1
  16. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/requirements.prompt.js +8 -4
  17. package/dist/src/presentation/cli/commands/feat/show.command.d.ts +6 -0
  18. package/dist/src/presentation/cli/commands/feat/show.command.d.ts.map +1 -1
  19. package/dist/src/presentation/cli/commands/feat/show.command.js +52 -14
  20. package/dist/src/presentation/web/app/actions/approve-feature.d.ts +2 -1
  21. package/dist/src/presentation/web/app/actions/approve-feature.d.ts.map +1 -1
  22. package/dist/src/presentation/web/app/actions/approve-feature.js +2 -2
  23. package/dist/src/presentation/web/app/actions/reject-feature.d.ts +7 -0
  24. package/dist/src/presentation/web/app/actions/reject-feature.d.ts.map +1 -0
  25. package/dist/src/presentation/web/app/actions/reject-feature.js +34 -0
  26. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar-config.d.ts +24 -0
  27. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar-config.d.ts.map +1 -0
  28. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar-config.js +1 -0
  29. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar.d.ts +3 -0
  30. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar.d.ts.map +1 -0
  31. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar.js +12 -0
  32. package/dist/src/presentation/web/components/common/drawer-action-bar/index.d.ts +3 -0
  33. package/dist/src/presentation/web/components/common/drawer-action-bar/index.d.ts.map +1 -0
  34. package/dist/src/presentation/web/components/common/drawer-action-bar/index.js +1 -0
  35. package/dist/src/presentation/web/components/common/drawer-revision-input/drawer-revision-input-config.d.ts +11 -0
  36. package/dist/src/presentation/web/components/common/drawer-revision-input/drawer-revision-input-config.d.ts.map +1 -0
  37. package/dist/src/presentation/web/components/common/drawer-revision-input/drawer-revision-input-config.js +1 -0
  38. package/dist/src/presentation/web/components/common/drawer-revision-input/drawer-revision-input.d.ts +3 -0
  39. package/dist/src/presentation/web/components/common/drawer-revision-input/drawer-revision-input.d.ts.map +1 -0
  40. package/dist/src/presentation/web/components/common/drawer-revision-input/drawer-revision-input.js +18 -0
  41. package/dist/src/presentation/web/components/common/drawer-revision-input/index.d.ts +3 -0
  42. package/dist/src/presentation/web/components/common/drawer-revision-input/index.d.ts.map +1 -0
  43. package/dist/src/presentation/web/components/common/drawer-revision-input/index.js +1 -0
  44. package/dist/src/presentation/web/components/common/merge-review/merge-review-config.d.ts +4 -2
  45. package/dist/src/presentation/web/components/common/merge-review/merge-review-config.d.ts.map +1 -1
  46. package/dist/src/presentation/web/components/common/merge-review/merge-review.d.ts +1 -1
  47. package/dist/src/presentation/web/components/common/merge-review/merge-review.d.ts.map +1 -1
  48. package/dist/src/presentation/web/components/common/merge-review/merge-review.js +4 -15
  49. package/dist/src/presentation/web/components/common/merge-review/merge-review.stories.js +1 -1
  50. package/dist/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire-config.d.ts +4 -2
  51. package/dist/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire-config.d.ts.map +1 -1
  52. package/dist/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire.d.ts +1 -1
  53. package/dist/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire.d.ts.map +1 -1
  54. package/dist/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire.js +6 -15
  55. package/dist/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire.stories.d.ts +4 -0
  56. package/dist/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire.stories.d.ts.map +1 -1
  57. package/dist/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire.stories.js +24 -3
  58. package/dist/src/presentation/web/components/common/reject-feedback-dialog/index.d.ts +3 -0
  59. package/dist/src/presentation/web/components/common/reject-feedback-dialog/index.d.ts.map +1 -0
  60. package/dist/src/presentation/web/components/common/reject-feedback-dialog/index.js +1 -0
  61. package/dist/src/presentation/web/components/common/reject-feedback-dialog/reject-feedback-dialog-config.d.ts +9 -0
  62. package/dist/src/presentation/web/components/common/reject-feedback-dialog/reject-feedback-dialog-config.d.ts.map +1 -0
  63. package/dist/src/presentation/web/components/common/reject-feedback-dialog/reject-feedback-dialog-config.js +1 -0
  64. package/dist/src/presentation/web/components/common/reject-feedback-dialog/reject-feedback-dialog.d.ts +3 -0
  65. package/dist/src/presentation/web/components/common/reject-feedback-dialog/reject-feedback-dialog.d.ts.map +1 -0
  66. package/dist/src/presentation/web/components/common/reject-feedback-dialog/reject-feedback-dialog.js +19 -0
  67. package/dist/src/presentation/web/components/common/reject-feedback-dialog/reject-feedback-dialog.stories.d.ts +14 -0
  68. package/dist/src/presentation/web/components/common/reject-feedback-dialog/reject-feedback-dialog.stories.d.ts.map +1 -0
  69. package/dist/src/presentation/web/components/common/reject-feedback-dialog/reject-feedback-dialog.stories.js +46 -0
  70. package/dist/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review-config.d.ts +4 -2
  71. package/dist/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review-config.d.ts.map +1 -1
  72. package/dist/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.d.ts +1 -1
  73. package/dist/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.d.ts.map +1 -1
  74. package/dist/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.js +4 -14
  75. package/dist/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.stories.d.ts +4 -0
  76. package/dist/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.stories.d.ts.map +1 -1
  77. package/dist/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.stories.js +24 -6
  78. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  79. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +48 -33
  80. package/dist/tsconfig.build.tsbuildinfo +1 -1
  81. package/package.json +1 -1
  82. package/web/.next/BUILD_ID +1 -1
  83. package/web/.next/build-manifest.json +2 -2
  84. package/web/.next/cache/.previewinfo +1 -1
  85. package/web/.next/cache/.rscinfo +1 -1
  86. package/web/.next/cache/.tsbuildinfo +1 -1
  87. package/web/.next/cache/config.json +3 -3
  88. package/web/.next/fallback-build-manifest.json +2 -2
  89. package/web/.next/prerender-manifest.json +3 -3
  90. package/web/.next/required-server-files.js +1 -1
  91. package/web/.next/required-server-files.json +1 -1
  92. package/web/.next/server/app/_global-error.html +2 -2
  93. package/web/.next/server/app/_global-error.rsc +1 -1
  94. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  95. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  96. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  97. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  98. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  99. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  100. package/web/.next/server/app/page/server-reference-manifest.json +41 -26
  101. package/web/.next/server/app/page.js +2 -1
  102. package/web/.next/server/app/page.js.nft.json +1 -1
  103. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  104. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  105. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +4 -4
  106. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
  107. package/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  108. package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +9 -0
  109. package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js.map +1 -0
  110. package/web/.next/server/chunks/ssr/[root-of-the-server]__d8fbe735._.js +3 -0
  111. package/web/.next/server/chunks/ssr/[root-of-the-server]__d8fbe735._.js.map +1 -0
  112. package/web/.next/server/chunks/ssr/_1fafa9db._.js.map +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 +42 -27
  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_client-reference-manifest.js +1 -1
  129. package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +41 -26
  130. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js +2 -1
  131. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
  132. package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
  133. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  134. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +4 -4
  135. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  136. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +9 -0
  137. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__d8fbe735._.js +3 -0
  138. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  139. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  140. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +42 -27
  141. package/web/.next/standalone/src/presentation/web/app/actions/approve-feature.ts +4 -2
  142. package/web/.next/standalone/src/presentation/web/app/actions/reject-feature.ts +52 -0
  143. package/web/.next/standalone/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar-config.ts +24 -0
  144. package/web/.next/standalone/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar.tsx +64 -0
  145. package/web/.next/standalone/src/presentation/web/components/common/drawer-action-bar/index.ts +2 -0
  146. package/web/.next/standalone/src/presentation/web/components/common/drawer-revision-input/drawer-revision-input-config.ts +10 -0
  147. package/web/.next/standalone/src/presentation/web/components/common/drawer-revision-input/drawer-revision-input.tsx +41 -0
  148. package/web/.next/standalone/src/presentation/web/components/common/drawer-revision-input/index.ts +2 -0
  149. package/web/.next/standalone/src/presentation/web/components/common/merge-review/merge-review-config.ts +4 -2
  150. package/web/.next/standalone/src/presentation/web/components/common/merge-review/merge-review.stories.tsx +1 -1
  151. package/web/.next/standalone/src/presentation/web/components/common/merge-review/merge-review.tsx +24 -53
  152. package/web/.next/standalone/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire-config.ts +4 -2
  153. package/web/.next/standalone/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire.stories.tsx +36 -8
  154. package/web/.next/standalone/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire.tsx +16 -47
  155. package/web/.next/standalone/src/presentation/web/components/common/reject-feedback-dialog/index.ts +2 -0
  156. package/web/.next/standalone/src/presentation/web/components/common/reject-feedback-dialog/reject-feedback-dialog-config.ts +8 -0
  157. package/web/.next/standalone/src/presentation/web/components/common/reject-feedback-dialog/reject-feedback-dialog.stories.tsx +63 -0
  158. package/web/.next/standalone/src/presentation/web/components/common/reject-feedback-dialog/reject-feedback-dialog.tsx +78 -0
  159. package/web/.next/standalone/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review-config.ts +4 -2
  160. package/web/.next/standalone/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.stories.tsx +27 -6
  161. package/web/.next/standalone/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.tsx +14 -42
  162. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +94 -49
  163. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  164. package/web/.next/static/chunks/25cab79117866d51.js +10 -0
  165. package/web/.next/static/chunks/59b8bd47023e0249.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]__19d52794._.js +0 -9
  169. package/web/.next/server/chunks/ssr/[root-of-the-server]__19d52794._.js.map +0 -1
  170. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__19d52794._.js +0 -9
  171. package/web/.next/static/chunks/66378a6c055c4e67.js +0 -10
  172. package/web/.next/static/chunks/8668962d3e682263.css +0 -2
  173. /package/web/.next/static/{1kZFDMQ0i-Y8dy1KkHlzv → y92RZZcCAiudDG54kYSQf}/_buildManifest.js +0 -0
  174. /package/web/.next/static/{1kZFDMQ0i-Y8dy1KkHlzv → y92RZZcCAiudDG54kYSQf}/_clientMiddlewareManifest.json +0 -0
  175. /package/web/.next/static/{1kZFDMQ0i-Y8dy1KkHlzv → y92RZZcCAiudDG54kYSQf}/_ssgManifest.js +0 -0
@@ -1,6 +1,5 @@
1
1
  'use client';
2
2
 
3
- import { useState } from 'react';
4
3
  import {
5
4
  CheckCircle2,
6
5
  ExternalLink,
@@ -13,12 +12,10 @@ import {
13
12
  GitBranch,
14
13
  ArrowRight,
15
14
  Layers,
16
- Send,
17
15
  } from 'lucide-react';
18
16
  import { CiStatus } from '@shepai/core/domain/generated/output';
19
17
  import { Badge } from '@/components/ui/badge';
20
- import { Button } from '@/components/ui/button';
21
- import { Input } from '@/components/ui/input';
18
+ import { DrawerActionBar } from '@/components/common/drawer-action-bar';
22
19
  import type { MergeReviewProps, MergeReviewPhase } from './merge-review-config';
23
20
 
24
21
  function CiStatusBadge({ status }: { status: CiStatus }) {
@@ -77,17 +74,14 @@ function PhaseList({ phases }: { phases: MergeReviewPhase[] }) {
77
74
  );
78
75
  }
79
76
 
80
- export function MergeReview({ data, onApprove, onRefine, isProcessing = false }: MergeReviewProps) {
77
+ export function MergeReview({
78
+ data,
79
+ onApprove,
80
+ onReject,
81
+ isProcessing = false,
82
+ isRejecting = false,
83
+ }: MergeReviewProps) {
81
84
  const { pr, diffSummary, phases, branch, warning } = data;
82
- const [chatInput, setChatInput] = useState('');
83
-
84
- function handleSubmit(e: { preventDefault: () => void }) {
85
- e.preventDefault();
86
- const text = chatInput.trim();
87
- if (!text) return;
88
- onRefine(text);
89
- setChatInput('');
90
- }
91
85
 
92
86
  return (
93
87
  <div className="flex h-full flex-col">
@@ -208,45 +202,22 @@ export function MergeReview({ data, onApprove, onRefine, isProcessing = false }:
208
202
  {phases && phases.length > 0 ? <PhaseList phases={phases} /> : null}
209
203
  </div>
210
204
 
211
- {/* Action bar: chat + approve */}
212
- <div className="border-border shrink-0 border-t">
213
- <form onSubmit={handleSubmit} className="flex items-center gap-2 p-4 pb-2">
214
- <Input
215
- type="text"
216
- placeholder="Ask AI to revise before merging..."
217
- aria-label="Ask AI to revise before merging"
218
- disabled={isProcessing}
219
- value={chatInput}
220
- onChange={(e) => setChatInput(e.target.value)}
221
- className="flex-1"
222
- />
223
- <Button
224
- type="submit"
225
- variant="secondary"
226
- size="icon"
227
- aria-label="Send"
228
- disabled={isProcessing}
229
- >
230
- <Send />
231
- </Button>
232
- </form>
233
- <div className="px-4 pt-2 pb-4">
234
- <Button
235
- type="button"
236
- className="w-full"
237
- disabled={isProcessing}
238
- onClick={onApprove}
239
- aria-label="Approve Merge"
240
- >
241
- {isProcessing ? (
242
- <Loader2 className="mr-1.5 h-4 w-4 animate-spin" />
243
- ) : (
244
- <GitMerge className="mr-1.5 h-4 w-4" />
245
- )}
246
- Approve Merge
247
- </Button>
248
- </div>
249
- </div>
205
+ <DrawerActionBar
206
+ onReject={onReject}
207
+ onApprove={onApprove}
208
+ approveLabel="Approve Merge"
209
+ approveIcon={
210
+ isProcessing ? (
211
+ <Loader2 className="mr-1.5 h-4 w-4 animate-spin" />
212
+ ) : (
213
+ <GitMerge className="mr-1.5 h-4 w-4" />
214
+ )
215
+ }
216
+ revisionPlaceholder="Ask AI to revise before merging..."
217
+ rejectDialogTitle="Reject Merge"
218
+ isProcessing={isProcessing}
219
+ isRejecting={isRejecting}
220
+ />
250
221
  </div>
251
222
  );
252
223
  }
@@ -14,12 +14,14 @@ export interface PrdQuestionnaireProps {
14
14
  selections: Record<string, string>;
15
15
  /** Selection change callback */
16
16
  onSelect: (questionId: string, optionId: string) => void;
17
- /** Chat refinement submit callback */
18
- onRefine: (text: string) => void;
19
17
  /** Finalize requirements callback */
20
18
  onApprove: (actionId: string) => void;
19
+ /** Reject requirements callback — opens feedback dialog when provided; also used for inline text rejection */
20
+ onReject?: (feedback: string) => void;
21
21
  /** Controls disabled/animated state during refinement */
22
22
  isProcessing?: boolean;
23
+ /** Whether a reject operation is in flight */
24
+ isRejecting?: boolean;
23
25
  /** Show the goal/context header (default: false) */
24
26
  showHeader?: boolean;
25
27
  }
@@ -146,10 +146,9 @@ const mockFinalAction = {
146
146
  function InteractiveQuestionnaire({
147
147
  selections: initialSelections = {},
148
148
  ...props
149
- }: Omit<
150
- React.ComponentProps<typeof PrdQuestionnaire>,
151
- 'onSelect' | 'onRefine' | 'onApprove' | 'selections'
152
- > & { selections?: Record<string, string> }) {
149
+ }: Omit<React.ComponentProps<typeof PrdQuestionnaire>, 'onSelect' | 'onApprove' | 'selections'> & {
150
+ selections?: Record<string, string>;
151
+ }) {
153
152
  const [selections, setSelections] = useState<Record<string, string>>(initialSelections);
154
153
 
155
154
  return (
@@ -157,7 +156,6 @@ function InteractiveQuestionnaire({
157
156
  {...props}
158
157
  selections={selections}
159
158
  onSelect={(qId, optId) => setSelections((prev) => ({ ...prev, [qId]: optId }))}
160
- onRefine={fn().mockName('onRefine')}
161
159
  onApprove={fn().mockName('onApprove')}
162
160
  />
163
161
  );
@@ -174,7 +172,6 @@ const meta: Meta<typeof PrdQuestionnaire> = {
174
172
  },
175
173
  argTypes: {
176
174
  onSelect: { action: 'onSelect' },
177
- onRefine: { action: 'onRefine' },
178
175
  onApprove: { action: 'onApprove' },
179
176
  },
180
177
  decorators: [
@@ -296,7 +293,7 @@ function DrawerTemplate({
296
293
  ...props
297
294
  }: Omit<
298
295
  React.ComponentProps<typeof PrdQuestionnaireDrawer>,
299
- 'open' | 'onClose' | 'onSelect' | 'onRefine' | 'onApprove' | 'selections'
296
+ 'open' | 'onClose' | 'onSelect' | 'onApprove' | 'selections'
300
297
  > & { selections?: Record<string, string> }) {
301
298
  const [open, setOpen] = useState(true);
302
299
  const [selections, setSelections] = useState<Record<string, string>>(initialSelections);
@@ -316,7 +313,6 @@ function DrawerTemplate({
316
313
  onClose={() => setOpen(false)}
317
314
  selections={selections}
318
315
  onSelect={(qId, optId) => setSelections((prev) => ({ ...prev, [qId]: optId }))}
319
- onRefine={fn().mockName('onRefine')}
320
316
  onApprove={fn().mockName('onApprove')}
321
317
  />
322
318
  </div>
@@ -383,3 +379,35 @@ export const DeletingState: DrawerStory = {
383
379
  />
384
380
  ),
385
381
  };
382
+
383
+ /* ─── Reject Variants ─── */
384
+
385
+ const allAnsweredSelections = {
386
+ problem: 'user_pain',
387
+ priority: 'p1',
388
+ success: 'adoption',
389
+ timeline: 'sprint',
390
+ scope: 'mvp',
391
+ stakeholders: 'end_users',
392
+ };
393
+
394
+ /** All answered with reject button visible on last step. */
395
+ export const WithRejectButton: Story = {
396
+ render: () => (
397
+ <InteractiveQuestionnaire
398
+ data={mockData}
399
+ selections={allAnsweredSelections}
400
+ onReject={fn().mockName('onReject')}
401
+ />
402
+ ),
403
+ };
404
+
405
+ /** Rejecting state — reject button disabled with spinner while reject action is in flight. */
406
+ export const RejectingState: Story = {
407
+ args: {
408
+ data: mockData,
409
+ selections: allAnsweredSelections,
410
+ onReject: fn().mockName('onReject'),
411
+ isRejecting: true,
412
+ },
413
+ };
@@ -1,25 +1,25 @@
1
1
  'use client';
2
2
 
3
- import { Send, Check, ChevronLeft, ChevronRight } from 'lucide-react';
3
+ import { Check, ChevronLeft, ChevronRight } from 'lucide-react';
4
4
  import { useCallback, useMemo, useState } from 'react';
5
5
  import { cn } from '@/lib/utils';
6
6
  import { Button } from '@/components/ui/button';
7
- import { Input } from '@/components/ui/input';
8
7
  import { Badge } from '@/components/ui/badge';
8
+ import { DrawerActionBar } from '@/components/common/drawer-action-bar';
9
9
  import type { PrdQuestionnaireProps } from './prd-questionnaire-config';
10
10
 
11
11
  export function PrdQuestionnaire({
12
12
  data,
13
13
  selections,
14
14
  onSelect,
15
- onRefine,
16
15
  onApprove,
16
+ onReject,
17
17
  isProcessing = false,
18
+ isRejecting = false,
18
19
  showHeader = false,
19
20
  }: PrdQuestionnaireProps) {
20
21
  const { question, context, questions, finalAction } = data;
21
22
  const [currentStep, setCurrentStep] = useState(0);
22
- const [chatInput, setChatInput] = useState('');
23
23
 
24
24
  const total = questions.length;
25
25
  const isFirstStep = currentStep === 0;
@@ -39,14 +39,6 @@ export function PrdQuestionnaire({
39
39
  [onSelect, isLastStep]
40
40
  );
41
41
 
42
- function handleSubmit(e: { preventDefault: () => void }) {
43
- e.preventDefault();
44
- const text = chatInput.trim();
45
- if (!text) return;
46
- onRefine(text);
47
- setChatInput('');
48
- }
49
-
50
42
  if (total === 0) return null;
51
43
 
52
44
  return (
@@ -143,17 +135,7 @@ export function PrdQuestionnaire({
143
135
  Previous
144
136
  </Button>
145
137
 
146
- {isLastStep ? (
147
- <Button
148
- type="button"
149
- size="sm"
150
- disabled={isProcessing || answeredCount < total}
151
- onClick={() => onApprove(finalAction.id)}
152
- >
153
- <Check className="mr-1 h-4 w-4" />
154
- {finalAction.label}
155
- </Button>
156
- ) : (
138
+ {!isLastStep ? (
157
139
  <Button
158
140
  type="button"
159
141
  variant="ghost"
@@ -164,12 +146,19 @@ export function PrdQuestionnaire({
164
146
  {selections[currentQuestion.id] ? 'Next' : 'Skip'}
165
147
  <ChevronRight className="ml-1 h-4 w-4" />
166
148
  </Button>
167
- )}
149
+ ) : null}
168
150
  </div>
169
151
  </div>
170
152
 
171
- {/* Action Bar */}
172
- <div className="border-border bg-background shrink-0 border-t">
153
+ <DrawerActionBar
154
+ onReject={onReject}
155
+ onApprove={() => onApprove(finalAction.id)}
156
+ approveLabel={finalAction.label}
157
+ approveIcon={<Check className="mr-1.5 h-4 w-4" />}
158
+ revisionPlaceholder="Ask AI to refine requirements..."
159
+ isProcessing={isProcessing}
160
+ isRejecting={isRejecting}
161
+ >
173
162
  <div
174
163
  className={cn(
175
164
  'bg-muted h-1.5 overflow-hidden',
@@ -190,27 +179,7 @@ export function PrdQuestionnaire({
190
179
  />
191
180
  )}
192
181
  </div>
193
- <form onSubmit={handleSubmit} className="flex items-center gap-2 p-4">
194
- <Input
195
- type="text"
196
- placeholder="Ask AI to refine requirements..."
197
- aria-label="Ask AI to refine requirements"
198
- disabled={isProcessing}
199
- value={chatInput}
200
- onChange={(e) => setChatInput(e.target.value)}
201
- className="flex-1"
202
- />
203
- <Button
204
- type="submit"
205
- variant="secondary"
206
- size="icon"
207
- aria-label="Send"
208
- disabled={isProcessing}
209
- >
210
- <Send />
211
- </Button>
212
- </form>
213
- </div>
182
+ </DrawerActionBar>
214
183
  </div>
215
184
  );
216
185
  }
@@ -0,0 +1,2 @@
1
+ export { RejectFeedbackDialog } from './reject-feedback-dialog';
2
+ export type { RejectFeedbackDialogProps } from './reject-feedback-dialog-config';
@@ -0,0 +1,8 @@
1
+ export interface RejectFeedbackDialogProps {
2
+ open: boolean;
3
+ onOpenChange: (open: boolean) => void;
4
+ onConfirm: (feedback: string) => void;
5
+ isSubmitting: boolean;
6
+ title?: string;
7
+ description?: string;
8
+ }
@@ -0,0 +1,63 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { fn } from '@storybook/test';
3
+ import { RejectFeedbackDialog } from './reject-feedback-dialog';
4
+
5
+ const meta: Meta<typeof RejectFeedbackDialog> = {
6
+ title: 'Composed/RejectFeedbackDialog',
7
+ component: RejectFeedbackDialog,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ layout: 'centered',
11
+ },
12
+ args: {
13
+ open: true,
14
+ onOpenChange: fn().mockName('onOpenChange'),
15
+ onConfirm: fn().mockName('onConfirm'),
16
+ isSubmitting: false,
17
+ },
18
+ };
19
+
20
+ export default meta;
21
+ type Story = StoryObj<typeof RejectFeedbackDialog>;
22
+
23
+ /** Default — dialog open with empty feedback; confirm button disabled. */
24
+ export const Default: Story = {};
25
+
26
+ /** With feedback entered — confirm button enabled. */
27
+ export const WithFeedback: Story = {
28
+ play: async ({ canvasElement }) => {
29
+ // Type feedback into the textarea to show enabled state
30
+ const textarea = canvasElement.ownerDocument.querySelector(
31
+ 'textarea[aria-label="Rejection feedback"]'
32
+ ) as HTMLTextAreaElement | null;
33
+ if (textarea) {
34
+ // Simulate React-compatible input
35
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
36
+ window.HTMLTextAreaElement.prototype,
37
+ 'value'
38
+ )?.set;
39
+ nativeInputValueSetter?.call(
40
+ textarea,
41
+ 'The requirements are missing error handling for edge cases. Please add validation for empty inputs.'
42
+ );
43
+ textarea.dispatchEvent(new Event('input', { bubbles: true }));
44
+ textarea.dispatchEvent(new Event('change', { bubbles: true }));
45
+ }
46
+ },
47
+ };
48
+
49
+ /** Submitting state — spinner shown, buttons disabled. */
50
+ export const Submitting: Story = {
51
+ args: {
52
+ isSubmitting: true,
53
+ },
54
+ };
55
+
56
+ /** Custom title and description for tech decisions context. */
57
+ export const CustomTitle: Story = {
58
+ args: {
59
+ title: 'Reject Plan',
60
+ description:
61
+ 'Provide feedback on the technical plan for the agent to revise. Be specific about which decisions need changes.',
62
+ },
63
+ };
@@ -0,0 +1,78 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { Loader2 } from 'lucide-react';
5
+ import {
6
+ AlertDialog,
7
+ AlertDialogAction,
8
+ AlertDialogCancel,
9
+ AlertDialogContent,
10
+ AlertDialogDescription,
11
+ AlertDialogFooter,
12
+ AlertDialogHeader,
13
+ AlertDialogTitle,
14
+ } from '@/components/ui/alert-dialog';
15
+ import { Textarea } from '@/components/ui/textarea';
16
+ import type { RejectFeedbackDialogProps } from './reject-feedback-dialog-config';
17
+
18
+ const DEFAULT_TITLE = 'Reject Requirements';
19
+ const DEFAULT_DESCRIPTION =
20
+ 'Provide feedback for the agent to address in the next iteration. Feedback is required.';
21
+
22
+ export function RejectFeedbackDialog({
23
+ open,
24
+ onOpenChange,
25
+ onConfirm,
26
+ isSubmitting,
27
+ title = DEFAULT_TITLE,
28
+ description = DEFAULT_DESCRIPTION,
29
+ }: RejectFeedbackDialogProps) {
30
+ const [feedback, setFeedback] = useState('');
31
+
32
+ // Reset feedback when dialog opens
33
+ useEffect(() => {
34
+ if (open) {
35
+ setFeedback('');
36
+ }
37
+ }, [open]);
38
+
39
+ const isFeedbackEmpty = feedback.trim().length === 0;
40
+
41
+ return (
42
+ <AlertDialog open={open} onOpenChange={onOpenChange}>
43
+ <AlertDialogContent>
44
+ <AlertDialogHeader>
45
+ <AlertDialogTitle>{title}</AlertDialogTitle>
46
+ <AlertDialogDescription>{description}</AlertDialogDescription>
47
+ </AlertDialogHeader>
48
+ <Textarea
49
+ aria-label="Rejection feedback"
50
+ placeholder="Describe what needs to change..."
51
+ value={feedback}
52
+ onChange={(e) => setFeedback(e.target.value)}
53
+ disabled={isSubmitting}
54
+ rows={4}
55
+ />
56
+ <AlertDialogFooter>
57
+ <AlertDialogCancel disabled={isSubmitting} onClick={() => onOpenChange(false)}>
58
+ Cancel
59
+ </AlertDialogCancel>
60
+ <AlertDialogAction
61
+ variant="destructive"
62
+ disabled={isFeedbackEmpty || isSubmitting}
63
+ onClick={() => onConfirm(feedback.trim())}
64
+ >
65
+ {isSubmitting ? (
66
+ <>
67
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
68
+ Rejecting…
69
+ </>
70
+ ) : (
71
+ 'Confirm Reject'
72
+ )}
73
+ </AlertDialogAction>
74
+ </AlertDialogFooter>
75
+ </AlertDialogContent>
76
+ </AlertDialog>
77
+ );
78
+ }
@@ -12,12 +12,14 @@ export interface TechDecisionsReviewData {
12
12
  export interface TechDecisionsReviewProps {
13
13
  /** Tech decisions data from the research artifact */
14
14
  data: TechDecisionsReviewData;
15
- /** Chat refinement callback — sends user feedback to the agent */
16
- onRefine: (text: string) => void;
17
15
  /** Approve plan callback */
18
16
  onApprove: () => void;
17
+ /** Reject plan callback — opens feedback dialog when provided; also used for inline text rejection */
18
+ onReject?: (feedback: string) => void;
19
19
  /** Controls disabled state during loading */
20
20
  isProcessing?: boolean;
21
+ /** Whether a reject operation is in flight */
22
+ isRejecting?: boolean;
21
23
  }
22
24
 
23
25
  export interface TechDecisionsDrawerProps extends TechDecisionsReviewProps {
@@ -67,7 +67,7 @@ type Story = StoryObj<typeof TechDecisionsReview>;
67
67
  export const Default: Story = {
68
68
  args: {
69
69
  data: mockData,
70
- onRefine: fn().mockName('onRefine'),
70
+ onReject: fn().mockName('onReject'),
71
71
  onApprove: fn().mockName('onApprove'),
72
72
  },
73
73
  };
@@ -85,7 +85,7 @@ export const Processing: Story = {
85
85
  export const SingleDecision: Story = {
86
86
  args: {
87
87
  data: { ...mockData, decisions: [mockData.decisions[0]] },
88
- onRefine: fn().mockName('onRefine'),
88
+ onReject: fn().mockName('onReject'),
89
89
  onApprove: fn().mockName('onApprove'),
90
90
  },
91
91
  };
@@ -94,7 +94,7 @@ export const SingleDecision: Story = {
94
94
  export const NoTechnologies: Story = {
95
95
  args: {
96
96
  data: { ...mockData, technologies: [] },
97
- onRefine: fn().mockName('onRefine'),
97
+ onReject: fn().mockName('onReject'),
98
98
  onApprove: fn().mockName('onApprove'),
99
99
  },
100
100
  };
@@ -139,8 +139,8 @@ export const InDrawer: DrawerStory = {
139
139
  featureName="Agent Executor Abstraction"
140
140
  featureId="FEAT-099"
141
141
  data={mockData}
142
- onRefine={fn().mockName('onRefine')}
143
142
  onApprove={fn().mockName('onApprove')}
143
+ onReject={fn().mockName('onReject')}
144
144
  />
145
145
  ),
146
146
  };
@@ -153,8 +153,8 @@ export const WithDeleteButton: DrawerStory = {
153
153
  featureName="Agent Executor Abstraction"
154
154
  featureId="FEAT-099"
155
155
  data={mockData}
156
- onRefine={fn().mockName('onRefine')}
157
156
  onApprove={fn().mockName('onApprove')}
157
+ onReject={fn().mockName('onReject')}
158
158
  onDelete={fn().mockName('onDelete')}
159
159
  />
160
160
  ),
@@ -168,10 +168,31 @@ export const DeletingState: DrawerStory = {
168
168
  featureName="Agent Executor Abstraction"
169
169
  featureId="FEAT-099"
170
170
  data={mockData}
171
- onRefine={fn().mockName('onRefine')}
172
171
  onApprove={fn().mockName('onApprove')}
172
+ onReject={fn().mockName('onReject')}
173
173
  onDelete={fn().mockName('onDelete')}
174
174
  isDeleting
175
175
  />
176
176
  ),
177
177
  };
178
+
179
+ /* ─── Reject Variants ─── */
180
+
181
+ /** With reject button visible next to approve. */
182
+ export const WithRejectButton: Story = {
183
+ args: {
184
+ data: mockData,
185
+ onApprove: fn().mockName('onApprove'),
186
+ onReject: fn().mockName('onReject'),
187
+ },
188
+ };
189
+
190
+ /** Rejecting state — reject button disabled while reject action is in flight. */
191
+ export const RejectingState: Story = {
192
+ args: {
193
+ data: mockData,
194
+ onApprove: fn().mockName('onApprove'),
195
+ onReject: fn().mockName('onReject'),
196
+ isRejecting: true,
197
+ },
198
+ };
@@ -3,10 +3,9 @@
3
3
  import { useState } from 'react';
4
4
  import type { Components } from 'react-markdown';
5
5
  import Markdown from 'react-markdown';
6
- import { Check, ChevronRight, GitCompareArrows, Layers, Send } from 'lucide-react';
6
+ import { Check, ChevronRight, GitCompareArrows, Layers } from 'lucide-react';
7
7
  import { Badge } from '@/components/ui/badge';
8
- import { Button } from '@/components/ui/button';
9
- import { Input } from '@/components/ui/input';
8
+ import { DrawerActionBar } from '@/components/common/drawer-action-bar';
10
9
  import type { TechDecisionsReviewProps, TechDecision } from './tech-decisions-review-config';
11
10
 
12
11
  const markdownComponents: Components = {
@@ -109,20 +108,12 @@ function DecisionCard({ decision, index }: { decision: TechDecision; index: numb
109
108
 
110
109
  export function TechDecisionsReview({
111
110
  data,
112
- onRefine,
113
111
  onApprove,
112
+ onReject,
114
113
  isProcessing = false,
114
+ isRejecting = false,
115
115
  }: TechDecisionsReviewProps) {
116
116
  const { summary, decisions } = data;
117
- const [chatInput, setChatInput] = useState('');
118
-
119
- function handleSubmit(e: { preventDefault: () => void }) {
120
- e.preventDefault();
121
- const text = chatInput.trim();
122
- if (!text) return;
123
- onRefine(text);
124
- setChatInput('');
125
- }
126
117
 
127
118
  if (decisions.length === 0) return null;
128
119
 
@@ -154,35 +145,16 @@ export function TechDecisionsReview({
154
145
  ))}
155
146
  </div>
156
147
 
157
- {/* Action bar: chat + approve */}
158
- <div className="border-border shrink-0 border-t">
159
- <form onSubmit={handleSubmit} className="flex items-center gap-2 p-4 pb-2">
160
- <Input
161
- type="text"
162
- placeholder="Ask AI to revise the plan..."
163
- aria-label="Ask AI to revise the plan"
164
- disabled={isProcessing}
165
- value={chatInput}
166
- onChange={(e) => setChatInput(e.target.value)}
167
- className="flex-1"
168
- />
169
- <Button
170
- type="submit"
171
- variant="secondary"
172
- size="icon"
173
- aria-label="Send"
174
- disabled={isProcessing}
175
- >
176
- <Send />
177
- </Button>
178
- </form>
179
- <div className="px-4 pt-2 pb-4">
180
- <Button type="button" className="w-full" disabled={isProcessing} onClick={onApprove}>
181
- <Check className="mr-1.5 h-4 w-4" />
182
- Approve Plan
183
- </Button>
184
- </div>
185
- </div>
148
+ <DrawerActionBar
149
+ onReject={onReject}
150
+ onApprove={onApprove}
151
+ approveLabel="Approve Plan"
152
+ approveIcon={<Check className="mr-1.5 h-4 w-4" />}
153
+ revisionPlaceholder="Ask AI to revise the plan..."
154
+ rejectDialogTitle="Reject Plan"
155
+ isProcessing={isProcessing}
156
+ isRejecting={isRejecting}
157
+ />
186
158
  </div>
187
159
  );
188
160
  }