@ash-qw/dsh-theme-prts 2.5.4 → 2.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/lib/client.js +92 -15
- package/package.json +1 -1
- package/src/client/conversation-control-adapter.js +6 -1
- package/src/client/cursor-adapter.js +37 -6
- package/src/client/preview-markdown.js +5 -2
- package/src/client/session-summary-preview.js +49 -5
- package/src/styles/facility-vector.css +11 -2
- package/src/styles/prts.css +1 -1
package/package.json
CHANGED
|
@@ -23,6 +23,8 @@ function isPendingInteractionAction(button) {
|
|
|
23
23
|
function classify(button, operation) {
|
|
24
24
|
// The host's process disclosure owns its transparent, full-width hit area.
|
|
25
25
|
if (button.hasAttribute('data-turn-process')) return null
|
|
26
|
+
// Schedule cards use an absolute transparent button as the whole-card hit area.
|
|
27
|
+
if (button.parentElement?.matches('[data-tool="schedule_create"]') && hasClassSuffix(button, '_cardOpen')) return null
|
|
26
28
|
if (button.querySelector('[data-compaction-disclosure]')) return null
|
|
27
29
|
// Notification headers belong to the card surface, not standalone buttons.
|
|
28
30
|
if (button.parentElement?.matches('[data-turn-trigger]')) return null
|
|
@@ -183,8 +185,11 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
183
185
|
const find=node=>{
|
|
184
186
|
if(activeModal && !activeModal.contains(node))return null
|
|
185
187
|
if (!node?.closest || node.closest('[data-prts-workspace-actions],[data-prts-session-actions]')) return null
|
|
186
|
-
const
|
|
188
|
+
const schedule=node.closest('[data-tool="schedule_create"]')
|
|
189
|
+
const scheduleOpen=schedule && [...schedule.children].find(child=>child.tagName==='BUTTON' && hasClassSuffix(child,'_cardOpen') && !child.matches(':disabled,[aria-disabled="true"]'))
|
|
187
190
|
const button=node.closest('button')
|
|
191
|
+
// The transparent hit layer targets the card; nested actions keep their own frame.
|
|
192
|
+
const card=node.closest('[data-presented-file],[data-changed-files]') || (scheduleOpen && (!button || button===scheduleOpen)?schedule:null)
|
|
188
193
|
const pendingAction=operation.contains(button) && isPendingInteractionAction(button) ? button : null
|
|
189
194
|
const candidate=card || pendingAction || node.closest(selector)
|
|
190
195
|
return candidate && !candidate.matches(':disabled,[aria-disabled="true"]') && !candidate.closest('[inert]') ? candidate : null
|
|
@@ -7,6 +7,7 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
7
7
|
let handPhase
|
|
8
8
|
let gestureAssets = assets
|
|
9
9
|
let textTarget
|
|
10
|
+
let moveFrame, pendingMove
|
|
10
11
|
let lastPoint
|
|
11
12
|
const heroTextSelector = '[data-phase="hero"] [class$="_titleGroup"]'
|
|
12
13
|
const contentSelector = '[data-markdown], [data-slot*="markdown" i], [class*="markdown" i], :is([data-chat-flow-kind="user"], [data-chat-flow-kind="steering"], [data-submission-echo], [data-pending-steering]) [class$="_bubble"]'
|
|
@@ -90,6 +91,7 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
90
91
|
let originalPriority
|
|
91
92
|
let appliedCursor
|
|
92
93
|
let appliedPriority
|
|
94
|
+
let lastHandFrame
|
|
93
95
|
function ownsHandCursor() {
|
|
94
96
|
return handTarget?.style.cursor === appliedCursor
|
|
95
97
|
&& handTarget.style.getPropertyPriority('cursor') === appliedPriority
|
|
@@ -106,6 +108,7 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
106
108
|
}
|
|
107
109
|
handTarget?.removeAttribute('data-prts-hand-active')
|
|
108
110
|
handTarget = undefined
|
|
111
|
+
lastHandFrame = undefined
|
|
109
112
|
}
|
|
110
113
|
// Cursor is inherited. Animating a large container still restyles its subtree.
|
|
111
114
|
// Keep those background hits static, while controls retain native animation.
|
|
@@ -130,7 +133,9 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
130
133
|
}
|
|
131
134
|
function applyHandFrame() {
|
|
132
135
|
if (stopIfHandChanged() || !handTarget) return
|
|
136
|
+
if(lastHandFrame === handCursor && ownsHandCursor())return
|
|
133
137
|
handTarget.style.setProperty('cursor', handCursor)
|
|
138
|
+
lastHandFrame = handCursor
|
|
134
139
|
appliedCursor = handTarget.style.cursor
|
|
135
140
|
appliedPriority = handTarget.style.getPropertyPriority('cursor')
|
|
136
141
|
}
|
|
@@ -156,20 +161,27 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
156
161
|
}
|
|
157
162
|
function play(frames, done) {
|
|
158
163
|
clearTimer()
|
|
159
|
-
|
|
164
|
+
const started = window.performance.now()
|
|
165
|
+
let index = 0, deadline = frames[0]?.duration ?? 0
|
|
160
166
|
function next() {
|
|
161
167
|
timer = undefined
|
|
162
168
|
if (stopIfHandChanged()) return
|
|
163
|
-
|
|
169
|
+
const elapsed = window.performance.now() - started
|
|
170
|
+
// Skip expired frames after a busy main thread; never replay a backlog.
|
|
171
|
+
while (index < frames.length - 1 && elapsed >= deadline) {
|
|
172
|
+
index += 1
|
|
173
|
+
deadline += frames[index]?.duration ?? 0
|
|
174
|
+
}
|
|
175
|
+
// The source ANI ends with a 60-second hold frame, which is not animated.
|
|
164
176
|
if (index >= frames.length - 1) { done(); return }
|
|
165
|
-
|
|
166
|
-
show(frame.url)
|
|
177
|
+
show(frames[index].url)
|
|
167
178
|
if (!handCursor) return
|
|
168
|
-
timer = window.setTimeout(next,
|
|
179
|
+
timer = window.setTimeout(next, Math.max(1, deadline - (window.performance.now() - started)))
|
|
169
180
|
}
|
|
170
181
|
next()
|
|
171
182
|
}
|
|
172
183
|
function down(event) {
|
|
184
|
+
clearPendingMove()
|
|
173
185
|
if (event.pointerType !== 'mouse' || event.button !== 0 || event.isPrimary === false) return
|
|
174
186
|
updateResize(event)
|
|
175
187
|
updateTextHit(event)
|
|
@@ -203,13 +215,32 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
203
215
|
if (handPhase === 'press') return
|
|
204
216
|
releaseHand()
|
|
205
217
|
}
|
|
206
|
-
function
|
|
218
|
+
function applyMove(event) {
|
|
207
219
|
updateResize(event)
|
|
208
220
|
updateTextHit(event)
|
|
209
221
|
if (handCursor) followHand(event.target?.nodeType === 1 ? event.target : event.target?.parentElement)
|
|
210
222
|
if (pointerId !== undefined && event.pointerId === pointerId && !(event.buttons & 1)) reset()
|
|
211
223
|
}
|
|
224
|
+
function clearPendingMove() {
|
|
225
|
+
if (moveFrame !== undefined) window.cancelAnimationFrame?.(moveFrame)
|
|
226
|
+
moveFrame = undefined
|
|
227
|
+
pendingMove = undefined
|
|
228
|
+
}
|
|
229
|
+
function move(event) {
|
|
230
|
+
if (event.pointerType !== 'mouse') return
|
|
231
|
+
// Lost releases must clear synchronously even when the next paint is delayed.
|
|
232
|
+
if (pointerId !== undefined && event.pointerId === pointerId && !(event.buttons & 1)) reset()
|
|
233
|
+
if (!window.requestAnimationFrame) { applyMove(event); return }
|
|
234
|
+
pendingMove = { target:event.target, pointerType:event.pointerType, pointerId:event.pointerId,
|
|
235
|
+
buttons:event.buttons, clientX:event.clientX, clientY:event.clientY }
|
|
236
|
+
if (moveFrame === undefined) moveFrame = window.requestAnimationFrame(() => {
|
|
237
|
+
const latest = pendingMove
|
|
238
|
+
clearPendingMove()
|
|
239
|
+
if (latest?.target?.isConnected) applyMove(latest)
|
|
240
|
+
})
|
|
241
|
+
}
|
|
212
242
|
function resetInteraction() {
|
|
243
|
+
clearPendingMove()
|
|
213
244
|
reset()
|
|
214
245
|
clearResize()
|
|
215
246
|
clearTextHit()
|
|
@@ -27,9 +27,12 @@ export function createPreviewContentCache(document, render = renderPreviewMarkdo
|
|
|
27
27
|
const html = render(text)
|
|
28
28
|
const plain = document.createElement('div')
|
|
29
29
|
plain.innerHTML = html
|
|
30
|
-
const
|
|
30
|
+
const fullSummary = plain.textContent.replace(/\s+/g, ' ').trim()
|
|
31
|
+
const summary = fullSummary.slice(0, 100)
|
|
32
|
+
const truncated = fullSummary.length > summary.length
|
|
33
|
+
const needsDetail = truncated || Boolean(plain.querySelector('a,pre,ul,ol,table,blockquote,h1,h2,h3,h4,h5,h6,br') || plain.children.length > 1)
|
|
31
34
|
const cost = text.length + html.length + summary.length
|
|
32
|
-
const value = {html, summary, cost}
|
|
35
|
+
const value = {html, summary, cost, needsDetail, truncated}
|
|
33
36
|
if (cost <= 262144) {
|
|
34
37
|
while (entries.size && (entries.size >= 32 || size + cost > 262144)) {
|
|
35
38
|
const key = entries.keys().next().value
|
|
@@ -293,12 +293,29 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
293
293
|
const height = Math.max(1, (viewport?.height ?? window.innerHeight) - 16)
|
|
294
294
|
const place = (surface, reference, preferredTop, noticeOwner, anchorBottom = preferredTop) => {
|
|
295
295
|
surface.style.maxWidth = 'none'
|
|
296
|
-
|
|
296
|
+
let fixedEdge
|
|
297
|
+
if (surface === panel) {
|
|
298
|
+
// Pick a direction once, before async replies arrive; keep the same edge
|
|
299
|
+
// while content grows and let the existing scroll surface absorb overflow.
|
|
300
|
+
if (!current.placement) {
|
|
301
|
+
const below = top + height - preferredTop
|
|
302
|
+
const above = anchorBottom - top
|
|
303
|
+
current.placement = below < 160 && above > below
|
|
304
|
+
? { direction: 'above', edge: anchorBottom }
|
|
305
|
+
: { direction: 'below', edge: preferredTop }
|
|
306
|
+
}
|
|
307
|
+
fixedEdge = Math.max(top, Math.min(current.placement.edge, top + height))
|
|
308
|
+
const available = current.placement.direction === 'above' ? fixedEdge - top : top + height - fixedEdge
|
|
309
|
+
surface.style.maxHeight = Math.max(1, available) + 'px'
|
|
310
|
+
surface.setAttribute('data-prts-preview-direction', current.placement.direction)
|
|
311
|
+
} else surface.style.maxHeight = 'none'
|
|
297
312
|
const bounds = surface.getBoundingClientRect()
|
|
298
313
|
const x = reference ? reference.right + 10 : left
|
|
299
|
-
const fitsVertically = y => y >= top && y + bounds.height <= top + height
|
|
314
|
+
const fitsVertically = y => y >= top - 1 && y + bounds.height <= top + height + 1
|
|
300
315
|
const above = anchorBottom - bounds.height
|
|
301
|
-
const y =
|
|
316
|
+
const y = surface === panel
|
|
317
|
+
? current.placement.direction === 'above' ? fixedEdge - bounds.height : fixedEdge
|
|
318
|
+
: !fitsVertically(preferredTop) && fitsVertically(above) ? above : preferredTop
|
|
302
319
|
const insufficient = Boolean(reference) && (x < left || x + bounds.width > left + width || !fitsVertically(y))
|
|
303
320
|
surface.toggleAttribute('data-prts-preview-no-space', insufficient)
|
|
304
321
|
const noticeKey = surface === panel ? 'spaceNotice' : 'detailSpaceNotice'
|
|
@@ -319,6 +336,7 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
319
336
|
surface.style.top = y + 'px'
|
|
320
337
|
return !insufficient
|
|
321
338
|
}
|
|
339
|
+
updateReplyAvailability()
|
|
322
340
|
const menu = current.anchor?.closest('[data-prts-session-summary-popover]')
|
|
323
341
|
const reference = menu?.getBoundingClientRect()
|
|
324
342
|
const row = current.anchor?.getBoundingClientRect()
|
|
@@ -343,6 +361,8 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
343
361
|
current.spaceNotice?.remove()
|
|
344
362
|
current.detailSpaceNotice?.remove()
|
|
345
363
|
panel.removeAttribute('data-prts-preview-no-space')
|
|
364
|
+
panel.removeAttribute('data-prts-preview-direction')
|
|
365
|
+
panel.style.removeProperty('max-height')
|
|
346
366
|
positionObserver?.disconnect()
|
|
347
367
|
window.cancelAnimationFrame(positionFrame)
|
|
348
368
|
window.removeEventListener('resize', schedulePosition)
|
|
@@ -537,8 +557,24 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
537
557
|
detailEntry?.removeAttribute('data-prts-reply-active')
|
|
538
558
|
detailEntry = undefined
|
|
539
559
|
}
|
|
560
|
+
const replyIsTruncated = item => {
|
|
561
|
+
const summary = item.querySelector('span')
|
|
562
|
+
return item.dataset.prtsSummaryTruncated === 'true' || summary.scrollHeight > summary.clientHeight + 1 || summary.scrollWidth > summary.clientWidth + 1
|
|
563
|
+
}
|
|
564
|
+
const updateReplyAvailability = () => {
|
|
565
|
+
const entries = [...body.querySelectorAll('[data-prts-reply-entry]')].map(item => [item, replyIsTruncated(item)])
|
|
566
|
+
for (const [item, truncated] of entries) {
|
|
567
|
+
const needed = truncated || item.dataset.prtsReplyTruncated === 'true'
|
|
568
|
+
item.toggleAttribute('data-prts-reply-overflow', truncated)
|
|
569
|
+
item.toggleAttribute('data-prts-reply-complete', !needed)
|
|
570
|
+
item.tabIndex = needed ? 0 : -1
|
|
571
|
+
item.setAttribute('aria-disabled', String(!needed))
|
|
572
|
+
if (!needed && detailEntry === item) hideDetail()
|
|
573
|
+
}
|
|
574
|
+
}
|
|
540
575
|
const showDetail = (reply, index, item, refresh = false) => {
|
|
541
576
|
keep()
|
|
577
|
+
if (!reply.tool && item.hasAttribute('data-prts-reply-complete')) { hideDetail(); return }
|
|
542
578
|
if (!refresh && detailKey === reply.key && current.detail && !current.detail.hidden) return
|
|
543
579
|
if (!current.detail) {
|
|
544
580
|
const detail = panel.ownerDocument.createElement('section')
|
|
@@ -708,8 +744,15 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
708
744
|
icon.append(outline)
|
|
709
745
|
heading.prepend(icon)
|
|
710
746
|
const summary = panel.ownerDocument.createElement('span')
|
|
711
|
-
|
|
712
|
-
|
|
747
|
+
const content = current.contentCache(reply.text)
|
|
748
|
+
summary.textContent = content.summary
|
|
749
|
+
item.dataset.prtsReplyTruncated = String(content.needsDetail)
|
|
750
|
+
item.dataset.prtsSummaryTruncated = String(content.truncated)
|
|
751
|
+
const ellipsis = panel.ownerDocument.createElement('i')
|
|
752
|
+
ellipsis.setAttribute('data-prts-reply-ellipsis', '')
|
|
753
|
+
ellipsis.setAttribute('aria-hidden', 'true')
|
|
754
|
+
ellipsis.textContent = '…'
|
|
755
|
+
item.append(heading, summary, ellipsis)
|
|
713
756
|
item.addEventListener('pointermove', () => showDetail(reply, index, item))
|
|
714
757
|
item.addEventListener('focus', () => showDetail(reply, index, item))
|
|
715
758
|
item.addEventListener('click', () => showDetail(reply, index, item))
|
|
@@ -725,6 +768,7 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
725
768
|
if (scrollAnchor?.isConnected) body.scrollTop += scrollAnchor.getBoundingClientRect().top - anchorTop
|
|
726
769
|
else if (previousTop === 0) body.scrollTop = 0
|
|
727
770
|
historyKeys = replies
|
|
771
|
+
schedulePosition()
|
|
728
772
|
previousText = undefined
|
|
729
773
|
previousQuestion = undefined
|
|
730
774
|
return
|
|
@@ -48,7 +48,7 @@ html[data-dsh-prts] [data-prts-session-idle-indicator] {
|
|
|
48
48
|
fill: var(--prts-dim);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
html[data-dsh-prts] [data-prts-session-state] [data-prts-session-idle-indicator] {
|
|
51
|
+
html[data-dsh-prts] :is([data-prts-session-state], [data-prts-session-row]:has([data-session-schedule-mark])) [data-prts-session-idle-indicator] {
|
|
52
52
|
opacity: 0;
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -824,7 +824,7 @@ html[data-dsh-prts] [data-prts-session-summary-popover]:has(> [data-prts-preview
|
|
|
824
824
|
}
|
|
825
825
|
|
|
826
826
|
/* Keep different-sized state markers centered on the same axis. */
|
|
827
|
-
html[data-dsh-prts] [data-prts-session-row]
|
|
827
|
+
html[data-dsh-prts] [data-prts-session-row] :is([data-state="ongoing"], [data-state="done"], [data-state="warning"], [data-state="error"], [data-session-schedule-mark]) {
|
|
828
828
|
flex-shrink: 0;
|
|
829
829
|
margin-left: 20px;
|
|
830
830
|
}
|
|
@@ -889,3 +889,12 @@ html[data-dsh-prts] [data-prts-tool-record] p { display:-webkit-box;-webkit-box-
|
|
|
889
889
|
|
|
890
890
|
/* Show the baseline immediately; only start the pickup after a deliberate hover. */
|
|
891
891
|
html[data-dsh-prts] [data-prts-session-row]:not([data-prts-pickup-ready]) :is([data-prts-session-pickup-bar],[data-prts-session-pickup-indicator]) { visibility:hidden; }
|
|
892
|
+
|
|
893
|
+
html[data-dsh-prts] [data-prts-reply-entry][data-prts-reply-complete] { cursor:text; }
|
|
894
|
+
|
|
895
|
+
/* Reserve the cue's width so showing it never changes the truncation measurement. */
|
|
896
|
+
html[data-dsh-prts] [data-prts-reply-entry] > span { display:block;-webkit-line-clamp:unset;max-height:3.2em;color:#b8bec6; }
|
|
897
|
+
html[data-dsh-prts] [data-prts-reply-ellipsis] { flex:0 0 12px;align-self:flex-end;color:#f0e6d6;font:600 14px/1.4 var(--prts-body);visibility:hidden; }
|
|
898
|
+
html[data-dsh-prts] [data-prts-reply-overflow] > [data-prts-reply-ellipsis] { visibility:visible; }
|
|
899
|
+
|
|
900
|
+
html[data-dsh-prts] [data-prts-reply-overflow] > span { color:#f0e6d6; }
|
package/src/styles/prts.css
CHANGED
|
@@ -963,7 +963,7 @@ html[data-dsh-prts] [data-prts-row-title] {
|
|
|
963
963
|
white-space: nowrap;
|
|
964
964
|
}
|
|
965
965
|
|
|
966
|
-
html[data-dsh-prts] [data-prts-session-row] > span:has(> [data-state]) {
|
|
966
|
+
html[data-dsh-prts] [data-prts-session-row] > span:has(> :is([data-state], [data-session-schedule-mark])) {
|
|
967
967
|
padding-left: 0 !important;
|
|
968
968
|
}
|
|
969
969
|
|