github-issue-tower-defence-management 1.190.0 → 1.192.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +2 -0
  3. package/bin/adapter/entry-points/cli/index.js +5 -0
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/cli/projectConfig.js +3 -0
  6. package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
  7. package/bin/adapter/entry-points/console/ui-dist/assets/{index-B22eMy23.js → index-RaFqjB7u.js} +3 -3
  8. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  9. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +23 -0
  10. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  11. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +19 -0
  12. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  13. package/bin/domain/usecases/StartPreparationUseCase.js +9 -5
  14. package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
  15. package/bin/domain/usecases/awaitingOwnerApprovalMessage.js +7 -0
  16. package/bin/domain/usecases/awaitingOwnerApprovalMessage.js.map +1 -0
  17. package/bin/domain/usecases/isWaitingForOwnerApproval.js +16 -0
  18. package/bin/domain/usecases/isWaitingForOwnerApproval.js.map +1 -0
  19. package/package.json +1 -1
  20. package/src/adapter/entry-points/cli/index.test.ts +2 -0
  21. package/src/adapter/entry-points/cli/index.ts +9 -0
  22. package/src/adapter/entry-points/cli/projectConfig.ts +8 -0
  23. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +35 -1
  24. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +13 -1
  25. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.stories.tsx +7 -0
  26. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.test.tsx +82 -0
  27. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.tsx +23 -3
  28. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.ts +4 -3
  29. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +79 -0
  30. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +19 -3
  31. package/src/adapter/entry-points/console/ui-dist/assets/{index-B22eMy23.js → index-RaFqjB7u.js} +3 -3
  32. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  33. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +86 -0
  34. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +52 -0
  35. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +93 -0
  36. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +47 -1
  37. package/src/domain/usecases/StartPreparationUseCase.test.ts +76 -0
  38. package/src/domain/usecases/StartPreparationUseCase.ts +9 -5
  39. package/src/domain/usecases/awaitingOwnerApprovalMessage.ts +5 -0
  40. package/src/domain/usecases/isWaitingForOwnerApproval.test.ts +91 -0
  41. package/src/domain/usecases/isWaitingForOwnerApproval.ts +17 -0
  42. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  43. package/types/adapter/entry-points/cli/projectConfig.d.ts +1 -0
  44. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  45. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +1 -0
  46. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  47. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts +1 -0
  48. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  49. package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
  50. package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts +3 -0
  51. package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts.map +1 -0
  52. package/types/domain/usecases/isWaitingForOwnerApproval.d.ts +2 -0
  53. package/types/domain/usecases/isWaitingForOwnerApproval.d.ts.map +1 -0
@@ -329,6 +329,88 @@ describe('ConsoleCommentComposer', () => {
329
329
  });
330
330
  });
331
331
 
332
+ it('renders only the Comment button when onSubmitAndMoveToAwaitingWorkspace is not provided', () => {
333
+ const { queryByText, getByText } = render(
334
+ <ConsoleCommentComposer initiallyOpen onSubmit={stubSubmit} />,
335
+ );
336
+ expect(getByText('Comment')).toBeInTheDocument();
337
+ expect(queryByText('Comment & Awaiting Workspace')).toBeNull();
338
+ });
339
+
340
+ it('renders both Comment and Comment & Awaiting Workspace buttons when onSubmitAndMoveToAwaitingWorkspace is provided', () => {
341
+ const { getByText } = render(
342
+ <ConsoleCommentComposer
343
+ initiallyOpen
344
+ onSubmit={stubSubmit}
345
+ onSubmitAndMoveToAwaitingWorkspace={stubSubmit}
346
+ />,
347
+ );
348
+ expect(getByText('Comment')).toBeInTheDocument();
349
+ expect(getByText('Comment & Awaiting Workspace')).toBeInTheDocument();
350
+ });
351
+
352
+ it('calls onSubmit and clears draft when the left Comment button is clicked', async () => {
353
+ const onSubmit = jest.fn(stubSubmit);
354
+ const onSubmitAndMoveToAwaitingWorkspace = jest.fn(stubSubmit);
355
+ const { getByPlaceholderText, getByText } = render(
356
+ <ConsoleCommentComposer
357
+ initiallyOpen
358
+ onSubmit={onSubmit}
359
+ onSubmitAndMoveToAwaitingWorkspace={onSubmitAndMoveToAwaitingWorkspace}
360
+ />,
361
+ );
362
+ fireEvent.change(getByPlaceholderText('Leave a comment…'), {
363
+ target: { value: 'Left button comment.' },
364
+ });
365
+ fireEvent.click(getByText('Comment'));
366
+ await waitFor(() => {
367
+ expect(getByPlaceholderText('Leave a comment…')).toHaveValue('');
368
+ });
369
+ expect(onSubmit).toHaveBeenCalledWith('Left button comment.');
370
+ expect(onSubmitAndMoveToAwaitingWorkspace).not.toHaveBeenCalled();
371
+ });
372
+
373
+ it('calls onSubmitAndMoveToAwaitingWorkspace and clears draft when the right button is clicked', async () => {
374
+ const onSubmit = jest.fn(stubSubmit);
375
+ const onSubmitAndMoveToAwaitingWorkspace = jest.fn(stubSubmit);
376
+ const { getByPlaceholderText, getByText } = render(
377
+ <ConsoleCommentComposer
378
+ initiallyOpen
379
+ onSubmit={onSubmit}
380
+ onSubmitAndMoveToAwaitingWorkspace={onSubmitAndMoveToAwaitingWorkspace}
381
+ />,
382
+ );
383
+ fireEvent.change(getByPlaceholderText('Leave a comment…'), {
384
+ target: { value: 'Right button comment.' },
385
+ });
386
+ fireEvent.click(getByText('Comment & Awaiting Workspace'));
387
+ await waitFor(() => {
388
+ expect(getByPlaceholderText('Leave a comment…')).toHaveValue('');
389
+ });
390
+ expect(onSubmitAndMoveToAwaitingWorkspace).toHaveBeenCalledWith(
391
+ 'Right button comment.',
392
+ );
393
+ expect(onSubmit).not.toHaveBeenCalled();
394
+ });
395
+
396
+ it('shows an error message when the right button click results in an error', async () => {
397
+ const { getByPlaceholderText, getByText, findByRole } = render(
398
+ <ConsoleCommentComposer
399
+ initiallyOpen
400
+ onSubmit={stubSubmit}
401
+ onSubmitAndMoveToAwaitingWorkspace={async () => {
402
+ throw new Error('move failed');
403
+ }}
404
+ />,
405
+ );
406
+ fireEvent.change(getByPlaceholderText('Leave a comment…'), {
407
+ target: { value: 'Will fail to move.' },
408
+ });
409
+ fireEvent.click(getByText('Comment & Awaiting Workspace'));
410
+ const alert = await findByRole('alert');
411
+ expect(alert.textContent).toContain('move failed');
412
+ });
413
+
332
414
  it('shows the upload failure reason when the upload rejects', async () => {
333
415
  const { getByLabelText, findByRole } = render(
334
416
  <ConsoleCommentComposer
@@ -6,6 +6,9 @@ export type ConsoleCommentComposerProps = {
6
6
  initialDraft?: string;
7
7
  onSubmit: (body: string) => Promise<ConsoleComment>;
8
8
  onDraftChange?: (draft: string) => void;
9
+ onSubmitAndMoveToAwaitingWorkspace?: (
10
+ body: string,
11
+ ) => Promise<ConsoleComment>;
9
12
  onUploadFile?: (file: File) => Promise<string>;
10
13
  };
11
14
 
@@ -78,6 +81,7 @@ export const ConsoleCommentComposer = ({
78
81
  initialDraft,
79
82
  onSubmit,
80
83
  onDraftChange,
84
+ onSubmitAndMoveToAwaitingWorkspace,
81
85
  onUploadFile,
82
86
  }: ConsoleCommentComposerProps) => {
83
87
  const [open, setOpen] = useState<boolean>(initiallyOpen);
@@ -91,14 +95,18 @@ export const ConsoleCommentComposer = ({
91
95
  });
92
96
  const fileInputRef = useRef<HTMLInputElement | null>(null);
93
97
 
94
- const submit = async (): Promise<void> => {
98
+ const submit = async (withMove: boolean): Promise<void> => {
95
99
  const body = draft.trim();
96
100
  if (body.length === 0 || status.kind === 'posting') {
97
101
  return;
98
102
  }
99
103
  setStatus({ kind: 'posting' });
100
104
  try {
101
- await onSubmit(body);
105
+ if (withMove && onSubmitAndMoveToAwaitingWorkspace !== undefined) {
106
+ await onSubmitAndMoveToAwaitingWorkspace(body);
107
+ } else {
108
+ await onSubmit(body);
109
+ }
102
110
  setDraft('');
103
111
  setStatus({ kind: 'idle' });
104
112
  } catch (error) {
@@ -231,11 +239,23 @@ export const ConsoleCommentComposer = ({
231
239
  className="console-composer-submit"
232
240
  disabled={status.kind === 'posting'}
233
241
  onClick={() => {
234
- void submit();
242
+ void submit(false);
235
243
  }}
236
244
  >
237
245
  Comment
238
246
  </button>
247
+ {onSubmitAndMoveToAwaitingWorkspace !== undefined && (
248
+ <button
249
+ type="button"
250
+ className="console-composer-submit"
251
+ disabled={status.kind === 'posting'}
252
+ onClick={() => {
253
+ void submit(true);
254
+ }}
255
+ >
256
+ Comment & Awaiting Workspace
257
+ </button>
258
+ )}
239
259
  </div>
240
260
  </div>
241
261
  )}
@@ -41,15 +41,16 @@ export type ConsoleOperationHandlers = {
41
41
  onOkAndAwaitingWorkspace: (option: ConsoleFieldOption) => void;
42
42
  };
43
43
 
44
+ export const IN_TMUX_BY_HUMAN_NAME = 'In Tmux by human';
45
+ export const AWAITING_WORKSPACE_NAME = 'Awaiting Workspace';
46
+
44
47
  export const STATUS_BUTTON_NAMES: string[] = [
45
48
  'In Tmux by agent',
46
49
  'In Tmux by human',
47
50
  'Todo by human',
48
51
  'Todo by agent',
49
- 'Awaiting Workspace',
52
+ AWAITING_WORKSPACE_NAME,
50
53
  ];
51
54
 
52
- export const IN_TMUX_BY_HUMAN_NAME = 'In Tmux by human';
53
-
54
55
  export const isManualTriageTab = (tab: ConsoleTabName): boolean =>
55
56
  tab === 'todo-by-human' || tab === 'todo-by-agent';
@@ -2,6 +2,7 @@ import { fireEvent, render, waitFor } from '@testing-library/react';
2
2
  import type { ConsoleCaches } from '../hooks/useConsoleCaches';
3
3
  import type { ConsoleOperationsApi } from '../hooks/useConsoleOperations';
4
4
  import { ResourceCache } from '../lib/resourceCache';
5
+ import { AWAITING_WORKSPACE_NAME } from '../logic/operations';
5
6
  import type {
6
7
  ConsoleChangedFile,
7
8
  ConsoleRelatedPullRequest,
@@ -405,6 +406,84 @@ describe('ConsoleItemDetailContainer', () => {
405
406
  ]);
406
407
  });
407
408
 
409
+ it('passes onSubmitAndMoveToAwaitingWorkspace to the composer when statusOptions includes Awaiting Workspace', () => {
410
+ const { getByText } = render(
411
+ <ConsoleItemDetailContainer
412
+ tab="todo-by-human"
413
+ item={issueItem}
414
+ caches={buildCaches()}
415
+ operations={buildOperations()}
416
+ statusOptions={consoleStatusOptionsFixture}
417
+ storyOptions={consoleStoryOptionsFixture}
418
+ storyColors={consoleStoryColorsFixture}
419
+ storyName="TDPM Console port"
420
+ overlayStatus={null}
421
+ now={Date.parse('2026-06-19T12:00:00.000Z')}
422
+ onQueueAction={jest.fn()}
423
+ />,
424
+ );
425
+ expect(getByText('Comment & Awaiting Workspace')).toBeInTheDocument();
426
+ });
427
+
428
+ it('does not pass onSubmitAndMoveToAwaitingWorkspace to the composer when statusOptions does not include Awaiting Workspace', () => {
429
+ const statusOptionsWithoutAwaitingWorkspace =
430
+ consoleStatusOptionsFixture.filter(
431
+ (o) => o.name !== AWAITING_WORKSPACE_NAME,
432
+ );
433
+ const { queryByText } = render(
434
+ <ConsoleItemDetailContainer
435
+ tab="todo-by-human"
436
+ item={issueItem}
437
+ caches={buildCaches()}
438
+ operations={buildOperations()}
439
+ statusOptions={statusOptionsWithoutAwaitingWorkspace}
440
+ storyOptions={consoleStoryOptionsFixture}
441
+ storyColors={consoleStoryColorsFixture}
442
+ storyName="TDPM Console port"
443
+ overlayStatus={null}
444
+ now={Date.parse('2026-06-19T12:00:00.000Z')}
445
+ onQueueAction={jest.fn()}
446
+ />,
447
+ );
448
+ expect(queryByText('Comment & Awaiting Workspace')).toBeNull();
449
+ });
450
+
451
+ it('clicking Comment & Awaiting Workspace calls addComment and queues a set_status action for the Awaiting Workspace option', async () => {
452
+ const operations = buildOperations();
453
+ const onQueueAction = jest.fn();
454
+ const { getByPlaceholderText, getByText } = render(
455
+ <ConsoleItemDetailContainer
456
+ tab="todo-by-human"
457
+ item={issueItem}
458
+ caches={buildCaches()}
459
+ operations={operations}
460
+ statusOptions={consoleStatusOptionsFixture}
461
+ storyOptions={consoleStoryOptionsFixture}
462
+ storyColors={consoleStoryColorsFixture}
463
+ storyName="TDPM Console port"
464
+ overlayStatus={null}
465
+ now={Date.parse('2026-06-19T12:00:00.000Z')}
466
+ onQueueAction={onQueueAction}
467
+ />,
468
+ );
469
+ fireEvent.change(getByPlaceholderText('Leave a comment…'), {
470
+ target: { value: 'test comment body' },
471
+ });
472
+ fireEvent.click(getByText('Comment & Awaiting Workspace'));
473
+ await waitFor(() => {
474
+ expect(operations.addComment).toHaveBeenCalledWith(
475
+ issueItem,
476
+ 'test comment body',
477
+ );
478
+ });
479
+ expect(onQueueAction).toHaveBeenCalledWith(
480
+ expect.objectContaining({
481
+ kind: { type: 'set_status', optionName: AWAITING_WORKSPACE_NAME },
482
+ item: issueItem,
483
+ }),
484
+ );
485
+ });
486
+
408
487
  it('collects an inline comment on an issue related pull request diff, enables Reject, and submits it as the request-changes review for that pull request url', async () => {
409
488
  const operations = buildOperations();
410
489
  const onQueueAction = jest.fn();
@@ -9,9 +9,10 @@ import type { ConsoleOperationsApi } from '../hooks/useConsoleOperations';
9
9
  import { buildImageProxyUrl } from '../lib/imageProxy';
10
10
  import type { ConsoleActionKind } from '../logic/actionToast';
11
11
  import { resolveStoryColorEnum } from '../logic/grouping';
12
- import type {
13
- ConsoleOperationHandlers,
14
- ConsolePendingReviewComment,
12
+ import {
13
+ AWAITING_WORKSPACE_NAME,
14
+ type ConsoleOperationHandlers,
15
+ type ConsolePendingReviewComment,
15
16
  } from '../logic/operations';
16
17
  import { mergePostedComments } from '../logic/postedComments';
17
18
  import type {
@@ -163,6 +164,18 @@ export const ConsoleItemDetailContainer = ({
163
164
  },
164
165
  };
165
166
 
167
+ const awaitingWorkspaceOption =
168
+ statusOptions.find((o) => o.name === AWAITING_WORKSPACE_NAME) ?? null;
169
+
170
+ const addCommentAndMoveToAwaitingWorkspace =
171
+ awaitingWorkspaceOption !== null
172
+ ? async (body: string): Promise<ConsoleComment> => {
173
+ const comment = await addComment(body);
174
+ handlers.onSetStatus(awaitingWorkspaceOption);
175
+ return comment;
176
+ }
177
+ : undefined;
178
+
166
179
  const resolvedStoryName =
167
180
  storyName ?? (item.story.trim() !== '' ? item.story : null);
168
181
  const storyColorEnum: ConsoleColor | null =
@@ -205,6 +218,9 @@ export const ConsoleItemDetailContainer = ({
205
218
  initialDraft={initialCommentDraft}
206
219
  onSubmit={addComment}
207
220
  onDraftChange={onCommentDraftChange}
221
+ onSubmitAndMoveToAwaitingWorkspace={
222
+ addCommentAndMoveToAwaitingWorkspace
223
+ }
208
224
  onUploadFile={(file) => operations.uploadAttachment(item, file)}
209
225
  />
210
226
  }