@shepai/cli 1.90.1 → 1.91.0-pr271.f9e060f

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 (155) hide show
  1. package/dist/packages/core/src/application/ports/output/services/worktree-service.interface.d.ts +6 -0
  2. package/dist/packages/core/src/application/ports/output/services/worktree-service.interface.d.ts.map +1 -1
  3. package/dist/packages/core/src/application/use-cases/features/cleanup-feature-worktree.use-case.d.ts.map +1 -1
  4. package/dist/packages/core/src/application/use-cases/features/cleanup-feature-worktree.use-case.js +8 -0
  5. package/dist/packages/core/src/infrastructure/services/git/worktree.service.d.ts +1 -0
  6. package/dist/packages/core/src/infrastructure/services/git/worktree.service.d.ts.map +1 -1
  7. package/dist/packages/core/src/infrastructure/services/git/worktree.service.js +8 -0
  8. package/dist/src/presentation/web/app/actions/get-feature-phase-timings.d.ts +2 -0
  9. package/dist/src/presentation/web/app/actions/get-feature-phase-timings.d.ts.map +1 -1
  10. package/dist/src/presentation/web/app/actions/get-feature-phase-timings.js +4 -0
  11. package/dist/src/presentation/web/app/api/attachments/preview/route.d.ts.map +1 -1
  12. package/dist/src/presentation/web/app/api/attachments/preview/route.js +47 -11
  13. package/dist/src/presentation/web/components/common/feature-drawer-tabs/activity-tab.d.ts.map +1 -1
  14. package/dist/src/presentation/web/components/common/feature-drawer-tabs/activity-tab.js +61 -18
  15. package/dist/src/presentation/web/components/common/feature-drawer-tabs/activity-tab.stories.d.ts +2 -0
  16. package/dist/src/presentation/web/components/common/feature-drawer-tabs/activity-tab.stories.d.ts.map +1 -1
  17. package/dist/src/presentation/web/components/common/feature-drawer-tabs/activity-tab.stories.js +73 -0
  18. package/dist/src/presentation/web/components/common/feature-drawer-tabs/overview-tab.d.ts.map +1 -1
  19. package/dist/src/presentation/web/components/common/feature-drawer-tabs/overview-tab.js +2 -1
  20. package/dist/src/presentation/web/components/common/inline-attachments/index.d.ts +3 -0
  21. package/dist/src/presentation/web/components/common/inline-attachments/index.d.ts.map +1 -0
  22. package/dist/src/presentation/web/components/common/inline-attachments/index.js +1 -0
  23. package/dist/src/presentation/web/components/common/inline-attachments/inline-attachments.d.ts +20 -0
  24. package/dist/src/presentation/web/components/common/inline-attachments/inline-attachments.d.ts.map +1 -0
  25. package/dist/src/presentation/web/components/common/inline-attachments/inline-attachments.js +72 -0
  26. package/dist/src/presentation/web/components/common/inline-attachments/inline-attachments.stories.d.ts +16 -0
  27. package/dist/src/presentation/web/components/common/inline-attachments/inline-attachments.stories.d.ts.map +1 -0
  28. package/dist/src/presentation/web/components/common/inline-attachments/inline-attachments.stories.js +46 -0
  29. package/dist/tsconfig.build.tsbuildinfo +1 -1
  30. package/package.json +1 -1
  31. package/web/.next/BUILD_ID +1 -1
  32. package/web/.next/build-manifest.json +2 -2
  33. package/web/.next/fallback-build-manifest.json +2 -2
  34. package/web/.next/prerender-manifest.json +3 -3
  35. package/web/.next/required-server-files.js +1 -1
  36. package/web/.next/required-server-files.json +1 -1
  37. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +18 -18
  38. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  39. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  40. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +21 -21
  41. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  42. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  43. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +14 -14
  44. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  45. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  46. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +18 -18
  47. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  48. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  49. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +21 -21
  50. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  51. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  52. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +14 -14
  53. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  54. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  55. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +14 -14
  56. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  57. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  58. package/web/.next/server/app/_global-error.html +2 -2
  59. package/web/.next/server/app/_global-error.rsc +1 -1
  60. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  61. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  62. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  63. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  64. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  65. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  66. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  67. package/web/.next/server/app/api/attachments/preview/route.js.nft.json +1 -1
  68. package/web/.next/server/app/settings/page/server-reference-manifest.json +5 -5
  69. package/web/.next/server/app/settings/page.js.nft.json +1 -1
  70. package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  71. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  72. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  73. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  74. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  75. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  76. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  77. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  78. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  79. package/web/.next/server/chunks/[root-of-the-server]__0b10355c._.js +1 -1
  80. package/web/.next/server/chunks/[root-of-the-server]__0b10355c._.js.map +1 -1
  81. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  82. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  83. package/web/.next/server/chunks/ssr/7f428_lucide-react_dist_esm_icons_9027d30a._.js +1 -1
  84. package/web/.next/server/chunks/ssr/7f428_lucide-react_dist_esm_icons_9027d30a._.js.map +1 -1
  85. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_da7eef33.js +1 -1
  86. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_da7eef33.js.map +1 -1
  87. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_0a9251aa.js +1 -1
  88. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_0a9251aa.js.map +1 -1
  89. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js +1 -1
  90. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js.map +1 -1
  91. package/web/.next/server/chunks/ssr/[root-of-the-server]__17143953._.js +1 -1
  92. package/web/.next/server/chunks/ssr/[root-of-the-server]__17143953._.js.map +1 -1
  93. package/web/.next/server/chunks/ssr/[root-of-the-server]__357d99f9._.js +1 -1
  94. package/web/.next/server/chunks/ssr/[root-of-the-server]__4be9a9bb._.js +1 -1
  95. package/web/.next/server/chunks/ssr/[root-of-the-server]__5d87af2e._.js +1 -1
  96. package/web/.next/server/chunks/ssr/[root-of-the-server]__9de266fa._.js +1 -1
  97. package/web/.next/server/chunks/ssr/[root-of-the-server]__9de266fa._.js.map +1 -1
  98. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  99. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  100. package/web/.next/server/chunks/ssr/[root-of-the-server]__b3a4cef4._.js +1 -1
  101. package/web/.next/server/chunks/ssr/[root-of-the-server]__b3a4cef4._.js.map +1 -1
  102. package/web/.next/server/chunks/ssr/[root-of-the-server]__c2173b6d._.js +1 -1
  103. package/web/.next/server/chunks/ssr/[root-of-the-server]__c2173b6d._.js.map +1 -1
  104. package/web/.next/server/chunks/ssr/[root-of-the-server]__f0d8d054._.js +2 -2
  105. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js +1 -1
  106. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js.map +1 -1
  107. package/web/.next/server/chunks/ssr/_135edb9c._.js +1 -1
  108. package/web/.next/server/chunks/ssr/_135edb9c._.js.map +1 -1
  109. package/web/.next/server/chunks/ssr/{_2fa756cd._.js → _3735fe72._.js} +2 -2
  110. package/web/.next/server/chunks/ssr/{_2fa756cd._.js.map → _3735fe72._.js.map} +1 -1
  111. package/web/.next/server/chunks/ssr/_752e01fd._.js +1 -1
  112. package/web/.next/server/chunks/ssr/_752e01fd._.js.map +1 -1
  113. package/web/.next/server/chunks/ssr/_8dacd1e7._.js +1 -1
  114. package/web/.next/server/chunks/ssr/_8dacd1e7._.js.map +1 -1
  115. package/web/.next/server/chunks/ssr/_cc77a143._.js +1 -1
  116. package/web/.next/server/chunks/ssr/_cc77a143._.js.map +1 -1
  117. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
  118. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
  119. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_021a05f2.js +1 -1
  120. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_021a05f2.js.map +1 -1
  121. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js +1 -1
  122. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js.map +1 -1
  123. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js +1 -1
  124. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js.map +1 -1
  125. package/web/.next/server/chunks/ssr/src_presentation_web_c4348b8a._.js +2 -2
  126. package/web/.next/server/chunks/ssr/src_presentation_web_c4348b8a._.js.map +1 -1
  127. package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js +1 -1
  128. package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js.map +1 -1
  129. package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js +1 -1
  130. package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js.map +1 -1
  131. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  132. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  133. package/web/.next/server/chunks/ssr/src_presentation_web_e4510d10._.js +2 -2
  134. package/web/.next/server/chunks/ssr/src_presentation_web_e4510d10._.js.map +1 -1
  135. package/web/.next/server/pages/500.html +2 -2
  136. package/web/.next/server/server-reference-manifest.js +1 -1
  137. package/web/.next/server/server-reference-manifest.json +28 -28
  138. package/web/.next/static/chunks/{0cbb35f940c9feef.js → 0e16acc45407464e.js} +1 -1
  139. package/web/.next/static/chunks/2a09571d38368a18.css +2 -0
  140. package/web/.next/static/chunks/2bc592fcedb175e3.js +1 -0
  141. package/web/.next/static/chunks/{38bb02c07a427f1e.js → 355d71c0450b4a02.js} +1 -1
  142. package/web/.next/static/chunks/{7c7da9e978216d2b.js → 359c068ab4a0da6f.js} +1 -1
  143. package/web/.next/static/chunks/{54de5c2f98832b92.js → 3b01d6c5f2c80432.js} +1 -1
  144. package/web/.next/static/chunks/{132ff681f7e12121.js → 3c887e1254ebcf9b.js} +1 -1
  145. package/web/.next/static/chunks/55e7ff9b53257d70.js +1 -0
  146. package/web/.next/static/chunks/{bb3e32c60cb2e794.js → 63aece55683a5d9e.js} +1 -1
  147. package/web/.next/static/chunks/{c0f23bc23fd8b8ad.js → a60332c83c5a9a99.js} +1 -1
  148. package/web/.next/static/chunks/{4906aba274bc6920.js → dfe944323bed074c.js} +1 -1
  149. package/web/.next/static/chunks/{4354b4b30513e7f0.js → e1c70b7a7e2d1977.js} +2 -2
  150. package/web/.next/static/chunks/5c55da0e28e6cdc8.js +0 -1
  151. package/web/.next/static/chunks/cb5c85c5e16e82d3.js +0 -1
  152. package/web/.next/static/chunks/f98eb6cb17b61cb2.css +0 -2
  153. /package/web/.next/static/{o8F4OKh6WJmRpbTqaCZ67 → MBJ8GLCn8wUUq7rR0bnER}/_buildManifest.js +0 -0
  154. /package/web/.next/static/{o8F4OKh6WJmRpbTqaCZ67 → MBJ8GLCn8wUUq7rR0bnER}/_clientMiddlewareManifest.json +0 -0
  155. /package/web/.next/static/{o8F4OKh6WJmRpbTqaCZ67 → MBJ8GLCn8wUUq7rR0bnER}/_ssgManifest.js +0 -0
@@ -97,6 +97,12 @@ export interface IWorktreeService {
97
97
  * @returns Computed worktree path (e.g., /repo/.worktrees/branch-name)
98
98
  */
99
99
  getWorktreePath(repoPath: string, branch: string): string;
100
+ /**
101
+ * Prune stale worktree entries whose directories no longer exist.
102
+ *
103
+ * @param repoPath - Path to the git repository
104
+ */
105
+ prune(repoPath: string): Promise<void>;
100
106
  /**
101
107
  * Ensure a directory is a valid git repository with at least one commit.
102
108
  * If the directory is not a git repo, initializes it with `git init`
@@ -1 +1 @@
1
- {"version":3,"file":"worktree-service.interface.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/application/ports/output/services/worktree-service.interface.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,oBAAY,iBAAiB;IAC3B,cAAc,mBAAmB;IACjC,aAAa,kBAAkB;IAC/B,SAAS,cAAc;IACvB,cAAc,mBAAmB;IACjC,SAAS,cAAc;CACxB;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;aAGpB,IAAI,EAAE,iBAAiB;aACvB,KAAK,CAAC,EAAE,KAAK;gBAF7B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,iBAAiB,EACvB,KAAK,CAAC,EAAE,KAAK,YAAA;CAKhC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;OASG;IACH,MAAM,CACJ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzB;;;;;;OAMG;IACH,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7D;;;;;OAKG;IACH,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAEhD;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE3D;;;;;;OAMG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEjE;;;;;;OAMG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvE;;;;;;OAMG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IAE1D;;;;;;;OAOG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD"}
1
+ {"version":3,"file":"worktree-service.interface.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/application/ports/output/services/worktree-service.interface.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,oBAAY,iBAAiB;IAC3B,cAAc,mBAAmB;IACjC,aAAa,kBAAkB;IAC/B,SAAS,cAAc;IACvB,cAAc,mBAAmB;IACjC,SAAS,cAAc;CACxB;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;aAGpB,IAAI,EAAE,iBAAiB;aACvB,KAAK,CAAC,EAAE,KAAK;gBAF7B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,iBAAiB,EACvB,KAAK,CAAC,EAAE,KAAK,YAAA;CAKhC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;OASG;IACH,MAAM,CACJ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzB;;;;;;OAMG;IACH,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7D;;;;;OAKG;IACH,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAEhD;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE3D;;;;;;OAMG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEjE;;;;;;OAMG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvE;;;;;;OAMG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IAE1D;;;;OAIG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC;;;;;;;OAOG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD"}
@@ -1 +1 @@
1
- {"version":3,"file":"cleanup-feature-worktree.use-case.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/application/use-cases/features/cleanup-feature-worktree.use-case.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iEAAiE,CAAC;AAC1G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2DAA2D,CAAC;AAClG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yDAAyD,CAAC;AAE7F,qBACa,6BAA6B;IAER,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY;gBAFP,WAAW,EAAE,kBAAkB,EACjC,eAAe,EAAE,gBAAgB,EACpC,YAAY,EAAE,aAAa;IAGjE,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAqChD"}
1
+ {"version":3,"file":"cleanup-feature-worktree.use-case.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/application/use-cases/features/cleanup-feature-worktree.use-case.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iEAAiE,CAAC;AAC1G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2DAA2D,CAAC;AAClG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yDAAyD,CAAC;AAE7F,qBACa,6BAA6B;IAER,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY;gBAFP,WAAW,EAAE,kBAAkB,EACjC,eAAe,EAAE,gBAAgB,EACpC,YAAY,EAAE,aAAa;IAGjE,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA4ChD"}
@@ -44,6 +44,14 @@ let CleanupFeatureWorktreeUseCase = class CleanupFeatureWorktreeUseCase {
44
44
  catch (err) {
45
45
  // eslint-disable-next-line no-console
46
46
  console.warn('[CleanupFeatureWorktreeUseCase] worktree remove failed:', err);
47
+ // Prune stale worktree entries so the branch is no longer considered "in use"
48
+ try {
49
+ await this.worktreeService.prune(feature.repositoryPath);
50
+ }
51
+ catch (pruneErr) {
52
+ // eslint-disable-next-line no-console
53
+ console.warn('[CleanupFeatureWorktreeUseCase] worktree prune failed:', pruneErr);
54
+ }
47
55
  }
48
56
  // Step 2: Delete the local feature branch
49
57
  try {
@@ -19,6 +19,7 @@ export declare class WorktreeService implements IWorktreeService {
19
19
  constructor(execFile: ExecFunction);
20
20
  create(repoPath: string, branch: string, worktreePath: string, startPoint?: string): Promise<WorktreeInfo>;
21
21
  remove(worktreePath: string, force?: boolean): Promise<void>;
22
+ prune(repoPath: string): Promise<void>;
22
23
  list(repoPath: string): Promise<WorktreeInfo[]>;
23
24
  exists(repoPath: string, branch: string): Promise<boolean>;
24
25
  branchExists(repoPath: string, branch: string): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"worktree.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/git/worktree.service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACb,MAAM,0EAA0E,CAAC;AAOlF;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,qBACa,eAAgB,YAAW,gBAAgB;IAClB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,YAAY;IAErE,MAAM,CACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC;IAqBlB,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAW5D,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAO/C,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK1D,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWhE,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWtE,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmC1D,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAMzD,OAAO,CAAC,mBAAmB;IA2B3B,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,qBAAqB;CAU9B"}
1
+ {"version":3,"file":"worktree.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/git/worktree.service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACb,MAAM,0EAA0E,CAAC;AAOlF;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,qBACa,eAAgB,YAAW,gBAAgB;IAClB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,YAAY;IAErE,MAAM,CACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC;IAqBlB,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAW5D,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAO/C,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK1D,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWhE,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWtE,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmC1D,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAMzD,OAAO,CAAC,mBAAmB;IA2B3B,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,qBAAqB;CAU9B"}
@@ -60,6 +60,14 @@ let WorktreeService = class WorktreeService {
60
60
  throw this.parseGitError(error);
61
61
  }
62
62
  }
63
+ async prune(repoPath) {
64
+ try {
65
+ await this.execFile('git', ['worktree', 'prune'], { cwd: repoPath });
66
+ }
67
+ catch (error) {
68
+ throw this.parseGitError(error);
69
+ }
70
+ }
63
71
  async list(repoPath) {
64
72
  const { stdout } = await this.execFile('git', ['worktree', 'list', '--porcelain'], {
65
73
  cwd: repoPath,
@@ -11,6 +11,8 @@ export interface RejectionFeedbackData {
11
11
  iteration: number;
12
12
  message: string;
13
13
  phase?: string;
14
+ timestamp?: string;
15
+ attachments?: string[];
14
16
  }
15
17
  type GetPhaseTimingsResult = {
16
18
  timings: PhaseTimingData[];
@@ -1 +1 @@
1
- {"version":3,"file":"get-feature-phase-timings.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/get-feature-phase-timings.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,qBAAqB,GACtB;IAAE,OAAO,EAAE,eAAe,EAAE,CAAC;IAAC,iBAAiB,EAAE,qBAAqB,EAAE,CAAA;CAAE,GAC1E;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtB,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA2B9F"}
1
+ {"version":3,"file":"get-feature-phase-timings.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/get-feature-phase-timings.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,KAAK,qBAAqB,GACtB;IAAE,OAAO,EAAE,eAAe,EAAE,CAAC;IAAC,iBAAiB,EAAE,qBAAqB,EAAE,CAAA;CAAE,GAC1E;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtB,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA2B9F"}
@@ -42,6 +42,10 @@ async function readRejectionFeedback(featureId) {
42
42
  iteration: Number(entry.iteration ?? 1),
43
43
  message: String(entry.message ?? ''),
44
44
  phase: entry.phase ? String(entry.phase) : undefined,
45
+ timestamp: entry.timestamp ? String(entry.timestamp) : undefined,
46
+ attachments: Array.isArray(entry.attachments)
47
+ ? entry.attachments.map(String)
48
+ : undefined,
45
49
  }));
46
50
  }
47
51
  catch {
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/presentation/web/app/api/attachments/preview/route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsB3C,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CA6BrE"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/presentation/web/app/api/attachments/preview/route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA8D3C,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CA0BrE"}
@@ -1,6 +1,6 @@
1
1
  import { NextResponse } from 'next/server';
2
- import { readFile, stat } from 'fs/promises';
3
- import { extname, resolve } from 'path';
2
+ import { readFile, stat, readdir } from 'fs/promises';
3
+ import { basename, extname, join, resolve } from 'path';
4
4
  import { getShepHomeDir } from '../../../../../../../packages/core/src/infrastructure/services/filesystem/shep-directory.service.js';
5
5
  const MIME_MAP = {
6
6
  '.png': 'image/png',
@@ -18,6 +18,44 @@ const MIME_MAP = {
18
18
  '.yaml': 'text/yaml',
19
19
  '.yml': 'text/yaml',
20
20
  };
21
+ /**
22
+ * When the exact path doesn't exist (e.g. pending-<id> was renamed to <slug>),
23
+ * search all sibling attachment directories for the same filename.
24
+ */
25
+ async function findAttachmentFallback(requestedPath, attachmentsRoot) {
26
+ const filename = basename(requestedPath);
27
+ try {
28
+ const dirs = await readdir(attachmentsRoot, { withFileTypes: true });
29
+ for (const entry of dirs) {
30
+ if (!entry.isDirectory())
31
+ continue;
32
+ const candidate = join(attachmentsRoot, entry.name, filename);
33
+ try {
34
+ await stat(candidate);
35
+ return candidate;
36
+ }
37
+ catch {
38
+ // not in this directory
39
+ }
40
+ }
41
+ }
42
+ catch {
43
+ // attachments root doesn't exist
44
+ }
45
+ return null;
46
+ }
47
+ function serveFile(filePath, mimeTypeHint) {
48
+ return readFile(filePath).then((buffer) => {
49
+ const ext = extname(filePath).toLowerCase();
50
+ const contentType = MIME_MAP[ext] ?? mimeTypeHint ?? 'application/octet-stream';
51
+ return new NextResponse(buffer, {
52
+ headers: {
53
+ 'Content-Type': contentType,
54
+ 'Cache-Control': 'private, max-age=3600',
55
+ },
56
+ });
57
+ });
58
+ }
21
59
  export async function GET(request) {
22
60
  const path = request.nextUrl.searchParams.get('path');
23
61
  const mimeTypeHint = request.nextUrl.searchParams.get('mimeType');
@@ -31,17 +69,15 @@ export async function GET(request) {
31
69
  }
32
70
  try {
33
71
  await stat(path);
34
- const buffer = await readFile(path);
35
- const ext = extname(path).toLowerCase();
36
- const contentType = MIME_MAP[ext] ?? mimeTypeHint ?? 'application/octet-stream';
37
- return new NextResponse(buffer, {
38
- headers: {
39
- 'Content-Type': contentType,
40
- 'Cache-Control': 'private, max-age=3600',
41
- },
42
- });
72
+ return await serveFile(path, mimeTypeHint);
43
73
  }
44
74
  catch {
75
+ // File not found at exact path — try fallback search
76
+ // This handles the case where pending-<sessionId>/ was renamed to <featureSlug>/
77
+ const fallbackPath = await findAttachmentFallback(path, attachmentsRoot);
78
+ if (fallbackPath) {
79
+ return await serveFile(fallbackPath, mimeTypeHint);
80
+ }
45
81
  return NextResponse.json({ error: 'File not found' }, { status: 404 });
46
82
  }
47
83
  }
@@ -1 +1 @@
1
- {"version":3,"file":"activity-tab.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/activity-tab.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EACf,qBAAqB,EACtB,MAAM,yCAAyC,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iBAAiB,CAAC,EAAE,qBAAqB,EAAE,CAAC;CAC7C;AAsED,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,2CA8D3F"}
1
+ {"version":3,"file":"activity-tab.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/activity-tab.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EACf,qBAAqB,EACtB,MAAM,yCAAyC,CAAC;AAGjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iBAAiB,CAAC,EAAE,qBAAqB,EAAE,CAAC;CAC7C;AA6HD,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,2CAiE3F"}
@@ -1,6 +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 { Loader2, AlertCircle, Clock } from 'lucide-react';
4
+ import { InlineAttachments } from '../../common/inline-attachments/index.js';
4
5
  const NODE_TO_PHASE = {
5
6
  analyze: 'Analyzing',
6
7
  requirements: 'Requirements',
@@ -38,22 +39,63 @@ function groupTimingsByRun(timings) {
38
39
  return groups;
39
40
  }
40
41
  /**
41
- * Map each run:rejected timing entry to its rejection feedback message.
42
- * Matches by sequential order: 1st rejected event 1st feedback entry, etc.
42
+ * Ensure every rejection feedback entry has a corresponding run:rejected timing event
43
+ * and build a map from each event key to its display data.
44
+ *
45
+ * The timings array may contain fewer run:rejected events than the feedback array
46
+ * (e.g. 2 timing events for 4 feedback entries). For any unmatched feedback entries,
47
+ * synthetic run:rejected timing events are inserted after the last existing
48
+ * run:rejected event in the timings array so that all feedback is rendered.
43
49
  */
44
- function buildRejectionMessageMap(timings, feedback) {
50
+ function ensureRejectionTimingsAndBuildMap(timings, feedback) {
45
51
  const map = new Map();
46
52
  if (!feedback?.length)
47
- return map;
48
- let rejectionIdx = 0;
49
- for (const t of timings) {
50
- if (t.phase === 'run:rejected' && rejectionIdx < feedback.length) {
51
- const key = `${t.agentRunId}-${t.phase}-${t.startedAt}`;
52
- map.set(key, feedback[rejectionIdx].message);
53
- rejectionIdx++;
53
+ return { timings, rejectionMessages: map };
54
+ // Collect existing run:rejected events
55
+ const existingRejections = [];
56
+ for (let i = 0; i < timings.length; i++) {
57
+ if (timings[i].phase === 'run:rejected') {
58
+ existingRejections.push({ index: i, timing: timings[i] });
54
59
  }
55
60
  }
56
- return map;
61
+ // Pair existing events with feedback sequentially
62
+ const paired = Math.min(existingRejections.length, feedback.length);
63
+ for (let i = 0; i < paired; i++) {
64
+ const t = existingRejections[i].timing;
65
+ const key = `${t.agentRunId}-${t.phase}-${t.startedAt}`;
66
+ map.set(key, { message: feedback[i].message, attachments: feedback[i].attachments });
67
+ }
68
+ // If all feedback is covered, no need to inject synthetic entries
69
+ if (paired >= feedback.length) {
70
+ return { timings, rejectionMessages: map };
71
+ }
72
+ // Synthesize additional run:rejected timing entries for remaining feedback
73
+ const result = [...timings];
74
+ // Determine where to insert: after the last existing run:rejected, or at the end
75
+ let insertAfter = result.length - 1;
76
+ if (existingRejections.length > 0) {
77
+ insertAfter = existingRejections[existingRejections.length - 1].index;
78
+ }
79
+ // Use the last existing rejected event as a template for agentRunId,
80
+ // or fall back to the first timing entry's agentRunId
81
+ const templateRunId = existingRejections.length > 0
82
+ ? existingRejections[existingRejections.length - 1].timing.agentRunId
83
+ : (timings[0]?.agentRunId ?? 'unknown');
84
+ const synthetic = [];
85
+ for (let i = paired; i < feedback.length; i++) {
86
+ const fb = feedback[i];
87
+ const syntheticTiming = {
88
+ agentRunId: templateRunId,
89
+ phase: 'run:rejected',
90
+ startedAt: fb.timestamp ?? `synthetic-${i}`,
91
+ };
92
+ const key = `${syntheticTiming.agentRunId}-${syntheticTiming.phase}-${syntheticTiming.startedAt}`;
93
+ map.set(key, { message: fb.message, attachments: fb.attachments });
94
+ synthetic.push(syntheticTiming);
95
+ }
96
+ // Insert synthetic entries after the last existing run:rejected
97
+ result.splice(insertAfter + 1, 0, ...synthetic);
98
+ return { timings: result, rejectionMessages: map };
57
99
  }
58
100
  export function ActivityTab({ timings, loading, error, rejectionFeedback }) {
59
101
  if (loading) {
@@ -67,9 +109,9 @@ export function ActivityTab({ timings, loading, error, rejectionFeedback }) {
67
109
  }
68
110
  const nodeTimings = timings.filter((t) => !isLifecycleEvent(t.phase));
69
111
  const maxDurationMs = Math.max(...nodeTimings.map((t) => t.durationMs ?? 0), ...nodeTimings.map((t) => t.approvalWaitMs ?? 0), 0);
70
- // Build a map from rejection index to feedback message
71
- const rejectionMessages = buildRejectionMessageMap(timings, rejectionFeedback);
72
- const groups = groupTimingsByRun(timings);
112
+ // Ensure every rejection feedback entry has a timing event and build display map
113
+ const { timings: enrichedTimings, rejectionMessages } = ensureRejectionTimingsAndBuildMap(timings, rejectionFeedback);
114
+ const groups = groupTimingsByRun(enrichedTimings);
73
115
  const multiRun = groups.length > 1;
74
116
  const totalExecMs = nodeTimings.reduce((sum, t) => sum + (t.durationMs ?? 0), 0);
75
117
  const totalWaitMs = nodeTimings.reduce((sum, t) => sum + (t.approvalWaitMs ?? 0), 0);
@@ -79,17 +121,18 @@ function RunGroup({ group, groupIdx, multiRun, maxDurationMs, rejectionMessages,
79
121
  return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [multiRun ? (_jsxs("div", { className: "text-muted-foreground text-xs font-medium", children: ["Run #", groupIdx + 1] })) : null, group.timings.map((t) => {
80
122
  if (isLifecycleEvent(t.phase)) {
81
123
  const key = `${t.agentRunId}-${t.phase}-${t.startedAt}`;
82
- const message = t.phase === 'run:rejected' ? rejectionMessages.get(key) : undefined;
83
- return _jsx(LifecycleEventRow, { timing: t, message: message }, key);
124
+ const rejection = t.phase === 'run:rejected' ? rejectionMessages.get(key) : undefined;
125
+ return (_jsx(LifecycleEventRow, { timing: t, message: rejection?.message, attachments: rejection?.attachments }, key));
84
126
  }
85
127
  return (_jsx(NodeTimingRow, { timing: t, maxDurationMs: maxDurationMs }, `${t.agentRunId}-${t.phase}-${t.startedAt}`));
86
128
  })] }));
87
129
  }
88
- function LifecycleEventRow({ timing, message }) {
130
+ function LifecycleEventRow({ timing, message, attachments, }) {
89
131
  const event = LIFECYCLE_EVENTS[timing.phase];
90
132
  if (!event)
91
133
  return null;
92
- return (_jsxs("div", { className: `text-xs ${event.colorClass}`, children: [_jsx("span", { children: event.label }), message ? (_jsxs("span", { "data-testid": "rejection-feedback-text", className: "text-muted-foreground ml-2 font-normal italic", children: ["\u2014 ", message] })) : null] }));
134
+ const hasAttachments = attachments && attachments.length > 0;
135
+ return (_jsxs("div", { className: `text-xs ${event.colorClass}`, children: [_jsx("span", { children: event.label }), message ? (_jsxs("span", { "data-testid": "rejection-feedback-text", className: "text-muted-foreground ml-2 font-normal italic", children: ["\u2014 ", message] })) : null, hasAttachments ? (_jsx("div", { "data-testid": "rejection-feedback-attachments", className: "mt-1.5 ml-2", children: _jsx(InlineAttachments, { text: "", attachmentPaths: attachments }) })) : null] }));
93
136
  }
94
137
  function NodeTimingRow({ timing, maxDurationMs, }) {
95
138
  const label = timing.phase.includes(':')
@@ -17,6 +17,8 @@ export declare const Empty: Story;
17
17
  export declare const Error: Story;
18
18
  /** Rejection — shows rejected lifecycle event with user feedback text. */
19
19
  export declare const WithRejection: Story;
20
+ /** Multiple rejections — all feedback entries shown even with fewer run:rejected events. */
21
+ export declare const MultipleRejections: Story;
20
22
  /** Approval wait indicators — phases with approval wait sub-rows. */
21
23
  export declare const WithApprovalWaits: Story;
22
24
  //# sourceMappingURL=activity-tab.stories.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"activity-tab.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/activity-tab.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,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;AAmK1C,2EAA2E;AAC3E,eAAO,MAAM,OAAO,EAAE,KAMrB,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,YAAY,EAAE,KAM1B,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,YAAY,EAAE,KAM1B,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,OAAO,EAAE,KAMrB,CAAC;AAEF,8CAA8C;AAC9C,eAAO,MAAM,KAAK,EAAE,KAMnB,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,KAAK,EAAE,KAMnB,CAAC;AAEF,0EAA0E;AAC1E,eAAO,MAAM,aAAa,EAAE,KAsE3B,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,iBAAiB,EAAE,KAgC/B,CAAC"}
1
+ {"version":3,"file":"activity-tab.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/activity-tab.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,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;AAmK1C,2EAA2E;AAC3E,eAAO,MAAM,OAAO,EAAE,KAMrB,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,YAAY,EAAE,KAM1B,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,YAAY,EAAE,KAM1B,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,OAAO,EAAE,KAMrB,CAAC;AAEF,8CAA8C;AAC9C,eAAO,MAAM,KAAK,EAAE,KAMnB,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,KAAK,EAAE,KAMnB,CAAC;AAEF,0EAA0E;AAC1E,eAAO,MAAM,aAAa,EAAE,KAsE3B,CAAC;AAEF,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,EAAE,KAwEhC,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,iBAAiB,EAAE,KAgC/B,CAAC"}
@@ -288,6 +288,79 @@ export const WithRejection = {
288
288
  error: null,
289
289
  },
290
290
  };
291
+ /** Multiple rejections — all feedback entries shown even with fewer run:rejected events. */
292
+ export const MultipleRejections = {
293
+ args: {
294
+ timings: [
295
+ {
296
+ agentRunId: 'run-1',
297
+ phase: 'run:started',
298
+ startedAt: '2025-01-15T10:00:00Z',
299
+ durationMs: 0,
300
+ },
301
+ {
302
+ agentRunId: 'run-1',
303
+ phase: 'plan',
304
+ startedAt: '2025-01-15T10:00:01Z',
305
+ completedAt: '2025-01-15T10:05:00Z',
306
+ durationMs: 300000,
307
+ },
308
+ {
309
+ agentRunId: 'run-1',
310
+ phase: 'run:rejected',
311
+ startedAt: '2025-01-15T10:05:00Z',
312
+ durationMs: 0,
313
+ },
314
+ {
315
+ agentRunId: 'run-1',
316
+ phase: 'run:resumed',
317
+ startedAt: '2025-01-15T10:10:00Z',
318
+ durationMs: 0,
319
+ },
320
+ {
321
+ agentRunId: 'run-1',
322
+ phase: 'merge',
323
+ startedAt: '2025-01-15T10:10:01Z',
324
+ completedAt: '2025-01-15T10:15:00Z',
325
+ durationMs: 299000,
326
+ },
327
+ {
328
+ agentRunId: 'run-1',
329
+ phase: 'run:rejected',
330
+ startedAt: '2025-01-15T10:15:00Z',
331
+ durationMs: 0,
332
+ },
333
+ ],
334
+ rejectionFeedback: [
335
+ {
336
+ iteration: 1,
337
+ message: 'rebase on main',
338
+ phase: 'plan',
339
+ timestamp: '2025-01-15T10:05:00Z',
340
+ },
341
+ {
342
+ iteration: 2,
343
+ message: 'rebase on main',
344
+ phase: 'merge',
345
+ timestamp: '2025-01-15T10:15:00Z',
346
+ },
347
+ {
348
+ iteration: 3,
349
+ message: 'add support for evidence agent into fast mode as well',
350
+ phase: 'merge',
351
+ timestamp: '2025-01-15T10:20:00Z',
352
+ },
353
+ {
354
+ iteration: 4,
355
+ message: "I've test and created a feature but the screenshots seems to be broken https://github.com/shep-ai/cli/pull/258 fix",
356
+ phase: 'merge',
357
+ timestamp: '2025-01-15T10:25:00Z',
358
+ },
359
+ ],
360
+ loading: false,
361
+ error: null,
362
+ },
363
+ };
291
364
  /** Approval wait indicators — phases with approval wait sub-rows. */
292
365
  export const WithApprovalWaits = {
293
366
  args: {
@@ -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,2CAwCrD"}
1
+ {"version":3,"file":"overview-tab.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/overview-tab.tsx"],"names":[],"mappings":"AAWA,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"}
@@ -1,6 +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 { ExternalLink, GitBranch, GitCommitHorizontal, Zap } from 'lucide-react';
4
+ import { InlineAttachments } from '../../common/inline-attachments/index.js';
4
5
  import { PrStatus } from '../../../../../../packages/core/src/domain/generated/output.js';
5
6
  import { cn } from '../../../lib/utils.js';
6
7
  import { Badge } from '../../ui/badge.js';
@@ -50,7 +51,7 @@ function FeatureInfo({ data }) {
50
51
  Boolean(data.createdAt);
51
52
  if (!hasInfo)
52
53
  return null;
53
- return (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { "data-testid": "feature-drawer-info", className: "flex flex-col gap-3 p-4", children: [data.branch ? (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Branch" }), _jsxs("span", { className: "flex items-center gap-1.5 text-sm", children: [_jsx(GitBranch, { className: "text-muted-foreground h-3.5 w-3.5 shrink-0" }), _jsx("code", { className: "bg-muted rounded px-1 py-0.5 font-mono text-xs", children: data.branch }), data.baseBranch ? (_jsxs("span", { className: "text-muted-foreground text-xs", children: ["from ", data.baseBranch] })) : null] })] })) : null, data.oneLiner ? (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "One-Liner" }), _jsx("span", { className: "text-sm leading-relaxed", children: data.oneLiner })] })) : null, data.userQuery ? (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "User Query" }), _jsx("span", { className: "text-sm leading-relaxed", children: data.userQuery })] })) : null, data.summary ? (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Summary" }), _jsx("span", { className: "text-sm leading-relaxed", children: data.summary })] })) : null, data.createdAt ? (_jsx(DetailRow, { label: "Created", value: formatRelativeTime(data.createdAt) })) : null] })] }));
54
+ return (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { "data-testid": "feature-drawer-info", className: "flex flex-col gap-3 p-4", children: [data.branch ? (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Branch" }), _jsxs("span", { className: "flex items-center gap-1.5 text-sm", children: [_jsx(GitBranch, { className: "text-muted-foreground h-3.5 w-3.5 shrink-0" }), _jsx("code", { className: "bg-muted rounded px-1 py-0.5 font-mono text-xs", children: data.branch }), data.baseBranch ? (_jsxs("span", { className: "text-muted-foreground text-xs", children: ["from ", data.baseBranch] })) : null] })] })) : null, data.oneLiner ? (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "One-Liner" }), _jsx("span", { className: "text-sm leading-relaxed", children: data.oneLiner })] })) : null, data.userQuery ? (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "User Query" }), _jsx(InlineAttachments, { text: data.userQuery })] })) : null, data.summary ? (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Summary" }), _jsx("span", { className: "text-sm leading-relaxed", children: data.summary })] })) : null, data.createdAt ? (_jsx(DetailRow, { label: "Created", value: formatRelativeTime(data.createdAt) })) : null] })] }));
54
55
  }
55
56
  // ── PR Info section ──────────────────────────────────────────────────
56
57
  const prStatusStyles = {
@@ -0,0 +1,3 @@
1
+ export { InlineAttachments, parseAttachmentRefs } from './inline-attachments.js';
2
+ export type { InlineAttachmentsProps } from './inline-attachments.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/inline-attachments/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,YAAY,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1 @@
1
+ export { InlineAttachments, parseAttachmentRefs } from './inline-attachments.js';
@@ -0,0 +1,20 @@
1
+ export interface InlineAttachmentsProps {
2
+ /** Text that may contain `@/path/to/file` attachment references. */
3
+ text: string;
4
+ /** Additional attachment paths to render (e.g. from rejection feedback). */
5
+ attachmentPaths?: string[];
6
+ }
7
+ interface TextSegment {
8
+ type: 'text';
9
+ value: string;
10
+ }
11
+ interface AttachmentSegment {
12
+ type: 'attachment';
13
+ path: string;
14
+ }
15
+ type Segment = TextSegment | AttachmentSegment;
16
+ /** Parse text into segments of plain text and attachment references. */
17
+ export declare function parseAttachmentRefs(text: string): Segment[];
18
+ export declare function InlineAttachments({ text, attachmentPaths }: InlineAttachmentsProps): import("react/jsx-runtime").JSX.Element;
19
+ export {};
20
+ //# sourceMappingURL=inline-attachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-attachments.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/inline-attachments/inline-attachments.tsx"],"names":[],"mappings":"AA6BA,MAAM,WAAW,sBAAsB;IACrC,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,iBAAiB;IACzB,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,KAAK,OAAO,GAAG,WAAW,GAAG,iBAAiB,CAAC;AAE/C,wEAAwE;AACxE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAwB3D;AAED,wBAAgB,iBAAiB,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,EAAE,sBAAsB,2CA4BlF"}
@@ -0,0 +1,72 @@
1
+ /* eslint-disable @next/next/no-img-element -- Local file preview requires raw <img>, not next/image */
2
+ 'use client';
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ import { useState } from 'react';
5
+ import { Dialog, DialogTrigger, DialogContent, DialogTitle } from '../../ui/dialog.js';
6
+ import { VisuallyHidden } from 'radix-ui';
7
+ import { DownloadIcon, ImageOff } from 'lucide-react';
8
+ const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.bmp']);
9
+ function isImagePath(path) {
10
+ const dot = path.lastIndexOf('.');
11
+ return dot >= 0 && IMAGE_EXTS.has(path.slice(dot).toLowerCase());
12
+ }
13
+ function previewUrl(path) {
14
+ const ext = path.lastIndexOf('.') >= 0 ? path.slice(path.lastIndexOf('.')) : '';
15
+ const mimeGuess = isImagePath(path) ? `image/${ext.slice(1).replace('jpg', 'jpeg')}` : '';
16
+ const params = new URLSearchParams({ path, ...(mimeGuess && { mimeType: mimeGuess }) });
17
+ return `/api/attachments/preview?${params.toString()}`;
18
+ }
19
+ function getFilename(path) {
20
+ return path.split('/').pop() ?? path;
21
+ }
22
+ /** Regex matching `@/absolute/path` attachment references in text. */
23
+ const ATTACHMENT_REF_RE = /(?:^|\s)@(\/[^\s]+)/g;
24
+ /** Parse text into segments of plain text and attachment references. */
25
+ export function parseAttachmentRefs(text) {
26
+ const segments = [];
27
+ let lastIndex = 0;
28
+ for (const match of text.matchAll(ATTACHMENT_REF_RE)) {
29
+ const fullMatchStart = match.index;
30
+ // The path capture starts after the optional leading whitespace
31
+ const leadingWs = match[0].length - match[0].trimStart().length;
32
+ const refStart = fullMatchStart + leadingWs;
33
+ // Add text before this reference (including leading whitespace)
34
+ if (refStart > lastIndex) {
35
+ segments.push({ type: 'text', value: text.slice(lastIndex, refStart) });
36
+ }
37
+ segments.push({ type: 'attachment', path: match[1] });
38
+ lastIndex = fullMatchStart + match[0].length;
39
+ }
40
+ if (lastIndex < text.length) {
41
+ segments.push({ type: 'text', value: text.slice(lastIndex) });
42
+ }
43
+ return segments;
44
+ }
45
+ export function InlineAttachments({ text, attachmentPaths }) {
46
+ const segments = parseAttachmentRefs(text);
47
+ const hasInlineAttachments = segments.some((s) => s.type === 'attachment');
48
+ const extraPaths = attachmentPaths?.filter(Boolean) ?? [];
49
+ if (!hasInlineAttachments && extraPaths.length === 0) {
50
+ return _jsx("span", { className: "text-sm leading-relaxed", children: text });
51
+ }
52
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [segments.map((segment) => {
53
+ if (segment.type === 'text') {
54
+ const trimmed = segment.value.trim();
55
+ if (!trimmed)
56
+ return null;
57
+ return (_jsx("span", { className: "text-sm leading-relaxed", children: trimmed }, `text-${trimmed.slice(0, 40)}`));
58
+ }
59
+ return _jsx(AttachmentPreview, { path: segment.path }, `att-${segment.path}`);
60
+ }), extraPaths.map((path) => (_jsx(AttachmentPreview, { path: path }, `extra-${path}`)))] }));
61
+ }
62
+ function AttachmentPreview({ path }) {
63
+ const filename = getFilename(path);
64
+ const [loadError, setLoadError] = useState(false);
65
+ if (isImagePath(path)) {
66
+ if (loadError) {
67
+ return (_jsxs("div", { "data-testid": "inline-attachment-image-error", className: "text-muted-foreground flex items-center gap-2 rounded-md border px-3 py-2 text-sm", children: [_jsx(ImageOff, { className: "h-4 w-4 shrink-0" }), _jsx("span", { className: "truncate", children: filename })] }));
68
+ }
69
+ return (_jsxs(Dialog, { children: [_jsx(DialogTrigger, { asChild: true, children: _jsx("img", { src: previewUrl(path), alt: filename, "data-testid": "inline-attachment-image", onError: () => setLoadError(true), className: "max-h-48 max-w-full cursor-pointer rounded-md border object-contain transition-opacity hover:opacity-80" }) }), _jsxs(DialogContent, { className: "max-w-3xl gap-0 overflow-hidden border-0 p-0 [&>button:last-child]:!cursor-pointer [&>button:last-child]:!rounded-full [&>button:last-child]:!bg-black/70 [&>button:last-child]:!p-1.5 [&>button:last-child]:!text-white [&>button:last-child]:!opacity-100 [&>button:last-child]:!shadow-lg [&>button:last-child]:!backdrop-blur-md [&>button:last-child]:hover:!bg-black/90", children: [_jsx(VisuallyHidden.Root, { children: _jsxs(DialogTitle, { children: ["Preview: ", filename] }) }), _jsx("div", { className: "relative bg-black/90", children: _jsx("img", { src: previewUrl(path), alt: filename, className: "h-auto max-h-[70vh] w-full object-contain" }) }), _jsxs("div", { className: "bg-background flex items-center gap-3 px-4 py-3", children: [_jsx("div", { className: "flex min-w-0 flex-1 flex-col", children: _jsx("span", { className: "truncate text-sm font-medium", children: filename }) }), _jsx("a", { href: previewUrl(path), download: filename, className: "text-muted-foreground hover:text-foreground shrink-0 cursor-pointer rounded p-1.5 transition-colors", "aria-label": `Download ${filename}`, children: _jsx(DownloadIcon, { className: "h-4 w-4" }) })] })] })] }));
70
+ }
71
+ return (_jsx("a", { href: previewUrl(path), download: filename, "data-testid": "inline-attachment-file", className: "text-primary text-sm underline underline-offset-2", children: filename }));
72
+ }
@@ -0,0 +1,16 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { InlineAttachments } from './inline-attachments.js';
3
+ declare const meta: Meta<typeof InlineAttachments>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof InlineAttachments>;
6
+ /** Plain text with no attachment references. */
7
+ export declare const PlainText: Story;
8
+ /** Text with an image attachment reference. */
9
+ export declare const WithImageAttachment: Story;
10
+ /** Text with multiple attachment references. */
11
+ export declare const MultipleAttachments: Story;
12
+ /** Text with a non-image file attachment. */
13
+ export declare const FileAttachment: Story;
14
+ /** Extra attachment paths passed via prop (e.g. rejection feedback). */
15
+ export declare const ExtraAttachmentPaths: Story;
16
+ //# sourceMappingURL=inline-attachments.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-attachments.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/inline-attachments/inline-attachments.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,iBAAiB,CAYxC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEhD,gDAAgD;AAChD,eAAO,MAAM,SAAS,EAAE,KAIvB,CAAC;AAEF,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB,EAAE,KAIjC,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,mBAAmB,EAAE,KAIjC,CAAC;AAEF,6CAA6C;AAC7C,eAAO,MAAM,cAAc,EAAE,KAI5B,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,oBAAoB,EAAE,KAQlC,CAAC"}