github-issue-tower-defence-management 1.179.0 → 1.180.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 (101) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bin/adapter/entry-points/console/consoleOperationApi.js +42 -1
  3. package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
  4. package/bin/adapter/entry-points/console/ui-dist/assets/index-I_2Yu8fM.js +86 -0
  5. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  6. package/bin/adapter/entry-points/console/webServer.js +6 -0
  7. package/bin/adapter/entry-points/console/webServer.js.map +1 -1
  8. package/bin/adapter/repositories/SystemDateRepository.js +10 -10
  9. package/bin/adapter/repositories/SystemDateRepository.js.map +1 -1
  10. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +23 -4
  11. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  12. package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js +1 -1
  13. package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js.map +1 -1
  14. package/bin/domain/usecases/AnalyzeStoriesUseCase.js +1 -1
  15. package/bin/domain/usecases/AnalyzeStoriesUseCase.js.map +1 -1
  16. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js +2 -1
  17. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js.map +1 -1
  18. package/bin/domain/usecases/ClearPastNextActionDateHourUseCase.js +1 -1
  19. package/bin/domain/usecases/ClearPastNextActionDateHourUseCase.js.map +1 -1
  20. package/bin/domain/usecases/CreateEstimationIssueUseCase.js +3 -3
  21. package/bin/domain/usecases/CreateEstimationIssueUseCase.js.map +1 -1
  22. package/bin/domain/usecases/HandleScheduledEventUseCase.js +6 -6
  23. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  24. package/bin/domain/usecases/IssueRejectionEvaluator.js +3 -1
  25. package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
  26. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +2 -1
  27. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  28. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +2 -1
  29. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  30. package/bin/domain/usecases/StartPreparationUseCase.js +1 -1
  31. package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
  32. package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js +3 -3
  33. package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js.map +1 -1
  34. package/bin/domain/usecases/findLastAgentReport.js +2 -3
  35. package/bin/domain/usecases/findLastAgentReport.js.map +1 -1
  36. package/bin/domain/usecases/isAgentReportBody.js +43 -0
  37. package/bin/domain/usecases/isAgentReportBody.js.map +1 -0
  38. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js +2 -2
  39. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js.map +1 -1
  40. package/bin/domain/usecases/issueReactivationTriggerIsPending.js +2 -2
  41. package/bin/domain/usecases/issueReactivationTriggerIsPending.js.map +1 -1
  42. package/package.json +1 -1
  43. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +230 -0
  44. package/src/adapter/entry-points/console/consoleOperationApi.ts +48 -1
  45. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +36 -0
  46. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +25 -1
  47. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.stories.tsx +30 -0
  48. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.test.tsx +111 -0
  49. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.tsx +91 -0
  50. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +21 -0
  51. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.test.ts +24 -2
  52. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.ts +3 -1
  53. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +60 -11
  54. package/src/adapter/entry-points/console/ui-dist/assets/index-I_2Yu8fM.js +86 -0
  55. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  56. package/src/adapter/entry-points/console/webServer.ts +11 -0
  57. package/src/adapter/repositories/SystemDateRepository.test.ts +60 -0
  58. package/src/adapter/repositories/SystemDateRepository.ts +10 -10
  59. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +268 -2
  60. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +33 -4
  61. package/src/domain/usecases/AnalyzeProblemByIssueUseCase.ts +1 -1
  62. package/src/domain/usecases/AnalyzeStoriesUseCase.test.ts +80 -0
  63. package/src/domain/usecases/AnalyzeStoriesUseCase.ts +1 -1
  64. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +2 -1
  65. package/src/domain/usecases/ClearPastNextActionDateHourUseCase.ts +6 -4
  66. package/src/domain/usecases/CreateEstimationIssueUseCase.test.ts +225 -0
  67. package/src/domain/usecases/CreateEstimationIssueUseCase.ts +3 -3
  68. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +107 -0
  69. package/src/domain/usecases/HandleScheduledEventUseCase.ts +7 -7
  70. package/src/domain/usecases/IssueRejectionEvaluator.test.ts +83 -0
  71. package/src/domain/usecases/IssueRejectionEvaluator.ts +3 -1
  72. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +50 -0
  73. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +2 -1
  74. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +2 -1
  75. package/src/domain/usecases/StartPreparationUseCase.ts +5 -3
  76. package/src/domain/usecases/TriagerApprovalDispatchUseCase.ts +6 -5
  77. package/src/domain/usecases/findLastAgentReport.test.ts +28 -0
  78. package/src/domain/usecases/findLastAgentReport.ts +2 -3
  79. package/src/domain/usecases/isAgentReportBody.test.ts +108 -0
  80. package/src/domain/usecases/isAgentReportBody.ts +52 -0
  81. package/src/domain/usecases/isPullRequestDeclaredUnnecessary.ts +2 -2
  82. package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +33 -33
  83. package/src/domain/usecases/issueReactivationTriggerIsPending.ts +6 -4
  84. package/types/adapter/entry-points/console/consoleOperationApi.d.ts +3 -1
  85. package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
  86. package/types/adapter/entry-points/console/webServer.d.ts +2 -0
  87. package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
  88. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  89. package/types/domain/usecases/CheckIssueReviewReadinessUseCase.d.ts.map +1 -1
  90. package/types/domain/usecases/ClearPastNextActionDateHourUseCase.d.ts.map +1 -1
  91. package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
  92. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  93. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  94. package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
  95. package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts.map +1 -1
  96. package/types/domain/usecases/findLastAgentReport.d.ts.map +1 -1
  97. package/types/domain/usecases/isAgentReportBody.d.ts +4 -0
  98. package/types/domain/usecases/isAgentReportBody.d.ts.map +1 -0
  99. package/types/domain/usecases/issueReactivationTriggerIsPending.d.ts.map +1 -1
  100. package/bin/adapter/entry-points/console/ui-dist/assets/index-KCmqh781.js +0 -86
  101. package/src/adapter/entry-points/console/ui-dist/assets/index-KCmqh781.js +0 -86
@@ -0,0 +1,111 @@
1
+ import { fireEvent, render, waitFor } from '@testing-library/react';
2
+ import type { ConsoleFieldOption } from '../../logic/types';
3
+ import { ConsoleStoryReorderPanel } from './ConsoleStoryReorderPanel';
4
+
5
+ const stories: ConsoleFieldOption[] = [
6
+ { id: 'opt-a', name: 'Alpha story', color: 'BLUE' },
7
+ { id: 'opt-b', name: 'Beta story', color: 'GREEN' },
8
+ { id: 'opt-c', name: 'Gamma story', color: 'RED' },
9
+ ];
10
+
11
+ describe('ConsoleStoryReorderPanel', () => {
12
+ it('renders story names in the given order', () => {
13
+ const { getAllByRole } = render(
14
+ <ConsoleStoryReorderPanel
15
+ stories={stories}
16
+ onReorderStory={() => Promise.resolve()}
17
+ />,
18
+ );
19
+ const items = getAllByRole('listitem');
20
+ expect(items[0]).toHaveTextContent('Alpha story');
21
+ expect(items[1]).toHaveTextContent('Beta story');
22
+ expect(items[2]).toHaveTextContent('Gamma story');
23
+ });
24
+
25
+ it('disables up-button for the first story and down-button for the last story', () => {
26
+ const { getAllByRole } = render(
27
+ <ConsoleStoryReorderPanel
28
+ stories={stories}
29
+ onReorderStory={() => Promise.resolve()}
30
+ />,
31
+ );
32
+ const upButtons = getAllByRole('button', { name: 'Move up' });
33
+ const downButtons = getAllByRole('button', { name: 'Move down' });
34
+ expect(upButtons[0]).toBeDisabled();
35
+ expect(upButtons[1]).not.toBeDisabled();
36
+ expect(upButtons[2]).not.toBeDisabled();
37
+ expect(downButtons[0]).not.toBeDisabled();
38
+ expect(downButtons[1]).not.toBeDisabled();
39
+ expect(downButtons[2]).toBeDisabled();
40
+ });
41
+
42
+ it('calls onReorderStory with storyOptionId and "up" when up-button clicked on a non-first story', async () => {
43
+ const onReorderStory = jest.fn().mockResolvedValue(undefined);
44
+ const { getAllByRole } = render(
45
+ <ConsoleStoryReorderPanel
46
+ stories={stories}
47
+ onReorderStory={onReorderStory}
48
+ />,
49
+ );
50
+ const upButtons = getAllByRole('button', { name: 'Move up' });
51
+ fireEvent.click(upButtons[1]);
52
+ await waitFor(() => expect(onReorderStory).toHaveBeenCalledTimes(1));
53
+ expect(onReorderStory).toHaveBeenCalledWith('opt-b', 'up');
54
+ });
55
+
56
+ it('calls onReorderStory with storyOptionId and "down" when down-button clicked on a non-last story', async () => {
57
+ const onReorderStory = jest.fn().mockResolvedValue(undefined);
58
+ const { getAllByRole } = render(
59
+ <ConsoleStoryReorderPanel
60
+ stories={stories}
61
+ onReorderStory={onReorderStory}
62
+ />,
63
+ );
64
+ const downButtons = getAllByRole('button', { name: 'Move down' });
65
+ fireEvent.click(downButtons[1]);
66
+ await waitFor(() => expect(onReorderStory).toHaveBeenCalledTimes(1));
67
+ expect(onReorderStory).toHaveBeenCalledWith('opt-b', 'down');
68
+ });
69
+
70
+ it('disables both buttons while onReorderStory is in progress', async () => {
71
+ let resolveReorder: (() => void) | undefined;
72
+ const onReorderStory = jest.fn(
73
+ () =>
74
+ new Promise<void>((resolve) => {
75
+ resolveReorder = resolve;
76
+ }),
77
+ );
78
+ const { getAllByRole } = render(
79
+ <ConsoleStoryReorderPanel
80
+ stories={stories}
81
+ onReorderStory={onReorderStory}
82
+ />,
83
+ );
84
+ const downButtons = getAllByRole('button', { name: 'Move down' });
85
+ fireEvent.click(downButtons[1]);
86
+ await waitFor(() => expect(onReorderStory).toHaveBeenCalledTimes(1));
87
+ const upButtonsDuring = getAllByRole('button', { name: 'Move up' });
88
+ const downButtonsDuring = getAllByRole('button', { name: 'Move down' });
89
+ expect(upButtonsDuring[1]).toBeDisabled();
90
+ expect(downButtonsDuring[1]).toBeDisabled();
91
+ resolveReorder?.();
92
+ await waitFor(() => expect(upButtonsDuring[1]).not.toBeDisabled());
93
+ });
94
+
95
+ it('shows an error message when onReorderStory rejects', async () => {
96
+ const onReorderStory = jest
97
+ .fn()
98
+ .mockRejectedValue(new Error('server error'));
99
+ const { getAllByRole, getByRole } = render(
100
+ <ConsoleStoryReorderPanel
101
+ stories={stories}
102
+ onReorderStory={onReorderStory}
103
+ />,
104
+ );
105
+ const downButtons = getAllByRole('button', { name: 'Move down' });
106
+ fireEvent.click(downButtons[0]);
107
+ await waitFor(() =>
108
+ expect(getByRole('alert')).toHaveTextContent('server error'),
109
+ );
110
+ });
111
+ });
@@ -0,0 +1,91 @@
1
+ import { useState } from 'react';
2
+ import type { ConsoleFieldOption } from '../../logic/types';
3
+
4
+ export type ConsoleStoryReorderPanelProps = {
5
+ stories: ConsoleFieldOption[];
6
+ onReorderStory: (
7
+ storyOptionId: string,
8
+ direction: 'up' | 'down',
9
+ ) => Promise<void>;
10
+ };
11
+
12
+ type RowState = {
13
+ inProgress: boolean;
14
+ error: string | null;
15
+ };
16
+
17
+ export const ConsoleStoryReorderPanel = ({
18
+ stories,
19
+ onReorderStory,
20
+ }: ConsoleStoryReorderPanelProps) => {
21
+ const [rowStates, setRowStates] = useState<Record<string, RowState>>({});
22
+
23
+ const getRowState = (id: string): RowState =>
24
+ rowStates[id] ?? { inProgress: false, error: null };
25
+
26
+ const handleReorder = async (
27
+ storyOptionId: string,
28
+ direction: 'up' | 'down',
29
+ ): Promise<void> => {
30
+ setRowStates((prev) => ({
31
+ ...prev,
32
+ [storyOptionId]: { inProgress: true, error: null },
33
+ }));
34
+ try {
35
+ await onReorderStory(storyOptionId, direction);
36
+ setRowStates((prev) => ({
37
+ ...prev,
38
+ [storyOptionId]: { inProgress: false, error: null },
39
+ }));
40
+ } catch (err) {
41
+ setRowStates((prev) => ({
42
+ ...prev,
43
+ [storyOptionId]: {
44
+ inProgress: false,
45
+ error: err instanceof Error ? err.message : String(err),
46
+ },
47
+ }));
48
+ }
49
+ };
50
+
51
+ return (
52
+ <ul className="console-story-reorder-panel">
53
+ {stories.map((story, index) => {
54
+ const { inProgress, error } = getRowState(story.id);
55
+ const isFirst = index === 0;
56
+ const isLast = index === stories.length - 1;
57
+ return (
58
+ <li key={story.id} className="console-story-reorder-row">
59
+ <button
60
+ type="button"
61
+ className="console-op-button"
62
+ aria-label="Move up"
63
+ disabled={isFirst || inProgress}
64
+ onClick={() => void handleReorder(story.id, 'up')}
65
+ >
66
+ ↑
67
+ </button>
68
+ <button
69
+ type="button"
70
+ className="console-op-button"
71
+ aria-label="Move down"
72
+ disabled={isLast || inProgress}
73
+ onClick={() => void handleReorder(story.id, 'down')}
74
+ >
75
+ ↓
76
+ </button>
77
+ <span className="console-story-reorder-name">{story.name}</span>
78
+ {inProgress && (
79
+ <span className="console-story-reorder-progress">…</span>
80
+ )}
81
+ {error !== null && (
82
+ <span role="alert" className="console-story-reorder-error">
83
+ {error}
84
+ </span>
85
+ )}
86
+ </li>
87
+ );
88
+ })}
89
+ </ul>
90
+ );
91
+ };
@@ -417,3 +417,24 @@ export const postConsoleCreateIssue = async (
417
417
  }
418
418
  return payload.issueUrl;
419
419
  };
420
+
421
+ export const REORDER_STORY_OPERATION_PATH = '/api/reorderstory';
422
+
423
+ export type ConsoleReorderStoryRequest = {
424
+ pjcode: string;
425
+ storyOptionId: string;
426
+ direction: 'up' | 'down';
427
+ };
428
+
429
+ export const postConsoleReorderStory = async (
430
+ request: ConsoleReorderStoryRequest,
431
+ ): Promise<void> => {
432
+ const response = await fetch(REORDER_STORY_OPERATION_PATH, {
433
+ method: 'POST',
434
+ headers: { 'Content-Type': 'application/json' },
435
+ body: JSON.stringify(request),
436
+ });
437
+ if (!response.ok) {
438
+ throw new Error(await readOperationErrorReason(response));
439
+ }
440
+ };
@@ -39,14 +39,36 @@ describe('formatRelativeTime', () => {
39
39
  );
40
40
  });
41
41
 
42
+ it('returns month and day for 30+ days in the same UTC year', () => {
43
+ expect(formatRelativeTime('2026-05-01T12:00:00.000Z', now)).toBe('May 1');
44
+ });
45
+
46
+ it('includes the UTC year when the date is in a prior year', () => {
47
+ const result = formatRelativeTime('2025-12-01T12:00:00.000Z', now);
48
+ expect(result).toContain('2025');
49
+ expect(result).toContain('Dec');
50
+ });
51
+
52
+ it('uses UTC date boundary for the same-year check', () => {
53
+ const nowEndOf2025 = Date.parse('2025-12-31T23:59:59.000Z');
54
+ const result = formatRelativeTime('2025-10-01T00:00:00.000Z', nowEndOf2025);
55
+ expect(result).toContain('Oct');
56
+ expect(result).not.toContain('2025');
57
+ });
58
+
42
59
  it('returns an empty string for an invalid date', () => {
43
60
  expect(formatRelativeTime('nonsense', now)).toBe('');
44
61
  });
45
62
  });
46
63
 
47
64
  describe('formatFullTimestamp', () => {
48
- it('formats a timestamp and returns empty for invalid input', () => {
49
- expect(formatFullTimestamp('2026-06-19T12:00:00.000Z')).not.toBe('');
65
+ it('formats a UTC timestamp in ja-JP locale', () => {
66
+ expect(formatFullTimestamp('2026-06-19T09:05:00.000Z')).toMatch(
67
+ /2026.06.19.09.05/,
68
+ );
69
+ });
70
+
71
+ it('returns empty string for invalid input', () => {
50
72
  expect(formatFullTimestamp('nonsense')).toBe('');
51
73
  });
52
74
  });
@@ -28,8 +28,9 @@ export const formatRelativeTime = (iso: string, now: number): string => {
28
28
  return `${days} days ago`;
29
29
  }
30
30
  const date = new Date(then);
31
- const sameYear = date.getFullYear() === new Date(now).getFullYear();
31
+ const sameYear = date.getUTCFullYear() === new Date(now).getUTCFullYear();
32
32
  return date.toLocaleDateString('en-US', {
33
+ timeZone: 'UTC',
33
34
  year: sameYear ? undefined : 'numeric',
34
35
  month: 'short',
35
36
  day: 'numeric',
@@ -42,6 +43,7 @@ export const formatFullTimestamp = (iso: string): string => {
42
43
  return '';
43
44
  }
44
45
  return new Date(then).toLocaleString('ja-JP', {
46
+ timeZone: 'UTC',
45
47
  year: 'numeric',
46
48
  month: '2-digit',
47
49
  day: '2-digit',
@@ -1,7 +1,8 @@
1
- import { useCallback, useEffect, useMemo, useRef } from 'react';
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { ConsoleTabList } from '../components/layout/ConsoleTabList';
3
3
  import { ConsoleItemList } from '../components/list/ConsoleItemList';
4
4
  import { ConsoleStoryList } from '../components/list/ConsoleStoryList';
5
+ import { ConsoleStoryReorderPanel } from '../components/list/ConsoleStoryReorderPanel';
5
6
  import {
6
7
  ConsoleErrorToast,
7
8
  ConsoleUndoToast,
@@ -15,7 +16,10 @@ import { useConsoleOverlay } from '../hooks/useConsoleOverlay';
15
16
  import { useConsolePjcode } from '../hooks/useConsolePjcode';
16
17
  import { useConsoleSwipeNavigation } from '../hooks/useConsoleSwipeNavigation';
17
18
  import { useConsoleTabData } from '../hooks/useConsoleTabData';
18
- import { postConsoleCreateIssue } from '../lib/consoleApi';
19
+ import {
20
+ postConsoleCreateIssue,
21
+ postConsoleReorderStory,
22
+ } from '../lib/consoleApi';
19
23
  import {
20
24
  actionAdvances,
21
25
  actionToastColor,
@@ -136,6 +140,17 @@ export const ConsolePage = () => {
136
140
  const generatedAt = activeSnapshot?.generatedAt ?? null;
137
141
  const fromCache = activeSnapshot?.fromCache ?? false;
138
142
 
143
+ const [localStoryOptionsOverride, setLocalStoryOptionsOverride] = useState<{
144
+ generatedAt: string | undefined;
145
+ stories: typeof storyOptions;
146
+ } | null>(null);
147
+
148
+ const triageStoryOptions =
149
+ localStoryOptionsOverride !== null &&
150
+ localStoryOptionsOverride.generatedAt === activeSnapshot?.generatedAt
151
+ ? localStoryOptionsOverride.stories
152
+ : storyOptions.filter((o) => o.color !== 'GRAY');
153
+
139
154
  const selectedItem = useMemo<ConsoleListItem | null>(() => {
140
155
  if (selectedItemKey === null || activeSnapshot === null) {
141
156
  return null;
@@ -265,6 +280,32 @@ export const ConsolePage = () => {
265
280
  [pjcode, defaultNameWithOwner],
266
281
  );
267
282
 
283
+ const handleReorderStory = useCallback(
284
+ async (storyOptionId: string, direction: 'up' | 'down'): Promise<void> => {
285
+ if (pjcode === null) {
286
+ throw new Error('No project specified in the URL path.');
287
+ }
288
+ await postConsoleReorderStory({ pjcode, storyOptionId, direction });
289
+ const index = triageStoryOptions.findIndex((o) => o.id === storyOptionId);
290
+ if (index === -1) {
291
+ return;
292
+ }
293
+ const swapIndex = index + (direction === 'up' ? -1 : 1);
294
+ if (swapIndex < 0 || swapIndex >= triageStoryOptions.length) {
295
+ return;
296
+ }
297
+ const next = [...triageStoryOptions];
298
+ const temp = next[index];
299
+ next[index] = next[swapIndex];
300
+ next[swapIndex] = temp;
301
+ setLocalStoryOptionsOverride({
302
+ generatedAt: activeSnapshot?.generatedAt,
303
+ stories: next,
304
+ });
305
+ },
306
+ [pjcode, triageStoryOptions, activeSnapshot?.generatedAt],
307
+ );
308
+
268
309
  return (
269
310
  <main className="console-app">
270
311
  {actionQueue.pending !== null && (
@@ -299,15 +340,23 @@ export const ConsolePage = () => {
299
340
  onCreateIssue={handleCreateIssue}
300
341
  />
301
342
  ) : selectedItem === null ? (
302
- <ConsoleItemList
303
- rows={rows}
304
- storyColors={storyColors}
305
- activeItemId={null}
306
- now={now}
307
- isLoading={isLoading}
308
- error={error}
309
- onSelectItem={(item) => navigation.openItem(item.projectItemId)}
310
- />
343
+ <>
344
+ {activeTab === 'triage' && triageStoryOptions.length > 0 && (
345
+ <ConsoleStoryReorderPanel
346
+ stories={triageStoryOptions}
347
+ onReorderStory={handleReorderStory}
348
+ />
349
+ )}
350
+ <ConsoleItemList
351
+ rows={rows}
352
+ storyColors={storyColors}
353
+ activeItemId={null}
354
+ now={now}
355
+ isLoading={isLoading}
356
+ error={error}
357
+ onSelectItem={(item) => navigation.openItem(item.projectItemId)}
358
+ />
359
+ </>
311
360
  ) : (
312
361
  <div className="console-detail-screen" ref={detailScreenRef}>
313
362
  <ConsoleItemDetailContainer