github-issue-tower-defence-management 1.116.8 → 1.117.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 (60) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/bin/adapter/entry-points/console/consoleReadApi.js +13 -1
  4. package/bin/adapter/entry-points/console/consoleReadApi.js.map +1 -1
  5. package/bin/adapter/entry-points/console/ui-dist/assets/index-CJJsRF8i.js +101 -0
  6. package/bin/adapter/entry-points/console/ui-dist/assets/{index-D1V2fi29.css → index-DGVKvwux.css} +1 -1
  7. package/bin/adapter/entry-points/console/ui-dist/index.html +2 -2
  8. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +1 -0
  9. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  10. package/bin/domain/usecases/NotifySilentLiveSessionsUseCase.js +10 -2
  11. package/bin/domain/usecases/NotifySilentLiveSessionsUseCase.js.map +1 -1
  12. package/package.json +1 -1
  13. package/src/adapter/entry-points/console/consoleReadApi.test.ts +4 -0
  14. package/src/adapter/entry-points/console/consoleReadApi.ts +18 -0
  15. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +2 -0
  16. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.stories.tsx +2 -0
  17. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.test.tsx +3 -1
  18. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +1 -1
  19. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.test.tsx +1 -0
  20. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.tsx +1 -1
  21. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.stories.tsx +15 -5
  22. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.test.tsx +38 -8
  23. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.tsx +35 -12
  24. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.test.ts +117 -0
  25. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.ts +83 -0
  26. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +2 -0
  27. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.ts +1 -0
  28. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +67 -0
  29. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +26 -6
  30. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +21 -0
  31. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +14 -0
  32. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.test.ts +48 -0
  33. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +24 -0
  34. package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +4 -0
  35. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +1 -0
  36. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +9 -1
  37. package/src/adapter/entry-points/console/ui/src/features/console/testing/fixtures.ts +1 -0
  38. package/src/adapter/entry-points/console/ui/src/index.css +10 -1
  39. package/src/adapter/entry-points/console/ui-dist/assets/index-CJJsRF8i.js +101 -0
  40. package/src/adapter/entry-points/console/ui-dist/assets/{index-D1V2fi29.css → index-DGVKvwux.css} +1 -1
  41. package/src/adapter/entry-points/console/ui-dist/index.html +2 -2
  42. package/src/adapter/entry-points/console/webServer.test.ts +2 -0
  43. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +30 -2
  44. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +1 -0
  45. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +1 -0
  46. package/src/domain/usecases/IssueRejectionEvaluator.test.ts +1 -0
  47. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +194 -33
  48. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.ts +19 -1
  49. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +1 -0
  50. package/src/domain/usecases/StartPreparationUseCase.test.ts +9 -0
  51. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +1 -0
  52. package/types/adapter/entry-points/console/consoleReadApi.d.ts +4 -0
  53. package/types/adapter/entry-points/console/consoleReadApi.d.ts.map +1 -1
  54. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  55. package/types/domain/usecases/NotifySilentLiveSessionsUseCase.d.ts +1 -0
  56. package/types/domain/usecases/NotifySilentLiveSessionsUseCase.d.ts.map +1 -1
  57. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +1 -0
  58. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
  59. package/bin/adapter/entry-points/console/ui-dist/assets/index-Bm0wt3Mn.js +0 -101
  60. package/src/adapter/entry-points/console/ui-dist/assets/index-Bm0wt3Mn.js +0 -101
@@ -3,6 +3,7 @@ import type {
3
3
  ConsoleComment,
4
4
  ConsoleCommit,
5
5
  ConsoleIssueState,
6
+ ConsoleMergeableStatus,
6
7
  ConsolePullRequestStatus,
7
8
  ConsoleRelatedPullRequest,
8
9
  } from '../logic/types';
@@ -136,6 +137,16 @@ const parseStringArray = (value: unknown): string[] =>
136
137
  ? value.filter((name): name is string => typeof name === 'string')
137
138
  : [];
138
139
 
140
+ const parseMergeableStatus = (value: unknown): ConsoleMergeableStatus => {
141
+ if (value === 'MERGEABLE') {
142
+ return 'MERGEABLE';
143
+ }
144
+ if (value === 'CONFLICTING') {
145
+ return 'CONFLICTING';
146
+ }
147
+ return 'UNKNOWN';
148
+ };
149
+
139
150
  const parseRelatedPrs = (payload: unknown): ConsoleRelatedPullRequest[] => {
140
151
  if (!isRecord(payload) || !Array.isArray(payload.relatedPullRequests)) {
141
152
  return [];
@@ -146,6 +157,7 @@ const parseRelatedPrs = (payload: unknown): ConsoleRelatedPullRequest[] => {
146
157
  createdAt: getString(pr.createdAt),
147
158
  isDraft: getBoolean(pr.isDraft),
148
159
  isConflicted: getBoolean(pr.isConflicted),
160
+ mergeableStatus: parseMergeableStatus(pr.mergeableStatus),
149
161
  isPassedAllCiJob: getBoolean(pr.isPassedAllCiJob),
150
162
  isCiStateSuccess: getBoolean(pr.isCiStateSuccess),
151
163
  isResolvedAllReviewComments: getBoolean(pr.isResolvedAllReviewComments),
@@ -160,6 +172,7 @@ const parsePullRequestStatus = (payload: unknown): ConsolePullRequestStatus => {
160
172
  return {
161
173
  found: false,
162
174
  isConflicted: false,
175
+ mergeableStatus: 'UNKNOWN',
163
176
  isPassedAllCiJob: false,
164
177
  isCiStateSuccess: false,
165
178
  isBranchOutOfDate: false,
@@ -170,6 +183,7 @@ const parsePullRequestStatus = (payload: unknown): ConsolePullRequestStatus => {
170
183
  return {
171
184
  found: true,
172
185
  isConflicted: getBoolean(status.isConflicted),
186
+ mergeableStatus: parseMergeableStatus(status.mergeableStatus),
173
187
  isPassedAllCiJob: getBoolean(status.isPassedAllCiJob),
174
188
  isCiStateSuccess: getBoolean(status.isCiStateSuccess),
175
189
  isBranchOutOfDate: getBoolean(status.isBranchOutOfDate),
@@ -37,6 +37,54 @@ describe('ResourceCache', () => {
37
37
  expect(await cache.load('k', 'u')).toBe('ok');
38
38
  expect(fetcher).toHaveBeenCalledTimes(2);
39
39
  });
40
+
41
+ it('prefetches into the cache and serves the next load from it', async () => {
42
+ const fetcher = jest.fn(async (url: string) => `value:${url}`);
43
+ const cache = new ResourceCache(fetcher);
44
+ cache.prefetch('k', 'u');
45
+ await Promise.resolve();
46
+ await Promise.resolve();
47
+ expect(cache.peek('k')).toBe('value:u');
48
+ expect(await cache.load('k', 'u')).toBe('value:u');
49
+ expect(fetcher).toHaveBeenCalledTimes(1);
50
+ });
51
+
52
+ it('does not prefetch a key that is already cached', async () => {
53
+ const fetcher = jest.fn(async (url: string) => `value:${url}`);
54
+ const cache = new ResourceCache(fetcher);
55
+ await cache.load('k', 'u');
56
+ cache.prefetch('k', 'u');
57
+ expect(fetcher).toHaveBeenCalledTimes(1);
58
+ });
59
+
60
+ it('does not start a second prefetch while one is in flight', async () => {
61
+ const fetcher = jest.fn(() => new Promise<string>(() => {}));
62
+ const cache = new ResourceCache(fetcher);
63
+ cache.prefetch('k', 'u');
64
+ cache.prefetch('k', 'u');
65
+ expect(fetcher).toHaveBeenCalledTimes(1);
66
+ });
67
+
68
+ it('swallows prefetch errors and leaves the key uncached', async () => {
69
+ const fetcher = jest
70
+ .fn<Promise<string>, [string]>()
71
+ .mockRejectedValue(new Error('boom'));
72
+ const cache = new ResourceCache(fetcher);
73
+ cache.prefetch('k', 'u');
74
+ await Promise.resolve();
75
+ await Promise.resolve();
76
+ expect(cache.peek('k')).toBeUndefined();
77
+ });
78
+
79
+ it('invalidate removes a cached value so the next load refetches', async () => {
80
+ const fetcher = jest.fn(async (url: string) => `value:${url}`);
81
+ const cache = new ResourceCache(fetcher);
82
+ await cache.load('k', 'u');
83
+ cache.invalidate('k');
84
+ expect(cache.peek('k')).toBeUndefined();
85
+ await cache.load('k', 'u');
86
+ expect(fetcher).toHaveBeenCalledTimes(2);
87
+ });
40
88
  });
41
89
 
42
90
  describe('runWithConcurrency', () => {
@@ -3,6 +3,7 @@ export type ResourceFetcher<T> = (url: string) => Promise<T>;
3
3
  export class ResourceCache<T> {
4
4
  private readonly cache = new Map<string, T>();
5
5
  private readonly inFlight = new Map<string, Promise<T>>();
6
+ private readonly prefetchInFlight = new Set<string>();
6
7
 
7
8
  constructor(private readonly fetcher: ResourceFetcher<T>) {}
8
9
 
@@ -30,6 +31,29 @@ export class ResourceCache<T> {
30
31
  this.inFlight.set(key, promise);
31
32
  return promise;
32
33
  };
34
+
35
+ prefetch = (key: string, url: string): void => {
36
+ if (
37
+ this.cache.has(key) ||
38
+ this.inFlight.has(key) ||
39
+ this.prefetchInFlight.has(key)
40
+ ) {
41
+ return;
42
+ }
43
+ this.prefetchInFlight.add(key);
44
+ this.fetcher(url)
45
+ .then((value) => {
46
+ this.cache.set(key, value);
47
+ })
48
+ .catch(() => {})
49
+ .finally(() => {
50
+ this.prefetchInFlight.delete(key);
51
+ });
52
+ };
53
+
54
+ invalidate = (key: string): void => {
55
+ this.cache.delete(key);
56
+ };
33
57
  }
34
58
 
35
59
  export const runWithConcurrency = async (
@@ -64,9 +64,12 @@ export type ConsoleIssueState = {
64
64
  title: string;
65
65
  };
66
66
 
67
+ export type ConsoleMergeableStatus = 'MERGEABLE' | 'CONFLICTING' | 'UNKNOWN';
68
+
67
69
  export type ConsolePullRequestStatus = {
68
70
  found: boolean;
69
71
  isConflicted: boolean;
72
+ mergeableStatus: ConsoleMergeableStatus;
70
73
  isPassedAllCiJob: boolean;
71
74
  isCiStateSuccess: boolean;
72
75
  isBranchOutOfDate: boolean;
@@ -100,6 +103,7 @@ export type ConsoleRelatedPullRequest = {
100
103
  createdAt: string;
101
104
  isDraft: boolean;
102
105
  isConflicted: boolean;
106
+ mergeableStatus: ConsoleMergeableStatus;
103
107
  isPassedAllCiJob: boolean;
104
108
  isCiStateSuccess: boolean;
105
109
  isResolvedAllReviewComments: boolean;
@@ -44,6 +44,7 @@ const buildCaches = (overrides: CachesOverrides = {}): ConsoleCaches => {
44
44
  fetchPullRequestStatus: async () => ({
45
45
  found: true,
46
46
  isConflicted: false,
47
+ mergeableStatus: 'MERGEABLE' as const,
47
48
  isPassedAllCiJob: true,
48
49
  isCiStateSuccess: true,
49
50
  isBranchOutOfDate: false,
@@ -7,6 +7,7 @@ import {
7
7
  } from '../components/operations/ConsoleUndoToast';
8
8
  import { useConsoleActionQueue } from '../hooks/useConsoleActionQueue';
9
9
  import { useConsoleCaches } from '../hooks/useConsoleCaches';
10
+ import { useConsoleDetailPrefetch } from '../hooks/useConsoleDetailPrefetch';
10
11
  import { useConsoleNavigation } from '../hooks/useConsoleNavigation';
11
12
  import { useConsoleOperations } from '../hooks/useConsoleOperations';
12
13
  import { useConsoleOverlay } from '../hooks/useConsoleOverlay';
@@ -78,7 +79,12 @@ export const ConsolePage = () => {
78
79
  navigation;
79
80
 
80
81
  const caches = useConsoleCaches();
81
- const operations = useConsoleOperations(pjcode, activeTab, overlayState);
82
+ const operations = useConsoleOperations(
83
+ pjcode,
84
+ activeTab,
85
+ overlayState,
86
+ caches,
87
+ );
82
88
  const actionQueue = useConsoleActionQueue();
83
89
  const now = Date.now();
84
90
 
@@ -120,6 +126,8 @@ export const ConsolePage = () => {
120
126
  );
121
127
  }, [selectedItemKey, activeSnapshot]);
122
128
 
129
+ useConsoleDetailPrefetch(caches, selectedItem, pendingItems);
130
+
123
131
  useEffect(() => {
124
132
  if (selectedItemKey === null) {
125
133
  return;
@@ -343,6 +343,7 @@ export const consoleRelatedPullRequestsFixture: ConsoleRelatedPullRequest[] = [
343
343
  createdAt: '2026-06-16T22:30:00.000Z',
344
344
  isDraft: false,
345
345
  isConflicted: false,
346
+ mergeableStatus: 'MERGEABLE',
346
347
  isPassedAllCiJob: true,
347
348
  isCiStateSuccess: true,
348
349
  isResolvedAllReviewComments: true,
@@ -351,6 +351,15 @@ body {
351
351
  opacity: 0.85;
352
352
  }
353
353
 
354
+ .console-detail-mergeable-chip {
355
+ font-weight: 600;
356
+ }
357
+
358
+ .console-detail-mergeable-chip-unknown {
359
+ opacity: 0.7;
360
+ font-weight: 400;
361
+ }
362
+
354
363
  .console-detail-labels {
355
364
  display: flex;
356
365
  flex-wrap: wrap;
@@ -655,7 +664,7 @@ body {
655
664
  }
656
665
 
657
666
  .console-op-button {
658
- padding: 7px 16px;
667
+ padding: 9px 16px;
659
668
  border: 1px solid #30363d;
660
669
  border-radius: 6px;
661
670
  background: #21262d;