github-issue-tower-defence-management 1.89.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 (160) hide show
  1. package/.github/CODEOWNERS +1 -2
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +15 -2
  4. package/bin/adapter/entry-points/cli/index.js +16 -12
  5. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  6. package/bin/adapter/entry-points/cli/projectConfig.js +2 -0
  7. package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
  8. package/bin/adapter/entry-points/console/consoleOperationApi.js +54 -27
  9. package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
  10. package/bin/adapter/entry-points/console/consoleProjectResolver.js +38 -0
  11. package/bin/adapter/entry-points/console/consoleProjectResolver.js.map +1 -0
  12. package/bin/adapter/entry-points/console/consoleServer.js +43 -17
  13. package/bin/adapter/entry-points/console/consoleServer.js.map +1 -1
  14. package/bin/adapter/entry-points/console/ui-dist/assets/index-BU6p3cGU.css +1 -0
  15. package/bin/adapter/entry-points/console/ui-dist/assets/index-BvuSQN9s.js +100 -0
  16. package/bin/adapter/entry-points/console/ui-dist/index.html +2 -2
  17. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +16 -0
  18. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  19. package/jest.config.js +57 -9
  20. package/package.json +17 -13
  21. package/src/adapter/entry-points/cli/index.test.ts +12 -2
  22. package/src/adapter/entry-points/cli/index.ts +30 -12
  23. package/src/adapter/entry-points/cli/projectConfig.ts +3 -0
  24. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +129 -15
  25. package/src/adapter/entry-points/console/consoleOperationApi.ts +83 -28
  26. package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +96 -0
  27. package/src/adapter/entry-points/console/consoleProjectResolver.ts +50 -0
  28. package/src/adapter/entry-points/console/consoleServer.test.ts +86 -4
  29. package/src/adapter/entry-points/console/consoleServer.ts +53 -23
  30. package/src/adapter/entry-points/console/ui/jest.setup.ts +1 -0
  31. package/src/adapter/entry-points/console/ui/jest.styleMock.js +1 -0
  32. package/src/adapter/entry-points/console/ui/src/features/console/colors.test.ts +34 -0
  33. package/src/adapter/entry-points/console/ui/src/features/console/colors.ts +73 -0
  34. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleChangedFileList.stories.tsx +28 -0
  35. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleChangedFileList.test.tsx +42 -0
  36. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleChangedFileList.tsx +55 -0
  37. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCloseButtonGroup.stories.tsx +14 -0
  38. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCloseButtonGroup.test.tsx +23 -0
  39. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCloseButtonGroup.tsx +26 -0
  40. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommentList.stories.tsx +29 -0
  41. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommentList.test.tsx +55 -0
  42. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommentList.tsx +66 -0
  43. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommitList.stories.tsx +25 -0
  44. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommitList.test.tsx +53 -0
  45. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommitList.tsx +53 -0
  46. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemDetail.stories.tsx +79 -0
  47. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemDetail.test.tsx +81 -0
  48. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemDetail.tsx +226 -0
  49. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemIcon.stories.tsx +82 -0
  50. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemIcon.test.tsx +52 -0
  51. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemIcon.tsx +32 -0
  52. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListItemRow.stories.tsx +25 -0
  53. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListItemRow.test.tsx +43 -0
  54. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListItemRow.tsx +34 -0
  55. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListView.stories.tsx +22 -6
  56. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListView.test.tsx +87 -0
  57. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListView.tsx +36 -32
  58. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMarkdownView.stories.tsx +27 -0
  59. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMarkdownView.test.tsx +36 -0
  60. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMarkdownView.tsx +50 -0
  61. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMermaidDiagram.stories.tsx +22 -0
  62. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMermaidDiagram.test.tsx +38 -0
  63. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMermaidDiagram.tsx +65 -0
  64. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleNextActionDateGroup.stories.tsx +20 -0
  65. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleNextActionDateGroup.test.tsx +42 -0
  66. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleNextActionDateGroup.tsx +28 -0
  67. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleOperationBar.stories.tsx +55 -0
  68. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleOperationBar.test.tsx +85 -0
  69. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleOperationBar.tsx +55 -0
  70. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePanel.stories.tsx +26 -0
  71. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePanel.test.tsx +32 -0
  72. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePanel.tsx +36 -0
  73. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleProjectHeader.stories.tsx +29 -0
  74. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleProjectHeader.test.tsx +14 -0
  75. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleProjectHeader.tsx +14 -0
  76. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestReviewGroup.stories.tsx +14 -0
  77. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestReviewGroup.test.tsx +33 -0
  78. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestReviewGroup.tsx +34 -0
  79. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestSection.stories.tsx +31 -0
  80. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestSection.test.tsx +40 -0
  81. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestSection.tsx +88 -0
  82. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStatusButtonGroup.stories.tsx +17 -0
  83. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStatusButtonGroup.test.tsx +49 -0
  84. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStatusButtonGroup.tsx +63 -0
  85. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryButtonGroup.stories.tsx +17 -0
  86. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryButtonGroup.test.tsx +45 -0
  87. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryButtonGroup.tsx +42 -0
  88. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryGroupHeader.stories.tsx +27 -0
  89. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryGroupHeader.test.tsx +24 -0
  90. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryGroupHeader.tsx +28 -0
  91. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleTabBar.stories.tsx +41 -5
  92. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleTabBar.test.tsx +59 -0
  93. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleTabBar.tsx +28 -19
  94. package/src/adapter/entry-points/console/ui/src/features/console/fileStatus.test.ts +35 -0
  95. package/src/adapter/entry-points/console/ui/src/features/console/fileStatus.ts +21 -0
  96. package/src/adapter/entry-points/console/ui/src/features/console/fixtures.ts +206 -9
  97. package/src/adapter/entry-points/console/ui/src/features/console/grouping.test.ts +91 -0
  98. package/src/adapter/entry-points/console/ui/src/features/console/grouping.ts +79 -0
  99. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.test.ts +22 -0
  100. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +42 -0
  101. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +126 -0
  102. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.ts +167 -0
  103. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +198 -0
  104. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +243 -0
  105. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts +40 -0
  106. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.ts +71 -0
  107. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.test.ts +24 -0
  108. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.ts +17 -0
  109. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.test.ts +41 -0
  110. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.ts +57 -0
  111. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +63 -0
  112. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +129 -0
  113. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleToken.test.ts +41 -0
  114. package/src/adapter/entry-points/console/ui/src/features/console/itemIcons.test.ts +97 -0
  115. package/src/adapter/entry-points/console/ui/src/features/console/itemIcons.ts +95 -0
  116. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +155 -0
  117. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +187 -0
  118. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.test.ts +76 -0
  119. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.ts +73 -0
  120. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.test.ts +27 -0
  121. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.ts +71 -0
  122. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.test.ts +56 -0
  123. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +51 -0
  124. package/src/adapter/entry-points/console/ui/src/features/console/operations.test.ts +37 -0
  125. package/src/adapter/entry-points/console/ui/src/features/console/operations.ts +35 -0
  126. package/src/adapter/entry-points/console/ui/src/features/console/overlay.test.ts +124 -0
  127. package/src/adapter/entry-points/console/ui/src/features/console/overlay.ts +101 -0
  128. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +79 -0
  129. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +109 -0
  130. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +74 -0
  131. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +137 -7
  132. package/src/adapter/entry-points/console/ui/src/features/console/relativeTime.test.ts +52 -0
  133. package/src/adapter/entry-points/console/ui/src/features/console/relativeTime.ts +51 -0
  134. package/src/adapter/entry-points/console/ui/src/features/console/types.ts +73 -1
  135. package/src/adapter/entry-points/console/ui/src/index.css +352 -2
  136. package/src/adapter/entry-points/console/ui/tsconfig.json +3 -1
  137. package/src/adapter/entry-points/console/ui/vite.config.ts +6 -1
  138. package/src/adapter/entry-points/console/ui-dist/assets/index-BU6p3cGU.css +1 -0
  139. package/src/adapter/entry-points/console/ui-dist/assets/index-BvuSQN9s.js +100 -0
  140. package/src/adapter/entry-points/console/ui-dist/index.html +2 -2
  141. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +25 -0
  142. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +4 -0
  143. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  144. package/types/adapter/entry-points/cli/projectConfig.d.ts +1 -0
  145. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  146. package/types/adapter/entry-points/console/consoleOperationApi.d.ts +6 -2
  147. package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
  148. package/types/adapter/entry-points/console/consoleProjectResolver.d.ts +6 -0
  149. package/types/adapter/entry-points/console/consoleProjectResolver.d.ts.map +1 -0
  150. package/types/adapter/entry-points/console/consoleServer.d.ts +3 -3
  151. package/types/adapter/entry-points/console/consoleServer.d.ts.map +1 -1
  152. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +1 -0
  153. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  154. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +1 -0
  155. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
  156. package/bin/adapter/entry-points/console/ui-dist/assets/index-DFxrSRH4.css +0 -1
  157. package/bin/adapter/entry-points/console/ui-dist/assets/index-DcOZ02ON.js +0 -49
  158. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleList.ts +0 -65
  159. package/src/adapter/entry-points/console/ui-dist/assets/index-DFxrSRH4.css +0 -1
  160. package/src/adapter/entry-points/console/ui-dist/assets/index-DcOZ02ON.js +0 -49
@@ -0,0 +1,28 @@
1
+ import { colorFromEnum } from '../colors';
2
+ import type { ConsoleColor } from '../types';
3
+
4
+ export type ConsoleStoryGroupHeaderProps = {
5
+ story: string;
6
+ count: number;
7
+ colorEnum: ConsoleColor | null;
8
+ };
9
+
10
+ export const ConsoleStoryGroupHeader = ({
11
+ story,
12
+ count,
13
+ colorEnum,
14
+ }: ConsoleStoryGroupHeaderProps) => {
15
+ const palette = colorFromEnum(colorEnum);
16
+ return (
17
+ <div className="console-group-header">
18
+ <span className="console-storytag">
19
+ <span
20
+ className="console-story-dot"
21
+ style={{ backgroundColor: palette.dot }}
22
+ />
23
+ {story}
24
+ </span>
25
+ <span className="console-group-count">{count}</span>
26
+ </div>
27
+ );
28
+ };
@@ -12,23 +12,59 @@ export default meta;
12
12
 
13
13
  type Story = StoryObj<typeof ConsoleTabBar>;
14
14
 
15
- export const PrsActive: Story = {
15
+ const counts: Record<ConsoleTabName, number> = {
16
+ prs: 35,
17
+ triage: 12,
18
+ unread: 7,
19
+ 'failed-preparation': 2,
20
+ 'todo-by-human': 4,
21
+ };
22
+
23
+ export const AllTabsWithCounts: Story = {
24
+ args: {
25
+ activeTab: 'prs',
26
+ counts,
27
+ onSelectTab: () => {},
28
+ },
29
+ };
30
+
31
+ export const ZeroCountTabsHidden: Story = {
16
32
  args: {
17
33
  activeTab: 'prs',
18
- onSelectTab: () => undefined,
34
+ counts: {
35
+ prs: 35,
36
+ triage: 0,
37
+ unread: 7,
38
+ 'failed-preparation': 0,
39
+ 'todo-by-human': 0,
40
+ },
41
+ onSelectTab: () => {},
19
42
  },
20
43
  };
21
44
 
22
- export const TriageActive: Story = {
45
+ export const ActiveZeroCountTabStaysVisible: Story = {
23
46
  args: {
24
47
  activeTab: 'triage',
25
- onSelectTab: () => undefined,
48
+ counts: {
49
+ prs: 35,
50
+ triage: 0,
51
+ unread: 7,
52
+ 'failed-preparation': 0,
53
+ 'todo-by-human': 0,
54
+ },
55
+ onSelectTab: () => {},
26
56
  },
27
57
  };
28
58
 
29
59
  export const Interactive: Story = {
30
60
  render: () => {
31
61
  const [activeTab, setActiveTab] = useState<ConsoleTabName>('prs');
32
- return <ConsoleTabBar activeTab={activeTab} onSelectTab={setActiveTab} />;
62
+ return (
63
+ <ConsoleTabBar
64
+ activeTab={activeTab}
65
+ counts={counts}
66
+ onSelectTab={setActiveTab}
67
+ />
68
+ );
33
69
  },
34
70
  };
@@ -0,0 +1,59 @@
1
+ import { fireEvent, render } from '@testing-library/react';
2
+ import type { ConsoleTabName } from '../types';
3
+ import { ConsoleTabBar } from './ConsoleTabBar';
4
+
5
+ const counts: Record<ConsoleTabName, number> = {
6
+ prs: 3,
7
+ triage: 0,
8
+ unread: 5,
9
+ 'failed-preparation': 0,
10
+ 'todo-by-human': 2,
11
+ };
12
+
13
+ describe('ConsoleTabBar', () => {
14
+ it('hides zero-count tabs except the active tab', () => {
15
+ const { queryByText } = render(
16
+ <ConsoleTabBar activeTab="prs" counts={counts} onSelectTab={() => {}} />,
17
+ );
18
+ expect(queryByText('Awaiting Quality Check')).not.toBeNull();
19
+ expect(queryByText('Unread')).not.toBeNull();
20
+ expect(queryByText('Todo by human')).not.toBeNull();
21
+ expect(queryByText('Triage')).toBeNull();
22
+ expect(queryByText('Failed Preparation')).toBeNull();
23
+ });
24
+
25
+ it('keeps a zero-count active tab visible', () => {
26
+ const { queryByText } = render(
27
+ <ConsoleTabBar
28
+ activeTab="triage"
29
+ counts={counts}
30
+ onSelectTab={() => {}}
31
+ />,
32
+ );
33
+ expect(queryByText('Triage')).not.toBeNull();
34
+ });
35
+
36
+ it('uses the exact lowercase Todo by human label', () => {
37
+ const { getByText } = render(
38
+ <ConsoleTabBar
39
+ activeTab="todo-by-human"
40
+ counts={counts}
41
+ onSelectTab={() => {}}
42
+ />,
43
+ );
44
+ expect(getByText('Todo by human')).toBeInTheDocument();
45
+ });
46
+
47
+ it('reports the selected tab', () => {
48
+ const onSelectTab = jest.fn();
49
+ const { getByText } = render(
50
+ <ConsoleTabBar
51
+ activeTab="prs"
52
+ counts={counts}
53
+ onSelectTab={onSelectTab}
54
+ />,
55
+ );
56
+ fireEvent.click(getByText('Unread'));
57
+ expect(onSelectTab).toHaveBeenCalledWith('unread');
58
+ });
59
+ });
@@ -1,32 +1,41 @@
1
- import { Button } from '@/components/ui/button';
2
- import { cn } from '@/lib/utils';
3
1
  import { CONSOLE_TABS, type ConsoleTabName } from '../types';
4
2
 
5
3
  export type ConsoleTabBarProps = {
6
4
  activeTab: ConsoleTabName;
5
+ counts: Record<ConsoleTabName, number>;
7
6
  onSelectTab: (tab: ConsoleTabName) => void;
8
7
  };
9
8
 
10
9
  export const ConsoleTabBar = ({
11
10
  activeTab,
11
+ counts,
12
12
  onSelectTab,
13
13
  }: ConsoleTabBarProps) => (
14
- <nav
15
- aria-label="Console tabs"
16
- className="flex flex-wrap gap-1 border-b border-border p-2"
17
- >
18
- {CONSOLE_TABS.map((tab) => (
19
- <Button
20
- key={tab.name}
21
- type="button"
22
- size="sm"
23
- variant={tab.name === activeTab ? 'default' : 'ghost'}
24
- aria-current={tab.name === activeTab ? 'page' : undefined}
25
- className={cn(tab.name === activeTab && 'font-semibold')}
26
- onClick={() => onSelectTab(tab.name)}
27
- >
28
- {tab.label}
29
- </Button>
30
- ))}
14
+ <nav aria-label="Console tabs" className="console-tabbar">
15
+ {CONSOLE_TABS.map((tab) => {
16
+ const count = counts[tab.name] ?? 0;
17
+ const isActive = tab.name === activeTab;
18
+ if (count === 0 && !isActive) {
19
+ return null;
20
+ }
21
+ return (
22
+ <button
23
+ key={tab.name}
24
+ type="button"
25
+ className="console-tab"
26
+ data-active={isActive ? 'true' : undefined}
27
+ aria-current={isActive ? 'page' : undefined}
28
+ onClick={() => onSelectTab(tab.name)}
29
+ >
30
+ <span className="console-tab-label">{tab.label}</span>
31
+ <span
32
+ className="console-tab-badge"
33
+ data-zero={count === 0 ? 'true' : undefined}
34
+ >
35
+ {count}
36
+ </span>
37
+ </button>
38
+ );
39
+ })}
31
40
  </nav>
32
41
  );
@@ -0,0 +1,35 @@
1
+ import { fileStatusBadge } from './fileStatus';
2
+
3
+ describe('fileStatusBadge', () => {
4
+ it('maps added to a green A badge', () => {
5
+ expect(fileStatusBadge('added')).toEqual({ label: 'A', color: '#3fb950' });
6
+ });
7
+
8
+ it('maps modified to a yellow M badge', () => {
9
+ expect(fileStatusBadge('modified')).toEqual({
10
+ label: 'M',
11
+ color: '#d29922',
12
+ });
13
+ });
14
+
15
+ it('maps removed to a red D badge', () => {
16
+ expect(fileStatusBadge('removed')).toEqual({
17
+ label: 'D',
18
+ color: '#f85149',
19
+ });
20
+ });
21
+
22
+ it('maps renamed to a purple R badge', () => {
23
+ expect(fileStatusBadge('renamed')).toEqual({
24
+ label: 'R',
25
+ color: '#a371f7',
26
+ });
27
+ });
28
+
29
+ it('maps an unknown status to a gray question badge', () => {
30
+ expect(fileStatusBadge('whatever')).toEqual({
31
+ label: '?',
32
+ color: '#8b949e',
33
+ });
34
+ });
35
+ });
@@ -0,0 +1,21 @@
1
+ export type ConsoleFileStatusBadge = {
2
+ label: string;
3
+ color: string;
4
+ };
5
+
6
+ export const fileStatusBadge = (status: string): ConsoleFileStatusBadge => {
7
+ switch (status) {
8
+ case 'added':
9
+ return { label: 'A', color: '#3fb950' };
10
+ case 'modified':
11
+ return { label: 'M', color: '#d29922' };
12
+ case 'removed':
13
+ return { label: 'D', color: '#f85149' };
14
+ case 'renamed':
15
+ return { label: 'R', color: '#a371f7' };
16
+ case 'changed':
17
+ return { label: 'M', color: '#d29922' };
18
+ default:
19
+ return { label: '?', color: '#8b949e' };
20
+ }
21
+ };
@@ -1,9 +1,53 @@
1
- import type { ConsoleListItem } from './types';
1
+ import type {
2
+ ConsoleChangedFile,
3
+ ConsoleComment,
4
+ ConsoleCommit,
5
+ ConsoleFieldOption,
6
+ ConsoleIssueState,
7
+ ConsoleListItem,
8
+ ConsoleRelatedPullRequest,
9
+ ConsoleStoryColorSource,
10
+ } from './types';
11
+
12
+ export const consoleStatusOptionsFixture: ConsoleFieldOption[] = [
13
+ { id: 'f75ad846', name: 'Unread', color: 'ORANGE' },
14
+ { id: 'd1c19cce', name: 'Awaiting Workspace', color: 'BLUE' },
15
+ { id: 'f57f1ce9', name: 'Preparation', color: 'YELLOW' },
16
+ { id: 'fd313492', name: 'Failed Preparation', color: 'RED' },
17
+ { id: 'e9931e57', name: 'Todo by human', color: 'PINK' },
18
+ { id: 'c2d278b2', name: 'In Tmux by human', color: 'RED' },
19
+ { id: 'e9f6a726', name: 'In Tmux by agent', color: 'YELLOW' },
20
+ { id: 'bd8358eb', name: 'Icebox', color: 'GRAY' },
21
+ ];
22
+
23
+ export const consoleStoryOptionsFixture: ConsoleFieldOption[] = [
24
+ { id: '28415d6c', name: 'regular / workflow improvement', color: 'GRAY' },
25
+ { id: '1491051e', name: 'TDPM Console port', color: 'BLUE' },
26
+ { id: 'e35b3da2', name: 'regular / WORKFLOW BLOCKER', color: 'RED' },
27
+ {
28
+ id: 'd7cdcb61',
29
+ name: 'regular / regular payment invoice tax',
30
+ color: 'YELLOW',
31
+ },
32
+ {
33
+ id: 'f7cd5cbc',
34
+ name: 'Publish product documentation site',
35
+ color: 'GREEN',
36
+ },
37
+ { id: '564803ee', name: 'Move to Okinawa', color: 'PURPLE' },
38
+ ];
39
+
40
+ export const consoleStoryColorsFixture: ConsoleStoryColorSource = {
41
+ 'TDPM Console port': { color: 'BLUE' },
42
+ 'regular / workflow improvement': { color: 'GRAY' },
43
+ 'Publish product documentation site': { color: 'GREEN' },
44
+ 'Move to Okinawa': { color: 'PURPLE' },
45
+ };
2
46
 
3
47
  export const consoleListItemsFixture: ConsoleListItem[] = [
4
48
  {
5
- number: 844,
6
- title: 'Add serveConsole server skeleton under entry-points',
49
+ number: 851,
50
+ title: 'Add serveConsole subcommand under entry-points',
7
51
  url: 'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/pull/851',
8
52
  repo: 'HiromiShikata/npm-cli-github-issue-tower-defence-management',
9
53
  nameWithOwner:
@@ -16,20 +60,34 @@ export const consoleListItemsFixture: ConsoleListItem[] = [
16
60
  createdAt: '2026-06-17T02:14:33.000Z',
17
61
  },
18
62
  {
19
- number: 845,
63
+ number: 853,
20
64
  title:
21
- 'Scaffold React console UI under entry-points with build bundling and minimal tab view',
22
- url: 'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/845',
65
+ 'Add server-side console API handlers for read and operation endpoints',
66
+ url: 'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/pull/853',
23
67
  repo: 'HiromiShikata/npm-cli-github-issue-tower-defence-management',
24
68
  nameWithOwner:
25
69
  'HiromiShikata/npm-cli-github-issue-tower-defence-management',
26
70
  projectItemId: 'PVTI_lADOABCD1234zgABCD02',
27
71
  itemId: 'PVTI_lADOABCD1234zgABCD02',
28
- isPr: false,
72
+ isPr: true,
29
73
  story: 'TDPM Console port',
30
74
  labels: ['claude'],
31
75
  createdAt: '2026-06-17T05:48:09.000Z',
32
76
  },
77
+ {
78
+ number: 845,
79
+ title: 'Scaffold React console UI under entry-points with build bundling',
80
+ url: 'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/845',
81
+ repo: 'HiromiShikata/npm-cli-github-issue-tower-defence-management',
82
+ nameWithOwner:
83
+ 'HiromiShikata/npm-cli-github-issue-tower-defence-management',
84
+ projectItemId: 'PVTI_lADOABCD1234zgABCD03',
85
+ itemId: 'PVTI_lADOABCD1234zgABCD03',
86
+ isPr: false,
87
+ story: 'TDPM Console port',
88
+ labels: [],
89
+ createdAt: '2026-06-16T22:01:55.000Z',
90
+ },
33
91
  {
34
92
  number: 778,
35
93
  title: 'Add Sonnet to Opus weekly-limit fallback routing per token',
@@ -37,11 +95,150 @@ export const consoleListItemsFixture: ConsoleListItem[] = [
37
95
  repo: 'HiromiShikata/npm-cli-github-issue-tower-defence-management',
38
96
  nameWithOwner:
39
97
  'HiromiShikata/npm-cli-github-issue-tower-defence-management',
40
- projectItemId: 'PVTI_lADOABCD1234zgABCD03',
41
- itemId: 'PVTI_lADOABCD1234zgABCD03',
98
+ projectItemId: 'PVTI_lADOABCD1234zgABCD04',
99
+ itemId: 'PVTI_lADOABCD1234zgABCD04',
42
100
  isPr: false,
43
101
  story: 'regular / workflow improvement',
44
102
  labels: [],
45
103
  createdAt: '2026-06-12T23:01:55.000Z',
46
104
  },
105
+ {
106
+ number: 692,
107
+ title: 'Publish the generated documentation site to GitHub Pages',
108
+ url: 'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/692',
109
+ repo: 'HiromiShikata/npm-cli-github-issue-tower-defence-management',
110
+ nameWithOwner:
111
+ 'HiromiShikata/npm-cli-github-issue-tower-defence-management',
112
+ projectItemId: 'PVTI_lADOABCD1234zgABCD05',
113
+ itemId: 'PVTI_lADOABCD1234zgABCD05',
114
+ isPr: false,
115
+ story: 'Publish product documentation site',
116
+ labels: ['documentation'],
117
+ createdAt: '2026-06-10T11:42:00.000Z',
118
+ },
47
119
  ];
120
+
121
+ export const consoleMarkdownBodyFixture = `## Summary
122
+
123
+ This pull request adds the \`serveConsole\` subcommand that serves the static
124
+ console bundle and the token-protected \`/api\` endpoints.
125
+
126
+ - Adds an HTTP server bound to port 9981 by default
127
+ - Serves \`list.json\` per tab and the read APIs by \`url\`
128
+ - Records acted-on items into a cross-tab done store
129
+
130
+ ### Checklist
131
+
132
+ - [x] Unit tests for the data delivery routing
133
+ - [x] Token validation via \`?k=\` and the \`x-pv-token\` header
134
+ `;
135
+
136
+ export const consoleMermaidBodyFixture = `Here is the request flow:
137
+
138
+ \`\`\`mermaid
139
+ sequenceDiagram
140
+ participant Browser
141
+ participant ConsoleServer
142
+ participant IssueRepository
143
+ Browser->>ConsoleServer: GET /api/itembody?url=...
144
+ ConsoleServer->>IssueRepository: getIssueOrPullRequestBody(url)
145
+ IssueRepository-->>ConsoleServer: body markdown
146
+ ConsoleServer-->>Browser: { body }
147
+ \`\`\`
148
+
149
+ The diagram above is rendered lazily.
150
+ `;
151
+
152
+ export const consoleMermaidCodeFixture = `sequenceDiagram
153
+ participant Browser
154
+ participant ConsoleServer
155
+ participant IssueRepository
156
+ Browser->>ConsoleServer: GET /api/itembody?url=...
157
+ ConsoleServer->>IssueRepository: getIssueOrPullRequestBody(url)
158
+ IssueRepository-->>ConsoleServer: body markdown
159
+ ConsoleServer-->>Browser: { body }`;
160
+
161
+ export const consoleCommentsFixture: ConsoleComment[] = [
162
+ {
163
+ author: 'HiromiShikata',
164
+ body: 'Please split the token validation into its own tested function.',
165
+ createdAt: '2026-06-17T06:12:40.000Z',
166
+ },
167
+ {
168
+ author: 'github-actions',
169
+ body: 'All required checks have passed on this pull request.',
170
+ createdAt: '2026-06-17T07:48:11.000Z',
171
+ },
172
+ {
173
+ author: 'HiromiShikata',
174
+ body: 'Looks good now. Approving once the rebase is green.',
175
+ createdAt: '2026-06-17T09:03:27.000Z',
176
+ },
177
+ ];
178
+
179
+ export const consoleChangedFilesFixture: ConsoleChangedFile[] = [
180
+ {
181
+ path: 'src/adapter/entry-points/console/consoleServer.ts',
182
+ additions: 312,
183
+ deletions: 4,
184
+ status: 'added',
185
+ patch: null,
186
+ },
187
+ {
188
+ path: 'src/adapter/entry-points/console/consoleServer.test.ts',
189
+ additions: 268,
190
+ deletions: 0,
191
+ status: 'added',
192
+ patch: null,
193
+ },
194
+ {
195
+ path: 'package.json',
196
+ additions: 6,
197
+ deletions: 1,
198
+ status: 'modified',
199
+ patch: null,
200
+ },
201
+ ];
202
+
203
+ export const consoleCommitsFixture: ConsoleCommit[] = [
204
+ {
205
+ sha: '4f9c2a1b6d8e0f3a7c5b9d1e2f4a6c8b0d2e4f6a',
206
+ message: 'feat(console): add serveConsole subcommand and HTTP server',
207
+ author: 'HiromiShikata',
208
+ authoredAt: '2026-06-17T02:10:11.000Z',
209
+ },
210
+ {
211
+ sha: '8b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d',
212
+ message: 'test(console): cover token validation and data routing',
213
+ author: 'HiromiShikata',
214
+ authoredAt: '2026-06-17T05:41:52.000Z',
215
+ },
216
+ ];
217
+
218
+ export const consoleRelatedPullRequestsFixture: ConsoleRelatedPullRequest[] = [
219
+ {
220
+ url: 'https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/pull/849',
221
+ branchName: 'feature/849-react-console-scaffold',
222
+ createdAt: '2026-06-16T22:30:00.000Z',
223
+ isDraft: false,
224
+ isConflicted: false,
225
+ isPassedAllCiJob: true,
226
+ isCiStateSuccess: true,
227
+ isResolvedAllReviewComments: true,
228
+ isBranchOutOfDate: false,
229
+ missingRequiredCheckNames: [],
230
+ summary: {
231
+ title: 'Scaffold React console UI under entry-points with build bundling',
232
+ body: 'Adds the React + Vite + Tailwind scaffold and the committed `ui-dist` single artifact.',
233
+ additions: 1184,
234
+ deletions: 12,
235
+ changedFiles: 27,
236
+ },
237
+ },
238
+ ];
239
+
240
+ export const consoleIssueStateFixture: ConsoleIssueState = {
241
+ state: 'open',
242
+ merged: false,
243
+ isPullRequest: true,
244
+ };
@@ -0,0 +1,91 @@
1
+ import {
2
+ buildConsoleListRows,
3
+ CONSOLE_NO_STORY_LABEL,
4
+ resolveItemStory,
5
+ resolveStoryColorEnum,
6
+ } from './grouping';
7
+ import type { ConsoleListItem, ConsoleOverlay } from './types';
8
+
9
+ const item = (
10
+ overrides: Partial<ConsoleListItem> &
11
+ Pick<ConsoleListItem, 'number' | 'story'>,
12
+ ): ConsoleListItem => ({
13
+ title: `Item ${overrides.number}`,
14
+ url: `https://github.com/o/r/issues/${overrides.number}`,
15
+ repo: 'o/r',
16
+ nameWithOwner: 'o/r',
17
+ projectItemId: `PVTI_${overrides.number}`,
18
+ itemId: `PVTI_${overrides.number}`,
19
+ isPr: false,
20
+ labels: [],
21
+ createdAt: '2026-06-10T00:00:00.000Z',
22
+ ...overrides,
23
+ });
24
+
25
+ describe('resolveStoryColorEnum', () => {
26
+ it('reads a wrapped color object shape', () => {
27
+ expect(resolveStoryColorEnum({ s: { color: 'BLUE' } }, 's')).toBe('BLUE');
28
+ });
29
+
30
+ it('reads a bare enum shape', () => {
31
+ expect(resolveStoryColorEnum({ s: 'RED' }, 's')).toBe('RED');
32
+ });
33
+
34
+ it('returns null for an unknown story', () => {
35
+ expect(resolveStoryColorEnum({}, 's')).toBeNull();
36
+ });
37
+ });
38
+
39
+ describe('resolveItemStory', () => {
40
+ it('prefers the overlay story name', () => {
41
+ const overlay: ConsoleOverlay = {
42
+ PVTI_1: {
43
+ ts: 1,
44
+ mode: 'triage',
45
+ story: { name: 'Overlay', color: 'BLUE' },
46
+ },
47
+ };
48
+ expect(
49
+ resolveItemStory(item({ number: 1, story: 'Original' }), overlay),
50
+ ).toBe('Overlay');
51
+ });
52
+
53
+ it('uses the trimmed item story when no overlay story exists', () => {
54
+ expect(resolveItemStory(item({ number: 2, story: ' Real ' }), {})).toBe(
55
+ ' Real ',
56
+ );
57
+ });
58
+
59
+ it('falls back to the no-story label when empty', () => {
60
+ expect(resolveItemStory(item({ number: 3, story: ' ' }), {})).toBe(
61
+ CONSOLE_NO_STORY_LABEL,
62
+ );
63
+ });
64
+ });
65
+
66
+ describe('buildConsoleListRows', () => {
67
+ it('inserts a group header whenever the story changes and keeps array order', () => {
68
+ const items = [
69
+ item({ number: 1, story: 'Alpha' }),
70
+ item({ number: 2, story: 'Alpha' }),
71
+ item({ number: 3, story: 'Beta' }),
72
+ item({ number: 4, story: 'Alpha' }),
73
+ ];
74
+ const rows = buildConsoleListRows(items, {});
75
+ expect(rows.map((row) => row.kind)).toEqual([
76
+ 'group-header',
77
+ 'item',
78
+ 'item',
79
+ 'group-header',
80
+ 'item',
81
+ 'group-header',
82
+ 'item',
83
+ ]);
84
+ const firstHeader = rows[0];
85
+ expect(firstHeader.kind === 'group-header' && firstHeader.count).toBe(3);
86
+ });
87
+
88
+ it('returns no rows for an empty list', () => {
89
+ expect(buildConsoleListRows([], {})).toEqual([]);
90
+ });
91
+ });
@@ -0,0 +1,79 @@
1
+ import type {
2
+ ConsoleColor,
3
+ ConsoleListItem,
4
+ ConsoleOverlay,
5
+ ConsoleStoryColorSource,
6
+ } from './types';
7
+
8
+ export const CONSOLE_NO_STORY_LABEL = '(No story)';
9
+
10
+ export const resolveStoryColorEnum = (
11
+ storyColors: ConsoleStoryColorSource,
12
+ storyName: string,
13
+ ): ConsoleColor | null => {
14
+ const entry = storyColors[storyName];
15
+ if (entry === undefined) {
16
+ return null;
17
+ }
18
+ if (typeof entry === 'string') {
19
+ return entry;
20
+ }
21
+ return entry.color;
22
+ };
23
+
24
+ export const resolveItemStory = (
25
+ item: ConsoleListItem,
26
+ overlay: ConsoleOverlay,
27
+ ): string => {
28
+ const overlayKey =
29
+ item.projectItemId !== '' ? item.projectItemId : item.itemId;
30
+ const overlayEntry = overlay[overlayKey];
31
+ if (
32
+ overlayEntry?.story?.name !== undefined &&
33
+ overlayEntry.story.name !== ''
34
+ ) {
35
+ return overlayEntry.story.name;
36
+ }
37
+ const trimmed = item.story.trim();
38
+ return trimmed !== '' ? item.story : CONSOLE_NO_STORY_LABEL;
39
+ };
40
+
41
+ export type ConsoleListGroupRow = {
42
+ kind: 'group-header';
43
+ story: string;
44
+ count: number;
45
+ };
46
+
47
+ export type ConsoleListItemRow = {
48
+ kind: 'item';
49
+ item: ConsoleListItem;
50
+ };
51
+
52
+ export type ConsoleListRow = ConsoleListGroupRow | ConsoleListItemRow;
53
+
54
+ export const buildConsoleListRows = (
55
+ items: ConsoleListItem[],
56
+ overlay: ConsoleOverlay,
57
+ ): ConsoleListRow[] => {
58
+ const storyCounts = new Map<string, number>();
59
+ for (const item of items) {
60
+ const story = resolveItemStory(item, overlay);
61
+ storyCounts.set(story, (storyCounts.get(story) ?? 0) + 1);
62
+ }
63
+
64
+ const rows: ConsoleListRow[] = [];
65
+ let previousStory: string | null = null;
66
+ for (const item of items) {
67
+ const story = resolveItemStory(item, overlay);
68
+ if (story !== previousStory) {
69
+ rows.push({
70
+ kind: 'group-header',
71
+ story,
72
+ count: storyCounts.get(story) ?? 0,
73
+ });
74
+ previousStory = story;
75
+ }
76
+ rows.push({ kind: 'item', item });
77
+ }
78
+ return rows;
79
+ };