@chestnut23/dsh-conversation-outline 0.1.0 → 0.1.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/lib/client/OutlinePanel.js +108 -21
- package/lib/client/styles.js +24 -0
- package/lib/client.js +118 -25
- package/lib/client.js.map +1 -1
- package/lib/types/client/styles.d.ts +1 -1
- package/package.json +1 -1
|
@@ -53,13 +53,27 @@ export function OutlinePanel({ sessions, useSessions, t }) {
|
|
|
53
53
|
const subscribe = useCallback((onChange) => (session ? session.subscribe(onChange) : () => { }), [session]);
|
|
54
54
|
const getSnapshot = useCallback(() => (session ? session.getSnapshot() : NO_SESSION), [session]);
|
|
55
55
|
const snapshot = useSyncExternalStore(subscribe, getSnapshot);
|
|
56
|
-
|
|
56
|
+
// Two independent sources for the panel being open: `pinned` is a click
|
|
57
|
+
// gesture (sticky until Escape / outside click / session change), `hovered`
|
|
58
|
+
// is pointer presence over the rail or the panel. Deriving `open` from both
|
|
59
|
+
// means a click on the rail opens the panel even when no hover event ever
|
|
60
|
+
// fires (touch, synthetic/automation input, or a pointer that entered the
|
|
61
|
+
// strip without crossing its boundary), and a hover preview still collapses
|
|
62
|
+
// on its own once the pointer leaves.
|
|
63
|
+
const [pinned, setPinned] = useState(false);
|
|
64
|
+
const [hovered, setHovered] = useState(false);
|
|
65
|
+
const open = pinned || hovered;
|
|
57
66
|
const [query, setQuery] = useState('');
|
|
58
67
|
const [copiedKey, setCopiedKey] = useState(null);
|
|
68
|
+
// The question whose jump could not be completed; the panel says so instead
|
|
69
|
+
// of failing silently (the row may sit outside the loaded history window).
|
|
70
|
+
const [jumpFailed, setJumpFailed] = useState(null);
|
|
59
71
|
const timeoutsRef = useRef([]);
|
|
60
72
|
const rafsRef = useRef([]);
|
|
61
73
|
const flashedRowRef = useRef(null);
|
|
62
74
|
const collapseTimerRef = useRef(null);
|
|
75
|
+
const railRef = useRef(null);
|
|
76
|
+
const panelRef = useRef(null);
|
|
63
77
|
// Cancel pending jump work (RAFs/timeouts), the flash highlight, and a
|
|
64
78
|
// scheduled hover collapse.
|
|
65
79
|
const clearPending = useCallback(() => {
|
|
@@ -78,23 +92,47 @@ export function OutlinePanel({ sessions, useSessions, t }) {
|
|
|
78
92
|
flashedRowRef.current = null;
|
|
79
93
|
}
|
|
80
94
|
}, []);
|
|
95
|
+
/** Close the panel from any trigger: drop the pin and the hover preview. */
|
|
96
|
+
const closePanel = useCallback(() => {
|
|
97
|
+
setPinned(false);
|
|
98
|
+
setHovered(false);
|
|
99
|
+
cancelCollapseRef.current();
|
|
100
|
+
}, []);
|
|
81
101
|
// Session change → cancel pending work and collapse (navigate closes the
|
|
82
102
|
// panel, spec §2.1; a mid-jump poll must not target the stale session).
|
|
83
103
|
useEffect(() => {
|
|
84
104
|
clearPending();
|
|
85
|
-
|
|
105
|
+
setPinned(false);
|
|
106
|
+
setHovered(false);
|
|
107
|
+
setJumpFailed(null);
|
|
86
108
|
}, [current, clearPending]);
|
|
87
|
-
// Escape closes the panel (hover-opened or
|
|
109
|
+
// Escape closes the panel (hover-opened or click-pinned).
|
|
88
110
|
useEffect(() => {
|
|
89
111
|
if (!open)
|
|
90
112
|
return;
|
|
91
113
|
const onKey = (event) => {
|
|
92
114
|
if (event.key === 'Escape')
|
|
93
|
-
|
|
115
|
+
closePanel();
|
|
94
116
|
};
|
|
95
117
|
window.addEventListener('keydown', onKey);
|
|
96
118
|
return () => window.removeEventListener('keydown', onKey);
|
|
97
|
-
}, [open]);
|
|
119
|
+
}, [open, closePanel]);
|
|
120
|
+
// A click anywhere outside the rail and the panel releases a pin, so the
|
|
121
|
+
// panel behaves like every other dismissible overlay on the page.
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (!pinned)
|
|
124
|
+
return;
|
|
125
|
+
const onPointerDown = (event) => {
|
|
126
|
+
const target = event.target;
|
|
127
|
+
if (!(target instanceof Node))
|
|
128
|
+
return;
|
|
129
|
+
if (railRef.current?.contains(target) || panelRef.current?.contains(target))
|
|
130
|
+
return;
|
|
131
|
+
closePanel();
|
|
132
|
+
};
|
|
133
|
+
window.addEventListener('mousedown', onPointerDown);
|
|
134
|
+
return () => window.removeEventListener('mousedown', onPointerDown);
|
|
135
|
+
}, [pinned, closePanel]);
|
|
98
136
|
// Unmount cleanup: cancel pending flash/copied timeouts, jump RAFs, collapse
|
|
99
137
|
// timer, and the flash attribute.
|
|
100
138
|
useEffect(() => clearPending, [clearPending]);
|
|
@@ -103,30 +141,64 @@ export function OutlinePanel({ sessions, useSessions, t }) {
|
|
|
103
141
|
const blank = snapshot?.blank ?? true;
|
|
104
142
|
const hasMore = snapshot?.hasMore ?? false;
|
|
105
143
|
const loadingOlder = snapshot?.loadingOlder ?? false;
|
|
106
|
-
// ----
|
|
144
|
+
// ---- open mechanics: click pins, hover previews, grace period between ---
|
|
107
145
|
const cancelCollapse = useCallback(() => {
|
|
108
146
|
if (collapseTimerRef.current !== null) {
|
|
109
147
|
window.clearTimeout(collapseTimerRef.current);
|
|
110
148
|
collapseTimerRef.current = null;
|
|
111
149
|
}
|
|
112
150
|
}, []);
|
|
151
|
+
// `closePanel` above runs before this declaration; a ref keeps the two
|
|
152
|
+
// callbacks independent of declaration order.
|
|
153
|
+
const cancelCollapseRef = useRef(cancelCollapse);
|
|
154
|
+
cancelCollapseRef.current = cancelCollapse;
|
|
113
155
|
const scheduleCollapse = useCallback(() => {
|
|
114
156
|
cancelCollapse();
|
|
115
157
|
collapseTimerRef.current = window.setTimeout(() => {
|
|
116
158
|
collapseTimerRef.current = null;
|
|
117
|
-
|
|
159
|
+
// Only a preview collapses on its own: a pinned panel stays until it is
|
|
160
|
+
// closed, and a pointer still inside the rail or the panel (Chromium
|
|
161
|
+
// raises no further enter events while the pointer sits still) keeps the
|
|
162
|
+
// preview up.
|
|
163
|
+
if (railRef.current?.matches(':hover') || panelRef.current?.matches(':hover'))
|
|
164
|
+
return;
|
|
165
|
+
setHovered(false);
|
|
118
166
|
}, COLLAPSE_DELAY_MS);
|
|
119
167
|
}, [cancelCollapse]);
|
|
120
|
-
|
|
168
|
+
/** Pointer entered the rail or the panel: cancel a pending collapse, preview. */
|
|
169
|
+
const onEnter = useCallback(() => {
|
|
121
170
|
cancelCollapse();
|
|
122
|
-
|
|
171
|
+
setHovered(true);
|
|
123
172
|
}, [cancelCollapse]);
|
|
173
|
+
/** Pointer left: let the grace period decide, unless the panel is pinned. */
|
|
174
|
+
const onLeave = useCallback(() => {
|
|
175
|
+
if (pinned)
|
|
176
|
+
return;
|
|
177
|
+
scheduleCollapse();
|
|
178
|
+
}, [pinned, scheduleCollapse]);
|
|
179
|
+
/** A click on the rail (or any bar) opens the panel and keeps it open. */
|
|
180
|
+
const togglePin = useCallback(() => {
|
|
181
|
+
cancelCollapse();
|
|
182
|
+
setPinned((value) => !value);
|
|
183
|
+
setHovered(true);
|
|
184
|
+
}, [cancelCollapse]);
|
|
185
|
+
/** The panel's own controls: clicking inside never closes it. */
|
|
186
|
+
const keepOpen = useCallback(() => {
|
|
187
|
+
cancelCollapse();
|
|
188
|
+
setHovered(true);
|
|
189
|
+
}, [cancelCollapse]);
|
|
190
|
+
/** Grid of candidate keys for one node: the anchor the row renders first. */
|
|
191
|
+
function jumpKeyCandidates(key) {
|
|
192
|
+
return key.includes('|') ? [key.split('|')[0], key] : [key];
|
|
193
|
+
}
|
|
124
194
|
/** Find the chat row for a node key (pure predicate over DOM rows). */
|
|
125
195
|
const findRow = useCallback((key) => {
|
|
126
196
|
const rows = document.querySelectorAll('[data-chat-anchor-key]');
|
|
127
|
-
for (const
|
|
128
|
-
|
|
129
|
-
|
|
197
|
+
for (const candidate of jumpKeyCandidates(key)) {
|
|
198
|
+
for (const row of rows) {
|
|
199
|
+
if (isJumpTargetRow(row, candidate))
|
|
200
|
+
return row;
|
|
201
|
+
}
|
|
130
202
|
}
|
|
131
203
|
return null;
|
|
132
204
|
}, []);
|
|
@@ -155,11 +227,12 @@ export function OutlinePanel({ sessions, useSessions, t }) {
|
|
|
155
227
|
}, FLASH_MS);
|
|
156
228
|
}, []);
|
|
157
229
|
/** Jump-to-message algorithm (spec §2.2). */
|
|
158
|
-
const jumpTo = useCallback((key) => {
|
|
230
|
+
const jumpTo = useCallback((key, label) => {
|
|
159
231
|
// The conversation page must be mounted; abort silently otherwise.
|
|
160
232
|
const scrollport = document.querySelector('[data-conversation-scroll]');
|
|
161
233
|
if (!scrollport)
|
|
162
234
|
return;
|
|
235
|
+
setJumpFailed(null);
|
|
163
236
|
// Ensure the Chat view is active: chat is order 0 — the FIRST tab of the
|
|
164
237
|
// conversation-root tablist, when one exists (setView is idempotent).
|
|
165
238
|
// A profile without extra views has no header tablist at all — that is
|
|
@@ -178,18 +251,21 @@ export function OutlinePanel({ sessions, useSessions, t }) {
|
|
|
178
251
|
const row = findRow(key);
|
|
179
252
|
if (row) {
|
|
180
253
|
flashAndScroll(row);
|
|
181
|
-
|
|
254
|
+
closePanel();
|
|
182
255
|
return;
|
|
183
256
|
}
|
|
184
257
|
if (Date.now() < deadline) {
|
|
185
258
|
scheduleRaf(rafsRef, poll);
|
|
186
259
|
}
|
|
187
260
|
else {
|
|
261
|
+
// Never fail silently: the panel states what happened, and the
|
|
262
|
+
// console keeps the key for bug reports.
|
|
188
263
|
console.warn(t('jumpFailed'), { key });
|
|
264
|
+
setJumpFailed(label ?? key);
|
|
189
265
|
}
|
|
190
266
|
};
|
|
191
267
|
scheduleRaf(rafsRef, poll);
|
|
192
|
-
}, [findRow, flashAndScroll, t]);
|
|
268
|
+
}, [findRow, flashAndScroll, t, closePanel]);
|
|
193
269
|
/** Copy a question's text (clipboard API with execCommand fallback). */
|
|
194
270
|
const copyText = useCallback((text, key) => {
|
|
195
271
|
const done = () => {
|
|
@@ -231,16 +307,27 @@ export function OutlinePanel({ sessions, useSessions, t }) {
|
|
|
231
307
|
const start = Math.max(0, allItems.length - MAX_BARS);
|
|
232
308
|
const bars = allItems.slice(start);
|
|
233
309
|
const overflow = start;
|
|
234
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "dso-rail", role: "group", "aria-label": t('title'), tabIndex: 0, onMouseEnter:
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
310
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { ref: railRef, className: "dso-rail", role: "group", "aria-label": t('title'), tabIndex: 0, onMouseEnter: onEnter, onMouseLeave: onLeave, onFocus: onEnter, onBlur: onLeave, onClick: (event) => {
|
|
311
|
+
// Any click on the strip pins the panel open — the container itself,
|
|
312
|
+
// its padding, or the "+N" marker. Bars stop propagation and reach
|
|
313
|
+
// the same pin through the shared jump gesture below.
|
|
314
|
+
if (event.target === event.currentTarget || event.target instanceof HTMLSpanElement) {
|
|
315
|
+
togglePin();
|
|
316
|
+
}
|
|
238
317
|
}, onKeyDown: (event) => {
|
|
239
318
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
240
319
|
event.preventDefault();
|
|
241
|
-
|
|
320
|
+
togglePin();
|
|
242
321
|
}
|
|
243
|
-
}, children: [overflow > 0 && (_jsxs("span", { className: "dso-rail-more", "aria-label": t('moreBars', { count: overflow }), children: ["+", overflow] })), bars.map((item, index) => (_jsx("button", { type: "button", className: "dso-bar", "aria-label": t('barLabel', { n: start + index + 1 }), onClick: (
|
|
322
|
+
}, "data-dso-open": open ? 'true' : undefined, children: [overflow > 0 && (_jsxs("span", { className: "dso-rail-more", "aria-label": t('moreBars', { count: overflow }), children: ["+", overflow] })), bars.map((item, index) => (_jsx("button", { type: "button", className: "dso-bar", "aria-label": t('barLabel', { n: start + index + 1 }), onClick: (event) => {
|
|
323
|
+
event.stopPropagation();
|
|
324
|
+
// The bar's own job is the jump; the panel comes up pinned first
|
|
325
|
+
// so a click always has a visible effect, then jumpTo collapses
|
|
326
|
+
// it once the row is on screen.
|
|
327
|
+
setPinned(true);
|
|
328
|
+
setHovered(true);
|
|
329
|
+
jumpTo(item.key, item.text);
|
|
330
|
+
} }, item.key)))] }), open && (_jsxs("div", { ref: panelRef, className: "dso-panel", role: "region", "aria-label": t('title'), "data-dso-pinned": pinned ? 'true' : undefined, onMouseEnter: onEnter, onMouseLeave: onLeave, onFocus: onEnter, onBlur: onLeave, onMouseDown: keepOpen, children: [_jsxs("header", { className: "dso-panel-header", children: [_jsx("span", { className: "dso-panel-title", children: t('title') }), _jsx("span", { className: "dso-panel-count", children: t('count', { count: allItems.length }) }), _jsx("button", { type: "button", className: "dso-panel-close", "aria-label": t('close'), onClick: closePanel, children: "\u00D7" })] }), _jsx("input", { className: "dso-search", type: "search", value: query, placeholder: t('searchPlaceholder'), "aria-label": t('searchPlaceholder'), onChange: (event) => setQuery(event.target.value) }), _jsx("div", { className: "dso-list", children: items.length === 0 ? (_jsx("div", { className: "dso-empty", children: t('empty') })) : (items.map((item) => (_jsxs("div", { className: "dso-row", children: [_jsxs("button", { type: "button", className: "dso-row-main", onClick: () => jumpTo(item.key, item.text), children: [_jsx("span", { className: "dso-turn", children: item.turn !== undefined ? `#${item.turn}` : '' }), _jsx("span", { className: "dso-text", children: item.text }), item.kind === 'steering' && _jsx("span", { className: "dso-steer", children: t('steerTag') }), _jsx("span", { className: "dso-time", children: formatTime(item.time) })] }), _jsx("button", { type: "button", className: copiedKey === item.key ? 'dso-copy dso-copied' : 'dso-copy', "aria-label": copiedKey === item.key ? t('copied') : t('copy'), onClick: () => copyText(item.text, item.key), children: copiedKey === item.key ? t('copied') : t('copy') })] }, item.key)))) }), jumpFailed !== null && (_jsx("p", { className: "dso-jump-failed", role: "status", "data-dso-jump-failed": "true", children: t('jumpFailed') })), hasMore && (_jsx("footer", { className: "dso-footer", children: _jsx("button", { type: "button", className: "dso-load-older", disabled: loadingOlder, onClick: () => {
|
|
244
331
|
void session.loadOlder();
|
|
245
332
|
}, children: loadingOlder ? t('loadingOlder') : t('loadOlder') }) }))] }))] }));
|
|
246
333
|
}
|
package/lib/client/styles.js
CHANGED
|
@@ -55,6 +55,15 @@ export const panelCss = `
|
|
|
55
55
|
outline: 2px solid var(--dsw-alias-state-business-primary);
|
|
56
56
|
outline-offset: 2px;
|
|
57
57
|
}
|
|
58
|
+
/* Clickability affordance: a hovered or pinned strip lights its bars up, so
|
|
59
|
+
it reads as a control rather than as decoration. */
|
|
60
|
+
.dso-rail:hover .dso-bar,
|
|
61
|
+
.dso-rail[data-dso-open] .dso-bar {
|
|
62
|
+
background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 72%, transparent);
|
|
63
|
+
}
|
|
64
|
+
.dso-rail:hover .dso-bar:hover {
|
|
65
|
+
background: var(--dsw-alias-state-business-primary);
|
|
66
|
+
}
|
|
58
67
|
|
|
59
68
|
/* ---- Hover-expanded preview panel --------------------------------------- */
|
|
60
69
|
.dso-panel {
|
|
@@ -77,6 +86,11 @@ export const panelCss = `
|
|
|
77
86
|
color: var(--dsw-alias-label-primary);
|
|
78
87
|
animation: dso-panel-in 140ms ease-out;
|
|
79
88
|
}
|
|
89
|
+
/* A pinned panel (clicked open) keeps a defined edge, so it is obvious that it
|
|
90
|
+
no longer collapses when the pointer leaves. */
|
|
91
|
+
.dso-panel[data-dso-pinned] {
|
|
92
|
+
border-color: var(--dsw-alias-state-business-primary);
|
|
93
|
+
}
|
|
80
94
|
@keyframes dso-panel-in {
|
|
81
95
|
from { opacity: 0; transform: translateY(-50%) translateX(12px); }
|
|
82
96
|
to { opacity: 1; transform: translateY(-50%) translateX(0); }
|
|
@@ -245,6 +259,16 @@ export const panelCss = `
|
|
|
245
259
|
color: var(--dsw-alias-label-tertiary);
|
|
246
260
|
}
|
|
247
261
|
|
|
262
|
+
/* ---- Jump failure notice ------------------------------------------------ */
|
|
263
|
+
.dso-jump-failed {
|
|
264
|
+
margin: 0;
|
|
265
|
+
padding: 8px 14px;
|
|
266
|
+
border-top: 1px solid var(--dsw-alias-line-normal);
|
|
267
|
+
font-size: 12px;
|
|
268
|
+
line-height: 1.5;
|
|
269
|
+
color: var(--dsw-alias-state-warning, var(--dsw-alias-label-secondary));
|
|
270
|
+
}
|
|
271
|
+
|
|
248
272
|
/* ---- Footer / load older ------------------------------------------------ */
|
|
249
273
|
.dso-footer {
|
|
250
274
|
display: flex;
|
package/lib/client.js
CHANGED
|
@@ -124,13 +124,18 @@ window.__ModuleLoader__.load({
|
|
|
124
124
|
const current = useSessions((s) => s.current);
|
|
125
125
|
const session = current ? sessions.binding(current)?.session : void 0;
|
|
126
126
|
const snapshot = (0, react.useSyncExternalStore)((0, react.useCallback)((onChange) => session ? session.subscribe(onChange) : () => {}, [session]), (0, react.useCallback)(() => session ? session.getSnapshot() : NO_SESSION, [session]));
|
|
127
|
-
const [
|
|
127
|
+
const [pinned, setPinned] = (0, react.useState)(false);
|
|
128
|
+
const [hovered, setHovered] = (0, react.useState)(false);
|
|
129
|
+
const open = pinned || hovered;
|
|
128
130
|
const [query, setQuery] = (0, react.useState)("");
|
|
129
131
|
const [copiedKey, setCopiedKey] = (0, react.useState)(null);
|
|
132
|
+
const [jumpFailed, setJumpFailed] = (0, react.useState)(null);
|
|
130
133
|
const timeoutsRef = (0, react.useRef)([]);
|
|
131
134
|
const rafsRef = (0, react.useRef)([]);
|
|
132
135
|
const flashedRowRef = (0, react.useRef)(null);
|
|
133
136
|
const collapseTimerRef = (0, react.useRef)(null);
|
|
137
|
+
const railRef = (0, react.useRef)(null);
|
|
138
|
+
const panelRef = (0, react.useRef)(null);
|
|
134
139
|
const clearPending = (0, react.useCallback)(() => {
|
|
135
140
|
for (const id of rafsRef.current) window.cancelAnimationFrame(id);
|
|
136
141
|
for (const id of timeoutsRef.current) window.clearTimeout(id);
|
|
@@ -145,18 +150,37 @@ window.__ModuleLoader__.load({
|
|
|
145
150
|
flashedRowRef.current = null;
|
|
146
151
|
}
|
|
147
152
|
}, []);
|
|
153
|
+
/** Close the panel from any trigger: drop the pin and the hover preview. */
|
|
154
|
+
const closePanel = (0, react.useCallback)(() => {
|
|
155
|
+
setPinned(false);
|
|
156
|
+
setHovered(false);
|
|
157
|
+
cancelCollapseRef.current();
|
|
158
|
+
}, []);
|
|
148
159
|
(0, react.useEffect)(() => {
|
|
149
160
|
clearPending();
|
|
150
|
-
|
|
161
|
+
setPinned(false);
|
|
162
|
+
setHovered(false);
|
|
163
|
+
setJumpFailed(null);
|
|
151
164
|
}, [current, clearPending]);
|
|
152
165
|
(0, react.useEffect)(() => {
|
|
153
166
|
if (!open) return;
|
|
154
167
|
const onKey = (event) => {
|
|
155
|
-
if (event.key === "Escape")
|
|
168
|
+
if (event.key === "Escape") closePanel();
|
|
156
169
|
};
|
|
157
170
|
window.addEventListener("keydown", onKey);
|
|
158
171
|
return () => window.removeEventListener("keydown", onKey);
|
|
159
|
-
}, [open]);
|
|
172
|
+
}, [open, closePanel]);
|
|
173
|
+
(0, react.useEffect)(() => {
|
|
174
|
+
if (!pinned) return;
|
|
175
|
+
const onPointerDown = (event) => {
|
|
176
|
+
const target = event.target;
|
|
177
|
+
if (!(target instanceof Node)) return;
|
|
178
|
+
if (railRef.current?.contains(target) || panelRef.current?.contains(target)) return;
|
|
179
|
+
closePanel();
|
|
180
|
+
};
|
|
181
|
+
window.addEventListener("mousedown", onPointerDown);
|
|
182
|
+
return () => window.removeEventListener("mousedown", onPointerDown);
|
|
183
|
+
}, [pinned, closePanel]);
|
|
160
184
|
(0, react.useEffect)(() => clearPending, [clearPending]);
|
|
161
185
|
const allItems = (0, react.useMemo)(() => snapshot ? collectQuestions(snapshot) : [], [snapshot]);
|
|
162
186
|
const items = (0, react.useMemo)(() => filterQuestions(allItems, query), [allItems, query]);
|
|
@@ -169,21 +193,45 @@ window.__ModuleLoader__.load({
|
|
|
169
193
|
collapseTimerRef.current = null;
|
|
170
194
|
}
|
|
171
195
|
}, []);
|
|
196
|
+
const cancelCollapseRef = (0, react.useRef)(cancelCollapse);
|
|
197
|
+
cancelCollapseRef.current = cancelCollapse;
|
|
172
198
|
const scheduleCollapse = (0, react.useCallback)(() => {
|
|
173
199
|
cancelCollapse();
|
|
174
200
|
collapseTimerRef.current = window.setTimeout(() => {
|
|
175
201
|
collapseTimerRef.current = null;
|
|
176
|
-
|
|
202
|
+
if (railRef.current?.matches(":hover") || panelRef.current?.matches(":hover")) return;
|
|
203
|
+
setHovered(false);
|
|
177
204
|
}, COLLAPSE_DELAY_MS);
|
|
178
205
|
}, [cancelCollapse]);
|
|
179
|
-
|
|
206
|
+
/** Pointer entered the rail or the panel: cancel a pending collapse, preview. */
|
|
207
|
+
const onEnter = (0, react.useCallback)(() => {
|
|
208
|
+
cancelCollapse();
|
|
209
|
+
setHovered(true);
|
|
210
|
+
}, [cancelCollapse]);
|
|
211
|
+
/** Pointer left: let the grace period decide, unless the panel is pinned. */
|
|
212
|
+
const onLeave = (0, react.useCallback)(() => {
|
|
213
|
+
if (pinned) return;
|
|
214
|
+
scheduleCollapse();
|
|
215
|
+
}, [pinned, scheduleCollapse]);
|
|
216
|
+
/** A click on the rail (or any bar) opens the panel and keeps it open. */
|
|
217
|
+
const togglePin = (0, react.useCallback)(() => {
|
|
180
218
|
cancelCollapse();
|
|
181
|
-
|
|
219
|
+
setPinned((value) => !value);
|
|
220
|
+
setHovered(true);
|
|
182
221
|
}, [cancelCollapse]);
|
|
222
|
+
/** The panel's own controls: clicking inside never closes it. */
|
|
223
|
+
const keepOpen = (0, react.useCallback)(() => {
|
|
224
|
+
cancelCollapse();
|
|
225
|
+
setHovered(true);
|
|
226
|
+
}, [cancelCollapse]);
|
|
227
|
+
/** Grid of candidate keys for one node: the anchor the row renders first. */
|
|
228
|
+
function jumpKeyCandidates(key) {
|
|
229
|
+
return key.includes("|") ? [key.split("|")[0], key] : [key];
|
|
230
|
+
}
|
|
183
231
|
/** Find the chat row for a node key (pure predicate over DOM rows). */
|
|
184
232
|
const findRow = (0, react.useCallback)((key) => {
|
|
185
233
|
const rows = document.querySelectorAll("[data-chat-anchor-key]");
|
|
186
|
-
for (const row of rows) if (isJumpTargetRow(row,
|
|
234
|
+
for (const candidate of jumpKeyCandidates(key)) for (const row of rows) if (isJumpTargetRow(row, candidate)) return row;
|
|
187
235
|
return null;
|
|
188
236
|
}, []);
|
|
189
237
|
/** Scroll the row into view (96px headroom) and flash a highlight. */
|
|
@@ -208,9 +256,10 @@ window.__ModuleLoader__.load({
|
|
|
208
256
|
}, FLASH_MS);
|
|
209
257
|
}, []);
|
|
210
258
|
/** Jump-to-message algorithm (spec §2.2). */
|
|
211
|
-
const jumpTo = (0, react.useCallback)((key) => {
|
|
259
|
+
const jumpTo = (0, react.useCallback)((key, label) => {
|
|
212
260
|
const scrollport = document.querySelector("[data-conversation-scroll]");
|
|
213
261
|
if (!scrollport) return;
|
|
262
|
+
setJumpFailed(null);
|
|
214
263
|
const tablist = findConversationTablist(scrollport);
|
|
215
264
|
if (tablist) {
|
|
216
265
|
const firstTab = tablist.querySelector("button[role=\"tab\"]");
|
|
@@ -221,17 +270,21 @@ window.__ModuleLoader__.load({
|
|
|
221
270
|
const row = findRow(key);
|
|
222
271
|
if (row) {
|
|
223
272
|
flashAndScroll(row);
|
|
224
|
-
|
|
273
|
+
closePanel();
|
|
225
274
|
return;
|
|
226
275
|
}
|
|
227
276
|
if (Date.now() < deadline) scheduleRaf(rafsRef, poll);
|
|
228
|
-
else
|
|
277
|
+
else {
|
|
278
|
+
console.warn(t("jumpFailed"), { key });
|
|
279
|
+
setJumpFailed(label ?? key);
|
|
280
|
+
}
|
|
229
281
|
};
|
|
230
282
|
scheduleRaf(rafsRef, poll);
|
|
231
283
|
}, [
|
|
232
284
|
findRow,
|
|
233
285
|
flashAndScroll,
|
|
234
|
-
t
|
|
286
|
+
t,
|
|
287
|
+
closePanel
|
|
235
288
|
]);
|
|
236
289
|
/** Copy a question's text (clipboard API with execCommand fallback). */
|
|
237
290
|
const copyText = (0, react.useCallback)((text, key) => {
|
|
@@ -261,23 +314,25 @@ window.__ModuleLoader__.load({
|
|
|
261
314
|
const bars = allItems.slice(start);
|
|
262
315
|
const overflow = start;
|
|
263
316
|
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
317
|
+
ref: railRef,
|
|
264
318
|
className: "dso-rail",
|
|
265
319
|
role: "group",
|
|
266
320
|
"aria-label": t("title"),
|
|
267
321
|
tabIndex: 0,
|
|
268
|
-
onMouseEnter:
|
|
269
|
-
onMouseLeave:
|
|
270
|
-
onFocus:
|
|
271
|
-
onBlur:
|
|
322
|
+
onMouseEnter: onEnter,
|
|
323
|
+
onMouseLeave: onLeave,
|
|
324
|
+
onFocus: onEnter,
|
|
325
|
+
onBlur: onLeave,
|
|
272
326
|
onClick: (event) => {
|
|
273
|
-
if (event.target === event.currentTarget)
|
|
327
|
+
if (event.target === event.currentTarget || event.target instanceof HTMLSpanElement) togglePin();
|
|
274
328
|
},
|
|
275
329
|
onKeyDown: (event) => {
|
|
276
330
|
if (event.key === "Enter" || event.key === " ") {
|
|
277
331
|
event.preventDefault();
|
|
278
|
-
|
|
332
|
+
togglePin();
|
|
279
333
|
}
|
|
280
334
|
},
|
|
335
|
+
"data-dso-open": open ? "true" : void 0,
|
|
281
336
|
children: [overflow > 0 && (0, react_jsx_runtime.jsxs)("span", {
|
|
282
337
|
className: "dso-rail-more",
|
|
283
338
|
"aria-label": t("moreBars", { count: overflow }),
|
|
@@ -286,16 +341,24 @@ window.__ModuleLoader__.load({
|
|
|
286
341
|
type: "button",
|
|
287
342
|
className: "dso-bar",
|
|
288
343
|
"aria-label": t("barLabel", { n: start + index + 1 }),
|
|
289
|
-
onClick: () =>
|
|
344
|
+
onClick: (event) => {
|
|
345
|
+
event.stopPropagation();
|
|
346
|
+
setPinned(true);
|
|
347
|
+
setHovered(true);
|
|
348
|
+
jumpTo(item.key, item.text);
|
|
349
|
+
}
|
|
290
350
|
}, item.key))]
|
|
291
351
|
}), open && (0, react_jsx_runtime.jsxs)("div", {
|
|
352
|
+
ref: panelRef,
|
|
292
353
|
className: "dso-panel",
|
|
293
354
|
role: "region",
|
|
294
355
|
"aria-label": t("title"),
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
356
|
+
"data-dso-pinned": pinned ? "true" : void 0,
|
|
357
|
+
onMouseEnter: onEnter,
|
|
358
|
+
onMouseLeave: onLeave,
|
|
359
|
+
onFocus: onEnter,
|
|
360
|
+
onBlur: onLeave,
|
|
361
|
+
onMouseDown: keepOpen,
|
|
299
362
|
children: [
|
|
300
363
|
(0, react_jsx_runtime.jsxs)("header", {
|
|
301
364
|
className: "dso-panel-header",
|
|
@@ -312,7 +375,7 @@ window.__ModuleLoader__.load({
|
|
|
312
375
|
type: "button",
|
|
313
376
|
className: "dso-panel-close",
|
|
314
377
|
"aria-label": t("close"),
|
|
315
|
-
onClick:
|
|
378
|
+
onClick: closePanel,
|
|
316
379
|
children: "×"
|
|
317
380
|
})
|
|
318
381
|
]
|
|
@@ -335,7 +398,7 @@ window.__ModuleLoader__.load({
|
|
|
335
398
|
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
336
399
|
type: "button",
|
|
337
400
|
className: "dso-row-main",
|
|
338
|
-
onClick: () => jumpTo(item.key),
|
|
401
|
+
onClick: () => jumpTo(item.key, item.text),
|
|
339
402
|
children: [
|
|
340
403
|
(0, react_jsx_runtime.jsx)("span", {
|
|
341
404
|
className: "dso-turn",
|
|
@@ -363,6 +426,12 @@ window.__ModuleLoader__.load({
|
|
|
363
426
|
})]
|
|
364
427
|
}, item.key))
|
|
365
428
|
}),
|
|
429
|
+
jumpFailed !== null && (0, react_jsx_runtime.jsx)("p", {
|
|
430
|
+
className: "dso-jump-failed",
|
|
431
|
+
role: "status",
|
|
432
|
+
"data-dso-jump-failed": "true",
|
|
433
|
+
children: t("jumpFailed")
|
|
434
|
+
}),
|
|
366
435
|
hasMore && (0, react_jsx_runtime.jsx)("footer", {
|
|
367
436
|
className: "dso-footer",
|
|
368
437
|
children: (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -472,6 +541,15 @@ window.__ModuleLoader__.load({
|
|
|
472
541
|
outline: 2px solid var(--dsw-alias-state-business-primary);
|
|
473
542
|
outline-offset: 2px;
|
|
474
543
|
}
|
|
544
|
+
/* Clickability affordance: a hovered or pinned strip lights its bars up, so
|
|
545
|
+
it reads as a control rather than as decoration. */
|
|
546
|
+
.dso-rail:hover .dso-bar,
|
|
547
|
+
.dso-rail[data-dso-open] .dso-bar {
|
|
548
|
+
background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 72%, transparent);
|
|
549
|
+
}
|
|
550
|
+
.dso-rail:hover .dso-bar:hover {
|
|
551
|
+
background: var(--dsw-alias-state-business-primary);
|
|
552
|
+
}
|
|
475
553
|
|
|
476
554
|
/* ---- Hover-expanded preview panel --------------------------------------- */
|
|
477
555
|
.dso-panel {
|
|
@@ -494,6 +572,11 @@ window.__ModuleLoader__.load({
|
|
|
494
572
|
color: var(--dsw-alias-label-primary);
|
|
495
573
|
animation: dso-panel-in 140ms ease-out;
|
|
496
574
|
}
|
|
575
|
+
/* A pinned panel (clicked open) keeps a defined edge, so it is obvious that it
|
|
576
|
+
no longer collapses when the pointer leaves. */
|
|
577
|
+
.dso-panel[data-dso-pinned] {
|
|
578
|
+
border-color: var(--dsw-alias-state-business-primary);
|
|
579
|
+
}
|
|
497
580
|
@keyframes dso-panel-in {
|
|
498
581
|
from { opacity: 0; transform: translateY(-50%) translateX(12px); }
|
|
499
582
|
to { opacity: 1; transform: translateY(-50%) translateX(0); }
|
|
@@ -662,6 +745,16 @@ window.__ModuleLoader__.load({
|
|
|
662
745
|
color: var(--dsw-alias-label-tertiary);
|
|
663
746
|
}
|
|
664
747
|
|
|
748
|
+
/* ---- Jump failure notice ------------------------------------------------ */
|
|
749
|
+
.dso-jump-failed {
|
|
750
|
+
margin: 0;
|
|
751
|
+
padding: 8px 14px;
|
|
752
|
+
border-top: 1px solid var(--dsw-alias-line-normal);
|
|
753
|
+
font-size: 12px;
|
|
754
|
+
line-height: 1.5;
|
|
755
|
+
color: var(--dsw-alias-state-warning, var(--dsw-alias-label-secondary));
|
|
756
|
+
}
|
|
757
|
+
|
|
665
758
|
/* ---- Footer / load older ------------------------------------------------ */
|
|
666
759
|
.dso-footer {
|
|
667
760
|
display: flex;
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["_Fragment"],"sources":["client/outline.js","client/OutlinePanel.js","client/locales.js","client/styles.js","client/index.js"],"sourcesContent":["/**\n * Pure logic for the conversation outline (implementation-spec §2.3).\n *\n * Deliberately has NO DOM access and NO React imports so scripts/verify.mjs can\n * import it directly in Node from the tsc output (lib/client/outline.js) and\n * assert the derivations offline.\n *\n * Input shapes are structural \"lite\" views of the runtime snapshot types\n * (@deepseek-ai/dsh-client-runtime/client) — the real objects are assignable\n * to them, and Node can import this module without the browser type graph.\n */\nconst IMAGE_PLACEHOLDER = '[image]';\n/**\n * Flatten message content blocks into a single display string: text blocks are\n * joined, image blocks become a placeholder, and whitespace is trimmed and\n * collapsed.\n */\nexport function flattenQuestionText(content) {\n return content\n .map((block) => {\n if (block.type === 'text')\n return block.text ?? '';\n if (block.type === 'image')\n return IMAGE_PLACEHOLDER;\n return '';\n })\n .join(' ')\n .trim()\n .replace(/\\s+/g, ' ');\n}\n/** Turn number of a node location: step/turn → location.turn.turn, else undefined. */\nfunction turnOf(location) {\n if (!location)\n return undefined;\n if (location.kind === 'step' || location.kind === 'turn')\n return location.turn?.turn;\n return undefined;\n}\n/**\n * Walk the chat flow in order, keep user/steering nodes, skip empty text, and\n * produce chronological outline items (spec §2.3).\n */\nexport function collectQuestions(snapshot) {\n const items = [];\n for (const key of snapshot.chat.order) {\n const node = snapshot.chat.nodes.get(key);\n if (!node)\n continue;\n if (node.kind !== 'user' && node.kind !== 'steering')\n continue;\n const data = node.data;\n const text = flattenQuestionText(data?.content ?? []);\n if (!text)\n continue;\n items.push({\n key: node.key,\n kind: node.kind,\n seq: data?.seq ?? 0,\n time: data?.time ?? 0,\n turn: turnOf(node.location),\n text,\n });\n }\n return items;\n}\n/** Case-insensitive substring filter over the flattened question text. */\nexport function filterQuestions(items, query) {\n const q = query.trim().toLowerCase();\n if (!q)\n return items;\n return items.filter((item) => item.text.toLowerCase().includes(q));\n}\n/** Local `HH:MM` from a unix-ms timestamp (spec §2.3). */\nexport function formatTime(ms) {\n const d = new Date(ms);\n const hh = String(d.getHours()).padStart(2, '0');\n const mm = String(d.getMinutes()).padStart(2, '0');\n return `${hh}:${mm}`;\n}\n/** Pure DOM predicate for the jump loop: is this row the target node? */\nexport function isJumpTargetRow(row, key) {\n return row.getAttribute('data-chat-anchor-key') === key;\n}\n","import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from \"react/jsx-runtime\";\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react';\nimport { collectQuestions, filterQuestions, formatTime, isJumpTargetRow, } from \"./outline.js\";\n/** Stable getSnapshot value while no session is current (uSES contract). */\nconst NO_SESSION = null;\nconst JUMP_HEADROOM = 96;\nconst JUMP_TIMEOUT_MS = 1500;\nconst FLASH_MS = 1900;\nconst COPIED_MS = 1500;\n/** Grace period before the hover panel collapses (lets the pointer travel). */\nconst COLLAPSE_DELAY_MS = 240;\n/** Rail capacity; older questions fold into the \"+N\" marker. */\nconst MAX_BARS = 60;\n/**\n * The conversation view's header tablist, scoped to the conversation root:\n * walk up from the scrollport's PARENT (the scrollport itself may contain\n * other tablists, e.g. the trajectory event-details tabs) and return the\n * nearest ancestor containing a `[role=\"tablist\"]`. Never falls back to a\n * document-wide query, so other tablists (trajectory event details,\n * settings, cordis source viewer) are not hit.\n */\nfunction findConversationTablist(scrollport) {\n let node = scrollport.parentElement;\n while (node) {\n const tablist = node.querySelector('[role=\"tablist\"]');\n if (tablist)\n return tablist;\n node = node.parentElement;\n }\n return null;\n}\n/** RAF that prunes itself from the tracking ref once it fires. */\nfunction scheduleRaf(rafRef, fn) {\n const id = requestAnimationFrame(() => {\n rafRef.current = rafRef.current.filter((x) => x !== id);\n fn();\n });\n rafRef.current.push(id);\n}\n/** Timeout that prunes itself from the tracking ref once it fires. */\nfunction scheduleTimeout(timeoutRef, fn, ms) {\n const id = window.setTimeout(() => {\n timeoutRef.current = timeoutRef.current.filter((x) => x !== id);\n fn();\n }, ms);\n timeoutRef.current.push(id);\n}\nexport function OutlinePanel({ sessions, useSessions, t }) {\n const current = useSessions((s) => s.current);\n const session = current ? sessions.binding(current)?.session : undefined;\n // Conversation snapshot subscription (spec §1.5): live updates while the\n // session runs, load-older, blank detection — all ride this one store.\n const subscribe = useCallback((onChange) => (session ? session.subscribe(onChange) : () => { }), [session]);\n const getSnapshot = useCallback(() => (session ? session.getSnapshot() : NO_SESSION), [session]);\n const snapshot = useSyncExternalStore(subscribe, getSnapshot);\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState('');\n const [copiedKey, setCopiedKey] = useState(null);\n const timeoutsRef = useRef([]);\n const rafsRef = useRef([]);\n const flashedRowRef = useRef(null);\n const collapseTimerRef = useRef(null);\n // Cancel pending jump work (RAFs/timeouts), the flash highlight, and a\n // scheduled hover collapse.\n const clearPending = useCallback(() => {\n for (const id of rafsRef.current)\n window.cancelAnimationFrame(id);\n for (const id of timeoutsRef.current)\n window.clearTimeout(id);\n rafsRef.current = [];\n timeoutsRef.current = [];\n if (collapseTimerRef.current !== null) {\n window.clearTimeout(collapseTimerRef.current);\n collapseTimerRef.current = null;\n }\n if (flashedRowRef.current) {\n flashedRowRef.current.removeAttribute('data-dsh-outline-flash');\n flashedRowRef.current = null;\n }\n }, []);\n // Session change → cancel pending work and collapse (navigate closes the\n // panel, spec §2.1; a mid-jump poll must not target the stale session).\n useEffect(() => {\n clearPending();\n setOpen(false);\n }, [current, clearPending]);\n // Escape closes the panel (hover-opened or tap-pinned).\n useEffect(() => {\n if (!open)\n return;\n const onKey = (event) => {\n if (event.key === 'Escape')\n setOpen(false);\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [open]);\n // Unmount cleanup: cancel pending flash/copied timeouts, jump RAFs, collapse\n // timer, and the flash attribute.\n useEffect(() => clearPending, [clearPending]);\n const allItems = useMemo(() => (snapshot ? collectQuestions(snapshot) : []), [snapshot]);\n const items = useMemo(() => filterQuestions(allItems, query), [allItems, query]);\n const blank = snapshot?.blank ?? true;\n const hasMore = snapshot?.hasMore ?? false;\n const loadingOlder = snapshot?.loadingOlder ?? false;\n // ---- hover-open mechanics (rail ⇄ panel travel survives via the grace) ---\n const cancelCollapse = useCallback(() => {\n if (collapseTimerRef.current !== null) {\n window.clearTimeout(collapseTimerRef.current);\n collapseTimerRef.current = null;\n }\n }, []);\n const scheduleCollapse = useCallback(() => {\n cancelCollapse();\n collapseTimerRef.current = window.setTimeout(() => {\n collapseTimerRef.current = null;\n setOpen(false);\n }, COLLAPSE_DELAY_MS);\n }, [cancelCollapse]);\n const showPanel = useCallback(() => {\n cancelCollapse();\n setOpen(true);\n }, [cancelCollapse]);\n /** Find the chat row for a node key (pure predicate over DOM rows). */\n const findRow = useCallback((key) => {\n const rows = document.querySelectorAll('[data-chat-anchor-key]');\n for (const row of rows) {\n if (isJumpTargetRow(row, key))\n return row;\n }\n return null;\n }, []);\n /** Scroll the row into view (96px headroom) and flash a highlight. */\n const flashAndScroll = useCallback((row) => {\n const scrollport = row.closest('[data-conversation-scroll]') ?? document.querySelector('[data-conversation-scroll]');\n if (scrollport) {\n const flowTop = row.getBoundingClientRect().top - scrollport.getBoundingClientRect().top;\n const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n scrollport.scrollTo({\n top: scrollport.scrollTop + flowTop - JUMP_HEADROOM,\n behavior: reduced ? 'auto' : 'smooth',\n });\n }\n // A second jump within FLASH_MS: clear the previous row first.\n if (flashedRowRef.current && flashedRowRef.current !== row) {\n flashedRowRef.current.removeAttribute('data-dsh-outline-flash');\n }\n flashedRowRef.current = row;\n row.setAttribute('data-dsh-outline-flash', 'true');\n scheduleTimeout(timeoutsRef, () => {\n if (flashedRowRef.current === row) {\n row.removeAttribute('data-dsh-outline-flash');\n flashedRowRef.current = null;\n }\n }, FLASH_MS);\n }, []);\n /** Jump-to-message algorithm (spec §2.2). */\n const jumpTo = useCallback((key) => {\n // The conversation page must be mounted; abort silently otherwise.\n const scrollport = document.querySelector('[data-conversation-scroll]');\n if (!scrollport)\n return;\n // Ensure the Chat view is active: chat is order 0 — the FIRST tab of the\n // conversation-root tablist, when one exists (setView is idempotent).\n // A profile without extra views has no header tablist at all — that is\n // fine, the rows are already visible.\n const tablist = findConversationTablist(scrollport);\n if (tablist) {\n const firstTab = tablist.querySelector('button[role=\"tab\"]');\n if (firstTab)\n firstTab.click();\n }\n // Poll for the target row regardless of tablist presence — render timing\n // only; the row is guaranteed in the loaded window (it came from the\n // snapshot). Canceled on session change/unmount via clearPending.\n const deadline = Date.now() + JUMP_TIMEOUT_MS;\n const poll = () => {\n const row = findRow(key);\n if (row) {\n flashAndScroll(row);\n setOpen(false);\n return;\n }\n if (Date.now() < deadline) {\n scheduleRaf(rafsRef, poll);\n }\n else {\n console.warn(t('jumpFailed'), { key });\n }\n };\n scheduleRaf(rafsRef, poll);\n }, [findRow, flashAndScroll, t]);\n /** Copy a question's text (clipboard API with execCommand fallback). */\n const copyText = useCallback((text, key) => {\n const done = () => {\n setCopiedKey(key);\n scheduleTimeout(timeoutsRef, () => setCopiedKey((k) => (k === key ? null : k)), COPIED_MS);\n };\n const fallback = () => {\n try {\n const ta = document.createElement('textarea');\n ta.value = text;\n ta.setAttribute('readonly', '');\n ta.style.position = 'fixed';\n ta.style.opacity = '0';\n document.body.appendChild(ta);\n ta.select();\n document.execCommand('copy');\n ta.remove();\n done();\n }\n catch {\n // clipboard unavailable — ignore\n }\n };\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(text).then(done, fallback);\n }\n else {\n fallback();\n }\n }, []);\n // No current session, or a blank session → render nothing (rail included).\n // MUST stay below every hook: this overlay entry stays mounted across\n // session changes, and blank flips false on the first accepted prompt, so a\n // conditional hook would crash React (reviewer MUST-FIX #1).\n if (!current || !session || blank)\n return null;\n // Rail bars: chronological top→bottom, capped at MAX_BARS with the overflow\n // folded into a \"+N\" marker (the rail is a minimap, not the full list).\n const start = Math.max(0, allItems.length - MAX_BARS);\n const bars = allItems.slice(start);\n const overflow = start;\n return (_jsxs(_Fragment, { children: [_jsxs(\"div\", { className: \"dso-rail\", role: \"group\", \"aria-label\": t('title'), tabIndex: 0, onMouseEnter: showPanel, onMouseLeave: scheduleCollapse, onFocus: showPanel, onBlur: scheduleCollapse, onClick: (event) => {\n // Tap (touch) on the strip itself pins/unpins the panel.\n if (event.target === event.currentTarget)\n setOpen((v) => !v);\n }, onKeyDown: (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n setOpen((v) => !v);\n }\n }, children: [overflow > 0 && (_jsxs(\"span\", { className: \"dso-rail-more\", \"aria-label\": t('moreBars', { count: overflow }), children: [\"+\", overflow] })), bars.map((item, index) => (_jsx(\"button\", { type: \"button\", className: \"dso-bar\", \"aria-label\": t('barLabel', { n: start + index + 1 }), onClick: () => jumpTo(item.key) }, item.key)))] }), open && (_jsxs(\"div\", { className: \"dso-panel\", role: \"region\", \"aria-label\": t('title'), onMouseEnter: showPanel, onMouseLeave: scheduleCollapse, onFocus: showPanel, onBlur: scheduleCollapse, children: [_jsxs(\"header\", { className: \"dso-panel-header\", children: [_jsx(\"span\", { className: \"dso-panel-title\", children: t('title') }), _jsx(\"span\", { className: \"dso-panel-count\", children: t('count', { count: allItems.length }) }), _jsx(\"button\", { type: \"button\", className: \"dso-panel-close\", \"aria-label\": t('close'), onClick: () => setOpen(false), children: \"\\u00D7\" })] }), _jsx(\"input\", { className: \"dso-search\", type: \"search\", value: query, placeholder: t('searchPlaceholder'), \"aria-label\": t('searchPlaceholder'), onChange: (event) => setQuery(event.target.value) }), _jsx(\"div\", { className: \"dso-list\", children: items.length === 0 ? (_jsx(\"div\", { className: \"dso-empty\", children: t('empty') })) : (items.map((item) => (_jsxs(\"div\", { className: \"dso-row\", children: [_jsxs(\"button\", { type: \"button\", className: \"dso-row-main\", onClick: () => jumpTo(item.key), children: [_jsx(\"span\", { className: \"dso-turn\", children: item.turn !== undefined ? `#${item.turn}` : '' }), _jsx(\"span\", { className: \"dso-text\", children: item.text }), item.kind === 'steering' && _jsx(\"span\", { className: \"dso-steer\", children: t('steerTag') }), _jsx(\"span\", { className: \"dso-time\", children: formatTime(item.time) })] }), _jsx(\"button\", { type: \"button\", className: copiedKey === item.key ? 'dso-copy dso-copied' : 'dso-copy', \"aria-label\": copiedKey === item.key ? t('copied') : t('copy'), onClick: () => copyText(item.text, item.key), children: copiedKey === item.key ? t('copied') : t('copy') })] }, item.key)))) }), hasMore && (_jsx(\"footer\", { className: \"dso-footer\", children: _jsx(\"button\", { type: \"button\", className: \"dso-load-older\", disabled: loadingOlder, onClick: () => {\n void session.loadOlder();\n }, children: loadingOlder ? t('loadingOlder') : t('loadOlder') }) }))] }))] }));\n}\n","export const NS = 'dsh-conversation-outline';\nexport const zh = {\n title: '会话大纲',\n count: '{count} 个问题',\n searchPlaceholder: '搜索问题…',\n empty: '当前会话还没有问题',\n loadOlder: '加载更早',\n loadingOlder: '加载中…',\n copy: '复制',\n copied: '已复制',\n steerTag: '追问',\n close: '关闭',\n jumpFailed: '跳转失败:未找到对应消息',\n barLabel: '跳到第 {n} 个问题',\n moreBars: '还有 {count} 个更早的问题',\n};\nexport const en = {\n title: 'Outline',\n count: '{count} questions',\n searchPlaceholder: 'Search questions…',\n empty: 'No questions in this conversation yet',\n loadOlder: 'Load older',\n loadingOlder: 'Loading…',\n copy: 'Copy',\n copied: 'Copied',\n steerTag: 'steer',\n close: 'Close',\n jumpFailed: 'Jump failed: target message not found',\n barLabel: 'Jump to question {n}',\n moreBars: '{count} more earlier questions',\n};\nexport const dictionaries = { zh, en };\n","/**\n * Outline rail + hover panel CSS (implementation-spec §1.8/§2.1, revised):\n * a thin right-edge rail (conversation minimap) that is always visible while\n * the session has questions, and a preview panel that expands on hover.\n * Plain CSS string injected via an HMR-safe\n * `<style data-plugin=\"dsh-conversation-outline\" data-plugin-css=\"...\">` tag.\n * Stable prefixed class names (`dso_*`), DSH theme vars (`--dsw-alias-*`) —\n * never hashed class names of other packages. No layout shift: the panel is a\n * pure overlay.\n */\nexport const panelCss = `\n/* ---- Right-edge rail (always-visible minimap) --------------------------- */\n.dso-rail {\n position: fixed;\n top: 50%;\n right: 0;\n transform: translateY(-50%);\n z-index: 2147483000;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 3px;\n width: 30px;\n max-height: 55vh;\n padding: 8px 10px;\n overflow: hidden;\n cursor: pointer;\n}\n.dso-rail-more {\n flex: none;\n font-size: 9px;\n font-weight: 600;\n line-height: 10px;\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n}\n.dso-bar {\n flex: none;\n width: 10px;\n height: 6px;\n padding: 0;\n border: none;\n border-radius: 3px;\n background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 45%, transparent);\n cursor: pointer;\n transition: background 0.12s, transform 0.12s;\n}\n.dso-bar:hover {\n background: var(--dsw-alias-state-business-primary);\n transform: scaleX(1.35);\n}\n.dso-rail:focus-visible,\n.dso-bar:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary);\n outline-offset: 2px;\n}\n\n/* ---- Hover-expanded preview panel --------------------------------------- */\n.dso-panel {\n position: fixed;\n top: 50%;\n right: 16px;\n transform: translateY(-50%);\n z-index: 2147483000;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n width: min(340px, calc(100vw - 24px));\n max-height: min(70vh, 600px);\n overflow: hidden;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 16px;\n background: color-mix(in srgb, var(--dsw-alias-bg-module-platform) 92%, transparent);\n backdrop-filter: blur(20px);\n box-shadow: 0 16px 48px color-mix(in srgb, var(--dsw-alias-label-primary) 18%, transparent);\n color: var(--dsw-alias-label-primary);\n animation: dso-panel-in 140ms ease-out;\n}\n@keyframes dso-panel-in {\n from { opacity: 0; transform: translateY(-50%) translateX(12px); }\n to { opacity: 1; transform: translateY(-50%) translateX(0); }\n}\n\n.dso-panel-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 14px 10px;\n border-bottom: 1px solid var(--dsw-alias-line-normal);\n}\n.dso-panel-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n.dso-panel-count {\n flex: 1;\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dso-panel-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 16px;\n line-height: 1;\n cursor: pointer;\n transition: background 0.12s;\n}\n.dso-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n\n.dso-panel-close:focus-visible,\n.dso-search:focus-visible,\n.dso-row-main:focus-visible,\n.dso-copy:focus-visible,\n.dso-load-older:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary);\n outline-offset: 2px;\n}\n\n.dso-search {\n margin: 10px 12px 4px;\n box-sizing: border-box;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n outline: none;\n}\n.dso-search::placeholder { color: var(--dsw-alias-label-tertiary); }\n\n/* ---- Question list ------------------------------------------------------ */\n.dso-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px;\n}\n/* Row container: two sibling buttons (jump + copy) — no nested interactive\n controls. Hover highlights the whole row. */\n.dso-row {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 2px;\n border-radius: 10px;\n transition: background 0.12s;\n}\n.dso-row:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n.dso-row-main {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 6px;\n height: 28px;\n padding: 2px 6px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background 0.12s;\n}\n.dso-row-main:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n\n.dso-turn {\n flex: none;\n min-width: 26px;\n height: 16px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: var(--dsw-alias-bg-fill-business);\n color: var(--dsw-alias-label-on-fill);\n font-size: 10px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n}\n/* Single-line truncation: shows the opening words of each question —\n anything longer simply ellipsizes (per user request). */\n.dso-text {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n line-height: 18px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.dso-time {\n flex: none;\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n}\n.dso-steer {\n flex: none;\n padding: 0 6px;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 999px;\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n}\n.dso-copy {\n flex: none;\n padding: 2px 6px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 11px;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.12s, color 0.12s, background 0.12s;\n}\n.dso-row:hover .dso-copy,\n.dso-row:focus-within .dso-copy,\n.dso-copy:focus-visible { opacity: 1; }\n.dso-copy:hover { background: var(--dsw-alias-bg-fill-neutral); }\n.dso-copy.dso-copied { color: var(--dsw-alias-state-success); }\n\n.dso-empty {\n padding: 28px 16px;\n text-align: center;\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ---- Footer / load older ------------------------------------------------ */\n.dso-footer {\n display: flex;\n justify-content: center;\n padding: 8px 12px 10px;\n border-top: 1px solid var(--dsw-alias-line-normal);\n}\n.dso-load-older {\n padding: 4px 14px;\n border: none;\n border-radius: 14px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 12px;\n cursor: pointer;\n}\n.dso-load-older:disabled { cursor: default; opacity: 0.6; }\n\n/* ---- Jump flash highlight ----------------------------------------------- */\n[data-dsh-outline-flash] {\n animation: dso-flash 1.8s ease-out;\n}\n@keyframes dso-flash {\n 0% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--dsw-alias-state-business-primary) 50%, transparent);\n background-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 24%, transparent);\n }\n 70% {\n box-shadow: 0 0 0 6px transparent;\n background-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 8%, transparent);\n }\n 100% {\n box-shadow: 0 0 0 0 transparent;\n background-color: transparent;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dso-panel { animation: none; }\n .dso-bar { transition: none; }\n [data-dsh-outline-flash] { animation: none; }\n}\n`;\n/** Inject the style tag, owned by the client fiber (removed on dispose/HMR). */\nexport function injectStyle(ctx) {\n ctx.effect(() => {\n const tag = document.createElement('style');\n tag.dataset.plugin = 'dsh-conversation-outline';\n tag.dataset.pluginCss = 'dsh-conversation-outline/panel.css';\n tag.textContent = panelCss;\n document.head.appendChild(tag);\n return () => {\n tag.remove();\n };\n }, 'dsh-conversation-outline: panel css');\n}\n","import { OutlinePanel } from \"./OutlinePanel.js\";\nimport { NS, dictionaries } from \"./locales.js\";\nimport { injectStyle } from \"./styles.js\";\n/**\n * Client entry (implementation-spec §1.1/§2.4): the browser half of the\n * plugin. Cordis services this fiber waits for before apply() runs.\n */\nexport const inject = ['slots', 'sessions', 'locale'];\nexport function apply(ctx) {\n // Locale dictionaries — the typed register form (both shipped locales\n // required, keys compile-checked). The disposer is owned by the fiber, so\n // HMR removes the namespace on unload.\n ctx.effect(() => ctx.locale.register(NS, dictionaries), 'dsh-conversation-outline: locale');\n // Panel CSS as an HMR-safe <style data-plugin> tag (effect-owned).\n injectStyle(ctx);\n // Right-edge rail + hover panel in the frame-wide overlay layer.\n // slots.inject waits for the layout's declaration of `shell.overlay` and\n // routes the registration (and its unload cascade) through this fiber.\n // `locale: NS` gives the component the typed `t` seat; the inject factory\n // passes the sessions service face.\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\n name: 'shell.overlay',\n id: 'dsh-conversation-outline.rail',\n locale: NS,\n inject: () => ({ sessions: ctx.sessions }),\n }, OutlinePanel));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["_Fragment"],"sources":["client/outline.js","client/OutlinePanel.js","client/locales.js","client/styles.js","client/index.js"],"sourcesContent":["/**\n * Pure logic for the conversation outline (implementation-spec §2.3).\n *\n * Deliberately has NO DOM access and NO React imports so scripts/verify.mjs can\n * import it directly in Node from the tsc output (lib/client/outline.js) and\n * assert the derivations offline.\n *\n * Input shapes are structural \"lite\" views of the runtime snapshot types\n * (@deepseek-ai/dsh-client-runtime/client) — the real objects are assignable\n * to them, and Node can import this module without the browser type graph.\n */\nconst IMAGE_PLACEHOLDER = '[image]';\n/**\n * Flatten message content blocks into a single display string: text blocks are\n * joined, image blocks become a placeholder, and whitespace is trimmed and\n * collapsed.\n */\nexport function flattenQuestionText(content) {\n return content\n .map((block) => {\n if (block.type === 'text')\n return block.text ?? '';\n if (block.type === 'image')\n return IMAGE_PLACEHOLDER;\n return '';\n })\n .join(' ')\n .trim()\n .replace(/\\s+/g, ' ');\n}\n/** Turn number of a node location: step/turn → location.turn.turn, else undefined. */\nfunction turnOf(location) {\n if (!location)\n return undefined;\n if (location.kind === 'step' || location.kind === 'turn')\n return location.turn?.turn;\n return undefined;\n}\n/**\n * Walk the chat flow in order, keep user/steering nodes, skip empty text, and\n * produce chronological outline items (spec §2.3).\n */\nexport function collectQuestions(snapshot) {\n const items = [];\n for (const key of snapshot.chat.order) {\n const node = snapshot.chat.nodes.get(key);\n if (!node)\n continue;\n if (node.kind !== 'user' && node.kind !== 'steering')\n continue;\n const data = node.data;\n const text = flattenQuestionText(data?.content ?? []);\n if (!text)\n continue;\n items.push({\n key: node.key,\n kind: node.kind,\n seq: data?.seq ?? 0,\n time: data?.time ?? 0,\n turn: turnOf(node.location),\n text,\n });\n }\n return items;\n}\n/** Case-insensitive substring filter over the flattened question text. */\nexport function filterQuestions(items, query) {\n const q = query.trim().toLowerCase();\n if (!q)\n return items;\n return items.filter((item) => item.text.toLowerCase().includes(q));\n}\n/** Local `HH:MM` from a unix-ms timestamp (spec §2.3). */\nexport function formatTime(ms) {\n const d = new Date(ms);\n const hh = String(d.getHours()).padStart(2, '0');\n const mm = String(d.getMinutes()).padStart(2, '0');\n return `${hh}:${mm}`;\n}\n/** Pure DOM predicate for the jump loop: is this row the target node? */\nexport function isJumpTargetRow(row, key) {\n return row.getAttribute('data-chat-anchor-key') === key;\n}\n","import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from \"react/jsx-runtime\";\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react';\nimport { collectQuestions, filterQuestions, formatTime, isJumpTargetRow, } from \"./outline.js\";\n/** Stable getSnapshot value while no session is current (uSES contract). */\nconst NO_SESSION = null;\nconst JUMP_HEADROOM = 96;\nconst JUMP_TIMEOUT_MS = 1500;\nconst FLASH_MS = 1900;\nconst COPIED_MS = 1500;\n/** Grace period before the hover panel collapses (lets the pointer travel). */\nconst COLLAPSE_DELAY_MS = 240;\n/** Rail capacity; older questions fold into the \"+N\" marker. */\nconst MAX_BARS = 60;\n/**\n * The conversation view's header tablist, scoped to the conversation root:\n * walk up from the scrollport's PARENT (the scrollport itself may contain\n * other tablists, e.g. the trajectory event-details tabs) and return the\n * nearest ancestor containing a `[role=\"tablist\"]`. Never falls back to a\n * document-wide query, so other tablists (trajectory event details,\n * settings, cordis source viewer) are not hit.\n */\nfunction findConversationTablist(scrollport) {\n let node = scrollport.parentElement;\n while (node) {\n const tablist = node.querySelector('[role=\"tablist\"]');\n if (tablist)\n return tablist;\n node = node.parentElement;\n }\n return null;\n}\n/** RAF that prunes itself from the tracking ref once it fires. */\nfunction scheduleRaf(rafRef, fn) {\n const id = requestAnimationFrame(() => {\n rafRef.current = rafRef.current.filter((x) => x !== id);\n fn();\n });\n rafRef.current.push(id);\n}\n/** Timeout that prunes itself from the tracking ref once it fires. */\nfunction scheduleTimeout(timeoutRef, fn, ms) {\n const id = window.setTimeout(() => {\n timeoutRef.current = timeoutRef.current.filter((x) => x !== id);\n fn();\n }, ms);\n timeoutRef.current.push(id);\n}\nexport function OutlinePanel({ sessions, useSessions, t }) {\n const current = useSessions((s) => s.current);\n const session = current ? sessions.binding(current)?.session : undefined;\n // Conversation snapshot subscription (spec §1.5): live updates while the\n // session runs, load-older, blank detection — all ride this one store.\n const subscribe = useCallback((onChange) => (session ? session.subscribe(onChange) : () => { }), [session]);\n const getSnapshot = useCallback(() => (session ? session.getSnapshot() : NO_SESSION), [session]);\n const snapshot = useSyncExternalStore(subscribe, getSnapshot);\n // Two independent sources for the panel being open: `pinned` is a click\n // gesture (sticky until Escape / outside click / session change), `hovered`\n // is pointer presence over the rail or the panel. Deriving `open` from both\n // means a click on the rail opens the panel even when no hover event ever\n // fires (touch, synthetic/automation input, or a pointer that entered the\n // strip without crossing its boundary), and a hover preview still collapses\n // on its own once the pointer leaves.\n const [pinned, setPinned] = useState(false);\n const [hovered, setHovered] = useState(false);\n const open = pinned || hovered;\n const [query, setQuery] = useState('');\n const [copiedKey, setCopiedKey] = useState(null);\n // The question whose jump could not be completed; the panel says so instead\n // of failing silently (the row may sit outside the loaded history window).\n const [jumpFailed, setJumpFailed] = useState(null);\n const timeoutsRef = useRef([]);\n const rafsRef = useRef([]);\n const flashedRowRef = useRef(null);\n const collapseTimerRef = useRef(null);\n const railRef = useRef(null);\n const panelRef = useRef(null);\n // Cancel pending jump work (RAFs/timeouts), the flash highlight, and a\n // scheduled hover collapse.\n const clearPending = useCallback(() => {\n for (const id of rafsRef.current)\n window.cancelAnimationFrame(id);\n for (const id of timeoutsRef.current)\n window.clearTimeout(id);\n rafsRef.current = [];\n timeoutsRef.current = [];\n if (collapseTimerRef.current !== null) {\n window.clearTimeout(collapseTimerRef.current);\n collapseTimerRef.current = null;\n }\n if (flashedRowRef.current) {\n flashedRowRef.current.removeAttribute('data-dsh-outline-flash');\n flashedRowRef.current = null;\n }\n }, []);\n /** Close the panel from any trigger: drop the pin and the hover preview. */\n const closePanel = useCallback(() => {\n setPinned(false);\n setHovered(false);\n cancelCollapseRef.current();\n }, []);\n // Session change → cancel pending work and collapse (navigate closes the\n // panel, spec §2.1; a mid-jump poll must not target the stale session).\n useEffect(() => {\n clearPending();\n setPinned(false);\n setHovered(false);\n setJumpFailed(null);\n }, [current, clearPending]);\n // Escape closes the panel (hover-opened or click-pinned).\n useEffect(() => {\n if (!open)\n return;\n const onKey = (event) => {\n if (event.key === 'Escape')\n closePanel();\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [open, closePanel]);\n // A click anywhere outside the rail and the panel releases a pin, so the\n // panel behaves like every other dismissible overlay on the page.\n useEffect(() => {\n if (!pinned)\n return;\n const onPointerDown = (event) => {\n const target = event.target;\n if (!(target instanceof Node))\n return;\n if (railRef.current?.contains(target) || panelRef.current?.contains(target))\n return;\n closePanel();\n };\n window.addEventListener('mousedown', onPointerDown);\n return () => window.removeEventListener('mousedown', onPointerDown);\n }, [pinned, closePanel]);\n // Unmount cleanup: cancel pending flash/copied timeouts, jump RAFs, collapse\n // timer, and the flash attribute.\n useEffect(() => clearPending, [clearPending]);\n const allItems = useMemo(() => (snapshot ? collectQuestions(snapshot) : []), [snapshot]);\n const items = useMemo(() => filterQuestions(allItems, query), [allItems, query]);\n const blank = snapshot?.blank ?? true;\n const hasMore = snapshot?.hasMore ?? false;\n const loadingOlder = snapshot?.loadingOlder ?? false;\n // ---- open mechanics: click pins, hover previews, grace period between ---\n const cancelCollapse = useCallback(() => {\n if (collapseTimerRef.current !== null) {\n window.clearTimeout(collapseTimerRef.current);\n collapseTimerRef.current = null;\n }\n }, []);\n // `closePanel` above runs before this declaration; a ref keeps the two\n // callbacks independent of declaration order.\n const cancelCollapseRef = useRef(cancelCollapse);\n cancelCollapseRef.current = cancelCollapse;\n const scheduleCollapse = useCallback(() => {\n cancelCollapse();\n collapseTimerRef.current = window.setTimeout(() => {\n collapseTimerRef.current = null;\n // Only a preview collapses on its own: a pinned panel stays until it is\n // closed, and a pointer still inside the rail or the panel (Chromium\n // raises no further enter events while the pointer sits still) keeps the\n // preview up.\n if (railRef.current?.matches(':hover') || panelRef.current?.matches(':hover'))\n return;\n setHovered(false);\n }, COLLAPSE_DELAY_MS);\n }, [cancelCollapse]);\n /** Pointer entered the rail or the panel: cancel a pending collapse, preview. */\n const onEnter = useCallback(() => {\n cancelCollapse();\n setHovered(true);\n }, [cancelCollapse]);\n /** Pointer left: let the grace period decide, unless the panel is pinned. */\n const onLeave = useCallback(() => {\n if (pinned)\n return;\n scheduleCollapse();\n }, [pinned, scheduleCollapse]);\n /** A click on the rail (or any bar) opens the panel and keeps it open. */\n const togglePin = useCallback(() => {\n cancelCollapse();\n setPinned((value) => !value);\n setHovered(true);\n }, [cancelCollapse]);\n /** The panel's own controls: clicking inside never closes it. */\n const keepOpen = useCallback(() => {\n cancelCollapse();\n setHovered(true);\n }, [cancelCollapse]);\n /** Grid of candidate keys for one node: the anchor the row renders first. */\n function jumpKeyCandidates(key) {\n return key.includes('|') ? [key.split('|')[0], key] : [key];\n }\n /** Find the chat row for a node key (pure predicate over DOM rows). */\n const findRow = useCallback((key) => {\n const rows = document.querySelectorAll('[data-chat-anchor-key]');\n for (const candidate of jumpKeyCandidates(key)) {\n for (const row of rows) {\n if (isJumpTargetRow(row, candidate))\n return row;\n }\n }\n return null;\n }, []);\n /** Scroll the row into view (96px headroom) and flash a highlight. */\n const flashAndScroll = useCallback((row) => {\n const scrollport = row.closest('[data-conversation-scroll]') ?? document.querySelector('[data-conversation-scroll]');\n if (scrollport) {\n const flowTop = row.getBoundingClientRect().top - scrollport.getBoundingClientRect().top;\n const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n scrollport.scrollTo({\n top: scrollport.scrollTop + flowTop - JUMP_HEADROOM,\n behavior: reduced ? 'auto' : 'smooth',\n });\n }\n // A second jump within FLASH_MS: clear the previous row first.\n if (flashedRowRef.current && flashedRowRef.current !== row) {\n flashedRowRef.current.removeAttribute('data-dsh-outline-flash');\n }\n flashedRowRef.current = row;\n row.setAttribute('data-dsh-outline-flash', 'true');\n scheduleTimeout(timeoutsRef, () => {\n if (flashedRowRef.current === row) {\n row.removeAttribute('data-dsh-outline-flash');\n flashedRowRef.current = null;\n }\n }, FLASH_MS);\n }, []);\n /** Jump-to-message algorithm (spec §2.2). */\n const jumpTo = useCallback((key, label) => {\n // The conversation page must be mounted; abort silently otherwise.\n const scrollport = document.querySelector('[data-conversation-scroll]');\n if (!scrollport)\n return;\n setJumpFailed(null);\n // Ensure the Chat view is active: chat is order 0 — the FIRST tab of the\n // conversation-root tablist, when one exists (setView is idempotent).\n // A profile without extra views has no header tablist at all — that is\n // fine, the rows are already visible.\n const tablist = findConversationTablist(scrollport);\n if (tablist) {\n const firstTab = tablist.querySelector('button[role=\"tab\"]');\n if (firstTab)\n firstTab.click();\n }\n // Poll for the target row regardless of tablist presence — render timing\n // only; the row is guaranteed in the loaded window (it came from the\n // snapshot). Canceled on session change/unmount via clearPending.\n const deadline = Date.now() + JUMP_TIMEOUT_MS;\n const poll = () => {\n const row = findRow(key);\n if (row) {\n flashAndScroll(row);\n closePanel();\n return;\n }\n if (Date.now() < deadline) {\n scheduleRaf(rafsRef, poll);\n }\n else {\n // Never fail silently: the panel states what happened, and the\n // console keeps the key for bug reports.\n console.warn(t('jumpFailed'), { key });\n setJumpFailed(label ?? key);\n }\n };\n scheduleRaf(rafsRef, poll);\n }, [findRow, flashAndScroll, t, closePanel]);\n /** Copy a question's text (clipboard API with execCommand fallback). */\n const copyText = useCallback((text, key) => {\n const done = () => {\n setCopiedKey(key);\n scheduleTimeout(timeoutsRef, () => setCopiedKey((k) => (k === key ? null : k)), COPIED_MS);\n };\n const fallback = () => {\n try {\n const ta = document.createElement('textarea');\n ta.value = text;\n ta.setAttribute('readonly', '');\n ta.style.position = 'fixed';\n ta.style.opacity = '0';\n document.body.appendChild(ta);\n ta.select();\n document.execCommand('copy');\n ta.remove();\n done();\n }\n catch {\n // clipboard unavailable — ignore\n }\n };\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(text).then(done, fallback);\n }\n else {\n fallback();\n }\n }, []);\n // No current session, or a blank session → render nothing (rail included).\n // MUST stay below every hook: this overlay entry stays mounted across\n // session changes, and blank flips false on the first accepted prompt, so a\n // conditional hook would crash React (reviewer MUST-FIX #1).\n if (!current || !session || blank)\n return null;\n // Rail bars: chronological top→bottom, capped at MAX_BARS with the overflow\n // folded into a \"+N\" marker (the rail is a minimap, not the full list).\n const start = Math.max(0, allItems.length - MAX_BARS);\n const bars = allItems.slice(start);\n const overflow = start;\n return (_jsxs(_Fragment, { children: [_jsxs(\"div\", { ref: railRef, className: \"dso-rail\", role: \"group\", \"aria-label\": t('title'), tabIndex: 0, onMouseEnter: onEnter, onMouseLeave: onLeave, onFocus: onEnter, onBlur: onLeave, onClick: (event) => {\n // Any click on the strip pins the panel open — the container itself,\n // its padding, or the \"+N\" marker. Bars stop propagation and reach\n // the same pin through the shared jump gesture below.\n if (event.target === event.currentTarget || event.target instanceof HTMLSpanElement) {\n togglePin();\n }\n }, onKeyDown: (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n togglePin();\n }\n }, \"data-dso-open\": open ? 'true' : undefined, children: [overflow > 0 && (_jsxs(\"span\", { className: \"dso-rail-more\", \"aria-label\": t('moreBars', { count: overflow }), children: [\"+\", overflow] })), bars.map((item, index) => (_jsx(\"button\", { type: \"button\", className: \"dso-bar\", \"aria-label\": t('barLabel', { n: start + index + 1 }), onClick: (event) => {\n event.stopPropagation();\n // The bar's own job is the jump; the panel comes up pinned first\n // so a click always has a visible effect, then jumpTo collapses\n // it once the row is on screen.\n setPinned(true);\n setHovered(true);\n jumpTo(item.key, item.text);\n } }, item.key)))] }), open && (_jsxs(\"div\", { ref: panelRef, className: \"dso-panel\", role: \"region\", \"aria-label\": t('title'), \"data-dso-pinned\": pinned ? 'true' : undefined, onMouseEnter: onEnter, onMouseLeave: onLeave, onFocus: onEnter, onBlur: onLeave, onMouseDown: keepOpen, children: [_jsxs(\"header\", { className: \"dso-panel-header\", children: [_jsx(\"span\", { className: \"dso-panel-title\", children: t('title') }), _jsx(\"span\", { className: \"dso-panel-count\", children: t('count', { count: allItems.length }) }), _jsx(\"button\", { type: \"button\", className: \"dso-panel-close\", \"aria-label\": t('close'), onClick: closePanel, children: \"\\u00D7\" })] }), _jsx(\"input\", { className: \"dso-search\", type: \"search\", value: query, placeholder: t('searchPlaceholder'), \"aria-label\": t('searchPlaceholder'), onChange: (event) => setQuery(event.target.value) }), _jsx(\"div\", { className: \"dso-list\", children: items.length === 0 ? (_jsx(\"div\", { className: \"dso-empty\", children: t('empty') })) : (items.map((item) => (_jsxs(\"div\", { className: \"dso-row\", children: [_jsxs(\"button\", { type: \"button\", className: \"dso-row-main\", onClick: () => jumpTo(item.key, item.text), children: [_jsx(\"span\", { className: \"dso-turn\", children: item.turn !== undefined ? `#${item.turn}` : '' }), _jsx(\"span\", { className: \"dso-text\", children: item.text }), item.kind === 'steering' && _jsx(\"span\", { className: \"dso-steer\", children: t('steerTag') }), _jsx(\"span\", { className: \"dso-time\", children: formatTime(item.time) })] }), _jsx(\"button\", { type: \"button\", className: copiedKey === item.key ? 'dso-copy dso-copied' : 'dso-copy', \"aria-label\": copiedKey === item.key ? t('copied') : t('copy'), onClick: () => copyText(item.text, item.key), children: copiedKey === item.key ? t('copied') : t('copy') })] }, item.key)))) }), jumpFailed !== null && (_jsx(\"p\", { className: \"dso-jump-failed\", role: \"status\", \"data-dso-jump-failed\": \"true\", children: t('jumpFailed') })), hasMore && (_jsx(\"footer\", { className: \"dso-footer\", children: _jsx(\"button\", { type: \"button\", className: \"dso-load-older\", disabled: loadingOlder, onClick: () => {\n void session.loadOlder();\n }, children: loadingOlder ? t('loadingOlder') : t('loadOlder') }) }))] }))] }));\n}\n","export const NS = 'dsh-conversation-outline';\nexport const zh = {\n title: '会话大纲',\n count: '{count} 个问题',\n searchPlaceholder: '搜索问题…',\n empty: '当前会话还没有问题',\n loadOlder: '加载更早',\n loadingOlder: '加载中…',\n copy: '复制',\n copied: '已复制',\n steerTag: '追问',\n close: '关闭',\n jumpFailed: '跳转失败:未找到对应消息',\n barLabel: '跳到第 {n} 个问题',\n moreBars: '还有 {count} 个更早的问题',\n};\nexport const en = {\n title: 'Outline',\n count: '{count} questions',\n searchPlaceholder: 'Search questions…',\n empty: 'No questions in this conversation yet',\n loadOlder: 'Load older',\n loadingOlder: 'Loading…',\n copy: 'Copy',\n copied: 'Copied',\n steerTag: 'steer',\n close: 'Close',\n jumpFailed: 'Jump failed: target message not found',\n barLabel: 'Jump to question {n}',\n moreBars: '{count} more earlier questions',\n};\nexport const dictionaries = { zh, en };\n","/**\n * Outline rail + hover panel CSS (implementation-spec §1.8/§2.1, revised):\n * a thin right-edge rail (conversation minimap) that is always visible while\n * the session has questions, and a preview panel that expands on hover.\n * Plain CSS string injected via an HMR-safe\n * `<style data-plugin=\"dsh-conversation-outline\" data-plugin-css=\"...\">` tag.\n * Stable prefixed class names (`dso_*`), DSH theme vars (`--dsw-alias-*`) —\n * never hashed class names of other packages. No layout shift: the panel is a\n * pure overlay.\n */\nexport const panelCss = `\n/* ---- Right-edge rail (always-visible minimap) --------------------------- */\n.dso-rail {\n position: fixed;\n top: 50%;\n right: 0;\n transform: translateY(-50%);\n z-index: 2147483000;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 3px;\n width: 30px;\n max-height: 55vh;\n padding: 8px 10px;\n overflow: hidden;\n cursor: pointer;\n}\n.dso-rail-more {\n flex: none;\n font-size: 9px;\n font-weight: 600;\n line-height: 10px;\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n}\n.dso-bar {\n flex: none;\n width: 10px;\n height: 6px;\n padding: 0;\n border: none;\n border-radius: 3px;\n background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 45%, transparent);\n cursor: pointer;\n transition: background 0.12s, transform 0.12s;\n}\n.dso-bar:hover {\n background: var(--dsw-alias-state-business-primary);\n transform: scaleX(1.35);\n}\n.dso-rail:focus-visible,\n.dso-bar:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary);\n outline-offset: 2px;\n}\n/* Clickability affordance: a hovered or pinned strip lights its bars up, so\n it reads as a control rather than as decoration. */\n.dso-rail:hover .dso-bar,\n.dso-rail[data-dso-open] .dso-bar {\n background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 72%, transparent);\n}\n.dso-rail:hover .dso-bar:hover {\n background: var(--dsw-alias-state-business-primary);\n}\n\n/* ---- Hover-expanded preview panel --------------------------------------- */\n.dso-panel {\n position: fixed;\n top: 50%;\n right: 16px;\n transform: translateY(-50%);\n z-index: 2147483000;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n width: min(340px, calc(100vw - 24px));\n max-height: min(70vh, 600px);\n overflow: hidden;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 16px;\n background: color-mix(in srgb, var(--dsw-alias-bg-module-platform) 92%, transparent);\n backdrop-filter: blur(20px);\n box-shadow: 0 16px 48px color-mix(in srgb, var(--dsw-alias-label-primary) 18%, transparent);\n color: var(--dsw-alias-label-primary);\n animation: dso-panel-in 140ms ease-out;\n}\n/* A pinned panel (clicked open) keeps a defined edge, so it is obvious that it\n no longer collapses when the pointer leaves. */\n.dso-panel[data-dso-pinned] {\n border-color: var(--dsw-alias-state-business-primary);\n}\n@keyframes dso-panel-in {\n from { opacity: 0; transform: translateY(-50%) translateX(12px); }\n to { opacity: 1; transform: translateY(-50%) translateX(0); }\n}\n\n.dso-panel-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 14px 10px;\n border-bottom: 1px solid var(--dsw-alias-line-normal);\n}\n.dso-panel-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n.dso-panel-count {\n flex: 1;\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dso-panel-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 16px;\n line-height: 1;\n cursor: pointer;\n transition: background 0.12s;\n}\n.dso-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n\n.dso-panel-close:focus-visible,\n.dso-search:focus-visible,\n.dso-row-main:focus-visible,\n.dso-copy:focus-visible,\n.dso-load-older:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary);\n outline-offset: 2px;\n}\n\n.dso-search {\n margin: 10px 12px 4px;\n box-sizing: border-box;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n outline: none;\n}\n.dso-search::placeholder { color: var(--dsw-alias-label-tertiary); }\n\n/* ---- Question list ------------------------------------------------------ */\n.dso-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px;\n}\n/* Row container: two sibling buttons (jump + copy) — no nested interactive\n controls. Hover highlights the whole row. */\n.dso-row {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 2px;\n border-radius: 10px;\n transition: background 0.12s;\n}\n.dso-row:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n.dso-row-main {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 6px;\n height: 28px;\n padding: 2px 6px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background 0.12s;\n}\n.dso-row-main:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n\n.dso-turn {\n flex: none;\n min-width: 26px;\n height: 16px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: var(--dsw-alias-bg-fill-business);\n color: var(--dsw-alias-label-on-fill);\n font-size: 10px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n}\n/* Single-line truncation: shows the opening words of each question —\n anything longer simply ellipsizes (per user request). */\n.dso-text {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n line-height: 18px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.dso-time {\n flex: none;\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n}\n.dso-steer {\n flex: none;\n padding: 0 6px;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 999px;\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n}\n.dso-copy {\n flex: none;\n padding: 2px 6px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 11px;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.12s, color 0.12s, background 0.12s;\n}\n.dso-row:hover .dso-copy,\n.dso-row:focus-within .dso-copy,\n.dso-copy:focus-visible { opacity: 1; }\n.dso-copy:hover { background: var(--dsw-alias-bg-fill-neutral); }\n.dso-copy.dso-copied { color: var(--dsw-alias-state-success); }\n\n.dso-empty {\n padding: 28px 16px;\n text-align: center;\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ---- Jump failure notice ------------------------------------------------ */\n.dso-jump-failed {\n margin: 0;\n padding: 8px 14px;\n border-top: 1px solid var(--dsw-alias-line-normal);\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-warning, var(--dsw-alias-label-secondary));\n}\n\n/* ---- Footer / load older ------------------------------------------------ */\n.dso-footer {\n display: flex;\n justify-content: center;\n padding: 8px 12px 10px;\n border-top: 1px solid var(--dsw-alias-line-normal);\n}\n.dso-load-older {\n padding: 4px 14px;\n border: none;\n border-radius: 14px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 12px;\n cursor: pointer;\n}\n.dso-load-older:disabled { cursor: default; opacity: 0.6; }\n\n/* ---- Jump flash highlight ----------------------------------------------- */\n[data-dsh-outline-flash] {\n animation: dso-flash 1.8s ease-out;\n}\n@keyframes dso-flash {\n 0% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--dsw-alias-state-business-primary) 50%, transparent);\n background-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 24%, transparent);\n }\n 70% {\n box-shadow: 0 0 0 6px transparent;\n background-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 8%, transparent);\n }\n 100% {\n box-shadow: 0 0 0 0 transparent;\n background-color: transparent;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dso-panel { animation: none; }\n .dso-bar { transition: none; }\n [data-dsh-outline-flash] { animation: none; }\n}\n`;\n/** Inject the style tag, owned by the client fiber (removed on dispose/HMR). */\nexport function injectStyle(ctx) {\n ctx.effect(() => {\n const tag = document.createElement('style');\n tag.dataset.plugin = 'dsh-conversation-outline';\n tag.dataset.pluginCss = 'dsh-conversation-outline/panel.css';\n tag.textContent = panelCss;\n document.head.appendChild(tag);\n return () => {\n tag.remove();\n };\n }, 'dsh-conversation-outline: panel css');\n}\n","import { OutlinePanel } from \"./OutlinePanel.js\";\nimport { NS, dictionaries } from \"./locales.js\";\nimport { injectStyle } from \"./styles.js\";\n/**\n * Client entry (implementation-spec §1.1/§2.4): the browser half of the\n * plugin. Cordis services this fiber waits for before apply() runs.\n */\nexport const inject = ['slots', 'sessions', 'locale'];\nexport function apply(ctx) {\n // Locale dictionaries — the typed register form (both shipped locales\n // required, keys compile-checked). The disposer is owned by the fiber, so\n // HMR removes the namespace on unload.\n ctx.effect(() => ctx.locale.register(NS, dictionaries), 'dsh-conversation-outline: locale');\n // Panel CSS as an HMR-safe <style data-plugin> tag (effect-owned).\n injectStyle(ctx);\n // Right-edge rail + hover panel in the frame-wide overlay layer.\n // slots.inject waits for the layout's declaration of `shell.overlay` and\n // routes the registration (and its unload cascade) through this fiber.\n // `locale: NS` gives the component the typed `t` seat; the inject factory\n // passes the sessions service face.\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\n name: 'shell.overlay',\n id: 'dsh-conversation-outline.rail',\n locale: NS,\n inject: () => ({ sessions: ctx.sessions }),\n }, OutlinePanel));\n}\n"],"mappings":"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"}
|
|
@@ -9,6 +9,6 @@ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
|
9
9
|
* never hashed class names of other packages. No layout shift: the panel is a
|
|
10
10
|
* pure overlay.
|
|
11
11
|
*/
|
|
12
|
-
export declare const panelCss = "\n/* ---- Right-edge rail (always-visible minimap) --------------------------- */\n.dso-rail {\n position: fixed;\n top: 50%;\n right: 0;\n transform: translateY(-50%);\n z-index: 2147483000;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 3px;\n width: 30px;\n max-height: 55vh;\n padding: 8px 10px;\n overflow: hidden;\n cursor: pointer;\n}\n.dso-rail-more {\n flex: none;\n font-size: 9px;\n font-weight: 600;\n line-height: 10px;\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n}\n.dso-bar {\n flex: none;\n width: 10px;\n height: 6px;\n padding: 0;\n border: none;\n border-radius: 3px;\n background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 45%, transparent);\n cursor: pointer;\n transition: background 0.12s, transform 0.12s;\n}\n.dso-bar:hover {\n background: var(--dsw-alias-state-business-primary);\n transform: scaleX(1.35);\n}\n.dso-rail:focus-visible,\n.dso-bar:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary);\n outline-offset: 2px;\n}\n\n/* ---- Hover-expanded preview panel --------------------------------------- */\n.dso-panel {\n position: fixed;\n top: 50%;\n right: 16px;\n transform: translateY(-50%);\n z-index: 2147483000;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n width: min(340px, calc(100vw - 24px));\n max-height: min(70vh, 600px);\n overflow: hidden;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 16px;\n background: color-mix(in srgb, var(--dsw-alias-bg-module-platform) 92%, transparent);\n backdrop-filter: blur(20px);\n box-shadow: 0 16px 48px color-mix(in srgb, var(--dsw-alias-label-primary) 18%, transparent);\n color: var(--dsw-alias-label-primary);\n animation: dso-panel-in 140ms ease-out;\n}\n@keyframes dso-panel-in {\n from { opacity: 0; transform: translateY(-50%) translateX(12px); }\n to { opacity: 1; transform: translateY(-50%) translateX(0); }\n}\n\n.dso-panel-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 14px 10px;\n border-bottom: 1px solid var(--dsw-alias-line-normal);\n}\n.dso-panel-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n.dso-panel-count {\n flex: 1;\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dso-panel-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 16px;\n line-height: 1;\n cursor: pointer;\n transition: background 0.12s;\n}\n.dso-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n\n.dso-panel-close:focus-visible,\n.dso-search:focus-visible,\n.dso-row-main:focus-visible,\n.dso-copy:focus-visible,\n.dso-load-older:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary);\n outline-offset: 2px;\n}\n\n.dso-search {\n margin: 10px 12px 4px;\n box-sizing: border-box;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n outline: none;\n}\n.dso-search::placeholder { color: var(--dsw-alias-label-tertiary); }\n\n/* ---- Question list ------------------------------------------------------ */\n.dso-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px;\n}\n/* Row container: two sibling buttons (jump + copy) \u2014 no nested interactive\n controls. Hover highlights the whole row. */\n.dso-row {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 2px;\n border-radius: 10px;\n transition: background 0.12s;\n}\n.dso-row:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n.dso-row-main {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 6px;\n height: 28px;\n padding: 2px 6px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background 0.12s;\n}\n.dso-row-main:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n\n.dso-turn {\n flex: none;\n min-width: 26px;\n height: 16px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: var(--dsw-alias-bg-fill-business);\n color: var(--dsw-alias-label-on-fill);\n font-size: 10px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n}\n/* Single-line truncation: shows the opening words of each question \u2014\n anything longer simply ellipsizes (per user request). */\n.dso-text {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n line-height: 18px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.dso-time {\n flex: none;\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n}\n.dso-steer {\n flex: none;\n padding: 0 6px;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 999px;\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n}\n.dso-copy {\n flex: none;\n padding: 2px 6px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 11px;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.12s, color 0.12s, background 0.12s;\n}\n.dso-row:hover .dso-copy,\n.dso-row:focus-within .dso-copy,\n.dso-copy:focus-visible { opacity: 1; }\n.dso-copy:hover { background: var(--dsw-alias-bg-fill-neutral); }\n.dso-copy.dso-copied { color: var(--dsw-alias-state-success); }\n\n.dso-empty {\n padding: 28px 16px;\n text-align: center;\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ---- Footer / load older ------------------------------------------------ */\n.dso-footer {\n display: flex;\n justify-content: center;\n padding: 8px 12px 10px;\n border-top: 1px solid var(--dsw-alias-line-normal);\n}\n.dso-load-older {\n padding: 4px 14px;\n border: none;\n border-radius: 14px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 12px;\n cursor: pointer;\n}\n.dso-load-older:disabled { cursor: default; opacity: 0.6; }\n\n/* ---- Jump flash highlight ----------------------------------------------- */\n[data-dsh-outline-flash] {\n animation: dso-flash 1.8s ease-out;\n}\n@keyframes dso-flash {\n 0% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--dsw-alias-state-business-primary) 50%, transparent);\n background-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 24%, transparent);\n }\n 70% {\n box-shadow: 0 0 0 6px transparent;\n background-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 8%, transparent);\n }\n 100% {\n box-shadow: 0 0 0 0 transparent;\n background-color: transparent;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dso-panel { animation: none; }\n .dso-bar { transition: none; }\n [data-dsh-outline-flash] { animation: none; }\n}\n";
|
|
12
|
+
export declare const panelCss = "\n/* ---- Right-edge rail (always-visible minimap) --------------------------- */\n.dso-rail {\n position: fixed;\n top: 50%;\n right: 0;\n transform: translateY(-50%);\n z-index: 2147483000;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 3px;\n width: 30px;\n max-height: 55vh;\n padding: 8px 10px;\n overflow: hidden;\n cursor: pointer;\n}\n.dso-rail-more {\n flex: none;\n font-size: 9px;\n font-weight: 600;\n line-height: 10px;\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n}\n.dso-bar {\n flex: none;\n width: 10px;\n height: 6px;\n padding: 0;\n border: none;\n border-radius: 3px;\n background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 45%, transparent);\n cursor: pointer;\n transition: background 0.12s, transform 0.12s;\n}\n.dso-bar:hover {\n background: var(--dsw-alias-state-business-primary);\n transform: scaleX(1.35);\n}\n.dso-rail:focus-visible,\n.dso-bar:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary);\n outline-offset: 2px;\n}\n/* Clickability affordance: a hovered or pinned strip lights its bars up, so\n it reads as a control rather than as decoration. */\n.dso-rail:hover .dso-bar,\n.dso-rail[data-dso-open] .dso-bar {\n background: color-mix(in srgb, var(--dsw-alias-label-tertiary) 72%, transparent);\n}\n.dso-rail:hover .dso-bar:hover {\n background: var(--dsw-alias-state-business-primary);\n}\n\n/* ---- Hover-expanded preview panel --------------------------------------- */\n.dso-panel {\n position: fixed;\n top: 50%;\n right: 16px;\n transform: translateY(-50%);\n z-index: 2147483000;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n width: min(340px, calc(100vw - 24px));\n max-height: min(70vh, 600px);\n overflow: hidden;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 16px;\n background: color-mix(in srgb, var(--dsw-alias-bg-module-platform) 92%, transparent);\n backdrop-filter: blur(20px);\n box-shadow: 0 16px 48px color-mix(in srgb, var(--dsw-alias-label-primary) 18%, transparent);\n color: var(--dsw-alias-label-primary);\n animation: dso-panel-in 140ms ease-out;\n}\n/* A pinned panel (clicked open) keeps a defined edge, so it is obvious that it\n no longer collapses when the pointer leaves. */\n.dso-panel[data-dso-pinned] {\n border-color: var(--dsw-alias-state-business-primary);\n}\n@keyframes dso-panel-in {\n from { opacity: 0; transform: translateY(-50%) translateX(12px); }\n to { opacity: 1; transform: translateY(-50%) translateX(0); }\n}\n\n.dso-panel-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 14px 10px;\n border-bottom: 1px solid var(--dsw-alias-line-normal);\n}\n.dso-panel-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n.dso-panel-count {\n flex: 1;\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dso-panel-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 16px;\n line-height: 1;\n cursor: pointer;\n transition: background 0.12s;\n}\n.dso-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n\n.dso-panel-close:focus-visible,\n.dso-search:focus-visible,\n.dso-row-main:focus-visible,\n.dso-copy:focus-visible,\n.dso-load-older:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary);\n outline-offset: 2px;\n}\n\n.dso-search {\n margin: 10px 12px 4px;\n box-sizing: border-box;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n outline: none;\n}\n.dso-search::placeholder { color: var(--dsw-alias-label-tertiary); }\n\n/* ---- Question list ------------------------------------------------------ */\n.dso-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 6px;\n}\n/* Row container: two sibling buttons (jump + copy) \u2014 no nested interactive\n controls. Hover highlights the whole row. */\n.dso-row {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 2px;\n border-radius: 10px;\n transition: background 0.12s;\n}\n.dso-row:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n.dso-row-main {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 6px;\n height: 28px;\n padding: 2px 6px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background 0.12s;\n}\n.dso-row-main:hover { background: var(--dsw-alias-interactive-bg-hover-solid); }\n\n.dso-turn {\n flex: none;\n min-width: 26px;\n height: 16px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: var(--dsw-alias-bg-fill-business);\n color: var(--dsw-alias-label-on-fill);\n font-size: 10px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n}\n/* Single-line truncation: shows the opening words of each question \u2014\n anything longer simply ellipsizes (per user request). */\n.dso-text {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n line-height: 18px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.dso-time {\n flex: none;\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n}\n.dso-steer {\n flex: none;\n padding: 0 6px;\n border: 1px solid var(--dsw-alias-line-normal);\n border-radius: 999px;\n font-size: 10px;\n color: var(--dsw-alias-label-secondary);\n}\n.dso-copy {\n flex: none;\n padding: 2px 6px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 11px;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.12s, color 0.12s, background 0.12s;\n}\n.dso-row:hover .dso-copy,\n.dso-row:focus-within .dso-copy,\n.dso-copy:focus-visible { opacity: 1; }\n.dso-copy:hover { background: var(--dsw-alias-bg-fill-neutral); }\n.dso-copy.dso-copied { color: var(--dsw-alias-state-success); }\n\n.dso-empty {\n padding: 28px 16px;\n text-align: center;\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ---- Jump failure notice ------------------------------------------------ */\n.dso-jump-failed {\n margin: 0;\n padding: 8px 14px;\n border-top: 1px solid var(--dsw-alias-line-normal);\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-warning, var(--dsw-alias-label-secondary));\n}\n\n/* ---- Footer / load older ------------------------------------------------ */\n.dso-footer {\n display: flex;\n justify-content: center;\n padding: 8px 12px 10px;\n border-top: 1px solid var(--dsw-alias-line-normal);\n}\n.dso-load-older {\n padding: 4px 14px;\n border: none;\n border-radius: 14px;\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 12px;\n cursor: pointer;\n}\n.dso-load-older:disabled { cursor: default; opacity: 0.6; }\n\n/* ---- Jump flash highlight ----------------------------------------------- */\n[data-dsh-outline-flash] {\n animation: dso-flash 1.8s ease-out;\n}\n@keyframes dso-flash {\n 0% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--dsw-alias-state-business-primary) 50%, transparent);\n background-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 24%, transparent);\n }\n 70% {\n box-shadow: 0 0 0 6px transparent;\n background-color: color-mix(in srgb, var(--dsw-alias-state-business-primary) 8%, transparent);\n }\n 100% {\n box-shadow: 0 0 0 0 transparent;\n background-color: transparent;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dso-panel { animation: none; }\n .dso-bar { transition: none; }\n [data-dsh-outline-flash] { animation: none; }\n}\n";
|
|
13
13
|
/** Inject the style tag, owned by the client fiber (removed on dispose/HMR). */
|
|
14
14
|
export declare function injectStyle(ctx: ClientContext): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chestnut23/dsh-conversation-outline",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Codex-style conversation outline for DeepSeek Harness: a floating panel listing every user question in the current conversation with search, load-older and click-to-jump (scroll + highlight). Bilingual zh-CN / en.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|