github-issue-tower-defence-management 1.90.0 → 1.91.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 (155) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +14 -1
  3. package/bin/adapter/entry-points/cli/index.js +16 -12
  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/repositories/issue/ApiV3CheerioRestIssueRepository.js +16 -0
  17. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  18. package/jest.config.js +57 -9
  19. package/package.json +17 -13
  20. package/src/adapter/entry-points/cli/index.test.ts +12 -2
  21. package/src/adapter/entry-points/cli/index.ts +30 -12
  22. package/src/adapter/entry-points/cli/projectConfig.ts +3 -0
  23. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +129 -15
  24. package/src/adapter/entry-points/console/consoleOperationApi.ts +83 -28
  25. package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +96 -0
  26. package/src/adapter/entry-points/console/consoleProjectResolver.ts +50 -0
  27. package/src/adapter/entry-points/console/consoleServer.test.ts +5 -4
  28. package/src/adapter/entry-points/console/consoleServer.ts +5 -7
  29. package/src/adapter/entry-points/console/ui/jest.setup.ts +1 -0
  30. package/src/adapter/entry-points/console/ui/jest.styleMock.js +1 -0
  31. package/src/adapter/entry-points/console/ui/src/features/console/colors.test.ts +34 -0
  32. package/src/adapter/entry-points/console/ui/src/features/console/colors.ts +73 -0
  33. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleChangedFileList.stories.tsx +28 -0
  34. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleChangedFileList.test.tsx +42 -0
  35. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleChangedFileList.tsx +55 -0
  36. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCloseButtonGroup.stories.tsx +14 -0
  37. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCloseButtonGroup.test.tsx +23 -0
  38. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCloseButtonGroup.tsx +26 -0
  39. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommentList.stories.tsx +29 -0
  40. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommentList.test.tsx +55 -0
  41. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommentList.tsx +66 -0
  42. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommitList.stories.tsx +25 -0
  43. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommitList.test.tsx +53 -0
  44. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommitList.tsx +53 -0
  45. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemDetail.stories.tsx +79 -0
  46. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemDetail.test.tsx +81 -0
  47. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemDetail.tsx +226 -0
  48. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemIcon.stories.tsx +82 -0
  49. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemIcon.test.tsx +52 -0
  50. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemIcon.tsx +32 -0
  51. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListItemRow.stories.tsx +25 -0
  52. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListItemRow.test.tsx +43 -0
  53. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListItemRow.tsx +34 -0
  54. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListView.stories.tsx +22 -6
  55. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListView.test.tsx +87 -0
  56. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListView.tsx +36 -32
  57. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMarkdownView.stories.tsx +27 -0
  58. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMarkdownView.test.tsx +36 -0
  59. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMarkdownView.tsx +50 -0
  60. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMermaidDiagram.stories.tsx +22 -0
  61. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMermaidDiagram.test.tsx +38 -0
  62. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMermaidDiagram.tsx +65 -0
  63. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleNextActionDateGroup.stories.tsx +20 -0
  64. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleNextActionDateGroup.test.tsx +42 -0
  65. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleNextActionDateGroup.tsx +28 -0
  66. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleOperationBar.stories.tsx +55 -0
  67. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleOperationBar.test.tsx +85 -0
  68. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleOperationBar.tsx +55 -0
  69. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePanel.stories.tsx +26 -0
  70. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePanel.test.tsx +32 -0
  71. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePanel.tsx +36 -0
  72. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleProjectHeader.test.tsx +14 -0
  73. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestReviewGroup.stories.tsx +14 -0
  74. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestReviewGroup.test.tsx +33 -0
  75. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestReviewGroup.tsx +34 -0
  76. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestSection.stories.tsx +31 -0
  77. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestSection.test.tsx +40 -0
  78. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestSection.tsx +88 -0
  79. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStatusButtonGroup.stories.tsx +17 -0
  80. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStatusButtonGroup.test.tsx +49 -0
  81. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStatusButtonGroup.tsx +63 -0
  82. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryButtonGroup.stories.tsx +17 -0
  83. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryButtonGroup.test.tsx +45 -0
  84. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryButtonGroup.tsx +42 -0
  85. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryGroupHeader.stories.tsx +27 -0
  86. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryGroupHeader.test.tsx +24 -0
  87. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryGroupHeader.tsx +28 -0
  88. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleTabBar.stories.tsx +41 -5
  89. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleTabBar.test.tsx +59 -0
  90. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleTabBar.tsx +28 -19
  91. package/src/adapter/entry-points/console/ui/src/features/console/fileStatus.test.ts +35 -0
  92. package/src/adapter/entry-points/console/ui/src/features/console/fileStatus.ts +21 -0
  93. package/src/adapter/entry-points/console/ui/src/features/console/fixtures.ts +206 -9
  94. package/src/adapter/entry-points/console/ui/src/features/console/grouping.test.ts +91 -0
  95. package/src/adapter/entry-points/console/ui/src/features/console/grouping.ts +79 -0
  96. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.test.ts +22 -0
  97. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +42 -0
  98. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +126 -0
  99. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.ts +167 -0
  100. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +198 -0
  101. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +243 -0
  102. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts +40 -0
  103. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.ts +71 -0
  104. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.test.ts +41 -0
  105. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.ts +57 -0
  106. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +63 -0
  107. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +129 -0
  108. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleToken.test.ts +41 -0
  109. package/src/adapter/entry-points/console/ui/src/features/console/itemIcons.test.ts +97 -0
  110. package/src/adapter/entry-points/console/ui/src/features/console/itemIcons.ts +95 -0
  111. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +155 -0
  112. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +187 -0
  113. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.test.ts +76 -0
  114. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.ts +73 -0
  115. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.test.ts +27 -0
  116. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.ts +71 -0
  117. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.test.ts +56 -0
  118. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +51 -0
  119. package/src/adapter/entry-points/console/ui/src/features/console/operations.test.ts +37 -0
  120. package/src/adapter/entry-points/console/ui/src/features/console/operations.ts +35 -0
  121. package/src/adapter/entry-points/console/ui/src/features/console/overlay.test.ts +124 -0
  122. package/src/adapter/entry-points/console/ui/src/features/console/overlay.ts +101 -0
  123. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +79 -0
  124. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +109 -0
  125. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +74 -0
  126. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +133 -7
  127. package/src/adapter/entry-points/console/ui/src/features/console/relativeTime.test.ts +52 -0
  128. package/src/adapter/entry-points/console/ui/src/features/console/relativeTime.ts +51 -0
  129. package/src/adapter/entry-points/console/ui/src/features/console/types.ts +73 -1
  130. package/src/adapter/entry-points/console/ui/src/index.css +352 -2
  131. package/src/adapter/entry-points/console/ui/tsconfig.json +1 -0
  132. package/src/adapter/entry-points/console/ui/vite.config.ts +5 -0
  133. package/src/adapter/entry-points/console/ui-dist/assets/index-BU6p3cGU.css +1 -0
  134. package/src/adapter/entry-points/console/ui-dist/assets/index-BvuSQN9s.js +100 -0
  135. package/src/adapter/entry-points/console/ui-dist/index.html +2 -2
  136. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +25 -0
  137. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +4 -0
  138. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  139. package/types/adapter/entry-points/cli/projectConfig.d.ts +1 -0
  140. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  141. package/types/adapter/entry-points/console/consoleOperationApi.d.ts +6 -2
  142. package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
  143. package/types/adapter/entry-points/console/consoleProjectResolver.d.ts +6 -0
  144. package/types/adapter/entry-points/console/consoleProjectResolver.d.ts.map +1 -0
  145. package/types/adapter/entry-points/console/consoleServer.d.ts +2 -3
  146. package/types/adapter/entry-points/console/consoleServer.d.ts.map +1 -1
  147. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +1 -0
  148. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  149. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +1 -0
  150. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
  151. package/bin/adapter/entry-points/console/ui-dist/assets/index-DDjYPXRT.js +0 -49
  152. package/bin/adapter/entry-points/console/ui-dist/assets/index-DHlBLm7d.css +0 -1
  153. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleList.ts +0 -78
  154. package/src/adapter/entry-points/console/ui-dist/assets/index-DDjYPXRT.js +0 -49
  155. package/src/adapter/entry-points/console/ui-dist/assets/index-DHlBLm7d.css +0 -1
@@ -0,0 +1,20 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ConsoleNextActionDateGroup } from './ConsoleNextActionDateGroup';
3
+
4
+ const meta: Meta<typeof ConsoleNextActionDateGroup> = {
5
+ title: 'Console/ConsoleNextActionDateGroup',
6
+ component: ConsoleNextActionDateGroup,
7
+ args: { onSetNextActionDate: () => {} },
8
+ };
9
+
10
+ export default meta;
11
+
12
+ type Story = StoryObj<typeof ConsoleNextActionDateGroup>;
13
+
14
+ export const StandardTab: Story = {
15
+ args: { isTodoByHuman: false },
16
+ };
17
+
18
+ export const TodoByHumanTab: Story = {
19
+ args: { isTodoByHuman: true },
20
+ };
@@ -0,0 +1,42 @@
1
+ import { fireEvent, render } from '@testing-library/react';
2
+ import { ConsoleNextActionDateGroup } from './ConsoleNextActionDateGroup';
3
+
4
+ describe('ConsoleNextActionDateGroup', () => {
5
+ it('shows +1 day and +1 week outside the todo-by-human tab', () => {
6
+ const { getByText, queryByText } = render(
7
+ <ConsoleNextActionDateGroup
8
+ isTodoByHuman={false}
9
+ onSetNextActionDate={() => {}}
10
+ />,
11
+ );
12
+ expect(getByText('+1 day')).toBeInTheDocument();
13
+ expect(getByText('+1 week')).toBeInTheDocument();
14
+ expect(queryByText('+1 week and skip')).toBeNull();
15
+ });
16
+
17
+ it('shows +1 week and skip on the todo-by-human tab', () => {
18
+ const { getByText } = render(
19
+ <ConsoleNextActionDateGroup
20
+ isTodoByHuman
21
+ onSetNextActionDate={() => {}}
22
+ />,
23
+ );
24
+ expect(getByText('+1 week and skip')).toBeInTheDocument();
25
+ });
26
+
27
+ it('reports the snooze actions', () => {
28
+ const onSetNextActionDate = jest.fn();
29
+ const { getByText } = render(
30
+ <ConsoleNextActionDateGroup
31
+ isTodoByHuman={false}
32
+ onSetNextActionDate={onSetNextActionDate}
33
+ />,
34
+ );
35
+ fireEvent.click(getByText('+1 day'));
36
+ fireEvent.click(getByText('+1 week'));
37
+ expect(onSetNextActionDate.mock.calls.map((call) => call[0])).toEqual([
38
+ 'snooze_1day',
39
+ 'snooze_1week',
40
+ ]);
41
+ });
42
+ });
@@ -0,0 +1,28 @@
1
+ import type { ConsoleNextActionDateAction } from '../operations';
2
+
3
+ export type ConsoleNextActionDateGroupProps = {
4
+ isTodoByHuman: boolean;
5
+ onSetNextActionDate: (action: ConsoleNextActionDateAction) => void;
6
+ };
7
+
8
+ export const ConsoleNextActionDateGroup = ({
9
+ isTodoByHuman,
10
+ onSetNextActionDate,
11
+ }: ConsoleNextActionDateGroupProps) => (
12
+ <div className="console-op-group">
13
+ <button
14
+ type="button"
15
+ className="console-op-button"
16
+ onClick={() => onSetNextActionDate('snooze_1day')}
17
+ >
18
+ +1 day
19
+ </button>
20
+ <button
21
+ type="button"
22
+ className="console-op-button"
23
+ onClick={() => onSetNextActionDate('snooze_1week')}
24
+ >
25
+ {isTodoByHuman ? '+1 week and skip' : '+1 week'}
26
+ </button>
27
+ </div>
28
+ );
@@ -0,0 +1,55 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import {
3
+ consoleListItemsFixture,
4
+ consoleStatusOptionsFixture,
5
+ consoleStoryOptionsFixture,
6
+ } from '../fixtures';
7
+ import type { ConsoleOperationHandlers } from '../operations';
8
+ import { ConsoleOperationBar } from './ConsoleOperationBar';
9
+
10
+ const handlers: ConsoleOperationHandlers = {
11
+ onReview: () => {},
12
+ onSetNextActionDate: () => {},
13
+ onSetStory: () => {},
14
+ onSetStatus: () => {},
15
+ onSetInTmuxByHuman: () => {},
16
+ onClose: () => {},
17
+ };
18
+
19
+ const meta: Meta<typeof ConsoleOperationBar> = {
20
+ title: 'Console/ConsoleOperationBar',
21
+ component: ConsoleOperationBar,
22
+ args: {
23
+ statusOptions: consoleStatusOptionsFixture,
24
+ storyOptions: consoleStoryOptionsFixture,
25
+ handlers,
26
+ },
27
+ };
28
+
29
+ export default meta;
30
+
31
+ type Story = StoryObj<typeof ConsoleOperationBar>;
32
+
33
+ export const PrsTabPullRequest: Story = {
34
+ args: {
35
+ tab: 'prs',
36
+ item: consoleListItemsFixture[0],
37
+ hasPullRequest: true,
38
+ },
39
+ };
40
+
41
+ export const TriageTabIssueWithStoryGroup: Story = {
42
+ args: {
43
+ tab: 'triage',
44
+ item: consoleListItemsFixture[2],
45
+ hasPullRequest: false,
46
+ },
47
+ };
48
+
49
+ export const TodoByHumanTabIssue: Story = {
50
+ args: {
51
+ tab: 'todo-by-human',
52
+ item: consoleListItemsFixture[2],
53
+ hasPullRequest: false,
54
+ },
55
+ };
@@ -0,0 +1,85 @@
1
+ import { render } from '@testing-library/react';
2
+ import {
3
+ consoleListItemsFixture,
4
+ consoleStatusOptionsFixture,
5
+ consoleStoryOptionsFixture,
6
+ } from '../fixtures';
7
+ import type { ConsoleOperationHandlers } from '../operations';
8
+ import { ConsoleOperationBar } from './ConsoleOperationBar';
9
+
10
+ const handlers: ConsoleOperationHandlers = {
11
+ onReview: jest.fn(),
12
+ onSetNextActionDate: jest.fn(),
13
+ onSetStory: jest.fn(),
14
+ onSetStatus: jest.fn(),
15
+ onSetInTmuxByHuman: jest.fn(),
16
+ onClose: jest.fn(),
17
+ };
18
+
19
+ const prItem = consoleListItemsFixture[0];
20
+ const issueItem = consoleListItemsFixture[2];
21
+
22
+ describe('ConsoleOperationBar', () => {
23
+ it('shows the review group for a PR and hides story and close groups outside triage', () => {
24
+ const { getByText, queryByText } = render(
25
+ <ConsoleOperationBar
26
+ tab="prs"
27
+ item={prItem}
28
+ hasPullRequest
29
+ statusOptions={consoleStatusOptionsFixture}
30
+ storyOptions={consoleStoryOptionsFixture}
31
+ handlers={handlers}
32
+ />,
33
+ );
34
+ expect(getByText('Approve')).toBeInTheDocument();
35
+ expect(getByText('+1 day')).toBeInTheDocument();
36
+ expect(getByText('Awaiting Workspace')).toBeInTheDocument();
37
+ expect(queryByText('Close')).toBeNull();
38
+ expect(queryByText('TDPM Console port')).toBeNull();
39
+ });
40
+
41
+ it('shows the story group on the triage tab and the close group for an issue', () => {
42
+ const { getByText } = render(
43
+ <ConsoleOperationBar
44
+ tab="triage"
45
+ item={issueItem}
46
+ hasPullRequest={false}
47
+ statusOptions={consoleStatusOptionsFixture}
48
+ storyOptions={consoleStoryOptionsFixture}
49
+ handlers={handlers}
50
+ />,
51
+ );
52
+ expect(getByText('Move to Okinawa')).toBeInTheDocument();
53
+ expect(getByText('Close')).toBeInTheDocument();
54
+ expect(getByText('Close as not planned')).toBeInTheDocument();
55
+ });
56
+
57
+ it('shows +1 week and skip on the todo-by-human tab', () => {
58
+ const { getByText } = render(
59
+ <ConsoleOperationBar
60
+ tab="todo-by-human"
61
+ item={issueItem}
62
+ hasPullRequest={false}
63
+ statusOptions={consoleStatusOptionsFixture}
64
+ storyOptions={consoleStoryOptionsFixture}
65
+ handlers={handlers}
66
+ />,
67
+ );
68
+ expect(getByText('+1 week and skip')).toBeInTheDocument();
69
+ });
70
+
71
+ it('hides the review group when there is no pull request', () => {
72
+ const { queryByText } = render(
73
+ <ConsoleOperationBar
74
+ tab="unread"
75
+ item={issueItem}
76
+ hasPullRequest={false}
77
+ statusOptions={consoleStatusOptionsFixture}
78
+ storyOptions={consoleStoryOptionsFixture}
79
+ handlers={handlers}
80
+ />,
81
+ );
82
+ expect(queryByText('Approve')).toBeNull();
83
+ expect(queryByText('Close')).not.toBeNull();
84
+ });
85
+ });
@@ -0,0 +1,55 @@
1
+ import { type ConsoleOperationHandlers, isTodoByHumanTab } from '../operations';
2
+ import type {
3
+ ConsoleFieldOption,
4
+ ConsoleListItem,
5
+ ConsoleTabName,
6
+ } from '../types';
7
+ import { ConsoleCloseButtonGroup } from './ConsoleCloseButtonGroup';
8
+ import { ConsoleNextActionDateGroup } from './ConsoleNextActionDateGroup';
9
+ import { ConsolePullRequestReviewGroup } from './ConsolePullRequestReviewGroup';
10
+ import { ConsoleStatusButtonGroup } from './ConsoleStatusButtonGroup';
11
+ import { ConsoleStoryButtonGroup } from './ConsoleStoryButtonGroup';
12
+
13
+ export type ConsoleOperationBarProps = {
14
+ tab: ConsoleTabName;
15
+ item: ConsoleListItem;
16
+ hasPullRequest: boolean;
17
+ statusOptions: ConsoleFieldOption[];
18
+ storyOptions: ConsoleFieldOption[];
19
+ handlers: ConsoleOperationHandlers;
20
+ };
21
+
22
+ export const ConsoleOperationBar = ({
23
+ tab,
24
+ item,
25
+ hasPullRequest,
26
+ statusOptions,
27
+ storyOptions,
28
+ handlers,
29
+ }: ConsoleOperationBarProps) => {
30
+ const showStory = tab === 'triage';
31
+ const showClose = !item.isPr;
32
+ return (
33
+ <div className="console-operation-bar">
34
+ {hasPullRequest && (
35
+ <ConsolePullRequestReviewGroup onReview={handlers.onReview} />
36
+ )}
37
+ <ConsoleNextActionDateGroup
38
+ isTodoByHuman={isTodoByHumanTab(tab)}
39
+ onSetNextActionDate={handlers.onSetNextActionDate}
40
+ />
41
+ {showStory && (
42
+ <ConsoleStoryButtonGroup
43
+ storyOptions={storyOptions}
44
+ onSetStory={handlers.onSetStory}
45
+ />
46
+ )}
47
+ <ConsoleStatusButtonGroup
48
+ statusOptions={statusOptions}
49
+ onSetStatus={handlers.onSetStatus}
50
+ onSetInTmuxByHuman={handlers.onSetInTmuxByHuman}
51
+ />
52
+ {showClose && <ConsoleCloseButtonGroup onClose={handlers.onClose} />}
53
+ </div>
54
+ );
55
+ };
@@ -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
+ };
@@ -0,0 +1,14 @@
1
+ import { render } from '@testing-library/react';
2
+ import { ConsoleProjectHeader } from './ConsoleProjectHeader';
3
+
4
+ describe('ConsoleProjectHeader', () => {
5
+ it('shows the active project code when a pjcode is provided', () => {
6
+ const { getByText } = render(<ConsoleProjectHeader 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(<ConsoleProjectHeader pjcode={null} />);
12
+ expect(getByText('no project selected')).toBeInTheDocument();
13
+ });
14
+ });
@@ -0,0 +1,14 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ConsolePullRequestReviewGroup } from './ConsolePullRequestReviewGroup';
3
+
4
+ const meta: Meta<typeof ConsolePullRequestReviewGroup> = {
5
+ title: 'Console/ConsolePullRequestReviewGroup',
6
+ component: ConsolePullRequestReviewGroup,
7
+ args: { onReview: () => {} },
8
+ };
9
+
10
+ export default meta;
11
+
12
+ type Story = StoryObj<typeof ConsolePullRequestReviewGroup>;
13
+
14
+ export const Default: Story = {};
@@ -0,0 +1,33 @@
1
+ import { fireEvent, render } from '@testing-library/react';
2
+ import { ConsolePullRequestReviewGroup } from './ConsolePullRequestReviewGroup';
3
+
4
+ describe('ConsolePullRequestReviewGroup', () => {
5
+ it('renders the four buttons left to right', () => {
6
+ const { getAllByRole } = render(
7
+ <ConsolePullRequestReviewGroup onReview={() => {}} />,
8
+ );
9
+ expect(getAllByRole('button').map((button) => button.textContent)).toEqual([
10
+ 'Unnecessary',
11
+ 'Totally wrong',
12
+ 'Reject',
13
+ 'Approve',
14
+ ]);
15
+ });
16
+
17
+ it('reports each review action', () => {
18
+ const onReview = jest.fn();
19
+ const { getByText } = render(
20
+ <ConsolePullRequestReviewGroup onReview={onReview} />,
21
+ );
22
+ fireEvent.click(getByText('Unnecessary'));
23
+ fireEvent.click(getByText('Totally wrong'));
24
+ fireEvent.click(getByText('Reject'));
25
+ fireEvent.click(getByText('Approve'));
26
+ expect(onReview.mock.calls.map((call) => call[0])).toEqual([
27
+ 'unnecessary',
28
+ 'totally_wrong',
29
+ 'request_changes',
30
+ 'approve',
31
+ ]);
32
+ });
33
+ });
@@ -0,0 +1,34 @@
1
+ import type { ConsoleReviewAction } from '../operations';
2
+
3
+ export type ConsolePullRequestReviewGroupProps = {
4
+ onReview: (action: ConsoleReviewAction) => void;
5
+ };
6
+
7
+ const REVIEW_BUTTONS: {
8
+ action: ConsoleReviewAction;
9
+ label: string;
10
+ color: string;
11
+ }[] = [
12
+ { action: 'unnecessary', label: 'Unnecessary', color: '#8b949e' },
13
+ { action: 'totally_wrong', label: 'Totally wrong', color: '#f85149' },
14
+ { action: 'request_changes', label: 'Reject', color: '#d29922' },
15
+ { action: 'approve', label: 'Approve', color: '#3fb950' },
16
+ ];
17
+
18
+ export const ConsolePullRequestReviewGroup = ({
19
+ onReview,
20
+ }: ConsolePullRequestReviewGroupProps) => (
21
+ <div className="console-op-group">
22
+ {REVIEW_BUTTONS.map((button) => (
23
+ <button
24
+ key={button.action}
25
+ type="button"
26
+ className="console-op-button"
27
+ style={{ color: button.color, borderColor: button.color }}
28
+ onClick={() => onReview(button.action)}
29
+ >
30
+ {button.label}
31
+ </button>
32
+ ))}
33
+ </div>
34
+ );
@@ -0,0 +1,31 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import {
3
+ consoleChangedFilesFixture,
4
+ consoleCommitsFixture,
5
+ consoleRelatedPullRequestsFixture,
6
+ } from '../fixtures';
7
+ import { ConsolePullRequestSection } from './ConsolePullRequestSection';
8
+
9
+ const meta: Meta<typeof ConsolePullRequestSection> = {
10
+ title: 'Console/ConsolePullRequestSection',
11
+ component: ConsolePullRequestSection,
12
+ args: { now: Date.parse('2026-06-19T12:00:00.000Z') },
13
+ };
14
+
15
+ export default meta;
16
+
17
+ type Story = StoryObj<typeof ConsolePullRequestSection>;
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 '../fixtures';
7
+ import { ConsolePullRequestSection } from './ConsolePullRequestSection';
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('ConsolePullRequestSection', () => {
17
+ it('renders the title, stat bar and changed files', () => {
18
+ const { getByText } = render(
19
+ <ConsolePullRequestSection
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 '../types';
6
+ import { ConsoleChangedFileList } from './ConsoleChangedFileList';
7
+ import { ConsoleCommitList } from './ConsoleCommitList';
8
+ import { ConsoleMarkdownView } from './ConsoleMarkdownView';
9
+ import { ConsolePanel } from './ConsolePanel';
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 ConsolePullRequestSection = ({
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
+ <ConsoleMarkdownView 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,17 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { consoleStatusOptionsFixture } from '../fixtures';
3
+ import { ConsoleStatusButtonGroup } from './ConsoleStatusButtonGroup';
4
+
5
+ const meta: Meta<typeof ConsoleStatusButtonGroup> = {
6
+ title: 'Console/ConsoleStatusButtonGroup',
7
+ component: ConsoleStatusButtonGroup,
8
+ args: { onSetStatus: () => {}, onSetInTmuxByHuman: () => {} },
9
+ };
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof ConsoleStatusButtonGroup>;
14
+
15
+ export const AllStatusOptions: Story = {
16
+ args: { statusOptions: consoleStatusOptionsFixture },
17
+ };