deepseek-harness-background 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/README.zh.md +5 -4
- package/lib/client.js +377 -169
- package/lib/client.js.map +1 -1
- package/lib/index.js +111 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -61,16 +61,17 @@ window.__ModuleLoader__.load({
|
|
|
61
61
|
* as small cards/buttons over the wallpaper are glassed — composer card,
|
|
62
62
|
* message bubbles, code surfaces (blocks, inline code, tool IO cards,
|
|
63
63
|
* skill/MCP call cards), the three chrome buttons (new session, composer +
|
|
64
|
-
* button, scroll-to-bottom), the
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
64
|
+
* button, scroll-to-bottom), the load-earlier history button, the composer
|
|
65
|
+
* dock family (agent task strips: TodoPanel / GoalBar / QueueDock) and its
|
|
66
|
+
* takeover panels (approval, question, plan review), the subagent lineage
|
|
67
|
+
* popover, the home hero "preview" badge, the sidebar build badge, and the
|
|
68
|
+
* timeline rail (timeline-css.ts). Every glassed surface shares ONE recipe:
|
|
69
|
+
* fill from the painter's --dsw-specific-input-major token (same alpha
|
|
70
|
+
* curve + theme dimming), the full shared blur/saturate/brightness chain
|
|
71
|
+
* driven by the glass-blur slider, and the shared sheen vars — so no
|
|
72
|
+
* surface reads heavier than the composer card. Dialogs, the settings UI,
|
|
73
|
+
* menus, tooltips, toasts and every hover/accent fill keep the OFFICIAL
|
|
74
|
+
* opaque paints — they are reading surfaces and must stay legible.
|
|
74
75
|
*
|
|
75
76
|
* Selectors target authored attributes (`data-dsh-bg`, `data-composer-card`,
|
|
76
77
|
* `data-terminal`…), the `:global` `.md-code-block` anchor, and CSS-module
|
|
@@ -227,6 +228,53 @@ window.__ModuleLoader__.load({
|
|
|
227
228
|
body[data-ds-dark-theme][data-dsh-bg-glass] [class*="_previewBadge"] {
|
|
228
229
|
background-color: color-mix(in srgb, rgb(52 65 91) calc(var(--bg-glass-alpha, 0.72) * 100%), transparent);
|
|
229
230
|
}
|
|
231
|
+
|
|
232
|
+
/* ---- Composer dock family: task strips + takeover panels --------------
|
|
233
|
+
The agent task strips (TodoPanel, GoalBar, QueueDock — one dock column
|
|
234
|
+
above the composer) and the composer takeovers (approval, question,
|
|
235
|
+
plan review) paint with tokens the painter turns translucent
|
|
236
|
+
(--dsw-specific-tip / --dsw-specific-input-major) but their own styles
|
|
237
|
+
never blur anything: translucent-without-blur reads as bare
|
|
238
|
+
transparency, not frosted glass. Give each the shared sheen + filter
|
|
239
|
+
chain; their fills stay token-driven so the panel-opacity slider keeps
|
|
240
|
+
driving them. Anchors are the components' own stable data attributes
|
|
241
|
+
plus a structural child, so no generic class suffix is swept in. */
|
|
242
|
+
body[data-dsh-bg-glass] [data-testid="todo-panel"],
|
|
243
|
+
body[data-dsh-bg-glass] [data-goal-bar] > div,
|
|
244
|
+
body[data-dsh-bg-glass] [data-queue-dock] > div,
|
|
245
|
+
body[data-dsh-bg-glass] [data-approval-key] > div,
|
|
246
|
+
body[data-dsh-bg-glass] [data-question-key] > section,
|
|
247
|
+
body[data-dsh-bg-glass] [data-plan-review-key] > section {
|
|
248
|
+
background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));
|
|
249
|
+
-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);
|
|
250
|
+
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* ---- "Load earlier" history button (top of long conversations) --------
|
|
254
|
+
Its official fill is an opaque solid button token that is deliberately
|
|
255
|
+
NOT overridden, so unlike the tip family it needs an explicit glass
|
|
256
|
+
fill too — same recipe as the three chrome buttons above. */
|
|
257
|
+
body[data-dsh-bg-glass] [class*="_older"] button {
|
|
258
|
+
background-color: var(--dsw-specific-input-major);
|
|
259
|
+
background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));
|
|
260
|
+
-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);
|
|
261
|
+
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
262
|
+
box-shadow:
|
|
263
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.32),
|
|
264
|
+
inset 0 0 0 0.5px rgba(255, 255, 255, 0.12),
|
|
265
|
+
0 4px 14px rgba(0, 0, 0, 0.08);
|
|
266
|
+
}
|
|
267
|
+
body[data-dsh-bg-glass] [class*="_older"] button:hover:not(:disabled) {
|
|
268
|
+
background-color: color-mix(in srgb, rgb(255 255 255) calc(var(--bg-glass-alpha-strong, 0.8) * 100%), transparent);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* Sidebar build badge: translucent ink already, but it sat directly on
|
|
272
|
+
the wallpaper without frost. Join the shared blur chain under the same
|
|
273
|
+
gate (its theme-aware ink fills from the active block above). */
|
|
274
|
+
body[data-dsh-bg-glass] [class*="_buildRevision"] {
|
|
275
|
+
-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);
|
|
276
|
+
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
277
|
+
}
|
|
230
278
|
`;
|
|
231
279
|
/**
|
|
232
280
|
* Inject the backdrop stylesheet once. Safe to call repeatedly; a second call
|
|
@@ -248,6 +296,12 @@ window.__ModuleLoader__.load({
|
|
|
248
296
|
const DEFAULT_SCRIM = .25;
|
|
249
297
|
/** Default panel opacity (0..1); 1 = fully opaque official surfaces, 0 = fully transparent glass. */
|
|
250
298
|
const DEFAULT_PANEL_OPACITY = .15;
|
|
299
|
+
/**
|
|
300
|
+
* Session-projection key this plugin owns in the client-visible value map —
|
|
301
|
+
* the host fold registers it (src/projection.ts) and the rail reads it
|
|
302
|
+
* (src/client/timeline.tsx); one shared constant so the two can never drift.
|
|
303
|
+
*/
|
|
304
|
+
const TIMELINE_PROJECTION_KEY = "bgTimeline";
|
|
251
305
|
/** Default background rendering mode. */
|
|
252
306
|
const DEFAULT_FIT = "cover";
|
|
253
307
|
/** Image positioning modes. `cover` fills the frame (cropping as needed); `contain` fits the whole image inside the frame (letterboxing). */
|
|
@@ -269,6 +323,11 @@ window.__ModuleLoader__.load({
|
|
|
269
323
|
const LAYER_CLASS = "dsh-bg-layer";
|
|
270
324
|
const IMAGE_CLASS = "dsh-bg-image";
|
|
271
325
|
const SCRIM_CLASS = "dsh-bg-scrim";
|
|
326
|
+
/** Body style properties the painter owns while a wallpaper paints: the app
|
|
327
|
+
* frame and sidebar fills go transparent so every column shares one art.
|
|
328
|
+
* Inline values outrank any stylesheet rule (the body[data-dsh-bg] gate only
|
|
329
|
+
* covers the CSS copy), so a sourceless section must actively restore them. */
|
|
330
|
+
const FRAME_TOKENS = ["--dsw-alias-bg-base", "--dsw-specific-sidebar-fill"];
|
|
272
331
|
/**
|
|
273
332
|
* White-glass tokens — the WHITELISTED surfaces only. Everything that floats
|
|
274
333
|
* as a small card over the wallpaper: the composer input stack, message
|
|
@@ -342,12 +401,21 @@ window.__ModuleLoader__.load({
|
|
|
342
401
|
};
|
|
343
402
|
}
|
|
344
403
|
/**
|
|
404
|
+
* The theme-aware base alpha for one panelOpacity — THE curve every glass
|
|
405
|
+
* surface shares (surface tokens, bare alpha knobs and the settings preview
|
|
406
|
+
* included). Single-sourced here so the live painter and the preview card
|
|
407
|
+
* cannot drift.
|
|
408
|
+
*/
|
|
409
|
+
function glassBaseAlpha(panelOpacity, inDark) {
|
|
410
|
+
return glassAlpha(panelOpacity) * (inDark ? .4 : .8);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
345
413
|
* The theme-aware glass surface alphas for one panelOpacity: the input-surface
|
|
346
414
|
* token and the bubble token share one curve, dimmed per scheme. Shared by the
|
|
347
415
|
* live painter and the settings-row preview card so the two cannot drift.
|
|
348
416
|
*/
|
|
349
417
|
function glassSurfaceAlphas(panelOpacity, inDark) {
|
|
350
|
-
const alpha =
|
|
418
|
+
const alpha = glassBaseAlpha(panelOpacity, inDark);
|
|
351
419
|
return {
|
|
352
420
|
major: `rgba(255, 255, 255, ${alpha.toFixed(3)})`,
|
|
353
421
|
bubble: `rgba(255, 255, 255, ${(alpha * .8).toFixed(3)})`
|
|
@@ -393,20 +461,21 @@ window.__ModuleLoader__.load({
|
|
|
393
461
|
return;
|
|
394
462
|
}
|
|
395
463
|
injectBackgroundCss();
|
|
396
|
-
this.rememberOnce("--dsw-alias-bg-base");
|
|
397
|
-
this.rememberOnce("--dsw-specific-sidebar-fill");
|
|
398
|
-
document.body.style.setProperty("--dsw-alias-bg-base", "transparent");
|
|
399
|
-
document.body.style.setProperty("--dsw-specific-sidebar-fill", "transparent");
|
|
400
464
|
const url = backgroundImageUrl(settings, uploadBase);
|
|
401
465
|
if (url === "") {
|
|
402
466
|
this.observer?.disconnect();
|
|
403
467
|
this.observer = void 0;
|
|
468
|
+
this.restoreFrameTokens();
|
|
404
469
|
this.applySurfaceGlass(settings, true);
|
|
405
470
|
this.active = false;
|
|
406
471
|
this.removeLayers();
|
|
407
472
|
document.body.removeAttribute(ACTIVE_ATTR);
|
|
408
473
|
return;
|
|
409
474
|
}
|
|
475
|
+
for (const prop of FRAME_TOKENS) {
|
|
476
|
+
this.rememberOnce(prop);
|
|
477
|
+
document.body.style.setProperty(prop, "transparent");
|
|
478
|
+
}
|
|
410
479
|
if (!this.layer) {
|
|
411
480
|
this.layer = document.createElement("div");
|
|
412
481
|
this.layer.className = LAYER_CLASS;
|
|
@@ -448,6 +517,20 @@ window.__ModuleLoader__.load({
|
|
|
448
517
|
document.body.style.setProperty(name, value);
|
|
449
518
|
}
|
|
450
519
|
/**
|
|
520
|
+
* Return the owned frame-transparency properties to their saved state (or
|
|
521
|
+
* remove them when this session never overrode them). dispose() restores
|
|
522
|
+
* everything wholesale; this targeted variant serves the sourceless path,
|
|
523
|
+
* where the painter keeps running for a later source.
|
|
524
|
+
*/
|
|
525
|
+
restoreFrameTokens() {
|
|
526
|
+
for (const prop of FRAME_TOKENS) {
|
|
527
|
+
this.rememberOnce(prop);
|
|
528
|
+
const original = this.savedVars.get(prop);
|
|
529
|
+
if (original !== void 0 && original !== "") document.body.style.setProperty(prop, original);
|
|
530
|
+
else document.body.style.removeProperty(prop);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
451
534
|
* Update one effect knob without re-running the full apply. This is the hot
|
|
452
535
|
* path for slider drags (many events per second): only the single CSS
|
|
453
536
|
* variable changes, the DOM layers are untouched.
|
|
@@ -506,7 +589,7 @@ window.__ModuleLoader__.load({
|
|
|
506
589
|
this.setVar("--bg-glass-brightness", exposure.brightness);
|
|
507
590
|
this.setVar("--bg-glass-sheen", exposure.sheen);
|
|
508
591
|
this.setVar("--bg-glass-sheen-mid", exposure.sheenMid);
|
|
509
|
-
const base =
|
|
592
|
+
const base = glassBaseAlpha(settings.panelOpacity, inDark);
|
|
510
593
|
for (const { token, factor } of GLASS_SURFACE_TOKENS) s.setProperty(token, `rgba(255, 255, 255, ${(base * factor).toFixed(3)})`);
|
|
511
594
|
this.setVar("--bg-glass-alpha", base.toFixed(3));
|
|
512
595
|
this.setVar("--bg-glass-alpha-strong", Math.min(.9, base * 1.5).toFixed(3));
|
|
@@ -772,6 +855,9 @@ window.__ModuleLoader__.load({
|
|
|
772
855
|
};
|
|
773
856
|
const promise = run();
|
|
774
857
|
this.pending = promise;
|
|
858
|
+
promise.then(() => {
|
|
859
|
+
if (this.pending === promise) this.pending = void 0;
|
|
860
|
+
});
|
|
775
861
|
return promise;
|
|
776
862
|
}
|
|
777
863
|
/**
|
|
@@ -799,7 +885,7 @@ window.__ModuleLoader__.load({
|
|
|
799
885
|
const settingsClient = new SettingsClient();
|
|
800
886
|
//#endregion
|
|
801
887
|
//#region \0dsh-css:D:\Project\deepseek-harness-background\src\client\SettingsRow.module.css.mjs
|
|
802
|
-
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:#
|
|
888
|
+
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}}";
|
|
803
889
|
const tagId = "deepseek-harness-background/SettingsRow.module.css";
|
|
804
890
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
805
891
|
const tag = document.createElement("style");
|
|
@@ -809,41 +895,41 @@ window.__ModuleLoader__.load({
|
|
|
809
895
|
document.head.appendChild(tag);
|
|
810
896
|
}
|
|
811
897
|
var SettingsRow_module_css_default = {
|
|
898
|
+
"error": "-ozqdq_error",
|
|
899
|
+
"urlRow": "-ozqdq_urlRow",
|
|
900
|
+
"switch": "-ozqdq_switch",
|
|
901
|
+
"hint": "-ozqdq_hint",
|
|
902
|
+
"titleRow": "-ozqdq_titleRow",
|
|
903
|
+
"clearRow": "-ozqdq_clearRow",
|
|
904
|
+
"previewGlass": "-ozqdq_previewGlass",
|
|
905
|
+
"previewEmpty": "-ozqdq_previewEmpty",
|
|
812
906
|
"sourceRow": "-ozqdq_sourceRow",
|
|
907
|
+
"urlInput": "-ozqdq_urlInput",
|
|
908
|
+
"segGroup": "-ozqdq_segGroup",
|
|
813
909
|
"previewScrim": "-ozqdq_previewScrim",
|
|
814
|
-
"
|
|
815
|
-
"
|
|
816
|
-
"
|
|
910
|
+
"previewGlassText": "-ozqdq_previewGlassText",
|
|
911
|
+
"title": "-ozqdq_title",
|
|
912
|
+
"sliderValue": "-ozqdq_sliderValue",
|
|
913
|
+
"sliderLabel": "-ozqdq_sliderLabel",
|
|
817
914
|
"switchKnob": "-ozqdq_switchKnob",
|
|
818
|
-
"switch": "-ozqdq_switch",
|
|
819
|
-
"segGroup": "-ozqdq_segGroup",
|
|
820
|
-
"segActive": "-ozqdq_segActive",
|
|
821
|
-
"segRow": "-ozqdq_segRow",
|
|
822
915
|
"btn": "-ozqdq_btn",
|
|
823
|
-
"previewEmpty": "-ozqdq_previewEmpty",
|
|
824
|
-
"previewGlass": "-ozqdq_previewGlass",
|
|
825
|
-
"row": "-ozqdq_row",
|
|
826
|
-
"titleRow": "-ozqdq_titleRow",
|
|
827
|
-
"btnDanger": "-ozqdq_btnDanger",
|
|
828
916
|
"desc": "-ozqdq_desc",
|
|
829
917
|
"sliderRow": "-ozqdq_sliderRow",
|
|
830
|
-
"
|
|
831
|
-
"
|
|
832
|
-
"urlRow": "-ozqdq_urlRow",
|
|
918
|
+
"segRow": "-ozqdq_segRow",
|
|
919
|
+
"sectionLabel": "-ozqdq_sectionLabel",
|
|
833
920
|
"enabledBadge": "-ozqdq_enabledBadge",
|
|
834
|
-
"
|
|
835
|
-
"
|
|
836
|
-
"slider": "-ozqdq_slider",
|
|
837
|
-
"urlInput": "-ozqdq_urlInput",
|
|
838
|
-
"btnPrimary": "-ozqdq_btnPrimary",
|
|
839
|
-
"clearBtn": "-ozqdq_clearBtn",
|
|
840
|
-
"error": "-ozqdq_error",
|
|
921
|
+
"segActive": "-ozqdq_segActive",
|
|
922
|
+
"btnDanger": "-ozqdq_btnDanger",
|
|
841
923
|
"previewImg": "-ozqdq_previewImg",
|
|
842
|
-
"
|
|
843
|
-
"
|
|
924
|
+
"segBtn": "-ozqdq_segBtn",
|
|
925
|
+
"clearBtn": "-ozqdq_clearBtn",
|
|
844
926
|
"preview": "-ozqdq_preview",
|
|
845
|
-
"
|
|
846
|
-
"
|
|
927
|
+
"row": "-ozqdq_row",
|
|
928
|
+
"segLabel": "-ozqdq_segLabel",
|
|
929
|
+
"header": "-ozqdq_header",
|
|
930
|
+
"slider": "-ozqdq_slider",
|
|
931
|
+
"btnPrimary": "-ozqdq_btnPrimary",
|
|
932
|
+
"section": "-ozqdq_section"
|
|
847
933
|
};
|
|
848
934
|
//#endregion
|
|
849
935
|
//#region src/client/SettingsRow.tsx
|
|
@@ -1389,7 +1475,16 @@ window.__ModuleLoader__.load({
|
|
|
1389
1475
|
* nav 34px-wide rail, fixed right edge, vertically centered; collapsed
|
|
1390
1476
|
* blurred capsule (height − 8px); expanding panel (max 260px, radius 16)
|
|
1391
1477
|
* whose scroll area holds 30px rows (tick indicator + fading-in title);
|
|
1392
|
-
* 32px gradient fade veils top/bottom shown while that
|
|
1478
|
+
* 32px gradient fade veils top/bottom, expanded-only, shown while that
|
|
1479
|
+
* side actually clips.
|
|
1480
|
+
*
|
|
1481
|
+
* Deviations from the reference port, all fidelity fixes:
|
|
1482
|
+
* - The filter bar collapses to zero height while idle instead of staying
|
|
1483
|
+
* in layout invisibly (it pushed every tick ~33px down).
|
|
1484
|
+
* - Short stacks center vertically; overflowing stacks are bottom-pinned
|
|
1485
|
+
* by the component so the newest tick hugs the capsule's bottom edge
|
|
1486
|
+
* (the official idle strategy — the reference port flowed ticks from
|
|
1487
|
+
* the top and clipped the newest away).
|
|
1393
1488
|
*
|
|
1394
1489
|
* Glass integration: the official chat.deepseek.com paints (light
|
|
1395
1490
|
* rgba(255,255,255,.8)/.94, dark rgba(21,21,23,.6)/rgba(28,28,32,.95),
|
|
@@ -1416,6 +1511,9 @@ window.__ModuleLoader__.load({
|
|
|
1416
1511
|
const TIMELINE_CSS = `
|
|
1417
1512
|
.dsbt-nav {
|
|
1418
1513
|
user-select: none; -webkit-user-select: none;
|
|
1514
|
+
/* Filter-bar chrome height shared by the bar itself and the top veil's
|
|
1515
|
+
expanded offset. */
|
|
1516
|
+
--dsbt-bar-h: 33px;
|
|
1419
1517
|
z-index: 90; align-items: center; width: 34px;
|
|
1420
1518
|
height: var(--dsbt-h, 300px);
|
|
1421
1519
|
transition: right .2s ease, opacity .2s ease;
|
|
@@ -1481,7 +1579,10 @@ window.__ModuleLoader__.load({
|
|
|
1481
1579
|
/* Content fade veils — the official ::before/::after pair rebuilt as
|
|
1482
1580
|
elements: a bar of the panel's OWN fill color masked to nothing over
|
|
1483
1581
|
32px (solid -> mask == gradient-to-transparent, but works with the
|
|
1484
|
-
translucent fill).
|
|
1582
|
+
translucent fill). They belong to the EXPANDED panel only — the idle
|
|
1583
|
+
capsule hard-clips its ticks at the rounded ends like the official
|
|
1584
|
+
strip — and while expanded the top veil starts BELOW the filter bar,
|
|
1585
|
+
not over it. Shown only while that side actually clips. */
|
|
1485
1586
|
.dsbt-fade {
|
|
1486
1587
|
z-index: 2; pointer-events: none; opacity: 0;
|
|
1487
1588
|
background-color: rgba(255, 255, 255, .94);
|
|
@@ -1492,15 +1593,25 @@ window.__ModuleLoader__.load({
|
|
|
1492
1593
|
}
|
|
1493
1594
|
body[data-ds-dark-theme] .dsbt-fade { background-color: rgba(28, 28, 32, .95); }
|
|
1494
1595
|
.dsbt-fade.dsbt-fade-bot { top: auto; bottom: 0; transform: rotate(180deg); }
|
|
1495
|
-
.dsbt-fade.dsbt-fade-on { opacity: 1; }
|
|
1596
|
+
.dsbt-wrap.dsbt-show .dsbt-fade.dsbt-fade-on { opacity: 1; }
|
|
1597
|
+
.dsbt-wrap.dsbt-show .dsbt-fade:not(.dsbt-fade-bot) { top: var(--dsbt-bar-h, 33px); }
|
|
1496
1598
|
|
|
1497
|
-
/* Filter bar — "marked only" toggle pinned above the scroll area
|
|
1599
|
+
/* Filter bar — "marked only" toggle pinned above the scroll area while
|
|
1600
|
+
expanded. Collapsed, it collapses to ZERO height (not just opacity): a
|
|
1601
|
+
transparent-but-in-layout bar pushed every tick ~33px down, one of the
|
|
1602
|
+
reasons the idle capsule read as off-alignment. */
|
|
1498
1603
|
.dsbt-filterbar {
|
|
1604
|
+
height: var(--dsbt-bar-h, 33px);
|
|
1499
1605
|
padding: 8px 12px 4px; display: flex; justify-content: flex-end;
|
|
1500
1606
|
align-items: center; border-bottom: 1px solid rgba(0, 0, 0, .05);
|
|
1501
|
-
box-sizing: border-box; flex: none;
|
|
1607
|
+
box-sizing: border-box; flex: none; overflow: hidden;
|
|
1608
|
+
transition: opacity .2s ease, height .2s ease, padding .2s ease,
|
|
1609
|
+
border-color .2s ease;
|
|
1610
|
+
}
|
|
1611
|
+
.dsbt-wrap:not(.dsbt-show) .dsbt-filterbar {
|
|
1612
|
+
height: 0; padding-top: 0; padding-bottom: 0; opacity: 0;
|
|
1613
|
+
border-bottom-color: transparent;
|
|
1502
1614
|
}
|
|
1503
|
-
.dsbt-wrap:not(.dsbt-show) .dsbt-filterbar { opacity: 0; }
|
|
1504
1615
|
body[data-ds-dark-theme] .dsbt-filterbar { border-bottom-color: rgba(255, 255, 255, .06); }
|
|
1505
1616
|
.dsbt-filterbtn {
|
|
1506
1617
|
font-size: 11px; line-height: 16px; padding: 2px 8px; border-radius: 10px;
|
|
@@ -1522,13 +1633,19 @@ window.__ModuleLoader__.load({
|
|
|
1522
1633
|
background: rgba(251, 191, 36, .18); border-color: #fbbf24; color: #fbbf24;
|
|
1523
1634
|
}
|
|
1524
1635
|
|
|
1525
|
-
/* Scroll area — rows hug the right edge; scrolls only when overfull.
|
|
1636
|
+
/* Scroll area — rows hug the right edge; scrolls only when overfull. The
|
|
1637
|
+
auto-margin pseudo pair vertically centers the stack when it fits the
|
|
1638
|
+
capsule (short conversations), and resolves to zero free space when it
|
|
1639
|
+
overflows, so bottom-pinned scrolling behaves like a plain column. */
|
|
1526
1640
|
.dsbt-page {
|
|
1527
1641
|
padding: 15px 0 15px 24px; box-sizing: border-box;
|
|
1528
1642
|
overscroll-behavior: contain; flex: 1 1 auto; min-height: 0;
|
|
1529
1643
|
flex-direction: column; align-items: flex-end; display: flex;
|
|
1530
1644
|
position: relative; width: 100%; overflow: hidden;
|
|
1531
1645
|
}
|
|
1646
|
+
.dsbt-page::before, .dsbt-page::after {
|
|
1647
|
+
content: ''; flex: none; margin-block: auto;
|
|
1648
|
+
}
|
|
1532
1649
|
.dsbt-wrap.dsbt-show .dsbt-page {
|
|
1533
1650
|
overflow-y: auto; overflow-x: hidden;
|
|
1534
1651
|
scrollbar-width: thin; scrollbar-color: rgba(0, 0, 0, .15) transparent;
|
|
@@ -1548,15 +1665,26 @@ window.__ModuleLoader__.load({
|
|
|
1548
1665
|
|
|
1549
1666
|
/* One message row: 30px, star bookmark + tick indicator at the right,
|
|
1550
1667
|
title fading in. */
|
|
1668
|
+
/* Row shell: layout + shared ink only — the interactive hit areas are the
|
|
1669
|
+
sibling .dsbt-jump / .dsbt-star buttons below. */
|
|
1551
1670
|
.dsbt-item {
|
|
1552
|
-
|
|
1671
|
+
height: 30px; min-height: 30px; flex-shrink: 0;
|
|
1553
1672
|
justify-content: flex-end; align-items: center;
|
|
1554
|
-
width: calc(100% - 4px); margin-right: 4px;
|
|
1555
|
-
display: flex;
|
|
1556
|
-
text-align: right; box-sizing: border-box; padding: 0;
|
|
1673
|
+
width: calc(100% - 4px); margin-right: 4px;
|
|
1674
|
+
display: flex; box-sizing: border-box;
|
|
1557
1675
|
color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .65));
|
|
1558
1676
|
transition: color .15s ease;
|
|
1559
1677
|
}
|
|
1678
|
+
|
|
1679
|
+
/* Jump target: a real button so Enter/Space work natively without nesting
|
|
1680
|
+
an interactive element inside another. */
|
|
1681
|
+
.dsbt-jump {
|
|
1682
|
+
cursor: pointer; background: none; border: none; margin: 0; padding: 0;
|
|
1683
|
+
font: inherit; line-height: 20px; text-align: right; color: inherit;
|
|
1684
|
+
flex: 1 1 auto; min-width: 0; height: 100%;
|
|
1685
|
+
display: flex; align-items: center; justify-content: flex-end;
|
|
1686
|
+
}
|
|
1687
|
+
.dsbt-jump:focus-visible { outline: 1px solid currentColor; outline-offset: -1px; border-radius: 6px; }
|
|
1560
1688
|
.dsbt-item:hover { color: var(--dsw-alias-label-primary, rgba(0, 0, 0, .95)); }
|
|
1561
1689
|
body[data-ds-dark-theme] .dsbt-item { color: rgba(255, 255, 255, .65); }
|
|
1562
1690
|
body[data-ds-dark-theme] .dsbt-item:hover { color: rgba(255, 255, 255, .95); }
|
|
@@ -1569,14 +1697,17 @@ window.__ModuleLoader__.load({
|
|
|
1569
1697
|
.dsbt-show .dsbt-title { opacity: 1; }
|
|
1570
1698
|
|
|
1571
1699
|
/* Star bookmark — appears on row hover while the panel is expanded; gold
|
|
1572
|
-
when marked.
|
|
1700
|
+
when marked. A real sibling button next to the jump target, so marking
|
|
1701
|
+
stays keyboard-operable without nested interactive elements. */
|
|
1573
1702
|
.dsbt-star {
|
|
1574
1703
|
font-size: 13px; line-height: 16px; width: 18px; height: 18px;
|
|
1575
1704
|
display: inline-flex; align-items: center; justify-content: center;
|
|
1576
1705
|
opacity: 0; cursor: pointer; transition: all .15s ease; margin-right: 6px;
|
|
1577
1706
|
flex-shrink: 0; color: rgba(0, 0, 0, .25); border-radius: 4px; user-select: none;
|
|
1578
1707
|
-webkit-user-select: none;
|
|
1708
|
+
background: none; border: none; padding: 0; font-family: inherit;
|
|
1579
1709
|
}
|
|
1710
|
+
.dsbt-star:focus-visible { outline: 1px solid currentColor; outline-offset: 1px; opacity: .9; }
|
|
1580
1711
|
.dsbt-wrap.dsbt-show .dsbt-item:hover .dsbt-star { opacity: .75; }
|
|
1581
1712
|
.dsbt-wrap.dsbt-show .dsbt-star:hover { opacity: 1; transform: scale(1.2); color: #d97706; }
|
|
1582
1713
|
.dsbt-wrap.dsbt-show .dsbt-star.dsbt-staron { opacity: 1; color: #f59e0b; }
|
|
@@ -1629,7 +1760,7 @@ window.__ModuleLoader__.load({
|
|
|
1629
1760
|
}
|
|
1630
1761
|
|
|
1631
1762
|
@media (prefers-reduced-motion: reduce) {
|
|
1632
|
-
.dsbt-nav, .dsbt-bg, .dsbt-wrap, .dsbt-fade, .dsbt-title, .dsbt-line, .dsbt-star, .dsbt-filterbtn, .dsbt-filterbar { transition: none; }
|
|
1763
|
+
.dsbt-nav, .dsbt-bg, .dsbt-wrap, .dsbt-fade, .dsbt-title, .dsbt-line, .dsbt-jump, .dsbt-star, .dsbt-filterbtn, .dsbt-filterbar { transition: none; }
|
|
1633
1764
|
}
|
|
1634
1765
|
`;
|
|
1635
1766
|
//#endregion
|
|
@@ -1656,10 +1787,17 @@ window.__ModuleLoader__.load({
|
|
|
1656
1787
|
* - Rewind integration: messages withdrawn by a rewind command node are
|
|
1657
1788
|
* filtered out of the rail.
|
|
1658
1789
|
*
|
|
1659
|
-
* Data source
|
|
1660
|
-
*
|
|
1661
|
-
*
|
|
1662
|
-
*
|
|
1790
|
+
* Data source, fastest first: the host-side `bgTimeline` session projection
|
|
1791
|
+
* (src/projection.ts — a complete enumeration of the session's user messages
|
|
1792
|
+
* delivered through the projection baseline/push channel) -> the loaded chat
|
|
1793
|
+
* node window. The conversation's own lazy loading is never driven from here;
|
|
1794
|
+
* clicking a row pages older history on demand, scrolls the conversation to
|
|
1795
|
+
* the target row, and keeps the rail's own active row visible.
|
|
1796
|
+
*
|
|
1797
|
+
* Collapsed idle strategy (official ScrollNav behavior): the tick stack is
|
|
1798
|
+
* pinned to the BOTTOM inner edge — the newest question's tick always sits at
|
|
1799
|
+
* the bottom of the capsule, older ticks clip away at the top. Hovering
|
|
1800
|
+
* expands the SAME box and scrolls the active row into view instead.
|
|
1663
1801
|
*
|
|
1664
1802
|
* The rail reads the shared settings transport: it renders unless
|
|
1665
1803
|
* `timeline: false`. Its glass paints are self-contained official values
|
|
@@ -1746,13 +1884,30 @@ window.__ModuleLoader__.load({
|
|
|
1746
1884
|
* @param chat - a chat snapshot (loosely typed on purpose).
|
|
1747
1885
|
*/
|
|
1748
1886
|
function hiddenSeqsOfChat(chat) {
|
|
1887
|
+
const hidden = rewindHiddenSeqsOfChat(chat);
|
|
1888
|
+
const spans = rewindSpansOfChat(chat);
|
|
1889
|
+
if (spans.length === 0) return hidden;
|
|
1890
|
+
const nodes = chat.nodes;
|
|
1891
|
+
if (nodes === void 0 || typeof nodes.values !== "function") return hidden;
|
|
1892
|
+
for (const node of nodes.values()) {
|
|
1893
|
+
if (node === null || typeof node !== "object") continue;
|
|
1894
|
+
const anchor = typeof node.anchorSeq === "number" ? node.anchorSeq : void 0;
|
|
1895
|
+
if (anchor === void 0) continue;
|
|
1896
|
+
if (spans.some((span) => anchor >= span.start && anchor <= span.end)) hidden.add(anchor);
|
|
1897
|
+
}
|
|
1898
|
+
return hidden;
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Seq numbers a rewind hid directly: successful commands hide their own row;
|
|
1902
|
+
* preview commands hide their target.
|
|
1903
|
+
* @param chat - a chat snapshot (loosely typed on purpose).
|
|
1904
|
+
*/
|
|
1905
|
+
function rewindHiddenSeqsOfChat(chat) {
|
|
1749
1906
|
const hidden = /* @__PURE__ */ new Set();
|
|
1750
1907
|
if (chat === null || typeof chat !== "object") return hidden;
|
|
1751
1908
|
const nodes = chat.nodes;
|
|
1752
1909
|
if (nodes === void 0 || typeof nodes.values !== "function") return hidden;
|
|
1753
|
-
const
|
|
1754
|
-
const spans = [];
|
|
1755
|
-
for (const node of all) {
|
|
1910
|
+
for (const node of nodes.values()) {
|
|
1756
1911
|
if (node === null || typeof node !== "object") continue;
|
|
1757
1912
|
if (node.kind !== "command") continue;
|
|
1758
1913
|
const command = node.data;
|
|
@@ -1764,23 +1919,39 @@ window.__ModuleLoader__.load({
|
|
|
1764
1919
|
}
|
|
1765
1920
|
const outcome = command.outcome;
|
|
1766
1921
|
if (outcome === null || typeof outcome !== "object" || outcome.kind !== "success") continue;
|
|
1922
|
+
if (typeof command.seq === "number") hidden.add(command.seq);
|
|
1923
|
+
}
|
|
1924
|
+
return hidden;
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* The [target, sourceEventSeq] spans successful rewind commands cut — the
|
|
1928
|
+
* range form of hiding, needed to filter sources that are NOT chat nodes
|
|
1929
|
+
* (the host projection) by plain seq membership.
|
|
1930
|
+
* @param chat - a chat snapshot (loosely typed on purpose).
|
|
1931
|
+
*/
|
|
1932
|
+
function rewindSpansOfChat(chat) {
|
|
1933
|
+
const spans = [];
|
|
1934
|
+
if (chat === null || typeof chat !== "object") return spans;
|
|
1935
|
+
const nodes = chat.nodes;
|
|
1936
|
+
if (nodes === void 0 || typeof nodes.values !== "function") return spans;
|
|
1937
|
+
for (const node of nodes.values()) {
|
|
1938
|
+
if (node === null || typeof node !== "object") continue;
|
|
1939
|
+
if (node.kind !== "command") continue;
|
|
1940
|
+
const command = node.data;
|
|
1941
|
+
if (command === null || typeof command !== "object") continue;
|
|
1942
|
+
if (command.name !== "rewind") continue;
|
|
1943
|
+
if (isRewindPreviewCommand(command)) continue;
|
|
1944
|
+
const outcome = command.outcome;
|
|
1945
|
+
if (outcome === null || typeof outcome !== "object" || outcome.kind !== "success") continue;
|
|
1767
1946
|
const marker = outcome.sourceEventSeq;
|
|
1768
1947
|
if (typeof marker !== "number") continue;
|
|
1769
|
-
if (typeof command.seq === "number") hidden.add(command.seq);
|
|
1770
1948
|
const target = rewindTargetOfOutcome(outcome.text);
|
|
1771
1949
|
if (target !== void 0) spans.push({
|
|
1772
1950
|
start: target,
|
|
1773
1951
|
end: marker
|
|
1774
1952
|
});
|
|
1775
1953
|
}
|
|
1776
|
-
|
|
1777
|
-
for (const node of all) {
|
|
1778
|
-
if (node === null || typeof node !== "object") continue;
|
|
1779
|
-
const anchor = typeof node.anchorSeq === "number" ? node.anchorSeq : void 0;
|
|
1780
|
-
if (anchor === void 0) continue;
|
|
1781
|
-
if (spans.some((span) => anchor >= span.start && anchor <= span.end)) hidden.add(anchor);
|
|
1782
|
-
}
|
|
1783
|
-
return hidden;
|
|
1954
|
+
return spans;
|
|
1784
1955
|
}
|
|
1785
1956
|
/**
|
|
1786
1957
|
* Enumerate user messages from the loaded chat-node snapshot (the client-side
|
|
@@ -1816,6 +1987,65 @@ window.__ModuleLoader__.load({
|
|
|
1816
1987
|
function resolveAnchorKey(m) {
|
|
1817
1988
|
return typeof m.key === "string" && m.key !== "" ? m.key : void 0;
|
|
1818
1989
|
}
|
|
1990
|
+
/** The user-message node definition kind in ui-conversation. */
|
|
1991
|
+
const INPUT_MESSAGE_KIND = "input-message";
|
|
1992
|
+
/**
|
|
1993
|
+
* Rebuild a chat row's DOM anchor key from the durable message id — the same
|
|
1994
|
+
* engine formula as conversationContextKey(kind, id) for the input-message
|
|
1995
|
+
* definition (`13:input-message<id>`), so projected entries can jump even
|
|
1996
|
+
* when their chat node is not loaded yet.
|
|
1997
|
+
* @param id - the durable message id carried by the projection entry.
|
|
1998
|
+
*/
|
|
1999
|
+
function inputAnchorKeyOf(id) {
|
|
2000
|
+
return `13:${INPUT_MESSAGE_KIND}${id}`;
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Validate one projected wire value into rail entries (defensive: the value
|
|
2004
|
+
* crosses the wire and may be absent, partial, or stale).
|
|
2005
|
+
* @param value - the raw useProjection(TIMELINE_PROJECTION_KEY) snapshot.
|
|
2006
|
+
*/
|
|
2007
|
+
function normalizeProjectedTimeline(value) {
|
|
2008
|
+
if (value === null || typeof value !== "object") return [];
|
|
2009
|
+
const raw = value.messages;
|
|
2010
|
+
if (!Array.isArray(raw)) return [];
|
|
2011
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2012
|
+
const out = [];
|
|
2013
|
+
for (const item of raw) {
|
|
2014
|
+
if (item === null || typeof item !== "object") continue;
|
|
2015
|
+
const rec = item;
|
|
2016
|
+
if (typeof rec.seq !== "number" || !Number.isFinite(rec.seq)) continue;
|
|
2017
|
+
if (seen.has(rec.seq)) continue;
|
|
2018
|
+
seen.add(rec.seq);
|
|
2019
|
+
out.push({
|
|
2020
|
+
seq: rec.seq,
|
|
2021
|
+
time: typeof rec.time === "number" ? rec.time : 0,
|
|
2022
|
+
text: typeof rec.text === "string" ? rec.text.slice(0, 80) : "",
|
|
2023
|
+
...typeof rec.id === "string" && rec.id !== "" ? { key: inputAnchorKeyOf(rec.id) } : {}
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
out.sort((a, b) => a.seq - b.seq);
|
|
2027
|
+
return out;
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* Rail messages with the fastest source first: the host projection covers the
|
|
2031
|
+
* WHOLE session (and already excludes rewind cuts), so it wins whenever it
|
|
2032
|
+
* has anything; otherwise fall back to the loaded chat-node window. The
|
|
2033
|
+
* projection path still applies locally-known rewind hiding so a cut lands
|
|
2034
|
+
* instantly even before the next projection push arrives.
|
|
2035
|
+
* @param snapshot - the session conversation snapshot (node fallback).
|
|
2036
|
+
* @param projected - the raw bgTimeline projection value.
|
|
2037
|
+
*/
|
|
2038
|
+
function railMessages(snapshot, projected) {
|
|
2039
|
+
const projectedMessages = normalizeProjectedTimeline(projected);
|
|
2040
|
+
if (projectedMessages.length > 0) {
|
|
2041
|
+
const chat = snapshot?.chat;
|
|
2042
|
+
const hidden = rewindHiddenSeqsOfChat(chat);
|
|
2043
|
+
const spans = rewindSpansOfChat(chat);
|
|
2044
|
+
if (hidden.size === 0 && spans.length === 0) return projectedMessages;
|
|
2045
|
+
return projectedMessages.filter((m) => !hidden.has(m.seq) && !spans.some((span) => m.seq >= span.start && m.seq <= span.end));
|
|
2046
|
+
}
|
|
2047
|
+
return collectMessages(snapshot);
|
|
2048
|
+
}
|
|
1819
2049
|
/**
|
|
1820
2050
|
* Outer rail height for a message count: rows + breathing room, clamped so
|
|
1821
2051
|
* both states always share the exact same height (never jumps on expand).
|
|
@@ -1839,7 +2069,7 @@ window.__ModuleLoader__.load({
|
|
|
1839
2069
|
if (typeof document !== "undefined") {
|
|
1840
2070
|
const ctx = document.createElement("canvas").getContext("2d");
|
|
1841
2071
|
if (ctx) {
|
|
1842
|
-
ctx.font =
|
|
2072
|
+
ctx.font = `13px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
|
|
1843
2073
|
for (const t of texts) {
|
|
1844
2074
|
const w = ctx.measureText(t === "" ? "…" : t).width;
|
|
1845
2075
|
if (w > widest) widest = w;
|
|
@@ -1894,7 +2124,7 @@ window.__ModuleLoader__.load({
|
|
|
1894
2124
|
* conversation input dock purely to bind per-session lifecycle.
|
|
1895
2125
|
*/
|
|
1896
2126
|
function TimelineRail(props) {
|
|
1897
|
-
const { sessionId, sessionsService, t } = props;
|
|
2127
|
+
const { sessionId, sessionsService, useProjection, t } = props;
|
|
1898
2128
|
const settings = react.default.useSyncExternalStore(settingsClient.subscribe, settingsClient.getSnapshot);
|
|
1899
2129
|
const timelineUnknown = settings.status === "loading";
|
|
1900
2130
|
const timelineDisabled = settings.value?.timeline === false;
|
|
@@ -1902,8 +2132,8 @@ window.__ModuleLoader__.load({
|
|
|
1902
2132
|
const subscribeSession = react.default.useCallback((listener) => session === void 0 ? () => {} : session.subscribe(listener), [session]);
|
|
1903
2133
|
const snapshotSession = react.default.useCallback(() => session === void 0 ? void 0 : session.getSnapshot(), [session]);
|
|
1904
2134
|
const nodeSnapshot = react.default.useSyncExternalStore(subscribeSession, snapshotSession);
|
|
1905
|
-
const
|
|
1906
|
-
const
|
|
2135
|
+
const projected = useProjection !== void 0 ? useProjection(TIMELINE_PROJECTION_KEY) : void 0;
|
|
2136
|
+
const messages = react.default.useMemo(() => railMessages(nodeSnapshot, projected), [nodeSnapshot, projected]);
|
|
1907
2137
|
const [activeIndex, setActiveIndex] = react.default.useState(-1);
|
|
1908
2138
|
const [show, setShow] = react.default.useState(false);
|
|
1909
2139
|
const [rightOffset, setRightOffset] = react.default.useState(16);
|
|
@@ -1944,40 +2174,8 @@ window.__ModuleLoader__.load({
|
|
|
1944
2174
|
return () => mql.removeEventListener("change", onChange);
|
|
1945
2175
|
}, []);
|
|
1946
2176
|
react.default.useEffect(() => {
|
|
1947
|
-
if (session === void 0 || isNarrow || sessionId === void 0 || timelineUnknown || timelineDisabled || hidden) return;
|
|
1948
|
-
setLoadedAll(false);
|
|
1949
|
-
let cancelled = false;
|
|
1950
|
-
const run = async () => {
|
|
1951
|
-
let guard = 0;
|
|
1952
|
-
while (!cancelled && guard++ < 120) {
|
|
1953
|
-
const snap = session.getSnapshot();
|
|
1954
|
-
if (snap?.hasMore !== true) break;
|
|
1955
|
-
if (snap.loadingOlder === true) {
|
|
1956
|
-
await delay(50);
|
|
1957
|
-
continue;
|
|
1958
|
-
}
|
|
1959
|
-
await session.loadOlder();
|
|
1960
|
-
}
|
|
1961
|
-
if (!cancelled) setLoadedAll(true);
|
|
1962
|
-
};
|
|
1963
|
-
run().catch(() => {
|
|
1964
|
-
if (!cancelled) setLoadedAll(true);
|
|
1965
|
-
});
|
|
1966
|
-
return () => {
|
|
1967
|
-
cancelled = true;
|
|
1968
|
-
};
|
|
1969
|
-
}, [
|
|
1970
|
-
sessionId,
|
|
1971
|
-
isNarrow,
|
|
1972
|
-
session,
|
|
1973
|
-
timelineUnknown,
|
|
1974
|
-
timelineDisabled,
|
|
1975
|
-
hidden
|
|
1976
|
-
]);
|
|
1977
|
-
react.default.useEffect(() => {
|
|
1978
|
-
if (!loadedAll && messages.length > 0) return;
|
|
1979
2177
|
setRailWidth(railWidthFor(messages.map((m) => m.text)));
|
|
1980
|
-
}, [
|
|
2178
|
+
}, [messages]);
|
|
1981
2179
|
react.default.useEffect(() => {
|
|
1982
2180
|
if (messages.length === 0) return;
|
|
1983
2181
|
const indexByKey = /* @__PURE__ */ new Map();
|
|
@@ -1985,17 +2183,21 @@ window.__ModuleLoader__.load({
|
|
|
1985
2183
|
const key = resolveAnchorKey(m);
|
|
1986
2184
|
if (key !== void 0) indexByKey.set(key, i);
|
|
1987
2185
|
});
|
|
2186
|
+
const sp = document.querySelector("[data-conversation-scroll]");
|
|
2187
|
+
if (sp === null) return;
|
|
2188
|
+
const rows = sp.querySelectorAll("[data-chat-anchor-key]");
|
|
2189
|
+
let settleTimer;
|
|
2190
|
+
let frame = 0;
|
|
2191
|
+
const nearRows = /* @__PURE__ */ new Set();
|
|
1988
2192
|
const updateActive = () => {
|
|
1989
2193
|
if (jumpPendingRef.current) return;
|
|
1990
|
-
const sp = document.querySelector("[data-conversation-scroll]");
|
|
1991
|
-
if (sp === null) return;
|
|
1992
2194
|
const rect = sp.getBoundingClientRect();
|
|
1993
2195
|
if (rect.height === 0) return;
|
|
2196
|
+
const candidates = nearRows.size > 0 ? nearRows : rows;
|
|
1994
2197
|
const line = rect.top + rect.height * .4;
|
|
1995
|
-
const rows = sp.querySelectorAll("[data-chat-anchor-key]");
|
|
1996
2198
|
let best = -1;
|
|
1997
2199
|
let bestDist = Infinity;
|
|
1998
|
-
for (const row of
|
|
2200
|
+
for (const row of candidates) {
|
|
1999
2201
|
const key = row.getAttribute("data-chat-anchor-key");
|
|
2000
2202
|
if (key === null) continue;
|
|
2001
2203
|
const idx = indexByKey.get(key) ?? -1;
|
|
@@ -2009,10 +2211,22 @@ window.__ModuleLoader__.load({
|
|
|
2009
2211
|
}
|
|
2010
2212
|
setActiveIndex(best);
|
|
2011
2213
|
};
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2214
|
+
const scheduleMeasure = () => {
|
|
2215
|
+
if (frame !== 0) return;
|
|
2216
|
+
frame = window.requestAnimationFrame(() => {
|
|
2217
|
+
frame = 0;
|
|
2218
|
+
updateActive();
|
|
2219
|
+
});
|
|
2220
|
+
};
|
|
2221
|
+
let io;
|
|
2222
|
+
if (typeof IntersectionObserver === "function") {
|
|
2223
|
+
io = new IntersectionObserver((entries) => {
|
|
2224
|
+
for (const entry of entries) if (entry.isIntersecting) nearRows.add(entry.target);
|
|
2225
|
+
else nearRows.delete(entry.target);
|
|
2226
|
+
scheduleMeasure();
|
|
2227
|
+
}, { root: sp });
|
|
2228
|
+
rows.forEach((row) => io?.observe(row));
|
|
2229
|
+
}
|
|
2016
2230
|
const onScroll = () => {
|
|
2017
2231
|
if (settleTimer !== void 0) clearTimeout(settleTimer);
|
|
2018
2232
|
settleTimer = window.setTimeout(() => {
|
|
@@ -2026,22 +2240,20 @@ window.__ModuleLoader__.load({
|
|
|
2026
2240
|
updateActive();
|
|
2027
2241
|
}
|
|
2028
2242
|
}, 150);
|
|
2029
|
-
|
|
2030
|
-
timer = window.setTimeout(() => {
|
|
2031
|
-
timer = void 0;
|
|
2032
|
-
updateActive();
|
|
2033
|
-
}, 60);
|
|
2243
|
+
scheduleMeasure();
|
|
2034
2244
|
};
|
|
2035
|
-
|
|
2036
|
-
const interval = window.setInterval(
|
|
2245
|
+
sp.addEventListener("scroll", onScroll, { passive: true });
|
|
2246
|
+
const interval = window.setInterval(scheduleMeasure, 2e3);
|
|
2247
|
+
scheduleMeasure();
|
|
2037
2248
|
return () => {
|
|
2038
|
-
|
|
2249
|
+
io?.disconnect();
|
|
2250
|
+
if (frame !== 0) cancelAnimationFrame(frame);
|
|
2039
2251
|
if (settleTimer !== void 0) clearTimeout(settleTimer);
|
|
2040
2252
|
if (jumpFallbackRef.current !== void 0) {
|
|
2041
2253
|
clearTimeout(jumpFallbackRef.current);
|
|
2042
2254
|
jumpFallbackRef.current = void 0;
|
|
2043
2255
|
}
|
|
2044
|
-
|
|
2256
|
+
sp.removeEventListener("scroll", onScroll);
|
|
2045
2257
|
clearInterval(interval);
|
|
2046
2258
|
};
|
|
2047
2259
|
}, [messages]);
|
|
@@ -2065,7 +2277,6 @@ window.__ModuleLoader__.load({
|
|
|
2065
2277
|
syncFades,
|
|
2066
2278
|
show,
|
|
2067
2279
|
messages.length,
|
|
2068
|
-
loadedAll,
|
|
2069
2280
|
marksOnly
|
|
2070
2281
|
]);
|
|
2071
2282
|
react.default.useLayoutEffect(() => {
|
|
@@ -2082,6 +2293,18 @@ window.__ModuleLoader__.load({
|
|
|
2082
2293
|
show,
|
|
2083
2294
|
syncFades
|
|
2084
2295
|
]);
|
|
2296
|
+
const height = railHeightFor(messages.length);
|
|
2297
|
+
react.default.useLayoutEffect(() => {
|
|
2298
|
+
if (show) return;
|
|
2299
|
+
const page = pageRef.current;
|
|
2300
|
+
if (page === null) return;
|
|
2301
|
+
page.scrollTop = page.scrollHeight;
|
|
2302
|
+
}, [
|
|
2303
|
+
show,
|
|
2304
|
+
messages.length,
|
|
2305
|
+
marksOnly,
|
|
2306
|
+
height
|
|
2307
|
+
]);
|
|
2085
2308
|
react.default.useEffect(() => {
|
|
2086
2309
|
let raf = 0;
|
|
2087
2310
|
const measure = () => {
|
|
@@ -2109,8 +2332,7 @@ window.__ModuleLoader__.load({
|
|
|
2109
2332
|
clearInterval(guard);
|
|
2110
2333
|
};
|
|
2111
2334
|
}, []);
|
|
2112
|
-
if (timelineUnknown || timelineDisabled || sessionId === void 0 || sessionsService === void 0 || isNarrow || hidden || messages.length
|
|
2113
|
-
const height = railHeightFor(messages.length);
|
|
2335
|
+
if (timelineUnknown || timelineDisabled || sessionId === void 0 || sessionsService === void 0 || isNarrow || hidden || messages.length === 0) return null;
|
|
2114
2336
|
const activeKey = activeIndex >= 0 && activeIndex < messages.length ? markKeyOf(messages[activeIndex]) : "";
|
|
2115
2337
|
const displayMessages = marksOnly ? messages.filter((m) => markedSet.has(markKeyOf(m))) : messages;
|
|
2116
2338
|
return react_dom.default.createPortal(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2157,51 +2379,37 @@ window.__ModuleLoader__.load({
|
|
|
2157
2379
|
const mk = markKeyOf(m);
|
|
2158
2380
|
const marked = markedSet.has(mk);
|
|
2159
2381
|
const isActive = activeKey !== "" && mk === activeKey;
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2163
|
-
type: "button",
|
|
2164
|
-
ref: isActive ? activeItemRef : void 0,
|
|
2165
|
-
className: cls,
|
|
2166
|
-
title: `${marked ? "★ " : ""}${m.text === "" ? t("timeline.noText") : m.text}`,
|
|
2167
|
-
"aria-label": label,
|
|
2168
|
-
"aria-current": isActive ? "location" : void 0,
|
|
2169
|
-
onClick: () => {
|
|
2170
|
-
if (key === void 0) return;
|
|
2171
|
-
jumpPendingRef.current = true;
|
|
2172
|
-
if (jumpFallbackRef.current !== void 0) clearTimeout(jumpFallbackRef.current);
|
|
2173
|
-
jumpFallbackRef.current = window.setTimeout(() => {
|
|
2174
|
-
jumpFallbackRef.current = void 0;
|
|
2175
|
-
jumpPendingRef.current = false;
|
|
2176
|
-
}, 800);
|
|
2177
|
-
jumpToMessage(sessionsService, sessionId, key).catch(() => {});
|
|
2178
|
-
},
|
|
2382
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2383
|
+
className: `dsbt-item${isActive ? " dsbt-active" : ""}${marked ? " dsbt-marked" : ""}`,
|
|
2179
2384
|
children: [
|
|
2180
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
2181
|
-
|
|
2182
|
-
|
|
2385
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2386
|
+
type: "button",
|
|
2387
|
+
className: "dsbt-jump",
|
|
2388
|
+
ref: isActive ? activeItemRef : void 0,
|
|
2389
|
+
title: `${marked ? "★ " : ""}${m.text === "" ? t("timeline.noText") : m.text}`,
|
|
2390
|
+
"aria-label": `${marked ? "★ " : ""}${t("timeline.roleUser")}: ${m.text.slice(0, 60) || t("timeline.noText")}`,
|
|
2391
|
+
"aria-current": isActive ? "location" : void 0,
|
|
2392
|
+
onClick: () => {
|
|
2393
|
+
if (key === void 0) return;
|
|
2394
|
+
jumpPendingRef.current = true;
|
|
2395
|
+
if (jumpFallbackRef.current !== void 0) clearTimeout(jumpFallbackRef.current);
|
|
2396
|
+
jumpFallbackRef.current = window.setTimeout(() => {
|
|
2397
|
+
jumpFallbackRef.current = void 0;
|
|
2398
|
+
jumpPendingRef.current = false;
|
|
2399
|
+
}, 800);
|
|
2400
|
+
jumpToMessage(sessionsService, sessionId, key).catch(() => {});
|
|
2401
|
+
},
|
|
2402
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2403
|
+
className: "dsbt-title",
|
|
2404
|
+
children: m.text === "" ? t("timeline.noText") : m.text
|
|
2405
|
+
})
|
|
2183
2406
|
}),
|
|
2184
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
2407
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2408
|
+
type: "button",
|
|
2185
2409
|
className: marked ? "dsbt-star dsbt-staron" : "dsbt-star",
|
|
2186
|
-
role: "button",
|
|
2187
|
-
tabIndex: 0,
|
|
2188
2410
|
"aria-pressed": marked,
|
|
2189
2411
|
"aria-label": marked ? t("timeline.unmark") : t("timeline.mark"),
|
|
2190
|
-
|
|
2191
|
-
e.stopPropagation();
|
|
2192
|
-
e.preventDefault();
|
|
2193
|
-
},
|
|
2194
|
-
onClick: (e) => {
|
|
2195
|
-
e.stopPropagation();
|
|
2196
|
-
toggleMark(m);
|
|
2197
|
-
},
|
|
2198
|
-
onKeyDown: (e) => {
|
|
2199
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
2200
|
-
e.preventDefault();
|
|
2201
|
-
e.stopPropagation();
|
|
2202
|
-
toggleMark(m);
|
|
2203
|
-
}
|
|
2204
|
-
},
|
|
2412
|
+
onClick: () => toggleMark(m),
|
|
2205
2413
|
children: "★"
|
|
2206
2414
|
}),
|
|
2207
2415
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|