github-issue-tower-defence-management 1.90.0 → 1.91.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +19 -5
  3. package/bin/adapter/entry-points/cli/index.js +17 -13
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/cli/projectConfig.js +2 -0
  6. package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
  7. package/bin/adapter/entry-points/console/consoleOperationApi.js +54 -27
  8. package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
  9. package/bin/adapter/entry-points/console/consoleProjectResolver.js +38 -0
  10. package/bin/adapter/entry-points/console/consoleProjectResolver.js.map +1 -0
  11. package/bin/adapter/entry-points/console/consoleServer.js +3 -4
  12. package/bin/adapter/entry-points/console/consoleServer.js.map +1 -1
  13. package/bin/adapter/entry-points/console/ui-dist/assets/index-BU6p3cGU.css +1 -0
  14. package/bin/adapter/entry-points/console/ui-dist/assets/index-BvuSQN9s.js +100 -0
  15. package/bin/adapter/entry-points/console/ui-dist/index.html +2 -2
  16. package/bin/adapter/entry-points/handlers/consoleListsWriter.js +1 -0
  17. package/bin/adapter/entry-points/handlers/consoleListsWriter.js.map +1 -1
  18. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +16 -0
  19. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  20. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +3 -0
  21. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
  22. package/jest.config.js +57 -9
  23. package/package.json +17 -13
  24. package/src/adapter/entry-points/cli/index.test.ts +18 -3
  25. package/src/adapter/entry-points/cli/index.ts +32 -14
  26. package/src/adapter/entry-points/cli/projectConfig.ts +3 -0
  27. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +129 -15
  28. package/src/adapter/entry-points/console/consoleOperationApi.ts +83 -28
  29. package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +96 -0
  30. package/src/adapter/entry-points/console/consoleProjectResolver.ts +50 -0
  31. package/src/adapter/entry-points/console/consoleServer.test.ts +5 -4
  32. package/src/adapter/entry-points/console/consoleServer.ts +5 -7
  33. package/src/adapter/entry-points/console/ui/jest.setup.ts +1 -0
  34. package/src/adapter/entry-points/console/ui/jest.styleMock.js +1 -0
  35. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.stories.tsx +27 -0
  36. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.test.tsx +36 -0
  37. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.tsx +50 -0
  38. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.stories.tsx +22 -0
  39. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.test.tsx +38 -0
  40. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.tsx +65 -0
  41. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.stories.tsx +28 -0
  42. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.test.tsx +42 -0
  43. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.tsx +55 -0
  44. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.stories.tsx +29 -0
  45. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.test.tsx +55 -0
  46. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.tsx +66 -0
  47. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.stories.tsx +25 -0
  48. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.test.tsx +53 -0
  49. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.tsx +53 -0
  50. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.stories.tsx +79 -0
  51. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.test.tsx +81 -0
  52. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +229 -0
  53. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.stories.tsx +82 -0
  54. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.test.tsx +52 -0
  55. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.tsx +32 -0
  56. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.stories.tsx +31 -0
  57. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.test.tsx +40 -0
  58. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.tsx +88 -0
  59. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.stories.tsx +26 -0
  60. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.test.tsx +32 -0
  61. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.tsx +36 -0
  62. package/src/adapter/entry-points/console/ui/src/features/console/components/{ConsoleProjectHeader.stories.tsx → layout/ConsoleProjectSummary.stories.tsx} +5 -5
  63. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleProjectSummary.test.tsx +14 -0
  64. package/src/adapter/entry-points/console/ui/src/features/console/components/{ConsoleProjectHeader.tsx → layout/ConsoleProjectSummary.tsx} +3 -1
  65. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +70 -0
  66. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +59 -0
  67. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.tsx +41 -0
  68. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.stories.tsx +60 -0
  69. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.test.tsx +87 -0
  70. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.tsx +68 -0
  71. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.stories.tsx +25 -0
  72. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.test.tsx +43 -0
  73. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.tsx +34 -0
  74. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.stories.tsx +27 -0
  75. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.test.tsx +24 -0
  76. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.tsx +28 -0
  77. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.stories.tsx +14 -0
  78. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.test.tsx +21 -0
  79. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.tsx +26 -0
  80. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.stories.tsx +20 -0
  81. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.test.tsx +42 -0
  82. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.tsx +28 -0
  83. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.stories.tsx +55 -0
  84. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.test.tsx +85 -0
  85. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.tsx +58 -0
  86. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.stories.tsx +14 -0
  87. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.test.tsx +33 -0
  88. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.tsx +34 -0
  89. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.stories.tsx +17 -0
  90. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.test.tsx +49 -0
  91. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.tsx +66 -0
  92. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStoryActions.stories.tsx +17 -0
  93. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStoryActions.test.tsx +39 -0
  94. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStoryActions.tsx +42 -0
  95. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.test.ts +22 -0
  96. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +42 -0
  97. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +126 -0
  98. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.ts +167 -0
  99. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +198 -0
  100. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +243 -0
  101. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts +40 -0
  102. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.ts +71 -0
  103. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.test.ts +41 -0
  104. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.ts +57 -0
  105. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +63 -0
  106. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +129 -0
  107. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleToken.test.ts +41 -0
  108. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +155 -0
  109. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +187 -0
  110. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.test.ts +76 -0
  111. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.ts +73 -0
  112. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.test.ts +27 -0
  113. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.ts +71 -0
  114. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.test.ts +56 -0
  115. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +51 -0
  116. package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.test.ts +34 -0
  117. package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.ts +73 -0
  118. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.test.ts +35 -0
  119. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.ts +21 -0
  120. package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.test.ts +91 -0
  121. package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.ts +79 -0
  122. package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.test.ts +97 -0
  123. package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.ts +95 -0
  124. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.test.ts +37 -0
  125. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.ts +35 -0
  126. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.test.ts +124 -0
  127. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.ts +101 -0
  128. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.test.ts +52 -0
  129. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.ts +51 -0
  130. package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +141 -0
  131. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +79 -0
  132. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +109 -0
  133. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +74 -0
  134. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +137 -11
  135. package/src/adapter/entry-points/console/ui/src/features/console/testing/fixtures.ts +244 -0
  136. package/src/adapter/entry-points/console/ui/src/index.css +352 -2
  137. package/src/adapter/entry-points/console/ui/tsconfig.json +1 -0
  138. package/src/adapter/entry-points/console/ui/vite.config.ts +5 -0
  139. package/src/adapter/entry-points/console/ui-dist/assets/index-BU6p3cGU.css +1 -0
  140. package/src/adapter/entry-points/console/ui-dist/assets/index-PtVrAcBb.js +100 -0
  141. package/src/adapter/entry-points/console/ui-dist/index.html +2 -2
  142. package/src/adapter/entry-points/handlers/consoleListsWriter.test.ts +27 -2
  143. package/src/adapter/entry-points/handlers/consoleListsWriter.ts +1 -0
  144. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +25 -0
  145. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +4 -0
  146. package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +26 -0
  147. package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +17 -1
  148. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  149. package/types/adapter/entry-points/cli/projectConfig.d.ts +1 -0
  150. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  151. package/types/adapter/entry-points/console/consoleOperationApi.d.ts +6 -2
  152. package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
  153. package/types/adapter/entry-points/console/consoleProjectResolver.d.ts +6 -0
  154. package/types/adapter/entry-points/console/consoleProjectResolver.d.ts.map +1 -0
  155. package/types/adapter/entry-points/console/consoleServer.d.ts +2 -3
  156. package/types/adapter/entry-points/console/consoleServer.d.ts.map +1 -1
  157. package/types/adapter/entry-points/handlers/consoleListsWriter.d.ts.map +1 -1
  158. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +1 -0
  159. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  160. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +1 -0
  161. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
  162. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts +2 -1
  163. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
  164. package/bin/adapter/entry-points/console/ui-dist/assets/index-DDjYPXRT.js +0 -49
  165. package/bin/adapter/entry-points/console/ui-dist/assets/index-DHlBLm7d.css +0 -1
  166. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListView.stories.tsx +0 -44
  167. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListView.tsx +0 -58
  168. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleTabBar.stories.tsx +0 -34
  169. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleTabBar.tsx +0 -32
  170. package/src/adapter/entry-points/console/ui/src/features/console/fixtures.ts +0 -47
  171. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleList.ts +0 -78
  172. package/src/adapter/entry-points/console/ui/src/features/console/types.ts +0 -69
  173. package/src/adapter/entry-points/console/ui-dist/assets/index-DDjYPXRT.js +0 -49
  174. package/src/adapter/entry-points/console/ui-dist/assets/index-DHlBLm7d.css +0 -1
@@ -0,0 +1,82 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ConsoleItemIcon } from './ConsoleItemIcon';
3
+
4
+ const meta: Meta<typeof ConsoleItemIcon> = {
5
+ title: 'Console/ConsoleItemIcon',
6
+ component: ConsoleItemIcon,
7
+ args: { size: 20 },
8
+ };
9
+
10
+ export default meta;
11
+
12
+ type Story = StoryObj<typeof ConsoleItemIcon>;
13
+
14
+ export const PullRequestOpen: Story = {
15
+ args: {
16
+ isPr: true,
17
+ state: 'open',
18
+ merged: false,
19
+ isDraft: false,
20
+ stateReason: '',
21
+ },
22
+ };
23
+
24
+ export const PullRequestMerged: Story = {
25
+ args: {
26
+ isPr: true,
27
+ state: 'closed',
28
+ merged: true,
29
+ isDraft: false,
30
+ stateReason: '',
31
+ },
32
+ };
33
+
34
+ export const PullRequestClosed: Story = {
35
+ args: {
36
+ isPr: true,
37
+ state: 'closed',
38
+ merged: false,
39
+ isDraft: false,
40
+ stateReason: '',
41
+ },
42
+ };
43
+
44
+ export const PullRequestDraft: Story = {
45
+ args: {
46
+ isPr: true,
47
+ state: 'open',
48
+ merged: false,
49
+ isDraft: true,
50
+ stateReason: '',
51
+ },
52
+ };
53
+
54
+ export const IssueOpen: Story = {
55
+ args: {
56
+ isPr: false,
57
+ state: 'open',
58
+ merged: false,
59
+ isDraft: false,
60
+ stateReason: '',
61
+ },
62
+ };
63
+
64
+ export const IssueClosedCompleted: Story = {
65
+ args: {
66
+ isPr: false,
67
+ state: 'closed',
68
+ merged: false,
69
+ isDraft: false,
70
+ stateReason: 'completed',
71
+ },
72
+ };
73
+
74
+ export const IssueClosedNotPlanned: Story = {
75
+ args: {
76
+ isPr: false,
77
+ state: 'closed',
78
+ merged: false,
79
+ isDraft: false,
80
+ stateReason: 'not_planned',
81
+ },
82
+ };
@@ -0,0 +1,52 @@
1
+ import { render } from '@testing-library/react';
2
+ import { CONSOLE_ITEM_ICONS } from '../../logic/itemIcons';
3
+ import { ConsoleItemIcon } from './ConsoleItemIcon';
4
+
5
+ describe('ConsoleItemIcon', () => {
6
+ it('renders the green open pull-request icon for an open PR', () => {
7
+ const { getByRole } = render(
8
+ <ConsoleItemIcon
9
+ isPr
10
+ state="open"
11
+ merged={false}
12
+ isDraft={false}
13
+ stateReason=""
14
+ />,
15
+ );
16
+ const svg = getByRole('img');
17
+ expect(svg).toHaveAttribute('fill', CONSOLE_ITEM_ICONS.prOpen.color);
18
+ expect(svg).toHaveAttribute('aria-label', 'prOpen');
19
+ });
20
+
21
+ it('renders the red closed pull-request icon for a closed PR', () => {
22
+ const { getByRole } = render(
23
+ <ConsoleItemIcon
24
+ isPr
25
+ state="closed"
26
+ merged={false}
27
+ isDraft={false}
28
+ stateReason=""
29
+ />,
30
+ );
31
+ expect(getByRole('img')).toHaveAttribute(
32
+ 'fill',
33
+ CONSOLE_ITEM_ICONS.prClosed.color,
34
+ );
35
+ });
36
+
37
+ it('renders the gray not-planned icon for a closed unplanned issue', () => {
38
+ const { getByRole } = render(
39
+ <ConsoleItemIcon
40
+ isPr={false}
41
+ state="closed"
42
+ merged={false}
43
+ isDraft={false}
44
+ stateReason="not_planned"
45
+ />,
46
+ );
47
+ expect(getByRole('img')).toHaveAttribute(
48
+ 'fill',
49
+ CONSOLE_ITEM_ICONS.issueClosedNotPlanned.color,
50
+ );
51
+ });
52
+ });
@@ -0,0 +1,32 @@
1
+ import {
2
+ CONSOLE_ITEM_ICONS,
3
+ type ConsoleItemIconInput,
4
+ resolveConsoleItemIconKind,
5
+ } from '../../logic/itemIcons';
6
+
7
+ export type ConsoleItemIconProps = ConsoleItemIconInput & {
8
+ size?: number;
9
+ };
10
+
11
+ export const ConsoleItemIcon = ({
12
+ size = 12,
13
+ ...input
14
+ }: ConsoleItemIconProps) => {
15
+ const kind = resolveConsoleItemIconKind(input);
16
+ const definition = CONSOLE_ITEM_ICONS[kind];
17
+ return (
18
+ <svg
19
+ className="console-item-icon"
20
+ viewBox="0 0 16 16"
21
+ width={size}
22
+ height={size}
23
+ fill={definition.color}
24
+ role="img"
25
+ aria-label={kind}
26
+ >
27
+ {definition.paths.map((d) => (
28
+ <path key={d} d={d} />
29
+ ))}
30
+ </svg>
31
+ );
32
+ };
@@ -0,0 +1,31 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import {
3
+ consoleChangedFilesFixture,
4
+ consoleCommitsFixture,
5
+ consoleRelatedPullRequestsFixture,
6
+ } from '../../testing/fixtures';
7
+ import { ConsolePullRequestDetail } from './ConsolePullRequestDetail';
8
+
9
+ const meta: Meta<typeof ConsolePullRequestDetail> = {
10
+ title: 'Console/ConsolePullRequestDetail',
11
+ component: ConsolePullRequestDetail,
12
+ args: { now: Date.parse('2026-06-19T12:00:00.000Z') },
13
+ };
14
+
15
+ export default meta;
16
+
17
+ type Story = StoryObj<typeof ConsolePullRequestDetail>;
18
+
19
+ export const WithDetail: Story = {
20
+ args: {
21
+ pullRequest: consoleRelatedPullRequestsFixture[0],
22
+ body: consoleRelatedPullRequestsFixture[0].summary?.body ?? '',
23
+ bodyIsLoading: false,
24
+ files: consoleChangedFilesFixture,
25
+ filesAreLoading: false,
26
+ filesError: null,
27
+ commits: consoleCommitsFixture,
28
+ commitsAreLoading: false,
29
+ commitsError: null,
30
+ },
31
+ };
@@ -0,0 +1,40 @@
1
+ import { render } from '@testing-library/react';
2
+ import {
3
+ consoleChangedFilesFixture,
4
+ consoleCommitsFixture,
5
+ consoleRelatedPullRequestsFixture,
6
+ } from '../../testing/fixtures';
7
+ import { ConsolePullRequestDetail } from './ConsolePullRequestDetail';
8
+
9
+ jest.mock('../../lib/mermaidLoader', () => ({
10
+ renderMermaidToSvg: jest.fn(async () => '<svg></svg>'),
11
+ }));
12
+
13
+ const now = Date.parse('2026-06-19T12:00:00.000Z');
14
+ const pullRequest = consoleRelatedPullRequestsFixture[0];
15
+
16
+ describe('ConsolePullRequestDetail', () => {
17
+ it('renders the title, stat bar and changed files', () => {
18
+ const { getByText } = render(
19
+ <ConsolePullRequestDetail
20
+ pullRequest={pullRequest}
21
+ body={pullRequest.summary?.body ?? ''}
22
+ bodyIsLoading={false}
23
+ files={consoleChangedFilesFixture}
24
+ filesAreLoading={false}
25
+ filesError={null}
26
+ commits={consoleCommitsFixture}
27
+ commitsAreLoading={false}
28
+ commitsError={null}
29
+ now={now}
30
+ />,
31
+ );
32
+ expect(
33
+ getByText(
34
+ 'Scaffold React console UI under entry-points with build bundling',
35
+ ),
36
+ ).toBeInTheDocument();
37
+ expect(getByText('+1184')).toBeInTheDocument();
38
+ expect(getByText('27 files')).toBeInTheDocument();
39
+ });
40
+ });
@@ -0,0 +1,88 @@
1
+ import type {
2
+ ConsoleChangedFile,
3
+ ConsoleCommit,
4
+ ConsoleRelatedPullRequest,
5
+ } from '../../logic/types';
6
+ import { ConsoleMarkdownContent } from '../content/ConsoleMarkdownContent';
7
+ import { ConsolePanel } from '../layout/ConsolePanel';
8
+ import { ConsoleChangedFileList } from './ConsoleChangedFileList';
9
+ import { ConsoleCommitList } from './ConsoleCommitList';
10
+
11
+ export type ConsolePullRequestSectionProps = {
12
+ pullRequest: ConsoleRelatedPullRequest;
13
+ body: string;
14
+ bodyIsLoading: boolean;
15
+ files: ConsoleChangedFile[];
16
+ filesAreLoading: boolean;
17
+ filesError: string | null;
18
+ commits: ConsoleCommit[];
19
+ commitsAreLoading: boolean;
20
+ commitsError: string | null;
21
+ now: number;
22
+ };
23
+
24
+ export const ConsolePullRequestDetail = ({
25
+ pullRequest,
26
+ body,
27
+ bodyIsLoading,
28
+ files,
29
+ filesAreLoading,
30
+ filesError,
31
+ commits,
32
+ commitsAreLoading,
33
+ commitsError,
34
+ now,
35
+ }: ConsolePullRequestSectionProps) => {
36
+ const summary = pullRequest.summary;
37
+ return (
38
+ <section className="console-pr-section">
39
+ <header className="console-pr-section-header">
40
+ <a
41
+ href={pullRequest.url}
42
+ className="console-pr-section-title"
43
+ target="_blank"
44
+ rel="noopener noreferrer"
45
+ >
46
+ {summary?.title ?? pullRequest.url}
47
+ </a>
48
+ {pullRequest.isDraft && (
49
+ <span className="console-pr-section-state">draft</span>
50
+ )}
51
+ </header>
52
+ <div className="console-pr-statbar">
53
+ {pullRequest.branchName !== null && (
54
+ <span className="console-pr-branch">{pullRequest.branchName}</span>
55
+ )}
56
+ {summary !== null && (
57
+ <>
58
+ <span className="console-pr-add">+{summary.additions}</span>
59
+ <span className="console-pr-del">-{summary.deletions}</span>
60
+ <span className="console-pr-files-count">
61
+ {summary.changedFiles} files
62
+ </span>
63
+ </>
64
+ )}
65
+ </div>
66
+ {bodyIsLoading ? (
67
+ <p className="console-pr-body-loading">Loading description...</p>
68
+ ) : (
69
+ <ConsoleMarkdownContent body={summary?.body ?? body} />
70
+ )}
71
+ <ConsolePanel title="Changed files">
72
+ <ConsoleChangedFileList
73
+ files={files}
74
+ isLoading={filesAreLoading}
75
+ error={filesError}
76
+ />
77
+ </ConsolePanel>
78
+ <ConsolePanel title="Commits" defaultCollapsed>
79
+ <ConsoleCommitList
80
+ commits={commits}
81
+ isLoading={commitsAreLoading}
82
+ error={commitsError}
83
+ now={now}
84
+ />
85
+ </ConsolePanel>
86
+ </section>
87
+ );
88
+ };
@@ -0,0 +1,26 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ConsolePanel } from './ConsolePanel';
3
+
4
+ const meta: Meta<typeof ConsolePanel> = {
5
+ title: 'Console/ConsolePanel',
6
+ component: ConsolePanel,
7
+ };
8
+
9
+ export default meta;
10
+
11
+ type Story = StoryObj<typeof ConsolePanel>;
12
+
13
+ export const Expanded: Story = {
14
+ args: {
15
+ title: 'Description',
16
+ children: <p style={{ padding: 12 }}>Panel body content</p>,
17
+ },
18
+ };
19
+
20
+ export const Collapsed: Story = {
21
+ args: {
22
+ title: 'Commits',
23
+ defaultCollapsed: true,
24
+ children: <p style={{ padding: 12 }}>Panel body content</p>,
25
+ },
26
+ };
@@ -0,0 +1,32 @@
1
+ import { fireEvent, render } from '@testing-library/react';
2
+ import { ConsolePanel } from './ConsolePanel';
3
+
4
+ describe('ConsolePanel', () => {
5
+ it('renders expanded by default', () => {
6
+ const { getByText } = render(
7
+ <ConsolePanel title="Description">
8
+ <p>content</p>
9
+ </ConsolePanel>,
10
+ );
11
+ expect(getByText('content')).toBeInTheDocument();
12
+ });
13
+
14
+ it('renders collapsed when defaultCollapsed is set', () => {
15
+ const { queryByText } = render(
16
+ <ConsolePanel title="Commits" defaultCollapsed>
17
+ <p>content</p>
18
+ </ConsolePanel>,
19
+ );
20
+ expect(queryByText('content')).toBeNull();
21
+ });
22
+
23
+ it('toggles collapsed state on the header button', () => {
24
+ const { getByRole, queryByText } = render(
25
+ <ConsolePanel title="Comments" defaultCollapsed>
26
+ <p>content</p>
27
+ </ConsolePanel>,
28
+ );
29
+ fireEvent.click(getByRole('button'));
30
+ expect(queryByText('content')).not.toBeNull();
31
+ });
32
+ });
@@ -0,0 +1,36 @@
1
+ import { type ReactNode, useState } from 'react';
2
+
3
+ export type ConsolePanelProps = {
4
+ title: string;
5
+ defaultCollapsed?: boolean;
6
+ headerAction?: ReactNode;
7
+ children: ReactNode;
8
+ };
9
+
10
+ export const ConsolePanel = ({
11
+ title,
12
+ defaultCollapsed = false,
13
+ headerAction,
14
+ children,
15
+ }: ConsolePanelProps) => {
16
+ const [collapsed, setCollapsed] = useState<boolean>(defaultCollapsed);
17
+ return (
18
+ <section className="console-panel">
19
+ <header className="console-panel-header">
20
+ <button
21
+ type="button"
22
+ className="console-panel-toggle"
23
+ aria-expanded={!collapsed}
24
+ onClick={() => setCollapsed((value) => !value)}
25
+ >
26
+ <span className="console-panel-caret">{collapsed ? '▸' : '▾'}</span>
27
+ <span className="console-panel-title">{title}</span>
28
+ </button>
29
+ {headerAction !== undefined && (
30
+ <div className="console-panel-action">{headerAction}</div>
31
+ )}
32
+ </header>
33
+ {!collapsed && <div className="console-panel-body">{children}</div>}
34
+ </section>
35
+ );
36
+ };
@@ -1,14 +1,14 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { ConsoleProjectHeader } from './ConsoleProjectHeader';
2
+ import { ConsoleProjectSummary } from './ConsoleProjectSummary';
3
3
 
4
- const meta: Meta<typeof ConsoleProjectHeader> = {
5
- title: 'Console/ConsoleProjectHeader',
6
- component: ConsoleProjectHeader,
4
+ const meta: Meta<typeof ConsoleProjectSummary> = {
5
+ title: 'Console/ConsoleProjectSummary',
6
+ component: ConsoleProjectSummary,
7
7
  };
8
8
 
9
9
  export default meta;
10
10
 
11
- type Story = StoryObj<typeof ConsoleProjectHeader>;
11
+ type Story = StoryObj<typeof ConsoleProjectSummary>;
12
12
 
13
13
  export const WithProject: Story = {
14
14
  args: {
@@ -0,0 +1,14 @@
1
+ import { render } from '@testing-library/react';
2
+ import { ConsoleProjectSummary } from './ConsoleProjectSummary';
3
+
4
+ describe('ConsoleProjectSummary', () => {
5
+ it('shows the active project code when a pjcode is provided', () => {
6
+ const { getByText } = render(<ConsoleProjectSummary pjcode="umino" />);
7
+ expect(getByText('project: umino')).toBeInTheDocument();
8
+ });
9
+
10
+ it('shows a no-project message when the pjcode is null', () => {
11
+ const { getByText } = render(<ConsoleProjectSummary pjcode={null} />);
12
+ expect(getByText('no project selected')).toBeInTheDocument();
13
+ });
14
+ });
@@ -2,7 +2,9 @@ export type ConsoleProjectHeaderProps = {
2
2
  pjcode: string | null;
3
3
  };
4
4
 
5
- export const ConsoleProjectHeader = ({ pjcode }: ConsoleProjectHeaderProps) => (
5
+ export const ConsoleProjectSummary = ({
6
+ pjcode,
7
+ }: ConsoleProjectHeaderProps) => (
6
8
  <header className="flex items-baseline gap-2 border-b border-border p-3">
7
9
  <h1 className="text-base font-semibold">TDPM Console</h1>
8
10
  {pjcode === null ? (
@@ -0,0 +1,70 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { useState } from 'react';
3
+ import type { ConsoleTabName } from '../../logic/types';
4
+ import { ConsoleTabList } from './ConsoleTabList';
5
+
6
+ const meta: Meta<typeof ConsoleTabList> = {
7
+ title: 'Console/ConsoleTabList',
8
+ component: ConsoleTabList,
9
+ };
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof ConsoleTabList>;
14
+
15
+ const counts: Record<ConsoleTabName, number> = {
16
+ prs: 35,
17
+ triage: 12,
18
+ unread: 7,
19
+ 'failed-preparation': 2,
20
+ 'todo-by-human': 4,
21
+ };
22
+
23
+ export const AllTabsWithCounts: Story = {
24
+ args: {
25
+ activeTab: 'prs',
26
+ counts,
27
+ onSelectTab: () => {},
28
+ },
29
+ };
30
+
31
+ export const ZeroCountTabsHidden: Story = {
32
+ args: {
33
+ activeTab: 'prs',
34
+ counts: {
35
+ prs: 35,
36
+ triage: 0,
37
+ unread: 7,
38
+ 'failed-preparation': 0,
39
+ 'todo-by-human': 0,
40
+ },
41
+ onSelectTab: () => {},
42
+ },
43
+ };
44
+
45
+ export const ActiveZeroCountTabStaysVisible: Story = {
46
+ args: {
47
+ activeTab: 'triage',
48
+ counts: {
49
+ prs: 35,
50
+ triage: 0,
51
+ unread: 7,
52
+ 'failed-preparation': 0,
53
+ 'todo-by-human': 0,
54
+ },
55
+ onSelectTab: () => {},
56
+ },
57
+ };
58
+
59
+ export const Interactive: Story = {
60
+ render: () => {
61
+ const [activeTab, setActiveTab] = useState<ConsoleTabName>('prs');
62
+ return (
63
+ <ConsoleTabList
64
+ activeTab={activeTab}
65
+ counts={counts}
66
+ onSelectTab={setActiveTab}
67
+ />
68
+ );
69
+ },
70
+ };
@@ -0,0 +1,59 @@
1
+ import { fireEvent, render } from '@testing-library/react';
2
+ import type { ConsoleTabName } from '../../logic/types';
3
+ import { ConsoleTabList } from './ConsoleTabList';
4
+
5
+ const counts: Record<ConsoleTabName, number> = {
6
+ prs: 3,
7
+ triage: 0,
8
+ unread: 5,
9
+ 'failed-preparation': 0,
10
+ 'todo-by-human': 2,
11
+ };
12
+
13
+ describe('ConsoleTabList', () => {
14
+ it('hides zero-count tabs except the active tab', () => {
15
+ const { queryByText } = render(
16
+ <ConsoleTabList activeTab="prs" counts={counts} onSelectTab={() => {}} />,
17
+ );
18
+ expect(queryByText('Awaiting Quality Check')).not.toBeNull();
19
+ expect(queryByText('Unread')).not.toBeNull();
20
+ expect(queryByText('Todo by human')).not.toBeNull();
21
+ expect(queryByText('Triage')).toBeNull();
22
+ expect(queryByText('Failed Preparation')).toBeNull();
23
+ });
24
+
25
+ it('keeps a zero-count active tab visible', () => {
26
+ const { queryByText } = render(
27
+ <ConsoleTabList
28
+ activeTab="triage"
29
+ counts={counts}
30
+ onSelectTab={() => {}}
31
+ />,
32
+ );
33
+ expect(queryByText('Triage')).not.toBeNull();
34
+ });
35
+
36
+ it('uses the exact lowercase Todo by human label', () => {
37
+ const { getByText } = render(
38
+ <ConsoleTabList
39
+ activeTab="todo-by-human"
40
+ counts={counts}
41
+ onSelectTab={() => {}}
42
+ />,
43
+ );
44
+ expect(getByText('Todo by human')).toBeInTheDocument();
45
+ });
46
+
47
+ it('reports the selected tab', () => {
48
+ const onSelectTab = jest.fn();
49
+ const { getByText } = render(
50
+ <ConsoleTabList
51
+ activeTab="prs"
52
+ counts={counts}
53
+ onSelectTab={onSelectTab}
54
+ />,
55
+ );
56
+ fireEvent.click(getByText('Unread'));
57
+ expect(onSelectTab).toHaveBeenCalledWith('unread');
58
+ });
59
+ });
@@ -0,0 +1,41 @@
1
+ import { CONSOLE_TABS, type ConsoleTabName } from '../../logic/types';
2
+
3
+ export type ConsoleTabBarProps = {
4
+ activeTab: ConsoleTabName;
5
+ counts: Record<ConsoleTabName, number>;
6
+ onSelectTab: (tab: ConsoleTabName) => void;
7
+ };
8
+
9
+ export const ConsoleTabList = ({
10
+ activeTab,
11
+ counts,
12
+ onSelectTab,
13
+ }: ConsoleTabBarProps) => (
14
+ <nav aria-label="Console tabs" className="console-tabbar">
15
+ {CONSOLE_TABS.map((tab) => {
16
+ const count = counts[tab.name] ?? 0;
17
+ const isActive = tab.name === activeTab;
18
+ if (count === 0 && !isActive) {
19
+ return null;
20
+ }
21
+ return (
22
+ <button
23
+ key={tab.name}
24
+ type="button"
25
+ className="console-tab"
26
+ data-active={isActive ? 'true' : undefined}
27
+ aria-current={isActive ? 'page' : undefined}
28
+ onClick={() => onSelectTab(tab.name)}
29
+ >
30
+ <span className="console-tab-label">{tab.label}</span>
31
+ <span
32
+ className="console-tab-badge"
33
+ data-zero={count === 0 ? 'true' : undefined}
34
+ >
35
+ {count}
36
+ </span>
37
+ </button>
38
+ );
39
+ })}
40
+ </nav>
41
+ );