deepseek-harness-background 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/README.zh.md +8 -4
- package/lib/client.js +569 -167
- 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));
|
|
@@ -605,6 +688,198 @@ window.__ModuleLoader__.load({
|
|
|
605
688
|
function clearPreviewSurface(el) {
|
|
606
689
|
for (const name of PREVIEW_VARS) el.style.removeProperty(name);
|
|
607
690
|
}
|
|
691
|
+
/** Property name on window where the api is published. */
|
|
692
|
+
const GLASS_BRIDGE_GLOBAL = "__DSH_BACKGROUND_GLASS__";
|
|
693
|
+
/** DOM event announced once the api becomes available (detail = the api). */
|
|
694
|
+
const GLASS_READY_EVENT = "dsh-background-glass:ready";
|
|
695
|
+
/** Style-tag dedup key of the registry stylesheet (data-plugin-css). */
|
|
696
|
+
const REGISTRY_CSS_TAG = "deepseek-harness-background/glass-registry";
|
|
697
|
+
/** Plugin identity stamped onto the registry <style> element. */
|
|
698
|
+
const PLUGIN_ID = "deepseek-harness-background";
|
|
699
|
+
/** Characters whose presence would let a selector escape its rule context. */
|
|
700
|
+
const FORBIDDEN_SELECTOR_CHARS = [
|
|
701
|
+
"{",
|
|
702
|
+
"}",
|
|
703
|
+
";",
|
|
704
|
+
"@",
|
|
705
|
+
"<",
|
|
706
|
+
">"
|
|
707
|
+
];
|
|
708
|
+
/** Hard cap on one selector's length (diagnostics-friendly, abuse-proof). */
|
|
709
|
+
const MAX_SELECTOR_LENGTH = 500;
|
|
710
|
+
/** Hard cap on selectors per registration call. */
|
|
711
|
+
const MAX_SELECTORS_PER_SPEC = 64;
|
|
712
|
+
/**
|
|
713
|
+
* Validate one raw selector string. Structural checks only: anything that
|
|
714
|
+
* could escape the generated rule body ({}, ;, @, <, >) is rejected, and a
|
|
715
|
+
* backslash is refused too (escape sequences have no business in an anchor
|
|
716
|
+
* selector); ordinary CSS mistakes (a typo'd pseudo-class) are left to the
|
|
717
|
+
* browser, which drops just that one rule — a safe failure mode.
|
|
718
|
+
* @returns an error message, or null when the selector is acceptable.
|
|
719
|
+
*/
|
|
720
|
+
function selectorProblem(selector) {
|
|
721
|
+
if (typeof selector !== "string") return "must be a string";
|
|
722
|
+
const trimmed = selector.trim();
|
|
723
|
+
if (trimmed === "") return "must not be empty";
|
|
724
|
+
if (trimmed.length > MAX_SELECTOR_LENGTH) return "exceeds the 500-character limit (" + trimmed.length + ")";
|
|
725
|
+
for (const ch of FORBIDDEN_SELECTOR_CHARS) if (trimmed.includes(ch)) return "contains a forbidden character (" + JSON.stringify(ch) + ")";
|
|
726
|
+
if (trimmed.includes(String.fromCharCode(92))) return "contains a backslash (escape sequences are not accepted)";
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
/** Normalize a spec into validated (plugin, mode, selector) triples.
|
|
730
|
+
* @returns the accepted triples plus one warning line per rejected selector. */
|
|
731
|
+
function normalizeSpec(spec) {
|
|
732
|
+
const plugin = typeof spec?.plugin === "string" && spec.plugin.trim() !== "" ? spec.plugin.trim() : "(anonymous)";
|
|
733
|
+
const mode = spec?.mode === "fill" ? "fill" : "token";
|
|
734
|
+
const raw = Array.isArray(spec?.selectors) ? [...spec.selectors] : [spec?.selectors];
|
|
735
|
+
const rows = [];
|
|
736
|
+
const warnings = [];
|
|
737
|
+
if (raw.length > MAX_SELECTORS_PER_SPEC) warnings.push("[glass-registry] " + plugin + ": " + raw.length + " selectors exceeds the 64-selector cap; extra entries dropped.");
|
|
738
|
+
for (const item of raw.slice(0, MAX_SELECTORS_PER_SPEC)) {
|
|
739
|
+
const problem = selectorProblem(item);
|
|
740
|
+
if (typeof item !== "string" || problem !== null) {
|
|
741
|
+
warnings.push("[glass-registry] " + plugin + ": rejected selector " + JSON.stringify(item) + " — " + (problem ?? "must be a string") + ".");
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
rows.push({
|
|
745
|
+
plugin,
|
|
746
|
+
mode,
|
|
747
|
+
selector: item.trim()
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
return {
|
|
751
|
+
rows,
|
|
752
|
+
warnings
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Render one registered surface as a gated CSS rule — the same recipes the
|
|
757
|
+
* built-in sheets emit for their explicit-fill families.
|
|
758
|
+
*/
|
|
759
|
+
function ruleFor(row) {
|
|
760
|
+
const fill = row.mode === "fill" ? "background-color: var(--dsw-specific-input-major); 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)); -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); backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);" : "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)); -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); backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);";
|
|
761
|
+
return "body[data-dsh-bg-glass] " + row.selector + " { " + fill + " }";
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Owns the registered surfaces and the registry stylesheet.
|
|
765
|
+
*/
|
|
766
|
+
var GlassRegistry = class {
|
|
767
|
+
entries = /* @__PURE__ */ new Map();
|
|
768
|
+
styleEl = null;
|
|
769
|
+
disposed = false;
|
|
770
|
+
/**
|
|
771
|
+
* Register one spec. See BackgroundGlassApi.register.
|
|
772
|
+
* @param spec - the surfaces to glass.
|
|
773
|
+
* @returns the unregister function (idempotent).
|
|
774
|
+
*/
|
|
775
|
+
register(spec) {
|
|
776
|
+
if (this.disposed || publishedRegistry !== void 0 && publishedRegistry !== this) {
|
|
777
|
+
console.warn("[glass-registry] registration ignored: this bridge is no longer the live publication");
|
|
778
|
+
return () => {};
|
|
779
|
+
}
|
|
780
|
+
let normalized;
|
|
781
|
+
try {
|
|
782
|
+
normalized = normalizeSpec(spec);
|
|
783
|
+
} catch (error) {
|
|
784
|
+
console.warn("[glass-registry] spec rejected: " + (error instanceof Error ? error.message : String(error)));
|
|
785
|
+
normalized = {
|
|
786
|
+
rows: [],
|
|
787
|
+
warnings: []
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
const { rows, warnings } = normalized;
|
|
791
|
+
for (const message of warnings) console.warn(message);
|
|
792
|
+
const owner = {};
|
|
793
|
+
const keys = [];
|
|
794
|
+
for (const row of rows) {
|
|
795
|
+
const key = JSON.stringify([
|
|
796
|
+
row.plugin,
|
|
797
|
+
row.mode,
|
|
798
|
+
row.selector
|
|
799
|
+
]);
|
|
800
|
+
this.entries.set(key, {
|
|
801
|
+
row,
|
|
802
|
+
owner
|
|
803
|
+
});
|
|
804
|
+
keys.push(key);
|
|
805
|
+
}
|
|
806
|
+
this.sync();
|
|
807
|
+
let unregistered = false;
|
|
808
|
+
return () => {
|
|
809
|
+
if (unregistered) return;
|
|
810
|
+
unregistered = true;
|
|
811
|
+
for (const key of keys) {
|
|
812
|
+
const entry = this.entries.get(key);
|
|
813
|
+
if (entry !== void 0 && entry.owner === owner) this.entries.delete(key);
|
|
814
|
+
}
|
|
815
|
+
this.sync();
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
/** Rebuild the registry stylesheet from the live entries. */
|
|
819
|
+
sync() {
|
|
820
|
+
if (this.entries.size === 0) {
|
|
821
|
+
this.styleEl?.remove();
|
|
822
|
+
this.styleEl = null;
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const seen = /* @__PURE__ */ new Set();
|
|
826
|
+
const rules = [];
|
|
827
|
+
for (const { row } of this.entries.values()) {
|
|
828
|
+
const rule = ruleFor(row);
|
|
829
|
+
if (!seen.has(rule)) {
|
|
830
|
+
seen.add(rule);
|
|
831
|
+
rules.push(rule);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (this.styleEl === null) {
|
|
835
|
+
this.styleEl = document.createElement("style");
|
|
836
|
+
this.styleEl.dataset.plugin = PLUGIN_ID;
|
|
837
|
+
this.styleEl.dataset.pluginCss = REGISTRY_CSS_TAG;
|
|
838
|
+
document.head.appendChild(this.styleEl);
|
|
839
|
+
}
|
|
840
|
+
this.styleEl.textContent = rules.join(String.fromCharCode(10));
|
|
841
|
+
}
|
|
842
|
+
/** Tear everything down: entries, stylesheet, and the window publication. */
|
|
843
|
+
dispose() {
|
|
844
|
+
this.disposed = true;
|
|
845
|
+
this.entries.clear();
|
|
846
|
+
this.sync();
|
|
847
|
+
const w = window;
|
|
848
|
+
if (w["__DSH_BACKGROUND_GLASS__"] === this.api) delete w[GLASS_BRIDGE_GLOBAL];
|
|
849
|
+
if (publishedRegistry === this) publishedRegistry = void 0;
|
|
850
|
+
}
|
|
851
|
+
/** The published api face over this registry. */
|
|
852
|
+
api = {
|
|
853
|
+
version: 1,
|
|
854
|
+
bridgeId: "deepseek-harness-background",
|
|
855
|
+
isActive: () => typeof document !== "undefined" && document.body !== null && document.body.hasAttribute("data-dsh-bg-glass"),
|
|
856
|
+
register: (spec) => this.register(spec)
|
|
857
|
+
};
|
|
858
|
+
};
|
|
859
|
+
/** The registry whose api is CURRENTLY published on window. Module-level on
|
|
860
|
+
* purpose: the published api face deliberately carries no dispose member
|
|
861
|
+
* (internal teardown stays unreachable for consumers), so reinstall-time
|
|
862
|
+
* cleanup needs a handle the window key cannot provide. */
|
|
863
|
+
let publishedRegistry;
|
|
864
|
+
/**
|
|
865
|
+
* Publish the glass registry bridge onto window and announce it. Safe to
|
|
866
|
+
* call repeatedly: a previous publication's REGISTRY is disposed first, so
|
|
867
|
+
* exactly one bridge — and one stylesheet — is ever live.
|
|
868
|
+
* @returns the bridge handle — call .dispose() when this plugin's client
|
|
869
|
+
* fiber tears down.
|
|
870
|
+
*/
|
|
871
|
+
function installGlassBridge() {
|
|
872
|
+
publishedRegistry?.dispose();
|
|
873
|
+
document.querySelector("style[data-plugin-css=\"deepseek-harness-background/glass-registry\"]")?.remove();
|
|
874
|
+
const registry = new GlassRegistry();
|
|
875
|
+
publishedRegistry = registry;
|
|
876
|
+
window[GLASS_BRIDGE_GLOBAL] = registry.api;
|
|
877
|
+
window.dispatchEvent(new CustomEvent(GLASS_READY_EVENT, { detail: registry.api }));
|
|
878
|
+
return {
|
|
879
|
+
api: registry.api,
|
|
880
|
+
dispose: () => registry.dispose()
|
|
881
|
+
};
|
|
882
|
+
}
|
|
608
883
|
//#endregion
|
|
609
884
|
//#region src/client/locales.ts
|
|
610
885
|
/** Chinese copy. */
|
|
@@ -772,6 +1047,9 @@ window.__ModuleLoader__.load({
|
|
|
772
1047
|
};
|
|
773
1048
|
const promise = run();
|
|
774
1049
|
this.pending = promise;
|
|
1050
|
+
promise.then(() => {
|
|
1051
|
+
if (this.pending === promise) this.pending = void 0;
|
|
1052
|
+
});
|
|
775
1053
|
return promise;
|
|
776
1054
|
}
|
|
777
1055
|
/**
|
|
@@ -799,7 +1077,7 @@ window.__ModuleLoader__.load({
|
|
|
799
1077
|
const settingsClient = new SettingsClient();
|
|
800
1078
|
//#endregion
|
|
801
1079
|
//#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:#
|
|
1080
|
+
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
1081
|
const tagId = "deepseek-harness-background/SettingsRow.module.css";
|
|
804
1082
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
805
1083
|
const tag = document.createElement("style");
|
|
@@ -809,41 +1087,41 @@ window.__ModuleLoader__.load({
|
|
|
809
1087
|
document.head.appendChild(tag);
|
|
810
1088
|
}
|
|
811
1089
|
var SettingsRow_module_css_default = {
|
|
812
|
-
"sourceRow": "-ozqdq_sourceRow",
|
|
813
|
-
"previewScrim": "-ozqdq_previewScrim",
|
|
814
|
-
"segBtn": "-ozqdq_segBtn",
|
|
815
|
-
"header": "-ozqdq_header",
|
|
816
|
-
"clearRow": "-ozqdq_clearRow",
|
|
817
|
-
"switchKnob": "-ozqdq_switchKnob",
|
|
818
1090
|
"switch": "-ozqdq_switch",
|
|
819
|
-
"
|
|
820
|
-
"
|
|
1091
|
+
"preview": "-ozqdq_preview",
|
|
1092
|
+
"sliderLabel": "-ozqdq_sliderLabel",
|
|
821
1093
|
"segRow": "-ozqdq_segRow",
|
|
822
|
-
"btn": "-ozqdq_btn",
|
|
823
|
-
"previewEmpty": "-ozqdq_previewEmpty",
|
|
824
|
-
"previewGlass": "-ozqdq_previewGlass",
|
|
825
|
-
"row": "-ozqdq_row",
|
|
826
|
-
"titleRow": "-ozqdq_titleRow",
|
|
827
|
-
"btnDanger": "-ozqdq_btnDanger",
|
|
828
|
-
"desc": "-ozqdq_desc",
|
|
829
|
-
"sliderRow": "-ozqdq_sliderRow",
|
|
830
|
-
"segLabel": "-ozqdq_segLabel",
|
|
831
|
-
"title": "-ozqdq_title",
|
|
832
1094
|
"urlRow": "-ozqdq_urlRow",
|
|
833
|
-
"
|
|
834
|
-
"
|
|
1095
|
+
"previewImg": "-ozqdq_previewImg",
|
|
1096
|
+
"previewGlass": "-ozqdq_previewGlass",
|
|
835
1097
|
"hint": "-ozqdq_hint",
|
|
1098
|
+
"header": "-ozqdq_header",
|
|
836
1099
|
"slider": "-ozqdq_slider",
|
|
1100
|
+
"title": "-ozqdq_title",
|
|
1101
|
+
"segLabel": "-ozqdq_segLabel",
|
|
837
1102
|
"urlInput": "-ozqdq_urlInput",
|
|
1103
|
+
"error": "-ozqdq_error",
|
|
1104
|
+
"enabledBadge": "-ozqdq_enabledBadge",
|
|
838
1105
|
"btnPrimary": "-ozqdq_btnPrimary",
|
|
839
1106
|
"clearBtn": "-ozqdq_clearBtn",
|
|
840
|
-
"error": "-ozqdq_error",
|
|
841
|
-
"previewImg": "-ozqdq_previewImg",
|
|
842
1107
|
"sectionLabel": "-ozqdq_sectionLabel",
|
|
1108
|
+
"switchKnob": "-ozqdq_switchKnob",
|
|
843
1109
|
"section": "-ozqdq_section",
|
|
844
|
-
"preview": "-ozqdq_preview",
|
|
845
1110
|
"sliderValue": "-ozqdq_sliderValue",
|
|
846
|
-
"
|
|
1111
|
+
"row": "-ozqdq_row",
|
|
1112
|
+
"previewScrim": "-ozqdq_previewScrim",
|
|
1113
|
+
"desc": "-ozqdq_desc",
|
|
1114
|
+
"segGroup": "-ozqdq_segGroup",
|
|
1115
|
+
"clearRow": "-ozqdq_clearRow",
|
|
1116
|
+
"segActive": "-ozqdq_segActive",
|
|
1117
|
+
"sliderRow": "-ozqdq_sliderRow",
|
|
1118
|
+
"btn": "-ozqdq_btn",
|
|
1119
|
+
"titleRow": "-ozqdq_titleRow",
|
|
1120
|
+
"btnDanger": "-ozqdq_btnDanger",
|
|
1121
|
+
"previewGlassText": "-ozqdq_previewGlassText",
|
|
1122
|
+
"sourceRow": "-ozqdq_sourceRow",
|
|
1123
|
+
"segBtn": "-ozqdq_segBtn",
|
|
1124
|
+
"previewEmpty": "-ozqdq_previewEmpty"
|
|
847
1125
|
};
|
|
848
1126
|
//#endregion
|
|
849
1127
|
//#region src/client/SettingsRow.tsx
|
|
@@ -1389,7 +1667,16 @@ window.__ModuleLoader__.load({
|
|
|
1389
1667
|
* nav 34px-wide rail, fixed right edge, vertically centered; collapsed
|
|
1390
1668
|
* blurred capsule (height − 8px); expanding panel (max 260px, radius 16)
|
|
1391
1669
|
* whose scroll area holds 30px rows (tick indicator + fading-in title);
|
|
1392
|
-
* 32px gradient fade veils top/bottom shown while that
|
|
1670
|
+
* 32px gradient fade veils top/bottom, expanded-only, shown while that
|
|
1671
|
+
* side actually clips.
|
|
1672
|
+
*
|
|
1673
|
+
* Deviations from the reference port, all fidelity fixes:
|
|
1674
|
+
* - The filter bar collapses to zero height while idle instead of staying
|
|
1675
|
+
* in layout invisibly (it pushed every tick ~33px down).
|
|
1676
|
+
* - Short stacks center vertically; overflowing stacks are bottom-pinned
|
|
1677
|
+
* by the component so the newest tick hugs the capsule's bottom edge
|
|
1678
|
+
* (the official idle strategy — the reference port flowed ticks from
|
|
1679
|
+
* the top and clipped the newest away).
|
|
1393
1680
|
*
|
|
1394
1681
|
* Glass integration: the official chat.deepseek.com paints (light
|
|
1395
1682
|
* rgba(255,255,255,.8)/.94, dark rgba(21,21,23,.6)/rgba(28,28,32,.95),
|
|
@@ -1416,6 +1703,9 @@ window.__ModuleLoader__.load({
|
|
|
1416
1703
|
const TIMELINE_CSS = `
|
|
1417
1704
|
.dsbt-nav {
|
|
1418
1705
|
user-select: none; -webkit-user-select: none;
|
|
1706
|
+
/* Filter-bar chrome height shared by the bar itself and the top veil's
|
|
1707
|
+
expanded offset. */
|
|
1708
|
+
--dsbt-bar-h: 33px;
|
|
1419
1709
|
z-index: 90; align-items: center; width: 34px;
|
|
1420
1710
|
height: var(--dsbt-h, 300px);
|
|
1421
1711
|
transition: right .2s ease, opacity .2s ease;
|
|
@@ -1481,7 +1771,10 @@ window.__ModuleLoader__.load({
|
|
|
1481
1771
|
/* Content fade veils — the official ::before/::after pair rebuilt as
|
|
1482
1772
|
elements: a bar of the panel's OWN fill color masked to nothing over
|
|
1483
1773
|
32px (solid -> mask == gradient-to-transparent, but works with the
|
|
1484
|
-
translucent fill).
|
|
1774
|
+
translucent fill). They belong to the EXPANDED panel only — the idle
|
|
1775
|
+
capsule hard-clips its ticks at the rounded ends like the official
|
|
1776
|
+
strip — and while expanded the top veil starts BELOW the filter bar,
|
|
1777
|
+
not over it. Shown only while that side actually clips. */
|
|
1485
1778
|
.dsbt-fade {
|
|
1486
1779
|
z-index: 2; pointer-events: none; opacity: 0;
|
|
1487
1780
|
background-color: rgba(255, 255, 255, .94);
|
|
@@ -1492,15 +1785,25 @@ window.__ModuleLoader__.load({
|
|
|
1492
1785
|
}
|
|
1493
1786
|
body[data-ds-dark-theme] .dsbt-fade { background-color: rgba(28, 28, 32, .95); }
|
|
1494
1787
|
.dsbt-fade.dsbt-fade-bot { top: auto; bottom: 0; transform: rotate(180deg); }
|
|
1495
|
-
.dsbt-fade.dsbt-fade-on { opacity: 1; }
|
|
1788
|
+
.dsbt-wrap.dsbt-show .dsbt-fade.dsbt-fade-on { opacity: 1; }
|
|
1789
|
+
.dsbt-wrap.dsbt-show .dsbt-fade:not(.dsbt-fade-bot) { top: var(--dsbt-bar-h, 33px); }
|
|
1496
1790
|
|
|
1497
|
-
/* Filter bar — "marked only" toggle pinned above the scroll area
|
|
1791
|
+
/* Filter bar — "marked only" toggle pinned above the scroll area while
|
|
1792
|
+
expanded. Collapsed, it collapses to ZERO height (not just opacity): a
|
|
1793
|
+
transparent-but-in-layout bar pushed every tick ~33px down, one of the
|
|
1794
|
+
reasons the idle capsule read as off-alignment. */
|
|
1498
1795
|
.dsbt-filterbar {
|
|
1796
|
+
height: var(--dsbt-bar-h, 33px);
|
|
1499
1797
|
padding: 8px 12px 4px; display: flex; justify-content: flex-end;
|
|
1500
1798
|
align-items: center; border-bottom: 1px solid rgba(0, 0, 0, .05);
|
|
1501
|
-
box-sizing: border-box; flex: none;
|
|
1799
|
+
box-sizing: border-box; flex: none; overflow: hidden;
|
|
1800
|
+
transition: opacity .2s ease, height .2s ease, padding .2s ease,
|
|
1801
|
+
border-color .2s ease;
|
|
1802
|
+
}
|
|
1803
|
+
.dsbt-wrap:not(.dsbt-show) .dsbt-filterbar {
|
|
1804
|
+
height: 0; padding-top: 0; padding-bottom: 0; opacity: 0;
|
|
1805
|
+
border-bottom-color: transparent;
|
|
1502
1806
|
}
|
|
1503
|
-
.dsbt-wrap:not(.dsbt-show) .dsbt-filterbar { opacity: 0; }
|
|
1504
1807
|
body[data-ds-dark-theme] .dsbt-filterbar { border-bottom-color: rgba(255, 255, 255, .06); }
|
|
1505
1808
|
.dsbt-filterbtn {
|
|
1506
1809
|
font-size: 11px; line-height: 16px; padding: 2px 8px; border-radius: 10px;
|
|
@@ -1522,13 +1825,19 @@ window.__ModuleLoader__.load({
|
|
|
1522
1825
|
background: rgba(251, 191, 36, .18); border-color: #fbbf24; color: #fbbf24;
|
|
1523
1826
|
}
|
|
1524
1827
|
|
|
1525
|
-
/* Scroll area — rows hug the right edge; scrolls only when overfull.
|
|
1828
|
+
/* Scroll area — rows hug the right edge; scrolls only when overfull. The
|
|
1829
|
+
auto-margin pseudo pair vertically centers the stack when it fits the
|
|
1830
|
+
capsule (short conversations), and resolves to zero free space when it
|
|
1831
|
+
overflows, so bottom-pinned scrolling behaves like a plain column. */
|
|
1526
1832
|
.dsbt-page {
|
|
1527
1833
|
padding: 15px 0 15px 24px; box-sizing: border-box;
|
|
1528
1834
|
overscroll-behavior: contain; flex: 1 1 auto; min-height: 0;
|
|
1529
1835
|
flex-direction: column; align-items: flex-end; display: flex;
|
|
1530
1836
|
position: relative; width: 100%; overflow: hidden;
|
|
1531
1837
|
}
|
|
1838
|
+
.dsbt-page::before, .dsbt-page::after {
|
|
1839
|
+
content: ''; flex: none; margin-block: auto;
|
|
1840
|
+
}
|
|
1532
1841
|
.dsbt-wrap.dsbt-show .dsbt-page {
|
|
1533
1842
|
overflow-y: auto; overflow-x: hidden;
|
|
1534
1843
|
scrollbar-width: thin; scrollbar-color: rgba(0, 0, 0, .15) transparent;
|
|
@@ -1548,15 +1857,26 @@ window.__ModuleLoader__.load({
|
|
|
1548
1857
|
|
|
1549
1858
|
/* One message row: 30px, star bookmark + tick indicator at the right,
|
|
1550
1859
|
title fading in. */
|
|
1860
|
+
/* Row shell: layout + shared ink only — the interactive hit areas are the
|
|
1861
|
+
sibling .dsbt-jump / .dsbt-star buttons below. */
|
|
1551
1862
|
.dsbt-item {
|
|
1552
|
-
|
|
1863
|
+
height: 30px; min-height: 30px; flex-shrink: 0;
|
|
1553
1864
|
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;
|
|
1865
|
+
width: calc(100% - 4px); margin-right: 4px;
|
|
1866
|
+
display: flex; box-sizing: border-box;
|
|
1557
1867
|
color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .65));
|
|
1558
1868
|
transition: color .15s ease;
|
|
1559
1869
|
}
|
|
1870
|
+
|
|
1871
|
+
/* Jump target: a real button so Enter/Space work natively without nesting
|
|
1872
|
+
an interactive element inside another. */
|
|
1873
|
+
.dsbt-jump {
|
|
1874
|
+
cursor: pointer; background: none; border: none; margin: 0; padding: 0;
|
|
1875
|
+
font: inherit; line-height: 20px; text-align: right; color: inherit;
|
|
1876
|
+
flex: 1 1 auto; min-width: 0; height: 100%;
|
|
1877
|
+
display: flex; align-items: center; justify-content: flex-end;
|
|
1878
|
+
}
|
|
1879
|
+
.dsbt-jump:focus-visible { outline: 1px solid currentColor; outline-offset: -1px; border-radius: 6px; }
|
|
1560
1880
|
.dsbt-item:hover { color: var(--dsw-alias-label-primary, rgba(0, 0, 0, .95)); }
|
|
1561
1881
|
body[data-ds-dark-theme] .dsbt-item { color: rgba(255, 255, 255, .65); }
|
|
1562
1882
|
body[data-ds-dark-theme] .dsbt-item:hover { color: rgba(255, 255, 255, .95); }
|
|
@@ -1569,14 +1889,17 @@ window.__ModuleLoader__.load({
|
|
|
1569
1889
|
.dsbt-show .dsbt-title { opacity: 1; }
|
|
1570
1890
|
|
|
1571
1891
|
/* Star bookmark — appears on row hover while the panel is expanded; gold
|
|
1572
|
-
when marked.
|
|
1892
|
+
when marked. A real sibling button next to the jump target, so marking
|
|
1893
|
+
stays keyboard-operable without nested interactive elements. */
|
|
1573
1894
|
.dsbt-star {
|
|
1574
1895
|
font-size: 13px; line-height: 16px; width: 18px; height: 18px;
|
|
1575
1896
|
display: inline-flex; align-items: center; justify-content: center;
|
|
1576
1897
|
opacity: 0; cursor: pointer; transition: all .15s ease; margin-right: 6px;
|
|
1577
1898
|
flex-shrink: 0; color: rgba(0, 0, 0, .25); border-radius: 4px; user-select: none;
|
|
1578
1899
|
-webkit-user-select: none;
|
|
1900
|
+
background: none; border: none; padding: 0; font-family: inherit;
|
|
1579
1901
|
}
|
|
1902
|
+
.dsbt-star:focus-visible { outline: 1px solid currentColor; outline-offset: 1px; opacity: .9; }
|
|
1580
1903
|
.dsbt-wrap.dsbt-show .dsbt-item:hover .dsbt-star { opacity: .75; }
|
|
1581
1904
|
.dsbt-wrap.dsbt-show .dsbt-star:hover { opacity: 1; transform: scale(1.2); color: #d97706; }
|
|
1582
1905
|
.dsbt-wrap.dsbt-show .dsbt-star.dsbt-staron { opacity: 1; color: #f59e0b; }
|
|
@@ -1629,7 +1952,7 @@ window.__ModuleLoader__.load({
|
|
|
1629
1952
|
}
|
|
1630
1953
|
|
|
1631
1954
|
@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; }
|
|
1955
|
+
.dsbt-nav, .dsbt-bg, .dsbt-wrap, .dsbt-fade, .dsbt-title, .dsbt-line, .dsbt-jump, .dsbt-star, .dsbt-filterbtn, .dsbt-filterbar { transition: none; }
|
|
1633
1956
|
}
|
|
1634
1957
|
`;
|
|
1635
1958
|
//#endregion
|
|
@@ -1656,10 +1979,17 @@ window.__ModuleLoader__.load({
|
|
|
1656
1979
|
* - Rewind integration: messages withdrawn by a rewind command node are
|
|
1657
1980
|
* filtered out of the rail.
|
|
1658
1981
|
*
|
|
1659
|
-
* Data source
|
|
1660
|
-
*
|
|
1661
|
-
*
|
|
1662
|
-
*
|
|
1982
|
+
* Data source, fastest first: the host-side `bgTimeline` session projection
|
|
1983
|
+
* (src/projection.ts — a complete enumeration of the session's user messages
|
|
1984
|
+
* delivered through the projection baseline/push channel) -> the loaded chat
|
|
1985
|
+
* node window. The conversation's own lazy loading is never driven from here;
|
|
1986
|
+
* clicking a row pages older history on demand, scrolls the conversation to
|
|
1987
|
+
* the target row, and keeps the rail's own active row visible.
|
|
1988
|
+
*
|
|
1989
|
+
* Collapsed idle strategy (official ScrollNav behavior): the tick stack is
|
|
1990
|
+
* pinned to the BOTTOM inner edge — the newest question's tick always sits at
|
|
1991
|
+
* the bottom of the capsule, older ticks clip away at the top. Hovering
|
|
1992
|
+
* expands the SAME box and scrolls the active row into view instead.
|
|
1663
1993
|
*
|
|
1664
1994
|
* The rail reads the shared settings transport: it renders unless
|
|
1665
1995
|
* `timeline: false`. Its glass paints are self-contained official values
|
|
@@ -1746,13 +2076,30 @@ window.__ModuleLoader__.load({
|
|
|
1746
2076
|
* @param chat - a chat snapshot (loosely typed on purpose).
|
|
1747
2077
|
*/
|
|
1748
2078
|
function hiddenSeqsOfChat(chat) {
|
|
2079
|
+
const hidden = rewindHiddenSeqsOfChat(chat);
|
|
2080
|
+
const spans = rewindSpansOfChat(chat);
|
|
2081
|
+
if (spans.length === 0) return hidden;
|
|
2082
|
+
const nodes = chat.nodes;
|
|
2083
|
+
if (nodes === void 0 || typeof nodes.values !== "function") return hidden;
|
|
2084
|
+
for (const node of nodes.values()) {
|
|
2085
|
+
if (node === null || typeof node !== "object") continue;
|
|
2086
|
+
const anchor = typeof node.anchorSeq === "number" ? node.anchorSeq : void 0;
|
|
2087
|
+
if (anchor === void 0) continue;
|
|
2088
|
+
if (spans.some((span) => anchor >= span.start && anchor <= span.end)) hidden.add(anchor);
|
|
2089
|
+
}
|
|
2090
|
+
return hidden;
|
|
2091
|
+
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Seq numbers a rewind hid directly: successful commands hide their own row;
|
|
2094
|
+
* preview commands hide their target.
|
|
2095
|
+
* @param chat - a chat snapshot (loosely typed on purpose).
|
|
2096
|
+
*/
|
|
2097
|
+
function rewindHiddenSeqsOfChat(chat) {
|
|
1749
2098
|
const hidden = /* @__PURE__ */ new Set();
|
|
1750
2099
|
if (chat === null || typeof chat !== "object") return hidden;
|
|
1751
2100
|
const nodes = chat.nodes;
|
|
1752
2101
|
if (nodes === void 0 || typeof nodes.values !== "function") return hidden;
|
|
1753
|
-
const
|
|
1754
|
-
const spans = [];
|
|
1755
|
-
for (const node of all) {
|
|
2102
|
+
for (const node of nodes.values()) {
|
|
1756
2103
|
if (node === null || typeof node !== "object") continue;
|
|
1757
2104
|
if (node.kind !== "command") continue;
|
|
1758
2105
|
const command = node.data;
|
|
@@ -1764,23 +2111,39 @@ window.__ModuleLoader__.load({
|
|
|
1764
2111
|
}
|
|
1765
2112
|
const outcome = command.outcome;
|
|
1766
2113
|
if (outcome === null || typeof outcome !== "object" || outcome.kind !== "success") continue;
|
|
2114
|
+
if (typeof command.seq === "number") hidden.add(command.seq);
|
|
2115
|
+
}
|
|
2116
|
+
return hidden;
|
|
2117
|
+
}
|
|
2118
|
+
/**
|
|
2119
|
+
* The [target, sourceEventSeq] spans successful rewind commands cut — the
|
|
2120
|
+
* range form of hiding, needed to filter sources that are NOT chat nodes
|
|
2121
|
+
* (the host projection) by plain seq membership.
|
|
2122
|
+
* @param chat - a chat snapshot (loosely typed on purpose).
|
|
2123
|
+
*/
|
|
2124
|
+
function rewindSpansOfChat(chat) {
|
|
2125
|
+
const spans = [];
|
|
2126
|
+
if (chat === null || typeof chat !== "object") return spans;
|
|
2127
|
+
const nodes = chat.nodes;
|
|
2128
|
+
if (nodes === void 0 || typeof nodes.values !== "function") return spans;
|
|
2129
|
+
for (const node of nodes.values()) {
|
|
2130
|
+
if (node === null || typeof node !== "object") continue;
|
|
2131
|
+
if (node.kind !== "command") continue;
|
|
2132
|
+
const command = node.data;
|
|
2133
|
+
if (command === null || typeof command !== "object") continue;
|
|
2134
|
+
if (command.name !== "rewind") continue;
|
|
2135
|
+
if (isRewindPreviewCommand(command)) continue;
|
|
2136
|
+
const outcome = command.outcome;
|
|
2137
|
+
if (outcome === null || typeof outcome !== "object" || outcome.kind !== "success") continue;
|
|
1767
2138
|
const marker = outcome.sourceEventSeq;
|
|
1768
2139
|
if (typeof marker !== "number") continue;
|
|
1769
|
-
if (typeof command.seq === "number") hidden.add(command.seq);
|
|
1770
2140
|
const target = rewindTargetOfOutcome(outcome.text);
|
|
1771
2141
|
if (target !== void 0) spans.push({
|
|
1772
2142
|
start: target,
|
|
1773
2143
|
end: marker
|
|
1774
2144
|
});
|
|
1775
2145
|
}
|
|
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;
|
|
2146
|
+
return spans;
|
|
1784
2147
|
}
|
|
1785
2148
|
/**
|
|
1786
2149
|
* Enumerate user messages from the loaded chat-node snapshot (the client-side
|
|
@@ -1816,6 +2179,65 @@ window.__ModuleLoader__.load({
|
|
|
1816
2179
|
function resolveAnchorKey(m) {
|
|
1817
2180
|
return typeof m.key === "string" && m.key !== "" ? m.key : void 0;
|
|
1818
2181
|
}
|
|
2182
|
+
/** The user-message node definition kind in ui-conversation. */
|
|
2183
|
+
const INPUT_MESSAGE_KIND = "input-message";
|
|
2184
|
+
/**
|
|
2185
|
+
* Rebuild a chat row's DOM anchor key from the durable message id — the same
|
|
2186
|
+
* engine formula as conversationContextKey(kind, id) for the input-message
|
|
2187
|
+
* definition (`13:input-message<id>`), so projected entries can jump even
|
|
2188
|
+
* when their chat node is not loaded yet.
|
|
2189
|
+
* @param id - the durable message id carried by the projection entry.
|
|
2190
|
+
*/
|
|
2191
|
+
function inputAnchorKeyOf(id) {
|
|
2192
|
+
return `13:${INPUT_MESSAGE_KIND}${id}`;
|
|
2193
|
+
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Validate one projected wire value into rail entries (defensive: the value
|
|
2196
|
+
* crosses the wire and may be absent, partial, or stale).
|
|
2197
|
+
* @param value - the raw useProjection(TIMELINE_PROJECTION_KEY) snapshot.
|
|
2198
|
+
*/
|
|
2199
|
+
function normalizeProjectedTimeline(value) {
|
|
2200
|
+
if (value === null || typeof value !== "object") return [];
|
|
2201
|
+
const raw = value.messages;
|
|
2202
|
+
if (!Array.isArray(raw)) return [];
|
|
2203
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2204
|
+
const out = [];
|
|
2205
|
+
for (const item of raw) {
|
|
2206
|
+
if (item === null || typeof item !== "object") continue;
|
|
2207
|
+
const rec = item;
|
|
2208
|
+
if (typeof rec.seq !== "number" || !Number.isFinite(rec.seq)) continue;
|
|
2209
|
+
if (seen.has(rec.seq)) continue;
|
|
2210
|
+
seen.add(rec.seq);
|
|
2211
|
+
out.push({
|
|
2212
|
+
seq: rec.seq,
|
|
2213
|
+
time: typeof rec.time === "number" ? rec.time : 0,
|
|
2214
|
+
text: typeof rec.text === "string" ? rec.text.slice(0, 80) : "",
|
|
2215
|
+
...typeof rec.id === "string" && rec.id !== "" ? { key: inputAnchorKeyOf(rec.id) } : {}
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
out.sort((a, b) => a.seq - b.seq);
|
|
2219
|
+
return out;
|
|
2220
|
+
}
|
|
2221
|
+
/**
|
|
2222
|
+
* Rail messages with the fastest source first: the host projection covers the
|
|
2223
|
+
* WHOLE session (and already excludes rewind cuts), so it wins whenever it
|
|
2224
|
+
* has anything; otherwise fall back to the loaded chat-node window. The
|
|
2225
|
+
* projection path still applies locally-known rewind hiding so a cut lands
|
|
2226
|
+
* instantly even before the next projection push arrives.
|
|
2227
|
+
* @param snapshot - the session conversation snapshot (node fallback).
|
|
2228
|
+
* @param projected - the raw bgTimeline projection value.
|
|
2229
|
+
*/
|
|
2230
|
+
function railMessages(snapshot, projected) {
|
|
2231
|
+
const projectedMessages = normalizeProjectedTimeline(projected);
|
|
2232
|
+
if (projectedMessages.length > 0) {
|
|
2233
|
+
const chat = snapshot?.chat;
|
|
2234
|
+
const hidden = rewindHiddenSeqsOfChat(chat);
|
|
2235
|
+
const spans = rewindSpansOfChat(chat);
|
|
2236
|
+
if (hidden.size === 0 && spans.length === 0) return projectedMessages;
|
|
2237
|
+
return projectedMessages.filter((m) => !hidden.has(m.seq) && !spans.some((span) => m.seq >= span.start && m.seq <= span.end));
|
|
2238
|
+
}
|
|
2239
|
+
return collectMessages(snapshot);
|
|
2240
|
+
}
|
|
1819
2241
|
/**
|
|
1820
2242
|
* Outer rail height for a message count: rows + breathing room, clamped so
|
|
1821
2243
|
* both states always share the exact same height (never jumps on expand).
|
|
@@ -1839,7 +2261,7 @@ window.__ModuleLoader__.load({
|
|
|
1839
2261
|
if (typeof document !== "undefined") {
|
|
1840
2262
|
const ctx = document.createElement("canvas").getContext("2d");
|
|
1841
2263
|
if (ctx) {
|
|
1842
|
-
ctx.font =
|
|
2264
|
+
ctx.font = `13px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
|
|
1843
2265
|
for (const t of texts) {
|
|
1844
2266
|
const w = ctx.measureText(t === "" ? "…" : t).width;
|
|
1845
2267
|
if (w > widest) widest = w;
|
|
@@ -1894,7 +2316,7 @@ window.__ModuleLoader__.load({
|
|
|
1894
2316
|
* conversation input dock purely to bind per-session lifecycle.
|
|
1895
2317
|
*/
|
|
1896
2318
|
function TimelineRail(props) {
|
|
1897
|
-
const { sessionId, sessionsService, t } = props;
|
|
2319
|
+
const { sessionId, sessionsService, useProjection, t } = props;
|
|
1898
2320
|
const settings = react.default.useSyncExternalStore(settingsClient.subscribe, settingsClient.getSnapshot);
|
|
1899
2321
|
const timelineUnknown = settings.status === "loading";
|
|
1900
2322
|
const timelineDisabled = settings.value?.timeline === false;
|
|
@@ -1902,8 +2324,8 @@ window.__ModuleLoader__.load({
|
|
|
1902
2324
|
const subscribeSession = react.default.useCallback((listener) => session === void 0 ? () => {} : session.subscribe(listener), [session]);
|
|
1903
2325
|
const snapshotSession = react.default.useCallback(() => session === void 0 ? void 0 : session.getSnapshot(), [session]);
|
|
1904
2326
|
const nodeSnapshot = react.default.useSyncExternalStore(subscribeSession, snapshotSession);
|
|
1905
|
-
const
|
|
1906
|
-
const
|
|
2327
|
+
const projected = useProjection !== void 0 ? useProjection(TIMELINE_PROJECTION_KEY) : void 0;
|
|
2328
|
+
const messages = react.default.useMemo(() => railMessages(nodeSnapshot, projected), [nodeSnapshot, projected]);
|
|
1907
2329
|
const [activeIndex, setActiveIndex] = react.default.useState(-1);
|
|
1908
2330
|
const [show, setShow] = react.default.useState(false);
|
|
1909
2331
|
const [rightOffset, setRightOffset] = react.default.useState(16);
|
|
@@ -1944,40 +2366,8 @@ window.__ModuleLoader__.load({
|
|
|
1944
2366
|
return () => mql.removeEventListener("change", onChange);
|
|
1945
2367
|
}, []);
|
|
1946
2368
|
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
2369
|
setRailWidth(railWidthFor(messages.map((m) => m.text)));
|
|
1980
|
-
}, [
|
|
2370
|
+
}, [messages]);
|
|
1981
2371
|
react.default.useEffect(() => {
|
|
1982
2372
|
if (messages.length === 0) return;
|
|
1983
2373
|
const indexByKey = /* @__PURE__ */ new Map();
|
|
@@ -1985,17 +2375,21 @@ window.__ModuleLoader__.load({
|
|
|
1985
2375
|
const key = resolveAnchorKey(m);
|
|
1986
2376
|
if (key !== void 0) indexByKey.set(key, i);
|
|
1987
2377
|
});
|
|
2378
|
+
const sp = document.querySelector("[data-conversation-scroll]");
|
|
2379
|
+
if (sp === null) return;
|
|
2380
|
+
const rows = sp.querySelectorAll("[data-chat-anchor-key]");
|
|
2381
|
+
let settleTimer;
|
|
2382
|
+
let frame = 0;
|
|
2383
|
+
const nearRows = /* @__PURE__ */ new Set();
|
|
1988
2384
|
const updateActive = () => {
|
|
1989
2385
|
if (jumpPendingRef.current) return;
|
|
1990
|
-
const sp = document.querySelector("[data-conversation-scroll]");
|
|
1991
|
-
if (sp === null) return;
|
|
1992
2386
|
const rect = sp.getBoundingClientRect();
|
|
1993
2387
|
if (rect.height === 0) return;
|
|
2388
|
+
const candidates = nearRows.size > 0 ? nearRows : rows;
|
|
1994
2389
|
const line = rect.top + rect.height * .4;
|
|
1995
|
-
const rows = sp.querySelectorAll("[data-chat-anchor-key]");
|
|
1996
2390
|
let best = -1;
|
|
1997
2391
|
let bestDist = Infinity;
|
|
1998
|
-
for (const row of
|
|
2392
|
+
for (const row of candidates) {
|
|
1999
2393
|
const key = row.getAttribute("data-chat-anchor-key");
|
|
2000
2394
|
if (key === null) continue;
|
|
2001
2395
|
const idx = indexByKey.get(key) ?? -1;
|
|
@@ -2009,10 +2403,22 @@ window.__ModuleLoader__.load({
|
|
|
2009
2403
|
}
|
|
2010
2404
|
setActiveIndex(best);
|
|
2011
2405
|
};
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2406
|
+
const scheduleMeasure = () => {
|
|
2407
|
+
if (frame !== 0) return;
|
|
2408
|
+
frame = window.requestAnimationFrame(() => {
|
|
2409
|
+
frame = 0;
|
|
2410
|
+
updateActive();
|
|
2411
|
+
});
|
|
2412
|
+
};
|
|
2413
|
+
let io;
|
|
2414
|
+
if (typeof IntersectionObserver === "function") {
|
|
2415
|
+
io = new IntersectionObserver((entries) => {
|
|
2416
|
+
for (const entry of entries) if (entry.isIntersecting) nearRows.add(entry.target);
|
|
2417
|
+
else nearRows.delete(entry.target);
|
|
2418
|
+
scheduleMeasure();
|
|
2419
|
+
}, { root: sp });
|
|
2420
|
+
rows.forEach((row) => io?.observe(row));
|
|
2421
|
+
}
|
|
2016
2422
|
const onScroll = () => {
|
|
2017
2423
|
if (settleTimer !== void 0) clearTimeout(settleTimer);
|
|
2018
2424
|
settleTimer = window.setTimeout(() => {
|
|
@@ -2026,22 +2432,20 @@ window.__ModuleLoader__.load({
|
|
|
2026
2432
|
updateActive();
|
|
2027
2433
|
}
|
|
2028
2434
|
}, 150);
|
|
2029
|
-
|
|
2030
|
-
timer = window.setTimeout(() => {
|
|
2031
|
-
timer = void 0;
|
|
2032
|
-
updateActive();
|
|
2033
|
-
}, 60);
|
|
2435
|
+
scheduleMeasure();
|
|
2034
2436
|
};
|
|
2035
|
-
|
|
2036
|
-
const interval = window.setInterval(
|
|
2437
|
+
sp.addEventListener("scroll", onScroll, { passive: true });
|
|
2438
|
+
const interval = window.setInterval(scheduleMeasure, 2e3);
|
|
2439
|
+
scheduleMeasure();
|
|
2037
2440
|
return () => {
|
|
2038
|
-
|
|
2441
|
+
io?.disconnect();
|
|
2442
|
+
if (frame !== 0) cancelAnimationFrame(frame);
|
|
2039
2443
|
if (settleTimer !== void 0) clearTimeout(settleTimer);
|
|
2040
2444
|
if (jumpFallbackRef.current !== void 0) {
|
|
2041
2445
|
clearTimeout(jumpFallbackRef.current);
|
|
2042
2446
|
jumpFallbackRef.current = void 0;
|
|
2043
2447
|
}
|
|
2044
|
-
|
|
2448
|
+
sp.removeEventListener("scroll", onScroll);
|
|
2045
2449
|
clearInterval(interval);
|
|
2046
2450
|
};
|
|
2047
2451
|
}, [messages]);
|
|
@@ -2065,7 +2469,6 @@ window.__ModuleLoader__.load({
|
|
|
2065
2469
|
syncFades,
|
|
2066
2470
|
show,
|
|
2067
2471
|
messages.length,
|
|
2068
|
-
loadedAll,
|
|
2069
2472
|
marksOnly
|
|
2070
2473
|
]);
|
|
2071
2474
|
react.default.useLayoutEffect(() => {
|
|
@@ -2082,6 +2485,18 @@ window.__ModuleLoader__.load({
|
|
|
2082
2485
|
show,
|
|
2083
2486
|
syncFades
|
|
2084
2487
|
]);
|
|
2488
|
+
const height = railHeightFor(messages.length);
|
|
2489
|
+
react.default.useLayoutEffect(() => {
|
|
2490
|
+
if (show) return;
|
|
2491
|
+
const page = pageRef.current;
|
|
2492
|
+
if (page === null) return;
|
|
2493
|
+
page.scrollTop = page.scrollHeight;
|
|
2494
|
+
}, [
|
|
2495
|
+
show,
|
|
2496
|
+
messages.length,
|
|
2497
|
+
marksOnly,
|
|
2498
|
+
height
|
|
2499
|
+
]);
|
|
2085
2500
|
react.default.useEffect(() => {
|
|
2086
2501
|
let raf = 0;
|
|
2087
2502
|
const measure = () => {
|
|
@@ -2109,8 +2524,7 @@ window.__ModuleLoader__.load({
|
|
|
2109
2524
|
clearInterval(guard);
|
|
2110
2525
|
};
|
|
2111
2526
|
}, []);
|
|
2112
|
-
if (timelineUnknown || timelineDisabled || sessionId === void 0 || sessionsService === void 0 || isNarrow || hidden || messages.length
|
|
2113
|
-
const height = railHeightFor(messages.length);
|
|
2527
|
+
if (timelineUnknown || timelineDisabled || sessionId === void 0 || sessionsService === void 0 || isNarrow || hidden || messages.length === 0) return null;
|
|
2114
2528
|
const activeKey = activeIndex >= 0 && activeIndex < messages.length ? markKeyOf(messages[activeIndex]) : "";
|
|
2115
2529
|
const displayMessages = marksOnly ? messages.filter((m) => markedSet.has(markKeyOf(m))) : messages;
|
|
2116
2530
|
return react_dom.default.createPortal(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2157,51 +2571,37 @@ window.__ModuleLoader__.load({
|
|
|
2157
2571
|
const mk = markKeyOf(m);
|
|
2158
2572
|
const marked = markedSet.has(mk);
|
|
2159
2573
|
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
|
-
},
|
|
2574
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2575
|
+
className: `dsbt-item${isActive ? " dsbt-active" : ""}${marked ? " dsbt-marked" : ""}`,
|
|
2179
2576
|
children: [
|
|
2180
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
2181
|
-
|
|
2182
|
-
|
|
2577
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2578
|
+
type: "button",
|
|
2579
|
+
className: "dsbt-jump",
|
|
2580
|
+
ref: isActive ? activeItemRef : void 0,
|
|
2581
|
+
title: `${marked ? "★ " : ""}${m.text === "" ? t("timeline.noText") : m.text}`,
|
|
2582
|
+
"aria-label": `${marked ? "★ " : ""}${t("timeline.roleUser")}: ${m.text.slice(0, 60) || t("timeline.noText")}`,
|
|
2583
|
+
"aria-current": isActive ? "location" : void 0,
|
|
2584
|
+
onClick: () => {
|
|
2585
|
+
if (key === void 0) return;
|
|
2586
|
+
jumpPendingRef.current = true;
|
|
2587
|
+
if (jumpFallbackRef.current !== void 0) clearTimeout(jumpFallbackRef.current);
|
|
2588
|
+
jumpFallbackRef.current = window.setTimeout(() => {
|
|
2589
|
+
jumpFallbackRef.current = void 0;
|
|
2590
|
+
jumpPendingRef.current = false;
|
|
2591
|
+
}, 800);
|
|
2592
|
+
jumpToMessage(sessionsService, sessionId, key).catch(() => {});
|
|
2593
|
+
},
|
|
2594
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2595
|
+
className: "dsbt-title",
|
|
2596
|
+
children: m.text === "" ? t("timeline.noText") : m.text
|
|
2597
|
+
})
|
|
2183
2598
|
}),
|
|
2184
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
2599
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2600
|
+
type: "button",
|
|
2185
2601
|
className: marked ? "dsbt-star dsbt-staron" : "dsbt-star",
|
|
2186
|
-
role: "button",
|
|
2187
|
-
tabIndex: 0,
|
|
2188
2602
|
"aria-pressed": marked,
|
|
2189
2603
|
"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
|
-
},
|
|
2604
|
+
onClick: () => toggleMark(m),
|
|
2205
2605
|
children: "★"
|
|
2206
2606
|
}),
|
|
2207
2607
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -2236,6 +2636,7 @@ window.__ModuleLoader__.load({
|
|
|
2236
2636
|
* @param ctx - client cordis context (slots/locale/sessions injected).
|
|
2237
2637
|
*/
|
|
2238
2638
|
function apply(ctx) {
|
|
2639
|
+
const glassBridge = installGlassBridge();
|
|
2239
2640
|
const paint = () => {
|
|
2240
2641
|
const snapshot = settingsClient.getSnapshot();
|
|
2241
2642
|
const value = snapshot.status === "ready" ? snapshot.value : void 0;
|
|
@@ -2266,6 +2667,7 @@ window.__ModuleLoader__.load({
|
|
|
2266
2667
|
}, TimelineRail));
|
|
2267
2668
|
ctx.effect(() => () => {
|
|
2268
2669
|
unsubscribe();
|
|
2670
|
+
glassBridge.dispose();
|
|
2269
2671
|
backgroundPainter.dispose();
|
|
2270
2672
|
}, "deepseek-harness-background: background surface");
|
|
2271
2673
|
}
|