@ash-qw/dsh-theme-prts 0.1.100 → 0.1.101

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": "0.1.100",
3
+ "version": "0.1.101",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Unofficial Arknights P.R.T.S.-inspired UI theme for DeepSeek Harness Web",
@@ -7,6 +7,9 @@ export const FACILITY_GEOMETRY = Object.freeze({
7
7
  topOpening: 50,
8
8
  topInner: 40,
9
9
  topDepth: 10 / 3,
10
+ sessionTopOpening: 50,
11
+ sessionTopInner: 36.5,
12
+ sessionTopDepth: 4.5,
10
13
  })
11
14
 
12
15
  function number(value) {
@@ -17,7 +20,7 @@ function point(x, y) {
17
20
  return `${number(x)} ${number(y)}`
18
21
  }
19
22
 
20
- export function createFacilityPath({ width, height, topNotch = false } = {}) {
23
+ export function createFacilityPath({ width, height, topNotch = false, sessionNotch = false } = {}) {
21
24
  if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 2 || height <= FACILITY_GEOMETRY.sideOpening + 2) return ''
22
25
 
23
26
  const halfStroke = FACILITY_GEOMETRY.stroke / 2
@@ -29,12 +32,15 @@ export function createFacilityPath({ width, height, topNotch = false } = {}) {
29
32
  const centerY = height / 2
30
33
  const points = [point(left + FACILITY_GEOMETRY.corner, top)]
31
34
 
32
- if (topNotch && width >= FACILITY_GEOMETRY.topOpening + FACILITY_GEOMETRY.corner * 2 + FACILITY_GEOMETRY.stroke) {
35
+ const topOpening = sessionNotch ? FACILITY_GEOMETRY.sessionTopOpening : FACILITY_GEOMETRY.topOpening
36
+ const topInner = sessionNotch ? FACILITY_GEOMETRY.sessionTopInner : FACILITY_GEOMETRY.topInner
37
+ const topDepth = sessionNotch ? FACILITY_GEOMETRY.sessionTopDepth : FACILITY_GEOMETRY.topDepth
38
+ if (topNotch && width >= topOpening + FACILITY_GEOMETRY.corner * 2 + FACILITY_GEOMETRY.stroke) {
33
39
  points.push(
34
- point(centerX - FACILITY_GEOMETRY.topOpening / 2, top),
35
- point(centerX - FACILITY_GEOMETRY.topInner / 2, top + FACILITY_GEOMETRY.topDepth),
36
- point(centerX + FACILITY_GEOMETRY.topInner / 2, top + FACILITY_GEOMETRY.topDepth),
37
- point(centerX + FACILITY_GEOMETRY.topOpening / 2, top),
40
+ point(centerX - topOpening / 2, top),
41
+ point(centerX - topInner / 2, top + topDepth),
42
+ point(centerX + topInner / 2, top + topDepth),
43
+ point(centerX + topOpening / 2, top),
38
44
  )
39
45
  }
40
46
 
@@ -1,4 +1,4 @@
1
- import { createFacilityPath } from './facility-geometry.js'
1
+ import { createFacilityPath, FACILITY_GEOMETRY } from './facility-geometry.js'
2
2
  import { configureSessionLifeline } from './session-lifeline.js'
3
3
 
4
4
  const SVG_NS = 'http://www.w3.org/2000/svg'
@@ -167,6 +167,28 @@ function appendSessionPickup(document, graphic) {
167
167
  graphic.insertBefore(pickup, graphic.querySelector('[data-prts-facility-layer="outline"]'))
168
168
  }
169
169
 
170
+ function appendSessionStatusUnderlay(document, graphic) {
171
+ const surface = graphic.querySelector('[data-prts-facility-layer="surface"]')
172
+ if (!surface) return
173
+ if (!graphic.querySelector('[data-prts-session-status-underlay]')) {
174
+ graphic.insertBefore(svgNode(document, 'path', {
175
+ 'data-prts-session-status-underlay': '',
176
+ 'aria-hidden': 'true',
177
+ transform: 'translate(3 2)',
178
+ 'shape-rendering': 'geometricPrecision',
179
+ }), surface)
180
+ }
181
+ if (!graphic.querySelector('[data-prts-session-idle-indicator]')) {
182
+ graphic.insertBefore(svgNode(document, 'rect', {
183
+ 'data-prts-session-idle-indicator': '',
184
+ 'aria-hidden': 'true',
185
+ y: '2.5',
186
+ width: '50',
187
+ height: '2.5',
188
+ }), surface)
189
+ }
190
+ }
191
+
170
192
  function clamp(value, min, max) {
171
193
  return Math.min(max, Math.max(min, value))
172
194
  }
@@ -179,7 +201,7 @@ function sessionLifelineGeometry(width, height) {
179
201
  return { start, end, baseline, lift }
180
202
  }
181
203
 
182
- export function ensureFacilityGraphic(document, owner, { kind, topNotch = false, texture = 'none' } = {}) {
204
+ export function ensureFacilityGraphic(document, owner, { kind, topNotch = false, sessionNotch = false, texture = 'none' } = {}) {
183
205
  if (!owner) return null
184
206
  let graphic = owner.querySelector?.(':scope > svg[data-prts-facility-svg]')
185
207
  if (!graphic) {
@@ -205,9 +227,13 @@ export function ensureFacilityGraphic(document, owner, { kind, topNotch = false,
205
227
  owner.insertBefore(graphic, owner.firstChild)
206
228
  }
207
229
  if (texture === 'silhouette' && !graphic.querySelector('[data-prts-facility-silhouette]')) appendSilhouetteScene(document, graphic)
208
- if (texture === 'pickup' && !graphic.querySelector('[data-prts-session-pickup]')) appendSessionPickup(document, graphic)
230
+ if (texture === 'pickup') {
231
+ appendSessionStatusUnderlay(document, graphic)
232
+ if (!graphic.querySelector('[data-prts-session-pickup]')) appendSessionPickup(document, graphic)
233
+ }
209
234
  graphic.setAttribute('data-prts-facility-svg', kind)
210
235
  graphic.toggleAttribute('data-prts-top-notch', Boolean(topNotch))
236
+ graphic.toggleAttribute('data-prts-session-notch', Boolean(sessionNotch))
211
237
  if (!owner.hasAttribute('data-prts-facility-texture')) owner.setAttribute('data-prts-facility-texture', texture)
212
238
  return graphic
213
239
  }
@@ -222,19 +248,24 @@ function setReady(owner, ready) {
222
248
  export function renderFacilityGraphic(owner, { width, height } = {}) {
223
249
  const graphic = owner?.querySelector?.(':scope > svg[data-prts-facility-svg]')
224
250
  const topNotch = graphic?.hasAttribute('data-prts-top-notch')
225
- const path = createFacilityPath({ width, height, topNotch })
251
+ const sessionNotch = graphic?.hasAttribute('data-prts-session-notch')
252
+ const path = createFacilityPath({ width, height, topNotch, sessionNotch })
226
253
  if (!graphic || !path) {
227
254
  owner?.removeAttribute?.('data-prts-facility-size')
228
255
  setReady(owner, false)
229
256
  return false
230
257
  }
231
258
 
232
- const size = `${Number(width.toFixed(3))}x${Number(height.toFixed(3))}`
259
+ const size = `${Number(width.toFixed(3))}x${Number(height.toFixed(3))}:${topNotch ? 't' : '-'}${sessionNotch ? 's' : '-'}`
233
260
  const lifeline = graphic.querySelector('[data-prts-session-lifeline]')
234
261
  const lifelineGeometry = lifeline ? sessionLifelineGeometry(width, height) : undefined
235
262
  if (owner.getAttribute('data-prts-facility-size') !== size) {
236
263
  graphic.setAttribute('viewBox', `0 0 ${Number(width.toFixed(3))} ${Number(height.toFixed(3))}`)
237
264
  for (const layer of graphic.querySelectorAll('[data-prts-facility-layer], [data-prts-facility-clip]')) layer.setAttribute('d', path)
265
+ const statusUnderlay = graphic.querySelector('[data-prts-session-status-underlay]')
266
+ statusUnderlay?.setAttribute('d', createFacilityPath({ width, height }))
267
+ const idleIndicator = graphic.querySelector('[data-prts-session-idle-indicator]')
268
+ idleIndicator?.setAttribute('x', String(Number((width / 2 - 25).toFixed(3))))
238
269
  const silhouette = graphic.querySelector('[data-prts-facility-silhouette]')
239
270
  if (silhouette) {
240
271
  const availableHeight = Math.max(1, height - 2)
@@ -86,7 +86,12 @@ export function applyPrtsPlugin(ctx, environment) {
86
86
  const toBottom = createToBottomAdapter({ document, window })
87
87
  const conversationControls = createConversationControlAdapter({ document, window })
88
88
  const layoutDiagnostics = createLayoutStabilityDiagnostics({ document, window })
89
- const sidebarControls = createSidebarControlAdapter({ document, window })
89
+ const sidebarControls = createSidebarControlAdapter({
90
+ document,
91
+ window,
92
+ sessions: contextService(ctx, 'sessions'),
93
+ workspaces: contextService(ctx, 'workspaces'),
94
+ })
90
95
  const floatingGlass = createFloatingGlassAdapter({ document, window })
91
96
  const adapter = createRc7Adapter({ document })
92
97
  const hostGeometry = createHostGeometryAdapter({ document, window })
@@ -17,6 +17,9 @@ const OWNED_ATTRIBUTES = [
17
17
  'data-prts-session-menu',
18
18
  'data-prts-session-time',
19
19
  'data-prts-session-index',
20
+ 'data-prts-session-state',
21
+ 'data-prts-session-summary-header',
22
+ 'data-prts-session-summary-label',
20
23
  'data-prts-facility-face',
21
24
  'data-prts-facility-spine',
22
25
  'data-prts-facility-vector',
@@ -25,6 +28,54 @@ const OWNED_ATTRIBUTES = [
25
28
  'data-prts-spine-vector',
26
29
  ]
27
30
 
31
+ const SESSION_STATES = Object.freeze(['warning', 'ongoing', 'error', 'done'])
32
+
33
+ function snapshotOf(source) {
34
+ try {
35
+ return source?.getSnapshot?.()
36
+ } catch {
37
+ return undefined
38
+ }
39
+ }
40
+
41
+ function nativeSessionState(row) {
42
+ for (const state of SESSION_STATES) {
43
+ if (row.querySelector(`[data-state="${state}"]:not([data-prts-owned-session-status])`)) return state
44
+ }
45
+ return undefined
46
+ }
47
+
48
+ function resolveSectionHeader(root) {
49
+ const tree = root?.querySelector?.('[role="tree"]')
50
+ if (!tree) return {}
51
+ for (const candidate of root.querySelectorAll('div')) {
52
+ if (candidate.contains(tree)) continue
53
+ const label = Array.from(candidate.children).find(child => (
54
+ child.tagName === 'SPAN'
55
+ && child.textContent.trim()
56
+ && !child.querySelector('button, input')
57
+ ))
58
+ if (!label || candidate.querySelectorAll('button').length < 1) continue
59
+ return { header: candidate, label }
60
+ }
61
+ return {}
62
+ }
63
+
64
+ function visibleSessionSummaries(sessions, workspaces) {
65
+ const list = snapshotOf(sessions?.list)
66
+ if (!list?.byId) return []
67
+ const workspaceList = snapshotOf(workspaces?.list)
68
+ const archived = new Set(workspaceList?.archivedSessionIds ?? [])
69
+ const ids = Array.isArray(list.ids) ? list.ids : Object.keys(list.byId)
70
+ return ids.flatMap(id => {
71
+ const summary = list.byId[id]
72
+ if (!summary || summary.blank || summary.origin === 'subagent' || archived.has(id)) return []
73
+ if (summary.running) return [{ id, title: summary.displayTitle || id, state: 'ongoing' }]
74
+ if (summary.completed === true) return [{ id, title: summary.displayTitle || id, state: 'done' }]
75
+ return []
76
+ })
77
+ }
78
+
28
79
  function markSidebarNode(node, attribute, next) {
29
80
  if (!node) return
30
81
  node.setAttribute(attribute, '')
@@ -119,18 +170,134 @@ function ensureFacilitySpine(document, row, kind, next) {
119
170
  }
120
171
 
121
172
 
122
- export function createSidebarControlAdapter({ document, window }) {
173
+ export function createSidebarControlAdapter({ document, window, sessions, workspaces }) {
123
174
  let observer
124
175
  let resizeObserver
125
176
  let resizeFrame
126
177
  let resizeTimer
127
178
  let scanFrame
179
+ let stopSessionList = () => {}
180
+ let stopWorkspaceList = () => {}
181
+ let statusSourcesStarted = false
182
+ let summaryRoot
183
+ let summarySignature = ''
128
184
  let owned = new Set()
129
185
  let vectorOwners = new Set()
130
186
  const dirtyVectors = new Map()
131
187
  const vectorSizes = new WeakMap()
132
188
  const lifelineAnimator = createSessionLifelineAnimator({ document, window })
133
189
 
190
+ function openSession(sessionId) {
191
+ try {
192
+ if (typeof sessions?.open === 'function') {
193
+ sessions.open(sessionId)
194
+ return
195
+ }
196
+ } catch {}
197
+ const row = Array.from(document.querySelectorAll('[data-prts-session-row]')).find(candidate => (
198
+ candidate.querySelector('[data-prts-row-title]')?.textContent.trim() === sessionId
199
+ ))
200
+ row?.click?.()
201
+ }
202
+
203
+ function statusSignal(document, state) {
204
+ const signal = document.createElement('span')
205
+ signal.setAttribute('data-prts-session-summary-signal', state)
206
+ signal.setAttribute('aria-hidden', 'true')
207
+ return signal
208
+ }
209
+
210
+ function buildSummaryGroup(state, label, items) {
211
+ const group = document.createElement('span')
212
+ group.setAttribute('data-prts-session-summary-group', state)
213
+
214
+ const trigger = document.createElement('button')
215
+ trigger.type = 'button'
216
+ trigger.setAttribute('data-prts-session-summary-trigger', state)
217
+ trigger.setAttribute('aria-haspopup', 'menu')
218
+ trigger.setAttribute('aria-label', `${label} ${items.length}`)
219
+ trigger.append(statusSignal(document, state))
220
+ const count = document.createElement('span')
221
+ count.setAttribute('data-prts-session-summary-count', '')
222
+ count.textContent = String(items.length)
223
+ trigger.append(count)
224
+
225
+ const popover = document.createElement('span')
226
+ popover.setAttribute('data-prts-session-summary-popover', state)
227
+ popover.setAttribute('role', 'menu')
228
+ popover.setAttribute('aria-label', label)
229
+ const heading = document.createElement('span')
230
+ heading.setAttribute('data-prts-session-summary-heading', '')
231
+ const headingLabel = document.createElement('strong')
232
+ headingLabel.textContent = label
233
+ const hint = document.createElement('small')
234
+ hint.textContent = '选择会话'
235
+ heading.append(headingLabel, hint)
236
+ popover.append(heading)
237
+ for (const item of items) {
238
+ const button = document.createElement('button')
239
+ button.type = 'button'
240
+ button.setAttribute('role', 'menuitem')
241
+ button.setAttribute('data-prts-session-summary-item', item.id)
242
+ button.append(statusSignal(document, state))
243
+ const title = document.createElement('span')
244
+ title.textContent = item.title
245
+ button.append(title)
246
+ button.addEventListener('click', event => {
247
+ event.stopPropagation()
248
+ openSession(item.id)
249
+ })
250
+ popover.append(button)
251
+ }
252
+ group.append(trigger, popover)
253
+ return group
254
+ }
255
+
256
+ function syncSummary(root, next) {
257
+ const { header, label } = resolveSectionHeader(root)
258
+ if (!header || !label) {
259
+ summaryRoot?.remove()
260
+ summaryRoot = undefined
261
+ summarySignature = ''
262
+ return
263
+ }
264
+ header.setAttribute('data-prts-session-summary-header', '')
265
+ label.setAttribute('data-prts-session-summary-label', '')
266
+ next.add(header)
267
+ next.add(label)
268
+ const fromService = visibleSessionSummaries(sessions, workspaces)
269
+ const rows = fromService.length ? fromService : Array.from(root.querySelectorAll('[data-prts-session-row]')).flatMap(row => {
270
+ const state = row.getAttribute('data-prts-session-state')
271
+ if (state !== 'ongoing' && state !== 'done') return []
272
+ const title = row.querySelector('[data-prts-row-title]')?.textContent.trim()
273
+ return title ? [{ id: title, title, state }] : []
274
+ })
275
+ const ongoing = rows.filter(item => item.state === 'ongoing')
276
+ const done = rows.filter(item => item.state === 'done')
277
+ const signature = JSON.stringify({
278
+ ongoing: ongoing.map(item => [item.id, item.title]),
279
+ done: done.map(item => [item.id, item.title]),
280
+ })
281
+ if (!summaryRoot) {
282
+ summaryRoot = document.createElement('span')
283
+ summaryRoot.setAttribute('data-prts-owned-session-summary', '')
284
+ summaryRoot.setAttribute('data-prts-session-summary', '')
285
+ summaryRoot.setAttribute('role', 'group')
286
+ summaryRoot.setAttribute('aria-label', '会话状态总览')
287
+ label.insertAdjacentElement('afterend', summaryRoot)
288
+ } else if (summaryRoot.parentElement !== header) {
289
+ label.insertAdjacentElement('afterend', summaryRoot)
290
+ }
291
+ const labelHidden = Array.from(label.classList).some(name => /hidden/i.test(name))
292
+ summaryRoot.toggleAttribute('data-prts-summary-search-hidden', labelHidden)
293
+ summaryRoot.hidden = ongoing.length === 0 && done.length === 0
294
+ if (signature === summarySignature) return
295
+ summarySignature = signature
296
+ summaryRoot.replaceChildren()
297
+ if (ongoing.length) summaryRoot.append(buildSummaryGroup('ongoing', '运行中的会话', ongoing))
298
+ if (done.length) summaryRoot.append(buildSummaryGroup('done', '完成未查看', done))
299
+ }
300
+
134
301
  function scheduleVectors() {
135
302
  if (resizeFrame !== undefined) return
136
303
  const requestFrame = window?.requestAnimationFrame?.bind(window) ?? (callback => window?.setTimeout?.(callback, 0))
@@ -161,12 +328,17 @@ export function createSidebarControlAdapter({ document, window }) {
161
328
 
162
329
  function registerVector(owner, options, next, nextVectorOwners) {
163
330
  if (!owner) return
331
+ const previousSessionNotch = owner.querySelector?.(':scope > svg[data-prts-facility-svg]')
332
+ ?.hasAttribute('data-prts-session-notch') ?? false
164
333
  const graphic = ensureFacilityGraphic(document, owner, options)
165
334
  if (!graphic) return
166
335
  next.add(graphic)
167
336
  nextVectorOwners.add(owner)
168
337
  if (!vectorOwners.has(owner)) resizeObserver?.observe(owner)
169
- if (!vectorSizes.has(owner)) dirtyVectors.set(owner, undefined)
338
+ if (previousSessionNotch !== Boolean(options.sessionNotch)) vectorSizes.delete(owner)
339
+ if (!vectorSizes.has(owner)) {
340
+ dirtyVectors.set(owner, undefined)
341
+ }
170
342
  }
171
343
 
172
344
  function syncVectorOwners(next) {
@@ -222,15 +394,22 @@ export function createSidebarControlAdapter({ document, window }) {
222
394
  for (const row of root.querySelectorAll('div[role="treeitem"][aria-selected]')) {
223
395
  const sessionIndex = String(++sessionOrdinal).padStart(2, '0')
224
396
  const controls = resolveRowControls(row, 'session')
397
+ const state = nativeSessionState(row)
225
398
  const face = ensureFacilityFace(document, row, 'session', next)
226
399
  const spine = ensureFacilitySpine(document, row, 'session', next)
227
- registerVector(face, { kind: 'face', topNotch: true, texture: 'pickup' }, next, nextVectorOwners)
400
+ registerVector(face, { kind: 'face', topNotch: true, sessionNotch: true, texture: 'pickup' }, next, nextVectorOwners)
228
401
  registerVector(spine, { kind: 'spine' }, next, nextVectorOwners)
229
402
  if (!activeLifelineRow && row.getAttribute('aria-selected') === 'true') {
230
403
  activeLifelineRow = row
231
404
  activeLifeline = face.querySelector('[data-prts-session-lifeline]')
232
405
  }
233
406
  markSidebarNode(row, 'data-prts-session-row', next)
407
+ if (state) {
408
+ row.setAttribute('data-prts-session-state', state)
409
+ next.add(row)
410
+ } else {
411
+ row.removeAttribute('data-prts-session-state')
412
+ }
234
413
  row.dataset.prtsSessionIndex = sessionIndex
235
414
  const title = resolveRowTitle(row, 'session', controls.actions)
236
415
  markSidebarNode(title, 'data-prts-row-title', next)
@@ -243,6 +422,11 @@ export function createSidebarControlAdapter({ document, window }) {
243
422
  markSidebarNode(controls.menu, 'data-prts-session-menu', next)
244
423
  registerVector(controls.menu, { kind: 'button' }, next, nextVectorOwners)
245
424
  }
425
+ syncSummary(root, next)
426
+ } else {
427
+ summaryRoot?.remove()
428
+ summaryRoot = undefined
429
+ summarySignature = ''
246
430
  }
247
431
  lifelineAnimator.setTarget(activeLifelineRow, activeLifeline)
248
432
  for (const node of owned) {
@@ -272,6 +456,9 @@ export function createSidebarControlAdapter({ document, window }) {
272
456
  vectorOwners.clear()
273
457
  dirtyVectors.clear()
274
458
  owned.clear()
459
+ summaryRoot?.remove()
460
+ summaryRoot = undefined
461
+ summarySignature = ''
275
462
  }
276
463
 
277
464
  return {
@@ -294,6 +481,11 @@ export function createSidebarControlAdapter({ document, window }) {
294
481
  })
295
482
  }
296
483
  scan()
484
+ if (!statusSourcesStarted) {
485
+ statusSourcesStarted = true
486
+ stopSessionList = sessions?.list?.subscribe?.(queueScan) ?? (() => {})
487
+ stopWorkspaceList = workspaces?.list?.subscribe?.(queueScan) ?? (() => {})
488
+ }
297
489
  if (observer || !window?.MutationObserver) return
298
490
  const observationRoot = document.querySelector('[data-prts-region="sessions"]')
299
491
  if (!observationRoot) return
@@ -302,12 +494,17 @@ export function createSidebarControlAdapter({ document, window }) {
302
494
  subtree: true,
303
495
  childList: true,
304
496
  attributes: true,
305
- attributeFilter: ['aria-expanded', 'aria-selected', 'class'],
497
+ attributeFilter: ['aria-expanded', 'aria-selected', 'class', 'data-state'],
306
498
  })
307
499
  },
308
500
  dispose() {
309
501
  observer?.disconnect()
310
502
  observer = undefined
503
+ stopSessionList()
504
+ stopSessionList = () => {}
505
+ stopWorkspaceList()
506
+ stopWorkspaceList = () => {}
507
+ statusSourcesStarted = false
311
508
  if (scanFrame !== undefined) {
312
509
  if (window?.cancelAnimationFrame) window.cancelAnimationFrame(scanFrame)
313
510
  else window?.clearTimeout?.(scanFrame)
@@ -17,6 +17,241 @@ html[data-dsh-prts] [data-prts-facility-svg] {
17
17
  pointer-events: none;
18
18
  }
19
19
 
20
+ /* A pure status-colored card underlay sits behind the stable face. Its small
21
+ right-down offset is visible through the top notch and along two outer edges. */
22
+ html[data-dsh-prts] [data-prts-session-status-underlay] {
23
+ fill: var(--prts-session-status-color, transparent);
24
+ opacity: 0;
25
+ }
26
+
27
+ html[data-dsh-prts] [data-prts-session-state="ongoing"] {
28
+ --prts-session-status-color: var(--dsw-static-deepseek-450, var(--prts-cyan));
29
+ }
30
+
31
+ html[data-dsh-prts] [data-prts-session-state="done"] {
32
+ --prts-session-status-color: var(--dsw-alias-state-success-primary, var(--prts-green));
33
+ }
34
+
35
+ html[data-dsh-prts] [data-prts-session-state="warning"] {
36
+ --prts-session-status-color: var(--dsw-alias-state-warn-primary, var(--prts-yellow));
37
+ }
38
+
39
+ html[data-dsh-prts] [data-prts-session-state="error"] {
40
+ --prts-session-status-color: var(--dsw-alias-state-error-primary, var(--prts-red));
41
+ }
42
+
43
+ html[data-dsh-prts] [data-prts-session-state] [data-prts-session-status-underlay] {
44
+ opacity: 1;
45
+ }
46
+
47
+ html[data-dsh-prts] [data-prts-session-idle-indicator] {
48
+ fill: var(--prts-dim);
49
+ }
50
+
51
+ html[data-dsh-prts] [data-prts-session-state] [data-prts-session-idle-indicator] {
52
+ opacity: 0;
53
+ }
54
+
55
+ /* Workspace status summary. */
56
+ html[data-dsh-prts] [data-prts-session-summary-header] {
57
+ position: relative;
58
+ z-index: 18;
59
+ overflow: visible !important;
60
+ }
61
+
62
+ html[data-dsh-prts] [data-prts-session-summary-label] {
63
+ margin-right: 2px;
64
+ }
65
+
66
+ html[data-dsh-prts] [data-prts-session-summary] {
67
+ position: static;
68
+ z-index: 19;
69
+ display: inline-flex;
70
+ flex: 0 0 auto;
71
+ align-items: center;
72
+ gap: 3px;
73
+ color: var(--prts-muted);
74
+ font-family: var(--prts-mono);
75
+ }
76
+
77
+ html[data-dsh-prts] [data-prts-session-summary][data-prts-summary-search-hidden] {
78
+ display: none;
79
+ }
80
+
81
+ html[data-dsh-prts] [data-prts-session-summary-group] {
82
+ position: static;
83
+ display: inline-flex;
84
+ }
85
+
86
+ html[data-dsh-prts] [data-prts-session-summary-trigger] {
87
+ min-width: 30px;
88
+ height: 22px;
89
+ display: inline-flex;
90
+ align-items: center;
91
+ justify-content: center;
92
+ gap: 5px;
93
+ padding: 0 6px;
94
+ border: 1px solid color-mix(in srgb, currentColor 38%, transparent);
95
+ border-radius: 0;
96
+ background: color-mix(in srgb, currentColor 7%, transparent);
97
+ color: inherit;
98
+ font: 700 9px/1 var(--prts-mono);
99
+ cursor: pointer;
100
+ }
101
+
102
+ html[data-dsh-prts] [data-prts-session-summary-group="ongoing"] {
103
+ --prts-summary-state-color: var(--dsw-static-deepseek-450, var(--prts-cyan));
104
+ color: var(--prts-summary-state-color);
105
+ }
106
+
107
+ html[data-dsh-prts] [data-prts-session-summary-group="done"] {
108
+ --prts-summary-state-color: var(--dsw-alias-state-success-primary, var(--prts-green));
109
+ color: var(--prts-summary-state-color);
110
+ }
111
+
112
+ html[data-dsh-prts] [data-prts-session-summary-trigger]:is(:hover, :focus-visible) {
113
+ background: color-mix(in srgb, currentColor 14%, var(--prts-panel));
114
+ }
115
+
116
+ html[data-dsh-prts] [data-prts-session-summary-signal] {
117
+ position: relative;
118
+ width: 9px;
119
+ height: 9px;
120
+ display: inline-block;
121
+ flex: 0 0 9px;
122
+ color: inherit;
123
+ }
124
+
125
+ html[data-dsh-prts] [data-prts-session-summary-signal="ongoing"]::before {
126
+ content: "";
127
+ position: absolute;
128
+ top: 0;
129
+ left: 0;
130
+ width: 2px;
131
+ height: 2px;
132
+ background: currentColor;
133
+ box-shadow:
134
+ 3.5px 0 currentColor,
135
+ 7px 0 currentColor,
136
+ 7px 3.5px currentColor,
137
+ 7px 7px currentColor,
138
+ 3.5px 7px currentColor,
139
+ 0 7px currentColor,
140
+ 0 3.5px currentColor;
141
+ }
142
+
143
+ html[data-dsh-prts] [data-prts-session-summary-signal="done"]::before {
144
+ content: "";
145
+ position: absolute;
146
+ inset: 1px;
147
+ background: currentColor;
148
+ clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
149
+ }
150
+
151
+ html[data-dsh-prts] [data-prts-session-summary-popover] {
152
+ position: absolute;
153
+ z-index: 40;
154
+ top: calc(100% + 6px);
155
+ right: 0;
156
+ left: 0;
157
+ width: auto;
158
+ display: block;
159
+ padding: 8px;
160
+ border: 1px solid color-mix(in srgb, currentColor 44%, var(--prts-line));
161
+ background: var(--prts-glass-dense, var(--prts-panel));
162
+ box-shadow: var(--prts-shadow-glass-overlay);
163
+ color: var(--prts-ink);
164
+ opacity: 0;
165
+ visibility: hidden;
166
+ transform: translate3d(0, -4px, 0);
167
+ transition: opacity 120ms ease, transform 160ms var(--prts-ease-snap), visibility 0s linear 160ms;
168
+ pointer-events: none;
169
+ }
170
+
171
+ html[data-dsh-prts] [data-prts-session-summary-popover]::before {
172
+ content: "";
173
+ position: absolute;
174
+ right: 0;
175
+ bottom: 100%;
176
+ left: 0;
177
+ /* Cover the space from the vertically centered trigger to the popover. */
178
+ height: 16px;
179
+ }
180
+
181
+ html[data-dsh-prts] [data-prts-session-summary-group]:is(:hover, :has(:focus-visible)) [data-prts-session-summary-popover] {
182
+ opacity: 1;
183
+ visibility: visible;
184
+ transform: translate3d(0, 0, 0);
185
+ transition-delay: 0s;
186
+ pointer-events: auto;
187
+ }
188
+
189
+ @media (hover: none) {
190
+ html[data-dsh-prts] [data-prts-session-summary-group]:focus-within [data-prts-session-summary-popover] {
191
+ opacity: 1;
192
+ visibility: visible;
193
+ transform: translate3d(0, 0, 0);
194
+ transition-delay: 0s;
195
+ pointer-events: auto;
196
+ }
197
+ }
198
+
199
+ html[data-dsh-prts] [data-prts-session-summary-heading] {
200
+ height: 28px;
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: space-between;
204
+ gap: 12px;
205
+ padding: 0 6px 6px;
206
+ color: currentColor;
207
+ font: 700 10px/1 var(--prts-mono);
208
+ }
209
+
210
+ html[data-dsh-prts] [data-prts-session-summary-heading] small {
211
+ color: var(--prts-dim);
212
+ font-size: 9px;
213
+ font-weight: 500;
214
+ }
215
+
216
+ html[data-dsh-prts] [data-prts-session-summary-item] {
217
+ width: 100%;
218
+ height: 34px;
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 8px;
222
+ padding: 0 7px;
223
+ border: 0;
224
+ border-top: 1px solid var(--prts-line);
225
+ border-radius: 0;
226
+ background: transparent;
227
+ color: var(--prts-ink);
228
+ cursor: pointer;
229
+ }
230
+
231
+ html[data-dsh-prts] [data-prts-session-summary-item] [data-prts-session-summary-signal] {
232
+ color: var(--prts-summary-state-color);
233
+ }
234
+
235
+ html[data-dsh-prts] [data-prts-session-summary-item] > span:not([data-prts-session-summary-signal]) {
236
+ min-width: 0;
237
+ overflow: hidden;
238
+ font-size: 11px;
239
+ text-align: left;
240
+ text-overflow: ellipsis;
241
+ white-space: nowrap;
242
+ }
243
+
244
+ html[data-dsh-prts] [data-prts-session-summary-item]:is(:hover, :focus-visible) {
245
+ background: color-mix(in srgb, currentColor 9%, transparent);
246
+ }
247
+
248
+ @media (prefers-reduced-motion: reduce) {
249
+ html[data-dsh-prts] [data-prts-session-summary-popover] {
250
+ animation: none;
251
+ transition-duration: 0s;
252
+ }
253
+ }
254
+
20
255
  html[data-dsh-prts] [data-prts-facility-layer="surface"] {
21
256
  fill: var(--prts-facility-surface, var(--prts-panel-raised));
22
257
  }