@design.estate/dees-catalog 3.101.0 → 3.102.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.
- package/dist_bundle/bundle.js +274 -8
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.d.ts +30 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +321 -9
- package/dist_ts_web/elements/00group-harness/interfaces.d.ts +21 -0
- package/package.json +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +327 -7
- package/ts_web/elements/00group-harness/interfaces.ts +24 -0
package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts
CHANGED
|
@@ -10,7 +10,13 @@ import {
|
|
|
10
10
|
cssManager,
|
|
11
11
|
} from '@design.estate/dees-element';
|
|
12
12
|
import { themeDefaultStyles } from '../../00theme.js';
|
|
13
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
IHarnessGroupContextDetail,
|
|
15
|
+
IHarnessSessionContextDetail,
|
|
16
|
+
IHarnessSessionGroup,
|
|
17
|
+
IHarnessSessionMeta,
|
|
18
|
+
IHarnessSessionMoveDetail,
|
|
19
|
+
} from '../interfaces.js';
|
|
14
20
|
import { harnessFormatRelativeTime } from '../harness.helpers.js';
|
|
15
21
|
import '../../00group-utility/dees-icon/dees-icon.js';
|
|
16
22
|
|
|
@@ -20,11 +26,24 @@ declare global {
|
|
|
20
26
|
}
|
|
21
27
|
}
|
|
22
28
|
|
|
29
|
+
interface IDropSlot {
|
|
30
|
+
groupId: string | null;
|
|
31
|
+
index: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
/**
|
|
24
35
|
* Searchable conversation list, embeddable in a sidebar or a DeesModal.
|
|
25
36
|
* Emits `harness-session-select` on click, `harness-session-open` on
|
|
26
37
|
* double-click or Enter, and `harness-session-context` with viewport
|
|
27
38
|
* pointer coordinates on right-click (the browser menu is suppressed).
|
|
39
|
+
*
|
|
40
|
+
* With `enableGrouping`, sessions render under user-defined `groups` and can
|
|
41
|
+
* be dragged between groups and reordered. The component never mutates its
|
|
42
|
+
* inputs: drops emit `harness-session-move`, the "new group" button emits
|
|
43
|
+
* `harness-group-create-request`, and right-clicking a group header emits
|
|
44
|
+
* `harness-group-context` — the host persists changes and passes fresh
|
|
45
|
+
* `groups` back in. Sessions in no group render in the trailing ungrouped
|
|
46
|
+
* section, recency-sorted. While a search query is active the list is flat.
|
|
28
47
|
*/
|
|
29
48
|
@customElement('dees-harness-session-list')
|
|
30
49
|
export class DeesHarnessSessionList extends DeesElement {
|
|
@@ -43,9 +62,24 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
43
62
|
@property()
|
|
44
63
|
accessor emptyText: string = 'No conversations yet.';
|
|
45
64
|
|
|
65
|
+
@property({ attribute: false })
|
|
66
|
+
accessor groups: IHarnessSessionGroup[] = [];
|
|
67
|
+
|
|
68
|
+
@property({ type: Boolean })
|
|
69
|
+
accessor enableGrouping: boolean = false;
|
|
70
|
+
|
|
46
71
|
@state()
|
|
47
72
|
accessor search: string = '';
|
|
48
73
|
|
|
74
|
+
@state()
|
|
75
|
+
accessor draggingSessionId: string = '';
|
|
76
|
+
|
|
77
|
+
@state()
|
|
78
|
+
accessor dropSlot: IDropSlot | undefined = undefined;
|
|
79
|
+
|
|
80
|
+
@state()
|
|
81
|
+
accessor collapsedGroupIds: ReadonlySet<string> = new Set<string>();
|
|
82
|
+
|
|
49
83
|
public static styles = [
|
|
50
84
|
themeDefaultStyles,
|
|
51
85
|
cssManager.defaultStyles,
|
|
@@ -118,6 +152,10 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
118
152
|
box-shadow: inset 2px 0 0 var(--dees-color-accent-primary);
|
|
119
153
|
}
|
|
120
154
|
|
|
155
|
+
.item.dragging {
|
|
156
|
+
opacity: 0.4;
|
|
157
|
+
}
|
|
158
|
+
|
|
121
159
|
.itemTitle {
|
|
122
160
|
display: flex;
|
|
123
161
|
align-items: baseline;
|
|
@@ -155,9 +193,109 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
155
193
|
font-size: var(--dees-font-control-size-sm, 12px);
|
|
156
194
|
text-align: center;
|
|
157
195
|
}
|
|
196
|
+
|
|
197
|
+
.groupHeader {
|
|
198
|
+
display: flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
gap: var(--dees-spacing-xs);
|
|
201
|
+
width: 100%;
|
|
202
|
+
margin-top: var(--dees-spacing-xs);
|
|
203
|
+
padding: 2px var(--dees-spacing-sm);
|
|
204
|
+
border: 0;
|
|
205
|
+
border-radius: var(--dees-radius-md);
|
|
206
|
+
background: transparent;
|
|
207
|
+
color: var(--dees-color-text-secondary);
|
|
208
|
+
cursor: pointer;
|
|
209
|
+
font-family: inherit;
|
|
210
|
+
font-size: var(--dees-font-caption1-size, 11px);
|
|
211
|
+
font-weight: 650;
|
|
212
|
+
text-transform: uppercase;
|
|
213
|
+
letter-spacing: 0.05em;
|
|
214
|
+
text-align: left;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.groupHeader:hover {
|
|
218
|
+
background: var(--dees-color-hover);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.groupHeader .chevron {
|
|
222
|
+
transition: transform var(--dees-transition-fast) var(--dees-ease-standard);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.groupHeader.collapsed .chevron {
|
|
226
|
+
transform: rotate(-90deg);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.groupHeader .groupCount {
|
|
230
|
+
margin-left: auto;
|
|
231
|
+
font-weight: 500;
|
|
232
|
+
text-transform: none;
|
|
233
|
+
letter-spacing: normal;
|
|
234
|
+
color: var(--dees-color-text-muted);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.groupHeader.dropTarget {
|
|
238
|
+
outline: 1px dashed var(--dees-color-accent-primary);
|
|
239
|
+
outline-offset: -1px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.dropLine {
|
|
243
|
+
height: 2px;
|
|
244
|
+
margin: 0 var(--dees-spacing-sm);
|
|
245
|
+
border-radius: 1px;
|
|
246
|
+
background: var(--dees-color-accent-primary);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.newGroupRow {
|
|
250
|
+
flex: 0 0 auto;
|
|
251
|
+
padding: var(--dees-spacing-xs) var(--dees-spacing-sm) var(--dees-spacing-sm);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.newGroupButton {
|
|
255
|
+
display: flex;
|
|
256
|
+
align-items: center;
|
|
257
|
+
gap: var(--dees-spacing-xs);
|
|
258
|
+
width: 100%;
|
|
259
|
+
padding: var(--dees-spacing-xs) var(--dees-spacing-sm);
|
|
260
|
+
border: 1px dashed var(--dees-color-border-default);
|
|
261
|
+
border-radius: var(--dees-radius-md);
|
|
262
|
+
background: transparent;
|
|
263
|
+
color: var(--dees-color-text-secondary);
|
|
264
|
+
cursor: pointer;
|
|
265
|
+
font-family: inherit;
|
|
266
|
+
font-size: var(--dees-font-control-size-sm, 12px);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.newGroupButton:hover {
|
|
270
|
+
border-color: var(--dees-color-accent-primary);
|
|
271
|
+
color: var(--dees-color-text-primary);
|
|
272
|
+
}
|
|
158
273
|
`,
|
|
159
274
|
];
|
|
160
275
|
|
|
276
|
+
private get sessionsById(): Map<string, IHarnessSessionMeta> {
|
|
277
|
+
return new Map(this.sessions.map((session) => [session.id, session]));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private get groupedSessionIds(): Set<string> {
|
|
281
|
+
const ids = new Set<string>();
|
|
282
|
+
for (const group of this.groups) {
|
|
283
|
+
for (const id of group.sessionIds) ids.add(id);
|
|
284
|
+
}
|
|
285
|
+
return ids;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Ungrouped sessions, most recent first. */
|
|
289
|
+
private get ungroupedSessions(): IHarnessSessionMeta[] {
|
|
290
|
+
const grouped = this.groupedSessionIds;
|
|
291
|
+
return [...this.sessions]
|
|
292
|
+
.filter((session) => !grouped.has(session.id))
|
|
293
|
+
.sort(
|
|
294
|
+
(left, right) =>
|
|
295
|
+
(right.updatedAt ?? right.createdAt ?? 0) - (left.updatedAt ?? left.createdAt ?? 0),
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
161
299
|
private get filteredSessions(): IHarnessSessionMeta[] {
|
|
162
300
|
const query = this.search.trim().toLowerCase();
|
|
163
301
|
const sorted = [...this.sessions].sort(
|
|
@@ -174,7 +312,7 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
174
312
|
}
|
|
175
313
|
|
|
176
314
|
public render(): TemplateResult {
|
|
177
|
-
const
|
|
315
|
+
const groupingActive = this.enableGrouping && !this.search.trim();
|
|
178
316
|
return html`
|
|
179
317
|
<div class="searchRow">
|
|
180
318
|
<input
|
|
@@ -188,18 +326,138 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
188
326
|
<div class="list">
|
|
189
327
|
${this.loading
|
|
190
328
|
? html`<div class="emptyNote">Loading conversations…</div>`
|
|
191
|
-
:
|
|
192
|
-
?
|
|
193
|
-
:
|
|
329
|
+
: groupingActive
|
|
330
|
+
? this.renderGroupedList()
|
|
331
|
+
: this.renderFlatList()}
|
|
194
332
|
</div>
|
|
333
|
+
${groupingActive
|
|
334
|
+
? html`
|
|
335
|
+
<div class="newGroupRow">
|
|
336
|
+
<button
|
|
337
|
+
class="newGroupButton"
|
|
338
|
+
type="button"
|
|
339
|
+
@click=${() => {
|
|
340
|
+
this.dispatchEvent(
|
|
341
|
+
new CustomEvent('harness-group-create-request', {
|
|
342
|
+
bubbles: true,
|
|
343
|
+
composed: true,
|
|
344
|
+
}),
|
|
345
|
+
);
|
|
346
|
+
}}
|
|
347
|
+
>
|
|
348
|
+
<dees-icon icon="lucide:FolderPlus" iconSize="13"></dees-icon>
|
|
349
|
+
New group
|
|
350
|
+
</button>
|
|
351
|
+
</div>
|
|
352
|
+
`
|
|
353
|
+
: ''}
|
|
354
|
+
`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private renderFlatList(): TemplateResult {
|
|
358
|
+
const sessions = this.filteredSessions;
|
|
359
|
+
if (!sessions.length) {
|
|
360
|
+
return html`<div class="emptyNote">
|
|
361
|
+
${this.sessions.length ? 'No conversations match this search.' : this.emptyText}
|
|
362
|
+
</div>`;
|
|
363
|
+
}
|
|
364
|
+
return html`${sessions.map((session) => this.renderItem(session, null, -1))}`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private renderGroupedList(): TemplateResult {
|
|
368
|
+
const byId = this.sessionsById;
|
|
369
|
+
const ungrouped = this.ungroupedSessions;
|
|
370
|
+
if (!this.sessions.length && !this.groups.length) {
|
|
371
|
+
return html`<div class="emptyNote">${this.emptyText}</div>`;
|
|
372
|
+
}
|
|
373
|
+
return html`
|
|
374
|
+
${this.groups.map((group) => {
|
|
375
|
+
const members = group.sessionIds
|
|
376
|
+
.map((id) => byId.get(id))
|
|
377
|
+
.filter((session): session is IHarnessSessionMeta => session !== undefined);
|
|
378
|
+
const collapsed = this.collapsedGroupIds.has(group.id);
|
|
379
|
+
const isHeaderDrop =
|
|
380
|
+
this.dropSlot?.groupId === group.id && this.dropSlot.index === members.length && collapsed;
|
|
381
|
+
return html`
|
|
382
|
+
<button
|
|
383
|
+
class="groupHeader ${collapsed ? 'collapsed' : ''} ${isHeaderDrop ? 'dropTarget' : ''}"
|
|
384
|
+
type="button"
|
|
385
|
+
@click=${() => this.toggleGroupCollapsed(group.id)}
|
|
386
|
+
@contextmenu=${(event: MouseEvent) => {
|
|
387
|
+
event.preventDefault();
|
|
388
|
+
const detail: IHarnessGroupContextDetail = {
|
|
389
|
+
group,
|
|
390
|
+
clientX: event.clientX,
|
|
391
|
+
clientY: event.clientY,
|
|
392
|
+
originalEvent: event,
|
|
393
|
+
};
|
|
394
|
+
this.dispatchEvent(
|
|
395
|
+
new CustomEvent('harness-group-context', { detail, bubbles: true, composed: true }),
|
|
396
|
+
);
|
|
397
|
+
}}
|
|
398
|
+
@dragover=${(event: DragEvent) => this.handleDragOverSlot(event, group.id, members.length)}
|
|
399
|
+
@drop=${(event: DragEvent) => this.handleDrop(event, group.id, members.length)}
|
|
400
|
+
>
|
|
401
|
+
<dees-icon class="chevron" icon="lucide:ChevronDown" iconSize="12"></dees-icon>
|
|
402
|
+
<span>${group.name}</span>
|
|
403
|
+
<span class="groupCount">${members.length}</span>
|
|
404
|
+
</button>
|
|
405
|
+
${collapsed
|
|
406
|
+
? ''
|
|
407
|
+
: this.renderSection(members, group.id)}
|
|
408
|
+
`;
|
|
409
|
+
})}
|
|
410
|
+
${this.groups.length && ungrouped.length
|
|
411
|
+
? html`
|
|
412
|
+
<button
|
|
413
|
+
class="groupHeader"
|
|
414
|
+
type="button"
|
|
415
|
+
tabindex="-1"
|
|
416
|
+
@dragover=${(event: DragEvent) => this.handleDragOverSlot(event, null, 0)}
|
|
417
|
+
@drop=${(event: DragEvent) => this.handleDrop(event, null, 0)}
|
|
418
|
+
>
|
|
419
|
+
<span>Ungrouped</span>
|
|
420
|
+
<span class="groupCount">${ungrouped.length}</span>
|
|
421
|
+
</button>
|
|
422
|
+
`
|
|
423
|
+
: ''}
|
|
424
|
+
${this.renderSection(ungrouped, null)}
|
|
425
|
+
`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private renderSection(
|
|
429
|
+
sessions: IHarnessSessionMeta[],
|
|
430
|
+
groupId: string | null,
|
|
431
|
+
): TemplateResult {
|
|
432
|
+
const slot = this.dropSlot;
|
|
433
|
+
return html`
|
|
434
|
+
${sessions.map(
|
|
435
|
+
(session, index) => html`
|
|
436
|
+
${slot && slot.groupId === groupId && slot.index === index
|
|
437
|
+
? html`<div class="dropLine"></div>`
|
|
438
|
+
: ''}
|
|
439
|
+
${this.renderItem(session, groupId, index)}
|
|
440
|
+
`,
|
|
441
|
+
)}
|
|
442
|
+
${slot && slot.groupId === groupId && slot.index === sessions.length
|
|
443
|
+
? html`<div class="dropLine"></div>`
|
|
444
|
+
: ''}
|
|
195
445
|
`;
|
|
196
446
|
}
|
|
197
447
|
|
|
198
|
-
private renderItem(
|
|
448
|
+
private renderItem(
|
|
449
|
+
session: IHarnessSessionMeta,
|
|
450
|
+
groupId: string | null,
|
|
451
|
+
index: number,
|
|
452
|
+
): TemplateResult {
|
|
453
|
+
const draggable = this.enableGrouping && index >= 0;
|
|
199
454
|
return html`
|
|
200
455
|
<button
|
|
201
|
-
class="item ${session.id === this.selectedSessionId ? 'selected' : ''}
|
|
456
|
+
class="item ${session.id === this.selectedSessionId ? 'selected' : ''} ${
|
|
457
|
+
this.draggingSessionId === session.id ? 'dragging' : ''
|
|
458
|
+
}"
|
|
202
459
|
type="button"
|
|
460
|
+
draggable=${draggable ? 'true' : 'false'}
|
|
203
461
|
@click=${() => this.emit('harness-session-select', session)}
|
|
204
462
|
@dblclick=${() => this.emit('harness-session-open', session)}
|
|
205
463
|
@keydown=${(event: KeyboardEvent) => {
|
|
@@ -217,6 +475,29 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
217
475
|
new CustomEvent('harness-session-context', { detail, bubbles: true, composed: true }),
|
|
218
476
|
);
|
|
219
477
|
}}
|
|
478
|
+
@dragstart=${(event: DragEvent) => {
|
|
479
|
+
if (!draggable) return;
|
|
480
|
+
this.draggingSessionId = session.id;
|
|
481
|
+
event.dataTransfer?.setData('text/plain', session.id);
|
|
482
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move';
|
|
483
|
+
}}
|
|
484
|
+
@dragend=${() => {
|
|
485
|
+
this.draggingSessionId = '';
|
|
486
|
+
this.dropSlot = undefined;
|
|
487
|
+
}}
|
|
488
|
+
@dragover=${(event: DragEvent) => {
|
|
489
|
+
if (!this.draggingSessionId || index < 0) return;
|
|
490
|
+
event.preventDefault();
|
|
491
|
+
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
492
|
+
const before = event.clientY < rect.top + rect.height / 2;
|
|
493
|
+
this.setDropSlot(groupId, before ? index : index + 1);
|
|
494
|
+
}}
|
|
495
|
+
@drop=${(event: DragEvent) => {
|
|
496
|
+
if (index < 0) return;
|
|
497
|
+
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
498
|
+
const before = event.clientY < rect.top + rect.height / 2;
|
|
499
|
+
this.handleDrop(event, groupId, before ? index : index + 1);
|
|
500
|
+
}}
|
|
220
501
|
>
|
|
221
502
|
<span class="itemTitle">
|
|
222
503
|
<span class="titleText">${session.title || session.id}</span>
|
|
@@ -229,6 +510,45 @@ export class DeesHarnessSessionList extends DeesElement {
|
|
|
229
510
|
`;
|
|
230
511
|
}
|
|
231
512
|
|
|
513
|
+
private toggleGroupCollapsed(groupIdArg: string): void {
|
|
514
|
+
const next = new Set(this.collapsedGroupIds);
|
|
515
|
+
if (next.has(groupIdArg)) {
|
|
516
|
+
next.delete(groupIdArg);
|
|
517
|
+
} else {
|
|
518
|
+
next.add(groupIdArg);
|
|
519
|
+
}
|
|
520
|
+
this.collapsedGroupIds = next;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private setDropSlot(groupIdArg: string | null, indexArg: number): void {
|
|
524
|
+
const current = this.dropSlot;
|
|
525
|
+
if (current && current.groupId === groupIdArg && current.index === indexArg) return;
|
|
526
|
+
this.dropSlot = { groupId: groupIdArg, index: indexArg };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private handleDragOverSlot(event: DragEvent, groupIdArg: string | null, indexArg: number): void {
|
|
530
|
+
if (!this.draggingSessionId) return;
|
|
531
|
+
event.preventDefault();
|
|
532
|
+
this.setDropSlot(groupIdArg, indexArg);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private handleDrop(event: DragEvent, groupIdArg: string | null, indexArg: number): void {
|
|
536
|
+
event.preventDefault();
|
|
537
|
+
event.stopPropagation();
|
|
538
|
+
const sessionId = this.draggingSessionId || event.dataTransfer?.getData('text/plain') || '';
|
|
539
|
+
this.draggingSessionId = '';
|
|
540
|
+
this.dropSlot = undefined;
|
|
541
|
+
if (!sessionId) return;
|
|
542
|
+
const detail: IHarnessSessionMoveDetail = {
|
|
543
|
+
sessionId,
|
|
544
|
+
groupId: groupIdArg,
|
|
545
|
+
index: Math.max(0, indexArg),
|
|
546
|
+
};
|
|
547
|
+
this.dispatchEvent(
|
|
548
|
+
new CustomEvent('harness-session-move', { detail, bubbles: true, composed: true }),
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
232
552
|
private emit(name: string, session: IHarnessSessionMeta): void {
|
|
233
553
|
this.dispatchEvent(new CustomEvent(name, { detail: { session }, bubbles: true, composed: true }));
|
|
234
554
|
}
|
|
@@ -210,6 +210,30 @@ export interface IHarnessSessionContextDetail {
|
|
|
210
210
|
originalEvent: MouseEvent;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
/** A user-defined session group; ordering of `sessionIds` is the display order. */
|
|
214
|
+
export interface IHarnessSessionGroup {
|
|
215
|
+
id: string;
|
|
216
|
+
name: string;
|
|
217
|
+
sessionIds: string[];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Detail of `harness-session-move`: a session was dropped into a group slot. */
|
|
221
|
+
export interface IHarnessSessionMoveDetail {
|
|
222
|
+
sessionId: string;
|
|
223
|
+
/** Target group, or null for the ungrouped section. */
|
|
224
|
+
groupId: string | null;
|
|
225
|
+
/** Insertion index within the target section's current display order. */
|
|
226
|
+
index: number;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Detail of `harness-group-context`, fired on right-click of a group header. */
|
|
230
|
+
export interface IHarnessGroupContextDetail {
|
|
231
|
+
group: IHarnessSessionGroup;
|
|
232
|
+
clientX: number;
|
|
233
|
+
clientY: number;
|
|
234
|
+
originalEvent: MouseEvent;
|
|
235
|
+
}
|
|
236
|
+
|
|
213
237
|
export type THarnessTodoStatus = 'pending' | 'in_progress' | 'completed';
|
|
214
238
|
|
|
215
239
|
export interface IHarnessTodoItem {
|