github-issue-tower-defence-management 1.145.0 → 1.146.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 (94) hide show
  1. package/CHANGELOG.md +22 -8
  2. package/README.md +1 -1
  3. package/bin/adapter/entry-points/cli/index.js +9 -7
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/console/ensureConsoleRunning.js +6 -1
  6. package/bin/adapter/entry-points/console/ensureConsoleRunning.js.map +1 -1
  7. package/bin/adapter/entry-points/console/webServer.js +1 -3
  8. package/bin/adapter/entry-points/console/webServer.js.map +1 -1
  9. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +3 -1
  10. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  11. package/bin/adapter/repositories/GraphqlProjectRepository.js +75 -4
  12. package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
  13. package/bin/adapter/repositories/utils.js +2 -4
  14. package/bin/adapter/repositories/utils.js.map +1 -1
  15. package/bin/domain/entities/ProjectFieldName.js +6 -0
  16. package/bin/domain/entities/ProjectFieldName.js.map +1 -0
  17. package/bin/domain/entities/RequiredProjectField.js +47 -0
  18. package/bin/domain/entities/RequiredProjectField.js.map +1 -0
  19. package/bin/domain/usecases/HandleScheduledEventUseCase.js +5 -1
  20. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  21. package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js +25 -0
  22. package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js.map +1 -0
  23. package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js +6 -1
  24. package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js.map +1 -1
  25. package/bin/domain/usecases/dashboard/DashboardProjectCode.js +17 -12
  26. package/bin/domain/usecases/dashboard/DashboardProjectCode.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/adapter/entry-points/cli/index.test.ts +62 -2
  29. package/src/adapter/entry-points/cli/index.ts +12 -11
  30. package/src/adapter/entry-points/console/consoleDataDelivery.test.ts +37 -39
  31. package/src/adapter/entry-points/console/consoleDoneStore.test.ts +33 -33
  32. package/src/adapter/entry-points/console/consoleItemUrlLookup.test.ts +6 -6
  33. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +66 -66
  34. package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +35 -35
  35. package/src/adapter/entry-points/console/dashboardComposeService.test.ts +28 -28
  36. package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +26 -18
  37. package/src/adapter/entry-points/console/ensureConsoleRunning.test.ts +19 -2
  38. package/src/adapter/entry-points/console/ensureConsoleRunning.ts +8 -0
  39. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +1 -1
  40. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +1 -1
  41. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +2 -2
  42. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +3 -3
  43. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +19 -31
  44. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +24 -32
  45. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts +6 -6
  46. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.test.ts +4 -4
  47. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +8 -8
  48. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +5 -5
  49. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.test.ts +1 -1
  50. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +13 -13
  51. package/src/adapter/entry-points/console/webServer.test.ts +55 -52
  52. package/src/adapter/entry-points/console/webServer.ts +0 -3
  53. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +2 -2
  54. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +4 -0
  55. package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +5 -5
  56. package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +5 -5
  57. package/src/adapter/repositories/GraphqlProjectRepository.ts +110 -4
  58. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +1 -1
  59. package/src/adapter/repositories/utils.ts +3 -3
  60. package/src/domain/entities/ProjectFieldName.ts +2 -0
  61. package/src/domain/entities/RequiredProjectField.ts +64 -0
  62. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +4 -0
  63. package/src/domain/usecases/HandleScheduledEventUseCase.ts +5 -0
  64. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +3 -3
  65. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +159 -0
  66. package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts +32 -0
  67. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.test.ts +19 -19
  68. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +24 -14
  69. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +10 -1
  70. package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +6 -0
  71. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +28 -28
  72. package/src/domain/usecases/dashboard/DashboardProjectCode.test.ts +21 -19
  73. package/src/domain/usecases/dashboard/DashboardProjectCode.ts +21 -14
  74. package/types/adapter/entry-points/console/ensureConsoleRunning.d.ts +1 -1
  75. package/types/adapter/entry-points/console/ensureConsoleRunning.d.ts.map +1 -1
  76. package/types/adapter/entry-points/console/webServer.d.ts +0 -1
  77. package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
  78. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  79. package/types/adapter/repositories/GraphqlProjectRepository.d.ts +4 -1
  80. package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
  81. package/types/adapter/repositories/utils.d.ts.map +1 -1
  82. package/types/domain/entities/ProjectFieldName.d.ts +2 -0
  83. package/types/domain/entities/ProjectFieldName.d.ts.map +1 -0
  84. package/types/domain/entities/RequiredProjectField.d.ts +13 -0
  85. package/types/domain/entities/RequiredProjectField.d.ts.map +1 -0
  86. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +3 -1
  87. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  88. package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts +10 -0
  89. package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts.map +1 -0
  90. package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts.map +1 -1
  91. package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts +3 -0
  92. package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts.map +1 -1
  93. package/types/domain/usecases/dashboard/DashboardProjectCode.d.ts +2 -2
  94. package/types/domain/usecases/dashboard/DashboardProjectCode.d.ts.map +1 -1
@@ -9,33 +9,33 @@ import {
9
9
  describe('buildPjcodeToProjectUrl', () => {
10
10
  it('adds the default pjcode entry when it is not already present', () => {
11
11
  const mapping = buildPjcodeToProjectUrl(
12
- 'umino',
13
- 'https://github.com/orgs/umino/projects/1',
14
- { xmile: 'https://github.com/orgs/xmile/projects/2' },
12
+ 'acme',
13
+ 'https://github.com/orgs/acme/projects/1',
14
+ { globex: 'https://github.com/orgs/globex/projects/2' },
15
15
  );
16
16
  expect(mapping).toEqual({
17
- umino: 'https://github.com/orgs/umino/projects/1',
18
- xmile: 'https://github.com/orgs/xmile/projects/2',
17
+ acme: 'https://github.com/orgs/acme/projects/1',
18
+ globex: 'https://github.com/orgs/globex/projects/2',
19
19
  });
20
20
  });
21
21
 
22
22
  it('keeps an explicit default pjcode entry from consoleProjects', () => {
23
23
  const mapping = buildPjcodeToProjectUrl(
24
- 'umino',
25
- 'https://github.com/orgs/umino/projects/1',
26
- { umino: 'https://github.com/orgs/umino/projects/9' },
24
+ 'acme',
25
+ 'https://github.com/orgs/acme/projects/1',
26
+ { acme: 'https://github.com/orgs/acme/projects/9' },
27
27
  );
28
- expect(mapping.umino).toBe('https://github.com/orgs/umino/projects/9');
28
+ expect(mapping.acme).toBe('https://github.com/orgs/acme/projects/9');
29
29
  });
30
30
 
31
31
  it('uses only the default entry when no consoleProjects mapping is configured', () => {
32
32
  const mapping = buildPjcodeToProjectUrl(
33
- 'umino',
34
- 'https://github.com/orgs/umino/projects/1',
33
+ 'acme',
34
+ 'https://github.com/orgs/acme/projects/1',
35
35
  null,
36
36
  );
37
37
  expect(mapping).toEqual({
38
- umino: 'https://github.com/orgs/umino/projects/1',
38
+ acme: 'https://github.com/orgs/acme/projects/1',
39
39
  });
40
40
  });
41
41
  });
@@ -43,44 +43,44 @@ describe('buildPjcodeToProjectUrl', () => {
43
43
  describe('createPjcodeConfigChecker', () => {
44
44
  it('reports true only for a configured pjcode without loading any project', () => {
45
45
  const isConfigured = createPjcodeConfigChecker({
46
- umino: 'https://github.com/orgs/umino/projects/1',
47
- xmile: 'https://github.com/orgs/xmile/projects/2',
46
+ acme: 'https://github.com/orgs/acme/projects/1',
47
+ globex: 'https://github.com/orgs/globex/projects/2',
48
48
  });
49
- expect(isConfigured('umino')).toBe(true);
50
- expect(isConfigured('xmile')).toBe(true);
49
+ expect(isConfigured('acme')).toBe(true);
50
+ expect(isConfigured('globex')).toBe(true);
51
51
  expect(isConfigured('unknown')).toBe(false);
52
52
  });
53
53
  });
54
54
 
55
55
  describe('createConsoleProjectResolver', () => {
56
- const uminoProject: Project = { ...mock<Project>(), id: 'PVT_umino' };
57
- const xmileProject: Project = { ...mock<Project>(), id: 'PVT_xmile' };
56
+ const acmeProject: Project = { ...mock<Project>(), id: 'PVT_acme' };
57
+ const globexProject: Project = { ...mock<Project>(), id: 'PVT_globex' };
58
58
 
59
59
  it('resolves a known pjcode to its loaded project', async () => {
60
60
  const loadProject = jest.fn(async (url: string) =>
61
- url.includes('umino') ? uminoProject : xmileProject,
61
+ url.includes('acme') ? acmeProject : globexProject,
62
62
  );
63
63
  const resolver = createConsoleProjectResolver(
64
64
  {
65
- umino: 'https://github.com/orgs/umino/projects/1',
66
- xmile: 'https://github.com/orgs/xmile/projects/2',
65
+ acme: 'https://github.com/orgs/acme/projects/1',
66
+ globex: 'https://github.com/orgs/globex/projects/2',
67
67
  },
68
68
  loadProject,
69
69
  );
70
- await expect(resolver('umino')).resolves.toEqual({
71
- pjcode: 'umino',
72
- project: uminoProject,
70
+ await expect(resolver('acme')).resolves.toEqual({
71
+ pjcode: 'acme',
72
+ project: acmeProject,
73
73
  });
74
- await expect(resolver('xmile')).resolves.toEqual({
75
- pjcode: 'xmile',
76
- project: xmileProject,
74
+ await expect(resolver('globex')).resolves.toEqual({
75
+ pjcode: 'globex',
76
+ project: globexProject,
77
77
  });
78
78
  });
79
79
 
80
80
  it('returns null for a pjcode that has no configured project url', async () => {
81
- const loadProject = jest.fn(async () => uminoProject);
81
+ const loadProject = jest.fn(async () => acmeProject);
82
82
  const resolver = createConsoleProjectResolver(
83
- { umino: 'https://github.com/orgs/umino/projects/1' },
83
+ { acme: 'https://github.com/orgs/acme/projects/1' },
84
84
  loadProject,
85
85
  );
86
86
  await expect(resolver('unknown')).resolves.toBeNull();
@@ -90,20 +90,20 @@ describe('createConsoleProjectResolver', () => {
90
90
  it('returns null when the project fails to load', async () => {
91
91
  const loadProject = jest.fn(async () => null);
92
92
  const resolver = createConsoleProjectResolver(
93
- { umino: 'https://github.com/orgs/umino/projects/1' },
93
+ { acme: 'https://github.com/orgs/acme/projects/1' },
94
94
  loadProject,
95
95
  );
96
- await expect(resolver('umino')).resolves.toBeNull();
96
+ await expect(resolver('acme')).resolves.toBeNull();
97
97
  });
98
98
 
99
99
  it('loads each project at most once and serves later calls from cache', async () => {
100
- const loadProject = jest.fn(async () => uminoProject);
100
+ const loadProject = jest.fn(async () => acmeProject);
101
101
  const resolver = createConsoleProjectResolver(
102
- { umino: 'https://github.com/orgs/umino/projects/1' },
102
+ { acme: 'https://github.com/orgs/acme/projects/1' },
103
103
  loadProject,
104
104
  );
105
- await resolver('umino');
106
- await resolver('umino');
105
+ await resolver('acme');
106
+ await resolver('acme');
107
107
  expect(loadProject).toHaveBeenCalledTimes(1);
108
108
  });
109
109
  });
@@ -22,8 +22,8 @@ describe('buildComposeDashboardInput', () => {
22
22
  it('reads every requested project code in order and yields null for absent files', () => {
23
23
  const dataDir = makeDataDir();
24
24
  try {
25
- writeProject(dataDir, 'umino', {
26
- pjcode: 'umino',
25
+ writeProject(dataDir, 'acme', {
26
+ pjcode: 'acme',
27
27
  capturedAt: 'x',
28
28
  unread: 3,
29
29
  todo: 1,
@@ -36,11 +36,11 @@ describe('buildComposeDashboardInput', () => {
36
36
  });
37
37
  const input = buildComposeDashboardInput({
38
38
  dashboardDataDir: dataDir,
39
- projectNames: ['umino', 'xcare'],
39
+ projectNames: ['acme', 'initech'],
40
40
  });
41
41
  expect(input.projects).toEqual([
42
42
  {
43
- code: 'um',
43
+ code: 'ac',
44
44
  row: {
45
45
  unread: 3,
46
46
  todo: 1,
@@ -52,7 +52,7 @@ describe('buildComposeDashboardInput', () => {
52
52
  blocker: 0,
53
53
  },
54
54
  },
55
- { code: 'xc', row: null },
55
+ { code: 'in', row: null },
56
56
  ]);
57
57
  } finally {
58
58
  fs.rmSync(dataDir, { recursive: true, force: true });
@@ -62,10 +62,10 @@ describe('buildComposeDashboardInput', () => {
62
62
  it('treats a project file with a missing field as an absent row', () => {
63
63
  const dataDir = makeDataDir();
64
64
  try {
65
- writeProject(dataDir, 'umino', { unread: 1, todo: 1 });
65
+ writeProject(dataDir, 'acme', { unread: 1, todo: 1 });
66
66
  const input = buildComposeDashboardInput({
67
67
  dashboardDataDir: dataDir,
68
- projectNames: ['umino'],
68
+ projectNames: ['acme'],
69
69
  });
70
70
  expect(input.projects[0].row).toBeNull();
71
71
  } finally {
@@ -77,13 +77,10 @@ describe('buildComposeDashboardInput', () => {
77
77
  const dataDir = makeDataDir();
78
78
  try {
79
79
  fs.mkdirSync(path.join(dataDir, 'projects'), { recursive: true });
80
- fs.writeFileSync(
81
- path.join(dataDir, 'projects', 'umino.json'),
82
- 'not json',
83
- );
80
+ fs.writeFileSync(path.join(dataDir, 'projects', 'acme.json'), 'not json');
84
81
  const input = buildComposeDashboardInput({
85
82
  dashboardDataDir: dataDir,
86
- projectNames: ['umino'],
83
+ projectNames: ['acme'],
87
84
  });
88
85
  expect(input.projects[0].row).toBeNull();
89
86
  } finally {
@@ -365,8 +362,8 @@ describe('composeDashboardText', () => {
365
362
  it('composes the full byte-identical dashboard text from the data files', () => {
366
363
  const dataDir = makeDataDir();
367
364
  try {
368
- writeProject(dataDir, 'umino', {
369
- pjcode: 'umino',
365
+ writeProject(dataDir, 'acme', {
366
+ pjcode: 'acme',
370
367
  capturedAt: 'x',
371
368
  unread: 3,
372
369
  todo: 1,
@@ -409,14 +406,14 @@ describe('composeDashboardText', () => {
409
406
  expect(
410
407
  composeDashboardText({
411
408
  dashboardDataDir: dataDir,
412
- projectNames: ['umino', 'xcare'],
409
+ projectNames: ['acme', 'initech'],
413
410
  }),
414
411
  ).toBe(
415
412
  '<tt>M55%&nbsp;C62%&nbsp;D89%&nbsp;cy14</tt><br>\n' +
416
413
  '<tt>LA&nbsp;16&nbsp;23&nbsp;40</tt><br>\n' +
417
414
  '<tt>pj&nbsp;&nbsp;&nbsp;unr&nbsp;tdo&nbsp;aqc&nbsp;fal&nbsp;prp&nbsp;aws&nbsp;dep</tt><br>\n' +
418
- '<tt>🟢um&nbsp;&nbsp;&nbsp;3&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;4&nbsp;&nbsp;&nbsp;1</tt><br>\n' +
419
- '<tt>&nbsp;&nbsp;xc&nbsp;&nbsp;--&nbsp;&nbsp;--&nbsp;&nbsp;--&nbsp;&nbsp;--&nbsp;&nbsp;--&nbsp;&nbsp;--&nbsp;&nbsp;--</tt><br>\n' +
415
+ '<tt>🟢ac&nbsp;&nbsp;&nbsp;3&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;4&nbsp;&nbsp;&nbsp;1</tt><br>\n' +
416
+ '<tt>&nbsp;&nbsp;in&nbsp;&nbsp;--&nbsp;&nbsp;--&nbsp;&nbsp;--&nbsp;&nbsp;--&nbsp;&nbsp;--&nbsp;&nbsp;--&nbsp;&nbsp;--</tt><br>\n' +
420
417
  '<tt></tt><br>\n' +
421
418
  '<tt>🟢alice&nbsp;&nbsp;10%&nbsp;0d01h00&nbsp;&nbsp;12%&nbsp;5d00h00&nbsp;2&nbsp;1</tt><br>\n',
422
419
  );
@@ -446,7 +443,7 @@ describe('dashboardComposeFilesPresent', () => {
446
443
  };
447
444
 
448
445
  const minimalProject = {
449
- pjcode: 'umino',
446
+ pjcode: 'acme',
450
447
  capturedAt: 'x',
451
448
  unread: 0,
452
449
  todo: 0,
@@ -462,12 +459,15 @@ describe('dashboardComposeFilesPresent', () => {
462
459
  const dataDir = makeDataDir();
463
460
  try {
464
461
  writeMachineAndToken(dataDir);
465
- writeProject(dataDir, 'umino', minimalProject);
466
- writeProject(dataDir, 'xcare', { ...minimalProject, pjcode: 'xcare' });
462
+ writeProject(dataDir, 'acme', minimalProject);
463
+ writeProject(dataDir, 'initech', {
464
+ ...minimalProject,
465
+ pjcode: 'initech',
466
+ });
467
467
  expect(
468
468
  dashboardComposeFilesPresent({
469
469
  dashboardDataDir: dataDir,
470
- projectNames: ['umino', 'xcare'],
470
+ projectNames: ['acme', 'initech'],
471
471
  }),
472
472
  ).toBe(true);
473
473
  } finally {
@@ -481,7 +481,7 @@ describe('dashboardComposeFilesPresent', () => {
481
481
  expect(
482
482
  dashboardComposeFilesPresent({
483
483
  dashboardDataDir: dataDir,
484
- projectNames: ['umino'],
484
+ projectNames: ['acme'],
485
485
  }),
486
486
  ).toBe(false);
487
487
  } finally {
@@ -493,11 +493,11 @@ describe('dashboardComposeFilesPresent', () => {
493
493
  const dataDir = makeDataDir();
494
494
  try {
495
495
  writeMachineAndToken(dataDir);
496
- writeProject(dataDir, 'umino', minimalProject);
496
+ writeProject(dataDir, 'acme', minimalProject);
497
497
  expect(
498
498
  dashboardComposeFilesPresent({
499
499
  dashboardDataDir: dataDir,
500
- projectNames: ['umino', 'xcare'],
500
+ projectNames: ['acme', 'initech'],
501
501
  }),
502
502
  ).toBe(false);
503
503
  } finally {
@@ -512,11 +512,11 @@ describe('dashboardComposeFilesPresent', () => {
512
512
  path.join(dataDir, 'token-status.json'),
513
513
  JSON.stringify({ tokens: [], capturedAt: 'x' }),
514
514
  );
515
- writeProject(dataDir, 'umino', minimalProject);
515
+ writeProject(dataDir, 'acme', minimalProject);
516
516
  expect(
517
517
  dashboardComposeFilesPresent({
518
518
  dashboardDataDir: dataDir,
519
- projectNames: ['umino'],
519
+ projectNames: ['acme'],
520
520
  }),
521
521
  ).toBe(false);
522
522
  } finally {
@@ -538,11 +538,11 @@ describe('dashboardComposeFilesPresent', () => {
538
538
  capturedAt: 'x',
539
539
  }),
540
540
  );
541
- writeProject(dataDir, 'umino', minimalProject);
541
+ writeProject(dataDir, 'acme', minimalProject);
542
542
  expect(
543
543
  dashboardComposeFilesPresent({
544
544
  dashboardDataDir: dataDir,
545
- projectNames: ['umino'],
545
+ projectNames: ['acme'],
546
546
  }),
547
547
  ).toBe(false);
548
548
  } finally {
@@ -2,19 +2,23 @@ import * as fs from 'fs';
2
2
  import * as os from 'os';
3
3
  import * as path from 'path';
4
4
  import { Issue } from '../../../domain/entities/Issue';
5
- import {
6
- DASHBOARD_PROJECT_NAMES,
7
- toDashboardDisplayLabel,
8
- } from '../../../domain/usecases/dashboard/DashboardProjectCode';
5
+ import { toDashboardDisplayLabel } from '../../../domain/usecases/dashboard/DashboardProjectCode';
9
6
  import { writeDashboardRow } from '../handlers/dashboardRowWriter';
10
7
  import {
11
8
  composeDashboardText,
12
9
  dashboardComposeFilesPresent,
13
10
  } from './dashboardComposeService';
14
- import { DEFAULT_DASHBOARD_PROJECT_NAMES } from './webServer';
15
11
 
16
12
  const ASSIGNEE = 'HiromiShikata';
17
13
 
14
+ const CONFIGURED_PROJECT_NAMES = [
15
+ 'acme',
16
+ 'globex',
17
+ 'initech',
18
+ 'umbrella',
19
+ 'soylent',
20
+ ];
21
+
18
22
  let issueCounter = 0;
19
23
  const makeIssue = (overrides: Partial<Issue>): Issue => {
20
24
  issueCounter += 1;
@@ -59,7 +63,7 @@ describe('dashboard emitter filename matches composer lookup key', () => {
59
63
  fs.rmSync(dir, { recursive: true, force: true });
60
64
  });
61
65
 
62
- it.each(DASHBOARD_PROJECT_NAMES)(
66
+ it.each(CONFIGURED_PROJECT_NAMES)(
63
67
  'emits projects/%s.json under the full project name the composer reads',
64
68
  (projectName) => {
65
69
  writeDashboardRow({
@@ -80,7 +84,7 @@ describe('dashboard emitter filename matches composer lookup key', () => {
80
84
  );
81
85
 
82
86
  it('detects compose files present when every project name file exists', () => {
83
- for (const projectName of DASHBOARD_PROJECT_NAMES) {
87
+ for (const projectName of CONFIGURED_PROJECT_NAMES) {
84
88
  writeDashboardRow({
85
89
  dashboardDataDir: dir,
86
90
  pjcode: projectName,
@@ -107,19 +111,20 @@ describe('dashboard emitter filename matches composer lookup key', () => {
107
111
  expect(
108
112
  dashboardComposeFilesPresent({
109
113
  dashboardDataDir: dir,
110
- projectNames: DEFAULT_DASHBOARD_PROJECT_NAMES,
114
+ projectNames: CONFIGURED_PROJECT_NAMES,
111
115
  }),
112
116
  ).toBe(true);
113
117
  });
114
118
 
115
119
  it('composes the full grid with no fallback row and shows the 2-char display labels', () => {
116
120
  const issuesByName: Record<string, Issue[]> = {
117
- umino: [makeIssue({ status: 'Unread' }), makeIssue({ status: 'Unread' })],
118
- xmile: [makeIssue({ status: 'Awaiting Quality Check' })],
119
- xcare: [makeIssue({ status: 'Awaiting Workspace' })],
120
- utage3: [makeIssue({ status: 'Todo by human' })],
121
+ acme: [makeIssue({ status: 'Unread' }), makeIssue({ status: 'Unread' })],
122
+ globex: [makeIssue({ status: 'Awaiting Quality Check' })],
123
+ initech: [makeIssue({ status: 'Awaiting Workspace' })],
124
+ umbrella: [makeIssue({ status: 'Todo by human' })],
125
+ soylent: [makeIssue({ status: 'Preparation' })],
121
126
  };
122
- for (const projectName of DASHBOARD_PROJECT_NAMES) {
127
+ for (const projectName of CONFIGURED_PROJECT_NAMES) {
123
128
  writeDashboardRow({
124
129
  dashboardDataDir: dir,
125
130
  pjcode: projectName,
@@ -145,7 +150,7 @@ describe('dashboard emitter filename matches composer lookup key', () => {
145
150
 
146
151
  const composed = composeDashboardText({
147
152
  dashboardDataDir: dir,
148
- projectNames: DEFAULT_DASHBOARD_PROJECT_NAMES,
153
+ projectNames: CONFIGURED_PROJECT_NAMES,
149
154
  });
150
155
 
151
156
  const wrap = (line: string): string =>
@@ -156,16 +161,19 @@ describe('dashboard emitter filename matches composer lookup key', () => {
156
161
  wrap('LA 16 23 40'),
157
162
  wrap('pj unr tdo aqc fal prp aws dep'),
158
163
  wrap(
159
- `🟢${toDashboardDisplayLabel('umino')} 2 0 0 0 0 0 0`,
164
+ `🟢${toDashboardDisplayLabel('acme')} 2 0 0 0 0 0 0`,
165
+ ),
166
+ wrap(
167
+ `🟢${toDashboardDisplayLabel('globex')} 0 0 1 0 0 0 0`,
160
168
  ),
161
169
  wrap(
162
- `🟢${toDashboardDisplayLabel('xmile')} 0 0 1 0 0 0 0`,
170
+ `🟢${toDashboardDisplayLabel('initech')} 0 0 0 0 0 1 0`,
163
171
  ),
164
172
  wrap(
165
- `🟢${toDashboardDisplayLabel('xcare')} 0 0 0 0 0 1 0`,
173
+ `🟢${toDashboardDisplayLabel('umbrella')} 0 1 0 0 0 0 0`,
166
174
  ),
167
175
  wrap(
168
- `🟢${toDashboardDisplayLabel('utage3')} 0 1 0 0 0 0 0`,
176
+ `🟢${toDashboardDisplayLabel('soylent')} 0 0 0 0 1 0 0`,
169
177
  ),
170
178
  wrap(''),
171
179
  ].join('\n') + '\n';
@@ -54,14 +54,20 @@ describe('ensureConsoleRunning', () => {
54
54
 
55
55
  it('should short-circuit and return null when the port already responds', async () => {
56
56
  const port = await startProbeServer();
57
- const result = await ensureConsoleRunning('/path/to/config.yaml', port);
57
+ const result = await ensureConsoleRunning('/path/to/config.yaml', port, [
58
+ 'acme',
59
+ 'globex',
60
+ ]);
58
61
  expect(spawnCalls).toHaveLength(0);
59
62
  expect(result).toBeNull();
60
63
  });
61
64
 
62
65
  it('should spawn serveWeb when the port is unresponsive', async () => {
63
66
  const unusedPort = await findFreePort();
64
- await ensureConsoleRunning('/path/to/config.yaml', unusedPort);
67
+ await ensureConsoleRunning('/path/to/config.yaml', unusedPort, [
68
+ 'acme',
69
+ 'globex',
70
+ ]);
65
71
 
66
72
  expect(spawnCalls).toHaveLength(1);
67
73
  const [program, args, options] = spawnCalls[0];
@@ -80,16 +86,27 @@ describe('ensureConsoleRunning', () => {
80
86
  if (!isObject(options)) {
81
87
  throw new Error('Expected spawn options to be an object');
82
88
  }
89
+ expect(args[6]).toBe('--dashboardProjectNames');
90
+ expect(args[7]).toBe('acme,globex');
83
91
  expect(options.detached).toBe(true);
84
92
  expect(options.stdio).toBe('ignore');
85
93
  expect(unrefMock).toHaveBeenCalledTimes(1);
86
94
  });
87
95
 
96
+ it('should refuse to start the console when no project name is configured', async () => {
97
+ const unusedPort = await findFreePort();
98
+ await expect(
99
+ ensureConsoleRunning('/path/to/config.yaml', unusedPort, []),
100
+ ).rejects.toThrow('consoleProjects is empty');
101
+ expect(spawnCalls).toHaveLength(0);
102
+ });
103
+
88
104
  it('should return an object with a kill method that delegates to the child process', async () => {
89
105
  const unusedPort = await findFreePort();
90
106
  const result = await ensureConsoleRunning(
91
107
  '/path/to/config.yaml',
92
108
  unusedPort,
109
+ ['acme', 'globex'],
93
110
  );
94
111
  expect(result).not.toBeNull();
95
112
  result?.kill();
@@ -32,7 +32,13 @@ const sleep = (ms: number): Promise<void> =>
32
32
  export const ensureConsoleRunning = async (
33
33
  configFilePath: string,
34
34
  port: number,
35
+ dashboardProjectNames: string[],
35
36
  ): Promise<ConsoleProcess | null> => {
37
+ if (dashboardProjectNames.length === 0) {
38
+ throw new Error(
39
+ `Cannot start the console from ${configFilePath}: consoleProjects is empty, so the dashboard has no project to render`,
40
+ );
41
+ }
36
42
  if (await isPortResponding(port)) return null;
37
43
  const cliEntry = path.resolve(__dirname, '../cli/index.js');
38
44
  const child = spawn(
@@ -44,6 +50,8 @@ export const ensureConsoleRunning = async (
44
50
  configFilePath,
45
51
  '--port',
46
52
  String(port),
53
+ '--dashboardProjectNames',
54
+ dashboardProjectNames.join(','),
47
55
  ],
48
56
  {
49
57
  detached: true,
@@ -244,7 +244,7 @@ test('lists a still-open item and keeps its tab visible when the browser overlay
244
244
 
245
245
  await page.evaluate(() => {
246
246
  localStorage.setItem(
247
- 'pv_overlay_umino',
247
+ 'pv_overlay_acme',
248
248
  JSON.stringify({
249
249
  PVTI_lADOABCD1234zgTODO00869: {
250
250
  done: true,
@@ -19,7 +19,7 @@ import {
19
19
  } from '../../consoleReadApi';
20
20
  import { startWebServer } from '../../webServer';
21
21
 
22
- export const CONSOLE_E2E_PJCODE = 'umino';
22
+ export const CONSOLE_E2E_PJCODE = 'acme';
23
23
  export const CONSOLE_E2E_TOKEN = 'console-e2e-fixture-token-3f9c1a';
24
24
 
25
25
  export type ConsoleE2eReviewCommentCall = {
@@ -7,9 +7,9 @@ const meta: Meta<typeof ConsoleTabList> = {
7
7
  title: 'Console/ConsoleTabList',
8
8
  component: ConsoleTabList,
9
9
  args: {
10
- pjcode: 'umino',
10
+ pjcode: 'acme',
11
11
  generatedAt: '2026-06-19T08:42:11.000Z',
12
- tabHref: (tab: ConsoleTabName) => `/projects/umino/${tab}`,
12
+ tabHref: (tab: ConsoleTabName) => `/projects/acme/${tab}`,
13
13
  onSelectTab: () => {},
14
14
  },
15
15
  };
@@ -13,9 +13,9 @@ const counts: Record<ConsoleTabName, number> = {
13
13
  };
14
14
 
15
15
  const baseProps = {
16
- pjcode: 'umino',
16
+ pjcode: 'acme',
17
17
  generatedAt: '2026-06-19T08:42:11.000Z',
18
- tabHref: (tab: ConsoleTabName) => `/projects/umino/${tab}`,
18
+ tabHref: (tab: ConsoleTabName) => `/projects/acme/${tab}`,
19
19
  onSelectTab: () => {},
20
20
  };
21
21
 
@@ -87,7 +87,7 @@ describe('ConsoleTabList', () => {
87
87
  const { getByText } = render(
88
88
  <ConsoleTabList {...baseProps} activeTab="prs" counts={counts} />,
89
89
  );
90
- expect(getByText('umino')).toBeInTheDocument();
90
+ expect(getByText('acme')).toBeInTheDocument();
91
91
  expect(getByText('snapshot: 2026-06-19T08:42:11.000Z')).toBeInTheDocument();
92
92
  });
93
93