deepseek-harness-background 0.5.3 → 0.5.5
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/README.md +174 -172
- package/README.zh.md +172 -170
- package/lib/client.js +485 -439
- package/lib/client.js.map +1 -1
- package/lib/index.js +281 -19
- package/package.json +10 -10
package/lib/client.js
CHANGED
|
@@ -161,19 +161,13 @@ window.__ModuleLoader__.load({
|
|
|
161
161
|
|
|
162
162
|
/* The sidebar build badge paints with the label-ink token — a TEXT token we
|
|
163
163
|
must not override globally — so it gets a theme-aware translucent ink. */
|
|
164
|
-
body[data-dsh-bg] [class*="
|
|
164
|
+
body[data-dsh-bg] [class*="_buildVersion"] {
|
|
165
165
|
background-color: rgba(15, 17, 21, 0.62);
|
|
166
166
|
}
|
|
167
|
-
body[data-ds-dark-theme][data-dsh-bg] [class*="
|
|
167
|
+
body[data-ds-dark-theme][data-dsh-bg] [class*="_buildVersion"] {
|
|
168
168
|
background-color: rgba(249, 250, 251, 0.62);
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
/* The empty-state hero glow asset would wash out the wallpaper behind the
|
|
172
|
-
welcome message — dim it so the art stays visible. */
|
|
173
|
-
body[data-dsh-bg] [class*="_heroGlow"] {
|
|
174
|
-
opacity: 0.2;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
171
|
/* ---- Whitelisted chrome buttons --------------------------------------
|
|
178
172
|
These three buttons' official fills ride button-* tokens shared with
|
|
179
173
|
unrelated surfaces, so instead of overriding the tokens they get explicit
|
|
@@ -279,7 +273,7 @@ window.__ModuleLoader__.load({
|
|
|
279
273
|
/* Sidebar build badge: translucent ink already, but it sat directly on
|
|
280
274
|
the wallpaper without frost. Join the shared blur chain under the same
|
|
281
275
|
gate (its theme-aware ink fills from the active block above). */
|
|
282
|
-
body[data-dsh-bg-glass] [class*="
|
|
276
|
+
body[data-dsh-bg-glass] [class*="_buildVersion"] {
|
|
283
277
|
-webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
284
278
|
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
285
279
|
}
|
|
@@ -961,7 +955,9 @@ window.__ModuleLoader__.load({
|
|
|
961
955
|
"background.uploadFailed": "上传失败,请重试",
|
|
962
956
|
"background.saveFailed": "保存失败,请重试",
|
|
963
957
|
"background.timelineEnhance": "会话时间线增强",
|
|
964
|
-
"background.timelineEnhanceNarrowHint": "为官方会话时间线的已加载轮次补上平滑跳转"
|
|
958
|
+
"background.timelineEnhanceNarrowHint": "为官方会话时间线的已加载轮次补上平滑跳转",
|
|
959
|
+
"background.migratedNotice": "已从旧版 dsh 的设置中恢复你的背景设置",
|
|
960
|
+
"background.migratedDismiss": "知道了"
|
|
965
961
|
};
|
|
966
962
|
/** English copy. */
|
|
967
963
|
const en = {
|
|
@@ -990,7 +986,9 @@ window.__ModuleLoader__.load({
|
|
|
990
986
|
"background.uploadFailed": "Upload failed, try again",
|
|
991
987
|
"background.saveFailed": "Save failed, try again",
|
|
992
988
|
"background.timelineEnhance": "Conversation timeline enhancement",
|
|
993
|
-
"background.timelineEnhanceNarrowHint": "Adds smooth jumps for the rail's loaded turns"
|
|
989
|
+
"background.timelineEnhanceNarrowHint": "Adds smooth jumps for the rail's loaded turns",
|
|
990
|
+
"background.migratedNotice": "Restored your background settings from the previous dsh version",
|
|
991
|
+
"background.migratedDismiss": "Got it"
|
|
994
992
|
};
|
|
995
993
|
//#endregion
|
|
996
994
|
//#region node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
|
|
@@ -1046,7 +1044,8 @@ window.__ModuleLoader__.load({
|
|
|
1046
1044
|
if (!body.ok || body.value === void 0) throw new Error("unexpected payload");
|
|
1047
1045
|
this.snapshot = {
|
|
1048
1046
|
status: "ready",
|
|
1049
|
-
value: body.value
|
|
1047
|
+
value: body.value,
|
|
1048
|
+
...body.migrated === void 0 ? {} : { migrated: body.migrated }
|
|
1050
1049
|
};
|
|
1051
1050
|
} catch {
|
|
1052
1051
|
this.snapshot = { status: "error" };
|
|
@@ -1075,7 +1074,8 @@ window.__ModuleLoader__.load({
|
|
|
1075
1074
|
if (seq !== this.saveSeq) return "superseded";
|
|
1076
1075
|
this.snapshot = {
|
|
1077
1076
|
status: "ready",
|
|
1078
|
-
value: body.value
|
|
1077
|
+
value: body.value,
|
|
1078
|
+
...this.snapshot.migrated === void 0 ? {} : { migrated: this.snapshot.migrated }
|
|
1079
1079
|
};
|
|
1080
1080
|
this.notify();
|
|
1081
1081
|
return "ok";
|
|
@@ -1115,7 +1115,7 @@ window.__ModuleLoader__.load({
|
|
|
1115
1115
|
const settingsClient = new SettingsClient();
|
|
1116
1116
|
//#endregion
|
|
1117
1117
|
//#region \0dsh-css:D:\Project\deepseek-harness-background\src\client\SettingsRow.module.css.mjs
|
|
1118
|
-
const css = ".-ozqdq_row{border-bottom:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:14px;padding:16px 0;display:flex}.-ozqdq_header{flex-direction:column;gap:4px;display:flex}.-ozqdq_titleRow{justify-content:space-between;align-items:center;gap:12px;min-height:22px;display:flex}.-ozqdq_clearBtn{border-radius:12px;flex:none;height:24px;padding:0 10px;font-size:12px;line-height:24px}.-ozqdq_title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.-ozqdq_desc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.-ozqdq_preview{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:12px;height:132px;position:relative;overflow:hidden}.-ozqdq_previewImg{object-fit:var(--bg-object-fit,cover);width:100%;height:100%;opacity:var(--bg-opacity,1);filter:blur(var(--bg-wallpaper-blur,0px));transform:scale(var(--bg-wallpaper-scale,1));display:block;position:absolute;inset:0}.-ozqdq_previewScrim{background:rgba(255, 255, 255, var(--bg-scrim,.25));position:absolute;inset:0}body[data-ds-dark-theme] .-ozqdq_previewScrim{background:rgba(0, 0, 0, var(--bg-scrim,.25))}.-ozqdq_previewGlass{background:var(--bg-preview-glass,#ffffff24);width:44%;height:46px;backdrop-filter:blur(var(--bg-glass-blur,16px)) saturate(var(--bg-glass-saturate,1.42)) brightness(var(--bg-glass-brightness,1));border:1px solid #ffffff38;border-radius:10px;justify-content:center;align-items:center;display:flex;position:absolute;bottom:16px;left:16px;box-shadow:inset 0 1px #ffffff4d}.-ozqdq_previewGlassText{color:#0f172ac7;text-shadow:0 1px 2px #fff6;white-space:nowrap;font-size:11px;line-height:1}body[data-ds-dark-theme] .-ozqdq_previewGlassText{color:#ffffffd9;text-shadow:0 1px 2px #00000059}.-ozqdq_previewEmpty{color:var(--dsw-alias-label-tertiary);justify-content:center;align-items:center;font-size:12px;line-height:1.5;display:flex;position:absolute;inset:0}.-ozqdq_section{flex-direction:column;gap:8px;display:flex}.-ozqdq_sectionLabel{color:var(--dsw-alias-label-tertiary);letter-spacing:.01em;font-size:12px;font-weight:500;line-height:18px}.-ozqdq_sourceRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.-ozqdq_urlRow{align-items:center;gap:8px;display:flex}.-ozqdq_urlInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);min-width:0;height:32px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;flex:1;padding:0 10px;font-size:13px;line-height:1.5}.-ozqdq_urlInput::placeholder{color:var(--dsw-alias-label-dimmed)}.-ozqdq_urlInput:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.-ozqdq_hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.-ozqdq_enabledBadge{white-space:nowrap;background:var(--dsw-alias-state-success-tertiary);color:var(--dsw-alias-state-success-primary);border-radius:999px;align-items:center;gap:4px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px;display:inline-flex}.-ozqdq_btn{border:1px solid var(--dsw-alias-border-l2);height:28px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;background:0 0;border-radius:14px;justify-content:center;align-items:center;padding:0 12px;font-size:12px;line-height:18px;transition:background-color .15s,border-color .15s,color .15s;display:inline-flex}.-ozqdq_btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.-ozqdq_btn:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.-ozqdq_btn:disabled{opacity:.45;cursor:default}.-ozqdq_btnPrimary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.-ozqdq_btnPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.-ozqdq_btnDanger{color:var(--dsw-alias-label-error)}.-ozqdq_btnDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.-ozqdq_sliderRow{align-items:center;gap:10px;display:flex}.-ozqdq_sliderLabel{color:var(--dsw-alias-label-primary);flex:0 0 96px;font-size:13px;line-height:1.5}.-ozqdq_slider{-webkit-appearance:none;appearance:none;cursor:pointer;background:0 0;flex:1;min-width:0;height:18px;margin:0}.-ozqdq_slider::-webkit-slider-runnable-track{background:linear-gradient(90deg, var(--dsw-alias-brand-primary) var(--bg-fill,0%), var(--dsw-alias-border-l3) var(--bg-fill,0%));border-radius:2px;height:4px}.-ozqdq_slider::-moz-range-track{background:var(--dsw-alias-border-l3);border-radius:2px;height:4px}.-ozqdq_slider::-moz-range-progress{background:var(--dsw-alias-brand-primary);border-radius:2px;height:4px}.-ozqdq_slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;border:2px solid var(--dsw-alias-brand-primary);width:14px;height:14px;box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent), 0 1px 3px #00000047;background:#fff;border-radius:50%;margin-top:-5px}.-ozqdq_slider::-moz-range-thumb{border:2px solid var(--dsw-alias-brand-primary);width:14px;height:14px;box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent), 0 1px 3px #00000047;background:#fff;border-radius:50%}.-ozqdq_slider:hover::-webkit-slider-thumb{box-shadow:0 0 0 5px color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent), 0 1px 4px #0000004d}.-ozqdq_slider:hover::-moz-range-thumb{box-shadow:0 0 0 5px color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent), 0 1px 4px #0000004d}.-ozqdq_slider:active::-webkit-slider-thumb{box-shadow:0 0 0 7px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent), 0 1px 5px #00000052;transform:scale(1.12)}.-ozqdq_slider:active::-moz-range-thumb{box-shadow:0 0 0 7px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent), 0 1px 5px #00000052;transform:scale(1.12)}.-ozqdq_slider:focus-visible{outline:none}.-ozqdq_slider:focus-visible::-webkit-slider-thumb{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_slider:focus-visible::-moz-range-thumb{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_sliderValue{text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);flex:0 0 52px;font-size:13px;line-height:1.5}.-ozqdq_segRow{align-items:center;gap:10px;display:flex}.-ozqdq_segLabel{color:var(--dsw-alias-label-primary);flex:0 0 96px;font-size:13px;line-height:1.5}.-ozqdq_segGroup{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:9px;flex:1;gap:2px;min-width:0;padding:2px;display:inline-flex}.-ozqdq_segBtn{height:24px;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:7px;flex:1;padding:0 16px;font-size:12px;line-height:24px}.-ozqdq_segBtn:hover:not(.-ozqdq_segActive){color:var(--dsw-alias-label-primary)}.-ozqdq_segActive{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font-weight:500}.-ozqdq_error{color:var(--dsw-alias-label-error);font-size:12px;line-height:1.5}.-ozqdq_clearRow{align-items:center;gap:10px;display:flex}.-ozqdq_switch{background:var(--dsw-alias-border-l3);cursor:pointer;border:none;border-radius:11px;flex:none;width:40px;height:22px;padding:0;transition:background-color .15s;position:relative}.-ozqdq_switch[aria-checked=true]{background:var(--dsw-alias-brand-primary)}.-ozqdq_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_switchKnob{background:#fff;border-radius:50%;width:16px;height:16px;transition:transform .15s;position:absolute;top:3px;left:3px;box-shadow:0 1px 3px #00000047}.-ozqdq_switch[aria-checked=true] .-ozqdq_switchKnob{transform:translate(18px)}@media (prefers-reduced-motion:reduce){.-ozqdq_btn,.-ozqdq_switch,.-ozqdq_switchKnob{transition:none}}";
|
|
1118
|
+
const css = ".-ozqdq_row{border-bottom:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:14px;padding:16px 0;display:flex}.-ozqdq_header{flex-direction:column;gap:4px;display:flex}.-ozqdq_titleRow{justify-content:space-between;align-items:center;gap:12px;min-height:22px;display:flex}.-ozqdq_clearBtn{border-radius:12px;flex:none;height:24px;padding:0 10px;font-size:12px;line-height:24px}.-ozqdq_title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.-ozqdq_desc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.-ozqdq_notice{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;justify-content:space-between;align-items:center;gap:12px;padding:8px 12px;display:flex}.-ozqdq_noticeText{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.-ozqdq_noticeBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-size:12px;line-height:18px;text-decoration:underline}.-ozqdq_noticeBtn:hover{color:var(--dsw-alias-label-primary)}.-ozqdq_preview{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:12px;height:132px;position:relative;overflow:hidden}.-ozqdq_previewImg{object-fit:var(--bg-object-fit,cover);width:100%;height:100%;opacity:var(--bg-opacity,1);filter:blur(var(--bg-wallpaper-blur,0px));transform:scale(var(--bg-wallpaper-scale,1));display:block;position:absolute;inset:0}.-ozqdq_previewScrim{background:rgba(255, 255, 255, var(--bg-scrim,.25));position:absolute;inset:0}body[data-ds-dark-theme] .-ozqdq_previewScrim{background:rgba(0, 0, 0, var(--bg-scrim,.25))}.-ozqdq_previewGlass{background:var(--bg-preview-glass,#ffffff24);width:44%;height:46px;backdrop-filter:blur(var(--bg-glass-blur,16px)) saturate(var(--bg-glass-saturate,1.42)) brightness(var(--bg-glass-brightness,1));border:1px solid #ffffff38;border-radius:10px;justify-content:center;align-items:center;display:flex;position:absolute;bottom:16px;left:16px;box-shadow:inset 0 1px #ffffff4d}.-ozqdq_previewGlassText{color:#0f172ac7;text-shadow:0 1px 2px #fff6;white-space:nowrap;font-size:11px;line-height:1}body[data-ds-dark-theme] .-ozqdq_previewGlassText{color:#ffffffd9;text-shadow:0 1px 2px #00000059}.-ozqdq_previewEmpty{color:var(--dsw-alias-label-tertiary);justify-content:center;align-items:center;font-size:12px;line-height:1.5;display:flex;position:absolute;inset:0}.-ozqdq_section{flex-direction:column;gap:8px;display:flex}.-ozqdq_sectionLabel{color:var(--dsw-alias-label-tertiary);letter-spacing:.01em;font-size:12px;font-weight:500;line-height:18px}.-ozqdq_sourceRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.-ozqdq_urlRow{align-items:center;gap:8px;display:flex}.-ozqdq_urlInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);min-width:0;height:32px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;flex:1;padding:0 10px;font-size:13px;line-height:1.5}.-ozqdq_urlInput::placeholder{color:var(--dsw-alias-label-dimmed)}.-ozqdq_urlInput:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.-ozqdq_hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.-ozqdq_enabledBadge{white-space:nowrap;background:var(--dsw-alias-state-success-tertiary);color:var(--dsw-alias-state-success-primary);border-radius:999px;align-items:center;gap:4px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px;display:inline-flex}.-ozqdq_btn{border:1px solid var(--dsw-alias-border-l2);height:28px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;background:0 0;border-radius:14px;justify-content:center;align-items:center;padding:0 12px;font-size:12px;line-height:18px;transition:background-color .15s,border-color .15s,color .15s;display:inline-flex}.-ozqdq_btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.-ozqdq_btn:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.-ozqdq_btn:disabled{opacity:.45;cursor:default}.-ozqdq_btnPrimary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.-ozqdq_btnPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.-ozqdq_btnDanger{color:var(--dsw-alias-label-error)}.-ozqdq_btnDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.-ozqdq_sliderRow{align-items:center;gap:10px;display:flex}.-ozqdq_sliderLabel{color:var(--dsw-alias-label-primary);flex:0 0 96px;font-size:13px;line-height:1.5}.-ozqdq_slider{-webkit-appearance:none;appearance:none;cursor:pointer;background:0 0;flex:1;min-width:0;height:18px;margin:0}.-ozqdq_slider::-webkit-slider-runnable-track{background:linear-gradient(90deg, var(--dsw-alias-brand-primary) var(--bg-fill,0%), var(--dsw-alias-border-l3) var(--bg-fill,0%));border-radius:2px;height:4px}.-ozqdq_slider::-moz-range-track{background:var(--dsw-alias-border-l3);border-radius:2px;height:4px}.-ozqdq_slider::-moz-range-progress{background:var(--dsw-alias-brand-primary);border-radius:2px;height:4px}.-ozqdq_slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;border:2px solid var(--dsw-alias-brand-primary);width:14px;height:14px;box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent), 0 1px 3px #00000047;background:#fff;border-radius:50%;margin-top:-5px}.-ozqdq_slider::-moz-range-thumb{border:2px solid var(--dsw-alias-brand-primary);width:14px;height:14px;box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent), 0 1px 3px #00000047;background:#fff;border-radius:50%}.-ozqdq_slider:hover::-webkit-slider-thumb{box-shadow:0 0 0 5px color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent), 0 1px 4px #0000004d}.-ozqdq_slider:hover::-moz-range-thumb{box-shadow:0 0 0 5px color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent), 0 1px 4px #0000004d}.-ozqdq_slider:active::-webkit-slider-thumb{box-shadow:0 0 0 7px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent), 0 1px 5px #00000052;transform:scale(1.12)}.-ozqdq_slider:active::-moz-range-thumb{box-shadow:0 0 0 7px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent), 0 1px 5px #00000052;transform:scale(1.12)}.-ozqdq_slider:focus-visible{outline:none}.-ozqdq_slider:focus-visible::-webkit-slider-thumb{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_slider:focus-visible::-moz-range-thumb{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_sliderValue{text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);flex:0 0 52px;font-size:13px;line-height:1.5}.-ozqdq_segRow{align-items:center;gap:10px;display:flex}.-ozqdq_segLabel{color:var(--dsw-alias-label-primary);flex:0 0 96px;font-size:13px;line-height:1.5}.-ozqdq_segGroup{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:9px;flex:1;gap:2px;min-width:0;padding:2px;display:inline-flex}.-ozqdq_segBtn{height:24px;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:7px;flex:1;padding:0 16px;font-size:12px;line-height:24px}.-ozqdq_segBtn:hover:not(.-ozqdq_segActive){color:var(--dsw-alias-label-primary)}.-ozqdq_segActive{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font-weight:500}.-ozqdq_error{color:var(--dsw-alias-label-error);font-size:12px;line-height:1.5}.-ozqdq_clearRow{align-items:center;gap:10px;display:flex}.-ozqdq_switch{background:var(--dsw-alias-border-l3);cursor:pointer;border:none;border-radius:11px;flex:none;width:40px;height:22px;padding:0;transition:background-color .15s;position:relative}.-ozqdq_switch[aria-checked=true]{background:var(--dsw-alias-brand-primary)}.-ozqdq_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_switchKnob{background:#fff;border-radius:50%;width:16px;height:16px;transition:transform .15s;position:absolute;top:3px;left:3px;box-shadow:0 1px 3px #00000047}.-ozqdq_switch[aria-checked=true] .-ozqdq_switchKnob{transform:translate(18px)}@media (prefers-reduced-motion:reduce){.-ozqdq_btn,.-ozqdq_switch,.-ozqdq_switchKnob{transition:none}}";
|
|
1119
1119
|
const tagId = "deepseek-harness-background/SettingsRow.module.css";
|
|
1120
1120
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
1121
1121
|
const tag = document.createElement("style");
|
|
@@ -1125,41 +1125,44 @@ window.__ModuleLoader__.load({
|
|
|
1125
1125
|
document.head.appendChild(tag);
|
|
1126
1126
|
}
|
|
1127
1127
|
var SettingsRow_module_css_default = {
|
|
1128
|
-
"
|
|
1128
|
+
"previewGlassText": "-ozqdq_previewGlassText",
|
|
1129
|
+
"title": "-ozqdq_title",
|
|
1130
|
+
"switchKnob": "-ozqdq_switchKnob",
|
|
1131
|
+
"sliderValue": "-ozqdq_sliderValue",
|
|
1132
|
+
"error": "-ozqdq_error",
|
|
1133
|
+
"urlRow": "-ozqdq_urlRow",
|
|
1134
|
+
"btn": "-ozqdq_btn",
|
|
1135
|
+
"sliderLabel": "-ozqdq_sliderLabel",
|
|
1129
1136
|
"slider": "-ozqdq_slider",
|
|
1130
|
-
"
|
|
1137
|
+
"clearBtn": "-ozqdq_clearBtn",
|
|
1131
1138
|
"preview": "-ozqdq_preview",
|
|
1132
|
-
"
|
|
1133
|
-
"
|
|
1134
|
-
"
|
|
1135
|
-
"previewEmpty": "-ozqdq_previewEmpty",
|
|
1139
|
+
"urlInput": "-ozqdq_urlInput",
|
|
1140
|
+
"segRow": "-ozqdq_segRow",
|
|
1141
|
+
"enabledBadge": "-ozqdq_enabledBadge",
|
|
1136
1142
|
"titleRow": "-ozqdq_titleRow",
|
|
1137
|
-
"
|
|
1138
|
-
"
|
|
1143
|
+
"sectionLabel": "-ozqdq_sectionLabel",
|
|
1144
|
+
"sourceRow": "-ozqdq_sourceRow",
|
|
1139
1145
|
"segBtn": "-ozqdq_segBtn",
|
|
1140
|
-
"previewGlass": "-ozqdq_previewGlass",
|
|
1141
1146
|
"row": "-ozqdq_row",
|
|
1142
|
-
"
|
|
1143
|
-
"previewScrim": "-ozqdq_previewScrim",
|
|
1144
|
-
"switchKnob": "-ozqdq_switchKnob",
|
|
1145
|
-
"urlInput": "-ozqdq_urlInput",
|
|
1146
|
-
"btnPrimary": "-ozqdq_btnPrimary",
|
|
1147
|
-
"segActive": "-ozqdq_segActive",
|
|
1148
|
-
"segLabel": "-ozqdq_segLabel",
|
|
1149
|
-
"urlRow": "-ozqdq_urlRow",
|
|
1150
|
-
"previewGlassText": "-ozqdq_previewGlassText",
|
|
1151
|
-
"sectionLabel": "-ozqdq_sectionLabel",
|
|
1152
|
-
"enabledBadge": "-ozqdq_enabledBadge",
|
|
1153
|
-
"segRow": "-ozqdq_segRow",
|
|
1147
|
+
"noticeBtn": "-ozqdq_noticeBtn",
|
|
1154
1148
|
"previewImg": "-ozqdq_previewImg",
|
|
1155
|
-
"
|
|
1156
|
-
"
|
|
1149
|
+
"switch": "-ozqdq_switch",
|
|
1150
|
+
"btnDanger": "-ozqdq_btnDanger",
|
|
1151
|
+
"segLabel": "-ozqdq_segLabel",
|
|
1152
|
+
"previewGlass": "-ozqdq_previewGlass",
|
|
1153
|
+
"previewEmpty": "-ozqdq_previewEmpty",
|
|
1154
|
+
"noticeText": "-ozqdq_noticeText",
|
|
1157
1155
|
"header": "-ozqdq_header",
|
|
1158
|
-
"clearRow": "-ozqdq_clearRow",
|
|
1159
|
-
"desc": "-ozqdq_desc",
|
|
1160
1156
|
"section": "-ozqdq_section",
|
|
1157
|
+
"previewScrim": "-ozqdq_previewScrim",
|
|
1158
|
+
"desc": "-ozqdq_desc",
|
|
1159
|
+
"notice": "-ozqdq_notice",
|
|
1160
|
+
"sliderRow": "-ozqdq_sliderRow",
|
|
1161
|
+
"segGroup": "-ozqdq_segGroup",
|
|
1161
1162
|
"hint": "-ozqdq_hint",
|
|
1162
|
-
"
|
|
1163
|
+
"btnPrimary": "-ozqdq_btnPrimary",
|
|
1164
|
+
"segActive": "-ozqdq_segActive",
|
|
1165
|
+
"clearRow": "-ozqdq_clearRow"
|
|
1163
1166
|
};
|
|
1164
1167
|
//#endregion
|
|
1165
1168
|
//#region src/client/SettingsRow.tsx
|
|
@@ -1331,6 +1334,7 @@ window.__ModuleLoader__.load({
|
|
|
1331
1334
|
const [uploading, setUploading] = (0, react.useState)(false);
|
|
1332
1335
|
const [urlText, setUrlText] = (0, react.useState)("");
|
|
1333
1336
|
const [error, setError] = (0, react.useState)("");
|
|
1337
|
+
const [noticeDismissed, setNoticeDismissed] = (0, react.useState)(false);
|
|
1334
1338
|
const fileInput = (0, react.useRef)(null);
|
|
1335
1339
|
const previewRef = (0, react.useRef)(null);
|
|
1336
1340
|
const draftRef = (0, react.useRef)(DEFAULTS);
|
|
@@ -1513,6 +1517,19 @@ window.__ModuleLoader__.load({
|
|
|
1513
1517
|
children: t("background.description")
|
|
1514
1518
|
})]
|
|
1515
1519
|
}),
|
|
1520
|
+
snapshot.migrated !== void 0 && !noticeDismissed && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1521
|
+
className: SettingsRow_module_css_default.notice,
|
|
1522
|
+
role: "status",
|
|
1523
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1524
|
+
className: SettingsRow_module_css_default.noticeText,
|
|
1525
|
+
children: t("background.migratedNotice")
|
|
1526
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1527
|
+
type: "button",
|
|
1528
|
+
className: SettingsRow_module_css_default.noticeBtn,
|
|
1529
|
+
onClick: () => setNoticeDismissed(true),
|
|
1530
|
+
children: t("background.migratedDismiss")
|
|
1531
|
+
})]
|
|
1532
|
+
}),
|
|
1516
1533
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1517
1534
|
className: SettingsRow_module_css_default.preview,
|
|
1518
1535
|
ref: previewRef,
|
|
@@ -1695,123 +1712,445 @@ window.__ModuleLoader__.load({
|
|
|
1695
1712
|
]
|
|
1696
1713
|
});
|
|
1697
1714
|
}
|
|
1698
|
-
//#endregion
|
|
1699
|
-
//#region src/client/timeline/jump.ts
|
|
1700
|
-
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1701
|
-
/** How long one jump may keep paging older history before giving up. */
|
|
1702
|
-
const JUMP_PAGE_DEADLINE_MS = 3e4;
|
|
1703
|
-
/** How long to wait for the chat view to commit the target row. */
|
|
1704
|
-
const JUMP_ROW_WAIT_MS = 3e3;
|
|
1705
|
-
/** Settle between consecutive pages so a resolver without loadingOlder cannot hot-spin the loop. */
|
|
1706
|
-
const PAGE_COOLDOWN_MS = 50;
|
|
1707
|
-
/** Distance from the scrollport top the official ChatView lands a jumped row at. */
|
|
1708
|
-
const OFFICIAL_LANDING_INSET_PX = 24;
|
|
1709
|
-
/** Distances under this snap instead of animating. */
|
|
1710
|
-
const MIN_GLIDE_DISTANCE_PX = 8;
|
|
1711
|
-
/** Glide duration floor / cap — a far-boundary jump stays a tangible slide. */
|
|
1712
|
-
const MIN_GLIDE_MS = 280;
|
|
1713
|
-
const MAX_GLIDE_MS = 900;
|
|
1714
|
-
/** Every mounted conversation scrollport (multi-column layouts have several). */
|
|
1715
|
-
function conversationScrollports() {
|
|
1716
|
-
return Array.from(document.querySelectorAll("[data-conversation-scroll]"));
|
|
1717
|
-
}
|
|
1718
1715
|
/**
|
|
1719
|
-
*
|
|
1720
|
-
*
|
|
1721
|
-
*
|
|
1722
|
-
* node store, unreachable from `session.getSnapshot()` — its SessionSnapshot
|
|
1723
|
-
* faces no node table at all), but the rendered DOM is the ground truth both
|
|
1724
|
-
* share, so a jump probes it FIRST before deciding whether paging is needed.
|
|
1725
|
-
* @param anchorKey - chat anchor key of the target row.
|
|
1716
|
+
* The rail's own inset, read off the custom property the official component
|
|
1717
|
+
* publishes (`--turn-rail-inset`). Falls back to the official constant.
|
|
1718
|
+
* @param rail - the rail element (or null).
|
|
1726
1719
|
*/
|
|
1727
|
-
function
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
return
|
|
1720
|
+
function railInsetOf(rail) {
|
|
1721
|
+
if (rail === null) return 6;
|
|
1722
|
+
const px = Number.parseFloat(rail.style.getPropertyValue("--turn-rail-inset"));
|
|
1723
|
+
return Number.isFinite(px) && px >= 0 ? px : 6;
|
|
1731
1724
|
}
|
|
1732
1725
|
/**
|
|
1733
|
-
*
|
|
1734
|
-
*
|
|
1735
|
-
*
|
|
1736
|
-
*
|
|
1737
|
-
*
|
|
1738
|
-
*
|
|
1739
|
-
* @param
|
|
1726
|
+
* Whether `rail` is the frame-style rail (dsh >= 0.1.2-alpha.3): marks sit
|
|
1727
|
+
* at a fixed pitch and scroll INSIDE the frame, whose scroller offset the
|
|
1728
|
+
* component publishes inline as `--turn-scroll-top` — every render, resting
|
|
1729
|
+
* at `0px` included. The compressing alpha.1/2 rail never publishes that
|
|
1730
|
+
* property, so its presence is the generation test. The legacy port paints
|
|
1731
|
+
* its own `--dsbt-*` properties and can never trip this.
|
|
1732
|
+
* @param rail - the rail element (or null).
|
|
1740
1733
|
*/
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
const deadline = Date.now() + timeoutMs;
|
|
1744
|
-
for (;;) {
|
|
1745
|
-
for (const scrollport of conversationScrollports()) {
|
|
1746
|
-
const row = scrollport.querySelector(selector);
|
|
1747
|
-
if (row !== null) return row;
|
|
1748
|
-
}
|
|
1749
|
-
if (Date.now() >= deadline) return null;
|
|
1750
|
-
await delay(40);
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
/** Maximum scrollable offset of a conversation scrollport. */
|
|
1754
|
-
function scrollFloorOf(el) {
|
|
1755
|
-
return Math.max(0, el.scrollHeight - el.clientHeight);
|
|
1734
|
+
function isFrameRail(rail) {
|
|
1735
|
+
return rail !== null && rail.style.getPropertyValue("--turn-scroll-top") !== "";
|
|
1756
1736
|
}
|
|
1757
1737
|
/**
|
|
1758
|
-
*
|
|
1759
|
-
*
|
|
1760
|
-
* the
|
|
1761
|
-
*
|
|
1762
|
-
*
|
|
1738
|
+
* Resolve the entry under a pointer Y on the frame-style rail — the official
|
|
1739
|
+
* alpha.3 geometry verbatim (`itemAtPointer` in that generation's
|
|
1740
|
+
* TurnNavigator.tsx): marks live in the scrolled ladder at a fixed 10px
|
|
1741
|
+
* pitch, so the click offset adds the scroller offset. Read off the published
|
|
1742
|
+
* `--turn-scroll-top` var rather than the scroller element itself — the same
|
|
1743
|
+
* metric the official preview positioning uses, so any one-frame render lag
|
|
1744
|
+
* affects both identically.
|
|
1745
|
+
* @param count - number of entries the rail shows.
|
|
1746
|
+
* @param rail - the rail element.
|
|
1747
|
+
* @param clientY - pointer viewport Y.
|
|
1763
1748
|
*/
|
|
1764
|
-
function
|
|
1765
|
-
const
|
|
1766
|
-
const
|
|
1767
|
-
|
|
1768
|
-
|
|
1749
|
+
function frameIndexAtPointer(count, rail, clientY) {
|
|
1750
|
+
const rect = rail.getBoundingClientRect();
|
|
1751
|
+
const inset = railInsetOf(rail);
|
|
1752
|
+
const raw = Number.parseFloat(rail.style.getPropertyValue("--turn-scroll-top"));
|
|
1753
|
+
const scrollTop = Number.isFinite(raw) ? raw : 0;
|
|
1754
|
+
const offset = clientY - rect.top + scrollTop - inset;
|
|
1755
|
+
return Math.max(0, Math.min(count - 1, Math.round(offset / 10)));
|
|
1769
1756
|
}
|
|
1770
1757
|
/**
|
|
1771
|
-
*
|
|
1772
|
-
*
|
|
1773
|
-
*
|
|
1774
|
-
*
|
|
1775
|
-
*
|
|
1758
|
+
* Resolve the gesture that produced a rail click to an entry index.
|
|
1759
|
+
*
|
|
1760
|
+
* Two rail generations answer here, told apart by capability:
|
|
1761
|
+
*
|
|
1762
|
+
* - The virtualized rail (dsh 0.1.7+) tags every RENDERED mark with its own
|
|
1763
|
+
* ladder index (`data-index`), so the gesture resolves by data. This is the
|
|
1764
|
+
* robust path: the virtualizer mounts only a window of marks, and their DOM
|
|
1765
|
+
* order is not the ladder order.
|
|
1766
|
+
* - The frame-style rail tags nothing and paints marks with
|
|
1767
|
+
* `pointer-events: none`, so a MOUSE gesture arrives at the `<nav>` with
|
|
1768
|
+
* coordinates and resolves through the rail geometry over the published
|
|
1769
|
+
* `--turn-scroll-top`; a KEYBOARD gesture activates the focused mark button,
|
|
1770
|
+
* recovered by its position among its siblings.
|
|
1771
|
+
*
|
|
1772
|
+
* Returns -1 when neither applies (a keyboard target outside the rail, or a
|
|
1773
|
+
* rail of an unsupported generation, which the kernel's own handler then owns).
|
|
1774
|
+
*
|
|
1775
|
+
* Assumption: the mark ticks are the ONLY buttons under the rail — the
|
|
1776
|
+
* official rail renders marks exclusively and its preview tooltip carries
|
|
1777
|
+
* none. A future non-mark control added inside the rail would shift the
|
|
1778
|
+
* button index and must revisit this.
|
|
1779
|
+
*
|
|
1780
|
+
* @param rail - the rail element.
|
|
1781
|
+
* @param event - the click event.
|
|
1776
1782
|
*/
|
|
1777
|
-
function
|
|
1778
|
-
const
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1783
|
+
function indexForEvent(rail, event) {
|
|
1784
|
+
const target = event.target;
|
|
1785
|
+
if (target instanceof Element) {
|
|
1786
|
+
const mark = target.closest("[data-index]");
|
|
1787
|
+
if (mark !== null && rail.contains(mark)) {
|
|
1788
|
+
const tagged = Number(mark.getAttribute("data-index"));
|
|
1789
|
+
if (Number.isSafeInteger(tagged) && tagged >= 0) return tagged;
|
|
1790
|
+
}
|
|
1791
|
+
const button = target.closest("button");
|
|
1792
|
+
if (button !== null && rail.contains(button)) {
|
|
1793
|
+
const index = Array.from(rail.querySelectorAll("button")).indexOf(button);
|
|
1794
|
+
if (index >= 0) return index;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
const count = rail.querySelectorAll("button").length;
|
|
1798
|
+
if (count === 0) return -1;
|
|
1799
|
+
return isFrameRail(rail) ? frameIndexAtPointer(count, rail, event.clientY) : -1;
|
|
1782
1800
|
}
|
|
1783
1801
|
/**
|
|
1784
|
-
*
|
|
1785
|
-
*
|
|
1786
|
-
*
|
|
1787
|
-
* @param
|
|
1802
|
+
* Validate the official navigation array crossing the slot boundary. The
|
|
1803
|
+
* value is produced by the kernel and may be absent, partial, or from an
|
|
1804
|
+
* older build, so every field is checked before use.
|
|
1805
|
+
* @param value - the raw `useChat(s => s.navigation.items())` result.
|
|
1788
1806
|
*/
|
|
1789
|
-
function
|
|
1790
|
-
if (!
|
|
1791
|
-
|
|
1807
|
+
function normalizeNavigationItems(value) {
|
|
1808
|
+
if (!Array.isArray(value)) return [];
|
|
1809
|
+
const out = [];
|
|
1810
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1811
|
+
for (const item of value) {
|
|
1812
|
+
if (item === null || typeof item !== "object") continue;
|
|
1813
|
+
const rec = item;
|
|
1814
|
+
if (typeof rec.turn !== "number" || !Number.isFinite(rec.turn)) continue;
|
|
1815
|
+
if (typeof rec.anchorKey !== "string" || rec.anchorKey === "") continue;
|
|
1816
|
+
if (seen.has(rec.turn)) continue;
|
|
1817
|
+
seen.add(rec.turn);
|
|
1818
|
+
out.push({
|
|
1819
|
+
turn: rec.turn,
|
|
1820
|
+
anchorKey: rec.anchorKey,
|
|
1821
|
+
prompt: typeof rec.prompt === "string" ? rec.prompt : "",
|
|
1822
|
+
response: typeof rec.response === "string" ? rec.response : ""
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
return out;
|
|
1792
1826
|
}
|
|
1793
1827
|
/**
|
|
1794
|
-
*
|
|
1795
|
-
*
|
|
1796
|
-
*
|
|
1797
|
-
*
|
|
1798
|
-
*
|
|
1799
|
-
*
|
|
1800
|
-
*
|
|
1801
|
-
* @param
|
|
1828
|
+
* Validate one `turnOutline` wire value into its entries. The value is the
|
|
1829
|
+
* host projection's wire view (an array of `{ turn, seq, prompt, response }`
|
|
1830
|
+
* — see `dsh-session-turn-outline`) read through `useProjection`, so it may
|
|
1831
|
+
* be absent (pre-baseline, or an older kernel that never registers the key —
|
|
1832
|
+
* which reads `undefined`), partial, or damaged. `turn` and `seq` are the
|
|
1833
|
+
* load-bearing fields — a mark cannot exist or page without them — and a
|
|
1834
|
+
* damaged one drops the entry; the previews are decorative and degrade to ''.
|
|
1835
|
+
* @param value - the raw `useProjection('turnOutline')` value.
|
|
1802
1836
|
*/
|
|
1803
|
-
function
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1837
|
+
function normalizeOutlineItems(value) {
|
|
1838
|
+
if (!Array.isArray(value)) return [];
|
|
1839
|
+
const out = [];
|
|
1840
|
+
for (const item of value) {
|
|
1841
|
+
if (item === null || typeof item !== "object") continue;
|
|
1842
|
+
const rec = item;
|
|
1843
|
+
if (typeof rec.turn !== "number" || !Number.isSafeInteger(rec.turn) || rec.turn < 0) continue;
|
|
1844
|
+
if (typeof rec.seq !== "number" || !Number.isSafeInteger(rec.seq) || rec.seq < 0) continue;
|
|
1845
|
+
out.push({
|
|
1846
|
+
turn: rec.turn,
|
|
1847
|
+
seq: rec.seq,
|
|
1848
|
+
prompt: typeof rec.prompt === "string" ? rec.prompt : "",
|
|
1849
|
+
response: typeof rec.response === "string" ? rec.response : ""
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
return out;
|
|
1810
1853
|
}
|
|
1811
1854
|
/**
|
|
1812
|
-
*
|
|
1813
|
-
*
|
|
1814
|
-
*
|
|
1855
|
+
* Rebuild the rail's full mark ladder exactly as the official ChatView does
|
|
1856
|
+
* (a mirror of `mergeTurnRailItems` in dsh-client-ui-chat): outline entries
|
|
1857
|
+
* seed every known turn as unloaded, loaded items then override with their
|
|
1858
|
+
* anchor key and richer previews, ascending by turn. The enhancer maps a
|
|
1859
|
+
* click to a mark by INDEX, so its ladder must match the RENDERED marks —
|
|
1860
|
+
* which are exactly this merge on an alpha.3 kernel; without the outline
|
|
1861
|
+
* value the merge degrades to the loaded items (the alpha.1/2 ladder).
|
|
1862
|
+
* @param loaded - the validated loaded-window items.
|
|
1863
|
+
* @param outline - the raw turnOutline wire value.
|
|
1864
|
+
*/
|
|
1865
|
+
function mergeRailItems(loaded, outline) {
|
|
1866
|
+
const byTurn = /* @__PURE__ */ new Map();
|
|
1867
|
+
for (const entry of normalizeOutlineItems(outline)) byTurn.set(entry.turn, {
|
|
1868
|
+
turn: entry.turn,
|
|
1869
|
+
prompt: entry.prompt,
|
|
1870
|
+
response: entry.response
|
|
1871
|
+
});
|
|
1872
|
+
for (const item of loaded) {
|
|
1873
|
+
const preview = byTurn.get(item.turn);
|
|
1874
|
+
byTurn.set(item.turn, {
|
|
1875
|
+
turn: item.turn,
|
|
1876
|
+
prompt: item.prompt !== "" ? item.prompt : preview?.prompt ?? "",
|
|
1877
|
+
response: item.response !== "" ? item.response : preview?.response ?? "",
|
|
1878
|
+
anchorKey: item.anchorKey
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
if (byTurn.size === 0) return [];
|
|
1882
|
+
return [...byTurn.values()].sort((left, right) => left.turn - right.turn);
|
|
1883
|
+
}
|
|
1884
|
+
//#endregion
|
|
1885
|
+
//#region src/client/timeline/official-enhance.tsx
|
|
1886
|
+
/**
|
|
1887
|
+
* Official-rail enhancer — behaviour-only improvements to the kernel's own
|
|
1888
|
+
* turn navigator (dsh >= 0.1.2-rc.1, `TurnNavigator` in dsh-client-ui-chat).
|
|
1889
|
+
*
|
|
1890
|
+
* This component renders NOTHING. It borrows the official rail exactly as the
|
|
1891
|
+
* kernel painted it and fixes its behaviour, scoped to what the running rail
|
|
1892
|
+
* generation still lacks — told apart by capability, never by version compare:
|
|
1893
|
+
*
|
|
1894
|
+
* Smooth jump. The official `navigateToTurn` is one assignment
|
|
1895
|
+
* (`el.scrollTop += flowTop(row, el) - 24`); a click across a long transcript
|
|
1896
|
+
* teleports. Clicks on LOADED marks are intercepted in the capture phase and
|
|
1897
|
+
* re-run through the shared glide instead. Both generations also render marks
|
|
1898
|
+
* for turns OUTSIDE the loaded window (fed by the whole-log `turnOutline`
|
|
1899
|
+
* projection); those jumps page history through the kernel's own loadThrough
|
|
1900
|
+
* machinery — marks without an anchor key are left to it, and the click index
|
|
1901
|
+
* is resolved against the same merged ladder the kernel renders. While one of
|
|
1902
|
+
* those kernel jumps is still paging (a mark pulses `aria-busy`), the plugin
|
|
1903
|
+
* stands down entirely: the kernel's loaded branch cancels its own pending
|
|
1904
|
+
* jump before landing, and an interception would bypass that cancellation.
|
|
1905
|
+
*
|
|
1906
|
+
* Two rail generations are supported, discovered by {@link findOfficialRail}:
|
|
1907
|
+
* the frame-style rail of dsh 0.1.2-rc.1 … 0.1.6 (inline frame metrics, marks
|
|
1908
|
+
* mapped by geometry) and the virtualized rail of dsh 0.1.7+ (no inline
|
|
1909
|
+
* metrics, marks tagged `data-index`, only a window of them mounted at once).
|
|
1910
|
+
*
|
|
1911
|
+
* State-ownership note (why intercepting the official click loses nothing):
|
|
1912
|
+
* the kernel's scrollport (`data-conversation-scroll`, ChatView's
|
|
1913
|
+
* `onScrollRef` handler) treats every unaccounted scrollTop assignment as a
|
|
1914
|
+
* reader move and on each frame (a) flips its own at-bottom machinery,
|
|
1915
|
+
* (b) `chatScroll.save(position)` for view-tab restore, and (c) re-derives
|
|
1916
|
+
* the rail's active mark. The glide animates the real scrollport, so all three
|
|
1917
|
+
* keep converging to the target row while it runs — the only thing the
|
|
1918
|
+
* interception skips is the official handler's synchronous active-mark
|
|
1919
|
+
* assignment, which its own scroll-driven derivation re-settles by the time
|
|
1920
|
+
* the glide lands.
|
|
1921
|
+
*
|
|
1922
|
+
* The jump goes through the shared backend (jump.ts).
|
|
1923
|
+
*
|
|
1924
|
+
* Interception notes: React 18 dispatches `onClick` from a listener on the
|
|
1925
|
+
* root container during the BUBBLE phase. A capture listener on the rail
|
|
1926
|
+
* itself therefore runs first, and `stopImmediatePropagation()` there prevents
|
|
1927
|
+
* the event from ever reaching React's root — the official handler never
|
|
1928
|
+
* fires. The frame-style rail owns pointer input for its whole column (marks
|
|
1929
|
+
* carry `pointer-events: none`); the virtualized rail's marks are real
|
|
1930
|
+
* buttons. Both arrive at the same capture listener on the rail.
|
|
1931
|
+
*
|
|
1932
|
+
* Nothing here touches the official DOM: no nodes are injected into a
|
|
1933
|
+
* React-owned subtree and no official class or paint is overridden.
|
|
1934
|
+
*/
|
|
1935
|
+
/**
|
|
1936
|
+
* Find the official turn rail, in either supported generation.
|
|
1937
|
+
*
|
|
1938
|
+
* Identity comes from the SEAT, not from a rendered mark: the rail is the only
|
|
1939
|
+
* `<nav>` inside the conversation scrollport, and dsh 0.1.7's virtualizer
|
|
1940
|
+
* mounts its marks lazily — measured on the real rail, the `<nav>` exists with
|
|
1941
|
+
* an EMPTY marks container until the virtualizer has layout. Requiring a
|
|
1942
|
+
* rendered `button[data-index]` would therefore miss the rail exactly when it
|
|
1943
|
+
* first appears.
|
|
1944
|
+
*
|
|
1945
|
+
* The generation is then told apart by capability where it matters: the click
|
|
1946
|
+
* resolver reads a mark's own `data-index`, and only falls back to the legacy
|
|
1947
|
+
* frame geometry for a rail that publishes `--turn-scroll-top`.
|
|
1948
|
+
* @param root - the tree to search (defaults to the document).
|
|
1949
|
+
* @returns the rail element, or null when the conversation shows none.
|
|
1950
|
+
*/
|
|
1951
|
+
function findOfficialRail(root = document) {
|
|
1952
|
+
return root.querySelector("[data-conversation-scroll] nav");
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Rails claimed by an enhancer instance, keyed by the owning instance's
|
|
1956
|
+
* claim token. One session-scoped enhancer mounts per session; without a
|
|
1957
|
+
* claim two instances would both attach capture listeners to the FIRST rail
|
|
1958
|
+
* in the document (multi-column layouts) and one of them — not necessarily
|
|
1959
|
+
* the one owning the reader's column — would win the click. The token
|
|
1960
|
+
* distinguishes "already mine" (the poll keeps it) from "someone else's"
|
|
1961
|
+
* (this instance must stand down). Weak: a replaced rail element disappears
|
|
1962
|
+
* as soon as the browser collects it, so remounts need no bookkeeping.
|
|
1963
|
+
*/
|
|
1964
|
+
const claimedRails = /* @__PURE__ */ new WeakMap();
|
|
1965
|
+
/** How often the official rail is looked for (it mounts with the chat view). */
|
|
1966
|
+
const RAIL_POLL_MS = 400;
|
|
1967
|
+
/**
|
|
1968
|
+
* The enhancer: no visual output, only the behaviour fixes the running rail
|
|
1969
|
+
* generation lacks.
|
|
1970
|
+
* @param props - dock slot props plus the enabled gate.
|
|
1971
|
+
* @returns null (always).
|
|
1972
|
+
*/
|
|
1973
|
+
function OfficialTimelineEnhancer(props) {
|
|
1974
|
+
const { jumpToAnchor, useChat, useProjection, enabled } = props;
|
|
1975
|
+
const ladder = mergeRailItems(normalizeNavigationItems(useChat === void 0 ? void 0 : useChat((snapshot) => {
|
|
1976
|
+
const nav = snapshot?.navigation;
|
|
1977
|
+
return typeof nav?.items === "function" ? nav.items() : void 0;
|
|
1978
|
+
})), useProjection === void 0 ? void 0 : useProjection("turnOutline"));
|
|
1979
|
+
const [rail, setRail] = react.default.useState(null);
|
|
1980
|
+
const ladderRef = react.default.useRef(ladder);
|
|
1981
|
+
ladderRef.current = ladder;
|
|
1982
|
+
const railRef = react.default.useRef(rail);
|
|
1983
|
+
railRef.current = rail;
|
|
1984
|
+
const jumpToAnchorRef = react.default.useRef(jumpToAnchor);
|
|
1985
|
+
jumpToAnchorRef.current = jumpToAnchor;
|
|
1986
|
+
const claimToken = react.default.useMemo(() => Symbol("dsh-bg-timeline-claim"), []);
|
|
1987
|
+
react.default.useEffect(() => {
|
|
1988
|
+
const check = () => {
|
|
1989
|
+
const found = findOfficialRail();
|
|
1990
|
+
if (found === null) {
|
|
1991
|
+
setRail(null);
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
const owner = claimedRails.get(found);
|
|
1995
|
+
if (owner !== void 0 && owner !== claimToken) {
|
|
1996
|
+
setRail(null);
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
claimedRails.set(found, claimToken);
|
|
2000
|
+
setRail(found);
|
|
2001
|
+
};
|
|
2002
|
+
check();
|
|
2003
|
+
const timer = window.setInterval(check, RAIL_POLL_MS);
|
|
2004
|
+
return () => {
|
|
2005
|
+
window.clearInterval(timer);
|
|
2006
|
+
if (railRef.current !== null) claimedRails.delete(railRef.current);
|
|
2007
|
+
};
|
|
2008
|
+
}, [claimToken]);
|
|
2009
|
+
const navigate = react.default.useCallback((item) => {
|
|
2010
|
+
if (item.anchorKey === void 0) return;
|
|
2011
|
+
const jump = jumpToAnchorRef.current;
|
|
2012
|
+
if (jump === void 0) return;
|
|
2013
|
+
jump(item.anchorKey).catch(() => {});
|
|
2014
|
+
}, []);
|
|
2015
|
+
react.default.useEffect(() => {
|
|
2016
|
+
if (!enabled || rail === null) return;
|
|
2017
|
+
const onClick = (event) => {
|
|
2018
|
+
if (rail.querySelector("[aria-busy=\"true\"]") !== null) return;
|
|
2019
|
+
const index = indexForEvent(rail, event);
|
|
2020
|
+
const item = ladderRef.current[index];
|
|
2021
|
+
if (item === void 0 || item.anchorKey === void 0) return;
|
|
2022
|
+
event.preventDefault();
|
|
2023
|
+
event.stopImmediatePropagation();
|
|
2024
|
+
navigate(item);
|
|
2025
|
+
};
|
|
2026
|
+
rail.addEventListener("click", onClick, true);
|
|
2027
|
+
return () => {
|
|
2028
|
+
rail.removeEventListener("click", onClick, true);
|
|
2029
|
+
};
|
|
2030
|
+
}, [
|
|
2031
|
+
enabled,
|
|
2032
|
+
rail,
|
|
2033
|
+
navigate
|
|
2034
|
+
]);
|
|
2035
|
+
return null;
|
|
2036
|
+
}
|
|
2037
|
+
//#endregion
|
|
2038
|
+
//#region src/client/timeline/jump.ts
|
|
2039
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2040
|
+
/** How long one jump may keep paging older history before giving up. */
|
|
2041
|
+
const JUMP_PAGE_DEADLINE_MS = 3e4;
|
|
2042
|
+
/** How long to wait for the chat view to commit the target row. */
|
|
2043
|
+
const JUMP_ROW_WAIT_MS = 3e3;
|
|
2044
|
+
/** Settle between consecutive pages so a resolver without loadingOlder cannot hot-spin the loop. */
|
|
2045
|
+
const PAGE_COOLDOWN_MS = 50;
|
|
2046
|
+
/** Distance from the scrollport top the official ChatView lands a jumped row at. */
|
|
2047
|
+
const OFFICIAL_LANDING_INSET_PX = 24;
|
|
2048
|
+
/** Distances under this snap instead of animating. */
|
|
2049
|
+
const MIN_GLIDE_DISTANCE_PX = 8;
|
|
2050
|
+
/** Glide duration floor / cap — a far-boundary jump stays a tangible slide. */
|
|
2051
|
+
const MIN_GLIDE_MS = 280;
|
|
2052
|
+
const MAX_GLIDE_MS = 900;
|
|
2053
|
+
/** Every mounted conversation scrollport (multi-column layouts have several). */
|
|
2054
|
+
function conversationScrollports() {
|
|
2055
|
+
return Array.from(document.querySelectorAll("[data-conversation-scroll]"));
|
|
2056
|
+
}
|
|
2057
|
+
/**
|
|
2058
|
+
* Whether the target chat row is committed in ANY mounted conversation
|
|
2059
|
+
* scrollport. Kernel generations disagree on where the node table lives
|
|
2060
|
+
* (0.1.1: `SessionSnapshot.chat.nodes`; 0.1.2+: the `useChat` ChatSnapshot
|
|
2061
|
+
* node store, unreachable from `session.getSnapshot()` — its SessionSnapshot
|
|
2062
|
+
* faces no node table at all), but the rendered DOM is the ground truth both
|
|
2063
|
+
* share, so a jump probes it FIRST before deciding whether paging is needed.
|
|
2064
|
+
* @param anchorKey - chat anchor key of the target row.
|
|
2065
|
+
*/
|
|
2066
|
+
function rowRenderedInDom(anchorKey) {
|
|
2067
|
+
const selector = `[data-chat-anchor-key="${CSS.escape(anchorKey)}"]`;
|
|
2068
|
+
for (const scrollport of conversationScrollports()) if (scrollport.querySelector(selector) !== null) return true;
|
|
2069
|
+
return false;
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Poll the conversation DOM until the target row is committed. The session
|
|
2073
|
+
* store updates BEFORE React renders the prepended page, so the old one-shot
|
|
2074
|
+
* query right after loadOlder() resolved could miss the row and silently
|
|
2075
|
+
* give up — the reported "click does nothing until the conversation is
|
|
2076
|
+
* nudged (scroll/click)" behavior.
|
|
2077
|
+
* @param key - chat anchor key to look for.
|
|
2078
|
+
* @param timeoutMs - polling budget.
|
|
2079
|
+
*/
|
|
2080
|
+
async function waitForChatRow(key, timeoutMs) {
|
|
2081
|
+
const selector = `[data-chat-anchor-key="${CSS.escape(key)}"]`;
|
|
2082
|
+
const deadline = Date.now() + timeoutMs;
|
|
2083
|
+
for (;;) {
|
|
2084
|
+
for (const scrollport of conversationScrollports()) {
|
|
2085
|
+
const row = scrollport.querySelector(selector);
|
|
2086
|
+
if (row !== null) return row;
|
|
2087
|
+
}
|
|
2088
|
+
if (Date.now() >= deadline) return null;
|
|
2089
|
+
await delay(40);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
/** Maximum scrollable offset of a conversation scrollport. */
|
|
2093
|
+
function scrollFloorOf(el) {
|
|
2094
|
+
return Math.max(0, el.scrollHeight - el.clientHeight);
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* Destination scrollTop that centers `row` inside `el` — the same geometry
|
|
2098
|
+
* as scrollIntoView({ block: 'center' }), computed directly so the glide owns
|
|
2099
|
+
* the scroll position instead of the browser.
|
|
2100
|
+
* @param el - the conversation scrollport.
|
|
2101
|
+
* @param row - the target chat row.
|
|
2102
|
+
*/
|
|
2103
|
+
function centeredScrollTopFor(el, row) {
|
|
2104
|
+
const floor = scrollFloorOf(el);
|
|
2105
|
+
const target = el.scrollTop + (row.getBoundingClientRect().top - el.getBoundingClientRect().top) - (el.clientHeight - row.offsetHeight) / 2;
|
|
2106
|
+
if (!Number.isFinite(target)) return el.scrollTop;
|
|
2107
|
+
return Math.max(0, Math.min(floor, target));
|
|
2108
|
+
}
|
|
2109
|
+
/**
|
|
2110
|
+
* Destination scrollTop the official ChatView uses for a navigation jump:
|
|
2111
|
+
* the row's top minus a 24px inset. Kept as a named resolver so the enhancer
|
|
2112
|
+
* lands exactly where the official would — only animated instead of assigned.
|
|
2113
|
+
* @param el - the conversation scrollport.
|
|
2114
|
+
* @param row - the target chat row.
|
|
2115
|
+
*/
|
|
2116
|
+
function officialTargetTopFor(el, row) {
|
|
2117
|
+
const floor = scrollFloorOf(el);
|
|
2118
|
+
const target = el.scrollTop + (row.getBoundingClientRect().top - el.getBoundingClientRect().top) - OFFICIAL_LANDING_INSET_PX;
|
|
2119
|
+
if (!Number.isFinite(target)) return el.scrollTop;
|
|
2120
|
+
return Math.max(0, Math.min(floor, target));
|
|
2121
|
+
}
|
|
2122
|
+
/**
|
|
2123
|
+
* Glide duration for a distance: longer slides take longer, bounded to
|
|
2124
|
+
* [MIN_GLIDE_MS, MAX_GLIDE_MS] (a deep-history jump reads as a deliberate
|
|
2125
|
+
* glide, not a sluggish race).
|
|
2126
|
+
* @param distance - px to travel.
|
|
2127
|
+
*/
|
|
2128
|
+
function glideDurationFor(distance) {
|
|
2129
|
+
if (!Number.isFinite(distance)) return MIN_GLIDE_MS;
|
|
2130
|
+
return Math.round(Math.max(MIN_GLIDE_MS, Math.min(MAX_GLIDE_MS, 240 + Math.abs(distance) * .14)));
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* Detach the host's bottom-follow state when the reader currently sits inside
|
|
2134
|
+
* its follow zone: assign the scroll position just outside the boundary. That
|
|
2135
|
+
* one synchronous scroll event settles at-bottom to false (moved-by-reader +
|
|
2136
|
+
* outside the zone) so a tip-moved re-render cannot yank the glide back to
|
|
2137
|
+
* the floor. No-op when already above the zone or when the assignment cannot
|
|
2138
|
+
* move (content shorter than the viewport).
|
|
2139
|
+
* @param el - the conversation scrollport.
|
|
2140
|
+
* @param followZonePx - the host's own follow threshold.
|
|
2141
|
+
*/
|
|
2142
|
+
function detachBottomFollow(el, followZonePx = 24) {
|
|
2143
|
+
const floor = scrollFloorOf(el);
|
|
2144
|
+
if (el.scrollTop < floor - followZonePx) return false;
|
|
2145
|
+
const detached = Math.max(0, floor - followZonePx - 1);
|
|
2146
|
+
if (Math.abs(el.scrollTop - detached) < 1) return false;
|
|
2147
|
+
el.scrollTop = detached;
|
|
2148
|
+
return true;
|
|
2149
|
+
}
|
|
2150
|
+
/**
|
|
2151
|
+
* Wait until a detach assignment's scroll event has been dispatched AND
|
|
2152
|
+
* consumed by the host's scroll handler: scroll events fire on a later frame
|
|
2153
|
+
* than the assignment, and a paging commit's layout effect runs before that
|
|
1815
2154
|
* dispatch — it would re-run the follow logic while the at-bottom flag is
|
|
1816
2155
|
* still true and drag the viewport to the growing floor, undoing the detach
|
|
1817
2156
|
* (two rAFs + a macrotask bracket the dispatch window).
|
|
@@ -1957,306 +2296,6 @@ window.__ModuleLoader__.load({
|
|
|
1957
2296
|
}
|
|
1958
2297
|
return await animateScrollTop(scroller, target, glideDurationFor(Math.abs(target - scroller.scrollTop)));
|
|
1959
2298
|
}
|
|
1960
|
-
/**
|
|
1961
|
-
* The rail's own inset, read off the custom property the official component
|
|
1962
|
-
* publishes (`--turn-rail-inset`). Falls back to the official constant.
|
|
1963
|
-
* @param rail - the rail element (or null).
|
|
1964
|
-
*/
|
|
1965
|
-
function railInsetOf(rail) {
|
|
1966
|
-
if (rail === null) return 6;
|
|
1967
|
-
const px = Number.parseFloat(rail.style.getPropertyValue("--turn-rail-inset"));
|
|
1968
|
-
return Number.isFinite(px) && px >= 0 ? px : 6;
|
|
1969
|
-
}
|
|
1970
|
-
/**
|
|
1971
|
-
* Whether `rail` is the frame-style rail (dsh >= 0.1.2-alpha.3): marks sit
|
|
1972
|
-
* at a fixed pitch and scroll INSIDE the frame, whose scroller offset the
|
|
1973
|
-
* component publishes inline as `--turn-scroll-top` — every render, resting
|
|
1974
|
-
* at `0px` included. The compressing alpha.1/2 rail never publishes that
|
|
1975
|
-
* property, so its presence is the generation test. The legacy port paints
|
|
1976
|
-
* its own `--dsbt-*` properties and can never trip this.
|
|
1977
|
-
* @param rail - the rail element (or null).
|
|
1978
|
-
*/
|
|
1979
|
-
function isFrameRail(rail) {
|
|
1980
|
-
return rail !== null && rail.style.getPropertyValue("--turn-scroll-top") !== "";
|
|
1981
|
-
}
|
|
1982
|
-
/**
|
|
1983
|
-
* Resolve the entry under a pointer Y on the frame-style rail — the official
|
|
1984
|
-
* alpha.3 geometry verbatim (`itemAtPointer` in that generation's
|
|
1985
|
-
* TurnNavigator.tsx): marks live in the scrolled ladder at a fixed 10px
|
|
1986
|
-
* pitch, so the click offset adds the scroller offset. Read off the published
|
|
1987
|
-
* `--turn-scroll-top` var rather than the scroller element itself — the same
|
|
1988
|
-
* metric the official preview positioning uses, so any one-frame render lag
|
|
1989
|
-
* affects both identically.
|
|
1990
|
-
* @param count - number of entries the rail shows.
|
|
1991
|
-
* @param rail - the rail element.
|
|
1992
|
-
* @param clientY - pointer viewport Y.
|
|
1993
|
-
*/
|
|
1994
|
-
function frameIndexAtPointer(count, rail, clientY) {
|
|
1995
|
-
const rect = rail.getBoundingClientRect();
|
|
1996
|
-
const inset = railInsetOf(rail);
|
|
1997
|
-
const raw = Number.parseFloat(rail.style.getPropertyValue("--turn-scroll-top"));
|
|
1998
|
-
const scrollTop = Number.isFinite(raw) ? raw : 0;
|
|
1999
|
-
const offset = clientY - rect.top + scrollTop - inset;
|
|
2000
|
-
return Math.max(0, Math.min(count - 1, Math.round(offset / 10)));
|
|
2001
|
-
}
|
|
2002
|
-
/**
|
|
2003
|
-
* Resolve the gesture that produced a rail click to an entry index.
|
|
2004
|
-
*
|
|
2005
|
-
* Keyboard activation targets the focused mark button (recovered by its
|
|
2006
|
-
* position among the rail's buttons); a mouse click carries coordinates and
|
|
2007
|
-
* resolves through the rail geometry — the frame-style mapping over the
|
|
2008
|
-
* published `--turn-scroll-top`. Returns -1 when neither applies (a
|
|
2009
|
-
* keyboard target outside the rail, or a rail of an unsupported generation,
|
|
2010
|
-
* which the kernel's own handler then owns).
|
|
2011
|
-
*
|
|
2012
|
-
* Assumption: the mark ticks are the ONLY buttons under the rail — the
|
|
2013
|
-
* official rail renders marks exclusively and its preview tooltip carries
|
|
2014
|
-
* none. A future non-mark control added inside the rail would shift the
|
|
2015
|
-
* button index and must revisit this.
|
|
2016
|
-
*
|
|
2017
|
-
* @param rail - the rail element.
|
|
2018
|
-
* @param event - the click event.
|
|
2019
|
-
*/
|
|
2020
|
-
function indexForEvent(rail, event) {
|
|
2021
|
-
const target = event.target;
|
|
2022
|
-
if (target instanceof Element) {
|
|
2023
|
-
const button = target.closest("button");
|
|
2024
|
-
if (button !== null && rail.contains(button)) {
|
|
2025
|
-
const index = Array.from(rail.querySelectorAll("button")).indexOf(button);
|
|
2026
|
-
if (index >= 0) return index;
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
const count = rail.querySelectorAll("button").length;
|
|
2030
|
-
if (count === 0) return -1;
|
|
2031
|
-
return isFrameRail(rail) ? frameIndexAtPointer(count, rail, event.clientY) : -1;
|
|
2032
|
-
}
|
|
2033
|
-
/**
|
|
2034
|
-
* Validate the official navigation array crossing the slot boundary. The
|
|
2035
|
-
* value is produced by the kernel and may be absent, partial, or from an
|
|
2036
|
-
* older build, so every field is checked before use.
|
|
2037
|
-
* @param value - the raw `useChat(s => s.navigation.items())` result.
|
|
2038
|
-
*/
|
|
2039
|
-
function normalizeNavigationItems(value) {
|
|
2040
|
-
if (!Array.isArray(value)) return [];
|
|
2041
|
-
const out = [];
|
|
2042
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2043
|
-
for (const item of value) {
|
|
2044
|
-
if (item === null || typeof item !== "object") continue;
|
|
2045
|
-
const rec = item;
|
|
2046
|
-
if (typeof rec.turn !== "number" || !Number.isFinite(rec.turn)) continue;
|
|
2047
|
-
if (typeof rec.anchorKey !== "string" || rec.anchorKey === "") continue;
|
|
2048
|
-
if (seen.has(rec.turn)) continue;
|
|
2049
|
-
seen.add(rec.turn);
|
|
2050
|
-
out.push({
|
|
2051
|
-
turn: rec.turn,
|
|
2052
|
-
anchorKey: rec.anchorKey,
|
|
2053
|
-
prompt: typeof rec.prompt === "string" ? rec.prompt : "",
|
|
2054
|
-
response: typeof rec.response === "string" ? rec.response : ""
|
|
2055
|
-
});
|
|
2056
|
-
}
|
|
2057
|
-
return out;
|
|
2058
|
-
}
|
|
2059
|
-
/**
|
|
2060
|
-
* Validate one `turnOutline` wire value into its entries. The value is the
|
|
2061
|
-
* host projection's wire view (an array of `{ turn, seq, prompt, response }`
|
|
2062
|
-
* — see `dsh-session-turn-outline`) read through `useProjection`, so it may
|
|
2063
|
-
* be absent (pre-baseline, or an older kernel that never registers the key —
|
|
2064
|
-
* which reads `undefined`), partial, or damaged. `turn` and `seq` are the
|
|
2065
|
-
* load-bearing fields — a mark cannot exist or page without them — and a
|
|
2066
|
-
* damaged one drops the entry; the previews are decorative and degrade to ''.
|
|
2067
|
-
* @param value - the raw `useProjection('turnOutline')` value.
|
|
2068
|
-
*/
|
|
2069
|
-
function normalizeOutlineItems(value) {
|
|
2070
|
-
if (!Array.isArray(value)) return [];
|
|
2071
|
-
const out = [];
|
|
2072
|
-
for (const item of value) {
|
|
2073
|
-
if (item === null || typeof item !== "object") continue;
|
|
2074
|
-
const rec = item;
|
|
2075
|
-
if (typeof rec.turn !== "number" || !Number.isSafeInteger(rec.turn) || rec.turn < 0) continue;
|
|
2076
|
-
if (typeof rec.seq !== "number" || !Number.isSafeInteger(rec.seq) || rec.seq < 0) continue;
|
|
2077
|
-
out.push({
|
|
2078
|
-
turn: rec.turn,
|
|
2079
|
-
seq: rec.seq,
|
|
2080
|
-
prompt: typeof rec.prompt === "string" ? rec.prompt : "",
|
|
2081
|
-
response: typeof rec.response === "string" ? rec.response : ""
|
|
2082
|
-
});
|
|
2083
|
-
}
|
|
2084
|
-
return out;
|
|
2085
|
-
}
|
|
2086
|
-
/**
|
|
2087
|
-
* Rebuild the rail's full mark ladder exactly as the official ChatView does
|
|
2088
|
-
* (a mirror of `mergeTurnRailItems` in dsh-client-ui-chat): outline entries
|
|
2089
|
-
* seed every known turn as unloaded, loaded items then override with their
|
|
2090
|
-
* anchor key and richer previews, ascending by turn. The enhancer maps a
|
|
2091
|
-
* click to a mark by INDEX, so its ladder must match the RENDERED marks —
|
|
2092
|
-
* which are exactly this merge on an alpha.3 kernel; without the outline
|
|
2093
|
-
* value the merge degrades to the loaded items (the alpha.1/2 ladder).
|
|
2094
|
-
* @param loaded - the validated loaded-window items.
|
|
2095
|
-
* @param outline - the raw turnOutline wire value.
|
|
2096
|
-
*/
|
|
2097
|
-
function mergeRailItems(loaded, outline) {
|
|
2098
|
-
const byTurn = /* @__PURE__ */ new Map();
|
|
2099
|
-
for (const entry of normalizeOutlineItems(outline)) byTurn.set(entry.turn, {
|
|
2100
|
-
turn: entry.turn,
|
|
2101
|
-
prompt: entry.prompt,
|
|
2102
|
-
response: entry.response
|
|
2103
|
-
});
|
|
2104
|
-
for (const item of loaded) {
|
|
2105
|
-
const preview = byTurn.get(item.turn);
|
|
2106
|
-
byTurn.set(item.turn, {
|
|
2107
|
-
turn: item.turn,
|
|
2108
|
-
prompt: item.prompt !== "" ? item.prompt : preview?.prompt ?? "",
|
|
2109
|
-
response: item.response !== "" ? item.response : preview?.response ?? "",
|
|
2110
|
-
anchorKey: item.anchorKey
|
|
2111
|
-
});
|
|
2112
|
-
}
|
|
2113
|
-
if (byTurn.size === 0) return [];
|
|
2114
|
-
return [...byTurn.values()].sort((left, right) => left.turn - right.turn);
|
|
2115
|
-
}
|
|
2116
|
-
//#endregion
|
|
2117
|
-
//#region src/client/timeline/official-enhance.tsx
|
|
2118
|
-
/**
|
|
2119
|
-
* Official-rail enhancer — behaviour-only improvements to the kernel's own
|
|
2120
|
-
* turn navigator (dsh >= 0.1.2-rc.1, `TurnNavigator` in dsh-client-ui-chat).
|
|
2121
|
-
*
|
|
2122
|
-
* This component renders NOTHING. It borrows the official rail exactly as the
|
|
2123
|
-
* kernel painted it and fixes its behaviour, scoped to what the frame-style
|
|
2124
|
-
* rail (dsh >= 0.1.2-alpha.3, unchanged on 0.1.2-rc.1) still lacks — told
|
|
2125
|
-
* apart by capability, never by version compare:
|
|
2126
|
-
*
|
|
2127
|
-
* Smooth jump. The official `navigateToTurn` is one assignment
|
|
2128
|
-
* (`el.scrollTop += flowTop(row, el) - 24`); a click across a long transcript
|
|
2129
|
-
* teleports. Clicks on LOADED marks are intercepted in the capture phase and
|
|
2130
|
-
* re-run through the shared glide instead. The frame-style rail also renders
|
|
2131
|
-
* marks for turns OUTSIDE the loaded window (fed by the whole-log
|
|
2132
|
-
* `turnOutline` projection); those jumps page history through the kernel's
|
|
2133
|
-
* own loadThrough machinery — marks without an anchor key are left to it, and
|
|
2134
|
-
* the click index is resolved against the same merged ladder the kernel
|
|
2135
|
-
* renders. While one of those kernel jumps is still paging (a mark pulses
|
|
2136
|
-
* `aria-busy`), the plugin stands down entirely: the kernel's loaded branch
|
|
2137
|
-
* cancels its own pending jump before landing, and an interception would
|
|
2138
|
-
* bypass that cancellation.
|
|
2139
|
-
*
|
|
2140
|
-
* State-ownership note (why intercepting the official click loses nothing):
|
|
2141
|
-
* the kernel's scrollport (`data-conversation-scroll`, ChatView's
|
|
2142
|
-
* `onScrollRef` handler) treats every unaccounted scrollTop assignment as a
|
|
2143
|
-
* reader move and on each frame (a) flips its own at-bottom machinery,
|
|
2144
|
-
* (b) `chatScroll.save(position)` for view-tab restore, and (c)
|
|
2145
|
-
* `scheduleActiveTurn()` to re-derive the rail's active mark. The glide
|
|
2146
|
-
* animates the real scrollport, so all three keep converging to the target
|
|
2147
|
-
* row while it runs — the only thing the interception skips is the official
|
|
2148
|
-
* handler's synchronous `setActiveTurn(item.turn)`, which its own
|
|
2149
|
-
* scroll-driven active derivation re-settles by the time the glide lands.
|
|
2150
|
-
*
|
|
2151
|
-
* The jump goes through the shared backend (jump.ts).
|
|
2152
|
-
*
|
|
2153
|
-
* Interception notes: React 18 dispatches `onClick` from a listener on the
|
|
2154
|
-
* root container during the BUBBLE phase. A capture listener on the rail
|
|
2155
|
-
* itself therefore runs first, and `stopImmediatePropagation()` there prevents
|
|
2156
|
-
* the event from ever reaching React's root — the official handler never
|
|
2157
|
-
* fires. The rail owns pointer input for its whole column (marks carry
|
|
2158
|
-
* `pointer-events: none`), so one listener catches mouse and keyboard alike.
|
|
2159
|
-
*
|
|
2160
|
-
* Nothing here touches the official DOM: no nodes are injected into a
|
|
2161
|
-
* React-owned subtree and no official class or paint is overridden.
|
|
2162
|
-
*/
|
|
2163
|
-
/**
|
|
2164
|
-
* Structural anchor of the official rail — its inline style carries the
|
|
2165
|
-
* frame's own metric (`--turn-natural-height`), published on every supported
|
|
2166
|
-
* kernel (dsh >= 0.1.2-rc.1). Whether the found rail is the supported
|
|
2167
|
-
* frame-style generation is then told apart by isFrameRail, never by a
|
|
2168
|
-
* version compare.
|
|
2169
|
-
*/
|
|
2170
|
-
const OFFICIAL_RAIL_SELECTOR = "[data-conversation-scroll] nav[style*=\"--turn-natural-height\"]";
|
|
2171
|
-
/**
|
|
2172
|
-
* Rails claimed by an enhancer instance, keyed by the owning instance's
|
|
2173
|
-
* claim token. One session-scoped enhancer mounts per session; without a
|
|
2174
|
-
* claim two instances would both attach capture listeners to the FIRST rail
|
|
2175
|
-
* in the document (multi-column layouts) and one of them — not necessarily
|
|
2176
|
-
* the one owning the reader's column — would win the click. The token
|
|
2177
|
-
* distinguishes "already mine" (the poll keeps it) from "someone else's"
|
|
2178
|
-
* (this instance must stand down). Weak: a replaced rail element disappears
|
|
2179
|
-
* as soon as the browser collects it, so remounts need no bookkeeping.
|
|
2180
|
-
*/
|
|
2181
|
-
const claimedRails = /* @__PURE__ */ new WeakMap();
|
|
2182
|
-
/** How often the official rail is looked for (it mounts with the chat view). */
|
|
2183
|
-
const RAIL_POLL_MS = 400;
|
|
2184
|
-
/**
|
|
2185
|
-
* The enhancer: no visual output, only the behaviour fixes the running rail
|
|
2186
|
-
* generation lacks.
|
|
2187
|
-
* @param props - dock slot props plus the enabled gate.
|
|
2188
|
-
* @returns null (always).
|
|
2189
|
-
*/
|
|
2190
|
-
function OfficialTimelineEnhancer(props) {
|
|
2191
|
-
const { sessionId, sessionsService, useChat, useProjection, enabled } = props;
|
|
2192
|
-
const ladder = mergeRailItems(normalizeNavigationItems(useChat === void 0 ? void 0 : useChat((snapshot) => {
|
|
2193
|
-
const nav = snapshot?.navigation;
|
|
2194
|
-
return typeof nav?.items === "function" ? nav.items() : void 0;
|
|
2195
|
-
})), useProjection === void 0 ? void 0 : useProjection("turnOutline"));
|
|
2196
|
-
const [rail, setRail] = react.default.useState(null);
|
|
2197
|
-
const ladderRef = react.default.useRef(ladder);
|
|
2198
|
-
ladderRef.current = ladder;
|
|
2199
|
-
const railRef = react.default.useRef(rail);
|
|
2200
|
-
railRef.current = rail;
|
|
2201
|
-
const claimToken = react.default.useMemo(() => Symbol("dsh-bg-timeline-claim"), []);
|
|
2202
|
-
react.default.useEffect(() => {
|
|
2203
|
-
const check = () => {
|
|
2204
|
-
const found = document.querySelector(OFFICIAL_RAIL_SELECTOR);
|
|
2205
|
-
if (found === null) {
|
|
2206
|
-
setRail(null);
|
|
2207
|
-
return;
|
|
2208
|
-
}
|
|
2209
|
-
const owner = claimedRails.get(found);
|
|
2210
|
-
if (owner !== void 0 && owner !== claimToken) {
|
|
2211
|
-
setRail(null);
|
|
2212
|
-
return;
|
|
2213
|
-
}
|
|
2214
|
-
claimedRails.set(found, claimToken);
|
|
2215
|
-
setRail(found);
|
|
2216
|
-
};
|
|
2217
|
-
check();
|
|
2218
|
-
const timer = window.setInterval(check, RAIL_POLL_MS);
|
|
2219
|
-
return () => {
|
|
2220
|
-
window.clearInterval(timer);
|
|
2221
|
-
if (railRef.current !== null) claimedRails.delete(railRef.current);
|
|
2222
|
-
};
|
|
2223
|
-
}, [claimToken]);
|
|
2224
|
-
const navigate = react.default.useCallback((item) => {
|
|
2225
|
-
if (item.anchorKey === void 0) return;
|
|
2226
|
-
if (sessionsService === void 0 || sessionId === void 0) return;
|
|
2227
|
-
const scrollport = rail === null ? null : rail.closest("[data-conversation-scroll]");
|
|
2228
|
-
jumpToMessage(sessionsService, sessionId, item.anchorKey, {
|
|
2229
|
-
followZonePx: 24,
|
|
2230
|
-
targetTop: officialTargetTopFor,
|
|
2231
|
-
scrollport
|
|
2232
|
-
}).catch(() => {});
|
|
2233
|
-
}, [
|
|
2234
|
-
sessionsService,
|
|
2235
|
-
sessionId,
|
|
2236
|
-
rail
|
|
2237
|
-
]);
|
|
2238
|
-
react.default.useEffect(() => {
|
|
2239
|
-
if (!enabled || rail === null) return;
|
|
2240
|
-
const onClick = (event) => {
|
|
2241
|
-
if (rail.querySelector("[aria-busy=\"true\"]") !== null) return;
|
|
2242
|
-
const index = indexForEvent(rail, event);
|
|
2243
|
-
const item = ladderRef.current[index];
|
|
2244
|
-
if (item === void 0 || item.anchorKey === void 0) return;
|
|
2245
|
-
event.preventDefault();
|
|
2246
|
-
event.stopImmediatePropagation();
|
|
2247
|
-
navigate(item);
|
|
2248
|
-
};
|
|
2249
|
-
rail.addEventListener("click", onClick, true);
|
|
2250
|
-
return () => {
|
|
2251
|
-
rail.removeEventListener("click", onClick, true);
|
|
2252
|
-
};
|
|
2253
|
-
}, [
|
|
2254
|
-
enabled,
|
|
2255
|
-
rail,
|
|
2256
|
-
navigate
|
|
2257
|
-
]);
|
|
2258
|
-
return null;
|
|
2259
|
-
}
|
|
2260
2299
|
//#endregion
|
|
2261
2300
|
//#region src/client/timeline/index.tsx
|
|
2262
2301
|
/**
|
|
@@ -2361,7 +2400,14 @@ window.__ModuleLoader__.load({
|
|
|
2361
2400
|
id: "deepseek-harness-background.timeline",
|
|
2362
2401
|
order: 45,
|
|
2363
2402
|
locale: "ui-background",
|
|
2364
|
-
inject: () => ({
|
|
2403
|
+
inject: (sessionId) => ({ jumpToAnchor: (anchorKey) => {
|
|
2404
|
+
const scrollport = findOfficialRail()?.closest("[data-conversation-scroll]") ?? null;
|
|
2405
|
+
return jumpToMessage(ctx.sessions, sessionId, anchorKey, {
|
|
2406
|
+
followZonePx: 24,
|
|
2407
|
+
targetTop: officialTargetTopFor,
|
|
2408
|
+
scrollport
|
|
2409
|
+
});
|
|
2410
|
+
} })
|
|
2365
2411
|
}, TimelineBridge));
|
|
2366
2412
|
ctx.effect(() => () => {
|
|
2367
2413
|
unsubscribe();
|