@shepai/cli 1.84.0 → 1.85.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/apis/json-schema/Attachment.yaml +31 -0
  2. package/apis/json-schema/Feature.yaml +5 -0
  3. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.d.ts +3 -1
  4. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.d.ts.map +1 -1
  5. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.js +35 -3
  6. package/dist/packages/core/src/application/use-cases/features/create/types.d.ts +7 -1
  7. package/dist/packages/core/src/application/use-cases/features/create/types.d.ts.map +1 -1
  8. package/dist/packages/core/src/domain/generated/output.d.ts +33 -0
  9. package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
  10. package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
  11. package/dist/packages/core/src/infrastructure/di/container.js +3 -0
  12. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/feature.mapper.d.ts +1 -0
  13. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/feature.mapper.d.ts.map +1 -1
  14. package/dist/packages/core/src/infrastructure/persistence/sqlite/mappers/feature.mapper.js +4 -0
  15. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.d.ts.map +1 -1
  16. package/dist/packages/core/src/infrastructure/persistence/sqlite/migrations.js +16 -0
  17. package/dist/packages/core/src/infrastructure/services/attachment-storage.service.d.ts +31 -0
  18. package/dist/packages/core/src/infrastructure/services/attachment-storage.service.d.ts.map +1 -0
  19. package/dist/packages/core/src/infrastructure/services/attachment-storage.service.js +136 -0
  20. package/dist/src/presentation/cli/commands/feat/new.command.d.ts.map +1 -1
  21. package/dist/src/presentation/cli/commands/feat/new.command.js +21 -1
  22. package/dist/src/presentation/web/app/actions/compose-user-input.d.ts +7 -0
  23. package/dist/src/presentation/web/app/actions/compose-user-input.d.ts.map +1 -0
  24. package/dist/src/presentation/web/app/actions/compose-user-input.js +7 -0
  25. package/dist/src/presentation/web/app/actions/create-feature.d.ts +1 -0
  26. package/dist/src/presentation/web/app/actions/create-feature.d.ts.map +1 -1
  27. package/dist/src/presentation/web/app/actions/create-feature.js +3 -9
  28. package/dist/src/presentation/web/app/api/attachments/preview/route.d.ts +4 -0
  29. package/dist/src/presentation/web/app/api/attachments/preview/route.d.ts.map +1 -0
  30. package/dist/src/presentation/web/app/api/attachments/preview/route.js +47 -0
  31. package/dist/src/presentation/web/app/api/attachments/upload/route.d.ts +3 -0
  32. package/dist/src/presentation/web/app/api/attachments/upload/route.d.ts.map +1 -0
  33. package/dist/src/presentation/web/app/api/attachments/upload/route.js +100 -0
  34. package/dist/src/presentation/web/components/common/attachment-card/attachment-card.d.ts +16 -0
  35. package/dist/src/presentation/web/components/common/attachment-card/attachment-card.d.ts.map +1 -0
  36. package/dist/src/presentation/web/components/common/attachment-card/attachment-card.js +53 -0
  37. package/dist/src/presentation/web/components/common/attachment-card/attachment-card.stories.d.ts +12 -0
  38. package/dist/src/presentation/web/components/common/attachment-card/attachment-card.stories.d.ts.map +1 -0
  39. package/dist/src/presentation/web/components/common/attachment-card/attachment-card.stories.js +55 -0
  40. package/dist/src/presentation/web/components/common/attachment-card/index.d.ts +3 -0
  41. package/dist/src/presentation/web/components/common/attachment-card/index.d.ts.map +1 -0
  42. package/dist/src/presentation/web/components/common/attachment-card/index.js +1 -0
  43. package/dist/src/presentation/web/components/common/attachment-chip/attachment-chip.d.ts +11 -0
  44. package/dist/src/presentation/web/components/common/attachment-chip/attachment-chip.d.ts.map +1 -0
  45. package/dist/src/presentation/web/components/common/attachment-chip/attachment-chip.js +34 -0
  46. package/dist/src/presentation/web/components/common/attachment-chip/attachment-chip.stories.d.ts +10 -0
  47. package/dist/src/presentation/web/components/common/attachment-chip/attachment-chip.stories.d.ts.map +1 -0
  48. package/dist/src/presentation/web/components/common/attachment-chip/attachment-chip.stories.js +44 -0
  49. package/dist/src/presentation/web/components/common/attachment-chip/index.d.ts +3 -0
  50. package/dist/src/presentation/web/components/common/attachment-chip/index.d.ts.map +1 -0
  51. package/dist/src/presentation/web/components/common/attachment-chip/index.js +1 -0
  52. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts +11 -2
  53. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts.map +1 -1
  54. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js +199 -60
  55. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts +6 -0
  56. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts.map +1 -1
  57. package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.js +18 -0
  58. package/dist/src/presentation/web/components/ui/dialog.js +1 -1
  59. package/dist/src/presentation/web/next.config.d.ts.map +1 -1
  60. package/dist/src/presentation/web/next.config.js +6 -0
  61. package/dist/tsconfig.build.tsbuildinfo +1 -1
  62. package/package.json +1 -1
  63. package/web/.next/BUILD_ID +1 -1
  64. package/web/.next/app-path-routes-manifest.json +2 -0
  65. package/web/.next/build-manifest.json +2 -2
  66. package/web/.next/fallback-build-manifest.json +2 -2
  67. package/web/.next/prerender-manifest.json +3 -3
  68. package/web/.next/required-server-files.js +4 -1
  69. package/web/.next/required-server-files.json +4 -1
  70. package/web/.next/routes-manifest.json +12 -0
  71. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +18 -18
  72. package/web/.next/server/app/(dashboard)/@drawer/create/page.js +2 -2
  73. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  74. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  75. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +21 -21
  76. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +2 -2
  77. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  78. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  79. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +14 -14
  80. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +2 -2
  81. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  82. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  83. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +18 -18
  84. package/web/.next/server/app/(dashboard)/create/page.js +2 -2
  85. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  86. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  87. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +21 -21
  88. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +2 -2
  89. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  90. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  91. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +14 -14
  92. package/web/.next/server/app/(dashboard)/page.js +2 -2
  93. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  94. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  95. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +14 -14
  96. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +2 -2
  97. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  98. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  99. package/web/.next/server/app/_global-error/page.js +2 -2
  100. package/web/.next/server/app/_global-error/page.js.nft.json +1 -1
  101. package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  102. package/web/.next/server/app/_global-error.html +2 -2
  103. package/web/.next/server/app/_global-error.rsc +1 -1
  104. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  105. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  106. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  107. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  108. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  109. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  110. package/web/.next/server/app/_not-found/page.js +2 -2
  111. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  112. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  113. package/web/.next/server/app/api/attachments/preview/route/app-paths-manifest.json +3 -0
  114. package/web/.next/server/app/api/attachments/preview/route/build-manifest.json +11 -0
  115. package/web/.next/server/app/api/attachments/preview/route/server-reference-manifest.json +4 -0
  116. package/web/.next/server/app/api/attachments/preview/route.js +7 -0
  117. package/web/.next/server/app/api/attachments/preview/route.js.map +5 -0
  118. package/web/.next/server/app/api/attachments/preview/route.js.nft.json +1 -0
  119. package/web/.next/server/app/api/attachments/preview/route_client-reference-manifest.js +2 -0
  120. package/web/.next/server/app/api/attachments/upload/route/app-paths-manifest.json +3 -0
  121. package/web/.next/server/app/api/attachments/upload/route/build-manifest.json +11 -0
  122. package/web/.next/server/app/api/attachments/upload/route/server-reference-manifest.json +4 -0
  123. package/web/.next/server/app/api/attachments/upload/route.js +7 -0
  124. package/web/.next/server/app/api/attachments/upload/route.js.map +5 -0
  125. package/web/.next/server/app/api/attachments/upload/route.js.nft.json +1 -0
  126. package/web/.next/server/app/api/attachments/upload/route_client-reference-manifest.js +2 -0
  127. package/web/.next/server/app/api/feature-logs/route.js +1 -1
  128. package/web/.next/server/app/api/feature-logs/route.js.nft.json +1 -1
  129. package/web/.next/server/app/settings/page/server-reference-manifest.json +5 -5
  130. package/web/.next/server/app/settings/page.js +2 -2
  131. package/web/.next/server/app/settings/page.js.nft.json +1 -1
  132. package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  133. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  134. package/web/.next/server/app/skills/page.js +2 -2
  135. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  136. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  137. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  138. package/web/.next/server/app/tools/page.js +2 -2
  139. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  140. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  141. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  142. package/web/.next/server/app/version/page.js +2 -2
  143. package/web/.next/server/app/version/page.js.nft.json +1 -1
  144. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  145. package/web/.next/server/app-paths-manifest.json +2 -0
  146. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -1
  147. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_attachments_preview_route_actions_0f48c3e9.js +3 -0
  148. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_attachments_preview_route_actions_0f48c3e9.js.map +1 -0
  149. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_attachments_upload_route_actions_48fe4a8f.js +3 -0
  150. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_attachments_upload_route_actions_48fe4a8f.js.map +1 -0
  151. package/web/.next/server/chunks/[root-of-the-server]__0b10355c._.js +3 -0
  152. package/web/.next/server/chunks/[root-of-the-server]__0b10355c._.js.map +1 -0
  153. package/web/.next/server/chunks/{[root-of-the-server]__91d44424._.js → [root-of-the-server]__52c318d1._.js} +2 -2
  154. package/web/.next/server/chunks/[root-of-the-server]__b2f9a412._.js +3 -0
  155. package/web/.next/server/chunks/[root-of-the-server]__b2f9a412._.js.map +1 -0
  156. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  157. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  158. package/web/.next/server/chunks/ssr/7f428_lucide-react_dist_esm_icons_9751e686._.js +3 -0
  159. package/web/.next/server/chunks/ssr/7f428_lucide-react_dist_esm_icons_9751e686._.js.map +1 -0
  160. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_da7eef33.js +1 -1
  161. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_da7eef33.js.map +1 -1
  162. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_0a9251aa.js +1 -1
  163. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_0a9251aa.js.map +1 -1
  164. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js +1 -1
  165. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js.map +1 -1
  166. package/web/.next/server/chunks/ssr/[root-of-the-server]__17143953._.js +1 -1
  167. package/web/.next/server/chunks/ssr/[root-of-the-server]__17143953._.js.map +1 -1
  168. package/web/.next/server/chunks/ssr/[root-of-the-server]__357d99f9._.js +1 -1
  169. package/web/.next/server/chunks/ssr/[root-of-the-server]__4be9a9bb._.js +1 -1
  170. package/web/.next/server/chunks/ssr/[root-of-the-server]__5d87af2e._.js +1 -1
  171. package/web/.next/server/chunks/ssr/{[root-of-the-server]__3534949f._.js → [root-of-the-server]__8d9536c4._.js} +2 -2
  172. package/web/.next/server/chunks/ssr/[root-of-the-server]__9de266fa._.js +1 -1
  173. package/web/.next/server/chunks/ssr/[root-of-the-server]__9de266fa._.js.map +1 -1
  174. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  175. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  176. package/web/.next/server/chunks/ssr/[root-of-the-server]__b14946f5._.js +10 -0
  177. package/web/.next/server/chunks/ssr/{[root-of-the-server]__c3694f82._.js.map → [root-of-the-server]__b14946f5._.js.map} +1 -1
  178. package/web/.next/server/chunks/ssr/[root-of-the-server]__b3a4cef4._.js +1 -1
  179. package/web/.next/server/chunks/ssr/[root-of-the-server]__b3a4cef4._.js.map +1 -1
  180. package/web/.next/server/chunks/ssr/[root-of-the-server]__b65f78cf._.js +3 -0
  181. package/web/.next/server/chunks/ssr/{[root-of-the-server]__55f60c9d._.js.map → [root-of-the-server]__b65f78cf._.js.map} +1 -1
  182. package/web/.next/server/chunks/ssr/[root-of-the-server]__f0d8d054._.js +2 -2
  183. package/web/.next/server/chunks/ssr/[root-of-the-server]__f0d8d054._.js.map +1 -1
  184. package/web/.next/server/chunks/ssr/{[root-of-the-server]__ad2b2a3c._.js → [root-of-the-server]__f1a05100._.js} +2 -2
  185. package/web/.next/server/chunks/ssr/[root-of-the-server]__f8210f45._.js +3 -0
  186. package/web/.next/server/chunks/ssr/{[root-of-the-server]__01a37f7a._.js.map → [root-of-the-server]__f8210f45._.js.map} +1 -1
  187. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js +1 -1
  188. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js.map +1 -1
  189. package/web/.next/server/chunks/ssr/_1bb131c4._.js +1 -1
  190. package/web/.next/server/chunks/ssr/_1bb131c4._.js.map +1 -1
  191. package/web/.next/server/chunks/ssr/_1e3cf73b._.js +1 -1
  192. package/web/.next/server/chunks/ssr/_1e3cf73b._.js.map +1 -1
  193. package/web/.next/server/chunks/ssr/_34fbed18._.js +1 -1
  194. package/web/.next/server/chunks/ssr/_34fbed18._.js.map +1 -1
  195. package/web/.next/server/chunks/ssr/_63868199._.js +1 -1
  196. package/web/.next/server/chunks/ssr/_63868199._.js.map +1 -1
  197. package/web/.next/server/chunks/ssr/{_3785d5be._.js → _6bffa94a._.js} +2 -2
  198. package/web/.next/server/chunks/ssr/_6bffa94a._.js.map +1 -0
  199. package/web/.next/server/chunks/ssr/_6e08ce21._.js.map +1 -1
  200. package/web/.next/server/chunks/ssr/_8dacd1e7._.js +1 -1
  201. package/web/.next/server/chunks/ssr/_8dacd1e7._.js.map +1 -1
  202. package/web/.next/server/chunks/ssr/_9ac70f88._.js +5 -0
  203. package/web/.next/server/chunks/ssr/_9ac70f88._.js.map +1 -0
  204. package/web/.next/server/chunks/ssr/{_3e88ce9d._.js → _cf366ac8._.js} +2 -2
  205. package/web/.next/server/chunks/ssr/{_3e88ce9d._.js.map → _cf366ac8._.js.map} +1 -1
  206. package/web/.next/server/chunks/ssr/_d42594dd._.js +3 -0
  207. package/web/.next/server/chunks/ssr/_d42594dd._.js.map +1 -0
  208. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
  209. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
  210. package/web/.next/server/chunks/ssr/node_modules__pnpm_07f6911c._.js +3 -0
  211. package/web/.next/server/chunks/ssr/node_modules__pnpm_07f6911c._.js.map +1 -0
  212. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_021a05f2.js +1 -1
  213. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_021a05f2.js.map +1 -1
  214. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js +1 -1
  215. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js.map +1 -1
  216. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js +1 -1
  217. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js.map +1 -1
  218. package/web/.next/server/chunks/ssr/src_presentation_web_c4348b8a._.js +2 -3
  219. package/web/.next/server/chunks/ssr/src_presentation_web_c4348b8a._.js.map +1 -1
  220. package/web/.next/server/chunks/ssr/src_presentation_web_components_80b5b4bc._.js +3 -0
  221. package/web/.next/server/chunks/ssr/src_presentation_web_components_80b5b4bc._.js.map +1 -0
  222. package/web/.next/server/chunks/ssr/src_presentation_web_components_82416b8f._.js +3 -0
  223. package/web/.next/server/chunks/ssr/src_presentation_web_components_82416b8f._.js.map +1 -0
  224. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  225. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  226. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_tools_tools-page-client_tsx_3d0aa70c._.js +1 -1
  227. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_tools_tools-page-client_tsx_3d0aa70c._.js.map +1 -1
  228. package/web/.next/server/chunks/ssr/src_presentation_web_e4510d10._.js +2 -3
  229. package/web/.next/server/chunks/ssr/src_presentation_web_e4510d10._.js.map +1 -1
  230. package/web/.next/server/pages/500.html +2 -2
  231. package/web/.next/server/server-reference-manifest.js +1 -1
  232. package/web/.next/server/server-reference-manifest.json +28 -28
  233. package/web/.next/static/chunks/{4de791d92c72ae58.js → 26f58bbf4766e1c4.js} +1 -1
  234. package/web/.next/static/chunks/30c827a88342741e.js +1 -0
  235. package/web/.next/static/chunks/{d00436ccde44862f.js → 31c1e96c4cfeb16b.js} +2 -2
  236. package/web/.next/static/chunks/50f89c3112f6dd23.css +2 -0
  237. package/web/.next/static/chunks/52b56677c842e45f.js +3 -0
  238. package/web/.next/static/chunks/{48060fe38d7b8432.js → 5b707c839eafb404.js} +3 -3
  239. package/web/.next/static/chunks/79f610312f57dc66.js +1 -0
  240. package/web/.next/static/chunks/8fa4bf83842a3389.js +1 -0
  241. package/web/.next/static/chunks/{0fb7745c30469b39.js → b31f2c82654437a6.js} +1 -1
  242. package/web/.next/static/chunks/c9333b53106c0ca9.js +1 -0
  243. package/web/.next/static/chunks/ce7a578a83924232.js +1 -0
  244. package/web/.next/static/chunks/{de2515446676e0ae.js → d8e93250744112df.js} +1 -1
  245. package/web/.next/static/chunks/da7d7608abd1032f.js +1 -0
  246. package/web/.next/static/chunks/{6b4620c8b57a0683.js → df38705b7992b33f.js} +1 -1
  247. package/web/.next/server/chunks/ssr/7f428_lucide-react_dist_esm_icons_c7cfe351._.js +0 -3
  248. package/web/.next/server/chunks/ssr/7f428_lucide-react_dist_esm_icons_c7cfe351._.js.map +0 -1
  249. package/web/.next/server/chunks/ssr/[root-of-the-server]__01a37f7a._.js +0 -3
  250. package/web/.next/server/chunks/ssr/[root-of-the-server]__55f60c9d._.js +0 -3
  251. package/web/.next/server/chunks/ssr/[root-of-the-server]__c3694f82._.js +0 -10
  252. package/web/.next/server/chunks/ssr/_0387eb83._.js +0 -3
  253. package/web/.next/server/chunks/ssr/_0387eb83._.js.map +0 -1
  254. package/web/.next/server/chunks/ssr/_3785d5be._.js.map +0 -1
  255. package/web/.next/server/chunks/ssr/_67dd9041._.js +0 -3
  256. package/web/.next/server/chunks/ssr/_67dd9041._.js.map +0 -1
  257. package/web/.next/server/chunks/ssr/_c3a8bcb1._.js +0 -3
  258. package/web/.next/server/chunks/ssr/_c3a8bcb1._.js.map +0 -1
  259. package/web/.next/server/chunks/ssr/node_modules__pnpm_04f4e45f._.js +0 -5
  260. package/web/.next/server/chunks/ssr/node_modules__pnpm_04f4e45f._.js.map +0 -1
  261. package/web/.next/server/chunks/ssr/node_modules__pnpm_c931457d._.js +0 -3
  262. package/web/.next/server/chunks/ssr/node_modules__pnpm_c931457d._.js.map +0 -1
  263. package/web/.next/static/chunks/2a24ac257fc1c10b.js +0 -1
  264. package/web/.next/static/chunks/6d0190cd8484320c.js +0 -1
  265. package/web/.next/static/chunks/6f0622cb06faf358.js +0 -1
  266. package/web/.next/static/chunks/7e92542991a14568.js +0 -1
  267. package/web/.next/static/chunks/a56b3854d2169db0.js +0 -1
  268. package/web/.next/static/chunks/ae01d2be1d0bad47.css +0 -2
  269. package/web/.next/static/chunks/df0b93fbb9b512e6.js +0 -1
  270. package/web/.next/static/chunks/e405f0502431ea60.js +0 -3
  271. /package/web/.next/server/chunks/{[root-of-the-server]__91d44424._.js.map → [root-of-the-server]__52c318d1._.js.map} +0 -0
  272. /package/web/.next/server/chunks/ssr/{[root-of-the-server]__3534949f._.js.map → [root-of-the-server]__8d9536c4._.js.map} +0 -0
  273. /package/web/.next/server/chunks/ssr/{[root-of-the-server]__ad2b2a3c._.js.map → [root-of-the-server]__f1a05100._.js.map} +0 -0
  274. /package/web/.next/static/{7IllAyl6ItP_HYk-RfUYQ → Lf0zFNsMS4qCNRm_LSgya}/_buildManifest.js +0 -0
  275. /package/web/.next/static/{7IllAyl6ItP_HYk-RfUYQ → Lf0zFNsMS4qCNRm_LSgya}/_clientMiddlewareManifest.json +0 -0
  276. /package/web/.next/static/{7IllAyl6ItP_HYk-RfUYQ → Lf0zFNsMS4qCNRm_LSgya}/_ssgManifest.js +0 -0
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState, useCallback, useEffect, useRef } from 'react';
4
- import { PlusIcon, FileIcon, FileTextIcon, ImageIcon, CodeIcon, Trash2Icon, ChevronsUpDown, CheckIcon, Zap, } from 'lucide-react';
4
+ import { PaperclipIcon, ChevronsUpDown, CheckIcon, Zap } from 'lucide-react';
5
5
  import { cn } from '../../../lib/utils.js';
6
6
  import { useSoundAction } from '../../../hooks/use-sound-action.js';
7
7
  import { BaseDrawer } from '../../common/base-drawer/index.js';
@@ -15,8 +15,71 @@ import { Popover, PopoverContent, PopoverTrigger } from '../../ui/popover.js';
15
15
  import { Switch } from '../../ui/switch.js';
16
16
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip.js';
17
17
  import { useGuardedDrawerClose } from '../../../hooks/drawer-close-guard.js';
18
+ import { AttachmentChip } from '../../common/attachment-chip/index.js';
18
19
  import { AgentModelPicker } from '../../features/settings/AgentModelPicker/index.js';
19
20
  import { pickFiles } from './pick-files.js';
21
+ const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
22
+ const ALLOWED_EXTENSIONS = new Set([
23
+ '.png',
24
+ '.jpg',
25
+ '.jpeg',
26
+ '.gif',
27
+ '.webp',
28
+ '.svg',
29
+ '.bmp',
30
+ '.ico',
31
+ '.pdf',
32
+ '.doc',
33
+ '.docx',
34
+ '.xls',
35
+ '.xlsx',
36
+ '.ppt',
37
+ '.pptx',
38
+ '.txt',
39
+ '.md',
40
+ '.csv',
41
+ '.json',
42
+ '.yaml',
43
+ '.yml',
44
+ '.xml',
45
+ '.ts',
46
+ '.tsx',
47
+ '.js',
48
+ '.jsx',
49
+ '.py',
50
+ '.rb',
51
+ '.go',
52
+ '.rs',
53
+ '.java',
54
+ '.c',
55
+ '.cpp',
56
+ '.h',
57
+ '.hpp',
58
+ '.cs',
59
+ '.swift',
60
+ '.kt',
61
+ '.html',
62
+ '.css',
63
+ '.scss',
64
+ '.less',
65
+ '.sh',
66
+ '.bash',
67
+ '.zsh',
68
+ '.fish',
69
+ '.toml',
70
+ '.ini',
71
+ '.cfg',
72
+ '.conf',
73
+ '.env',
74
+ '.zip',
75
+ '.tar',
76
+ '.gz',
77
+ '.log',
78
+ ]);
79
+ function getExtension(filename) {
80
+ const dot = filename.lastIndexOf('.');
81
+ return dot >= 0 ? filename.slice(dot).toLowerCase() : '';
82
+ }
20
83
  const AUTO_APPROVE_OPTIONS = [
21
84
  { id: 'allowPrd', label: 'PRD', description: 'Auto-approve requirements move to planning.' },
22
85
  { id: 'allowPlan', label: 'Plan', description: 'Auto-approve planning move to implementation.' },
@@ -41,6 +104,11 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
41
104
  const [fast, setFast] = useState(false);
42
105
  const [overrideAgent, setOverrideAgent] = useState(undefined);
43
106
  const [overrideModel, setOverrideModel] = useState(undefined);
107
+ const [isDragOver, setIsDragOver] = useState(false);
108
+ const [uploadError, setUploadError] = useState(null);
109
+ // Stable sessionId per mount — used for upload dedup grouping
110
+ const sessionIdRef = useRef(crypto.randomUUID());
111
+ const dragCounterRef = useRef(0);
44
112
  // Sync state when workflowDefaults load asynchronously
45
113
  useEffect(() => {
46
114
  if (workflowDefaults) {
@@ -65,11 +133,121 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
65
133
  setFast(false);
66
134
  setOverrideAgent(undefined);
67
135
  setOverrideModel(undefined);
136
+ setUploadError(null);
137
+ dragCounterRef.current = 0;
138
+ setIsDragOver(false);
68
139
  }, [defaultGates, defaultPush, defaultOpenPr]);
69
140
  // Track whether the form has unsaved data
70
141
  const isDirty = description.trim() !== '' || attachments.length > 0;
71
142
  // Shared close guard — shows confirmation when dirty, prevents navigation
72
143
  const { attemptClose } = useGuardedDrawerClose({ open, isDirty, onClose, onReset: resetForm });
144
+ /** Validate and upload files from drop or paste. */
145
+ const handleFiles = useCallback(async (fileList) => {
146
+ setUploadError(null);
147
+ for (const file of fileList) {
148
+ // Client-side size validation
149
+ if (file.size > MAX_FILE_SIZE) {
150
+ setUploadError(`"${file.name}" exceeds 10 MB limit`);
151
+ return;
152
+ }
153
+ // Client-side extension validation
154
+ const ext = getExtension(file.name);
155
+ if (ext && !ALLOWED_EXTENSIONS.has(ext)) {
156
+ setUploadError(`File type "${ext}" is not allowed`);
157
+ return;
158
+ }
159
+ }
160
+ for (const file of fileList) {
161
+ const tempId = crypto.randomUUID();
162
+ // Optimistic loading placeholder
163
+ setAttachments((prev) => [
164
+ ...prev,
165
+ {
166
+ id: tempId,
167
+ name: file.name,
168
+ size: file.size,
169
+ mimeType: file.type || 'application/octet-stream',
170
+ path: '',
171
+ loading: true,
172
+ },
173
+ ]);
174
+ try {
175
+ const formData = new FormData();
176
+ formData.append('file', file);
177
+ formData.append('sessionId', sessionIdRef.current);
178
+ const res = await fetch('/api/attachments/upload', {
179
+ method: 'POST',
180
+ body: formData,
181
+ });
182
+ if (!res.ok) {
183
+ const body = await res.json().catch(() => ({ error: 'Upload failed' }));
184
+ // Remove loading placeholder on error
185
+ setAttachments((prev) => prev.filter((a) => a.id !== tempId));
186
+ setUploadError(body.error ?? 'Upload failed');
187
+ return;
188
+ }
189
+ const uploaded = await res.json();
190
+ // Server dedup may return the same path for identical content — drop the duplicate.
191
+ setAttachments((prev) => {
192
+ const isDupe = prev.some((a) => a.id !== tempId && a.path === uploaded.path);
193
+ if (isDupe)
194
+ return prev.filter((a) => a.id !== tempId);
195
+ return prev.map((a) => a.id === tempId ? { ...uploaded, id: tempId, loading: false } : a);
196
+ });
197
+ }
198
+ catch {
199
+ setAttachments((prev) => prev.filter((a) => a.id !== tempId));
200
+ setUploadError('Upload failed');
201
+ }
202
+ }
203
+ }, []);
204
+ const handleDragEnter = useCallback((e) => {
205
+ e.preventDefault();
206
+ e.stopPropagation();
207
+ dragCounterRef.current += 1;
208
+ if (dragCounterRef.current === 1) {
209
+ setIsDragOver(true);
210
+ }
211
+ }, []);
212
+ const handleDragLeave = useCallback((e) => {
213
+ e.preventDefault();
214
+ e.stopPropagation();
215
+ dragCounterRef.current -= 1;
216
+ if (dragCounterRef.current === 0) {
217
+ setIsDragOver(false);
218
+ }
219
+ }, []);
220
+ const handleDragOver = useCallback((e) => {
221
+ e.preventDefault();
222
+ e.stopPropagation();
223
+ }, []);
224
+ const handleDrop = useCallback((e) => {
225
+ e.preventDefault();
226
+ e.stopPropagation();
227
+ dragCounterRef.current = 0;
228
+ setIsDragOver(false);
229
+ const files = Array.from(e.dataTransfer.files);
230
+ if (files.length > 0) {
231
+ handleFiles(files);
232
+ }
233
+ }, [handleFiles]);
234
+ const handlePaste = useCallback((e) => {
235
+ const items = e.clipboardData?.items;
236
+ if (!items)
237
+ return;
238
+ const files = [];
239
+ for (const item of Array.from(items)) {
240
+ if (item.kind === 'file') {
241
+ const file = item.getAsFile();
242
+ if (file)
243
+ files.push(file);
244
+ }
245
+ }
246
+ if (files.length > 0) {
247
+ e.preventDefault();
248
+ handleFiles(files);
249
+ }
250
+ }, [handleFiles]);
73
251
  const handleSubmit = useCallback((e) => {
74
252
  e.preventDefault();
75
253
  if (!description.trim())
@@ -77,7 +255,7 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
77
255
  createSound.play();
78
256
  onSubmit({
79
257
  description: description.trim(),
80
- attachments,
258
+ attachments: attachments.filter((a) => !a.loading),
81
259
  repositoryPath,
82
260
  approvalGates: {
83
261
  allowPrd: approvalGates.allowPrd ?? false,
@@ -90,6 +268,7 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
90
268
  ...(overrideAgent ? { agentType: overrideAgent } : {}),
91
269
  ...(overrideModel ? { model: overrideModel } : {}),
92
270
  ...(parentId ? { parentId } : {}),
271
+ sessionId: sessionIdRef.current,
93
272
  });
94
273
  resetForm();
95
274
  }, [
@@ -111,15 +290,27 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
111
290
  try {
112
291
  const files = await pickFiles();
113
292
  if (files) {
114
- setAttachments((prev) => [...prev, ...files]);
293
+ setAttachments((prev) => {
294
+ const existingPaths = new Set(prev.map((f) => f.path));
295
+ const unique = files
296
+ .filter((f) => !existingPaths.has(f.path))
297
+ .map((f) => ({
298
+ id: crypto.randomUUID(),
299
+ name: f.name,
300
+ size: f.size,
301
+ mimeType: 'application/octet-stream',
302
+ path: f.path,
303
+ }));
304
+ return unique.length > 0 ? [...prev, ...unique] : prev;
305
+ });
115
306
  }
116
307
  }
117
308
  catch {
118
309
  // Native dialog failed — silently ignore (user can retry)
119
310
  }
120
311
  }, []);
121
- const handleRemoveFile = useCallback((path) => {
122
- setAttachments((prev) => prev.filter((f) => f.path !== path));
312
+ const handleRemoveFile = useCallback((id) => {
313
+ setAttachments((prev) => prev.filter((f) => f.id !== id));
123
314
  }, []);
124
315
  const formRef = useRef(null);
125
316
  const handleKeyDown = useCallback((e) => {
@@ -129,10 +320,10 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
129
320
  }
130
321
  }, []);
131
322
  const hasFeatures = features && features.length > 0;
132
- return (_jsx(BaseDrawer, { open: open, onClose: attemptClose, size: "md", modal: false, dismissOnOutsideClick: true, "data-testid": "feature-create-drawer", header: _jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-2.5 w-2.5 shrink-0 rounded-full bg-blue-500" }), _jsx(DrawerTitle, { children: "NEW FEATURE" })] }), isSubmitting ? (_jsx(DrawerDescription, { asChild: true, children: _jsx("div", { children: _jsx(Badge, { variant: "secondary", children: "Creating..." }) }) })) : null] }), footer: _jsxs("div", { className: "flex flex-row justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: attemptClose, disabled: isSubmitting, children: "Cancel" }), _jsx(Button, { type: "submit", form: "create-feature-form", disabled: !description.trim() || isSubmitting, children: isSubmitting ? 'Creating...' : '+ Create Feature' })] }), children: _jsx("div", { className: "overflow-y-auto p-4", children: _jsx(TooltipProvider, { delayDuration: 400, children: _jsxs("form", { ref: formRef, id: "create-feature-form", onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-description", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "DESCRIBE YOUR FEATURE" }), _jsxs("div", { className: "border-input focus-within:ring-ring/50 focus-within:border-ring overflow-hidden rounded-md border shadow-xs transition-[color,box-shadow] focus-within:ring-[3px]", children: [_jsx(Textarea, { id: "feature-description", placeholder: "e.g. Add GitHub OAuth login with callback handling and token refresh...", value: description, onChange: (e) => setDescription(e.target.value), onKeyDown: handleKeyDown, rows: 7, required: true, disabled: isSubmitting, className: "field-sizing-fixed min-h-42! resize-none rounded-none border-0 shadow-none focus-visible:ring-0" }), _jsxs("div", { className: "border-input flex items-center gap-3 border-t px-3 py-1.5", children: [_jsx(AgentModelPicker, { initialAgentType: overrideAgent ?? currentAgentType ?? 'claude-code', initialModel: overrideModel ?? currentModel ?? 'claude-sonnet-4-6', mode: "override", onAgentModelChange: (agent, model) => {
323
+ return (_jsx(BaseDrawer, { open: open, onClose: attemptClose, size: "md", modal: false, dismissOnOutsideClick: true, "data-testid": "feature-create-drawer", header: _jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-2.5 w-2.5 shrink-0 rounded-full bg-blue-500" }), _jsx(DrawerTitle, { children: "NEW FEATURE" })] }), isSubmitting ? (_jsx(DrawerDescription, { asChild: true, children: _jsx("div", { children: _jsx(Badge, { variant: "secondary", children: "Creating..." }) }) })) : null] }), footer: _jsxs("div", { className: "flex flex-row justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: attemptClose, disabled: isSubmitting, children: "Cancel" }), _jsx(Button, { type: "submit", form: "create-feature-form", disabled: !description.trim() || isSubmitting, children: isSubmitting ? 'Creating...' : '+ Create Feature' })] }), children: _jsx("div", { className: "overflow-y-auto p-4", children: _jsx(TooltipProvider, { delayDuration: 400, children: _jsxs("form", { ref: formRef, id: "create-feature-form", onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("div", { role: "region", "aria-label": "File drop zone", "data-drag-over": isDragOver ? 'true' : 'false', onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, className: cn('flex flex-col gap-1.5 rounded-md border-2 border-transparent p-1 transition-colors', isDragOver && 'border-primary/50 bg-primary/5'), children: [_jsx(Label, { htmlFor: "feature-description", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "DESCRIBE YOUR FEATURE" }), _jsxs("div", { className: "border-input focus-within:ring-ring/50 focus-within:border-ring flex h-56 flex-col overflow-hidden rounded-md border shadow-xs transition-[color,box-shadow] focus-within:ring-[3px]", children: [_jsx(Textarea, { id: "feature-description", placeholder: "e.g. Add GitHub OAuth login with callback handling and token refresh...", value: description, onChange: (e) => setDescription(e.target.value), onKeyDown: handleKeyDown, onPaste: handlePaste, required: true, disabled: isSubmitting, className: "min-h-0 flex-1 resize-none rounded-none border-0 shadow-none focus-visible:ring-0" }), 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: isSubmitting, 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-3 border-t px-3 py-1.5", children: [_jsx(AgentModelPicker, { initialAgentType: overrideAgent ?? currentAgentType ?? 'claude-code', initialModel: overrideModel ?? currentModel ?? 'claude-sonnet-4-6', mode: "override", onAgentModelChange: (agent, model) => {
133
324
  setOverrideAgent(agent);
134
325
  setOverrideModel(model);
135
- }, disabled: isSubmitting, className: "w-55" }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("div", { className: "ml-auto flex cursor-pointer items-center gap-2", children: [_jsx(Switch, { id: "fast-mode", checked: fast, onCheckedChange: setFast, disabled: isSubmitting }), _jsxs(Label, { htmlFor: "fast-mode", className: "flex cursor-pointer items-center gap-1 text-sm font-medium", children: [_jsx(Zap, { className: "h-3.5 w-3.5" }), "Fast Mode"] })] }) }), _jsx(TooltipContent, { side: "bottom", children: "Skip SDLC phases and implement directly from your prompt." })] })] })] })] }), hasFeatures && initialParentId !== undefined ? (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "parent-feature", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "PARENT FEATURE" }), _jsx(ParentFeatureCombobox, { features: features, value: parentId, onChange: setParentId, disabled: isSubmitting })] })) : null, _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "border-input flex items-center gap-4 rounded-md border px-3 py-2.5", children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: "text-muted-foreground w-16 shrink-0 cursor-default text-xs font-semibold tracking-wider", children: "APPROVE" }) }), _jsx(TooltipContent, { side: "bottom", children: "Auto-approve phase transitions without manual review." })] }), _jsx("div", { className: "flex flex-1 items-center gap-4", children: AUTO_APPROVE_OPTIONS.map((opt) => (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("div", { className: "flex cursor-pointer items-center gap-1.5", children: [_jsx(Switch, { id: `approve-${opt.id}`, size: "sm", checked: approvalGates[opt.id] ?? false, onCheckedChange: (v) => setApprovalGates((prev) => ({ ...prev, [opt.id]: v })), disabled: isSubmitting ||
326
+ }, disabled: isSubmitting, className: "w-55" }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("div", { className: "ml-auto flex cursor-pointer items-center gap-2", children: [_jsx(Switch, { id: "fast-mode", checked: fast, onCheckedChange: setFast, disabled: isSubmitting }), _jsxs(Label, { htmlFor: "fast-mode", className: "flex cursor-pointer items-center gap-1 text-sm font-medium", children: [_jsx(Zap, { className: "h-3.5 w-3.5" }), "Fast Mode"] })] }) }), _jsx(TooltipContent, { side: "bottom", children: "Skip SDLC phases and implement directly from your prompt." })] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: handleAddFiles, disabled: isSubmitting, "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: "bottom", children: "Attach files" })] })] })] })] }), hasFeatures && initialParentId !== undefined ? (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "parent-feature", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "PARENT FEATURE" }), _jsx(ParentFeatureCombobox, { features: features, value: parentId, onChange: setParentId, disabled: isSubmitting })] })) : null, _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "border-input flex items-center gap-4 rounded-md border px-3 py-2.5", children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: "text-muted-foreground w-16 shrink-0 cursor-default text-xs font-semibold tracking-wider", children: "APPROVE" }) }), _jsx(TooltipContent, { side: "bottom", children: "Auto-approve phase transitions without manual review." })] }), _jsx("div", { className: "flex flex-1 items-center gap-4", children: AUTO_APPROVE_OPTIONS.map((opt) => (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("div", { className: "flex cursor-pointer items-center gap-1.5", children: [_jsx(Switch, { id: `approve-${opt.id}`, size: "sm", checked: approvalGates[opt.id] ?? false, onCheckedChange: (v) => setApprovalGates((prev) => ({ ...prev, [opt.id]: v })), disabled: isSubmitting ||
136
327
  (fast && (opt.id === 'allowPrd' || opt.id === 'allowPlan')) }), _jsx(Label, { htmlFor: `approve-${opt.id}`, className: "cursor-pointer text-xs font-medium", children: opt.label })] }) }), _jsx(TooltipContent, { side: "bottom", children: fast && (opt.id === 'allowPrd' || opt.id === 'allowPlan')
137
328
  ? 'Skipped in Fast Mode'
138
329
  : opt.description })] }, opt.id))) }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: () => {
@@ -145,7 +336,7 @@ export function FeatureCreateDrawer({ open, onClose, onSubmit, repositoryPath, i
145
336
  setPush(v);
146
337
  if (!v && openPr)
147
338
  setOpenPr(false);
148
- }, disabled: isSubmitting }), _jsx(Label, { htmlFor: "push", className: "cursor-pointer text-xs font-medium", children: "Push" })] }) }), _jsx(TooltipContent, { side: "bottom", children: "Push branch to remote after implementation." })] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("div", { className: "flex cursor-pointer items-center gap-1.5", children: [_jsx(Switch, { id: "open-pr", size: "sm", checked: openPr, onCheckedChange: setOpenPr, disabled: isSubmitting }), _jsx(Label, { htmlFor: "open-pr", className: "cursor-pointer text-xs font-medium", children: "PR" })] }) }), _jsx(TooltipContent, { side: "bottom", children: "Open a pull request after pushing." })] })] })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: ["ATTACHMENTS", attachments.length > 0 && (_jsxs("span", { className: "text-muted-foreground/60 ml-1.5", children: ["(", attachments.length, ")"] }))] }), _jsxs(Button, { type: "button", variant: "outline", size: "xs", onClick: handleAddFiles, disabled: isSubmitting, children: [_jsx(PlusIcon, { className: "size-3" }), "Add Files"] })] }), attachments.length > 0 && (_jsx("div", { className: "flex flex-col gap-2", children: attachments.map((file) => (_jsx(AttachmentCard, { file: file, onRemove: () => handleRemoveFile(file.path), disabled: isSubmitting }, file.path))) }))] })] }) }) }) }));
339
+ }, disabled: isSubmitting }), _jsx(Label, { htmlFor: "push", className: "cursor-pointer text-xs font-medium", children: "Push" })] }) }), _jsx(TooltipContent, { side: "bottom", children: "Push branch to remote after implementation." })] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("div", { className: "flex cursor-pointer items-center gap-1.5", children: [_jsx(Switch, { id: "open-pr", size: "sm", checked: openPr, onCheckedChange: setOpenPr, disabled: isSubmitting }), _jsx(Label, { htmlFor: "open-pr", className: "cursor-pointer text-xs font-medium", children: "PR" })] }) }), _jsx(TooltipContent, { side: "bottom", children: "Open a pull request after pushing." })] })] })] })] })] }) }) }) }));
149
340
  }
150
341
  function ParentFeatureCombobox({ features, value, onChange, disabled, }) {
151
342
  const [open, setOpen] = useState(false);
@@ -173,55 +364,3 @@ function ParentFeatureCombobox({ features, value, onChange, disabled, }) {
173
364
  ? `${selectedFeature.name} (${selectedFeature.id.slice(0, 8)})`
174
365
  : 'Select parent feature...' }), _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "w-80 p-0", align: "start", "data-testid": "parent-feature-combobox-content", children: _jsxs("div", { className: "flex flex-col", children: [_jsx("div", { className: "border-b p-2", children: _jsx(Input, { ref: inputRef, placeholder: "Search features...", value: query, onChange: (e) => setQuery(e.target.value), className: "h-8 border-0 p-0 text-sm shadow-none focus-visible:ring-0", "data-testid": "parent-feature-search" }) }), _jsxs("div", { className: "max-h-48 overflow-y-auto py-1", role: "listbox", "aria-label": "Features", children: [_jsxs("button", { type: "button", role: "option", "aria-selected": value === undefined, onClick: () => handleSelect(undefined), className: cn('hover:bg-accent hover:text-accent-foreground flex w-full items-center gap-2 px-3 py-2 text-sm', value === undefined && 'bg-accent/50'), "data-testid": "parent-feature-option-none", children: [_jsx(CheckIcon, { className: cn('h-4 w-4 shrink-0', value !== undefined && 'invisible') }), _jsx("span", { className: "text-muted-foreground italic", children: "No parent" })] }), filtered.length === 0 && query ? (_jsx("p", { className: "text-muted-foreground px-3 py-2 text-sm", children: "No features found." })) : (filtered.map((f) => (_jsxs("button", { type: "button", role: "option", "aria-selected": value === f.id, onClick: () => handleSelect(f.id), className: cn('hover:bg-accent hover:text-accent-foreground flex w-full items-center gap-2 px-3 py-2 text-sm', value === f.id && 'bg-accent/50'), "data-testid": `parent-feature-option-${f.id}`, children: [_jsx(CheckIcon, { className: cn('h-4 w-4 shrink-0', value !== f.id && 'invisible') }), _jsxs("span", { className: "truncate", children: [f.name, ' ', _jsxs("span", { className: "text-muted-foreground font-mono text-xs", children: ["(", f.id.slice(0, 8), ")"] })] })] }, f.id))))] })] }) })] }));
175
366
  }
176
- /* ---------------------------------------------------------------------------
177
- * Private sub-components & utilities
178
- * ------------------------------------------------------------------------- */
179
- function AttachmentCard({ file, onRemove, disabled, }) {
180
- const ext = getExtension(file.name);
181
- const Icon = getFileIcon(ext);
182
- const iconColorClass = getFileIconColor(ext);
183
- return (_jsxs("div", { className: "flex items-center gap-3 rounded-md border p-2", children: [_jsx("div", { className: cn('flex h-8 w-8 shrink-0 items-center justify-center rounded', iconColorClass), children: _jsx(Icon, { className: "h-4 w-4" }) }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "truncate text-sm font-medium", children: file.name }), _jsx("span", { className: "text-muted-foreground truncate text-xs", children: file.path }), _jsx("span", { className: "text-muted-foreground text-xs", children: formatFileSize(file.size) })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon-xs", onClick: onRemove, disabled: disabled, "aria-label": `Remove ${file.name}`, children: _jsx(Trash2Icon, { className: "h-3 w-3" }) })] }));
184
- }
185
- function getExtension(filename) {
186
- const dot = filename.lastIndexOf('.');
187
- return dot >= 0 ? filename.slice(dot).toLowerCase() : '';
188
- }
189
- const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.bmp']);
190
- const CODE_EXTS = new Set([
191
- '.ts',
192
- '.tsx',
193
- '.js',
194
- '.jsx',
195
- '.json',
196
- '.yaml',
197
- '.yml',
198
- '.xml',
199
- '.html',
200
- '.css',
201
- '.md',
202
- ]);
203
- function getFileIcon(ext) {
204
- if (IMAGE_EXTS.has(ext))
205
- return ImageIcon;
206
- if (ext === '.pdf')
207
- return FileTextIcon;
208
- if (CODE_EXTS.has(ext))
209
- return CodeIcon;
210
- return FileIcon;
211
- }
212
- function getFileIconColor(ext) {
213
- if (ext === '.pdf')
214
- return 'bg-red-50 text-red-600';
215
- if (IMAGE_EXTS.has(ext))
216
- return 'bg-blue-50 text-blue-600';
217
- if (CODE_EXTS.has(ext))
218
- return 'bg-emerald-50 text-emerald-600';
219
- return 'bg-gray-50 text-gray-600';
220
- }
221
- function formatFileSize(bytes) {
222
- if (bytes === 0)
223
- return '0 B';
224
- const units = ['B', 'KB', 'MB', 'GB'];
225
- const i = Math.floor(Math.log(bytes) / Math.log(1024));
226
- return `${(bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
227
- }
@@ -113,5 +113,11 @@ export declare const WithParentFeature: Story;
113
113
  * "Discard unsaved changes?" dialog. Demonstrates the dirty-form guard.
114
114
  */
115
115
  export declare const DiscardConfirmation: Story;
116
+ /**
117
+ * Drawer ready for drag-and-drop — open the drawer and drag a file over
118
+ * the "DESCRIBE YOUR FEATURE" area to see the drag-over highlight.
119
+ * In Storybook, the upload API isn't available, but the visual states render.
120
+ */
121
+ export declare const DragDropReady: Story;
116
122
  export declare const Interactive: Story;
117
123
  //# sourceMappingURL=feature-create-drawer.stories.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-create-drawer.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAU9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,mBAAmB,CAgC1C,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAmDlD,uFAAuF;AACvF,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,KAiBvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAMhC,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,SAAS,EAAE,KAMvB,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,KAM1B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,KAUxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,KAUrB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,KAUvB,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAE,KAUtB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS,EAAE,KAUvB,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,KAMzB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,KAUxB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAgBjC,CAAC;AAYF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,KAWlC,CAAC;AAkDF,+EAA+E;AAC/E,eAAO,MAAM,QAAQ,EAAE,KAEtB,CAAC;AAwCF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAM/B,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAgBjC,CAAC;AAMF,eAAO,MAAM,WAAW,EAAE,KAwCzB,CAAC"}
1
+ {"version":3,"file":"feature-create-drawer.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAU9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,mBAAmB,CAgC1C,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAmDlD,uFAAuF;AACvF,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,KAiBvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAMhC,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,SAAS,EAAE,KAMvB,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,KAM1B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,KAUxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,KAUrB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,KAUvB,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAE,KAUtB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS,EAAE,KAUvB,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,KAMzB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,KAUxB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAgBjC,CAAC;AAYF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,KAWlC,CAAC;AAkDF,+EAA+E;AAC/E,eAAO,MAAM,QAAQ,EAAE,KAEtB,CAAC;AAwCF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAM/B,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAgBjC,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,KAY3B,CAAC;AAMF,eAAO,MAAM,WAAW,EAAE,KAwCzB,CAAC"}
@@ -360,6 +360,24 @@ export const DiscardConfirmation = {
360
360
  await userEvent.click(cancelButton);
361
361
  },
362
362
  };
363
+ /* ---------------------------------------------------------------------------
364
+ * Attachment stories
365
+ * ------------------------------------------------------------------------- */
366
+ /**
367
+ * Drawer ready for drag-and-drop — open the drawer and drag a file over
368
+ * the "DESCRIBE YOUR FEATURE" area to see the drag-over highlight.
369
+ * In Storybook, the upload API isn't available, but the visual states render.
370
+ */
371
+ export const DragDropReady = {
372
+ render: () => _jsx(CreateDrawerTrigger, { label: "Open (Drag & Drop)" }),
373
+ play: async ({ canvasElement }) => {
374
+ const canvas = within(canvasElement);
375
+ await userEvent.click(canvas.getByRole('button', { name: 'Open (Drag & Drop)' }));
376
+ const body = within(canvasElement.ownerDocument.body);
377
+ const descInput = await body.findByPlaceholderText('e.g. Add GitHub OAuth login with callback handling and token refresh...');
378
+ await userEvent.type(descInput, 'Add attachment support with drag and drop');
379
+ },
380
+ };
363
381
  /* ---------------------------------------------------------------------------
364
382
  * Interactive story — full paths shown in submitted data panel
365
383
  * ------------------------------------------------------------------------- */
@@ -15,7 +15,7 @@ const DialogOverlay = React.forwardRef(({ className, onClick, ...props }, ref) =
15
15
  onClick?.(e);
16
16
  }, ...props })));
17
17
  DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
18
- const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { ref: ref, className: cn('bg-background fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg', className), ...props, children: [children, _jsxs(DialogPrimitive.Close, { className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none", children: [_jsx(X, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: "Close" })] })] })] })));
18
+ const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { ref: ref, className: cn('bg-background fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg', className), ...props, children: [children, _jsxs(DialogPrimitive.Close, { className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 cursor-pointer rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none", children: [_jsx(X, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: "Close" })] })] })] })));
19
19
  DialogContent.displayName = DialogPrimitive.Content.displayName;
20
20
  const DialogHeader = ({ className, ...props }) => (_jsx("div", { className: cn('flex flex-col space-y-1.5 text-center sm:text-left', className), ...props }));
21
21
  DialogHeader.displayName = 'DialogHeader';
@@ -1 +1 @@
1
- {"version":3,"file":"next.config.d.ts","sourceRoot":"","sources":["../../../../src/presentation/web/next.config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAgCvC,QAAA,MAAM,UAAU,EAAE,UA+BjB,CAAC;AAEF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"next.config.d.ts","sourceRoot":"","sources":["../../../../src/presentation/web/next.config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAgCvC,QAAA,MAAM,UAAU,EAAE,UAsCjB,CAAC;AAEF,eAAe,UAAU,CAAC"}
@@ -53,5 +53,11 @@ const nextConfig = {
53
53
  // Use standalone output mode for server-side rendering on demand
54
54
  // This prevents issues with statically prerendering pages that depend on client context
55
55
  output: 'standalone',
56
+ // Allow attachment uploads up to 11 MB (10 MB limit + multipart overhead)
57
+ experimental: {
58
+ serverActions: {
59
+ bodySizeLimit: '11mb',
60
+ },
61
+ },
56
62
  };
57
63
  export default nextConfig;