github-issue-tower-defence-management 1.193.0 → 1.194.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.
- package/CHANGELOG.md +14 -0
- package/README.md +4 -0
- package/bin/adapter/entry-points/cli/index.js +19 -0
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/{index-RaFqjB7u.js → index-CYwLv9pf.js} +2 -2
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/entry-points/handlers/tokenStatusWriter.js +1 -0
- package/bin/adapter/entry-points/handlers/tokenStatusWriter.js.map +1 -1
- package/bin/adapter/repositories/NodeLocalCommandRunner.js +5 -0
- package/bin/adapter/repositories/NodeLocalCommandRunner.js.map +1 -1
- package/bin/adapter/repositories/NodeTmuxSessionRepository.js +29 -0
- package/bin/adapter/repositories/NodeTmuxSessionRepository.js.map +1 -1
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js +4 -2
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +12 -2
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +1 -3
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
- package/bin/domain/usecases/dashboard/GenerateTokenStatusUseCase.js +12 -4
- package/bin/domain/usecases/dashboard/GenerateTokenStatusUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +102 -0
- package/src/adapter/entry-points/cli/index.ts +43 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +45 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +7 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.stories.tsx +31 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +96 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +20 -2
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +159 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +17 -1
- package/src/adapter/entry-points/console/ui-dist/assets/{index-RaFqjB7u.js → index-CYwLv9pf.js} +2 -2
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.test.ts +1 -0
- package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +1 -0
- package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.test.ts +4 -1
- package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +2 -0
- package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.test.ts +1 -0
- package/src/adapter/entry-points/handlers/tokenStatusWriter.ts +1 -0
- package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.test.ts +4 -0
- package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.test.ts +1 -0
- package/src/adapter/repositories/NodeLocalCommandRunner.test.ts +16 -0
- package/src/adapter/repositories/NodeLocalCommandRunner.ts +5 -1
- package/src/adapter/repositories/NodeSubAgentProcessLister.test.ts +1 -0
- package/src/adapter/repositories/NodeTmuxSessionRepository.test.ts +132 -0
- package/src/adapter/repositories/NodeTmuxSessionRepository.ts +37 -0
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +38 -0
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +6 -2
- package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.test.ts +2 -0
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +1 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +110 -2
- package/src/domain/usecases/StartPreparationUseCase.ts +34 -17
- package/src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts +1 -0
- package/src/domain/usecases/adapter-interfaces/TmuxSessionRepository.ts +4 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +5 -4
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +9 -11
- package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.test.ts +63 -0
- package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.ts +13 -4
- package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +1 -0
- package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +1 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/tokenStatusWriter.d.ts.map +1 -1
- package/types/adapter/repositories/NodeLocalCommandRunner.d.ts +1 -0
- package/types/adapter/repositories/NodeLocalCommandRunner.d.ts.map +1 -1
- package/types/adapter/repositories/NodeTmuxSessionRepository.d.ts +1 -0
- package/types/adapter/repositories/NodeTmuxSessionRepository.d.ts.map +1 -1
- package/types/adapter/repositories/ProxyClaudeTokenUsageRepository.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts +1 -0
- package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/TmuxSessionRepository.d.ts +1 -0
- package/types/domain/usecases/adapter-interfaces/TmuxSessionRepository.d.ts.map +1 -1
- package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
- package/types/domain/usecases/dashboard/GenerateTokenStatusUseCase.d.ts +1 -0
- package/types/domain/usecases/dashboard/GenerateTokenStatusUseCase.d.ts.map +1 -1
|
@@ -420,6 +420,51 @@ test('renders the stories tab with non-gray stories, their open item counts, and
|
|
|
420
420
|
).toHaveCount(0);
|
|
421
421
|
});
|
|
422
422
|
|
|
423
|
+
test('shows and hides gray stories with the Show archived toggle button on the stories tab', async ({
|
|
424
|
+
page,
|
|
425
|
+
}) => {
|
|
426
|
+
await page.goto(harness.appRootUrl);
|
|
427
|
+
|
|
428
|
+
await tabByLabel(page, 'Stories').click();
|
|
429
|
+
await expect(activeTabLabel(page)).toHaveText('Stories');
|
|
430
|
+
|
|
431
|
+
await expect(
|
|
432
|
+
page.locator('.console-op-button', { hasText: 'Show archived' }),
|
|
433
|
+
).toBeVisible();
|
|
434
|
+
|
|
435
|
+
await expect(
|
|
436
|
+
page.locator('.console-story-list-row', {
|
|
437
|
+
hasText: 'regular / workflow improvement',
|
|
438
|
+
}),
|
|
439
|
+
).toHaveCount(0);
|
|
440
|
+
|
|
441
|
+
await page
|
|
442
|
+
.locator('.console-op-button', { hasText: 'Show archived' })
|
|
443
|
+
.click();
|
|
444
|
+
|
|
445
|
+
await expect(
|
|
446
|
+
page.locator('.console-story-list-row', {
|
|
447
|
+
hasText: 'regular / workflow improvement',
|
|
448
|
+
}),
|
|
449
|
+
).toBeVisible();
|
|
450
|
+
await expect(
|
|
451
|
+
page.locator('.console-op-button', { hasText: 'Hide archived' }),
|
|
452
|
+
).toBeVisible();
|
|
453
|
+
|
|
454
|
+
await page
|
|
455
|
+
.locator('.console-op-button', { hasText: 'Hide archived' })
|
|
456
|
+
.click();
|
|
457
|
+
|
|
458
|
+
await expect(
|
|
459
|
+
page.locator('.console-story-list-row', {
|
|
460
|
+
hasText: 'regular / workflow improvement',
|
|
461
|
+
}),
|
|
462
|
+
).toHaveCount(0);
|
|
463
|
+
await expect(
|
|
464
|
+
page.locator('.console-op-button', { hasText: 'Show archived' }),
|
|
465
|
+
).toBeVisible();
|
|
466
|
+
});
|
|
467
|
+
|
|
423
468
|
test('shows the agent label and value in the list row and the agent chip in the detail view', async ({
|
|
424
469
|
page,
|
|
425
470
|
}) => {
|
|
@@ -295,6 +295,13 @@ const CONSOLE_E2E_STORIES_SNAPSHOT = {
|
|
|
295
295
|
storyViewUrl:
|
|
296
296
|
'https://github.com/orgs/HiromiShikata/projects/6/views/1?sliceBy%5Bvalue%5D=TDPM%20Console%20port',
|
|
297
297
|
},
|
|
298
|
+
{
|
|
299
|
+
storyName: 'regular / workflow improvement',
|
|
300
|
+
storyOptionId: '28415d6c',
|
|
301
|
+
color: 'GRAY',
|
|
302
|
+
openItemCount: 1,
|
|
303
|
+
storyViewUrl: null,
|
|
304
|
+
},
|
|
298
305
|
{
|
|
299
306
|
storyName: 'Publish product documentation site',
|
|
300
307
|
storyOptionId: 'f7cd5cbc',
|
|
@@ -47,13 +47,26 @@ const storiesWithUrl = storiesWithoutUrl.map((s) => ({
|
|
|
47
47
|
storyViewUrl: `${BASE_VIEW_URL}?sliceBy%5Bvalue%5D=${encodeURI(s.storyName).replace(/#/g, '%23').replace(/&/g, '%26')}`,
|
|
48
48
|
}));
|
|
49
49
|
|
|
50
|
+
const storiesWithArchived = [
|
|
51
|
+
...storiesWithoutUrl,
|
|
52
|
+
{
|
|
53
|
+
storyName: 'regular / NO STORY; SET STORY FIELD',
|
|
54
|
+
storyOptionId: '28415d6c',
|
|
55
|
+
color: 'GRAY' as const,
|
|
56
|
+
openItemCount: 5,
|
|
57
|
+
storyViewUrl: null,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
50
61
|
const meta: Meta<typeof ConsoleStoryList> = {
|
|
51
62
|
title: 'Console/ConsoleStoryList',
|
|
52
63
|
component: ConsoleStoryList,
|
|
53
64
|
args: {
|
|
65
|
+
showGray: false,
|
|
54
66
|
onCreateIssue: () => Promise.resolve(),
|
|
55
67
|
onAddStory: () => Promise.resolve(),
|
|
56
68
|
onSelectColor: () => undefined,
|
|
69
|
+
onToggleGray: () => undefined,
|
|
57
70
|
optimisticColors: {},
|
|
58
71
|
colorChangeInFlight: null,
|
|
59
72
|
colorErrors: {},
|
|
@@ -103,3 +116,21 @@ export const ErrorState: Story = {
|
|
|
103
116
|
error: 'HTTP 404',
|
|
104
117
|
},
|
|
105
118
|
};
|
|
119
|
+
|
|
120
|
+
export const WithArchivedHidden: Story = {
|
|
121
|
+
args: {
|
|
122
|
+
stories: storiesWithArchived,
|
|
123
|
+
isLoading: false,
|
|
124
|
+
error: null,
|
|
125
|
+
showGray: false,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export const WithArchivedShown: Story = {
|
|
130
|
+
args: {
|
|
131
|
+
stories: storiesWithArchived,
|
|
132
|
+
isLoading: false,
|
|
133
|
+
error: null,
|
|
134
|
+
showGray: true,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
@@ -19,13 +19,25 @@ const storyEntries: ConsoleStoryEntry[] = [
|
|
|
19
19
|
},
|
|
20
20
|
];
|
|
21
21
|
|
|
22
|
+
const grayStoryEntry: ConsoleStoryEntry = {
|
|
23
|
+
storyName: 'Archived Story',
|
|
24
|
+
storyOptionId: 'gray-id',
|
|
25
|
+
color: 'GRAY',
|
|
26
|
+
openItemCount: 3,
|
|
27
|
+
storyViewUrl: null,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const entriesWithGray: ConsoleStoryEntry[] = [...storyEntries, grayStoryEntry];
|
|
31
|
+
|
|
22
32
|
const defaultProps = {
|
|
23
33
|
stories: storyEntries,
|
|
24
34
|
isLoading: false,
|
|
25
35
|
error: null,
|
|
36
|
+
showGray: false,
|
|
26
37
|
onCreateIssue: () => Promise.resolve(),
|
|
27
38
|
onAddStory: () => Promise.resolve(),
|
|
28
39
|
onSelectColor: () => undefined,
|
|
40
|
+
onToggleGray: () => undefined,
|
|
29
41
|
optimisticColors: {} as Record<string, ConsoleColor>,
|
|
30
42
|
colorChangeInFlight: null as string | null,
|
|
31
43
|
colorErrors: {} as Record<string, string>,
|
|
@@ -370,4 +382,88 @@ describe('ConsoleStoryList', () => {
|
|
|
370
382
|
});
|
|
371
383
|
expect(writeText).toHaveBeenCalledWith(storyEntries[0].storyName);
|
|
372
384
|
});
|
|
385
|
+
|
|
386
|
+
describe('gray story toggle', () => {
|
|
387
|
+
it('hides gray stories when showGray is false', () => {
|
|
388
|
+
const { queryByText } = render(
|
|
389
|
+
<ConsoleStoryList
|
|
390
|
+
{...defaultProps}
|
|
391
|
+
stories={entriesWithGray}
|
|
392
|
+
showGray={false}
|
|
393
|
+
/>,
|
|
394
|
+
);
|
|
395
|
+
expect(queryByText('Archived Story')).toBeNull();
|
|
396
|
+
expect(queryByText('TDPM Console port')).toBeInTheDocument();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('shows gray stories when showGray is true', () => {
|
|
400
|
+
const { getByText } = render(
|
|
401
|
+
<ConsoleStoryList
|
|
402
|
+
{...defaultProps}
|
|
403
|
+
stories={entriesWithGray}
|
|
404
|
+
showGray={true}
|
|
405
|
+
/>,
|
|
406
|
+
);
|
|
407
|
+
expect(getByText('Archived Story')).toBeInTheDocument();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('shows the toggle button labeled Show archived when at least one gray story exists and showGray is false', () => {
|
|
411
|
+
const { getByRole } = render(
|
|
412
|
+
<ConsoleStoryList
|
|
413
|
+
{...defaultProps}
|
|
414
|
+
stories={entriesWithGray}
|
|
415
|
+
showGray={false}
|
|
416
|
+
/>,
|
|
417
|
+
);
|
|
418
|
+
expect(
|
|
419
|
+
getByRole('button', { name: 'Show archived' }),
|
|
420
|
+
).toBeInTheDocument();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('shows the toggle button labeled Hide archived when showGray is true', () => {
|
|
424
|
+
const { getByRole } = render(
|
|
425
|
+
<ConsoleStoryList
|
|
426
|
+
{...defaultProps}
|
|
427
|
+
stories={entriesWithGray}
|
|
428
|
+
showGray={true}
|
|
429
|
+
/>,
|
|
430
|
+
);
|
|
431
|
+
expect(
|
|
432
|
+
getByRole('button', { name: 'Hide archived' }),
|
|
433
|
+
).toBeInTheDocument();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('does not show the toggle button when no gray stories exist', () => {
|
|
437
|
+
const { queryByRole } = render(
|
|
438
|
+
<ConsoleStoryList {...defaultProps} stories={storyEntries} />,
|
|
439
|
+
);
|
|
440
|
+
expect(queryByRole('button', { name: 'Show archived' })).toBeNull();
|
|
441
|
+
expect(queryByRole('button', { name: 'Hide archived' })).toBeNull();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('calls onToggleGray when the toggle button is clicked', () => {
|
|
445
|
+
const onToggleGray = jest.fn();
|
|
446
|
+
const { getByRole } = render(
|
|
447
|
+
<ConsoleStoryList
|
|
448
|
+
{...defaultProps}
|
|
449
|
+
stories={entriesWithGray}
|
|
450
|
+
showGray={false}
|
|
451
|
+
onToggleGray={onToggleGray}
|
|
452
|
+
/>,
|
|
453
|
+
);
|
|
454
|
+
fireEvent.click(getByRole('button', { name: 'Show archived' }));
|
|
455
|
+
expect(onToggleGray).toHaveBeenCalledTimes(1);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('shows No active stories when all stories are gray and showGray is false', () => {
|
|
459
|
+
const { getByText } = render(
|
|
460
|
+
<ConsoleStoryList
|
|
461
|
+
{...defaultProps}
|
|
462
|
+
stories={[grayStoryEntry]}
|
|
463
|
+
showGray={false}
|
|
464
|
+
/>,
|
|
465
|
+
);
|
|
466
|
+
expect(getByText('No active stories')).toBeInTheDocument();
|
|
467
|
+
});
|
|
468
|
+
});
|
|
373
469
|
});
|
|
@@ -132,9 +132,11 @@ export type ConsoleStoryListProps = {
|
|
|
132
132
|
stories: ConsoleStoryEntry[];
|
|
133
133
|
isLoading: boolean;
|
|
134
134
|
error: string | null;
|
|
135
|
+
showGray: boolean;
|
|
135
136
|
onCreateIssue: (storyOptionId: string, title: string) => Promise<void>;
|
|
136
137
|
onAddStory: (storyName: string) => Promise<void>;
|
|
137
138
|
onSelectColor: (storyOptionId: string, newColor: ConsoleColor) => void;
|
|
139
|
+
onToggleGray: () => void;
|
|
138
140
|
optimisticColors: Record<string, ConsoleColor>;
|
|
139
141
|
colorChangeInFlight: string | null;
|
|
140
142
|
colorErrors: Record<string, string>;
|
|
@@ -144,9 +146,11 @@ export const ConsoleStoryList = ({
|
|
|
144
146
|
stories,
|
|
145
147
|
isLoading,
|
|
146
148
|
error,
|
|
149
|
+
showGray,
|
|
147
150
|
onCreateIssue,
|
|
148
151
|
onAddStory,
|
|
149
152
|
onSelectColor,
|
|
153
|
+
onToggleGray,
|
|
150
154
|
optimisticColors,
|
|
151
155
|
colorChangeInFlight,
|
|
152
156
|
colorErrors,
|
|
@@ -197,13 +201,18 @@ export const ConsoleStoryList = ({
|
|
|
197
201
|
onSelectColor(storyOptionId, newColor);
|
|
198
202
|
};
|
|
199
203
|
|
|
204
|
+
const visibleStories = showGray
|
|
205
|
+
? stories
|
|
206
|
+
: stories.filter((s) => s.color !== 'GRAY');
|
|
207
|
+
const hasGrayStories = stories.some((s) => s.color === 'GRAY');
|
|
208
|
+
|
|
200
209
|
return (
|
|
201
210
|
<div className="console-story-list-container">
|
|
202
|
-
{
|
|
211
|
+
{visibleStories.length === 0 ? (
|
|
203
212
|
<p className="console-list-empty">No active stories</p>
|
|
204
213
|
) : (
|
|
205
214
|
<ul className="console-story-list">
|
|
206
|
-
{
|
|
215
|
+
{visibleStories.map((entry) => {
|
|
207
216
|
const displayColor: ConsoleColor =
|
|
208
217
|
optimisticColors[entry.storyOptionId] ?? entry.color;
|
|
209
218
|
const palette = colorFromEnum(displayColor);
|
|
@@ -285,6 +294,15 @@ export const ConsoleStoryList = ({
|
|
|
285
294
|
})}
|
|
286
295
|
</ul>
|
|
287
296
|
)}
|
|
297
|
+
{hasGrayStories && (
|
|
298
|
+
<button
|
|
299
|
+
type="button"
|
|
300
|
+
className="console-op-button"
|
|
301
|
+
onClick={onToggleGray}
|
|
302
|
+
>
|
|
303
|
+
{showGray ? 'Hide archived' : 'Show archived'}
|
|
304
|
+
</button>
|
|
305
|
+
)}
|
|
288
306
|
<div className="console-add-story-section">
|
|
289
307
|
<button
|
|
290
308
|
type="button"
|
|
@@ -994,6 +994,165 @@ describe('ConsolePage auto-advance tab', () => {
|
|
|
994
994
|
});
|
|
995
995
|
});
|
|
996
996
|
|
|
997
|
+
it('shows the stories tab badge count as the number of non-GRAY stories', async () => {
|
|
998
|
+
global.fetch = jest.fn(async (url: string) => {
|
|
999
|
+
const listMatch = url.match(/\/projects\/[^/]+\/([^/]+)\/list\.json/);
|
|
1000
|
+
if (listMatch !== null) {
|
|
1001
|
+
const tab = listMatch[1];
|
|
1002
|
+
if (tab === 'stories') {
|
|
1003
|
+
return {
|
|
1004
|
+
ok: true,
|
|
1005
|
+
status: 200,
|
|
1006
|
+
json: async () => ({
|
|
1007
|
+
generatedAt: '2026-06-19T00:00:00.000Z',
|
|
1008
|
+
stories: [
|
|
1009
|
+
{
|
|
1010
|
+
storyName: 'TDPM Console port',
|
|
1011
|
+
storyOptionId: 'st1',
|
|
1012
|
+
color: 'BLUE',
|
|
1013
|
+
openItemCount: 3,
|
|
1014
|
+
storyViewUrl: null,
|
|
1015
|
+
},
|
|
1016
|
+
{
|
|
1017
|
+
storyName: 'regular / workflow improvement',
|
|
1018
|
+
storyOptionId: 'st2',
|
|
1019
|
+
color: 'GRAY',
|
|
1020
|
+
openItemCount: 2,
|
|
1021
|
+
storyViewUrl: null,
|
|
1022
|
+
},
|
|
1023
|
+
],
|
|
1024
|
+
defaultNameWithOwner: null,
|
|
1025
|
+
storyOrder: [],
|
|
1026
|
+
}),
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
return { ok: true, status: 200, json: async () => listPayload(tab) };
|
|
1030
|
+
}
|
|
1031
|
+
if (url === '/api/projects') {
|
|
1032
|
+
return {
|
|
1033
|
+
ok: true,
|
|
1034
|
+
status: 200,
|
|
1035
|
+
json: async () => ({ pjcodes: ['acme'] }),
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
return { ok: true, status: 200, json: async () => ({ body: '# body' }) };
|
|
1039
|
+
}) as unknown as typeof fetch;
|
|
1040
|
+
|
|
1041
|
+
const { container } = render(<ConsolePage />);
|
|
1042
|
+
|
|
1043
|
+
await waitFor(() => {
|
|
1044
|
+
const tabs = [...container.querySelectorAll('.console-tab')];
|
|
1045
|
+
const storiesTab = tabs.find((el) => el.textContent?.includes('Stories'));
|
|
1046
|
+
expect(storiesTab).toBeTruthy();
|
|
1047
|
+
expect(storiesTab?.querySelector('.console-tab-badge')?.textContent).toBe(
|
|
1048
|
+
'1',
|
|
1049
|
+
);
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
|
|
1053
|
+
it('reads console-story-show-gray from localStorage and shows gray stories when true', async () => {
|
|
1054
|
+
localStorage.setItem('console-story-show-gray', 'true');
|
|
1055
|
+
window.history.replaceState({}, '', '/projects/acme/stories?k=token');
|
|
1056
|
+
global.fetch = jest.fn(async (url: string) => {
|
|
1057
|
+
const listMatch = url.match(/\/projects\/[^/]+\/([^/]+)\/list\.json/);
|
|
1058
|
+
if (listMatch !== null) {
|
|
1059
|
+
const tab = listMatch[1];
|
|
1060
|
+
if (tab === 'stories') {
|
|
1061
|
+
return {
|
|
1062
|
+
ok: true,
|
|
1063
|
+
status: 200,
|
|
1064
|
+
json: async () => ({
|
|
1065
|
+
generatedAt: '2026-06-19T00:00:00.000Z',
|
|
1066
|
+
stories: [
|
|
1067
|
+
{
|
|
1068
|
+
storyName: 'TDPM Console port',
|
|
1069
|
+
storyOptionId: 'st1',
|
|
1070
|
+
color: 'BLUE',
|
|
1071
|
+
openItemCount: 3,
|
|
1072
|
+
storyViewUrl: null,
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
storyName: 'regular / workflow improvement',
|
|
1076
|
+
storyOptionId: 'st2',
|
|
1077
|
+
color: 'GRAY',
|
|
1078
|
+
openItemCount: 2,
|
|
1079
|
+
storyViewUrl: null,
|
|
1080
|
+
},
|
|
1081
|
+
],
|
|
1082
|
+
defaultNameWithOwner: null,
|
|
1083
|
+
storyOrder: [],
|
|
1084
|
+
}),
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
return { ok: true, status: 200, json: async () => listPayload(tab) };
|
|
1088
|
+
}
|
|
1089
|
+
if (url === '/api/projects') {
|
|
1090
|
+
return {
|
|
1091
|
+
ok: true,
|
|
1092
|
+
status: 200,
|
|
1093
|
+
json: async () => ({ pjcodes: ['acme'] }),
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
return { ok: true, status: 200, json: async () => ({ body: '# body' }) };
|
|
1097
|
+
}) as unknown as typeof fetch;
|
|
1098
|
+
|
|
1099
|
+
const { findByText } = render(<ConsolePage />);
|
|
1100
|
+
expect(
|
|
1101
|
+
await findByText('regular / workflow improvement'),
|
|
1102
|
+
).toBeInTheDocument();
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
it('writes true to console-story-show-gray in localStorage when the toggle button is clicked', async () => {
|
|
1106
|
+
window.history.replaceState({}, '', '/projects/acme/stories?k=token');
|
|
1107
|
+
global.fetch = jest.fn(async (url: string) => {
|
|
1108
|
+
const listMatch = url.match(/\/projects\/[^/]+\/([^/]+)\/list\.json/);
|
|
1109
|
+
if (listMatch !== null) {
|
|
1110
|
+
const tab = listMatch[1];
|
|
1111
|
+
if (tab === 'stories') {
|
|
1112
|
+
return {
|
|
1113
|
+
ok: true,
|
|
1114
|
+
status: 200,
|
|
1115
|
+
json: async () => ({
|
|
1116
|
+
generatedAt: '2026-06-19T00:00:00.000Z',
|
|
1117
|
+
stories: [
|
|
1118
|
+
{
|
|
1119
|
+
storyName: 'TDPM Console port',
|
|
1120
|
+
storyOptionId: 'st1',
|
|
1121
|
+
color: 'BLUE',
|
|
1122
|
+
openItemCount: 3,
|
|
1123
|
+
storyViewUrl: null,
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
storyName: 'regular / workflow improvement',
|
|
1127
|
+
storyOptionId: 'st2',
|
|
1128
|
+
color: 'GRAY',
|
|
1129
|
+
openItemCount: 2,
|
|
1130
|
+
storyViewUrl: null,
|
|
1131
|
+
},
|
|
1132
|
+
],
|
|
1133
|
+
defaultNameWithOwner: null,
|
|
1134
|
+
storyOrder: [],
|
|
1135
|
+
}),
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
return { ok: true, status: 200, json: async () => listPayload(tab) };
|
|
1139
|
+
}
|
|
1140
|
+
if (url === '/api/projects') {
|
|
1141
|
+
return {
|
|
1142
|
+
ok: true,
|
|
1143
|
+
status: 200,
|
|
1144
|
+
json: async () => ({ pjcodes: ['acme'] }),
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
return { ok: true, status: 200, json: async () => ({ body: '# body' }) };
|
|
1148
|
+
}) as unknown as typeof fetch;
|
|
1149
|
+
|
|
1150
|
+
const { findByRole } = render(<ConsolePage />);
|
|
1151
|
+
const toggleBtn = await findByRole('button', { name: 'Show archived' });
|
|
1152
|
+
fireEvent.click(toggleBtn);
|
|
1153
|
+
expect(localStorage.getItem('console-story-show-gray')).toBe('true');
|
|
1154
|
+
});
|
|
1155
|
+
|
|
997
1156
|
it('navigates to the next project when a completing action fires after the project timer elapses', async () => {
|
|
998
1157
|
localStorage.setItem(
|
|
999
1158
|
'tdpm-timer-settings',
|
|
@@ -97,7 +97,9 @@ export const ConsolePage = () => {
|
|
|
97
97
|
continue;
|
|
98
98
|
}
|
|
99
99
|
if (tab.name === 'stories') {
|
|
100
|
-
result[tab.name] = snapshot.stories.
|
|
100
|
+
result[tab.name] = snapshot.stories.filter(
|
|
101
|
+
(s) => s.color !== 'GRAY',
|
|
102
|
+
).length;
|
|
101
103
|
} else {
|
|
102
104
|
result[tab.name] = countPendingItems(
|
|
103
105
|
snapshot.items,
|
|
@@ -330,6 +332,18 @@ export const ConsolePage = () => {
|
|
|
330
332
|
|
|
331
333
|
const detailScreenRef = useConsoleSwipeNavigation(handleSwipe);
|
|
332
334
|
|
|
335
|
+
const [showGray, setShowGray] = useState(
|
|
336
|
+
() => localStorage.getItem('console-story-show-gray') === 'true',
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const handleToggleGray = useCallback((): void => {
|
|
340
|
+
setShowGray((prev) => {
|
|
341
|
+
const next = !prev;
|
|
342
|
+
localStorage.setItem('console-story-show-gray', String(next));
|
|
343
|
+
return next;
|
|
344
|
+
});
|
|
345
|
+
}, []);
|
|
346
|
+
|
|
333
347
|
const storiesSnapshot = snapshots.stories;
|
|
334
348
|
const storyEntries = storiesSnapshot?.stories ?? [];
|
|
335
349
|
const defaultNameWithOwner = storiesSnapshot?.defaultNameWithOwner ?? null;
|
|
@@ -502,9 +516,11 @@ export const ConsolePage = () => {
|
|
|
502
516
|
stories={storyEntries}
|
|
503
517
|
isLoading={isLoading}
|
|
504
518
|
error={error}
|
|
519
|
+
showGray={showGray}
|
|
505
520
|
onCreateIssue={handleCreateIssue}
|
|
506
521
|
onAddStory={handleStoryAdd}
|
|
507
522
|
onSelectColor={handleSelectColor}
|
|
523
|
+
onToggleGray={handleToggleGray}
|
|
508
524
|
optimisticColors={storyOptimisticColors}
|
|
509
525
|
colorChangeInFlight={storyColorChangeInFlight}
|
|
510
526
|
colorErrors={storyColorErrors}
|