github-issue-tower-defence-management 1.151.1 → 1.151.2
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 +8 -0
- package/bin/adapter/entry-points/console/consoleDoneStore.js +19 -1
- package/bin/adapter/entry-points/console/consoleDoneStore.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleOperationApi.js +17 -5
- package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-CY96HROQ.js +83 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/consoleDoneStore.test.ts +21 -0
- package/src/adapter/entry-points/console/consoleDoneStore.ts +25 -0
- package/src/adapter/entry-points/console/consoleOperationApi.test.ts +58 -7
- package/src/adapter/entry-points/console/consoleOperationApi.ts +43 -6
- package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.test.ts +79 -1
- package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.ts +20 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +42 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +6 -2
- package/src/adapter/entry-points/console/ui-dist/assets/index-CY96HROQ.js +83 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/types/adapter/entry-points/console/consoleDoneStore.d.ts +3 -0
- package/types/adapter/entry-points/console/consoleDoneStore.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-UQt5L756.js +0 -83
- package/src/adapter/entry-points/console/ui-dist/assets/index-UQt5L756.js +0 -83
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>TDPM Console</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-CY96HROQ.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-Dihcph6j.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -3,6 +3,8 @@ import * as os from 'os';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import {
|
|
5
5
|
CONSOLE_DONE_FILE_NAME,
|
|
6
|
+
CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES,
|
|
7
|
+
CONSOLE_DONE_STORY_SELECTED_TAB_NAMES,
|
|
6
8
|
CONSOLE_DONE_TAB_NAMES,
|
|
7
9
|
doneFilePathForTab,
|
|
8
10
|
readDoneProjectItemIds,
|
|
@@ -145,4 +147,23 @@ describe('consoleDoneStore', () => {
|
|
|
145
147
|
}
|
|
146
148
|
});
|
|
147
149
|
});
|
|
150
|
+
|
|
151
|
+
describe('selector partitions of the console done tabs', () => {
|
|
152
|
+
it('covers every console done tab exactly once across the status and story partitions', () => {
|
|
153
|
+
expect(
|
|
154
|
+
[
|
|
155
|
+
...CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES,
|
|
156
|
+
...CONSOLE_DONE_STORY_SELECTED_TAB_NAMES,
|
|
157
|
+
].sort(),
|
|
158
|
+
).toEqual([...CONSOLE_DONE_TAB_NAMES].sort());
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('places no console done tab in both the status and the story partition', () => {
|
|
162
|
+
expect(
|
|
163
|
+
CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES.filter((tab) =>
|
|
164
|
+
CONSOLE_DONE_STORY_SELECTED_TAB_NAMES.includes(tab),
|
|
165
|
+
),
|
|
166
|
+
).toEqual([]);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
148
169
|
});
|
|
@@ -89,6 +89,31 @@ export const CONSOLE_DONE_TAB_NAMES: string[] = [
|
|
|
89
89
|
'in-tmux-by-human',
|
|
90
90
|
];
|
|
91
91
|
|
|
92
|
+
export const CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES: string[] = [
|
|
93
|
+
'prs',
|
|
94
|
+
'unread',
|
|
95
|
+
'failed-preparation',
|
|
96
|
+
'todo-by-human',
|
|
97
|
+
'todo-by-agent',
|
|
98
|
+
'in-tmux-by-human',
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
export const CONSOLE_DONE_STORY_SELECTED_TAB_NAMES: string[] = [
|
|
102
|
+
'workflow-blocker',
|
|
103
|
+
'triage',
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
export const recordDoneProjectItemIdForTabs = (
|
|
107
|
+
consoleDataOutputDir: string,
|
|
108
|
+
pjcode: string,
|
|
109
|
+
projectItemId: string,
|
|
110
|
+
tabNames: string[],
|
|
111
|
+
): void => {
|
|
112
|
+
for (const tab of tabNames) {
|
|
113
|
+
recordDoneProjectItemId(consoleDataOutputDir, pjcode, tab, projectItemId);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
92
117
|
export const recordDoneProjectItemIdAcrossTabs = (
|
|
93
118
|
consoleDataOutputDir: string,
|
|
94
119
|
pjcode: string,
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
CONSOLE_DONE_TAB_NAMES,
|
|
20
20
|
readDoneProjectItemIds,
|
|
21
|
+
CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES,
|
|
22
|
+
CONSOLE_DONE_STORY_SELECTED_TAB_NAMES,
|
|
21
23
|
} from './consoleDoneStore';
|
|
22
24
|
|
|
23
25
|
describe('consoleOperationApi', () => {
|
|
@@ -104,6 +106,23 @@ describe('consoleOperationApi', () => {
|
|
|
104
106
|
}
|
|
105
107
|
};
|
|
106
108
|
|
|
109
|
+
const expectRecordedOnlyIn = (
|
|
110
|
+
projectItemId: string,
|
|
111
|
+
recordedTabs: string[],
|
|
112
|
+
): void => {
|
|
113
|
+
for (const tab of CONSOLE_DONE_TAB_NAMES) {
|
|
114
|
+
if (recordedTabs.includes(tab)) {
|
|
115
|
+
expect(readDoneProjectItemIds(baseDir, 'acme', tab)).toContain(
|
|
116
|
+
projectItemId,
|
|
117
|
+
);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
expect(readDoneProjectItemIds(baseDir, 'acme', tab)).not.toContain(
|
|
121
|
+
projectItemId,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
107
126
|
describe('handleReview', () => {
|
|
108
127
|
it('approves and sets Awaiting workspace then records done', async () => {
|
|
109
128
|
const response = await handleReview(context, {
|
|
@@ -125,7 +144,7 @@ describe('consoleOperationApi', () => {
|
|
|
125
144
|
'status_aw',
|
|
126
145
|
);
|
|
127
146
|
expect(issueRepository.get).not.toHaveBeenCalled();
|
|
128
|
-
|
|
147
|
+
expectRecordedOnlyIn('PVTI_a', CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES);
|
|
129
148
|
});
|
|
130
149
|
|
|
131
150
|
it('resolves the issue repository from the url of the operated pull request', async () => {
|
|
@@ -173,7 +192,7 @@ describe('consoleOperationApi', () => {
|
|
|
173
192
|
side: 'RIGHT',
|
|
174
193
|
},
|
|
175
194
|
);
|
|
176
|
-
|
|
195
|
+
expectRecordedOnlyIn('PVTI_b', CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES);
|
|
177
196
|
});
|
|
178
197
|
|
|
179
198
|
it('requests changes without a line anchor when line and side are missing', async () => {
|
|
@@ -194,7 +213,7 @@ describe('consoleOperationApi', () => {
|
|
|
194
213
|
'please fix',
|
|
195
214
|
null,
|
|
196
215
|
);
|
|
197
|
-
|
|
216
|
+
expectRecordedOnlyIn('PVTI_b', CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES);
|
|
198
217
|
});
|
|
199
218
|
|
|
200
219
|
it('rejects request_changes without a comment body', async () => {
|
|
@@ -430,7 +449,37 @@ describe('consoleOperationApi', () => {
|
|
|
430
449
|
'status_todo',
|
|
431
450
|
);
|
|
432
451
|
expect(issueRepository.get).not.toHaveBeenCalled();
|
|
433
|
-
|
|
452
|
+
expectRecordedOnlyIn('PVTI_d', CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('keeps a status change out of the story-selected tabs', async () => {
|
|
456
|
+
const response = await handleTriage(context, {
|
|
457
|
+
pjcode: 'acme',
|
|
458
|
+
action: 'set_status',
|
|
459
|
+
issueUrl: 'https://github.com/o/r/issues/1',
|
|
460
|
+
projectItemId: 'PVTI_status_only',
|
|
461
|
+
statusName: 'Todo',
|
|
462
|
+
});
|
|
463
|
+
expect(response.statusCode).toBe(200);
|
|
464
|
+
expectRecordedOnlyIn(
|
|
465
|
+
'PVTI_status_only',
|
|
466
|
+
CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES,
|
|
467
|
+
);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('keeps a story change out of the status-selected tabs', async () => {
|
|
471
|
+
const response = await handleTriage(context, {
|
|
472
|
+
pjcode: 'acme',
|
|
473
|
+
action: 'set_story',
|
|
474
|
+
issueUrl: 'https://github.com/o/r/issues/1',
|
|
475
|
+
projectItemId: 'PVTI_story_only',
|
|
476
|
+
storyOptionId: 'story_opt_1',
|
|
477
|
+
});
|
|
478
|
+
expect(response.statusCode).toBe(200);
|
|
479
|
+
expectRecordedOnlyIn(
|
|
480
|
+
'PVTI_story_only',
|
|
481
|
+
CONSOLE_DONE_STORY_SELECTED_TAB_NAMES,
|
|
482
|
+
);
|
|
434
483
|
});
|
|
435
484
|
|
|
436
485
|
it('rejects an unknown status name', async () => {
|
|
@@ -460,7 +509,7 @@ describe('consoleOperationApi', () => {
|
|
|
460
509
|
'story_opt_1',
|
|
461
510
|
);
|
|
462
511
|
expect(issueRepository.get).not.toHaveBeenCalled();
|
|
463
|
-
|
|
512
|
+
expectRecordedOnlyIn('PVTI_e', CONSOLE_DONE_STORY_SELECTED_TAB_NAMES);
|
|
464
513
|
});
|
|
465
514
|
|
|
466
515
|
it('snoozes for one day via updateNextActionDate', async () => {
|
|
@@ -794,7 +843,7 @@ describe('consoleOperationApi', () => {
|
|
|
794
843
|
'status_intmux',
|
|
795
844
|
);
|
|
796
845
|
expect(issueRepository.get).not.toHaveBeenCalled();
|
|
797
|
-
|
|
846
|
+
expectRecordedOnlyIn('PVTI_i', CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES);
|
|
798
847
|
});
|
|
799
848
|
|
|
800
849
|
it('rejects an unknown intmux action', async () => {
|
|
@@ -907,10 +956,12 @@ describe('consoleOperationApi', () => {
|
|
|
907
956
|
expect.objectContaining({ itemId: 'PVTI_x' }),
|
|
908
957
|
'status_todo',
|
|
909
958
|
);
|
|
910
|
-
for (const tab of
|
|
959
|
+
for (const tab of CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES) {
|
|
911
960
|
expect(readDoneProjectItemIds(baseDir, 'globex', tab)).toContain(
|
|
912
961
|
'PVTI_x',
|
|
913
962
|
);
|
|
963
|
+
}
|
|
964
|
+
for (const tab of CONSOLE_DONE_TAB_NAMES) {
|
|
914
965
|
expect(readDoneProjectItemIds(baseDir, 'acme', tab)).not.toContain(
|
|
915
966
|
'PVTI_x',
|
|
916
967
|
);
|
|
@@ -5,7 +5,12 @@ import {
|
|
|
5
5
|
import { IssueAttachmentRepository } from '../../../domain/usecases/adapter-interfaces/IssueAttachmentRepository';
|
|
6
6
|
import { Project } from '../../../domain/entities/Project';
|
|
7
7
|
import { Issue } from '../../../domain/entities/Issue';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
recordDoneProjectItemIdAcrossTabs,
|
|
10
|
+
recordDoneProjectItemIdForTabs,
|
|
11
|
+
CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES,
|
|
12
|
+
CONSOLE_DONE_STORY_SELECTED_TAB_NAMES,
|
|
13
|
+
} from './consoleDoneStore';
|
|
9
14
|
import { findConsoleItemUrl } from './consoleItemUrlLookup';
|
|
10
15
|
|
|
11
16
|
export const AWAITING_WORKSPACE_STATUS_NAME = 'awaiting workspace';
|
|
@@ -135,6 +140,38 @@ const recordDone = (
|
|
|
135
140
|
);
|
|
136
141
|
};
|
|
137
142
|
|
|
143
|
+
const recordDoneForStatusChange = (
|
|
144
|
+
context: ConsoleOperationContext,
|
|
145
|
+
pjcode: string,
|
|
146
|
+
projectItemId: string,
|
|
147
|
+
): void => {
|
|
148
|
+
if (context.consoleDataOutputDir === null) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
recordDoneProjectItemIdForTabs(
|
|
152
|
+
context.consoleDataOutputDir,
|
|
153
|
+
pjcode,
|
|
154
|
+
projectItemId,
|
|
155
|
+
CONSOLE_DONE_STATUS_SELECTED_TAB_NAMES,
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const recordDoneForStoryChange = (
|
|
160
|
+
context: ConsoleOperationContext,
|
|
161
|
+
pjcode: string,
|
|
162
|
+
projectItemId: string,
|
|
163
|
+
): void => {
|
|
164
|
+
if (context.consoleDataOutputDir === null) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
recordDoneProjectItemIdForTabs(
|
|
168
|
+
context.consoleDataOutputDir,
|
|
169
|
+
pjcode,
|
|
170
|
+
projectItemId,
|
|
171
|
+
CONSOLE_DONE_STORY_SELECTED_TAB_NAMES,
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
138
175
|
const resolveBinding = async (
|
|
139
176
|
context: ConsoleOperationContext,
|
|
140
177
|
body: Record<string, unknown>,
|
|
@@ -298,7 +335,7 @@ export const handleReview = async (
|
|
|
298
335
|
if (failure !== null) {
|
|
299
336
|
return failure;
|
|
300
337
|
}
|
|
301
|
-
|
|
338
|
+
recordDoneForStatusChange(context, pjcode, projectItemId);
|
|
302
339
|
return ok();
|
|
303
340
|
}
|
|
304
341
|
|
|
@@ -334,7 +371,7 @@ export const handleReview = async (
|
|
|
334
371
|
if (failure !== null) {
|
|
335
372
|
return failure;
|
|
336
373
|
}
|
|
337
|
-
|
|
374
|
+
recordDoneForStatusChange(context, pjcode, projectItemId);
|
|
338
375
|
return ok();
|
|
339
376
|
}
|
|
340
377
|
|
|
@@ -403,7 +440,7 @@ export const handleTriage = async (
|
|
|
403
440
|
if (failure !== null) {
|
|
404
441
|
return failure;
|
|
405
442
|
}
|
|
406
|
-
|
|
443
|
+
recordDoneForStatusChange(context, pjcode, projectItemId);
|
|
407
444
|
return ok();
|
|
408
445
|
}
|
|
409
446
|
|
|
@@ -422,7 +459,7 @@ export const handleTriage = async (
|
|
|
422
459
|
projectItemReference(issueUrl, projectItemId),
|
|
423
460
|
storyOptionId,
|
|
424
461
|
);
|
|
425
|
-
|
|
462
|
+
recordDoneForStoryChange(context, pjcode, projectItemId);
|
|
426
463
|
return ok();
|
|
427
464
|
}
|
|
428
465
|
|
|
@@ -590,6 +627,6 @@ export const handleIntmux = async (
|
|
|
590
627
|
if (failure !== null) {
|
|
591
628
|
return failure;
|
|
592
629
|
}
|
|
593
|
-
|
|
630
|
+
recordDoneForStatusChange(context, pjcode, projectItemId);
|
|
594
631
|
return ok();
|
|
595
632
|
};
|
|
@@ -4,10 +4,15 @@ import {
|
|
|
4
4
|
isOverlayEntryActed,
|
|
5
5
|
overlayEntriesActedSinceSnapshot,
|
|
6
6
|
overlayKeyForItem,
|
|
7
|
+
overlayStatusSinceSnapshot,
|
|
7
8
|
overlayStorageKey,
|
|
8
9
|
writeOverlayEntry,
|
|
9
10
|
} from './overlay';
|
|
10
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
ConsoleListItem,
|
|
13
|
+
ConsoleOverlay,
|
|
14
|
+
ConsoleOverlayStatus,
|
|
15
|
+
} from './types';
|
|
11
16
|
|
|
12
17
|
const item = (number: number): ConsoleListItem => ({
|
|
13
18
|
number,
|
|
@@ -230,3 +235,76 @@ describe('writeOverlayEntry', () => {
|
|
|
230
235
|
});
|
|
231
236
|
});
|
|
232
237
|
});
|
|
238
|
+
|
|
239
|
+
describe('overlayStatusSinceSnapshot', () => {
|
|
240
|
+
const snapshotGeneratedAt = '2026-08-13T21:52:01.000Z';
|
|
241
|
+
const snapshotEpochMs = Date.parse(snapshotGeneratedAt);
|
|
242
|
+
const inTmuxByHuman: ConsoleOverlayStatus = {
|
|
243
|
+
name: 'In Tmux by human',
|
|
244
|
+
color: 'RED',
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const overlayWithStatusAt = (epochMs: number): ConsoleOverlay => ({
|
|
248
|
+
[overlayKeyForItem(item(1))]: {
|
|
249
|
+
status: inTmuxByHuman,
|
|
250
|
+
ts: epochMs,
|
|
251
|
+
mode: 'todo-by-human',
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('shows a status the owner set after the snapshot was generated', () => {
|
|
256
|
+
expect(
|
|
257
|
+
overlayStatusSinceSnapshot(
|
|
258
|
+
overlayWithStatusAt(snapshotEpochMs + 1000),
|
|
259
|
+
item(1),
|
|
260
|
+
snapshotGeneratedAt,
|
|
261
|
+
),
|
|
262
|
+
).toEqual(inTmuxByHuman);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('drops a status the snapshot already superseded', () => {
|
|
266
|
+
expect(
|
|
267
|
+
overlayStatusSinceSnapshot(
|
|
268
|
+
overlayWithStatusAt(snapshotEpochMs - 1000),
|
|
269
|
+
item(1),
|
|
270
|
+
snapshotGeneratedAt,
|
|
271
|
+
),
|
|
272
|
+
).toBeNull();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('drops every status when the snapshot time is unusable', () => {
|
|
276
|
+
expect(
|
|
277
|
+
overlayStatusSinceSnapshot(
|
|
278
|
+
overlayWithStatusAt(snapshotEpochMs + 1000),
|
|
279
|
+
item(1),
|
|
280
|
+
null,
|
|
281
|
+
),
|
|
282
|
+
).toBeNull();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('shows a status set at the exact moment the snapshot was generated', () => {
|
|
286
|
+
expect(
|
|
287
|
+
overlayStatusSinceSnapshot(
|
|
288
|
+
overlayWithStatusAt(snapshotEpochMs),
|
|
289
|
+
item(1),
|
|
290
|
+
snapshotGeneratedAt,
|
|
291
|
+
),
|
|
292
|
+
).toEqual(inTmuxByHuman);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('drops every status when the snapshot time cannot be parsed', () => {
|
|
296
|
+
expect(
|
|
297
|
+
overlayStatusSinceSnapshot(
|
|
298
|
+
overlayWithStatusAt(snapshotEpochMs + 1000),
|
|
299
|
+
item(1),
|
|
300
|
+
'not a timestamp',
|
|
301
|
+
),
|
|
302
|
+
).toBeNull();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('returns null when the item has no overlay entry', () => {
|
|
306
|
+
expect(
|
|
307
|
+
overlayStatusSinceSnapshot({}, item(1), snapshotGeneratedAt),
|
|
308
|
+
).toBeNull();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
ConsoleListItem,
|
|
3
3
|
ConsoleOverlay,
|
|
4
4
|
ConsoleOverlayEntry,
|
|
5
|
+
ConsoleOverlayStatus,
|
|
5
6
|
ConsoleTabName,
|
|
6
7
|
} from './types';
|
|
7
8
|
|
|
@@ -47,6 +48,25 @@ export const overlayEntriesActedSinceSnapshot = (
|
|
|
47
48
|
return acted;
|
|
48
49
|
};
|
|
49
50
|
|
|
51
|
+
export const overlayStatusSinceSnapshot = (
|
|
52
|
+
overlay: ConsoleOverlay,
|
|
53
|
+
item: ConsoleListItem,
|
|
54
|
+
snapshotGeneratedAt: string | null,
|
|
55
|
+
): ConsoleOverlayStatus | null => {
|
|
56
|
+
if (snapshotGeneratedAt === null) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const snapshotGeneratedAtMs = Date.parse(snapshotGeneratedAt);
|
|
60
|
+
if (Number.isNaN(snapshotGeneratedAtMs)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const entry = overlay[overlayKeyForItem(item)];
|
|
64
|
+
if (entry === undefined || entry.ts < snapshotGeneratedAtMs) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return entry.status ?? null;
|
|
68
|
+
};
|
|
69
|
+
|
|
50
70
|
export const writeOverlayEntry = (
|
|
51
71
|
overlay: ConsoleOverlay,
|
|
52
72
|
key: string,
|
|
@@ -106,6 +106,48 @@ describe('ConsolePage', () => {
|
|
|
106
106
|
).toContain('TDPM Console port');
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
+
it('keeps a status the snapshot already superseded out of the detail header', async () => {
|
|
110
|
+
localStorage.setItem(
|
|
111
|
+
'pv_overlay_acme',
|
|
112
|
+
JSON.stringify({
|
|
113
|
+
PVTI_1: {
|
|
114
|
+
status: { name: 'In Tmux by human', color: 'RED' },
|
|
115
|
+
ts: Date.parse('2026-06-18T00:00:00.000Z'),
|
|
116
|
+
mode: 'prs',
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
const { getByText, findByText, container } = render(<ConsolePage />);
|
|
121
|
+
await waitFor(() => {
|
|
122
|
+
expect(getByText('Add serveConsole subcommand')).toBeInTheDocument();
|
|
123
|
+
});
|
|
124
|
+
fireEvent.click(getByText('Add serveConsole subcommand'));
|
|
125
|
+
expect(await findByText('Approve')).toBeInTheDocument();
|
|
126
|
+
expect(container.querySelector('.console-detail-status-chip')).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('shows a status the owner set after the snapshot was generated in the detail header', async () => {
|
|
130
|
+
localStorage.setItem(
|
|
131
|
+
'pv_overlay_acme',
|
|
132
|
+
JSON.stringify({
|
|
133
|
+
PVTI_1: {
|
|
134
|
+
status: { name: 'In Tmux by human', color: 'RED' },
|
|
135
|
+
ts: Date.parse('2026-06-19T00:10:00.000Z'),
|
|
136
|
+
mode: 'prs',
|
|
137
|
+
},
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
const { getByText, findByText, container } = render(<ConsolePage />);
|
|
141
|
+
await waitFor(() => {
|
|
142
|
+
expect(getByText('Add serveConsole subcommand')).toBeInTheDocument();
|
|
143
|
+
});
|
|
144
|
+
fireEvent.click(getByText('Add serveConsole subcommand'));
|
|
145
|
+
expect(await findByText('Approve')).toBeInTheDocument();
|
|
146
|
+
expect(
|
|
147
|
+
container.querySelector('.console-detail-status-chip')?.textContent,
|
|
148
|
+
).toBe('In Tmux by human');
|
|
149
|
+
});
|
|
150
|
+
|
|
109
151
|
it('opens the detail view when an item is selected', async () => {
|
|
110
152
|
const { getByText, findByText } = render(<ConsolePage />);
|
|
111
153
|
await waitFor(() => {
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
filterPendingItems,
|
|
31
31
|
overlayEntriesActedSinceSnapshot,
|
|
32
32
|
overlayKeyForItem,
|
|
33
|
+
overlayStatusSinceSnapshot,
|
|
33
34
|
} from '../logic/overlay';
|
|
34
35
|
import type { ConsoleSwipeDirection } from '../logic/swipe';
|
|
35
36
|
import { findNextNonEmptyTabToRight } from '../logic/tabAdvance';
|
|
@@ -164,8 +165,11 @@ export const ConsolePage = () => {
|
|
|
164
165
|
if (selectedItem === null) {
|
|
165
166
|
return null;
|
|
166
167
|
}
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
return overlayStatusSinceSnapshot(
|
|
169
|
+
overlayState.overlay,
|
|
170
|
+
selectedItem,
|
|
171
|
+
snapshots[activeTab]?.generatedAt ?? null,
|
|
172
|
+
);
|
|
169
173
|
})();
|
|
170
174
|
|
|
171
175
|
const storyNameForSelected =
|