@shepai/cli 1.88.0 → 1.89.0-pr262.a408524

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 (210) hide show
  1. package/apis/json-schema/PrdRejectionPayload.yaml +5 -0
  2. package/apis/json-schema/RejectionFeedbackEntry.yaml +5 -0
  3. package/dist/packages/core/src/application/ports/output/services/git-pr-service.interface.d.ts +48 -0
  4. package/dist/packages/core/src/application/ports/output/services/git-pr-service.interface.d.ts.map +1 -1
  5. package/dist/packages/core/src/application/use-cases/agents/reject-agent-run.use-case.d.ts +1 -1
  6. package/dist/packages/core/src/application/use-cases/agents/reject-agent-run.use-case.d.ts.map +1 -1
  7. package/dist/packages/core/src/application/use-cases/agents/reject-agent-run.use-case.js +3 -1
  8. package/dist/packages/core/src/domain/generated/output.d.ts +8 -0
  9. package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
  10. package/dist/packages/core/src/infrastructure/services/git/git-pr.service.d.ts +4 -1
  11. package/dist/packages/core/src/infrastructure/services/git/git-pr.service.d.ts.map +1 -1
  12. package/dist/packages/core/src/infrastructure/services/git/git-pr.service.js +103 -0
  13. package/dist/src/presentation/web/app/actions/get-merge-review-data.d.ts.map +1 -1
  14. package/dist/src/presentation/web/app/actions/get-merge-review-data.js +11 -3
  15. package/dist/src/presentation/web/app/actions/reject-feature.d.ts +1 -1
  16. package/dist/src/presentation/web/app/actions/reject-feature.d.ts.map +1 -1
  17. package/dist/src/presentation/web/app/actions/reject-feature.js +2 -2
  18. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.d.ts.map +1 -1
  19. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js +6 -5
  20. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar-config.d.ts +12 -5
  21. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar-config.d.ts.map +1 -1
  22. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar.d.ts +1 -1
  23. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar.d.ts.map +1 -1
  24. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar.js +266 -13
  25. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar.stories.d.ts +2 -4
  26. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar.stories.d.ts.map +1 -1
  27. package/dist/src/presentation/web/components/common/drawer-action-bar/drawer-action-bar.stories.js +3 -11
  28. package/dist/src/presentation/web/components/common/drawer-action-bar/index.d.ts +1 -1
  29. package/dist/src/presentation/web/components/common/drawer-action-bar/index.d.ts.map +1 -1
  30. package/dist/src/presentation/web/components/common/feature-drawer-tabs/feature-drawer-tabs.d.ts +4 -3
  31. package/dist/src/presentation/web/components/common/feature-drawer-tabs/feature-drawer-tabs.d.ts.map +1 -1
  32. package/dist/src/presentation/web/components/common/feature-drawer-tabs/feature-drawer-tabs.js +2 -2
  33. package/dist/src/presentation/web/components/common/feature-drawer-tabs/overview-tab.d.ts.map +1 -1
  34. package/dist/src/presentation/web/components/common/feature-drawer-tabs/overview-tab.js +3 -2
  35. package/dist/src/presentation/web/components/common/feature-drawer-tabs/overview-tab.stories.d.ts +2 -0
  36. package/dist/src/presentation/web/components/common/feature-drawer-tabs/overview-tab.stories.d.ts.map +1 -1
  37. package/dist/src/presentation/web/components/common/feature-drawer-tabs/overview-tab.stories.js +18 -0
  38. package/dist/src/presentation/web/components/common/merge-review/diff-view.d.ts +6 -0
  39. package/dist/src/presentation/web/components/common/merge-review/diff-view.d.ts.map +1 -0
  40. package/dist/src/presentation/web/components/common/merge-review/diff-view.js +30 -0
  41. package/dist/src/presentation/web/components/common/merge-review/diff-view.stories.d.ts +18 -0
  42. package/dist/src/presentation/web/components/common/merge-review/diff-view.stories.d.ts.map +1 -0
  43. package/dist/src/presentation/web/components/common/merge-review/diff-view.stories.js +174 -0
  44. package/dist/src/presentation/web/components/common/merge-review/index.d.ts +2 -1
  45. package/dist/src/presentation/web/components/common/merge-review/index.d.ts.map +1 -1
  46. package/dist/src/presentation/web/components/common/merge-review/index.js +1 -0
  47. package/dist/src/presentation/web/components/common/merge-review/merge-review-config.d.ts +25 -1
  48. package/dist/src/presentation/web/components/common/merge-review/merge-review-config.d.ts.map +1 -1
  49. package/dist/src/presentation/web/components/common/merge-review/merge-review.d.ts.map +1 -1
  50. package/dist/src/presentation/web/components/common/merge-review/merge-review.js +4 -3
  51. package/dist/src/presentation/web/components/common/merge-review/merge-review.stories.d.ts +2 -0
  52. package/dist/src/presentation/web/components/common/merge-review/merge-review.stories.d.ts.map +1 -1
  53. package/dist/src/presentation/web/components/common/merge-review/merge-review.stories.js +69 -0
  54. package/dist/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire-config.d.ts +2 -1
  55. package/dist/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire-config.d.ts.map +1 -1
  56. package/dist/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire.d.ts.map +1 -1
  57. package/dist/src/presentation/web/components/common/prd-questionnaire/prd-questionnaire.js +2 -2
  58. package/dist/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.d.ts.map +1 -1
  59. package/dist/src/presentation/web/components/common/tech-decisions-review/tech-decisions-review.js +2 -2
  60. package/dist/tsconfig.build.tsbuildinfo +1 -1
  61. package/package.json +1 -1
  62. package/web/.next/BUILD_ID +1 -1
  63. package/web/.next/build-manifest.json +2 -2
  64. package/web/.next/fallback-build-manifest.json +2 -2
  65. package/web/.next/prerender-manifest.json +3 -3
  66. package/web/.next/required-server-files.js +1 -1
  67. package/web/.next/required-server-files.json +1 -1
  68. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +18 -18
  69. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  70. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  71. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +21 -21
  72. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  73. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  74. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +14 -14
  75. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  76. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  77. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +18 -18
  78. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  79. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  80. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +21 -21
  81. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  82. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  83. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +14 -14
  84. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  85. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  86. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +14 -14
  87. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  88. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  89. package/web/.next/server/app/_global-error.html +2 -2
  90. package/web/.next/server/app/_global-error.rsc +1 -1
  91. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  92. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  93. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  94. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  95. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  96. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  97. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  98. package/web/.next/server/app/api/attachments/preview/route.js.nft.json +1 -1
  99. package/web/.next/server/app/settings/page/server-reference-manifest.json +5 -5
  100. package/web/.next/server/app/settings/page.js.nft.json +1 -1
  101. package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  102. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  103. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  104. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  105. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  106. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  107. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  108. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  109. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  110. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -1
  111. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  112. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  113. package/web/.next/server/chunks/ssr/7f428_lucide-react_dist_esm_icons_9027d30a._.js +3 -0
  114. package/web/.next/server/chunks/ssr/7f428_lucide-react_dist_esm_icons_9027d30a._.js.map +1 -0
  115. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_da7eef33.js +1 -1
  116. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_da7eef33.js.map +1 -1
  117. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_0a9251aa.js +1 -1
  118. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_0a9251aa.js.map +1 -1
  119. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js +1 -1
  120. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js.map +1 -1
  121. package/web/.next/server/chunks/ssr/[root-of-the-server]__17143953._.js +1 -1
  122. package/web/.next/server/chunks/ssr/[root-of-the-server]__17143953._.js.map +1 -1
  123. package/web/.next/server/chunks/ssr/[root-of-the-server]__357d99f9._.js +1 -1
  124. package/web/.next/server/chunks/ssr/[root-of-the-server]__4be9a9bb._.js +1 -1
  125. package/web/.next/server/chunks/ssr/[root-of-the-server]__5d87af2e._.js +1 -1
  126. package/web/.next/server/chunks/ssr/[root-of-the-server]__9de266fa._.js +1 -1
  127. package/web/.next/server/chunks/ssr/[root-of-the-server]__9de266fa._.js.map +1 -1
  128. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  129. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  130. package/web/.next/server/chunks/ssr/[root-of-the-server]__b3a4cef4._.js +1 -1
  131. package/web/.next/server/chunks/ssr/[root-of-the-server]__b3a4cef4._.js.map +1 -1
  132. package/web/.next/server/chunks/ssr/[root-of-the-server]__c2173b6d._.js +3 -0
  133. package/web/.next/server/chunks/ssr/[root-of-the-server]__c2173b6d._.js.map +1 -0
  134. package/web/.next/server/chunks/ssr/[root-of-the-server]__f0d8d054._.js +2 -2
  135. package/web/.next/server/chunks/ssr/[root-of-the-server]__f0d8d054._.js.map +1 -1
  136. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js +1 -1
  137. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js.map +1 -1
  138. package/web/.next/server/chunks/ssr/_135edb9c._.js +1 -1
  139. package/web/.next/server/chunks/ssr/_135edb9c._.js.map +1 -1
  140. package/web/.next/server/chunks/ssr/_1bb131c4._.js.map +1 -1
  141. package/web/.next/server/chunks/ssr/{_cf366ac8._.js → _411648c4._.js} +2 -2
  142. package/web/.next/server/chunks/ssr/{_cf366ac8._.js.map → _411648c4._.js.map} +1 -1
  143. package/web/.next/server/chunks/ssr/_6e08ce21._.js.map +1 -1
  144. package/web/.next/server/chunks/ssr/_752e01fd._.js +1 -1
  145. package/web/.next/server/chunks/ssr/_752e01fd._.js.map +1 -1
  146. package/web/.next/server/chunks/ssr/_8dacd1e7._.js +1 -1
  147. package/web/.next/server/chunks/ssr/_8dacd1e7._.js.map +1 -1
  148. package/web/.next/server/chunks/ssr/{_887258d0._.js → _903aed47._.js} +2 -2
  149. package/web/.next/server/chunks/ssr/{_887258d0._.js.map → _903aed47._.js.map} +1 -1
  150. package/web/.next/server/chunks/ssr/_ca282f80._.js +4 -0
  151. package/web/.next/server/chunks/ssr/{_744d9aa6._.js.map → _ca282f80._.js.map} +1 -1
  152. package/web/.next/server/chunks/ssr/_cc46119f._.js +3 -0
  153. package/web/.next/server/chunks/ssr/_cc46119f._.js.map +1 -0
  154. package/web/.next/server/chunks/ssr/_cc77a143._.js +1 -1
  155. package/web/.next/server/chunks/ssr/_cc77a143._.js.map +1 -1
  156. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
  157. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
  158. package/web/.next/server/chunks/ssr/node_modules__pnpm_c931457d._.js +3 -0
  159. package/web/.next/server/chunks/ssr/node_modules__pnpm_c931457d._.js.map +1 -0
  160. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_021a05f2.js +1 -1
  161. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_021a05f2.js.map +1 -1
  162. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js +1 -1
  163. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js.map +1 -1
  164. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js +1 -1
  165. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js.map +1 -1
  166. package/web/.next/server/chunks/ssr/src_presentation_web_c4348b8a._.js +2 -2
  167. package/web/.next/server/chunks/ssr/src_presentation_web_c4348b8a._.js.map +1 -1
  168. package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js +1 -1
  169. package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js.map +1 -1
  170. package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js +1 -1
  171. package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js.map +1 -1
  172. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  173. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  174. package/web/.next/server/chunks/ssr/src_presentation_web_e4510d10._.js +2 -2
  175. package/web/.next/server/chunks/ssr/src_presentation_web_e4510d10._.js.map +1 -1
  176. package/web/.next/server/pages/500.html +2 -2
  177. package/web/.next/server/server-reference-manifest.js +1 -1
  178. package/web/.next/server/server-reference-manifest.json +28 -28
  179. package/web/.next/static/chunks/{86d25f53671f133b.js → 03206d63d05b1138.js} +1 -1
  180. package/web/.next/static/chunks/0e9caf22c951fd5f.js +1 -0
  181. package/web/.next/static/chunks/1e0cfdcb44a16493.js +1 -0
  182. package/web/.next/static/chunks/{c964cf44a600135f.js → 2887a5f7c4b02fd9.js} +1 -1
  183. package/web/.next/static/chunks/{f2b5b44370c16518.js → 3959643d408aec10.js} +1 -1
  184. package/web/.next/static/chunks/548adb8fecaaddba.css +2 -0
  185. package/web/.next/static/chunks/5c55da0e28e6cdc8.js +1 -0
  186. package/web/.next/static/chunks/90f913219e76237c.js +2 -0
  187. package/web/.next/static/chunks/{2711fcfc3de5c268.js → b88d69f2828c5666.js} +1 -1
  188. package/web/.next/static/chunks/{162eea5cec3e0cef.js → b994fa5dd79c8de2.js} +1 -1
  189. package/web/.next/static/chunks/cd49bbf8f5b5025a.js +1 -0
  190. package/web/.next/static/chunks/{cdac1d56e0ce1200.js → f369a1c7baf85c79.js} +1 -1
  191. package/web/.next/static/chunks/f6a4048906c342ba.js +1 -0
  192. package/web/.next/server/chunks/ssr/7f428_lucide-react_dist_esm_icons_9751e686._.js +0 -3
  193. package/web/.next/server/chunks/ssr/7f428_lucide-react_dist_esm_icons_9751e686._.js.map +0 -1
  194. package/web/.next/server/chunks/ssr/[root-of-the-server]__ae824a5b._.js +0 -3
  195. package/web/.next/server/chunks/ssr/[root-of-the-server]__ae824a5b._.js.map +0 -1
  196. package/web/.next/server/chunks/ssr/_5860e4e2._.js +0 -3
  197. package/web/.next/server/chunks/ssr/_5860e4e2._.js.map +0 -1
  198. package/web/.next/server/chunks/ssr/_744d9aa6._.js +0 -4
  199. package/web/.next/server/chunks/ssr/node_modules__pnpm_07f6911c._.js +0 -3
  200. package/web/.next/server/chunks/ssr/node_modules__pnpm_07f6911c._.js.map +0 -1
  201. package/web/.next/static/chunks/09b191310178b8e4.js +0 -1
  202. package/web/.next/static/chunks/30940e6d0fa13220.js +0 -1
  203. package/web/.next/static/chunks/30c827a88342741e.js +0 -1
  204. package/web/.next/static/chunks/6b44bad97dd899ff.js +0 -2
  205. package/web/.next/static/chunks/91d63f8e429f5900.css +0 -2
  206. package/web/.next/static/chunks/a1c2ecb710b44081.js +0 -1
  207. package/web/.next/static/chunks/e19e3a6022a2d917.js +0 -1
  208. /package/web/.next/static/{toIALOYK1PbHh0Fu33jJ3 → 5bc69ANFhLtSF-z8QqiI1}/_buildManifest.js +0 -0
  209. /package/web/.next/static/{toIALOYK1PbHh0Fu33jJ3 → 5bc69ANFhLtSF-z8QqiI1}/_clientMiddlewareManifest.json +0 -0
  210. /package/web/.next/static/{toIALOYK1PbHh0Fu33jJ3 → 5bc69ANFhLtSF-z8QqiI1}/_ssgManifest.js +0 -0
@@ -1,29 +1,282 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState } from 'react';
4
- import { Send } from 'lucide-react';
3
+ import { useState, useCallback, useRef, useEffect } from 'react';
4
+ import { PaperclipIcon, Send, ChevronLeft, Check } from 'lucide-react';
5
+ import { cn } from '../../../lib/utils.js';
5
6
  import { Button } from '../../ui/button.js';
6
- import { Input } from '../../ui/input.js';
7
+ import { Textarea } from '../../ui/textarea.js';
8
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip.js';
7
9
  import { useSoundAction } from '../../../hooks/use-sound-action.js';
8
- export function DrawerActionBar({ onReject, onApprove, approveLabel, approveIcon, revisionPlaceholder, isProcessing = false, isRejecting = false, children, chatInput: controlledChatInput, onChatInputChange, }) {
10
+ import { AttachmentChip } from '../../common/attachment-chip/index.js';
11
+ import { pickFiles } from '../../common/feature-create-drawer/pick-files.js';
12
+ const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
13
+ const ALLOWED_EXTENSIONS = new Set([
14
+ '.png',
15
+ '.jpg',
16
+ '.jpeg',
17
+ '.gif',
18
+ '.webp',
19
+ '.svg',
20
+ '.bmp',
21
+ '.ico',
22
+ '.pdf',
23
+ '.doc',
24
+ '.docx',
25
+ '.xls',
26
+ '.xlsx',
27
+ '.ppt',
28
+ '.pptx',
29
+ '.txt',
30
+ '.md',
31
+ '.csv',
32
+ '.json',
33
+ '.yaml',
34
+ '.yml',
35
+ '.xml',
36
+ '.ts',
37
+ '.tsx',
38
+ '.js',
39
+ '.jsx',
40
+ '.py',
41
+ '.rb',
42
+ '.go',
43
+ '.rs',
44
+ '.java',
45
+ '.c',
46
+ '.cpp',
47
+ '.h',
48
+ '.hpp',
49
+ '.cs',
50
+ '.swift',
51
+ '.kt',
52
+ '.html',
53
+ '.css',
54
+ '.scss',
55
+ '.less',
56
+ '.sh',
57
+ '.bash',
58
+ '.zsh',
59
+ '.fish',
60
+ '.toml',
61
+ '.ini',
62
+ '.cfg',
63
+ '.conf',
64
+ '.env',
65
+ '.zip',
66
+ '.tar',
67
+ '.gz',
68
+ '.log',
69
+ ]);
70
+ function getExtension(filename) {
71
+ const dot = filename.lastIndexOf('.');
72
+ return dot >= 0 ? filename.slice(dot).toLowerCase() : '';
73
+ }
74
+ export function DrawerActionBar({ onReject, onApprove, approveLabel, revisionPlaceholder, isProcessing = false, isRejecting = false, children, chatInput: controlledChatInput, onChatInputChange, }) {
9
75
  const [internalChatInput, setInternalChatInput] = useState('');
10
76
  const chatInput = controlledChatInput ?? internalChatInput;
11
77
  const setChatInput = onChatInputChange ?? setInternalChatInput;
12
78
  const approveSound = useSoundAction('approve');
13
79
  const disabled = isProcessing || isRejecting;
14
- function handleRevisionSubmit(e) {
80
+ const [attachments, setAttachments] = useState([]);
81
+ const [isDragOver, setIsDragOver] = useState(false);
82
+ const [uploadError, setUploadError] = useState(null);
83
+ const [hoverExpanded, setHoverExpanded] = useState(false);
84
+ const [ctrlHeld, setCtrlHeld] = useState(false);
85
+ const [shiftHeld, setShiftHeld] = useState(false);
86
+ const approveExpanded = hoverExpanded || (ctrlHeld && shiftHeld);
87
+ const rejectHighlighted = ctrlHeld && !shiftHeld;
88
+ const dragCounterRef = useRef(0);
89
+ const sessionIdRef = useRef(crypto.randomUUID());
90
+ const formRef = useRef(null);
91
+ // Track Ctrl/Meta + Shift keys for button state
92
+ useEffect(() => {
93
+ function onKeyDown(e) {
94
+ if (e.key === 'Control' || e.key === 'Meta')
95
+ setCtrlHeld(true);
96
+ if (e.key === 'Shift')
97
+ setShiftHeld(true);
98
+ }
99
+ function onKeyUp(e) {
100
+ if (e.key === 'Control' || e.key === 'Meta')
101
+ setCtrlHeld(false);
102
+ if (e.key === 'Shift')
103
+ setShiftHeld(false);
104
+ }
105
+ function onBlur() {
106
+ setCtrlHeld(false);
107
+ setShiftHeld(false);
108
+ }
109
+ window.addEventListener('keydown', onKeyDown);
110
+ window.addEventListener('keyup', onKeyUp);
111
+ window.addEventListener('blur', onBlur);
112
+ return () => {
113
+ window.removeEventListener('keydown', onKeyDown);
114
+ window.removeEventListener('keyup', onKeyUp);
115
+ window.removeEventListener('blur', onBlur);
116
+ };
117
+ }, []);
118
+ const handleFiles = useCallback(async (fileList) => {
119
+ setUploadError(null);
120
+ for (const file of fileList) {
121
+ if (file.size > MAX_FILE_SIZE) {
122
+ setUploadError(`"${file.name}" exceeds 10 MB limit`);
123
+ return;
124
+ }
125
+ const ext = getExtension(file.name);
126
+ if (ext && !ALLOWED_EXTENSIONS.has(ext)) {
127
+ setUploadError(`File type "${ext}" is not allowed`);
128
+ return;
129
+ }
130
+ }
131
+ for (const file of fileList) {
132
+ const tempId = crypto.randomUUID();
133
+ setAttachments((prev) => [
134
+ ...prev,
135
+ {
136
+ id: tempId,
137
+ name: file.name,
138
+ size: file.size,
139
+ mimeType: file.type || 'application/octet-stream',
140
+ path: '',
141
+ loading: true,
142
+ },
143
+ ]);
144
+ try {
145
+ const formData = new FormData();
146
+ formData.append('file', file);
147
+ formData.append('sessionId', sessionIdRef.current);
148
+ const res = await fetch('/api/attachments/upload', {
149
+ method: 'POST',
150
+ body: formData,
151
+ });
152
+ if (!res.ok) {
153
+ const body = await res.json().catch(() => ({ error: 'Upload failed' }));
154
+ setAttachments((prev) => prev.filter((a) => a.id !== tempId));
155
+ setUploadError(body.error ?? 'Upload failed');
156
+ return;
157
+ }
158
+ const uploaded = await res.json();
159
+ setAttachments((prev) => {
160
+ const isDupe = prev.some((a) => a.id !== tempId && a.path === uploaded.path);
161
+ if (isDupe)
162
+ return prev.filter((a) => a.id !== tempId);
163
+ return prev.map((a) => a.id === tempId ? { ...uploaded, id: tempId, loading: false } : a);
164
+ });
165
+ }
166
+ catch {
167
+ setAttachments((prev) => prev.filter((a) => a.id !== tempId));
168
+ setUploadError('Upload failed');
169
+ }
170
+ }
171
+ }, []);
172
+ const handleDragEnter = useCallback((e) => {
15
173
  e.preventDefault();
16
- const text = chatInput.trim();
17
- if (!text || !onReject)
174
+ e.stopPropagation();
175
+ dragCounterRef.current += 1;
176
+ if (dragCounterRef.current === 1)
177
+ setIsDragOver(true);
178
+ }, []);
179
+ const handleDragLeave = useCallback((e) => {
180
+ e.preventDefault();
181
+ e.stopPropagation();
182
+ dragCounterRef.current -= 1;
183
+ if (dragCounterRef.current === 0)
184
+ setIsDragOver(false);
185
+ }, []);
186
+ const handleDragOver = useCallback((e) => {
187
+ e.preventDefault();
188
+ e.stopPropagation();
189
+ }, []);
190
+ const handleDrop = useCallback((e) => {
191
+ e.preventDefault();
192
+ e.stopPropagation();
193
+ dragCounterRef.current = 0;
194
+ setIsDragOver(false);
195
+ const files = Array.from(e.dataTransfer.files);
196
+ if (files.length > 0)
197
+ handleFiles(files);
198
+ }, [handleFiles]);
199
+ const handlePaste = useCallback((e) => {
200
+ const items = e.clipboardData?.items;
201
+ if (!items)
18
202
  return;
19
- onReject(text);
203
+ const files = [];
204
+ for (const item of Array.from(items)) {
205
+ if (item.kind === 'file') {
206
+ const file = item.getAsFile();
207
+ if (file)
208
+ files.push(file);
209
+ }
210
+ }
211
+ if (files.length > 0) {
212
+ e.preventDefault();
213
+ handleFiles(files);
214
+ }
215
+ }, [handleFiles]);
216
+ const handleAddFiles = useCallback(async () => {
217
+ try {
218
+ const files = await pickFiles();
219
+ if (files) {
220
+ setAttachments((prev) => {
221
+ const existingPaths = new Set(prev.map((f) => f.path));
222
+ const unique = files
223
+ .filter((f) => !existingPaths.has(f.path))
224
+ .map((f) => ({
225
+ id: crypto.randomUUID(),
226
+ name: f.name,
227
+ size: f.size,
228
+ mimeType: 'application/octet-stream',
229
+ path: f.path,
230
+ }));
231
+ return unique.length > 0 ? [...prev, ...unique] : prev;
232
+ });
233
+ }
234
+ }
235
+ catch {
236
+ // Native dialog failed — silently ignore
237
+ }
238
+ }, []);
239
+ const handleRemoveFile = useCallback((id) => {
240
+ setAttachments((prev) => prev.filter((f) => f.id !== id));
241
+ }, []);
242
+ const clearForm = useCallback(() => {
20
243
  setChatInput('');
244
+ setAttachments([]);
245
+ setUploadError(null);
246
+ }, [setChatInput]);
247
+ function handleFormSubmit(e) {
248
+ e.preventDefault();
249
+ const text = chatInput.trim();
250
+ if (hoverExpanded || (ctrlHeld && shiftHeld)) {
251
+ // Approve mode (hovering arrow or Shift held)
252
+ approveSound.play();
253
+ onApprove();
254
+ clearForm();
255
+ }
256
+ else {
257
+ // Reject mode — text required
258
+ if (!text || !onReject)
259
+ return;
260
+ onReject(text, attachments.filter((a) => !a.loading));
261
+ clearForm();
262
+ }
21
263
  }
22
- return (_jsxs("div", { className: "border-border shrink-0 border-t", children: [children, onReject ? (_jsxs("form", { onSubmit: handleRevisionSubmit, className: "flex items-center gap-2 p-4", children: [_jsx(Input, { type: "text", placeholder: revisionPlaceholder ?? 'Ask AI to revise...', "aria-label": revisionPlaceholder ?? 'Ask AI to revise...', disabled: disabled, value: chatInput, onChange: (e) => setChatInput(e.target.value), className: "flex-1", "data-testid": "drawer-chat-input" }), _jsx(Button, { type: "submit", variant: "secondary", size: "icon", "aria-label": "Send", disabled: disabled, "data-testid": "drawer-chat-send", children: _jsx(Send, {}) }), _jsxs(Button, { type: "button", disabled: disabled, onClick: () => {
23
- approveSound.play();
24
- onApprove();
25
- }, children: [approveIcon, approveLabel] })] })) : (_jsx("div", { className: "flex items-center gap-2 px-4 pb-4", children: _jsxs(Button, { type: "button", className: "flex-1", disabled: disabled, onClick: () => {
264
+ const handleKeyDown = useCallback((e) => {
265
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
266
+ e.preventDefault();
267
+ // Ctrl+Shift+Enter = approve, Ctrl+Enter = reject
268
+ formRef.current?.requestSubmit();
269
+ }
270
+ }, []);
271
+ const modKey = typeof navigator !== 'undefined' && /Mac/i.test(navigator.userAgent) ? '⌘' : 'Ctrl';
272
+ return (_jsxs("div", { className: "border-border shrink-0 border-t", children: [children, onReject ? (_jsx(TooltipProvider, { delayDuration: 400, children: _jsx("form", { ref: formRef, onSubmit: handleFormSubmit, className: "p-3", children: _jsx("div", { role: "region", "aria-label": "File drop zone", "data-drag-over": isDragOver ? 'true' : 'false', onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, className: cn('rounded-md border-2 border-transparent transition-colors', isDragOver && 'border-primary/50 bg-primary/5'), children: _jsxs("div", { className: "border-input focus-within:ring-ring/50 focus-within:border-ring flex flex-col overflow-hidden rounded-md border shadow-xs transition-[color,box-shadow] focus-within:ring-[3px]", children: [_jsx(Textarea, { placeholder: revisionPlaceholder ?? 'Ask AI to revise...', "aria-label": revisionPlaceholder ?? 'Ask AI to revise...', disabled: disabled, value: chatInput, onChange: (e) => setChatInput(e.target.value), onKeyDown: handleKeyDown, onPaste: handlePaste, rows: 3, className: "min-h-0 flex-1 resize-none rounded-none border-0 shadow-none focus-visible:ring-0", "data-testid": "drawer-chat-input" }), attachments.length > 0 && (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 px-3 py-2", children: attachments.map((file) => (_jsx(AttachmentChip, { name: file.name, size: file.size, mimeType: file.mimeType, path: file.path, onRemove: () => handleRemoveFile(file.id), disabled: disabled, loading: file.loading }, file.id))) })), uploadError ? (_jsx("p", { className: "text-destructive px-3 pb-2 text-xs", children: uploadError })) : null, _jsxs("div", { className: "border-input flex items-center gap-2 border-t px-3 py-1.5", children: [_jsxs("span", { className: "text-muted-foreground flex-1 truncate text-[11px]", children: [_jsxs("kbd", { className: "bg-muted rounded px-1 py-0.5 font-mono text-[10px]", children: [modKey, "+Enter"] }), ' ', "reject", _jsx("span", { className: "mx-1 opacity-40", children: "|" }), _jsxs("kbd", { className: "bg-muted rounded px-1 py-0.5 font-mono text-[10px]", children: [modKey, "+Shift+Enter"] }), ' ', "approve"] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: handleAddFiles, disabled: disabled, "aria-label": "Attach files", className: "text-muted-foreground hover:text-foreground cursor-pointer rounded p-1 transition-colors", children: _jsx(PaperclipIcon, { className: "h-4 w-4" }) }) }), _jsx(TooltipContent, { side: "top", children: "Attach files" })] }), _jsx("div", { onMouseLeave: () => setHoverExpanded(false), children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "submit", disabled: disabled, "data-testid": "drawer-action-submit", className: cn('relative flex h-8 min-w-[10rem] cursor-pointer items-center overflow-hidden rounded-md border pr-10 pl-4 text-sm font-medium whitespace-nowrap transition-colors', 'disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50', approveExpanded
273
+ ? 'border-blue-400/60 text-white'
274
+ : rejectHighlighted
275
+ ? 'border-primary bg-muted ring-primary/30 shadow-sm ring-1'
276
+ : 'border-border bg-muted/50 hover:bg-muted shadow-sm'), children: [_jsx("div", { className: "pointer-events-none absolute inset-0 bg-blue-500/85 transition-transform duration-300 ease-in-out", style: {
277
+ transform: approveExpanded ? 'translateX(0)' : 'translateX(100%)',
278
+ } }), _jsxs("span", { className: cn('absolute inset-0 z-10 flex items-center justify-center gap-1.5 pr-8 transition-opacity duration-300', approveExpanded ? 'opacity-0' : 'opacity-100'), children: [_jsx(Send, { className: "h-3.5 w-3.5 shrink-0" }), "Reject"] }), _jsxs("span", { className: cn('absolute inset-0 z-10 flex items-center justify-center gap-1.5 text-white transition-opacity duration-300', approveExpanded ? 'opacity-100' : 'opacity-0'), children: [_jsx(Check, { className: "h-3.5 w-3.5 shrink-0" }), approveLabel] }), _jsx("span", { className: cn('border-input/60 absolute inset-y-0 right-0 z-20 flex w-8 cursor-pointer items-center justify-center rounded-r-[5px] border-l bg-blue-500/85 transition-opacity duration-300', approveExpanded && 'pointer-events-none opacity-0'), onMouseEnter: () => setHoverExpanded(true), children: _jsx(ChevronLeft, { className: "h-3.5 w-3.5 text-white" }) })] }) }), _jsx(TooltipContent, { side: "top", children: approveExpanded ? 'Approve' : 'Send revision feedback' })] }) })] })] }) }) }) })) : (_jsx("div", { className: "flex items-center gap-2 px-4 pb-4", children: _jsx(Button, { type: "button", className: "flex-1", disabled: disabled, onClick: () => {
26
279
  approveSound.play();
27
280
  onApprove();
28
- }, children: [approveIcon, approveLabel] }) }))] }));
281
+ }, children: approveLabel }) }))] }));
29
282
  }
@@ -3,11 +3,9 @@ import { DrawerActionBar } from './drawer-action-bar.js';
3
3
  declare const meta: Meta<typeof DrawerActionBar>;
4
4
  export default meta;
5
5
  type Story = StoryObj<typeof DrawerActionBar>;
6
- /** Default — approve button only. Plays "approve" sound on click. */
6
+ /** Default — approve-only button (no reject input). */
7
7
  export declare const Default: Story;
8
- /** With approve icon. */
9
- export declare const WithIcon: Story;
10
- /** With revision input — single line: chat input, send, approve. */
8
+ /** Two-button bar: Reject + Approve with slide-expand animation on hover. */
11
9
  export declare const WithReject: Story;
12
10
  /** Processing state — all controls disabled. */
13
11
  export declare const Processing: Story;
@@ -1 +1 @@
1
- {"version":3,"file":"drawer-action-bar.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/drawer-action-bar/drawer-action-bar.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAGvD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,eAAe,CAkBtC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9C,qEAAqE;AACrE,eAAO,MAAM,OAAO,EAAE,KAAU,CAAC;AAEjC,yBAAyB;AACzB,eAAO,MAAM,QAAQ,EAAE,KAKtB,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,UAAU,EAAE,KAKxB,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,UAAU,EAAE,KAMxB,CAAC;AAEF,+CAA+C;AAC/C,eAAO,MAAM,SAAS,EAAE,KAMvB,CAAC;AAEF,mCAAmC;AACnC,eAAO,MAAM,iBAAiB,EAAE,KAM/B,CAAC"}
1
+ {"version":3,"file":"drawer-action-bar.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/drawer-action-bar/drawer-action-bar.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,eAAe,CAkBtC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9C,uDAAuD;AACvD,eAAO,MAAM,OAAO,EAAE,KAAU,CAAC;AAEjC,6EAA6E;AAC7E,eAAO,MAAM,UAAU,EAAE,KAKxB,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,UAAU,EAAE,KAMxB,CAAC;AAEF,+CAA+C;AAC/C,eAAO,MAAM,SAAS,EAAE,KAMvB,CAAC;AAEF,mCAAmC;AACnC,eAAO,MAAM,iBAAiB,EAAE,KAM/B,CAAC"}
@@ -1,6 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { fn } from '@storybook/test';
3
- import { Check } from 'lucide-react';
4
3
  import { DrawerActionBar } from './drawer-action-bar.js';
5
4
  const meta = {
6
5
  title: 'Drawers/Base/DrawerActionBar',
@@ -18,16 +17,9 @@ const meta = {
18
17
  ],
19
18
  };
20
19
  export default meta;
21
- /** Default — approve button only. Plays "approve" sound on click. */
20
+ /** Default — approve-only button (no reject input). */
22
21
  export const Default = {};
23
- /** With approve icon. */
24
- export const WithIcon = {
25
- args: {
26
- approveLabel: 'Approve PRD',
27
- approveIcon: _jsx(Check, { className: "mr-1.5 h-4 w-4" }),
28
- },
29
- };
30
- /** With revision input — single line: chat input, send, approve. */
22
+ /** Two-button bar: Reject + Approve with slide-expand animation on hover. */
31
23
  export const WithReject = {
32
24
  args: {
33
25
  onReject: fn().mockName('onReject'),
@@ -54,7 +46,7 @@ export const Rejecting = {
54
46
  export const WithRevisionInput = {
55
47
  args: {
56
48
  onReject: fn().mockName('onReject'),
57
- approveLabel: 'Approve',
49
+ approveLabel: 'Approve Plan',
58
50
  revisionPlaceholder: 'Ask AI to revise the plan...',
59
51
  },
60
52
  };
@@ -1,3 +1,3 @@
1
1
  export { DrawerActionBar } from './drawer-action-bar.js';
2
- export type { DrawerActionBarProps } from './drawer-action-bar-config.js';
2
+ export type { DrawerActionBarProps, RejectAttachment } from './drawer-action-bar-config.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/drawer-action-bar/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/drawer-action-bar/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC"}
@@ -3,6 +3,7 @@ import type { PrdQuestionnaireData } from '../../common/prd-questionnaire/index.
3
3
  import type { TechDecisionsReviewData } from '../../common/tech-decisions-review/index.js';
4
4
  import type { ProductDecisionsSummaryData } from '../../common/product-decisions-summary/index.js';
5
5
  import type { MergeReviewData } from '../../common/merge-review/index.js';
6
+ import type { RejectAttachment } from '../../common/drawer-action-bar/index.js';
6
7
  import type { FeatureTabKey } from '../../common/control-center-drawer/drawer-view.js';
7
8
  export interface FeatureDrawerTabsProps {
8
9
  featureNode: FeatureNodeData;
@@ -12,16 +13,16 @@ export interface FeatureDrawerTabsProps {
12
13
  prdSelections?: Record<string, string>;
13
14
  onPrdSelect?: (questionId: string, optionId: string) => void;
14
15
  onPrdApprove?: (actionId: string) => void;
15
- onPrdReject?: (feedback: string) => void;
16
+ onPrdReject?: (feedback: string, attachments: RejectAttachment[]) => void;
16
17
  isPrdLoading?: boolean;
17
18
  techData?: TechDecisionsReviewData | null;
18
19
  onTechApprove?: () => void;
19
- onTechReject?: (feedback: string) => void;
20
+ onTechReject?: (feedback: string, attachments: RejectAttachment[]) => void;
20
21
  isTechLoading?: boolean;
21
22
  productData?: ProductDecisionsSummaryData | null;
22
23
  mergeData?: MergeReviewData | null;
23
24
  onMergeApprove?: () => void;
24
- onMergeReject?: (feedback: string) => void;
25
+ onMergeReject?: (feedback: string, attachments: RejectAttachment[]) => void;
25
26
  isMergeLoading?: boolean;
26
27
  isRejecting?: boolean;
27
28
  chatInput?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"feature-drawer-tabs.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/feature-drawer-tabs.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAClF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AACzF,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,+CAA+C,CAAC;AACjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAaxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAC;AAiD3F,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,eAAe,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,aAAa,CAAC;IAG3B,OAAO,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,YAAY,CAAC,EAAE,OAAO,CAAC;IAGvB,QAAQ,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC1C,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,aAAa,CAAC,EAAE,OAAO,CAAC;IAGxB,WAAW,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAGjD,SAAS,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,cAAc,CAAC,EAAE,OAAO,CAAC;IAGzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AAwBD,wBAAgB,iBAAiB,CAAC,EAChC,WAAW,EACX,SAAS,EACT,UAAU,EACV,OAAO,EACP,aAAa,EACb,WAAW,EACX,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,SAAS,EACT,cAAc,EACd,aAAa,EACb,cAAc,EACd,WAAW,EACX,SAAS,EACT,iBAAiB,GAClB,EAAE,sBAAsB,2CA4MxB"}
1
+ {"version":3,"file":"feature-drawer-tabs.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/feature-drawer-tabs.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAClF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AACzF,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,+CAA+C,CAAC;AACjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAMxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAQ9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAC;AAiD3F,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,eAAe,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,aAAa,CAAC;IAG3B,OAAO,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IAGvB,QAAQ,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC1C,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IAC3E,aAAa,CAAC,EAAE,OAAO,CAAC;IAGxB,WAAW,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAGjD,SAAS,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IAC5E,cAAc,CAAC,EAAE,OAAO,CAAC;IAGzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AAwBD,wBAAgB,iBAAiB,CAAC,EAChC,WAAW,EACX,SAAS,EACT,UAAU,EACV,OAAO,EACP,aAAa,EACb,WAAW,EACX,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,SAAS,EACT,cAAc,EACd,aAAa,EACb,cAAc,EACd,WAAW,EACX,SAAS,EACT,iBAAiB,GAClB,EAAE,sBAAsB,2CA4MxB"}
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
4
- import { Loader2, Check } from 'lucide-react';
4
+ import { Loader2 } from 'lucide-react';
5
5
  import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../ui/tabs.js';
6
6
  import { getFeaturePhaseTimings } from '../../../app/actions/get-feature-phase-timings.js';
7
7
  import { getFeaturePlan } from '../../../app/actions/get-feature-plan.js';
@@ -119,5 +119,5 @@ export function FeatureDrawerTabs({ featureNode, featureId, initialTab, prdData,
119
119
  }
120
120
  // ── Private helper ──────────────────────────────────────────────────────
121
121
  function DrawerActionBarForTech({ onApprove, onReject, isProcessing, isRejecting, chatInput, onChatInputChange, }) {
122
- return (_jsx(DrawerActionBar, { onReject: onReject, onApprove: onApprove, approveLabel: "Approve Plan", approveIcon: _jsx(Check, { className: "mr-1.5 h-4 w-4" }), revisionPlaceholder: "Ask AI to revise the plan...", isProcessing: isProcessing, isRejecting: isRejecting, chatInput: chatInput, onChatInputChange: onChatInputChange }));
122
+ return (_jsx(DrawerActionBar, { onReject: onReject, onApprove: onApprove, approveLabel: "Approve Plan", revisionPlaceholder: "Ask AI to revise the plan...", isProcessing: isProcessing, isRejecting: isRejecting, chatInput: chatInput, onChatInputChange: onChatInputChange }));
123
123
  }
@@ -1 +1 @@
1
- {"version":3,"file":"overview-tab.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/overview-tab.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAMxE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,eAAe,CAAC;CACvB;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,gBAAgB,2CAoCrD"}
1
+ {"version":3,"file":"overview-tab.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/overview-tab.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAMxE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,eAAe,CAAC;CACvB;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,gBAAgB,2CAwCrD"}
@@ -10,7 +10,8 @@ import { CometSpinner } from '../../ui/comet-spinner.js';
10
10
  import { featureNodeStateConfig, lifecycleDisplayLabels } from '../../common/feature-node/index.js';
11
11
  import { getAgentTypeIcon, agentTypeLabels, } from '../../common/feature-node/agent-type-icons.js';
12
12
  export function OverviewTab({ data }) {
13
- return (_jsxs(_Fragment, { children: [_jsxs("div", { "data-testid": "feature-drawer-status", className: "flex flex-col gap-3 p-4", children: [_jsx("div", { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: lifecycleDisplayLabels[data.lifecycle] }), _jsx(FeatureStateBadge, { data: data }), data.progress > 0 ? (_jsxs("div", { "data-testid": "feature-drawer-progress", className: "flex flex-col gap-1", children: [_jsxs("div", { className: "text-muted-foreground flex items-center justify-between text-xs", children: [_jsx("span", { children: "Progress" }), _jsxs("span", { children: [data.progress, "%"] })] }), _jsx("div", { className: "bg-muted h-2 w-full overflow-hidden rounded-full", children: _jsx("div", { className: cn('h-full rounded-full transition-all', featureNodeStateConfig[data.state].progressClass), style: { width: `${data.progress}%` } }) })] })) : null] }), _jsx(FeatureInfo, { data: data }), data.pr ? (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(FeaturePrInfo, { pr: data.pr })] })) : null, _jsx(FeatureDetails, { data: data })] }));
13
+ const isCompleted = data.lifecycle === 'maintain';
14
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { "data-testid": "feature-drawer-status", className: "flex flex-col gap-3 p-4", children: [_jsx("div", { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: lifecycleDisplayLabels[data.lifecycle] }), _jsx(FeatureStateBadge, { data: data }), !isCompleted && data.progress > 0 ? (_jsxs("div", { "data-testid": "feature-drawer-progress", className: "flex flex-col gap-1", children: [_jsxs("div", { className: "text-muted-foreground flex items-center justify-between text-xs", children: [_jsx("span", { children: "Progress" }), _jsxs("span", { children: [data.progress, "%"] })] }), _jsx("div", { className: "bg-muted h-2 w-full overflow-hidden rounded-full", children: _jsx("div", { className: cn('h-full rounded-full transition-all', featureNodeStateConfig[data.state].progressClass), style: { width: `${data.progress}%` } }) })] })) : null, isCompleted && data.pr ? _jsx(FeaturePrInfo, { pr: data.pr }) : null] }), _jsx(FeatureInfo, { data: data }), !isCompleted && data.pr ? (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx("div", { className: "p-4", children: _jsx(FeaturePrInfo, { pr: data.pr }) })] })) : null, _jsx(FeatureDetails, { data: data })] }));
14
15
  }
15
16
  function FeatureStateBadge({ data }) {
16
17
  const config = featureNodeStateConfig[data.state];
@@ -58,7 +59,7 @@ const prStatusStyles = {
58
59
  [PrStatus.Closed]: 'border-transparent bg-red-50 text-red-700 hover:bg-red-50',
59
60
  };
60
61
  function FeaturePrInfo({ pr }) {
61
- return (_jsx("div", { "data-testid": "feature-drawer-pr", className: "border-border mx-4 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, { className: prStatusStyles[pr.status], 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] }) }));
62
+ return (_jsx("div", { "data-testid": "feature-drawer-pr", children: _jsxs("div", { className: "space-y-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, { className: prStatusStyles[pr.status], 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] }) }));
62
63
  }
63
64
  // ── Details section ──────────────────────────────────────────────────
64
65
  function FeatureDetails({ data }) {
@@ -27,4 +27,6 @@ export declare const ResearchPhase: Story;
27
27
  export declare const ReviewPhase: Story;
28
28
  /** Maintain phase. */
29
29
  export declare const MaintainPhase: Story;
30
+ /** Completed feature with PR — progress hidden, PR shown in status section without borders. */
31
+ export declare const CompletedWithPr: Story;
30
32
  //# sourceMappingURL=overview-tab.stories.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"overview-tab.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/overview-tab.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG7C,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,WAAW,CAclC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,WAAW,CAAC,CAAC;AA0G1C,uEAAuE;AACvE,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF,8CAA8C;AAC9C,eAAO,MAAM,IAAI,EAAE,KAElB,CAAC;AAEF,yDAAyD;AACzD,eAAO,MAAM,UAAU,EAAE,KAExB,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,EAAE,KAE9B,CAAC;AAEF,iDAAiD;AACjD,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,KAAK,EAAE,KAEnB,CAAC;AAEF,sEAAsE;AACtE,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAwBF,mEAAmE;AACnE,eAAO,MAAM,QAAQ,EAAE,KAEtB,CAAC;AAMF,0BAA0B;AAC1B,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,sBAAsB;AACtB,eAAO,MAAM,aAAa,EAAE,KAE3B,CAAC;AAEF,oBAAoB;AACpB,eAAO,MAAM,WAAW,EAAE,KAEzB,CAAC;AAEF,sBAAsB;AACtB,eAAO,MAAM,aAAa,EAAE,KAE3B,CAAC"}
1
+ {"version":3,"file":"overview-tab.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/overview-tab.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG7C,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,WAAW,CAclC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,WAAW,CAAC,CAAC;AA0G1C,uEAAuE;AACvE,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF,8CAA8C;AAC9C,eAAO,MAAM,IAAI,EAAE,KAElB,CAAC;AAEF,yDAAyD;AACzD,eAAO,MAAM,UAAU,EAAE,KAExB,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,EAAE,KAE9B,CAAC;AAEF,iDAAiD;AACjD,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,KAAK,EAAE,KAEnB,CAAC;AAEF,sEAAsE;AACtE,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAwBF,mEAAmE;AACnE,eAAO,MAAM,QAAQ,EAAE,KAEtB,CAAC;AAMF,0BAA0B;AAC1B,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,sBAAsB;AACtB,eAAO,MAAM,aAAa,EAAE,KAE3B,CAAC;AAEF,oBAAoB;AACpB,eAAO,MAAM,WAAW,EAAE,KAEzB,CAAC;AAEF,sBAAsB;AACtB,eAAO,MAAM,aAAa,EAAE,KAE3B,CAAC;AAEF,+FAA+F;AAC/F,eAAO,MAAM,eAAe,EAAE,KAgB7B,CAAC"}
@@ -179,3 +179,21 @@ export const ReviewPhase = {
179
179
  export const MaintainPhase = {
180
180
  args: { data: { ...noPrData, lifecycle: 'maintain' } },
181
181
  };
182
+ /** Completed feature with PR — progress hidden, PR shown in status section without borders. */
183
+ export const CompletedWithPr = {
184
+ args: {
185
+ data: {
186
+ ...fullData,
187
+ lifecycle: 'maintain',
188
+ state: 'done',
189
+ progress: 100,
190
+ pr: {
191
+ url: 'https://github.com/org/repo/pull/55',
192
+ number: 55,
193
+ status: PrStatus.Merged,
194
+ ciStatus: CiStatus.Success,
195
+ commitHash: 'def4567890abc123',
196
+ },
197
+ },
198
+ },
199
+ };
@@ -0,0 +1,6 @@
1
+ import type { MergeReviewFileDiff } from './merge-review-config.js';
2
+ export interface DiffViewProps {
3
+ fileDiffs: MergeReviewFileDiff[];
4
+ }
5
+ export declare function DiffView({ fileDiffs }: DiffViewProps): import("react/jsx-runtime").JSX.Element | null;
6
+ //# sourceMappingURL=diff-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-view.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/merge-review/diff-view.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAuB,MAAM,uBAAuB,CAAC;AA4GtF,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,mBAAmB,EAAE,CAAC;CAClC;AAED,wBAAgB,QAAQ,CAAC,EAAE,SAAS,EAAE,EAAE,aAAa,kDAmBpD"}
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { ChevronRight, FileText, FilePlus, FileMinus, FileEdit } from 'lucide-react';
5
+ import { cn } from '../../../lib/utils.js';
6
+ const STATUS_CONFIG = {
7
+ added: { icon: FilePlus, label: 'A', className: 'text-green-600' },
8
+ modified: { icon: FileEdit, label: 'M', className: 'text-amber-600' },
9
+ deleted: { icon: FileMinus, label: 'D', className: 'text-red-600' },
10
+ renamed: { icon: FileEdit, label: 'R', className: 'text-blue-600' },
11
+ };
12
+ function FileStatusIcon({ status }) {
13
+ const config = STATUS_CONFIG[status];
14
+ const Icon = config.icon;
15
+ return _jsx(Icon, { className: cn('h-3.5 w-3.5 shrink-0', config.className) });
16
+ }
17
+ function HunkView({ hunk }) {
18
+ return (_jsxs("div", { className: "border-border border-t first:border-t-0", children: [_jsx("div", { className: "bg-muted/50 text-muted-foreground px-3 py-1 font-mono text-[10px]", children: hunk.header }), _jsx("div", { className: "font-mono text-[11px] leading-[18px]", children: hunk.lines.map((line) => (_jsxs("div", { className: cn('flex', line.type === 'added' && 'bg-green-50 dark:bg-green-950/30', line.type === 'removed' && 'bg-red-50 dark:bg-red-950/30'), children: [_jsx("span", { className: "text-muted-foreground w-10 shrink-0 px-1 text-right text-[10px] select-none", children: line.oldNumber ?? '' }), _jsx("span", { className: "text-muted-foreground w-10 shrink-0 px-1 text-right text-[10px] select-none", children: line.newNumber ?? '' }), _jsx("span", { className: cn('w-4 shrink-0 text-center select-none', line.type === 'added' && 'text-green-700 dark:text-green-400', line.type === 'removed' && 'text-red-700 dark:text-red-400'), children: line.type === 'added' ? '+' : line.type === 'removed' ? '-' : ' ' }), _jsx("span", { className: "min-w-0 flex-1 pr-2 break-all whitespace-pre-wrap", children: line.content })] }, `${line.type}-${line.oldNumber ?? ''}-${line.newNumber ?? ''}`))) })] }));
19
+ }
20
+ function FileDiffItem({ file }) {
21
+ const [isOpen, setIsOpen] = useState(false);
22
+ const fileName = file.path.split('/').pop() ?? file.path;
23
+ const dirPath = file.path.includes('/') ? file.path.slice(0, file.path.lastIndexOf('/')) : '';
24
+ return (_jsxs("div", { className: "border-border border-b last:border-b-0", children: [_jsxs("button", { type: "button", onClick: () => setIsOpen(!isOpen), className: "hover:bg-muted/50 flex w-full items-center gap-2 px-3 py-2 text-left", children: [_jsx(ChevronRight, { className: cn('text-muted-foreground h-3 w-3 shrink-0 transition-transform duration-150', isOpen && 'rotate-90') }), _jsx(FileStatusIcon, { status: file.status }), _jsx("span", { className: "text-foreground min-w-0 flex-1 truncate text-xs", children: dirPath ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "text-muted-foreground", children: [dirPath, "/"] }), fileName] })) : (fileName) }), file.oldPath ? (_jsxs("span", { className: "text-muted-foreground truncate text-[10px]", children: ["\u2190 ", file.oldPath.split('/').pop()] })) : null, _jsxs("span", { className: "shrink-0 text-[10px]", children: [file.additions > 0 ? _jsxs("span", { className: "text-green-600", children: ["+", file.additions] }) : null, file.additions > 0 && file.deletions > 0 ? ' ' : null, file.deletions > 0 ? _jsxs("span", { className: "text-red-600", children: ["-", file.deletions] }) : null] })] }), isOpen && file.hunks.length > 0 ? (_jsx("div", { className: "border-border overflow-x-auto border-t", children: file.hunks.map((hunk) => (_jsx(HunkView, { hunk: hunk }, hunk.header))) })) : null] }));
25
+ }
26
+ export function DiffView({ fileDiffs }) {
27
+ if (fileDiffs.length === 0)
28
+ return null;
29
+ return (_jsxs("div", { className: "border-border rounded-lg border", children: [_jsx("div", { className: "px-4 py-3", children: _jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(FileText, { className: "text-muted-foreground h-4 w-4" }), _jsx("span", { className: "text-foreground text-xs font-semibold", children: "Changed Files" }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", fileDiffs.length, ")"] })] }) }), _jsx("div", { className: "border-border border-t", children: fileDiffs.map((file) => (_jsx(FileDiffItem, { file: file }, file.path))) })] }));
30
+ }
@@ -0,0 +1,18 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { DiffView } from './diff-view.js';
3
+ declare const meta: Meta<typeof DiffView>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof DiffView>;
6
+ /** Default — multiple files with various change types. */
7
+ export declare const Default: Story;
8
+ /** Single modified file. */
9
+ export declare const SingleFile: Story;
10
+ /** Only new files added. */
11
+ export declare const OnlyAdditions: Story;
12
+ /** Only deleted files. */
13
+ export declare const OnlyDeletions: Story;
14
+ /** Renamed file. */
15
+ export declare const RenamedFile: Story;
16
+ /** Empty diff list — renders nothing. */
17
+ export declare const EmptyDiffs: Story;
18
+ //# sourceMappingURL=diff-view.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-view.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/merge-review/diff-view.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AA2HvC,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,QAAQ,CAuB/B,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEvC,0DAA0D;AAC1D,eAAO,MAAM,OAAO,EAAE,KAIrB,CAAC;AAEF,4BAA4B;AAC5B,eAAO,MAAM,UAAU,EAAE,KAIxB,CAAC;AAEF,4BAA4B;AAC5B,eAAO,MAAM,aAAa,EAAE,KAI3B,CAAC;AAEF,0BAA0B;AAC1B,eAAO,MAAM,aAAa,EAAE,KAI3B,CAAC;AAEF,oBAAoB;AACpB,eAAO,MAAM,WAAW,EAAE,KAIzB,CAAC;AAEF,yCAAyC;AACzC,eAAO,MAAM,UAAU,EAAE,KAIxB,CAAC"}