github-issue-tower-defence-management 1.193.0 → 1.194.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 (88) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +4 -0
  3. package/bin/adapter/entry-points/cli/index.js +19 -0
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/console/ui-dist/assets/{index-RaFqjB7u.js → index-CYwLv9pf.js} +2 -2
  6. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  7. package/bin/adapter/entry-points/handlers/tokenStatusWriter.js +1 -0
  8. package/bin/adapter/entry-points/handlers/tokenStatusWriter.js.map +1 -1
  9. package/bin/adapter/repositories/NodeLocalCommandRunner.js +5 -0
  10. package/bin/adapter/repositories/NodeLocalCommandRunner.js.map +1 -1
  11. package/bin/adapter/repositories/NodeTmuxSessionRepository.js +29 -0
  12. package/bin/adapter/repositories/NodeTmuxSessionRepository.js.map +1 -1
  13. package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js +4 -2
  14. package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js.map +1 -1
  15. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +8 -3
  16. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  17. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +4 -2
  18. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  19. package/bin/domain/usecases/StartPreparationUseCase.js +12 -2
  20. package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
  21. package/bin/domain/usecases/awaitingOwnerApprovalMessage.js +1 -1
  22. package/bin/domain/usecases/awaitingOwnerApprovalMessage.js.map +1 -1
  23. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +1 -3
  24. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
  25. package/bin/domain/usecases/dashboard/GenerateTokenStatusUseCase.js +12 -4
  26. package/bin/domain/usecases/dashboard/GenerateTokenStatusUseCase.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/adapter/entry-points/cli/index.test.ts +102 -0
  29. package/src/adapter/entry-points/cli/index.ts +43 -0
  30. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +45 -0
  31. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +7 -0
  32. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.stories.tsx +31 -0
  33. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +96 -0
  34. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +20 -2
  35. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +159 -0
  36. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +17 -1
  37. package/src/adapter/entry-points/console/ui-dist/assets/{index-RaFqjB7u.js → index-CYwLv9pf.js} +2 -2
  38. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  39. package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.test.ts +1 -0
  40. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +1 -0
  41. package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.test.ts +4 -1
  42. package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +2 -0
  43. package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.test.ts +1 -0
  44. package/src/adapter/entry-points/handlers/tokenStatusWriter.ts +1 -0
  45. package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.test.ts +4 -0
  46. package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.test.ts +1 -0
  47. package/src/adapter/repositories/NodeLocalCommandRunner.test.ts +16 -0
  48. package/src/adapter/repositories/NodeLocalCommandRunner.ts +5 -1
  49. package/src/adapter/repositories/NodeSubAgentProcessLister.test.ts +1 -0
  50. package/src/adapter/repositories/NodeTmuxSessionRepository.test.ts +132 -0
  51. package/src/adapter/repositories/NodeTmuxSessionRepository.ts +37 -0
  52. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +38 -0
  53. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +6 -2
  54. package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.test.ts +2 -0
  55. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +78 -4
  56. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +15 -3
  57. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +9 -2
  58. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +10 -2
  59. package/src/domain/usecases/StartPreparationUseCase.test.ts +110 -2
  60. package/src/domain/usecases/StartPreparationUseCase.ts +34 -17
  61. package/src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts +1 -0
  62. package/src/domain/usecases/adapter-interfaces/TmuxSessionRepository.ts +4 -0
  63. package/src/domain/usecases/awaitingOwnerApprovalMessage.ts +1 -1
  64. package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +5 -4
  65. package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +9 -11
  66. package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.test.ts +63 -0
  67. package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.ts +13 -4
  68. package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +1 -0
  69. package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +1 -0
  70. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  71. package/types/adapter/entry-points/handlers/tokenStatusWriter.d.ts.map +1 -1
  72. package/types/adapter/repositories/NodeLocalCommandRunner.d.ts +1 -0
  73. package/types/adapter/repositories/NodeLocalCommandRunner.d.ts.map +1 -1
  74. package/types/adapter/repositories/NodeTmuxSessionRepository.d.ts +1 -0
  75. package/types/adapter/repositories/NodeTmuxSessionRepository.d.ts.map +1 -1
  76. package/types/adapter/repositories/ProxyClaudeTokenUsageRepository.d.ts.map +1 -1
  77. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  78. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  79. package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
  80. package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts +1 -0
  81. package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts.map +1 -1
  82. package/types/domain/usecases/adapter-interfaces/TmuxSessionRepository.d.ts +1 -0
  83. package/types/domain/usecases/adapter-interfaces/TmuxSessionRepository.d.ts.map +1 -1
  84. package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts +1 -1
  85. package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts.map +1 -1
  86. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
  87. package/types/domain/usecases/dashboard/GenerateTokenStatusUseCase.d.ts +1 -0
  88. package/types/domain/usecases/dashboard/GenerateTokenStatusUseCase.d.ts.map +1 -1
@@ -74,8 +74,12 @@ class GenerateTokenStatusUseCase {
74
74
  }
75
75
  const fiveHourReset = snapshot.fiveHourReset > 0 ? snapshot.fiveHourReset : null;
76
76
  const sevenDayReset = snapshot.sevenDayReset > 0 ? snapshot.sevenDayReset : null;
77
- const fiveHourExpired = fiveHourReset !== null && fiveHourReset < nowEpochSeconds;
78
- const sevenDayExpired = sevenDayReset !== null && sevenDayReset < nowEpochSeconds;
77
+ const fiveHourExpired = fiveHourReset !== null &&
78
+ fiveHourReset < nowEpochSeconds &&
79
+ snapshot.lastUpdatedEpoch >= fiveHourReset;
80
+ const sevenDayExpired = sevenDayReset !== null &&
81
+ sevenDayReset < nowEpochSeconds &&
82
+ snapshot.lastUpdatedEpoch >= sevenDayReset;
79
83
  return {
80
84
  fiveHourUtilizationPercent: fiveHourExpired
81
85
  ? 0
@@ -108,8 +112,12 @@ class GenerateTokenStatusUseCase {
108
112
  partial: true,
109
113
  };
110
114
  }
111
- const fiveHourExpired = snapshot.fiveHourReset > 0 && snapshot.fiveHourReset < nowEpochSeconds;
112
- const sevenDayExpired = snapshot.sevenDayReset > 0 && snapshot.sevenDayReset < nowEpochSeconds;
115
+ const fiveHourExpired = snapshot.fiveHourReset > 0 &&
116
+ snapshot.fiveHourReset < nowEpochSeconds &&
117
+ snapshot.lastUpdatedEpoch >= snapshot.fiveHourReset;
118
+ const sevenDayExpired = snapshot.sevenDayReset > 0 &&
119
+ snapshot.sevenDayReset < nowEpochSeconds &&
120
+ snapshot.lastUpdatedEpoch >= snapshot.sevenDayReset;
113
121
  return {
114
122
  fiveHourUtilization: fiveHourExpired ? 0 : snapshot.fiveHourUtilization,
115
123
  sevenDayUtilization: sevenDayExpired ? 0 : snapshot.sevenDayUtilization,
@@ -1 +1 @@
1
- {"version":3,"file":"GenerateTokenStatusUseCase.js","sourceRoot":"","sources":["../../../../src/domain/usecases/dashboard/GenerateTokenStatusUseCase.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"GenerateTokenStatusUseCase.js","sourceRoot":"","sources":["../../../../src/domain/usecases/dashboard/GenerateTokenStatusUseCase.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-issue-tower-defence-management",
3
- "version": "1.193.0",
3
+ "version": "1.194.1",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
@@ -8,6 +8,7 @@ jest.mock('fs', () => {
8
8
  return {
9
9
  ...actualFs,
10
10
  readFileSync: jest.fn(actualFs.readFileSync),
11
+ existsSync: jest.fn(actualFs.existsSync),
11
12
  };
12
13
  });
13
14
  import {
@@ -70,9 +71,11 @@ const mockRunCommand = jest.fn<
70
71
  Promise<{ stdout: string; stderr: string; exitCode: number }>,
71
72
  [string, string[]]
72
73
  >();
74
+ const mockSpawnInteractive = jest.fn<void, [string, string[]]>();
73
75
  jest.mock('../../repositories/NodeLocalCommandRunner', () => ({
74
76
  NodeLocalCommandRunner: jest.fn().mockImplementation(() => ({
75
77
  runCommand: mockRunCommand,
78
+ spawnInteractive: mockSpawnInteractive,
76
79
  })),
77
80
  }));
78
81
  jest.mock('../../repositories/OauthAPIClaudeRepository', () => ({
@@ -2463,6 +2466,105 @@ mysteryKey: 'value'
2463
2466
  });
2464
2467
  });
2465
2468
 
2469
+ describe('attachOrCreate', () => {
2470
+ const issueUrl = 'https://github.com/owner/repo/issues/9';
2471
+ let scopeLibPath = '';
2472
+
2473
+ beforeEach(() => {
2474
+ scopeLibPath = path.join(
2475
+ os.tmpdir(),
2476
+ `test-cl-scope-lib-${Date.now()}.sh`,
2477
+ );
2478
+ fs.writeFileSync(scopeLibPath, '#!/bin/bash\n');
2479
+ process.env.CL_SCOPE_LIB_PATH = scopeLibPath;
2480
+ });
2481
+
2482
+ afterEach(() => {
2483
+ if (fs.existsSync(scopeLibPath)) {
2484
+ fs.unlinkSync(scopeLibPath);
2485
+ }
2486
+ delete process.env.CL_SCOPE_LIB_PATH;
2487
+ });
2488
+
2489
+ it('attaches to the registered session when registry returns a live session', async () => {
2490
+ mockRunCommand
2491
+ .mockResolvedValueOnce({ stdout: 'wq7x3mk\n', stderr: '', exitCode: 0 })
2492
+ .mockResolvedValueOnce({ stdout: '', stderr: '', exitCode: 0 });
2493
+
2494
+ await program.parseAsync([
2495
+ 'node',
2496
+ 'test',
2497
+ 'attachOrCreate',
2498
+ '--issueUrl',
2499
+ issueUrl,
2500
+ ]);
2501
+
2502
+ expect(mockRunCommand.mock.calls[0]).toEqual([
2503
+ 'bash',
2504
+ [scopeLibPath, 'registry-get-session', issueUrl],
2505
+ ]);
2506
+ expect(mockRunCommand.mock.calls[1]).toEqual([
2507
+ 'tmux',
2508
+ ['has-session', '-t', '=wq7x3mk'],
2509
+ ]);
2510
+ expect(mockSpawnInteractive).toHaveBeenCalledWith('tmux', [
2511
+ 'attach-session',
2512
+ '-t',
2513
+ '=wq7x3mk',
2514
+ ]);
2515
+ });
2516
+
2517
+ it('creates a new session when no registered session is found', async () => {
2518
+ mockRunCommand.mockResolvedValueOnce({
2519
+ stdout: '',
2520
+ stderr: '',
2521
+ exitCode: 0,
2522
+ });
2523
+
2524
+ await program.parseAsync([
2525
+ 'node',
2526
+ 'test',
2527
+ 'attachOrCreate',
2528
+ '--issueUrl',
2529
+ issueUrl,
2530
+ ]);
2531
+
2532
+ expect(mockSpawnInteractive).toHaveBeenCalledWith('tmux', [
2533
+ 'new-session',
2534
+ '-A',
2535
+ '-s',
2536
+ issueUrl,
2537
+ 'cl',
2538
+ issueUrl,
2539
+ ]);
2540
+ });
2541
+
2542
+ it('creates a new session when no scope lib path is available', async () => {
2543
+ jest
2544
+ .mocked(fs.existsSync)
2545
+ .mockReturnValueOnce(false)
2546
+ .mockReturnValueOnce(false);
2547
+
2548
+ await program.parseAsync([
2549
+ 'node',
2550
+ 'test',
2551
+ 'attachOrCreate',
2552
+ '--issueUrl',
2553
+ issueUrl,
2554
+ ]);
2555
+
2556
+ expect(mockRunCommand.mock.calls).toHaveLength(0);
2557
+ expect(mockSpawnInteractive).toHaveBeenCalledWith('tmux', [
2558
+ 'new-session',
2559
+ '-A',
2560
+ '-s',
2561
+ issueUrl,
2562
+ 'cl',
2563
+ issueUrl,
2564
+ ]);
2565
+ });
2566
+ });
2567
+
2466
2568
  describe('ownerCallFileAppend and ownerCallFileDelete', () => {
2467
2569
  const issueUrl = 'https://github.com/OWNER/REPO/issues/1';
2468
2570
  const otherIssueUrl = 'https://github.com/OWNER/REPO/issues/2';
@@ -36,6 +36,7 @@ import { ProcTakeOwnershipSpawnRepository } from '../../repositories/ProcTakeOwn
36
36
  import { DEFAULT_THRESHOLD_FOR_DISPATCH_LOOP } from '../../../domain/usecases/resolveNextStepAgentDispatchRepetition';
37
37
  import { ProxyClaudeTokenUsageRepository } from '../../repositories/ProxyClaudeTokenUsageRepository';
38
38
  import { SystemDateRepository } from '../../repositories/SystemDateRepository';
39
+ import * as os from 'os';
39
40
  import {
40
41
  createConsoleGithubTokenResolver,
41
42
  createConsoleGithubTokenResolverByItemUrl,
@@ -198,6 +199,27 @@ type KillTmuxSessionOptions = {
198
199
  self?: boolean;
199
200
  };
200
201
 
202
+ type AttachOrCreateOptions = {
203
+ issueUrl: string;
204
+ };
205
+
206
+ const resolveScopeLibPath = (): string | null => {
207
+ const explicitPath = process.env.CL_SCOPE_LIB_PATH;
208
+ if (explicitPath !== undefined && fs.existsSync(explicitPath)) {
209
+ return explicitPath;
210
+ }
211
+ const defaultPath = path.join(
212
+ os.homedir(),
213
+ 'git',
214
+ 'secretary',
215
+ 'machine',
216
+ 'sk',
217
+ 'sh',
218
+ 'cl-scope-lib.sh',
219
+ );
220
+ return fs.existsSync(defaultPath) ? defaultPath : null;
221
+ };
222
+
201
223
  const buildGithubRepositoryParams = (
202
224
  localStorageRepository: LocalStorageRepository,
203
225
  token: string,
@@ -1302,6 +1324,27 @@ program
1302
1324
  });
1303
1325
  });
1304
1326
 
1327
+ program
1328
+ .command('attachOrCreate')
1329
+ .description(
1330
+ 'Attach to an existing tmux session registered for the given issue URL, or create a new one. Looks up the cl session registry via cl-scope-lib.sh to find a registered session name; if that session is still alive it attaches to it, otherwise creates a new interactive session running cl for the issue URL.',
1331
+ )
1332
+ .requiredOption(
1333
+ '--issueUrl <url>',
1334
+ 'GitHub issue URL to attach to or create a session for',
1335
+ )
1336
+ .action(async (options: AttachOrCreateOptions) => {
1337
+ const localCommandRunner = new NodeLocalCommandRunner();
1338
+ const tmuxSessionRepository = new NodeTmuxSessionRepository(
1339
+ localCommandRunner,
1340
+ );
1341
+ const scopeLibPath = resolveScopeLibPath();
1342
+ await tmuxSessionRepository.attachOrCreateInteractiveSession(
1343
+ options.issueUrl,
1344
+ scopeLibPath,
1345
+ );
1346
+ });
1347
+
1305
1348
  export const reportFatalErrorAndExit = (error: unknown): void => {
1306
1349
  console.error(error);
1307
1350
  process.exit(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
- {stories.length === 0 ? (
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
- {stories.map((entry) => {
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"