dsh-milestone 0.6.6 → 0.7.1
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 +192 -165
- package/lib/client.js +657 -179
- package/lib/index.js +250 -6
- package/package.json +29 -9
package/lib/client.js
CHANGED
|
@@ -4,9 +4,31 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
-
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
7
|
let react = require("react");
|
|
9
|
-
let
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
10
|
+
//#region src/client/modal-tokens.ts
|
|
11
|
+
/**
|
|
12
|
+
* modal-tokens: the ONE source for the settings modal's dark-panel palette and
|
|
13
|
+
* radius scale, shared with the 0.6.5 coach-tour bubble so the two surfaces
|
|
14
|
+
* cannot drift (same dark panel on a dark host, same three text tiers, same
|
|
15
|
+
* 12px panel / 8px control radius, same border tone).
|
|
16
|
+
*
|
|
17
|
+
* The static MODAL_CSS block (hover/focus-visible/reduced-motion rules) stays
|
|
18
|
+
* in MilestoneRail.tsx because it also styles the settings modal's own chrome
|
|
19
|
+
* (`[data-support-card]`, `[data-toolbar-*]`, ...); the tour bubble carries
|
|
20
|
+
* its own small TOUR_CSS block for its controls, built from these same tokens.
|
|
21
|
+
* MilestoneRail.tsx imports these constants verbatim so a future palette
|
|
22
|
+
* change edits exactly one file.
|
|
23
|
+
*/
|
|
24
|
+
const MODAL_BG = "rgba(20, 24, 32, 0.98)";
|
|
25
|
+
const MODAL_FG = "#e6e8ee";
|
|
26
|
+
const MODAL_TITLE = "#c7cede";
|
|
27
|
+
const MODAL_TEXT = "#b9c2d4";
|
|
28
|
+
const MODAL_HINT = "#8b96ab";
|
|
29
|
+
const MODAL_BORDER = "rgba(255, 255, 255, 0.14)";
|
|
30
|
+
const MODAL_TIP_BG = "#222834";
|
|
31
|
+
//#endregion
|
|
10
32
|
//#region src/client/MilestoneOverlay.tsx
|
|
11
33
|
/**
|
|
12
34
|
* @param props - runtime share (root kit) + the narrowed renderSlot and the
|
|
@@ -15,7 +37,7 @@ window.__ModuleLoader__.load({
|
|
|
15
37
|
function MilestoneOverlay({ SessionProvider, renderSlot }) {
|
|
16
38
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionProvider, {
|
|
17
39
|
empty: () => null,
|
|
18
|
-
children:
|
|
40
|
+
children: renderSlot("milestone.rail", {})
|
|
19
41
|
});
|
|
20
42
|
}
|
|
21
43
|
//#endregion
|
|
@@ -129,6 +151,23 @@ window.__ModuleLoader__.load({
|
|
|
129
151
|
* @param input - the mark's snapshot signals.
|
|
130
152
|
* @returns the winning badge kind, or null when no signal applies.
|
|
131
153
|
*/
|
|
154
|
+
/**
|
|
155
|
+
* 0.1.5: durable badge kinds for one projected turn. The fold no longer sees
|
|
156
|
+
* the chat-layer `turn-error` / `turn-max-tokens` node kinds, but `turn/end`'s
|
|
157
|
+
* reason carries the same signal, and every `assistant/attempt` settlement
|
|
158
|
+
* (failed / retried / cancelled / stream error) is a retry.
|
|
159
|
+
* @param turn - the projection's turn meta (only the two fields are read).
|
|
160
|
+
* @returns the node-kind vocabulary {@link deriveBadge} consumes; empty when normal.
|
|
161
|
+
*/
|
|
162
|
+
function kindsForTurn(turn) {
|
|
163
|
+
const kinds = [];
|
|
164
|
+
if (turn.endReason === "error" || turn.endReason === "interrupted") kinds.push("turn-error");
|
|
165
|
+
if (turn.endReason === "max-tokens") kinds.push("turn-max-tokens");
|
|
166
|
+
if ((turn.attempts ?? 0) > 0) kinds.push("model-retry");
|
|
167
|
+
return kinds.length === 0 ? EMPTY_KINDS : kinds;
|
|
168
|
+
}
|
|
169
|
+
/** Stable empty result so normal turns never allocate a fresh array. */
|
|
170
|
+
const EMPTY_KINDS = Object.freeze([]);
|
|
132
171
|
function deriveBadge(input) {
|
|
133
172
|
if (input.nodeKinds.includes("turn-error")) return "error";
|
|
134
173
|
if (input.nodeKinds.includes("turn-max-tokens")) return "max-tokens";
|
|
@@ -198,6 +237,82 @@ window.__ModuleLoader__.load({
|
|
|
198
237
|
50% { opacity: 0.45; box-shadow: ${`0 0 0 2px ${layer(.35)}, 0 0 4px 1px ${layer(.25)}, 0 0 9px 3px ${layer(.12)}`}; }
|
|
199
238
|
}`;
|
|
200
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Clamp a ball's top-left so the WHOLE circle stays inside the viewport.
|
|
242
|
+
*
|
|
243
|
+
* Per axis: `max = Math.max(0, viewportSize - ballSize - margin)` and
|
|
244
|
+
* `min = Math.min(margin, max)`. When there IS room, `min` is the margin, so
|
|
245
|
+
* the ball keeps its preferred distance from the edge; when the viewport is
|
|
246
|
+
* smaller than ball + margins, `max` collapses to 0 (or below) and `min`
|
|
247
|
+
* follows it, pinning the ball to the top/left outer edge instead of letting
|
|
248
|
+
* it escape the viewport.
|
|
249
|
+
*
|
|
250
|
+
* Non-finite `pos` values degrade to `0` BEFORE clamping, so a corrupt call
|
|
251
|
+
* still lands on a finite, fully-visible position.
|
|
252
|
+
*/
|
|
253
|
+
function clampBallPosition(pos, viewport, ballSize = 40, margin = 8) {
|
|
254
|
+
const xRaw = Number.isFinite(pos.x) ? pos.x : 0;
|
|
255
|
+
const yRaw = Number.isFinite(pos.y) ? pos.y : 0;
|
|
256
|
+
const maxX = Math.max(0, viewport.width - ballSize - margin);
|
|
257
|
+
const minX = Math.min(margin, maxX);
|
|
258
|
+
const maxY = Math.max(0, viewport.height - ballSize - margin);
|
|
259
|
+
return {
|
|
260
|
+
x: Math.min(maxX, Math.max(minX, xRaw)),
|
|
261
|
+
y: Math.min(maxY, Math.max(Math.min(margin, maxY), yRaw))
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* True when pointer travel from `start` to `current` exceeds the drag
|
|
266
|
+
* threshold — the gesture vocabulary of the ball: a press that stays put (or
|
|
267
|
+
* wiggles less than the threshold) is a CLICK (toggle the rail), a press that
|
|
268
|
+
* travels farther is a DRAG (move the ball).
|
|
269
|
+
*
|
|
270
|
+
* Distance is Euclidean, so a diagonal move counts the same as a straight one.
|
|
271
|
+
* EXACTLY at the threshold is still a click (strictly greater than).
|
|
272
|
+
*/
|
|
273
|
+
function isDragGesture(start, current, threshold = 5) {
|
|
274
|
+
const dx = current.x - start.x;
|
|
275
|
+
const dy = current.y - start.y;
|
|
276
|
+
return Math.hypot(dx, dy) > threshold;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Sanitize a persisted ball position; return null when unusable.
|
|
280
|
+
*
|
|
281
|
+
* Accepts only a plain object carrying FINITE numeric `x` and `y` (extra keys
|
|
282
|
+
* are ignored, so a future blob extension stays readable). Everything else —
|
|
283
|
+
* `null`, arrays, strings, numbers, NaN/Infinity, missing keys — degrades to
|
|
284
|
+
* `null`, which callers read as "no stored position, use the computed default
|
|
285
|
+
* resting spot" (see {@link defaultBallPosition}).
|
|
286
|
+
*
|
|
287
|
+
* No clamping happens here: the stored value is resolution-agnostic and gets
|
|
288
|
+
* clamped against the live viewport by {@link clampBallPosition} at render
|
|
289
|
+
* time.
|
|
290
|
+
*/
|
|
291
|
+
function sanitizeBallPosition(raw) {
|
|
292
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
293
|
+
const { x, y } = raw;
|
|
294
|
+
if (typeof x !== "number" || !Number.isFinite(x)) return null;
|
|
295
|
+
if (typeof y !== "number" || !Number.isFinite(y)) return null;
|
|
296
|
+
return {
|
|
297
|
+
x,
|
|
298
|
+
y
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Default resting spot: hugging `side` at `inset` from the edge, vertically
|
|
303
|
+
* centered. `inset` is measured from the nearest screen edge; the vertical
|
|
304
|
+
* axis is simply centered on the viewport.
|
|
305
|
+
*
|
|
306
|
+
* The raw spot is always passed through {@link clampBallPosition} with the
|
|
307
|
+
* default margin, so the resting spot is fully visible even when `inset`
|
|
308
|
+
* overflows or the viewport is smaller than the ball.
|
|
309
|
+
*/
|
|
310
|
+
function defaultBallPosition(viewport, side, inset, ballSize = 40) {
|
|
311
|
+
return clampBallPosition({
|
|
312
|
+
x: side === "left" ? inset : viewport.width - ballSize - inset,
|
|
313
|
+
y: (viewport.height - ballSize) / 2
|
|
314
|
+
}, viewport, ballSize, 8);
|
|
315
|
+
}
|
|
201
316
|
//#endregion
|
|
202
317
|
//#region src/client/bookmark-logic.ts
|
|
203
318
|
/**
|
|
@@ -385,65 +500,21 @@ window.__ModuleLoader__.load({
|
|
|
385
500
|
inputTokens: null,
|
|
386
501
|
outputTokens: null
|
|
387
502
|
};
|
|
388
|
-
/** True when the value is a plain (non-array, non-null) object. */
|
|
389
|
-
function isRecord(value) {
|
|
390
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* Decode a `usage` payload structurally: only numeric `inputTokens` /
|
|
394
|
-
* `outputTokens` survive; anything else (absent, malformed, wrong types)
|
|
395
|
-
* degrades to null — the boundary owns trust, the callers get plain numbers.
|
|
396
|
-
* @param usage - untrusted usage payload (typed `unknown` at runtime).
|
|
397
|
-
* @returns the token counts with null for every missing/malformed field.
|
|
398
|
-
*/
|
|
399
|
-
function decodeUsage(usage) {
|
|
400
|
-
if (!isRecord(usage)) return {
|
|
401
|
-
inputTokens: null,
|
|
402
|
-
outputTokens: null
|
|
403
|
-
};
|
|
404
|
-
return {
|
|
405
|
-
inputTokens: typeof usage.inputTokens === "number" ? usage.inputTokens : null,
|
|
406
|
-
outputTokens: typeof usage.outputTokens === "number" ? usage.outputTokens : null
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
/** Resolve model/purpose from a request config, falling back to provenance. */
|
|
410
|
-
function metaFromRecord(record) {
|
|
411
|
-
return {
|
|
412
|
-
model: record.requestConfig?.model ?? record.provenance?.model ?? null,
|
|
413
|
-
purpose: record.requestConfig?.purpose ?? null,
|
|
414
|
-
...decodeUsage(record.usage)
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
503
|
/**
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
* no assistant-step node yields a model or purpose);
|
|
423
|
-
* 3. all-null when the turn is absent, no node matches, or everything is
|
|
424
|
-
* malformed. Never throws.
|
|
425
|
-
* @param nodes - stable per-key chat node reader (as exposed by the snapshot).
|
|
426
|
-
* @param locations - turn -> ordered node keys index.
|
|
427
|
-
* @param turn - owning turn; undefined yields all-null.
|
|
428
|
-
* @param trajectoryRequests - optional fallback request log.
|
|
504
|
+
* 0.1.2: derive hover metadata from the `milestone.messages` projection's
|
|
505
|
+
* per-turn fold state. The fold carries token usage and timing; provider /
|
|
506
|
+
* model provenance is not folded yet, so those degrade to null.
|
|
507
|
+
* @param turn - the projection's turn meta, or undefined when absent.
|
|
429
508
|
* @returns the turn's metadata, null where unknown.
|
|
430
509
|
*/
|
|
431
|
-
function
|
|
510
|
+
function deriveTurnMetaFromProjection(turn) {
|
|
432
511
|
if (turn === void 0) return EMPTY_META;
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
if (meta.model !== null || meta.purpose !== null) return meta;
|
|
440
|
-
}
|
|
441
|
-
if (trajectoryRequests !== void 0) {
|
|
442
|
-
let latest;
|
|
443
|
-
for (const request of trajectoryRequests) if (request.turn === turn) latest = request;
|
|
444
|
-
if (latest !== void 0) return metaFromRecord(latest);
|
|
445
|
-
}
|
|
446
|
-
return EMPTY_META;
|
|
512
|
+
return {
|
|
513
|
+
model: turn.model ?? null,
|
|
514
|
+
purpose: null,
|
|
515
|
+
inputTokens: turn.usage?.input ?? null,
|
|
516
|
+
outputTokens: turn.usage?.output ?? null
|
|
517
|
+
};
|
|
447
518
|
}
|
|
448
519
|
//#endregion
|
|
449
520
|
//#region src/client/rail-keyboard.ts
|
|
@@ -481,22 +552,6 @@ window.__ModuleLoader__.load({
|
|
|
481
552
|
* can consume them directly and tests can exercise them in isolation.
|
|
482
553
|
*/
|
|
483
554
|
/**
|
|
484
|
-
* Extract the FULL plain text of a ContentBlock[] payload: the `text` of every
|
|
485
|
-
* `{ type: 'text', text: string }` block, joined with a single space and
|
|
486
|
-
* trimmed. Unlike the rail's hover preview this is NOT truncated — callers use
|
|
487
|
-
* it for search matching, so the entire message must be searchable.
|
|
488
|
-
* @param content - untrusted payload; anything that is not an array yields ''.
|
|
489
|
-
*/
|
|
490
|
-
function extractText(content) {
|
|
491
|
-
if (!Array.isArray(content)) return "";
|
|
492
|
-
const parts = [];
|
|
493
|
-
for (const block of content) if (block !== null && typeof block === "object" && block.type === "text") {
|
|
494
|
-
const text = block.text;
|
|
495
|
-
if (typeof text === "string") parts.push(text);
|
|
496
|
-
}
|
|
497
|
-
return parts.join(" ").trim();
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
555
|
* Case-insensitive substring filter over mark texts.
|
|
501
556
|
* @param marks - marks in rail order.
|
|
502
557
|
* @param query - the search query; empty/whitespace matches everything.
|
|
@@ -621,6 +676,10 @@ window.__ModuleLoader__.load({
|
|
|
621
676
|
"rail.label": "会话里程碑",
|
|
622
677
|
/** aria-label on the dot list. */
|
|
623
678
|
"rail.list": "会话里程碑列表",
|
|
679
|
+
/** aria-label on the rail-collapse control (issue #4: fold the rail into the floating ball). */
|
|
680
|
+
"rail.collapse": "收起为悬浮球",
|
|
681
|
+
/** aria-label + title on the collapsed rail's floating ball (click = expand). */
|
|
682
|
+
"ball.expand": "展开里程碑条",
|
|
624
683
|
/** Hover preview fallback for empty message text. */
|
|
625
684
|
"no.text": "(无文本)",
|
|
626
685
|
/** Relative time: `< 60s`. */
|
|
@@ -711,6 +770,8 @@ window.__ModuleLoader__.load({
|
|
|
711
770
|
"settings.section.personal": "个性化",
|
|
712
771
|
/** Settings modal: section heading for the focus-mode controls (0.6.3). */
|
|
713
772
|
"settings.section.focus": "聚焦",
|
|
773
|
+
/** Settings modal: section heading for the floating-ball controls (issue #4). */
|
|
774
|
+
"settings.section.ball": "悬浮球",
|
|
714
775
|
/** Settings: personalization-section hint shown inside the expanded block. */
|
|
715
776
|
"settings.personal.hint": "圆点、强调色与位置,即调即存",
|
|
716
777
|
/** Settings: aria-label on the personalization block toggle while COLLAPSED. */
|
|
@@ -747,6 +808,16 @@ window.__ModuleLoader__.load({
|
|
|
747
808
|
"settings.side.left": "左侧",
|
|
748
809
|
/** Settings personalization: side radio — hug the right edge. */
|
|
749
810
|
"settings.side.right": "右侧",
|
|
811
|
+
/** Settings: floating-ball block — hint shown inside the expanded block. */
|
|
812
|
+
"settings.ball.hint": "收起后变为悬浮球;可固定,也可自由拖动到任意位置",
|
|
813
|
+
/** Settings: floating-ball behavior row label + radiogroup aria-label. */
|
|
814
|
+
"settings.ball.mode": "行为",
|
|
815
|
+
/** Settings: floating-ball behavior radio — pinned to the resting spot. */
|
|
816
|
+
"settings.ball.mode.fixed": "固定",
|
|
817
|
+
/** Settings: floating-ball behavior radio — free drag anywhere. */
|
|
818
|
+
"settings.ball.mode.draggable": "可拖动",
|
|
819
|
+
/** Settings: floating-ball action — drop the persisted drag position. */
|
|
820
|
+
"settings.ball.reset": "重置位置",
|
|
750
821
|
/** Settings: focus block — hint shown inside the expanded block. */
|
|
751
822
|
"settings.focus.hint": "这些选项自由组合成你的「聚焦搭配」;总开关仍是工具栏的眼睛按钮",
|
|
752
823
|
/** Settings: aria-label on the focus block toggle while COLLAPSED. */
|
|
@@ -853,6 +924,8 @@ window.__ModuleLoader__.load({
|
|
|
853
924
|
"load.older": "Load older messages",
|
|
854
925
|
"rail.label": "Session milestones",
|
|
855
926
|
"rail.list": "Session milestone list",
|
|
927
|
+
"rail.collapse": "Collapse to floating ball",
|
|
928
|
+
"ball.expand": "Expand milestone rail",
|
|
856
929
|
"no.text": "(no text)",
|
|
857
930
|
"time.justNow": "Just now",
|
|
858
931
|
"time.minutes": "{n} minutes ago",
|
|
@@ -899,6 +972,7 @@ window.__ModuleLoader__.load({
|
|
|
899
972
|
"settings.section.features": "Features & Shortcuts",
|
|
900
973
|
"settings.section.personal": "Personalization",
|
|
901
974
|
"settings.section.focus": "Focus",
|
|
975
|
+
"settings.section.ball": "Floating ball",
|
|
902
976
|
"settings.personal.hint": "Dot size, accent color, and position — saved as you adjust",
|
|
903
977
|
"settings.personal.expand": "Expand personalization",
|
|
904
978
|
"settings.personal.collapse": "Collapse personalization",
|
|
@@ -917,6 +991,11 @@ window.__ModuleLoader__.load({
|
|
|
917
991
|
"settings.side": "Position",
|
|
918
992
|
"settings.side.left": "Left",
|
|
919
993
|
"settings.side.right": "Right",
|
|
994
|
+
"settings.ball.hint": "Collapses into a floating ball — pin it, or drag it anywhere",
|
|
995
|
+
"settings.ball.mode": "Behavior",
|
|
996
|
+
"settings.ball.mode.fixed": "Fixed",
|
|
997
|
+
"settings.ball.mode.draggable": "Draggable",
|
|
998
|
+
"settings.ball.reset": "Reset position",
|
|
920
999
|
"settings.focus.hint": "Combine these options into your own focus recipe; the eye button on the toolbar stays the master switch",
|
|
921
1000
|
"settings.focus.expand": "Expand focus settings",
|
|
922
1001
|
"settings.focus.collapse": "Collapse focus settings",
|
|
@@ -1423,12 +1502,6 @@ window.__ModuleLoader__.load({
|
|
|
1423
1502
|
]
|
|
1424
1503
|
});
|
|
1425
1504
|
}
|
|
1426
|
-
const MODAL_FG = "#e6e8ee";
|
|
1427
|
-
const MODAL_TITLE = "#c7cede";
|
|
1428
|
-
const MODAL_TEXT = "#b9c2d4";
|
|
1429
|
-
const MODAL_HINT = "#8b96ab";
|
|
1430
|
-
const MODAL_BORDER = "rgba(255, 255, 255, 0.14)";
|
|
1431
|
-
const MODAL_TIP_BG = "#222834";
|
|
1432
1505
|
//#endregion
|
|
1433
1506
|
//#region src/client/onboarding-store.ts
|
|
1434
1507
|
/**
|
|
@@ -2287,7 +2360,8 @@ window.__ModuleLoader__.load({
|
|
|
2287
2360
|
* toolbar-prefs: the persistence layer for the milestone rail's toolbar
|
|
2288
2361
|
* personalization — WHICH function keys stay visible outside the collapse
|
|
2289
2362
|
* (pinned) plus the settings-module appearance prefs (accent color, icon/dot
|
|
2290
|
-
* size, distance from the rail's screen edge,
|
|
2363
|
+
* size, distance from the rail's screen edge, rail side) and the collapsed
|
|
2364
|
+
* rail's floating ball (mode + last position).
|
|
2291
2365
|
*
|
|
2292
2366
|
* Storage contract: one localStorage key (`dsh-milestone.toolbar`) holding a
|
|
2293
2367
|
* JSON object:
|
|
@@ -2295,13 +2369,17 @@ window.__ModuleLoader__.load({
|
|
|
2295
2369
|
* { "pinned": string[], "accent": "#rrggbb", "iconSize": number,
|
|
2296
2370
|
* "inset": number, "side": "left" | "right", "locale": "system"|"zh"|"en",
|
|
2297
2371
|
* "focus": { "dimThink": boolean, "dimTools": boolean,
|
|
2298
|
-
* "collapseThink": boolean, "opacity": number }
|
|
2372
|
+
* "collapseThink": boolean, "opacity": number },
|
|
2373
|
+
* "ballMode": "fixed" | "draggable",
|
|
2374
|
+
* "ball": { "x": number, "y": number } | null }
|
|
2299
2375
|
*
|
|
2300
2376
|
* Backward compatibility: the pre-personalization blob `{ "pinned": string[] }`
|
|
2301
2377
|
* (and an entirely absent value) parses to the DEFAULT prefs with the new
|
|
2302
2378
|
* fields at their defaults — old users keep their pins untouched. The same
|
|
2303
2379
|
* rule covers the `focus` object: a blob stored before 0.6.3 (no `focus`
|
|
2304
|
-
* field) gains the default focus mix
|
|
2380
|
+
* field) gains the default focus mix, and a blob stored before the floating
|
|
2381
|
+
* ball (no `ballMode` / `ball`) gains `ballMode: 'draggable'` + `ball: null`
|
|
2382
|
+
* (the computed default resting spot).
|
|
2305
2383
|
*
|
|
2306
2384
|
* All reads are sanitized per field:
|
|
2307
2385
|
* - `pinned`: whitelisted ids only (`TOOLBAR_PIN_IDS`), duplicates dropped,
|
|
@@ -2313,7 +2391,11 @@ window.__ModuleLoader__.load({
|
|
|
2313
2391
|
* - `side`: exactly `'left'` or `'right'`;
|
|
2314
2392
|
* - `focus`: three booleans (`dimThink` / `dimTools` / `collapseThink`)
|
|
2315
2393
|
* defaulting to `true` / `false` / `false`, plus the dim `opacity`
|
|
2316
|
-
* snapped to the 0.1 step and clamped to [0.2, 0.8]
|
|
2394
|
+
* snapped to the 0.1 step and clamped to [0.2, 0.8];
|
|
2395
|
+
* - `ballMode`: exactly `'fixed'`, otherwise `'draggable'` (including a
|
|
2396
|
+
* legacy blob stored without the field);
|
|
2397
|
+
* - `ball`: a finite `{x, y}` pair, otherwise `null` — viewport clamping
|
|
2398
|
+
* happens at render time (`clampBallPosition`), never in storage.
|
|
2317
2399
|
*
|
|
2318
2400
|
* The whitelist lives HERE (not in MilestoneRail) so the pure functions stay
|
|
2319
2401
|
* dependency-free and unit-testable; MilestoneRail's feature registry keys
|
|
@@ -2357,7 +2439,9 @@ window.__ModuleLoader__.load({
|
|
|
2357
2439
|
inset: 14,
|
|
2358
2440
|
side: "right",
|
|
2359
2441
|
locale: "system",
|
|
2360
|
-
focus: { ...DEFAULT_FOCUS_PREFS }
|
|
2442
|
+
focus: { ...DEFAULT_FOCUS_PREFS },
|
|
2443
|
+
ballMode: "draggable",
|
|
2444
|
+
ball: null
|
|
2361
2445
|
};
|
|
2362
2446
|
/** Type guard for registry ids — unknown strings never survive a parse. */
|
|
2363
2447
|
function isToolbarPinId(id) {
|
|
@@ -2425,7 +2509,7 @@ window.__ModuleLoader__.load({
|
|
|
2425
2509
|
return { ...DEFAULT_PREFS };
|
|
2426
2510
|
}
|
|
2427
2511
|
if (typeof parsed !== "object" || parsed === null) return { ...DEFAULT_PREFS };
|
|
2428
|
-
const { pinned, accent, iconSize, inset, side, locale, focus } = parsed;
|
|
2512
|
+
const { pinned, accent, iconSize, inset, side, locale, focus, ballMode, ball } = parsed;
|
|
2429
2513
|
return {
|
|
2430
2514
|
pinned: sanitizePinned(pinned),
|
|
2431
2515
|
accent: typeof accent === "string" && isHexColor(accent) ? accent.toLowerCase() : DEFAULT_PREFS.accent,
|
|
@@ -2433,7 +2517,9 @@ window.__ModuleLoader__.load({
|
|
|
2433
2517
|
inset: clampStep(inset, 0, 40, 2, DEFAULT_PREFS.inset),
|
|
2434
2518
|
side: side === "left" || side === "right" ? side : DEFAULT_PREFS.side,
|
|
2435
2519
|
locale: locale === "zh" || locale === "en" || locale === "system" ? locale : DEFAULT_PREFS.locale,
|
|
2436
|
-
focus: sanitizeFocus(focus)
|
|
2520
|
+
focus: sanitizeFocus(focus),
|
|
2521
|
+
ballMode: ballMode === "fixed" ? "fixed" : "draggable",
|
|
2522
|
+
ball: sanitizeBallPosition(ball)
|
|
2437
2523
|
};
|
|
2438
2524
|
}
|
|
2439
2525
|
/**
|
|
@@ -2779,7 +2865,7 @@ window.__ModuleLoader__.load({
|
|
|
2779
2865
|
* Installed plugin version. Injected at build time as
|
|
2780
2866
|
* `__DSH_MILESTONE_VERSION__`; falls back to `0.0.0-dev` when unbuilt.
|
|
2781
2867
|
*/
|
|
2782
|
-
const PLUGIN_VERSION = "0.
|
|
2868
|
+
const PLUGIN_VERSION = "0.7.1";
|
|
2783
2869
|
//#endregion
|
|
2784
2870
|
//#region src/client/MilestoneRail.tsx
|
|
2785
2871
|
/**
|
|
@@ -2833,9 +2919,12 @@ window.__ModuleLoader__.load({
|
|
|
2833
2919
|
*/
|
|
2834
2920
|
/** Minimum user messages before the rail adds value. */
|
|
2835
2921
|
const MIN_MARKS = 2;
|
|
2836
|
-
const PREVIEW_LENGTH = 80;
|
|
2837
2922
|
/** Stable no-bookmarks fallback for render paths without the store seat. */
|
|
2838
2923
|
const NO_BOOKMARKS = [];
|
|
2924
|
+
/** Stable empty projection fallback before the host unit mounts. */
|
|
2925
|
+
const EMPTY_PROJECTION_MESSAGES = Object.freeze([]);
|
|
2926
|
+
/** 0.1.2: per-turn badge kinds are not projected yet; stays empty. */
|
|
2927
|
+
const EMPTY_KINDS_BY_TURN = /* @__PURE__ */ new Map();
|
|
2839
2928
|
/** Stable no-kinds fallback for marks whose turn carries no badge nodes. */
|
|
2840
2929
|
const NO_KINDS = [];
|
|
2841
2930
|
/** Visual dot diameter at the default icon size (px). */
|
|
@@ -2958,24 +3047,24 @@ window.__ModuleLoader__.load({
|
|
|
2958
3047
|
/* Row and header washes (the inline styles deliberately leave backgrounds
|
|
2959
3048
|
unset so these rules win over the default padding-box background). */
|
|
2960
3049
|
[data-toolbar-pin-toggle]:hover, [data-toolbar-pin-toggle]:focus-visible { background: rgba(255, 255, 255, 0.06); }
|
|
2961
|
-
[data-personal-toggle]:hover, [data-focus-toggle-settings]:hover { background: rgba(255, 255, 255, 0.05); }
|
|
3050
|
+
[data-personal-toggle]:hover, [data-focus-toggle-settings]:hover, [data-ball-toggle]:hover { background: rgba(255, 255, 255, 0.05); }
|
|
2962
3051
|
[data-focus-option]:hover { background: rgba(255, 255, 255, 0.04); }
|
|
2963
3052
|
[data-toolbar-settings-close]:hover { background: rgba(255, 255, 255, 0.08); }
|
|
2964
|
-
[data-toolbar-settings-reset], [data-onboarding-reopen] { background: rgba(255, 255, 255, 0.06); }
|
|
2965
|
-
[data-toolbar-settings-reset]:hover, [data-onboarding-reopen]:hover { background: rgba(255, 255, 255, 0.1); }
|
|
3053
|
+
[data-toolbar-settings-reset], [data-onboarding-reopen], [data-ball-reset] { background: rgba(255, 255, 255, 0.06); }
|
|
3054
|
+
[data-toolbar-settings-reset]:hover, [data-onboarding-reopen]:hover, [data-ball-reset]:hover { background: rgba(255, 255, 255, 0.1); }
|
|
2966
3055
|
/* BASE state reset: every modal surface must sit transparent on the dark
|
|
2967
3056
|
panel — without it the UA default button face (light gray) floods through
|
|
2968
3057
|
and rows become unreadable light-on-light. Hover washes above take over
|
|
2969
3058
|
on interaction. */
|
|
2970
|
-
[data-toolbar-pin-toggle], [data-personal-toggle], [data-focus-toggle-settings],
|
|
3059
|
+
[data-toolbar-pin-toggle], [data-personal-toggle], [data-focus-toggle-settings], [data-ball-toggle],
|
|
2971
3060
|
[data-toolbar-settings-close], [data-focus-option] {
|
|
2972
3061
|
background: transparent;
|
|
2973
3062
|
}
|
|
2974
3063
|
/* ONE accent ring for keyboard focus on every modal control. */
|
|
2975
3064
|
[data-toolbar-pin-toggle]:focus-visible, [data-personal-toggle]:focus-visible,
|
|
2976
|
-
[data-focus-toggle-settings]:focus-visible,
|
|
3065
|
+
[data-focus-toggle-settings]:focus-visible, [data-ball-toggle]:focus-visible,
|
|
2977
3066
|
[data-toolbar-settings-close]:focus-visible, [data-toolbar-settings-reset]:focus-visible,
|
|
2978
|
-
[data-onboarding-reopen]:focus-visible {
|
|
3067
|
+
[data-onboarding-reopen]:focus-visible, [data-ball-reset]:focus-visible {
|
|
2979
3068
|
box-shadow: 0 0 0 2px var(--ms-accent-soft);
|
|
2980
3069
|
}
|
|
2981
3070
|
/* Near-row description tip: a rotated square peeks out of the LEFT edge so
|
|
@@ -2989,10 +3078,10 @@ window.__ModuleLoader__.load({
|
|
|
2989
3078
|
}
|
|
2990
3079
|
/* Tip + chevron motion lives here (not inline) so reduced-motion can kill it. */
|
|
2991
3080
|
[data-settings-tip] { transition: opacity 140ms ease, transform 140ms ease, visibility 140ms; }
|
|
2992
|
-
[data-personal-toggle] svg, [data-focus-toggle-settings] svg { transition: transform 150ms ease; }
|
|
2993
|
-
/* One authored reveal: the personalization/focus bodies fade in on expand. */
|
|
3081
|
+
[data-personal-toggle] svg, [data-focus-toggle-settings] svg, [data-ball-toggle] svg { transition: transform 150ms ease; }
|
|
3082
|
+
/* One authored reveal: the personalization/focus/ball bodies fade in on expand. */
|
|
2994
3083
|
@keyframes ms-settings-fade { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: none; } }
|
|
2995
|
-
[data-settings-personal-body], [data-settings-focus-body] { animation: ms-settings-fade 140ms ease; }
|
|
3084
|
+
[data-settings-personal-body], [data-settings-focus-body], [data-settings-ball-body] { animation: ms-settings-fade 140ms ease; }
|
|
2996
3085
|
/* Thin themed scrollbar for the scrollable modal panel. */
|
|
2997
3086
|
[data-toolbar-settings-panel]::-webkit-scrollbar { width: 10px; }
|
|
2998
3087
|
[data-toolbar-settings-panel]::-webkit-scrollbar-thumb {
|
|
@@ -3000,8 +3089,8 @@ window.__ModuleLoader__.load({
|
|
|
3000
3089
|
}
|
|
3001
3090
|
[data-toolbar-settings-panel]::-webkit-scrollbar-track { background: transparent; }
|
|
3002
3091
|
@media (prefers-reduced-motion: reduce) {
|
|
3003
|
-
[data-settings-tip], [data-personal-toggle] svg, [data-focus-toggle-settings] svg, [data-support-card] { transition: none; }
|
|
3004
|
-
[data-settings-personal-body], [data-settings-focus-body] { animation: none; }
|
|
3092
|
+
[data-settings-tip], [data-personal-toggle] svg, [data-focus-toggle-settings] svg, [data-ball-toggle] svg, [data-support-card] { transition: none; }
|
|
3093
|
+
[data-settings-personal-body], [data-settings-focus-body], [data-settings-ball-body] { animation: none; }
|
|
3005
3094
|
}
|
|
3006
3095
|
[data-search-toggle] { color: #8b96ab !important; }
|
|
3007
3096
|
[data-search-toggle][aria-pressed="true"] { background: var(--ms-accent-bg) !important; color: var(--ms-accent-soft) !important; }
|
|
@@ -3075,26 +3164,21 @@ window.__ModuleLoader__.load({
|
|
|
3075
3164
|
* Find a chat row by its node key, avoiding CSS.escape pitfalls on keys that
|
|
3076
3165
|
* contain `<`/`>`/`:` (the node key is `13:input-message<messageId>`).
|
|
3077
3166
|
*/
|
|
3078
|
-
function findRow(key) {
|
|
3079
|
-
|
|
3167
|
+
function findRow(key, messageId) {
|
|
3168
|
+
const rows = document.querySelectorAll("[data-chat-anchor-key]");
|
|
3169
|
+
for (const row of rows) if (row.dataset.chatAnchorKey === key) return row;
|
|
3170
|
+
if (messageId !== void 0 && messageId !== "") for (const row of rows) {
|
|
3171
|
+
const anchor = row.dataset.chatAnchorKey;
|
|
3172
|
+
if (anchor !== void 0 && anchor.endsWith(messageId)) return row;
|
|
3173
|
+
}
|
|
3080
3174
|
return null;
|
|
3081
3175
|
}
|
|
3082
|
-
/** Extract a plain-text hover preview (first 80 chars) from a ContentBlock[]. */
|
|
3083
|
-
function extractPreview(content) {
|
|
3084
|
-
return extractText(content).slice(0, PREVIEW_LENGTH);
|
|
3085
|
-
}
|
|
3086
3176
|
/** Compact duration label (ms). */
|
|
3087
3177
|
function formatDuration(ms) {
|
|
3088
3178
|
if (ms < 1e3) return `${ms}ms`;
|
|
3089
3179
|
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
3090
3180
|
return `${Math.floor(ms / 6e4)}m${Math.floor(ms % 6e4 / 1e3)}s`;
|
|
3091
3181
|
}
|
|
3092
|
-
/** Read the ui-conversation 'turn-tail' location data (ttftMs/tokensPerSecond). */
|
|
3093
|
-
function turnTailOf(turn) {
|
|
3094
|
-
const data = turn.data;
|
|
3095
|
-
if (data?.get === void 0) return void 0;
|
|
3096
|
-
return data.get("turn-tail");
|
|
3097
|
-
}
|
|
3098
3182
|
/**
|
|
3099
3183
|
* @param props - session standard kit (useSession, sessionId, useProjection),
|
|
3100
3184
|
* the injected loadOlder/forkAt actions, the bookmarks store pair (useStore +
|
|
@@ -3103,52 +3187,37 @@ window.__ModuleLoader__.load({
|
|
|
3103
3187
|
* `locale: 'dsh-milestone'`; defaults to a key-pass fallback for renders
|
|
3104
3188
|
* outside the slot machinery).
|
|
3105
3189
|
*/
|
|
3106
|
-
function MilestoneRail({ useSession, loadOlder, forkAt, useStore, actions, searchSessions = async () => ({
|
|
3190
|
+
function MilestoneRail({ useSession, useProjection, loadOlder, forkAt, useStore, actions, searchSessions = async () => ({
|
|
3107
3191
|
items: [],
|
|
3108
3192
|
hasMore: false
|
|
3109
3193
|
}), openSession = () => {}, t: frameworkT = (key) => key }) {
|
|
3110
|
-
const
|
|
3111
|
-
const nodes = useSession((s) => s.chat.nodes);
|
|
3112
|
-
const locations = useSession((s) => s.chat.locations);
|
|
3113
|
-
const timeline = useSession((s) => s.chat.timeline);
|
|
3114
|
-
const trajectoryRequests = useSession((s) => s.views.get("trajectory")?.requests);
|
|
3194
|
+
const projection = useProjection?.("milestone.messages");
|
|
3115
3195
|
const hasMore = useSession((s) => s.hasMore);
|
|
3116
3196
|
const loadingOlder = useSession((s) => s.loadingOlder);
|
|
3117
3197
|
const bookmarkedKeys = useStore?.((s) => s.keys) ?? NO_BOOKMARKS;
|
|
3118
3198
|
const marks = (0, react.useMemo)(() => {
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
time: data.time ?? 0,
|
|
3130
|
-
text: extractText(data.content),
|
|
3131
|
-
preview: extractPreview(data.content)
|
|
3132
|
-
});
|
|
3133
|
-
}
|
|
3134
|
-
return result;
|
|
3135
|
-
}, [order, nodes]);
|
|
3199
|
+
return (projection?.messages ?? EMPTY_PROJECTION_MESSAGES).map((m) => ({
|
|
3200
|
+
key: String(m.seq),
|
|
3201
|
+
turn: m.turn,
|
|
3202
|
+
seq: m.seq,
|
|
3203
|
+
messageId: m.messageId,
|
|
3204
|
+
time: m.time,
|
|
3205
|
+
text: m.text,
|
|
3206
|
+
preview: m.preview
|
|
3207
|
+
}));
|
|
3208
|
+
}, [projection]);
|
|
3136
3209
|
const kindsByTurn = (0, react.useMemo)(() => {
|
|
3137
|
-
const
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
if (node.location.kind !== "turn" && node.location.kind !== "step") continue;
|
|
3144
|
-
const kinds = result.get(node.location.turn.turn) ?? [];
|
|
3145
|
-
kinds.push(node.kind);
|
|
3146
|
-
result.set(node.location.turn.turn, kinds);
|
|
3210
|
+
const turns = projection?.turns;
|
|
3211
|
+
if (turns === void 0 || turns.length === 0) return EMPTY_KINDS_BY_TURN;
|
|
3212
|
+
const map = /* @__PURE__ */ new Map();
|
|
3213
|
+
for (const t of turns) {
|
|
3214
|
+
const kinds = kindsForTurn(t);
|
|
3215
|
+
if (kinds.length > 0) map.set(t.turn, kinds);
|
|
3147
3216
|
}
|
|
3148
|
-
return
|
|
3149
|
-
}, [
|
|
3217
|
+
return map;
|
|
3218
|
+
}, [projection]);
|
|
3150
3219
|
const running = useSession((s) => s.running);
|
|
3151
|
-
const awaitingInput = useSession((s) => s.
|
|
3220
|
+
const awaitingInput = useSession((s) => s.queue).length > 0;
|
|
3152
3221
|
const [railBox, setRailBox] = (0, react.useState)(null);
|
|
3153
3222
|
const [hover, setHover] = (0, react.useState)(null);
|
|
3154
3223
|
const [search, setSearch] = (0, react.useState)({
|
|
@@ -3167,7 +3236,7 @@ window.__ModuleLoader__.load({
|
|
|
3167
3236
|
const [collapsedTurns, setCollapsedTurns] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
3168
3237
|
const [focusIndex, setFocusIndex] = (0, react.useState)(0);
|
|
3169
3238
|
const listRef = (0, react.useRef)(null);
|
|
3170
|
-
const currentKey = useCurrentAnchor(
|
|
3239
|
+
const currentKey = useCurrentAnchor(marks.map((m) => m.key));
|
|
3171
3240
|
/**
|
|
3172
3241
|
* P3: jump to the chat row with the given node key — smooth-scroll it into
|
|
3173
3242
|
* view and write the position back into the URL hash (`#msg=<key>`) so
|
|
@@ -3177,8 +3246,8 @@ window.__ModuleLoader__.load({
|
|
|
3177
3246
|
* rail's own updates. No-op when the row is not (yet) rendered — the
|
|
3178
3247
|
* deep-link mount retry and the load-older flow cover that case.
|
|
3179
3248
|
*/
|
|
3180
|
-
const jump = (key) => {
|
|
3181
|
-
const row = findRow(key);
|
|
3249
|
+
const jump = (key, messageId) => {
|
|
3250
|
+
const row = findRow(key, messageId);
|
|
3182
3251
|
if (row === null) return;
|
|
3183
3252
|
row.scrollIntoView({
|
|
3184
3253
|
behavior: "smooth",
|
|
@@ -3215,7 +3284,7 @@ window.__ModuleLoader__.load({
|
|
|
3215
3284
|
}, [displayMarks]);
|
|
3216
3285
|
const displayTurns = (0, react.useMemo)(() => buildDisplayTurns(marks), [marks]);
|
|
3217
3286
|
const [prefs, setPrefs] = (0, react.useState)(() => loadPrefs());
|
|
3218
|
-
const { pinned, accent, iconSize, inset, side } = prefs;
|
|
3287
|
+
const { pinned, accent, iconSize, inset, side, ballMode, ball } = prefs;
|
|
3219
3288
|
const scale = iconSize / DOT_HIT;
|
|
3220
3289
|
const hit = iconSize;
|
|
3221
3290
|
const size = DOT_SIZE * scale;
|
|
@@ -3295,6 +3364,27 @@ window.__ModuleLoader__.load({
|
|
|
3295
3364
|
/** B-design (0.6.3): the focus block mirrors the personalization block —
|
|
3296
3365
|
* collapsed by default, the header leads with a live option summary. */
|
|
3297
3366
|
const [focusOpen, setFocusOpen] = (0, react.useState)(false);
|
|
3367
|
+
const [railCollapsed, setRailCollapsed] = (0, react.useState)(false);
|
|
3368
|
+
const [railCollapseHovered, setRailCollapseHovered] = (0, react.useState)(false);
|
|
3369
|
+
/** Issue #4 settings: the floating-ball block mirrors the personalization /
|
|
3370
|
+
* focus collapsibles — collapsed by default, the header leads with the mode. */
|
|
3371
|
+
const [ballOpen, setBallOpen] = (0, react.useState)(false);
|
|
3372
|
+
/** The live ball position while a press is in flight (`null` = resting). */
|
|
3373
|
+
const [dragPos, setDragPos] = (0, react.useState)(null);
|
|
3374
|
+
const dragRef = (0, react.useRef)(null);
|
|
3375
|
+
/** Detaches the in-flight press's window listeners (set on pointerdown). */
|
|
3376
|
+
const dragListenersRef = (0, react.useRef)(null);
|
|
3377
|
+
(0, react.useEffect)(() => () => {
|
|
3378
|
+
dragListenersRef.current?.();
|
|
3379
|
+
dragListenersRef.current = null;
|
|
3380
|
+
}, []);
|
|
3381
|
+
const [, setBallTick] = (0, react.useState)(0);
|
|
3382
|
+
(0, react.useEffect)(() => {
|
|
3383
|
+
if (!railCollapsed) return;
|
|
3384
|
+
const onResize = () => setBallTick((n) => n + 1);
|
|
3385
|
+
window.addEventListener("resize", onResize);
|
|
3386
|
+
return () => window.removeEventListener("resize", onResize);
|
|
3387
|
+
}, [railCollapsed]);
|
|
3298
3388
|
const settingsRef = (0, react.useRef)(null);
|
|
3299
3389
|
const settingsBtnRef = (0, react.useRef)(null);
|
|
3300
3390
|
const [updateOpen, setUpdateOpen] = (0, react.useState)(false);
|
|
@@ -3396,11 +3486,12 @@ window.__ModuleLoader__.load({
|
|
|
3396
3486
|
let timer;
|
|
3397
3487
|
const attempt = (pollsLeft, canLoadOlder) => {
|
|
3398
3488
|
if (cancelled) return;
|
|
3399
|
-
|
|
3400
|
-
|
|
3489
|
+
const mark = marksRef.current.find((m) => m.key === key);
|
|
3490
|
+
if (findRow(key, mark?.messageId) !== null) {
|
|
3491
|
+
jump(key, mark?.messageId);
|
|
3401
3492
|
return;
|
|
3402
3493
|
}
|
|
3403
|
-
if (marksRef.current.length > 0 &&
|
|
3494
|
+
if (marksRef.current.length > 0 && mark === void 0) return;
|
|
3404
3495
|
if (pollsLeft > 0) {
|
|
3405
3496
|
timer = window.setTimeout(() => attempt(pollsLeft - 1, canLoadOlder), DEEP_LINK_POLL_DELAY);
|
|
3406
3497
|
return;
|
|
@@ -3422,7 +3513,8 @@ window.__ModuleLoader__.load({
|
|
|
3422
3513
|
const onHashChange = () => {
|
|
3423
3514
|
const key = parseDeepLinkHash(window.location.hash);
|
|
3424
3515
|
if (key === null) return;
|
|
3425
|
-
|
|
3516
|
+
const mark = marksRef.current.find((m) => m.key === key);
|
|
3517
|
+
if (mark !== void 0) jump(key, mark.messageId);
|
|
3426
3518
|
};
|
|
3427
3519
|
window.addEventListener("hashchange", onHashChange);
|
|
3428
3520
|
return () => window.removeEventListener("hashchange", onHashChange);
|
|
@@ -3531,7 +3623,8 @@ window.__ModuleLoader__.load({
|
|
|
3531
3623
|
...s,
|
|
3532
3624
|
activePos: next
|
|
3533
3625
|
}));
|
|
3534
|
-
|
|
3626
|
+
const mark = displayMarks[matches[next]];
|
|
3627
|
+
jump(mark.key, mark.messageId);
|
|
3535
3628
|
};
|
|
3536
3629
|
const onSearchKeyDown = (e) => {
|
|
3537
3630
|
if (e.key === "Enter") advanceMatch();
|
|
@@ -3861,24 +3954,21 @@ window.__ModuleLoader__.load({
|
|
|
3861
3954
|
const buildHover = (mark, index) => {
|
|
3862
3955
|
if (copiedKey !== null && mark.key !== copiedKey) setCopiedKey(null);
|
|
3863
3956
|
if (forkedKey !== null && mark.key !== forkedKey) setForkedKey(null);
|
|
3864
|
-
const
|
|
3957
|
+
const turnMeta = mark.turn !== void 0 ? projection?.turns.find((turn) => turn.turn === mark.turn) : void 0;
|
|
3865
3958
|
let durationLabel = null;
|
|
3866
3959
|
let reasonLabel = null;
|
|
3867
3960
|
let ttftLabel = null;
|
|
3868
3961
|
let tpsLabel = null;
|
|
3869
|
-
if (
|
|
3870
|
-
if (
|
|
3871
|
-
if (
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
if (tail !== void 0) {
|
|
3877
|
-
if (tail.ttftMs !== void 0) ttftLabel = formatDuration(tail.ttftMs);
|
|
3878
|
-
if (tail.tokensPerSecond !== void 0) tpsLabel = `${tail.tokensPerSecond.toFixed(1)} tok/s`;
|
|
3962
|
+
if (turnMeta !== void 0) {
|
|
3963
|
+
if (turnMeta.startTime !== void 0 && turnMeta.endTime !== void 0) durationLabel = formatDuration(turnMeta.endTime - turnMeta.startTime);
|
|
3964
|
+
if (turnMeta.endReason !== void 0) reasonLabel = t(reasonKeyOf(turnMeta.endReason));
|
|
3965
|
+
if (turnMeta.firstChunkTime !== void 0 && turnMeta.startTime !== void 0) ttftLabel = formatDuration(turnMeta.firstChunkTime - turnMeta.startTime);
|
|
3966
|
+
if (turnMeta.usage !== void 0 && durationLabel !== null) {
|
|
3967
|
+
const seconds = (turnMeta.endTime - turnMeta.startTime) / 1e3;
|
|
3968
|
+
if (seconds > 0) tpsLabel = `${(turnMeta.usage.output / seconds).toFixed(1)} tok/s`;
|
|
3879
3969
|
}
|
|
3880
3970
|
}
|
|
3881
|
-
const meta =
|
|
3971
|
+
const meta = deriveTurnMetaFromProjection(turnMeta);
|
|
3882
3972
|
const summaryCount = collapsedSummaries.get(mark.key);
|
|
3883
3973
|
return {
|
|
3884
3974
|
mark,
|
|
@@ -3943,6 +4033,157 @@ window.__ModuleLoader__.load({
|
|
|
3943
4033
|
forkAt(mark.seq).then(() => setForkedKey(mark.key));
|
|
3944
4034
|
};
|
|
3945
4035
|
const showLoadOlder = hasMore && marks.length >= MIN_MARKS;
|
|
4036
|
+
if (railCollapsed) {
|
|
4037
|
+
const viewport = {
|
|
4038
|
+
width: window.innerWidth,
|
|
4039
|
+
height: window.innerHeight
|
|
4040
|
+
};
|
|
4041
|
+
const resting = clampBallPosition(ball ?? defaultBallPosition(viewport, side, inset), viewport);
|
|
4042
|
+
const pos = dragPos ?? resting;
|
|
4043
|
+
/** Detach the in-flight press's window listeners (idempotent). */
|
|
4044
|
+
const removeDragListeners = () => {
|
|
4045
|
+
const detach = dragListenersRef.current;
|
|
4046
|
+
if (detach === null) return;
|
|
4047
|
+
dragListenersRef.current = null;
|
|
4048
|
+
detach();
|
|
4049
|
+
};
|
|
4050
|
+
/** Release the pointer capture when the DOM implementation supports it. */
|
|
4051
|
+
const releaseBallCapture = (el, pointerId) => {
|
|
4052
|
+
if (typeof el.releasePointerCapture !== "function") return;
|
|
4053
|
+
try {
|
|
4054
|
+
el.releasePointerCapture(pointerId);
|
|
4055
|
+
} catch {}
|
|
4056
|
+
};
|
|
4057
|
+
/**
|
|
4058
|
+
* Press start: in draggable mode record the gesture anchor and arm the
|
|
4059
|
+
* window pointer listeners; in fixed mode do nothing drag-related — the
|
|
4060
|
+
* plain onClick fallback below still expands the rail.
|
|
4061
|
+
*/
|
|
4062
|
+
const onBallPointerDown = (e) => {
|
|
4063
|
+
if (ballMode !== "draggable") return;
|
|
4064
|
+
const el = e.currentTarget;
|
|
4065
|
+
if (typeof el.setPointerCapture === "function") try {
|
|
4066
|
+
el.setPointerCapture(e.pointerId);
|
|
4067
|
+
} catch {}
|
|
4068
|
+
if (dragRef.current !== null) removeDragListeners();
|
|
4069
|
+
dragRef.current = {
|
|
4070
|
+
pointerId: e.pointerId,
|
|
4071
|
+
startPointer: {
|
|
4072
|
+
x: e.clientX,
|
|
4073
|
+
y: e.clientY
|
|
4074
|
+
},
|
|
4075
|
+
startPos: pos,
|
|
4076
|
+
moved: false
|
|
4077
|
+
};
|
|
4078
|
+
const onMove = (ev) => {
|
|
4079
|
+
const drag = dragRef.current;
|
|
4080
|
+
if (drag === null) return;
|
|
4081
|
+
const live = {
|
|
4082
|
+
width: window.innerWidth,
|
|
4083
|
+
height: window.innerHeight
|
|
4084
|
+
};
|
|
4085
|
+
setDragPos(clampBallPosition({
|
|
4086
|
+
x: drag.startPos.x + (ev.clientX - drag.startPointer.x),
|
|
4087
|
+
y: drag.startPos.y + (ev.clientY - drag.startPointer.y)
|
|
4088
|
+
}, live));
|
|
4089
|
+
drag.moved = drag.moved || isDragGesture(drag.startPointer, {
|
|
4090
|
+
x: ev.clientX,
|
|
4091
|
+
y: ev.clientY
|
|
4092
|
+
});
|
|
4093
|
+
};
|
|
4094
|
+
const onUp = (ev) => {
|
|
4095
|
+
const drag = dragRef.current;
|
|
4096
|
+
if (drag === null) return;
|
|
4097
|
+
removeDragListeners();
|
|
4098
|
+
releaseBallCapture(el, drag.pointerId);
|
|
4099
|
+
if (drag.moved) {
|
|
4100
|
+
const live = {
|
|
4101
|
+
width: window.innerWidth,
|
|
4102
|
+
height: window.innerHeight
|
|
4103
|
+
};
|
|
4104
|
+
updatePrefs({ ball: clampBallPosition({
|
|
4105
|
+
x: drag.startPos.x + (ev.clientX - drag.startPointer.x),
|
|
4106
|
+
y: drag.startPos.y + (ev.clientY - drag.startPointer.y)
|
|
4107
|
+
}, live) });
|
|
4108
|
+
} else setRailCollapsed(false);
|
|
4109
|
+
setDragPos(null);
|
|
4110
|
+
dragRef.current = null;
|
|
4111
|
+
};
|
|
4112
|
+
const onCancel = () => {
|
|
4113
|
+
const drag = dragRef.current;
|
|
4114
|
+
if (drag === null) return;
|
|
4115
|
+
removeDragListeners();
|
|
4116
|
+
releaseBallCapture(el, drag.pointerId);
|
|
4117
|
+
setDragPos(null);
|
|
4118
|
+
dragRef.current = null;
|
|
4119
|
+
};
|
|
4120
|
+
window.addEventListener("pointermove", onMove);
|
|
4121
|
+
window.addEventListener("pointerup", onUp);
|
|
4122
|
+
window.addEventListener("pointercancel", onCancel);
|
|
4123
|
+
dragListenersRef.current = () => {
|
|
4124
|
+
window.removeEventListener("pointermove", onMove);
|
|
4125
|
+
window.removeEventListener("pointerup", onUp);
|
|
4126
|
+
window.removeEventListener("pointercancel", onCancel);
|
|
4127
|
+
};
|
|
4128
|
+
};
|
|
4129
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4130
|
+
"data-milestone-ball": true,
|
|
4131
|
+
role: "button",
|
|
4132
|
+
tabIndex: 0,
|
|
4133
|
+
"aria-label": t("ball.expand"),
|
|
4134
|
+
title: t("ball.expand"),
|
|
4135
|
+
"data-ball-x": String(Math.round(pos.x)),
|
|
4136
|
+
"data-ball-y": String(Math.round(pos.y)),
|
|
4137
|
+
onPointerDown: onBallPointerDown,
|
|
4138
|
+
onClick: ballMode === "fixed" ? () => setRailCollapsed(false) : void 0,
|
|
4139
|
+
onKeyDown: (e) => {
|
|
4140
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
4141
|
+
e.preventDefault();
|
|
4142
|
+
setRailCollapsed(false);
|
|
4143
|
+
},
|
|
4144
|
+
style: {
|
|
4145
|
+
position: "fixed",
|
|
4146
|
+
left: pos.x,
|
|
4147
|
+
top: pos.y,
|
|
4148
|
+
width: 40,
|
|
4149
|
+
height: 40,
|
|
4150
|
+
borderRadius: "50%",
|
|
4151
|
+
background: accent,
|
|
4152
|
+
color: "#ffffff",
|
|
4153
|
+
opacity: .9,
|
|
4154
|
+
zIndex: 100,
|
|
4155
|
+
display: "flex",
|
|
4156
|
+
alignItems: "center",
|
|
4157
|
+
justifyContent: "center",
|
|
4158
|
+
touchAction: "none",
|
|
4159
|
+
userSelect: "none",
|
|
4160
|
+
cursor: ballMode === "draggable" ? "grab" : "pointer",
|
|
4161
|
+
boxShadow: "0 4px 14px rgba(0, 0, 0, 0.35)"
|
|
4162
|
+
},
|
|
4163
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4164
|
+
width: "18",
|
|
4165
|
+
height: "18",
|
|
4166
|
+
viewBox: "0 0 24 24",
|
|
4167
|
+
fill: "none",
|
|
4168
|
+
stroke: "currentColor",
|
|
4169
|
+
strokeWidth: "2",
|
|
4170
|
+
strokeLinecap: "round",
|
|
4171
|
+
strokeLinejoin: "round",
|
|
4172
|
+
"aria-hidden": "true",
|
|
4173
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4174
|
+
cx: "12",
|
|
4175
|
+
cy: "12",
|
|
4176
|
+
r: "8"
|
|
4177
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4178
|
+
cx: "12",
|
|
4179
|
+
cy: "12",
|
|
4180
|
+
r: "2.5",
|
|
4181
|
+
fill: "currentColor",
|
|
4182
|
+
stroke: "none"
|
|
4183
|
+
})]
|
|
4184
|
+
})
|
|
4185
|
+
});
|
|
4186
|
+
}
|
|
3946
4187
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3947
4188
|
style: {
|
|
3948
4189
|
position: "fixed",
|
|
@@ -4000,6 +4241,40 @@ window.__ModuleLoader__.load({
|
|
|
4000
4241
|
},
|
|
4001
4242
|
children: "···"
|
|
4002
4243
|
}),
|
|
4244
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4245
|
+
type: "button",
|
|
4246
|
+
"data-rail-collapse": true,
|
|
4247
|
+
"aria-label": t("rail.collapse"),
|
|
4248
|
+
title: t("rail.collapse"),
|
|
4249
|
+
onClick: () => setRailCollapsed(true),
|
|
4250
|
+
onMouseEnter: () => setRailCollapseHovered(true),
|
|
4251
|
+
onMouseLeave: () => setRailCollapseHovered(false),
|
|
4252
|
+
onFocus: () => setRailCollapseHovered(true),
|
|
4253
|
+
onBlur: () => setRailCollapseHovered(false),
|
|
4254
|
+
style: chromeButtonStyle(railCollapseHovered),
|
|
4255
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4256
|
+
width: "16",
|
|
4257
|
+
height: "16",
|
|
4258
|
+
viewBox: "0 0 24 24",
|
|
4259
|
+
fill: "none",
|
|
4260
|
+
stroke: "currentColor",
|
|
4261
|
+
strokeWidth: "2",
|
|
4262
|
+
strokeLinecap: "round",
|
|
4263
|
+
strokeLinejoin: "round",
|
|
4264
|
+
"aria-hidden": "true",
|
|
4265
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4266
|
+
cx: "12",
|
|
4267
|
+
cy: "12",
|
|
4268
|
+
r: "8"
|
|
4269
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4270
|
+
cx: "12",
|
|
4271
|
+
cy: "12",
|
|
4272
|
+
r: "2.5",
|
|
4273
|
+
fill: "currentColor",
|
|
4274
|
+
stroke: "none"
|
|
4275
|
+
})]
|
|
4276
|
+
})
|
|
4277
|
+
}),
|
|
4003
4278
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4004
4279
|
type: "button",
|
|
4005
4280
|
"data-toolbar-expand": true,
|
|
@@ -4509,6 +4784,139 @@ window.__ModuleLoader__.load({
|
|
|
4509
4784
|
]
|
|
4510
4785
|
})]
|
|
4511
4786
|
}),
|
|
4787
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4788
|
+
"data-settings-section": true,
|
|
4789
|
+
"data-settings-ball": true,
|
|
4790
|
+
style: { marginBottom: 20 },
|
|
4791
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4792
|
+
type: "button",
|
|
4793
|
+
"data-ball-toggle": true,
|
|
4794
|
+
"aria-expanded": ballOpen,
|
|
4795
|
+
"aria-label": t("settings.section.ball"),
|
|
4796
|
+
title: t("settings.section.ball"),
|
|
4797
|
+
onClick: () => setBallOpen((v) => !v),
|
|
4798
|
+
style: SECTION_TOGGLE_STYLE,
|
|
4799
|
+
children: [
|
|
4800
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
4801
|
+
width: "14",
|
|
4802
|
+
height: "14",
|
|
4803
|
+
viewBox: "0 0 24 24",
|
|
4804
|
+
fill: "none",
|
|
4805
|
+
stroke: "currentColor",
|
|
4806
|
+
strokeWidth: "2.5",
|
|
4807
|
+
strokeLinecap: "round",
|
|
4808
|
+
strokeLinejoin: "round",
|
|
4809
|
+
"aria-hidden": "true",
|
|
4810
|
+
style: {
|
|
4811
|
+
flexShrink: 0,
|
|
4812
|
+
transform: ballOpen ? "rotate(90deg)" : "none"
|
|
4813
|
+
},
|
|
4814
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m9 18 6-6-6-6" })
|
|
4815
|
+
}),
|
|
4816
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4817
|
+
"data-settings-section-title": true,
|
|
4818
|
+
style: { flexShrink: 0 },
|
|
4819
|
+
children: t("settings.section.ball")
|
|
4820
|
+
}),
|
|
4821
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4822
|
+
"data-ball-summary": true,
|
|
4823
|
+
style: SECTION_SUMMARY_STYLE,
|
|
4824
|
+
children: ballMode === "fixed" ? t("settings.ball.mode.fixed") : t("settings.ball.mode.draggable")
|
|
4825
|
+
})
|
|
4826
|
+
]
|
|
4827
|
+
}), ballOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4828
|
+
"data-settings-ball-body": true,
|
|
4829
|
+
style: {
|
|
4830
|
+
padding: "10px 4px 8px",
|
|
4831
|
+
display: "flex",
|
|
4832
|
+
flexDirection: "column",
|
|
4833
|
+
gap: 12
|
|
4834
|
+
},
|
|
4835
|
+
children: [
|
|
4836
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4837
|
+
"data-settings-ball-hint": true,
|
|
4838
|
+
style: {
|
|
4839
|
+
fontSize: 12,
|
|
4840
|
+
color: "#8b96ab",
|
|
4841
|
+
lineHeight: 1.5,
|
|
4842
|
+
padding: "0 6px"
|
|
4843
|
+
},
|
|
4844
|
+
children: t("settings.ball.hint")
|
|
4845
|
+
}),
|
|
4846
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4847
|
+
role: "radiogroup",
|
|
4848
|
+
"aria-label": t("settings.ball.mode"),
|
|
4849
|
+
style: {
|
|
4850
|
+
display: "flex",
|
|
4851
|
+
alignItems: "center",
|
|
4852
|
+
gap: 10,
|
|
4853
|
+
flexWrap: "wrap"
|
|
4854
|
+
},
|
|
4855
|
+
children: [
|
|
4856
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4857
|
+
style: {
|
|
4858
|
+
fontSize: 12.5,
|
|
4859
|
+
color: "#8b96ab",
|
|
4860
|
+
width: 90,
|
|
4861
|
+
flexShrink: 0
|
|
4862
|
+
},
|
|
4863
|
+
children: t("settings.ball.mode")
|
|
4864
|
+
}),
|
|
4865
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4866
|
+
style: {
|
|
4867
|
+
display: "inline-flex",
|
|
4868
|
+
alignItems: "center",
|
|
4869
|
+
gap: 5,
|
|
4870
|
+
fontSize: 13,
|
|
4871
|
+
color: "#e6e8ee",
|
|
4872
|
+
cursor: "pointer"
|
|
4873
|
+
},
|
|
4874
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4875
|
+
type: "radio",
|
|
4876
|
+
name: "ms-ball-mode",
|
|
4877
|
+
"data-ball-mode-radio": true,
|
|
4878
|
+
value: "fixed",
|
|
4879
|
+
checked: ballMode === "fixed",
|
|
4880
|
+
onChange: () => updatePrefs({ ballMode: "fixed" })
|
|
4881
|
+
}), t("settings.ball.mode.fixed")]
|
|
4882
|
+
}),
|
|
4883
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4884
|
+
style: {
|
|
4885
|
+
display: "inline-flex",
|
|
4886
|
+
alignItems: "center",
|
|
4887
|
+
gap: 5,
|
|
4888
|
+
fontSize: 13,
|
|
4889
|
+
color: "#e6e8ee",
|
|
4890
|
+
cursor: "pointer"
|
|
4891
|
+
},
|
|
4892
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4893
|
+
type: "radio",
|
|
4894
|
+
name: "ms-ball-mode",
|
|
4895
|
+
"data-ball-mode-radio": true,
|
|
4896
|
+
value: "draggable",
|
|
4897
|
+
checked: ballMode === "draggable",
|
|
4898
|
+
onChange: () => updatePrefs({ ballMode: "draggable" })
|
|
4899
|
+
}), t("settings.ball.mode.draggable")]
|
|
4900
|
+
})
|
|
4901
|
+
]
|
|
4902
|
+
}),
|
|
4903
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4904
|
+
type: "button",
|
|
4905
|
+
"data-ball-reset": true,
|
|
4906
|
+
onClick: () => updatePrefs({ ball: null }),
|
|
4907
|
+
style: {
|
|
4908
|
+
padding: "7px 16px",
|
|
4909
|
+
border: `1px solid rgba(255, 255, 255, 0.14)`,
|
|
4910
|
+
borderRadius: 8,
|
|
4911
|
+
cursor: "pointer",
|
|
4912
|
+
color: "#b9c2d4",
|
|
4913
|
+
fontSize: 12.5
|
|
4914
|
+
},
|
|
4915
|
+
children: t("settings.ball.reset")
|
|
4916
|
+
}) })
|
|
4917
|
+
]
|
|
4918
|
+
})]
|
|
4919
|
+
}),
|
|
4512
4920
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4513
4921
|
"data-settings-section": true,
|
|
4514
4922
|
"data-focus-settings": true,
|
|
@@ -4907,7 +5315,7 @@ window.__ModuleLoader__.load({
|
|
|
4907
5315
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4908
5316
|
style: { color: "#8b96ab" },
|
|
4909
5317
|
children: [t("update.current"), ": "]
|
|
4910
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "0.
|
|
5318
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "0.7.1" })] }),
|
|
4911
5319
|
updateCheck.phase === "ok" && updateCheck.latest !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4912
5320
|
"data-update-latest": true,
|
|
4913
5321
|
children: [
|
|
@@ -5085,7 +5493,7 @@ window.__ModuleLoader__.load({
|
|
|
5085
5493
|
top: rect.top + rect.height / 2
|
|
5086
5494
|
});
|
|
5087
5495
|
},
|
|
5088
|
-
onClick: () => jump(mark.key),
|
|
5496
|
+
onClick: () => jump(mark.key, mark.messageId),
|
|
5089
5497
|
"data-rail-dot": true,
|
|
5090
5498
|
"data-turn-gap": showGroupGap ? "true" : void 0,
|
|
5091
5499
|
"data-turn": showGroupGap && mark.turn !== void 0 ? mark.turn : void 0,
|
|
@@ -5190,6 +5598,70 @@ window.__ModuleLoader__.load({
|
|
|
5190
5598
|
});
|
|
5191
5599
|
}
|
|
5192
5600
|
//#endregion
|
|
5601
|
+
//#region src/client/CompatBoundary.tsx
|
|
5602
|
+
/**
|
|
5603
|
+
* Compatibility boundary for both plugin surfaces.
|
|
5604
|
+
*
|
|
5605
|
+
* The plugin consumes the framework's session standard kit (`useSession`,
|
|
5606
|
+
* `useProjection`, the store seat) and the slot registry, all of which a DSH
|
|
5607
|
+
* upgrade may rename or reshape. Without a boundary such a failure throws out
|
|
5608
|
+
* of the plugin's render — and when the registry silently ignores a stale
|
|
5609
|
+
* entry the rail simply vanishes with no explanation.
|
|
5610
|
+
*
|
|
5611
|
+
* The boundary converts any throw into ONE compact, honest notice instead: the
|
|
5612
|
+
* surrounding harness keeps working, and the user is told what to do (0.1.2→
|
|
5613
|
+
* 0.1.5 has already shown that these upgrades do break third-party plugins).
|
|
5614
|
+
*
|
|
5615
|
+
* A class boundary rather than a per-render capability probe is deliberate:
|
|
5616
|
+
* React exposes error boundaries only through class components, and swapping
|
|
5617
|
+
* hook implementations based on a runtime probe would break hook order.
|
|
5618
|
+
*/
|
|
5619
|
+
/** Catches a render failure anywhere below and shows the incompatibility notice. */
|
|
5620
|
+
var CompatBoundary = class extends react.Component {
|
|
5621
|
+
state = { failed: false };
|
|
5622
|
+
static getDerivedStateFromError() {
|
|
5623
|
+
return { failed: true };
|
|
5624
|
+
}
|
|
5625
|
+
componentDidCatch(error, info) {
|
|
5626
|
+
console.error("[dsh-milestone] incompatible with this DSH version — the rail is disabled. Please update dsh-milestone (or pin an older DSH).", error, info.componentStack);
|
|
5627
|
+
}
|
|
5628
|
+
render() {
|
|
5629
|
+
if (!this.state.failed) return this.props.children;
|
|
5630
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5631
|
+
"data-milestone-incompatible": true,
|
|
5632
|
+
role: "status",
|
|
5633
|
+
style: {
|
|
5634
|
+
position: "fixed",
|
|
5635
|
+
right: 14,
|
|
5636
|
+
bottom: 14,
|
|
5637
|
+
zIndex: 100,
|
|
5638
|
+
maxWidth: 300,
|
|
5639
|
+
padding: "10px 12px",
|
|
5640
|
+
background: MODAL_BG,
|
|
5641
|
+
color: MODAL_HINT,
|
|
5642
|
+
border: `1px solid ${MODAL_BORDER}`,
|
|
5643
|
+
borderRadius: 12,
|
|
5644
|
+
fontSize: 12,
|
|
5645
|
+
lineHeight: 1.5,
|
|
5646
|
+
fontFamily: "inherit"
|
|
5647
|
+
},
|
|
5648
|
+
children: [
|
|
5649
|
+
"dsh-milestone 与当前 DSH 版本不兼容,已停用。请升级插件。",
|
|
5650
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
|
|
5651
|
+
"Incompatible with this DSH version — please update dsh-milestone."
|
|
5652
|
+
]
|
|
5653
|
+
});
|
|
5654
|
+
}
|
|
5655
|
+
};
|
|
5656
|
+
/** The `shell.overlay` seat wrapped in the compatibility boundary. */
|
|
5657
|
+
function GuardedMilestoneOverlay(props) {
|
|
5658
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompatBoundary, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MilestoneOverlay, { ...props }) });
|
|
5659
|
+
}
|
|
5660
|
+
/** The `milestone.rail` seat wrapped in the compatibility boundary. */
|
|
5661
|
+
function GuardedMilestoneRail(props) {
|
|
5662
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompatBoundary, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MilestoneRail, { ...props }) });
|
|
5663
|
+
}
|
|
5664
|
+
//#endregion
|
|
5193
5665
|
//#region src/client/bookmarkStore.ts
|
|
5194
5666
|
/**
|
|
5195
5667
|
* Persisted per-session bookmarks store for the milestone rail.
|
|
@@ -5200,13 +5672,18 @@ window.__ModuleLoader__.load({
|
|
|
5200
5672
|
* resolved by the engine's `create(scopeKey)`). Consumers must call the
|
|
5201
5673
|
* FACTORY (never a module-level handle — module-cache identity is a disguised
|
|
5202
5674
|
* singleton across plugin reloads).
|
|
5675
|
+
*
|
|
5676
|
+
* 0.1.2 compat: the snapshot-store engine moved out of
|
|
5677
|
+
* `@deepseek-ai/dsh-client-runtime/client` into the platform module
|
|
5678
|
+
* `@deepseek-ai/dsh-client-store` (a web module-table seed); the old
|
|
5679
|
+
* specifier is gone from the 0.1.2 module table.
|
|
5203
5680
|
*/
|
|
5204
5681
|
/**
|
|
5205
5682
|
* Declare the bookmarks store handle. Returns a fresh handle per call; the
|
|
5206
5683
|
* framework (or tests) create per-session instances via `create(scopeKey)`.
|
|
5207
5684
|
*/
|
|
5208
5685
|
function createBookmarksStore() {
|
|
5209
|
-
return (0,
|
|
5686
|
+
return (0, _deepseek_ai_dsh_client_store.defineStore)({
|
|
5210
5687
|
init: () => ({ keys: [] }),
|
|
5211
5688
|
persist: "dsh-milestone.bookmarks",
|
|
5212
5689
|
actions: {
|
|
@@ -5245,7 +5722,7 @@ window.__ModuleLoader__.load({
|
|
|
5245
5722
|
return {
|
|
5246
5723
|
items: result.value.items.map((item) => ({
|
|
5247
5724
|
...item,
|
|
5248
|
-
title: byId[item.sessionId]?.
|
|
5725
|
+
title: byId[item.sessionId]?.title
|
|
5249
5726
|
})),
|
|
5250
5727
|
hasMore: result.value.hasMore
|
|
5251
5728
|
};
|
|
@@ -5314,6 +5791,7 @@ window.__ModuleLoader__.load({
|
|
|
5314
5791
|
* @param ctx - client root context.
|
|
5315
5792
|
*/
|
|
5316
5793
|
function apply(ctx) {
|
|
5794
|
+
const sessions = ctx.sessions;
|
|
5317
5795
|
ctx.effect(() => ctx.locale.register("dsh-milestone", {
|
|
5318
5796
|
zh,
|
|
5319
5797
|
en
|
|
@@ -5326,18 +5804,18 @@ window.__ModuleLoader__.load({
|
|
|
5326
5804
|
kind: "single",
|
|
5327
5805
|
scope: "session"
|
|
5328
5806
|
} }
|
|
5329
|
-
},
|
|
5807
|
+
}, GuardedMilestoneOverlay));
|
|
5330
5808
|
ctx.slots.inject("milestone.rail", () => ctx.slots.register({
|
|
5331
5809
|
name: "milestone.rail",
|
|
5332
5810
|
store: createBookmarksStore,
|
|
5333
5811
|
locale: "dsh-milestone",
|
|
5334
5812
|
inject: (sessionId) => ({
|
|
5335
|
-
loadOlder: createLoadOlder(
|
|
5336
|
-
forkAt: createForkAt(
|
|
5337
|
-
searchSessions: createSessionSearch(
|
|
5338
|
-
openSession: createOpenSession(
|
|
5813
|
+
loadOlder: createLoadOlder(sessions, sessionId),
|
|
5814
|
+
forkAt: createForkAt(sessions, sessionId),
|
|
5815
|
+
searchSessions: createSessionSearch(sessions),
|
|
5816
|
+
openSession: createOpenSession(sessions)
|
|
5339
5817
|
})
|
|
5340
|
-
},
|
|
5818
|
+
}, GuardedMilestoneRail));
|
|
5341
5819
|
}
|
|
5342
5820
|
//#endregion
|
|
5343
5821
|
exports.apply = apply;
|