@ash-qw/dsh-theme-prts 0.1.92 → 0.1.93

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,9 +1,18 @@
1
1
  {
2
2
  "name": "@ash-qw/dsh-theme-prts",
3
- "version": "0.1.92",
3
+ "version": "0.1.93",
4
4
  "private": false,
5
5
  "type": "module",
6
- "description": "Unofficial personal non-commercial Arknights P.R.T.S. fan UI for DeepSeek Harness",
6
+ "description": "Unofficial Arknights P.R.T.S.-inspired UI theme for DeepSeek Harness Web",
7
+ "keywords": [
8
+ "deepseek-harness",
9
+ "dsh",
10
+ "dsh-plugin",
11
+ "theme",
12
+ "ui",
13
+ "arknights",
14
+ "prts"
15
+ ],
7
16
  "license": "SEE LICENSE IN LICENSE",
8
17
  "repository": {
9
18
  "type": "git",
@@ -44,6 +53,7 @@
44
53
  "cordis.patch.yml",
45
54
  "README.md",
46
55
  "README.en.md",
56
+ "CHANGELOG.md",
47
57
  "LICENSE",
48
58
  "THIRD_PARTY_NOTICES.md"
49
59
  ],
@@ -107,6 +107,7 @@ const PREVIEW_TOUCH_DURATION = 1200
107
107
  const SNAP_DELAY = 110
108
108
  const TOUCH_WAVE_DURATION = 620
109
109
  const HISTORY_PAGE_SETTLE_LIMIT = 4000
110
+ const GEOMETRY_SETTLE_FRAME_LIMIT = 24
110
111
  let scaleInstanceSeed = 0
111
112
 
112
113
  function clamp(value, minimum, maximum) {
@@ -764,6 +765,9 @@ export function createConversationScaleAdapter({ document, window, sessions }) {
764
765
  let scaleLengths = resolveConversationScaleLengths(focusContrast)
765
766
  let contentLeft
766
767
  let geometryDirty = true
768
+ let geometrySettleFrame
769
+ let geometrySettleFramesRemaining = 0
770
+ let geometrySettleSignature
767
771
  let observedOperationWidth
768
772
  let observedOperationHeight
769
773
  let started = false
@@ -786,6 +790,63 @@ export function createConversationScaleAdapter({ document, window, sessions }) {
786
790
  ? timestamp
787
791
  : window?.performance?.now?.() ?? Date.now()
788
792
 
793
+ function currentGeometrySignature() {
794
+ if (!operation || !scroller) return undefined
795
+ const operationBox = operation.getBoundingClientRect?.()
796
+ const scrollerBox = scroller.getBoundingClientRect?.()
797
+ const composerBox = composer?.getBoundingClientRect?.()
798
+ if (!operationBox || !scrollerBox) return undefined
799
+ const values = [
800
+ operationBox.width,
801
+ operationBox.height,
802
+ scrollerBox.top - operationBox.top,
803
+ scrollerBox.bottom - operationBox.top,
804
+ scrollerBox.left - operationBox.left,
805
+ scrollerBox.right - operationBox.left,
806
+ composerBox ? composerBox.top - operationBox.top : -1,
807
+ composerBox ? composerBox.bottom - operationBox.top : -1,
808
+ composerBox ? composerBox.left - operationBox.left : -1,
809
+ composerBox ? composerBox.right - operationBox.left : -1,
810
+ ]
811
+ if (!values.every(Number.isFinite)) return undefined
812
+ return values.map(value => Number(value.toFixed(2))).join(':')
813
+ }
814
+
815
+ function sampleGeometryStabilization() {
816
+ geometrySettleFrame = undefined
817
+ if (!started || failed || geometrySettleFramesRemaining <= 0) return
818
+ geometrySettleFramesRemaining -= 1
819
+ const signature = currentGeometrySignature()
820
+ if (signature !== undefined && signature !== geometrySettleSignature) {
821
+ geometrySettleSignature = signature
822
+ geometryDirty = true
823
+ obstructionDirty = true
824
+ schedule()
825
+ }
826
+ if (geometrySettleFramesRemaining > 0) {
827
+ geometrySettleFrame = requestFrame(sampleGeometryStabilization)
828
+ }
829
+ }
830
+
831
+ function requestGeometryStabilization() {
832
+ if (!started || failed) return
833
+ geometrySettleFramesRemaining = GEOMETRY_SETTLE_FRAME_LIMIT
834
+ if (geometrySettleFrame === undefined) {
835
+ geometrySettleFrame = requestFrame(sampleGeometryStabilization)
836
+ }
837
+ }
838
+
839
+ function stopGeometryStabilization() {
840
+ cancelFrame(geometrySettleFrame)
841
+ geometrySettleFrame = undefined
842
+ geometrySettleFramesRemaining = 0
843
+ geometrySettleSignature = undefined
844
+ }
845
+
846
+ function onHostGeometryChange() {
847
+ requestGeometryStabilization()
848
+ }
849
+
789
850
  function reducedMotion() {
790
851
  return window?.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches
791
852
  || document.documentElement.dataset.prtsMotion === 'reduced'
@@ -1117,10 +1178,12 @@ export function createConversationScaleAdapter({ document, window, sessions }) {
1117
1178
  setScaleResizeState(resizeActive)
1118
1179
  cancelJump()
1119
1180
  resetWave()
1181
+ requestGeometryStabilization()
1120
1182
  return true
1121
1183
  }
1122
1184
 
1123
1185
  function disconnectRoots() {
1186
+ stopGeometryStabilization()
1124
1187
  clearTimers()
1125
1188
  hidePreview()
1126
1189
  disconnectPreviewObserver()
@@ -2129,10 +2192,15 @@ export function createConversationScaleAdapter({ document, window, sessions }) {
2129
2192
  if (started || !document) return
2130
2193
  started = true
2131
2194
  failed = false
2195
+ window?.addEventListener?.('resize', onHostGeometryChange)
2196
+ window?.addEventListener?.('orientationchange', onHostGeometryChange)
2197
+ window?.addEventListener?.('pageshow', onHostGeometryChange)
2198
+ window?.visualViewport?.addEventListener?.('resize', onHostGeometryChange)
2132
2199
  const ResizeObserver = window?.ResizeObserver
2133
2200
  if (typeof ResizeObserver === 'function') {
2134
2201
  resizeObserver = new ResizeObserver((entries = []) => {
2135
2202
  geometryDirty = true
2203
+ requestGeometryStabilization()
2136
2204
  const operationEntry = entries.find(entry => entry.target === operation)
2137
2205
  const width = Number(operationEntry?.contentRect?.width)
2138
2206
  const height = Number(operationEntry?.contentRect?.height)
@@ -2173,6 +2241,7 @@ export function createConversationScaleAdapter({ document, window, sessions }) {
2173
2241
  const external = mutations.filter(mutation =>
2174
2242
  !mutation.target?.closest?.('[data-prts-conversation-scale], [data-prts-conversation-preview], [data-prts-conversation-history-hint], [data-prts-conversation-history-status]')
2175
2243
  )
2244
+ if (external.length) requestGeometryStabilization()
2176
2245
  const historyRelevant = external.some(mutation =>
2177
2246
  mutation.type === 'childList'
2178
2247
  && operation?.contains?.(mutation.target)
@@ -2210,9 +2279,17 @@ export function createConversationScaleAdapter({ document, window, sessions }) {
2210
2279
  ],
2211
2280
  })
2212
2281
  }
2282
+ document.fonts?.ready?.then?.(() => {
2283
+ if (started) requestGeometryStabilization()
2284
+ }).catch?.(() => {})
2213
2285
  schedule(true)
2214
2286
  },
2215
2287
  dispose() {
2288
+ window?.removeEventListener?.('resize', onHostGeometryChange)
2289
+ window?.removeEventListener?.('orientationchange', onHostGeometryChange)
2290
+ window?.removeEventListener?.('pageshow', onHostGeometryChange)
2291
+ window?.visualViewport?.removeEventListener?.('resize', onHostGeometryChange)
2292
+ stopGeometryStabilization()
2216
2293
  started = false
2217
2294
  failed = false
2218
2295
  mutationObserver?.disconnect()
@@ -45,7 +45,6 @@ export function applyPrtsPlugin(ctx, environment) {
45
45
  const safeMode = isSafeMode(window?.location?.search ?? '')
46
46
  const storage = window?.localStorage
47
47
  const themeService = contextService(ctx, 'theme')
48
- const theme = createThemeController({ document, window, cssText, service: themeService })
49
48
  const startup = createPrtsStartupSequence({ document, window, prtsEmblem: assets.prtsEmblem, rhodesEmblem: assets.emblem, timings: startupTimings })
50
49
  const composerGlass = createComposerGlassAdapter({ document, window })
51
50
  const assistantGlass = createAssistantGlassAdapter({ document, window })
@@ -60,6 +59,15 @@ export function applyPrtsPlugin(ctx, environment) {
60
59
  sync()
61
60
  },
62
61
  })
62
+ const theme = createThemeController({
63
+ document,
64
+ window,
65
+ cssText,
66
+ service: themeService,
67
+ onTransitionStateChange(active) {
68
+ particleField.setSchemeTransitionActive(active)
69
+ },
70
+ })
63
71
  const sessions = contextService(ctx, 'sessions')
64
72
  const toBottom = createToBottomAdapter({ document, window })
65
73
  const conversationControls = createConversationControlAdapter({ document, window })
@@ -160,8 +168,11 @@ export function applyPrtsPlugin(ctx, environment) {
160
168
  if (!themeAlreadyApplied) theme.apply(preferences)
161
169
  operations ||= createOperationsShell({
162
170
  document, window, assets, adapter, hostGeometry,
163
- onSchemeToggle(next) {
164
- theme.setTheme(next)
171
+ onSchemeToggle(next, interaction) {
172
+ return theme.setTheme(next, { ...interaction, animate: true })
173
+ },
174
+ onSchemeDragStart(next, interaction) {
175
+ return theme.beginThemeTransition(next, interaction)
165
176
  },
166
177
  onThemeDisable() {
167
178
  persistAndApply(updatePreferenceValue(preferences, 'enabled', false))
@@ -169,6 +180,10 @@ export function applyPrtsPlugin(ctx, environment) {
169
180
  onPreferenceChange(key, value) {
170
181
  persistAndApply(updatePreferenceValue(preferences, key, value))
171
182
  },
183
+ onPreferencePreview(key, value) {
184
+ if (key !== 'particleTraversalSpeed') return
185
+ particleField.update(updatePreferenceValue(preferences, key, value))
186
+ },
172
187
  onVisualReset() {
173
188
  persistAndApply({
174
189
  ...DEFAULT_PREFERENCES,
@@ -35,8 +35,10 @@ export function createOperationsShell({
35
35
  adapter,
36
36
  hostGeometry,
37
37
  onSchemeToggle = () => {},
38
+ onSchemeDragStart = () => undefined,
38
39
  onThemeDisable = () => {},
39
40
  onPreferenceChange = () => {},
41
+ onPreferencePreview = () => {},
40
42
  onVisualReset = () => {},
41
43
  getConversationScalePreview = () => ({ visible: false, reason: 'unmeasured', mode: 'hidden' }),
42
44
  onRetrySave = () => {},
@@ -47,14 +49,79 @@ export function createOperationsShell({
47
49
  let navRail
48
50
  let railLauncher
49
51
  let railMode
52
+ let railViewport
53
+ let railDefaultHidden = false
50
54
  let railViewportQuery
51
55
  let settingsOverlay
52
56
  let schemeToggle
57
+ let schemeIntent
58
+ let schemePressRevision = 0
59
+ let schemePressAnimation
60
+ let schemePointer
61
+ let suppressSchemeClick = false
62
+ let suppressSchemeClickTimer
53
63
  let themeDisable
54
64
  let connectionIndicator
55
65
  let conversationObserver
56
66
  let schemeObserver
57
67
  let operationRegion
68
+ const schemePressDelays = new Set()
69
+
70
+ function reducedMotion() {
71
+ if (root.dataset.prtsMotion === 'reduced') return true
72
+ try { return window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true } catch { return false }
73
+ }
74
+
75
+ function playSchemePress() {
76
+ if (!schemeToggle || reducedMotion()) return
77
+ const revision = ++schemePressRevision
78
+ schemePressAnimation?.cancel?.()
79
+ schemePressAnimation = undefined
80
+ schemeToggle.removeAttribute('data-prts-scheme-press')
81
+
82
+ if (typeof schemeToggle.animate === 'function') {
83
+ const animation = schemeToggle.animate([
84
+ { transform: 'translateY(0) scale(1)' },
85
+ { transform: 'translateY(0) scale(.96)', offset: 0.5 },
86
+ { transform: 'translateY(0) scale(1)' },
87
+ ], { duration: 100, easing: 'cubic-bezier(.22, 1, .36, 1)' })
88
+ schemePressAnimation = animation
89
+ Promise.resolve(animation.finished).catch(() => {}).finally(() => {
90
+ if (revision === schemePressRevision && schemePressAnimation === animation) schemePressAnimation = undefined
91
+ })
92
+ return
93
+ }
94
+
95
+ schemeToggle.setAttribute('data-prts-scheme-press', '')
96
+ const entry = { timer: undefined }
97
+ entry.timer = window.setTimeout(() => {
98
+ schemePressDelays.delete(entry)
99
+ if (revision === schemePressRevision) schemeToggle?.removeAttribute('data-prts-scheme-press')
100
+ }, 100)
101
+ schemePressDelays.add(entry)
102
+ }
103
+
104
+ function clearSchemePressDelays() {
105
+ schemePressRevision += 1
106
+ schemePressAnimation?.cancel?.()
107
+ schemePressAnimation = undefined
108
+ schemeToggle?.removeAttribute('data-prts-scheme-press')
109
+ for (const entry of schemePressDelays) {
110
+ window.clearTimeout(entry.timer)
111
+ }
112
+ schemePressDelays.clear()
113
+ }
114
+
115
+ function schemeTarget() {
116
+ const current = schemeIntent ?? (root.dataset.prtsScheme === 'dark' ? 'dark' : 'light')
117
+ return current === 'dark' ? 'light' : 'dark'
118
+ }
119
+
120
+ function schemeOrigin() {
121
+ const rect = schemeToggle?.getBoundingClientRect?.()
122
+ return rect ? { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 } : undefined
123
+ }
124
+
58
125
  function syncConversationState() {
59
126
  if (!operationRegion) return
60
127
  const candidate = operationRegion.querySelector(
@@ -65,13 +132,122 @@ export function createOperationsShell({
65
132
  settingsOverlay?.refreshScalePreview()
66
133
  }
67
134
 
68
- function toggleScheme() {
69
- onSchemeToggle(root.dataset.prtsScheme === 'dark' ? 'light' : 'dark')
135
+ function toggleScheme(event) {
136
+ if (suppressSchemeClick) {
137
+ event?.preventDefault?.()
138
+ return
139
+ }
140
+ const next = schemeTarget()
141
+ schemeIntent = next
142
+ playSchemePress()
143
+ let request
144
+ try { request = onSchemeToggle(next, { origin: schemeOrigin() }) } catch { request = undefined }
145
+ Promise.resolve(request).catch(() => {}).finally(() => {
146
+ if (schemeIntent === next) schemeIntent = undefined
147
+ })
148
+ }
149
+
150
+ function schemePointerProgress(event) {
151
+ const width = Math.max(1, Number(window.innerWidth) || Number(root.clientWidth) || 1)
152
+ return Math.min(1, Math.max(0, Number(event?.clientX) / width || 0))
153
+ }
154
+
155
+ function clearSchemeClickSuppression() {
156
+ if (suppressSchemeClickTimer !== undefined) window.clearTimeout(suppressSchemeClickTimer)
157
+ suppressSchemeClickTimer = undefined
158
+ suppressSchemeClick = false
159
+ }
160
+
161
+ function suppressNextSchemeClick() {
162
+ clearSchemeClickSuppression()
163
+ suppressSchemeClick = true
164
+ suppressSchemeClickTimer = window.setTimeout(() => {
165
+ suppressSchemeClickTimer = undefined
166
+ suppressSchemeClick = false
167
+ }, 0)
168
+ }
169
+
170
+ function settleSchemePointer({ commit = false } = {}) {
171
+ const pointer = schemePointer
172
+ if (!pointer) return
173
+ schemePointer = undefined
174
+ try { schemeToggle?.releasePointerCapture?.(pointer.pointerId) } catch {}
175
+ if (!pointer.dragging) return
176
+ suppressNextSchemeClick()
177
+ let result
178
+ try { result = pointer.gesture?.finish?.(commit) } catch { result = undefined }
179
+ Promise.resolve(result).catch(() => {}).finally(() => {
180
+ if (schemeIntent === pointer.target) schemeIntent = undefined
181
+ })
182
+ }
183
+
184
+ function onSchemePointerDown(event) {
185
+ if ((event.button ?? 0) !== 0 || schemePointer) return
186
+ const pointerId = event.pointerId ?? 1
187
+ schemePointer = {
188
+ pointerId,
189
+ startX: Number(event.clientX) || 0,
190
+ startY: Number(event.clientY) || 0,
191
+ target: schemeTarget(),
192
+ origin: schemeOrigin(),
193
+ dragging: false,
194
+ gesture: undefined,
195
+ }
196
+ try { schemeToggle?.setPointerCapture?.(pointerId) } catch {}
197
+ }
198
+
199
+ function onSchemePointerMove(event) {
200
+ const pointer = schemePointer
201
+ if (!pointer || (event.pointerId ?? 1) !== pointer.pointerId) return
202
+ const deltaX = (Number(event.clientX) || 0) - pointer.startX
203
+ if (!pointer.dragging) {
204
+ if (deltaX < 8) return
205
+ pointer.dragging = true
206
+ schemeIntent = pointer.target
207
+ playSchemePress()
208
+ const progress = schemePointerProgress(event)
209
+ try {
210
+ pointer.gesture = onSchemeDragStart(pointer.target, {
211
+ origin: pointer.origin,
212
+ progress,
213
+ clientX: Number(event.clientX) || 0,
214
+ })
215
+ } catch {
216
+ pointer.gesture = undefined
217
+ }
218
+ }
219
+ event.preventDefault?.()
220
+ pointer.gesture?.update?.(schemePointerProgress(event))
221
+ }
222
+
223
+ function onSchemePointerUp(event) {
224
+ const pointer = schemePointer
225
+ if (!pointer || (event.pointerId ?? 1) !== pointer.pointerId) return
226
+ const commit = pointer.dragging && schemePointerProgress(event) >= .5
227
+ settleSchemePointer({ commit })
228
+ }
229
+
230
+ function cancelSchemePointer() {
231
+ const pointer = schemePointer
232
+ if (!pointer) return
233
+ if (pointer.dragging) {
234
+ try { pointer.gesture?.cancel?.() } catch {}
235
+ }
236
+ schemePointer = undefined
237
+ try { schemeToggle?.releasePointerCapture?.(pointer.pointerId) } catch {}
238
+ if (pointer.dragging) suppressNextSchemeClick()
239
+ if (schemeIntent === pointer.target) schemeIntent = undefined
240
+ }
241
+
242
+ function onSchemeLostPointerCapture(event) {
243
+ if (!schemePointer || (event.pointerId ?? 1) !== schemePointer.pointerId) return
244
+ cancelSchemePointer()
70
245
  }
71
246
 
72
247
  function syncSchemeToggle() {
73
248
  if (!schemeToggle) return
74
249
  const current = root.dataset.prtsScheme === 'dark' ? 'dark' : 'light'
250
+ if (schemeIntent === current) schemeIntent = undefined
75
251
  schemeToggle.dataset.prtsSchemeCurrent = current
76
252
  schemeToggle.setAttribute('aria-label', current === 'dark' ? '切换到日间模式' : '切换到夜间模式')
77
253
  }
@@ -110,13 +286,16 @@ export function createOperationsShell({
110
286
  else window?.setTimeout?.(focusFirst, 0)
111
287
  }
112
288
 
113
- function setRailViewport(width = window.innerWidth) {
114
- const nextMode = Number(width) >= 1180 ? 'docked' : 'overlay'
115
- if (nextMode === railMode) return railMode
289
+ function setRailViewport(width = window.innerWidth, { forceClose = false } = {}) {
290
+ const nextViewport = Number(width) >= 1180 ? 'wide' : 'narrow'
291
+ const viewportChanged = nextViewport !== railViewport
292
+ const nextMode = railDefaultHidden || nextViewport === 'narrow' ? 'overlay' : 'docked'
293
+ const modeChanged = nextMode !== railMode
294
+ if (!viewportChanged && !modeChanged && !forceClose) return railMode
295
+ railViewport = nextViewport
116
296
  railMode = nextMode
117
297
  root.dataset.prtsRailMode = nextMode
118
- if (nextMode === 'docked') root.removeAttribute('data-prts-rail-open')
119
- else root.removeAttribute('data-prts-rail-open')
298
+ root.removeAttribute('data-prts-rail-open')
120
299
  syncRailState()
121
300
  return nextMode
122
301
  }
@@ -134,15 +313,12 @@ export function createOperationsShell({
134
313
 
135
314
  function onDocumentClick(event) {
136
315
  if (railMode !== 'overlay' || !root.hasAttribute('data-prts-rail-open')) return
137
- if (navRail?.contains?.(event.target) || railLauncher?.contains?.(event.target)) return
316
+ const settingsPanel = shell?.querySelector?.('[data-prts-theme-settings]')
317
+ const insideSettingsContent = settingsPanel?.contains?.(event.target) && event.target !== settingsPanel
318
+ if (navRail?.contains?.(event.target) || railLauncher?.contains?.(event.target) || insideSettingsContent) return
138
319
  closeRail()
139
320
  }
140
321
 
141
- function onRailAction(event) {
142
- const action = event.target?.closest?.('[data-prts-rail-brand], [data-prts-scheme-toggle]')
143
- if (action && navRail?.contains?.(action)) closeRail()
144
- }
145
-
146
322
  function onResponsiveChange() {
147
323
  setRailViewport(window.innerWidth)
148
324
  settingsOverlay?.scheduleScalePreviewRefresh?.()
@@ -177,6 +353,7 @@ export function createOperationsShell({
177
353
  panel: shell.querySelector('[data-prts-theme-settings]'),
178
354
  backdrop: shell.querySelector('[data-prts-settings-backdrop]'),
179
355
  onPreferenceChange,
356
+ onPreferencePreview,
180
357
  onResetVisual: onVisualReset,
181
358
  onRetrySave,
182
359
  getConversationScalePreview,
@@ -191,10 +368,15 @@ export function createOperationsShell({
191
368
  schemeObserver = new window.MutationObserver(syncSchemeToggle)
192
369
  schemeObserver.observe(root, { attributes: true, attributeFilter: ['data-prts-scheme'] })
193
370
  railLauncher.addEventListener('click', toggleRail)
194
- navRail.addEventListener('click', onRailAction)
195
371
  document.addEventListener('keydown', onRailKeydown)
196
372
  document.addEventListener('click', onDocumentClick)
197
373
  schemeToggle.addEventListener('click', toggleScheme)
374
+ schemeToggle.addEventListener('pointerdown', onSchemePointerDown)
375
+ schemeToggle.addEventListener('pointermove', onSchemePointerMove)
376
+ schemeToggle.addEventListener('pointerup', onSchemePointerUp)
377
+ schemeToggle.addEventListener('pointercancel', cancelSchemePointer)
378
+ schemeToggle.addEventListener('lostpointercapture', onSchemeLostPointerCapture)
379
+ window.addEventListener?.('blur', cancelSchemePointer)
198
380
  themeDisable.addEventListener('click', disableTheme)
199
381
  syncConversationState()
200
382
  syncSchemeToggle()
@@ -202,11 +384,19 @@ export function createOperationsShell({
202
384
  }
203
385
 
204
386
  function dispose() {
387
+ cancelSchemePointer()
388
+ clearSchemeClickSuppression()
389
+ clearSchemePressDelays()
205
390
  railLauncher?.removeEventListener('click', toggleRail)
206
- navRail?.removeEventListener('click', onRailAction)
207
391
  document.removeEventListener('keydown', onRailKeydown)
208
392
  document.removeEventListener('click', onDocumentClick)
209
393
  schemeToggle?.removeEventListener('click', toggleScheme)
394
+ schemeToggle?.removeEventListener('pointerdown', onSchemePointerDown)
395
+ schemeToggle?.removeEventListener('pointermove', onSchemePointerMove)
396
+ schemeToggle?.removeEventListener('pointerup', onSchemePointerUp)
397
+ schemeToggle?.removeEventListener('pointercancel', cancelSchemePointer)
398
+ schemeToggle?.removeEventListener('lostpointercapture', onSchemeLostPointerCapture)
399
+ window.removeEventListener?.('blur', cancelSchemePointer)
210
400
  themeDisable?.removeEventListener('click', disableTheme)
211
401
  railViewportQuery?.removeEventListener?.('change', onResponsiveChange)
212
402
  railViewportQuery = undefined
@@ -218,7 +408,10 @@ export function createOperationsShell({
218
408
  root.removeAttribute('data-prts-conversation-state')
219
409
  root.removeAttribute('data-prts-rail-mode')
220
410
  root.removeAttribute('data-prts-rail-open')
221
- navRail = railLauncher = railMode = undefined
411
+ root.removeAttribute('data-prts-rail-default-hidden')
412
+ navRail = railLauncher = railMode = railViewport = undefined
413
+ railDefaultHidden = false
414
+ schemeIntent = undefined
222
415
  shell = settingsOverlay = schemeToggle = themeDisable = connectionIndicator = undefined
223
416
  conversationObserver = schemeObserver = undefined
224
417
  operationRegion = undefined
@@ -232,7 +425,12 @@ export function createOperationsShell({
232
425
  dispose()
233
426
  return false
234
427
  }
428
+ const nextRailDefaultHidden = Boolean(preferences.railDefaultHidden)
429
+ const railPreferenceChanged = nextRailDefaultHidden !== railDefaultHidden
430
+ railDefaultHidden = nextRailDefaultHidden
235
431
  if (!mount()) return false
432
+ root.toggleAttribute('data-prts-rail-default-hidden', railDefaultHidden)
433
+ setRailViewport(window.innerWidth, { forceClose: railPreferenceChanged })
236
434
  const connection = status?.connection || 'unknown'
237
435
  const connectionLabel = status?.connectionLabel || '未知'
238
436
  connectionIndicator.dataset.state = connection