@design.estate/dees-catalog 6.9.0 → 6.10.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/.smartconfig.json +10 -2
- package/dist_bundle/bundle.js +354 -75
- package/dist_ts_web/00_commitinfo_data.js +2 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.d.ts +3 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.js +2 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.js +29 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.d.ts +30 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.js +297 -17
- package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/renderers.js +30 -1
- package/dist_ts_web/elements/00group-harness/interfaces.d.ts +1 -1
- package/dist_ts_web/elements/00group-harness/toolregistry.js +12 -2
- package/package.json +1 -1
- package/readme.md +10 -8
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.ts +1 -1
- package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts +12 -0
- package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +282 -19
- package/ts_web/elements/00group-harness/dees-harness-tool-card/renderers.ts +28 -0
- package/ts_web/elements/00group-harness/interfaces.ts +1 -1
- package/ts_web/elements/00group-harness/toolregistry.ts +11 -1
package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts
CHANGED
|
@@ -40,6 +40,8 @@ declare global {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
const pinThresholdPx = 48;
|
|
43
|
+
const loadEarlierThresholdPx = 24;
|
|
44
|
+
const scrollIntentSettlementMs = 250;
|
|
43
45
|
const virtualizationThreshold = 40;
|
|
44
46
|
const virtualOverscanPx = 800;
|
|
45
47
|
const virtualGapPx = 8;
|
|
@@ -112,6 +114,16 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
112
114
|
@property({ type: Boolean })
|
|
113
115
|
accessor autoFollow: boolean = true;
|
|
114
116
|
|
|
117
|
+
/** Stable host-owned identity for one transcript and its scroll context. */
|
|
118
|
+
@property()
|
|
119
|
+
accessor transcriptKey: string = '';
|
|
120
|
+
|
|
121
|
+
@property({ type: Boolean })
|
|
122
|
+
accessor hasEarlierMessages: boolean = false;
|
|
123
|
+
|
|
124
|
+
@property({ type: Boolean })
|
|
125
|
+
accessor loadingEarlier: boolean = false;
|
|
126
|
+
|
|
115
127
|
@property({ type: Boolean })
|
|
116
128
|
accessor hideScrollbar: boolean = false;
|
|
117
129
|
|
|
@@ -200,6 +212,16 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
200
212
|
private virtualizationDisablePending = false;
|
|
201
213
|
private virtualizationActivationFrame: number | null = null;
|
|
202
214
|
private selectionChangeListening = false;
|
|
215
|
+
private loadEarlierLatched = false;
|
|
216
|
+
private loadingEarlierObserved = false;
|
|
217
|
+
private earlierScrollIntent = false;
|
|
218
|
+
private scrollbarGestureActive = false;
|
|
219
|
+
private touchScrollY: number | undefined;
|
|
220
|
+
private scrollIntentTimer: ReturnType<typeof setTimeout> | undefined;
|
|
221
|
+
private programmaticScrollTop: number | undefined;
|
|
222
|
+
private programmaticScrollGeneration = 0;
|
|
223
|
+
private programmaticScrollFrame: number | null = null;
|
|
224
|
+
private programmaticScrollSecondFrame: number | null = null;
|
|
203
225
|
|
|
204
226
|
public static styles = [
|
|
205
227
|
themeDefaultStyles,
|
|
@@ -492,6 +514,20 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
492
514
|
|
|
493
515
|
public willUpdate(changedProperties: Map<PropertyKey, unknown>): void {
|
|
494
516
|
super.willUpdate(changedProperties);
|
|
517
|
+
if (changedProperties.has('transcriptKey')) this.resetTranscriptContext();
|
|
518
|
+
if (changedProperties.has('hasEarlierMessages') && !this.hasEarlierMessages) {
|
|
519
|
+
this.loadEarlierLatched = false;
|
|
520
|
+
this.loadingEarlierObserved = false;
|
|
521
|
+
this.clearEarlierScrollIntent();
|
|
522
|
+
}
|
|
523
|
+
if (changedProperties.has('loadingEarlier')) {
|
|
524
|
+
const wasLoadingEarlier = changedProperties.get('loadingEarlier') === true;
|
|
525
|
+
if (this.loadingEarlier) this.loadingEarlierObserved = true;
|
|
526
|
+
else if (wasLoadingEarlier && this.loadingEarlierObserved) {
|
|
527
|
+
this.loadEarlierLatched = false;
|
|
528
|
+
this.loadingEarlierObserved = false;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
495
531
|
if (changedProperties.has('viewState') && this.viewState) {
|
|
496
532
|
this.clearTranscriptViewState(this.localViewState);
|
|
497
533
|
}
|
|
@@ -503,6 +539,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
503
539
|
|| changedProperties.has('questions')
|
|
504
540
|
|| changedProperties.has('toolRegistry')
|
|
505
541
|
|| changedProperties.has('viewState')
|
|
542
|
+
|| changedProperties.has('transcriptKey')
|
|
506
543
|
|| registryChanged
|
|
507
544
|
) {
|
|
508
545
|
this.rebuildTimeline();
|
|
@@ -766,8 +803,12 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
766
803
|
<div
|
|
767
804
|
class=${`scroll ${this.hideScrollbar ? 'hideScrollbar' : ''} ${this.scrollFadeTop ? 'fadeTop' : ''} ${this.scrollFadeBottom ? 'fadeBottom' : ''}`}
|
|
768
805
|
@scroll=${this.handleScroll}
|
|
806
|
+
@scrollend=${this.handleScrollEnd}
|
|
769
807
|
@wheel=${this.handleUserScrollIntent}
|
|
808
|
+
@touchstart=${this.handleTouchStart}
|
|
770
809
|
@touchmove=${this.handleUserScrollIntent}
|
|
810
|
+
@touchend=${this.handleTouchEnd}
|
|
811
|
+
@touchcancel=${this.handleTouchCancel}
|
|
771
812
|
@pointerdown=${this.handlePointerDown}
|
|
772
813
|
@pointerup=${this.handlePointerUp}
|
|
773
814
|
@pointercancel=${this.handlePointerCancel}
|
|
@@ -1042,8 +1083,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1042
1083
|
if (!this.autoFollow || this.userUnpinned) return;
|
|
1043
1084
|
const container = this.scrollContainer;
|
|
1044
1085
|
if (!container) return;
|
|
1045
|
-
container
|
|
1046
|
-
this.lastObservedScrollTop = container.scrollTop;
|
|
1086
|
+
this.setProgrammaticScrollTop(container, container.scrollHeight);
|
|
1047
1087
|
};
|
|
1048
1088
|
|
|
1049
1089
|
private followAfterContentChange(): void {
|
|
@@ -1068,26 +1108,213 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1068
1108
|
}
|
|
1069
1109
|
|
|
1070
1110
|
private handleUserScrollIntent = (event: Event): void => {
|
|
1071
|
-
if (
|
|
1072
|
-
(candidate) => candidate instanceof DeesHarnessMessageList && candidate !== this,
|
|
1073
|
-
)) return;
|
|
1111
|
+
if (this.eventComesFromNestedMessageList(event)) return;
|
|
1074
1112
|
if (event.type === 'wheel') {
|
|
1075
1113
|
this.cancelFullAnchorRestore();
|
|
1076
|
-
|
|
1114
|
+
const deltaY = (event as WheelEvent).deltaY;
|
|
1115
|
+
if (deltaY < 0) {
|
|
1116
|
+
this.armEarlierScrollIntent();
|
|
1117
|
+
this.requestEarlierIfAtTop();
|
|
1118
|
+
this.markUserUnpinned();
|
|
1119
|
+
} else if (deltaY > 0) {
|
|
1120
|
+
this.clearEarlierScrollIntent();
|
|
1121
|
+
}
|
|
1122
|
+
return;
|
|
1077
1123
|
}
|
|
1078
|
-
if (
|
|
1079
|
-
event
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1124
|
+
if (event.type === 'keydown') {
|
|
1125
|
+
if (this.keyboardEventOriginIsInteractive(event as KeyboardEvent)) return;
|
|
1126
|
+
const key = (event as KeyboardEvent).key;
|
|
1127
|
+
if (['ArrowUp', 'PageUp', 'Home'].includes(key)) {
|
|
1128
|
+
this.cancelFullAnchorRestore();
|
|
1129
|
+
this.armEarlierScrollIntent();
|
|
1130
|
+
this.requestEarlierIfAtTop();
|
|
1131
|
+
this.markUserUnpinned();
|
|
1132
|
+
} else if (['ArrowDown', 'PageDown', 'End', ' '].includes(key)) {
|
|
1133
|
+
this.clearEarlierScrollIntent();
|
|
1083
1134
|
this.cancelFullAnchorRestore();
|
|
1084
1135
|
}
|
|
1085
1136
|
return;
|
|
1086
1137
|
}
|
|
1087
|
-
if (event.type === '
|
|
1088
|
-
|
|
1138
|
+
if (event.type === 'touchmove') {
|
|
1139
|
+
const currentY = (event as TouchEvent).touches[0]?.clientY;
|
|
1140
|
+
const previousY = this.touchScrollY;
|
|
1141
|
+
this.touchScrollY = currentY;
|
|
1142
|
+
if (currentY === undefined || previousY === undefined) return;
|
|
1143
|
+
if (currentY > previousY + 0.5) {
|
|
1144
|
+
this.armEarlierScrollIntent();
|
|
1145
|
+
this.requestEarlierIfAtTop();
|
|
1146
|
+
} else if (currentY < previousY - 0.5) {
|
|
1147
|
+
this.clearEarlierScrollIntent();
|
|
1148
|
+
} else {
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
this.cancelFullAnchorRestore();
|
|
1152
|
+
this.markUserUnpinned();
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
if (event.type === 'pointerdown') {
|
|
1156
|
+
if ((event as PointerEvent).button !== 0) return;
|
|
1157
|
+
if (event.target !== this.scrollContainer) return;
|
|
1158
|
+
this.scrollbarGestureActive = true;
|
|
1159
|
+
this.cancelFullAnchorRestore();
|
|
1160
|
+
this.markUserUnpinned();
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
private markUserUnpinned(): void {
|
|
1089
1165
|
this.userUnpinned = true;
|
|
1090
1166
|
this.measurementAnchor = undefined;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
private eventComesFromNestedMessageList(eventArg: Event): boolean {
|
|
1170
|
+
return eventArg.composedPath().some(
|
|
1171
|
+
(candidate) => candidate instanceof DeesHarnessMessageList && candidate !== this,
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
private keyboardEventOriginIsInteractive(eventArg: KeyboardEvent): boolean {
|
|
1176
|
+
return eventArg.composedPath().some((candidate) => (
|
|
1177
|
+
candidate instanceof HTMLElement
|
|
1178
|
+
&& candidate !== this
|
|
1179
|
+
&& (
|
|
1180
|
+
candidate.isContentEditable
|
|
1181
|
+
|| candidate.matches('input, textarea, select, button, [contenteditable]')
|
|
1182
|
+
)
|
|
1183
|
+
));
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
private armEarlierScrollIntent(): void {
|
|
1187
|
+
this.earlierScrollIntent = true;
|
|
1188
|
+
this.scheduleScrollIntentSettlement();
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
private scheduleScrollIntentSettlement(): void {
|
|
1192
|
+
if (this.scrollIntentTimer) clearTimeout(this.scrollIntentTimer);
|
|
1193
|
+
this.scrollIntentTimer = setTimeout(() => {
|
|
1194
|
+
this.scrollIntentTimer = undefined;
|
|
1195
|
+
this.earlierScrollIntent = false;
|
|
1196
|
+
}, scrollIntentSettlementMs);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
private clearEarlierScrollIntent(): void {
|
|
1200
|
+
if (this.scrollIntentTimer) clearTimeout(this.scrollIntentTimer);
|
|
1201
|
+
this.scrollIntentTimer = undefined;
|
|
1202
|
+
this.earlierScrollIntent = false;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
private requestEarlierIfAtTop(): void {
|
|
1206
|
+
const container = this.scrollContainer;
|
|
1207
|
+
if (
|
|
1208
|
+
!container
|
|
1209
|
+
|| container.scrollTop > loadEarlierThresholdPx
|
|
1210
|
+
|| !this.hasEarlierMessages
|
|
1211
|
+
|| this.loadingEarlier
|
|
1212
|
+
|| this.loadEarlierLatched
|
|
1213
|
+
) return;
|
|
1214
|
+
this.loadEarlierLatched = true;
|
|
1215
|
+
this.clearEarlierScrollIntent();
|
|
1216
|
+
this.dispatchEvent(new CustomEvent('harness-load-earlier', {
|
|
1217
|
+
bubbles: true,
|
|
1218
|
+
composed: true,
|
|
1219
|
+
}));
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
private resetLoadEarlierState(): void {
|
|
1223
|
+
this.loadEarlierLatched = false;
|
|
1224
|
+
this.loadingEarlierObserved = false;
|
|
1225
|
+
this.scrollbarGestureActive = false;
|
|
1226
|
+
this.touchScrollY = undefined;
|
|
1227
|
+
this.clearEarlierScrollIntent();
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
private setProgrammaticScrollTop(containerArg: HTMLElement, topArg: number): void {
|
|
1231
|
+
this.clearProgrammaticScrollMarker();
|
|
1232
|
+
const generation = ++this.programmaticScrollGeneration;
|
|
1233
|
+
containerArg.scrollTop = topArg;
|
|
1234
|
+
this.programmaticScrollTop = containerArg.scrollTop;
|
|
1235
|
+
this.lastObservedScrollTop = containerArg.scrollTop;
|
|
1236
|
+
this.programmaticScrollFrame = requestAnimationFrame(() => {
|
|
1237
|
+
this.programmaticScrollFrame = null;
|
|
1238
|
+
if (generation !== this.programmaticScrollGeneration) return;
|
|
1239
|
+
this.programmaticScrollSecondFrame = requestAnimationFrame(() => {
|
|
1240
|
+
this.programmaticScrollSecondFrame = null;
|
|
1241
|
+
if (generation === this.programmaticScrollGeneration) {
|
|
1242
|
+
this.programmaticScrollTop = undefined;
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
private consumeProgrammaticScroll(topArg: number): boolean {
|
|
1249
|
+
if (this.programmaticScrollTop === undefined) return false;
|
|
1250
|
+
const matches = Math.abs(this.programmaticScrollTop - topArg) <= 1;
|
|
1251
|
+
this.clearProgrammaticScrollMarker();
|
|
1252
|
+
return matches;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
private clearProgrammaticScrollMarker(): void {
|
|
1256
|
+
this.programmaticScrollGeneration += 1;
|
|
1257
|
+
if (this.programmaticScrollFrame !== null) cancelAnimationFrame(this.programmaticScrollFrame);
|
|
1258
|
+
if (this.programmaticScrollSecondFrame !== null) cancelAnimationFrame(this.programmaticScrollSecondFrame);
|
|
1259
|
+
this.programmaticScrollFrame = null;
|
|
1260
|
+
this.programmaticScrollSecondFrame = null;
|
|
1261
|
+
this.programmaticScrollTop = undefined;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
private resetTranscriptContext(): void {
|
|
1265
|
+
this.cancelFullAnchorRestore();
|
|
1266
|
+
if (this.followFrame !== null) cancelAnimationFrame(this.followFrame);
|
|
1267
|
+
if (this.followSecondFrame !== null) cancelAnimationFrame(this.followSecondFrame);
|
|
1268
|
+
if (this.virtualMeasureFrame !== null) cancelAnimationFrame(this.virtualMeasureFrame);
|
|
1269
|
+
if (this.virtualRangeFrame !== null) cancelAnimationFrame(this.virtualRangeFrame);
|
|
1270
|
+
if (this.virtualizationActivationFrame !== null) cancelAnimationFrame(this.virtualizationActivationFrame);
|
|
1271
|
+
this.followFrame = null;
|
|
1272
|
+
this.followSecondFrame = null;
|
|
1273
|
+
this.virtualMeasureFrame = null;
|
|
1274
|
+
this.virtualRangeFrame = null;
|
|
1275
|
+
this.virtualizationActivationFrame = null;
|
|
1276
|
+
this.pendingVirtualAnchor = undefined;
|
|
1277
|
+
this.pendingFullAnchor = undefined;
|
|
1278
|
+
this.measurementAnchor = undefined;
|
|
1279
|
+
this.pendingVirtualRepin = false;
|
|
1280
|
+
this.virtualizationEnabled = false;
|
|
1281
|
+
this.virtualizationEnablePending = false;
|
|
1282
|
+
this.virtualizationDisablePending = false;
|
|
1283
|
+
this.virtualStartIndex = 0;
|
|
1284
|
+
this.virtualEndIndex = 0;
|
|
1285
|
+
this.retainedVirtualKeys.clear();
|
|
1286
|
+
this.explicitRetainedVirtualKey = undefined;
|
|
1287
|
+
this.measuredVirtualHeights.clear();
|
|
1288
|
+
this.virtualOffsets = [];
|
|
1289
|
+
this.virtualTotalHeight = 0;
|
|
1290
|
+
this.toolGroupIdByMessageId.clear();
|
|
1291
|
+
this.nextToolGroupId = 1;
|
|
1292
|
+
this.timelineRegistryRevision = -1;
|
|
1293
|
+
this.knownContentKeys.clear();
|
|
1294
|
+
this.currentNewContentKeys.clear();
|
|
1295
|
+
this.clearTranscriptViewState(this.localViewState);
|
|
1296
|
+
this.clearSelectionGesture();
|
|
1297
|
+
this.disconnectSelectionChangeListener();
|
|
1298
|
+
this.userUnpinned = false;
|
|
1299
|
+
this.lastObservedScrollTop = 0;
|
|
1300
|
+
this.showJumpChip = false;
|
|
1301
|
+
this.resetLoadEarlierState();
|
|
1302
|
+
this.clearProgrammaticScrollMarker();
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
private readonly handleTouchStart = (eventArg: TouchEvent): void => {
|
|
1306
|
+
if (this.eventComesFromNestedMessageList(eventArg)) return;
|
|
1307
|
+
this.touchScrollY = eventArg.touches[0]?.clientY;
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
private readonly handleTouchEnd = (): void => {
|
|
1311
|
+
this.touchScrollY = undefined;
|
|
1312
|
+
if (this.earlierScrollIntent) this.scheduleScrollIntentSettlement();
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1315
|
+
private readonly handleTouchCancel = (): void => {
|
|
1316
|
+
this.touchScrollY = undefined;
|
|
1317
|
+
this.clearEarlierScrollIntent();
|
|
1091
1318
|
};
|
|
1092
1319
|
|
|
1093
1320
|
public scrollToBottom(force = false): void {
|
|
@@ -1099,8 +1326,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1099
1326
|
this.measurementAnchor = undefined;
|
|
1100
1327
|
}
|
|
1101
1328
|
if (force || this.isPinnedToBottom) {
|
|
1102
|
-
container
|
|
1103
|
-
this.lastObservedScrollTop = container.scrollTop;
|
|
1329
|
+
this.setProgrammaticScrollTop(container, container.scrollHeight);
|
|
1104
1330
|
this.showJumpChip = false;
|
|
1105
1331
|
}
|
|
1106
1332
|
this.syncScrollEdgeFades();
|
|
@@ -1110,6 +1336,20 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1110
1336
|
const container = this.scrollContainer;
|
|
1111
1337
|
if (!container) return;
|
|
1112
1338
|
const top = container.scrollTop;
|
|
1339
|
+
const previousTop = this.lastObservedScrollTop;
|
|
1340
|
+
const programmatic = this.consumeProgrammaticScroll(top);
|
|
1341
|
+
if (programmatic) {
|
|
1342
|
+
this.clearEarlierScrollIntent();
|
|
1343
|
+
} else {
|
|
1344
|
+
if (this.scrollbarGestureActive && top < previousTop - 0.5) {
|
|
1345
|
+
this.armEarlierScrollIntent();
|
|
1346
|
+
} else if (top > previousTop + 0.5) {
|
|
1347
|
+
this.clearEarlierScrollIntent();
|
|
1348
|
+
} else if (this.earlierScrollIntent) {
|
|
1349
|
+
this.scheduleScrollIntentSettlement();
|
|
1350
|
+
}
|
|
1351
|
+
if (this.earlierScrollIntent) this.requestEarlierIfAtTop();
|
|
1352
|
+
}
|
|
1113
1353
|
this.syncScrollEdgeFades();
|
|
1114
1354
|
if (this.isPinnedToBottom) {
|
|
1115
1355
|
// reaching the bottom — by any means — resumes following
|
|
@@ -1123,6 +1363,10 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1123
1363
|
this.schedulePendingVirtualizationActivation();
|
|
1124
1364
|
};
|
|
1125
1365
|
|
|
1366
|
+
private readonly handleScrollEnd = (): void => {
|
|
1367
|
+
this.clearEarlierScrollIntent();
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1126
1370
|
private schedulePendingVirtualizationActivation(): void {
|
|
1127
1371
|
if (
|
|
1128
1372
|
(!this.virtualizationEnablePending && !this.virtualizationDisablePending)
|
|
@@ -1187,6 +1431,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1187
1431
|
};
|
|
1188
1432
|
|
|
1189
1433
|
private readonly handlePointerUp = (eventArg: PointerEvent): void => {
|
|
1434
|
+
this.scrollbarGestureActive = false;
|
|
1190
1435
|
if (eventArg.button !== 0) return;
|
|
1191
1436
|
const key = this.timelineKeyForEvent(eventArg);
|
|
1192
1437
|
if (key) this.selectionGestureKeys.add(key);
|
|
@@ -1205,6 +1450,8 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1205
1450
|
};
|
|
1206
1451
|
|
|
1207
1452
|
private readonly handlePointerCancel = (): void => {
|
|
1453
|
+
this.scrollbarGestureActive = false;
|
|
1454
|
+
this.clearEarlierScrollIntent();
|
|
1208
1455
|
this.clearSelectionGesture();
|
|
1209
1456
|
this.handleProtectedStateChange();
|
|
1210
1457
|
};
|
|
@@ -1228,6 +1475,13 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1228
1475
|
|
|
1229
1476
|
public updated(changedProperties: Map<string, any>): void {
|
|
1230
1477
|
super.updated(changedProperties);
|
|
1478
|
+
if (changedProperties.has('transcriptKey')) {
|
|
1479
|
+
const container = this.scrollContainer;
|
|
1480
|
+
if (container) {
|
|
1481
|
+
if (this.autoFollow) this.scrollToBottom(true);
|
|
1482
|
+
else this.setProgrammaticScrollTop(container, 0);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1231
1485
|
if (changedProperties.has('messages') || changedProperties.has('status') || changedProperties.has('permissions') || changedProperties.has('questions')) {
|
|
1232
1486
|
this.followAfterContentChange();
|
|
1233
1487
|
}
|
|
@@ -1424,7 +1678,10 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1424
1678
|
const index = this.timelineIndexByKey.get(anchorArg.key);
|
|
1425
1679
|
if (index === undefined) return;
|
|
1426
1680
|
const stack = this.shadowRoot?.querySelector<HTMLElement>('.virtualStack');
|
|
1427
|
-
|
|
1681
|
+
this.setProgrammaticScrollTop(
|
|
1682
|
+
containerArg,
|
|
1683
|
+
(stack?.offsetTop ?? 0) + (this.virtualOffsets[index] ?? 0) + anchorArg.offset,
|
|
1684
|
+
);
|
|
1428
1685
|
}
|
|
1429
1686
|
|
|
1430
1687
|
private restoreFullAnchor(
|
|
@@ -1435,9 +1692,13 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1435
1692
|
`.stack > [data-timeline-key="${CSS.escape(anchorArg.key)}"]`,
|
|
1436
1693
|
);
|
|
1437
1694
|
if (!element) return;
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1695
|
+
this.setProgrammaticScrollTop(
|
|
1696
|
+
containerArg,
|
|
1697
|
+
containerArg.scrollTop
|
|
1698
|
+
+ element.getBoundingClientRect().top
|
|
1699
|
+
- containerArg.getBoundingClientRect().top
|
|
1700
|
+
+ anchorArg.offset,
|
|
1701
|
+
);
|
|
1441
1702
|
}
|
|
1442
1703
|
|
|
1443
1704
|
private scheduleFullAnchorRestore(
|
|
@@ -1774,6 +2035,8 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1774
2035
|
this.explicitRetainedVirtualKey = undefined;
|
|
1775
2036
|
this.retainedVirtualKeys.clear();
|
|
1776
2037
|
this.clearSelectionGesture();
|
|
2038
|
+
this.resetLoadEarlierState();
|
|
2039
|
+
this.clearProgrammaticScrollMarker();
|
|
1777
2040
|
this.entryAnimationsEnabled = true;
|
|
1778
2041
|
this.disconnectContentResizeObserver();
|
|
1779
2042
|
this.disconnectVirtualObservers();
|
|
@@ -334,6 +334,33 @@ const renderTodo = (call: IHarnessToolCall, helpers: IHarnessToolRenderHelpers):
|
|
|
334
334
|
return html`<dees-harness-todos compact .todos=${todos}></dees-harness-todos>`;
|
|
335
335
|
};
|
|
336
336
|
|
|
337
|
+
const projectTaskOutputItems = (output: unknown): unknown[] | undefined => {
|
|
338
|
+
if (!harnessIsRecord(output)) return undefined;
|
|
339
|
+
if (Array.isArray(output.tasks)) return output.tasks;
|
|
340
|
+
if (Object.prototype.hasOwnProperty.call(output, 'task')) return [output.task];
|
|
341
|
+
return harnessIsRecord(output.state) && Array.isArray(output.state.tasks)
|
|
342
|
+
? output.state.tasks
|
|
343
|
+
: undefined;
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const hasUnsupportedTodoStatus = (items: unknown[]): boolean => items.some((item) => {
|
|
347
|
+
if (!harnessIsRecord(item) || typeof item.status !== 'string') return false;
|
|
348
|
+
return item.status !== 'pending' && item.status !== 'in_progress' && item.status !== 'completed';
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const renderProjectTask = (call: IHarnessToolCall, helpers: IHarnessToolRenderHelpers): TemplateResult => {
|
|
352
|
+
const output = outputText(call);
|
|
353
|
+
const outputItems = projectTaskOutputItems(call.output);
|
|
354
|
+
if (outputItems !== undefined) {
|
|
355
|
+
const tasks = hasUnsupportedTodoStatus(outputItems) ? [] : harnessParseTodos(outputItems);
|
|
356
|
+
if (tasks.length) {
|
|
357
|
+
return html`<dees-harness-todos compact .todos=${tasks}></dees-harness-todos>`;
|
|
358
|
+
}
|
|
359
|
+
return helpers.monoBlock(output || harnessStringifyValue(outputItems));
|
|
360
|
+
}
|
|
361
|
+
return helpers.monoBlock(output || harnessSummarizeValue(call.input));
|
|
362
|
+
};
|
|
363
|
+
|
|
337
364
|
const renderMcp = (call: IHarnessToolCall, helpers: IHarnessToolRenderHelpers): TemplateResult => {
|
|
338
365
|
return html`
|
|
339
366
|
${call.contentBlocks?.length ? helpers.contentBlocks(call.contentBlocks) : ''}
|
|
@@ -373,6 +400,7 @@ export const builtinBodyRenderers: Record<
|
|
|
373
400
|
'json': renderJson,
|
|
374
401
|
'search': renderSearch,
|
|
375
402
|
'subtask': renderSubtask,
|
|
403
|
+
'project-task': renderProjectTask,
|
|
376
404
|
'todo': renderTodo,
|
|
377
405
|
'mcp': renderMcp,
|
|
378
406
|
'unknown': renderUnknown,
|
|
@@ -222,7 +222,7 @@ export interface IHarnessToolCall {
|
|
|
222
222
|
isError?: boolean;
|
|
223
223
|
errorText?: string;
|
|
224
224
|
mcp?: IHarnessMcpMeta;
|
|
225
|
-
/** Session spawned by this call (subagent
|
|
225
|
+
/** Session spawned by this call (delegate/subagent tools); enables drill-in. */
|
|
226
226
|
childSessionId?: string;
|
|
227
227
|
/** Bounded live preview of the spawned session. Consumers own hydration. */
|
|
228
228
|
subtask?: IHarnessSubtaskStream;
|
|
@@ -180,7 +180,7 @@ const defaultDescriptors: IHarnessToolDescriptor[] = [
|
|
|
180
180
|
},
|
|
181
181
|
{
|
|
182
182
|
kind: 'subtask',
|
|
183
|
-
names: ['
|
|
183
|
+
names: ['delegate', 'subagent'],
|
|
184
184
|
label: 'Subagent',
|
|
185
185
|
icon: 'lucide:Bot',
|
|
186
186
|
// The child's model belongs in the collapsed header: a subagent often
|
|
@@ -202,6 +202,16 @@ const defaultDescriptors: IHarnessToolDescriptor[] = [
|
|
|
202
202
|
subtitle: (call) => inputField(call, 'question') || inputField(call, 'prompt') || harnessSummaryLine(call.input),
|
|
203
203
|
defaultExpanded: true,
|
|
204
204
|
},
|
|
205
|
+
{
|
|
206
|
+
kind: 'project-task',
|
|
207
|
+
names: ['task'],
|
|
208
|
+
label: 'Project task',
|
|
209
|
+
icon: 'lucide:ListChecks',
|
|
210
|
+
subtitle: (call) => {
|
|
211
|
+
const action = inputField(call, 'action');
|
|
212
|
+
return action ? `Action: ${action}` : harnessSummaryLine(call.input);
|
|
213
|
+
},
|
|
214
|
+
},
|
|
205
215
|
{
|
|
206
216
|
kind: 'todo',
|
|
207
217
|
names: ['todowrite', 'todo_write', 'todoread'],
|