@ash-qw/dsh-theme-prts 2.5.1 → 2.5.3

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.1",
3
+ "version": "2.5.3",
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",
@@ -11,6 +11,15 @@ function closestWithClassSuffix(node, suffix) {
11
11
  return null
12
12
  }
13
13
 
14
+ function isPendingInteractionAction(button) {
15
+ if (!button || button.tagName !== 'BUTTON') return false
16
+ if (button.closest('[data-approval-key]')) return true
17
+ if (button.closest('[data-question-key]')) {
18
+ return hasClassSuffix(button, '_waitButton') || Boolean(closestWithClassSuffix(button, '_footerActions'))
19
+ }
20
+ return Boolean(button.closest('[data-plan-review-key]') && closestWithClassSuffix(button, '_actions'))
21
+ }
22
+
14
23
  function classify(button, operation) {
15
24
  // The host's process disclosure owns its transparent, full-width hit area.
16
25
  if (button.hasAttribute('data-turn-process')) return null
@@ -138,14 +147,16 @@ function mountBodyTargetFrame(document, window, operation) {
138
147
  // Nonvisual hosts can still mount the theme without an animation clock.
139
148
  if (!window?.requestAnimationFrame || !window?.cancelAnimationFrame) return () => {}
140
149
  const composerButton=':is([data-composer-card],[data-prts-composer-fallback]) button:not([data-slot="conversation.input.attachments"] *,[data-prts-glass-menu] *,[data-prts-floating-glass] *,[role="menu"] *,[role="listbox"] *)'
141
- const selector='[data-prts-conversation-control],[data-presented-file],[data-changed-files],[data-produced-files-row] > button,[data-prts-glass-control],[data-prts-footer-action],[data-prts-transfer] button,'+composerButton
150
+ const selector='[data-prts-to-bottom],[data-prts-conversation-control],[data-presented-file],[data-changed-files],[data-produced-files-row] > button,[data-prts-glass-control],[data-prts-footer-action],[data-prts-transfer] button,'+composerButton
142
151
  const frame=document.createElement('div')
143
152
  frame.setAttribute('data-prts-body-target-frame','');frame.setAttribute('aria-hidden','true')
144
153
  const corners=Array.from({length:4},()=>frame.appendChild(document.createElement('i')))
145
154
  document.body.append(frame)
146
155
  if(frame.showPopover)frame.setAttribute('popover','manual')
147
- let layer=null
148
- const hideFrame=()=>{if(frame.hidePopover && frame.matches(':popover-open'))frame.hidePopover()}
156
+ let layer=null, imageRetiring=false
157
+ const hideFrame=()=>{
158
+ if(frame.hidePopover && frame.matches(':popover-open'))frame.hidePopover()
159
+ }
149
160
  const showFrame=()=>{
150
161
  if(!frame.showPopover)return
151
162
  const modal=target?.closest('dialog[open]')
@@ -153,27 +164,36 @@ function mountBodyTargetFrame(document, window, operation) {
153
164
  if(!frame.matches(':popover-open'))frame.showPopover()
154
165
  layer=modal
155
166
  }
156
- let target=null, hovered=null, focused=null, raf=0, pressed=false, disposed=false
157
- let pointer={x:0,y:0}, state=null, settled=false, tween=null, bounds=null, boundsDirty=false, hitDirty=false, exitGoal=null, motionDuration=240, targetArm=7
167
+ let target=null, hovered=null, focused=null, raf=0, pressed=false, releasePending=false, disposed=false
168
+ let pointer={x:0,y:0}, state=null, settled=false, tween=null, bounds=null, boundsDirty=false, hitDirty=false, exitGoal=null, motionDuration=240, targetArm=7, retiringBounds=null, targetRetires=false
158
169
  const resizeObserver=window.ResizeObserver?new window.ResizeObserver(()=>{boundsDirty=true;wake()}):null
159
170
  const reduced=window.matchMedia?.('(prefers-reduced-motion: reduce)')
160
171
  const fine=window.matchMedia?.('(hover:hover) and (pointer:fine)')
172
+ let activeModal=null
161
173
  const find=node=>{
174
+ if(activeModal && !activeModal.contains(node))return null
162
175
  if (!node?.closest || node.closest('[data-prts-workspace-actions],[data-prts-session-actions]')) return null
163
176
  const card=node.closest('[data-presented-file],[data-changed-files]')
164
- const candidate=card || node.closest(selector)
177
+ const button=node.closest('button')
178
+ const pendingAction=operation.contains(button) && isPendingInteractionAction(button) ? button : null
179
+ const candidate=card || pendingAction || node.closest(selector)
165
180
  return candidate && !candidate.matches(':disabled,[aria-disabled="true"]') && !candidate.closest('[inert]') ? candidate : null
166
181
  }
167
182
  const measure=()=>{bounds=target.getBoundingClientRect();targetArm=7+Math.max(0,Math.min(1,(Math.min(bounds.width,bounds.height)-36)/36))*5;boundsDirty=false}
168
183
  const wake=()=>{if(!raf && !disposed)raf=window.requestAnimationFrame(draw)}
169
184
  const choose=()=>{
170
- const next=hovered || focused
185
+ const next=(hovered?.isConnected?hovered:null) || (focused?.isConnected?focused:null)
171
186
  if(next!==target){
187
+ const retire=!next && target && !target.isConnected && targetRetires && releasePending && bounds && !reduced?.matches
188
+ imageRetiring=false
189
+ retiringBounds=retire?{left:bounds.left,top:bounds.top,width:bounds.width,height:bounds.height}:null
172
190
  target?.removeAttribute('data-prts-corner-target')
173
- pressed=false;settled=false;tween=null;exitGoal=null;motionDuration=240;target=next;boundsDirty=true
191
+ if(retire){hovered=focused=target=null;boundsDirty=false;resizeObserver?.disconnect();wake();return}
192
+ pressed=false;releasePending=false;settled=false;tween=null;exitGoal=null;motionDuration=240;target=next;boundsDirty=true
174
193
  resizeObserver?.disconnect()
175
194
  if(target)resizeObserver?.observe(target)
176
195
  if(target){
196
+ targetRetires=hasClassSuffix(target,'_toBottom') || isPendingInteractionAction(target)
177
197
  target.setAttribute('data-prts-corner-target','');showFrame()
178
198
  measure();const box=bounds
179
199
  if(!state || state.alpha<.01)state={x:(hovered?pointer.x:box.left+box.width/2)-10,y:(hovered?pointer.y:box.top+box.height/2)-10,w:20,h:20,alpha:0,arm:7}
@@ -185,14 +205,15 @@ function mountBodyTargetFrame(document, window, operation) {
185
205
  }
186
206
  function draw(time) {
187
207
  raf=0
208
+ if(target && !target.isConnected)choose()
188
209
  if(hitDirty){hitDirty=false;hovered=find(document.elementFromPoint(pointer.x,pointer.y));choose()}
189
- if(target && !target.isConnected){target.removeAttribute('data-prts-corner-target');target=hovered=focused=null;pressed=false;settled=false;exitGoal=null}
210
+ if(target && !target.isConnected){target.removeAttribute('data-prts-corner-target');target=hovered=focused=null;pressed=false;releasePending=false;settled=false;exitGoal=null}
190
211
  if(!state)return
191
212
  if(target && boundsDirty){
192
213
  measure()
193
- if(!bounds.width && !bounds.height){target.removeAttribute('data-prts-corner-target');target=hovered=focused=null;pressed=false;settled=false;exitGoal=null;resizeObserver?.disconnect()}
214
+ if(!bounds.width && !bounds.height){target.removeAttribute('data-prts-corner-target');target=hovered=focused=null;pressed=false;releasePending=false;settled=false;exitGoal=null;resizeObserver?.disconnect()}
194
215
  }
195
- const box=target?bounds:null,gap=pressed?3:5
216
+ const box=target?bounds:retiringBounds,gap=pressed?(imageRetiring?0:3):5
196
217
  // Freeze the exit destination: subsequent pointer motion must not restart the fade.
197
218
  const goal=box?{x:box.left-gap,y:box.top-gap,w:box.width+gap*2,h:box.height+gap*2,alpha:1,arm:targetArm}:
198
219
  (exitGoal ||= {x:pointer.x-10,y:pointer.y-10,w:20,h:20,alpha:0,arm:7})
@@ -223,7 +244,11 @@ function mountBodyTargetFrame(document, window, operation) {
223
244
  if(frame.style.opacity!==opacity)frame.style.opacity=opacity
224
245
  settled=!moving
225
246
  frame.toggleAttribute('data-pressed',pressed)
226
- if(moving)wake()
247
+ if(releasePending && pressed && !moving){
248
+ if(retiringBounds){exitGoal={...state,alpha:0};retiringBounds=null}
249
+ releasePending=false;pressed=false;motionDuration=imageRetiring?120:140;settled=false;wake()
250
+ }
251
+ else if(moving)wake()
227
252
  else {tween=null;if(!state.alpha)hideFrame()}
228
253
  }
229
254
  const move=event=>{
@@ -240,21 +265,49 @@ function mountBodyTargetFrame(document, window, operation) {
240
265
  pointer={x:event.clientX,y:event.clientY}
241
266
  if(event.pointerType==='touch')hovered=find(event.target)
242
267
  choose()
243
- if(target && find(event.target)===target){pressed=true;motionDuration=100;settled=false;wake()}
268
+ if(target && find(event.target)===target){pressed=true;releasePending=false;motionDuration=100;settled=false;wake()}
269
+ }
270
+ const release=()=>{
271
+ // A tap can release before the first paint. Finish one contraction, then
272
+ // expand; one flag keeps repeated taps interruptible without a timer queue.
273
+ if(pressed && !settled && !reduced?.matches){releasePending=true;settled=false;wake()}
274
+ else {pressed=false;releasePending=false;motionDuration=140;settled=false;wake()}
244
275
  }
245
- const up=event=>{pressed=false;motionDuration=140;settled=false;if(event.pointerType==='touch')hovered=null;choose()}
276
+ const up=event=>{release();if(event.pointerType==='touch')hovered=null;choose()}
246
277
  const reset=()=>{
247
278
  target?.removeAttribute('data-prts-corner-target');hideFrame()
248
- hovered=focused=target=null;state=tween=bounds=exitGoal=null
249
- pressed=false;settled=true;boundsDirty=hitDirty=false
279
+ hovered=focused=target=null;state=tween=bounds=exitGoal=retiringBounds=null
280
+ imageRetiring=false
281
+ pressed=false;releasePending=false;settled=true;boundsDirty=hitDirty=false
250
282
  resizeObserver?.disconnect();window.cancelAnimationFrame(raf);raf=0
251
283
  frame.style.opacity='0';frame.removeAttribute('data-pressed')
252
284
  }
285
+ const syncModal=()=>{
286
+ const modals=[...document.querySelectorAll('dialog[open],[role="dialog"][aria-modal="true"]')]
287
+ const previousModal=activeModal
288
+ activeModal=modals.filter(node=>!node.hidden && node.getAttribute('aria-hidden')!=='true').at(-1) ?? null
289
+ if(activeModal===previousModal)return
290
+ if(activeModal && target && !activeModal.contains(target) && target.querySelector('img') && activeModal.querySelector('img') && releasePending && bounds && !reduced?.matches){
291
+ // The preview is opaque: show a bounded click acknowledgment above it,
292
+ // then fade at the thumbnail position instead of retaining a hover target.
293
+ imageRetiring=true;motionDuration=120;tween=null
294
+ retiringBounds={left:bounds.left,top:bounds.top,width:bounds.width,height:bounds.height}
295
+ target.removeAttribute('data-prts-corner-target');hovered=focused=target=null
296
+ boundsDirty=hitDirty=false;resizeObserver?.disconnect();settled=false;wake()
297
+ }else if(imageRetiring || activeModal && (!target || !activeModal.contains(target)))reset()
298
+ }
299
+ // Native image previews mount a body portal without moving the pointer.
300
+ // Watch portal changes only, not the streaming conversation subtree.
301
+ const modalObserver=window.MutationObserver?new window.MutationObserver(syncModal):null
302
+ modalObserver?.observe(document.body,{childList:true})
303
+ const modalToggle=event=>{if(event.target?.matches?.('dialog'))syncModal()}
304
+ document.addEventListener('toggle',modalToggle,true)
305
+ syncModal()
253
306
  const visibility=()=>{if(document.hidden)reset()}
254
307
  const scroll=()=>{if(!target && !state?.alpha)return;boundsDirty=true;hitDirty=!!hovered;wake()}
255
308
  document.addEventListener('scroll',scroll,true)
256
309
  window.addEventListener('resize',reset)
257
- const key=event=>{if(target && (event.key===' ' || event.key==='Enter')){pressed=event.type==='keydown';motionDuration=pressed?100:140;settled=false;wake()}}
310
+ const key=event=>{if(target && (event.key===' ' || event.key==='Enter')){if(event.type==='keyup')release();else {pressed=true;releasePending=false;motionDuration=100;settled=false;wake()}}}
258
311
  const listeners=[
259
312
  [document,'pointermove',move],[document,'pointerleave',leave],
260
313
  [document,'focusin',focus],[document,'focusout',blur],
@@ -264,6 +317,7 @@ function mountBodyTargetFrame(document, window, operation) {
264
317
  ]
265
318
  for(const [node,event,handler] of listeners)node.addEventListener(event,handler)
266
319
  return ()=>{
320
+ modalObserver?.disconnect();document.removeEventListener('toggle',modalToggle,true)
267
321
  disposed=true;target?.removeAttribute('data-prts-corner-target');hideFrame();window.cancelAnimationFrame(raf);resizeObserver?.disconnect()
268
322
  document.removeEventListener('scroll',scroll,true);window.removeEventListener('resize',reset)
269
323
  for(const [node,event,handler] of listeners)node.removeEventListener(event,handler)
@@ -43,7 +43,7 @@ export function createAvatarStore({window,document}){
43
43
  const focus=()=>{if(window.fetch)void refresh().catch(()=>{})}
44
44
  window.addEventListener('focus',focus)
45
45
  return{
46
- snapshot:()=>state,refresh,
46
+ snapshot:()=>state,refresh,applySnapshot:apply,
47
47
  subscribe(fn){listeners.add(fn);fn(state);return()=>listeners.delete(fn)},
48
48
  async edit(role){
49
49
  const response=await window.fetch(API+'/edit/'+role,{credentials:'same-origin',signal:controller?.signal})
@@ -208,6 +208,7 @@ export function applyPrtsPlugin(ctx, environment) {
208
208
  document, window, assets, adapter, hostGeometry, customEmblemStore, avatarStore, backgroundStore,
209
209
  themeTransfer: themeTransfer ||= createThemeTransfer({ window, getPreferences: () => nativePreferences.value,
210
210
  savePreferences: (value, revision) => nativePreferences.replace(value, revision),
211
+ onAvatarConfiguration: value => avatarStore.applySnapshot(value),
211
212
  checkWritable: () => nativePreferences.checkWritable(), checkExport: () => nativePreferences.checkReadable(), themeVersion: version }),
212
213
  onSchemeToggle(next, interaction) {
213
214
  return theme.setTheme(next, { ...interaction, animate: true })
@@ -95,17 +95,24 @@ function visibleSessionSummaries(sessions, workspaces, sessionStatus) {
95
95
  })
96
96
  }
97
97
 
98
+ function setSidebarAttribute(node, attribute, value) {
99
+ if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value)
100
+ }
101
+
98
102
  function markSidebarNode(node, attribute, next) {
99
103
  if (!node) return
100
- node.setAttribute(attribute, '')
104
+ setSidebarAttribute(node, attribute, '')
101
105
  next.add(node)
102
106
  }
103
107
 
104
108
  function resolveRowControls(row, kind) {
105
109
  const menu = row.querySelector(':scope > span > span > button[type="button"]')
106
110
  const anchor = menu?.parentElement
107
- const actions = anchor?.parentElement
108
- if (!menu || !anchor || actions?.parentElement !== row) return {}
111
+ // Ungrouped workspaces have a create button but no management menu.
112
+ const actions = anchor?.parentElement ?? (kind === 'workspace'
113
+ ? Array.from(row.children).find(node => node.matches('span') && node.querySelector(':scope > button[type="button"]'))
114
+ : undefined)
115
+ if (actions?.parentElement !== row) return {}
109
116
  const create = kind === 'workspace'
110
117
  ? actions.querySelector(':scope > button[type="button"]')
111
118
  : undefined
@@ -171,9 +178,9 @@ function ensureFacilityFace(document, row, kind, next) {
171
178
  face.setAttribute('data-prts-owned-facility', '')
172
179
  row.insertBefore(face, row.firstChild)
173
180
  }
174
- face.setAttribute('data-prts-facility-face', kind)
175
- face.setAttribute('data-prts-facility-texture', kind === 'workspace' ? 'silhouette' : 'pickup')
176
- face.setAttribute('aria-hidden', 'true')
181
+ setSidebarAttribute(face, 'data-prts-facility-face', kind)
182
+ setSidebarAttribute(face, 'data-prts-facility-texture', kind === 'workspace' ? 'silhouette' : 'pickup')
183
+ setSidebarAttribute(face, 'aria-hidden', 'true')
177
184
  next.add(face)
178
185
  return face
179
186
  }
@@ -185,8 +192,8 @@ function ensureFacilitySpine(document, row, kind, next) {
185
192
  spine.setAttribute('data-prts-owned-facility-spine', '')
186
193
  row.insertBefore(spine, row.firstChild)
187
194
  }
188
- spine.setAttribute('data-prts-facility-spine', kind)
189
- spine.setAttribute('aria-hidden', 'true')
195
+ setSidebarAttribute(spine, 'data-prts-facility-spine', kind)
196
+ setSidebarAttribute(spine, 'aria-hidden', 'true')
190
197
  next.add(spine)
191
198
  return spine
192
199
  }
@@ -220,6 +227,7 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
220
227
  let statusSourcesStarted = false
221
228
  let summaryRoot
222
229
  let summarySignature = ''
230
+ let sourceSummarySignature = ''
223
231
  let heldPreviewState
224
232
  let hoveredSummary, summaryLeaveTimer
225
233
  let summaryDismissed = false, pointerLeftSummary = false
@@ -324,18 +332,26 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
324
332
  sourceRetryTimer = undefined
325
333
  }
326
334
  }
335
+ function onSummarySourceChange() {
336
+ // Streaming activity often republishes unchanged title/status summaries.
337
+ // Native DOM mutations still handle row mounts, selection and hierarchy.
338
+ const signature = JSON.stringify(visibleSessionSummaries(sessions, workspaces, sessionStatus))
339
+ if (signature === sourceSummarySignature && !pendingReveal) return
340
+ sourceSummarySignature = signature
341
+ queueScan()
342
+ }
327
343
  function connectSummarySources() {
328
344
  if (getSessions) sessions = getSessions()
329
345
  if (getWorkspaces) workspaces = getWorkspaces()
330
346
  if (subscribedSessions !== sessions?.list) {
331
347
  stopSessionList()
332
348
  subscribedSessions = sessions?.list
333
- stopSessionList = subscribedSessions?.subscribe?.(queueScan) ?? (() => {})
349
+ stopSessionList = subscribedSessions?.subscribe?.(onSummarySourceChange) ?? (() => {})
334
350
  }
335
351
  if (subscribedWorkspaces !== workspaces?.list) {
336
352
  stopWorkspaceList()
337
353
  subscribedWorkspaces = workspaces?.list
338
- stopWorkspaceList = subscribedWorkspaces?.subscribe?.(queueScan) ?? (() => {})
354
+ stopWorkspaceList = subscribedWorkspaces?.subscribe?.(onSummarySourceChange) ?? (() => {})
339
355
  }
340
356
  }
341
357
  function connectSessionStatus() {
@@ -343,7 +359,7 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
343
359
  if (source === sessionStatus) return
344
360
  stopSessionStatus()
345
361
  sessionStatus = source
346
- stopSessionStatus = source?.subscribe?.(queueScan) ?? (() => {})
362
+ stopSessionStatus = source?.subscribe?.(onSummarySourceChange) ?? (() => {})
347
363
  }
348
364
 
349
365
  let selectionTarget, selectionTargetTimer
@@ -858,6 +874,7 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
858
874
  function scan() {
859
875
  if (!adapterActive) return
860
876
  syncSummarySources()
877
+ sourceSummarySignature = JSON.stringify(visibleSessionSummaries(sessions, workspaces, sessionStatus))
861
878
  const root = document?.querySelector?.('[data-prts-region="sessions"] [data-slot="sidebar.workspaces"]')
862
879
  const next = new Set()
863
880
  const nextVectorOwners = new Set()
@@ -916,16 +933,16 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
916
933
  }
917
934
  markSidebarNode(row, 'data-prts-session-row', next)
918
935
  if (state) {
919
- row.setAttribute('data-prts-session-state', state)
936
+ setSidebarAttribute(row, 'data-prts-session-state', state)
920
937
  next.add(row)
921
938
  } else {
922
939
  row.removeAttribute('data-prts-session-state')
923
940
  }
924
- row.dataset.prtsSessionIndex = sessionIndex
941
+ setSidebarAttribute(row, 'data-prts-session-index', sessionIndex)
925
942
  const title = resolveRowTitle(row, 'session', controls.actions)
926
943
  markSidebarNode(title, 'data-prts-row-title', next)
927
944
  markSidebarNode(resolveSessionTime(row, title, controls.actions), 'data-prts-session-time', next)
928
- if (title) title.dataset.prtsSessionIndex = sessionIndex
945
+ if (title) setSidebarAttribute(title, 'data-prts-session-index', sessionIndex)
929
946
  row.toggleAttribute('data-prts-row-menu-open', controls.menu?.getAttribute('aria-expanded') === 'true'
930
947
  || row.classList.contains('menuOpen'))
931
948
  markSidebarNode(controls.actions, 'data-prts-session-actions', next)
@@ -101,7 +101,7 @@ export function describeThemePackage(value) {
101
101
  return `${count} 项主题设置、${transferSlots.filter(k => a.backgrounds[k]).length} 张背景、${transferRoles.filter(k => a.avatars[k]).length} 个头像、${a.emblems.items.length} 个自定义徽记。将替换当前素材及徽记排列,空项恢复为默认。`
102
102
  }
103
103
 
104
- export function createThemeTransfer({ window, getPreferences, savePreferences, checkWritable, checkExport = checkWritable, themeVersion }) {
104
+ export function createThemeTransfer({ window, getPreferences, savePreferences, checkWritable, checkExport = checkWritable, themeVersion, onAvatarConfiguration = () => {} }) {
105
105
  let busy = false, backup
106
106
  const request = async (path, data, signal) => {
107
107
  checkTransferCancelled(signal)
@@ -198,7 +198,10 @@ export function createThemeTransfer({ window, getPreferences, savePreferences, c
198
198
  undo.push(async () => {
199
199
  const restored = await request(kind + '/configuration', { ...previous, revision: revisions[kind] })
200
200
  revisions[kind] = restored.revision
201
+ if (kind === 'avatars') onAvatarConfiguration(restored)
201
202
  })
203
+ // Apply the acknowledged images locally; SSE remains for other pages.
204
+ if (kind === 'avatars') onAvatarConfiguration(result)
202
205
  }
203
206
  try {
204
207
  if (value.assets) {
@@ -345,6 +345,12 @@ html[data-dsh-prts] [data-prts-settings-section="background"] [data-prts-backgro
345
345
  [data-prts-composer-glass-setting], [data-prts-composer-glass-required] { display: block; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--prts-line); }
346
346
  [data-prts-composer-glass-required] { font-size: 12px; color: var(--prts-muted); }
347
347
 
348
+ /* Pending questions, approvals and plan reviews replace the glass composer.
349
+ * Keep its full-width seat clear even while no glass card is mounted. */
350
+ html[data-dsh-prts]:is([data-prts-background-mode="full"][data-prts-background-full],[data-prts-background-mode="split"][data-prts-background-conversation]) [data-composer-seat] {
351
+ background: transparent !important;
352
+ }
353
+
348
354
  /* The native active composer fades into DSH's base color. Baker paints a
349
355
  * different solid backdrop, so match that backdrop while keeping the fade. */
350
356
  html[data-dsh-prts][data-prts-conversation-style="deck-chat"][data-prts-background-mode="off"] [data-phase="active"] [data-composer-seat]:not(:has([data-prts-composer-glass-active])) {
@@ -479,3 +485,13 @@ background:color-mix(in srgb,var(--prts-panel) 28%,transparent);
479
485
  -webkit-backdrop-filter:blur(12px);
480
486
  backdrop-filter:blur(12px);
481
487
  }
488
+
489
+ /* The task manager mounts directly in main, outside the conversation root. */
490
+ html[data-dsh-prts] [data-prts-region="operation"] [data-testid="task-manager-page"],
491
+ html[data-dsh-prts] [data-testid="task-manager-page"] :is([class$="_detail"],[class$="_detailNotice"]) {
492
+ background: var(--prts-canvas);
493
+ }
494
+ html[data-dsh-prts]:is([data-prts-background-mode="full"][data-prts-background-full],[data-prts-background-mode="split"][data-prts-background-conversation]) [data-prts-region="operation"] [data-testid="task-manager-page"],
495
+ html[data-dsh-prts]:is([data-prts-background-mode="full"][data-prts-background-full],[data-prts-background-mode="split"][data-prts-background-conversation]) [data-testid="task-manager-page"] :is([class$="_detail"],[class$="_detailNotice"]) {
496
+ background: transparent;
497
+ }
@@ -32,20 +32,12 @@ html[data-dsh-prts] [data-prts-region="sessions"] [data-slot="sidebar.settings"]
32
32
  color: var(--prts-ink) !important;
33
33
  }
34
34
 
35
- /* Workspace hierarchy: remove only the folder glyph and retain the disclosure control. */
36
- html[data-dsh-prts] [data-prts-workspace-icon] {
37
- display: none !important;
38
- }
39
-
35
+ /* Keep native folder/chevron visibility and active color; align their shared seat. */
36
+ html[data-dsh-prts] [data-prts-workspace-icon],
40
37
  html[data-dsh-prts] [data-prts-workspace-disclosure] {
41
38
  position: relative;
42
39
  z-index: 4;
43
- width: 14px;
44
- flex: 0 0 14px;
45
- display: grid;
46
- place-items: center;
47
40
  margin-left: calc(var(--prts-spine-width) + 4px);
48
- color: var(--prts-muted);
49
41
  transform: translate3d(var(--prts-row-shift), 0, 0);
50
42
  transition: transform 220ms var(--prts-ease-spring), color 160ms ease;
51
43
  }
@@ -97,6 +89,10 @@ html[data-dsh-prts] [data-prts-workspace-actions] {
97
89
  width: 57px !important;
98
90
  }
99
91
 
92
+ html[data-dsh-prts] [data-prts-workspace-actions]:has(> [data-prts-workspace-create]:only-child) {
93
+ width: 28px !important;
94
+ }
95
+
100
96
  html[data-dsh-prts] [data-prts-session-actions] {
101
97
  width: auto !important;
102
98
  }