@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ash-qw/dsh-theme-prts",
3
- "version": "2.5.4",
3
+ "version": "2.5.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Unofficial Arknights P.R.T.S.-inspired UI theme for DeepSeek Harness Web and the official desktop client",
@@ -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 card=node.closest('[data-presented-file],[data-changed-files]')
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
- let index = 0
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
- // The source ANI ends with a 60-second hold frame. Use the static state instead.
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
- const frame = frames[index++]
166
- show(frame.url)
177
+ show(frames[index].url)
167
178
  if (!handCursor) return
168
- timer = window.setTimeout(next, frame.duration)
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 move(event) {
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 summary = plain.textContent.replace(/\s+/g, ' ').trim().slice(0, 100)
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
- surface.style.maxHeight = 'none'
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 = !fitsVertically(preferredTop) && fitsVertically(above) ? above : preferredTop
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
- summary.textContent = current.contentCache(reply.text).summary
712
- item.append(heading, summary)
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] [data-state]:is([data-state="ongoing"], [data-state="done"], [data-state="warning"], [data-state="error"]) {
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; }
@@ -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