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,85 @@
1
+ import { render } from '@testing-library/react';
2
+ import type { ConsoleOperationHandlers } from '../../logic/operations';
3
+ import {
4
+ consoleListItemsFixture,
5
+ consoleStatusOptionsFixture,
6
+ consoleStoryOptionsFixture,
7
+ } from '../../testing/fixtures';
8
+ import { ConsoleOperationMenu } from './ConsoleOperationMenu';
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('ConsoleOperationMenu', () => {
23
+ it('shows the review group for a PR and hides story and close groups outside triage', () => {
24
+ const { getByText, queryByText } = render(
25
+ <ConsoleOperationMenu
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
+ <ConsoleOperationMenu
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
+ <ConsoleOperationMenu
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
+ <ConsoleOperationMenu
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,58 @@
1
+ import {
2
+ type ConsoleOperationHandlers,
3
+ isTodoByHumanTab,
4
+ } from '../../logic/operations';
5
+ import type {
6
+ ConsoleFieldOption,
7
+ ConsoleListItem,
8
+ ConsoleTabName,
9
+ } from '../../logic/types';
10
+ import { ConsoleCloseActions } from './ConsoleCloseActions';
11
+ import { ConsoleNextActionDateActions } from './ConsoleNextActionDateActions';
12
+ import { ConsolePullRequestReviewActions } from './ConsolePullRequestReviewActions';
13
+ import { ConsoleStatusActions } from './ConsoleStatusActions';
14
+ import { ConsoleStoryActions } from './ConsoleStoryActions';
15
+
16
+ export type ConsoleOperationBarProps = {
17
+ tab: ConsoleTabName;
18
+ item: ConsoleListItem;
19
+ hasPullRequest: boolean;
20
+ statusOptions: ConsoleFieldOption[];
21
+ storyOptions: ConsoleFieldOption[];
22
+ handlers: ConsoleOperationHandlers;
23
+ };
24
+
25
+ export const ConsoleOperationMenu = ({
26
+ tab,
27
+ item,
28
+ hasPullRequest,
29
+ statusOptions,
30
+ storyOptions,
31
+ handlers,
32
+ }: ConsoleOperationBarProps) => {
33
+ const showStory = tab === 'triage';
34
+ const showClose = !item.isPr;
35
+ return (
36
+ <div className="console-operation-bar">
37
+ {hasPullRequest && (
38
+ <ConsolePullRequestReviewActions onReview={handlers.onReview} />
39
+ )}
40
+ <ConsoleNextActionDateActions
41
+ isTodoByHuman={isTodoByHumanTab(tab)}
42
+ onSetNextActionDate={handlers.onSetNextActionDate}
43
+ />
44
+ {showStory && (
45
+ <ConsoleStoryActions
46
+ storyOptions={storyOptions}
47
+ onSetStory={handlers.onSetStory}
48
+ />
49
+ )}
50
+ <ConsoleStatusActions
51
+ statusOptions={statusOptions}
52
+ onSetStatus={handlers.onSetStatus}
53
+ onSetInTmuxByHuman={handlers.onSetInTmuxByHuman}
54
+ />
55
+ {showClose && <ConsoleCloseActions onClose={handlers.onClose} />}
56
+ </div>
57
+ );
58
+ };
@@ -0,0 +1,14 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ConsolePullRequestReviewActions } from './ConsolePullRequestReviewActions';
3
+
4
+ const meta: Meta<typeof ConsolePullRequestReviewActions> = {
5
+ title: 'Console/ConsolePullRequestReviewActions',
6
+ component: ConsolePullRequestReviewActions,
7
+ args: { onReview: () => {} },
8
+ };
9
+
10
+ export default meta;
11
+
12
+ type Story = StoryObj<typeof ConsolePullRequestReviewActions>;
13
+
14
+ export const Default: Story = {};
@@ -0,0 +1,33 @@
1
+ import { fireEvent, render } from '@testing-library/react';
2
+ import { ConsolePullRequestReviewActions } from './ConsolePullRequestReviewActions';
3
+
4
+ describe('ConsolePullRequestReviewActions', () => {
5
+ it('renders the four buttons left to right', () => {
6
+ const { getAllByRole } = render(
7
+ <ConsolePullRequestReviewActions 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
+ <ConsolePullRequestReviewActions 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 '../../logic/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 ConsolePullRequestReviewActions = ({
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,17 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { consoleStatusOptionsFixture } from '../../testing/fixtures';
3
+ import { ConsoleStatusActions } from './ConsoleStatusActions';
4
+
5
+ const meta: Meta<typeof ConsoleStatusActions> = {
6
+ title: 'Console/ConsoleStatusActions',
7
+ component: ConsoleStatusActions,
8
+ args: { onSetStatus: () => {}, onSetInTmuxByHuman: () => {} },
9
+ };
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof ConsoleStatusActions>;
14
+
15
+ export const AllStatusOptions: Story = {
16
+ args: { statusOptions: consoleStatusOptionsFixture },
17
+ };
@@ -0,0 +1,49 @@
1
+ import { fireEvent, render } from '@testing-library/react';
2
+ import { consoleStatusOptionsFixture } from '../../testing/fixtures';
3
+ import { ConsoleStatusActions } from './ConsoleStatusActions';
4
+
5
+ describe('ConsoleStatusActions', () => {
6
+ it('renders only the four routed status buttons that exist in the data', () => {
7
+ const { getByText, queryByText } = render(
8
+ <ConsoleStatusActions
9
+ statusOptions={consoleStatusOptionsFixture}
10
+ onSetStatus={() => {}}
11
+ onSetInTmuxByHuman={() => {}}
12
+ />,
13
+ );
14
+ expect(getByText('In Tmux by agent')).toBeInTheDocument();
15
+ expect(getByText('In Tmux by human')).toBeInTheDocument();
16
+ expect(getByText('Todo by human')).toBeInTheDocument();
17
+ expect(getByText('Awaiting Workspace')).toBeInTheDocument();
18
+ expect(queryByText('Preparation')).toBeNull();
19
+ });
20
+
21
+ it('routes In Tmux by human to the intmux handler and others to the status handler', () => {
22
+ const onSetStatus = jest.fn();
23
+ const onSetInTmuxByHuman = jest.fn();
24
+ const { getByText } = render(
25
+ <ConsoleStatusActions
26
+ statusOptions={consoleStatusOptionsFixture}
27
+ onSetStatus={onSetStatus}
28
+ onSetInTmuxByHuman={onSetInTmuxByHuman}
29
+ />,
30
+ );
31
+ fireEvent.click(getByText('In Tmux by human'));
32
+ fireEvent.click(getByText('In Tmux by agent'));
33
+ expect(onSetInTmuxByHuman).toHaveBeenCalledTimes(1);
34
+ expect(onSetInTmuxByHuman.mock.calls[0][0].name).toBe('In Tmux by human');
35
+ expect(onSetStatus).toHaveBeenCalledTimes(1);
36
+ expect(onSetStatus.mock.calls[0][0].name).toBe('In Tmux by agent');
37
+ });
38
+
39
+ it('renders nothing when no routed option exists', () => {
40
+ const { container } = render(
41
+ <ConsoleStatusActions
42
+ statusOptions={[{ id: 'x', name: 'Preparation', color: 'YELLOW' }]}
43
+ onSetStatus={() => {}}
44
+ onSetInTmuxByHuman={() => {}}
45
+ />,
46
+ );
47
+ expect(container.firstChild).toBeNull();
48
+ });
49
+ });
@@ -0,0 +1,66 @@
1
+ import { colorFromEnum } from '../../logic/colors';
2
+ import {
3
+ IN_TMUX_BY_HUMAN_NAME,
4
+ STATUS_BUTTON_NAMES,
5
+ } from '../../logic/operations';
6
+ import type { ConsoleFieldOption } from '../../logic/types';
7
+
8
+ export type ConsoleStatusButtonGroupProps = {
9
+ statusOptions: ConsoleFieldOption[];
10
+ onSetStatus: (option: ConsoleFieldOption) => void;
11
+ onSetInTmuxByHuman: (option: ConsoleFieldOption) => void;
12
+ };
13
+
14
+ const findStatusOption = (
15
+ statusOptions: ConsoleFieldOption[],
16
+ name: string,
17
+ ): ConsoleFieldOption | null => {
18
+ const lower = name.toLowerCase();
19
+ return (
20
+ statusOptions.find((option) => option.name.toLowerCase() === lower) ?? null
21
+ );
22
+ };
23
+
24
+ export const ConsoleStatusActions = ({
25
+ statusOptions,
26
+ onSetStatus,
27
+ onSetInTmuxByHuman,
28
+ }: ConsoleStatusButtonGroupProps) => {
29
+ const buttons = STATUS_BUTTON_NAMES.map((name) => ({
30
+ name,
31
+ option: findStatusOption(statusOptions, name),
32
+ })).filter(
33
+ (entry): entry is { name: string; option: ConsoleFieldOption } =>
34
+ entry.option !== null,
35
+ );
36
+
37
+ if (buttons.length === 0) {
38
+ return null;
39
+ }
40
+
41
+ return (
42
+ <div className="console-op-group">
43
+ {buttons.map(({ name, option }) => {
44
+ const palette = colorFromEnum(option.color);
45
+ const isInTmuxByHuman = name === IN_TMUX_BY_HUMAN_NAME;
46
+ return (
47
+ <button
48
+ key={option.id}
49
+ type="button"
50
+ className="console-op-button"
51
+ style={{
52
+ color: palette.fg,
53
+ borderColor: palette.border,
54
+ backgroundColor: palette.bg,
55
+ }}
56
+ onClick={() =>
57
+ isInTmuxByHuman ? onSetInTmuxByHuman(option) : onSetStatus(option)
58
+ }
59
+ >
60
+ {option.name}
61
+ </button>
62
+ );
63
+ })}
64
+ </div>
65
+ );
66
+ };
@@ -0,0 +1,17 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { consoleStoryOptionsFixture } from '../../testing/fixtures';
3
+ import { ConsoleStoryActions } from './ConsoleStoryActions';
4
+
5
+ const meta: Meta<typeof ConsoleStoryActions> = {
6
+ title: 'Console/ConsoleStoryActions',
7
+ component: ConsoleStoryActions,
8
+ args: { onSetStory: () => {} },
9
+ };
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof ConsoleStoryActions>;
14
+
15
+ export const AssignableStories: Story = {
16
+ args: { storyOptions: consoleStoryOptionsFixture },
17
+ };
@@ -0,0 +1,39 @@
1
+ import { fireEvent, render } from '@testing-library/react';
2
+ import { ConsoleStoryActions } from './ConsoleStoryActions';
3
+
4
+ const options = [
5
+ { id: '1', name: 'TDPM Console port', color: 'BLUE' as const },
6
+ {
7
+ id: '2',
8
+ name: "regular / NO STORY; DON'T WORK ON THIS",
9
+ color: 'RED' as const,
10
+ },
11
+ { id: '3', name: 'Move to Okinawa', color: 'PURPLE' as const },
12
+ ];
13
+
14
+ describe('ConsoleStoryActions', () => {
15
+ it('excludes no-story options', () => {
16
+ const { getByText, queryByText } = render(
17
+ <ConsoleStoryActions storyOptions={options} onSetStory={() => {}} />,
18
+ );
19
+ expect(getByText('TDPM Console port')).toBeInTheDocument();
20
+ expect(getByText('Move to Okinawa')).toBeInTheDocument();
21
+ expect(queryByText(/NO STORY/)).toBeNull();
22
+ });
23
+
24
+ it('reports the selected story option', () => {
25
+ const onSetStory = jest.fn();
26
+ const { getByText } = render(
27
+ <ConsoleStoryActions storyOptions={options} onSetStory={onSetStory} />,
28
+ );
29
+ fireEvent.click(getByText('Move to Okinawa'));
30
+ expect(onSetStory).toHaveBeenCalledWith(options[2]);
31
+ });
32
+
33
+ it('renders nothing when only no-story options exist', () => {
34
+ const { container } = render(
35
+ <ConsoleStoryActions storyOptions={[options[1]]} onSetStory={() => {}} />,
36
+ );
37
+ expect(container.firstChild).toBeNull();
38
+ });
39
+ });
@@ -0,0 +1,42 @@
1
+ import { colorFromEnum } from '../../logic/colors';
2
+ import type { ConsoleFieldOption } from '../../logic/types';
3
+
4
+ export type ConsoleStoryButtonGroupProps = {
5
+ storyOptions: ConsoleFieldOption[];
6
+ onSetStory: (option: ConsoleFieldOption) => void;
7
+ };
8
+
9
+ const isNoStoryOption = (option: ConsoleFieldOption): boolean =>
10
+ option.name.toLowerCase().includes('no story');
11
+
12
+ export const ConsoleStoryActions = ({
13
+ storyOptions,
14
+ onSetStory,
15
+ }: ConsoleStoryButtonGroupProps) => {
16
+ const assignable = storyOptions.filter((option) => !isNoStoryOption(option));
17
+ if (assignable.length === 0) {
18
+ return null;
19
+ }
20
+ return (
21
+ <div className="console-op-group console-op-group-stories">
22
+ {assignable.map((option) => {
23
+ const palette = colorFromEnum(option.color);
24
+ return (
25
+ <button
26
+ key={option.id}
27
+ type="button"
28
+ className="console-op-button"
29
+ style={{
30
+ color: palette.fg,
31
+ borderColor: palette.border,
32
+ backgroundColor: palette.bg,
33
+ }}
34
+ onClick={() => onSetStory(option)}
35
+ >
36
+ {option.name}
37
+ </button>
38
+ );
39
+ })}
40
+ </div>
41
+ );
42
+ };
@@ -0,0 +1,22 @@
1
+ import { renderHook } from '@testing-library/react';
2
+ import { useConsoleCaches } from './useConsoleCaches';
3
+
4
+ describe('useConsoleCaches', () => {
5
+ beforeEach(() => {
6
+ localStorage.clear();
7
+ window.history.replaceState({}, '', '/?k=token');
8
+ });
9
+
10
+ it('exposes one cache per resource and a stable identity across renders', () => {
11
+ const { result, rerender } = renderHook(() => useConsoleCaches());
12
+ const first = result.current;
13
+ expect(first.body).toBeDefined();
14
+ expect(first.comments).toBeDefined();
15
+ expect(first.files).toBeDefined();
16
+ expect(first.commits).toBeDefined();
17
+ expect(first.relatedPrs).toBeDefined();
18
+ expect(first.state).toBeDefined();
19
+ rerender();
20
+ expect(result.current).toBe(first);
21
+ });
22
+ });
@@ -0,0 +1,42 @@
1
+ import { useMemo } from 'react';
2
+ import {
3
+ type ConsoleApiClient,
4
+ createConsoleApiClient,
5
+ } from '../lib/consoleApi';
6
+ import { ResourceCache } from '../lib/resourceCache';
7
+ import type {
8
+ ConsoleChangedFile,
9
+ ConsoleComment,
10
+ ConsoleCommit,
11
+ ConsoleIssueState,
12
+ ConsoleRelatedPullRequest,
13
+ } from '../logic/types';
14
+ import { useConsoleToken } from './useConsoleToken';
15
+
16
+ export type ConsoleCaches = {
17
+ body: ResourceCache<string>;
18
+ comments: ResourceCache<ConsoleComment[]>;
19
+ files: ResourceCache<ConsoleChangedFile[]>;
20
+ commits: ResourceCache<ConsoleCommit[]>;
21
+ relatedPrs: ResourceCache<ConsoleRelatedPullRequest[]>;
22
+ state: ResourceCache<ConsoleIssueState>;
23
+ client: ConsoleApiClient;
24
+ };
25
+
26
+ export const useConsoleCaches = (): ConsoleCaches => {
27
+ const { appendToken } = useConsoleToken();
28
+ return useMemo(() => {
29
+ const client = createConsoleApiClient(appendToken);
30
+ return {
31
+ client,
32
+ body: new ResourceCache<string>(client.fetchItemBody),
33
+ comments: new ResourceCache<ConsoleComment[]>(client.fetchComments),
34
+ files: new ResourceCache<ConsoleChangedFile[]>(client.fetchPrFiles),
35
+ commits: new ResourceCache<ConsoleCommit[]>(client.fetchPrCommits),
36
+ relatedPrs: new ResourceCache<ConsoleRelatedPullRequest[]>(
37
+ client.fetchRelatedPrs,
38
+ ),
39
+ state: new ResourceCache<ConsoleIssueState>(client.fetchIssueState),
40
+ };
41
+ }, [appendToken]);
42
+ };
@@ -0,0 +1,126 @@
1
+ import { renderHook, waitFor } from '@testing-library/react';
2
+ import { ResourceCache } from '../lib/resourceCache';
3
+ import type {
4
+ ConsoleChangedFile,
5
+ ConsoleComment,
6
+ ConsoleCommit,
7
+ ConsoleIssueState,
8
+ ConsoleListItem,
9
+ ConsoleRelatedPullRequest,
10
+ } from '../logic/types';
11
+ import type { ConsoleCaches } from './useConsoleCaches';
12
+ import { useConsoleItemDetailData } from './useConsoleItemDetailData';
13
+
14
+ const prItem: ConsoleListItem = {
15
+ number: 1,
16
+ title: 'PR',
17
+ url: 'https://github.com/o/r/pull/1',
18
+ repo: 'o/r',
19
+ nameWithOwner: 'o/r',
20
+ projectItemId: 'PVTI_1',
21
+ itemId: 'PVTI_1',
22
+ isPr: true,
23
+ story: 'Story',
24
+ labels: [],
25
+ createdAt: '2026-06-10T00:00:00.000Z',
26
+ };
27
+
28
+ const issueItem: ConsoleListItem = {
29
+ ...prItem,
30
+ isPr: false,
31
+ url: 'https://github.com/o/r/issues/2',
32
+ number: 2,
33
+ };
34
+
35
+ const buildCaches = (related: ConsoleRelatedPullRequest[]): ConsoleCaches => {
36
+ const client = {
37
+ fetchItemBody: async () => 'body',
38
+ fetchComments: async (): Promise<ConsoleComment[]> => [],
39
+ fetchPrFiles: async (): Promise<ConsoleChangedFile[]> => [
40
+ {
41
+ path: 'a.ts',
42
+ additions: 1,
43
+ deletions: 0,
44
+ status: 'added',
45
+ patch: null,
46
+ },
47
+ ],
48
+ fetchPrCommits: async (): Promise<ConsoleCommit[]> => [
49
+ {
50
+ sha: 'abc',
51
+ message: 'm',
52
+ author: 'a',
53
+ authoredAt: '2026-06-10T00:00:00.000Z',
54
+ },
55
+ ],
56
+ fetchRelatedPrs: async (): Promise<ConsoleRelatedPullRequest[]> => related,
57
+ fetchIssueState: async (): Promise<ConsoleIssueState> => ({
58
+ state: 'open',
59
+ merged: false,
60
+ isPullRequest: true,
61
+ }),
62
+ };
63
+ return {
64
+ client,
65
+ body: new ResourceCache(client.fetchItemBody),
66
+ comments: new ResourceCache(client.fetchComments),
67
+ files: new ResourceCache(client.fetchPrFiles),
68
+ commits: new ResourceCache(client.fetchPrCommits),
69
+ relatedPrs: new ResourceCache(client.fetchRelatedPrs),
70
+ state: new ResourceCache(client.fetchIssueState),
71
+ };
72
+ };
73
+
74
+ describe('useConsoleItemDetailData', () => {
75
+ it('loads body, files and commits for a PR item', async () => {
76
+ const caches = buildCaches([]);
77
+ const { result } = renderHook(() =>
78
+ useConsoleItemDetailData(caches, prItem),
79
+ );
80
+ await waitFor(() => {
81
+ expect(result.current.body).toBe('body');
82
+ expect(result.current.files.length).toBe(1);
83
+ expect(result.current.commits.length).toBe(1);
84
+ });
85
+ });
86
+
87
+ it('loads related pull request views for an issue item', async () => {
88
+ const related: ConsoleRelatedPullRequest[] = [
89
+ {
90
+ url: 'https://github.com/o/r/pull/9',
91
+ branchName: 'feat',
92
+ createdAt: '2026-06-10T00:00:00.000Z',
93
+ isDraft: false,
94
+ isConflicted: false,
95
+ isPassedAllCiJob: true,
96
+ isCiStateSuccess: true,
97
+ isResolvedAllReviewComments: true,
98
+ isBranchOutOfDate: false,
99
+ missingRequiredCheckNames: [],
100
+ summary: {
101
+ title: 'Linked PR',
102
+ body: 'body',
103
+ additions: 5,
104
+ deletions: 1,
105
+ changedFiles: 2,
106
+ },
107
+ },
108
+ ];
109
+ const caches = buildCaches(related);
110
+ const { result } = renderHook(() =>
111
+ useConsoleItemDetailData(caches, issueItem),
112
+ );
113
+ await waitFor(() => {
114
+ expect(result.current.relatedPullRequests.length).toBe(1);
115
+ expect(result.current.relatedPullRequests[0].filesAreLoading).toBe(false);
116
+ });
117
+ expect(result.current.relatedPullRequests[0].files.length).toBe(1);
118
+ });
119
+
120
+ it('returns defaults when no item is selected', () => {
121
+ const caches = buildCaches([]);
122
+ const { result } = renderHook(() => useConsoleItemDetailData(caches, null));
123
+ expect(result.current.body).toBe('');
124
+ expect(result.current.relatedPullRequests).toEqual([]);
125
+ });
126
+ });