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,96 @@
1
+ import { mock } from 'jest-mock-extended';
2
+ import { Project } from '../../../domain/entities/Project';
3
+ import {
4
+ buildPjcodeToProjectUrl,
5
+ createConsoleProjectResolver,
6
+ } from './consoleProjectResolver';
7
+
8
+ describe('buildPjcodeToProjectUrl', () => {
9
+ it('adds the default pjcode entry when it is not already present', () => {
10
+ const mapping = buildPjcodeToProjectUrl(
11
+ 'umino',
12
+ 'https://github.com/orgs/umino/projects/1',
13
+ { xmile: 'https://github.com/orgs/xmile/projects/2' },
14
+ );
15
+ expect(mapping).toEqual({
16
+ umino: 'https://github.com/orgs/umino/projects/1',
17
+ xmile: 'https://github.com/orgs/xmile/projects/2',
18
+ });
19
+ });
20
+
21
+ it('keeps an explicit default pjcode entry from consoleProjects', () => {
22
+ const mapping = buildPjcodeToProjectUrl(
23
+ 'umino',
24
+ 'https://github.com/orgs/umino/projects/1',
25
+ { umino: 'https://github.com/orgs/umino/projects/9' },
26
+ );
27
+ expect(mapping.umino).toBe('https://github.com/orgs/umino/projects/9');
28
+ });
29
+
30
+ it('uses only the default entry when no consoleProjects mapping is configured', () => {
31
+ const mapping = buildPjcodeToProjectUrl(
32
+ 'umino',
33
+ 'https://github.com/orgs/umino/projects/1',
34
+ null,
35
+ );
36
+ expect(mapping).toEqual({
37
+ umino: 'https://github.com/orgs/umino/projects/1',
38
+ });
39
+ });
40
+ });
41
+
42
+ describe('createConsoleProjectResolver', () => {
43
+ const uminoProject: Project = { ...mock<Project>(), id: 'PVT_umino' };
44
+ const xmileProject: Project = { ...mock<Project>(), id: 'PVT_xmile' };
45
+
46
+ it('resolves a known pjcode to its loaded project', async () => {
47
+ const loadProject = jest.fn(async (url: string) =>
48
+ url.includes('umino') ? uminoProject : xmileProject,
49
+ );
50
+ const resolver = createConsoleProjectResolver(
51
+ {
52
+ umino: 'https://github.com/orgs/umino/projects/1',
53
+ xmile: 'https://github.com/orgs/xmile/projects/2',
54
+ },
55
+ loadProject,
56
+ );
57
+ await expect(resolver('umino')).resolves.toEqual({
58
+ pjcode: 'umino',
59
+ project: uminoProject,
60
+ });
61
+ await expect(resolver('xmile')).resolves.toEqual({
62
+ pjcode: 'xmile',
63
+ project: xmileProject,
64
+ });
65
+ });
66
+
67
+ it('returns null for a pjcode that has no configured project url', async () => {
68
+ const loadProject = jest.fn(async () => uminoProject);
69
+ const resolver = createConsoleProjectResolver(
70
+ { umino: 'https://github.com/orgs/umino/projects/1' },
71
+ loadProject,
72
+ );
73
+ await expect(resolver('unknown')).resolves.toBeNull();
74
+ expect(loadProject).not.toHaveBeenCalled();
75
+ });
76
+
77
+ it('returns null when the project fails to load', async () => {
78
+ const loadProject = jest.fn(async () => null);
79
+ const resolver = createConsoleProjectResolver(
80
+ { umino: 'https://github.com/orgs/umino/projects/1' },
81
+ loadProject,
82
+ );
83
+ await expect(resolver('umino')).resolves.toBeNull();
84
+ });
85
+
86
+ it('loads each project at most once and serves later calls from cache', async () => {
87
+ const loadProject = jest.fn(async () => uminoProject);
88
+ const resolver = createConsoleProjectResolver(
89
+ { umino: 'https://github.com/orgs/umino/projects/1' },
90
+ loadProject,
91
+ );
92
+ await resolver('umino');
93
+ await resolver('umino');
94
+ expect(loadProject).toHaveBeenCalledTimes(1);
95
+ });
96
+ });
@@ -0,0 +1,50 @@
1
+ import { Project } from '../../../domain/entities/Project';
2
+ import {
3
+ ConsoleProjectBinding,
4
+ ConsoleProjectResolver,
5
+ } from './consoleOperationApi';
6
+
7
+ export type ConsoleProjectLoader = (
8
+ projectUrl: string,
9
+ ) => Promise<Project | null>;
10
+
11
+ export const buildPjcodeToProjectUrl = (
12
+ defaultPjcode: string,
13
+ defaultProjectUrl: string,
14
+ consoleProjects: Record<string, string> | null,
15
+ ): Record<string, string> => {
16
+ const mapping: Record<string, string> = {};
17
+ if (consoleProjects !== null) {
18
+ for (const [pjcode, projectUrl] of Object.entries(consoleProjects)) {
19
+ mapping[pjcode] = projectUrl;
20
+ }
21
+ }
22
+ if (!(defaultPjcode in mapping)) {
23
+ mapping[defaultPjcode] = defaultProjectUrl;
24
+ }
25
+ return mapping;
26
+ };
27
+
28
+ export const createConsoleProjectResolver = (
29
+ pjcodeToProjectUrl: Record<string, string>,
30
+ loadProject: ConsoleProjectLoader,
31
+ ): ConsoleProjectResolver => {
32
+ const cache = new Map<string, ConsoleProjectBinding>();
33
+ return async (pjcode: string): Promise<ConsoleProjectBinding | null> => {
34
+ const cached = cache.get(pjcode);
35
+ if (cached !== undefined) {
36
+ return cached;
37
+ }
38
+ const projectUrl = pjcodeToProjectUrl[pjcode];
39
+ if (projectUrl === undefined) {
40
+ return null;
41
+ }
42
+ const project = await loadProject(projectUrl);
43
+ if (project === null) {
44
+ return null;
45
+ }
46
+ const binding: ConsoleProjectBinding = { pjcode, project };
47
+ cache.set(pjcode, binding);
48
+ return binding;
49
+ };
50
+ };
@@ -480,7 +480,6 @@ describe('consoleServer new routes integration', () => {
480
480
  accessToken: testToken,
481
481
  uiDistDir: path.join(tmpDir, 'ui-dist'),
482
482
  consoleDataOutputDir: dataDir,
483
- pjcode: 'umino',
484
483
  port: 0,
485
484
  });
486
485
  try {
@@ -546,9 +545,9 @@ describe('consoleServer new routes integration', () => {
546
545
  accessToken: testToken,
547
546
  uiDistDir: path.join(tmpDir, 'ui-dist'),
548
547
  consoleDataOutputDir: dataDir,
549
- pjcode: 'umino',
550
548
  issueRepository,
551
- project: buildProject(),
549
+ resolveProject: async (pjcode) =>
550
+ pjcode === 'umino' ? { pjcode, project: buildProject() } : null,
552
551
  port: 0,
553
552
  });
554
553
  try {
@@ -557,6 +556,7 @@ describe('consoleServer new routes integration', () => {
557
556
  'POST',
558
557
  `/api/review?k=${testToken}`,
559
558
  {
559
+ pjcode: 'umino',
560
560
  action: 'approve',
561
561
  prUrl: 'https://github.com/o/r/pull/1',
562
562
  projectItemId: 'PVTI_op',
@@ -583,7 +583,8 @@ describe('consoleServer new routes integration', () => {
583
583
  uiDistDir: path.join(tmpDir, 'ui-dist'),
584
584
  consoleDataOutputDir: null,
585
585
  issueRepository,
586
- project: buildProject(),
586
+ resolveProject: async (pjcode) =>
587
+ pjcode === 'umino' ? { pjcode, project: buildProject() } : null,
587
588
  port: 0,
588
589
  });
589
590
  try {
@@ -2,7 +2,6 @@ import * as http from 'http';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import { IssueRepository } from '../../../domain/usecases/adapter-interfaces/IssueRepository';
5
- import { Project } from '../../../domain/entities/Project';
6
5
  import {
7
6
  CONSOLE_LIST_TAB_NAMES,
8
7
  buildConsoleDataResponse,
@@ -19,6 +18,7 @@ import {
19
18
  } from './consoleReadApi';
20
19
  import {
21
20
  ConsoleOperationContext,
21
+ ConsoleProjectResolver,
22
22
  handleIntmux,
23
23
  handleReview,
24
24
  handleTriage,
@@ -153,9 +153,8 @@ export type ConsoleServerOptions = {
153
153
  accessToken: string;
154
154
  uiDistDir: string;
155
155
  consoleDataOutputDir: string | null;
156
- pjcode?: string | null;
157
156
  issueRepository?: IssueRepository | null;
158
- project?: Project | null;
157
+ resolveProject?: ConsoleProjectResolver | null;
159
158
  issueTitleStateCache?: IssueTitleStateCache | null;
160
159
  };
161
160
 
@@ -291,15 +290,14 @@ const handleOperationApi = async (
291
290
  body: Record<string, unknown>,
292
291
  ): Promise<{ statusCode: number; body: unknown } | null> => {
293
292
  const issueRepository = options.issueRepository ?? null;
294
- const project = options.project ?? null;
295
- if (issueRepository === null || project === null) {
293
+ const resolveProject = options.resolveProject ?? null;
294
+ if (issueRepository === null || resolveProject === null) {
296
295
  return null;
297
296
  }
298
297
  const context: ConsoleOperationContext = {
299
298
  issueRepository,
300
- project,
299
+ resolveProject,
301
300
  consoleDataOutputDir: options.consoleDataOutputDir,
302
- pjcode: options.pjcode ?? null,
303
301
  };
304
302
  switch (requestPath) {
305
303
  case '/api/review':
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1 @@
1
+ module.exports = {};
@@ -0,0 +1,27 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import {
3
+ consoleMarkdownBodyFixture,
4
+ consoleMermaidBodyFixture,
5
+ } from '../../testing/fixtures';
6
+ import { ConsoleMarkdownContent } from './ConsoleMarkdownContent';
7
+
8
+ const meta: Meta<typeof ConsoleMarkdownContent> = {
9
+ title: 'Console/ConsoleMarkdownContent',
10
+ component: ConsoleMarkdownContent,
11
+ };
12
+
13
+ export default meta;
14
+
15
+ type Story = StoryObj<typeof ConsoleMarkdownContent>;
16
+
17
+ export const RichMarkdown: Story = {
18
+ args: { body: consoleMarkdownBodyFixture },
19
+ };
20
+
21
+ export const WithMermaidFence: Story = {
22
+ args: { body: consoleMermaidBodyFixture },
23
+ };
24
+
25
+ export const Empty: Story = {
26
+ args: { body: '' },
27
+ };
@@ -0,0 +1,36 @@
1
+ import { render, waitFor } from '@testing-library/react';
2
+ import { ConsoleMarkdownContent } from './ConsoleMarkdownContent';
3
+
4
+ jest.mock('../../lib/mermaidLoader', () => ({
5
+ renderMermaidToSvg: jest.fn(
6
+ async () => '<svg data-testid="mermaid-svg"></svg>',
7
+ ),
8
+ }));
9
+
10
+ describe('ConsoleMarkdownContent', () => {
11
+ it('renders markdown body content', () => {
12
+ const { container } = render(
13
+ <ConsoleMarkdownContent body={'## Heading\n\n- bullet'} />,
14
+ );
15
+ expect(container.querySelector('h2')).not.toBeNull();
16
+ expect(container.querySelector('li')).not.toBeNull();
17
+ });
18
+
19
+ it('shows the empty message for a blank body', () => {
20
+ const { getByText } = render(<ConsoleMarkdownContent body=" " />);
21
+ expect(getByText('No description provided.')).toBeInTheDocument();
22
+ });
23
+
24
+ it('renders a mermaid fence via the diagram component', async () => {
25
+ const { container } = render(
26
+ <ConsoleMarkdownContent
27
+ body={'intro\n\n```mermaid\ngraph TD; A-->B;\n```'}
28
+ />,
29
+ );
30
+ await waitFor(() => {
31
+ expect(
32
+ container.querySelector('.console-mermaid-rendered'),
33
+ ).not.toBeNull();
34
+ });
35
+ });
36
+ });
@@ -0,0 +1,50 @@
1
+ import { useEffect, useMemo, useRef } from 'react';
2
+ import {
3
+ renderMarkdownToSafeHtml,
4
+ splitMarkdownSegments,
5
+ } from '../../lib/markdown';
6
+ import { ConsoleMermaidDiagram } from './ConsoleMermaidDiagram';
7
+
8
+ export type ConsoleMarkdownViewProps = {
9
+ body: string;
10
+ };
11
+
12
+ type ConsoleMarkdownHtmlBlockProps = {
13
+ source: string;
14
+ };
15
+
16
+ const ConsoleMarkdownHtmlBlock = ({
17
+ source,
18
+ }: ConsoleMarkdownHtmlBlockProps) => {
19
+ const html = useMemo(() => renderMarkdownToSafeHtml(source), [source]);
20
+ const containerRef = useRef<HTMLDivElement>(null);
21
+
22
+ useEffect(() => {
23
+ const container = containerRef.current;
24
+ if (container !== null) {
25
+ container.innerHTML = html;
26
+ }
27
+ }, [html]);
28
+
29
+ return <div ref={containerRef} className="console-markdown" />;
30
+ };
31
+
32
+ export const ConsoleMarkdownContent = ({ body }: ConsoleMarkdownViewProps) => {
33
+ const segments = useMemo(() => splitMarkdownSegments(body), [body]);
34
+
35
+ if (body.trim() === '') {
36
+ return <p className="console-markdown-empty">No description provided.</p>;
37
+ }
38
+
39
+ return (
40
+ <div className="console-markdown-view">
41
+ {segments.map((segment) =>
42
+ segment.kind === 'mermaid' ? (
43
+ <ConsoleMermaidDiagram key={segment.key} code={segment.code} />
44
+ ) : (
45
+ <ConsoleMarkdownHtmlBlock key={segment.key} source={segment.source} />
46
+ ),
47
+ )}
48
+ </div>
49
+ );
50
+ };
@@ -0,0 +1,22 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { consoleMermaidCodeFixture } from '../../testing/fixtures';
3
+ import { ConsoleMermaidDiagram } from './ConsoleMermaidDiagram';
4
+
5
+ const meta: Meta<typeof ConsoleMermaidDiagram> = {
6
+ title: 'Console/ConsoleMermaidDiagram',
7
+ component: ConsoleMermaidDiagram,
8
+ };
9
+
10
+ export default meta;
11
+
12
+ type Story = StoryObj<typeof ConsoleMermaidDiagram>;
13
+
14
+ export const SequenceDiagram: Story = {
15
+ args: { code: consoleMermaidCodeFixture },
16
+ };
17
+
18
+ export const FlowDiagram: Story = {
19
+ args: {
20
+ code: 'graph TD;\n Start-->Fetch;\n Fetch-->Render;\n Render-->Done;',
21
+ },
22
+ };
@@ -0,0 +1,38 @@
1
+ import { render, waitFor } from '@testing-library/react';
2
+ import { renderMermaidToSvg } from '../../lib/mermaidLoader';
3
+ import { ConsoleMermaidDiagram } from './ConsoleMermaidDiagram';
4
+
5
+ jest.mock('../../lib/mermaidLoader', () => ({
6
+ renderMermaidToSvg: jest.fn(),
7
+ }));
8
+
9
+ const mockedRender = renderMermaidToSvg as jest.MockedFunction<
10
+ typeof renderMermaidToSvg
11
+ >;
12
+
13
+ describe('ConsoleMermaidDiagram', () => {
14
+ beforeEach(() => {
15
+ mockedRender.mockReset();
16
+ });
17
+
18
+ it('renders the sanitized svg once ready', async () => {
19
+ mockedRender.mockResolvedValue('<svg id="ok"></svg>');
20
+ const { container } = render(
21
+ <ConsoleMermaidDiagram code="graph TD; A-->B;" />,
22
+ );
23
+ await waitFor(() => {
24
+ expect(container.querySelector('svg#ok')).not.toBeNull();
25
+ });
26
+ });
27
+
28
+ it('shows the source and an error note when rendering fails', async () => {
29
+ mockedRender.mockRejectedValue(new Error('parse error'));
30
+ const { getByText } = render(<ConsoleMermaidDiagram code="bad diagram" />);
31
+ await waitFor(() => {
32
+ expect(
33
+ getByText(/Mermaid render error: parse error/),
34
+ ).toBeInTheDocument();
35
+ });
36
+ expect(getByText('bad diagram')).toBeInTheDocument();
37
+ });
38
+ });
@@ -0,0 +1,65 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { renderMermaidToSvg } from '../../lib/mermaidLoader';
3
+
4
+ export type ConsoleMermaidDiagramProps = {
5
+ code: string;
6
+ };
7
+
8
+ type MermaidRenderState =
9
+ | { status: 'loading' }
10
+ | { status: 'ready'; svg: string }
11
+ | { status: 'error'; message: string };
12
+
13
+ export const ConsoleMermaidDiagram = ({ code }: ConsoleMermaidDiagramProps) => {
14
+ const [state, setState] = useState<MermaidRenderState>({ status: 'loading' });
15
+ const containerRef = useRef<HTMLDivElement>(null);
16
+
17
+ useEffect(() => {
18
+ let cancelled = false;
19
+ setState({ status: 'loading' });
20
+ renderMermaidToSvg(code)
21
+ .then((svg) => {
22
+ if (!cancelled) {
23
+ setState({ status: 'ready', svg });
24
+ }
25
+ })
26
+ .catch((cause: unknown) => {
27
+ if (!cancelled) {
28
+ setState({
29
+ status: 'error',
30
+ message: cause instanceof Error ? cause.message : String(cause),
31
+ });
32
+ }
33
+ });
34
+ return () => {
35
+ cancelled = true;
36
+ };
37
+ }, [code]);
38
+
39
+ useEffect(() => {
40
+ const container = containerRef.current;
41
+ if (container === null) {
42
+ return;
43
+ }
44
+ container.innerHTML = state.status === 'ready' ? state.svg : '';
45
+ }, [state]);
46
+
47
+ if (state.status === 'loading') {
48
+ return <div className="console-mermaid-loading">Rendering diagram...</div>;
49
+ }
50
+
51
+ if (state.status === 'error') {
52
+ return (
53
+ <div className="console-mermaid">
54
+ <div className="console-mermaid-error">
55
+ Mermaid render error: {state.message}
56
+ </div>
57
+ <pre className="console-mermaid-source">
58
+ <code>{code}</code>
59
+ </pre>
60
+ </div>
61
+ );
62
+ }
63
+
64
+ return <div ref={containerRef} className="console-mermaid-rendered" />;
65
+ };
@@ -0,0 +1,28 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { consoleChangedFilesFixture } from '../../testing/fixtures';
3
+ import { ConsoleChangedFileList } from './ConsoleChangedFileList';
4
+
5
+ const meta: Meta<typeof ConsoleChangedFileList> = {
6
+ title: 'Console/ConsoleChangedFileList',
7
+ component: ConsoleChangedFileList,
8
+ };
9
+
10
+ export default meta;
11
+
12
+ type Story = StoryObj<typeof ConsoleChangedFileList>;
13
+
14
+ export const WithFiles: Story = {
15
+ args: { files: consoleChangedFilesFixture, isLoading: false, error: null },
16
+ };
17
+
18
+ export const Loading: Story = {
19
+ args: { files: [], isLoading: true, error: null },
20
+ };
21
+
22
+ export const Empty: Story = {
23
+ args: { files: [], isLoading: false, error: null },
24
+ };
25
+
26
+ export const ErrorState: Story = {
27
+ args: { files: [], isLoading: false, error: 'HTTP 502' },
28
+ };
@@ -0,0 +1,42 @@
1
+ import { render } from '@testing-library/react';
2
+ import { consoleChangedFilesFixture } from '../../testing/fixtures';
3
+ import { ConsoleChangedFileList } from './ConsoleChangedFileList';
4
+
5
+ describe('ConsoleChangedFileList', () => {
6
+ it('renders each file path, status badge and additions/deletions', () => {
7
+ const { getByText, getAllByText } = render(
8
+ <ConsoleChangedFileList
9
+ files={consoleChangedFilesFixture}
10
+ isLoading={false}
11
+ error={null}
12
+ />,
13
+ );
14
+ expect(
15
+ getByText('src/adapter/entry-points/console/consoleServer.ts'),
16
+ ).toBeInTheDocument();
17
+ expect(getByText('+312')).toBeInTheDocument();
18
+ expect(getAllByText('A').length).toBe(2);
19
+ expect(getByText('M')).toBeInTheDocument();
20
+ });
21
+
22
+ it('shows the loading state', () => {
23
+ const { getByText } = render(
24
+ <ConsoleChangedFileList files={[]} isLoading error={null} />,
25
+ );
26
+ expect(getByText('Loading changed files...')).toBeInTheDocument();
27
+ });
28
+
29
+ it('shows the empty state', () => {
30
+ const { getByText } = render(
31
+ <ConsoleChangedFileList files={[]} isLoading={false} error={null} />,
32
+ );
33
+ expect(getByText('No changed files.')).toBeInTheDocument();
34
+ });
35
+
36
+ it('shows the error state', () => {
37
+ const { getByRole } = render(
38
+ <ConsoleChangedFileList files={[]} isLoading={false} error="HTTP 502" />,
39
+ );
40
+ expect(getByRole('alert')).toHaveTextContent('HTTP 502');
41
+ });
42
+ });
@@ -0,0 +1,55 @@
1
+ import { fileStatusBadge } from '../../logic/fileStatus';
2
+ import type { ConsoleChangedFile } from '../../logic/types';
3
+
4
+ export type ConsoleChangedFileListProps = {
5
+ files: ConsoleChangedFile[];
6
+ isLoading: boolean;
7
+ error: string | null;
8
+ };
9
+
10
+ export const ConsoleChangedFileList = ({
11
+ files,
12
+ isLoading,
13
+ error,
14
+ }: ConsoleChangedFileListProps) => {
15
+ if (error !== null) {
16
+ return (
17
+ <p role="alert" className="console-files-error">
18
+ Failed to load changed files: {error}
19
+ </p>
20
+ );
21
+ }
22
+
23
+ if (isLoading) {
24
+ return <p className="console-files-loading">Loading changed files...</p>;
25
+ }
26
+
27
+ if (files.length === 0) {
28
+ return <p className="console-files-empty">No changed files.</p>;
29
+ }
30
+
31
+ return (
32
+ <ul className="console-files">
33
+ {files.map((file) => {
34
+ const badge = fileStatusBadge(file.status);
35
+ return (
36
+ <li key={file.path} className="console-file">
37
+ <span
38
+ className="console-file-badge"
39
+ style={{ color: badge.color, borderColor: badge.color }}
40
+ >
41
+ {badge.label}
42
+ </span>
43
+ <span className="console-file-path">{file.path}</span>
44
+ <span className="console-file-stat console-file-add">
45
+ +{file.additions}
46
+ </span>
47
+ <span className="console-file-stat console-file-del">
48
+ -{file.deletions}
49
+ </span>
50
+ </li>
51
+ );
52
+ })}
53
+ </ul>
54
+ );
55
+ };
@@ -0,0 +1,29 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { consoleCommentsFixture } from '../../testing/fixtures';
3
+ import { ConsoleCommentList } from './ConsoleCommentList';
4
+
5
+ const meta: Meta<typeof ConsoleCommentList> = {
6
+ title: 'Console/ConsoleCommentList',
7
+ component: ConsoleCommentList,
8
+ args: { now: Date.parse('2026-06-19T12:00:00.000Z') },
9
+ };
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof ConsoleCommentList>;
14
+
15
+ export const WithComments: Story = {
16
+ args: { comments: consoleCommentsFixture, isLoading: false, error: null },
17
+ };
18
+
19
+ export const Loading: Story = {
20
+ args: { comments: [], isLoading: true, error: null },
21
+ };
22
+
23
+ export const Empty: Story = {
24
+ args: { comments: [], isLoading: false, error: null },
25
+ };
26
+
27
+ export const ErrorState: Story = {
28
+ args: { comments: [], isLoading: false, error: 'HTTP 500' },
29
+ };