dsh-better-sidebar 0.10.0

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.
Files changed (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +139 -0
  3. package/README_EN.md +139 -0
  4. package/lib/client-docx.js +7649 -0
  5. package/lib/client-editor.js +32677 -0
  6. package/lib/client-pptx.js +63438 -0
  7. package/lib/client-registry.js +8021 -0
  8. package/lib/client-terminal.js +8060 -0
  9. package/lib/client-xlsx.js +359755 -0
  10. package/lib/client.js +8021 -0
  11. package/lib/index.js +2604 -0
  12. package/lib/invariant.js +22 -0
  13. package/lib/types/agent-pty.d.ts +220 -0
  14. package/lib/types/browser-probe.d.ts +15 -0
  15. package/lib/types/bundle-route.d.ts +13 -0
  16. package/lib/types/client/BrowserView.d.ts +22 -0
  17. package/lib/types/client/DiffTab.d.ts +6 -0
  18. package/lib/types/client/DiffView.d.ts +51 -0
  19. package/lib/types/client/EditorHost.d.ts +10 -0
  20. package/lib/types/client/ExplorerView.d.ts +9 -0
  21. package/lib/types/client/GitView.d.ts +8 -0
  22. package/lib/types/client/OrphanedTab.d.ts +11 -0
  23. package/lib/types/client/PdfView.d.ts +6 -0
  24. package/lib/types/client/PptxView.d.ts +6 -0
  25. package/lib/types/client/SandboxStatusBar.d.ts +10 -0
  26. package/lib/types/client/SideCardSection.d.ts +27 -0
  27. package/lib/types/client/Sidebar.d.ts +6 -0
  28. package/lib/types/client/SubagentView.d.ts +16 -0
  29. package/lib/types/client/TabBar.d.ts +38 -0
  30. package/lib/types/client/TerminalView.d.ts +8 -0
  31. package/lib/types/client/TextEditor.d.ts +10 -0
  32. package/lib/types/client/api.d.ts +170 -0
  33. package/lib/types/client/binary-download.d.ts +12 -0
  34. package/lib/types/client/breakpoints.d.ts +12 -0
  35. package/lib/types/client/browser.d.ts +50 -0
  36. package/lib/types/client/builtins/index.d.ts +17 -0
  37. package/lib/types/client/builtins/tabs.d.ts +6 -0
  38. package/lib/types/client/builtins/viewers.d.ts +3 -0
  39. package/lib/types/client/chunk-loader.d.ts +74 -0
  40. package/lib/types/client/chunks/docx.d.ts +8 -0
  41. package/lib/types/client/chunks/editor.d.ts +9 -0
  42. package/lib/types/client/chunks/pptx.d.ts +8 -0
  43. package/lib/types/client/chunks/terminal.d.ts +9 -0
  44. package/lib/types/client/chunks/xlsx.d.ts +9 -0
  45. package/lib/types/client/cm-themes.d.ts +24 -0
  46. package/lib/types/client/conversation-draft.d.ts +14 -0
  47. package/lib/types/client/docx-view.d.ts +8 -0
  48. package/lib/types/client/editor-load.d.ts +66 -0
  49. package/lib/types/client/icons.d.ts +47 -0
  50. package/lib/types/client/image-types.d.ts +3 -0
  51. package/lib/types/client/ime-guard.d.ts +36 -0
  52. package/lib/types/client/index.d.ts +10 -0
  53. package/lib/types/client/intercept.d.ts +20 -0
  54. package/lib/types/client/lang.d.ts +12 -0
  55. package/lib/types/client/lazy-chunk.d.ts +25 -0
  56. package/lib/types/client/link-intercept.d.ts +35 -0
  57. package/lib/types/client/locales.d.ts +237 -0
  58. package/lib/types/client/office-shared.d.ts +33 -0
  59. package/lib/types/client/office-types.d.ts +36 -0
  60. package/lib/types/client/office-view.d.ts +12 -0
  61. package/lib/types/client/openpath-intercept.d.ts +41 -0
  62. package/lib/types/client/paths.d.ts +19 -0
  63. package/lib/types/client/pdf-types.d.ts +2 -0
  64. package/lib/types/client/prefs.d.ts +30 -0
  65. package/lib/types/client/produced-files.d.ts +27 -0
  66. package/lib/types/client/selection-payload.d.ts +27 -0
  67. package/lib/types/client/service.d.ts +214 -0
  68. package/lib/types/client/split-pane.d.ts +29 -0
  69. package/lib/types/client/state.d.ts +322 -0
  70. package/lib/types/client/subagent-activity.d.ts +35 -0
  71. package/lib/types/client/subagent-detect.d.ts +47 -0
  72. package/lib/types/client/subagent-jobs.d.ts +63 -0
  73. package/lib/types/client/theme.d.ts +28 -0
  74. package/lib/types/client/xlsx-to-univer.d.ts +33 -0
  75. package/lib/types/client/xlsx-view.d.ts +10 -0
  76. package/lib/types/config.d.ts +41 -0
  77. package/lib/types/context-types.d.ts +431 -0
  78. package/lib/types/fs-tree.d.ts +41 -0
  79. package/lib/types/git.d.ts +75 -0
  80. package/lib/types/html-route.d.ts +48 -0
  81. package/lib/types/index.d.ts +39 -0
  82. package/lib/types/invariant.d.ts +15 -0
  83. package/lib/types/jobs-routes.d.ts +45 -0
  84. package/lib/types/prefs-shared.d.ts +106 -0
  85. package/lib/types/pty-manager.d.ts +76 -0
  86. package/lib/types/tools.d.ts +28 -0
  87. package/lib/types/trust-fence.d.ts +24 -0
  88. package/lib/types/wire.d.ts +38 -0
  89. package/package.json +168 -0
  90. package/src/agent-pty.ts +519 -0
  91. package/src/browser-probe.ts +26 -0
  92. package/src/bundle-route.ts +130 -0
  93. package/src/client/BrowserView.tsx +253 -0
  94. package/src/client/DiffTab.tsx +110 -0
  95. package/src/client/DiffView.tsx +266 -0
  96. package/src/client/EditorHost.tsx +101 -0
  97. package/src/client/ExplorerView.tsx +283 -0
  98. package/src/client/GitView.tsx +540 -0
  99. package/src/client/OrphanedTab.tsx +27 -0
  100. package/src/client/PdfView.tsx +110 -0
  101. package/src/client/PptxView.tsx +131 -0
  102. package/src/client/SandboxStatusBar.tsx +60 -0
  103. package/src/client/SideCardSection.module.css +271 -0
  104. package/src/client/SideCardSection.tsx +426 -0
  105. package/src/client/Sidebar.tsx +861 -0
  106. package/src/client/SubagentView.module.css +547 -0
  107. package/src/client/SubagentView.tsx +866 -0
  108. package/src/client/TabBar.tsx +188 -0
  109. package/src/client/TerminalView.tsx +233 -0
  110. package/src/client/TextEditor.tsx +382 -0
  111. package/src/client/api.ts +215 -0
  112. package/src/client/binary-download.tsx +23 -0
  113. package/src/client/breakpoints.ts +51 -0
  114. package/src/client/browser.ts +119 -0
  115. package/src/client/builtins/index.ts +33 -0
  116. package/src/client/builtins/tabs.tsx +211 -0
  117. package/src/client/builtins/viewers.tsx +151 -0
  118. package/src/client/chunk-loader.ts +196 -0
  119. package/src/client/chunks/docx.tsx +8 -0
  120. package/src/client/chunks/editor.tsx +9 -0
  121. package/src/client/chunks/pptx.tsx +8 -0
  122. package/src/client/chunks/terminal.tsx +9 -0
  123. package/src/client/chunks/xlsx.tsx +9 -0
  124. package/src/client/cm-themes.ts +129 -0
  125. package/src/client/conversation-draft.ts +29 -0
  126. package/src/client/css-modules.d.ts +5 -0
  127. package/src/client/docx-view.tsx +113 -0
  128. package/src/client/editor-load.ts +92 -0
  129. package/src/client/icons.tsx +137 -0
  130. package/src/client/image-types.ts +8 -0
  131. package/src/client/ime-guard.ts +47 -0
  132. package/src/client/index.tsx +238 -0
  133. package/src/client/intercept.tsx +94 -0
  134. package/src/client/lang.ts +95 -0
  135. package/src/client/layout.css +54 -0
  136. package/src/client/lazy-chunk.tsx +89 -0
  137. package/src/client/link-intercept.ts +66 -0
  138. package/src/client/locales.ts +476 -0
  139. package/src/client/office-shared.tsx +40 -0
  140. package/src/client/office-types.ts +48 -0
  141. package/src/client/office-view.tsx +12 -0
  142. package/src/client/openpath-intercept.ts +62 -0
  143. package/src/client/paths.ts +28 -0
  144. package/src/client/pdf-types.ts +4 -0
  145. package/src/client/prefs.ts +100 -0
  146. package/src/client/produced-files.ts +87 -0
  147. package/src/client/selection-payload.ts +86 -0
  148. package/src/client/service.ts +434 -0
  149. package/src/client/sidebar.module.css +1917 -0
  150. package/src/client/split-pane.tsx +292 -0
  151. package/src/client/state.ts +1048 -0
  152. package/src/client/subagent-activity.ts +67 -0
  153. package/src/client/subagent-detect.ts +125 -0
  154. package/src/client/subagent-jobs.ts +161 -0
  155. package/src/client/theme.ts +44 -0
  156. package/src/client/xlsx-to-univer.ts +260 -0
  157. package/src/client/xlsx-view.tsx +116 -0
  158. package/src/config.ts +95 -0
  159. package/src/context-types.ts +445 -0
  160. package/src/fs-tree.ts +115 -0
  161. package/src/git.ts +236 -0
  162. package/src/html-route.ts +70 -0
  163. package/src/index.ts +897 -0
  164. package/src/invariant.ts +32 -0
  165. package/src/jobs-routes.ts +257 -0
  166. package/src/prefs-shared.ts +127 -0
  167. package/src/pty-manager.ts +199 -0
  168. package/src/tools.ts +477 -0
  169. package/src/trust-fence.ts +77 -0
  170. package/src/wire.ts +94 -0
@@ -0,0 +1,131 @@
1
+ /** High-fidelity browser-native PPTX preview with slide navigation. */
2
+ import { useEffect, useRef, useState } from 'react'
3
+ import { downloadUrl, mediaUrl, type SessionScope } from './api.ts'
4
+ import { t } from './locales.ts'
5
+ import css from './sidebar.module.css'
6
+
7
+ type PptxViewerInstance = import('@aiden0z/pptx-renderer').PptxViewer
8
+
9
+ type LoadState =
10
+ | { status: 'loading' }
11
+ | { status: 'ready'; count: number }
12
+ | { status: 'error'; message: string }
13
+
14
+ export function PptxView(props: { scope: SessionScope; path: string; title: string }) {
15
+ const { scope, path, title } = props
16
+ const hostRef = useRef<HTMLDivElement>(null)
17
+ const viewerRef = useRef<PptxViewerInstance | null>(null)
18
+ const [load, setLoad] = useState<LoadState>({ status: 'loading' })
19
+ const [slide, setSlide] = useState(0)
20
+ const [navigating, setNavigating] = useState(false)
21
+
22
+ useEffect(() => {
23
+ const controller = new AbortController()
24
+ const host = hostRef.current
25
+ if (host === null) return
26
+ setLoad({ status: 'loading' })
27
+ setSlide(0)
28
+ void (async () => {
29
+ try {
30
+ const response = await fetch(mediaUrl(scope, path), { signal: controller.signal })
31
+ if (!response.ok) throw new Error(`HTTP ${response.status}`)
32
+ const bytes = await response.arrayBuffer()
33
+ if (controller.signal.aborted) return
34
+ const { PptxViewer, RECOMMENDED_ZIP_LIMITS } = await import('@aiden0z/pptx-renderer')
35
+ if (controller.signal.aborted) return
36
+ const viewer = await PptxViewer.open(bytes, host, {
37
+ renderMode: 'slide',
38
+ fitMode: 'contain',
39
+ lazyMedia: true,
40
+ lazySlides: true,
41
+ pdfjs: false,
42
+ signal: controller.signal,
43
+ zipLimits: RECOMMENDED_ZIP_LIMITS,
44
+ onSlideChange: (index) => {
45
+ if (!controller.signal.aborted) setSlide(index)
46
+ },
47
+ })
48
+ if (controller.signal.aborted) {
49
+ viewer.destroy()
50
+ return
51
+ }
52
+ viewerRef.current = viewer
53
+ setSlide(viewer.currentSlideIndex)
54
+ setLoad({ status: 'ready', count: viewer.slideCount })
55
+ } catch (error) {
56
+ if (controller.signal.aborted) return
57
+ try {
58
+ viewerRef.current?.destroy()
59
+ } catch {
60
+ // Partially initialized viewer — ignore disposal errors.
61
+ }
62
+ viewerRef.current = null
63
+ host.innerHTML = ''
64
+ setLoad({ status: 'error', message: error instanceof Error ? error.message : String(error) })
65
+ }
66
+ })()
67
+ return () => {
68
+ controller.abort()
69
+ try {
70
+ viewerRef.current?.destroy()
71
+ } catch {
72
+ // Already destroyed.
73
+ }
74
+ viewerRef.current = null
75
+ host.innerHTML = ''
76
+ }
77
+ }, [scope.sessionId, scope.cwd, path])
78
+
79
+ const navigate = (target: number): void => {
80
+ const viewer = viewerRef.current
81
+ if (viewer === null || load.status !== 'ready' || navigating) return
82
+ const next = Math.max(0, Math.min(load.count - 1, target))
83
+ if (next === slide) return
84
+ setNavigating(true)
85
+ void viewer.goToSlide(next, { behavior: 'auto' }).then(() => {
86
+ setSlide(viewer.currentSlideIndex)
87
+ }).catch((error: unknown) => {
88
+ setLoad({ status: 'error', message: error instanceof Error ? error.message : String(error) })
89
+ }).finally(() => {
90
+ setNavigating(false)
91
+ })
92
+ }
93
+
94
+ return (
95
+ <div className={css.editorPptx} aria-label={title}>
96
+ <div className={css.editorPptxToolbar}>
97
+ <button
98
+ type="button"
99
+ className={css.editorPptxButton}
100
+ disabled={load.status !== 'ready' || slide <= 0 || navigating}
101
+ onClick={() => { navigate(slide - 1) }}
102
+ >
103
+ {t('previousSlide')}
104
+ </button>
105
+ <span className={css.editorPptxPosition}>
106
+ {load.status === 'ready' ? `${slide + 1} / ${load.count}` : '– / –'}
107
+ </span>
108
+ <button
109
+ type="button"
110
+ className={css.editorPptxButton}
111
+ disabled={load.status !== 'ready' || slide >= load.count - 1 || navigating}
112
+ onClick={() => { navigate(slide + 1) }}
113
+ >
114
+ {t('nextSlide')}
115
+ </button>
116
+ <a className={css.editorDownloadLink} href={downloadUrl(scope, path)} download>
117
+ {t('downloadToView')}
118
+ </a>
119
+ </div>
120
+ <div className={css.editorPptxStage}>
121
+ <div className={css.editorPptxHost} ref={hostRef} />
122
+ {load.status !== 'ready' && (
123
+ <div className={css.editorOfficeOverlay}>
124
+ {load.status === 'loading' && <div className={css.editorPlaceholder}>{t('loading')}</div>}
125
+ {load.status === 'error' && <div className={css.editorError}>{load.message}</div>}
126
+ </div>
127
+ )}
128
+ </div>
129
+ </div>
130
+ )
131
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The live sandbox status row of the two built-in web surfaces (HTML
3
+ * preview and the browser tab): a green "sandbox on" state with a one-tap
4
+ * TEMPORARY unlock, or a RED "sandbox off" state (global setting or the
5
+ * temporary unlock) with a restore action.
6
+ *
7
+ * The temporary unlock is component state only — it never writes the
8
+ * global side card setting (`htmlViewerNoSandbox` / `browserNoSandbox`);
9
+ * it lasts until the surface unmounts (tab switch / file switch) or the
10
+ * user restores the sandbox from the row. When the global setting already
11
+ * drops the sandbox, no unlock/restore action is offered (changing the
12
+ * global setting is the settings page's job) — the red warning stands.
13
+ */
14
+ import clsx from 'clsx'
15
+ import { t } from './locales.ts'
16
+ import css from './sidebar.module.css'
17
+
18
+ export function SandboxStatusBar(props: {
19
+ /** The effective sandbox state (global pref OR the local temporary unlock). */
20
+ sandboxed: boolean
21
+ /** Whether the sandbox is off due to the LOCAL temporary unlock (shows the restore action). */
22
+ local: boolean
23
+ /** The red-state explanation (e.g. "the page runs with full GUI privileges"). */
24
+ dangerCopy: string
25
+ onUnlock: () => void
26
+ onRestore: () => void
27
+ }) {
28
+ const { sandboxed, local, dangerCopy, onUnlock, onRestore } = props
29
+ if (sandboxed) {
30
+ const copy = t('sandboxStatusOn')
31
+ return (
32
+ <div className={clsx(css.sandboxStatus, css.sandboxStatusOn)}>
33
+ <span className={css.sandboxDot} />
34
+ <span className={css.sandboxStatusText} title={copy}>{copy}</span>
35
+ <button
36
+ type="button"
37
+ className={css.sandboxAction}
38
+ onClick={onUnlock}
39
+ >
40
+ {t('sandboxUnlock')}
41
+ </button>
42
+ </div>
43
+ )
44
+ }
45
+ return (
46
+ <div className={clsx(css.sandboxStatus, css.sandboxStatusOff)}>
47
+ <span className={css.sandboxDot} />
48
+ <span className={css.sandboxStatusText} title={dangerCopy}>{dangerCopy}</span>
49
+ {local && (
50
+ <button
51
+ type="button"
52
+ className={css.sandboxAction}
53
+ onClick={onRestore}
54
+ >
55
+ {t('sandboxRestore')}
56
+ </button>
57
+ )}
58
+ </div>
59
+ )
60
+ }
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Side card settings section chrome (DSH-native tokens throughout):
3
+ * - the feature inventories are responsive GRIDS of small toggle cards
4
+ * (auto-fill columns — several cards per row, wrapping);
5
+ * - a card's on/off state IS its visual state: enabled = brand border +
6
+ * tinted fill + a check badge pinned to the far right of the top row,
7
+ * disabled = neutral hairline + dimmed text;
8
+ * - features that declare related settings carry a gear corner button
9
+ * opening a native Modal whose rows are the DSH settings-row recipe
10
+ * (title/desc left + native accent checkbox, hairline separators);
11
+ * - the width input stays a native settings row (a value, not a switch).
12
+ */
13
+
14
+ .section {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 8px;
18
+ width: 100%;
19
+ /* Tall inventories (7 tab cards + 9 viewer cards + general rows) must
20
+ never be clipped by the settings shell: fill the shell's options area
21
+ and scroll inside the section when the content outgrows the panel. */
22
+ height: 100%;
23
+ min-height: 0;
24
+ overflow-y: auto;
25
+ }
26
+
27
+ .section > :last-child {
28
+ border-bottom: none;
29
+ }
30
+
31
+ /* Responsive inventory: as many small cards per row as the width allows. */
32
+ .grid {
33
+ display: grid;
34
+ grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
35
+ gap: 8px;
36
+ }
37
+
38
+ /* One small toggle card (visual chrome; the interactive part is .cardMain). */
39
+ .card {
40
+ position: relative;
41
+ display: flex;
42
+ flex-direction: column;
43
+ border-radius: 8px;
44
+ border: 1px solid var(--dsw-alias-border-l2);
45
+ background: transparent;
46
+ font: inherit;
47
+ color: inherit;
48
+ cursor: pointer;
49
+ transition: background 0.12s ease, border-color 0.12s ease;
50
+ }
51
+
52
+ /* Hover affordance on disabled cards only (enabled cards are already lit). */
53
+ .card:not(.cardOn):hover {
54
+ background: var(--dsw-alias-interactive-bg-hover);
55
+ }
56
+
57
+ /* Enabled = highlighted: brand border + tinted fill. */
58
+ .cardOn {
59
+ border-color: var(--dsw-alias-button-primary-fill);
60
+ background: var(--dsw-alias-interactive-bg-active);
61
+ }
62
+
63
+ /* The card's switch: the whole main area (top row + desc) flips the feature. */
64
+ .cardMain {
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: 2px;
68
+ width: 100%;
69
+ padding: 10px 12px;
70
+ border: 0;
71
+ border-radius: inherit;
72
+ background: transparent;
73
+ font: inherit;
74
+ color: inherit;
75
+ text-align: left;
76
+ cursor: pointer;
77
+ }
78
+
79
+ .cardMain:focus-visible,
80
+ .cardGear:focus-visible {
81
+ outline: 2px solid var(--dsw-alias-border-l4);
82
+ outline-offset: 2px;
83
+ }
84
+
85
+ .cardTop {
86
+ display: flex;
87
+ align-items: center;
88
+ gap: 6px;
89
+ min-width: 0;
90
+ }
91
+
92
+ .cardIcon {
93
+ display: inline-flex;
94
+ align-items: center;
95
+ flex: none;
96
+ color: var(--dsw-alias-label-tertiary);
97
+ }
98
+
99
+ .cardOn .cardIcon {
100
+ color: var(--dsw-alias-button-primary-fill);
101
+ }
102
+
103
+ .cardTitle {
104
+ flex: 1;
105
+ min-width: 0;
106
+ font-size: 13px;
107
+ line-height: 20px;
108
+ color: var(--dsw-alias-label-secondary);
109
+ white-space: nowrap;
110
+ overflow: hidden;
111
+ text-overflow: ellipsis;
112
+ }
113
+
114
+ .cardOn .cardTitle {
115
+ color: var(--dsw-alias-label-primary);
116
+ }
117
+
118
+ /* The check badge: pinned to the FAR RIGHT of the top row (enabled only). */
119
+ .cardCheck {
120
+ display: inline-flex;
121
+ align-items: center;
122
+ flex: none;
123
+ color: var(--dsw-alias-button-primary-fill);
124
+ }
125
+
126
+ .cardDesc {
127
+ font-size: 11px;
128
+ line-height: 16px;
129
+ color: var(--dsw-alias-label-tertiary);
130
+ white-space: nowrap;
131
+ overflow: hidden;
132
+ text-overflow: ellipsis;
133
+ }
134
+
135
+ .cardOn .cardDesc {
136
+ color: var(--dsw-alias-label-secondary);
137
+ }
138
+
139
+ /* The gear corner button: bottom-right of cards that declare related
140
+ settings; the desc gets right padding so the ellipsis never runs under it. */
141
+ .cardWithGear .cardDesc {
142
+ padding-right: 18px;
143
+ }
144
+
145
+ .cardGear {
146
+ position: absolute;
147
+ right: 6px;
148
+ bottom: 6px;
149
+ display: inline-flex;
150
+ align-items: center;
151
+ justify-content: center;
152
+ width: 20px;
153
+ height: 20px;
154
+ padding: 0;
155
+ border: 0;
156
+ border-radius: 6px;
157
+ background: transparent;
158
+ color: var(--dsw-alias-label-tertiary);
159
+ cursor: pointer;
160
+ }
161
+
162
+ .cardGear:hover {
163
+ color: var(--dsw-alias-label-primary);
164
+ background: var(--dsw-alias-interactive-bg-hover);
165
+ }
166
+
167
+ /* Inventory section heading (常规 / 侧边栏内容 / 文件预览). */
168
+ .sectionHeading {
169
+ padding: 14px 0 0;
170
+ font-size: 12px;
171
+ line-height: 17px;
172
+ font-weight: 600;
173
+ letter-spacing: 0.02em;
174
+ color: var(--dsw-alias-label-secondary);
175
+ }
176
+
177
+ /* Native settings row recipe (the width input row + popup rows). */
178
+ .row {
179
+ display: flex;
180
+ align-items: center;
181
+ justify-content: space-between;
182
+ gap: 16px;
183
+ padding: 12px 0;
184
+ border-bottom: 1px solid var(--dsw-alias-border-l2);
185
+ }
186
+
187
+ .rowText {
188
+ display: flex;
189
+ flex-direction: column;
190
+ gap: 4px;
191
+ min-width: 0;
192
+ }
193
+
194
+ .title {
195
+ font-size: 14px;
196
+ line-height: 22px;
197
+ color: var(--dsw-alias-label-primary);
198
+ }
199
+
200
+ .desc {
201
+ font-size: 12px;
202
+ line-height: 17px;
203
+ color: var(--dsw-alias-label-secondary);
204
+ }
205
+
206
+ /* Native checkbox: 16px, brand accent (the settings-row recipe). */
207
+ .toggle {
208
+ flex: none;
209
+ width: 16px;
210
+ height: 16px;
211
+ margin: 0;
212
+ accent-color: var(--dsw-alias-button-primary-fill);
213
+ cursor: pointer;
214
+ }
215
+
216
+ .toggle:focus-visible {
217
+ outline: 2px solid var(--dsw-alias-border-l4);
218
+ outline-offset: 2px;
219
+ }
220
+
221
+ /* Secondary settings popup body: stacked native rows, trailing separator
222
+ stripped by the trailing-child rule. */
223
+ .popupRows {
224
+ display: flex;
225
+ flex-direction: column;
226
+ width: 100%;
227
+ }
228
+
229
+ .popupRow {
230
+ display: flex;
231
+ align-items: center;
232
+ justify-content: space-between;
233
+ gap: 16px;
234
+ padding: 16px 0;
235
+ border-bottom: 1px solid var(--dsw-alias-border-l2);
236
+ cursor: pointer;
237
+ }
238
+
239
+ .popupRows > :last-child {
240
+ border-bottom: none;
241
+ }
242
+
243
+ .control {
244
+ flex: none;
245
+ display: flex;
246
+ align-items: center;
247
+ gap: 6px;
248
+ }
249
+
250
+ .percentInput {
251
+ width: 76px;
252
+ }
253
+
254
+ .suffix {
255
+ font-size: 14px;
256
+ line-height: 22px;
257
+ color: var(--dsw-alias-label-secondary);
258
+ }
259
+
260
+ .error {
261
+ padding: 10px 0 2px;
262
+ font-size: 12px;
263
+ line-height: 17px;
264
+ color: var(--dsw-alias-state-error-primary);
265
+ }
266
+
267
+ @media (prefers-reduced-motion: reduce) {
268
+ .card {
269
+ transition: none;
270
+ }
271
+ }